First Commit

This commit is contained in:
2026-05-31 10:17:09 +07:00
commit 17a9c69379
4547 changed files with 1170384 additions and 0 deletions
@@ -0,0 +1,89 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
"""Manage physical materials (concrete, steel, etc) and their association to
elements
IFC supports both simple materials and parametric materials (materials that
have layered thicknesses or cross sectional profiles).
Parametric materials will include parametric constraints on the geometry of
the element. These API functions do not cover that responsibility. See
:mod:`ifcopenshell.api.geometry`.
Note that this API only covers physical materials, not visual styles. If you
want to look at visual styles such as colours, transparency, shading, or
rendering options, see :mod:`ifcopenshell.api.style`.
"""
from .. import wrap_usecases
from .add_constituent import add_constituent
from .add_layer import add_layer
from .add_list_item import add_list_item
from .add_material import add_material
from .add_material_set import add_material_set
from .add_profile import add_profile
from .assign_material import assign_material
from .assign_profile import assign_profile
from .copy_material import copy_material
from .edit_assigned_material import edit_assigned_material
from .edit_constituent import edit_constituent
from .edit_layer import edit_layer
from .edit_layer_usage import edit_layer_usage
from .edit_material import edit_material
from .edit_profile import edit_profile
from .edit_profile_usage import edit_profile_usage
from .remove_constituent import remove_constituent
from .remove_layer import remove_layer
from .remove_list_item import remove_list_item
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__)
__all__ = [
"add_constituent",
"add_layer",
"add_list_item",
"add_material",
"add_material_set",
"add_profile",
"assign_material",
"assign_profile",
"copy_material",
"edit_assigned_material",
"edit_constituent",
"edit_layer",
"edit_layer_usage",
"edit_material",
"edit_profile",
"edit_profile_usage",
"remove_constituent",
"remove_layer",
"remove_list_item",
"remove_material",
"remove_material_set",
"remove_profile",
"reorder_set_item",
"set_shape_aspect_constituents",
"unassign_material",
]
@@ -0,0 +1,92 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
from typing import Optional
import ifcopenshell
def add_constituent(
file: ifcopenshell.file,
constituent_set: ifcopenshell.entity_instance,
material: ifcopenshell.entity_instance,
name: Optional[str] = None,
) -> ifcopenshell.entity_instance:
"""Adds a new constituent to a constituent set
A constituent describes how a portion of an object is made out of a
material whereas other portions of the object is made out of other
materials. For example, a window might be made out of an aluminium frame
and a glass panel. The aluminium used for the frame is one constituent
of the material, and glass would be another constituent. Another example
might be concrete, where one constituent might be cement, and another
constituent might be binder. In the case of the window, the constituent
is represented explicitly by the geometry of the window frame and the
geometry of the window panel. In the case of a concrete slab, the
constituents might be represented in terms of percentages.
Constituents are not available in IFC2X3.
:param constituent_set: The IfcMaterialConstituentSet that the
constituent is part of. The constituent set represents a group of
constituents. See ifcopenshell.api.material.add_material_set for
information on how to add a constituent set.
:param material: The IfcMaterial that the constituent is made out of.
:param name: An optional name of the constituent.
:return: The newly created IfcMaterialConstituent
Example:
.. code:: python
# Let's imagine we have a window type that has an aluminium frame
# and a glass glazing panel. Notice we are assigning to the type
# only, as all occurrences of that type will automatically inherit
# the material.
window_type = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWindowType")
# First, let's create a constituent set. This will later be assigned
# to our window element.
material_set = ifcopenshell.api.material.add_material_set(model,
name="Window", set_type="IfcMaterialConstituentSet")
# Let's create a few materials, it's important to also give them
# categories. This makes it easy for model recipients to do things
# like "show me everything made out of aluminium / concrete / steel
# / glass / etc". The IFC specification states a list of categories
# you can use.
aluminium = ifcopenshell.api.material.add_material(model, name="AL01", category="aluminium")
glass = ifcopenshell.api.material.add_material(model, name="GLZ01", category="glass")
# Now let's use those materials as two constituents in our set.
ifcopenshell.api.material.add_constituent(model,
constituent_set=material_set, material=aluminium, name="Framing")
ifcopenshell.api.material.add_constituent(model,
constituent_set=material_set, material=glass, name="Glazing")
# Great! Let's assign our material set to our window type.
# We're technically not done here, we might want to add geometry to
# our window too, but to keep this example simple, geometry is
# optional and it is enough to say that this window is made out of
# aluminium and glass.
ifcopenshell.api.material.assign_material(model, products=[window_type], material=material_set)
"""
constituents = list(constituent_set.MaterialConstituents or [])
constituent = file.create_entity("IfcMaterialConstituent", Material=material, Name=name)
constituents.append(constituent)
constituent_set.MaterialConstituents = constituents
return constituent
@@ -0,0 +1,95 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
from typing import Optional
import ifcopenshell
import ifcopenshell.util.unit
def add_layer(
file: ifcopenshell.file,
layer_set: ifcopenshell.entity_instance,
material: ifcopenshell.entity_instance,
name: Optional[str] = None,
) -> ifcopenshell.entity_instance:
"""Adds a new layer to a layer set
A layer represents a portion of material within a layered build up,
defined by a thickness. Typical layered construction includes walls and
slabs, where a wall might include a layer of finish, a layer of
structure, a layer of insulation, and so on. It is recommended to define
layered construction this way where it is unnecessary to define the
exact geometry of how the wall or slab will be built, and it will
instead be determined on site by a trade.
Layers are defined in a particular order and thickness, so that it is
clear which layer comes next.
:param layer_set: The IfcMaterialLayerSet that the layer is part of. The
layer set represents a group of layers. See
ifcopenshell.api.material.add_material_set for more information on
how to add a layer set.
:param material: The IfcMaterial that the layer is made out of.
:param name: An optional name of the layer.
:return: The newly created IfcMaterialLayer
Example:
.. code:: python
# Let's imagine we have a wall type that has two layers of
# gypsum with steel studs inside. Notice we are assigning to
# the type only, as all occurrences of that type will automatically
# inherit the material.
wall_type = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWallType", name="WAL01")
# First, let's create a material set. This will later be assigned
# to our wall type element.
material_set = ifcopenshell.api.material.add_material_set(model,
name="GYP-ST-GYP", set_type="IfcMaterialLayerSet")
# Let's create a few materials, it's important to also give them
# categories. This makes it easy for model recipients to do things
# like "show me everything made out of aluminium / concrete / steel
# / glass / etc". The IFC specification states a list of categories
# you can use.
gypsum = ifcopenshell.api.material.add_material(model, name="PB01", category="gypsum")
steel = ifcopenshell.api.material.add_material(model, name="ST01", category="steel")
# Now let's use those materials as three layers in our set, such
# that the steel studs are sandwiched by the gypsum. Let's imagine
# we're setting the layer thickness in millimeters.
layer = ifcopenshell.api.material.add_layer(model, layer_set=material_set, material=gypsum)
ifcopenshell.api.material.edit_layer(model, layer=layer, attributes={"LayerThickness": 13})
layer = ifcopenshell.api.material.add_layer(model, layer_set=material_set, material=steel)
ifcopenshell.api.material.edit_layer(model, layer=layer, attributes={"LayerThickness": 92})
layer = ifcopenshell.api.material.add_layer(model, layer_set=material_set, material=gypsum)
ifcopenshell.api.material.edit_layer(model, layer=layer, attributes={"LayerThickness": 13})
# Great! Let's assign our material set to our wall type.
ifcopenshell.api.material.assign_material(model, products=[wall_type], material=material_set)
"""
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(file)
layers = list(layer_set.MaterialLayers or [])
if file.schema == "IFC2X3":
layer = file.create_entity("IfcMaterialLayer", Material=material, LayerThickness=0.1 / unit_scale)
else:
layer = file.create_entity("IfcMaterialLayer", Material=material, LayerThickness=0.1 / unit_scale, Name=name)
layers.append(layer)
layer_set.MaterialLayers = layers
return layer
@@ -0,0 +1,82 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import ifcopenshell
def add_list_item(
file: ifcopenshell.file, material_list: ifcopenshell.entity_instance, material: ifcopenshell.entity_instance
) -> None:
"""Adds a new material in a list of materials
In IFC2X3, if you wanted an object to have multiple materials (i.e. a
composite material) you would assign the object to a material list,
which would contain a list of materials. For example, a window might
have a list of 2 materials, one being aluminium for the frame, and
another being glass for the panel.
In IFC4 and above, this is deprecated and should not be used. Instead,
you should use constituent sets instead, which achieve the same thing
but are more powerful as they allow you to define the properties of the
constituents too.
However if you're stuck on IFC2X3, you have my condolences as well as
this function.
:param material_list: The IfcMaterialList the material should be added
to.
:param material: The IfcMaterial to add to the list
:return: None
Example:
.. code:: python
# Let's imagine we have a window type that has an aluminium frame
# and a glass glazing panel. Notice we are assigning to the type
# only, as all occurrences of that type will automatically inherit
# the material.
window_type = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWindowType")
# First, let's create a list. This will later be assigned to our
# window element.
material_set = ifcopenshell.api.material.add_material_set(model,
name="Window", set_type="IfcMaterialList")
# Let's create a few materials, it's important to also give them
# categories. This makes it easy for model recipients to do things
# like "show me everything made out of aluminium / concrete / steel
# / glass / etc". The IFC specification states a list of categories
# you can use.
aluminium = ifcopenshell.api.material.add_material(model, name="AL01", category="aluminium")
glass = ifcopenshell.api.material.add_material(model, name="GLZ01", category="glass")
# Now let's use those materials as two items in our list.
ifcopenshell.api.material.add_list_item(model, material_list=material_set, material=aluminium)
ifcopenshell.api.material.add_list_item(model, material_list=material_set, material=glass)
# Great! Let's assign our material set to our window type.
# We're technically not done here, we might want to add geometry to
# our window too, but to keep this example simple, geometry is
# optional and it is enough to say that this window is made out of
# aluminium and glass.
ifcopenshell.api.material.assign_material(model, products=[window_type], material=material_set)
"""
materials = list(material_list.Materials or [])
materials.append(material)
material_list.Materials = materials
@@ -0,0 +1,86 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
from typing import Optional
import ifcopenshell
def add_material(
file: ifcopenshell.file,
name: Optional[str] = None,
category: Optional[str] = None,
description: Optional[str] = None,
) -> ifcopenshell.entity_instance:
"""Adds a new material
A material in IFC represents a physical material, such as timber, steel,
concrete, aluminium, etc. It may also contain physical properties used
for structural or lighting simulation. Note that unlike the computer
graphics industry, a material by itself does not define any colour or
lighting information. Colours in IFC are known as "styles", and an IFC
material may or may not have any style information associated with it.
See ifcopenshell.api.style for more information.
A material is typically given a code name which is used by architects in
elevations and details when tagging finishes. Materials are also useful
to structural engineers in specifying the exact types of concrete and
steel to be used in structural simulations.
In addition, materials can belong to a category. Specifying this
category is critical to allow model recipients to make simple queries
like "show me all concrete / steel" elements in the model. Without
standardised category naming of all materials, this type of query
becomes a bespoke and inefficient task. A list of categories are:
'concrete', 'steel', 'aluminium', 'block', 'brick', 'stone', 'wood',
'glass', 'gypsum', 'plastic', and 'earth'. The user is allowed to
specify their own category instead if none of these categories are
appropriate.
Note that categories are not available in IFC2X3. This shortcoming is
one of the big reasons projects should upgrade to IFC4.
Additionally, a material's description provides more information beyond
its name or category.
:param name: The name of the material, typically tagged in a finishes
drawing or schedule.
:param category: The category of the material.
:param description: A description of the material.
:return: The newly created IfcMaterial
Example:
.. code:: python
# Let's create two materials with their respective categories
concrete = ifcopenshell.api.material.add_material(model, name="CON01", category="concrete", description="Garage Slab")
steel = ifcopenshell.api.material.add_material(model, name="ST01", category="steel", description="Corten Steel")
# Let's imagine an urban concrete bench which is purely made out of concrete
concrete_bench = ifcopenshell.api.root.create_entity(model, ifc_class="IfcFurnitureType")
# Assign the concrete material to that bench. Note that no colour
# "Style" has been specified.
ifcopenshell.api.material.assign_material(model, products=[concrete_bench], material=concrete)
"""
material = file.create_entity("IfcMaterial", **{"Name": name or "Unnamed"})
if category:
material.Category = category
if description:
material.Description = description
return material
@@ -0,0 +1,117 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
from typing import Literal
import ifcopenshell
MATERIAL_SET_TYPE = Literal[
"IfcMaterialLayerSet",
"IfcMaterialProfileSet",
"IfcMaterialConstituentSet",
"IfcMaterialList",
]
def add_material_set(
file: ifcopenshell.file, name: str = "Unnamed", set_type: MATERIAL_SET_TYPE = "IfcMaterialConstituentSet"
) -> ifcopenshell.entity_instance:
"""Adds a new material set
IFC allows you to state that objects are made out of multiple materials.
These are known generically as material sets, but may also be called
layered materials, composite materials, or other names in software.
There are three types of material sets:
- A layer set, used for layered construction such as walls, where the
element is parametrically made out of extruded layers, each layer
having a thickness defined. Even though this is known as a layer
"set" it is still recommended to use it for all standared layered
construction as it describes the intent of the element to be layered
construction and thus can be used for parametric editing.
- A profile set, used for profiled construction such as beams or
columns, where the element is parametrically made out of one or more
extruded profiles, where each profile may be parametric from a
standard section (e.g. standardised steel profile) or an arbitrary
shape (e.g. cold rolled sections, or skirtings, moldings, etc). Note
that even though this is called a profile "set", it should still be
used even if there is only a single profile. This is not available in
IFC2X3.
- A constituent set, used for arbitrary composite construction where
the object is made out of multiple materials. The constituents may be
explicitly defined via a shape, such as a window where the frame
geometry is made from one material and the panel geometry is made
from another material. Alternatively, the constituents may be
represented in terms of percentages, such as in mixtures like
concrete where there might be a percentage constituent of cement and
another percentage constituent of binder. This is not available in
IFC2X3.
There is also a fourth material set known as a material list, which is a
legacy type of set used by IFC2X3. It should not be used on IFC4 and
above, and constituent sets should be used instead.
:param name: The name of the material set, which may be purely
descriptive or annotated in drawings. Defaults to "Unnamed".
:param set_type: What type of set you want to create, chosen from
IfcMaterialLayerSet, IfcMaterialProfileSet,
IfcMaterialConstituentSet, or IfcMaterialList. Defaults to
IfcMaterialConstituentSet.
:return: The newly created material set element
Example:
.. code:: python
# Let's imagine we have a wall type that has two layers of
# gypsum with steel studs inside. Notice we are assigning to
# the type only, as all occurrences of that type will automatically
# inherit the material.
wall_type = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWallType", name="WAL01")
# First, let's create a material set. This will later be assigned
# to our wall type element.
material_set = ifcopenshell.api.material.add_material_set(model,
name="GYP-ST-GYP", set_type="IfcMaterialLayerSet")
# Let's create a few materials, it's important to also give them
# categories. This makes it easy for model recipients to do things
# like "show me everything made out of aluminium / concrete / steel
# / glass / etc". The IFC specification states a list of categories
# you can use.
gypsum = ifcopenshell.api.material.add_material(model, name="PB01", category="gypsum")
steel = ifcopenshell.api.material.add_material(model, name="ST01", category="steel")
# Now let's use those materials as three layers in our set, such
# that the steel studs are sandwiched by the gypsum. Let's imagine
# we're setting the layer thickness in millimeters.
layer = ifcopenshell.api.material.add_layer(model, layer_set=material_set, material=gypsum)
ifcopenshell.api.material.edit_layer(model, layer=layer, attributes={"LayerThickness": 13})
layer = ifcopenshell.api.material.add_layer(model, layer_set=material_set, material=steel)
ifcopenshell.api.material.edit_layer(model, layer=layer, attributes={"LayerThickness": 92})
layer = ifcopenshell.api.material.add_layer(model, layer_set=material_set, material=gypsum)
ifcopenshell.api.material.edit_layer(model, layer=layer, attributes={"LayerThickness": 13})
# Great! Let's assign our material set to our wall type.
ifcopenshell.api.material.assign_material(model, products=[wall_type], material=material_set)
"""
if set_type == "IfcMaterialLayerSet":
return file.create_entity("IfcMaterialLayerSet", LayerSetName=name or "Unnamed")
elif set_type == "IfcMaterialList":
return file.create_entity("IfcMaterialList")
return file.create_entity(set_type, Name=name or "Unnamed")
@@ -0,0 +1,101 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
from typing import Optional
import ifcopenshell
def add_profile(
file: ifcopenshell.file,
profile_set: ifcopenshell.entity_instance,
material: Optional[ifcopenshell.entity_instance] = None,
profile: Optional[ifcopenshell.entity_instance] = None,
name: Optional[str] = None,
) -> ifcopenshell.entity_instance:
"""Add a new profile item to a profile set
A profile item in a profile set represents an extruded 2D profile curve
that is extruded along the axis of the element. Most commonly there will
only be a single profile item in a profile set. For example, a beam will
have a material profile set containing a single profile item, which may
have a steel material and a I-beam shaped profile curve.
Note that the "profile item" represents a single extrusion in the
profile set, whereas the "profile curve" represents a 2D curve used by a
"profile item".
Profile is not optional for IfcMaterialProfile but it is optional for this API
call and can be assigned later with material.assign_profile.
In some cases, a profiled element (i.e. beam, column) may be a composite
beam or column and include multiple extrusions. This is rare. The order
of the profiles does not matter.
:param profile_set: The IfcMaterialProfileSet that the profile is part of. The
profile set represents a group of profile items. See
ifcopenshell.api.material.add_material_set for more information on
how to add a profile set.
:param material: The IfcMaterial that the profile item is made out of.
:param profile: The IfcProfileDef that represents the 2D cross section
of the the profile item.
:param name: An optional name of the material profile (not the geometric
profile).
:return: The newly created IfcMaterialProfile
Example:
.. code:: python
# Let's imagine we have a steel I-beam. Notice we are assigning to
# the type only, as all occurrences of that type will automatically
# inherit the material.
beam_type = ifcopenshell.api.root.create_entity(model, ifc_class="IfcBeamType", name="B1")
# First, let's create a material set. This will later be assigned
# to our beam type element.
material_set = ifcopenshell.api.material.add_material_set(model,
name="B1", set_type="IfcMaterialProfileSet")
# Create a steel material.
steel = ifcopenshell.api.material.add_material(model, name="ST01", category="steel")
# Create an I-beam profile curve. Notice how we name our profiles
# based on standardised steel profile names.
hea100 = file.create_entity(
"IfcIShapeProfileDef", ProfileName="HEA100", ProfileType="AREA",
OverallWidth=100, OverallDepth=96, WebThickness=5, FlangeThickness=8, FilletRadius=12,
)
# Define that steel material and cross section as a single profile
# item. If this were a composite beam, we might add multiple profile
# items instead, but this is rarely the case in most construction.
ifcopenshell.api.material.add_profile(model,
profile_set=material_set, material=steel, profile=hea100)
# Great! Let's assign our material set to our beam type.
ifcopenshell.api.material.assign_material(model, products=[beam_type], material=material_set)
"""
profiles = list(profile_set.MaterialProfiles or [])
mat_profile = file.create_entity("IfcMaterialProfile", Name=name)
if material:
mat_profile.Material = material
if profile:
mat_profile.Profile = profile
profiles.append(mat_profile)
profile_set.MaterialProfiles = profiles
return mat_profile
@@ -0,0 +1,364 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
from collections import defaultdict
from typing import Any, Optional, Union
import ifcopenshell
import ifcopenshell.api.material
import ifcopenshell.api.owner
import ifcopenshell.guid
import ifcopenshell.util.element
import ifcopenshell.util.representation
def assign_material(
file: ifcopenshell.file,
products: list[ifcopenshell.entity_instance],
type: ifcopenshell.util.element.MATERIAL_TYPE = "IfcMaterial",
material: Optional[ifcopenshell.entity_instance] = None,
) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance], None]:
"""Assigns a material to the list of products
Will unassign previously assigned material.
When a material is assigned to a product, it means that the product is
made out of that material. In its simplest form, a single material may
be assigned to a product, meaning that the entire product is made out of
that one material. Alternatively, a material set may be assigned to a
product, meaning that the product is made out of a set of materials.
There are three types of sets, including layered construction, profiled
materials, and arbitrary material constituents. See
ifcopenshell.api.material.add_material_set for details.
Materials are typically assigned to the element types rather than
individual occurrences of elements. Individual occurrences would then
inherit the material from the type.
If the type has a material set, then the geometry of the occurrences
must comply with the material set. For example, if the type has a
constituent set, then it is expected that all occurrences also inherit
the geometry of the type, which is made out of those constituents.
Alternatively, if the type has a layer set, then all occurrences must
have geometry that has a thickness equal to the sum of all layers. If a
type has a profile set, then all occurrences must has the same profile
extruded along its axis.
For layers and profiles assigned to types, the occurrences must be
assigned an IfcMaterialLayerSetUsage or an IfcMaterialProfileSetUsage.
This allows individual occurrences to override the layered or profiled
construction offset from a reference line.
:param products: The list of IfcProducts to assign the material or material set
to.
:param type: Choose from "IfcMaterial", "IfcMaterialConstituentSet",
"IfcMaterialLayerSet", "IfcMaterialLayerSetUsage",
"IfcMaterialProfileSet", "IfcMaterialProfileSetUsage", or
"IfcMaterialList". Note that "Set Usages" may only be assigned to
occurrences, not types. Defaults to "IfcMaterial".
:param material: The IfcMaterial or material set you are assigning here.
If type is Usage then no need to provide `material`, it will be deduced
from the element type automatically.
If IfcMaterial is provided as material and type is not IfcMaterial,
provided material will be ignored except for IfcMaterialList
where it will be used as part of the list.
:return: IfcRelAssociatesMaterial entity
or a list of IfcRelAssociatesMaterial entities
(possible if `type` is Usage
and `products` require different Usages)
or `None` if `products` was empty list.
Example:
.. code:: python
# Let's start with a simple concrete material
concrete = ifcopenshell.api.material.add_material(model, name="CON01", category="concrete")
# Let's imagine a concrete bench made out of a single concrete
# material. Let's assign it to the type.
bench_type = ifcopenshell.api.root.create_entity(model, ifc_class="IfcFurnitureType")
ifcopenshell.api.material.assign_material(model,
products=[bench_type], type="IfcMaterial", material=concrete)
# Let's imagine there are a two occurrences of this bench. It's not
# necessary to assign any material to these benches as they
# automatically inherit the material from the type.
bench1 = ifcopenshell.api.root.create_entity(model, ifc_class="IfcFurniture")
bench2 = ifcopenshell.api.root.create_entity(model, ifc_class="IfcFurniture")
ifcopenshell.api.type.assign_type(model, related_objects=[bench1], relating_type=bench_type)
ifcopenshell.api.type.assign_type(model, related_objects=[bench2], relating_type=bench_type)
# If we have a concrete wall, we should use a layer set. Again,
# let's start with a wall type, not occurrences.
wall_type = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWallType", name="WAL01")
# Even though there is only one layer in our layer set, we still use
# a layer set because it makes it clear that this is a layered
# construction. Let's say it's a 200mm thick concrete layer.
material_set = ifcopenshell.api.material.add_material_set(model,
name="CON200", set_type="IfcMaterialLayerSet")
layer = ifcopenshell.api.material.add_layer(model, layer_set=material_set, material=steel)
ifcopenshell.api.material.edit_layer(model, layer=layer, attributes={"LayerThickness": 200})
# Our wall type now has the layer set assigned to it
ifcopenshell.api.material.assign_material(model,
products=[wall_type], type="IfcMaterialLayerSet", material=material_set)
# Let's imagine an occurrence of this wall type.
wall = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall")
ifcopenshell.api.type.assign_type(model, related_objects=[wall], relating_type=wall_type)
# Our wall occurrence needs to have a "set usage" which describes
# how the layers relate to a reference line (typically a 2D line
# representing the extents of the wall). Usages are special since
# they automatically detect the inherited material set from the
# type. You'd write similar code for a profile set.
ifcopenshell.api.material.assign_material(model,
products=[wall], type="IfcMaterialLayerSetUsage")
# To be complete, let's create the wall's axis and body
# representation. Notice how the axis guides the walls "reference
# line" which determines where layers are extruded from, and the
# body has a thickness of 200mm, same as our total layer set
# thickness.
axis = ifcopenshell.api.geometry.add_axis_representation(model,
context=axis_context, axis=[(0.0, 0.0), (5000.0, 0.0)])
body = ifcopenshell.api.geometry.add_wall_representation(model,
context=body_context, length=5000, height=3000, thickness=200)
ifcopenshell.api.geometry.assign_representation(model, product=wall, representation=axis)
ifcopenshell.api.geometry.assign_representation(model, product=wall, representation=body)
ifcopenshell.api.geometry.edit_object_placement(model, product=wall)
"""
usecase = Usecase()
usecase.file = file
usecase.settings = {"products": products, "type": type, "material": material}
return usecase.execute()
class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self):
self.products: set[ifcopenshell.entity_instance] = set(self.settings["products"])
if not self.products:
return
# NOTE: we always reassign material, even if it might be assigned before
products_to_unassign_material = [p for p in self.products if ifcopenshell.util.element.get_material(p)]
if products_to_unassign_material:
ifcopenshell.api.material.unassign_material(self.file, products=products_to_unassign_material)
if self.settings["type"] == "IfcMaterial" or (
self.settings["material"]
and not self.settings["material"].is_a("IfcMaterial")
and not self.settings["type"].endswith("Usage")
):
return self.assign_ifc_material()
elif self.settings["type"] == "IfcMaterialConstituentSet":
material_set = self.file.create_entity(self.settings["type"])
return self.create_material_association(material_set)
elif self.settings["type"] == "IfcMaterialLayerSet":
material_set = self.file.create_entity(self.settings["type"])
return self.create_material_association(material_set)
elif self.settings["type"] == "IfcMaterialLayerSetUsage":
AXIS3_CLASSES = [
"IfcSlab",
"IfcSlabStandardCase",
"IfcSlabElementedCase",
"IfcRoof",
"IfcRamp",
"IfcPlate",
"IfcPlateStandardCase",
"IfcCovering",
"IfcFurniture",
]
provided_material_set = None
if self.settings["material"]:
provided_material_set = self.settings["material"]
material_set_class = provided_material_set.is_a()
assert (
material_set_class == "IfcMaterialLayerSet"
), f"{material_set_class} cannot be assiged as a IfcMaterialLayerSetUsage."
layer_types_to_products: defaultdict[
tuple[ifcopenshell.entity_instance, str], list[ifcopenshell.entity_instance]
]
layer_types_to_products = defaultdict(list)
types_to_material_sets: dict[Union[ifcopenshell.entity_instance, None], ifcopenshell.entity_instance]
types_to_material_sets = {}
for product in self.products:
# Figure what material set to assign.
if provided_material_set is not None:
material_set = provided_material_set
else:
# If material set is not provided, derive it from the type.
element_type = ifcopenshell.util.element.get_type(product)
if element_type in types_to_material_sets:
material_set = types_to_material_sets[element_type]
else:
element_type_material = None
if element_type is not None:
element_type_material = ifcopenshell.util.element.get_material(element_type)
if element_type_material and element_type_material.is_a("IfcMaterialLayerSet"):
material_set = element_type_material
else:
material_set = self.file.create_entity("IfcMaterialLayerSet")
layer_set_direction = "AXIS3" if product.is_a() in AXIS3_CLASSES else "AXIS2"
material_layer_type = (material_set, layer_set_direction)
layer_types_to_products[material_layer_type].append(product)
rels = [
self.create_layer_set_usage(material_set, layer_set_direction, products)
for (material_set, layer_set_direction), products in layer_types_to_products.items()
]
return rels[0] if len(rels) == 1 else rels
elif self.settings["type"] == "IfcMaterialProfileSet":
material_set = self.file.create_entity(self.settings["type"])
return self.create_material_association(material_set)
elif self.settings["type"] == "IfcMaterialProfileSetUsage":
provided_material_set = None
if self.settings["material"]:
provided_material_set = self.settings["material"]
material_set_class = provided_material_set.is_a()
assert (
material_set_class == "IfcMaterialProfileSet"
), f"{material_set_class} cannot be assiged as a IfcMaterialProfileSetUsage."
material_sets_to_products: dict[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance]]
material_sets_to_products = defaultdict(list)
types_to_material_sets: dict[Union[ifcopenshell.entity_instance, None], ifcopenshell.entity_instance]
types_to_material_sets = {}
for product in self.products:
# Figure what material set to assign.
if provided_material_set is not None:
material_set = provided_material_set
else:
# If material set is not provided, derive it from the type.
element_type = ifcopenshell.util.element.get_type(product)
if element_type in types_to_material_sets:
material_set = types_to_material_sets[element_type]
else:
element_type_material = None
if element_type is not None:
element_type_material = ifcopenshell.util.element.get_material(element_type)
if element_type_material and element_type_material.is_a("IfcMaterialProfileSet"):
material_set = element_type_material
else:
material_set = self.file.create_entity("IfcMaterialProfileSet")
material_sets_to_products[material_set].append(product)
rels: list[ifcopenshell.entity_instance] = []
for material_set, products in material_sets_to_products.items():
self.update_representation_profile(material_set, products)
material_set_usage = self.create_profile_set_usage(material_set)
rels.append(self.create_material_association(material_set_usage, products))
return rels[0] if len(rels) == 1 else rels
elif self.settings["type"] == "IfcMaterialList":
material_set = self.file.create_entity(self.settings["type"])
material_set.Materials = [self.settings["material"]]
return self.create_material_association(material_set)
def update_representation_profile(
self, material_set: ifcopenshell.entity_instance, products: list[ifcopenshell.entity_instance]
) -> None:
profile = material_set.CompositeProfile
if not profile and material_set.MaterialProfiles:
profile = material_set.MaterialProfiles[0].Profile
if not profile:
return
for product in products:
representation = ifcopenshell.util.representation.get_representation(product, "Model", "Body", "MODEL_VIEW")
if not representation:
return
for subelement in self.file.traverse(representation):
if subelement.is_a("IfcSweptAreaSolid"):
subelement.SweptArea = profile
def create_layer_set_usage(
self,
material_set: ifcopenshell.entity_instance,
layer_set_direction: str,
products: list[ifcopenshell.entity_instance],
) -> ifcopenshell.entity_instance:
usage = self.file.create_entity(
"IfcMaterialLayerSetUsage",
**{
"ForLayerSet": material_set,
"LayerSetDirection": layer_set_direction,
"DirectionSense": "POSITIVE",
"OffsetFromReferenceLine": 0,
},
)
return self.create_material_association(usage, products)
def create_profile_set_usage(self, material_set: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
return self.file.create_entity("IfcMaterialProfileSetUsage", **{"ForProfileSet": material_set})
def assign_ifc_material(self) -> ifcopenshell.entity_instance:
material = self.settings["material"] or self.file.create_entity("IfcMaterial")
rel = self.get_rel_associates_material(material)
if not rel:
return self.create_material_association(material)
previous_related_objects = set(rel.RelatedObjects)
rel.RelatedObjects = list(previous_related_objects | self.products)
ifcopenshell.api.owner.update_owner_history(self.file, element=rel)
return rel
def create_material_association(
self,
relating_material: ifcopenshell.entity_instance,
products: Optional[list[ifcopenshell.entity_instance]] = None,
) -> ifcopenshell.entity_instance:
if products is None:
products = list(self.products)
return self.file.create_entity(
"IfcRelAssociatesMaterial",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(self.file),
"RelatedObjects": products,
"RelatingMaterial": relating_material,
},
)
def get_rel_associates_material(
self, material: ifcopenshell.entity_instance
) -> Union[ifcopenshell.entity_instance, None]:
if self.file.schema == "IFC2X3" or material.is_a("IfcMaterialList"):
return next(
(
r
for r in self.file.by_type("IfcRelAssociatesMaterial")
if r.RelatingMaterial == self.settings["material"]
),
None,
)
return next(iter(material.AssociatedTo), None)
@@ -0,0 +1,127 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import ifcopenshell.util.representation
def assign_profile(
file: ifcopenshell.file, material_profile: ifcopenshell.entity_instance, profile: ifcopenshell.entity_instance
) -> None:
"""Changes the profile curve of a material profile item in a profile set
In addition to changing the profile curve, it will also change the
profile curve used in any body representation extrusions.
:param material_profile: The IfcMaterialProfile to change the profile
curve of. See ifcopenshell.api.material.add_profile to see how to
create profiles.
:param profile: The IfcProfileDef to set the profile item's curve to.
:return: None
Example:
.. code:: python
# Let's imagine we have a steel I-beam. Notice we are assigning to
# the type only, as all occurrences of that type will automatically
# inherit the material.
beam_type = ifcopenshell.api.root.create_entity(model, ifc_class="IfcBeamType", name="B1")
# First, let's create a material set. This will later be assigned
# to our beam type element.
material_set = ifcopenshell.api.material.add_material_set(model,
name="B1", set_type="IfcMaterialProfileSet")
# Create a steel material.
steel = ifcopenshell.api.material.add_material(model, name="ST01", category="steel")
# Create an I-beam profile curve. Notice how we name our profiles
# based on standardised steel profile names.
hea100 = model.create_entity(
"IfcIShapeProfileDef", ProfileName="HEA100", ProfileType="AREA",
OverallWidth=100, OverallDepth=96, WebThickness=5, FlangeThickness=8, FilletRadius=12,
)
# Define that steel material and cross section as a single profile
# item. If this were a composite beam, we might add multiple profile
# items instead, but this is rarely the case in most construction.
profile_item = ifcopenshell.api.material.add_profile(model,
profile_set=material_set, material=steel, profile=hea100)
# Great! Let's assign our material set to our beam type.
ifcopenshell.api.material.assign_material(model, products=[beam_type], material=material_set)
# Let's create an occurrence of this beam.
beam = ifcopenshell.api.root.create_entity(model, ifc_class="IfcBeam", name="B1.01")
ifcopenshell.api.material.assign_material(model,
products=[beam], type="IfcMaterialProfileSetUsage")
# Let's give a 1000mm long beam body representation.
body = ifcopenshell.api.geometry.add_profile_representation(
context=body_context, profile=hea100, depth=1000)
ifcopenshell.api.geometry.assign_representation(model, product=beam, representation=body)
ifcopenshell.api.geometry.edit_object_placement(model, product=beam)
# Now let's change the profile to a HEA200 standard profile instead.
# This will automatically change the body representation that we
# just added as well to a HEA200 profile.
hea200 = model.create_entity(
"IfcIShapeProfileDef", ProfileName="HEA200", ProfileType="AREA",
OverallWidth=200, OverallDepth=190, WebThickness=6.5, FlangeThickness=10, FilletRadius=18,
)
ifcopenshell.api.material.assign_profile(model, material_profile=profile_item, profile=hea200)
"""
usecase = Usecase()
usecase.file = file
return usecase.execute(material_profile, profile)
class Usecase:
file: ifcopenshell.file
def execute(self, material_profile: ifcopenshell.entity_instance, profile: ifcopenshell.entity_instance) -> None:
# TODO: handle composite profiles
old_profile = material_profile.Profile
material_profile.Profile = profile
for profile_set in material_profile.ToMaterialProfileSet:
for inverse in self.file.get_inverse(profile_set):
if not inverse.is_a("IfcMaterialProfileSetUsage"):
continue
if self.file.schema == "IFC2X3":
for rel in self.file.get_inverse(inverse):
if not rel.is_a("IfcRelAssociatesMaterial"):
continue
for element in rel.RelatedObjects:
self.change_profile(element, profile)
else:
for rel in inverse.AssociatedTo:
for element in rel.RelatedObjects:
self.change_profile(element, profile)
if old_profile and self.file.get_total_inverses(old_profile) == 0:
# TODO: check remove deep
self.file.remove(old_profile)
def change_profile(self, element: ifcopenshell.entity_instance, profile: ifcopenshell.entity_instance) -> None:
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if not representation:
return
for subelement in self.file.traverse(representation):
if subelement.is_a("IfcSweptAreaSolid"):
subelement.SweptArea = profile
@@ -0,0 +1,103 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.util.element
def copy_material(file: ifcopenshell.file, material: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
"""Copies a material or material set
All material psets and styles are copied. The copied material is not
associated to any elements.
If a material set is copied, the set items are also copied. However the
underlying materials (and profiles) used within the set items are reused.
If a material is associated with a presentation style, that presentation
style is reused.
:param material: The IfcMaterialDefinition to copy
:return: The new copy of the material
Example:
.. code:: python
concrete = ifcopenshell.api.material.add_material(model, name="CON01", category="concrete")
# Let's duplicate the concrete material
concrete_copy = ifcopenshell.api.material.copy_material(model, material=concrete)
"""
if material.is_a("IfcMaterial"):
return _copy_material_with_inverses(file, material)
elif material.is_a("IfcMaterialConstituentSet"):
new = _copy_material_with_inverses(file, material)
new.MaterialConstituents = [copy_material(file, i) for i in material.MaterialConstituents]
return new
elif material.is_a("IfcMaterialConstituent"):
return _copy_material_with_inverses(file, material)
elif material.is_a("IfcMaterialLayerSet"):
new = _copy_material_with_inverses(file, material)
new.MaterialLayers = [copy_material(file, i) for i in material.MaterialLayers]
return new
elif material.is_a("IfcMaterialLayer"):
return _copy_material_with_inverses(file, material)
elif material.is_a("IfcMaterialProfileSet"):
new = _copy_material_with_inverses(file, material)
new.MaterialProfiles = [copy_material(file, i) for i in material.MaterialProfiles]
return new
elif material.is_a("IfcMaterialProfile"):
return _copy_material_with_inverses(file, material)
elif material.is_a("IfcMaterialList"):
return _copy_material_with_inverses(file, material)
else:
raise Exception(f"Unexpected material type: '{material.is_a()}' ({material}).")
def _copy_material_with_inverses(
file: ifcopenshell.file, material: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
new = ifcopenshell.util.element.copy(file, material)
for inverse in file.get_inverse(material):
if inverse.is_a("IfcMaterialProperties"):
# Properties must not be shared between objects for convenience of authoring
inverse = ifcopenshell.util.element.copy(file, inverse)
inverse.Material = new
props_attribute = "Properties"
if file.schema == "IFC2X3":
if not inverse.is_a("IfcExtendedMaterialProperties"):
continue
props_attribute = "ExtendedProperties"
props = getattr(inverse, props_attribute)
if not props:
continue
copied_props = []
for pset in props:
copied_props.append(ifcopenshell.util.element.copy_deep(file, pset))
setattr(inverse, props_attribute, copied_props)
elif inverse.is_a("IfcMaterialDefinitionRepresentation"):
inverse = ifcopenshell.util.element.copy_deep(
file, inverse, exclude=["IfcRepresentationContext", "IfcMaterial", "IfcPresentationStyle"]
)
inverse.RepresentedMaterial = new
return new
@@ -0,0 +1,44 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
from typing import Any
import ifcopenshell
def edit_assigned_material(
file: ifcopenshell.file, element: ifcopenshell.entity_instance, attributes: dict[str, Any]
) -> None:
"""Edits the attributes of an IfcMaterial
For more information about the attributes and data types of an
IfcMaterial, consult the IFC documentation.
:param element: The IfcMaterial entity you want to edit
:param attributes: a dictionary of attribute names and values.
:return: None
Example:
.. code:: python
concrete = ifcopenshell.api.material.add_material(model, name="CON01", category="concrete")
ifcopenshell.api.material.edit_assigned_material(model,
element=concrete, attributes={"Description": "40MPA concrete with broom finish"})
"""
for name, value in attributes.items():
setattr(element, name, value)
@@ -0,0 +1,67 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
from typing import Any, Optional
import ifcopenshell
def edit_constituent(
file: ifcopenshell.file,
constituent: ifcopenshell.entity_instance,
attributes: Optional[dict[str, Any]] = None,
material: Optional[ifcopenshell.entity_instance] = None,
) -> None:
"""Edits the attributes of an IfcMaterialConstituent
For more information about the attributes and data types of an
IfcMaterialConstituent, consult the IFC documentation.
:param constituent: The IfcMaterialConstituent entity you want to edit
:param attributes: a dictionary of attribute names and values.
:param material: The IfcMaterial entity you want to change the constituent to
:return: None
Example:
.. code:: python
# Let's add two materials
aluminium1 = ifcopenshell.api.material.add_material(model, name="AL01", category="aluminium")
aluminium2 = ifcopenshell.api.material.add_material(model, name="AL02", category="aluminium")
glass = ifcopenshell.api.material.add_material(model, name="GLZ01", category="glass")
material_set = ifcopenshell.api.material.add_material_set(model,
name="Window", set_type="IfcMaterialConstituentSet")
# Set up two constituents, one for the frame and the other for the glazing.
framing = ifcopenshell.api.material.add_constituent(model,
constituent_set=material_set, material=aluminium1)
glazing = ifcopenshell.api.material.add_constituent(model,
constituent_set=material_set, material=glass)
# Let's make sure this constituent refers to the framing of the
# window and uses the second aluminium material instead.
ifcopenshell.api.material.edit_constituent(model,
constituent=framing, attributes={"Name": "Framing"}, material=aluminium2)
ifcopenshell.api.material.edit_constituent(model,
constituent=constituent, attributes={"Name": "Glazing"})
"""
for name, value in (attributes or {}).items():
setattr(constituent, name, value)
constituent.Material = material
@@ -0,0 +1,66 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
from typing import Any, Optional
import ifcopenshell
def edit_layer(
file: ifcopenshell.file,
layer: ifcopenshell.entity_instance,
attributes: Optional[dict[str, Any]] = None,
material: Optional[ifcopenshell.entity_instance] = None,
) -> None:
"""Edits the attributes of an IfcMaterialLayer
For more information about the attributes and data types of an
IfcMaterialLayer, consult the IFC documentation.
:param layer: The IfcMaterialLayer entity you want to edit
:param attributes: a dictionary of attribute names and values.
:param material: The IfcMaterial entity you want the layer to be made
from.
:return: None
Example:
.. code:: python
# Let's create two materials typically used for steel stud partition
# walls with gypsum lining.
gypsum = ifcopenshell.api.material.add_material(model, name="PB01", category="gypsum")
steel = ifcopenshell.api.material.add_material(model, name="ST01", category="steel")
# Create a material layer set to contain our layers.
material_set = ifcopenshell.api.material.add_material_set(model,
name="GYP-ST-GYP", set_type="IfcMaterialLayerSet")
# Now let's use those materials as three layers in our set, such
# that the steel studs are sandwiched by the gypsum. Let's imagine
# we're setting the layer thickness in millimeters.
layer = ifcopenshell.api.material.add_layer(model, layer_set=material_set, material=gypsum)
ifcopenshell.api.material.edit_layer(model, layer=layer, attributes={"LayerThickness": 13})
layer = ifcopenshell.api.material.add_layer(model, layer_set=material_set, material=steel)
ifcopenshell.api.material.edit_layer(model, layer=layer, attributes={"LayerThickness": 92})
layer = ifcopenshell.api.material.add_layer(model, layer_set=material_set, material=gypsum)
ifcopenshell.api.material.edit_layer(model, layer=layer, attributes={"LayerThickness": 13})
"""
for name, value in (attributes or {}).items():
setattr(layer, name, value)
if material:
layer.Material = material
@@ -0,0 +1,77 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
from typing import Any
import ifcopenshell
def edit_layer_usage(file: ifcopenshell.file, usage: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
"""Edits the attributes of an IfcMaterialLayerSetUsage
This is typically used to change the offset from the reference line to
the layers.
For more information about the attributes and data types of an
IfcMaterialLayerSetUsage, consult the IFC documentation.
:param usage: The IfcMaterialLayerSetUsage entity you want to edit
:param attributes: a dictionary of attribute names and values.
:return: None
Example:
.. code:: python
# Let's start with a simple concrete material
concrete = ifcopenshell.api.material.add_material(model, name="CON01", category="concrete")
# If we have a concrete wall, we should use a layer set. Again,
# let's start with a wall type, not occurrences.
wall_type = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWallType", name="WAL01")
# Even though there is only one layer in our layer set, we still use
# a layer set because it makes it clear that this is a layered
# construction. Let's say it's a 200mm thick concrete layer.
material_set = ifcopenshell.api.material.add_material_set(model,
name="CON200", set_type="IfcMaterialLayerSet")
layer = ifcopenshell.api.material.add_layer(model, layer_set=material_set, material=steel)
ifcopenshell.api.material.edit_layer(model, layer=layer, attributes={"LayerThickness": 200})
# Our wall type now has the layer set assigned to it
ifcopenshell.api.material.assign_material(model,
products=[wall_type], type="IfcMaterialLayerSet", material=material_set)
# Let's imagine an occurrence of this wall type.
wall = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall")
ifcopenshell.api.type.assign_type(model, related_objects=[wall], relating_type=wall_type)
# Our wall occurrence needs to have a "set usage" which describes
# how the layers relate to a reference line (typically a 2D line
# representing the extents of the wall). Usages are special since
# they automatically detect the inherited material set from the
# type. You'd write similar code for a profile set.
rel = ifcopenshell.api.material.assign_material(model,
products=[wall], type="IfcMaterialLayerSetUsage")
# Let's change the offset from the reference line to be 200mm
# instead of the default of 0mm.
ifcopenshell.api.material.edit_layer_usage(model,
usage=rel.RelatingMaterial, attributes={"OffsetFromReferenceLine": 200})
"""
for name, value in attributes.items():
setattr(usage, name, value)
@@ -0,0 +1,27 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
from typing import Any
import ifcopenshell
def edit_material(file: ifcopenshell.file, material: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
"""Edits the attributes of an IfcMaterial"""
for name, value in attributes.items():
setattr(material, name, value)
@@ -0,0 +1,84 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021, 2022 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
from typing import Any, Optional
import ifcopenshell
def edit_profile(
file: ifcopenshell.file,
profile: ifcopenshell.entity_instance,
attributes: Optional[dict[str, Any]] = None,
profile_def: Optional[ifcopenshell.entity_instance] = None,
material: Optional[ifcopenshell.entity_instance] = None,
) -> None:
"""Edits the attributes of an IfcMaterialProfile
For more information about the attributes and data types of an
IfcMaterialProfile, consult the IFC documentation.
:param profile: The IfcMaterialProfile entity you want to edit
:param attributes: a dictionary of attribute names and values.
:param profile_def: The IfcProfileDef entity the profile curve should be
extruded from.
:param material: The IfcMaterial entity you want to change the profile
to be made from.
:return: None
Example:
.. code:: python
# Let's create a material set to store our profiles.
material_set = ifcopenshell.api.material.add_material_set(model,
name="B1", set_type="IfcMaterialProfileSet")
# Create a couple steel materials.
steel1 = ifcopenshell.api.material.add_material(model, name="ST01", category="steel")
steel2 = ifcopenshell.api.material.add_material(model, name="ST01", category="steel")
# Create some I-shaped profiles. Notice how we name our profiles based
# on standardised steel profile names.
hea100 = file.create_entity(
"IfcIShapeProfileDef", ProfileName="HEA100", ProfileType="AREA",
OverallWidth=100, OverallDepth=96, WebThickness=5, FlangeThickness=8, FilletRadius=12,
)
hea200 = file.create_entity(
"IfcIShapeProfileDef", ProfileName="HEA200", ProfileType="AREA",
OverallWidth=200, OverallDepth=190, WebThickness=6.5, FlangeThickness=10, FilletRadius=18,
)
# Define that steel material and cross section as a single profile
# item. If this were a composite beam, we might add multiple profile
# items instead, but this is rarely the case in most construction.
profile_item = ifcopenshell.api.material.add_profile(model,
profile_set=material_set, material=steel1, profile=hea100)
# Edit our profile item to use a HEA200 profile instead made out of
# another type of steel.
ifcopenshell.api.material.edit_profile(model,
profile=profile_item, profile_def=hea200, material=steel2)
"""
for name, value in (attributes or {}).items():
setattr(profile, name, value)
if material:
profile.Material = material
if profile_def:
profile.Profile = profile_def
@@ -0,0 +1,223 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
from typing import Any
import ifcopenshell.geom
import ifcopenshell.util.representation
import ifcopenshell.util.shape
from ifcopenshell.geom import ShapeType
def edit_profile_usage(
file: ifcopenshell.file, usage: ifcopenshell.entity_instance, attributes: dict[str, Any]
) -> None:
"""Edits the attributes of an IfcMaterialProfileSetUsage
This is typically used to change the cardinal point of the profile.
The cardinal point represents whether the profile is extruded along the
center of the axis line, at a corner, at a shear center, at the bottom,
top, etc.
For more information about the attributes and data types of an
IfcMaterialProfileSetUsage, consult the IFC documentation.
:param usage: The IfcMaterialProfileSetUsage entity you want to edit
:param attributes: a dictionary of attribute names and values.
:return: None
Example:
.. code:: python
# Let's imagine we have a steel I-beam. Notice we are assigning to
# the type only, as all occurrences of that type will automatically
# inherit the material.
beam_type = ifcopenshell.api.root.create_entity(model, ifc_class="IfcBeamType", name="B1")
# First, let's create a material set. This will later be assigned
# to our beam type element.
material_set = ifcopenshell.api.material.add_material_set(model,
name="B1", set_type="IfcMaterialProfileSet")
# Create a steel material.
steel = ifcopenshell.api.material.add_material(model, name="ST01", category="steel")
# Create an I-beam profile curve. Notice how we name our profiles
# based on standardised steel profile names.
hea100 = model.create_entity(
"IfcIShapeProfileDef", ProfileName="HEA100", ProfileType="AREA",
OverallWidth=100, OverallDepth=96, WebThickness=5, FlangeThickness=8, FilletRadius=12,
)
# Define that steel material and cross section as a single profile
# item. If this were a composite beam, we might add multiple profile
# items instead, but this is rarely the case in most construction.
profile_item = ifcopenshell.api.material.add_profile(model,
profile_set=material_set, material=steel, profile=hea100)
# Great! Let's assign our material set to our beam type.
ifcopenshell.api.material.assign_material(model, products=[beam_type], material=material_set)
# Let's create an occurrence of this beam.
beam = ifcopenshell.api.root.create_entity(model, ifc_class="IfcBeam", name="B1.01")
rel = ifcopenshell.api.material.assign_material(model, material=material_set,
products=[beam], type="IfcMaterialProfileSetUsage")
# Let's give a 1000mm long beam body representation.
body = ifcopenshell.api.geometry.add_profile_representation(
context=body_context, profile=hea100, depth=1000)
ifcopenshell.api.geometry.assign_representation(model, product=beam, representation=body)
ifcopenshell.api.geometry.edit_object_placement(model, product=beam)
# Let's change the cardinal point to be the top center of the axis
# line. This is represented by the number "8". Consult the IFC
# documentation for all the numbers you can use.
ifcopenshell.api.material.edit_profile_usage(model,
usage=rel.RelatingMaterial, attributes={"CardinalPoint": 8})
"""
usecase = Usecase()
usecase.file = file
return usecase.execute(usage, attributes)
class Usecase:
file: ifcopenshell.file
def execute(self, usage: ifcopenshell.entity_instance, attributes: dict[str, Any]) -> None:
self.usage = usage
self.attributes = attributes
self.cardinal_point = attributes.get("CardinalPoint")
if self.cardinal_point and self.cardinal_point != usage.CardinalPoint:
self.update_cardinal_point()
for name, value in attributes.items():
setattr(usage, name, value)
def update_cardinal_point(self):
material_set = self.usage.ForProfileSet
self.profile = material_set.CompositeProfile
if not self.profile and material_set.MaterialProfiles:
self.profile = material_set.MaterialProfiles[0].Profile
if not self.profile:
return
self.position = self.calculate_position()
if self.file.schema == "IFC2X3":
for rel in self.file.get_inverse(self.usage):
if not rel.is_a("IfcRelAssociatesMaterial"):
continue
for element in rel.RelatedObjects:
self.update_representation(element)
else:
for rel in self.usage.AssociatedTo:
for element in rel.RelatedObjects:
self.update_representation(element)
def calculate_position(self):
self.dummy = ifcopenshell.file(schema=self.file.schema)
dummy_profile = self.dummy.add(self.profile)
# We clear all radiuses so that we can calculate geometric centroid easily
for i, attribute in enumerate(dummy_profile):
name = dummy_profile.attribute_name(i)
if "Radius" in name and name != "RoundingRadius":
dummy_profile[i] = None
dummy_solid = self.dummy.create_entity(
"IfcExtrudedAreaSolid",
**{
"SweptArea": dummy_profile,
"ExtrudedDirection": self.dummy.createIfcDirection((0.0, 0.0, 1.0)),
"Depth": 1,
}
)
self.settings_2d = ifcopenshell.geom.settings()
self.settings_2d.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
shape = ifcopenshell.geom.create_shape(self.settings_2d, dummy_solid)
# NOTE: points do not need unit conversion
# as dummy file is inherently using project units.
if self.cardinal_point == 1:
return self.get_bottom_left(shape)
elif self.cardinal_point == 2:
return self.get_bottom_centre(shape)
elif self.cardinal_point == 3:
return self.get_bottom_right(shape)
elif self.cardinal_point == 4:
return self.get_mid_depth_left(shape)
elif self.cardinal_point == 5:
return self.get_mid_depth_centre(shape)
elif self.cardinal_point == 6:
return self.get_mid_depth_right(shape)
elif self.cardinal_point == 7:
return self.get_top_left(shape)
elif self.cardinal_point == 8:
return self.get_top_centre(shape)
elif self.cardinal_point == 9:
return self.get_top_right(shape)
def get_bottom_left(self, shape: ShapeType) -> ifcopenshell.entity_instance:
width = ifcopenshell.util.shape.get_x(shape)
height = ifcopenshell.util.shape.get_y(shape)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width / 2, height / 2, 0.0)))
def get_bottom_centre(self, shape: ShapeType) -> ifcopenshell.entity_instance:
height = ifcopenshell.util.shape.get_y(shape)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0.0, height / 2, 0.0)))
def get_bottom_right(self, shape: ShapeType) -> ifcopenshell.entity_instance:
width = ifcopenshell.util.shape.get_x(shape)
height = ifcopenshell.util.shape.get_y(shape)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width / 2, height / 2, 0.0)))
def get_mid_depth_left(self, shape: ShapeType) -> ifcopenshell.entity_instance:
width = ifcopenshell.util.shape.get_x(shape)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width / 2, 0.0, 0.0)))
def get_mid_depth_centre(self, shape: ShapeType) -> ifcopenshell.entity_instance:
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)))
def get_mid_depth_right(self, shape: ShapeType) -> ifcopenshell.entity_instance:
width = ifcopenshell.util.shape.get_x(shape)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width / 2, 0.0, 0.0)))
def get_top_left(self, shape: ShapeType) -> ifcopenshell.entity_instance:
width = ifcopenshell.util.shape.get_x(shape)
height = ifcopenshell.util.shape.get_y(shape)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((-width / 2, -height / 2, 0.0)))
def get_top_centre(self, shape: ShapeType) -> ifcopenshell.entity_instance:
height = ifcopenshell.util.shape.get_y(shape)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((0.0, -height / 2, 0.0)))
def get_top_right(self, shape: ShapeType) -> ifcopenshell.entity_instance:
width = ifcopenshell.util.shape.get_x(shape)
height = ifcopenshell.util.shape.get_y(shape)
return self.file.createIfcAxis2Placement3D(self.file.createIfcCartesianPoint((width / 2, -height / 2, 0.0)))
def update_representation(self, element: ifcopenshell.entity_instance) -> None:
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if not representation:
return
for subelement in self.file.traverse(representation):
if subelement.is_a("IfcSweptAreaSolid") and subelement.SweptArea == self.profile:
self.update_swept_area_solid(subelement)
def update_swept_area_solid(self, element: ifcopenshell.entity_instance) -> None:
element.Position = self.position
@@ -0,0 +1,58 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.util.element
def remove_constituent(
file: ifcopenshell.file, constituent: ifcopenshell.entity_instance, should_remove_material: bool = False
) -> None:
"""Removes a constituent from a constituent set
Note that it is invalid to have zero items in a set, so you should leave
at least one constituent to ensure a valid IFC dataset.
:param constituent: The IfcMaterialConstituent entity you want to remove
:param should_remove_material: If true, materials with no users will be removed
Example:
.. code:: python
# Create a material set for windows made out of aluminium and glass.
material_set = ifcopenshell.api.material.add_material_set(model,
name="Window", set_type="IfcMaterialConstituentSet")
aluminium = ifcopenshell.api.material.add_material(model, name="AL01", category="aluminium")
glass = ifcopenshell.api.material.add_material(model, name="GLZ01", category="glass")
# Now let's use those materials as two constituents in our set.
framing = ifcopenshell.api.material.add_constituent(model,
constituent_set=material_set, material=aluminium)
glazing = ifcopenshell.api.material.add_constituent(model,
constituent_set=material_set, material=glass)
# Let's remove the glass constituent. Note that we should not remove
# the framing, at this would mean there are no constituents which is
# invalid.
ifcopenshell.api.material.remove_constituent(model, constituent=glazing)
"""
material = constituent.Material
file.remove(constituent)
if material and should_remove_material:
ifcopenshell.util.element.remove_deep2(file, material)
@@ -0,0 +1,60 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import ifcopenshell.util.element
def remove_layer(
file: ifcopenshell.file, layer: ifcopenshell.entity_instance, should_remove_material: bool = False
) -> None:
"""Removes a layer from a layer set
Note that it is invalid to have zero items in a set, so you should leave
at least one layer to ensure a valid IFC dataset.
:param layer: The IfcMaterialLayer entity you want to remove
:param should_remove_material: If true, materials with no users will be removed
Example:
.. code:: python
# Create a material set for steel stud partition walls.
material_set = ifcopenshell.api.material.add_material_set(model,
name="Window", set_type="IfcMaterialConstituentSet")
gypsum = ifcopenshell.api.material.add_material(model, name="PB01", category="gypsum")
steel = ifcopenshell.api.material.add_material(model, name="ST01", category="steel")
# Now let's use those materials as three layers in our set, such
# that the steel studs are sandwiched by the gypsum. Let's imagine
# we're setting the layer thickness in millimeters.
layer1 = ifcopenshell.api.material.add_layer(model, layer_set=material_set, material=gypsum)
ifcopenshell.api.material.edit_layer(model, layer=layer1, attributes={"LayerThickness": 13})
layer2 = ifcopenshell.api.material.add_layer(model, layer_set=material_set, material=steel)
ifcopenshell.api.material.edit_layer(model, layer=layer2, attributes={"LayerThickness": 92})
layer3 = ifcopenshell.api.material.add_layer(model, layer_set=material_set, material=gypsum)
ifcopenshell.api.material.edit_layer(model, layer=layer3, attributes={"LayerThickness": 13})
# Let's remove the last layer, such that the wall might be clad only
# one one side such as to line a services riser.
ifcopenshell.api.material.remove_layer(model, layer=layer3)
"""
material = layer.Material
file.remove(layer)
if material and should_remove_material:
ifcopenshell.util.element.remove_deep2(file, material)
@@ -0,0 +1,56 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import ifcopenshell
def remove_list_item(
file: ifcopenshell.file, material_list: ifcopenshell.entity_instance, material_index: int = 0
) -> None:
"""Removes an item in an material list
Note that it is invalid to have zero items in a list, so you should leave
at least one item to ensure a valid IFC dataset.
:param material_list: The IfcMaterialList entity you want to remove an
item from.
:param material_index: The index of the material you want to remove from
the list. Starts counting at 0. Defaults to 0.
:return: None
Example:
.. code:: python
# Create a material list for aluminium windows.
material_set = ifcopenshell.api.material.add_material_set(model,
name="Window", set_type="IfcMaterialMaterialList")
aluminium = ifcopenshell.api.material.add_material(model, name="AL01", category="aluminium")
glass = ifcopenshell.api.material.add_material(model, name="GLZ01", category="glass")
# Now let's use those materials as two items in our list.
ifcopenshell.api.material.add_list_item(model, material_list=material_set, material=aluminium)
ifcopenshell.api.material.add_list_item(model, material_list=material_set, material=glass)
# Let's remove the glass
ifcopenshell.api.material.remove_list_item(model, material_list=material_set, material_index=1)
"""
materials = list(material_list.Materials)
materials.pop(material_index)
material_list.Materials = materials
@@ -0,0 +1,75 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.util.element
def remove_material(file: ifcopenshell.file, material: ifcopenshell.entity_instance) -> None:
"""Removes a material
If the material is used in a material set, the corresponding layer,
profile, or constituent is also removed. Note that this may result in a
material set with zero items in it, which is invalid, so the user must
take care of this situation themselves.
:param material: The IfcMaterial entity you want to remove
:return: None
Example:
.. code:: python
# Create a material
aluminium = ifcopenshell.api.material.add_material(model, name="AL01", category="aluminium")
# ... and remove it
ifcopenshell.api.material.remove_material(model, material=aluminium)
"""
inverse_elements = file.get_inverse(material)
file.remove(material)
# TODO: Right now, we we choose only to delete set items (e.g. a layer) but not the material set
# This can lead to invalid material sets, but we assume the user will deal with it
for inverse in inverse_elements:
if inverse.is_a("IfcMaterialConstituent"):
file.remove(inverse)
elif inverse.is_a("IfcMaterialLayer"):
file.remove(inverse)
elif inverse.is_a("IfcMaterialProfile"):
file.remove(inverse)
elif inverse.is_a("IfcRelAssociatesMaterial"):
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcMaterialProperties"):
if file.schema != "IFC2X3":
props = inverse.Properties
else:
# only IfcExtendedMaterialProperties have properties in IFC2X3
props = getattr(inverse, "ExtendedProperties", None)
props = props or []
for prop in props:
file.remove(prop)
file.remove(inverse)
elif inverse.is_a("IfcMaterialDefinitionRepresentation"):
for representation in inverse.Representations:
for item in representation.Items:
file.remove(item)
file.remove(representation)
file.remove(inverse)
@@ -0,0 +1,94 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api.material
import ifcopenshell.util.element
def remove_material_set(file: ifcopenshell.file, material: ifcopenshell.entity_instance) -> None:
"""Removes a material set
All set items, such as layers, profiles, or constituents will also be
removed. All set usages are also removed.
However, the materials and profile curves used by the layers,
profiles and constituents will not be removed.
:param material: The IfcMaterialLayerSet, IfcMaterialConstituentSet,
IfcMaterialProfileSet entity you want to remove.
:return: None
Example:
.. code:: python
# Create a material set
material_set = ifcopenshell.api.material.add_material_set(model,
name="GYP-ST-GYP", set_type="IfcMaterialLayerSet")
# Create some materials
gypsum = ifcopenshell.api.material.add_material(model, name="PB01", category="gypsum")
steel = ifcopenshell.api.material.add_material(model, name="ST01", category="steel")
# Add some layers
layer = ifcopenshell.api.material.add_layer(model, layer_set=material_set, material=gypsum)
layer = ifcopenshell.api.material.add_layer(model, layer_set=material_set, material=steel)
layer = ifcopenshell.api.material.add_layer(model, layer_set=material_set, material=gypsum)
# Completely delete the set and all layers. The gypsum and steel
# material still exist, though.
ifcopenshell.api.material.remove_material_set(model, material=material_set)
"""
# Remove all usages for sets.
has_usages = material.is_a("IfcMaterialLayerSet") or material.is_a("IfcMaterialProfileSet")
if has_usages:
# Usage is invalid if it is not associated with some element,
# so we can remove usages through unassignment.
elements = ifcopenshell.util.element.get_elements_by_material(file, material)
if elements:
ifcopenshell.api.material.unassign_material(file, products=list(elements))
if material.is_a("IfcMaterialLayerSet"):
set_items = material.MaterialLayers or []
elif material.is_a("IfcMaterialProfileSet"):
set_items = material.MaterialProfiles or []
elif material.is_a("IfcMaterialConstituentSet"):
set_items = material.MaterialConstituents or []
elif material.is_a("IfcMaterialList"):
set_items = []
else:
raise ValueError(f"Unknown material set type: {material.is_a()}")
for set_item in set_items:
file.remove(set_item)
inverse_elements = file.get_inverse(material)
file.remove(material)
for inverse in inverse_elements:
if inverse.is_a("IfcRelAssociatesMaterial"):
# NOTE: for has_usages already handled by unassign_material.
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcMaterialProperties"):
for prop in inverse.Properties or []:
file.remove(prop)
file.remove(inverse)
@@ -0,0 +1,80 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import ifcopenshell.util.element
def remove_profile(
file: ifcopenshell.file,
profile: ifcopenshell.entity_instance,
should_remove_profile_def: bool = False,
should_remove_material: bool = False,
) -> None:
"""Removes a profile item from a profile set
Note that it is invalid to have zero items in a set, so you should leave
at least one profile to ensure a valid IFC dataset.
:param profile: The IfcMaterialProfile entity you want to remove
:param should_remove_profile_def: If true, profile defs with no users will be removed
:param should_remove_material: If true, materials with no users will be removed
Example:
.. code:: python
# First, let's create a material set.
material_set = ifcopenshell.api.material.add_material_set(model,
name="B1", set_type="IfcMaterialProfileSet")
# Create a steel material.
steel = ifcopenshell.api.material.add_material(model, name="ST01", category="steel")
# Create an I-beam profile curve. Notice how we name our profiles
# based on standardised steel profile names.
hea100 = file.create_entity(
"IfcIShapeProfileDef", ProfileName="HEA100", ProfileType="AREA",
OverallWidth=100, OverallDepth=96, WebThickness=5, FlangeThickness=8, FilletRadius=12,
)
# Define that steel material and cross section as a single profile item.
ifcopenshell.api.material.add_profile(model,
profile_set=material_set, material=steel, profile=hea100)
# Imagine a welded square along the length of the profile.
welded_square = ifcopenshell.api.profile.add_arbitrary_profile(model,
profile=[(.0025, .0025), (.0325, .0025), (.0325, -.0025), (.0025, -.0025), (.0025, .0025)])
weld_profile = ifcopenshell.api.material.add_profile(model,
profile_set=material_set, material=steel, profile=welded_square)
# Let's remove our welded square.
ifcopenshell.api.material.remove_profile(model, profile=weld_profile)
"""
subelements = set()
for attribute in profile:
if isinstance(attribute, ifcopenshell.entity_instance):
subelements.add(attribute)
file.remove(profile)
for subelement in subelements:
if subelement.is_a("IfcMaterial") and not should_remove_material:
continue
elif subelement.is_a("IfcProfileDef") and not should_remove_profile_def:
continue
ifcopenshell.util.element.remove_deep2(file, subelement)
@@ -0,0 +1,69 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import ifcopenshell
def reorder_set_item(
file: ifcopenshell.file, material_set: ifcopenshell.entity_instance, old_index: int = 0, new_index: int = 0
) -> None:
"""Reorders an item in a material set
In some material sets, the order have meaning, like in a layer set. In
other cases, it is purely for human convenience.
:param material_set: The IfcMaterialSet which you want to reorder an
item in.
:param old_index: The index of the item you want to move. This starts
counting from 0.
:param new_index: The index of the new position the item will move to.
This starts counting from 0.
:return: None
Example:
.. code:: python
material_set = ifcopenshell.api.material.add_material_set(model,
name="Window", set_type="IfcMaterialList")
aluminium = ifcopenshell.api.material.add_material(model, name="AL01", category="aluminium")
glass = ifcopenshell.api.material.add_material(model, name="GLZ01", category="glass")
# Now let's use those materials as two items in our list.
ifcopenshell.api.material.add_list_item(model, material_list=material_set, material=aluminium)
ifcopenshell.api.material.add_list_item(model, material_list=material_set, material=glass)
# Switch the order around, this has no meaning for a list, so this
# is just for fun.
ifcopenshell.api.material.reorder_set_item(model,
material_set=material_set, old_index=0, new_index=1)
"""
if material_set.is_a("IfcMaterialConstituentSet"):
set_name = "MaterialConstituents"
elif material_set.is_a("IfcMaterialLayerSet"):
set_name = "MaterialLayers"
elif material_set.is_a("IfcMaterialProfileSet"):
set_name = "MaterialProfiles"
elif material_set.is_a("IfcMaterialList"):
set_name = "Materials"
else:
raise ValueError(f"Unexpected material set type: '{material_set.is_a()}'.")
items = list(getattr(material_set, set_name) or [])
items.insert(new_index, items.pop(old_index))
setattr(material_set, set_name, items)
@@ -0,0 +1,114 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import ifcopenshell.api.material
import ifcopenshell.api.style
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()}
representation = ifcopenshell.util.representation.get_representation(element, context=context)
representation = ifcopenshell.util.representation.resolve_representation(representation)
for item in representation.Items:
if aspect := ifcopenshell.util.representation.get_item_shape_aspect(representation, item):
if style := styles.get(aspect.Name, None):
ifcopenshell.api.style.assign_item_style(file, item=item, style=style)
@@ -0,0 +1,135 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
import ifcopenshell
import ifcopenshell.api.owner
import ifcopenshell.util.element
def unassign_material(file: ifcopenshell.file, products: list[ifcopenshell.entity_instance]) -> None:
"""Removes any material relationship with the list of products
A product can only have one material assigned to it, which is why it is
not necessary to specify the material to unassign. The material is not
removed, only the relationship is removed.
If the product does not have a material, nothing happens.
Unassigning a LayerSet or ProfileSet from the product type will also
remove all Usages of the set.
:param products: The list IfcProducts that may or may not have a material
:return: None
Example:
.. code:: python
concrete = ifcopenshell.api.material.add_material(model, name="CON01", category="concrete")
# Let's imagine a concrete bench made out of concrete.
bench_type = ifcopenshell.api.root.create_entity(model, ifc_class="IfcFurnitureType")
ifcopenshell.api.material.assign_material(model,
products=[bench_type], type="IfcMaterial", material=concrete)
# Let's change our mind and remove the concrete assignment. The
# concrete material still exists, but the bench is no longer made
# out of concrete now.
ifcopenshell.api.material.unassign_material(model, products=[bench_type])
"""
usecase = Usecase()
usecase.file = file
return usecase.execute(products)
class Usecase:
file: ifcopenshell.file
def execute(self, products: list[ifcopenshell.entity_instance]) -> None:
if not products:
return
self.products = set(products)
self.remove_material_usages_from_types()
self.unassign_materials()
def remove_material_usages_from_types(self) -> None:
# remove material usages from types
for product in self.products:
if not product.is_a("IfcTypeObject"):
continue
material = ifcopenshell.util.element.get_material(product)
if not material:
continue
if material.is_a() in ["IfcMaterialLayerSet", "IfcMaterialProfileSet"]:
# Remove set usages
# TODO: be more considerate and remove only usages
# associated with the set + product type, not all usages?
for inverse in self.file.get_inverse(material):
if self.file.schema == "IFC2X3":
if not inverse.is_a("IfcMaterialLayerSetUsage"):
continue
# in IFC2X3 there is no .AssociatedTo
for inverse2 in self.file.get_inverse(inverse):
if inverse2.is_a("IfcRelAssociatesMaterial"):
history = inverse2.OwnerHistory
self.file.remove(inverse2)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
else:
if not inverse.is_a("IfcMaterialUsageDefinition"):
continue
for rel in inverse.AssociatedTo:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
self.file.remove(inverse)
def unassign_materials(self) -> None:
associations: set[ifcopenshell.entity_instance] = set()
for product in self.products:
associations.update(product.HasAssociations)
# we ensure that `associations` won't have removed elements
# to avoid crash during `material_inverses.issubset(associations)`
while associations:
rel = next(iter(associations))
if not rel.is_a("IfcRelAssociatesMaterial"):
associations.remove(rel)
else:
material = rel.RelatingMaterial
related_objects = set(rel.RelatedObjects) - self.products
if material.is_a() in ["IfcMaterialLayerSetUsage", "IfcMaterialProfileSetUsage"]:
# Warning: this may leave the model in a non-compliant state.
material_inverses = set(self.file.get_inverse(material))
if material_inverses.issubset(associations) and not related_objects:
self.file.remove(material)
associations.remove(rel)
if not related_objects:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
continue
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.owner.update_owner_history(self.file, element=rel)