Add "nest mode" similar to "aggregate mode".

This commit is contained in:
Bruno Perdigão
2025-01-17 22:47:11 -03:00
parent 3f914cdf27
commit a5afba03ab
8 changed files with 203 additions and 6 deletions
+1
View File
@@ -6,6 +6,7 @@ IfcOpenShell
<img src="https://github.com/IfcOpenShell/IfcOpenShell/assets/88302/34901387-e2dd-4a0c-8e38-9ffc32a66cde">
</p>
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
@@ -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"}
@@ -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
+32
View File
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
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,
)
+21
View File
@@ -16,6 +16,13 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
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()
+2
View File
@@ -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
+73
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
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()
+6
View File
@@ -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