diff --git a/src/bonsai/bonsai/bim/module/material/operator.py b/src/bonsai/bonsai/bim/module/material/operator.py index dfb60f11a2..2575e62c6c 100644 --- a/src/bonsai/bonsai/bim/module/material/operator.py +++ b/src/bonsai/bonsai/bim/module/material/operator.py @@ -156,6 +156,7 @@ class DuplicateMaterial(bpy.types.Operator, tool.Ifc.Operator): class AddMaterialSet(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.add_material_set" bl_label = "Add Material Set" + bl_description = "Create a new material set, will use a default material/profile as a placeholder" bl_options = {"REGISTER", "UNDO"} set_type: bpy.props.StringProperty() diff --git a/src/bonsai/bonsai/core/material.py b/src/bonsai/bonsai/core/material.py index 7c5523bd1e..fcec980206 100644 --- a/src/bonsai/bonsai/core/material.py +++ b/src/bonsai/bonsai/core/material.py @@ -40,6 +40,7 @@ def add_material( def add_material_set(ifc: tool.Ifc, material: tool.Material, set_type: str) -> ifcopenshell.entity_instance: ifc_material = ifc.run("material.add_material_set", name="Unnamed", set_type=set_type) + material.ensure_new_material_set_is_valid(ifc_material) if material.is_editing_materials(): material.import_material_definitions(material.get_active_material_type()) return ifc_material diff --git a/src/bonsai/bonsai/core/tool.py b/src/bonsai/bonsai/core/tool.py index f8ddc82421..356567fe71 100644 --- a/src/bonsai/bonsai/core/tool.py +++ b/src/bonsai/bonsai/core/tool.py @@ -510,6 +510,7 @@ class Material: def enable_editing_materials(cls): pass def ensure_material_assigned(cls, elements, material_type, material): pass def ensure_material_unassigned(cls, elements): pass + def ensure_new_material_set_is_valid(cls, material): pass def get_active_material_item(cls): pass def get_active_material_type(cls): pass def get_default_material(cls): pass diff --git a/src/bonsai/bonsai/tool/material.py b/src/bonsai/bonsai/tool/material.py index 48359829c8..4832f3bd76 100644 --- a/src/bonsai/bonsai/tool/material.py +++ b/src/bonsai/bonsai/tool/material.py @@ -27,7 +27,8 @@ import bonsai.bim.helper import ifcopenshell.util.unit import ifcopenshell.util.element from collections import defaultdict -from typing import Union, Any, TYPE_CHECKING, Optional +from typing import Union, Any, TYPE_CHECKING, Optional, Literal +from typing_extensions import assert_never if TYPE_CHECKING: # Avoid circular imports. @@ -352,3 +353,38 @@ class Material(bonsai.core.tool.Material): elements.extend(tool.Model.get_occurrences_without_material_override(element)) tool.Model.apply_ifc_material_changes(elements, assigned_material=assigned_material) + + @classmethod + def ensure_new_material_set_is_valid(cls, material: ifcopenshell.entity_instance) -> None: + material_type = material.is_a() + + ifc_file = tool.Ifc.get() + default_material = cls.get_default_material() + + if material_type == "IfcMaterialConstituentSet": + material_constituent = ifcopenshell.api.material.add_constituent( + ifc_file, + constituent_set=material, + material=default_material, + ) + material.MaterialConstituents = [material_constituent] + elif material_type == "IfcMaterialLayerSet": + material_layer = ifcopenshell.api.material.add_layer( + ifc_file, + layer_set=material, + material=default_material, + ) + material.MaterialLayers = [material_layer] + elif material_type == "IfcMaterialProfileSet": + profile = tool.Profile.get_default_profile() + material_profile = ifcopenshell.api.material.add_profile( + ifc_file, + profile_set=material, + profile=profile, + material=None, + ) + material.MaterialProfiles = [material_profile] + elif material_type == "IfcMaterialList": + material.Materials = [default_material] + else: + assert False, f"Invalid material type found: {material_type}." diff --git a/src/bonsai/bonsai/tool/profile.py b/src/bonsai/bonsai/tool/profile.py index 637e5f8fef..905b7cf753 100644 --- a/src/bonsai/bonsai/tool/profile.py +++ b/src/bonsai/bonsai/tool/profile.py @@ -17,6 +17,7 @@ # along with Bonsai. If not, see . import ifcopenshell +import ifcopenshell.api.profile import ifcopenshell.geom import ifcopenshell.util.element import ifcopenshell.util.unit @@ -78,6 +79,21 @@ class Profile(bonsai.core.tool.Profile): return profile return None + @classmethod + def get_default_profile(cls) -> ifcopenshell.entity_instance: + """Return first found IfcProfileDef in IFC file or create a new default profile.""" + ifc_file = tool.Ifc.get() + profile = next(iter(ifc_file.by_type("IfcProfileDef")), None) + if profile: + return profile + profile = ifcopenshell.api.profile.add_parameterized_profile(ifc_file, ifc_class="IfcRectangleProfileDef") + si_conversion = ifcopenshell.util.unit.calculate_unit_scale(ifc_file) + profile.ProfileType = "AREA" + profile.ProfileName = "Default Profile" + profile.XDim = 0.1 / si_conversion + profile.YDim = 0.1 / si_conversion + return profile + @classmethod def get_model_profiles(cls) -> list[ifcopenshell.entity_instance]: return tool.Ifc.get().by_type("IfcProfileDef") diff --git a/src/bonsai/test/core/test_material.py b/src/bonsai/test/core/test_material.py index f95ddea746..a8baee0c6c 100644 --- a/src/bonsai/test/core/test_material.py +++ b/src/bonsai/test/core/test_material.py @@ -49,6 +49,7 @@ class TestAddMaterialSet: ifc.run("material.add_material_set", name="Unnamed", set_type="set_type").should_be_called().will_return( "material" ) + material.ensure_new_material_set_is_valid("material").should_be_called() material.is_editing_materials().should_be_called().will_return(False) assert subject.add_material_set(ifc, material, set_type="set_type") == "material" @@ -56,6 +57,7 @@ class TestAddMaterialSet: ifc.run("material.add_material_set", name="Unnamed", set_type="set_type").should_be_called().will_return( "material" ) + material.ensure_new_material_set_is_valid("material").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() diff --git a/src/bonsai/test/tool/test_material.py b/src/bonsai/test/tool/test_material.py index 8c2357c5c7..9dcb63c40e 100644 --- a/src/bonsai/test/tool/test_material.py +++ b/src/bonsai/test/tool/test_material.py @@ -150,3 +150,36 @@ class TestIsMaterialUsedInSets(NewFile): material_set.MaterialLayers = [material_set_item] material_set_item.Material = material assert subject.is_material_used_in_sets(material) is True + + +class TestEnsureNewMaterialSetIsValid(NewFile): + def test_material_constituent_set(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + material_set = ifc.create_entity("IfcMaterialConstituentSet") + subject.ensure_new_material_set_is_valid(material_set) + assert len(constituents := material_set.MaterialConstituents) == 1 + assert constituents[0].Material + + def test_material_layer_set(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + material_set = ifc.create_entity("IfcMaterialLayerSet") + subject.ensure_new_material_set_is_valid(material_set) + assert len(layers := material_set.MaterialLayers) == 1 + assert layers[0].Material + + def test_material_profile_set(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + material_set = ifc.create_entity("IfcMaterialProfileSet") + subject.ensure_new_material_set_is_valid(material_set) + assert len(profiles := material_set.MaterialProfiles) == 1 + assert profiles[0].Profile + + def test_material_list(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + material_set = ifc.create_entity("IfcMaterialList") + subject.ensure_new_material_set_is_valid(material_set) + assert len(material_set.Materials) == 1