diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/__init__.py b/src/ifcopenshell-python/ifcopenshell/api/material/__init__.py index 6e57e28210..f836c5bd4f 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/material/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/api/material/__init__.py @@ -55,6 +55,7 @@ from .remove_material import remove_material from .remove_material_set import remove_material_set from .remove_profile import remove_profile from .reorder_set_item import reorder_set_item +from .set_shape_aspect_constituents import set_shape_aspect_constituents from .unassign_material import unassign_material wrap_usecases(__path__, __name__) @@ -83,5 +84,6 @@ __all__ = [ "remove_material_set", "remove_profile", "reorder_set_item", + "set_shape_aspect_constituents", "unassign_material", ] diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/set_shape_aspect_constituents.py b/src/ifcopenshell-python/ifcopenshell/api/material/set_shape_aspect_constituents.py new file mode 100644 index 0000000000..acd00335da --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/material/set_shape_aspect_constituents.py @@ -0,0 +1,122 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2022 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import ifcopenshell.api.style +import ifcopenshell.api.material +import ifcopenshell.util.element +import ifcopenshell.util.representation + + +def set_shape_aspect_constituents( + file: ifcopenshell.file, + *, + element: ifcopenshell.entity_instance, + context: ifcopenshell.entity_instance, + materials: dict[str, ifcopenshell.entity_instance], +) -> None: + """Assigns a material constituent set and sets styles based on shape aspects + + An IFC element may be assigned to a set of material constituents. For + example, a window may have a framing material and a glazing material. Each + constituent may have a name, such as "Framing" (which may be assigned to an + "Aluminium" material), and "Glazing" (assigned to a "Laminated Low-e Glass" + material). + + An IFC element's geometry may be composed of multiple geometric items. + These geometric items may have names, known as "Shape Aspects". For + example a solid extrusion for the framing named "Framing" and a solid + extrusion for the glass panel named "Glazing". + + A material may be associated with a style (i.e. colour). For example, a + grey style for the "Aluminium" material and a transparent blue style for + the "Laminated Low-e Glass" material. + + These three concepts of material constituents, shape aspects, and + associated styles are correlated. For example, if the name (e.g. "Framing") + of a material constituent and a shape aspect correlate, that means that the + geometric item inherits the style (i.e. grey). + + This function lets you specify named material constituents, and it'll + create a constituent set assigned to the element with those names. It'll + then find any geometric representation items with shape aspects matching + those names, and assign the correlating style. + + If an assigned material constituent set already exists matching those + values, it will be reused. If the values do not match, the existing + material constituent set will be removed if it is not used by anything + else. + + :param element: The IfcProduct or IfcTypeProduct + :param context: The IfcGeometricRepresentationContext, typically the body + context. You can get this via + :func:`ifcopenshell.util.representation.get_context`. + :param materials: The key is the name of the constituent, and the value is + the IfcMaterial. + + Example: + + .. code:: python + + # Create two materials + aluminium = ifcopenshell.api.material.add_material(model, name="AL01", category="aluminium") + glass = ifcopenshell.api.material.add_material(model, name="GLZ01", category="glass") + + # Auto assign material constituents and styles to items based on shape aspects + ifcopenshell.api.material.set_shape_aspect_constituents( + model, element=window, context=body, materials={ + "Framing": aluminium + "Lining": aluminium + "Glazing": glass + }) + """ + should_create_new_material_set = False + if material := ifcopenshell.util.element.get_material(element): + if ( + material.is_a("IfcMaterialConstituent") + and len(names := [c.Name for c in material.MaterialConstituents]) == len(materials) + and set(names) == set(materials.keys()) + ): + should_create_new_material_set = False + else: + should_create_new_material_set = True + ifcopenshell.api.material.unassign_material(file, products=[element]) + if not material.is_a("IfcMaterial") and not file.get_total_inverses(material): + ifcopenshell.api.material.remove_material_set(file, material=material) + else: + should_create_new_material_set = True + if should_create_new_material_set: + material_set = ifcopenshell.api.material.add_material_set(file, set_type="IfcMaterialConstituentSet") + for name, material in materials.items(): + ifcopenshell.api.material.add_constituent( + file, constituent_set=material_set, material=material, name=name + ) + ifcopenshell.api.material.assign_material(file, products=[element], material=material_set) + + styles = {n: ifcopenshell.util.representation.get_material_style(m, context) for n, m in materials.items()} + print('styels are', styles) + representation = ifcopenshell.util.representation.get_representation(element, context=context) + print('rep is', representation) + representation = ifcopenshell.util.representation.resolve_representation(representation) + print('rrep is', representation) + for item in representation.Items: + print('checking item', item) + if aspect := ifcopenshell.util.representation.get_item_shape_aspect(representation, item): + print('found', aspect.Name) + if style := styles.get(aspect.Name, None): + print('... and correlating style', style) + ifcopenshell.api.style.assign_item_style(file, item=item, style=style)