diff --git a/README.md b/README.md
index b65de757ef..9d5160b4fa 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@ IfcOpenShell
+
IfcOpenShell is an open source ([LGPL]) software library for working with Industry Foundation Classes ([IFC]). Complete
parsing support is provided for [IFC2x3 TC1], [IFC4 Add2 TC1], IFC4x1, IFC4x2, and [IFC4x3 Add2]. Extensive geometric support
is implemented for the IFC releases [IFC2x3 TC1] and [IFC4 Add2 TC1]. Extending with support for arbitrary IFC schemas
diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py
index cad0d190b6..c2096c6103 100644
--- a/src/bonsai/bonsai/bim/module/geometry/operator.py
+++ b/src/bonsai/bonsai/bim/module/geometry/operator.py
@@ -1894,12 +1894,20 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
bonsai.core.aggregate.enable_aggregate_mode(tool.Aggregate, context.active_object)
return {"FINISHED"}
+ if self.has_nests(selected_objs):
+ if not context.scene.BIMNestProperties.in_nest_mode:
+ bonsai.core.nest.enable_nest_mode(tool.Nest, context.active_object)
+ return {"FINISHED"}
+
if len(selected_objs) == 1 and context.active_object == selected_objs[0]:
self.handle_single_object(context, context.active_object)
elif len(selected_objs) == 0:
if context.scene.BIMAggregateProperties.in_aggregate_mode:
bonsai.core.aggregate.disable_aggregate_mode(tool.Aggregate)
return {"FINISHED"}
+ if context.scene.BIMNestProperties.in_nest_mode:
+ bonsai.core.nest.disable_nest_mode(tool.Nest)
+ return {"FINISHED"}
tool.Geometry.disable_item_mode()
elif len(selected_objs) > 1:
self.handle_multiple_selected_objects(context)
@@ -2018,6 +2026,18 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
else:
return False
+ def has_nests(self, objs):
+ for obj in objs:
+ element = tool.Ifc.get_entity(obj)
+ if not element:
+ continue
+ nest = ifcopenshell.util.element.get_nest(element)
+ components = ifcopenshell.util.element.get_components(element)
+ if (nest or components) and not bpy.context.scene.BIMNestProperties.in_nest_mode:
+ return True
+ else:
+ return False
+
class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
bl_description = "Switch from Edit to Item or Object mode"
@@ -3017,6 +3037,7 @@ class OverrideMoveAggregate(bpy.types.Operator):
return {"FINISHED"}
def _execute(self, context):
+ # Get aggregates
props = context.scene.BIMAggregateProperties
not_editing_objs = [o.obj for o in props.not_editing_objects]
aggregates_to_move = []
@@ -3039,10 +3060,47 @@ class OverrideMoveAggregate(bpy.types.Operator):
aggregates_to_move.append(tool.Ifc.get_object(aggregate))
obj.select_set(False)
aggregates_to_move = set(aggregates_to_move)
- for obj in aggregates_to_move:
- obj.select_set(True)
- for part in tool.Aggregate.get_parts_recursively(tool.Ifc.get_entity(obj)):
- part_obj = tool.Ifc.get_object(part)
- part_obj.select_set(True)
- self.new_active_obj = obj
+
+ if aggregates_to_move:
+ for obj in aggregates_to_move:
+ obj.select_set(True)
+ for part in tool.Aggregate.get_parts_recursively(tool.Ifc.get_entity(obj)):
+ part_obj = tool.Ifc.get_object(part)
+ part_obj.select_set(True)
+ self.new_active_obj = obj
+ return {"FINISHED"}
+
+ # Get nests
+ props = context.scene.BIMNestProperties
+ not_editing_objs = [o.obj for o in props.not_editing_objects]
+ nests_to_move = []
+ for obj in context.selected_objects:
+ self.new_active_obj = None
+ if obj in not_editing_objs:
+ obj.select_set(False)
+ continue
+ element = tool.Ifc.get_entity(obj)
+ if not element or not element.is_a("IfcElement"):
+ continue
+ components = ifcopenshell.util.element.get_components(element)
+ if components:
+ nests_to_move.append(tool.Ifc.get_object(element))
+ continue
+ if not components and props.in_nest_mode:
+ continue
+ nest = ifcopenshell.util.element.get_nest(element)
+ if nest:
+ nests_to_move.append(tool.Ifc.get_object(nest))
+ obj.select_set(False)
+ nests_to_move = set(nests_to_move)
+
+ if nests_to_move:
+ for obj in nests_to_move:
+ obj.select_set(True)
+ for component in tool.Nest.get_components_recursively(tool.Ifc.get_entity(obj)):
+ component_obj = tool.Ifc.get_object(component)
+ component_obj.select_set(True)
+ self.new_active_obj = obj
+ return {"FINISHED"}
+
return {"FINISHED"}
diff --git a/src/bonsai/bonsai/bim/module/nest/__init__.py b/src/bonsai/bonsai/bim/module/nest/__init__.py
index e5c07690c2..fc111fb818 100644
--- a/src/bonsai/bonsai/bim/module/nest/__init__.py
+++ b/src/bonsai/bonsai/bim/module/nest/__init__.py
@@ -27,13 +27,17 @@ classes = (
operator.BIM_OT_select_nest,
operator.BIM_OT_nest_unassign_object,
prop.BIMObjectNestProperties,
+ prop.Objects,
+ prop.BIMNestProperties,
ui.BIM_PT_nest,
)
def register():
bpy.types.Object.BIMObjectNestProperties = bpy.props.PointerProperty(type=prop.BIMObjectNestProperties)
+ bpy.types.Scene.BIMNestProperties = bpy.props.PointerProperty(type=prop.BIMNestProperties)
def unregister():
del bpy.types.Object.BIMObjectNestProperties
+ del bpy.types.Scene.BIMNestProperties
diff --git a/src/bonsai/bonsai/bim/module/nest/prop.py b/src/bonsai/bonsai/bim/module/nest/prop.py
index fafb75fbb5..7fbe30278a 100644
--- a/src/bonsai/bonsai/bim/module/nest/prop.py
+++ b/src/bonsai/bonsai/bim/module/nest/prop.py
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see .
import bpy
+import bonsai.tool as tool
from bonsai.bim.prop import StrProperty, Attribute
from bonsai.bim.module.spatial.data import SpatialData
from bpy.types import PropertyGroup
@@ -30,6 +31,7 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
+from bonsai.bim.module.nest.decorator import NestDecorator, NestModeDecorator
def update_relating_object(self, context):
@@ -46,3 +48,33 @@ def update_relating_object(self, context):
class BIMObjectNestProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing")
relating_object: PointerProperty(name="Nest Host", type=bpy.types.Object, update=update_relating_object)
+
+def update_nest_decorator(self, context):
+ if self.nest_decorator:
+ NestDecorator.install(bpy.context)
+ else:
+ NestDecorator.uninstall()
+
+
+def update_nest_mode_decorator(self, context):
+ if self.in_nest_mode:
+ NestModeDecorator.install(bpy.context)
+ else:
+ NestModeDecorator.uninstall()
+
+
+class Objects(bpy.types.PropertyGroup):
+ obj: PointerProperty(type=bpy.types.Object)
+ previous_display_type: bpy.props.StringProperty(default="TEXTURED")
+
+
+class BIMNestProperties(PropertyGroup):
+ in_nest_mode: BoolProperty(name="In Edit Mode", update=update_nest_mode_decorator)
+ editing_nest: PointerProperty(name="Editing nest", type=bpy.types.Object)
+ editing_objects: CollectionProperty(type=Objects)
+ not_editing_objects: CollectionProperty(type=Objects)
+ nest_decorator: BoolProperty(
+ name="Display Nest",
+ default=False,
+ update=update_nest_decorator,
+ )
diff --git a/src/bonsai/bonsai/core/nest.py b/src/bonsai/bonsai/core/nest.py
index c8eef3b759..94dcca9516 100644
--- a/src/bonsai/bonsai/core/nest.py
+++ b/src/bonsai/bonsai/core/nest.py
@@ -16,6 +16,13 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see .
+from __future__ import annotations
+from typing import TYPE_CHECKING, Optional, Union
+
+if TYPE_CHECKING:
+ import bpy
+ import ifcopenshell
+ import bonsai.tool as tool
def enable_editing_nest(nest, obj=None):
nest.enable_editing(obj)
@@ -58,3 +65,17 @@ def add_part_to_object(ifc, nest, collector, blender, obj, part_class, part_name
part_obj = blender.create_ifc_object(ifc_class=part_class, name=part_name)
assign_object(ifc, nest, collector, relating_obj=obj, related_obj=part_obj)
blender.set_active_object(obj)
+
+
+def enable_nest_mode(
+ nest: tool.Nest,
+ obj: bpy.types.Object,
+):
+ if nest.get_nest_mode():
+ disable_nest_mode(nest)
+
+ nest.enable_nest_mode(obj)
+
+
+def disable_nest_mode(nest: tool.Nest):
+ nest.disable_nest_mode()
diff --git a/src/bonsai/bonsai/core/root.py b/src/bonsai/bonsai/core/root.py
index b0a5385b4d..347b0fea4d 100644
--- a/src/bonsai/bonsai/core/root.py
+++ b/src/bonsai/bonsai/core/root.py
@@ -90,5 +90,7 @@ def assign_class(
ifc.run("spatial.assign_container", products=[element], relating_structure=default_container)
if relating_obj := root.is_in_aggregate_mode(element):
ifc.run("aggregate.assign_object", products=[element], relating_object=relating_obj)
+ if relating_obj := root.is_in_nest_mode(element):
+ ifc.run("nest.assign_object", related_objects=[element], relating_object=relating_obj)
collector.assign(obj)
return element
diff --git a/src/bonsai/bonsai/tool/nest.py b/src/bonsai/bonsai/tool/nest.py
index f971981a92..99b87cf912 100644
--- a/src/bonsai/bonsai/tool/nest.py
+++ b/src/bonsai/bonsai/tool/nest.py
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see .
+import bpy
import bonsai.core.tool
import bonsai.tool as tool
import ifcopenshell.util.element
@@ -47,3 +48,75 @@ class Nest(bonsai.core.tool.Nest):
@classmethod
def get_relating_object(cls, related_element):
return ifcopenshell.util.element.get_nest(related_element)
+
+ @classmethod
+ def get_components_recursively(cls, element: ifcopenshell.entity_instance) -> set[ifcopenshell.entity_instance]:
+ """Get elements components recursively, resulting set includes `element`."""
+ components = set()
+ queue = {element}
+ while queue:
+ element = queue.pop()
+ queue.update(new_components := set(ifcopenshell.util.element.get_components(element)))
+ components.update(new_components)
+ return components
+
+ @classmethod
+ def get_nest_mode(cls):
+ return bpy.context.scene.BIMNestProperties.in_nest_mode
+
+ @classmethod
+ def enable_nest_mode(cls, active_object: bpy.types.Object):
+ context = bpy.context
+ props = context.scene.BIMNestProperties
+
+ element = tool.Ifc.get_entity(active_object)
+ if not element:
+ return {"FINISHED"}
+ nest = ifcopenshell.util.element.get_nest(element)
+ components = ifcopenshell.util.element.get_components(element)
+ if not nest and not components:
+ return {"FINISHED"}
+ if not components:
+ components = ifcopenshell.util.element.get_components(nest)
+ if components:
+ props.editing_nest = tool.Ifc.get_object(nest) if nest else tool.Ifc.get_object(element)
+ components_objs = [tool.Ifc.get_object(component) for component in components]
+ objs = []
+ visible_objects = tool.Raycast.get_visible_objects(context)
+ for obj in visible_objects:
+ if obj.visible_in_viewport_get(context.space_data):
+ objs.append(obj.original)
+ for obj in objs:
+ if obj.original not in components_objs:
+ if obj == props.editing_nest:
+ continue
+ not_editing_obj = props.not_editing_objects.add()
+ not_editing_obj.obj = obj.original
+ not_editing_obj.previous_display_type = obj.original.display_type
+ obj.original.display_type = "WIRE"
+ else:
+ editing_obj = props.editing_objects.add()
+ editing_obj.obj = obj.original
+
+ props.in_nest_mode = True
+ return {"FINISHED"}
+
+ @classmethod
+ def disable_nest_mode(cls):
+ context = bpy.context
+ props = context.scene.BIMNestProperties
+ for obj_prop in props.not_editing_objects:
+ obj = obj_prop.obj
+ obj.original.display_type = obj_prop.previous_display_type
+ element = tool.Ifc.get_entity(obj)
+ if not element:
+ continue
+
+ components = ifcopenshell.util.element.get_components(tool.Ifc.get_entity(props.editing_nest))
+ objs = [tool.Ifc.get_object(component) for component in components]
+ if context.space_data.local_view:
+ bpy.ops.view3d.localview()
+
+ props.in_nest_mode = False
+ props.not_editing_objects.clear()
+ props.editing_objects.clear()
diff --git a/src/bonsai/bonsai/tool/root.py b/src/bonsai/bonsai/tool/root.py
index 7de7f453ad..b8ba2eeb02 100644
--- a/src/bonsai/bonsai/tool/root.py
+++ b/src/bonsai/bonsai/tool/root.py
@@ -223,6 +223,12 @@ class Root(bonsai.core.tool.Root):
if props.editing_aggregate and props.in_aggregate_mode:
return tool.Ifc.get_entity(props.editing_aggregate)
+ @classmethod
+ def is_in_nest_mode(cls, element: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
+ props = bpy.context.scene.BIMNestProperties
+ if props.editing_nest and props.in_nest_mode:
+ return tool.Ifc.get_entity(props.editing_nest)
+
@classmethod
def reload_grid_decorator(cls) -> None:
axes = bpy.context.scene.BIMGridProperties.grid_axes