diff --git a/src/bonsai/bonsai/bim/module/root/data.py b/src/bonsai/bonsai/bim/module/root/data.py index b09af3a8b5..7690210c9b 100644 --- a/src/bonsai/bonsai/bim/module/root/data.py +++ b/src/bonsai/bonsai/bim/module/root/data.py @@ -255,6 +255,8 @@ class IfcClassData: if not element: return False if element_type := ifcopenshell.util.element.get_type(element): + if element_type == element: + return False # Allow for None due to https://github.com/buildingSMART/IFC4.3.x-development/issues/818 return ifcopenshell.util.element.get_predefined_type(element_type) not in ("NOTDEFINED", None) return False diff --git a/src/bonsai/bonsai/bim/module/root/ui.py b/src/bonsai/bonsai/bim/module/root/ui.py index 5e9fa73eb0..facf619ad8 100644 --- a/src/bonsai/bonsai/bim/module/root/ui.py +++ b/src/bonsai/bonsai/bim/module/root/ui.py @@ -59,10 +59,14 @@ class BIM_PT_class(Panel): row = self.layout.row(align=True) row.operator("bim.reassign_class", icon="CHECKMARK") row.operator("bim.disable_reassign_class", icon="CANCEL", text="") + + # If Entity has inherited Predefined Type, disable PredefinedType / ObjectType dropdown to forbid double typing. See #7006) + enable_predef_types = not IfcClassData.data["has_inherited_predefined_type"] self.draw_class_dropdowns( context, root_prop.get_ifc_predefined_types(rprops, context), is_reassigning_class=True, + set_predefined_types_enabled=enable_predef_types, ) self.layout.prop(rprops, "relating_class_object", icon="COPYDOWN") else: @@ -82,22 +86,29 @@ class BIM_PT_class(Panel): return ifc_predefined_types = root_prop.get_ifc_predefined_types(rprops, context) - self.draw_class_dropdowns(context, ifc_predefined_types) + # If Entity has inherited Predefined Type, disable PredefinedType / ObjectType dropdown to forbid double typing. See #7006) + enable_predef_types = not IfcClassData.data["has_inherited_predefined_type"] + + self.draw_class_dropdowns(context, ifc_predefined_types, set_predefined_types_enabled=enable_predef_types) row = self.layout.row(align=True) op = row.operator("bim.assign_class") op.ifc_class = rprops.ifc_class op.predefined_type = rprops.ifc_predefined_type if ifc_predefined_types else "" op.userdefined_type = rprops.ifc_userdefined_type - def draw_class_dropdowns(self, context, ifc_predefined_types, is_reassigning_class=False): + def draw_class_dropdowns( + self, context, ifc_predefined_types, is_reassigning_class=False, set_predefined_types_enabled=True + ): props = tool.Root.get_root_props() layout = self.layout prop_with_search(layout, props, "ifc_product") prop_with_search(layout, props, "ifc_class") if ifc_predefined_types: - prop_with_search(layout, props, "ifc_predefined_type") + row = prop_with_search(layout, props, "ifc_predefined_type") + row.enabled = set_predefined_types_enabled if props.ifc_predefined_type == "USERDEFINED": row = layout.row() row.prop(props, "ifc_userdefined_type") + row.enabled = set_predefined_types_enabled if not is_reassigning_class: prop_with_search(layout, props, "contexts") diff --git a/src/ifcopenshell-python/ifcopenshell/api/type/assign_type.py b/src/ifcopenshell-python/ifcopenshell/api/type/assign_type.py index 396e4ca12f..f412d4dd2e 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/type/assign_type.py +++ b/src/ifcopenshell-python/ifcopenshell/api/type/assign_type.py @@ -188,10 +188,7 @@ class Usecase: if not related_objects: return - related_objects_set = set(related_objects) - ifc2x3 = self.file.schema == "IFC2X3" - related_objects_set = set(related_objects) if ifc2x3: types = next(iter(relating_type.ObjectTypeOf), None) @@ -257,6 +254,14 @@ class Usecase: relating_type=relating_type, ) self.map_material_usages(objects_to_change, relating_type) + + # Remove PredefinedType / ObjectType if existing to forbid double typing(See #7006) + predefined_type = ifcopenshell.util.element.get_predefined_type(relating_type) + if predefined_type != "NOTDEFINED" and predefined_type is not None: + for obj in related_objects_set: + obj.ObjectType = None + if hasattr(obj, "PredefinedType"): + obj.PredefinedType = None return types def map_material_usages( diff --git a/src/ifcopenshell-python/test/api/type/test_assign_type.py b/src/ifcopenshell-python/test/api/type/test_assign_type.py index 1237b000fe..c74df8cb4a 100644 --- a/src/ifcopenshell-python/test/api/type/test_assign_type.py +++ b/src/ifcopenshell-python/test/api/type/test_assign_type.py @@ -139,6 +139,37 @@ class TestAssignType(test.bootstrap.IFC4): assert (material := ifcopenshell.util.element.get_material(element1)) assert material.id() == material_id + def test_remove_predefined_type_if_type_assignment(self): + """ + if an element has a PredefinedType, it should be removed when assigning a type. + This is because the type will have its own PredefinedType, and the element's PredefinedType + will conflict with it. (See #7006) + """ + element_type = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType") + element_type.PredefinedType = "MOVABLE" + + element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + element.PredefinedType = "USERDEFINED" + element.ObjectType = "Test" + ifcopenshell.api.type.assign_type(self.file, related_objects=[element], relating_type=element_type) + assert element.PredefinedType is None + assert element.ObjectType is None + + def test_keep_predefined_type_if_type_assignment_is_notdefined(self): + """ + if an element has a PredefinedType, it will be removed when assigning a type.(See #7006) + This behavior needs to be blocked if the PredefinedType of the typing Entity is set to "NOTDEFINED". (See #7011) + """ + element_type = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType") + element_type.PredefinedType = "NOTDEFINED" + + element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + element.PredefinedType = "USERDEFINED" + element.ObjectType = "Test" + ifcopenshell.api.type.assign_type(self.file, related_objects=[element], relating_type=element_type) + assert element.PredefinedType == "USERDEFINED" + assert element.ObjectType == "Test" + class TestAssignTypeIFC2X3(test.bootstrap.IFC2X3, TestAssignType): pass