diff --git a/src/Mod/BIM/ArchCurtainWall.py b/src/Mod/BIM/ArchCurtainWall.py index 3a0c480ffc..eb9d6736ae 100644 --- a/src/Mod/BIM/ArchCurtainWall.py +++ b/src/Mod/BIM/ArchCurtainWall.py @@ -528,6 +528,8 @@ class ViewProviderCurtainWall(ArchComponent.ViewProviderComponent): if not obj.Shape or not obj.Shape.Solids: return + if not obj.ViewObject: + return basecolor = obj.ViewObject.ShapeColor basetransparency = obj.ViewObject.Transparency/100.0 panelcolor = ArchCommands.getDefaultColor("WindowGlass") diff --git a/src/Mod/BIM/CMakeLists.txt b/src/Mod/BIM/CMakeLists.txt index b4b13cd9c1..98756b73b6 100644 --- a/src/Mod/BIM/CMakeLists.txt +++ b/src/Mod/BIM/CMakeLists.txt @@ -193,6 +193,7 @@ SET(nativeifc_SRCS nativeifc/ifc_openshell.py nativeifc/ifc_types.py nativeifc/ifc_export.py + nativeifc/ifc_classification.py ) SOURCE_GROUP("" FILES ${Arch_SRCS}) diff --git a/src/Mod/BIM/bimcommands/BimClassification.py b/src/Mod/BIM/bimcommands/BimClassification.py index 36f42fe8c3..490d298888 100644 --- a/src/Mod/BIM/bimcommands/BimClassification.py +++ b/src/Mod/BIM/bimcommands/BimClassification.py @@ -58,6 +58,7 @@ class BIM_Classification: if not hasattr(self, "Classes"): self.Classes = {} self.isEditing = None + current = None # load the form and set the tree model up self.form = FreeCADGui.PySideUic.loadUi(":/ui/dialogClassification.ui") @@ -86,13 +87,22 @@ class BIM_Classification: # hide materials list if we are editing a particular object if len(FreeCADGui.Selection.getSelection()) == 1: self.isEditing = FreeCADGui.Selection.getSelection()[0] - if hasattr(self.isEditing, "StandardCode"): + pl = self.isEditing.PropertiesList + if ("StandardCode" in pl) or ("IfcClass" in pl): self.form.groupMaterials.hide() self.form.buttonApply.hide() self.form.buttonRename.hide() self.form.setWindowTitle( translate("BIM", "Editing") + " " + self.isEditing.Label ) + if "IfcClass" in pl: + # load existing class if needed + from nativeifc import ifc_classification + ifc_classification.show_classification(self.isEditing) + if "StandardCode" in pl: + current = self.isEditing.StandardCode + elif "Classification" in self.isEditing.PropertiesList: + current = self.isEditing.Classification # fill materials list self.objectslist = {} @@ -105,6 +115,9 @@ class BIM_Classification: else: self.objectslist[obj.Name] = obj.StandardCode self.labellist[obj.Name] = obj.Label + elif "Classification" in obj.PropertiesList: + self.objectslist[obj.Name] = obj.Classification + self.labellist[obj.Name] = obj.Label # fill objects list if not self.isEditing: @@ -129,37 +142,15 @@ class BIM_Classification: ) # connect signals - QtCore.QObject.connect( - self.form.comboSystem, - QtCore.SIGNAL("currentIndexChanged(int)"), - self.updateClasses, - ) - QtCore.QObject.connect( - self.form.buttonApply, QtCore.SIGNAL("clicked()"), self.apply - ) - QtCore.QObject.connect( - self.form.buttonRename, QtCore.SIGNAL("clicked()"), self.rename - ) - QtCore.QObject.connect( - self.form.search, QtCore.SIGNAL("textEdited(QString)"), self.updateClasses - ) - QtCore.QObject.connect( - self.form.buttonBox, QtCore.SIGNAL("accepted()"), self.accept - ) - QtCore.QObject.connect( - self.form.groupMode, - QtCore.SIGNAL("currentIndexChanged(int)"), - self.updateObjects, - ) - QtCore.QObject.connect( - self.form.treeClass, - QtCore.SIGNAL("itemDoubleClicked(QTreeWidgetItem*,int)"), - self.apply, - ) - QtCore.QObject.connect(self.form.search, QtCore.SIGNAL("up()"), self.onUpArrow) - QtCore.QObject.connect( - self.form.search, QtCore.SIGNAL("down()"), self.onDownArrow - ) + self.form.comboSystem.currentIndexChanged.connect(self.updateClasses) + self.form.buttonApply.clicked.connect(self.apply) + self.form.buttonRename.clicked.connect(self.rename) + self.form.search.textEdited.connect(self.updateClasses) + self.form.buttonBox.accepted.connect(self.accept) + self.form.groupMode.currentIndexChanged.connect(self.updateObjects) + self.form.treeClass.itemDoubleClicked.connect(self.apply) + self.form.search.up.connect(self.onUpArrow) + self.form.search.down.connect(self.onDownArrow) # center the dialog over FreeCAD window mw = FreeCADGui.getMainWindow() @@ -170,6 +161,21 @@ class BIM_Classification: ) self.updateClasses() + + # select current classification + if current: + system, classification = current.split(" ", 1) + print("searching for",classification) + if system in self.Classes: + self.form.comboSystem.setCurrentText(system) + res = self.form.treeClass.findItems( + classification, + QtCore.Qt.MatchExactly|QtCore.Qt.MatchRecursive, + 0 + ) + if res: + self.form.treeClass.setCurrentItem(res[0]) + self.form.show() self.form.search.setFocus() @@ -579,13 +585,24 @@ class BIM_Classification: if item.toolTip(0): obj = FreeCAD.ActiveDocument.getObject(item.toolTip(0)) if obj: - if code != obj.StandardCode: - if not changed: - FreeCAD.ActiveDocument.openTransaction( - "Change standard codes" - ) - changed = True - obj.StandardCode = code + if hasattr(obj, "StandardCode"): + if code != obj.StandardCode: + if not changed: + FreeCAD.ActiveDocument.openTransaction( + "Change standard codes" + ) + changed = True + obj.StandardCode = code + elif hasattr(obj, "IfcClass"): + if not "Classification" in obj.PropertiesList: + obj.addProperty("App::PropertyString", "Classification", "IFC") + if code != obj.Classification: + if not changed: + FreeCAD.ActiveDocument.openTransaction( + "Change standard codes" + ) + changed = True + obj.Classification = code if label != obj.Label: if not changed: FreeCAD.ActiveDocument.openTransaction( @@ -598,11 +615,17 @@ class BIM_Classification: FreeCAD.ActiveDocument.recompute() else: code = self.form.treeClass.selectedItems()[0].text(0) - if "StandardCode" in self.isEditing.PropertiesList: + pl = self.isEditing.PropertiesList + if ("StandardCode" in pl) or ("IfcClass" in pl): FreeCAD.ActiveDocument.openTransaction("Change standard codes") if self.form.checkPrefix.isChecked(): code = self.form.comboSystem.currentText() + " " + code - self.isEditing.StandardCode = code + if "StandardCode" in pl: + self.isEditing.StandardCode = code + else: + if not "Classification" in self.isEditing.PropertiesList: + self.isEditing.addProperty("App::PropertyString", "Classification", "IFC") + self.isEditing.Classification = code if hasattr(self.isEditing.ViewObject, "Proxy") and hasattr( self.isEditing.ViewObject.Proxy, "setTaskValue" ): diff --git a/src/Mod/BIM/bimcommands/BimMaterial.py b/src/Mod/BIM/bimcommands/BimMaterial.py index 50ddbcdb56..a8f896e24e 100644 --- a/src/Mod/BIM/bimcommands/BimMaterial.py +++ b/src/Mod/BIM/bimcommands/BimMaterial.py @@ -36,14 +36,17 @@ if FreeCAD.GuiUp: class MatLineEdit(QtGui.QLineEdit): "custom QLineEdit widget that has the power to catch up/down arrow keypress" + up = QtCore.Signal() + down = QtCore.Signal() + def __init__(self, parent=None): QtGui.QLineEdit.__init__(self, parent) def keyPressEvent(self, event): if event.key() == QtCore.Qt.Key_Up: - self.emit(QtCore.SIGNAL("up()")) + self.up.emit() elif event.key() == QtCore.Qt.Key_Down: - self.emit(QtCore.SIGNAL("down()")) + self.down.emit() else: QtGui.QLineEdit.keyPressEvent(self, event) @@ -435,8 +438,8 @@ class BIM_Material: FreeCAD.ActiveDocument.openTransaction("Change material") for obj in self.dlg.objects: if hasattr(obj, "StepId"): - from nativeifc import ifc_tools - ifc_tools.set_material(mat, obj) + from nativeifc import ifc_materials + ifc_materials.set_material(mat, obj) else: obj.Material = mat FreeCAD.ActiveDocument.commitTransaction() diff --git a/src/Mod/BIM/importers/exportIFC.py b/src/Mod/BIM/importers/exportIFC.py index 3f2b6420a6..88c11ab672 100644 --- a/src/Mod/BIM/importers/exportIFC.py +++ b/src/Mod/BIM/importers/exportIFC.py @@ -48,8 +48,7 @@ from importers.importIFCHelper import dd2dms from draftutils import params from draftutils.messages import _msg, _err -if FreeCAD.GuiUp: - import FreeCADGui +import FreeCADGui __title__ = "FreeCAD IFC export" __author__ = ("Yorik van Havre", "Jonathan Wiedemann", "Bernd Hahnebach") @@ -339,6 +338,8 @@ def export(exportList, filename, colors=None, preferences=None): shapedefs = {} # { ShapeDefString:[shapes],... } spatialelements = {} # {Name:IfcEntity, ... } uids = [] # store used UIDs to avoid reuse (some FreeCAD objects might have same IFC UID, ex. copy/pasted objects + classifications = {} # {Name:IfcEntity, ... } + curvestyles = {} # build clones table @@ -934,6 +935,33 @@ def export(exportList, filename, colors=None, preferences=None): pset ) + # Classifications + + classification = getattr(obj, "StandardCode", "") + if classification: + name, code = classification.split(" ", 1) + if name in classifications: + system = classifications[name] + else: + system = ifcfile.createIfcClassification(None, None, None, name) + classifications[name] = system + for ref in getattr(system, "HasReferences", []): + if code.startswith(ref.Name): + break + else: + ref = ifcfile.createIfcClassificationReference(None, code, None, system) + if getattr(ref, "ClassificationRefForObjects", None): + rel = ref.ClassificationRefForObjects[0] + rel.RelatedObjects = rel.RelatedObjects + [product] + else: + rel = ifcfile.createIfcRelAssociatesClassification( + ifcopenshell.guid.new(), + history,'FreeCADClassificationRel', + None, + [product], + ref + ) + count += 1 # relate structural analysis objects to the struct model @@ -2471,7 +2499,7 @@ def writeJson(filename,ifcfile): def create_annotation(anno, ifcfile, context, history, preferences): """Creates an annotation object""" - # uses global ifcbin, curvestyles + global curvestyles, ifcbin objectType = None ovc = None zvc = None @@ -2479,6 +2507,7 @@ def create_annotation(anno, ifcfile, context, history, preferences): reps = [] repid = "Annotation" reptype = "Annotation2D" + description = getattr(anno, "Description", None) if anno.isDerivedFrom("Part::Feature"): if Draft.getType(anno) == "Hatch": objectType = "HATCH" diff --git a/src/Mod/BIM/nativeifc/ifc_classification.py b/src/Mod/BIM/nativeifc/ifc_classification.py new file mode 100644 index 0000000000..0115ebe865 --- /dev/null +++ b/src/Mod/BIM/nativeifc/ifc_classification.py @@ -0,0 +1,104 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2024 Yorik van Havre * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU General Public License (GPL) * +# * as published by the Free Software Foundation; either version 3 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program 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 General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + + +from nativeifc import ifc_tools # lazy import + + +def edit_classification(obj): + """Edits the classification of this object""" + + element = ifc_tools.get_ifc_element(obj) + ifcfile = ifc_tools.get_ifcfile(obj) + if not element or not ifcfile: + return + # TODO: remove previous reference? + #ifc_tools.api_run("classification.remove_reference", + # ifcfile, reference=ref, products=[obj]) + classifications = ifcfile.by_type("IfcClassification") + classification = getattr(obj, "Classification", "") + if classification: + cname, code = classification.split(" ", 1) + cnames = [c.Name for c in classifications] + if cname in cnames: + system = classifications[cnames.index(cname)] + else: + system = ifc_tools.api_run("classification.add_classification", ifcfile, + classification=cname) + for ref in getattr(system, "HasReferences", []): + rname = ref.Name or ref.Identification + if code == rname: + return + elif code.startswith(rname): + if getattr(ref, "ClassificationRefForObjects", None): + rel = ref.ClassificationRefForObjects[0] + if not element in rel.RelatedObjects: + ifc_tools.edit_attribute(rel, "RelatedObjects", + rel.RelatedObjects + [element] + ) + else: + # we have a reference, but no classForObjects + # this is weird and shouldn't exist... + rel = ifcfile.createIfcRelAssociatesClassification( + ifc_tools.ifcopenshell.guid.new(), + history,'FreeCADClassificationRel', + None, + [element], + ref + ) + else: + ifc_tools.api_run("classification.add_reference", ifcfile, + products = [element], + classification = system, + identification = code + ) + else: + # classification property is empty + for rel in getattr(element, "HasAssociations", []): + if rel.is_a("IfcRelAssociatesClassification"): + # removing existing classification if only user + if len(rel.RelatedObjects) == 1 and rel.RelatedObjects[0] == element: + ifc_tools.api_run("classification.remove_reference", + ifcfile, + reference=rel.RelatingClassification, + products=[element] + ) + # TODO: Remove IfcClassification too? + + +def show_classification(obj): + """Loads the classification of this object""" + + element = ifc_tools.get_ifc_element(obj) + ifcfile = ifc_tools.get_ifcfile(obj) + if not element or not ifcfile: + return + for system in ifcfile.by_type("IfcClassification"): + for ref in getattr(system, "HasReferences", []): + for rel in ref.ClassificationRefForObjects: + if element in rel.RelatedObjects: + if not "Classification" in obj.PropertiesList: + obj.addProperty("App::PropertyString", "Classification", "IFC") + sname = system.Name + cname = ref.Name or ref.Identification + obj.Classification = sname + " " + cname + break diff --git a/src/Mod/BIM/nativeifc/ifc_objects.py b/src/Mod/BIM/nativeifc/ifc_objects.py index d7cc610971..e2c1b2323f 100644 --- a/src/Mod/BIM/nativeifc/ifc_objects.py +++ b/src/Mod/BIM/nativeifc/ifc_objects.py @@ -23,6 +23,7 @@ """This module contains IFC object definitions""" import FreeCAD +import FreeCADGui translate = FreeCAD.Qt.translate # the property groups below should not be treated as psets @@ -59,7 +60,9 @@ class ifc_object: elif prop == "Schema": self.edit_schema(obj, obj.Schema) elif prop == "Type": - self.edit_type(obj) + self.Classification(obj) + elif prop == "Classification": + self.edit_classification(obj) elif prop == "Group": self.edit_group(obj) elif hasattr(obj, prop) and obj.getGroupOfProperty(prop) == "IFC": @@ -111,11 +114,7 @@ class ifc_object: def fit_all(self): """Fits the view""" - import FreeCAD - if FreeCAD.GuiUp: - import FreeCADGui - FreeCADGui.SendMsgToActiveView("ViewFit") def rebuild_classlist(self, obj, setprops=False): @@ -322,38 +321,10 @@ class ifc_object: def edit_type(self, obj): """Edits the type of this object""" - from nativeifc import ifc_tools # lazy import - from nativeifc import ifc_types + from nativeifc import ifc_types # lazy import + + ifc_types.edit_type(obj) - element = ifc_tools.get_ifc_element(obj) - ifcfile = ifc_tools.get_ifcfile(obj) - if not element or not ifcfile: - return - typerel = getattr(element, "IsTypedBy", None) - if obj.Type: - # verify the type is compatible -ex IFcWall in IfcWallType - if obj.Type.Class != element.is_a() + "Type": - t = translate("BIM","Error: Incompatible type") - FreeCAD.Console.PrintError(obj.Label+": "+t+": "+obj.Type.Class+"\n") - obj.Type = None - return - # change type - new_type = ifc_tools.get_ifc_element(obj.Type) - if not new_type: - return - for rel in typerel: - if rel.RelatingType == new_type: - return - # assign the new type - ifc_tools.api_run("type.assign_type", - ifcfile, - related_objects=[element], - relating_type=new_type - ) - elif typerel: - # TODO remove type? - # Not doing anything right now because an unset Type property could screw the ifc file - pass def edit_quantity(self, obj, prop): @@ -403,6 +374,14 @@ class ifc_object: return [], None + def edit_classification(self, obj): + """Edits the classification of this object""" + + from nativeifc import ifc_classification # lazy loading + + ifc_classification.edit_classification(obj) + + class document_object: """Holder for the document's IFC objects""" diff --git a/src/Mod/BIM/nativeifc/ifc_status.py b/src/Mod/BIM/nativeifc/ifc_status.py index 8c0ed11012..14d36894e8 100644 --- a/src/Mod/BIM/nativeifc/ifc_status.py +++ b/src/Mod/BIM/nativeifc/ifc_status.py @@ -248,6 +248,10 @@ def on_activate(): from PySide import QtGui # lazy import + # always reset the menu to normal first + set_menu(False) + if FreeCADGui.activeWorkbench().name() != "BIMWorkbench": + return doc = FreeCAD.ActiveDocument if doc and "IfcFilePath" in doc.PropertiesList: checked = True diff --git a/src/Mod/BIM/nativeifc/ifc_tools.py b/src/Mod/BIM/nativeifc/ifc_tools.py index c248d54856..bc1599e0a8 100644 --- a/src/Mod/BIM/nativeifc/ifc_tools.py +++ b/src/Mod/BIM/nativeifc/ifc_tools.py @@ -185,9 +185,11 @@ def create_ifcfile(): param = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Document") user = param.GetString("prefAuthor", "") user = user.split("<")[0].strip() + org = param.GetString("prefCompany", "") + person = None + organisation = None if user: person = api_run("owner.add_person", ifcfile, family_name=user) - org = param.GetString("prefCompany", "") if org: organisation = api_run("owner.add_organisation", ifcfile, name=org) if user and org: @@ -1633,11 +1635,14 @@ def remove_tree(objs): def recompute(children): """Temporary function to recompute objects. Some objects don't get their shape correctly at creation""" - import time - stime = time.time() + #import time + #stime = time.time() + doc = None for c in children: - c.touch() - if not FreeCAD.ActiveDocument.Recomputing: - FreeCAD.ActiveDocument.recompute() - endtime = "%02d:%02d" % (divmod(round(time.time() - stime, 1), 60)) - print("DEBUG: Extra recomputing of",len(children),"objects took",endtime) + if c: + c.touch() + doc = c.Document + if doc: + doc.recompute() + #endtime = "%02d:%02d" % (divmod(round(time.time() - stime, 1), 60)) + #print("DEBUG: Extra recomputing of",len(children),"objects took",endtime) diff --git a/src/Mod/BIM/nativeifc/ifc_types.py b/src/Mod/BIM/nativeifc/ifc_types.py index ea31ba6c49..0a714fa6d5 100644 --- a/src/Mod/BIM/nativeifc/ifc_types.py +++ b/src/Mod/BIM/nativeifc/ifc_types.py @@ -91,3 +91,38 @@ def convert_to_type(obj, keep_object=False): else: ifc_tools.remove_ifc_element(obj, delete_obj=True) ifc_tools.get_group(project, "IfcTypesGroup").addObject(type_obj) + + +def edit_type(obj): + """Edits the type of this object""" + + element = ifc_tools.get_ifc_element(obj) + ifcfile = ifc_tools.get_ifcfile(obj) + if not element or not ifcfile: + return + + typerel = getattr(element, "IsTypedBy", None) + if obj.Type: + # verify the type is compatible -ex IFcWall in IfcWallType + if obj.Type.Class != element.is_a() + "Type": + t = translate("BIM","Error: Incompatible type") + FreeCAD.Console.PrintError(obj.Label+": "+t+": "+obj.Type.Class+"\n") + obj.Type = None + return + # change type + new_type = ifc_tools.get_ifc_element(obj.Type) + if not new_type: + return + for rel in typerel: + if rel.RelatingType == new_type: + return + # assign the new type + ifc_tools.api_run("type.assign_type", + ifcfile, + related_objects=[element], + relating_type=new_type + ) + elif typerel: + # TODO remove type? + # Not doing anything right now because an unset Type property could screw the ifc file + pass diff --git a/src/Mod/BIM/nativeifc/ifc_viewproviders.py b/src/Mod/BIM/nativeifc/ifc_viewproviders.py index 9402bf6610..77f99c20a9 100644 --- a/src/Mod/BIM/nativeifc/ifc_viewproviders.py +++ b/src/Mod/BIM/nativeifc/ifc_viewproviders.py @@ -380,6 +380,7 @@ class ifc_vp_object: from nativeifc import ifc_materials # lazy import from nativeifc import ifc_layers # lazy import from nativeifc import ifc_types # lazy import + from nativeifc import ifc_classification # lazy import # generic data loading ifc_geometry.add_geom_properties(vobj.Object) @@ -387,6 +388,7 @@ class ifc_vp_object: ifc_materials.show_material(vobj.Object) ifc_layers.add_layers(vobj.Object) ifc_types.show_type(vobj.Object) + ifc_classification.show_classification(vobj.Object) # expand children if self.hasChildren(vobj.Object):