Deprecate "duplicate" spatial tree browser in favour of regular object selection

This commit is contained in:
Dion Moult
2024-07-29 22:29:36 +10:00
parent f8848051ca
commit a9a4b0ccf0
7 changed files with 60 additions and 130 deletions
@@ -21,7 +21,6 @@ from . import ui, prop, operator, workspace
classes = ( classes = (
operator.AssignContainer, operator.AssignContainer,
operator.ChangeSpatialLevel,
operator.ContractContainer, operator.ContractContainer,
operator.CopyToContainer, operator.CopyToContainer,
operator.DeleteContainer, operator.DeleteContainer,
@@ -39,14 +38,11 @@ classes = (
operator.SelectSimilarContainer, operator.SelectSimilarContainer,
operator.SetContainerVisibility, operator.SetContainerVisibility,
operator.SetDefaultContainer, operator.SetDefaultContainer,
prop.SpatialElement,
prop.Element, prop.Element,
prop.BIMSpatialProperties,
prop.BIMObjectSpatialProperties, prop.BIMObjectSpatialProperties,
prop.BIMContainer, prop.BIMContainer,
prop.BIMSpatialDecompositionProperties, prop.BIMSpatialDecompositionProperties,
ui.BIM_PT_spatial, ui.BIM_PT_spatial,
ui.BIM_UL_containers,
ui.BIM_UL_containers_manager, ui.BIM_UL_containers_manager,
ui.BIM_UL_elements, ui.BIM_UL_elements,
ui.BIM_PT_spatial_decomposition, ui.BIM_PT_spatial_decomposition,
@@ -57,7 +53,6 @@ classes = (
def register(): def register():
if not bpy.app.background: if not bpy.app.background:
bpy.utils.register_tool(workspace.SpatialTool, after={"bim.annotation_tool"}, separator=False, group=False) bpy.utils.register_tool(workspace.SpatialTool, after={"bim.annotation_tool"}, separator=False, group=False)
bpy.types.Scene.BIMSpatialProperties = bpy.props.PointerProperty(type=prop.BIMSpatialProperties)
bpy.types.Object.BIMObjectSpatialProperties = bpy.props.PointerProperty(type=prop.BIMObjectSpatialProperties) bpy.types.Object.BIMObjectSpatialProperties = bpy.props.PointerProperty(type=prop.BIMObjectSpatialProperties)
bpy.types.Scene.BIMSpatialDecompositionProperties = bpy.props.PointerProperty( bpy.types.Scene.BIMSpatialDecompositionProperties = bpy.props.PointerProperty(
type=prop.BIMSpatialDecompositionProperties type=prop.BIMSpatialDecompositionProperties
@@ -67,6 +62,5 @@ def register():
def unregister(): def unregister():
if not bpy.app.background: if not bpy.app.background:
bpy.utils.unregister_tool(workspace.SpatialTool) bpy.utils.unregister_tool(workspace.SpatialTool)
del bpy.types.Scene.BIMSpatialProperties
del bpy.types.Object.BIMObjectSpatialProperties del bpy.types.Object.BIMObjectSpatialProperties
del bpy.types.Scene.BIMSpatialDecompositionProperties del bpy.types.Scene.BIMSpatialDecompositionProperties
@@ -37,7 +37,8 @@ class SpatialData:
if cls.data["poll"]: if cls.data["poll"]:
cls.data.update( cls.data.update(
{ {
"parent_container_id": cls.parent_container_id(), "default_container": cls.default_container(),
"selected_containers": cls.selected_containers(),
"is_directly_contained": cls.is_directly_contained(), "is_directly_contained": cls.is_directly_contained(),
"label": cls.label(), "label": cls.label(),
"references": cls.references(), "references": cls.references(),
@@ -56,14 +57,17 @@ class SpatialData:
return False return False
@classmethod @classmethod
def parent_container_id(cls): def default_container(cls) -> str | None:
container_id = bpy.context.scene.BIMSpatialProperties.active_container_id props = bpy.context.scene.BIMSpatialDecompositionProperties
if not container_id: if props.default_container:
return try:
container = tool.Ifc.get_entity_by_id(container_id) return tool.Ifc.get().by_id(props.default_container).Name
if not container or container.is_a("IfcProject"): except:
return pass
return container.Decomposes[0].RelatingObject.id()
@classmethod
def selected_containers(cls):
return sorted([e.Name or "Unnamed" for e in tool.Spatial.get_selected_containers()])
@classmethod @classmethod
def label(cls): def label(cls):
@@ -30,11 +30,9 @@ class ReferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.reference_structure" bl_idname = "bim.reference_structure"
bl_label = "Reference Structure" bl_label = "Reference Structure"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
structure: bpy.props.IntProperty()
def _execute(self, context): def _execute(self, context):
sprops = context.scene.BIMSpatialProperties containers = tool.Spatial.get_selected_containers()
containers = [tool.Ifc.get().by_id(c.ifc_definition_id) for c in sprops.containers if c.is_selected]
for obj in context.selected_objects: for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
if not element: if not element:
@@ -47,11 +45,9 @@ class DereferenceStructure(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.dereference_structure" bl_idname = "bim.dereference_structure"
bl_label = "Dereference Structure" bl_label = "Dereference Structure"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
structure: bpy.props.IntProperty()
def _execute(self, context): def _execute(self, context):
sprops = context.scene.BIMSpatialProperties containers = tool.Spatial.get_selected_containers()
containers = [tool.Ifc.get().by_id(c.ifc_definition_id) for c in sprops.containers if c.is_selected]
for obj in context.selected_objects: for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
if not element: if not element:
@@ -64,14 +60,13 @@ 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"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
structure: bpy.props.IntProperty()
def _execute(self, context): def _execute(self, context):
structure_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.structure)) if container := tool.Root.get_default_container():
for element_obj in context.selected_objects: for element_obj in context.selected_objects:
core.assign_container( core.assign_container(
tool.Ifc, tool.Collector, tool.Spatial, structure_obj=structure_obj, element_obj=element_obj tool.Ifc, tool.Collector, tool.Spatial, container=container, element_obj=element_obj
) )
class EnableEditingContainer(bpy.types.Operator, tool.Ifc.Operator): class EnableEditingContainer(bpy.types.Operator, tool.Ifc.Operator):
@@ -83,16 +78,6 @@ class EnableEditingContainer(bpy.types.Operator, tool.Ifc.Operator):
core.enable_editing_container(tool.Spatial, obj=context.active_object) core.enable_editing_container(tool.Spatial, obj=context.active_object)
class ChangeSpatialLevel(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.change_spatial_level"
bl_label = "Change Spatial Level"
bl_options = {"REGISTER", "UNDO"}
parent: bpy.props.IntProperty()
def _execute(self, context):
core.change_spatial_level(tool.Spatial, parent=tool.Ifc.get().by_id(self.parent))
class DisableEditingContainer(bpy.types.Operator, tool.Ifc.Operator): class DisableEditingContainer(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.disable_editing_container" bl_idname = "bim.disable_editing_container"
bl_label = "Disable Editing Container" bl_label = "Disable Editing Container"
@@ -129,11 +114,10 @@ class CopyToContainer(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def _execute(self, context): def _execute(self, context):
sprops = context.scene.BIMSpatialProperties
# Track decompositions so they can be recreated after the operation # Track decompositions so they can be recreated after the operation
relationships = tool.Root.get_decomposition_relationships(context.selected_objects) relationships = tool.Root.get_decomposition_relationships(context.selected_objects)
old_to_new = {} old_to_new = {}
containers = [tool.Ifc.get().by_id(c.ifc_definition_id) for c in sprops.containers if c.is_selected] containers = tool.Spatial.get_selected_containers()
for obj in context.selected_objects: for obj in context.selected_objects:
result_objs = core.copy_to_container(tool.Ifc, tool.Collector, tool.Spatial, obj=obj, containers=containers) result_objs = core.copy_to_container(tool.Ifc, tool.Collector, tool.Spatial, obj=obj, containers=containers)
if result_objs: if result_objs:
@@ -82,20 +82,6 @@ def is_object_applicable(self, obj):
return bool(element) return bool(element)
class SpatialElement(PropertyGroup):
name: StringProperty(name="Name")
long_name: StringProperty(name="Long Name")
has_decomposition: BoolProperty(name="Has Decomposition")
ifc_definition_id: IntProperty(name="IFC Definition ID")
is_selected: BoolProperty(name="Is Selected")
class BIMSpatialProperties(PropertyGroup):
containers: CollectionProperty(name="Containers", type=SpatialElement)
active_container_index: IntProperty(name="Active Container Index")
active_container_id: IntProperty(name="Active Container Id")
class BIMObjectSpatialProperties(PropertyGroup): class BIMObjectSpatialProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing") is_editing: BoolProperty(name="Is Editing")
relating_container_object: PointerProperty( relating_container_object: PointerProperty(
@@ -39,24 +39,32 @@ class BIM_PT_spatial(Panel):
if not SpatialData.is_loaded: if not SpatialData.is_loaded:
SpatialData.load() SpatialData.load()
props = context.scene.BIMSpatialProperties
osprops = context.active_object.BIMObjectSpatialProperties osprops = context.active_object.BIMObjectSpatialProperties
if osprops.is_editing: if osprops.is_editing:
row = self.layout.row(align=True) if SpatialData.data["default_container"]:
if SpatialData.data["parent_container_id"]: row = self.layout.row(align=True)
op = row.operator("bim.change_spatial_level", text="", icon="FRAME_PREV") row.label(text=f"Target: {SpatialData.data['default_container']}", icon="OUTLINER_COLLECTION")
op.parent = SpatialData.data["parent_container_id"] row.operator("bim.assign_container", icon="CHECKMARK", text="Reassign Container")
if props.containers and props.active_container_index < len(props.containers): row.operator("bim.disable_editing_container", icon="CANCEL", text="")
op = row.operator("bim.assign_container", icon="CHECKMARK")
op.structure = props.containers[props.active_container_index].ifc_definition_id
row.operator("bim.reference_structure", icon="LINKED", text="")
row.operator("bim.dereference_structure", icon="UNLINKED", text="")
row.operator("bim.copy_to_container", icon="COPYDOWN", text="")
row.operator("bim.disable_editing_container", icon="CANCEL", text="")
self.layout.template_list("BIM_UL_containers", "", props, "containers", props, "active_container_index") if SpatialData.data["selected_containers"]:
self.layout.prop(osprops, "relating_container_object") row = self.layout.row()
row.label(text=f"{len(SpatialData.data['selected_containers'])} Selected Containers")
for name in SpatialData.data['selected_containers'][:3]:
row = self.layout.row()
row.label(text=name, icon="OUTLINER_COLLECTION")
if len(SpatialData.data['selected_containers']) > 3:
row = self.layout.row()
row.label(text=f"... {len(SpatialData.data['selected_containers']) - 3} More")
row = self.layout.row(align=True)
row.operator("bim.reference_structure", icon="LINKED", text="Reference Selected")
row.operator("bim.dereference_structure", icon="UNLINKED", text="")
row = self.layout.row()
row.operator("bim.copy_to_container", icon="COPYDOWN", text="Copy Object To Selected")
else:
row = self.layout.row()
row.label(text="No Selected Containers")
else: else:
row = self.layout.row(align=True) row = self.layout.row(align=True)
if SpatialData.data["label"]: if SpatialData.data["label"]:
@@ -74,23 +82,6 @@ class BIM_PT_spatial(Panel):
row.label(text=reference, icon="LINKED") row.label(text=reference, icon="LINKED")
class BIM_UL_containers(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
if item:
if item.has_decomposition:
op = layout.operator("bim.change_spatial_level", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False)
op.parent = item.ifc_definition_id
layout.label(text=item.name)
layout.label(text=item.long_name)
layout.prop(
item,
"is_selected",
icon="CHECKBOX_HLT" if item.is_selected else "CHECKBOX_DEHLT",
text="",
emboss=False,
)
class BIM_PT_spatial_decomposition(Panel): class BIM_PT_spatial_decomposition(Panel):
bl_label = "Spatial Decomposition" bl_label = "Spatial Decomposition"
bl_idname = "BIM_PT_spatial_decomposition" bl_idname = "BIM_PT_spatial_decomposition"
+4 -13
View File
@@ -49,17 +49,13 @@ def assign_container(
ifc: tool.Ifc, ifc: tool.Ifc,
collector: tool.Collector, collector: tool.Collector,
spatial: tool.Spatial, spatial: tool.Spatial,
structure_obj: Optional[bpy.types.Object] = None, container: ifcopenshell.entity_instance,
element_obj: Optional[bpy.types.Object] = None, element_obj: Optional[bpy.types.Object] = None,
) -> Union[ifcopenshell.entity_instance, None]: ) -> Union[ifcopenshell.entity_instance, None]:
if not spatial.can_contain(structure_obj, element_obj): if not spatial.can_contain(container, element_obj):
return return
assert element_obj and structure_obj # Type checker. assert element_obj # Type checker.
rel = ifc.run( rel = ifc.run("spatial.assign_container", products=[ifc.get_entity(element_obj)], relating_structure=container)
"spatial.assign_container",
products=[ifc.get_entity(element_obj)],
relating_structure=ifc.get_entity(structure_obj),
)
spatial.disable_editing(element_obj) spatial.disable_editing(element_obj)
collector.assign(element_obj) collector.assign(element_obj)
return rel return rel
@@ -67,17 +63,12 @@ def assign_container(
def enable_editing_container(spatial: tool.Spatial, obj: bpy.types.Object) -> None: def enable_editing_container(spatial: tool.Spatial, obj: bpy.types.Object) -> None:
spatial.enable_editing(obj) spatial.enable_editing(obj)
spatial.import_containers()
def disable_editing_container(spatial: tool.Spatial, obj: bpy.types.Object) -> None: def disable_editing_container(spatial: tool.Spatial, obj: bpy.types.Object) -> None:
spatial.disable_editing(obj) spatial.disable_editing(obj)
def change_spatial_level(spatial: tool.Spatial, parent: Union[ifcopenshell.entity_instance, None] = None) -> None:
spatial.import_containers(parent=parent)
def remove_container(ifc: tool.Ifc, collector: tool.Collector, obj: bpy.types.Object) -> None: def remove_container(ifc: tool.Ifc, collector: tool.Collector, obj: bpy.types.Object) -> None:
ifc.run("spatial.unassign_container", products=[ifc.get_entity(obj)]) ifc.run("spatial.unassign_container", products=[ifc.get_entity(obj)])
collector.assign(obj) collector.assign(obj)
+13 -33
View File
@@ -39,23 +39,20 @@ import json
from math import pi from math import pi
from mathutils import Vector, Matrix from mathutils import Vector, Matrix
from shapely import Polygon from shapely import Polygon
from typing import Generator, Optional, Union, Literal, Any, List from typing import Generator, Optional, Union, Literal, List
class Spatial(blenderbim.core.tool.Spatial): class Spatial(blenderbim.core.tool.Spatial):
@classmethod @classmethod
def can_contain( def can_contain(cls, container: ifcopenshell.entity_instance, element_obj: Union[bpy.types.Object, None]) -> bool:
cls, structure_obj: Union[bpy.types.Object, None], element_obj: Union[bpy.types.Object, None]
) -> bool:
structure = tool.Ifc.get_entity(structure_obj)
element = tool.Ifc.get_entity(element_obj) element = tool.Ifc.get_entity(element_obj)
if not structure or not element: if not element:
return False return False
if tool.Ifc.get_schema() == "IFC2X3": if tool.Ifc.get_schema() == "IFC2X3":
if not structure.is_a("IfcSpatialStructureElement"): if not container.is_a("IfcSpatialStructureElement"):
return False return False
else: else:
if not structure.is_a("IfcSpatialStructureElement") and not structure.is_a( if not container.is_a("IfcSpatialStructureElement") and not container.is_a(
"IfcExternalSpatialStructureElement" "IfcExternalSpatialStructureElement"
): ):
return False return False
@@ -111,31 +108,6 @@ class Spatial(blenderbim.core.tool.Spatial):
def get_relative_object_matrix(cls, target_obj: bpy.types.Object, relative_to_obj: bpy.types.Object) -> Matrix: def get_relative_object_matrix(cls, target_obj: bpy.types.Object, relative_to_obj: bpy.types.Object) -> Matrix:
return relative_to_obj.matrix_world.inverted() @ target_obj.matrix_world return relative_to_obj.matrix_world.inverted() @ target_obj.matrix_world
@classmethod
def import_containers(cls, parent: Optional[ifcopenshell.entity_instance] = None) -> None:
props = bpy.context.scene.BIMSpatialProperties
props.containers.clear()
if not parent:
parent = tool.Ifc.get().by_type("IfcProject")[0]
props.active_container_id = parent.id()
for rel in parent.IsDecomposedBy or []:
related_objects = []
for element in rel.RelatedObjects:
# skip objects without placements
if not element.is_a("IfcProduct"):
continue
related_objects.append((element, ifcopenshell.util.placement.get_storey_elevation(element)))
related_objects = sorted(related_objects, key=lambda e: e[1])
for element, _ in related_objects:
new = props.containers.add()
new.name = element.Name or "Unnamed"
new.long_name = element.LongName or ""
new.has_decomposition = bool(element.IsDecomposedBy)
new.ifc_definition_id = element.id()
@classmethod @classmethod
def run_root_copy_class(cls, obj: bpy.types.Object) -> ifcopenshell.entity_instance: def run_root_copy_class(cls, obj: bpy.types.Object) -> ifcopenshell.entity_instance:
return blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj) return blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj)
@@ -886,3 +858,11 @@ class Spatial(blenderbim.core.tool.Spatial):
if subelement: if subelement:
return subelement return subelement
return None return None
@classmethod
def get_selected_containers(cls) -> List[ifcopenshell.entity_instance]:
results = []
for obj in tool.Blender.get_selected_objects():
if (element := tool.Ifc.get_entity(obj)) and tool.Root.is_spatial_element(element):
results.append(element)
return results