From a5adf0021f1ee101f5a827c718f40080a56abf11 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 2 Feb 2023 18:27:08 +1100 Subject: [PATCH] Fix #2599. Unassigning materials now also removes the Blender material. --- .../bim/module/material/operator.py | 43 +++++++------------ .../blenderbim/bim/module/model/handler.py | 4 ++ .../blenderbim/bim/module/model/product.py | 26 +++++++++++ .../test/bim/feature/material.feature | 22 ++-------- src/blenderbim/test/bim/test_feature.py | 5 +++ 5 files changed, 53 insertions(+), 47 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index bda297763c..1399562c0a 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -142,26 +142,20 @@ class AssignMaterial(bpy.types.Operator, tool.Ifc.Operator): material_type: bpy.props.StringProperty() def _execute(self, context): - self.file = IfcStore.get_file() - - if self.obj: - objects = [bpy.data.objects.get(self.obj)] - else: - objects = context.selected_objects + objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects active_obj = context.active_object active_object_material_type = self.material_type or active_obj.BIMObjectMaterialProperties.material_type - active_object_material = active_obj.BIMObjectMaterialProperties.material + material = tool.Ifc.get().by_id(int(active_obj.BIMObjectMaterialProperties.material)) for obj in objects: - element = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) - ifcopenshell.api.run( - "material.assign_material", - self.file, - **{ - "product": element, - "type": active_object_material_type, - "material": self.file.by_id(int(active_object_material)), - }, - ) + element = tool.Ifc.get_entity(obj) + if element: + ifcopenshell.api.run( + "material.assign_material", + tool.Ifc.get(), + product=element, + type=active_object_material_type, + material=material, + ) class UnassignMaterial(bpy.types.Operator, tool.Ifc.Operator): @@ -171,18 +165,11 @@ class UnassignMaterial(bpy.types.Operator, tool.Ifc.Operator): obj: bpy.props.StringProperty() def _execute(self, context): - self.file = IfcStore.get_file() - - if self.obj: - objects = [bpy.data.objects.get(self.obj)] - else: - objects = context.selected_objects + objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects for obj in objects: - ifcopenshell.api.run( - "material.unassign_material", - self.file, - **{"product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)}, - ) + element = tool.Ifc.get_entity(obj) + if element: + ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=element) class AddConstituent(bpy.types.Operator, tool.Ifc.Operator): diff --git a/src/blenderbim/blenderbim/bim/module/model/handler.py b/src/blenderbim/blenderbim/bim/module/model/handler.py index 886abfd793..90fc56adea 100644 --- a/src/blenderbim/blenderbim/bim/module/model/handler.py +++ b/src/blenderbim/blenderbim/bim/module/model/handler.py @@ -50,6 +50,10 @@ def load_post(*args): usecase, "BlenderBIM.Product.EnsureMaterialAssigned", product.ensure_material_assigned ) + ifcopenshell.api.add_post_listener( + "material.unassign_material", "BlenderBIM.Product.EnsureMaterialUnassigned", product.ensure_material_unassigned + ) + ifcopenshell.api.add_post_listener( "material.edit_profile_usage", "BlenderBIM.Product.RegenerateProfileUsage", diff --git a/src/blenderbim/blenderbim/bim/module/model/product.py b/src/blenderbim/blenderbim/bim/module/model/product.py index 187987a859..4058a5c926 100644 --- a/src/blenderbim/blenderbim/bim/module/model/product.py +++ b/src/blenderbim/blenderbim/bim/module/model/product.py @@ -527,3 +527,29 @@ def ensure_material_assigned(usecase_path, ifc_file, settings): continue obj.data.materials.append(IfcStore.get_element(material[0].id())) + + + +def ensure_material_unassigned(usecase_path, ifc_file, settings): + elements = [settings["product"]] + if elements[0].is_a("IfcElementType"): + elements.extend(ifcopenshell.util.element.get_types(elements[0])) + for element in elements: + obj = tool.Ifc.get_object(element) + if not obj or not obj.data: + continue + element_material = ifcopenshell.util.element.get_material(element) + if element_material: + continue + to_remove = [] + for i, slot in enumerate(obj.material_slots): + if not slot.material: + continue + material = tool.Ifc.get_entity(slot.material) + if material: + to_remove.append(i) + total_removed = 0 + for i in to_remove: + obj.active_material_index = i - total_removed + bpy.ops.object.material_slot_remove({'object': obj}) + total_removed += 1 diff --git a/src/blenderbim/test/bim/feature/material.feature b/src/blenderbim/test/bim/feature/material.feature index 7b4027ce16..db0bf23019 100644 --- a/src/blenderbim/test/bim/feature/material.feature +++ b/src/blenderbim/test/bim/feature/material.feature @@ -89,7 +89,7 @@ Scenario: Unassign material - single material And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterial" And I press "bim.assign_material" When I press "bim.unassign_material" - Then nothing happens + Then the object "IfcWall/Cube" does not have the material "Default" Scenario: Enable editing assigned material - single material Given an empty IFC project @@ -132,10 +132,6 @@ Scenario: Edit assigned material - single material Scenario: Assign material - material layer set Given an empty IFC project - And I add a cube - And the object "Cube" is selected - And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" - And I press "bim.assign_class" And I add an empty And the object "Empty" is selected And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" @@ -144,14 +140,10 @@ Scenario: Assign material - material layer set And I press "bim.add_material(obj='')" When I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet" And I press "bim.assign_material" - Then nothing happens + Then the object "IfcWallType/Empty" does not have the material "Default" Scenario: Unassign material - material layer set Given an empty IFC project - And I add a cube - And the object "Cube" is selected - And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" - And I press "bim.assign_class" And I add an empty And the object "Empty" is selected And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" @@ -219,10 +211,6 @@ Scenario: Edit assigned material - material layer set Scenario: Assign material - material profile set Given an empty IFC project - And I add a cube - And the object "Cube" is selected - And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" - And I press "bim.assign_class" And I add an empty And the object "Empty" is selected And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" @@ -231,14 +219,10 @@ Scenario: Assign material - material profile set And I press "bim.add_material(obj='')" When I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet" And I press "bim.assign_material" - Then nothing happens + Then the object "IfcWallType/Empty" does not have the material "Default" Scenario: Unassign material - material profile set Given an empty IFC project - And I add a cube - And the object "Cube" is selected - And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" - And I press "bim.assign_class" And I add an empty And the object "Empty" is selected And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" diff --git a/src/blenderbim/test/bim/test_feature.py b/src/blenderbim/test/bim/test_feature.py index 2b310fc151..1aa4c06bc4 100644 --- a/src/blenderbim/test/bim/test_feature.py +++ b/src/blenderbim/test/bim/test_feature.py @@ -498,6 +498,11 @@ def the_object_name_has_the_material_material(name, material): assert material in [ms.material.name for ms in the_object_name_exists(name).material_slots] +@then(parsers.parse('the object "{name}" does not have the material "{material}"')) +def the_object_name_does_not_have_the_material_material(name, material): + assert material not in [ms.material.name for ms in the_object_name_exists(name).material_slots] + + @then(parsers.parse('the object "{name}" is in the collection "{collection}"')) def the_object_name_is_in_the_collection_collection(name, collection): assert collection in [c.name for c in the_object_name_exists(name).users_collection]