From 657ff308835fe1fc89fc045bfab14129d3c80866 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 12 Apr 2024 16:15:14 +0500 Subject: [PATCH] material.unassign_material - support batching #4474 --- .../bim/module/geometry/operator.py | 4 +- .../blenderbim/bim/module/model/product.py | 2 +- src/blenderbim/blenderbim/core/material.py | 6 +- .../ifcopenshell/api/__init__.py | 11 +- .../api/material/assign_material.py | 7 +- .../api/material/unassign_material.py | 68 ++++++-- .../ifcopenshell/api/root/remove_product.py | 2 +- .../api/material/test_unassign_material.py | 155 ++++++++++++------ src/ifcopenshell-python/test/api/test_api.py | 12 ++ 9 files changed, 181 insertions(+), 86 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 115f8df352..1d7962b472 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -324,8 +324,8 @@ class UpdateRepresentation(bpy.types.Operator, Operator): # We are explicitly casting to a tessellation, so remove all parametric materials. element_type = ifcopenshell.util.element.get_type(product) if element_type: # Some invalid IFCs use material sets without a type. - ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=element_type) - ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=product) + ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), products=[element_type]) + ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), products=[product]) else: # These objects are parametrically based on an axis and should not be modified as a mesh return diff --git a/src/blenderbim/blenderbim/bim/module/model/product.py b/src/blenderbim/blenderbim/bim/module/model/product.py index f1aee69d64..8c66e838d9 100644 --- a/src/blenderbim/blenderbim/bim/module/model/product.py +++ b/src/blenderbim/blenderbim/bim/module/model/product.py @@ -551,7 +551,7 @@ def ensure_material_assigned(usecase_path, ifc_file, settings): def ensure_material_unassigned(usecase_path, ifc_file, settings): - elements = [settings["product"]] + elements = settings["products"] if elements[0].is_a("IfcElementType"): elements.extend(ifcopenshell.util.element.get_types(elements[0])) for element in elements: diff --git a/src/blenderbim/blenderbim/core/material.py b/src/blenderbim/blenderbim/core/material.py index f5cfcbf9ba..da265b2a7c 100644 --- a/src/blenderbim/blenderbim/core/material.py +++ b/src/blenderbim/blenderbim/core/material.py @@ -116,12 +116,12 @@ def unassign_material(ifc, material_tool, objects): inherited_material = material_tool.get_material(element, should_inherit=True) if material and "Usage" in material.is_a(): element_type = material_tool.get_type(element) - ifc.run("material.unassign_material", product=element_type) + ifc.run("material.unassign_material", products=[element_type]) elif not material and inherited_material: element_type = material_tool.get_type(element) - ifc.run("material.unassign_material", product=element_type) + ifc.run("material.unassign_material", products=[element_type]) elif material: - ifc.run("material.unassign_material", product=element) + ifc.run("material.unassign_material", products=[element]) def patch_non_parametric_mep_segment(ifc, material_tool, profile_tool, obj): diff --git a/src/ifcopenshell-python/ifcopenshell/api/__init__.py b/src/ifcopenshell-python/ifcopenshell/api/__init__.py index 5a90b4dc7a..8f7b82351c 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/__init__.py +++ b/src/ifcopenshell-python/ifcopenshell/api/__init__.py @@ -79,15 +79,14 @@ ARGUMENTS_DEPRECATION = { "type.unassign_type": partial( batching_argument_deprecation, prev_argument="related_object", new_argument="related_objects" ), - "system.assign_system": partial( - batching_argument_deprecation, prev_argument="product", new_argument="products" - ), - "system.unassign_system": partial( - batching_argument_deprecation, prev_argument="product", new_argument="products" - ), + "system.assign_system": partial(batching_argument_deprecation, prev_argument="product", new_argument="products"), + "system.unassign_system": partial(batching_argument_deprecation, prev_argument="product", new_argument="products"), "material.assign_material": partial( batching_argument_deprecation, prev_argument="product", new_argument="products" ), + "material.unassign_material": partial( + batching_argument_deprecation, prev_argument="product", new_argument="products" + ), } diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/assign_material.py b/src/ifcopenshell-python/ifcopenshell/api/material/assign_material.py index 2e91f2cce3..0445f2a995 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/material/assign_material.py +++ b/src/ifcopenshell-python/ifcopenshell/api/material/assign_material.py @@ -155,10 +155,9 @@ class Usecase: return # NOTE: we always reassign material, even if it might be assigned before - for product in self.products: - material = ifcopenshell.util.element.get_material(product) - if material: - ifcopenshell.api.run("material.unassign_material", self.file, product=product) + products_to_unassign_material = [p for p in self.products if ifcopenshell.util.element.get_material(p)] + if products_to_unassign_material: + ifcopenshell.api.run("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") diff --git a/src/ifcopenshell-python/ifcopenshell/api/material/unassign_material.py b/src/ifcopenshell-python/ifcopenshell/api/material/unassign_material.py index f18065aec9..60fac1382e 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/material/unassign_material.py +++ b/src/ifcopenshell-python/ifcopenshell/api/material/unassign_material.py @@ -17,12 +17,13 @@ # along with IfcOpenShell. If not, see . import ifcopenshell +import ifcopenshell.api import ifcopenshell.util.element class Usecase: - def __init__(self, file, product=None): - """Removes any material relationship with a product + def __init__(self, file: ifcopenshell.file, products: list[ifcopenshell.entity_instance]): + """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 @@ -30,8 +31,8 @@ class Usecase: If the product does not have a material, nothing happens. - :param product: The IfcProduct that may or may not have a material - :type product: ifcopenshell.entity_instance.entity_instance + :param products: The list IfcProducts that may or may not have a material + :type product: list[ifcopenshell.entity_instance.entity_instance] :return: None :rtype: None @@ -49,20 +50,34 @@ class Usecase: # 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.run("material.unassign_material", model, product=bench_type) + ifcopenshell.api.run("material.unassign_material", model, products=[bench_type]) """ self.file = file - self.settings = {"product": product} + self.settings = {"products": products} - def execute(self): - if self.settings["product"].is_a("IfcTypeObject"): - material = ifcopenshell.util.element.get_material(self.settings["product"]) + def execute(self) -> None: + self.products = set(self.settings["products"]) + if not self.products: + return + + 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 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 @@ -73,21 +88,40 @@ class Usecase: 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) - for rel in self.settings["product"].HasAssociations: - if rel.is_a("IfcRelAssociatesMaterial"): - if rel.RelatingMaterial.is_a() in ["IfcMaterialLayerSetUsage", "IfcMaterialProfileSetUsage"]: + 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. - if self.file.get_total_inverses(rel.RelatingMaterial) == 1 and len(rel.RelatedObjects) == 1: - self.file.remove(rel.RelatingMaterial) - if len(rel.RelatedObjects) == 1: + 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 - related_objects = set(rel.RelatedObjects) - related_objects.remove(self.settings["product"]) rel.RelatedObjects = list(related_objects) + ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel}) diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py b/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py index 51fe9516d5..179e7ca572 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py +++ b/src/ifcopenshell-python/ifcopenshell/api/root/remove_product.py @@ -126,7 +126,7 @@ class Usecase: pset=inverse.RelatingPropertyDefinition, ) elif inverse.is_a("IfcRelAssociatesMaterial"): - ifcopenshell.api.run("material.unassign_material", self.file, product=self.settings["product"]) + ifcopenshell.api.run("material.unassign_material", self.file, products=[self.settings["product"]]) elif inverse.is_a("IfcRelDefinesByType"): if inverse.RelatingType == self.settings["product"]: ifcopenshell.api.run("type.unassign_type", self.file, related_objects=inverse.RelatedObjects) diff --git a/src/ifcopenshell-python/test/api/material/test_unassign_material.py b/src/ifcopenshell-python/test/api/material/test_unassign_material.py index ace766add0..f1f280f5ad 100644 --- a/src/ifcopenshell-python/test/api/material/test_unassign_material.py +++ b/src/ifcopenshell-python/test/api/material/test_unassign_material.py @@ -21,16 +21,17 @@ import ifcopenshell.api import ifcopenshell.util.element -class TestUnassignMaterial(test.bootstrap.IFC4): +class TestUnassignMaterialIFC2X3(test.bootstrap.IFC2X3): def test_unassign_single_material(self): - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") material = ifcopenshell.api.run("material.add_material", self.file, name="CON01") ifcopenshell.api.run( - "material.assign_material", self.file, products=[element], type="IfcMaterial", material=material + "material.assign_material", self.file, products=[element1, element2], type="IfcMaterial", material=material ) - ifcopenshell.api.run("material.unassign_material", self.file, product=element) + ifcopenshell.api.run("material.unassign_material", self.file, products=[element1, element2]) assert len(self.file.by_type("IfcRelAssociatesMaterial")) == 0 - assert len(self.file.by_type("IfcWall")) == 1 + assert len(self.file.by_type("IfcWall")) == 2 assert len(self.file.by_type("IfcMaterial")) == 1 def test_unassign_single_material_with_multiple_elements(self): @@ -38,12 +39,9 @@ class TestUnassignMaterial(test.bootstrap.IFC4): element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") material = ifcopenshell.api.run("material.add_material", self.file, name="CON01") ifcopenshell.api.run( - "material.assign_material", self.file, products=[element1], type="IfcMaterial", material=material + "material.assign_material", self.file, products=[element1, element2], type="IfcMaterial", material=material ) - ifcopenshell.api.run( - "material.assign_material", self.file, products=[element2], type="IfcMaterial", material=material - ) - ifcopenshell.api.run("material.unassign_material", self.file, product=element2) + ifcopenshell.api.run("material.unassign_material", self.file, products=[element2]) assert element1.HasAssociations assert not element2.HasAssociations assert len(self.file.by_type("IfcRelAssociatesMaterial")) == 1 @@ -51,36 +49,63 @@ class TestUnassignMaterial(test.bootstrap.IFC4): assert len(self.file.by_type("IfcMaterial")) == 1 def test_unassign_material_layer_set_from_type(self): - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") - material = ifcopenshell.api.run("material.add_material", self.file, name="CON01") - ifcopenshell.api.run("material.assign_material", self.file, products=[element], type="IfcMaterialLayerSet") - ifcopenshell.api.run("material.unassign_material", self.file, product=element) + element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + ifcopenshell.api.run( + "material.assign_material", self.file, products=[element1, element2], type="IfcMaterialLayerSet" + ) + ifcopenshell.api.run("material.unassign_material", self.file, products=[element1, element2]) assert len(self.file.by_type("IfcRelAssociatesMaterial")) == 0 - assert len(self.file.by_type("IfcWallType")) == 1 + assert len(self.file.by_type("IfcWallType")) == 2 assert len(self.file.by_type("IfcMaterialLayerSet")) == 1 def test_unassign_material_layer_set_usage_from_element(self): element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=element_type) - material = ifcopenshell.api.run("material.add_material", self.file, name="CON01") + element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run( + "type.assign_type", self.file, related_objects=[element1, element2], relating_type=element_type + ) ifcopenshell.api.run("material.assign_material", self.file, products=[element_type], type="IfcMaterialLayerSet") - ifcopenshell.api.run("material.assign_material", self.file, products=[element], type="IfcMaterialLayerSetUsage") - ifcopenshell.api.run("material.unassign_material", self.file, product=element) + ifcopenshell.api.run( + "material.assign_material", self.file, products=[element1, element2], type="IfcMaterialLayerSetUsage" + ) + ifcopenshell.api.run("material.unassign_material", self.file, products=[element1, element2]) assert len(self.file.by_type("IfcRelAssociatesMaterial")) == 1 assert element_type.HasAssociations assert len(self.file.by_type("IfcWallType")) == 1 - assert len(self.file.by_type("IfcWall")) == 1 + assert len(self.file.by_type("IfcWall")) == 2 assert len(self.file.by_type("IfcMaterialLayerSet")) == 1 assert len(self.file.by_type("IfcMaterialLayerSetUsage")) == 0 + def test_unassign_material_layer_set_usage_shouldnt_remove_other_usages_of_the_type(self): + element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run( + "type.assign_type", self.file, related_objects=[element1, element2], relating_type=element_type + ) + ifcopenshell.api.run("material.assign_material", self.file, products=[element_type], type="IfcMaterialLayerSet") + ifcopenshell.api.run( + "material.assign_material", self.file, products=[element1], type="IfcMaterialLayerSetUsage" + ) + ifcopenshell.api.run( + "material.assign_material", self.file, products=[element2], type="IfcMaterialLayerSetUsage" + ) + ifcopenshell.api.run("material.unassign_material", self.file, products=[element1]) + assert len(self.file.by_type("IfcRelAssociatesMaterial")) == 2 + assert element_type.HasAssociations + assert len(self.file.by_type("IfcWallType")) == 1 + assert len(self.file.by_type("IfcWall")) == 2 + assert len(self.file.by_type("IfcMaterialLayerSet")) == 1 + assert len(self.file.by_type("IfcMaterialLayerSetUsage")) == 1 + def test_unassign_material_layer_set_usage_from_element_with_multiple_invalid_usages(self): element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=element_type) ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element2], relating_type=element_type) - material = ifcopenshell.api.run("material.add_material", self.file, name="CON01") ifcopenshell.api.run("material.assign_material", self.file, products=[element_type], type="IfcMaterialLayerSet") rel = ifcopenshell.api.run( "material.assign_material", self.file, products=[element], type="IfcMaterialLayerSetUsage" @@ -89,7 +114,7 @@ class TestUnassignMaterial(test.bootstrap.IFC4): # In some invalid IFCs from Revit, they reuse usages. Let's recreate this invalid scenario rel.RelatedObjects = list(rel.RelatedObjects) + [element2] - ifcopenshell.api.run("material.unassign_material", self.file, product=element) + ifcopenshell.api.run("material.unassign_material", self.file, products=[element]) assert len(self.file.by_type("IfcRelAssociatesMaterial")) == 2 for rel in self.file.by_type("IfcRelAssociatesMaterial"): @@ -102,46 +127,72 @@ class TestUnassignMaterial(test.bootstrap.IFC4): assert ifcopenshell.util.element.get_material(element, should_inherit=False) is None assert ifcopenshell.util.element.get_material(element2, should_inherit=False).is_a("IfcMaterialLayerSetUsage") - def test_unassign_material_profile_set_from_type(self): - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") - material = ifcopenshell.api.run("material.add_material", self.file, name="CON01") - ifcopenshell.api.run("material.assign_material", self.file, products=[element], type="IfcMaterialProfileSet") - ifcopenshell.api.run("material.unassign_material", self.file, product=element) + def test_unassign_element_material_list(self): + element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + material1 = ifcopenshell.api.run("material.add_material", self.file, name="CON01") + ifcopenshell.api.run( + "material.assign_material", + self.file, + products=[element1, element2], + type="IfcMaterialList", + material=material1, + ) + element3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + material3 = ifcopenshell.api.run("material.add_material", self.file, name="CON01") + ifcopenshell.api.run( + "material.assign_material", self.file, products=[element3], type="IfcMaterialList", material=material3 + ) + ifcopenshell.api.run("material.unassign_material", self.file, products=[element1, element2, element3]) assert len(self.file.by_type("IfcRelAssociatesMaterial")) == 0 - assert len(self.file.by_type("IfcWallType")) == 1 - assert len(self.file.by_type("IfcMaterialProfileSet")) == 1 + assert len(self.file.by_type("IfcMaterialList")) == 2 + assert len(self.file.by_type("IfcMaterial")) == 2 + + +class TestUnassignMaterialIFC4(test.bootstrap.IFC4, TestUnassignMaterialIFC2X3): + + def test_unassign_material_profile_set_from_type(self): + element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + ifcopenshell.api.run("material.assign_material", self.file, products=[element1], type="IfcMaterialProfileSet") + element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + ifcopenshell.api.run("material.assign_material", self.file, products=[element2], type="IfcMaterialProfileSet") + ifcopenshell.api.run("material.unassign_material", self.file, products=[element1, element2]) + assert len(self.file.by_type("IfcRelAssociatesMaterial")) == 0 + assert len(self.file.by_type("IfcWallType")) == 2 + assert len(self.file.by_type("IfcMaterialProfileSet")) == 2 def test_unassign_material_profile_set_usage_from_element(self): element_type = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - ifcopenshell.api.run("type.assign_type", self.file, related_objects=[element], relating_type=element_type) - material = ifcopenshell.api.run("material.add_material", self.file, name="CON01") - ifcopenshell.api.run("material.assign_material", self.file, products=[element_type], type="IfcMaterialProfileSet") - ifcopenshell.api.run("material.assign_material", self.file, products=[element], type="IfcMaterialProfileSetUsage") - ifcopenshell.api.run("material.unassign_material", self.file, product=element) + element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + ifcopenshell.api.run( + "type.assign_type", self.file, related_objects=[element1, element2], relating_type=element_type + ) + ifcopenshell.api.run( + "material.assign_material", self.file, products=[element_type], type="IfcMaterialProfileSet" + ) + ifcopenshell.api.run( + "material.assign_material", self.file, products=[element1, element2], type="IfcMaterialProfileSetUsage" + ) + ifcopenshell.api.run("material.unassign_material", self.file, products=[element1, element2]) assert len(self.file.by_type("IfcRelAssociatesMaterial")) == 1 assert element_type.HasAssociations assert len(self.file.by_type("IfcWallType")) == 1 - assert len(self.file.by_type("IfcWall")) == 1 + assert len(self.file.by_type("IfcWall")) == 2 assert len(self.file.by_type("IfcMaterialProfileSet")) == 1 assert len(self.file.by_type("IfcMaterialProfileSetUsage")) == 0 def test_unassign_material_constituent_set_from_type(self): - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") - material = ifcopenshell.api.run("material.add_material", self.file, name="CON01") - ifcopenshell.api.run("material.assign_material", self.file, products=[element], type="IfcMaterialConstituentSet") - ifcopenshell.api.run("material.unassign_material", self.file, product=element) - assert len(self.file.by_type("IfcRelAssociatesMaterial")) == 0 - assert len(self.file.by_type("IfcWallType")) == 1 - assert len(self.file.by_type("IfcMaterialConstituentSet")) == 1 - - def test_unassign_element_material_list(self): - element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") - material = ifcopenshell.api.run("material.add_material", self.file, name="CON01") + element1 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + element2 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") ifcopenshell.api.run( - "material.assign_material", self.file, products=[element], type="IfcMaterialList", material=material + "material.assign_material", self.file, products=[element1, element2], type="IfcMaterialConstituentSet" ) - ifcopenshell.api.run("material.unassign_material", self.file, product=element) + element3 = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWallType") + ifcopenshell.api.run( + "material.assign_material", self.file, products=[element3], type="IfcMaterialConstituentSet" + ) + ifcopenshell.api.run("material.unassign_material", self.file, products=[element1, element2, element3]) assert len(self.file.by_type("IfcRelAssociatesMaterial")) == 0 - assert len(self.file.by_type("IfcMaterialList")) == 1 - assert len(self.file.by_type("IfcMaterial")) == 1 + assert len(self.file.by_type("IfcWallType")) == 3 + assert len(self.file.by_type("IfcMaterialConstituentSet")) == 2 diff --git a/src/ifcopenshell-python/test/api/test_api.py b/src/ifcopenshell-python/test/api/test_api.py index 7f19b170e2..779d1238f2 100644 --- a/src/ifcopenshell-python/test/api/test_api.py +++ b/src/ifcopenshell-python/test/api/test_api.py @@ -174,3 +174,15 @@ class TestTemporarySupportForDeprecatedAPIArguments(test.bootstrap.IFC4): ) assert len(self.file.by_type("IfcRelAssociatesMaterial")) == 1 assert element.HasAssociations[0].RelatingMaterial == material + + @deprecation_check + def test_unassign_single_material(self): + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + material = ifcopenshell.api.run("material.add_material", self.file, name="CON01") + ifcopenshell.api.run( + "material.assign_material", self.file, products=[element], type="IfcMaterial", material=material + ) + ifcopenshell.api.run("material.unassign_material", self.file, product=element) + assert len(self.file.by_type("IfcRelAssociatesMaterial")) == 0 + assert len(self.file.by_type("IfcWall")) == 1 + assert len(self.file.by_type("IfcMaterial")) == 1