From bf9d84e7380f03a1256ed4033bdc77bca2717cc5 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Fri, 4 Oct 2024 12:10:15 +0500 Subject: [PATCH] typing --- .../bonsai/bim/module/drawing/annotation.py | 17 ++-- src/bonsai/bonsai/bim/module/model/door.py | 35 +++++--- src/bonsai/bonsai/bim/module/model/railing.py | 15 ++-- src/bonsai/bonsai/bim/module/model/roof.py | 43 ++++++---- src/bonsai/bonsai/bim/module/model/stair.py | 6 +- src/bonsai/bonsai/bim/module/model/window.py | 23 +++--- src/bonsai/bonsai/bim/module/project/prop.py | 10 +-- src/bonsai/bonsai/tool/model.py | 80 ++++++++++++------- 8 files changed, 141 insertions(+), 88 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/drawing/annotation.py b/src/bonsai/bonsai/bim/module/drawing/annotation.py index b78fac0170..6b13782f8d 100644 --- a/src/bonsai/bonsai/bim/module/drawing/annotation.py +++ b/src/bonsai/bonsai/bim/module/drawing/annotation.py @@ -23,11 +23,12 @@ import bmesh import bonsai.tool as tool import ifcopenshell.util.element from mathutils import Vector, Matrix +from typing import Optional class Annotator: @staticmethod - def add_text(related_element=None): + def add_text(related_element: Optional[ifcopenshell.entity_instance] = None) -> bpy.types.Object: curve = bpy.data.curves.new(type="FONT", name="Text") curve.body = "TEXT" obj = bpy.data.objects.new("Text", curve) @@ -53,7 +54,7 @@ class Annotator: return obj @staticmethod - def resize_text(text_obj): + def resize_text(text_obj: bpy.types.Object) -> None: camera = None group = tool.Drawing.get_drawing_group(tool.Ifc.get_entity(text_obj)) for element in tool.Drawing.get_drawing_elements(group): @@ -71,7 +72,9 @@ class Annotator: text_obj.data.size = font_size @staticmethod - def add_line_to_annotation(obj, co1=None, co2=None): + def add_line_to_annotation( + obj: bpy.types.Object, co1: Optional[Vector] = None, co2: Optional[Vector] = None + ) -> bpy.types.Object: if co1 is None: co1, co2, _, _ = Annotator.get_placeholder_coords() co1 = obj.matrix_world.inverted() @ co1 @@ -93,7 +96,7 @@ class Annotator: return obj @staticmethod - def add_vertex_to_annotation(obj): + def add_vertex_to_annotation(obj: bpy.types.Object) -> bpy.types.Object: verts_world_space = Annotator.get_placeholder_coords() vert_local = obj.matrix_world.inverted() @ verts_world_space[0] bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True) @@ -102,7 +105,7 @@ class Annotator: return obj @staticmethod - def add_plane_to_annotation(obj, remove_face=False): + def add_plane_to_annotation(obj: bpy.types.Object, remove_face: bool = False) -> bpy.types.Object: # default order = bot left, top left, bot right, top right # therefore we redefine the order face_verts = [0, 2, 3, 1] @@ -119,7 +122,7 @@ class Annotator: return obj @staticmethod - def get_annotation_obj(drawing, object_type, data_type): + def get_annotation_obj(drawing: ifcopenshell.entity_instance, object_type: str, data_type: str) -> bpy.types.Object: camera = tool.Ifc.get_object(drawing) co1, _, _, _ = Annotator.get_placeholder_coords(camera) matrix_world = tool.Drawing.get_camera_matrix(camera) @@ -162,7 +165,7 @@ class Annotator: return obj @staticmethod - def get_placeholder_coords(camera=None): + def get_placeholder_coords(camera: Optional[bpy.types.Object] = None) -> tuple[Vector, Vector, Vector, Vector]: if not camera: camera = bpy.context.scene.camera diff --git a/src/bonsai/bonsai/bim/module/model/door.py b/src/bonsai/bonsai/bim/module/model/door.py index 0269dbb7cf..9a6acbf5cc 100644 --- a/src/bonsai/bonsai/bim/module/model/door.py +++ b/src/bonsai/bonsai/bim/module/model/door.py @@ -35,6 +35,7 @@ import bonsai.core.root from bonsai.bim.module.model.window import create_bm_window, create_bm_box from mathutils import Vector, Matrix +from typing import Union, Any, Optional import json import collections @@ -169,7 +170,9 @@ def update_door_modifier_representation(context: bpy.types.Context) -> None: # TODO: move it out to tools -def bm_sort_out_geom(geom_data): +def bm_sort_out_geom( + geom_data: list[Union[bmesh.types.BMVert, bmesh.types.BMEdge, bmesh.types.BMFace]] +) -> dict[str, Any]: geom_dict = {"verts": [], "edges": [], "faces": []} for el in geom_data: @@ -183,8 +186,12 @@ def bm_sort_out_geom(geom_data): def bm_mirror( - bm, verts, mirror_axes: Vector = V(1, 0, 0).freeze(), mirror_point: Vector = V(0, 0, 0).freeze(), create_copy=False -): + bm: bmesh.types.BMesh, + verts: list[bmesh.types.BMVert], + mirror_axes: Vector = V(1, 0, 0).freeze(), + mirror_point: Vector = V(0, 0, 0).freeze(), + create_copy: bool = False, +) -> list[bmesh.types.BMVert]: matrix = Matrix.Translation(mirror_point) for i, v in enumerate(mirror_axes): if not v: @@ -208,14 +215,14 @@ def bm_mirror( def create_bm_extruded_profile( - bm, - points, - edges=None, - faces=None, + bm: bmesh.types.BMesh, + points: list[Vector], + edges: Optional[list[tuple[int, int]]] = None, + faces: Optional[list[list[int]]] = None, position: Vector = V(0, 0, 0).freeze(), - magnitude=1.0, + magnitude: float = 1.0, extrusion_vector: Vector = V(0, 0, 1).freeze(), -): +) -> list[bmesh.types.BMVert]: bm.verts.index_update() bm.edges.index_update() bm.faces.ensure_lookup_table() @@ -242,7 +249,9 @@ def create_bm_extruded_profile( return new_verts + extruded_verts -def create_bm_door_lining(bm, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze()): +def create_bm_door_lining( + bm: bmesh.types.BMesh, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze() +) -> list[bmesh.types.BMVert]: """`thickness` of the profile is defined as list in the following order: `(SIDE, TOP)` `thickness` can be also defined just as 1 float value. @@ -303,7 +312,7 @@ def create_bm_door_lining(bm, size: Vector, thickness: list, position: Vector = return new_verts + translate_verts -def update_door_modifier_bmesh(context): +def update_door_modifier_bmesh(context: bpy.types.Context) -> None: obj = context.active_object props = obj.BIMDoorProperties @@ -400,7 +409,9 @@ def update_door_modifier_bmesh(context): inner_casing_verts = create_bm_door_lining(bm, casing_size, inner_casing_thickness, inner_casing_position) casing_verts.extend(inner_casing_verts) - def create_bm_door_panel(panel_size, panel_position, door_swing_type): + def create_bm_door_panel( + panel_size: Vector, panel_position: Vector, door_swing_type: str + ) -> list[bmesh.types.BMVert]: door_verts = [] # add door panel door_verts.extend(create_bm_box(bm, panel_size, panel_position)) diff --git a/src/bonsai/bonsai/bim/module/model/railing.py b/src/bonsai/bonsai/bim/module/model/railing.py index 2ec5da1419..9a5e9f0853 100644 --- a/src/bonsai/bonsai/bim/module/model/railing.py +++ b/src/bonsai/bonsai/bim/module/model/railing.py @@ -33,13 +33,14 @@ from bonsai.bim.module.model.decorator import ProfileDecorator from mathutils import Vector import json +from typing import Any # reference: # https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRailing.htm # https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRailingType.htm -def bm_split_edge_at_offset(edge, offset): +def bm_split_edge_at_offset(edge: bmesh.types.BMEdge, offset: float) -> dict[str, Any]: v0, v1 = edge.verts offset = offset / 2 @@ -51,7 +52,7 @@ def bm_split_edge_at_offset(edge, offset): return new_geometry -def update_railing_modifier_ifc_data(context): +def update_railing_modifier_ifc_data(context: bpy.types.Context) -> None: """should be called after new geometry settled since it's going to update ifc representation """ @@ -113,7 +114,7 @@ def update_railing_modifier_ifc_data(context): tool.Ifc.edit(obj) -def update_bbim_railing_pset(element, railing_data): +def update_bbim_railing_pset(element: ifcopenshell.entity_instance, railing_data: dict[str, Any]) -> None: pset = tool.Pset.get_element_pset(element, "BBIM_Railing") if not pset: pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Railing") @@ -121,7 +122,7 @@ def update_bbim_railing_pset(element, railing_data): ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": railing_data}) -def update_railing_modifier_bmesh(context): +def update_railing_modifier_bmesh(context: bpy.types.Context) -> None: """before using should make sure that Data contains up-to-date information. If BBIM Pset just changed should call refresh() before updating bmesh """ @@ -153,7 +154,7 @@ def update_railing_modifier_bmesh(context): if props.railing_type != "FRAMELESS_PANEL": return - def generate_frameless_panel_railing(): + def generate_frameless_panel_railing() -> None: # generating FRAMELESS_PANEL railing height = props.height thickness = props.thickness @@ -214,7 +215,7 @@ def update_railing_modifier_bmesh(context): generate_frameless_panel_railing() -def get_path_data(obj): +def get_path_data(obj: bpy.types.Object) -> dict[str, Any]: si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) bm = tool.Blender.get_bmesh_for_mesh(obj.data) @@ -468,7 +469,7 @@ class EnableEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator): return {"FINISHED"} -def cancel_editing_railing_path(context): +def cancel_editing_railing_path(context: bpy.types.Context) -> set[str]: obj = context.active_object props = obj.BIMRailingProperties diff --git a/src/bonsai/bonsai/bim/module/model/roof.py b/src/bonsai/bonsai/bim/module/model/roof.py index 0c64aee046..9d53b8cfa5 100644 --- a/src/bonsai/bonsai/bim/module/model/roof.py +++ b/src/bonsai/bonsai/bim/module/model/roof.py @@ -37,13 +37,14 @@ import mathutils.geometry from bpypolyskel import bpypolyskel import shapely from pprint import pprint +from typing import Literal, Union, Any # reference: # https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRoof.htm # https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcRoofType.htm -def bm_mesh_clean_up(bm): +def bm_mesh_clean_up(bm: bmesh.types.BMesh) -> None: # remove internal edges and faces # adding missing faces so we could rely on `e.is_boundary` later bmesh.ops.contextual_create(bm, geom=bm.edges[:]) @@ -94,7 +95,7 @@ class GenerateHippedRoof(bpy.types.Operator, tool.Ifc.Operator): return {"FINISHED"} -def is_valid_roof_footprint(bm): +def is_valid_roof_footprint(bm: bmesh.types.BMesh) -> tuple[set[str], str]: # should be bmesh to support edit mode bm.verts.ensure_lookup_table() base_z = bm.verts[0].co.z @@ -108,8 +109,14 @@ def is_valid_roof_footprint(bm): def generate_hiped_roof_bmesh( - bm, mode="ANGLE", height=1.0, roof_thickness=0.1, angle=pi / 18, rafter_edge_angle=pi / 2, mutate_current_bmesh=True -): + bm: bmesh.types.BMesh, + mode: Literal["ANGLE", "HEIGHT"] = "ANGLE", + height: float = 1.0, + roof_thickness: float = 0.1, + angle: float = pi / 18, + rafter_edge_angle: float = pi / 2, + mutate_current_bmesh: float = True, +) -> bmesh.types.BMesh: """return bmesh with gable roof geometry `mutate_current_bmesh` is a flag to indicate whether the input bmesh @@ -199,16 +206,16 @@ def generate_hiped_roof_bmesh( new_edges = [bm.edges.new([new_verts[vi] for vi in edge]) for edge in edges] new_faces = [bm.faces.new([new_verts[vi] for vi in face]) for face in faces] - def find_identical_new_vert(co): + def find_identical_new_vert(co: Vector) -> Union[bmesh.types.BMVert, None]: for v in bm.verts: if tool.Cad.is_x((co - v.co).length, 0): return v - def find_other_polygon_verts(edge): + def find_other_polygon_verts(edge: bmesh.types.BMEdge) -> list[bmesh.types.BMVert]: polygon = edge.link_faces[0] return [v for v in polygon.verts if v not in edge.verts] - def change_angle(projected_vert_co, edge_verts, new_angle): + def change_angle(projected_vert_co: Vector, edge_verts: list[bmesh.types.BMVert], new_angle: float) -> Vector: A, B = [v.co for v in edge_verts] P = projected_vert_co @@ -232,10 +239,10 @@ def generate_hiped_roof_bmesh( verts_to_rip = [] bottom_chords_to_remove = [] - def is_footprint_vert(v): + def is_footprint_vert(v: bmesh.types.BMVert) -> bool: return tool.Cad.is_x(v.co.z - footprint_z, 0) - def is_footprint_edge(edge): + def is_footprint_edge(edge: bmesh.types.BMEdge) -> bool: return all(is_footprint_vert(v) for v in edge.verts) # find footprint edges @@ -279,7 +286,9 @@ def generate_hiped_roof_bmesh( if defined_angle >= pi / 2: bottom_chords_to_remove.append(identical_edge) - def separate_vert(bm, vert, edge, new_co): + def separate_vert( + bm: bmesh.types.BMesh, vert: bmesh.types.BMVert, edge: bmesh.types.BMEdge, new_co: Vector + ) -> bmesh.types.BMVert: face = next(f for f in vert.link_faces if edge in f.edges) new_v = bmesh.utils.face_vert_separate(face, vert) new_v.co = new_co @@ -378,11 +387,11 @@ def generate_hiped_roof_bmesh( return bm -def bm_get_indices(sequence): +def bm_get_indices(sequence) -> list[int]: return [i.index for i in sequence] -def update_roof_modifier_ifc_data(context): +def update_roof_modifier_ifc_data(context: bpy.types.Context) -> None: """should be called after new geometry settled since it's going to update ifc representation """ @@ -390,7 +399,7 @@ def update_roof_modifier_ifc_data(context): props = obj.BIMRoofProperties element = tool.Ifc.get_entity(obj) - def roof_is_gabled(): + def roof_is_gabled() -> bool: pset_data = tool.Model.get_modeling_bbim_pset_data(obj, "BBIM_Roof") path_data = pset_data["data_dict"]["path_data"] angle_layer = path_data.get("gable_roof_angles", None) @@ -411,7 +420,7 @@ def update_roof_modifier_ifc_data(context): tool.Ifc.edit(obj) -def update_bbim_roof_pset(element, roof_data): +def update_bbim_roof_pset(element: ifcopenshell.entity_instance, roof_data: dict[str, Any]) -> None: pset = tool.Pset.get_element_pset(element, "BBIM_Roof") if not pset: pset = ifcopenshell.api.run("pset.add_pset", tool.Ifc.get(), product=element, name="BBIM_Roof") @@ -419,7 +428,7 @@ def update_bbim_roof_pset(element, roof_data): ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": roof_data}) -def update_roof_modifier_bmesh(context): +def update_roof_modifier_bmesh(context: bpy.types.Context) -> None: """before using should make sure that Data contains up-to-date information. If BBIM Pset just changed should call refresh() before updating bmesh """ @@ -466,7 +475,7 @@ def update_roof_modifier_bmesh(context): tool.Blender.apply_bmesh(obj.data, bm) -def get_path_data(obj): +def get_path_data(obj: bpy.types.Object) -> Union[dict[str, Any], None]: """get path data for current mesh, path data is cleaned up""" si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) @@ -699,7 +708,7 @@ class EnableEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator): return {"FINISHED"} -def cancel_editing_roof_path(context): +def cancel_editing_roof_path(context: bpy.types.Context) -> set[str]: obj = context.active_object props = obj.BIMRoofProperties diff --git a/src/bonsai/bonsai/bim/module/model/stair.py b/src/bonsai/bonsai/bim/module/model/stair.py index a353edece2..ccb24dd857 100644 --- a/src/bonsai/bonsai/bim/module/model/stair.py +++ b/src/bonsai/bonsai/bim/module/model/stair.py @@ -33,7 +33,7 @@ from bpy.props import FloatProperty, IntProperty from bpy_extras.object_utils import AddObjectHelper, object_data_add -def regenerate_stair_mesh(context): +def regenerate_stair_mesh(context: bpy.types.Context) -> None: obj = context.active_object props_kwargs = obj.BIMStairProperties.get_props_kwargs() vertices, edges, faces = tool.Model.generate_stair_2d_profile(**props_kwargs) @@ -65,7 +65,7 @@ def regenerate_stair_mesh(context): obj.data.update() -def update_ifc_stair_props(obj): +def update_ifc_stair_props(obj: bpy.types.Object) -> None: """should be called after new geometry settled since it's going to update ifc representation """ @@ -114,7 +114,7 @@ def update_ifc_stair_props(obj): tool.Ifc.edit(obj) # update related annotation objects - def get_elements_from_product(product): + def get_elements_from_product(product: ifcopenshell.entity_instance) -> list[ifcopenshell.entity_instance]: elements = [] for rel in product.ReferencedBy: if not rel.is_a("IfcRelAssignsToProduct"): diff --git a/src/bonsai/bonsai/bim/module/model/window.py b/src/bonsai/bonsai/bim/module/model/window.py index 2989184157..c5eabc6295 100644 --- a/src/bonsai/bonsai/bim/module/model/window.py +++ b/src/bonsai/bonsai/bim/module/model/window.py @@ -34,9 +34,10 @@ import ifcopenshell.util.unit from ifcopenshell.util.shape_builder import V from bmesh.types import BMVert from mathutils import Vector +from typing import Optional -def update_window_modifier_representation(context): +def update_window_modifier_representation(context: bpy.types.Context) -> None: obj = context.active_object element = tool.Ifc.get_entity(obj) props = obj.BIMWindowProperties @@ -129,7 +130,9 @@ def update_window_modifier_representation(context): tool.Model.update_simple_openings(element) -def create_bm_window_frame(bm, size: Vector, thickness: list, position: Vector = V(0, 0, 0).freeze()): +def create_bm_window_frame( + bm: bmesh.types.BMesh, size: Vector, thickness: list, 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)` @@ -194,7 +197,9 @@ def create_bm_window_frame(bm, size: Vector, thickness: list, position: Vector = return new_verts + translate_verts -def create_bm_box(bm, size: Vector = V(1, 1, 1).freeze(), position: Vector = V(0, 0, 0).freeze()): +def create_bm_box( + bm: bmesh.types.BMesh, size: Vector = V(1, 1, 1).freeze(), position: Vector = V(0, 0, 0).freeze() +) -> list[bmesh.types.BMVert]: """create a box of `size`, position box first vertex at `position`""" box_verts = bmesh.ops.create_cube(bm, size=1)["verts"] bmesh.ops.translate(bm, vec=-box_verts[0].co, verts=box_verts) @@ -204,17 +209,17 @@ def create_bm_box(bm, size: Vector = V(1, 1, 1).freeze(), position: Vector = V(0 def create_bm_window( - bm, + bm: bmesh.types.BMesh, lining_size: Vector, lining_thickness: list, lining_to_panel_offset_x, lining_to_panel_offset_y_full, frame_size: Vector, - frame_thickness, - glass_thickness, + frame_thickness: float, + glass_thickness: float, position: Vector, - x_offsets: list = None, -): + x_offsets: Optional[list] = None, +) -> tuple[list[bmesh.types.BMVert], list[bmesh.types.BMVert], list[bmesh.types.BMVert]]: """`lining_thickness` and `x_offsets` are expected to be defined as a list, similarly to `create_bm_window_frame` `thickness` argument""" @@ -241,7 +246,7 @@ def create_bm_window( return (window_lining_verts, frame_verts, glass_verts) -def update_window_modifier_bmesh(context): +def update_window_modifier_bmesh(context: bpy.types.Context) -> None: obj = context.active_object props = obj.BIMWindowProperties panel_schema = DEFAULT_PANEL_SCHEMAS[props.window_type] diff --git a/src/bonsai/bonsai/bim/module/project/prop.py b/src/bonsai/bonsai/bim/module/project/prop.py index 45db19c547..84219bc64c 100644 --- a/src/bonsai/bonsai/bim/module/project/prop.py +++ b/src/bonsai/bonsai/bim/module/project/prop.py @@ -35,32 +35,32 @@ from bpy.props import ( ) -def get_export_schema(self, context): +def get_export_schema(self: "BIMProjectProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]: if not ProjectData.is_loaded: ProjectData.load() return ProjectData.data["export_schema"] -def get_template_file(self, context): +def get_template_file(self: "BIMProjectProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]: if not ProjectData.is_loaded: ProjectData.load() return ProjectData.data["template_file"] -def get_library_file(self, context): +def get_library_file(self: "BIMProjectProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]: if not ProjectData.is_loaded: ProjectData.load() return ProjectData.data["library_file"] -def update_library_file(self, context): +def update_library_file(self: "BIMProjectProperties", context: bpy.types.Context) -> None: if self.library_file != "0": bpy.ops.bim.select_library_file( filepath=os.path.join(bpy.context.scene.BIMProperties.data_dir, "libraries", self.library_file) ) -def update_filter_mode(self, context): +def update_filter_mode(self: "BIMProjectProperties", context: bpy.types.Context) -> None: self.filter_categories.clear() if self.filter_mode == "NONE": return diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py index f1042b93c5..e35d54bde1 100644 --- a/src/bonsai/bonsai/tool/model.py +++ b/src/bonsai/bonsai/tool/model.py @@ -248,7 +248,12 @@ class Model(bonsai.core.tool.Model): return obj @classmethod - def import_profile(cls, profile, obj=None, position=None): + def import_profile( + cls, + profile: ifcopenshell.entity_instance, + obj: Optional[bpy.types.Object] = None, + position: Optional[Matrix] = None, + ) -> bpy.types.Object: """Creates new profile mesh and assigns it to `obj`, if `obj` is `None` then new "Profile" object will be created. @@ -297,7 +302,9 @@ class Model(bonsai.core.tool.Model): return obj @classmethod - def import_surface(cls, surface, obj=None): + def import_surface( + cls, surface: ifcopenshell.entity_instance, obj: Optional[bpy.types.Object] = None + ) -> bpy.types.Object: cls.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) cls.vertices = [] @@ -333,7 +340,7 @@ class Model(bonsai.core.tool.Model): return obj @classmethod - def import_curve(cls, obj, position, curve): + def import_curve(cls, obj: bpy.types.Object, position: Matrix, curve: ifcopenshell.entity_instance) -> None: offset = len(cls.vertices) if curve.is_a("IfcPolyline"): @@ -402,7 +409,7 @@ class Model(bonsai.core.tool.Model): cls.edges.append((offset, offset + 1)) @classmethod - def import_rectangle(cls, obj, position, profile): + def import_rectangle(cls, obj: bpy.types.Object, position: Matrix, profile: ifcopenshell.entity_instance) -> None: if profile.Position: p_position = Matrix(ifcopenshell.util.placement.get_axis2placement(profile.Position).tolist()) p_position.translation *= cls.unit_scale @@ -424,7 +431,7 @@ class Model(bonsai.core.tool.Model): cls.edges[-1] = (len(cls.vertices) - 1, 0) # Close the loop @classmethod - def load_openings(cls, openings): + def load_openings(cls, openings: list[ifcopenshell.entity_instance]) -> Iterable[bpy.types.Object]: if not openings: return [] ifc_import_settings = import_ifc.IfcImportSettings.factory() @@ -476,7 +483,11 @@ class Model(bonsai.core.tool.Model): } @classmethod - def get_booleans(cls, element=None, representation=None) -> list[ifcopenshell.entity_instance]: + def get_booleans( + cls, + element: Optional[ifcopenshell.entity_instance] = None, + representation: Optional[ifcopenshell.entity_instance] = None, + ) -> list[ifcopenshell.entity_instance]: if representation is None: representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") if not representation: @@ -491,7 +502,9 @@ class Model(bonsai.core.tool.Model): return booleans @classmethod - def get_manual_booleans(cls, element, representation=None) -> list[ifcopenshell.entity_instance]: + def get_manual_booleans( + cls, element: ifcopenshell.entity_instance, representation: Optional[ifcopenshell.entity_instance] = None + ) -> list[ifcopenshell.entity_instance]: pset = ifcopenshell.util.element.get_pset(element, "BBIM_Boolean") if not pset: return [] @@ -521,7 +534,7 @@ class Model(bonsai.core.tool.Model): ifcopenshell.api.run("pset.edit_pset", tool.Ifc.get(), pset=pset, properties={"Data": data}) @classmethod - def unmark_manual_booleans(cls, element, boolean_ids): + def unmark_manual_booleans(cls, element: ifcopenshell.entity_instance, boolean_ids: list[int]) -> None: # NOTE: we use use boolean_ids instead of boolean entities # so it will be possible to unmark manual booleans after they already was deleted pset = ifcopenshell.util.element.get_pset(element, "BBIM_Boolean") @@ -538,12 +551,14 @@ class Model(bonsai.core.tool.Model): ifcopenshell.api.run("pset.remove_pset", tool.Ifc.get(), product=element, pset=pset) @classmethod - def get_flow_segment_axis(cls, obj): + def get_flow_segment_axis(cls, obj: bpy.types.Object) -> tuple[Vector, Vector]: z_values = [v[2] for v in obj.bound_box] return (obj.matrix_world @ Vector((0, 0, min(z_values))), obj.matrix_world @ Vector((0, 0, max(z_values)))) @classmethod - def get_flow_segment_profile(cls, element): + def get_flow_segment_profile( + cls, element: ifcopenshell.entity_instance + ) -> Union[ifcopenshell.entity_instance, None]: material = ifcopenshell.util.element.get_material(element, should_skip_usage=True) if material and material.is_a("IfcMaterialProfileSet") and len(material.MaterialProfiles) == 1: return material.MaterialProfiles[0].Profile @@ -579,7 +594,7 @@ class Model(bonsai.core.tool.Model): return "PROFILE" @classmethod - def get_wall_axis(cls, obj, layers=None): + def get_wall_axis(cls, obj: bpy.types.Object, layers: Optional[dict[str, Any]] = None) -> dict[str, Any]: x_values = [v[0] for v in obj.bound_box] min_x = min(x_values) max_x = max(x_values) @@ -602,7 +617,9 @@ class Model(bonsai.core.tool.Model): return axes @classmethod - def handle_array_on_copied_element(cls, element, array_data=None): + def handle_array_on_copied_element( + cls, element: ifcopenshell.entity_instance, array_data: Optional[dict[str, Any]] = None + ) -> None: """if no `array_data` is provided then an array will be removed from the element""" if array_data is None: @@ -647,7 +664,9 @@ class Model(bonsai.core.tool.Model): tool.Blender.Modifier.Array.constrain_children_to_parent(element) @classmethod - def regenerate_array(cls, parent_obj, data, array_layers_to_apply=tuple()): + def regenerate_array( + cls, parent_obj: bpy.types.Object, data: list[dict[str, Any]], array_layers_to_apply: Iterable[int] = tuple() + ) -> None: """`array_layers_to_apply` - list of array layer indices to apply""" tool.Blender.Modifier.Array.remove_constraints(tool.Ifc.get_entity(parent_obj)) @@ -744,7 +763,12 @@ class Model(bonsai.core.tool.Model): bpy.context.view_layer.update() @classmethod - def replace_object_ifc_representation(cls, ifc_context, obj, new_representation): + def replace_object_ifc_representation( + cls, + ifc_context: ifcopenshell.entity_instance, + obj: bpy.types.Object, + new_representation: ifcopenshell.entity_instance, + ) -> None: ifc_file = tool.Ifc.get() ifc_element = tool.Ifc.get_entity(obj) old_representation = ifcopenshell.util.representation.get_representation( @@ -771,7 +795,7 @@ class Model(bonsai.core.tool.Model): ) @classmethod - def update_thumbnail_for_element(cls, element, refresh=False): + def update_thumbnail_for_element(cls, element: ifcopenshell.entity_instance, refresh: bool = False) -> None: if bpy.app.background: return @@ -877,7 +901,7 @@ class Model(bonsai.core.tool.Model): ) @classmethod - def get_modeling_bbim_pset_data(cls, object, pset_name): + def get_modeling_bbim_pset_data(cls, object: bpy.types.Object, pset_name: str) -> Union[dict[str, Any], None]: """get modelling BBIM pset data (eg, BBIM_Roof) and loads it's `Data` as json to `data_dict`""" element = tool.Ifc.get_entity(object) if not element: @@ -890,7 +914,7 @@ class Model(bonsai.core.tool.Model): return pset_data @classmethod - def edit_element_placement(cls, element, matrix): + def edit_element_placement(cls, element: ifcopenshell.entity_instance, matrix: Matrix) -> None: """Useful for moving objects like ports or openings - the method will ensure it will be moved in blender scene too if it exists""" obj = tool.Ifc.get_object(element) @@ -900,13 +924,13 @@ class Model(bonsai.core.tool.Model): tool.Ifc.run("geometry.edit_object_placement", product=element, matrix=matrix, is_si=True) @classmethod - def sync_object_ifc_position(cls, obj): + def sync_object_ifc_position(cls, obj: bpy.types.Object) -> None: """make sure IFC position will be in sync with the Blender object position, if object was moved in Blender""" if tool.Ifc.is_moved(obj): bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj) @classmethod - def get_element_matrix(cls, element, keep_local=False): + def get_element_matrix(cls, element: ifcopenshell.entity_instance, keep_local: bool = False) -> Matrix: placement = element.ObjectPlacement if keep_local: placement = ifcopenshell.util.placement.get_axis2placement(placement.RelativePlacement) @@ -947,23 +971,23 @@ class Model(bonsai.core.tool.Model): ) @classmethod - def is_parametric_roof_active(cls): - return (RoofData.is_loaded or not RoofData.load()) and RoofData.data["pset_data"] + def is_parametric_roof_active(cls) -> bool: + return bool((RoofData.is_loaded or not RoofData.load()) and RoofData.data["pset_data"]) @classmethod - def is_parametric_railing_active(cls): - return (RailingData.is_loaded or not RailingData.load()) and RailingData.data["pset_data"] + def is_parametric_railing_active(cls) -> bool: + return bool((RailingData.is_loaded or not RailingData.load()) and RailingData.data["pset_data"]) @classmethod - def is_parametric_window_active(cls): - return (WindowData.is_loaded or not WindowData.load()) and WindowData.data["pset_data"] + def is_parametric_window_active(cls) -> bool: + return bool((WindowData.is_loaded or not WindowData.load()) and WindowData.data["pset_data"]) @classmethod - def is_parametric_door_active(cls): - return (DoorData.is_loaded or not DoorData.load()) and DoorData.data["pset_data"] + def is_parametric_door_active(cls) -> bool: + return bool((DoorData.is_loaded or not DoorData.load()) and DoorData.data["pset_data"]) @classmethod - def get_active_stair_calculated_params(cls, pset_data=None): + def get_active_stair_calculated_params(cls, pset_data: Optional[dict[str, Any]] = None) -> dict[str, Any]: props = bpy.context.active_object.BIMStairProperties if props.is_editing: