diff --git a/src/bonsai/bonsai/bim/handler.py b/src/bonsai/bonsai/bim/handler.py index 88e785194f..fc45d6c01c 100644 --- a/src/bonsai/bonsai/bim/handler.py +++ b/src/bonsai/bonsai/bim/handler.py @@ -347,8 +347,8 @@ def load_post(scene): # Bonsai overlays georeference_props = tool.Georeference.get_georeference_props() - aggregate_props = bpy.context.scene.BIMAggregateProperties - nest_props = bpy.context.scene.BIMNestProperties + aggregate_props = tool.Aggregate.get_aggregate_props() + nest_props = tool.Nest.get_nest_props() model_props = tool.Model.get_model_props() if georeference_props.should_visualise: GeoreferenceDecorator.install(bpy.context) diff --git a/src/bonsai/bonsai/bim/module/aggregate/decorator.py b/src/bonsai/bonsai/bim/module/aggregate/decorator.py index 239253ec2a..347daae714 100644 --- a/src/bonsai/bonsai/bim/module/aggregate/decorator.py +++ b/src/bonsai/bonsai/bim/module/aggregate/decorator.py @@ -20,6 +20,7 @@ import blf import bpy import gpu import ifcopenshell +import ifcopenshell.util.element import bonsai.tool as tool from bpy.types import SpaceView3D from bpy_extras import view3d_utils @@ -162,7 +163,8 @@ class AggregateDecorator: batch.draw(shader) def draw_aggregate(self, context): - if context.scene.BIMAggregateProperties.in_aggregate_mode: + props = tool.Aggregate.get_aggregate_props() + if props.in_aggregate_mode: return self.addon_prefs = tool.Blender.get_addon_preferences() decorator_color_special = self.addon_prefs.decorator_color_special @@ -210,7 +212,7 @@ class AggregateDecorator: self.draw_batch("LINES", line_y, color, [(0, 1)]) line_z = (location - Vector((0.0, 0.0, size)), location + Vector((0.0, 0.0, size))) self.draw_batch("LINES", line_z, color, [(0, 1)]) - if context.scene.BIMAggregateProperties.in_aggregate_mode: + if props.in_aggregate_mode: return parts = ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(aggregate)) parts_objs = [tool.Ifc.get_object(p) for p in parts] @@ -261,7 +263,7 @@ class AggregateModeDecorator: return region = context.region rv3d = region.data - props = context.scene.BIMAggregateProperties + props = tool.Aggregate.get_aggregate_props() aggregate_obj = props.editing_aggregate if not aggregate_obj: @@ -292,7 +294,7 @@ class AggregateModeDecorator: def draw_aggregate_empty(self, context): if context.mode == "EDIT_MESH": return - props = context.scene.BIMAggregateProperties + props = tool.Aggregate.get_aggregate_props() aggregate_obj = props.editing_aggregate if not aggregate_obj: return diff --git a/src/bonsai/bonsai/bim/module/aggregate/operator.py b/src/bonsai/bonsai/bim/module/aggregate/operator.py index c324037b89..f84dab2186 100644 --- a/src/bonsai/bonsai/bim/module/aggregate/operator.py +++ b/src/bonsai/bonsai/bim/module/aggregate/operator.py @@ -23,6 +23,7 @@ import ifcopenshell.util.element import bonsai.tool as tool import bonsai.core.aggregate as core import bonsai.core.spatial +from typing import TYPE_CHECKING class BIM_OT_aggregate_assign_object(bpy.types.Operator, tool.Ifc.Operator): @@ -37,16 +38,23 @@ class BIM_OT_aggregate_assign_object(bpy.types.Operator, tool.Ifc.Operator): relating_object: bpy.props.IntProperty() related_object: bpy.props.IntProperty() + if TYPE_CHECKING: + relating_object: int + related_object: int + def _execute(self, context): relating_obj = None if self.relating_object: relating_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object)) + assert relating_obj elif self.related_object: aggregate = ifcopenshell.util.element.get_aggregate(tool.Ifc.get().by_id(self.related_object)) if aggregate: relating_obj = tool.Ifc.get_object(aggregate) - elif context.active_object: + assert relating_obj + else: relating_obj = context.active_object + if not relating_obj: self.report({"ERROR"}, "No relating object is provided.") return @@ -66,7 +74,7 @@ class BIM_OT_aggregate_assign_object(bpy.types.Operator, tool.Ifc.Operator): relating_obj=relating_obj, related_obj=obj, ) - props = context.scene.BIMAggregateProperties + props = tool.Aggregate.get_aggregate_props() if relating_obj == props.editing_aggregate and props.in_aggregate_mode: new_editing_obj = props.editing_objects.add() new_editing_obj.obj = obj @@ -182,7 +190,7 @@ class BIM_OT_add_aggregate(bpy.types.Operator, tool.Ifc.Operator): ) core.assign_object(tool.Ifc, tool.Aggregate, tool.Collector, relating_obj=aggregate, related_obj=obj) - def create_aggregate(self, context, ifc_class, aggregate_name): + def create_aggregate(self, context: bpy.types.Context, ifc_class: str, aggregate_name: str) -> bpy.types.Object: aggregate = bpy.data.objects.new(aggregate_name, None) aggregate.location = context.scene.cursor.location bpy.ops.bim.assign_class(obj=aggregate.name, ifc_class=ifc_class) @@ -388,7 +396,7 @@ class BIM_OT_toggle_aggregate_mode_local_view(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - props = context.scene.BIMAggregateProperties + props = tool.Aggregate.get_aggregate_props() objs = [o.obj for o in props.editing_objects] if props.in_aggregate_mode: if context.space_data.local_view: diff --git a/src/bonsai/bonsai/bim/module/aggregate/prop.py b/src/bonsai/bonsai/bim/module/aggregate/prop.py index 1d524b3645..0fb63661b3 100644 --- a/src/bonsai/bonsai/bim/module/aggregate/prop.py +++ b/src/bonsai/bonsai/bim/module/aggregate/prop.py @@ -33,6 +33,7 @@ from bpy.props import ( CollectionProperty, ) from bonsai.bim.module.aggregate.decorator import AggregateDecorator, AggregateModeDecorator +from typing import TYPE_CHECKING, Union def can_aggregate(relating_obj: bpy.types.Object, related_obj: bpy.types.Object) -> bool: @@ -98,12 +99,22 @@ class BIMObjectAggregateProperties(PropertyGroup): poll=poll_related_object, ) + if TYPE_CHECKING: + is_editing: bool + relating_object: Union[bpy.types.Object, None] + related_object: Union[bpy.types.Object, None] + class Objects(bpy.types.PropertyGroup): obj: PointerProperty(type=bpy.types.Object) previous_display_type: bpy.props.StringProperty(default="TEXTURED") previous_hide_select: bpy.props.BoolProperty(default=False) + if TYPE_CHECKING: + obj: Union[bpy.types.Object, None] + previous_display_type: str + previous_hide_select: bool + class BIMAggregateProperties(PropertyGroup): in_aggregate_mode: BoolProperty(name="In Edit Mode", update=update_aggregate_mode_decorator) @@ -115,3 +126,10 @@ class BIMAggregateProperties(PropertyGroup): default=False, update=update_aggregate_decorator, ) + + if TYPE_CHECKING: + in_aggregate_mode: bool + editing_aggregate: Union[bpy.types.Object, None] + editing_objects: bpy.types.bpy_prop_collection_idprop[Objects] + not_editing_objects: bpy.types.bpy_prop_collection_idprop[Objects] + aggregate_decorator: bool diff --git a/src/bonsai/bonsai/bim/module/aggregate/ui.py b/src/bonsai/bonsai/bim/module/aggregate/ui.py index 0755d3a54f..91a4e138f4 100644 --- a/src/bonsai/bonsai/bim/module/aggregate/ui.py +++ b/src/bonsai/bonsai/bim/module/aggregate/ui.py @@ -49,11 +49,13 @@ class BIM_PT_aggregate(Panel): layout = self.layout row = layout.row() row.label(text="Aggregate Decorator") - row.prop(context.scene.BIMAggregateProperties, "aggregate_decorator", icon="HIDE_OFF", text="") + props = tool.Aggregate.get_aggregate_props() + row.prop(props, "aggregate_decorator", icon="HIDE_OFF", text="") if not AggregateData.is_loaded: AggregateData.load() - props = context.active_object.BIMObjectAggregateProperties + assert (obj := context.active_object) + props = tool.Aggregate.get_object_aggregate_props(obj) if props.is_editing: row = layout.row() @@ -131,9 +133,8 @@ class BIM_PT_linked_aggregate(Panel): if not AggregateData.is_loaded: AggregateData.load() - obj = context.active_object - element = tool.Ifc.get_entity(obj) - props = obj.BIMObjectAggregateProperties + assert (obj := context.active_object) + assert (element := tool.Ifc.get_entity(obj)) row = layout.row(align=True) if element.Decomposes: diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py index 3062aa332c..d779e52765 100644 --- a/src/bonsai/bonsai/bim/module/geometry/operator.py +++ b/src/bonsai/bonsai/bim/module/geometry/operator.py @@ -1929,7 +1929,7 @@ class OverrideEscape(bpy.types.Operator): tool.Geometry.disable_item_mode() elif tool.Model.get_model_props().openings: bpy.ops.bim.hide_all_openings() - elif context.scene.BIMAggregateProperties.in_aggregate_mode: + elif tool.Aggregate.get_aggregate_props().in_aggregate_mode: bpy.ops.bim.disable_aggregate_mode() elif active_object := context.active_object: if tool.Blender.Modifier.try_canceling_editing_modifier_parameters_or_path(active_object): @@ -1953,24 +1953,26 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): selected_objs = context.selected_objects # Purposely exclude active object + aprops = tool.Aggregate.get_aggregate_props() + nprops = tool.Nest.get_nest_props() if self.has_aggregates(selected_objs): - if not context.scene.BIMAggregateProperties.in_aggregate_mode: + if not aprops.in_aggregate_mode: 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: + if not nprops.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: + if aprops.in_aggregate_mode: bonsai.core.aggregate.disable_aggregate_mode(tool.Aggregate) return {"FINISHED"} - if context.scene.BIMNestProperties.in_nest_mode: + if nprops.in_nest_mode: bonsai.core.nest.disable_nest_mode(tool.Nest) return {"FINISHED"} tool.Geometry.disable_item_mode() @@ -1981,9 +1983,10 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator): element = tool.Ifc.get_entity(obj) props = tool.Geometry.get_geometry_props() pprops = tool.Project.get_project_props() + aprops = tool.Aggregate.get_aggregate_props() if obj == props.representation_obj: self.report({"ERROR"}, f"Element '{obj.name}' is in item mode and cannot be edited directly") - elif obj in [o.obj for o in context.scene.BIMAggregateProperties.not_editing_objects]: + elif obj in [o.obj for o in aprops.not_editing_objects]: obj.select_set(False) self.report( {"ERROR"}, f"Element '{obj.name}' does not belong to this aggregate and cannot be edited directly" @@ -2099,7 +2102,8 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator): continue aggregate = ifcopenshell.util.element.get_aggregate(element) parts = ifcopenshell.util.element.get_parts(element) - if (aggregate or parts) and not bpy.context.scene.BIMAggregateProperties.in_aggregate_mode: + props = tool.Aggregate.get_aggregate_props() + if (aggregate or parts) and not props.in_aggregate_mode: return True else: return False @@ -2111,7 +2115,8 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator): 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: + props = tool.Nest.get_nest_props() + if (nest or components) and not props.in_nest_mode: return True else: return False @@ -3270,7 +3275,7 @@ class OverrideMove(bpy.types.Operator): self.new_active_obj = obj # Get aggregates - props = context.scene.BIMAggregateProperties + props = tool.Aggregate.get_aggregate_props() not_editing_objs = [o.obj for o in props.not_editing_objects] aggregates_to_move = [] for obj in context.selected_objects: @@ -3305,7 +3310,7 @@ class OverrideMove(bpy.types.Operator): return {"FINISHED"} # Get nests - props = context.scene.BIMNestProperties + props = tool.Nest.get_nest_props() not_editing_objs = [o.obj for o in props.not_editing_objects] nests_to_move = [] for obj in context.selected_objects: diff --git a/src/bonsai/bonsai/bim/module/model/workspace.py b/src/bonsai/bonsai/bim/module/model/workspace.py index b4814a4d86..42a767e726 100644 --- a/src/bonsai/bonsai/bim/module/model/workspace.py +++ b/src/bonsai/bonsai/bim/module/model/workspace.py @@ -717,12 +717,14 @@ class EditObjectUI: AuthoringData.load(ifc_element_type) if context.region.type == "TOOL_HEADER": - if context.scene.BIMAggregateProperties.in_aggregate_mode: + aprops = tool.Aggregate.get_aggregate_props() + if aprops.in_aggregate_mode: layout.label(text=f"Aggregate Mode", icon="EMPTY_AXIS") row = cls.layout.row(align=True) op = row.operator("bim.disable_aggregate_mode", text="", icon="X") op = row.operator("bim.toggle_aggregate_mode_local_view", text="", icon="ZOOM_SELECTED") - if context.scene.BIMNestProperties.in_nest_mode: + nprops = tool.Nest.get_nest_props() + if nprops.in_nest_mode: layout.label(text=f"Nest Mode", icon="EMPTY_AXIS") row = cls.layout.row(align=True) op = row.operator("bim.disable_nest_mode", text="", icon="X") diff --git a/src/bonsai/bonsai/bim/module/nest/decorator.py b/src/bonsai/bonsai/bim/module/nest/decorator.py index 64f3083b78..5a16ef8e44 100644 --- a/src/bonsai/bonsai/bim/module/nest/decorator.py +++ b/src/bonsai/bonsai/bim/module/nest/decorator.py @@ -20,6 +20,7 @@ import blf import bpy import gpu import ifcopenshell +import ifcopenshell.util.element import bonsai.tool as tool from bpy.types import SpaceView3D from bpy_extras import view3d_utils @@ -162,8 +163,9 @@ class NestDecorator: shader.uniform_float("color", color) batch.draw(shader) - def draw_nest(self, context): - if context.scene.BIMNestProperties.in_nest_mode: + def draw_nest(self, context: bpy.types.Context) -> None: + props = tool.Nest.get_nest_props() + if props.in_nest_mode: return self.addon_prefs = tool.Blender.get_addon_preferences() decorator_color_special = self.addon_prefs.decorator_color_special @@ -262,7 +264,7 @@ class NestModeDecorator: return region = context.region rv3d = region.data - props = context.scene.BIMNestProperties + props = tool.Nest.get_nest_props() aggregate_obj = props.editing_nest if not aggregate_obj: @@ -291,7 +293,7 @@ class NestModeDecorator: def draw_nest_empty(self, context): if context.mode == "EDIT_MESH": return - props = context.scene.BIMNestProperties + props = tool.Nest.get_nest_props() nest_obj = props.editing_nest if not nest_obj: return diff --git a/src/bonsai/bonsai/bim/module/nest/operator.py b/src/bonsai/bonsai/bim/module/nest/operator.py index f3247817ec..68b6839e79 100644 --- a/src/bonsai/bonsai/bim/module/nest/operator.py +++ b/src/bonsai/bonsai/bim/module/nest/operator.py @@ -158,7 +158,7 @@ class BIM_OT_toggle_nest_mode_local_view(bpy.types.Operator): bl_options = {"REGISTER", "UNDO"} def execute(self, context): - props = context.scene.BIMNestProperties + props = tool.Nest.get_nest_props() objs = [o.obj for o in props.editing_objects] if props.in_nest_mode: if context.space_data.local_view: diff --git a/src/bonsai/bonsai/bim/module/nest/prop.py b/src/bonsai/bonsai/bim/module/nest/prop.py index d5e48b2479..6cb787b3d4 100644 --- a/src/bonsai/bonsai/bim/module/nest/prop.py +++ b/src/bonsai/bonsai/bim/module/nest/prop.py @@ -32,10 +32,11 @@ from bpy.props import ( CollectionProperty, ) from bonsai.bim.module.nest.decorator import NestDecorator, NestModeDecorator +from typing import TYPE_CHECKING, Union -def update_relating_object(self, context): - def message(self, context): +def update_relating_object(self: "BIMObjectNestProperties", context: bpy.types.Context) -> None: + def message(self, context: bpy.types.Context) -> None: self.layout.label(text="Please select a valid Ifc Element") if self.relating_object is None: @@ -49,15 +50,19 @@ class BIMObjectNestProperties(PropertyGroup): is_editing: BoolProperty(name="Is Editing") relating_object: PointerProperty(name="Nest Host", type=bpy.types.Object, update=update_relating_object) + if TYPE_CHECKING: + is_editing: bool + relating_object: bpy.types.Object -def update_nest_decorator(self, context): + +def update_nest_decorator(self: "BIMNestProperties", context: bpy.types.Context) -> None: if self.nest_decorator: NestDecorator.install(bpy.context) else: NestDecorator.uninstall() -def update_nest_mode_decorator(self, context): +def update_nest_mode_decorator(self: "BIMNestProperties", context: bpy.types.Context) -> None: if self.in_nest_mode: NestModeDecorator.install(bpy.context) else: @@ -68,6 +73,10 @@ class Objects(bpy.types.PropertyGroup): obj: PointerProperty(type=bpy.types.Object) previous_display_type: bpy.props.StringProperty(default="TEXTURED") + if TYPE_CHECKING: + obj: Union[bpy.types.Object, None] + previous_display_type: str + class BIMNestProperties(PropertyGroup): in_nest_mode: BoolProperty(name="In Edit Mode", update=update_nest_mode_decorator) @@ -79,3 +88,10 @@ class BIMNestProperties(PropertyGroup): default=False, update=update_nest_decorator, ) + + if TYPE_CHECKING: + in_nest_mode: bool + editing_nest: Union[bpy.types.Object, None] + editing_objects: bpy.types.bpy_prop_collection_idprop[Objects] + not_editing_objects: bpy.types.bpy_prop_collection_idprop[Objects] + nest_decorator: bool diff --git a/src/bonsai/bonsai/bim/module/nest/ui.py b/src/bonsai/bonsai/bim/module/nest/ui.py index 601fedf098..2ea33de49a 100644 --- a/src/bonsai/bonsai/bim/module/nest/ui.py +++ b/src/bonsai/bonsai/bim/module/nest/ui.py @@ -48,7 +48,8 @@ class BIM_PT_nest(Panel): if not NestData.is_loaded: NestData.load() - props = context.active_object.BIMObjectNestProperties + assert (obj := context.active_object) + props = tool.Nest.get_object_nest_props(obj) if props.is_editing: row = layout.row(align=True) diff --git a/src/bonsai/bonsai/bim/module/root/operator.py b/src/bonsai/bonsai/bim/module/root/operator.py index c204200a94..fcc9303fc4 100644 --- a/src/bonsai/bonsai/bim/module/root/operator.py +++ b/src/bonsai/bonsai/bim/module/root/operator.py @@ -35,6 +35,7 @@ import bonsai.bim.module.root.prop as root_prop from bonsai.bim.ifc import IfcStore from bonsai.bim.helper import get_enum_items, prop_with_search from mathutils import Vector +from typing import TYPE_CHECKING class EnableReassignClass(bpy.types.Operator): @@ -182,6 +183,15 @@ class AssignClass(bpy.types.Operator, tool.Ifc.Operator): should_add_representation: bpy.props.BoolProperty(default=True) ifc_representation_class: bpy.props.StringProperty() + if TYPE_CHECKING: + obj: str + ifc_class: str + predefined_type: str + userdefined_type: str + context_id: int + should_add_representation: bool + ifc_representation_class: str + def _execute(self, context): props = tool.Root.get_root_props() objects: list[bpy.types.Object] = [] diff --git a/src/bonsai/bonsai/bim/ui.py b/src/bonsai/bonsai/bim/ui.py index a6e8d2b25f..e73dab5288 100644 --- a/src/bonsai/bonsai/bim/ui.py +++ b/src/bonsai/bonsai/bim/ui.py @@ -1262,8 +1262,8 @@ class BIM_PT_decorators_overlay(Panel): overlay = view.overlay georeference_props = tool.Georeference.get_georeference_props() - aggregate_props = bpy.context.scene.BIMAggregateProperties - nest_props = bpy.context.scene.BIMNestProperties + aggregate_props = tool.Aggregate.get_aggregate_props() + nest_props = tool.Nest.get_nest_props() model_props = tool.Model.get_model_props() display_all = overlay.show_overlays diff --git a/src/bonsai/bonsai/core/nest.py b/src/bonsai/bonsai/core/nest.py index 45eb660a9d..18fe5c5831 100644 --- a/src/bonsai/bonsai/core/nest.py +++ b/src/bonsai/bonsai/core/nest.py @@ -25,15 +25,21 @@ if TYPE_CHECKING: import bonsai.tool as tool -def enable_editing_nest(nest, obj=None): +def enable_editing_nest(nest: tool.Nest, obj: bpy.types.Object) -> None: nest.enable_editing(obj) -def disable_editing_nest(nest, obj=None): +def disable_editing_nest(nest: tool.Nest, obj: bpy.types.Object) -> None: nest.disable_editing(obj) -def assign_object(ifc, nest, collector, relating_obj=None, related_obj=None): +def assign_object( + ifc: tool.Ifc, + nest: tool.Nest, + collector: tool.Collector, + relating_obj: bpy.types.Object, + related_obj: bpy.types.Object, +) -> Union[ifcopenshell.entity_instance, None]: if not nest.can_nest(relating_obj, related_obj): return rel = ifc.run( @@ -47,14 +53,22 @@ def assign_object(ifc, nest, collector, relating_obj=None, related_obj=None): return rel -def unassign_object(ifc, nest, collector, relating_obj=None, related_obj=None): +def unassign_object( + ifc: tool.Ifc, + nest: tool.Nest, + collector: tool.Collector, + relating_obj: bpy.types.Object, + related_obj: bpy.types.Object, +) -> Union[ifcopenshell.entity_instance, None]: related_element = ifc.get_entity(related_obj) + assert related_element container = nest.get_container(related_element) + # TODO: this branch is never used? if not relating_obj: relating_element = nest.get_relating_object(related_element) if related_element: relating_obj = ifc.get_object(relating_element) - if relating_obj: + else: ifc.run("nest.unassign_object", related_objects=[related_element]) if container: ifc.run("spatial.assign_container", products=[related_element], relating_structure=container) @@ -62,21 +76,28 @@ def unassign_object(ifc, nest, collector, relating_obj=None, related_obj=None): collector.assign(related_obj) -def add_part_to_object(ifc, nest, collector, blender, obj, part_class, part_name=None): +def add_part_to_object( + ifc: tool.Ifc, + nest: tool.Nest, + collector: tool.Collector, + blender: tool.Blender, + obj: bpy.types.Object, + part_class: str, + part_name: str, +) -> None: 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, -): +) -> None: if nest.get_nest_mode(): disable_nest_mode(nest) nest.enable_nest_mode(obj) -def disable_nest_mode(nest: tool.Nest): +def disable_nest_mode(nest: tool.Nest) -> None: nest.disable_nest_mode() diff --git a/src/bonsai/bonsai/tool/aggregate.py b/src/bonsai/bonsai/tool/aggregate.py index d3c0589cea..e2fb4df5f9 100644 --- a/src/bonsai/bonsai/tool/aggregate.py +++ b/src/bonsai/bonsai/tool/aggregate.py @@ -16,15 +16,27 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . +from __future__ import annotations import bpy import bonsai.core.tool import bonsai.tool as tool import ifcopenshell.util.element import ifcopenshell.util.representation -from typing import Union +from typing import Union, TYPE_CHECKING, Literal + +if TYPE_CHECKING: + from bonsai.bim.module.aggregate.prop import BIMAggregateProperties, BIMObjectAggregateProperties class Aggregate(bonsai.core.tool.Aggregate): + @classmethod + def get_aggregate_props(cls) -> BIMAggregateProperties: + return bpy.context.scene.BIMAggregateProperties + + @classmethod + def get_object_aggregate_props(cls, obj: bpy.types.Object) -> BIMObjectAggregateProperties: + return obj.BIMObjectAggregateProperties + @classmethod def can_aggregate(cls, relating_obj: bpy.types.Object, related_obj: bpy.types.Object) -> bool: relating_object = tool.Ifc.get_entity(relating_obj) @@ -56,11 +68,13 @@ class Aggregate(bonsai.core.tool.Aggregate): @classmethod def disable_editing(cls, obj: bpy.types.Object) -> None: - obj.BIMObjectAggregateProperties.is_editing = False + props = cls.get_object_aggregate_props(obj) + props.is_editing = False @classmethod def enable_editing(cls, obj: bpy.types.Object) -> None: - obj.BIMObjectAggregateProperties.is_editing = True + props = cls.get_object_aggregate_props(obj) + props.is_editing = True @classmethod def get_container(cls, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: @@ -86,13 +100,14 @@ class Aggregate(bonsai.core.tool.Aggregate): return parts @classmethod - def get_aggregate_mode(cls): - return bpy.context.scene.BIMAggregateProperties.in_aggregate_mode + def get_aggregate_mode(cls) -> bool: + props = cls.get_aggregate_props() + return props.in_aggregate_mode @classmethod - def enable_aggregate_mode(cls, active_object: bpy.types.Object): + def enable_aggregate_mode(cls, active_object: bpy.types.Object) -> set[Literal["FINISHED"]]: context = bpy.context - props = context.scene.BIMAggregateProperties + props = cls.get_aggregate_props() element = tool.Ifc.get_entity(active_object) if not element: @@ -131,7 +146,7 @@ class Aggregate(bonsai.core.tool.Aggregate): @classmethod def disable_aggregate_mode(cls): context = bpy.context - props = context.scene.BIMAggregateProperties + props = cls.get_aggregate_props() for obj_prop in props.not_editing_objects: obj = obj_prop.obj if not obj: diff --git a/src/bonsai/bonsai/tool/nest.py b/src/bonsai/bonsai/tool/nest.py index a4f0dc20ec..094299c499 100644 --- a/src/bonsai/bonsai/tool/nest.py +++ b/src/bonsai/bonsai/tool/nest.py @@ -16,13 +16,26 @@ # You should have received a copy of the GNU General Public License # along with Bonsai. If not, see . +from __future__ import annotations import bpy import bonsai.core.tool import bonsai.tool as tool import ifcopenshell.util.element +from typing import TYPE_CHECKING, Union + +if TYPE_CHECKING: + from bonsai.bim.module.nest.prop import BIMNestProperties, BIMObjectNestProperties class Nest(bonsai.core.tool.Nest): + @classmethod + def get_nest_props(cls) -> BIMNestProperties: + return bpy.context.scene.BIMNestProperties + + @classmethod + def get_object_nest_props(cls, obj: bpy.types.Object) -> BIMObjectNestProperties: + return obj.BIMObjectNestProperties + @classmethod def can_nest(cls, relating_obj, related_obj): relating_object = tool.Ifc.get_entity(relating_obj) @@ -34,19 +47,23 @@ class Nest(bonsai.core.tool.Nest): return False @classmethod - def disable_editing(cls, obj): - obj.BIMObjectNestProperties.is_editing = False + def disable_editing(cls, obj: bpy.types.Object) -> None: + props = cls.get_object_nest_props(obj) + props.is_editing = False @classmethod - def enable_editing(cls, obj): - obj.BIMObjectNestProperties.is_editing = True + def enable_editing(cls, obj: bpy.types.Object) -> None: + props = cls.get_object_nest_props(obj) + props.is_editing = True @classmethod - def get_container(cls, element): + def get_container(cls, element: ifcopenshell.entity_instance) -> Union[ifcopenshell.entity_instance, None]: return ifcopenshell.util.element.get_container(element) @classmethod - def get_relating_object(cls, related_element): + def get_relating_object( + cls, related_element: ifcopenshell.entity_instance + ) -> Union[ifcopenshell.entity_instance, None]: return ifcopenshell.util.element.get_nest(related_element) @classmethod @@ -61,13 +78,14 @@ class Nest(bonsai.core.tool.Nest): return components @classmethod - def get_nest_mode(cls): - return bpy.context.scene.BIMNestProperties.in_nest_mode + def get_nest_mode(cls) -> bool: + props = cls.get_nest_props() + return props.in_nest_mode @classmethod def enable_nest_mode(cls, active_object: bpy.types.Object): context = bpy.context - props = context.scene.BIMNestProperties + props = cls.get_nest_props() element = tool.Ifc.get_entity(active_object) if not element: diff --git a/src/bonsai/bonsai/tool/root.py b/src/bonsai/bonsai/tool/root.py index 7ce135b730..bb6aa116c0 100644 --- a/src/bonsai/bonsai/tool/root.py +++ b/src/bonsai/bonsai/tool/root.py @@ -238,13 +238,13 @@ class Root(bonsai.core.tool.Root): @classmethod def is_in_aggregate_mode(cls, element: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance: - props = bpy.context.scene.BIMAggregateProperties + props = tool.Aggregate.get_aggregate_props() 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 + props = tool.Nest.get_nest_props() if props.editing_nest and props.in_nest_mode: return tool.Ifc.get_entity(props.editing_nest) diff --git a/src/bonsai/test/tool/test_aggregate.py b/src/bonsai/test/tool/test_aggregate.py index 0f58f617eb..94130a5661 100644 --- a/src/bonsai/test/tool/test_aggregate.py +++ b/src/bonsai/test/tool/test_aggregate.py @@ -122,14 +122,16 @@ class TestDisableEditing(NewFile): obj = bpy.data.objects.new("Object", None) subject.enable_editing(obj) subject.disable_editing(obj) - assert obj.BIMObjectAggregateProperties.is_editing is False + props = tool.Aggregate.get_object_aggregate_props(obj) + assert props.is_editing is False class TestEnableEditing(NewFile): def test_run(self): obj = bpy.data.objects.new("Object", None) subject.enable_editing(obj) - assert obj.BIMObjectAggregateProperties.is_editing is True + props = tool.Aggregate.get_object_aggregate_props(obj) + assert props.is_editing is True class TestGetContainer(NewFile): diff --git a/src/bonsai/test/tool/test_nest.py b/src/bonsai/test/tool/test_nest.py index c68f5ae692..c9499e40de 100644 --- a/src/bonsai/test/tool/test_nest.py +++ b/src/bonsai/test/tool/test_nest.py @@ -54,14 +54,16 @@ class TestDisableEditing(NewFile): obj = bpy.data.objects.new("Object", None) subject.enable_editing(obj) subject.disable_editing(obj) - assert obj.BIMObjectNestProperties.is_editing is False + props = tool.Nest.get_object_nest_props(obj) + assert props.is_editing is False class TestEnableEditing(NewFile): def test_run(self): obj = bpy.data.objects.new("Object", None) subject.enable_editing(obj) - assert obj.BIMObjectNestProperties.is_editing is True + props = tool.Nest.get_object_nest_props(obj) + assert props.is_editing is True class TestGetContainer(NewFile): diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py index 8244c3e434..d6d729d8aa 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py @@ -112,15 +112,15 @@ class Usecase: ifc_vertices: list[ifcopenshell.entity_instance] coordinate_offset: Union[npt.NDArray[np.float64], None] geometry: Union[bpy.types.Mesh, bpy.types.Curve] + blender_object: bpy.types.Object def execute(self) -> Union[ifcopenshell.entity_instance, None]: self.is_manifold = None self.coordinate_offset = self.settings["coordinate_offset"] self.geometry = self.settings["geometry"] - if ( - isinstance(self.settings["geometry"], bpy.types.Mesh) - and self.settings["geometry"] == self.settings["blender_object"].data - ): + self.blender_object = self.settings["blender_object"] + + if isinstance(self.geometry, bpy.types.Mesh) and self.geometry == self.blender_object.data: self.evaluate_geometry() if self.settings["unit_scale"] is None: self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file) @@ -147,11 +147,11 @@ class Usecase: return any([abs((tM @ v.co).z) > threshold for v in face.verts]) def evaluate_geometry(self) -> None: - for modifier in self.settings["blender_object"].modifiers: + for modifier in self.blender_object.modifiers: if modifier.type == "BOOLEAN": modifier.show_viewport = False - mesh = self.settings["blender_object"].evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh() + mesh = self.blender_object.evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh() bm = bmesh.new() bm.from_mesh(mesh) @@ -173,7 +173,7 @@ class Usecase: self.settings["geometry"] = mesh - for modifier in self.settings["blender_object"].modifiers: + for modifier in self.blender_object.modifiers: if modifier.type == "BOOLEAN": modifier.show_viewport = True @@ -540,7 +540,7 @@ class Usecase: # create dummy object that will have more detailed curves # since now we do not really support splines curves natively - obj = self.settings["blender_object"] + obj = self.blender_object dummy = bpy.data.objects.new("Dummy", obj.data.copy()) bpy.context.scene.collection.objects.link(dummy) tool.Blender.select_and_activate_single_object(bpy.context, dummy) @@ -752,7 +752,7 @@ class Usecase: profile_def, position, self.file.createIfcDirection((0.0, 0.0, 1.0)), - self.convert_si_to_unit(self.settings["blender_object"].dimensions[2]), + self.convert_si_to_unit(self.blender_object.dimensions[2]), ) return self.file.createIfcShapeRepresentation( self.settings["context"], @@ -952,7 +952,7 @@ class Usecase: ) def create_box_representation(self) -> ifcopenshell.entity_instance: - obj = self.settings["blender_object"] + obj = self.blender_object bounding_box = self.file.createIfcBoundingBox( self.create_cartesian_point(obj.bound_box[0][0], obj.bound_box[0][1], obj.bound_box[0][2]), self.convert_si_to_unit(obj.dimensions[0]),