mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-08 08:51:35 +00:00
Deprecate "duplicate" spatial tree browser in favour of regular object selection
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user