From d0cf727b7ab0280384841d6cca8aefa8d415b22c Mon Sep 17 00:00:00 2001 From: Yorik van Havre Date: Tue, 3 Dec 2024 14:22:26 +0100 Subject: [PATCH] BIM: NativeIFC: Support for types --- src/Mod/BIM/CMakeLists.txt | 1 + src/Mod/BIM/Resources/Arch.qrc | 157 +++++++++--------- src/Mod/BIM/Resources/ui/dialogConvertType.ui | 87 ++++++++++ src/Mod/BIM/nativeifc/ifc_objects.py | 43 ++++- src/Mod/BIM/nativeifc/ifc_tools.py | 11 +- src/Mod/BIM/nativeifc/ifc_types.py | 93 +++++++++++ src/Mod/BIM/nativeifc/ifc_viewproviders.py | 29 +++- 7 files changed, 335 insertions(+), 86 deletions(-) create mode 100644 src/Mod/BIM/Resources/ui/dialogConvertType.ui create mode 100644 src/Mod/BIM/nativeifc/ifc_types.py diff --git a/src/Mod/BIM/CMakeLists.txt b/src/Mod/BIM/CMakeLists.txt index 14e80c6841..8b71a2cf24 100644 --- a/src/Mod/BIM/CMakeLists.txt +++ b/src/Mod/BIM/CMakeLists.txt @@ -187,6 +187,7 @@ SET(nativeifc_SRCS nativeifc/ifc_viewproviders.py nativeifc/__init__.py nativeifc/ifc_openshell.py + nativeifc/ifc_types.py ) SOURCE_GROUP("" FILES ${Arch_SRCS}) diff --git a/src/Mod/BIM/Resources/Arch.qrc b/src/Mod/BIM/Resources/Arch.qrc index 48a769697c..360eb6d3ca 100644 --- a/src/Mod/BIM/Resources/Arch.qrc +++ b/src/Mod/BIM/Resources/Arch.qrc @@ -1,23 +1,5 @@ - icons/IFC/IfcBeam.svg - icons/IFC/IfcBuilding.svg - icons/IFC/IfcBuildingStorey.svg - icons/IFC/IfcColumn.svg - icons/IFC/IfcCovering.svg - icons/IFC/IfcDoor.svg - icons/IFC/IfcFooting.svg - icons/IFC/IfcMember.svg - icons/IFC/IfcPile.svg - icons/IFC/IfcPlate.svg - icons/IFC/IfcRailing.svg - icons/IFC/IfcRamp.svg - icons/IFC/IfcRoof.svg - icons/IFC/IfcSite.svg - icons/IFC/IfcSlab.svg - icons/IFC/IfcStair.svg - icons/IFC/IfcWall.svg - icons/IFC/IfcWindow.svg icons/Arch_3Views.svg icons/Arch_Add.svg icons/Arch_Axis.svg @@ -163,66 +145,24 @@ icons/techdraw-ArchView.svg icons/techdraw-PageDefault.svg icons/warning.svg - ui/ArchMaterial.ui - ui/ArchMultiMaterial.ui - ui/ArchNest.ui - ui/ArchSchedule.ui - ui/BimServerTaskPanel.ui - ui/DialogBimServerLogin.ui - ui/DialogDisplayText.ui - ui/GitTaskPanel.ui - ui/ParametersBeam.svg - ui/ParametersDent.svg - ui/ParametersDoorGlass.svg - ui/ParametersDoorSimple.svg - ui/ParametersIbeam.svg - ui/ParametersOpening.svg - ui/ParametersPanel.svg - ui/ParametersPillar.svg - ui/ParametersSlab.svg - ui/ParametersStairs.svg - ui/ParametersWindowDouble.svg - ui/ParametersWindowFixed.svg - ui/ParametersWindowSimple.svg - ui/ParametersWindowStash.svg - ui/dialogAddPSet.ui - ui/dialogAddProperty.ui - ui/dialogClasses.ui - ui/dialogClassification.ui - ui/dialogConvertDocument.ui - ui/dialogCreateProject.ui - ui/dialogCustomProperties.ui - ui/dialogDiff.ui - ui/dialogExport.ui - ui/dialogIfcElements.ui - ui/dialogIfcProperties.ui - ui/dialogIfcPropertiesRedux.ui - ui/dialogIfcQuantities.ui - ui/dialogImport.ui - ui/dialogLayersIFC.ui - ui/dialogLibrary.ui - ui/dialogListWidget.ui - ui/dialogMaterialChooser.ui - ui/dialogNudgeValue.ui - ui/dialogPhases.ui - ui/dialogPreflight.ui - ui/dialogPreflightResults.ui - ui/dialogProjectManager.ui - ui/dialogQuantitySurveying.ui - ui/dialogReorder.ui - ui/dialogSetup.ui - ui/dialogSpaces.ui - ui/dialogTree.ui - ui/dialogTutorial.ui - ui/dialogViews.ui - ui/dialogWelcome.ui - ui/dialogWindows.ui - ui/preferences-arch.ui - ui/preferences-archdefaults.ui - ui/preferences-dae.ui - ui/preferences-ifc-export.ui - ui/preferences-ifc.ui - ui/preferencesNativeIFC.ui + icons/IFC/IfcBeam.svg + icons/IFC/IfcBuilding.svg + icons/IFC/IfcBuildingStorey.svg + icons/IFC/IfcColumn.svg + icons/IFC/IfcCovering.svg + icons/IFC/IfcDoor.svg + icons/IFC/IfcFooting.svg + icons/IFC/IfcMember.svg + icons/IFC/IfcPile.svg + icons/IFC/IfcPlate.svg + icons/IFC/IfcRailing.svg + icons/IFC/IfcRamp.svg + icons/IFC/IfcRoof.svg + icons/IFC/IfcSite.svg + icons/IFC/IfcSlab.svg + icons/IFC/IfcStair.svg + icons/IFC/IfcWall.svg + icons/IFC/IfcWindow.svg translations/Arch_af.qm translations/Arch_ar.qm translations/Arch_be.qm @@ -266,5 +206,66 @@ translations/Arch_vi.qm translations/Arch_zh-CN.qm translations/Arch_zh-TW.qm + ui/ArchMaterial.ui + ui/ArchMultiMaterial.ui + ui/ArchNest.ui + ui/ArchSchedule.ui + ui/BimServerTaskPanel.ui + ui/DialogBimServerLogin.ui + ui/DialogDisplayText.ui + ui/GitTaskPanel.ui + ui/ParametersBeam.svg + ui/ParametersDent.svg + ui/ParametersDoorGlass.svg + ui/ParametersDoorSimple.svg + ui/ParametersIbeam.svg + ui/ParametersOpening.svg + ui/ParametersPanel.svg + ui/ParametersPillar.svg + ui/ParametersSlab.svg + ui/ParametersStairs.svg + ui/ParametersWindowDouble.svg + ui/ParametersWindowFixed.svg + ui/ParametersWindowSimple.svg + ui/ParametersWindowStash.svg + ui/dialogAddPSet.ui + ui/dialogAddProperty.ui + ui/dialogClasses.ui + ui/dialogClassification.ui + ui/dialogConvertDocument.ui + ui/dialogConvertType.ui + ui/dialogCreateProject.ui + ui/dialogCustomProperties.ui + ui/dialogDiff.ui + ui/dialogExport.ui + ui/dialogIfcElements.ui + ui/dialogIfcProperties.ui + ui/dialogIfcPropertiesRedux.ui + ui/dialogIfcQuantities.ui + ui/dialogImport.ui + ui/dialogLayersIFC.ui + ui/dialogLibrary.ui + ui/dialogListWidget.ui + ui/dialogMaterialChooser.ui + ui/dialogNudgeValue.ui + ui/dialogPhases.ui + ui/dialogPreflight.ui + ui/dialogPreflightResults.ui + ui/dialogProjectManager.ui + ui/dialogQuantitySurveying.ui + ui/dialogReorder.ui + ui/dialogSetup.ui + ui/dialogSpaces.ui + ui/dialogTree.ui + ui/dialogTutorial.ui + ui/dialogViews.ui + ui/dialogWelcome.ui + ui/dialogWindows.ui + ui/preferences-arch.ui + ui/preferences-archdefaults.ui + ui/preferences-dae.ui + ui/preferences-ifc-export.ui + ui/preferences-ifc.ui + ui/preferencesNativeIFC.ui diff --git a/src/Mod/BIM/Resources/ui/dialogConvertType.ui b/src/Mod/BIM/Resources/ui/dialogConvertType.ui new file mode 100644 index 0000000000..4466ceacff --- /dev/null +++ b/src/Mod/BIM/Resources/ui/dialogConvertType.ui @@ -0,0 +1,87 @@ + + + Dialog + + + + 0 + 0 + 448 + 161 + + + + Convert to IFC type + + + + + + + 0 + 0 + + + + This object will be converted to a %1 type. Types can be used to give common attributes and properties to several objects at once. + + + true + + + + + + + Keep original object. The object will adopt the new type + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/Mod/BIM/nativeifc/ifc_objects.py b/src/Mod/BIM/nativeifc/ifc_objects.py index aa7c0c0462..5f22990942 100644 --- a/src/Mod/BIM/nativeifc/ifc_objects.py +++ b/src/Mod/BIM/nativeifc/ifc_objects.py @@ -22,6 +22,9 @@ """This module contains IFC object definitions""" +import FreeCAD +translate = FreeCAD.Qt.translate + class ifc_object: """Base class for all IFC-based objects""" @@ -52,6 +55,8 @@ class ifc_object: self.rebuild_classlist(obj, setprops=True) elif prop == "Schema": self.edit_schema(obj, obj.Schema) + elif prop == "Type": + self.edit_type(obj) elif prop == "Group": self.edit_group(obj) elif obj.getGroupOfProperty(prop) == "IFC": @@ -70,7 +75,7 @@ class ifc_object: obj.ViewObject.signalChangeIcon() elif obj.getGroupOfProperty(prop) == "Geometry": self.edit_geometry(obj, prop) - elif obj.getGroupOfProperty(prop) not in ["Base", "IFC", "", "Geometry"]: + elif obj.getGroupOfProperty(prop) not in ["Base", "IFC", "", "Geometry", "PhysicalProperties"]: # Treat all property groups outside the default ones as Psets # print("DEBUG: editinog pset prop",prop) self.edit_pset(obj, prop) @@ -246,6 +251,42 @@ class ifc_object: if newlist != obj.Group: obj.Group = newlist + def edit_type(self, obj): + """Edits the type of this object""" + + from nativeifc import ifc_tools # lazy import + from nativeifc import ifc_types + + 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 + class document_object: """Holder for the document's IFC objects""" diff --git a/src/Mod/BIM/nativeifc/ifc_tools.py b/src/Mod/BIM/nativeifc/ifc_tools.py index 0f1cc44bec..00e92de8ef 100644 --- a/src/Mod/BIM/nativeifc/ifc_tools.py +++ b/src/Mod/BIM/nativeifc/ifc_tools.py @@ -525,6 +525,8 @@ def add_properties( obj.ShapeMode = shapemode if not obj.isDerivedFrom("Part::Feature"): obj.setPropertyStatus("ShapeMode", "Hidden") + if ifcentity.is_a("IfcProduct"): + obj.addProperty("App::PropertyLink", "Type", "IFC") attr_defs = ifcentity.wrapped_data.declaration().as_entity().all_attributes() try: info_ifcentity = ifcentity.get_info() @@ -1190,7 +1192,7 @@ def get_subvolume(obj): def create_relationship(old_obj, obj, parent, element, ifcfile, mode=None): """Creates a relationship between an IFC object and a parent IFC object""" - if isinstance(parent, FreeCAD.DocumentObject): + if isinstance(parent, (FreeCAD.DocumentObject, FreeCAD.Document)): parent_element = get_ifc_element(parent) else: parent_element = parent @@ -1373,8 +1375,9 @@ def migrate_schema(ifcfile, schema): return newfile, table -def remove_ifc_element(obj): - """removes the IFC data associated with an object""" +def remove_ifc_element(obj,delete_obj=False): + """removes the IFC data associated with an object. + If delete_obj is True, the FreeCAD object is also deleted""" # This function can become pure IFC @@ -1382,6 +1385,8 @@ def remove_ifc_element(obj): element = get_ifc_element(obj) if ifcfile and element: api_run("root.remove_product", ifcfile, product=element) + if delete_obj: + obj.Document.removeObject(obj.Name) return True return False diff --git a/src/Mod/BIM/nativeifc/ifc_types.py b/src/Mod/BIM/nativeifc/ifc_types.py new file mode 100644 index 0000000000..f02303cb3a --- /dev/null +++ b/src/Mod/BIM/nativeifc/ifc_types.py @@ -0,0 +1,93 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2023 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 * +# * * +# *************************************************************************** + +"""Diffing tool for NativeIFC project objects""" + + +import FreeCAD +from nativeifc import ifc_tools + +translate = FreeCAD.Qt.translate + + +def show_type(obj): + """Adds the types of that object as FreeCAD objects""" + + element = ifc_tools.get_ifc_element(obj) + typerel = getattr(element, "IsTypedBy", None) + project = ifc_tools.get_project(obj) + ifcfile = ifc_tools.get_ifcfile(obj) + if not all([element, typerel, project, ifcfile]): + return + for rel in typerel: + typelt = rel.RelatingType + if typelt: + typeobj = ifc_tools.create_object(typelt, obj.Document, ifcfile) + ifc_tools.get_group(project, "IfcTypesGroup").addObject(typeobj) + obj.Type = typeobj + + +def is_typable(obj): + """Checks if an object can become a type""" + + element = ifc_tools.get_ifc_element(obj) + ifcfile = ifc_tools.get_ifcfile(obj) + if not element or not ifcfile: + return False + type_class = element.is_a() + "Type" + schema = ifcfile.wrapped_data.schema_name() + schema = ifc_tools.ifcopenshell.ifcopenshell_wrapper.schema_by_name(schema) + try: + declaration = schema.declaration_by_name(type_class) + except RuntimeError: + return False + return True + + +def convert_to_type(obj, keep_object=False): + """Converts an object to a type. If keep_object is + True, the original object is kept (and adopts the new type).""" + + if not is_typable(obj): + return + if not getattr(obj, "Shape", None): + return + if FreeCAD.GuiUp: + import FreeCADGui + dlg = FreeCADGui.PySideUic.loadUi(":/ui/dialogConvertType.ui") + result = dlg.exec_() + if not result: + return + keep_object = dlg.checkKeepObject.isChecked() + element = ifc_tools.get_ifc_element(obj) + ifcfile = ifc_tools.get_ifcfile(obj) + project = ifc_tools.get_project(obj) + if not element or not ifcfile or not project: + return + type_element = ifc_tools.api_run("root.copy_class", ifcfile, product=element) + type_element = ifc_tools.api_run("root.reassign_class", ifcfile, product=type_element, ifc_class=obj.Class+"Type") + type_obj = ifc_tools.create_object(type_element, obj.Document, ifcfile) + if keep_object: + obj.Type = type_obj + else: + ifc_tools.remove_ifc_element(obj, delete_obj=True) + ifc_tools.get_group(project, "IfcTypesGroup").addObject(type_obj) diff --git a/src/Mod/BIM/nativeifc/ifc_viewproviders.py b/src/Mod/BIM/nativeifc/ifc_viewproviders.py index ee7ac59fc2..3890b417cb 100644 --- a/src/Mod/BIM/nativeifc/ifc_viewproviders.py +++ b/src/Mod/BIM/nativeifc/ifc_viewproviders.py @@ -71,8 +71,8 @@ class ifc_vp_object: def getIcon(self): from PySide import QtCore, QtGui # lazy import - - rclass = self.Object.IfcClass.replace("StandardCase","") + + rclass = self.Object.IfcClass.replace("StandardCase","") ifcicon = ":/icons/IFC/" + rclass + ".svg" if QtCore.QFile.exists(ifcicon): if getattr(self, "ifcclass", "") != rclass: @@ -95,6 +95,7 @@ class ifc_vp_object: from nativeifc import ifc_tools # lazy import from nativeifc import ifc_psets from nativeifc import ifc_materials + from nativeifc import ifc_types from PySide import QtCore, QtGui # lazy import if FreeCADGui.activeWorkbench().name() != 'BIMWorkbench': @@ -150,11 +151,14 @@ class ifc_vp_object: action_material = QtGui.QAction(icon, "Load material",menu) action_material.triggered.connect(self.addMaterial) actions.append(action_material) + if ifc_types.is_typable(self.Object): + action_type = QtGui.QAction(icon, "Convert to type", menu) + action_type.triggered.connect(self.convertToType) + actions.append(action_type) if actions: ifc_menu = QtGui.QMenu("IFC") ifc_menu.setIcon(icon) - for a in actions: - ifc_menu.addAction(a) + ifc_menu.addActions(actions) menu.addMenu(ifc_menu) # generic actions @@ -362,6 +366,11 @@ class ifc_vp_object: self.Object.Document.recompute() def doubleClicked(self, vobj): + """On double-click""" + + self.expandProperties(vobj) + + def expandProperties(self, vobj): """Expands everything that needs to be expanded""" from nativeifc import ifc_geometry # lazy import @@ -369,12 +378,14 @@ class ifc_vp_object: from nativeifc import ifc_psets # lazy import from nativeifc import ifc_materials # lazy import from nativeifc import ifc_layers # lazy import + from nativeifc import ifc_types # lazy import # generic data loading ifc_geometry.add_geom_properties(vobj.Object) ifc_psets.show_psets(vobj.Object) ifc_materials.show_material(vobj.Object) ifc_layers.add_layers(vobj.Object) + ifc_types.show_type(vobj.Object) # expand children if self.hasChildren(vobj.Object): @@ -390,6 +401,16 @@ class ifc_vp_object: return True return None + def convertToType(self): + """Converts this object to a type""" + + if not hasattr(self, "Object"): + return + from nativeifc import ifc_types + ifc_types.convert_to_type(self.Object) + self.Object.Document.recompute() + + class ifc_vp_document(ifc_vp_object): """View provider for the IFC document object"""