diff --git a/src/bonsai/bonsai/bim/module/material/__init__.py b/src/bonsai/bonsai/bim/module/material/__init__.py index 5f14b170ee..50afbd4dff 100644 --- a/src/bonsai/bonsai/bim/module/material/__init__.py +++ b/src/bonsai/bonsai/bim/module/material/__init__.py @@ -56,6 +56,7 @@ classes = ( operator.RemoveMaterial, operator.RemoveMaterialSet, operator.RemoveProfile, + operator.RenameMaterial, operator.ReorderMaterialSetItem, operator.SelectByMaterial, operator.SelectMaterialInMaterialsUI, diff --git a/src/bonsai/bonsai/bim/module/material/operator.py b/src/bonsai/bonsai/bim/module/material/operator.py index 6ec1ccf188..b2ab1cfa06 100644 --- a/src/bonsai/bonsai/bim/module/material/operator.py +++ b/src/bonsai/bonsai/bim/module/material/operator.py @@ -102,6 +102,26 @@ class EditMaterial(bpy.types.Operator, tool.Ifc.Operator): core.edit_material(tool.Ifc, tool.Material, material=tool.Ifc.get().by_id(self.material)) +class RenameMaterial(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.rename_material" + bl_label = "Rename Material" + bl_description = "Rename an IfcMaterial" + bl_options = {"REGISTER", "UNDO"} + material: bpy.props.IntProperty() + name: bpy.props.StringProperty(name="Name") + + def invoke(self, context, event): + material = tool.Ifc.get().by_id(self.material) + self.name = material.Name or "" + return context.window_manager.invoke_props_dialog(self) + + def draw(self, context): + self.layout.prop(self, "name") + + def _execute(self, context): + core.rename_material(tool.Ifc, tool.Material, material=tool.Ifc.get().by_id(self.material), name=self.name) + + class DisableEditingMaterial(bpy.types.Operator): bl_idname = "bim.disable_editing_material" bl_label = "Disable Editing Material" diff --git a/src/bonsai/bonsai/bim/ui.py b/src/bonsai/bonsai/bim/ui.py index 5e79cba3aa..829e0ea25c 100644 --- a/src/bonsai/bonsai/bim/ui.py +++ b/src/bonsai/bonsai/bim/ui.py @@ -41,6 +41,7 @@ import bonsai.bim.helper import bonsai.tool as tool from bonsai.bim.ifc import is_cache_locked_by_other_process from bonsai.bim.module.bsdd.prop import BIMBSDDProperties, BSDDProperty +from bonsai.bim.module.material.operator import SelectByMaterial from bonsai.bim.module.model import prop as _model_prop from bonsai.bim.module.model import ui as _model_ui from bonsai.bim.module.pset.prop import IfcProperty @@ -1854,6 +1855,21 @@ def draw_statusbar(self, context): def draw_custom_context_menu(self: bpy.types.Menu, context: bpy.types.Context) -> None: # https://blender.stackexchange.com/a/275555/86891 + + # Context menu for material name buttons (e.g. `bim.select_by_material`), + # offering a quick "Rename Material" entry instead of having to look up + # the material in the scene Materials panel to rename it. + button_operator = getattr(context, "button_operator", None) + if button_operator is not None and button_operator.bl_rna.identifier == SelectByMaterial.bl_rna.identifier: + ifc_file = tool.Ifc.get() + material = ifc_file.by_id(button_operator.material) if ifc_file else None + if material is not None and material.is_a("IfcMaterial"): + assert self.layout + self.layout.separator() + op = self.layout.operator("bim.rename_material", text="Rename Material", icon="GREASEPENCIL") + op.material = material.id() + return + if ( not hasattr(context, "button_pointer") or not hasattr(context, "button_prop") diff --git a/src/bonsai/bonsai/core/material.py b/src/bonsai/bonsai/core/material.py index 0a08f5e0bf..96c624de64 100644 --- a/src/bonsai/bonsai/core/material.py +++ b/src/bonsai/bonsai/core/material.py @@ -107,6 +107,15 @@ def disable_editing_material(material_tool: type[tool.Material]) -> None: material_tool.disable_editing_material() +def rename_material( + ifc: type[tool.Ifc], material_tool: type[tool.Material], material: ifcopenshell.entity_instance, name: str +) -> None: + ifc.run("material.edit_material", material=material, attributes={"Name": name}) + if material_tool.is_editing_materials(): + material_tool.import_material_definitions(material_tool.get_active_material_type()) + material_tool.refresh() + + def assign_material( ifc: type[tool.Ifc], material_tool: type[tool.Material], diff --git a/src/bonsai/bonsai/core/tool.py b/src/bonsai/bonsai/core/tool.py index 51120ad088..8a77d36661 100644 --- a/src/bonsai/bonsai/core/tool.py +++ b/src/bonsai/bonsai/core/tool.py @@ -667,6 +667,7 @@ class Material: def is_editing_materials(cls): pass def is_material_used_in_sets(cls, material): pass def load_material_attributes(cls, material): pass + def refresh(cls): pass def replace_material_with_material_profile(cls, element): pass def update_elements_using_material(cls, material): pass diff --git a/src/bonsai/bonsai/tool/material.py b/src/bonsai/bonsai/tool/material.py index 4c55123a9c..300df8d4ce 100644 --- a/src/bonsai/bonsai/tool/material.py +++ b/src/bonsai/bonsai/tool/material.py @@ -146,6 +146,12 @@ class Material(bonsai.core.tool.Material): MaterialsData.data["material_styles_data"] = MaterialsData.material_styles_data() + @classmethod + def refresh(cls) -> None: + from bonsai.bim.module.material.data import refresh as refresh_material_data + + refresh_material_data() + @classmethod def is_editing_materials(cls) -> bool: props = tool.Material.get_material_props() diff --git a/src/bonsai/test/core/test_material.py b/src/bonsai/test/core/test_material.py index a2cbf8c433..28fe0ba326 100644 --- a/src/bonsai/test/core/test_material.py +++ b/src/bonsai/test/core/test_material.py @@ -93,6 +93,22 @@ class TestRemoveMaterialSet: subject.remove_material_set(ifc, material, material="material") +class TestRenameMaterial: + def test_renaming_a_material(self, ifc, material): + ifc.run("material.edit_material", material="material", attributes={"Name": "name"}).should_be_called() + material.is_editing_materials().should_be_called().will_return(False) + material.refresh().should_be_called() + subject.rename_material(ifc, material, material="material", name="name") + + def test_renaming_a_material_and_reloading_imported_materials(self, ifc, material): + ifc.run("material.edit_material", material="material", attributes={"Name": "name"}).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() + material.refresh().should_be_called() + subject.rename_material(ifc, material, material="material", name="name") + + class TestLoadMaterials: def test_run(self, material): material.import_material_definitions("material_type").should_be_called() diff --git a/src/bonsai/test/tool/test_material.py b/src/bonsai/test/tool/test_material.py index 870417a254..9cee7a5962 100644 --- a/src/bonsai/test/tool/test_material.py +++ b/src/bonsai/test/tool/test_material.py @@ -139,6 +139,17 @@ class TestImportMaterialDefinitions(NewFile): assert props.materials[0].total_elements == 0 +class TestRefresh(NewFile): + def test_run(self): + from bonsai.bim.module.material.data import MaterialsData, ObjectMaterialData + + MaterialsData.is_loaded = True + ObjectMaterialData.is_loaded = True + subject.refresh() + assert MaterialsData.is_loaded is False + assert ObjectMaterialData.is_loaded is False + + class TestIsEditingMaterials(NewFile): def test_run(self): props = tool.Material.get_material_props()