From faa5a78b39b76f877924223bd2987d9feacf5162 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 14 Aug 2023 11:55:15 +1000 Subject: [PATCH] Fix #3578. Support querying surface styles in tools like IfcCSV. --- .../ifcopenshell/util/element.py | 49 +++++++++++++++++++ .../ifcopenshell/util/selector.py | 4 ++ .../test/util/test_element.py | 47 ++++++++++++++++++ 3 files changed, 100 insertions(+) diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py index 2e8669b75d..bf0ddf616b 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/element.py +++ b/src/ifcopenshell-python/ifcopenshell/util/element.py @@ -339,6 +339,8 @@ def get_material(element, should_skip_usage=False, should_inherit=True): The material may be a single material, material set (layered, profiled, or constituent), or a material set usage. + :param element: The element to get the material of. + :type element: ifcopenshell.entity_instance.entity_instance :param should_skip_usage: If set to True, if the material is a material set usage, the material set itself will be returned. Useful if you don't care about occurrence usage parameters. If False, the usage will be @@ -378,6 +380,8 @@ def get_materials(element, should_inherit=True): If the element has a material set, the individual materials of that set are returned as a list. + :param element: The element to get the materials of. + :type element: ifcopenshell.entity_instance.entity_instance :param should_inherit: If True, any inherited materials from associated types will be considered. :return: The associated materials of the element. @@ -403,6 +407,51 @@ def get_materials(element, should_inherit=True): return [c.Material for c in material.MaterialConstituents] +def get_styles(element): + """Retrieves the styles used in an element's representation. + + Styles may be retreived from the material or the body representation. + + :param element: The element to get the styles of. + :type element: ifcopenshell.entity_instance.entity_instance + :return: A list of surface styles + :rtype: list[ifcopenshell.entity_instance.entity_instance] + + Example: + + .. code:: python + + wall = file.by_type("IfcWall")[0] + styles = ifcopenshell.util.element.get_styles(wall) + """ + styles = [] + + materials = ifcopenshell.util.element.get_materials(element) + for material in materials: + for material_definition_representation in material.HasRepresentation or []: + for representation in material_definition_representation.Representations: + for item in representation.Items: + styles.extend([s for s in item.Styles if s.is_a("IfcSurfaceStyle")]) + + body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") + if not body: + return styles + + for representation in [body]: + queue = list(representation.Items) + while queue: + item = queue.pop() + if item.is_a("IfcMappedItem"): + queue.extend(item.MappingSource.MappedRepresentation.Items) + if item.is_a("IfcBooleanResult"): + queue.append(item.FirstOperand) + queue.append(item.SecondOperand) + if item.StyledByItem: + styles.extend([s for s in item.StyledByItem[0].Styles if s.is_a("IfcSurfaceStyle")]) + return styles + + + def get_elements_by_material(ifc_file, material): """Retrieves the elements related to a material. diff --git a/src/ifcopenshell-python/ifcopenshell/util/selector.py b/src/ifcopenshell-python/ifcopenshell/util/selector.py index 6f940bdf8b..f44160fbf6 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/selector.py +++ b/src/ifcopenshell-python/ifcopenshell/util/selector.py @@ -520,6 +520,10 @@ class Selector: value = ifcopenshell.util.element.get_type(value) elif key in ("material", "mat"): value = ifcopenshell.util.element.get_material(value, should_skip_usage=True) + elif key in ("materials", "mats"): + value = ifcopenshell.util.element.get_materials(value) + elif key == "styles": + value = ifcopenshell.util.element.get_styles(value) elif key in ("item", "i"): if value.is_a("IfcMaterialLayerSet"): value = value.MaterialLayers diff --git a/src/ifcopenshell-python/test/util/test_element.py b/src/ifcopenshell-python/test/util/test_element.py index c4e8652472..499f448567 100644 --- a/src/ifcopenshell-python/test/util/test_element.py +++ b/src/ifcopenshell-python/test/util/test_element.py @@ -386,6 +386,53 @@ class TestGetMaterial(test.bootstrap.IFC4): assert subject.get_material(element, should_inherit=False) is None +class TestGetMaterials(test.bootstrap.IFC4): + def test_getting_the_materials_of_a_product(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + material = ifcopenshell.api.run("material.add_material", self.file) + ifcopenshell.api.run("material.assign_material", self.file, product=element, material=material) + assert subject.get_materials(element) == [material] + + +class TestGetStyles(test.bootstrap.IFC4): + def test_getting_the_styles_of_a_product(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + assert subject.get_styles(element) == [] + + model = ifcopenshell.api.run("context.add_context", self.file, context_type="Model") + body = ifcopenshell.api.run("context.add_context", self.file, + context_type="Model", context_identifier="Body", target_view="MODEL_VIEW", parent=model) + + material = ifcopenshell.api.run("material.add_material", self.file) + ifcopenshell.api.run("material.assign_material", self.file, product=element, material=material) + + style = ifcopenshell.api.run("style.add_style", self.file) + ifcopenshell.api.run("style.add_surface_style", self.file, + style=style, ifc_class="IfcSurfaceStyleShading", attributes={ + "SurfaceColour": { "Name": None, "Red": 1.0, "Green": 0.8, "Blue": 0.8 }, + "Transparency": 0., # 0 is opaque, 1 is transparent + }) + ifcopenshell.api.run("style.assign_material_style", self.file, material=material, style=style, context=body) + + assert subject.get_styles(element) == [style] + + style2 = ifcopenshell.api.run("style.add_style", self.file) + ifcopenshell.api.run("style.add_surface_style", self.file, + style=style2, ifc_class="IfcSurfaceStyleShading", attributes={ + "SurfaceColour": { "Name": None, "Red": 1.0, "Green": 0.8, "Blue": 0.8 }, + "Transparency": 0., # 0 is opaque, 1 is transparent + }) + + representation = ifcopenshell.api.run("geometry.add_wall_representation", self.file, + context=body, length=5, height=3, thickness=0.118) + + ifcopenshell.api.run("geometry.assign_representation", self.file, product=element, representation=representation) + ifcopenshell.api.run("style.assign_representation_styles", self.file, shape_representation=representation, styles=[style2]) + + assert subject.get_styles(element) == [style, style2] + + class TestGetElementsByMaterial(test.bootstrap.IFC4): def test_getting_elements_of_a_material(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")