Skip to content

Commit cb5c488

Browse files
committed
Fix #7213. IfcTester now supports checking for USERDEFINED predefined types
Can I just take this moment to complain about the overengineered complexity of how predefined types work in IFC.
1 parent b68e8de commit cb5c488

File tree

4 files changed

+102
-4
lines changed

4 files changed

+102
-4
lines changed

src/ifcopenshell-python/ifcopenshell/util/element.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,40 @@ def get_predefined_type(element: ifcopenshell.entity_instance) -> Union[str, Non
565565
return predefined_type
566566

567567

568+
def is_userdefined_type(element: ifcopenshell.entity_instance) -> bool:
569+
"""Checks if the predefined type is userdefined
570+
571+
:param element: The IFC Element entity
572+
:return: True if userdefined
573+
574+
Example:
575+
576+
.. code:: python
577+
578+
element = ifcopenshell.by_type("IfcWall")[0]
579+
is_userdefined_type = ifcopenshell.util.element.is_userdefined_type(element)
580+
"""
581+
if element_type := get_type(element):
582+
predefined_type = getattr(element_type, "PredefinedType", None)
583+
if predefined_type == "USERDEFINED":
584+
return True
585+
elif not predefined_type:
586+
predefined_type = getattr(element_type, "ElementType", ...)
587+
if predefined_type == ...:
588+
predefined_type = getattr(element_type, "ProcessType", None)
589+
if predefined_type:
590+
return True
591+
if predefined_type and predefined_type != "NOTDEFINED":
592+
return False
593+
594+
predefined_type = getattr(element, "PredefinedType", None)
595+
if predefined_type == "USERDEFINED":
596+
return True
597+
elif not predefined_type:
598+
return bool(getattr(element, "ObjectType", None))
599+
return False
600+
601+
568602
def get_type(element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]:
569603
"""Retrieves the construction type element of an element occurrence.
570604

src/ifcopenshell-python/test/util/test_element.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,65 @@ def test_getting_an_element_type_null_predefined_type(self):
369369
assert subject.get_predefined_type(element_type) == "NOTDEFINED"
370370

371371

372+
class TestIsUserdefinedTypeIFC4(test.bootstrap.IFC4):
373+
def test_getting_a_predefined_element(self):
374+
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
375+
element.PredefinedType = "PARTITIONING"
376+
assert not subject.is_userdefined_type(element)
377+
378+
def test_getting_an_element_userdefined_type(self):
379+
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
380+
element.PredefinedType = "USERDEFINED"
381+
element.ObjectType = "FOOBAR"
382+
assert subject.is_userdefined_type(element)
383+
384+
def test_getting_an_element_type_without_a_predefined_type_attribute(self):
385+
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcAnnotation")
386+
element.ObjectType = "FOOBAR"
387+
assert subject.is_userdefined_type(element)
388+
389+
def test_getting_an_inherited_predefined_type(self):
390+
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
391+
element_type = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType")
392+
ifcopenshell.api.type.assign_type(self.file, related_objects=[element], relating_type=element_type)
393+
element_type.PredefinedType = "PARTITIONING"
394+
assert not subject.is_userdefined_type(element)
395+
396+
def test_getting_an_inherited_userdefined_type_for_an_element_type(self):
397+
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
398+
element_type = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType")
399+
ifcopenshell.api.type.assign_type(self.file, related_objects=[element], relating_type=element_type)
400+
element_type.PredefinedType = "USERDEFINED"
401+
element_type.ElementType = "FOOBAR"
402+
assert subject.is_userdefined_type(element)
403+
404+
def test_getting_an_overriden_predefined_type(self):
405+
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
406+
element_type = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType")
407+
ifcopenshell.api.type.assign_type(self.file, related_objects=[element], relating_type=element_type)
408+
element_type.PredefinedType = "NOTDEFINED"
409+
element.PredefinedType = "PARTITIONING"
410+
assert not subject.is_userdefined_type(element)
411+
412+
def test_getting_an_inherited_userdefined_type_for_a_process_type(self):
413+
element = ifcopenshell.api.sequence.add_task(self.file)
414+
element_type = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcTaskType")
415+
ifcopenshell.api.type.assign_type(self.file, related_objects=[element], relating_type=element_type)
416+
element_type.PredefinedType = "USERDEFINED"
417+
element_type.ProcessType = "FOOBAR"
418+
assert subject.is_userdefined_type(element)
419+
420+
def test_getting_an_element_type_predefined_type(self):
421+
element_type = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType")
422+
element_type.PredefinedType = "PARTITIONING"
423+
assert not subject.is_userdefined_type(element_type)
424+
425+
def test_getting_an_element_type_null_predefined_type(self):
426+
element_type = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType")
427+
element_type.PredefinedType = "NOTDEFINED"
428+
assert not subject.is_userdefined_type(element_type)
429+
430+
372431
class TestGetTypeIFC4(test.bootstrap.IFC4):
373432
def test_getting_the_type_of_a_product(self):
374433
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")

src/ifctester/ifctester/facet.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,13 @@ def __call__(self, inst: ifcopenshell.entity_instance, logger: Optional[Logger]
227227
reason = {"type": "NAME", "actual": inst.is_a().upper()}
228228

229229
if is_pass and self.predefinedType:
230-
predefined_type = ifcopenshell.util.element.get_predefined_type(inst)
231-
is_pass = predefined_type == self.predefinedType
230+
if self.predefinedType == "USERDEFINED":
231+
is_pass = ifcopenshell.util.element.is_userdefined_type(inst)
232+
if not is_pass:
233+
predefined_type = ifcopenshell.util.element.get_predefined_type(inst)
234+
else:
235+
predefined_type = ifcopenshell.util.element.get_predefined_type(inst)
236+
is_pass = predefined_type == self.predefinedType
232237

233238
if not is_pass:
234239
reason = {"type": "PREDEFINEDTYPE", "actual": predefined_type}

src/ifctester/test/test_facet.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,10 @@ def test_filtering_using_an_entity_facet(self):
172172
facet = Entity(name="IFCWALL", predefinedType="USERDEFINED")
173173
ifc = ifcopenshell.file()
174174
run(
175-
"A predefined type must always specify a meaningful type, not USERDEFINED itself",
175+
"A predefined type may specify USERDEFINED itself",
176176
facet=facet,
177177
inst=ifc.createIfcWall(PredefinedType="USERDEFINED", ObjectType="WALDO"),
178-
expected=False,
178+
expected=True,
179179
)
180180

181181
ifc = ifcopenshell.file()

0 commit comments

Comments
 (0)