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.CopyToContainer,
operator.DeleteContainer,
operator.DereferenceFromProvidedStructure,
operator.DereferenceStructure,
operator.DisableEditingContainer,
operator.EnableEditingContainer,
operator.ExpandContainer,
operator.ImportSpatialDecomposition,
operator.ReferenceFromProvidedStructure,
operator.ReferenceStructure,
operator.RemoveContainer,
operator.SelectContainer,
+15 -3
View File
@@ -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):
@@ -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"
+8 -3
View File
@@ -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