From c478e5bf1b30f1758c38dbfc8d999f8ad0250030 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 21 Mar 2022 12:04:51 +1100 Subject: [PATCH] #2094. Deleting materials now also deletes Blender material if not used for a style --- .../bim/module/material/__init__.py | 1 + .../bim/module/material/operator.py | 28 ++++++------ .../blenderbim/bim/module/material/ui.py | 11 ++++- src/blenderbim/blenderbim/core/material.py | 16 +++++++ src/blenderbim/blenderbim/core/tool.py | 1 + src/blenderbim/blenderbim/tool/material.py | 4 ++ .../test/bim/feature/material.feature | 14 +++++- src/blenderbim/test/bim/test_feature.py | 5 +++ src/blenderbim/test/core/test_material.py | 44 +++++++++++++++++++ src/blenderbim/test/tool/test_material.py | 8 ++++ 10 files changed, 114 insertions(+), 18 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/material/__init__.py b/src/blenderbim/blenderbim/bim/module/material/__init__.py index d836036937..47bd7b6d0f 100644 --- a/src/blenderbim/blenderbim/bim/module/material/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/material/__init__.py @@ -41,6 +41,7 @@ classes = ( operator.RemoveLayer, operator.RemoveListItem, operator.RemoveMaterial, + operator.RemoveMaterialSet, operator.RemoveProfile, operator.ReorderMaterialSetItem, operator.UnassignMaterial, diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index 0bc6e0f969..e2d0542ae8 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -104,26 +104,26 @@ class AddMaterialSet(bpy.types.Operator, tool.Ifc.Operator): material_prop_purge() -class RemoveMaterial(bpy.types.Operator): +class RemoveMaterial(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_material" bl_label = "Remove Material" bl_options = {"REGISTER", "UNDO"} - obj: bpy.props.StringProperty() - - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) + material: bpy.props.IntProperty() def _execute(self, context): - obj = bpy.data.materials.get(self.obj) if self.obj else context.active_object.active_material - self.file = IfcStore.get_file() - result = ifcopenshell.api.run( - "material.remove_material", - self.file, - **{"material": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)}, - ) - obj.BIMObjectProperties.ifc_definition_id = 0 + core.remove_material(tool.Ifc, tool.Material, tool.Style, material=tool.Ifc.get().by_id(self.material)) + Data.load(IfcStore.get_file()) + + +class RemoveMaterialSet(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.remove_material_set" + bl_label = "Remove Material Set" + bl_options = {"REGISTER", "UNDO"} + material: bpy.props.IntProperty() + + def _execute(self, context): + core.remove_material_set(tool.Ifc, tool.Material, material=tool.Ifc.get().by_id(self.material)) Data.load(IfcStore.get_file()) - return {"FINISHED"} class UnlinkMaterial(bpy.types.Operator, tool.Ifc.Operator): diff --git a/src/blenderbim/blenderbim/bim/module/material/ui.py b/src/blenderbim/blenderbim/bim/module/material/ui.py index 0bbc09c630..7ea70c5813 100644 --- a/src/blenderbim/blenderbim/bim/module/material/ui.py +++ b/src/blenderbim/blenderbim/bim/module/material/ui.py @@ -59,8 +59,14 @@ class BIM_PT_materials(Panel): if self.props.material_type == "IfcMaterial": row.operator("bim.add_material", text="", icon="ADD") + if self.props.materials and self.props.active_material_index < len(self.props.materials): + material = self.props.materials[self.props.active_material_index] + row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id else: row.operator("bim.add_material_set", text="", icon="ADD").set_type = self.props.material_type + if self.props.materials and self.props.active_material_index < len(self.props.materials): + material = self.props.materials[self.props.active_material_index] + row.operator("bim.remove_material_set", text="", icon="X").material = material.ifc_definition_id self.layout.template_list("BIM_UL_materials", "", self.props, "materials", self.props, "active_material_index") @@ -78,8 +84,9 @@ class BIM_PT_material(Panel): def draw(self, context): row = self.layout.row(align=True) - if bool(context.active_object.active_material.BIMObjectProperties.ifc_definition_id): - row.operator("bim.remove_material", icon="X", text="Remove IFC Material") + material_id = context.active_object.active_material.BIMObjectProperties.ifc_definition_id + if bool(material_id): + row.operator("bim.remove_material", icon="X", text="Remove IFC Material").material = material_id row.operator("bim.unlink_material", icon="UNLINKED", text="") else: op = row.operator("bim.add_material", icon="ADD", text="Create IFC Material") diff --git a/src/blenderbim/blenderbim/core/material.py b/src/blenderbim/blenderbim/core/material.py index df9b35939c..15dd2442e8 100644 --- a/src/blenderbim/blenderbim/core/material.py +++ b/src/blenderbim/blenderbim/core/material.py @@ -43,6 +43,22 @@ def add_material_set(ifc, material, set_type=None): return ifc_material +def remove_material(ifc, material_tool, style, material=None): + obj = ifc.get_object(material) + ifc.unlink(element=material) + ifc.run("material.remove_material", material=material) + if obj and not style.get_style(obj): + material_tool.delete_object(obj) + if material_tool.is_editing_materials(): + material_tool.import_material_definitions(material_tool.get_active_material_type()) + + +def remove_material_set(ifc, material_tool, material=None): + ifc.run("material.remove_material_set", material=material) + if material_tool.is_editing_materials(): + material_tool.import_material_definitions(material_tool.get_active_material_type()) + + def load_materials(material, material_type): material.import_material_definitions(material_type) material.enable_editing_materials() diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 0e9f272a1d..21f8796d6c 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -276,6 +276,7 @@ class Library: @interface class Material: def add_default_material_object(cls): pass + def delete_object(cls, obj): pass def disable_editing_materials(cls): pass def enable_editing_materials(cls): pass def get_active_material_type(cls): pass diff --git a/src/blenderbim/blenderbim/tool/material.py b/src/blenderbim/blenderbim/tool/material.py index 304d9770e6..0e27d54803 100644 --- a/src/blenderbim/blenderbim/tool/material.py +++ b/src/blenderbim/blenderbim/tool/material.py @@ -28,6 +28,10 @@ class Material(blenderbim.core.tool.Material): def add_default_material_object(cls): return bpy.data.materials.new("Default") + @classmethod + def delete_object(cls, obj): + bpy.data.materials.remove(obj) + @classmethod def disable_editing_materials(cls): bpy.context.scene.BIMMaterialProperties.is_editing = False diff --git a/src/blenderbim/test/bim/feature/material.feature b/src/blenderbim/test/bim/feature/material.feature index db52e2b897..cbea52096b 100644 --- a/src/blenderbim/test/bim/feature/material.feature +++ b/src/blenderbim/test/bim/feature/material.feature @@ -46,8 +46,18 @@ Scenario: Remove material And the object "Cube" is selected And I add a material And I press "bim.add_material(obj='Material')" - When I press "bim.remove_material" - Then the material "Material" is not an IFC material + And the variable "material" is "{ifc}.by_type('IfcMaterial')[0].id()" + When I press "bim.remove_material(material={material})" + Then the material "Material" does not exist + +Scenario: Remove material set + Given an empty IFC project + And I set "scene.BIMMaterialProperties.material_type" to "IfcMaterialLayerSet" + And I press "bim.load_materials" + And I press "bim.add_material_set(set_type='IfcMaterialLayerSet')" + And the variable "material" is "{ifc}.by_type('IfcMaterialLayerSet')[0].id()" + When I press "bim.remove_material_set(material={material})" + Then nothing happens Scenario: Unlink material Given an empty IFC project diff --git a/src/blenderbim/test/bim/test_feature.py b/src/blenderbim/test/bim/test_feature.py index c28da88105..4cd6dbcd5a 100644 --- a/src/blenderbim/test/bim/test_feature.py +++ b/src/blenderbim/test/bim/test_feature.py @@ -297,6 +297,11 @@ def the_material_name_exists(name) -> bpy.types.Material: return obj +@then(parsers.parse('the material "{name}" does not exist')) +def the_material_name_does_not_exist(name): + assert bpy.data.materials.get(name) is None, "Material exists" + + @then("an IFC file does not exist") def an_ifc_file_does_not_exist(): ifc = IfcStore.get_file() diff --git a/src/blenderbim/test/core/test_material.py b/src/blenderbim/test/core/test_material.py index 5bdabdd9cd..accf5b50b7 100644 --- a/src/blenderbim/test/core/test_material.py +++ b/src/blenderbim/test/core/test_material.py @@ -92,6 +92,50 @@ class TestAddMaterialSet: assert subject.add_material_set(ifc, material, set_type="set_type") == "material" +class TestRemoveMaterial: + def test_removing_a_material(self, ifc, material, style): + ifc.get_object("material").should_be_called().will_return(None) + ifc.unlink(element="material").should_be_called() + ifc.run("material.remove_material", material="material").should_be_called() + material.is_editing_materials().should_be_called().will_return(False) + subject.remove_material(ifc, material, style, material="material") + + def test_removing_a_material_and_reloading_imported_materials(self, ifc, material, style): + ifc.get_object("material").should_be_called().will_return(None) + ifc.unlink(element="material").should_be_called() + ifc.run("material.remove_material", material="material").should_be_called() + material.is_editing_materials().should_be_called().will_return(True) + material.get_active_material_type().should_be_called().will_return("material_type") + material.import_material_definitions("material_type").should_be_called() + subject.remove_material(ifc, material, style, material="material") + + def test_removing_a_material_object_if_it_has_no_style(self, ifc, material, style): + ifc.get_object("material").should_be_called().will_return("obj") + ifc.unlink(element="material").should_be_called() + ifc.run("material.remove_material", material="material").should_be_called() + style.get_style("obj").should_be_called().will_return(None) + material.delete_object("obj").should_be_called() + material.is_editing_materials().should_be_called().will_return(False) + subject.remove_material(ifc, material, style, material="material") + + def test_preserving_a_material_object_if_it_is_still_used_as_a_style(self, ifc, material, style): + ifc.get_object("material").should_be_called().will_return("obj") + ifc.unlink(element="material").should_be_called() + ifc.run("material.remove_material", material="material").should_be_called() + style.get_style("obj").should_be_called().will_return("style") + material.is_editing_materials().should_be_called().will_return(False) + subject.remove_material(ifc, material, style, material="material") + + +class TestRemoveMaterialSet: + def test_run(self, ifc, material): + ifc.run("material.remove_material_set", material="material").should_be_called() + material.is_editing_materials().should_be_called().will_return(True) + material.get_active_material_type().should_be_called().will_return("material_type") + material.import_material_definitions("material_type").should_be_called() + subject.remove_material_set(ifc, material, material="material") + + class TestLoadMaterials: def test_run(self, material): material.import_material_definitions("material_type").should_be_called() diff --git a/src/blenderbim/test/tool/test_material.py b/src/blenderbim/test/tool/test_material.py index bf5ebea230..093aabec41 100644 --- a/src/blenderbim/test/tool/test_material.py +++ b/src/blenderbim/test/tool/test_material.py @@ -36,6 +36,14 @@ class TestAddDefaultMaterialObject(NewFile): assert material.name == "Default" +class TestDeleteObject(NewFile): + def test_run(self): + material = subject.add_default_material_object() + assert bpy.data.materials.get("Default") + subject.delete_object(material) + assert not bpy.data.materials.get("Default") + + class TestDisableEditingMaterials(NewFile): def test_run(self): bpy.context.scene.BIMMaterialProperties.is_editing = True