From 345a6e72d956a56cde8fa65398da335d8c3da403 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Tue, 8 Apr 2025 15:43:03 +0500 Subject: [PATCH] Make referencing in structure more discoverable #6514 Didn't wanted to disrupt existing operators, but we probably should deprecate them. --- .../bonsai/bim/module/spatial/__init__.py | 2 + src/bonsai/bonsai/bim/module/spatial/data.py | 18 ++++- .../bonsai/bim/module/spatial/operator.py | 70 +++++++++++++++++++ src/bonsai/bonsai/bim/module/spatial/ui.py | 11 ++- 4 files changed, 95 insertions(+), 6 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/spatial/__init__.py b/src/bonsai/bonsai/bim/module/spatial/__init__.py index ee40768f08..810766c552 100644 --- a/src/bonsai/bonsai/bim/module/spatial/__init__.py +++ b/src/bonsai/bonsai/bim/module/spatial/__init__.py @@ -24,11 +24,13 @@ classes = ( operator.ContractContainer, operator.CopyToContainer, operator.DeleteContainer, + operator.DereferenceFromProvidedStructure, operator.DereferenceStructure, operator.DisableEditingContainer, operator.EnableEditingContainer, operator.ExpandContainer, operator.ImportSpatialDecomposition, + operator.ReferenceFromProvidedStructure, operator.ReferenceStructure, operator.RemoveContainer, operator.SelectContainer, diff --git a/src/bonsai/bonsai/bim/module/spatial/data.py b/src/bonsai/bonsai/bim/module/spatial/data.py index b3e863a329..def6e50e5b 100644 --- a/src/bonsai/bonsai/bim/module/spatial/data.py +++ b/src/bonsai/bonsai/bim/module/spatial/data.py @@ -19,6 +19,7 @@ import bpy import bonsai.tool as tool import ifcopenshell.util.element +from typing import Any def refresh(): @@ -79,9 +80,20 @@ class SpatialData: return label @classmethod - def references(cls): - results = ifcopenshell.util.element.get_referenced_structures(tool.Ifc.get_entity(bpy.context.active_object)) - return sorted([f"{r.is_a()}/{r.Name or ''}" for r in results]) + def references(cls) -> list[dict[str, Any]]: + assert (obj := bpy.context.active_object) and (element := tool.Ifc.get_entity(obj)) + results: list[dict[str, Any]] = [] + for structure in ifcopenshell.util.element.get_referenced_structures(element): + ifc_class = structure.is_a() + results.append( + { + "id": structure.id(), + "name": f"{ifc_class}/{structure.Name or ''}", + "type": ifc_class, + } + ) + results.sort(key=lambda x: x["name"]) + return results @classmethod def is_directly_contained(cls): diff --git a/src/bonsai/bonsai/bim/module/spatial/operator.py b/src/bonsai/bonsai/bim/module/spatial/operator.py index ed788f7651..14c536f01f 100644 --- a/src/bonsai/bonsai/bim/module/spatial/operator.py +++ b/src/bonsai/bonsai/bim/module/spatial/operator.py @@ -73,6 +73,76 @@ class DereferenceStructure(bpy.types.Operator, tool.Ifc.Operator): core.dereference_structure(tool.Ifc, tool.Spatial, structure=container, element=element) +class ReferenceFromProvidedStructure(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.reference_from_provided_structure" + bl_label = "Reference from Provided Structure" + bl_description = "Reference selected objects from the provided structure.\n\n" "ALT + Click to dereference instead." + bl_options = {"REGISTER", "UNDO"} + + structure: bpy.props.IntProperty(options={"SKIP_SAVE"}) + dereference: bpy.props.BoolProperty(default=False, options={"SKIP_SAVE"}) + + @classmethod + def poll(cls, context): + if not tool.Blender.get_selected_objects(): + cls.poll_message_set("No objects selected.") + return False + return True + + def invoke(self, context, event): + self.dereference = event.alt + return self.execute(context) + + def _execute(self, context): + ifc_file = tool.Ifc.get() + structure = ifc_file.by_id(self.structure) + + objs = tool.Spatial.get_selected_objects_without_containers() + if not objs: + self.report({"INFO"}, "No non-spatial objects are selected.") + return + + elements = [e for o in objs if (e := tool.Ifc.get_entity(o))] + for element in elements: + if self.dereference: + core.dereference_structure(tool.Ifc, tool.Spatial, structure=structure, element=element) + else: + core.reference_structure(tool.Ifc, tool.Spatial, structure=structure, element=element) + + msg = "dereferenced" if self.dereference else "referenced" + self.report({"INFO"}, f"{len(elements)} elements {msg} from the structure.") + + +class DereferenceFromProvidedStructure(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.dereference_from_provided_structure" + bl_label = "Dereference from Provided Structure" + bl_description = "Dereference selected objects from the provided structure." + bl_options = {"REGISTER", "UNDO"} + + structure: bpy.props.IntProperty(options={"SKIP_SAVE"}) + + @classmethod + def poll(cls, context): + if not tool.Blender.get_selected_objects(): + cls.poll_message_set("No objects selected.") + return False + return True + + def _execute(self, context): + ifc_file = tool.Ifc.get() + structure = ifc_file.by_id(self.structure) + objs = tool.Spatial.get_selected_objects_without_containers() + if not objs: + self.report({"INFO"}, "No non-spatial objects are selected.") + return + + elements = [e for o in objs if (e := tool.Ifc.get_entity(o))] + for element in elements: + core.dereference_structure(tool.Ifc, tool.Spatial, structure=structure, element=element) + + self.report({"INFO"}, f"{len(elements)} elements dereferenced from the structure.") + + class AssignContainer(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.assign_container" bl_label = "Assign Container" diff --git a/src/bonsai/bonsai/bim/module/spatial/ui.py b/src/bonsai/bonsai/bim/module/spatial/ui.py index 03ce2e8f5a..9c25d455a3 100644 --- a/src/bonsai/bonsai/bim/module/spatial/ui.py +++ b/src/bonsai/bonsai/bim/module/spatial/ui.py @@ -21,7 +21,7 @@ import bpy from bpy.types import Panel, UIList from bonsai.bim.module.spatial.data import SpatialData, SpatialDecompositionData import bonsai.tool as tool -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, cast, Any if TYPE_CHECKING: from bonsai.bim.module.spatial.prop import BIMSpatialDecompositionProperties, BIMContainer, Element @@ -56,6 +56,8 @@ class BIM_PT_spatial(Panel): row.operator("bim.assign_container", icon="CHECKMARK", text="") row.operator("bim.disable_editing_container", icon="CANCEL", text="") + # TODO: deprecate as it's very hard to discover + # containers are not even selectable by default. if SpatialData.data["selected_containers"]: row = self.layout.row() row.label(text=f"{len(SpatialData.data['selected_containers'])} Selected Containers") @@ -86,14 +88,16 @@ class BIM_PT_spatial(Panel): row.label(text="No Spatial Container") row.operator("bim.enable_editing_container", icon="GREASEPENCIL", text="") - if references := SpatialData.data["references"]: + if references := cast(list[dict[str, Any]], SpatialData.data["references"]): self.layout.label(text=f"{len(references)} References:") else: self.layout.label(text="No References Found") for reference in references: row = self.layout.row() - row.label(text=reference, icon="LINKED") + row.label(text=reference["name"], icon="LINKED") + op = row.operator("bim.dereference_from_provided_structure", icon="X", text="") + op.structure = reference["id"] class BIM_PT_spatial_decomposition(Panel): @@ -200,6 +204,7 @@ class BIM_PT_spatial_decomposition(Panel): op.container = ifc_definition_id row.operator("bim.assign_container", icon="FOLDER_REDIRECT", text="").container = ifc_definition_id + row.operator("bim.reference_from_provided_structure", icon="LINKED", text="").structure = ifc_definition_id op = row.operator("bim.select_decomposed_element", icon="OBJECT_DATA", text="") if active_element := self.props.active_element: op.element = active_element.ifc_definition_id