diff --git a/src/ifcopenshell-python/ifcopenshell/ids.py b/src/ifcopenshell-python/ifcopenshell/ids.py index 32bdb65d08..3f7a9b281a 100644 --- a/src/ifcopenshell-python/ifcopenshell/ids.py +++ b/src/ifcopenshell-python/ifcopenshell/ids.py @@ -873,7 +873,7 @@ class property(facet): # TODO '@href': 'http://identifier.buildingsmart.org/uri/buildingsmart/ifc-4.3/prop/FireRating', #https://identifier.buildingsmart.org/uri/something return results - def __call__(self, inst, logger): + def __call__(self, inst, logger=None): """Validate an ifc instance against that property facet. :param inst: IFC entity element @@ -883,46 +883,41 @@ class property(facet): :return: result of the validation as bool and message :rtype: facet_evaluation(bool, str) """ - - # self.location = self.node["@location"] - - # TODO sometimes AttributeError: 'str' object has no attribute 'wrappedValue' - try: - instance_props = ifcopenshell.util.element.get_psets(inst) - except AttributeError: - instance_props = {} - - if ifcopenshell.util.element.get_type(inst): - # TODO sometimes AttributeError: 'str' object has no attribute 'wrappedValue' - try: - type_props = ifcopenshell.util.element.get_psets(ifcopenshell.util.element.get_type(inst)) - except AttributeError: - type_props = {} - else: - type_props = {} - if self.location == "instance": - props = instance_props - elif self.location == "type" and type_props: - props = type_props - elif self.location == "any" and (instance_props or type_props): - props = {**instance_props, **type_props} + all_psets = ifcopenshell.util.element.get_psets(inst, should_inherit=False) + elif self.location == "type": + element_type = ifcopenshell.util.element.get_type(inst) + all_psets = ifcopenshell.util.element.get_psets(element_type, should_inherit=False) + elif self.location == "any": + all_psets = ifcopenshell.util.element.get_psets(inst) + + if isinstance(self.propertySet, str): + pset = all_psets.get(self.propertySet, None) + psets = {self.propertySet: pset} if pset else {} else: + psets = {k: v for k, v in all_psets.items() if k == self.propertySet} + + is_pass = bool(psets) + + if is_pass: props = {} + for pset_name, pset_props in psets.items(): + props[pset_name] = {} + if isinstance(self.name, str): + prop = pset_props.get(self.name) + if prop: + props[pset_name][self.name] = prop + else: + props[pset_name] = {k: v for k, v in pset_props.items() if k == self.name} - pset = props.get(self.propertySet) - val = pset.get(self.name) if pset else None + if not bool(props[pset_name]): + is_pass = False + break - self.location_msg = location[self.location] - di = {"name": self.name, "propertySet": self.propertySet, "value": "'%s'" % val, "location": self.location_msg} - - if val is not None: - msg = self.message % di - else: - if pset: - msg = "does not have %(location)sproperty '%(name)s' in a set '%(propertySet)s'" % di - else: - msg = "does not have %(location)sset '%(propertySet)s'" % di + if self.value: + if any([v != self.value for v in props[pset_name].values()]): + is_pass = False + break # TODO implement data type comparison # xs:string @@ -935,7 +930,7 @@ class property(facet): # xs:dateTime YYYY-MM-DDThh:mm:ss # xs:duration PnYnMnDTnHnMnS - return facet_evaluation(val == self.value, msg) + return facet_evaluation(is_pass, "todo") class material(facet): diff --git a/src/ifcopenshell-python/test/test_ids.py b/src/ifcopenshell-python/test/test_ids.py index 7e0cfe7671..15fef5208d 100644 --- a/src/ifcopenshell-python/test/test_ids.py +++ b/src/ifcopenshell-python/test/test_ids.py @@ -621,6 +621,116 @@ class TestIdsAuthoring(unittest.TestCase): "@instructions": "instructions", } + def test_filtering_using_a_property_facet(self): + ifc = ifcopenshell.file() + + # A name check by itself only checks that a property is non-null and non empty string + # The logic is that unfortunately most BIM users cannot differentiate between the two. + facet = ids.property.create(propertySet="Foo_Bar", name="Foo") + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + assert bool(facet(element)) is False + pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo_Bar") + assert bool(facet(element)) is False + ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": None}) + assert bool(facet(element)) is False + ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Bar"}) + assert bool(facet(element)) is True + + # A simple value checks an exact case-sensitive match + facet = ids.property.create(propertySet="Foo_Bar", name="Foo", value="Bar") + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo_Bar") + ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Bar"}) + assert bool(facet(element)) is True + ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Baz"}) + assert bool(facet(element)) is False + + # Simple values only check string matches + facet = ids.property.create(propertySet="Foo_Bar", name="Foo", value="1") + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo_Bar") + ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "1"}) + assert bool(facet(element)) is True + ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": ifc.createIfcInteger(1)}) + assert bool(facet(element)) is False + + # Restrictions are supported for property sets. If multiple are matched, all must satisfy requirements. + restriction = ids.restriction.create(options="Foo_.*", type="pattern", base="string") + facet = ids.property.create(propertySet=restriction, name="Foo") + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo_Bar") + ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Bar"}) + assert bool(facet(element)) is True + pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo_Baz") + assert bool(facet(element)) is False + ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Bar"}) + assert bool(facet(element)) is True + + # Restrictions are supported for names. If multiple are matched, all must satisfy requirements. + restriction = ids.restriction.create(options="Foo.*", type="pattern", base="string") + facet = ids.property.create(propertySet="Foo_Bar", name=restriction, value="x") + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo_Bar") + ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foobar": "x"}) + assert bool(facet(element)) is True + ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foobar": "x", "Foobaz": "x"}) + assert bool(facet(element)) is True + ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foobar": "x", "Foobaz": "y"}) + assert bool(facet(element)) is False + + # Restrictions are supported for values. If multiple are matched, all must satisfy requirements. + restriction1 = ids.restriction.create(options="Foo.*", type="pattern", base="string") + restriction2 = ids.restriction.create(options=["x", "y"], type="enumeration", base="string") + facet = ids.property.create(propertySet="Foo_Bar", name=restriction1, value=restriction2) + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + pset = ifcopenshell.api.run("pset.add_pset", ifc, product=element, name="Foo_Bar") + ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foobar": "x", "Foobaz": "y"}) + assert bool(facet(element)) is True + ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foobar": "x", "Foobaz": "z"}) + assert bool(facet(element)) is False + + # Location instance only checks on the instance, even if the instance is a type. Yes, weird, I know. + wall = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + wall_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType") + ifcopenshell.api.run("type.assign_type", ifc, related_object=wall, relating_type=wall_type) + pset = ifcopenshell.api.run("pset.add_pset", ifc, product=wall_type, name="Foo_Bar") + ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Bar"}) + facet = ids.property.create(propertySet="Foo_Bar", name="Foo", location="instance") + assert bool(facet(wall)) is False + assert bool(facet(wall_type)) is True + + # Location type only checks the type + wall = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + wall_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType") + ifcopenshell.api.run("type.assign_type", ifc, related_object=wall, relating_type=wall_type) + pset = ifcopenshell.api.run("pset.add_pset", ifc, product=wall_type, name="Foo_Bar") + ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Bar"}) + facet = ids.property.create(propertySet="Foo_Bar", name="Foo", location="type") + assert bool(facet(wall)) is True + assert bool(facet(wall_type)) is True + + # Location any checks inherited properties from the type + wall = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + wall_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType") + ifcopenshell.api.run("type.assign_type", ifc, related_object=wall, relating_type=wall_type) + pset = ifcopenshell.api.run("pset.add_pset", ifc, product=wall_type, name="Foo_Bar") + ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Bar"}) + facet = ids.property.create(propertySet="Foo_Bar", name="Foo", location="any") + assert bool(facet(wall)) is True + assert bool(facet(wall_type)) is True + + # Location any checks overriden properties from the occurrence + wall = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + wall_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType") + ifcopenshell.api.run("type.assign_type", ifc, related_object=wall, relating_type=wall_type) + pset = ifcopenshell.api.run("pset.add_pset", ifc, product=wall_type, name="Foo_Bar") + ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Baz"}) + pset = ifcopenshell.api.run("pset.add_pset", ifc, product=wall, name="Foo_Bar") + ifcopenshell.api.run("pset.edit_pset", ifc, pset=pset, properties={"Foo": "Bar"}) + facet = ids.property.create(propertySet="Foo_Bar", name="Foo", value="Bar", location="any") + assert bool(facet(wall)) is True + assert bool(facet(wall_type)) is False + def test_material_create(self): m = ids.material.create(location="any", value="Test_Value") self.assertEqual(m.location, "any")