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"> <img src="https://github.com/IfcOpenShell/IfcOpenShell/assets/88302/34901387-e2dd-4a0c-8e38-9ffc32a66cde">
</p> </p>
IfcOpenShell is an open source ([LGPL]) software library for working with Industry Foundation Classes ([IFC]). Complete 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 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 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) bonsai.core.aggregate.enable_aggregate_mode(tool.Aggregate, context.active_object)
return {"FINISHED"} 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]: if len(selected_objs) == 1 and context.active_object == selected_objs[0]:
self.handle_single_object(context, context.active_object) self.handle_single_object(context, context.active_object)
elif len(selected_objs) == 0: elif len(selected_objs) == 0:
if context.scene.BIMAggregateProperties.in_aggregate_mode: if context.scene.BIMAggregateProperties.in_aggregate_mode:
bonsai.core.aggregate.disable_aggregate_mode(tool.Aggregate) bonsai.core.aggregate.disable_aggregate_mode(tool.Aggregate)
return {"FINISHED"} return {"FINISHED"}
if context.scene.BIMNestProperties.in_nest_mode:
bonsai.core.nest.disable_nest_mode(tool.Nest)
return {"FINISHED"}
tool.Geometry.disable_item_mode() tool.Geometry.disable_item_mode()
elif len(selected_objs) > 1: elif len(selected_objs) > 1:
self.handle_multiple_selected_objects(context) self.handle_multiple_selected_objects(context)
@@ -2018,6 +2026,18 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
else: else:
return False 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): class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
bl_description = "Switch from Edit to Item or Object mode" bl_description = "Switch from Edit to Item or Object mode"
@@ -3017,6 +3037,7 @@ class OverrideMoveAggregate(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
def _execute(self, context): def _execute(self, context):
# Get aggregates
props = context.scene.BIMAggregateProperties props = context.scene.BIMAggregateProperties
not_editing_objs = [o.obj for o in props.not_editing_objects] not_editing_objs = [o.obj for o in props.not_editing_objects]
aggregates_to_move = [] aggregates_to_move = []
@@ -3039,10 +3060,47 @@ class OverrideMoveAggregate(bpy.types.Operator):
aggregates_to_move.append(tool.Ifc.get_object(aggregate)) aggregates_to_move.append(tool.Ifc.get_object(aggregate))
obj.select_set(False) obj.select_set(False)
aggregates_to_move = set(aggregates_to_move) aggregates_to_move = set(aggregates_to_move)
for obj in aggregates_to_move:
obj.select_set(True) if aggregates_to_move:
for part in tool.Aggregate.get_parts_recursively(tool.Ifc.get_entity(obj)): for obj in aggregates_to_move:
part_obj = tool.Ifc.get_object(part) obj.select_set(True)
part_obj.select_set(True) for part in tool.Aggregate.get_parts_recursively(tool.Ifc.get_entity(obj)):
self.new_active_obj = 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"} return {"FINISHED"}
@@ -27,13 +27,17 @@ classes = (
operator.BIM_OT_select_nest, operator.BIM_OT_select_nest,
operator.BIM_OT_nest_unassign_object, operator.BIM_OT_nest_unassign_object,
prop.BIMObjectNestProperties, prop.BIMObjectNestProperties,
prop.Objects,
prop.BIMNestProperties,
ui.BIM_PT_nest, ui.BIM_PT_nest,
) )
def register(): def register():
bpy.types.Object.BIMObjectNestProperties = bpy.props.PointerProperty(type=prop.BIMObjectNestProperties) bpy.types.Object.BIMObjectNestProperties = bpy.props.PointerProperty(type=prop.BIMObjectNestProperties)
bpy.types.Scene.BIMNestProperties = bpy.props.PointerProperty(type=prop.BIMNestProperties)
def unregister(): def unregister():
del bpy.types.Object.BIMObjectNestProperties 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/>. # along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
import bonsai.tool as tool
from bonsai.bim.prop import StrProperty, Attribute from bonsai.bim.prop import StrProperty, Attribute
from bonsai.bim.module.spatial.data import SpatialData from bonsai.bim.module.spatial.data import SpatialData
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
@@ -30,6 +31,7 @@ from bpy.props import (
FloatVectorProperty, FloatVectorProperty,
CollectionProperty, CollectionProperty,
) )
from bonsai.bim.module.nest.decorator import NestDecorator, NestModeDecorator
def update_relating_object(self, context): def update_relating_object(self, context):
@@ -46,3 +48,33 @@ def update_relating_object(self, context):
class BIMObjectNestProperties(PropertyGroup): class BIMObjectNestProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing") is_editing: BoolProperty(name="Is Editing")
relating_object: PointerProperty(name="Nest Host", type=bpy.types.Object, update=update_relating_object) 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 # You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>. # 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): def enable_editing_nest(nest, obj=None):
nest.enable_editing(obj) 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) 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) assign_object(ifc, nest, collector, relating_obj=obj, related_obj=part_obj)
blender.set_active_object(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) ifc.run("spatial.assign_container", products=[element], relating_structure=default_container)
if relating_obj := root.is_in_aggregate_mode(element): if relating_obj := root.is_in_aggregate_mode(element):
ifc.run("aggregate.assign_object", products=[element], relating_object=relating_obj) 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) collector.assign(obj)
return element return element
+73
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>. # along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
import bonsai.core.tool import bonsai.core.tool
import bonsai.tool as tool import bonsai.tool as tool
import ifcopenshell.util.element import ifcopenshell.util.element
@@ -47,3 +48,75 @@ class Nest(bonsai.core.tool.Nest):
@classmethod @classmethod
def get_relating_object(cls, related_element): def get_relating_object(cls, related_element):
return ifcopenshell.util.element.get_nest(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: if props.editing_aggregate and props.in_aggregate_mode:
return tool.Ifc.get_entity(props.editing_aggregate) 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 @classmethod
def reload_grid_decorator(cls) -> None: def reload_grid_decorator(cls) -> None:
axes = bpy.context.scene.BIMGridProperties.grid_axes axes = bpy.context.scene.BIMGridProperties.grid_axes