Make referencing in structure more discoverable #6514

Didn't wanted to disrupt existing operators, but we probably should deprecate them.
This commit is contained in:
Andrej730
2025-04-08 15:43:03 +05:00
parent 8b775ea30f
commit 345a6e72d9
4 changed files with 95 additions and 6 deletions
@@ -24,11 +24,13 @@ classes = (
operator.ContractContainer, operator.ContractContainer,
operator.CopyToContainer, operator.CopyToContainer,
operator.DeleteContainer, operator.DeleteContainer,
operator.DereferenceFromProvidedStructure,
operator.DereferenceStructure, operator.DereferenceStructure,
operator.DisableEditingContainer, operator.DisableEditingContainer,
operator.EnableEditingContainer, operator.EnableEditingContainer,
operator.ExpandContainer, operator.ExpandContainer,
operator.ImportSpatialDecomposition, operator.ImportSpatialDecomposition,
operator.ReferenceFromProvidedStructure,
operator.ReferenceStructure, operator.ReferenceStructure,
operator.RemoveContainer, operator.RemoveContainer,
operator.SelectContainer, operator.SelectContainer,
+15 -3
View File
@@ -19,6 +19,7 @@
import bpy import bpy
import bonsai.tool as tool import bonsai.tool as tool
import ifcopenshell.util.element import ifcopenshell.util.element
from typing import Any
def refresh(): def refresh():
@@ -79,9 +80,20 @@ class SpatialData:
return label return label
@classmethod @classmethod
def references(cls): def references(cls) -> list[dict[str, Any]]:
results = ifcopenshell.util.element.get_referenced_structures(tool.Ifc.get_entity(bpy.context.active_object)) assert (obj := bpy.context.active_object) and (element := tool.Ifc.get_entity(obj))
return sorted([f"{r.is_a()}/{r.Name or ''}" for r in results]) 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 @classmethod
def is_directly_contained(cls): def is_directly_contained(cls):
@@ -73,6 +73,76 @@ class DereferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
core.dereference_structure(tool.Ifc, tool.Spatial, structure=container, element=element) 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): class AssignContainer(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.assign_container" bl_idname = "bim.assign_container"
bl_label = "Assign Container" bl_label = "Assign Container"
+8 -3
View File
@@ -21,7 +21,7 @@ import bpy
from bpy.types import Panel, UIList from bpy.types import Panel, UIList
from bonsai.bim.module.spatial.data import SpatialData, SpatialDecompositionData from bonsai.bim.module.spatial.data import SpatialData, SpatialDecompositionData
import bonsai.tool as tool import bonsai.tool as tool
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, cast, Any
if TYPE_CHECKING: if TYPE_CHECKING:
from bonsai.bim.module.spatial.prop import BIMSpatialDecompositionProperties, BIMContainer, Element 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.assign_container", icon="CHECKMARK", text="")
row.operator("bim.disable_editing_container", icon="CANCEL", 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"]: if SpatialData.data["selected_containers"]:
row = self.layout.row() row = self.layout.row()
row.label(text=f"{len(SpatialData.data['selected_containers'])} Selected Containers") 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.label(text="No Spatial Container")
row.operator("bim.enable_editing_container", icon="GREASEPENCIL", text="") 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:") self.layout.label(text=f"{len(references)} References:")
else: else:
self.layout.label(text="No References Found") self.layout.label(text="No References Found")
for reference in references: for reference in references:
row = self.layout.row() 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): class BIM_PT_spatial_decomposition(Panel):
@@ -200,6 +204,7 @@ class BIM_PT_spatial_decomposition(Panel):
op.container = ifc_definition_id op.container = ifc_definition_id
row.operator("bim.assign_container", icon="FOLDER_REDIRECT", text="").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="") op = row.operator("bim.select_decomposed_element", icon="OBJECT_DATA", text="")
if active_element := self.props.active_element: if active_element := self.props.active_element:
op.element = active_element.ifc_definition_id op.element = active_element.ifc_definition_id