# SPDX-License-Identifier: LGPL-2.1-or-later # *************************************************************************** # * * # * Copyright (c) 2015 Yorik van Havre * # * * # * This file is part of FreeCAD. * # * * # * FreeCAD is free software: you can redistribute it and/or modify it * # * under the terms of the GNU Lesser General Public License as * # * published by the Free Software Foundation, either version 2.1 of the * # * License, or (at your option) any later version. * # * * # * FreeCAD is distributed in the hope that it will be useful, but * # * WITHOUT ANY WARRANTY; without even the implied warranty of * # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * # * Lesser General Public License for more details. * # * * # * You should have received a copy of the GNU Lesser General Public * # * License along with FreeCAD. If not, see * # * . * # * * # *************************************************************************** __title__ = "Arch Material Management" __author__ = "Yorik van Havre" __url__ = "https://www.freecad.org" ## @package ArchMaterial # \ingroup ARCH # \brief The Material object and tools # # This module provides tools to add materials to # Arch objects import FreeCAD from draftutils import params if FreeCAD.GuiUp: import os from PySide import QtCore, QtGui from PySide.QtCore import QT_TRANSLATE_NOOP import FreeCADGui import Arch_rc # Needed for access to icons # lgtm [py/unused_import] from draftutils.translate import translate else: # \cond def translate(ctxt, txt): return txt def QT_TRANSLATE_NOOP(ctxt, txt): return txt # \endcond class _ArchMaterialContainer: "The Material Container" def __init__(self, obj): self.Type = "MaterialContainer" obj.Proxy = self def execute(self, obj): return def dumps(self): if hasattr(self, "Type"): return self.Type def loads(self, state): if state: self.Type = state class _ViewProviderArchMaterialContainer: "A View Provider for the Material Container" def __init__(self, vobj): vobj.Proxy = self def getIcon(self): return ":/icons/Arch_Material_Group.svg" def attach(self, vobj): self.Object = vobj.Object def setupContextMenu(self, vobj, menu): if FreeCADGui.activeWorkbench().name() != "BIMWorkbench": return actionMergeByName = QtGui.QAction( QtGui.QIcon(":/icons/Arch_Material_Group.svg"), translate("Arch", "Merge Duplicates"), menu, ) actionMergeByName.triggered.connect(self.mergeByName) menu.addAction(actionMergeByName) actionReorder = QtGui.QAction(translate("Arch", "Reorder Children Alphabetically"), menu) actionReorder.triggered.connect(self.reorder) menu.addAction(actionReorder) def doubleClicked(self, vobj): """Handle double-click on the materials group in the Tree View. Open the BIM Materials dialog and return True to indicate the event was handled (prevents the tree from starting inline label editing, Qt's default behavior if the event is not handled). """ FreeCADGui.runCommand("BIM_Material") return True def mergeByName(self): if hasattr(self, "Object"): mats = [o for o in self.Object.Group if o.isDerivedFrom("App::MaterialObject")] todelete = [] for mat in mats: orig = None for om in mats: if om.Label == mat.Label: orig = om break else: if ( mat.Label[-1].isdigit() and mat.Label[-2].isdigit() and mat.Label[-3].isdigit() ): for om in mats: if om.Label == mat.Label[:-3].strip(): orig = om break if orig: for par in mat.InList: for prop in par.PropertiesList: if getattr(par, prop) == mat: FreeCAD.Console.PrintMessage( "Changed property '" + prop + "' of object " + par.Label + " from " + mat.Label + " to " + orig.Label + "\n" ) setattr(par, prop, orig) todelete.append(mat) for tod in todelete: if not tod.InList: FreeCAD.Console.PrintMessage("Merging duplicate material " + tod.Label + "\n") FreeCAD.ActiveDocument.removeObject(tod.Name) elif (len(tod.InList) == 1) and ( tod.InList[0].isDerivedFrom("App::DocumentObjectGroup") ): FreeCAD.Console.PrintMessage("Merging duplicate material " + tod.Label + "\n") FreeCAD.ActiveDocument.removeObject(tod.Name) else: FreeCAD.Console.PrintMessage( "Unable to delete material " + tod.Label + ": InList not empty\n" ) def reorder(self): if hasattr(self, "Object"): if hasattr(self.Object, "Group") and self.Object.Group: g = self.Object.Group g.sort(key=lambda obj: obj.Label) self.Object.Group = g FreeCAD.ActiveDocument.recompute() def dumps(self): return None def loads(self, state): return None class _ArchMaterial: "The Material object" def __init__(self, obj): self.Type = "Material" obj.Proxy = self self.setProperties(obj) def onDocumentRestored(self, obj): self.setProperties(obj) def setProperties(self, obj): if not "Description" in obj.PropertiesList: obj.addProperty( "App::PropertyString", "Description", "Material", QT_TRANSLATE_NOOP("App::Property", "A description for this material"), locked=True, ) if not "StandardCode" in obj.PropertiesList: obj.addProperty( "App::PropertyString", "StandardCode", "Material", QT_TRANSLATE_NOOP("App::Property", "A standard code (MasterFormat, OmniClass,…)"), locked=True, ) if not "ProductURL" in obj.PropertiesList: obj.addProperty( "App::PropertyString", "ProductURL", "Material", QT_TRANSLATE_NOOP( "App::Property", "A URL where to find information about this material" ), locked=True, ) if not "Transparency" in obj.PropertiesList: obj.addProperty( "App::PropertyPercent", "Transparency", "Material", QT_TRANSLATE_NOOP("App::Property", "The transparency value of this material"), locked=True, ) if not "Color" in obj.PropertiesList: obj.addProperty( "App::PropertyColor", "Color", "Material", QT_TRANSLATE_NOOP("App::Property", "The color of this material"), locked=True, ) if not "SectionColor" in obj.PropertiesList: obj.addProperty( "App::PropertyColor", "SectionColor", "Material", QT_TRANSLATE_NOOP("App::Property", "The color of this material when cut"), locked=True, ) def isSameColor(self, c1, c2): r = 4 if round(c1[0], r) == round(c2[0], r): if round(c1[1], r) == round(c2[1], r): if round(c1[2], r) == round(c2[2], r): return True return False def onChanged(self, obj, prop): d = obj.Material if prop == "Material": if "SectionColor" in obj.Material: c = tuple( [ float(f) for f in obj.Material["SectionColor"].strip("()").strip("[]").split(",") ] ) if hasattr(obj, "SectionColor"): if not self.isSameColor(obj.SectionColor, c): obj.SectionColor = c if "DiffuseColor" in obj.Material: c = tuple( [ float(f) for f in obj.Material["DiffuseColor"].strip("()").strip("[]").split(",") ] ) if hasattr(obj, "Color"): if not self.isSameColor(obj.Color, c): obj.Color = c if "Transparency" in obj.Material: t = int(obj.Material["Transparency"]) if hasattr(obj, "Transparency"): if obj.Transparency != t: obj.Transparency = t if "ProductURL" in obj.Material: if hasattr(obj, "ProductURL"): if obj.ProductURL != obj.Material["ProductURL"]: obj.ProductURL = obj.Material["ProductURL"] if "StandardCode" in obj.Material: if hasattr(obj, "StandardCode"): if obj.StandardCode != obj.Material["StandardCode"]: obj.StandardCode = obj.Material["StandardCode"] if "Description" in obj.Material: if hasattr(obj, "Description"): if obj.Description != obj.Material["Description"]: obj.Description = obj.Material["Description"] if "Name" in obj.Material: if hasattr(obj, "Label"): if obj.Label != obj.Material["Name"]: obj.Label = obj.Material["Name"] elif prop == "Label": if "Name" in d: if d["Name"] == obj.Label: return d["Name"] = obj.Label elif prop == "SectionColor": if hasattr(obj, "SectionColor"): if "SectionColor" in d: if self.isSameColor( tuple( [float(f) for f in d["SectionColor"].strip("()").strip("[]").split(",")] ), obj.SectionColor[:3], ): return d["SectionColor"] = str(obj.SectionColor[:3]) elif prop == "Color": if hasattr(obj, "Color"): if "DiffuseColor" in d: if self.isSameColor( tuple( [float(f) for f in d["DiffuseColor"].strip("()").strip("[]").split(",")] ), obj.Color[:3], ): return d["DiffuseColor"] = str(obj.Color[:3]) elif prop == "Transparency": if hasattr(obj, "Transparency"): val = str(obj.Transparency) if "Transparency" in d: if d["Transparency"] == val: return d["Transparency"] = val elif prop == "ProductURL": if hasattr(obj, "ProductURL"): val = obj.ProductURL if "ProductURL" in d: if d["ProductURL"] == val: return obj.Material["ProductURL"] = val elif prop == "StandardCode": if hasattr(obj, "StandardCode"): val = obj.StandardCode if "StandardCode" in d: if d["StandardCode"] == val: return d["StandardCode"] = val elif prop == "Description": if hasattr(obj, "Description"): val = obj.Description if "Description" in d: if d["Description"] == val: return d["Description"] = val if d and (d != obj.Material): obj.Material = d # if FreeCAD.GuiUp: # import FreeCADGui # not sure why this is needed, but it is... # FreeCADGui.ActiveDocument.resetEdit() def execute(self, obj): if obj.Material: if FreeCAD.GuiUp: c = None t = None if "DiffuseColor" in obj.Material: c = tuple( [ float(f) for f in obj.Material["DiffuseColor"].strip("()").strip("[]").split(",") ] ) if "Transparency" in obj.Material: t = int(obj.Material["Transparency"]) for p in obj.InList: if ( hasattr(p, "Material") and p.Material.Name == obj.Name and getattr(obj.ViewObject, "UseMaterialColor", True) ): if c: p.ViewObject.ShapeColor = c if t: p.ViewObject.Transparency = t return def dumps(self): if hasattr(self, "Type"): return self.Type def loads(self, state): if state: self.Type = state class _ViewProviderArchMaterial: "A View Provider for the Material object" def __init__(self, vobj): vobj.Proxy = self def getIcon(self): if hasattr(self, "icondata"): return self.icondata return ":/icons/Arch_Material.svg" def attach(self, vobj): self.Object = vobj.Object def updateData(self, obj, prop): if prop == "Color": from PySide import QtCore, QtGui # custom icon if hasattr(obj, "Color"): c = obj.Color matcolor = QtGui.QColor(int(c[0] * 255), int(c[1] * 255), int(c[2] * 255)) darkcolor = QtGui.QColor(int(c[0] * 125), int(c[1] * 125), int(c[2] * 125)) im = QtGui.QImage(48, 48, QtGui.QImage.Format_ARGB32) im.fill(QtCore.Qt.transparent) pt = QtGui.QPainter(im) pt.setPen(QtGui.QPen(QtCore.Qt.black, 2, QtCore.Qt.SolidLine, QtCore.Qt.FlatCap)) # pt.setBrush(QtGui.QBrush(matcolor, QtCore.Qt.SolidPattern)) gradient = QtGui.QLinearGradient(0, 0, 48, 48) gradient.setColorAt(0, matcolor) gradient.setColorAt(1, darkcolor) pt.setBrush(QtGui.QBrush(gradient)) pt.drawEllipse(6, 6, 36, 36) pt.setPen(QtGui.QPen(QtCore.Qt.white, 1, QtCore.Qt.SolidLine, QtCore.Qt.FlatCap)) pt.setBrush(QtGui.QBrush(QtCore.Qt.white, QtCore.Qt.SolidPattern)) pt.drawEllipse(12, 12, 12, 12) pt.end() ba = QtCore.QByteArray() b = QtCore.QBuffer(ba) b.open(QtCore.QIODevice.WriteOnly) im.save(b, "XPM") self.icondata = ba.data().decode("latin1") obj.ViewObject.signalChangeIcon() def onChanged(self, vobj, prop): if prop == "Material": if "Father" in vobj.Object.Material: for o in FreeCAD.ActiveDocument.Objects: if o.isDerivedFrom("App::MaterialObject"): if o.Label == vobj.Object.Material["Father"]: o.touch() def setEdit(self, vobj, mode): if mode != 0: return None self.taskd = _ArchMaterialTaskPanel(vobj.Object) FreeCADGui.Control.showDialog(self.taskd) self.taskd.form.FieldName.setFocus() self.taskd.form.FieldName.selectAll() return True def unsetEdit(self, vobj, mode): if mode != 0: return None FreeCADGui.Control.closeDialog() return True def setupContextMenu(self, vobj, menu): if FreeCADGui.activeWorkbench().name() != "BIMWorkbench": return actionEdit = QtGui.QAction(translate("Arch", "Edit"), menu) actionEdit.triggered.connect(self.edit) menu.addAction(actionEdit) def edit(self): FreeCADGui.ActiveDocument.setEdit(self.Object, 0) def setTaskValue(self, widgetname, value): if hasattr(self, "taskd"): if hasattr(self.taskd, "form"): if hasattr(self.taskd.form, widgetname): widget = getattr(self.taskd.form, widgetname) if hasattr(widget, "setText"): widget.setText(value) elif hasattr(widget, "setValue"): widget.setValue(value) def dumps(self): return None def loads(self, state): return None def claimChildren(self): ch = [] if hasattr(self, "Object"): for o in self.Object.Document.Objects: if o.isDerivedFrom("App::MaterialObject"): if o.Material: if "Father" in o.Material: if o.Material["Father"] == self.Object.Label: ch.append(o) return ch class _ArchMaterialTaskPanel: """The editmode TaskPanel for Arch Material objects""" def __init__(self, obj=None): self.cards = None self.existingmaterials = [] self.obj = obj self.form = FreeCADGui.PySideUic.loadUi(":/ui/ArchMaterial.ui") colorPix = QtGui.QPixmap(16, 16) colorPix.fill(QtGui.QColor(204, 204, 204)) self.form.ButtonColor.setIcon(QtGui.QIcon(colorPix)) self.form.ButtonSectionColor.setIcon(QtGui.QIcon(colorPix)) self.form.ButtonUrl.setIcon(QtGui.QIcon(":/icons/internet-web-browser.svg")) self.form.comboBox_MaterialsInDir.currentIndexChanged.connect(self.chooseMat) self.form.comboBox_FromExisting.currentIndexChanged.connect(self.fromExisting) self.form.comboFather.currentTextChanged.connect(self.setFather) self.form.ButtonColor.pressed.connect(self.getColor) self.form.ButtonSectionColor.pressed.connect(self.getSectionColor) self.form.ButtonUrl.pressed.connect(self.openUrl) self.form.ButtonEditor.pressed.connect(self.openEditor) self.form.ButtonCode.pressed.connect(self.getCode) self.fillMaterialCombo() self.fillExistingCombo() try: from bimcommands import BimClassification except Exception: self.form.ButtonCode.hide() else: self.form.ButtonCode.setIcon(QtGui.QIcon(":/icons/BIM_Classification.svg")) if self.obj: if hasattr(self.obj, "Material"): self.material = self.obj.Material self.setFields() def setFields(self): "sets the task box contents from self.material" if "Name" in self.material: self.form.FieldName.setText(self.material["Name"]) elif self.obj: self.form.FieldName.setText(self.obj.Label) if "Description" in self.material: self.form.FieldDescription.setText(self.material["Description"]) if "DiffuseColor" in self.material: self.form.ButtonColor.setIcon(self.getColorIcon(self.material["DiffuseColor"])) elif "ViewColor" in self.material: self.form.ButtonColor.setIcon(self.getColorIcon(self.material["ViewColor"])) elif "Color" in self.material: self.form.ButtonColor.setIcon(self.getColorIcon(self.material["Color"])) if "SectionColor" in self.material: self.form.ButtonSectionColor.setIcon(self.getColorIcon(self.material["SectionColor"])) if "StandardCode" in self.material: self.form.FieldCode.setText(self.material["StandardCode"]) if "ProductURL" in self.material: self.form.FieldUrl.setText(self.material["ProductURL"]) if "Transparency" in self.material: self.form.SpinBox_Transparency.setValue(int(self.material["Transparency"])) if "Father" in self.material: father = self.material["Father"] else: father = None found = False self.form.comboFather.addItem("None") for o in FreeCAD.ActiveDocument.Objects: if o.isDerivedFrom("App::MaterialObject"): if o != self.obj: self.form.comboFather.addItem(o.Label) if o.Label == father: self.form.comboFather.setCurrentIndex(self.form.comboFather.count() - 1) found = True if father and not found: self.form.comboFather.addItem(father) self.form.comboFather.setCurrentIndex(self.form.comboFather.count() - 1) def getColorIcon(self, color): if color: if "(" in color: c = tuple([float(f) for f in color.strip("()").split(",")]) qcolor = QtGui.QColor() qcolor.setRgbF(c[0], c[1], c[2]) colorPix = QtGui.QPixmap(16, 16) colorPix.fill(qcolor) icon = QtGui.QIcon(colorPix) return icon return QtGui.QIcon() def getFields(self): "sets self.material from the contents of the task box" self.material["Name"] = self.form.FieldName.text() self.material["Description"] = self.form.FieldDescription.text() self.material["DiffuseColor"] = self.getColorFromIcon(self.form.ButtonColor.icon()) self.material["ViewColor"] = self.material["DiffuseColor"] self.material["Color"] = self.material["DiffuseColor"] self.material["SectionColor"] = self.getColorFromIcon(self.form.ButtonSectionColor.icon()) self.material["StandardCode"] = self.form.FieldCode.text() self.material["ProductURL"] = self.form.FieldUrl.text() self.material["Transparency"] = str(self.form.SpinBox_Transparency.value()) def getColorFromIcon(self, icon): "gets pixel color from the given icon" pixel = icon.pixmap(16, 16).toImage().pixel(0, 0) return str(QtGui.QColor(pixel).getRgbF()) def accept(self): self.getFields() if self.obj: if hasattr(self.obj, "Material"): self.obj.Material = self.material self.obj.Label = self.material["Name"] FreeCAD.ActiveDocument.recompute() FreeCADGui.ActiveDocument.resetEdit() return True def reject(self): FreeCADGui.ActiveDocument.resetEdit() return True def chooseMat(self, card): "sets self.material from a card" card = self.form.comboBox_MaterialsInDir.currentText() if card in self.cards: import importFCMat self.material = importFCMat.read(self.cards[card]) self.setFields() def fromExisting(self, index): "sets the contents from an existing material" if index > 0: if index <= len(self.existingmaterials): m = self.existingmaterials[index - 1] if m.Material: self.material = m.Material self.setFields() def setFather(self, text): "sets the father" if text: if text == "None": if "Father" in self.material: # for some have Father at first and change to none self.material.pop("Father") else: self.material["Father"] = text def getColor(self): self.getColorForButton(self.form.ButtonColor) def getSectionColor(self): self.getColorForButton(self.form.ButtonSectionColor) def getColorForButton(self, button): "opens a color picker dialog" icon = button.icon() pixel = icon.pixmap(16, 16).toImage().pixel(0, 0) color = QtGui.QColorDialog.getColor(QtGui.QColor(pixel)) if color.isValid(): colorPix = QtGui.QPixmap(16, 16) colorPix.fill(color) button.setIcon(QtGui.QIcon(colorPix)) def fillMaterialCombo(self): "fills the combo with the existing FCMat cards" # look for cards in both resources dir and a Materials sub-folder in the user folder. # User cards with same name will override system cards resources_mat_path = os.path.join( FreeCAD.getResourceDir(), "Mod", "Material", "Resources", "Materials" ) resources_mat_path_std = os.path.join(resources_mat_path, "Standard") user_mat_path = os.path.join(FreeCAD.ConfigGet("UserAppData"), "Material") paths = [resources_mat_path_std] if os.path.exists(user_mat_path): paths.append(user_mat_path) self.cards = {} for p in paths: for root, _, f_names in os.walk(p): for f in f_names: b, e = os.path.splitext(f) if e.upper() == ".FCMAT": self.cards[b] = os.path.join(root, f) if self.cards: for k in sorted(self.cards): self.form.comboBox_MaterialsInDir.addItem(k) def fillExistingCombo(self): "fills the existing materials combo" self.existingmaterials = [] for obj in FreeCAD.ActiveDocument.Objects: if obj.isDerivedFrom("App::MaterialObject"): if obj != self.obj: self.existingmaterials.append(obj) for m in self.existingmaterials: self.form.comboBox_FromExisting.addItem(m.Label) def openEditor(self): "opens the full material editor from the material module" self.getFields() if self.material: import MaterialEditor self.material = MaterialEditor.editMaterial(self.material) self.setFields() def openUrl(self): self.getFields() if self.material: if "ProductURL" in self.material: QtGui.QDesktopServices.openUrl(self.material["ProductURL"]) def getCode(self): FreeCADGui.Selection.addSelection(self.obj) FreeCADGui.runCommand("BIM_Classification") class _ArchMultiMaterial: "The MultiMaterial object" def __init__(self, obj): self.Type = "MultiMaterial" obj.Proxy = self obj.addProperty( "App::PropertyString", "Description", "Arch", QT_TRANSLATE_NOOP("App::Property", "A description for this material"), locked=True, ) obj.addProperty( "App::PropertyStringList", "Names", "Arch", QT_TRANSLATE_NOOP("App::Property", "The list of layer names"), locked=True, ) obj.addProperty( "App::PropertyLinkList", "Materials", "Arch", QT_TRANSLATE_NOOP("App::Property", "The list of layer materials"), locked=True, ) obj.addProperty( "App::PropertyFloatList", "Thicknesses", "Arch", QT_TRANSLATE_NOOP("App::Property", "The list of layer thicknesses"), locked=True, ) def dumps(self): if hasattr(self, "Type"): return self.Type def loads(self, state): if state: self.Type = state class _ViewProviderArchMultiMaterial: "A View Provider for the MultiMaterial object" def __init__(self, vobj): vobj.Proxy = self def getIcon(self): return ":/icons/Arch_Material_Multi.svg" def attach(self, vobj): self.Object = vobj.Object def setEdit(self, vobj, mode): if mode != 0: return None taskd = _ArchMultiMaterialTaskPanel(vobj.Object) FreeCADGui.Control.showDialog(taskd) return True def unsetEdit(self, vobj, mode): if mode != 0: return None FreeCADGui.Control.closeDialog() return True def doubleClicked(self, vobj): self.edit() return True def setupContextMenu(self, vobj, menu): if FreeCADGui.activeWorkbench().name() != "BIMWorkbench": return actionEdit = QtGui.QAction(translate("Arch", "Edit"), menu) actionEdit.triggered.connect(self.edit) menu.addAction(actionEdit) def edit(self): FreeCADGui.ActiveDocument.setEdit(self.Object, 0) def dumps(self): return None def loads(self, state): return None def isShow(self): return True if FreeCAD.GuiUp: class MultiMaterialDelegate(QtGui.QStyledItemDelegate): def __init__(self, parent=None, *args): self.mats = [] for obj in FreeCAD.ActiveDocument.Objects: if obj.isDerivedFrom("App::MaterialObject"): self.mats.append(obj) QtGui.QStyledItemDelegate.__init__(self, parent, *args) def createEditor(self, parent, option, index): if index.column() == 0: editor = QtGui.QComboBox(parent) editor.setEditable(True) elif index.column() == 1: editor = QtGui.QComboBox(parent) elif index.column() == 2: ui = FreeCADGui.UiLoader() editor = ui.createWidget("Gui::InputField") editor.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Minimum) editor.setParent(parent) else: editor = QtGui.QLineEdit(parent) return editor def setEditorData(self, editor, index): if index.column() == 0: import ArchWindow editor.addItems([index.data()] + ArchWindow.WindowPartTypes) elif index.column() == 1: idx = -1 for i, m in enumerate(self.mats): editor.addItem(m.Label) if m.Label == index.data(): idx = i editor.setCurrentIndex(idx) else: QtGui.QStyledItemDelegate.setEditorData(self, editor, index) def setModelData(self, editor, model, index): if index.column() == 0: if editor.currentIndex() == -1: model.setData(index, "") else: model.setData(index, editor.currentText()) elif index.column() == 1: if editor.currentIndex() == -1: model.setData(index, "") else: model.setData(index, self.mats[editor.currentIndex()].Label) else: QtGui.QStyledItemDelegate.setModelData(self, editor, model, index) class _ArchMultiMaterialTaskPanel: """The editmode TaskPanel for MultiMaterial objects""" def __init__(self, obj=None): self.obj = obj self.form = FreeCADGui.PySideUic.loadUi(":/ui/ArchMultiMaterial.ui") self.model = QtGui.QStandardItemModel() self.model.setHorizontalHeaderLabels( [ translate("Arch", "Name"), translate("Arch", "Material"), translate("Arch", "Thickness"), ] ) self.form.tree.setRootIsDecorated(False) # remove 1st column's extra left margin self.form.tree.setModel(self.model) self.form.tree.setUniformRowHeights(True) self.form.tree.setItemDelegate(MultiMaterialDelegate()) self.form.chooseCombo.currentIndexChanged.connect(self.fromExisting) self.form.addButton.pressed.connect(self.addLayer) self.form.upButton.pressed.connect(self.upLayer) self.form.downButton.pressed.connect(self.downLayer) self.form.delButton.pressed.connect(self.delLayer) self.form.invertButton.pressed.connect(self.invertLayer) self.model.itemChanged.connect(self.recalcThickness) self.fillExistingCombo() self.fillData() def fillData(self, obj=None): if not obj: obj = self.obj if obj: self.model.clear() self.model.setHorizontalHeaderLabels( [ translate("Arch", "Name"), translate("Arch", "Material"), translate("Arch", "Thickness"), ] ) # restore widths self.form.tree.setColumnWidth(0, params.get_param_arch("MultiMaterialColumnWidth0")) self.form.tree.setColumnWidth(1, params.get_param_arch("MultiMaterialColumnWidth1")) for i in range(len(obj.Names)): item1 = QtGui.QStandardItem(obj.Names[i]) item2 = QtGui.QStandardItem(obj.Materials[i].Label) item3 = QtGui.QStandardItem( FreeCAD.Units.Quantity( obj.Thicknesses[i], FreeCAD.Units.Length ).getUserPreferred()[0] ) self.model.appendRow([item1, item2, item3]) self.form.nameField.setText(obj.Label) def fillExistingCombo(self): "fills the existing multimaterials combo" import Draft self.existingmaterials = [] for obj in FreeCAD.ActiveDocument.Objects: if Draft.getType(obj) == "MultiMaterial": if obj != self.obj: self.existingmaterials.append(obj) for m in self.existingmaterials: self.form.chooseCombo.addItem(m.Label) def fromExisting(self, index): "sets the contents from an existing material" if index > 0: if index <= len(self.existingmaterials): m = self.existingmaterials[index - 1] if m: self.fillData(m) def addLayer(self): item1 = QtGui.QStandardItem(translate("Arch", "New layer")) item2 = QtGui.QStandardItem() item3 = QtGui.QStandardItem() self.model.appendRow([item1, item2, item3]) def delLayer(self): sel = self.form.tree.selectedIndexes() if sel: row = sel[0].row() if row >= 0: self.model.takeRow(row) self.recalcThickness() def moveLayer(self, mvt=0): sel = self.form.tree.selectedIndexes() if sel and mvt: row = sel[0].row() if row >= 0: if row + mvt >= 0: data = self.model.takeRow(row) self.model.insertRow(row + mvt, data) ind = self.model.index(row + mvt, 0) self.form.tree.setCurrentIndex(ind) def upLayer(self): self.moveLayer(mvt=-1) def downLayer(self): self.moveLayer(mvt=1) def invertLayer(self): items = [self.model.takeRow(row) for row in range(self.model.rowCount() - 1, -1, -1)] items.reverse() for item in items: self.model.insertRow(0, item) def recalcThickness(self, item=None): prefix = translate("Arch", "Total thickness") + ": " th = 0 suffix = "" for row in range(self.model.rowCount()): thick = 0 d = self.model.item(row, 2).text() try: d = float(d) except Exception: thick = FreeCAD.Units.Quantity(d).Value else: thick = FreeCAD.Units.Quantity(d, FreeCAD.Units.Length).Value th += abs(thick) if not thick: suffix = " (" + translate("Arch", "depends on the object") + ")" val = FreeCAD.Units.Quantity(th, FreeCAD.Units.Length).UserString self.form.labelTotalThickness.setText(prefix + val + suffix) def accept(self): # store widths params.set_param_arch("MultiMaterialColumnWidth0", self.form.tree.columnWidth(0)) params.set_param_arch("MultiMaterialColumnWidth1", self.form.tree.columnWidth(1)) if self.obj: mats = [] for m in FreeCAD.ActiveDocument.Objects: if m.isDerivedFrom("App::MaterialObject"): mats.append(m) names = [] materials = [] thicknesses = [] for row in range(self.model.rowCount()): name = self.model.item(row, 0).text() mat = None ml = self.model.item(row, 1).text() for m in mats: if m.Label == ml: mat = m d = self.model.item(row, 2).text() try: d = float(d) except Exception: thick = FreeCAD.Units.Quantity(d).Value else: thick = FreeCAD.Units.Quantity(d, FreeCAD.Units.Length).Value if round(thick, 32) == 0: thick = 0.0 if name and mat: names.append(name) materials.append(mat) thicknesses.append(thick) self.obj.Names = names self.obj.Materials = materials self.obj.Thicknesses = thicknesses if self.form.nameField.text(): self.obj.Label = self.form.nameField.text() FreeCAD.ActiveDocument.recompute() FreeCADGui.ActiveDocument.resetEdit() return True def reject(self): FreeCADGui.ActiveDocument.resetEdit() return True