diff --git a/src/ifcopenshell-python/ifcopenshell/ids.py b/src/ifcopenshell-python/ifcopenshell/ids.py index 22818a2f29..2e39c61fef 100644 --- a/src/ifcopenshell-python/ifcopenshell/ids.py +++ b/src/ifcopenshell-python/ifcopenshell/ids.py @@ -731,13 +731,9 @@ class partOf(facet): parameters = ["entity"] message = "relation as part of %(entity)s" - # TODO temp default - entity = "IfcElementAssembly" @staticmethod - # TODO should not assume IfcElementAssembly - # def create(entity=None): - def create(entity="IfcElementAssembly"): + def create(entity="IfcSystem"): """Create a partOf facet that can be added to applicability or requirements of IDS specification. :param entity: Entity that should contain this object. Could be alphanumeric or restriction object, defaults to None @@ -756,13 +752,9 @@ class partOf(facet): :return: Xmlschema compliant dictionary. :rtype: dict """ - fac_dict = { - "@entity": parameter_asdict(self.entity), - # "instructions": "SAMPLE_INSTRUCTIONS", - } - return fac_dict + return {"@entity": parameter_asdict(self.entity)} - def __call__(self, inst, logger): + def __call__(self, inst, logger=None): """Validate an ifc instance against that partOf facet. :param inst: IFC entity element @@ -772,48 +764,21 @@ class partOf(facet): :return: result of the validation as bool and message :rtype: facet_evaluation(bool, str) """ - - # TODO handle partOf facet - - # instance_classiciations = inst.HasAssociations - # if ifcopenshell.util.element.get_type(inst): - # type_classifications = ifcopenshell.util.element.get_type(inst).HasAssociations - # else: - # type_classifications = () - - # if self.location == "instance" and instance_classiciations: - # associations = instance_classiciations - # elif self.location == "type" and type_classifications: - # associations = type_classifications - # elif self.location == "any" and (instance_classiciations or type_classifications): - # associations = instance_classiciations + type_classifications - # else: - # associations = () - - refs = [] - # for association in associations: - # if association.is_a("IfcRelAssociatesClassification"): - # cref = association.RelatingClassification - # if hasattr(cref, "ItemReference"): # IFC2x3 - # refs.append((cref.ReferencedSource.Name, cref.ItemReference)) - # elif hasattr(cref, "Identification"): # IFC4 - # refs.append((cref.ReferencedSource.Name, cref.Identification)) - - # self.location_msg = location[self.location] - - if refs: - pass - # return facet_evaluation( - # (self.system, self.value) in refs, - # self.message - # % { - # "system": refs[0][0], - # "value": "'" + refs[0][1] + "'", - # "location": self.location_msg, - # }, # what if not first item of refs? - # ) + if self.entity == "IfcElementAssembly": + is_pass = False + aggregate = ifcopenshell.util.element.get_aggregate(inst) + while aggregate is not None: + if aggregate.is_a() == "IfcElementAssembly": + is_pass = True + break + aggregate = ifcopenshell.util.element.get_aggregate(aggregate) else: - return facet_evaluation(False, "is not a part of %s" % self.node["@entity"]) + is_pass = False + for rel in getattr(inst, "HasAssignments", []) or []: + if rel.is_a("IfcRelAssignsToGroup") and rel.RelatingGroup.is_a(self.entity): + is_pass = True + + return facet_evaluation(is_pass, "is not a part of") class property(facet): diff --git a/src/ifcopenshell-python/test/test_ids.py b/src/ifcopenshell-python/test/test_ids.py index adb769a6a3..684bb65a58 100644 --- a/src/ifcopenshell-python/test/test_ids.py +++ b/src/ifcopenshell-python/test/test_ids.py @@ -961,6 +961,70 @@ class TestIdsAuthoring(unittest.TestCase): assert bool(facet(element)) is True assert bool(facet(element_type)) is False + def test_creating_a_partof_facet(self): + facet = ids.partOf.create() + assert facet.asdict() == {"@entity": {"simpleValue": "IfcSystem"}} + facet = ids.partOf.create(entity="IfcGroup") + assert facet.asdict() == {"@entity": {"simpleValue": "IfcGroup"}} + + def test_filtering_using_a_partof_facet(self): + ifc = ifcopenshell.file() + + # An IfcElementAssembly entity only passes those who are part of an assembly + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly") + subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + ifcopenshell.api.run("aggregate.assign_object", ifc, product=subelement, relating_object=element) + facet = ids.partOf.create(entity="IfcElementAssembly") + assert bool(facet(element)) is False + assert bool(facet(subelement)) is True + + # An IfcElementAssembly strictly checks that the whole is an IfcElementAssembly class + 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 = ids.partOf.create(entity="IfcElementAssembly") + assert bool(facet(subelement)) is False + + # A nested subelement still passes so long as one of its parents is an IfcElementAssembly + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly") + subelement = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcSlab") + 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 = ids.partOf.create(entity="IfcElementAssembly") + assert bool(facet(subsubelement)) is True + + # An IfcGroup only checks that a group is assigned without any other logic + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly") + group = ifcopenshell.api.run("group.add_group", ifc) + facet = ids.partOf.create(entity="IfcGroup") + assert bool(facet(element)) is False + ifcopenshell.api.run("group.assign_group", ifc, product=element, group=group) + assert bool(facet(element)) is True + + # An IfcGroup can be passed by subtypes + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly") + group = ifc.createIfcInventory() + facet = ids.partOf.create(entity="IfcGroup") + ifcopenshell.api.run("group.assign_group", ifc, product=element, group=group) + assert bool(facet(element)) is True + + # An IfcSystem only checks that a system is assigned without any other logic + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly") + system = ifcopenshell.api.run("system.add_system", ifc) + facet = ids.partOf.create(entity="IfcSystem") + assert bool(facet(element)) is False + ifcopenshell.api.run("system.assign_system", ifc, product=element, system=system) + assert bool(facet(element)) is True + + # An IfcSystem allows subtypes + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcElementAssembly") + system = ifcopenshell.api.run("system.add_system", ifc, ifc_class="IfcDistributionSystem") + ifcopenshell.api.run("system.assign_system", ifc, product=element, system=system) + facet = ids.partOf.create(entity="IfcSystem") + assert bool(facet(element)) is True + + """ Creating IDS with restrictions """ def test_create_restrictions_enumeration(self):