material.unassign_material - support batching #4474

This commit is contained in:
Andrej730
2024-04-12 16:15:14 +05:00
parent cd25a50e96
commit 657ff30883
9 changed files with 181 additions and 86 deletions
@@ -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
@@ -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:
+3 -3
View File
@@ -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):
@@ -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"
),
}
@@ -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")
@@ -17,12 +17,13 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
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})
@@ -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)
@@ -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
@@ -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