Files
create/src/Mod/BIM/ArchMaterial.py
Roy-043 0bfc4ac41e BIM: improve MultiMaterial task panel
Remove extra left margin from the first column in the tree view.
2025-10-16 14:58:59 +02:00

1032 lines
38 KiB
Python

# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2015 Yorik van Havre <yorik@uncreated.net> *
# * *
# * 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 *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
__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 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")
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.setText(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()
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