diff --git a/src/blenderbim/blenderbim/bim/module/spatial/__init__.py b/src/blenderbim/blenderbim/bim/module/spatial/__init__.py index cd94108885..bdb5c81e8c 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/__init__.py @@ -21,7 +21,6 @@ from . import ui, prop, operator, workspace classes = ( operator.AssignContainer, - operator.ChangeSpatialLevel, operator.ContractContainer, operator.CopyToContainer, operator.DeleteContainer, @@ -39,14 +38,11 @@ classes = ( operator.SelectSimilarContainer, operator.SetContainerVisibility, operator.SetDefaultContainer, - prop.SpatialElement, prop.Element, - prop.BIMSpatialProperties, prop.BIMObjectSpatialProperties, prop.BIMContainer, prop.BIMSpatialDecompositionProperties, ui.BIM_PT_spatial, - ui.BIM_UL_containers, ui.BIM_UL_containers_manager, ui.BIM_UL_elements, ui.BIM_PT_spatial_decomposition, @@ -57,7 +53,6 @@ classes = ( def register(): if not bpy.app.background: 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.Scene.BIMSpatialDecompositionProperties = bpy.props.PointerProperty( type=prop.BIMSpatialDecompositionProperties @@ -67,6 +62,5 @@ def register(): def unregister(): if not bpy.app.background: bpy.utils.unregister_tool(workspace.SpatialTool) - del bpy.types.Scene.BIMSpatialProperties del bpy.types.Object.BIMObjectSpatialProperties del bpy.types.Scene.BIMSpatialDecompositionProperties diff --git a/src/blenderbim/blenderbim/bim/module/spatial/data.py b/src/blenderbim/blenderbim/bim/module/spatial/data.py index 09617ca9b2..7959e28bca 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/data.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/data.py @@ -37,7 +37,8 @@ class SpatialData: if cls.data["poll"]: 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(), "label": cls.label(), "references": cls.references(), @@ -56,14 +57,17 @@ class SpatialData: return False @classmethod - def parent_container_id(cls): - container_id = bpy.context.scene.BIMSpatialProperties.active_container_id - if not container_id: - return - container = tool.Ifc.get_entity_by_id(container_id) - if not container or container.is_a("IfcProject"): - return - return container.Decomposes[0].RelatingObject.id() + def default_container(cls) -> str | None: + props = bpy.context.scene.BIMSpatialDecompositionProperties + if props.default_container: + try: + return tool.Ifc.get().by_id(props.default_container).Name + except: + pass + + @classmethod + def selected_containers(cls): + return sorted([e.Name or "Unnamed" for e in tool.Spatial.get_selected_containers()]) @classmethod def label(cls): diff --git a/src/blenderbim/blenderbim/bim/module/spatial/operator.py b/src/blenderbim/blenderbim/bim/module/spatial/operator.py index cbe069f513..c6283f78a4 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/operator.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/operator.py @@ -30,11 +30,9 @@ class ReferenceStructure(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.reference_structure" bl_label = "Reference Structure" bl_options = {"REGISTER", "UNDO"} - structure: bpy.props.IntProperty() def _execute(self, context): - sprops = context.scene.BIMSpatialProperties - 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: element = tool.Ifc.get_entity(obj) if not element: @@ -47,11 +45,9 @@ class DereferenceStructure(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.dereference_structure" bl_label = "Dereference Structure" bl_options = {"REGISTER", "UNDO"} - structure: bpy.props.IntProperty() def _execute(self, context): - sprops = context.scene.BIMSpatialProperties - 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: element = tool.Ifc.get_entity(obj) if not element: @@ -64,14 +60,13 @@ class AssignContainer(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.assign_container" bl_label = "Assign Container" bl_options = {"REGISTER", "UNDO"} - structure: bpy.props.IntProperty() def _execute(self, context): - structure_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.structure)) - for element_obj in context.selected_objects: - core.assign_container( - tool.Ifc, tool.Collector, tool.Spatial, structure_obj=structure_obj, element_obj=element_obj - ) + if container := tool.Root.get_default_container(): + for element_obj in context.selected_objects: + core.assign_container( + tool.Ifc, tool.Collector, tool.Spatial, container=container, element_obj=element_obj + ) 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) -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): bl_idname = "bim.disable_editing_container" bl_label = "Disable Editing Container" @@ -129,11 +114,10 @@ class CopyToContainer(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - sprops = context.scene.BIMSpatialProperties # Track decompositions so they can be recreated after the operation relationships = tool.Root.get_decomposition_relationships(context.selected_objects) 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: result_objs = core.copy_to_container(tool.Ifc, tool.Collector, tool.Spatial, obj=obj, containers=containers) if result_objs: diff --git a/src/blenderbim/blenderbim/bim/module/spatial/prop.py b/src/blenderbim/blenderbim/bim/module/spatial/prop.py index f56ba3be2f..9a5608136e 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/prop.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/prop.py @@ -82,20 +82,6 @@ def is_object_applicable(self, obj): 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): is_editing: BoolProperty(name="Is Editing") relating_container_object: PointerProperty( diff --git a/src/blenderbim/blenderbim/bim/module/spatial/ui.py b/src/blenderbim/blenderbim/bim/module/spatial/ui.py index 9c37e89678..51e1dfe583 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/ui.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/ui.py @@ -39,24 +39,32 @@ class BIM_PT_spatial(Panel): if not SpatialData.is_loaded: SpatialData.load() - props = context.scene.BIMSpatialProperties osprops = context.active_object.BIMObjectSpatialProperties if osprops.is_editing: - row = self.layout.row(align=True) - if SpatialData.data["parent_container_id"]: - op = row.operator("bim.change_spatial_level", text="", icon="FRAME_PREV") - op.parent = SpatialData.data["parent_container_id"] - if props.containers and props.active_container_index < len(props.containers): - 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="") + if SpatialData.data["default_container"]: + row = self.layout.row(align=True) + row.label(text=f"Target: {SpatialData.data['default_container']}", icon="OUTLINER_COLLECTION") + row.operator("bim.assign_container", icon="CHECKMARK", text="Reassign Container") + row.operator("bim.disable_editing_container", icon="CANCEL", text="") - self.layout.template_list("BIM_UL_containers", "", props, "containers", props, "active_container_index") - self.layout.prop(osprops, "relating_container_object") + if SpatialData.data["selected_containers"]: + 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: row = self.layout.row(align=True) if SpatialData.data["label"]: @@ -74,23 +82,6 @@ class BIM_PT_spatial(Panel): 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): bl_label = "Spatial Decomposition" bl_idname = "BIM_PT_spatial_decomposition" diff --git a/src/blenderbim/blenderbim/core/spatial.py b/src/blenderbim/blenderbim/core/spatial.py index a256a9d9de..b265c70b2b 100644 --- a/src/blenderbim/blenderbim/core/spatial.py +++ b/src/blenderbim/blenderbim/core/spatial.py @@ -49,17 +49,13 @@ def assign_container( ifc: tool.Ifc, collector: tool.Collector, spatial: tool.Spatial, - structure_obj: Optional[bpy.types.Object] = None, + container: ifcopenshell.entity_instance, element_obj: Optional[bpy.types.Object] = None, ) -> Union[ifcopenshell.entity_instance, None]: - if not spatial.can_contain(structure_obj, element_obj): + if not spatial.can_contain(container, element_obj): return - assert element_obj and structure_obj # Type checker. - rel = ifc.run( - "spatial.assign_container", - products=[ifc.get_entity(element_obj)], - relating_structure=ifc.get_entity(structure_obj), - ) + assert element_obj # Type checker. + rel = ifc.run("spatial.assign_container", products=[ifc.get_entity(element_obj)], relating_structure=container) spatial.disable_editing(element_obj) collector.assign(element_obj) return rel @@ -67,17 +63,12 @@ def assign_container( def enable_editing_container(spatial: tool.Spatial, obj: bpy.types.Object) -> None: spatial.enable_editing(obj) - spatial.import_containers() def disable_editing_container(spatial: tool.Spatial, obj: bpy.types.Object) -> None: 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: ifc.run("spatial.unassign_container", products=[ifc.get_entity(obj)]) collector.assign(obj) diff --git a/src/blenderbim/blenderbim/tool/spatial.py b/src/blenderbim/blenderbim/tool/spatial.py index 642c9b4962..35b0918dbe 100644 --- a/src/blenderbim/blenderbim/tool/spatial.py +++ b/src/blenderbim/blenderbim/tool/spatial.py @@ -39,23 +39,20 @@ import json from math import pi from mathutils import Vector, Matrix 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): @classmethod - def can_contain( - cls, structure_obj: Union[bpy.types.Object, None], element_obj: Union[bpy.types.Object, None] - ) -> bool: - structure = tool.Ifc.get_entity(structure_obj) + def can_contain(cls, container: ifcopenshell.entity_instance, element_obj: Union[bpy.types.Object, None]) -> bool: element = tool.Ifc.get_entity(element_obj) - if not structure or not element: + if not element: return False if tool.Ifc.get_schema() == "IFC2X3": - if not structure.is_a("IfcSpatialStructureElement"): + if not container.is_a("IfcSpatialStructureElement"): return False else: - if not structure.is_a("IfcSpatialStructureElement") and not structure.is_a( + if not container.is_a("IfcSpatialStructureElement") and not container.is_a( "IfcExternalSpatialStructureElement" ): 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: 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 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) @@ -886,3 +858,11 @@ class Spatial(blenderbim.core.tool.Spatial): if subelement: return subelement 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