diff --git a/src/blenderbim/blenderbim/bim/module/material/__init__.py b/src/blenderbim/blenderbim/bim/module/material/__init__.py index 0c42d9c04d..d836036937 100644 --- a/src/blenderbim/blenderbim/bim/module/material/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/material/__init__.py @@ -21,10 +21,10 @@ from . import ui, prop, operator classes = ( operator.AddConstituent, - operator.AddDefaultMaterial, operator.AddLayer, operator.AddListItem, operator.AddMaterial, + operator.AddMaterialSet, operator.AddProfile, operator.AssignMaterial, operator.AssignParameterizedProfile, diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index 4fa6685123..0bc6e0f969 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -79,45 +79,29 @@ class AssignParameterizedProfile(bpy.types.Operator): return {"FINISHED"} -class AddDefaultMaterial(bpy.types.Operator, tool.Ifc.Operator): - bl_idname = "bim.add_default_material" - bl_label = "Add Default Material" - bl_options = {"REGISTER", "UNDO"} - - def _execute(self, context): - core.add_default_material(tool.Ifc, tool.Material) - Data.load(IfcStore.get_file()) - - -class AddMaterial(bpy.types.Operator): +class AddMaterial(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_material" bl_label = "Add Material" bl_options = {"REGISTER", "UNDO"} obj: bpy.props.StringProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - 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.add_material", self.file, **{"name": obj.name}) - IfcStore.link_element(result, obj) - if obj.BIMMaterialProperties.ifc_style_id: - context = ifcopenshell.util.representation.get_context(self.file, "Model", "Body", "MODEL_VIEW") - if context: - ifcopenshell.api.run( - "style.assign_material_style", - self.file, - **{ - "material": result, - "style": self.file.by_id(obj.BIMMaterialProperties.ifc_style_id), - "context": context, - }, - ) + obj = bpy.data.materials.get(self.obj) if self.obj else None + core.add_material(tool.Ifc, tool.Material, tool.Style, obj=obj) + Data.load(IfcStore.get_file()) + material_prop_purge() + + +class AddMaterialSet(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.add_material_set" + bl_label = "Add Material Set" + bl_options = {"REGISTER", "UNDO"} + set_type: bpy.props.StringProperty() + + def _execute(self, context): + core.add_material_set(tool.Ifc, tool.Material, set_type=self.set_type) Data.load(IfcStore.get_file()) material_prop_purge() - return {"FINISHED"} class RemoveMaterial(bpy.types.Operator): diff --git a/src/blenderbim/blenderbim/bim/module/material/ui.py b/src/blenderbim/blenderbim/bim/module/material/ui.py index 24f345a125..0bbc09c630 100644 --- a/src/blenderbim/blenderbim/bim/module/material/ui.py +++ b/src/blenderbim/blenderbim/bim/module/material/ui.py @@ -54,6 +54,14 @@ class BIM_PT_materials(Panel): row.operator("bim.load_materials", text="", icon="IMPORT") return + row = self.layout.row(align=True) + row.alignment = "RIGHT" + + if self.props.material_type == "IfcMaterial": + row.operator("bim.add_material", text="", icon="ADD") + else: + row.operator("bim.add_material_set", text="", icon="ADD").set_type = self.props.material_type + self.layout.template_list("BIM_UL_materials", "", self.props, "materials", self.props, "active_material_index") @@ -74,7 +82,8 @@ class BIM_PT_material(Panel): row.operator("bim.remove_material", icon="X", text="Remove IFC Material") row.operator("bim.unlink_material", icon="UNLINKED", text="") else: - row.operator("bim.add_material", icon="ADD", text="Create IFC Material") + op = row.operator("bim.add_material", icon="ADD", text="Create IFC Material") + op.obj = context.active_object.active_material.name class BIM_PT_object_material(Panel): @@ -113,7 +122,7 @@ class BIM_PT_object_material(Panel): if not Data.materials: row = self.layout.row(align=True) row.label(text="No Materials Available") - row.operator("bim.add_default_material", icon="ADD", text="") + row.operator("bim.add_material", icon="ADD", text="").obj = "" return if self.product_data: diff --git a/src/blenderbim/blenderbim/core/material.py b/src/blenderbim/blenderbim/core/material.py index ce2f6466dc..df9b35939c 100644 --- a/src/blenderbim/blenderbim/core/material.py +++ b/src/blenderbim/blenderbim/core/material.py @@ -21,10 +21,26 @@ def unlink_material(ifc, obj=None): ifc.unlink(obj=obj) -def add_default_material(ifc, material): - obj = material.add_default_material_object() - ifc.link(ifc.run("material.add_material", name="Default"), obj) - return obj +def add_material(ifc, material, style, obj=None): + if not obj: + obj = material.add_default_material_object() + ifc_material = ifc.run("material.add_material", name=material.get_name(obj)) + ifc.link(ifc_material, obj) + ifc_style = style.get_style(obj) + if ifc_style: + context = style.get_context(obj) + if context: + ifc.run("style.assign_material_style", material=ifc_material, style=ifc_style, context=context) + if material.is_editing_materials(): + material.import_material_definitions(material.get_active_material_type()) + return ifc_material + + +def add_material_set(ifc, material, set_type=None): + ifc_material = ifc.run("material.add_material_set", name="Unnamed", set_type=set_type) + if material.is_editing_materials(): + material.import_material_definitions(material.get_active_material_type()) + return ifc_material def load_materials(material, material_type): diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index f4a9450609..0e9f272a1d 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -278,7 +278,10 @@ class Material: def add_default_material_object(cls): pass def disable_editing_materials(cls): pass def enable_editing_materials(cls): pass + def get_active_material_type(cls): pass + def get_name(cls, obj): pass def import_material_definitions(cls, material_type): pass + def is_editing_materials(cls): pass @interface diff --git a/src/blenderbim/blenderbim/tool/material.py b/src/blenderbim/blenderbim/tool/material.py index 0c965347fe..304d9770e6 100644 --- a/src/blenderbim/blenderbim/tool/material.py +++ b/src/blenderbim/blenderbim/tool/material.py @@ -36,6 +36,14 @@ class Material(blenderbim.core.tool.Material): def enable_editing_materials(cls): bpy.context.scene.BIMMaterialProperties.is_editing = True + @classmethod + def get_active_material_type(cls): + return bpy.context.scene.BIMMaterialProperties.material_type + + @classmethod + def get_name(cls, obj): + return obj.name + @classmethod def import_material_definitions(cls, material_type): props = bpy.context.scene.BIMMaterialProperties @@ -49,3 +57,7 @@ class Material(blenderbim.core.tool.Material): new.name = "Unnamed" else: new.name = material.Name or "Unnamed" + + @classmethod + def is_editing_materials(cls): + return bpy.context.scene.BIMMaterialProperties.is_editing diff --git a/src/blenderbim/test/bim/feature/material.feature b/src/blenderbim/test/bim/feature/material.feature index 4dafefaec2..db52e2b897 100644 --- a/src/blenderbim/test/bim/feature/material.feature +++ b/src/blenderbim/test/bim/feature/material.feature @@ -3,20 +3,33 @@ Feature: Material Scenario: Load materials Given an empty IFC project - And I press "bim.add_default_material" + And I press "bim.add_material(obj='')" When I press "bim.load_materials" Then nothing happens Scenario: Disable editing materials Given an empty IFC project - And I press "bim.add_default_material" + And I press "bim.add_material(obj='')" And I press "bim.load_materials" When I press "bim.disable_editing_materials" Then nothing happens +Scenario: Load materials - then add material + Given an empty IFC project + And I press "bim.load_materials" + When I press "bim.add_material(obj='')" + Then the material "Default" exists + +Scenario: Load materials - then add material set + Given an empty IFC project + And I set "scene.BIMMaterialProperties.material_type" to "IfcMaterialLayerSet" + And I press "bim.load_materials" + When I press "bim.add_material_set(set_type='IfcMaterialLayerSet')" + Then nothing happens + Scenario: Add default material Given an empty IFC project - When I press "bim.add_default_material" + When I press "bim.add_material(obj='')" Then the material "Default" exists Scenario: Add material @@ -24,7 +37,7 @@ Scenario: Add material And I add a cube And the object "Cube" is selected And I add a material - When I press "bim.add_material" + When I press "bim.add_material(obj='Material')" Then the material "Material" is an IFC material Scenario: Remove material @@ -32,7 +45,7 @@ Scenario: Remove material And I add a cube And the object "Cube" is selected And I add a material - And I press "bim.add_material" + And I press "bim.add_material(obj='Material')" When I press "bim.remove_material" Then the material "Material" is not an IFC material @@ -41,7 +54,7 @@ Scenario: Unlink material And I add a cube And the object "Cube" is selected And I add a material - And I press "bim.add_material" + And I press "bim.add_material(obj='Material')" When I press "bim.unlink_material" Then the material "Material" is not an IFC material @@ -51,7 +64,7 @@ Scenario: Assign material - Assign a material And the object "Cube" is selected And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" And I press "bim.assign_class" - And I press "bim.add_default_material" + And I press "bim.add_material(obj='')" When I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterial" And I press "bim.assign_material" Then the object "IfcWall/Cube" has the material "Default" @@ -67,7 +80,7 @@ Scenario: Assign material - Assign a material layer set And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" And I press "bim.assign_class" - And I press "bim.add_default_material" + 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 @@ -83,7 +96,7 @@ Scenario: Assign material - Assign a material profile set And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" And I press "bim.assign_class" - And I press "bim.add_default_material" + 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 diff --git a/src/blenderbim/test/core/test_material.py b/src/blenderbim/test/core/test_material.py index 86404c1aa6..5bdabdd9cd 100644 --- a/src/blenderbim/test/core/test_material.py +++ b/src/blenderbim/test/core/test_material.py @@ -17,7 +17,7 @@ # along with BlenderBIM Add-on. If not, see . import blenderbim.core.material as subject -from test.core.bootstrap import ifc, material +from test.core.bootstrap import ifc, material, style class TestUnlinkMaterial: @@ -26,12 +26,70 @@ class TestUnlinkMaterial: subject.unlink_material(ifc, obj="obj") -class TestAddDefaultMaterial: - def test_run(self, ifc, material): +class TestAddMaterial: + def test_add_a_default_material(self, ifc, material, style): material.add_default_material_object().should_be_called().will_return("obj") - ifc.run("material.add_material", name="Default").should_be_called().will_return("material") + material.get_name("obj").should_be_called().will_return("name") + ifc.run("material.add_material", name="name").should_be_called().will_return("material") ifc.link("material", "obj").should_be_called() - assert subject.add_default_material(ifc, material) == "obj" + style.get_style("obj").should_be_called().will_return(None) + material.is_editing_materials().should_be_called().will_return(False) + assert subject.add_material(ifc, material, style) == "material" + + def test_add_a_material_to_a_blender_material_object(self, ifc, material, style): + material.get_name("obj").should_be_called().will_return("name") + ifc.run("material.add_material", name="name").should_be_called().will_return("material") + ifc.link("material", "obj").should_be_called() + style.get_style("obj").should_be_called().will_return(None) + material.is_editing_materials().should_be_called().will_return(False) + assert subject.add_material(ifc, material, style, obj="obj") == "material" + + def test_reloading_imported_materials_if_you_are_editing_scene_materials(self, ifc, material, style): + material.get_name("obj").should_be_called().will_return("name") + ifc.run("material.add_material", name="name").should_be_called().will_return("material") + ifc.link("material", "obj").should_be_called() + style.get_style("obj").should_be_called().will_return(None) + 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() + assert subject.add_material(ifc, material, style, obj="obj") == "material" + + def test_add_a_style_to_the_material_if_the_object_also_has_an_attached_style(self, ifc, material, style): + material.get_name("obj").should_be_called().will_return("name") + ifc.run("material.add_material", name="name").should_be_called().will_return("material") + ifc.link("material", "obj").should_be_called() + style.get_style("obj").should_be_called().will_return("style") + style.get_context("obj").should_be_called().will_return("context") + ifc.run("style.assign_material_style", material="material", style="style", context="context").should_be_called() + material.is_editing_materials().should_be_called().will_return(False) + assert subject.add_material(ifc, material, style, obj="obj") == "material" + + def test_not_adding_a_style_if_there_is_no_style_context_available(self, ifc, material, style): + material.get_name("obj").should_be_called().will_return("name") + ifc.run("material.add_material", name="name").should_be_called().will_return("material") + ifc.link("material", "obj").should_be_called() + style.get_style("obj").should_be_called().will_return("style") + style.get_context("obj").should_be_called().will_return(None) + material.is_editing_materials().should_be_called().will_return(False) + assert subject.add_material(ifc, material, style, obj="obj") == "material" + + +class TestAddMaterialSet: + def test_adding_a_material_set(self, ifc, material): + ifc.run("material.add_material_set", name="Unnamed", set_type="set_type").should_be_called().will_return( + "material" + ) + material.is_editing_materials().should_be_called().will_return(False) + assert subject.add_material_set(ifc, material, set_type="set_type") == "material" + + def test_adding_a_material_set_and_reloading_imported_materials(self, ifc, material): + ifc.run("material.add_material_set", name="Unnamed", set_type="set_type").should_be_called().will_return( + "material" + ) + 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() + assert subject.add_material_set(ifc, material, set_type="set_type") == "material" class TestLoadMaterials: diff --git a/src/blenderbim/test/tool/test_material.py b/src/blenderbim/test/tool/test_material.py index bd739a864e..bf5ebea230 100644 --- a/src/blenderbim/test/tool/test_material.py +++ b/src/blenderbim/test/tool/test_material.py @@ -50,6 +50,21 @@ class TestEnableEditingMaterials(NewFile): assert bpy.context.scene.BIMMaterialProperties.is_editing is True +class TestGetActiveMaterialType(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + bpy.context.scene.BIMMaterialProperties.material_type = "IfcMaterial" + assert subject.get_active_material_type() == "IfcMaterial" + bpy.context.scene.BIMMaterialProperties.material_type = "IfcMaterialLayerSet" + assert subject.get_active_material_type() == "IfcMaterialLayerSet" + + +class TestGetName(NewFile): + def test_run(self): + assert subject.get_name(bpy.data.materials.new("Material")) == "Material" + + class TestImportMaterialDefinitions(NewFile): def test_import_materials(self): ifc = ifcopenshell.file() @@ -95,3 +110,11 @@ class TestImportMaterialDefinitions(NewFile): props = bpy.context.scene.BIMMaterialProperties assert props.materials[0].ifc_definition_id == material.id() assert props.materials[0].name == "Unnamed" + + +class TestIsEditingMaterials(NewFile): + def test_run(self): + bpy.context.scene.BIMMaterialProperties.is_editing = False + subject.is_editing_materials() is False + bpy.context.scene.BIMMaterialProperties.is_editing = True + subject.is_editing_materials() is True