From f40281e97906459ed39712088d76f71b60eaa924 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 2 Nov 2023 12:28:53 +1100 Subject: [PATCH] Fix #3948. Bug where IfcTester PartOf facet didn't correctly read entity XML. --- src/ifctester/ifctester/facet.py | 42 ++++++++++++------------ src/ifctester/test/test_facet.py | 56 +++++++++++++++++--------------- 2 files changed, 50 insertions(+), 48 deletions(-) diff --git a/src/ifctester/ifctester/facet.py b/src/ifctester/ifctester/facet.py index 1f1560dbfc..f068ca4400 100644 --- a/src/ifctester/ifctester/facet.py +++ b/src/ifctester/ifctester/facet.py @@ -354,23 +354,23 @@ class Classification(Facet): class PartOf(Facet): def __init__( self, - entity="IFCWALL", + name="IFCWALL", predefinedType=None, relation=None, minOccurs=None, maxOccurs="unbounded", instructions=None, ): - self.parameters = ["entity", "predefinedType", "@relation", "@minOccurs", "@maxOccurs", "@instructions"] + self.parameters = ["name", "predefinedType", "@relation", "@minOccurs", "@maxOccurs", "@instructions"] self.applicability_templates = [ - "An element with an {relation} relationship with an {entity}", + "An element with an {relation} relationship with an {name}", "An element with an {relation} relationship", ] self.requirement_templates = [ - "An element must have an {relation} relationship with an {entity}", + "An element must have an {relation} relationship with an {name}", "An element must have an {relation} relationship", ] - super().__init__(entity, predefinedType, relation, minOccurs, maxOccurs, instructions) + super().__init__(name, predefinedType, relation, minOccurs, maxOccurs, instructions) def filter(self, ifc_file, elements): if isinstance(elements, list): @@ -380,9 +380,9 @@ class PartOf(Facet): def asdict(self): results = super().asdict() entity = {} - if "entity" in results: - entity["name"] = results["entity"] - del results["entity"] + if "name" in results: + entity["name"] = results["name"] + del results["name"] if "predefinedType" in results: entity["predefinedType"] = results["predefinedType"] del results["predefinedType"] @@ -407,7 +407,7 @@ class PartOf(Facet): parent = self.get_parent(inst) while parent: ancestors.append(parent.is_a()) - if parent.is_a().upper() == self.entity: + if parent.is_a().upper() == self.name: if self.predefinedType: if ifcopenshell.util.element.get_predefined_type(parent) == self.predefinedType: is_pass = True @@ -422,12 +422,12 @@ class PartOf(Facet): is_pass = aggregate is not None if not is_pass: reason = {"type": "NOVALUE"} - if is_pass and self.entity: + if is_pass and self.name: is_pass = False ancestors = [] while aggregate is not None: ancestors.append(aggregate.is_a()) - if aggregate.is_a().upper() == self.entity: + if aggregate.is_a().upper() == self.name: if self.predefinedType: if ifcopenshell.util.element.get_predefined_type(aggregate) == self.predefinedType: is_pass = True @@ -446,8 +446,8 @@ class PartOf(Facet): is_pass = group is not None if not is_pass: reason = {"type": "NOVALUE"} - if is_pass and self.entity: - if group.is_a().upper() != self.entity: + if is_pass and self.name: + if group.is_a().upper() != self.name: is_pass = False reason = {"type": "ENTITY", "actual": group.is_a().upper()} if self.predefinedType: @@ -460,8 +460,8 @@ class PartOf(Facet): is_pass = container is not None if not is_pass: reason = {"type": "NOVALUE"} - if is_pass and self.entity: - if container.is_a().upper() != self.entity: + if is_pass and self.name: + if container.is_a().upper() != self.name: is_pass = False reason = {"type": "ENTITY", "actual": container.is_a().upper()} if self.predefinedType: @@ -474,12 +474,12 @@ class PartOf(Facet): is_pass = nest is not None if not is_pass: reason = {"type": "NOVALUE"} - if is_pass and self.entity: + if is_pass and self.name: is_pass = False ancestors = [] while nest is not None: ancestors.append(nest.is_a()) - if nest.is_a().upper() == self.entity: + if nest.is_a().upper() == self.name: if self.predefinedType: if ifcopenshell.util.element.get_predefined_type(nest) == self.predefinedType: is_pass = True @@ -494,9 +494,9 @@ class PartOf(Facet): is_pass = building_element is not None if not is_pass: reason = {"type": "NOVALUE"} - if is_pass and self.entity: + if is_pass and self.name: is_pass = False - if building_element.is_a().upper() == self.entity: + if building_element.is_a().upper() == self.name: if self.predefinedType: if ifcopenshell.util.element.get_predefined_type(building_element) == self.predefinedType: is_pass = True @@ -509,9 +509,9 @@ class PartOf(Facet): is_pass = opening is not None if not is_pass: reason = {"type": "NOVALUE"} - if is_pass and self.entity: + if is_pass and self.name: is_pass = False - if opening.is_a().upper() == self.entity: + if opening.is_a().upper() == self.name: if self.predefinedType: if ifcopenshell.util.element.get_predefined_type(opening) == self.predefinedType: is_pass = True diff --git a/src/ifctester/test/test_facet.py b/src/ifctester/test/test_facet.py index a495fa2554..7d9307dba1 100644 --- a/src/ifctester/test/test_facet.py +++ b/src/ifctester/test/test_facet.py @@ -20,6 +20,7 @@ import uuid import ifcopenshell import ifcopenshell.api +import ifctester.facet from ifctester.facet import Entity, Attribute, Classification, Property, PartOf, Material, Restriction @@ -28,6 +29,8 @@ def set_facet(facet): def run(name, *, facet, inst, expected): + ifctester.facet.get_pset.cache_clear() + ifctester.facet.get_psets.cache_clear() assert bool(facet(inst)) is expected @@ -76,7 +79,6 @@ class TestEntity: expected=False, ) - # TODO But in that case why are the enumerations for things like partOf using the IFC capitalisation? facet = Entity(name="IfcWall") ifc = ifcopenshell.file() run( @@ -1406,7 +1408,7 @@ class TestPartOf: facet = PartOf() assert facet.asdict() == {"entity": {"name": {"simpleValue": "IFCWALL"}}, "@maxOccurs": "unbounded" } facet = PartOf( - entity="IFCGROUP", + name="IFCGROUP", predefinedType="predefinedType", relation="IFCRELASSIGNSTOGROUP", minOccurs="0", @@ -1431,16 +1433,16 @@ class TestPartOf: element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly") subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") - facet = PartOf(entity="IFCELEMENTASSEMBLY", relation="IFCRELAGGREGATES") + facet = PartOf(name="IFCELEMENTASSEMBLY", relation="IFCRELAGGREGATES") run("A non aggregated element fails an aggregate relationship", facet=facet, inst=subelement, expected=False) ifcopenshell.api.run("aggregate.assign_object", ifc, product=subelement, relating_object=element) run("The aggregated whole fails an aggregate relationship", facet=facet, inst=element, expected=False) run("The aggregated part passes an aggregate relationship", facet=facet, inst=subelement, expected=True) run("A required facet checks all parameters as normal", facet=facet, inst=subelement, expected=True) - facet = PartOf(entity="IFCELEMENTASSEMBLY", relation="IFCRELAGGREGATES", minOccurs=0, maxOccurs=0) + facet = PartOf(name="IFCELEMENTASSEMBLY", relation="IFCRELAGGREGATES", minOccurs=0, maxOccurs=0) run("A prohibited facet returns the opposite of a required facet", facet=facet, inst=subelement, expected=False) - facet = PartOf(entity="IFCELEMENTASSEMBLY", relation="IFCRELAGGREGATES", minOccurs=0) + facet = PartOf(name="IFCELEMENTASSEMBLY", relation="IFCRELAGGREGATES", minOccurs=0) run("An optional facet always passes regardless of outcome 1/2", facet=facet, inst=element, expected=True) run("An optional facet always passes regardless of outcome 2/2", facet=facet, inst=subelement, expected=True) @@ -1448,17 +1450,17 @@ class TestPartOf: element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcSlab") subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcBeam") ifcopenshell.api.run("aggregate.assign_object", ifc, product=subelement, relating_object=element) - facet = PartOf(entity="IFCSLAB", relation="IFCRELAGGREGATES") + facet = PartOf(name="IFCSLAB", relation="IFCRELAGGREGATES") run("An aggregate may specify the entity of the whole 1/2", facet=facet, inst=subelement, expected=True) - facet = PartOf(entity="IFCWALL", relation="IFCRELAGGREGATES") + facet = PartOf(name="IFCWALL", relation="IFCRELAGGREGATES") run("An aggregate may specify the entity of the whole 2/2", facet=facet, inst=subelement, expected=False) element.PredefinedType = "BASESLAB" - facet = PartOf(entity="IFCSLAB", predefinedType="BASESLAB", relation="IFCRELAGGREGATES") + facet = PartOf(name="IFCSLAB", predefinedType="BASESLAB", relation="IFCRELAGGREGATES") run( "An aggregate may specify the predefined type of the whole 1/2", facet=facet, inst=subelement, expected=True ) - facet = PartOf(entity="IFCSLAB", predefinedType="SLABRADOR", relation="IFCRELAGGREGATES") + facet = PartOf(name="IFCSLAB", predefinedType="SLABRADOR", relation="IFCRELAGGREGATES") run( "An aggregate may specify the predefined type of the whole 2/2", facet=facet, @@ -1472,13 +1474,13 @@ class TestPartOf: subsubelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcBeam") ifcopenshell.api.run("aggregate.assign_object", ifc, product=subelement, relating_object=element) ifcopenshell.api.run("aggregate.assign_object", ifc, product=subsubelement, relating_object=subelement) - facet = PartOf(entity="IFCELEMENTASSEMBLY", relation="IFCRELAGGREGATES") + facet = PartOf(name="IFCELEMENTASSEMBLY", relation="IFCRELAGGREGATES") run("An aggregate entity may pass any ancestral whole passes", facet=facet, inst=subsubelement, expected=True) ifc = ifcopenshell.file() element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly") group = ifcopenshell.api.run("group.add_group", ifc) - facet = PartOf(entity="IFCGROUP", relation="IFCRELASSIGNSTOGROUP") + facet = PartOf(name="IFCGROUP", relation="IFCRELASSIGNSTOGROUP") run("A non grouped element fails a group relationship", facet=facet, inst=element, expected=False) ifcopenshell.api.run("group.assign_group", ifc, products=[element], group=group) run("A grouped element passes a group relationship", facet=facet, inst=element, expected=True) @@ -1486,22 +1488,22 @@ class TestPartOf: ifc = ifcopenshell.file() element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly") group = ifc.createIfcInventory() - facet = PartOf(entity="IFCGROUP", relation="IFCRELASSIGNSTOGROUP") + facet = PartOf(name="IFCGROUP", relation="IFCRELASSIGNSTOGROUP") ifcopenshell.api.run("group.assign_group", ifc, products=[element], group=group) run("A group entity must match exactly 1/2", facet=facet, inst=element, expected=False) - facet = PartOf(entity="IFCINVENTORY", relation="IFCRELASSIGNSTOGROUP") + facet = PartOf(name="IFCINVENTORY", relation="IFCRELASSIGNSTOGROUP") run("A group entity must match exactly 2/2", facet=facet, inst=element, expected=True) group.ObjectType = "BUNNY" - facet = PartOf(entity="IFCINVENTORY", predefinedType="BUNNARY", relation="IFCRELASSIGNSTOGROUP") + facet = PartOf(name="IFCINVENTORY", predefinedType="BUNNARY", relation="IFCRELASSIGNSTOGROUP") run("A group predefined type must match exactly 2/2", facet=facet, inst=element, expected=False) - facet = PartOf(entity="IFCINVENTORY", predefinedType="BUNNY", relation="IFCRELASSIGNSTOGROUP") + facet = PartOf(name="IFCINVENTORY", predefinedType="BUNNY", relation="IFCRELASSIGNSTOGROUP") run("A group predefined type must match exactly 2/2", facet=facet, inst=element, expected=True) ifc = ifcopenshell.file() element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly") container = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcSpace") - facet = PartOf(entity="IFCSPACE", relation="IFCRELCONTAINEDINSPATIALSTRUCTURE") + facet = PartOf(name="IFCSPACE", relation="IFCRELCONTAINEDINSPATIALSTRUCTURE") run("Any contained element passes a containment relationship 1/2", facet=facet, inst=element, expected=False) ifcopenshell.api.run("spatial.assign_container", ifc, product=element, relating_structure=container) run("Any contained element passes a containment relationship 2/2", facet=facet, inst=element, expected=True) @@ -1511,15 +1513,15 @@ class TestPartOf: element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly") container = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcSpace") ifcopenshell.api.run("spatial.assign_container", ifc, product=element, relating_structure=container) - facet = PartOf(relation="IFCRELCONTAINEDINSPATIALSTRUCTURE", entity="IFCSITE") + facet = PartOf(relation="IFCRELCONTAINEDINSPATIALSTRUCTURE", name="IFCSITE") run("The container entity must match exactly 1/2", facet=facet, inst=element, expected=False) - facet = PartOf(relation="IFCRELCONTAINEDINSPATIALSTRUCTURE", entity="IFCSPACE") + facet = PartOf(relation="IFCRELCONTAINEDINSPATIALSTRUCTURE", name="IFCSPACE") run("The container entity must match exactly 2/2", facet=facet, inst=element, expected=True) container.ObjectType = "BURROW" - facet = PartOf(relation="IFCRELCONTAINEDINSPATIALSTRUCTURE", entity="IFCSPACE", predefinedType="WARREN") + facet = PartOf(relation="IFCRELCONTAINEDINSPATIALSTRUCTURE", name="IFCSPACE", predefinedType="WARREN") run("The container predefined type must match exactly 1/2", facet=facet, inst=element, expected=False) - facet = PartOf(relation="IFCRELCONTAINEDINSPATIALSTRUCTURE", entity="IFCSPACE", predefinedType="BURROW") + facet = PartOf(relation="IFCRELCONTAINEDINSPATIALSTRUCTURE", name="IFCSPACE", predefinedType="BURROW") run("The container predefined type must match exactly 2/2", facet=facet, inst=element, expected=True) ifc = ifcopenshell.file() @@ -1528,14 +1530,14 @@ class TestPartOf: ifcopenshell.api.run("aggregate.assign_object", ifc, product=subelement, relating_object=element) container = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcSpace") ifcopenshell.api.run("spatial.assign_container", ifc, product=element, relating_structure=container) - facet = PartOf(relation="IFCRELCONTAINEDINSPATIALSTRUCTURE", entity="IFCSPACE") + facet = PartOf(relation="IFCRELCONTAINEDINSPATIALSTRUCTURE", name="IFCSPACE") run("The container may be indirect", facet=facet, inst=subelement, expected=True) ifc = ifcopenshell.file() element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcFurniture") subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcDiscreteAccessory") ifcopenshell.api.run("nest.assign_object", ifc, related_object=subelement, relating_object=element) - facet = PartOf(entity="IFCFURNITURE", relation="IFCRELNESTS") + facet = PartOf(name="IFCFURNITURE", relation="IFCRELNESTS") run("Any nested part passes a nest relationship", facet=facet, inst=subelement, expected=True) run("Any nested whole fails a nest relationship", facet=facet, inst=element, expected=False) @@ -1543,16 +1545,16 @@ class TestPartOf: element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcFurniture") subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcDiscreteAccessory") ifcopenshell.api.run("nest.assign_object", ifc, related_object=subelement, relating_object=element) - facet = PartOf(relation="IFCRELNESTS", entity="IFCBEAM") + facet = PartOf(relation="IFCRELNESTS", name="IFCBEAM") run("The nest entity must match exactly 1/2", facet=facet, inst=subelement, expected=False) - facet = PartOf(relation="IFCRELNESTS", entity="IFCFURNITURE") + facet = PartOf(relation="IFCRELNESTS", name="IFCFURNITURE") run("The nest entity must match exactly 2/2", facet=facet, inst=subelement, expected=True) element.PredefinedType = "USERDEFINED" element.ObjectType = "WATERBOTTLE" - facet = PartOf(relation="IFCRELNESTS", entity="IFCFURNITURE", predefinedType="LITTERBOX") + facet = PartOf(relation="IFCRELNESTS", name="IFCFURNITURE", predefinedType="LITTERBOX") run("The nest predefined type must match exactly 1/2", facet=facet, inst=subelement, expected=False) - facet = PartOf(relation="IFCRELNESTS", entity="IFCFURNITURE", predefinedType="WATERBOTTLE") + facet = PartOf(relation="IFCRELNESTS", name="IFCFURNITURE", predefinedType="WATERBOTTLE") run("The nest predefined type must match exactly 2/2", facet=facet, inst=subelement, expected=True) ifc = ifcopenshell.file() @@ -1561,7 +1563,7 @@ class TestPartOf: subsubelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcMechanicalFastener") ifcopenshell.api.run("nest.assign_object", ifc, related_object=subelement, relating_object=element) ifcopenshell.api.run("nest.assign_object", ifc, related_object=subsubelement, relating_object=subelement) - facet = PartOf(relation="IFCRELNESTS", entity="IFCFURNITURE") + facet = PartOf(relation="IFCRELNESTS", name="IFCFURNITURE") run("Nesting may be indirect", facet=facet, inst=subsubelement, expected=True)