diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/remove_material_set.py b/src/ifcopenshell-python/ifcopenshell/api/material/remove_material_set.py
index 18477c6038..b11927e382 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/material/remove_material_set.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/material/remove_material_set.py
@@ -17,6 +17,7 @@
# along with IfcOpenShell. If not, see .
import ifcopenshell
+import ifcopenshell.api.material
import ifcopenshell.util.element
@@ -24,7 +25,9 @@ def remove_material_set(file: ifcopenshell.file, material: ifcopenshell.entity_i
"""Removes a material set
All set items, such as layers, profiles, or constituents will also be
- removed. However, the materials and profile curves used by the layers,
+ 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,
@@ -53,7 +56,15 @@ def remove_material_set(file: ifcopenshell.file, material: ifcopenshell.entity_i
ifcopenshell.api.material.remove_material_set(model, material=material_set)
"""
- inverse_elements = file.get_inverse(material)
+ # 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"):
@@ -66,9 +77,13 @@ def remove_material_set(file: ifcopenshell.file, material: ifcopenshell.entity_i
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:
diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/unassign_material.py b/src/ifcopenshell-python/ifcopenshell/api/material/unassign_material.py
index 2ed3fd9e14..455fc02bc4 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/material/unassign_material.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/material/unassign_material.py
@@ -31,6 +31,9 @@ def unassign_material(file: ifcopenshell.file, products: list[ifcopenshell.entit
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
@@ -76,6 +79,8 @@ class Usecase:
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"):
diff --git a/src/ifcopenshell-python/test/api/material/test_remove_material_set.py b/src/ifcopenshell-python/test/api/material/test_remove_material_set.py
index b7f0b47dd5..3c6c70b44a 100644
--- a/src/ifcopenshell-python/test/api/material/test_remove_material_set.py
+++ b/src/ifcopenshell-python/test/api/material/test_remove_material_set.py
@@ -17,9 +17,10 @@
# along with IfcOpenShell. If not, see .
import test.bootstrap
+import ifcopenshell.api.material
import ifcopenshell.api.pset
import ifcopenshell.api.root
-import ifcopenshell.api.material
+import ifcopenshell.api.type
class TestRemoveMaterialSetIFC2X3(test.bootstrap.IFC2X3):
@@ -45,6 +46,19 @@ class TestRemoveMaterialSetIFC2X3(test.bootstrap.IFC2X3):
assert len(self.file.by_type("IfcMaterialLayer")) == 0
assert len(self.file.by_type("IfcMaterial")) == 1
+ def test_removing_a_material_set_with_usages(self):
+ material = ifcopenshell.api.material.add_material_set(self.file, set_type="IfcMaterialLayerSet")
+ element_type = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType")
+ element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
+ ifcopenshell.api.type.assign_type(self.file, related_objects=[element], relating_type=element_type)
+ ifcopenshell.api.material.assign_material(self.file, products=[element_type], material=material)
+ ifcopenshell.api.material.assign_material(
+ self.file, products=[element], material=material, type="IfcMaterialLayerSetUsage"
+ )
+ ifcopenshell.api.material.remove_material_set(self.file, material=material)
+ assert len(self.file.by_type("IfcMaterialLayerSet")) == 0
+ assert len(self.file.by_type("IfcMaterialLayerSetUsage")) == 0
+
class TestRemoveMaterialSetIFC4(test.bootstrap.IFC4, TestRemoveMaterialSetIFC2X3):
# IFC2X3 doesn't support adding a pset to IfcMaterialLayerSet