From 4da5a38237c04eeff7ce2b99c487dc7e796c9333 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 12 May 2022 12:06:23 +1000 Subject: [PATCH] Implement IDS material null checks, inheritance checks, and set item checks --- src/ifcopenshell-python/ifcopenshell/ids.py | 88 +++++----- .../ifcopenshell/util/element.py | 9 +- src/ifcopenshell-python/test/test_ids.py | 150 +++++++++++++++++- .../test/util/test_element.py | 18 +++ 4 files changed, 212 insertions(+), 53 deletions(-) diff --git a/src/ifcopenshell-python/ifcopenshell/ids.py b/src/ifcopenshell-python/ifcopenshell/ids.py index 0c5f0ccfaf..22818a2f29 100644 --- a/src/ifcopenshell-python/ifcopenshell/ids.py +++ b/src/ifcopenshell-python/ifcopenshell/ids.py @@ -893,11 +893,13 @@ class property(facet): :return: result of the validation as bool and message :rtype: facet_evaluation(bool, str) """ + all_psets = {} if self.location == "instance": 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) + if element_type: + all_psets = ifcopenshell.util.element.get_psets(element_type, should_inherit=False) elif self.location == "any": all_psets = ifcopenshell.util.element.get_psets(inst) @@ -1003,7 +1005,7 @@ class material(facet): :return: Xmlschema compliant dictionary. :rtype: dict """ - results = { "@location": self.location } + results = {"@location": self.location} if self.value: results["value"] = parameter_asdict(self.value) if self.uri: @@ -1014,7 +1016,7 @@ class material(facet): results["@instructions"] = self.instructions return results - def __call__(self, inst, logger): + def __call__(self, inst, logger=None): """Validate an ifc instance against that material facet. :param inst: IFC entity element @@ -1024,57 +1026,47 @@ class material(facet): :return: result of the validation as bool and message :rtype: facet_evaluation(bool, str) """ - - # self.location = self.node["@location"] - - instance_material_rel = [rel for rel in inst.HasAssociations if rel.is_a("IfcRelAssociatesMaterial")] - if ifcopenshell.util.element.get_type(inst): - type_material_rel = [ - rel - for rel in ifcopenshell.util.element.get_type(inst).HasAssociations - if rel.is_a("IfcRelAssociatesMaterial") - ] - else: - type_material_rel = [] - + material = None if self.location == "instance": - material_relations = list(instance_material_rel) - elif self.location == "type" and type_material_rel: - material_relations = list(type_material_rel) - elif self.location == "any" and (instance_material_rel or type_material_rel): - material_relations = instance_material_rel + type_material_rel - else: - material_relations = [] + material = ifcopenshell.util.element.get_material(inst, should_skip_usage=True, should_inherit=False) + elif self.location == "type": + element_type = ifcopenshell.util.element.get_type(inst) + if element_type: + material = ifcopenshell.util.element.get_material(element_type, should_skip_usage=True) + elif self.location == "any": + material = ifcopenshell.util.element.get_material(inst, should_skip_usage=True) - materials = [] - for rel in material_relations: - if rel.RelatingMaterial.is_a() == "IfcMaterial": - materials.append(rel.RelatingMaterial.Name) - elif rel.RelatingMaterial.is_a() == "IfcMaterialList": # DEPRECATED in IFC4 - [materials.append(mat.Name) for mat in rel.RelatingMaterial.Materials] - elif rel.RelatingMaterial.is_a() == "IfcMaterialConstituentSet": - [materials.append(mat.Material.Name) for mat in rel.RelatingMaterial.MaterialConstituents] - elif rel.RelatingMaterial.is_a() == "IfcMaterialLayerSet": - [materials.append(mat.Name) for mat in rel.RelatingMaterial.MaterialLayers] - elif rel.RelatingMaterial.is_a() == "IfcMaterialLayerSetUsage": - layers = rel.RelatingMaterial.ForLayerSet.MaterialLayers - [materials.append(layer.Material.Name) for layer in layers] - elif rel.RelatingMaterial.is_a() == "IfcMaterialProfileSet": - [materials.append(mat.Material.Name) for mat in rel.RelatingMaterial.MaterialProfiles] - elif rel.RelatingMaterial.is_a() == "IfcMaterialProfileSetUsage": - profileSets = rel.RelatingMaterial.ForProfileSet.MaterialProfiles - [materials.append(pset.Material.Name) for pset in profileSets] - else: - raise Exception("IfcRelAssociatesMaterial not implemented") + is_pass = material is not None - if not materials: - materials.append("UNDEFINED") + if is_pass and self.value: + if material.is_a("IfcMaterial"): + values = {material.Name, getattr(material, "Category")} + elif material.is_a("IfcMaterialList"): + values = set() + for mat in material.Materials or []: + values.update([mat.Name, getattr(mat, "Category")]) + elif material.is_a("IfcMaterialLayerSet"): + values = {material.LayerSetName} + for item in material.MaterialLayers or []: + values.update([item.Name, item.Category, item.Material.Name, getattr(item.Material, "Category")]) + elif material.is_a("IfcMaterialProfileSet"): + values = {material.Name} + for item in material.MaterialProfiles or []: + values.update([item.Name, item.Category, item.Material.Name, getattr(item.Material, "Category")]) + elif material.is_a("IfcMaterialConstituentSet"): + values = {material.Name} + for item in material.MaterialConstituents or []: + values.update([item.Name, item.Category, item.Material.Name, getattr(item.Material, "Category")]) - self.location_msg = location[self.location] + is_pass = False + for value in values: + if value == self.value: + is_pass = True + break return facet_evaluation( - self.value in materials, - self.message % {"value": "'/'".join(materials), "location": self.location_msg}, + is_pass, + self.message % {"value": "todo", "location": "todo"}, ) diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index d4ef35aee5..9b73bd8ce9 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -121,7 +121,7 @@ def get_types(type): return [] -def get_material(element, should_skip_usage=False): +def get_material(element, should_skip_usage=False, should_inherit=True): if hasattr(element, "HasAssociations") and element.HasAssociations: for relationship in element.HasAssociations: if relationship.is_a("IfcRelAssociatesMaterial"): @@ -131,9 +131,10 @@ def get_material(element, should_skip_usage=False): elif relationship.RelatingMaterial.is_a("IfcMaterialProfileSetUsage"): return relationship.RelatingMaterial.ForProfileSet return relationship.RelatingMaterial - relating_type = get_type(element) - if relating_type != element and hasattr(relating_type, "HasAssociations") and relating_type.HasAssociations: - return get_material(relating_type, should_skip_usage) + if should_inherit: + relating_type = get_type(element) + if relating_type != element and hasattr(relating_type, "HasAssociations") and relating_type.HasAssociations: + return get_material(relating_type, should_skip_usage) def get_elements_by_material(ifc_file, material): diff --git a/src/ifcopenshell-python/test/test_ids.py b/src/ifcopenshell-python/test/test_ids.py index 80f3b21f39..adb769a6a3 100644 --- a/src/ifcopenshell-python/test/test_ids.py +++ b/src/ifcopenshell-python/test/test_ids.py @@ -803,7 +803,9 @@ class TestIdsAuthoring(unittest.TestCase): def test_material_create(self): facet = ids.material.create() assert facet.asdict() == {"@location": "any"} - facet = ids.material.create(value="value", location="instance", uri="https://test.com", use="required", instructions="instructions") + facet = ids.material.create( + value="value", location="instance", uri="https://test.com", use="required", instructions="instructions" + ) assert facet.asdict() == { "value": {"simpleValue": "value"}, "@location": "instance", @@ -812,6 +814,152 @@ class TestIdsAuthoring(unittest.TestCase): "@instructions": "instructions", } + def test_filtering_using_a_material_facet(self): + ifc = ifcopenshell.file() + + # A material facet with no data matches any present material + facet = ids.material.create() + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + assert bool(facet(element)) is False + material = ifcopenshell.api.run("material.add_material", ifc) + ifcopenshell.api.run("material.assign_material", ifc, product=element, material=material) + assert bool(facet(element)) is True + + # A value will match a material name or category + facet = ids.material.create(value="Foo") + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + material = ifcopenshell.api.run("material.add_material", ifc) + ifcopenshell.api.run("material.assign_material", ifc, product=element, material=material) + assert bool(facet(element)) is False + material.Name = "Foo" + assert bool(facet(element)) is True + material.Name = "Bar" + material.Category = "Foo" + assert bool(facet(element)) is True + + # A value will match any material name or category in a material list + facet = ids.material.create(value="Foo") + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + material_set = ifcopenshell.api.run("material.add_material_set", ifc, set_type="IfcMaterialList") + ifcopenshell.api.run("material.assign_material", ifc, product=element, material=material_set) + assert bool(facet(element)) is False + material = ifcopenshell.api.run("material.add_material", ifc) + ifcopenshell.api.run("material.add_list_item", ifc, material_list=material_set, material=material) + material.Name = "Foo" + assert bool(facet(element)) is True + material.Name = "Bar" + material.Category = "Foo" + assert bool(facet(element)) is True + + # A value will match any material name or category, or layer name or category in a layer set + facet = ids.material.create(value="Foo") + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + material_set = ifcopenshell.api.run("material.add_material_set", ifc, set_type="IfcMaterialLayerSet") + ifcopenshell.api.run("material.assign_material", ifc, product=element, material=material_set) + assert bool(facet(element)) is False + material = ifcopenshell.api.run("material.add_material", ifc) + layer = ifcopenshell.api.run("material.add_layer", ifc, layer_set=material_set, material=material) + layer.Name = "Foo" + assert bool(facet(element)) is True + layer.Name = "Bar" + layer.Category = "Foo" + assert bool(facet(element)) is True + layer.Category = "Bar" + material.Name = "Foo" + assert bool(facet(element)) is True + material.Name = "Bar" + material.Category = "Foo" + assert bool(facet(element)) is True + + # A value will match any material name or category, or profile name or category in a profile set + facet = ids.material.create(value="Foo") + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + material_set = ifcopenshell.api.run("material.add_material_set", ifc, set_type="IfcMaterialProfileSet") + ifcopenshell.api.run("material.assign_material", ifc, product=element, material=material_set) + assert bool(facet(element)) is False + material = ifcopenshell.api.run("material.add_material", ifc) + profile = ifcopenshell.api.run("material.add_profile", ifc, profile_set=material_set, material=material) + profile.Name = "Foo" + assert bool(facet(element)) is True + profile.Name = "Bar" + profile.Category = "Foo" + assert bool(facet(element)) is True + profile.Category = "Bar" + material.Name = "Foo" + assert bool(facet(element)) is True + material.Name = "Bar" + material.Category = "Foo" + assert bool(facet(element)) is True + + # A value will match any material name or category, or constituent name or category in a constituent set + facet = ids.material.create(value="Foo") + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + material_set = ifcopenshell.api.run("material.add_material_set", ifc, set_type="IfcMaterialConstituentSet") + ifcopenshell.api.run("material.assign_material", ifc, product=element, material=material_set) + assert bool(facet(element)) is False + material = ifcopenshell.api.run("material.add_material", ifc) + constituent = ifcopenshell.api.run( + "material.add_constituent", ifc, constituent_set=material_set, material=material + ) + constituent.Name = "Foo" + assert bool(facet(element)) is True + constituent.Name = "Bar" + constituent.Category = "Foo" + assert bool(facet(element)) is True + constituent.Category = "Bar" + material.Name = "Foo" + assert bool(facet(element)) is True + material.Name = "Bar" + material.Category = "Foo" + assert bool(facet(element)) is True + + # Location instance will only check instances + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + element_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType") + ifcopenshell.api.run("type.assign_type", ifc, related_object=element, relating_type=element_type) + material = ifcopenshell.api.run("material.add_material", ifc) + ifcopenshell.api.run("material.assign_material", ifc, product=element_type, material=material) + facet = ids.material.create(location="instance") + assert bool(facet(element)) is False + assert bool(facet(element_type)) is True + + # Location type will only check types + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + element_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType") + ifcopenshell.api.run("type.assign_type", ifc, related_object=element, relating_type=element_type) + material = ifcopenshell.api.run("material.add_material", ifc) + ifcopenshell.api.run("material.assign_material", ifc, product=element, material=material) + facet = ids.material.create(location="type") + assert bool(facet(element)) is False + assert bool(facet(element_type)) is False + ifcopenshell.api.run("material.assign_material", ifc, product=element_type, material=material) + assert bool(facet(element)) is True + assert bool(facet(element_type)) is True + + # Location any will check for inherited materials + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + element_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType") + ifcopenshell.api.run("type.assign_type", ifc, related_object=element, relating_type=element_type) + material = ifcopenshell.api.run("material.add_material", ifc) + ifcopenshell.api.run("material.assign_material", ifc, product=element_type, material=material) + material.Name = "Foo" + facet = ids.material.create(value="Foo", location="any") + assert bool(facet(element)) is True + assert bool(facet(element_type)) is True + + # Location any will check for overriden materials + element = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + element_type = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWallType") + ifcopenshell.api.run("type.assign_type", ifc, related_object=element, relating_type=element_type) + material = ifcopenshell.api.run("material.add_material", ifc) + ifcopenshell.api.run("material.assign_material", ifc, product=element_type, material=material) + material.Name = "Bar" + material = ifcopenshell.api.run("material.add_material", ifc) + ifcopenshell.api.run("material.assign_material", ifc, product=element, material=material) + material.Name = "Foo" + facet = ids.material.create(value="Foo", location="any") + assert bool(facet(element)) is True + assert bool(facet(element_type)) is False """ Creating IDS with restrictions """ diff --git a/src/ifcopenshell-python/test/util/test_element.py b/src/ifcopenshell-python/test/util/test_element.py index 1a840fd27f..1c351b61a8 100644 --- a/src/ifcopenshell-python/test/util/test_element.py +++ b/src/ifcopenshell-python/test/util/test_element.py @@ -286,6 +286,24 @@ class TestGetMaterial(test.bootstrap.IFC4): ifcopenshell.api.run("material.assign_material", self.file, product=element_type, material=material) assert subject.get_material(element) == material + def test_getting_an_overridden_material_from_the_elements_occurrence(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=element_type) + material = ifcopenshell.api.run("material.add_material", self.file) + ifcopenshell.api.run("material.assign_material", self.file, product=element_type, material=material) + material = ifcopenshell.api.run("material.add_material", self.file) + ifcopenshell.api.run("material.assign_material", self.file, product=element, material=material) + assert subject.get_material(element) == material + + def test_getting_direct_materials_without_checking_inheritance(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + ifcopenshell.api.run("type.assign_type", self.file, related_object=element, relating_type=element_type) + material = ifcopenshell.api.run("material.add_material", self.file) + ifcopenshell.api.run("material.assign_material", self.file, product=element_type, material=material) + assert subject.get_material(element, should_inherit=False) is None + class TestGetElementsByMaterial(test.bootstrap.IFC4): def test_getting_elements_of_a_material(self):