#2094. New rudimentary project materials browser to manage materials.

This commit is contained in:
Dion Moult
2022-03-18 17:24:59 +11:00
parent dcc1ed5607
commit b8aa90de00
14 changed files with 274 additions and 11 deletions
@@ -29,7 +29,7 @@ class BIM_PT_documents(Panel):
bl_space_type = "PROPERTIES" bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW" bl_region_type = "WINDOW"
bl_context = "scene" bl_context = "scene"
bl_parent_id = "BIM_PT_collaboration" bl_parent_id = "BIM_PT_project_setup"
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@@ -31,10 +31,12 @@ classes = (
operator.CopyMaterial, operator.CopyMaterial,
operator.DisableEditingAssignedMaterial, operator.DisableEditingAssignedMaterial,
operator.DisableEditingMaterialSetItem, operator.DisableEditingMaterialSetItem,
operator.DisableEditingMaterials,
operator.EditAssignedMaterial, operator.EditAssignedMaterial,
operator.EditMaterialSetItem, operator.EditMaterialSetItem,
operator.EnableEditingAssignedMaterial, operator.EnableEditingAssignedMaterial,
operator.EnableEditingMaterialSetItem, operator.EnableEditingMaterialSetItem,
operator.LoadMaterials,
operator.RemoveConstituent, operator.RemoveConstituent,
operator.RemoveLayer, operator.RemoveLayer,
operator.RemoveListItem, operator.RemoveListItem,
@@ -43,15 +45,21 @@ classes = (
operator.ReorderMaterialSetItem, operator.ReorderMaterialSetItem,
operator.UnassignMaterial, operator.UnassignMaterial,
operator.UnlinkMaterial, operator.UnlinkMaterial,
prop.Material,
prop.BIMMaterialProperties,
prop.BIMObjectMaterialProperties, prop.BIMObjectMaterialProperties,
ui.BIM_PT_materials,
ui.BIM_PT_material, ui.BIM_PT_material,
ui.BIM_PT_object_material, ui.BIM_PT_object_material,
ui.BIM_UL_materials,
) )
def register(): def register():
bpy.types.Scene.BIMMaterialProperties = bpy.props.PointerProperty(type=prop.BIMMaterialProperties)
bpy.types.Object.BIMObjectMaterialProperties = bpy.props.PointerProperty(type=prop.BIMObjectMaterialProperties) bpy.types.Object.BIMObjectMaterialProperties = bpy.props.PointerProperty(type=prop.BIMObjectMaterialProperties)
def unregister(): def unregister():
del bpy.types.Scene.BIMMaterialProperties
del bpy.types.Object.BIMObjectMaterialProperties del bpy.types.Object.BIMObjectMaterialProperties
@@ -0,0 +1,63 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of BlenderBIM Add-on.
#
# BlenderBIM Add-on is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BlenderBIM Add-on is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import os
import bpy
import ifcopenshell
import ifcopenshell.util.schema
import blenderbim.tool as tool
def refresh():
MaterialsData.is_loaded = False
class MaterialsData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {
"total_materials": cls.total_materials(),
"material_types": cls.material_types(),
}
cls.is_loaded = True
@classmethod
def total_materials(cls):
return (
len(tool.Ifc.get().by_type("IfcMaterial"))
+ len(tool.Ifc.get().by_type("IfcMaterialConstituentSet"))
+ len(tool.Ifc.get().by_type("IfcMaterialLayerSet"))
+ len(tool.Ifc.get().by_type("IfcMaterialProfileSet"))
+ len(tool.Ifc.get().by_type("IfcMaterialList"))
)
@classmethod
def material_types(cls):
material_types = [
"IfcMaterial",
"IfcMaterialConstituentSet",
"IfcMaterialLayerSet",
"IfcMaterialProfileSet",
"IfcMaterialList",
]
if tool.Ifc.get_schema() == "IFC2X3":
material_types = ["IfcMaterial", "IfcMaterialLayerSet", "IfcMaterialList"]
return [(m, m, "") for m in material_types]
@@ -31,11 +31,22 @@ from ifcopenshell.api.material.data import Data
from ifcopenshell.api.profile.data import Data as ProfileData from ifcopenshell.api.profile.data import Data as ProfileData
class Operator: class LoadMaterials(bpy.types.Operator, tool.Ifc.Operator):
def execute(self, context): bl_idname = "bim.load_materials"
IfcStore.execute_ifc_operator(self, context) bl_label = "Load Materials"
blenderbim.bim.handler.refresh_ui_data() bl_options = {"REGISTER", "UNDO"}
return {"FINISHED"}
def _execute(self, context):
core.load_materials(tool.Material, context.scene.BIMMaterialProperties.material_type)
class DisableEditingMaterials(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_materials"
bl_label = "Disable Editing Materials"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
core.disable_editing_materials(tool.Material)
class AssignParameterizedProfile(bpy.types.Operator): class AssignParameterizedProfile(bpy.types.Operator):
@@ -68,7 +79,7 @@ class AssignParameterizedProfile(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class AddDefaultMaterial(bpy.types.Operator, Operator): class AddDefaultMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_default_material" bl_idname = "bim.add_default_material"
bl_label = "Add Default Material" bl_label = "Add Default Material"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@@ -131,7 +142,7 @@ class RemoveMaterial(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class UnlinkMaterial(bpy.types.Operator, Operator): class UnlinkMaterial(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.unlink_material" bl_idname = "bim.unlink_material"
bl_label = "Unlink Material" bl_label = "Unlink Material"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@@ -18,6 +18,7 @@
import bpy import bpy
from ifcopenshell.api.material.data import Data from ifcopenshell.api.material.data import Data
from blenderbim.bim.module.material.data import MaterialsData
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.prop import StrProperty, Attribute from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
@@ -104,6 +105,24 @@ def getMaterialTypes(self, context):
return materialtypes_enum return materialtypes_enum
def get_material_types(self, context):
if not MaterialsData.is_loaded:
MaterialsData.load()
return MaterialsData.data["material_types"]
class Material(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
class BIMMaterialProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing", default=False)
material_type: EnumProperty(items=get_material_types, name="Material Type")
materials: CollectionProperty(name="Materials", type=Material)
active_material_index: IntProperty(name="Active Material Index")
class BIMObjectMaterialProperties(PropertyGroup): class BIMObjectMaterialProperties(PropertyGroup):
material_type: EnumProperty(items=getMaterialTypes, name="Material Type") material_type: EnumProperty(items=getMaterialTypes, name="Material Type")
material: EnumProperty(items=getMaterials, name="Material") material: EnumProperty(items=getMaterials, name="Material")
@@ -17,11 +17,44 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.bim.helper import blenderbim.bim.helper
from bpy.types import Panel from bpy.types import Panel, UIList
from ifcopenshell.api.material.data import Data from ifcopenshell.api.material.data import Data
from ifcopenshell.api.profile.data import Data as ProfileData from ifcopenshell.api.profile.data import Data as ProfileData
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from blenderbim.bim.helper import draw_attributes from blenderbim.bim.helper import draw_attributes
from blenderbim.bim.module.material.data import MaterialsData
class BIM_PT_materials(Panel):
bl_label = "IFC Materials"
bl_idname = "BIM_PT_materials"
bl_options = {"DEFAULT_CLOSED"}
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
bl_parent_id = "BIM_PT_geometry"
@classmethod
def poll(cls, context):
return IfcStore.get_file()
def draw(self, context):
if not MaterialsData.is_loaded:
MaterialsData.load()
self.props = context.scene.BIMMaterialProperties
row = self.layout.row(align=True)
row.label(text="{} Materials Found".format(MaterialsData.data["total_materials"]), icon="MATERIAL")
if self.props.is_editing:
row.operator("bim.disable_editing_materials", text="", icon="CANCEL")
else:
row = self.layout.row(align=True)
row.prop(self.props, "material_type", text="")
row.operator("bim.load_materials", text="", icon="IMPORT")
return
self.layout.template_list("BIM_UL_materials", "", self.props, "materials", self.props, "active_material_index")
class BIM_PT_material(Panel): class BIM_PT_material(Panel):
@@ -333,3 +366,10 @@ class BIM_PT_object_material(Panel):
if total_thickness: if total_thickness:
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.label(text=f"Total Thickness: {total_thickness:.3f}") row.label(text=f"Total Thickness: {total_thickness:.3f}")
class BIM_UL_materials(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
row = layout.row(align=True)
row.label(text=item.name)
+1 -1
View File
@@ -233,7 +233,7 @@ class BIM_PT_geometry(Panel):
class BIM_PT_4D5D(Panel): class BIM_PT_4D5D(Panel):
bl_label = "IFC 4D/5D" bl_label = "IFC Costing and Scheduling"
bl_space_type = "PROPERTIES" bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW" bl_region_type = "WINDOW"
bl_context = "scene" bl_context = "scene"
@@ -25,3 +25,12 @@ def add_default_material(ifc, material):
obj = material.add_default_material_object() obj = material.add_default_material_object()
ifc.link(ifc.run("material.add_material", name="Default"), obj) ifc.link(ifc.run("material.add_material", name="Default"), obj)
return obj return obj
def load_materials(material, material_type):
material.import_material_definitions(material_type)
material.enable_editing_materials()
def disable_editing_materials(material):
material.disable_editing_materials()
+3
View File
@@ -276,6 +276,9 @@ class Library:
@interface @interface
class Material: class Material:
def add_default_material_object(cls): pass def add_default_material_object(cls): pass
def disable_editing_materials(cls): pass
def enable_editing_materials(cls): pass
def import_material_definitions(cls, material_type): pass
@interface @interface
@@ -27,3 +27,25 @@ class Material(blenderbim.core.tool.Material):
@classmethod @classmethod
def add_default_material_object(cls): def add_default_material_object(cls):
return bpy.data.materials.new("Default") return bpy.data.materials.new("Default")
@classmethod
def disable_editing_materials(cls):
bpy.context.scene.BIMMaterialProperties.is_editing = False
@classmethod
def enable_editing_materials(cls):
bpy.context.scene.BIMMaterialProperties.is_editing = True
@classmethod
def import_material_definitions(cls, material_type):
props = bpy.context.scene.BIMMaterialProperties
props.materials.clear()
for material in tool.Ifc.get().by_type(material_type):
new = props.materials.add()
new.ifc_definition_id = material.id()
if material.is_a("IfcMaterialLayerSet"):
new.name = material.LayerSetName or "Unnamed"
elif material.is_a("IfcMaterialList"):
new.name = "Unnamed"
else:
new.name = material.Name or "Unnamed"
@@ -1,6 +1,19 @@
@material @material
Feature: Material Feature: Material
Scenario: Load materials
Given an empty IFC project
And I press "bim.add_default_material"
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.load_materials"
When I press "bim.disable_editing_materials"
Then nothing happens
Scenario: Add default material Scenario: Add default material
Given an empty IFC project Given an empty IFC project
When I press "bim.add_default_material" When I press "bim.add_default_material"
+2 -1
View File
@@ -141,7 +141,8 @@ class TestDisableEditingDrawings:
class TestAddDrawing: class TestAddDrawing:
def test_run(self, ifc, collector, drawing): def test_run(self, ifc, collector, drawing):
drawing.ensure_unique_drawing_name("UNTITLED").should_be_called().will_return("name") drawing.generate_drawing_name("target_view", "location_hint").should_be_called().will_return("drawing_name")
drawing.ensure_unique_drawing_name("drawing_name").should_be_called().will_return("name")
drawing.generate_drawing_matrix("target_view", "location_hint").should_be_called().will_return("matrix") drawing.generate_drawing_matrix("target_view", "location_hint").should_be_called().will_return("matrix")
drawing.create_camera("name", "matrix").should_be_called().will_return("obj") drawing.create_camera("name", "matrix").should_be_called().will_return("obj")
drawing.get_body_context().should_be_called().will_return("context") drawing.get_body_context().should_be_called().will_return("context")
+13
View File
@@ -32,3 +32,16 @@ class TestAddDefaultMaterial:
ifc.run("material.add_material", name="Default").should_be_called().will_return("material") ifc.run("material.add_material", name="Default").should_be_called().will_return("material")
ifc.link("material", "obj").should_be_called() ifc.link("material", "obj").should_be_called()
assert subject.add_default_material(ifc, material) == "obj" assert subject.add_default_material(ifc, material) == "obj"
class TestLoadMaterials:
def test_run(self, material):
material.import_material_definitions("material_type").should_be_called()
material.enable_editing_materials().should_be_called()
subject.load_materials(material, "material_type")
class TestDisableEditingMaterials:
def test_run(self, material):
material.disable_editing_materials().should_be_called()
subject.disable_editing_materials(material)
+61
View File
@@ -34,3 +34,64 @@ class TestAddDefaultMaterialObject(NewFile):
material = subject.add_default_material_object() material = subject.add_default_material_object()
assert isinstance(material, bpy.types.Material) assert isinstance(material, bpy.types.Material)
assert material.name == "Default" assert material.name == "Default"
class TestDisableEditingMaterials(NewFile):
def test_run(self):
bpy.context.scene.BIMMaterialProperties.is_editing = True
subject.disable_editing_materials()
assert bpy.context.scene.BIMMaterialProperties.is_editing is False
class TestEnableEditingMaterials(NewFile):
def test_run(self):
bpy.context.scene.BIMMaterialProperties.is_editing = False
subject.enable_editing_materials()
assert bpy.context.scene.BIMMaterialProperties.is_editing is True
class TestImportMaterialDefinitions(NewFile):
def test_import_materials(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
material = ifc.createIfcMaterial(Name="Name")
subject.import_material_definitions("IfcMaterial")
props = bpy.context.scene.BIMMaterialProperties
assert props.materials[0].ifc_definition_id == material.id()
assert props.materials[0].name == "Name"
def test_import_material_layer_sets(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
material = ifc.createIfcMaterialLayerSet(LayerSetName="Name")
subject.import_material_definitions("IfcMaterialLayerSet")
props = bpy.context.scene.BIMMaterialProperties
assert props.materials[0].ifc_definition_id == material.id()
assert props.materials[0].name == "Name"
def test_import_material_profile_sets(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
material = ifc.createIfcMaterialProfileSet(Name="Name")
subject.import_material_definitions("IfcMaterialProfileSet")
props = bpy.context.scene.BIMMaterialProperties
assert props.materials[0].ifc_definition_id == material.id()
assert props.materials[0].name == "Name"
def test_import_material_constituent_sets(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
material = ifc.createIfcMaterialConstituentSet(Name="Name")
subject.import_material_definitions("IfcMaterialConstituentSet")
props = bpy.context.scene.BIMMaterialProperties
assert props.materials[0].ifc_definition_id == material.id()
assert props.materials[0].name == "Name"
def test_import_material_lists(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
material = ifc.createIfcMaterialList()
subject.import_material_definitions("IfcMaterialList")
props = bpy.context.scene.BIMMaterialProperties
assert props.materials[0].ifc_definition_id == material.id()
assert props.materials[0].name == "Unnamed"