From b14da14614ed522fed724fb852cce10e611e046b Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 15 Mar 2026 12:25:19 +1100 Subject: [PATCH] Default to assigning material set usages if assigning to an occurrence. See #7794. Co-Authored-By: Claude Opus 4.6 --- .../bonsai/bim/module/material/operator.py | 23 ++++++++----------- src/bonsai/bonsai/core/material.py | 11 +++++++-- src/bonsai/bonsai/core/tool.py | 1 + src/bonsai/bonsai/tool/material.py | 4 ++++ 4 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/material/operator.py b/src/bonsai/bonsai/bim/module/material/operator.py index e0761bf416..08cddbb928 100644 --- a/src/bonsai/bonsai/bim/module/material/operator.py +++ b/src/bonsai/bonsai/bim/module/material/operator.py @@ -210,14 +210,15 @@ class AssignMaterialToSelected(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.assign_material_to_selected" bl_label = "Assign Material To Selected" bl_description = ( - "Assign currently selected material in Materials UI to the selected objects.\n\n" - "ALT+CLICK to assign material as a usage." + "Assign currently selected material in Materials UI to the selected objects.\n" + "Occurrences automatically get usages for layer/profile sets.\n\n" + "ALT+CLICK to assign without a usage." ) bl_options = {"REGISTER", "UNDO"} material: bpy.props.IntProperty(name="Material IFC ID") - assign_as_usage: bpy.props.BoolProperty( - name="Assign Material As A Usage", - default=False, + should_auto_assign_usage: bpy.props.BoolProperty( + name="Auto Assign Usage", + default=True, options={"SKIP_SAVE"}, ) @@ -230,25 +231,19 @@ class AssignMaterialToSelected(bpy.types.Operator, tool.Ifc.Operator): def invoke(self, context, event): if event.type == "LEFTMOUSE" and event.alt: - material_class = tool.Ifc.get().by_id(self.material).is_a() - if material_class not in ("IfcMaterialProfileSet", "IfcMaterialLayerSet"): - self.report({"ERROR"}, f"{material_class} cannot be assigned as a usage.") - return {"CANCELLED"} - self.assign_as_usage = True + self.should_auto_assign_usage = False return self.execute(context) def _execute(self, context): material = tool.Ifc.get().by_id(self.material) objects = tool.Blender.get_selected_objects() - material_type = material.is_a() - if self.assign_as_usage: - material_type += "Usage" core.assign_material( tool.Ifc, tool.Material, - material_type=material_type, + material_type=material.is_a(), objects=objects, material=material, + should_auto_assign_usage=self.should_auto_assign_usage, ) diff --git a/src/bonsai/bonsai/core/material.py b/src/bonsai/bonsai/core/material.py index 65c5955603..4fd254b6f2 100644 --- a/src/bonsai/bonsai/core/material.py +++ b/src/bonsai/bonsai/core/material.py @@ -113,6 +113,7 @@ def assign_material( material_type: Union[str, None], objects: list[bpy.types.Object], material: Optional[ifcopenshell.entity_instance] = None, + should_auto_assign_usage: bool = True, ) -> None: """Assign material to the provided objects. @@ -121,12 +122,18 @@ def assign_material( """ material_type = material_type or material_tool.get_object_ui_material_type() material = material or material_tool.get_object_ui_active_material() + can_be_usage = should_auto_assign_usage and material_type in ("IfcMaterialLayerSet", "IfcMaterialProfileSet") for obj in objects: element = ifc.get_entity(obj) if not element: continue - ifc.run("material.assign_material", products=[element], type=material_type, material=material) + if can_be_usage and not material_tool.is_type_product(element): + element_material_type = material_type + "Usage" + else: + element_material_type = material_type + + ifc.run("material.assign_material", products=[element], type=element_material_type, material=material) assigned_material = material_tool.get_material(element) assert assigned_material # Type checker. @@ -136,7 +143,7 @@ def assign_material( material_tool.add_material_to_set(material_set=material, material=default_material) elif material_tool.is_a_material_set(assigned_material): material_tool.add_material_to_set(material_set=assigned_material, material=material) - material_tool.ensure_material_assigned(elements=[element], material_type=material_type, material=material) + material_tool.ensure_material_assigned(elements=[element], material_type=element_material_type, material=material) def unassign_material(ifc: type[tool.Ifc], material_tool: type[tool.Material], objects: list[bpy.types.Object]) -> None: diff --git a/src/bonsai/bonsai/core/tool.py b/src/bonsai/bonsai/core/tool.py index 234c79c53c..340f9d7a64 100644 --- a/src/bonsai/bonsai/core/tool.py +++ b/src/bonsai/bonsai/core/tool.py @@ -580,6 +580,7 @@ class Material: def import_material_definitions(cls, material_type: str): pass def is_a_flow_segment(cls, element): pass def is_a_material_set(cls, material): pass + def is_type_product(cls, element): pass def is_editing_materials(cls): pass def is_material_used_in_sets(cls, material): pass def load_material_attributes(cls, material): pass diff --git a/src/bonsai/bonsai/tool/material.py b/src/bonsai/bonsai/tool/material.py index e09462a8c2..4c55123a9c 100644 --- a/src/bonsai/bonsai/tool/material.py +++ b/src/bonsai/bonsai/tool/material.py @@ -226,6 +226,10 @@ class Material(bonsai.core.tool.Material): "IfcMaterialProfileSet", ] + @classmethod + def is_type_product(cls, element: ifcopenshell.entity_instance) -> bool: + return element.is_a("IfcTypeProduct") + @classmethod def add_material_to_set( cls, material_set: ifcopenshell.entity_instance, material: ifcopenshell.entity_instance