From 03809b3d5e99cc5c0f24c3ac754f37aa6dec026d Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 28 Nov 2023 16:44:31 +1100 Subject: [PATCH] Fix #3996. Reassigning occurrences or types now also reassigns the correlating occurrence or type. --- .../blenderbim/bim/module/root/operator.py | 22 +++++++-- src/blenderbim/blenderbim/tool/loader.py | 2 +- .../ifcopenshell/api/root/reassign_class.py | 46 ++++++++++++------- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/root/operator.py b/src/blenderbim/blenderbim/bim/module/root/operator.py index 60ef040805..951aedae50 100644 --- a/src/blenderbim/blenderbim/bim/module/root/operator.py +++ b/src/blenderbim/blenderbim/bim/module/root/operator.py @@ -103,17 +103,33 @@ class ReassignClass(bpy.types.Operator): predefined_type = context.scene.BIMRootProperties.ifc_predefined_type if predefined_type == "USERDEFINED": predefined_type = context.scene.BIMRootProperties.ifc_userdefined_type + reassigned_elements = set() for obj in objects: product = ifcopenshell.api.run( "root.reassign_class", self.file, - product=self.file.by_id(obj.BIMObjectProperties.ifc_definition_id), + product=tool.Ifc.get_entity(obj), ifc_class=context.scene.BIMRootProperties.ifc_class, predefined_type=predefined_type, ) - obj.name = "{}/{}".format(product.is_a(), getattr(product, "Name", "None")) - tool.Ifc.link(product, obj) + reassigned_elements.add(product) + obj.name = tool.Loader.get_name(product) obj.BIMObjectProperties.is_reassigning_class = False + + dependent_elements = set() + for reassigned_element in reassigned_elements: + if reassigned_element.is_a("IfcTypeObject"): + dependent_elements.update(ifcopenshell.util.element.get_types(product)) + else: + element_type = ifcopenshell.util.element.get_type(product) + if element_type: + dependent_elements.add(element_type) + dependent_elements.update(ifcopenshell.util.element.get_types(element_type)) + + for dependent_element in dependent_elements: + obj = tool.Ifc.get_object(dependent_element) + if obj: + obj.name = tool.Loader.get_name(dependent_element) return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/tool/loader.py b/src/blenderbim/blenderbim/tool/loader.py index 19d6fbdfc2..9b00453bdb 100644 --- a/src/blenderbim/blenderbim/tool/loader.py +++ b/src/blenderbim/blenderbim/tool/loader.py @@ -61,7 +61,7 @@ class Loader(blenderbim.core.tool.Loader): @classmethod def get_name(cls, element): - return "{}/{}".format(element.is_a(), element.Name) + return "{}/{}".format(element.is_a(), getattr(element, "Name", "None")) @classmethod def link_mesh(cls, shape, mesh): diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/reassign_class.py b/src/ifcopenshell-python/ifcopenshell/api/root/reassign_class.py index 46acc6903a..c87df1e8e4 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/root/reassign_class.py +++ b/src/ifcopenshell-python/ifcopenshell/api/root/reassign_class.py @@ -39,6 +39,14 @@ class Usecase: This is especially useful when dealing with poorly classified data from proprietary software with limited IFC capabilities. + If you are reassigning a type, the occurrence classes are also + reassigned to maintain validity. + + Vice versa, if you are reassigning an occurrence, the type is also + reassigned in IFC4 and up. In IFC2X3, this may not occur if the type + cannot be unambiguously derived, so you are required to manually check + this. + :param product: The IfcProduct that you want to change the class of. :type product: ifcopenshell.entity_instance.entity_instance :param ifc_class: The new IFC class you want to change it to. @@ -70,10 +78,27 @@ class Usecase: } def execute(self): - element = ifcopenshell.util.schema.reassign_class( - self.file, self.settings["product"], self.settings["ifc_class"] - ) - is_type = element.is_a("IfcTypeProduct") + element = self.reassign_class(self.settings["product"], self.settings["ifc_class"]) + + if element.is_a("IfcTypeProduct"): + for occurrence in ifcopenshell.util.element.get_types(element) or []: + ifc_class = ifcopenshell.util.type.get_applicable_entities(self.settings["ifc_class"])[0] + self.reassign_class(occurrence, ifc_class) + else: + element_type = ifcopenshell.util.element.get_type(element) + if element_type: + ifc_class = ifcopenshell.util.type.get_applicable_types(self.settings["ifc_class"]) + if ifc_class and len(ifc_class) == 1: + element_type = self.reassign_class(element_type, ifc_class[0]) + ifc_class = element.is_a() + for occurrence in ifcopenshell.util.element.get_types(element_type) or []: + if occurrence == element: + continue + self.reassign_class(occurrence, ifc_class) + return element + + def reassign_class(self, element, ifc_class): + element = ifcopenshell.util.schema.reassign_class(self.file, element, ifc_class) if self.settings["predefined_type"] and hasattr(element, "PredefinedType"): try: element.PredefinedType = self.settings["predefined_type"] @@ -81,20 +106,9 @@ class Usecase: # PredefinedType wasn't in the respective enum, assume it's actually USERDEFINED # and set .ElementType / .ObjectType to the provided predefined type element.PredefinedType = "USERDEFINED" - if is_type: + if element.is_a("IfcTypeProduct"): element.ElementType = self.settings["predefined_type"] else: element.ObjectType = self.settings["predefined_type"] - # reassign classes for occurences connected to the type - if is_type: - for occurrence in ifcopenshell.util.element.get_types(element) or []: - ifc_class = ifcopenshell.util.type.get_applicable_entities(self.settings["ifc_class"])[0] - ifcopenshell.api.run( - "root.reassign_class", - self.file, - product=occurrence, - ifc_class=ifc_class, - predefined_type=self.settings["predefined_type"], - ) return element