diff --git a/src/blenderbim/blenderbim/bim/module/material/__init__.py b/src/blenderbim/blenderbim/bim/module/material/__init__.py index 8ba682ae5c..818cd76977 100644 --- a/src/blenderbim/blenderbim/bim/module/material/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/material/__init__.py @@ -28,6 +28,7 @@ classes = ( operator.AddProfile, operator.AssignMaterial, operator.AssignParameterizedProfile, + operator.ContractMaterialCategory, operator.CopyMaterial, operator.DisableEditingAssignedMaterial, operator.DisableEditingMaterialSetItem, @@ -36,6 +37,7 @@ classes = ( operator.EditMaterialSetItem, operator.EnableEditingAssignedMaterial, operator.EnableEditingMaterialSetItem, + operator.ExpandMaterialCategory, operator.LoadMaterials, operator.RemoveConstituent, operator.RemoveLayer, diff --git a/src/blenderbim/blenderbim/bim/module/material/operator.py b/src/blenderbim/blenderbim/bim/module/material/operator.py index 37e4781581..b635f13fa6 100644 --- a/src/blenderbim/blenderbim/bim/module/material/operator.py +++ b/src/blenderbim/blenderbim/bim/module/material/operator.py @@ -778,3 +778,31 @@ class CopyMaterial(bpy.types.Operator): if material.id() in object_material_ids: return obj.data.materials.append(IfcStore.get_element(material.id())) + + +class ExpandMaterialCategory(bpy.types.Operator): + bl_idname = "bim.expand_material_category" + bl_label = "Expand Material Category" + bl_options = {"REGISTER", "UNDO"} + category: bpy.props.StringProperty() + + def execute(self, context): + props = context.scene.BIMMaterialProperties + for category in [c for c in props.materials if c.is_category and c.name == self.category]: + category.is_expanded = True + core.load_materials(tool.Material, props.material_type) + return {"FINISHED"} + + +class ContractMaterialCategory(bpy.types.Operator): + bl_idname = "bim.contract_material_category" + bl_label = "Contract Material Category" + bl_options = {"REGISTER", "UNDO"} + category: bpy.props.StringProperty() + + def execute(self, context): + props = context.scene.BIMMaterialProperties + for category in [c for c in props.materials if c.is_category and c.name == self.category]: + category.is_expanded = False + core.load_materials(tool.Material, props.material_type) + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/material/prop.py b/src/blenderbim/blenderbim/bim/module/material/prop.py index 349d132452..8f400ab07f 100644 --- a/src/blenderbim/blenderbim/bim/module/material/prop.py +++ b/src/blenderbim/blenderbim/bim/module/material/prop.py @@ -109,6 +109,8 @@ def get_material_types(self, context): class Material(PropertyGroup): name: StringProperty(name="Name") ifc_definition_id: IntProperty(name="IFC Definition ID") + is_category: BoolProperty(name="Is Category", default=False) + is_expanded: BoolProperty(name="Is Expanded", default=True) total_elements: IntProperty(name="Total Elements") diff --git a/src/blenderbim/blenderbim/bim/module/material/ui.py b/src/blenderbim/blenderbim/bim/module/material/ui.py index 818d59cedb..f7493f3c27 100644 --- a/src/blenderbim/blenderbim/bim/module/material/ui.py +++ b/src/blenderbim/blenderbim/bim/module/material/ui.py @@ -61,16 +61,18 @@ class BIM_PT_materials(Panel): 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] - op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF") - op.material = material.ifc_definition_id - row.operator("bim.remove_material", text="", icon="X").material = material.ifc_definition_id + if material.ifc_definition_id: + op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF") + op.material = material.ifc_definition_id + 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] - op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF") - op.material = material.ifc_definition_id - row.operator("bim.remove_material_set", text="", icon="X").material = material.ifc_definition_id + if material.ifc_definition_id: + op = row.operator("bim.select_by_material", text="", icon="RESTRICT_SELECT_OFF") + op.material = material.ifc_definition_id + 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") @@ -395,7 +397,21 @@ 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) - row2 = row.row() - row2.alignment = "RIGHT" - row2.label(text=str(item.total_elements)) + + if item.is_category: + if item.is_expanded: + row.operator( + "bim.contract_material_category", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN" + ).category = item.name + else: + row.operator( + "bim.expand_material_category", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT" + ).category = item.name + row.label(text=item.name or "Uncategorised") + else: + row.label(text="", icon="BLANK1") + row.label(text=item.name, icon="MATERIAL") + + row2 = row.row() + row2.alignment = "RIGHT" + row2.label(text=str(item.total_elements)) diff --git a/src/blenderbim/blenderbim/tool/material.py b/src/blenderbim/blenderbim/tool/material.py index 3eef005d8e..1e47c643c7 100644 --- a/src/blenderbim/blenderbim/tool/material.py +++ b/src/blenderbim/blenderbim/tool/material.py @@ -55,6 +55,7 @@ class Material(blenderbim.core.tool.Material): @classmethod def import_material_definitions(cls, material_type): props = bpy.context.scene.BIMMaterialProperties + expanded_categories = {m.name for m in props.materials if m.is_expanded} props.materials.clear() get_name = lambda x: x.Name or "Unnamed" if material_type == "IfcMaterialLayerSet": @@ -62,11 +63,25 @@ class Material(blenderbim.core.tool.Material): elif material_type == "IfcMaterialList": get_name = lambda x: "Unnamed" materials = sorted(tool.Ifc.get().by_type(material_type), key=get_name) - for material in materials: - new = props.materials.add() - new.ifc_definition_id = material.id() - new.name = get_name(material) - new.total_elements = len(set(ifcopenshell.util.element.get_elements_by_material(tool.Ifc.get(), material))) + categories = {} + if material_type == "IfcMaterial": + [categories.setdefault(m.Category, []).append(m) for m in materials] + for category, mats in categories.items(): + cat = props.materials.add() + cat.name = category or "" + cat.is_category = True + cat.is_expanded = cat.name in expanded_categories + for material in mats if cat.is_expanded else []: + new = props.materials.add() + new.ifc_definition_id = material.id() + new.name = get_name(material) + new.total_elements = len(set(ifcopenshell.util.element.get_elements_by_material(tool.Ifc.get(), material))) + else: + for material in materials: + new = props.materials.add() + new.ifc_definition_id = material.id() + new.name = get_name(material) + new.total_elements = len(set(ifcopenshell.util.element.get_elements_by_material(tool.Ifc.get(), material))) @classmethod def is_editing_materials(cls): diff --git a/src/blenderbim/test/bim/feature/material.feature b/src/blenderbim/test/bim/feature/material.feature index 125369586d..c3d89dbbb1 100644 --- a/src/blenderbim/test/bim/feature/material.feature +++ b/src/blenderbim/test/bim/feature/material.feature @@ -118,3 +118,18 @@ Scenario: Select by material And the variable "material" is "{ifc}.by_type('IfcMaterial')[0].id()" When I press "bim.select_by_material(material={material})" Then nothing happens + +Scenario: Expand material category + Given an empty IFC project + And I press "bim.load_materials" + And I press "bim.add_material(obj='')" + When I press "bim.expand_material_category(category='')" + Then nothing happens + +Scenario: Contract material category + Given an empty IFC project + And I press "bim.load_materials" + And I press "bim.add_material(obj='')" + And I press "bim.expand_material_category(category='')" + When I press "bim.contract_material_category(category='')" + Then nothing happens diff --git a/src/blenderbim/test/tool/test_material.py b/src/blenderbim/test/tool/test_material.py index 4ce5af138f..430acbb04e 100644 --- a/src/blenderbim/test/tool/test_material.py +++ b/src/blenderbim/test/tool/test_material.py @@ -84,15 +84,30 @@ class TestGetName(NewFile): class TestImportMaterialDefinitions(NewFile): - def test_import_materials(self): + def test_import_materials_grouped_by_categories(self): ifc = ifcopenshell.file() tool.Ifc.set(ifc) - material = ifc.createIfcMaterial(Name="Name") + material = ifc.createIfcMaterial(Name="Name", Category="Category") 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" - assert props.materials[0].total_elements == 0 + assert props.materials[0].ifc_definition_id == 0 + assert props.materials[0].name == "Category" + assert props.materials[0].is_category is True + assert props.materials[0].is_expanded is False + assert len(props.materials) == 1 + + def test_importing_expanded_material_categories(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + material = ifc.createIfcMaterial(Name="Name", Category="Category") + subject.import_material_definitions("IfcMaterial") + props = bpy.context.scene.BIMMaterialProperties + props.materials[0].is_expanded = True + subject.import_material_definitions("IfcMaterial") + assert len(props.materials) == 2 + assert props.materials[1].ifc_definition_id == material.id() + assert props.materials[1].name == "Name" + assert props.materials[1].total_elements == 0 def test_import_material_layer_sets(self): ifc = ifcopenshell.file()