diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index 48d65ae7ae..22665772da 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -565,6 +565,40 @@ def get_predefined_type(element: ifcopenshell.entity_instance) -> Union[str, Non return predefined_type +def is_userdefined_type(element: ifcopenshell.entity_instance) -> bool: + """Checks if the predefined type is userdefined + + :param element: The IFC Element entity + :return: True if userdefined + + Example: + + .. code:: python + + element = ifcopenshell.by_type("IfcWall")[0] + is_userdefined_type = ifcopenshell.util.element.is_userdefined_type(element) + """ + if element_type := get_type(element): + predefined_type = getattr(element_type, "PredefinedType", None) + if predefined_type == "USERDEFINED": + return True + elif not predefined_type: + predefined_type = getattr(element_type, "ElementType", ...) + if predefined_type == ...: + predefined_type = getattr(element_type, "ProcessType", None) + if predefined_type: + return True + if predefined_type and predefined_type != "NOTDEFINED": + return False + + predefined_type = getattr(element, "PredefinedType", None) + if predefined_type == "USERDEFINED": + return True + elif not predefined_type: + return bool(getattr(element, "ObjectType", None)) + return False + + def get_type(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: """Retrieves the construction type element of an element occurrence. diff --git a/src/ifcopenshell-python/test/util/test_element.py b/src/ifcopenshell-python/test/util/test_element.py index 1a1e28d58b..0113e68d2f 100644 --- a/src/ifcopenshell-python/test/util/test_element.py +++ b/src/ifcopenshell-python/test/util/test_element.py @@ -369,6 +369,65 @@ class TestGetPredefinedTypeIFC4(test.bootstrap.IFC4): assert subject.get_predefined_type(element_type) == "NOTDEFINED" +class TestIsUserdefinedTypeIFC4(test.bootstrap.IFC4): + def test_getting_a_predefined_element(self): + element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + element.PredefinedType = "PARTITIONING" + assert not subject.is_userdefined_type(element) + + def test_getting_an_element_userdefined_type(self): + element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + element.PredefinedType = "USERDEFINED" + element.ObjectType = "FOOBAR" + assert subject.is_userdefined_type(element) + + def test_getting_an_element_type_without_a_predefined_type_attribute(self): + element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcAnnotation") + element.ObjectType = "FOOBAR" + assert subject.is_userdefined_type(element) + + def test_getting_an_inherited_predefined_type(self): + element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + element_type = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType") + ifcopenshell.api.type.assign_type(self.file, related_objects=[element], relating_type=element_type) + element_type.PredefinedType = "PARTITIONING" + assert not subject.is_userdefined_type(element) + + def test_getting_an_inherited_userdefined_type_for_an_element_type(self): + element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + element_type = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType") + ifcopenshell.api.type.assign_type(self.file, related_objects=[element], relating_type=element_type) + element_type.PredefinedType = "USERDEFINED" + element_type.ElementType = "FOOBAR" + assert subject.is_userdefined_type(element) + + def test_getting_an_overriden_predefined_type(self): + element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") + element_type = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType") + ifcopenshell.api.type.assign_type(self.file, related_objects=[element], relating_type=element_type) + element_type.PredefinedType = "NOTDEFINED" + element.PredefinedType = "PARTITIONING" + assert not subject.is_userdefined_type(element) + + def test_getting_an_inherited_userdefined_type_for_a_process_type(self): + element = ifcopenshell.api.sequence.add_task(self.file) + element_type = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcTaskType") + ifcopenshell.api.type.assign_type(self.file, related_objects=[element], relating_type=element_type) + element_type.PredefinedType = "USERDEFINED" + element_type.ProcessType = "FOOBAR" + assert subject.is_userdefined_type(element) + + def test_getting_an_element_type_predefined_type(self): + element_type = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType") + element_type.PredefinedType = "PARTITIONING" + assert not subject.is_userdefined_type(element_type) + + def test_getting_an_element_type_null_predefined_type(self): + element_type = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType") + element_type.PredefinedType = "NOTDEFINED" + assert not subject.is_userdefined_type(element_type) + + class TestGetTypeIFC4(test.bootstrap.IFC4): def test_getting_the_type_of_a_product(self): element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") diff --git a/src/ifctester/ifctester/facet.py b/src/ifctester/ifctester/facet.py index 97c24c59a1..fa8ff43fb3 100644 --- a/src/ifctester/ifctester/facet.py +++ b/src/ifctester/ifctester/facet.py @@ -227,8 +227,13 @@ class Entity(Facet): reason = {"type": "NAME", "actual": inst.is_a().upper()} if is_pass and self.predefinedType: - predefined_type = ifcopenshell.util.element.get_predefined_type(inst) - is_pass = predefined_type == self.predefinedType + if self.predefinedType == "USERDEFINED": + is_pass = ifcopenshell.util.element.is_userdefined_type(inst) + if not is_pass: + predefined_type = ifcopenshell.util.element.get_predefined_type(inst) + else: + predefined_type = ifcopenshell.util.element.get_predefined_type(inst) + is_pass = predefined_type == self.predefinedType if not is_pass: reason = {"type": "PREDEFINEDTYPE", "actual": predefined_type} diff --git a/src/ifctester/test/test_facet.py b/src/ifctester/test/test_facet.py index 10d94b2f03..045b796330 100644 --- a/src/ifctester/test/test_facet.py +++ b/src/ifctester/test/test_facet.py @@ -172,10 +172,10 @@ class TestEntity: facet = Entity(name="IFCWALL", predefinedType="USERDEFINED") ifc = ifcopenshell.file() run( - "A predefined type must always specify a meaningful type, not USERDEFINED itself", + "A predefined type may specify USERDEFINED itself", facet=facet, inst=ifc.createIfcWall(PredefinedType="USERDEFINED", ObjectType="WALDO"), - expected=False, + expected=True, ) ifc = ifcopenshell.file()