From 4c0a7e1ca9ba7fa6307605edeb66745c84259655 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 10 Feb 2025 13:31:50 +0500 Subject: [PATCH] typing --- src/bonsai/bonsai/bim/handler.py | 6 +- .../bonsai/bim/module/boundary/operator.py | 14 ++- .../bonsai/bim/module/covering/workspace.py | 2 +- .../bonsai/bim/module/drawing/annotation.py | 10 +- src/bonsai/bonsai/bim/module/drawing/data.py | 4 +- .../bonsai/bim/module/drawing/decoration.py | 2 +- .../bonsai/bim/module/drawing/helper.py | 2 +- .../bonsai/bim/module/drawing/operator.py | 28 ++++-- src/bonsai/bonsai/bim/module/drawing/prop.py | 17 +++- src/bonsai/bonsai/bim/module/drawing/ui.py | 3 +- .../bonsai/bim/module/drawing/workspace.py | 12 +-- .../bonsai/bim/module/geometry/operator.py | 4 +- src/bonsai/bonsai/bim/module/model/data.py | 14 ++- .../bonsai/bim/module/model/decorator.py | 2 +- src/bonsai/bonsai/bim/module/model/door.py | 25 +++-- src/bonsai/bonsai/bim/module/model/opening.py | 17 ++-- .../bonsai/bim/module/model/polyline.py | 22 +++-- src/bonsai/bonsai/bim/module/model/product.py | 15 +-- src/bonsai/bonsai/bim/module/model/profile.py | 8 +- src/bonsai/bonsai/bim/module/model/prop.py | 97 ++++++++++++++++--- src/bonsai/bonsai/bim/module/model/slab.py | 20 ++-- src/bonsai/bonsai/bim/module/model/ui.py | 12 ++- src/bonsai/bonsai/bim/module/model/wall.py | 20 ++-- src/bonsai/bonsai/bim/module/model/window.py | 5 +- .../bonsai/bim/module/model/workspace.py | 12 +-- src/bonsai/bonsai/bim/module/root/operator.py | 2 +- .../bonsai/bim/module/spatial/workspace.py | 2 +- src/bonsai/bonsai/bim/module/type/operator.py | 8 +- src/bonsai/bonsai/bim/ui.py | 11 ++- src/bonsai/bonsai/tool/blender.py | 3 +- src/bonsai/bonsai/tool/covering.py | 2 +- src/bonsai/bonsai/tool/drawing.py | 30 ++++-- src/bonsai/bonsai/tool/model.py | 22 ++++- src/bonsai/bonsai/tool/project.py | 2 +- src/bonsai/bonsai/tool/root.py | 3 +- src/bonsai/bonsai/tool/spatial.py | 8 +- src/bonsai/test/bim/test_feature.py | 9 +- src/bonsai/test/tool/test_drawing.py | 47 +++++---- src/bonsai/test/tool/test_model.py | 11 ++- src/bonsai/test/tool/test_project.py | 2 +- src/bonsai/test/tool/test_root.py | 2 +- 41 files changed, 348 insertions(+), 189 deletions(-) diff --git a/src/bonsai/bonsai/bim/handler.py b/src/bonsai/bonsai/bim/handler.py index d9ed21b005..16a2cdc636 100644 --- a/src/bonsai/bonsai/bim/handler.py +++ b/src/bonsai/bonsai/bim/handler.py @@ -117,7 +117,7 @@ def update_bim_tool_props(): if not representation: return - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() if element.is_a("IfcElementType") or element.is_a("IfcElement"): element_type = ifcopenshell.util.element.get_type(element) if element_type: @@ -130,7 +130,7 @@ def update_bim_tool_props(): if not extrusion: return - def get_x_angle(extrusion): + def get_x_angle(extrusion: ifcopenshell.entity_instance) -> float: x, y, z = extrusion.ExtrudedDirection.DirectionRatios x_angle = Vector((0, 1)).angle_signed(Vector((y, z))) return x_angle @@ -332,7 +332,7 @@ def load_post(scene): georeference_props = bpy.context.scene.BIMGeoreferenceProperties aggregate_props = bpy.context.scene.BIMAggregateProperties nest_props = bpy.context.scene.BIMNestProperties - model_props = bpy.context.scene.BIMModelProperties + model_props = tool.Model.get_model_props() if georeference_props.should_visualise: GeoreferenceDecorator.install(bpy.context) if aggregate_props.aggregate_decorator: diff --git a/src/bonsai/bonsai/bim/module/boundary/operator.py b/src/bonsai/bonsai/bim/module/boundary/operator.py index b2d08797cc..e3bc51683c 100644 --- a/src/bonsai/bonsai/bim/module/boundary/operator.py +++ b/src/bonsai/bonsai/bim/module/boundary/operator.py @@ -660,6 +660,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator): obj.select_set(True) def auto_generate_boundaries(self, space, space_obj): + props = tool.Model.get_model_props() # Identify all potential building elements # TODO: don't select everything, use AABB culling in Blender building_elements = ( @@ -775,9 +776,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator): # we cheat by using the exterior boundary to mean "gross". exterior_boundary_polygon = shapely.Polygon(gross_boundary_polygon.exterior.coords) - parent_boundary = tool.Ifc.run( - "root.create_entity", ifc_class=bpy.context.scene.BIMModelProperties.boundary_class - ) + parent_boundary = tool.Ifc.run("root.create_entity", ifc_class=props.boundary_class) if building_element.is_a("IfcVirtualElement"): parent_boundary.PhysicalOrVirtualBoundary = "VIRTUAL" else: @@ -854,9 +853,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator): if opening_polygon.intersection(exterior_boundary_polygon).area == 0: continue - boundary = tool.Ifc.run( - "root.create_entity", ifc_class=bpy.context.scene.BIMModelProperties.boundary_class - ) + boundary = tool.Ifc.run("root.create_entity", ifc_class=props.boundary_class) boundary.RelatingSpace = space boundary.RelatedBuildingElement = filling or opening boundary.ConnectionGeometry = self.create_connection_geometry_from_polygon( @@ -877,6 +874,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator): if not relating_space or not related_building_element: return + props = tool.Model.get_model_props() # Find which face on space should be bounded to related building element # TODO: Handle round wall where multiple faces need to be bound to the same related building element bm = bmesh.new() @@ -924,7 +922,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator): ): return - parent_boundary = tool.Ifc.run("root.create_entity", ifc_class=context.scene.BIMModelProperties.boundary_class) + parent_boundary = tool.Ifc.run("root.create_entity", ifc_class=props.boundary_class) parent_boundary.PhysicalOrVirtualBoundary = "PHYSICAL" # Set to EXTERNAL by default and turn later to internal if there is a corresponding boundary relating to an # internal space @@ -959,7 +957,7 @@ class AddBoundary(bpy.types.Operator, tool.Ifc.Operator): continue connection_geometry = self.create_connection_geometry_from_polygon(opening_polygon, target_face_matrix) - boundary = tool.Ifc.run("root.create_entity", ifc_class=context.scene.BIMModelProperties.boundary_class) + boundary = tool.Ifc.run("root.create_entity", ifc_class=props.boundary_class) boundary.RelatingSpace = relating_space boundary.RelatedBuildingElement = filling boundary.ConnectionGeometry = connection_geometry diff --git a/src/bonsai/bonsai/bim/module/covering/workspace.py b/src/bonsai/bonsai/bim/module/covering/workspace.py index fe044e566f..383a2322d9 100644 --- a/src/bonsai/bonsai/bim/module/covering/workspace.py +++ b/src/bonsai/bonsai/bim/module/covering/workspace.py @@ -54,7 +54,7 @@ class CoveringToolUI: @classmethod def draw(cls, context, layout, ifc_element_type=None): cls.layout = layout - cls.props = context.scene.BIMModelProperties + cls.props = tool.Model.get_model_props() cls.covering_props = context.scene.BIMCoveringProperties row = cls.layout.row(align=True) diff --git a/src/bonsai/bonsai/bim/module/drawing/annotation.py b/src/bonsai/bonsai/bim/module/drawing/annotation.py index f2946b623b..8bc9ee46d5 100644 --- a/src/bonsai/bonsai/bim/module/drawing/annotation.py +++ b/src/bonsai/bonsai/bim/module/drawing/annotation.py @@ -37,7 +37,7 @@ class Annotator: if related_element is None: location, _, _, _ = Annotator.get_placeholder_coords(bpy.context) else: - obj.data.BIMAssignedProductProperties.related_element = related_element + curve.BIMAssignedProductProperties.related_element = related_element location = related_element.location obj.location = location obj.hide_render = True @@ -47,8 +47,9 @@ class Annotator: tool.Blender.get_data_dir_path(Path("fonts") / "OpenGost Type B TT.ttf").__str__() ) font.name = "OpenGost Type B TT" - obj.data.font = font - obj.data.BIMTextProperties.font_size = "2.5" + curve.font = font + props = tool.Drawing.get_text_props(obj) + props.font_size = "2.5" collection = bpy.context.scene.camera.BIMObjectProperties.collection collection.objects.link(obj) Annotator.resize_text(obj) @@ -66,7 +67,8 @@ class Annotator: return # This is a magic number for OpenGost font_size = 1.6 / 1000 - font_size *= float(text_obj.data.BIMTextProperties.font_size) + props = tool.Drawing.get_text_props(text_obj) + font_size *= float(props.font_size) font_size /= tool.Drawing.get_scale_ratio(tool.Drawing.get_diagram_scale(camera)["Scale"]) diff --git a/src/bonsai/bonsai/bim/module/drawing/data.py b/src/bonsai/bonsai/bim/module/drawing/data.py index 53108485c9..be252a29c9 100644 --- a/src/bonsai/bonsai/bim/module/drawing/data.py +++ b/src/bonsai/bonsai/bim/module/drawing/data.py @@ -303,7 +303,7 @@ class DecoratorData: if not element or not tool.Drawing.is_annotation_object_type(element, ["TEXT", "TEXT_LEADER"]): return None - props = obj.BIMTextProperties + props = tool.Drawing.get_text_props(obj) # getting font size pset_data = ifcopenshell.util.element.get_pset(element, "EPset_Annotation") or {} # use `regular` as default @@ -399,7 +399,7 @@ class AnnotationData: @classmethod def load(cls): cls.is_loaded = True - cls.props = bpy.context.scene.BIMAnnotationProperties + cls.props = tool.Drawing.get_annotation_props() cls.data["relating_type_id"] = cls.relating_type_id() cls.data["relating_types"] = cls.relating_types() diff --git a/src/bonsai/bonsai/bim/module/drawing/decoration.py b/src/bonsai/bonsai/bim/module/drawing/decoration.py index f392fcbb47..e8fba1914d 100644 --- a/src/bonsai/bonsai/bim/module/drawing/decoration.py +++ b/src/bonsai/bonsai/bim/module/drawing/decoration.py @@ -589,7 +589,7 @@ class BaseDecorator: text_dir = self.get_annotation_direction(context, obj) pos = location_3d_to_region_2d(region, region3d, text_world_position) - props = obj.BIMTextProperties + props = tool.Drawing.get_text_props(obj) text_data = DecoratorData.get_ifc_text_data(obj) if props.is_editing: text_data = text_data | props.get_text_edited_data() diff --git a/src/bonsai/bonsai/bim/module/drawing/helper.py b/src/bonsai/bonsai/bim/module/drawing/helper.py index ec6e261886..5bd1661bfa 100644 --- a/src/bonsai/bonsai/bim/module/drawing/helper.py +++ b/src/bonsai/bonsai/bim/module/drawing/helper.py @@ -416,7 +416,7 @@ def elevate_segment(bounds, segm): return [Vector((x, ymin, zmin)), Vector((x, ymax, zmin))] -def add_newline_between_words(text, newline_at): +def add_newline_between_words(text: str, newline_at: int) -> str: result = [] start = 0 diff --git a/src/bonsai/bonsai/bim/module/drawing/operator.py b/src/bonsai/bonsai/bim/module/drawing/operator.py index 1bfee506a4..950d701e4a 100644 --- a/src/bonsai/bonsai/bim/module/drawing/operator.py +++ b/src/bonsai/bonsai/bim/module/drawing/operator.py @@ -84,7 +84,7 @@ class AddAnnotationType(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - props = context.scene.BIMAnnotationProperties + props = tool.Drawing.get_annotation_props() object_type = props.object_type has_representation = props.create_representation_for_type drawing = tool.Ifc.get_entity(bpy.context.scene.camera) @@ -115,7 +115,7 @@ class EnableAddAnnotationType(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - bpy.context.scene.BIMAnnotationProperties.is_adding_type = True + tool.Drawing.get_annotation_props().is_adding_type = True class DisableAddAnnotationType(bpy.types.Operator, tool.Ifc.Operator): @@ -124,7 +124,7 @@ class DisableAddAnnotationType(bpy.types.Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - bpy.context.scene.BIMAnnotationProperties.is_adding_type = False + tool.Drawing.get_annotation_props().is_adding_type = False class AddDrawing(bpy.types.Operator, tool.Ifc.Operator): @@ -1433,7 +1433,7 @@ class AddAnnotation(bpy.types.Operator, tool.Ifc.Operator): return operator.description or "" def _execute(self, context): - props = bpy.context.scene.BIMAnnotationProperties + props = tool.Drawing.get_annotation_props() if not (drawing := tool.Ifc.get_entity(context.scene.camera)): self.report({"WARNING"}, "Not a BIM camera") return @@ -2725,7 +2725,9 @@ class EditTextPopup(bpy.types.Operator): # need to keep them in sync or move to some common function # NOTE: that `popup_active_attribute` is used here when it's not used in `BIM_PT_text.draw()` - props = context.active_object.BIMTextProperties + obj = context.active_object + assert obj + props = tool.Drawing.get_text_props(obj) row = self.layout.row(align=True) row.operator("bim.add_text_literal", icon="ADD", text="Add Literal") @@ -2826,9 +2828,11 @@ class AddTextLiteral(bpy.types.Operator): def execute(self, context): obj = context.active_object + assert obj # similar to `tool.Drawing.import_text_attributes` - literal_props = obj.BIMTextProperties.literals.add() + props = tool.Drawing.get_text_props(obj) + literal_props = props.literals.add() literal_attributes = literal_props.attributes literal_attr_values = { "Literal": "Literal", @@ -2865,7 +2869,9 @@ class RemoveTextLiteral(bpy.types.Operator): def execute(self, context): obj = context.active_object - obj.BIMTextProperties.literals.remove(self.literal_prop_id) + assert obj + props = tool.Drawing.get_text_props(obj) + props.literals.remove(self.literal_prop_id) return {"FINISHED"} @@ -2880,7 +2886,9 @@ class OrderTextLiteralUp(bpy.types.Operator): def execute(self, context): obj = context.active_object - obj.BIMTextProperties.literals.move(self.literal_prop_id, self.literal_prop_id - 1) + assert obj + props = tool.Drawing.get_text_props(obj) + props.literals.move(self.literal_prop_id, self.literal_prop_id - 1) return {"FINISHED"} @@ -2895,7 +2903,9 @@ class OrderTextLiteralDown(bpy.types.Operator): def execute(self, context): obj = context.active_object - obj.BIMTextProperties.literals.move(self.literal_prop_id, self.literal_prop_id + 1) + assert obj + props = tool.Drawing.get_text_props(obj) + props.literals.move(self.literal_prop_id, self.literal_prop_id + 1) return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/module/drawing/prop.py b/src/bonsai/bonsai/bim/module/drawing/prop.py index 5fc5769c30..02ce195100 100644 --- a/src/bonsai/bonsai/bim/module/drawing/prop.py +++ b/src/bonsai/bonsai/bim/module/drawing/prop.py @@ -44,7 +44,7 @@ from bpy.props import ( CollectionProperty, BoolVectorProperty, ) -from typing import TYPE_CHECKING, Literal +from typing import TYPE_CHECKING, Literal, Any diagram_scales_enum = [] @@ -613,7 +613,13 @@ class BIMTextProperties(PropertyGroup): ) newline_at: IntProperty(name="Newline At") - def get_text_edited_data(self): + if TYPE_CHECKING: + is_editing: bool + literals: bpy.types.bpy_prop_collection_idprop[LiteralProps] + font_size: str + newline_at: int + + def get_text_edited_data(self) -> dict[str, Any]: """should be called only if `is_editing` otherwise should use `DecoratorData.get_ifc_text_data(obj)` instead because this data could be out of date @@ -676,3 +682,10 @@ class BIMAnnotationProperties(PropertyGroup): ) is_adding_type: bpy.props.BoolProperty(default=False) type_name: bpy.props.StringProperty(name="Name", default="TYPEX") + + if TYPE_CHECKING: + object_type: str + relating_type_id: str + create_representation_for_type: bool + is_adding_type: bool + type_name: str diff --git a/src/bonsai/bonsai/bim/module/drawing/ui.py b/src/bonsai/bonsai/bim/module/drawing/ui.py index 2d75bcb927..f35fc55325 100644 --- a/src/bonsai/bonsai/bim/module/drawing/ui.py +++ b/src/bonsai/bonsai/bim/module/drawing/ui.py @@ -520,7 +520,8 @@ class BIM_PT_text(Panel): def draw(self, context): obj = context.active_object - props = obj.BIMTextProperties + assert obj + props = tool.Drawing.get_text_props(obj) if props.is_editing: # shares most of the code with EditTextPopup.draw() diff --git a/src/bonsai/bonsai/bim/module/drawing/workspace.py b/src/bonsai/bonsai/bim/module/drawing/workspace.py index 0c641fadb8..30d57bf3db 100644 --- a/src/bonsai/bonsai/bim/module/drawing/workspace.py +++ b/src/bonsai/bonsai/bim/module/drawing/workspace.py @@ -45,7 +45,7 @@ class LaunchAnnotationTypeManager(bpy.types.Operator): if not AnnotationData.is_loaded: AnnotationData.load() - props = context.scene.BIMAnnotationProperties + props = tool.Drawing.get_annotation_props() columns = self.layout.column_flow(columns=3) row = columns.row() @@ -155,7 +155,7 @@ def add_layout_hotkey_operator( # TODO: move to operator def create_annotation_occurrence(context): - props = context.scene.BIMAnnotationProperties + props = tool.Drawing.get_annotation_props() relating_type = tool.Ifc.get().by_id(int(props.relating_type_id)) object_type = props.object_type @@ -200,7 +200,7 @@ class AnnotationToolUI: @classmethod def draw(cls, context, layout): cls.layout = layout - cls.props = context.scene.BIMAnnotationProperties + cls.props = tool.Drawing.get_annotation_props() row = cls.layout.row(align=True) if not tool.Ifc.get(): @@ -279,19 +279,19 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator): self.report({"ERROR"}, "No drawing active for annotation hotkeys.") return {"CANCELLED"} - self.props = context.scene.BIMAnnotationProperties + self.props = tool.Drawing.get_annotation_props() getattr(self, f"hotkey_{self.hotkey}")() def invoke(self, context, event): # https://blender.stackexchange.com/questions/276035/how-do-i-make-operators-remember-their-property-values-when-called-from-a-hotkey - self.props = context.scene.BIMAnnotationProperties + self.props = tool.Drawing.get_annotation_props() return self.execute(context) def draw(self, context): pass def hotkey_S_T(self): - props = bpy.context.scene.BIMAnnotationProperties + props = tool.Drawing.get_annotation_props() object_type = props.object_type if object_type not in tool.Drawing.ANNOTATION_TYPES_SUPPORT_SETUP: diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py index 62b0e5dae6..239b8e3fc0 100644 --- a/src/bonsai/bonsai/bim/module/geometry/operator.py +++ b/src/bonsai/bonsai/bim/module/geometry/operator.py @@ -743,7 +743,7 @@ class OverrideDelete(bpy.types.Operator): else: bpy.data.objects.remove(obj) - for opening in context.scene.BIMModelProperties.openings: + for opening in tool.Model.get_model_props().openings: if opening.obj is not None and not tool.Ifc.get_entity(opening.obj): bpy.data.objects.remove(opening.obj) tool.Model.purge_scene_openings() @@ -1874,7 +1874,7 @@ class OverrideEscape(bpy.types.Operator): elif context.scene.BIMGeometryProperties.mode == "EDIT": bpy.ops.bim.override_mode_set_object("INVOKE_DEFAULT", should_save=False) tool.Geometry.disable_item_mode() - elif context.scene.BIMModelProperties.openings: + elif tool.Model.get_model_props().openings: bpy.ops.bim.hide_all_openings() elif context.scene.BIMAggregateProperties.in_aggregate_mode: bpy.ops.bim.disable_aggregate_mode() diff --git a/src/bonsai/bonsai/bim/module/model/data.py b/src/bonsai/bonsai/bim/module/model/data.py index e9e0160615..813aea1d8a 100644 --- a/src/bonsai/bonsai/bim/module/model/data.py +++ b/src/bonsai/bonsai/bim/module/model/data.py @@ -51,7 +51,7 @@ class AuthoringData: @classmethod def load(cls, ifc_element_type=None): cls.is_loaded = True - cls.props = bpy.context.scene.BIMModelProperties + cls.props = tool.Model.get_model_props() if ifc_element_type: cls.ifc_element_type = None if ifc_element_type == "all" else ifc_element_type cls.data["default_container"] = cls.default_container() @@ -535,7 +535,9 @@ class DoorData: @classmethod def general_params(cls): - props = bpy.context.active_object.BIMDoorProperties + obj = bpy.context.active_object + assert obj + props = tool.Model.get_door_props(obj) data = cls.data["pset_data"]["data_dict"] general_params = {} general_props = props.get_general_kwargs() @@ -546,7 +548,9 @@ class DoorData: @classmethod def lining_params(cls): - props = bpy.context.active_object.BIMDoorProperties + obj = bpy.context.active_object + assert obj + props = tool.Model.get_door_props(obj) data = cls.data["pset_data"]["data_dict"] lining_data = data["lining_properties"] lining_params = {} @@ -558,7 +562,9 @@ class DoorData: @classmethod def panel_params(cls): - props = bpy.context.active_object.BIMDoorProperties + obj = bpy.context.active_object + assert obj + props = tool.Model.get_door_props(obj) data = cls.data["pset_data"]["data_dict"] panel_data = cls.data["pset_data"]["data_dict"]["panel_properties"] panel_params = {} diff --git a/src/bonsai/bonsai/bim/module/model/decorator.py b/src/bonsai/bonsai/bim/module/model/decorator.py index d4311be29a..126dad57ad 100644 --- a/src/bonsai/bonsai/bim/module/model/decorator.py +++ b/src/bonsai/bonsai/bim/module/model/decorator.py @@ -829,7 +829,7 @@ class ProductDecorator: polyline_points = polyline_data[0].polyline_points if polyline_data else [] self.relating_type = None - props = context.scene.BIMModelProperties + props = tool.Model.get_model_props() relating_type_id = props.relating_type_id if relating_type_id: self.relating_type = tool.Ifc.get().by_id(int(relating_type_id)) diff --git a/src/bonsai/bonsai/bim/module/model/door.py b/src/bonsai/bonsai/bim/module/model/door.py index e2bb50d9c9..5b1eaee39b 100644 --- a/src/bonsai/bonsai/bim/module/model/door.py +++ b/src/bonsai/bonsai/bim/module/model/door.py @@ -41,7 +41,7 @@ V_ = tool.Blender.V_ def update_door_modifier_representation(obj: bpy.types.Object) -> None: - props = obj.BIMDoorProperties + props = tool.Model.get_door_props(obj) element = tool.Ifc.get_entity(obj) ifc_file = tool.Ifc.get() sliding_door = "SLIDING" in props.door_type @@ -338,7 +338,8 @@ def create_bm_door_lining( def update_door_modifier_bmesh(context: bpy.types.Context) -> None: obj = context.active_object - props = obj.BIMDoorProperties + assert obj + props = tool.Model.get_door_props(obj) overall_width = props.overall_width overall_height = props.overall_height @@ -572,9 +573,10 @@ class AddDoor(bpy.types.Operator, tool.Ifc.Operator): bl_description = "Add a Parametric Door to the Selected IFC Door Elements" bl_options = {"REGISTER", "UNDO"} - def add_door_on_object(self, obj): + def add_door_on_object(self, obj: bpy.types.Object) -> None: element = tool.Ifc.get_entity(obj) - props = obj.BIMDoorProperties + assert element + props = tool.Model.get_door_props(obj) door_data = props.get_general_kwargs(convert_to_project_units=True) lining_props = props.get_lining_kwargs(convert_to_project_units=True) @@ -608,11 +610,12 @@ class CancelEditingDoor(bpy.types.Operator, tool.Ifc.Operator): bl_label = "Cancel Editing Door on Selected Objects" bl_options = {"REGISTER", "UNDO"} - def cancel_editing_door_on_object(self, obj): + def cancel_editing_door_on_object(self, obj: bpy.types.Object) -> None: element = tool.Ifc.get_entity(obj) + assert element if not tool.Blender.Modifier.is_door(element): return - props = obj.BIMDoorProperties + props = tool.Model.get_door_props(obj) data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Door", "Data")) data.update(data.pop("lining_properties")) data.update(data.pop("panel_properties")) @@ -646,9 +649,10 @@ class FinishEditingDoor(bpy.types.Operator, tool.Ifc.Operator): def finish_editing_door_on_object(self, obj): element = tool.Ifc.get_entity(obj) + assert element if not tool.Blender.Modifier.is_door(element): return - props = obj.BIMDoorProperties + props = tool.Model.get_door_props(obj) door_data = props.get_general_kwargs(convert_to_project_units=True) lining_props = props.get_lining_kwargs(convert_to_project_units=True) @@ -678,9 +682,10 @@ class EnableEditingDoor(bpy.types.Operator, tool.Ifc.Operator): def edit_door_on_obj(self, obj): element = tool.Ifc.get_entity(obj) + assert element if not tool.Blender.Modifier.is_door(element): return - props = obj.BIMDoorProperties + props = tool.Model.get_door_props(obj) data = json.loads(ifcopenshell.util.element.get_pset(element, "BBIM_Door", "Data")) data.update(data.pop("lining_properties")) data.update(data.pop("panel_properties")) @@ -702,9 +707,11 @@ class RemoveDoor(bpy.types.Operator, tool.Ifc.Operator): def remove_door_on_object(self, obj): element = tool.Ifc.get_entity(obj) + assert element if not tool.Blender.Modifier.is_door(element): return - obj.BIMDoorProperties.is_editing = False + props = tool.Model.get_door_props(obj) + props.is_editing = False pset = tool.Pset.get_element_pset(element, "BBIM_Door") ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset) diff --git a/src/bonsai/bonsai/bim/module/model/opening.py b/src/bonsai/bonsai/bim/module/model/opening.py index 3f7975d6da..6d63050fc5 100644 --- a/src/bonsai/bonsai/bim/module/model/opening.py +++ b/src/bonsai/bonsai/bim/module/model/opening.py @@ -60,7 +60,7 @@ class FilledOpeningGenerator: :param target: Target opening position. If ommited, cursor position is used. :return: None if there was no errors, otherwise returns a string with error message. """ - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() opening_thickness_si = 0.0 filling = tool.Ifc.get_entity(filling_obj) @@ -531,7 +531,8 @@ class UpdateOpeningsFocus(Operator): return {"FINISHED"} openings = set() building_objects = set() - for opening in context.scene.BIMModelProperties.openings: + props = tool.Model.get_model_props() + for opening in props.openings: if opening.obj: openings.add(opening.obj) opening_element = tool.Ifc.get_entity(opening.obj) @@ -545,7 +546,7 @@ class UpdateOpeningsFocus(Operator): obj.color[2], ( 1 - if not context.scene.BIMModelProperties.openings + if not props.openings or not building_objects or obj in openings or obj in building_objects @@ -558,7 +559,8 @@ class UpdateOpeningsFocus(Operator): def hide_openings(context: bpy.types.Context, objects: Sequence[bpy.types.Object]) -> None: objects_to_remove = set() - for opening_prop in context.scene.BIMModelProperties.openings: + props = tool.Model.get_model_props() + for opening_prop in props.openings: opening_obj = opening_prop.obj if not opening_obj: continue @@ -623,7 +625,7 @@ class EditOpenings(Operator, tool.Ifc.Operator): def get_buildings_and_openings( self, context: bpy.types.Context ) -> tuple[set[bpy.types.Object], set[ifcopenshell.entity_instance]]: - props = context.scene.BIMModelProperties + props = tool.Model.get_model_props() building_objs: set[bpy.types.Object] = set() opening_elements: set[ifcopenshell.entity_instance] = set() objects_to_remove = set() @@ -892,7 +894,8 @@ class DecorationsHandler: batch.draw(shader) def __call__(self, context): - if not context.scene.BIMModelProperties.openings: + props = tool.Model.get_model_props() + if not props.openings: return self.addon_prefs = tool.Blender.get_addon_preferences() selected_elements_color = self.addon_prefs.decorator_color_selected @@ -907,7 +910,7 @@ class DecorationsHandler: gpu.state.point_size_set(6) gpu.state.blend_set("ALPHA") - for opening in context.scene.BIMModelProperties.openings: + for opening in props.openings: obj = opening.obj if context.scene.BIMGeometryProperties.representation_obj == obj: # We are editing the representation of the opening : diff --git a/src/bonsai/bonsai/bim/module/model/polyline.py b/src/bonsai/bonsai/bim/module/model/polyline.py index 8c59e2d182..eeb8adfe18 100644 --- a/src/bonsai/bonsai/bim/module/model/polyline.py +++ b/src/bonsai/bonsai/bim/module/model/polyline.py @@ -23,6 +23,7 @@ import math import bmesh import ifcopenshell import ifcopenshell.api +import ifcopenshell.geom import ifcopenshell.util.unit import ifcopenshell.util.element import ifcopenshell.util.placement @@ -40,7 +41,7 @@ from mathutils import Vector, Matrix, Quaternion from bonsai.bim.module.model.opening import FilledOpeningGenerator from bonsai.bim.module.model.decorator import PolylineDecorator from bonsai.bim.module.geometry.decorator import ItemDecorator -from typing import Optional, Union, Literal +from typing import Optional, Union, Literal, Any from lark import Lark, Transformer @@ -63,7 +64,7 @@ def create_bmesh_from_vertices(vertices, is_closed=False): def get_wall_preview_data(context, relating_type): # Get properties from object type - model_props = context.scene.BIMModelProperties + model_props = tool.Model.get_model_props() direction_sense = model_props.direction_sense direction = 1 if direction_sense == "NEGATIVE": @@ -190,9 +191,8 @@ def get_wall_preview_data(context, relating_type): def get_slab_preview_data(context, relating_type): - props = context.scene.BIMModelProperties - x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else props.x_angle - model_props = context.scene.BIMModelProperties + model_props = tool.Model.get_model_props() + x_angle = 0 if tool.Cad.is_x(model_props.x_angle, 0, tolerance=0.001) else model_props.x_angle direction_sense = model_props.direction_sense direction = 1 if direction_sense == "NEGATIVE": @@ -254,14 +254,16 @@ def get_slab_preview_data(context, relating_type): return data -def get_vertical_profile_preview_data(context, relating_type): +def get_vertical_profile_preview_data( + context: bpy.types.Context, relating_type: ifcopenshell.entity_instance +) -> dict[str, Any]: material = ifcopenshell.util.element.get_material(relating_type) try: profile = material.MaterialProfiles[0].Profile except: return {} - model_props = context.scene.BIMModelProperties + model_props = tool.Model.get_model_props() extrusion_depth = model_props.extrusion_depth cardinal_point = model_props.cardinal_point rot_mat = Quaternion() @@ -373,7 +375,7 @@ def get_horizontal_profile_preview_data(context, relating_type): except: return {} - model_props = context.scene.BIMModelProperties + model_props = tool.Model.get_model_props() cardinal_point = model_props.cardinal_point polyline_verts = [] @@ -497,7 +499,7 @@ def get_horizontal_profile_preview_data(context, relating_type): def get_generic_product_preview_data(context, relating_type): - model_props = context.scene.BIMModelProperties + props = tool.Model.get_model_props() if relating_type.is_a("IfcDoorType"): rl = float(model_props.rl1) elif relating_type.is_a("IfcWindowType"): @@ -914,7 +916,7 @@ class PolylineOperator: t.value_3d = tri def set_offset(self, context: bpy.types.Context, relating_type: ifcopenshell.entity_instance) -> None: - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() direction_sense = props.direction_sense if tool.Model.get_usage_type(relating_type) == "LAYER2": offset_type = "offset_type_vertical" diff --git a/src/bonsai/bonsai/bim/module/model/product.py b/src/bonsai/bonsai/bim/module/model/product.py index 306068cf42..7edf9323f7 100644 --- a/src/bonsai/bonsai/bim/module/model/product.py +++ b/src/bonsai/bonsai/bim/module/model/product.py @@ -200,7 +200,7 @@ class AddOccurrence(bpy.types.Operator, PolylineOperator): def modal(self, context, event): # Ensure state of BIM tool props is valid - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() relating_type_id = tool.Blender.get_enum_safe(props, "relating_type_id") relating_type_id_data = AuthoringData.data["relating_type_id"] if not relating_type_id and relating_type_id_data: @@ -284,7 +284,7 @@ class AddConstrTypeInstance(bpy.types.Operator, tool.Ifc.Operator): ) def invoke(self, context, event): - props = context.scene.BIMModelProperties + props = tool.Model.get_model_props() relating_type_id = self.relating_type_id or props.relating_type_id if ( relating_type_id @@ -303,7 +303,7 @@ class AddConstrTypeInstance(bpy.types.Operator, tool.Ifc.Operator): row.prop(self, "representation_template", text="") def _execute(self, context): - props = context.scene.BIMModelProperties + props = tool.Model.get_model_props() relating_type_id = self.relating_type_id or props.relating_type_id if not relating_type_id: @@ -507,7 +507,8 @@ class AddConstrTypeInstance(bpy.types.Operator, tool.Ifc.Operator): def set_flow_segment_rl(self, obj): if self.container_obj: - obj.location[2] = self.container_obj.location[2] + bpy.context.scene.BIMModelProperties.rl2 + props = tool.Model.get_model_props() + obj.location[2] = self.container_obj.location[2] + props.rl2 @staticmethod def generate_layered_element(ifc_class: str, relating_type: ifcopenshell.entity_instance) -> bool: @@ -535,7 +536,7 @@ class ChangeTypePage(bpy.types.Operator, tool.Ifc.Operator): page: bpy.props.IntProperty() def _execute(self, context): - props = context.scene.BIMModelProperties + props = tool.Model.get_model_props() bpy.ops.bim.load_type_thumbnails(ifc_class=props.ifc_class, offset=9 * (self.page - 1), limit=9) props.type_page = self.page return {"FINISHED"} @@ -548,7 +549,7 @@ class SetActiveType(bpy.types.Operator, tool.Ifc.Operator): relating_type: bpy.props.IntProperty() def _execute(self, context): - props = context.scene.BIMModelProperties + props = tool.Model.get_model_props() props.relating_type_id = str(self.relating_type) @@ -623,7 +624,7 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator): if bpy.app.background: return - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() # Only process at most one paginated class at a time. # Large projects have hundreds of types which can lead to unnecessary lag. if not AuthoringData.is_loaded: diff --git a/src/bonsai/bonsai/bim/module/model/profile.py b/src/bonsai/bonsai/bim/module/model/profile.py index d0787d85fa..122208456a 100644 --- a/src/bonsai/bonsai/bim/module/model/profile.py +++ b/src/bonsai/bonsai/bim/module/model/profile.py @@ -57,7 +57,7 @@ class DumbProfileGenerator: self.body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW") self.axis_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Axis", "GRAPH_VIEW") - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() self.container = None self.container_obj = None @@ -68,7 +68,7 @@ class DumbProfileGenerator: self.depth = props.extrusion_depth self.rotation = 0 self.location = Vector((0, 0, 0)) - self.cardinal_point = int(bpy.context.scene.BIMModelProperties.cardinal_point) + self.cardinal_point = int(props.cardinal_point) if insertion_type == "POLYLINE": return self.derive_from_polyline() elif insertion_type == "CURSOR": @@ -1123,7 +1123,7 @@ class DrawPolylineProfile(bpy.types.Operator, PolylineOperator): def __init__(self): super().__init__() self.relating_type = None - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() relating_type_id = props.relating_type_id if relating_type_id: self.relating_type = tool.Ifc.get().by_id(int(relating_type_id)) @@ -1132,7 +1132,7 @@ class DrawPolylineProfile(bpy.types.Operator, PolylineOperator): if not self.relating_type: return {"FINISHED"} - model_props = context.scene.BIMModelProperties + model_props = tool.Model.get_model_props() direction_sense = model_props.direction_sense offset = model_props.offset diff --git a/src/bonsai/bonsai/bim/module/model/prop.py b/src/bonsai/bonsai/bim/module/model/prop.py index 756f2255e0..86efd9a026 100644 --- a/src/bonsai/bonsai/bim/module/model/prop.py +++ b/src/bonsai/bonsai/bim/module/model/prop.py @@ -26,6 +26,7 @@ from bonsai.bim.module.model.data import AuthoringData from bpy.types import PropertyGroup, NodeTree from math import pi, radians from bonsai.bim.module.model.decorator import WallAxisDecorator, SlabDirectionDecorator +from typing import TYPE_CHECKING, Literal, get_args def get_ifc_class(self, context): @@ -230,6 +231,39 @@ class BIMModelProperties(PropertyGroup): update=update_slab_direction_decorator, ) + if TYPE_CHECKING: + ifc_class: str + relating_type_id: str + search_name: str + menu_relating_type_id: int + icon_id: int + updating: bool + occurrence_name_style: Literal["CLASS", "TYPE", "CUSTOM"] + occurrence_name_function: str + extrusion_depth: float + cardinal_point: Literal[ + "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19" + ] + length: float + openings: bpy.types.bpy_prop_collection_idprop[ObjProperty] + x: float + y: float + z: float + rl_mode: Literal["BOTTOM", "CONTAINER", "CURSOR"] + rl1: float + rl2: float + rl3: float + type_page: int + x_angle: float + type_name: str + boundary_class: str + direction_sense: Literal["POSITIVE", "NEGATIVE"] + offset_type_vertical: Literal["EXTERIOR", "CENTER", "INTERIOR"] + offset_type_horizontal: Literal["TOP", "CENTER", "BOTTOM"] + offset: float + show_wall_axis: bool + show_slab_direction: bool + class BIMArrayProperties(PropertyGroup): is_editing: bpy.props.IntProperty( @@ -550,21 +584,24 @@ class BIMWindowProperties(PropertyGroup): setattr(self, prop_name, kwargs[prop_name]) +DoorType = Literal[ + "SINGLE_SWING_LEFT", + "SINGLE_SWING_RIGHT", + "DOUBLE_SWING_LEFT", + "DOUBLE_SWING_RIGHT", + "DOUBLE_DOOR_SINGLE_SWING", + "SLIDING_TO_LEFT", + "SLIDING_TO_RIGHT", + "DOUBLE_DOOR_SLIDING", +] + + class BIMDoorProperties(PropertyGroup): non_si_units_props = ("is_editing", "door_type", "panel_width_ratio") - door_types = ( - ("SINGLE_SWING_LEFT", "SINGLE_SWING_LEFT", ""), - ("SINGLE_SWING_RIGHT", "SINGLE_SWING_RIGHT", ""), - ("DOUBLE_SWING_LEFT", "DOUBLE_SWING_LEFT", ""), - ("DOUBLE_SWING_RIGHT", "DOUBLE_SWING_RIGHT", ""), - ("DOUBLE_DOOR_SINGLE_SWING", "DOUBLE_DOOR_SINGLE_SWING", ""), - ("SLIDING_TO_LEFT", "SLIDING_TO_LEFT", ""), - ("SLIDING_TO_RIGHT", "SLIDING_TO_RIGHT", ""), - ("DOUBLE_DOOR_SLIDING", "DOUBLE_DOOR_SLIDING", ""), - ) - is_editing: bpy.props.BoolProperty(default=False) - door_type: bpy.props.EnumProperty(name="Door Operation Type", items=door_types, default="SINGLE_SWING_LEFT") + door_type: bpy.props.EnumProperty( + name="Door Operation Type", items=tuple((i, i, "") for i in get_args(DoorType)), default="SINGLE_SWING_LEFT" + ) overall_height: bpy.props.FloatProperty(name="Overall Height", default=2.0, subtype="DISTANCE") overall_width: bpy.props.FloatProperty(name="Overall Width", default=0.9, subtype="DISTANCE") @@ -631,6 +668,42 @@ class BIMDoorProperties(PropertyGroup): panel_material: bpy.props.EnumProperty(name="Panel Material", items=get_materials, options=set()) lining_material: bpy.props.EnumProperty(name="Lining Material", items=get_materials, options=set()) + if TYPE_CHECKING: + is_editing: bool + door_type: DoorType + overall_height: float + overall_width: float + + # Lining. + lining_depth: float + lining_thickness: float + lining_offset: float + lining_to_panel_offset_x: float + lining_to_panel_offset_y: float + + # Transom. + transom_thickness: float + transom_offset: float + + # Casing. + casing_thickness: float + casing_depth: float + + # Threshold. + threshold_thickness: float + threshold_depth: float + threshold_offset: float + + # Panel. + panel_depth: float + panel_width_ratio: float + frame_thickness: float + frame_depth: float + + # Material. + panel_material: str + lining_material: str + def get_general_kwargs(self, convert_to_project_units=False): kwargs = { "door_type": self.door_type, diff --git a/src/bonsai/bonsai/bim/module/model/slab.py b/src/bonsai/bonsai/bim/module/model/slab.py index d3a1f384e4..bf33b6f413 100644 --- a/src/bonsai/bonsai/bim/module/model/slab.py +++ b/src/bonsai/bonsai/bim/module/model/slab.py @@ -62,7 +62,7 @@ class DumbSlabGenerator: tool.Ifc.get(), "Plan", "FootPrint", "SKETCH_VIEW" ) - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() self.polyline = None self.container = None @@ -319,7 +319,7 @@ class DumbSlabPlaner: extrusion.Position.Location.Coordinates = tuple(rot_offset) else: - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else props.x_angle new_rep = ifcopenshell.api.run( "geometry.add_slab_representation", @@ -344,7 +344,7 @@ class DumbSlabPlaner: ) return else: - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() x_angle = 0 if tool.Cad.is_x(props.x_angle, 0, tolerance=0.001) else props.x_angle representation = ifcopenshell.api.run( "geometry.add_slab_representation", @@ -844,7 +844,7 @@ class AddSlabFromWall(bpy.types.Operator, tool.Ifc.Operator): def __init__(self): self.relating_type = None - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() relating_type_id = props.relating_type_id if relating_type_id: self.relating_type = tool.Ifc.get().by_id(int(relating_type_id)) @@ -876,7 +876,7 @@ class DrawPolylineSlab(bpy.types.Operator, PolylineOperator): def __init__(self): super().__init__() self.relating_type = None - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() relating_type_id = props.relating_type_id if relating_type_id: self.relating_type = tool.Ifc.get().by_id(int(relating_type_id)) @@ -887,7 +887,7 @@ class DrawPolylineSlab(bpy.types.Operator, PolylineOperator): slab = DumbSlabGenerator(self.relating_type).generate("POLYLINE") - model_props = context.scene.BIMModelProperties + model_props = tool.Model.get_model_props() direction_sense = model_props.direction_sense offset = model_props.offset model = IfcStore.get_file() @@ -920,13 +920,11 @@ class DrawPolylineSlab(bpy.types.Operator, PolylineOperator): self.handle_mouse_move(context, event) return {"PASS_THROUGH"} + props = tool.Model.get_model_props() if event.value == "RELEASE" and event.type == "F": - direction_sense = context.scene.BIMModelProperties.direction_sense - context.scene.BIMModelProperties.direction_sense = ( - "NEGATIVE" if direction_sense == "POSITIVE" else "POSITIVE" - ) + direction_sense = props.direction_sense + props.direction_sense = "NEGATIVE" if direction_sense == "POSITIVE" else "POSITIVE" - props = bpy.context.scene.BIMModelProperties if event.value == "RELEASE" and event.type == "O": items = ["TOP", "CENTER", "BOTTOM"] index = items.index(props.offset_type_horizontal) diff --git a/src/bonsai/bonsai/bim/module/model/ui.py b/src/bonsai/bonsai/bim/module/model/ui.py index 04f103b69f..bb5899113a 100644 --- a/src/bonsai/bonsai/bim/module/model/ui.py +++ b/src/bonsai/bonsai/bim/module/model/ui.py @@ -55,7 +55,7 @@ class BIM_MT_type_menu(bpy.types.Menu): relating_type_id: bpy.props.IntProperty(name="Relating Type Id") def draw(self, context): - props = context.scene.BIMModelProperties + props = tool.Model.get_model_props() layout = self.layout op = layout.operator("bim.launch_rename_type", icon="GREASEPENCIL", text="Rename Type") op.element = props.menu_relating_type_id @@ -74,7 +74,7 @@ class LaunchTypeMenu(bpy.types.Operator): relating_type_id: bpy.props.IntProperty(name="Relating Type Id") def execute(self, context): - props = context.scene.BIMModelProperties + props = tool.Model.get_model_props() props.menu_relating_type_id = self.relating_type_id bpy.ops.wm.call_menu(name="BIM_MT_type_menu") return {"FINISHED"} @@ -90,7 +90,7 @@ class LaunchTypeManager(bpy.types.Operator): return {"FINISHED"} def invoke(self, context, event): - props = context.scene.BIMModelProperties + props = tool.Model.get_model_props() props.type_page = 1 if get_ifc_class(None, context): ifc_class = AuthoringData.data["ifc_class_current"] or AuthoringData.data["ifc_element_type"] @@ -103,7 +103,7 @@ class LaunchTypeManager(bpy.types.Operator): return context.window_manager.invoke_props_dialog(self, width=550, title="Type Manager", confirm_text="Close") def draw(self, context): - props = context.scene.BIMModelProperties + props = tool.Model.get_model_props() row = self.layout.row(align=True) text = f"{AuthoringData.data['total_types']} {AuthoringData.data['ifc_element_type'] or 'Types'}" if AuthoringData.data["total_types"] > 1: @@ -518,7 +518,9 @@ class BIM_PT_door(bpy.types.Panel): if not DoorData.is_loaded: DoorData.load() - props = context.active_object.BIMDoorProperties + obj = context.active_object + assert obj + props = tool.Model.get_door_props(obj) if DoorData.data["pset_data"]: row = self.layout.row(align=True) diff --git a/src/bonsai/bonsai/bim/module/model/wall.py b/src/bonsai/bonsai/bim/module/model/wall.py index ed22980926..92be690b24 100644 --- a/src/bonsai/bonsai/bim/module/model/wall.py +++ b/src/bonsai/bonsai/bim/module/model/wall.py @@ -334,7 +334,7 @@ class AddWallsFromSlab(bpy.types.Operator, tool.Ifc.Operator): def __init__(self): self.relating_type = None - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() relating_type_id = props.relating_type_id if relating_type_id: self.relating_type = tool.Ifc.get().by_id(int(relating_type_id)) @@ -368,7 +368,7 @@ class DrawPolylineWall(bpy.types.Operator, PolylineOperator): def __init__(self): super().__init__() self.relating_type = None - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() relating_type_id = props.relating_type_id if relating_type_id: self.relating_type = tool.Ifc.get().by_id(int(relating_type_id)) @@ -377,7 +377,7 @@ class DrawPolylineWall(bpy.types.Operator, PolylineOperator): if not self.relating_type: return {"FINISHED"} - model_props = context.scene.BIMModelProperties + model_props = tool.Model.get_model_props() direction_sense = model_props.direction_sense offset = model_props.offset @@ -420,17 +420,15 @@ class DrawPolylineWall(bpy.types.Operator, PolylineOperator): self.handle_mouse_move(context, event) return {"PASS_THROUGH"} + props = tool.Model.get_model_props() # Wall axis settings if event.value == "RELEASE" and event.type == "F": - direction_sense = context.scene.BIMModelProperties.direction_sense - context.scene.BIMModelProperties.direction_sense = ( - "NEGATIVE" if direction_sense == "POSITIVE" else "POSITIVE" - ) + direction_sense = props.direction_sense + props.direction_sense = "NEGATIVE" if direction_sense == "POSITIVE" else "POSITIVE" self.set_offset(context, self.relating_type) - props = bpy.context.scene.BIMModelProperties if event.value == "RELEASE" and event.type == "O": - items = ["EXTERIOR", "CENTER", "INTERIOR"] + items = ("EXTERIOR", "CENTER", "INTERIOR") index = items.index(props.offset_type_vertical) size = len(items) props.offset_type_vertical = items[((index + 1) % size)] @@ -608,7 +606,7 @@ class DumbWallGenerator: self.body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW") self.axis_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Plan", "Axis", "GRAPH_VIEW") - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() self.container = None self.container_obj = None @@ -800,7 +798,7 @@ class DumbWallGenerator: return self.create_wall() def create_wall(self) -> bpy.types.Object: - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() ifc_class = self.get_relating_type_class(self.relating_type) mesh = bpy.data.meshes.new("Dummy") obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh) diff --git a/src/bonsai/bonsai/bim/module/model/window.py b/src/bonsai/bonsai/bim/module/model/window.py index 852c34d14a..c82847eacd 100644 --- a/src/bonsai/bonsai/bim/module/model/window.py +++ b/src/bonsai/bonsai/bim/module/model/window.py @@ -21,6 +21,7 @@ import bpy import json import bmesh import collections +import collections.abc import ifcopenshell import bonsai.tool as tool import bonsai.core.root @@ -33,7 +34,7 @@ import ifcopenshell.util.shape_builder import ifcopenshell.util.unit from bmesh.types import BMVert from mathutils import Vector -from typing import Optional +from typing import Optional, Union V_ = tool.Blender.V_ @@ -132,7 +133,7 @@ def update_window_modifier_representation(context: bpy.types.Context) -> None: def create_bm_window_frame( - bm: bmesh.types.BMesh, size: Vector, thickness: list, position: Vector = V_(0, 0, 0).freeze() + bm: bmesh.types.BMesh, size: Vector, thickness: Union[float, list[float]], position: Vector = V_(0, 0, 0).freeze() ) -> list[bmesh.types.BMVert]: """`thickness` of the profile is defined as list in the following order: `(LEFT, TOP, RIGHT, BOTTOM)` diff --git a/src/bonsai/bonsai/bim/module/model/workspace.py b/src/bonsai/bonsai/bim/module/model/workspace.py index db79fc445a..b41f155dca 100644 --- a/src/bonsai/bonsai/bim/module/model/workspace.py +++ b/src/bonsai/bonsai/bim/module/model/workspace.py @@ -503,7 +503,7 @@ class CreateObjectUI: cls, context: bpy.types.Context, layout: bpy.types.UILayout, ifc_element_type: Optional[str] = None ) -> None: cls.layout = layout - cls.props = context.scene.BIMModelProperties + cls.props = tool.Model.get_model_props() row = cls.layout.row(align=True) if not tool.Ifc.get(): @@ -733,7 +733,7 @@ class EditObjectUI: cls, context: bpy.types.Context, layout: bpy.types.UILayout, ifc_element_type: Optional[str] = None ) -> None: cls.layout = layout - cls.props = context.scene.BIMModelProperties + cls.props = tool.Model.get_model_props() row = cls.layout.row(align=True) row.separator() @@ -1099,7 +1099,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator): return operator.description or "" def _execute(self, context): - self.props = context.scene.BIMModelProperties + self.props = tool.Model.get_model_props() self.has_ifc_class = True self.active_class = None @@ -1118,7 +1118,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator): def invoke(self, context, event): # https://blender.stackexchange.com/questions/276035/how-do-i-make-operators-remember-their-property-values-when-called-from-a-hotkey - self.props = context.scene.BIMModelProperties + self.props = tool.Model.get_model_props() self.x = self.props.x self.y = self.props.y self.z = self.props.z @@ -1138,7 +1138,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator): bpy.ops.wm.call_menu(name="BIM_MT_add_representation_item") return - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() relating_type_class = AuthoringData.data["ifc_class_current"] if not (relating_type_id := tool.Blender.get_enum_safe(props, "relating_type_id")): self.report({"ERROR"}, "No relating type selected") @@ -1431,7 +1431,7 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator): bpy.ops.bim.enable_editing_extrusion_axis() def hotkey_A_O(self): - if bpy.context.scene.BIMModelProperties.openings: + if tool.Model.get_model_props().openings: bpy.ops.bim.edit_openings(apply_all=True) else: bpy.ops.bim.show_openings() diff --git a/src/bonsai/bonsai/bim/module/root/operator.py b/src/bonsai/bonsai/bim/module/root/operator.py index 3579f0b89d..0ba93cc800 100644 --- a/src/bonsai/bonsai/bim/module/root/operator.py +++ b/src/bonsai/bonsai/bim/module/root/operator.py @@ -579,7 +579,7 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator): if props.ifc_product == "IfcFeatureElement": tool.Feature.add_feature(props.featured_obj, [obj]) - new = context.scene.BIMModelProperties.openings.add() + new = tool.Model.get_model_props().openings.add() new.obj = obj bpy.ops.bim.show_openings() tool.Model.purge_scene_openings() diff --git a/src/bonsai/bonsai/bim/module/spatial/workspace.py b/src/bonsai/bonsai/bim/module/spatial/workspace.py index 57a18641d3..7853f3a4af 100644 --- a/src/bonsai/bonsai/bim/module/spatial/workspace.py +++ b/src/bonsai/bonsai/bim/module/spatial/workspace.py @@ -58,7 +58,7 @@ class SpatialToolUI: @classmethod def draw(cls, context, layout): cls.layout = layout - cls.model_props = context.scene.BIMModelProperties + cls.model_props = tool.Model.get_model_props() row = cls.layout.row(align=True) if not tool.Ifc.get(): diff --git a/src/bonsai/bonsai/bim/module/type/operator.py b/src/bonsai/bonsai/bim/module/type/operator.py index 5b67d79957..866407be3e 100644 --- a/src/bonsai/bonsai/bim/module/type/operator.py +++ b/src/bonsai/bonsai/bim/module/type/operator.py @@ -48,7 +48,7 @@ class AssignType(bpy.types.Operator, tool.Ifc.Operator): if self.related_object else context.selected_objects or [context.active_object] ) - model_props = context.scene.BIMModelProperties + model_props = tool.Model.get_model_props() for obj in related_objects: element = tool.Ifc.get_entity(obj) core.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type) @@ -332,11 +332,11 @@ class DuplicateType(bpy.types.Operator, tool.Ifc.Operator): else: self.report({"INFO"}, "Type object can't be selected : It may be hidden or in an excluded collection.") - props = context.scene.BIMModelProperties + props = tool.Model.get_model_props() ifc_class = new.is_a() # Set duplicated type as active in current tool. if ifc_class in (i[0] for i in (bonsai.bim.helper.get_enum_items(props, "ifc_class", context) or ()) if i): - context.scene.BIMModelProperties.ifc_class = new.is_a() - context.scene.BIMModelProperties.relating_type_id = str(new_obj.BIMObjectProperties.ifc_definition_id) + props.ifc_class = new.is_a() + props.relating_type_id = str(new_obj.BIMObjectProperties.ifc_definition_id) return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/ui.py b/src/bonsai/bonsai/bim/ui.py index 3579366a1e..f92117fba5 100644 --- a/src/bonsai/bonsai/bim/ui.py +++ b/src/bonsai/bonsai/bim/ui.py @@ -347,10 +347,11 @@ class BIM_ADDON_preferences(bpy.types.AddonPreferences): layout.prop(context.scene.BIMProjectProperties, "should_disable_undo_on_save") layout.prop(context.scene.BIMProjectProperties, "should_stream") - def draw_model_settings(self, layout, context): - layout.prop(context.scene.BIMModelProperties, "occurrence_name_style") - if context.scene.BIMModelProperties == "CUSTOM": - layout.prop(context.scene.BIMModelProperties, "occurrence_name_function") + def draw_model_settings(self, layout: bpy.types.UILayout, context: bpy.types.Context) -> None: + props = tool.Model.get_model_props() + layout.prop(props, "occurrence_name_style") + if props == "CUSTOM": + layout.prop(props, "occurrence_name_function") def draw_directories(self, layout, context): row = layout.row(align=True) @@ -1221,7 +1222,7 @@ class BIM_PT_decorators_overlay(Panel): georeference_props = bpy.context.scene.BIMGeoreferenceProperties aggregate_props = bpy.context.scene.BIMAggregateProperties nest_props = bpy.context.scene.BIMNestProperties - model_props = bpy.context.scene.BIMModelProperties + model_props = tool.Model.get_model_props() display_all = overlay.show_overlays col = layout.column() diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index 1669a636bb..a8a0c0e8b1 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -1035,7 +1035,8 @@ class Blender(bonsai.core.tool.Blender): @classmethod def is_editing_door_parameters(cls, obj: bpy.types.Object) -> bool: - return obj.BIMDoorProperties.is_editing + props = tool.Model.get_door_props(obj) + return props.is_editing @classmethod def is_editing_stair_parameters(cls, obj: bpy.types.Object) -> bool: diff --git a/src/bonsai/bonsai/tool/covering.py b/src/bonsai/bonsai/tool/covering.py index d682761ae3..5141cbffbd 100644 --- a/src/bonsai/bonsai/tool/covering.py +++ b/src/bonsai/bonsai/tool/covering.py @@ -66,7 +66,7 @@ class Covering(bonsai.core.tool.Covering): ) -> bool: if not tool.Ifc.get(): return False - props = context.scene.BIMModelProperties + props = tool.Model.get_model_props() relating_type_id = tool.Blender.get_enum_safe(props, "relating_type_id") if relating_type_id is not None: relating_type = ifcopenshell.util.element.get_predefined_type(tool.Ifc.get().by_id(int(relating_type_id))) diff --git a/src/bonsai/bonsai/tool/drawing.py b/src/bonsai/bonsai/tool/drawing.py index b75717044a..439280bf05 100644 --- a/src/bonsai/bonsai/tool/drawing.py +++ b/src/bonsai/bonsai/tool/drawing.py @@ -55,7 +55,7 @@ from typing import Optional, Union, Iterable, Any, Literal, Sequence, TYPE_CHECK from pathlib import Path if TYPE_CHECKING: - from bonsai.bim.module.drawing.prop import DocProperties, Sheet + from bonsai.bim.module.drawing.prop import DocProperties, Sheet, BIMAnnotationProperties, BIMTextProperties from bonsai.bim.module.drawing.prop import Drawing as DrawingProperties @@ -91,6 +91,14 @@ class Drawing(bonsai.core.tool.Drawing): def get_document_props(cls) -> DocProperties: return bpy.context.scene.DocProperties + @classmethod + def get_annotation_props(cls) -> BIMAnnotationProperties: + return bpy.context.scene.BIMAnnotationProperties + + @classmethod + def get_text_props(cls, obj: bpy.types.Object) -> BIMTextProperties: + return obj.BIMTextProperties + @classmethod def canonicalise_class_name(cls, name: str) -> str: return re.sub("[^0-9a-zA-Z]+", "", name) @@ -349,7 +357,8 @@ class Drawing(bonsai.core.tool.Drawing): @classmethod def disable_editing_text(cls, obj: bpy.types.Object) -> None: - obj.BIMTextProperties.is_editing = False + props = tool.Drawing.get_text_props(obj) + props.is_editing = False @classmethod def disable_editing_assigned_product(cls, obj: bpy.types.Object) -> None: @@ -385,7 +394,8 @@ class Drawing(bonsai.core.tool.Drawing): @classmethod def enable_editing_text(cls, obj: bpy.types.Object) -> None: - obj.BIMTextProperties.is_editing = True + props = cls.get_text_props(obj) + props.is_editing = True @classmethod def enable_editing_assigned_product(cls, obj: bpy.types.Object) -> None: @@ -409,7 +419,8 @@ class Drawing(bonsai.core.tool.Drawing): @classmethod def export_text_literal_attributes(cls, obj: bpy.types.Object) -> list[dict[str, Any]]: literals = [] - for literal_props in obj.BIMTextProperties.literals: + props = tool.Drawing.get_text_props(obj) + for literal_props in props.literals: literal_data = bonsai.bim.helper.export_attributes(literal_props.attributes) literals.append(literal_data) return literals @@ -620,7 +631,8 @@ class Drawing(bonsai.core.tool.Drawing): def synchronise_ifc_and_text_attributes(cls, obj: bpy.types.Object) -> None: literals = cls.get_text_literal(obj, return_list=True) literals_attributes = cls.export_text_literal_attributes(obj) - defined_ifc_ids = [l.ifc_definition_id for l in obj.BIMTextProperties.literals] + props = cls.get_text_props(obj) + defined_ifc_ids = [l.ifc_definition_id for l in props.literals] ifc_file = tool.Ifc.get() for ifc_definition_id, attributes in zip(defined_ifc_ids, literals_attributes): @@ -921,7 +933,7 @@ class Drawing(bonsai.core.tool.Drawing): def import_text_attributes(cls, obj: bpy.types.Object) -> None: from bonsai.bim.module.drawing.prop import BOX_ALIGNMENT_POSITIONS - props = obj.BIMTextProperties + props = cls.get_text_props(obj) props.literals.clear() for ifc_literal in cls.get_text_literal(obj, return_list=True): @@ -1035,7 +1047,7 @@ class Drawing(bonsai.core.tool.Drawing): @classmethod def update_text_value(cls, obj: bpy.types.Object) -> None: - props = obj.BIMTextProperties + props = cls.get_text_props(obj) literals = cls.get_text_literal(obj, return_list=True) cls.import_text_attributes(obj) for i, literal in enumerate(literals): @@ -1049,7 +1061,7 @@ class Drawing(bonsai.core.tool.Drawing): """ from bonsai.bim.module.drawing.data import FONT_SIZES - props = obj.BIMTextProperties + props = cls.get_text_props(obj) element = tool.Ifc.get_entity(obj) # updating text font size in EPset_Annotation.Classes font_size = float(props.font_size) @@ -1080,7 +1092,7 @@ class Drawing(bonsai.core.tool.Drawing): @classmethod def update_newline_at(cls, obj: bpy.types.Object) -> None: - props = obj.BIMTextProperties + props = cls.get_text_props(obj) element = tool.Ifc.get_entity(obj) newline_at = int(props.newline_at) ifc_file = tool.Ifc.get() diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py index 517a0cd864..8b7a65bd6e 100644 --- a/src/bonsai/bonsai/tool/model.py +++ b/src/bonsai/bonsai/tool/model.py @@ -16,6 +16,7 @@ # 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 json import bmesh @@ -47,13 +48,24 @@ from bonsai.bim.module.geometry.helper import Helper from bonsai.bim.module.model.data import AuthoringData, RailingData, RoofData, WindowData, DoorData from bonsai.bim.module.model.opening import FilledOpeningGenerator from ifcopenshell.util.shape_builder import ShapeBuilder -from typing import Optional, Union, TypeVar, Any, Iterable, Literal +from typing import Optional, Union, TypeVar, Any, Iterable, Literal, TYPE_CHECKING T = TypeVar("T") V_ = tool.Blender.V_ +if TYPE_CHECKING: + from bonsai.bim.module.model.prop import BIMModelProperties, BIMDoorProperties + class Model(bonsai.core.tool.Model): + @classmethod + def get_model_props(cls) -> BIMModelProperties: + return bpy.context.scene.BIMModelProperties + + @classmethod + def get_door_props(cls, obj: bpy.types.Object) -> BIMDoorProperties: + return obj.BIMDoorProperties + @classmethod def convert_si_to_unit(cls, value: T) -> T: if isinstance(value, (tuple, list)): @@ -215,7 +227,7 @@ class Model(bonsai.core.tool.Model): @classmethod def generate_occurrence_name(cls, element_type: ifcopenshell.entity_instance, ifc_class: str) -> str: - props = bpy.context.scene.BIMModelProperties + props = cls.get_model_props() if props.occurrence_name_style == "CLASS": return ifc_class[3:] elif props.occurrence_name_style == "TYPE": @@ -521,7 +533,8 @@ class Model(bonsai.core.tool.Model): @classmethod def purge_scene_openings(cls) -> None: """Purge removed scene openings.""" - openings = bpy.context.scene.BIMModelProperties.openings + props = cls.get_model_props() + openings = props.openings for i in range(len(openings) - 1, -1, -1): if not openings[i].obj: openings.remove(i) @@ -1929,7 +1942,8 @@ class Model(bonsai.core.tool.Model): @classmethod def get_tracked_opening_type(cls, obj: bpy.types.Object) -> Union[Literal["OPENING", "BOOLEAN"], None]: """Get tracked opening type, return `None` if object is not a tracked opening.""" - for opening in bpy.context.scene.BIMModelProperties.openings: + props = cls.get_model_props() + for opening in props.openings: if opening.obj == obj: return opening.name return None diff --git a/src/bonsai/bonsai/tool/project.py b/src/bonsai/bonsai/tool/project.py index 33312a0e76..96a5c58c36 100644 --- a/src/bonsai/bonsai/tool/project.py +++ b/src/bonsai/bonsai/tool/project.py @@ -143,7 +143,7 @@ class Project(bonsai.core.tool.Project): @classmethod def set_default_modeling_dimensions(cls): - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) props.extrusion_depth = 3 props.length = 1 diff --git a/src/bonsai/bonsai/tool/root.py b/src/bonsai/bonsai/tool/root.py index 7d91716e08..b18e649338 100644 --- a/src/bonsai/bonsai/tool/root.py +++ b/src/bonsai/bonsai/tool/root.py @@ -35,7 +35,8 @@ class Root(bonsai.core.tool.Root): @classmethod def add_tracked_opening(cls, obj: bpy.types.Object, opening_type: Literal["OPENING", "BOOLEAN"]) -> None: """Add tracked opening or boolean object.""" - new = bpy.context.scene.BIMModelProperties.openings.add() + props = tool.Model.get_model_props() + new = props.openings.add() new.obj = obj new.name = opening_type diff --git a/src/bonsai/bonsai/tool/spatial.py b/src/bonsai/bonsai/tool/spatial.py index a10afc1a21..ecdc22233f 100644 --- a/src/bonsai/bonsai/tool/spatial.py +++ b/src/bonsai/bonsai/tool/spatial.py @@ -688,7 +688,8 @@ class Spatial(bonsai.core.tool.Spatial): @classmethod def get_boundary_lines_from_context_visible_objects(cls) -> list[shapely.LineString]: - calculation_rl = bpy.context.scene.BIMModelProperties.rl3 + props = props = tool.Model.get_model_props() + calculation_rl = props.rl3 container = tool.Root.get_default_container() container_obj = tool.Ifc.get_object(container) cut_point = container_obj.matrix_world.translation.copy() + Vector((0, 0, calculation_rl)) @@ -1054,7 +1055,7 @@ class Spatial(bonsai.core.tool.Spatial): @classmethod def get_relating_type_id(cls) -> int: - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() relating_type_id = props.relating_type_id return relating_type_id @@ -1110,7 +1111,8 @@ class Spatial(bonsai.core.tool.Spatial): @classmethod def assign_type_to_obj(cls, obj: bpy.types.Object) -> None: - relating_type_id = bpy.context.scene.BIMModelProperties.relating_type_id + props = tool.Model.get_model_props() + relating_type_id = props.relating_type_id relating_type = tool.Ifc.get().by_id(int(relating_type_id)) ifc_class = relating_type.is_a() instance_class = ifcopenshell.util.type.get_applicable_entities(ifc_class, tool.Ifc.get().schema)[0] diff --git a/src/bonsai/test/bim/test_feature.py b/src/bonsai/test/bim/test_feature.py index 39d048e1e6..95945ced81 100644 --- a/src/bonsai/test/bim/test_feature.py +++ b/src/bonsai/test/bim/test_feature.py @@ -360,7 +360,7 @@ def i_load_a_new_pset_template_file(): @given("I create default MEP types") def i_create_default_mep_types(): - model_props = bpy.context.scene.BIMModelProperties + model_props = tool.Model.get_model_props() # add couple segments types model_props.type_class = "IfcDuctSegmentType" @@ -1244,13 +1244,13 @@ def i_display_the_construction_type_browser(): @given("I add the construction type") @when("I add the construction type") def i_add_the_active_construction_type(): - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() bpy.ops.bim.add_constr_type_instance(relating_type_id=int(props.relating_type_id)) @then(parsers.parse("construction type is {relating_type_name}")) def construction_type(relating_type_name): - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() relating_type = AuthoringData.relating_type_name_by_id(props.ifc_class, props.relating_type_id) assert relating_type == relating_type_name, f"Construction Type is a {relating_type}, not a {relating_type_name}" @@ -1333,7 +1333,8 @@ def the_obj_layer_lenght_is_set_to(value): print(50 * "@", bpy.context.selected_objects) - bpy.context.scene.BIMModelProperties.length = value + props = tool.Model.get_model_props() + props.length = value bpy.ops.bim.change_layer_length(length=value) diff --git a/src/bonsai/test/tool/test_drawing.py b/src/bonsai/test/tool/test_drawing.py index d895b4f306..8f7c0bdcd0 100644 --- a/src/bonsai/test/tool/test_drawing.py +++ b/src/bonsai/test/tool/test_drawing.py @@ -22,6 +22,7 @@ import bpy import mathutils import ifcopenshell import ifcopenshell.guid +import ifcopenshell.util.element import bonsai.core.tool import bonsai.tool as tool from test.bim.bootstrap import NewFile @@ -107,7 +108,7 @@ class TestDeleteDrawingElements(NewFile): assert False except: pass - assert not bpy.data.objects.get("Object") + assert not bpy.data.objects["Object"] class TestDisableEditingDrawings(NewFile): @@ -141,9 +142,10 @@ class TestDisableEditingSheets(NewFile): class TestDisableEditingText(NewFile): def test_run(self): obj = bpy.data.objects.new("Object", None) - obj.BIMTextProperties.is_editing = True + props = tool.Drawing.get_text_props(obj) + props.is_editing = True subject.disable_editing_text(obj) - assert obj.BIMTextProperties.is_editing == False + assert props.is_editing == False class TestDisableEditingAssignedProduct(NewFile): @@ -194,7 +196,8 @@ class TestEnableEditingText(NewFile): def test_run(self): obj = bpy.data.objects.new("Object", None) subject.enable_editing_text(obj) - assert obj.BIMTextProperties.is_editing == True + props = tool.Drawing.get_text_props(obj) + assert props.is_editing == True class TestEnableEditingAssignedProduct(NewFile): @@ -238,7 +241,7 @@ class TestEnsureUniqueIdentification(NewFile): class TestExportTextLiteralAttributes(NewFile): def test_run(self): TestImportTextAttributes().test_run() - assert subject.export_text_literal_attributes(bpy.data.objects.get("Object")) == [ + assert subject.export_text_literal_attributes(bpy.data.objects["Object"]) == [ { "Literal": "Literal", "Path": "RIGHT", @@ -584,7 +587,8 @@ class TestImportTextAttributes(NewFile): element.ObjectType = "TEXT" # TODO: double check if it's valid to set this tool.Ifc.link(element, obj) subject.import_text_attributes(obj) - literal_props = obj.BIMTextProperties.literals[0] + props = tool.Drawing.get_text_props(obj) + literal_props = props.literals[0] assert literal_props.attributes.get("Literal").string_value == "Literal" assert literal_props.attributes.get("Path").enum_value == "RIGHT" assert literal_props.attributes.get("BoxAlignment").string_value == "bottom-left" @@ -733,11 +737,12 @@ class TestUpdateTextValue(NewFile): TestGetTextLiteral().test_run() ifc = tool.Ifc.get() - obj = bpy.data.objects.get("Object") + obj = bpy.data.objects["Object"] subject.update_text_value(obj) - literal = obj.BIMTextProperties.literals[0] + props = tool.Drawing.get_text_props(obj) + literal = props.literals[0] - assert obj.BIMTextProperties.font_size == "2.5" + assert props.font_size == "2.5" assert literal.value == "Literal" assert literal.box_alignment[:] == tuple([False] * 6 + [True] + [False] * 2) assert literal.ifc_definition_id == ifc.by_type("IfcTextLiteralWithExtent")[0].id() @@ -745,7 +750,7 @@ class TestUpdateTextValue(NewFile): def test_using_attribute_variables(self): TestGetTextLiteral().test_run() - obj = bpy.data.objects.get("Object") + obj = bpy.data.objects["Object"] ifc = tool.Ifc.get() wall = ifc.createIfcWall(Name="Baz") label = ifc.by_type("IfcAnnotation")[0] @@ -754,12 +759,13 @@ class TestUpdateTextValue(NewFile): ifc.by_type("IfcTextLiteralWithExtent")[0].Literal = "Foo {{Name}} Bar" subject.update_text_value(obj) - assert obj.BIMTextProperties.literals[0].value == "Foo Baz Bar" + props = tool.Drawing.get_text_props(obj) + assert props.literals[0].value == "Foo Baz Bar" def test_using_property_variables(self): TestGetTextLiteral().test_run() - obj = bpy.data.objects.get("Object") + obj = bpy.data.objects["Object"] ifc = tool.Ifc.get() wall = ifc.createIfcWall() pset = ifcopenshell.api.run("pset.add_pset", ifc, name="Custom_Pset", product=wall) @@ -770,14 +776,16 @@ class TestUpdateTextValue(NewFile): ifc.by_type("IfcTextLiteralWithExtent")[0].Literal = "Foo {{Custom_Pset.Key}} Bar" subject.update_text_value(obj) - assert obj.BIMTextProperties.literals[0].value == "Foo Baz Bar" + props = tool.Drawing.get_text_props(obj) + assert props.literals[0].value == "Foo Baz Bar" def test_update_text_font_size(self): TestGetTextLiteral().test_run() - obj = bpy.data.objects.get("Object") + obj = bpy.data.objects["Object"] with bpy.context.temp_override(active_object=obj): bpy.ops.bim.enable_editing_text() - obj.BIMTextProperties.font_size = "7.0" + props = tool.Drawing.get_text_props(obj) + props.font_size = "7.0" bpy.ops.bim.edit_text() annotation_classes = ifcopenshell.util.element.get_pset(tool.Ifc.get_entity(obj), "EPset_Annotation", "Classes") assert "title" in annotation_classes @@ -786,11 +794,12 @@ class TestUpdateTextValue(NewFile): def test_add_second_literal(self, setup=True): if setup: TestGetTextLiteral().test_run() - obj = bpy.data.objects.get("Object") + obj = bpy.data.objects["Object"] with bpy.context.temp_override(active_object=obj): bpy.ops.bim.enable_editing_text() bpy.ops.bim.add_text_literal() - literal = obj.BIMTextProperties.literals[1] + props = tool.Drawing.get_text_props(obj) + literal = props.literals[1] literal.attributes["Literal"].string_value = "test_value" bpy.ops.bim.edit_text() @@ -802,8 +811,8 @@ class TestUpdateTextValue(NewFile): self.test_update_text_font_size() # sets font size to "7.0" self.test_add_second_literal(setup=False) - obj = bpy.data.objects.get("Object") - props = obj.BIMTextProperties + obj = bpy.data.objects["Object"] + props = tool.Drawing.get_text_props(obj) assert obj is not None, obj with bpy.context.temp_override(active_object=obj): bpy.ops.bim.enable_editing_text() diff --git a/src/bonsai/test/tool/test_model.py b/src/bonsai/test/tool/test_model.py index 7667f09219..73e6ba1111 100644 --- a/src/bonsai/test/tool/test_model.py +++ b/src/bonsai/test/tool/test_model.py @@ -41,13 +41,15 @@ class TestGenerateOccurrenceName(NewFile): def test_generating_based_on_class(self): ifc = ifcopenshell.file() element_type = ifc.createIfcWallType(Name="Foobar") - bpy.context.scene.BIMModelProperties.occurrence_name_style = "CLASS" + props = tool.Model.get_model_props() + props.occurrence_name_style = "CLASS" assert subject.generate_occurrence_name(element_type, "IfcWall") == "Wall" def test_generating_based_on_type_name(self): ifc = ifcopenshell.file() element_type = ifc.createIfcWallType() - bpy.context.scene.BIMModelProperties.occurrence_name_style = "TYPE" + props = tool.Model.get_model_props() + props.occurrence_name_style = "TYPE" assert subject.generate_occurrence_name(element_type, "IfcWall") == "Unnamed" element_type.Name = "Foobar" assert subject.generate_occurrence_name(element_type, "IfcWall") == "Foobar" @@ -55,8 +57,9 @@ class TestGenerateOccurrenceName(NewFile): def test_generating_based_on_a_custom_function(self): ifc = ifcopenshell.file() element_type = ifc.createIfcWallType() - bpy.context.scene.BIMModelProperties.occurrence_name_style = "CUSTOM" - bpy.context.scene.BIMModelProperties.occurrence_name_function = '"Foobar"' + props = tool.Model.get_model_props() + props.occurrence_name_style = "CUSTOM" + props.occurrence_name_function = '"Foobar"' assert subject.generate_occurrence_name(element_type, "IfcWall") == "Foobar" diff --git a/src/bonsai/test/tool/test_project.py b/src/bonsai/test/tool/test_project.py index 4b68a97343..4af7494a85 100644 --- a/src/bonsai/test/tool/test_project.py +++ b/src/bonsai/test/tool/test_project.py @@ -125,7 +125,7 @@ class TestSetDefaultModelingDimensions(NewFile): ifc.createIfcProject() ifcopenshell.api.run("unit.assign_unit", ifc) subject.set_default_modeling_dimensions() - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() assert props.extrusion_depth == 3 assert props.length == 1 assert props.rl1 == 0 diff --git a/src/bonsai/test/tool/test_root.py b/src/bonsai/test/tool/test_root.py index 0d0e1fdce2..19cba02029 100644 --- a/src/bonsai/test/tool/test_root.py +++ b/src/bonsai/test/tool/test_root.py @@ -34,7 +34,7 @@ class TestAddTrackedOpening(NewFile): def test_run(self): obj = bpy.data.objects.new("Object", None) subject.add_tracked_opening(obj, "OPENING") - props = bpy.context.scene.BIMModelProperties + props = tool.Model.get_model_props() assert props.openings[0].obj == obj assert props.openings[0].name == "OPENING"