From 4d149e8a782572e906b228bb475b4bf55bc10106 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 13 Sep 2024 12:45:04 +0500 Subject: [PATCH] Purge unused materials to consider materials with styles and psets #3914 If material has a style or a pset it creates an inverse that shouldn't prevent material from being purged. --- .../bonsai/bim/module/debug/operator.py | 9 +++---- src/bonsai/bonsai/tool/material.py | 26 +++++++++++++++++++ src/bonsai/test/tool/test_material.py | 19 ++++++++++++++ 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/debug/operator.py b/src/bonsai/bonsai/bim/module/debug/operator.py index 83ee47d07f..9a88454bb0 100644 --- a/src/bonsai/bonsai/bim/module/debug/operator.py +++ b/src/bonsai/bonsai/bim/module/debug/operator.py @@ -632,14 +632,11 @@ class PurgeUnusedObjects(bpy.types.Operator, tool.Ifc.Operator): elif object_type == "PROFILE": purged = bonsai.core.profile.purge_unused_profiles(tool.Ifc, tool.Profile) elif object_type == "STYLE": + # It's okay to remove IfcPresentationStyle if just remove_deep + # as there are no white listed inverses. purged = tool.Debug.purge_unused_class("IfcPresentationStyle") elif object_type == "MATERIAL": - ifc_file = tool.Ifc.get() - is_ifc2x3 = ifc_file.schema == "IFC2X3" - if is_ifc2x3: - purged = tool.Debug.purge_unused_class("IfcMaterial") - else: - purged = tool.Debug.purge_unused_class("IfcMaterialDefinition") + purged = tool.Material.purge_unused_materials() else: self.report({"ERROR"}, f"Invalid object type {object_type}.") return {"CANCELLED"} diff --git a/src/bonsai/bonsai/tool/material.py b/src/bonsai/bonsai/tool/material.py index 4832f3bd76..7ddfba250e 100644 --- a/src/bonsai/bonsai/tool/material.py +++ b/src/bonsai/bonsai/tool/material.py @@ -388,3 +388,29 @@ class Material(bonsai.core.tool.Material): material.Materials = [default_material] else: assert False, f"Invalid material type found: {material_type}." + + @classmethod + def purge_unused_materials(cls) -> int: + ifc_file = tool.Ifc.get() + is_ifc2x3 = ifc_file.schema == "IFC2X3" + ifc_class = "IfcMaterial" if is_ifc2x3 else "IfcMaterialDefinition" + + skip_inverses = { + "IfcMaterialDefinitionRepresentation", + "IfcMaterialProperties", + } + + def is_safe_to_purge(material: ifcopenshell.entity_instance) -> bool: + for i in ifc_file.get_inverse(material): + if i.is_a() not in skip_inverses: + return False + return True + + materials = ifc_file.by_type(ifc_class) + i = 0 + for material in materials: + if ifc_file.get_total_inverses(material) != 0 and not is_safe_to_purge(material): + continue + ifcopenshell.api.material.remove_material(ifc_file, material) + i += 1 + return i diff --git a/src/bonsai/test/tool/test_material.py b/src/bonsai/test/tool/test_material.py index 9dcb63c40e..281c8918ac 100644 --- a/src/bonsai/test/tool/test_material.py +++ b/src/bonsai/test/tool/test_material.py @@ -19,6 +19,9 @@ import bpy import ifcopenshell import ifcopenshell.api +import ifcopenshell.api.material +import ifcopenshell.api.pset +import ifcopenshell.api.style import bonsai.core.tool import bonsai.tool as tool from test.bim.bootstrap import NewFile @@ -183,3 +186,19 @@ class TestEnsureNewMaterialSetIsValid(NewFile): material_set = ifc.create_entity("IfcMaterialList") subject.ensure_new_material_set_is_valid(material_set) assert len(material_set.Materials) == 1 + + +class TestPurgeUnusedMaterials(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + material = ifcopenshell.api.material.add_material(ifc) + # Add pset. + ifcopenshell.api.pset.add_pset(ifc, material, "Foo") + # Add style. + style = ifcopenshell.api.style.add_style(ifc) + context = ifc.create_entity("IfcRepresentationContext") + ifcopenshell.api.style.assign_material_style(ifc, material, style, context) + + assert subject.purge_unused_materials() == 1 + assert not ifc.by_type("IfcMaterial")