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