diff --git a/src/ifctester/ifctester/facet.py b/src/ifctester/ifctester/facet.py index 28290f5dee..a47e7886e1 100644 --- a/src/ifctester/ifctester/facet.py +++ b/src/ifctester/ifctester/facet.py @@ -302,8 +302,8 @@ class Classification(Facet): class PartOf(Facet): - def __init__(self, entity=None, relation="IfcRelAggregates", minOccurs=None, maxOccurs=None, instructions=None): - self.parameters = ["entity", "@relation", "@minOccurs", "@maxOccurs", "@instructions"] + def __init__(self, entity=None, predefinedType=None, relation="IfcRelAggregates", minOccurs=None, maxOccurs=None, instructions=None): + self.parameters = ["entity", "predefinedType", "@relation", "@minOccurs", "@maxOccurs", "@instructions"] self.applicability_templates = [ "An element with an {relation} relationship with an {entity}", "An element with an {relation} relationship", @@ -312,7 +312,26 @@ class PartOf(Facet): "An element must have an {relation} relationship with an {entity}", "An element must have an {relation} relationship", ] - super().__init__(entity, relation, minOccurs, maxOccurs, instructions) + super().__init__(entity, predefinedType, relation, minOccurs, maxOccurs, instructions) + + def asdict(self): + results = super().asdict() + entity = {} + if "entity" in results: + entity["name"] = results["entity"] + del results["entity"] + if "predefinedType" in results: + entity["predefinedType"] = results["predefinedType"] + del results["predefinedType"] + if entity: + results["entity"] = entity + return results + + def parse(self, xml): + if "entity" in xml: + super().parse(xml["entity"]) + del xml["entity"] + super().parse(xml) def __call__(self, inst, logger=None): if self.minOccurs == 0 and self.maxOccurs != 0: @@ -330,7 +349,11 @@ class PartOf(Facet): while aggregate is not None: ancestors.append(aggregate.is_a()) if aggregate.is_a().upper() == self.entity: - is_pass = True + if self.predefinedType: + if ifcopenshell.util.element.get_predefined_type(aggregate) == self.predefinedType: + is_pass = True + else: + is_pass = True break aggregate = ifcopenshell.util.element.get_aggregate(aggregate) if not is_pass: @@ -348,6 +371,11 @@ class PartOf(Facet): if group.is_a().upper() != self.entity: is_pass = False reason = {"type": "ENTITY", "actual": group.is_a().upper()} + if self.predefinedType: + predefined_type = ifcopenshell.util.element.get_predefined_type(group) + if predefined_type != self.predefinedType: + is_pass = False + reason = {"type": "PREDEFINEDTYPE", "actual": predefined_type} elif self.relation == "IfcRelContainedInSpatialStructure": container = ifcopenshell.util.element.get_container(inst) is_pass = container is not None @@ -357,6 +385,11 @@ class PartOf(Facet): if container.is_a().upper() != self.entity: is_pass = False reason = {"type": "ENTITY", "actual": container.is_a().upper()} + if self.predefinedType: + predefined_type = ifcopenshell.util.element.get_predefined_type(container) + if predefined_type != self.predefinedType: + is_pass = False + reason = {"type": "PREDEFINEDTYPE", "actual": predefined_type} elif self.relation == "IfcRelNests": nest = self.get_nested_whole(inst) is_pass = nest is not None @@ -368,7 +401,11 @@ class PartOf(Facet): while nest is not None: ancestors.append(nest.is_a()) if nest.is_a().upper() == self.entity: - is_pass = True + if self.predefinedType: + if ifcopenshell.util.element.get_predefined_type(nest) == self.predefinedType: + is_pass = True + else: + is_pass = True break nest = self.get_nested_whole(nest) if not is_pass: diff --git a/src/ifctester/ifctester/ids.xsd b/src/ifctester/ifctester/ids.xsd index 9a2bc9c39e..3aa6bbe4e2 100644 --- a/src/ifctester/ifctester/ids.xsd +++ b/src/ifctester/ifctester/ids.xsd @@ -1,6 +1,6 @@ - - - + + + @@ -58,7 +58,7 @@ - + @@ -198,7 +198,7 @@ - + @@ -248,4 +248,4 @@ - + \ No newline at end of file diff --git a/src/ifctester/test/test_facet.py b/src/ifctester/test/test_facet.py index d80bba3feb..555d772b12 100644 --- a/src/ifctester/test/test_facet.py +++ b/src/ifctester/test/test_facet.py @@ -1404,13 +1404,17 @@ class TestPartOf: assert facet.asdict() == {"@relation": "IfcRelAggregates"} facet = PartOf( entity="IfcGroup", + predefinedType="predefinedType", relation="IfcRelAssignsToGroup", minOccurs="0", maxOccurs="unbounded", instructions="instructions", ) assert facet.asdict() == { - "entity": {"simpleValue": "IfcGroup"}, + "entity": { + "name": {"simpleValue": "IfcGroup"}, + "predefinedType": {"simpleValue": "predefinedType"}, + }, "@relation": "IfcRelAssignsToGroup", "@minOccurs": "0", "@maxOccurs": "unbounded", @@ -1446,6 +1450,12 @@ class TestPartOf: facet = PartOf(entity="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") + 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") + run("An aggregate may specify the predefined type of the whole 2/2", facet=facet, inst=subelement, expected=False) + ifc = ifcopenshell.file() element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly") subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcSlab") @@ -1472,6 +1482,12 @@ class TestPartOf: facet = PartOf(entity="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") + run("A group predefined type must match exactly 2/2", facet=facet, inst=element, expected=False) + facet = PartOf(entity="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") @@ -1490,6 +1506,12 @@ class TestPartOf: facet = PartOf(relation="IfcRelContainedInSpatialStructure", entity="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") + run("The container predefined type must match exactly 1/2", facet=facet, inst=element, expected=False) + facet = PartOf(relation="IfcRelContainedInSpatialStructure", entity="IFCSPACE", predefinedType="BURROW") + run("The container 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="IfcSlab") subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcBeam") @@ -1516,6 +1538,12 @@ class TestPartOf: facet = PartOf(relation="IfcRelNests", entity="IFCFURNITURE") run("The nest entity must match exactly 2/2", facet=facet, inst=subelement, expected=True) + element.ObjectType = "WATERBOTTLE" + facet = PartOf(relation="IfcRelNests", entity="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") + run("The nest predefined type must match exactly 2/2", 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")