From 08fc62d4d98bd08234b2d64708a730cf4145a553 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 10 May 2022 21:20:10 +1000 Subject: [PATCH] Add support for userdefined predefined types in IDS entity facets --- src/ifcopenshell-python/ifcopenshell/ids.py | 57 +++++++++++---------- src/ifcopenshell-python/test/test_ids.py | 31 +++++++++-- 2 files changed, 56 insertions(+), 32 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/ids.py b/src/ifcopenshell-python/ifcopenshell/ids.py index 40dddb6a82..2f4174c09a 100644 --- a/src/ifcopenshell-python/ifcopenshell/ids.py +++ b/src/ifcopenshell-python/ifcopenshell/ids.py @@ -173,7 +173,6 @@ class ids: logging.basicConfig(level=logging.INFO, format="%(message)s") logger.setLevel(logging.INFO) - # Consider other way around: for elem, for spec so we can see if an element pass all IDSes? for spec in self.specifications: self.ifc_applicable = 0 @@ -216,8 +215,8 @@ class specification: ): """Create a specification to be added in ids. - :param name:, defaults to "Specification" - :type name: str, optional + :param name: Name describing the specification to a contract reader + :type name: str :param use: 'required'|'optional', defaults to "required" :type use: str, optional """ @@ -287,17 +286,19 @@ class specification: return spec def add_applicability(self, facet): - """Applicability specifies what conditions must be meet for an IFC object to be used for validation. Note, that at least one entity facet is required. + """Applicability specifies a filter for IFC entities are to be validated. - :param facet: any of entity|classification|property|material + At least one filter must be added. + + :param facet: any of entity|attribute|classification|property|material :type facet: facet Example:: - i = ids.ids() - i.specifications.append(ids.specification(name="Test_Specification")) - e = ids.entity.create(name="Test_Name", predefinedType="Test_PredefinedType") - i.specifications[0].add_applicability(e) + specs = ids.ids() + spec = ids.specification(name="Test_Specification") + spec.add_applicability(ids.entity.create(name="IfcWall")) + specs.specifications.append(spec) """ if self.applicability: self.applicability = boolean_and(self.applicability.terms + [facet]) @@ -305,9 +306,11 @@ class specification: self.applicability = boolean_and([facet]) def add_requirement(self, facet): - """Requirement is validated on all applicable IFC elements. Note, that at least one facet of any type is required. + """A requirement specifies data to be checked for all applicable entities. - :param facet: any of entity|classification|property|material + At least one requirement must be added. + + :param facet: any of entity|attribute|classification|property|material|partOf :type facet: facet """ if self.requirements: @@ -487,18 +490,17 @@ class entity(facet): :return: Xmlschema compliant dictionary. :rtype: dict """ - fac_dict = {"name": parameter_asdict(self.name)} - if "predefinedType" in self: - if self.predefinedType: - fac_dict["predefinedType"] = parameter_asdict(self.predefinedType) - # try: - # fac_dict["predefinedType"] = parameter_asdict(self.predefinedType) - # except (RecursionError, UnboundLocalError) as e: - # print(e) - return fac_dict + results = {"name": parameter_asdict(self.name)} + if self.predefinedType: + results["predefinedType"] = parameter_asdict(self.predefinedType) + return results - def __call__(self, inst, logger): - """Validate an ifc instance against that entity facet. + def __call__(self, inst, logger=None): + """Validate an entity. + + When a simple value is provided for the name, subclasses are also + treated as valid. PredefinedType checks support userdefined types for + both element and type elements. :param inst: IFC entity element :type inst: IFC entity @@ -507,13 +509,12 @@ class entity(facet): :return: result of the validation as bool and message :rtype: facet_evaluation(bool, str) """ - - # @nb with inheritance - if self.predefinedType and hasattr(inst, "PredefinedType"): + if self.predefinedType: + predefined_type = ifcopenshell.util.element.get_predefined_type(inst) self.message = "an entity name '%(name)s' of predefined type '%(predefinedType)s'" return facet_evaluation( - inst.is_a(self.name) and inst.PredefinedType == self.predefinedType, - self.message % {"name": inst.is_a(), "predefinedType": inst.PredefinedType}, + inst.is_a(self.name) and predefined_type == self.predefinedType, + self.message % {"name": inst.is_a(), "predefinedType": predefined_type}, ) else: self.message = "an entity name '%(name)s'" @@ -558,7 +559,7 @@ class attribute(facet): fac_dict["@location"] = self.location return fac_dict - def __call__(self, inst, logger): + def __call__(self, inst, logger=None): """Validate an ifc instance against that entity facet. :param inst: IFC entity element diff --git a/src/ifcopenshell-python/test/test_ids.py b/src/ifcopenshell-python/test/test_ids.py index 0b784df1f9..3605e18e1f 100644 --- a/src/ifcopenshell-python/test/test_ids.py +++ b/src/ifcopenshell-python/test/test_ids.py @@ -263,10 +263,33 @@ class TestIdsAuthoring(unittest.TestCase): i.specifications[0].add_requirement(m) self.assertEqual(i.specifications[0].requirements.terms[1].value, "Test_Value") - def test_entity_create(self): - e = ids.entity.create(name="Test_Name", predefinedType="Test_PredefinedType") - self.assertEqual(e.name, "Test_Name") - self.assertEqual(e.predefinedType, "Test_PredefinedType") + def test_create_an_entity_facet(self): + facet = ids.entity.create(name="IfcName") + assert facet.asdict() == {"name": {"simpleValue": "IfcName"}} + facet = ids.entity.create(name="IfcName", predefinedType="predefinedType") + assert facet.asdict() == { + "name": {"simpleValue": "IfcName"}, + "predefinedType": {"simpleValue": "predefinedType"}, + } + + def test_filtering_using_an_entity_facet(self): + ifc = ifcopenshell.file() + + facet = ids.entity.create(name="IfcWall") + assert bool(facet(ifc.createIfcWall())) is True + assert bool(facet(ifc.createIfcWall(PredefinedType="SOLIDWALL"))) is True + assert bool(facet(ifc.createIfcSlab())) is False + + facet = ids.entity.create(name="IfcWall", predefinedType="SOLIDWALL") + assert bool(facet(ifc.createIfcWall())) is False + assert bool(facet(ifc.createIfcWall(PredefinedType="SOLIDWALL"))) is True + assert bool(facet(ifc.createIfcWall(PredefinedType="PARTITIONING"))) is False + + facet = ids.entity.create(name="IfcWall", predefinedType="WALDO") + assert bool(facet(ifc.createIfcWall(PredefinedType="USERDEFINED", ObjectType="WALDO"))) is True + + facet = ids.entity.create(name="IfcWallType", predefinedType="WALDO") + assert bool(facet(ifc.createIfcWallType(PredefinedType="USERDEFINED", ElementType="WALDO"))) is True def test_attribute_create(self): attribute = ids.attribute.create(name="Name", value="Value")