From b2e3a4dd826fa98b43ece25d96c722ecc00c4d9c Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 18 Nov 2024 11:09:59 +0500 Subject: [PATCH] typing --- src/bonsai/bonsai/bim/module/drawing/prop.py | 2 +- .../bonsai/bim/module/geometry/helper.py | 61 +++++++++++-------- .../bonsai/bim/module/pset_template/prop.py | 1 + .../bonsai/bim/module/sequence/helper.py | 5 +- src/bonsai/bonsai/bim/module/sequence/ui.py | 3 +- src/bonsai/bonsai/core/drawing.py | 1 + src/bonsai/bonsai/tool/drawing.py | 4 +- .../ifcopenshell/util/cost.py | 4 +- .../ifcopenshell/util/date.py | 2 +- .../ifcopenshell/util/schema.py | 13 +++- 10 files changed, 62 insertions(+), 34 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/drawing/prop.py b/src/bonsai/bonsai/bim/module/drawing/prop.py index 40a7674705..556aa6948d 100644 --- a/src/bonsai/bonsai/bim/module/drawing/prop.py +++ b/src/bonsai/bonsai/bim/module/drawing/prop.py @@ -262,7 +262,7 @@ def update_titleblocks(self, context): SheetsData.data["titleblocks"] = SheetsData.titleblocks() -def update_should_draw_decorations(self, context): +def update_should_draw_decorations(self, context: bpy.types.Context) -> None: if self.should_draw_decorations: # TODO: design a proper text variable templating renderer collection = context.scene.camera.BIMObjectProperties.collection diff --git a/src/bonsai/bonsai/bim/module/geometry/helper.py b/src/bonsai/bonsai/bim/module/geometry/helper.py index 5251f842b8..830254ba49 100644 --- a/src/bonsai/bonsai/bim/module/geometry/helper.py +++ b/src/bonsai/bonsai/bim/module/geometry/helper.py @@ -27,7 +27,9 @@ import ifcopenshell.util.shape import bonsai.tool as tool from math import pi, pow from mathutils import Vector, Matrix, geometry -from typing import Union +from typing import Union, Any, TypeVar, Optional + +T = TypeVar("T") class Helper: @@ -39,7 +41,7 @@ class Helper: # edge that shares a single vertex only with that face to find the extrusion # edge. A face with the normal facing down is prioritised. A limited # dissolve ensure that faces are quads and not tris. - def auto_detect_rectangle_profile_extruded_area_solid(self, mesh: bpy.types.Mesh) -> dict: + def auto_detect_rectangle_profile_extruded_area_solid(self, mesh: bpy.types.Mesh) -> dict[str, Any]: bm = bmesh.new() bm.from_mesh(mesh) bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges) @@ -63,7 +65,7 @@ class Helper: # only ngon (this assumes the circle has at least a facetation of > 4 edges. # The extrusion direction is any edge that only shares a single vertex with # the profile. We prioritise the profile that has a downwards normal. - def auto_detect_circle_profile_extruded_area_solid(self, mesh): + def auto_detect_circle_profile_extruded_area_solid(self, mesh: bpy.types.Mesh) -> dict[str, Any]: bm = bmesh.new() bm.from_mesh(mesh) bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges) @@ -91,7 +93,7 @@ class Helper: # Failing that, it is equivalent to a rectangular profile. The extrusion # direction is any edge that only shares a single vertex with the profile. # We prioritise the profile that has a downwards normal. - def auto_detect_arbitrary_closed_profile_extruded_area_solid(self, mesh): + def auto_detect_arbitrary_closed_profile_extruded_area_solid(self, mesh: bpy.types.Mesh) -> dict[str, Any]: bm = bmesh.new() bm.from_mesh(mesh) bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges) @@ -131,7 +133,7 @@ class Helper: # set of edges. Then, connected edges (i.e. sharing a vertex) are joined to # form distinct loops. Finally, the outer loop is distinguished by being # the loop with the greatest area. - def auto_detect_arbitrary_profile_with_voids_extruded_area_solid(self, mesh): + def auto_detect_arbitrary_profile_with_voids_extruded_area_solid(self, mesh: bpy.types.Mesh) -> dict[str, Any]: bm = bmesh.new() bm.from_mesh(mesh) bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges) @@ -228,7 +230,7 @@ class Helper: # Before we begin we check that all faces are coplanar instead of finding suitable face. # Then we process the same way. # Only 2 parameters are returned as there is no extrusion. - def auto_detect_curve_bounded_plane(self, mesh, tolerance=0.001): + def auto_detect_curve_bounded_plane(self, mesh: bpy.types.Mesh, tolerance: float = 0.001) -> dict[str, Any]: bm = bmesh.new() bm.from_mesh(mesh) bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 1, verts=bm.verts, edges=bm.edges) @@ -316,9 +318,8 @@ class Helper: # An extrusion edge is an edge that shares a single vertex with a profile # face and is not on the plane of the face. - def detect_extrusion_edge(self, bm, profile_face): + def detect_extrusion_edge(self, bm: bmesh.types.BMesh, profile_face: bmesh.types.BMFace) -> Union[list[int], None]: bm.edges.ensure_lookup_table() - extrusion = None face_verts_set = set(profile_face.verts) for edge in bm.edges: unshared_verts = set(edge.verts) - face_verts_set @@ -336,7 +337,9 @@ class Helper: return [edge.verts[0].index, edge.verts[1].index] return [edge.verts[1].index, edge.verts[0].index] - def create_extruded_area_solid(self, mesh, extrusion_indices, profile_def): + def create_extruded_area_solid( + self, mesh: bpy.types.Mesh, extrusion_indices: list[int], profile_def: dict[str, Any] + ) -> ifcopenshell.entity_instance: position = self.create_ifc_axis_2_placement_3d( profile_def["curve_ucs"]["center"], profile_def["curve_ucs"]["z_axis"], profile_def["curve_ucs"]["x_axis"] ) @@ -349,13 +352,15 @@ class Helper: self.convert_si_to_unit(direction.length), ) - def create_arbitrary_closed_profile_def(self, mesh, profile_indices): + def create_arbitrary_closed_profile_def(self, mesh: bpy.types.Mesh, profile_indices: list[int]) -> dict[str, Any]: curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices) outer_curve = self.create_polyline_from_loop(mesh, profile_indices, curve_ucs) curve = self.file.createIfcArbitraryClosedProfileDef("AREA", None, outer_curve) return {"curve_ucs": curve_ucs, "curve": curve} - def create_arbitrary_profile_def_with_voids(self, mesh, profile_indices, inner_curve_indices): + def create_arbitrary_profile_def_with_voids( + self, mesh: bpy.types.Mesh, profile_indices: list[int], inner_curve_indices: list[int] + ) -> dict[str, Any]: curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices) outer_curve = self.create_polyline_from_loop(mesh, profile_indices, curve_ucs) inner_curves = [ @@ -364,7 +369,7 @@ class Helper: curve = self.file.createIfcArbitraryProfileDefWithVoids("AREA", None, outer_curve, inner_curves) return {"curve_ucs": curve_ucs, "curve": curve} - def create_rectangle_profile_def(self, mesh, profile_indices): + def create_rectangle_profile_def(self, mesh: bpy.types.Mesh, profile_indices: list[int]) -> dict[str, Any]: curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices) xdim = self.convert_si_to_unit( (mesh.vertices[profile_indices[0]].co - mesh.vertices[profile_indices[1]].co).length @@ -378,7 +383,7 @@ class Helper: curve = self.file.createIfcRectangleProfileDef("AREA", None, position, xdim, ydim) return {"curve_ucs": curve_ucs, "curve": curve} - def create_circle_profile_def(self, mesh, profile_indices): + def create_circle_profile_def(self, mesh: bpy.types.Mesh, profile_indices: list[int]) -> dict[str, Any]: curve_ucs = self.get_curve_profile_coordinate_system(mesh, profile_indices) radius = self.convert_si_to_unit( abs( @@ -395,13 +400,13 @@ class Helper: return {"curve_ucs": curve_ucs, "curve": curve} # Not used anywhere, but probably useful in the future - def get_loop_from_v_indices(self, obj, indices): + def get_loop_from_v_indices(self, obj: bpy.types.Object, indices: list[int]) -> list[int]: edges = self.get_edges_in_v_indices(obj, indices) loop = self.get_loop_from_edges(edges) loop.pop(-1) return loop - def get_loop_from_edges(self, edges): + def get_loop_from_edges(self, edges: list[bpy.types.MeshEdge]) -> list[int]: while edges: currentEdge = edges.pop() startVert = currentEdge.vertices[0] @@ -436,10 +441,10 @@ class Helper: del edges[i] return polyLine - def get_edges_in_v_indices(self, obj, indices): + def get_edges_in_v_indices(self, obj: bpy.types.Object, indices: list[int]) -> list[bpy.types.MeshEdge]: return [e for e in obj.data.edges if (e.vertices[0] in indices and e.vertices[1] in indices)] - def get_curve_profile_coordinate_system(self, mesh, loop): + def get_curve_profile_coordinate_system(self, mesh: bpy.types.Mesh, loop: list[int]) -> dict[str, Any]: profile_face = bpy.data.meshes.new("profile_face") profile_verts = [(mesh.vertices[p].co.x, mesh.vertices[p].co.y, mesh.vertices[p].co.z) for p in loop] profile_faces = [tuple(range(0, len(profile_verts)))] @@ -461,10 +466,12 @@ class Helper: "matrix": matrix.to_4x4() @ Matrix.Translation(-center), } - def convert_si_to_unit(self, co): + def convert_si_to_unit(self, co: T) -> T: return co / self.unit_scale - def create_polyline_from_loop(self, mesh, loop, curve_ucs): + def create_polyline_from_loop( + self, mesh: bpy.types.Mesh, loop: list[int], curve_ucs: dict[str, Any] + ) -> ifcopenshell.entity_instance: points = [] for point in loop: transformed_point = curve_ucs["matrix"] @ mesh.vertices[point].co @@ -472,7 +479,7 @@ class Helper: points.append(points[0]) return self.file.createIfcPolyline(points) - def create_cartesian_point(self, x, y, z=None): + def create_cartesian_point(self, x: float, y: float, z: Optional[float] = None) -> ifcopenshell.entity_instance: x = self.convert_si_to_unit(x) y = self.convert_si_to_unit(y) if z is None: @@ -480,22 +487,28 @@ class Helper: z = self.convert_si_to_unit(z) return self.file.createIfcCartesianPoint((x, y, z)) - def get_extrusion_direction(self, mesh, extrusion_indices, curve_ucs): + def get_extrusion_direction( + self, mesh: bpy.types.Mesh, extrusion_indices: list[int], curve_ucs: dict[str, Any] + ) -> Vector: return curve_ucs["matrix"] @ ( curve_ucs["center"] + (mesh.vertices[extrusion_indices[1]].co - mesh.vertices[extrusion_indices[0]].co) ) - def get_start_and_end_of_extrusion(self, profile_points, extrusion_edge): + def get_start_and_end_of_extrusion( + self, profile_points: list[int], extrusion_edge: bpy.types.MeshEdge + ) -> tuple[int, int]: if extrusion_edge.vertices[0] in profile_points: return (extrusion_edge.vertices[0], extrusion_edge.vertices[1]) return (extrusion_edge.vertices[1], extrusion_edge.vertices[0]) - def create_ifc_axis_2_placement_2d(self, point, forward): + def create_ifc_axis_2_placement_2d(self, point: Vector, forward: Vector) -> ifcopenshell.entity_instance: return self.file.createIfcAxis2Placement2D( self.create_cartesian_point(point.x, point.y), self.file.createIfcDirection((forward.x, forward.y)) ) - def create_ifc_axis_2_placement_3d(self, point, up, forward): + def create_ifc_axis_2_placement_3d( + self, point: Vector, up: Vector, forward: Vector + ) -> ifcopenshell.entity_instance: return self.file.createIfcAxis2Placement3D( self.create_cartesian_point(point.x, point.y, point.z), self.file.createIfcDirection((up.x, up.y, up.z)), diff --git a/src/bonsai/bonsai/bim/module/pset_template/prop.py b/src/bonsai/bonsai/bim/module/pset_template/prop.py index 7e171d8d04..405fa1fb0b 100644 --- a/src/bonsai/bonsai/bim/module/pset_template/prop.py +++ b/src/bonsai/bonsai/bim/module/pset_template/prop.py @@ -19,6 +19,7 @@ import os import bpy import ifcopenshell +import ifcopenshell.util.attribute from ifcopenshell.util.doc import get_attribute_doc from bonsai.bim.module.pset_template.data import PsetTemplatesData from bonsai.bim.prop import StrProperty, Attribute diff --git a/src/bonsai/bonsai/bim/module/sequence/helper.py b/src/bonsai/bonsai/bim/module/sequence/helper.py index a00943e249..eaf991dff8 100644 --- a/src/bonsai/bonsai/bim/module/sequence/helper.py +++ b/src/bonsai/bonsai/bim/module/sequence/helper.py @@ -19,7 +19,8 @@ import isodate from dateutil import parser import ifcopenshell.util.date -from datetime import timedelta +from datetime import timedelta, datetime +from typing import Union def parse_datetime(value): @@ -36,7 +37,7 @@ def parse_duration(value): return ifcopenshell.util.date.parse_duration(value) -def canonicalise_time(time): +def canonicalise_time(time: Union[datetime, None]) -> str: if not time: return "-" return time.strftime("%d/%m/%y") diff --git a/src/bonsai/bonsai/bim/module/sequence/ui.py b/src/bonsai/bonsai/bim/module/sequence/ui.py index fa850d8941..b2e6e996e5 100644 --- a/src/bonsai/bonsai/bim/module/sequence/ui.py +++ b/src/bonsai/bonsai/bim/module/sequence/ui.py @@ -28,6 +28,7 @@ from bonsai.bim.module.sequence.data import ( TaskICOMData, AnimationColorSchemeData, ) +from typing import Any class BIM_PT_status(Panel): @@ -1007,7 +1008,7 @@ class BIM_PT_work_calendars(Panel): if self.props.active_work_time_id == work_time["id"]: self.draw_editable_work_time_ui(work_time) - def draw_editable_work_time_ui(self, work_time): + def draw_editable_work_time_ui(self, work_time: dict[str, Any]) -> None: draw_attributes(self.props.work_time_attributes, self.layout) if work_time["RecurrencePattern"]: self.draw_editable_recurrence_pattern_ui( diff --git a/src/bonsai/bonsai/core/drawing.py b/src/bonsai/bonsai/core/drawing.py index f77f771d71..0a1ec7d5eb 100644 --- a/src/bonsai/bonsai/core/drawing.py +++ b/src/bonsai/bonsai/core/drawing.py @@ -57,6 +57,7 @@ def edit_assigned_product( ifc: tool.Ifc, drawing: tool.Drawing, obj: bpy.types.Object, product: Optional[ifcopenshell.entity_instance] = None ) -> None: element = ifc.get_entity(obj) + assert element existing_product = drawing.get_assigned_product(element) if existing_product != product: if existing_product: diff --git a/src/bonsai/bonsai/tool/drawing.py b/src/bonsai/bonsai/tool/drawing.py index 8fa654b8f1..04baa885bf 100644 --- a/src/bonsai/bonsai/tool/drawing.py +++ b/src/bonsai/bonsai/tool/drawing.py @@ -48,7 +48,7 @@ from shapely.ops import unary_union from lxml import etree from mathutils import Vector, Matrix from fractions import Fraction -from typing import Optional, Union, Iterable, Any, Literal +from typing import Optional, Union, Iterable, Any, Literal, Sequence from pathlib import Path @@ -212,7 +212,7 @@ class Drawing(bonsai.core.tool.Drawing): @classmethod def is_annotation_object_type( - cls, element: ifcopenshell.entity_instance, object_types: Union[str, list[str]] + cls, element: ifcopenshell.entity_instance, object_types: Union[str, Sequence[str]] ) -> bool: if not isinstance(object_types, collections.abc.Iterable): object_types = [object_types] diff --git a/src/ifcopenshell-python/ifcopenshell/util/cost.py b/src/ifcopenshell-python/ifcopenshell/util/cost.py index cbb19dd1a3..b63976a4ed 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/cost.py +++ b/src/ifcopenshell-python/ifcopenshell/util/cost.py @@ -56,7 +56,9 @@ def get_total_quantity(root_element: ifcopenshell.entity_instance) -> Union[floa def calculate_applied_value( - root_element: ifcopenshell.entity_instance, cost_value: ifcopenshell.entity_instance, category_filter=None + root_element: ifcopenshell.entity_instance, + cost_value: ifcopenshell.entity_instance, + category_filter: Optional[str] = None, ) -> float: if cost_value.ArithmeticOperator and cost_value.Components: component_values = [] diff --git a/src/ifcopenshell-python/ifcopenshell/util/date.py b/src/ifcopenshell-python/ifcopenshell/util/date.py index 50352544f7..d44a3e999e 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/date.py +++ b/src/ifcopenshell-python/ifcopenshell/util/date.py @@ -211,7 +211,7 @@ def string_to_duration(duration_string): return isodate.duration_isoformat(datetime.timedelta(days=days, hours=hours, minutes=minutes, seconds=seconds)) -def parse_duration(value): +def parse_duration(value: Union[str, None]) -> Union[datetime.timedelta, None]: if not value: return None if isinstance(value, str): diff --git a/src/ifcopenshell-python/ifcopenshell/util/schema.py b/src/ifcopenshell-python/ifcopenshell/util/schema.py index 2587bb0dd1..d6e2988446 100644 --- a/src/ifcopenshell-python/ifcopenshell/util/schema.py +++ b/src/ifcopenshell-python/ifcopenshell/util/schema.py @@ -246,6 +246,9 @@ class BatchReassignClass: class Migrator: + migrated_ids: dict[int, int] + attribute_overrides: dict[int, dict[int, str]] + def __init__(self): self.migrated_ids = {} self.attribute_overrides = {} @@ -308,7 +311,7 @@ class Migrator: "User": None, } - def preprocess(self, old_file: ifcopenshell.file, new_file: ifcopenshell.file): + def preprocess(self, old_file: ifcopenshell.file, new_file: ifcopenshell.file) -> None: to_delete = set() if old_file.schema == "IFC2X3" and new_file.schema == "IFC4": @@ -381,7 +384,13 @@ class Migrator: new_element = new_file.create_entity(self.class_2x3_to_4[ifc_class]) return new_element - def migrate_attributes(self, element, new_file, new_element, new_element_schema): + def migrate_attributes( + self, + element: ifcopenshell.entity_instance, + new_file: ifcopenshell.file, + new_element: ifcopenshell.entity_instance, + new_element_schema: ifcopenshell_wrapper.declaration, + ) -> ifcopenshell.entity_instance: for attribute_index, value in self.attribute_overrides.get(element.id(), {}).items(): new_element[attribute_index] = value for i, attribute in enumerate(new_element_schema.all_attributes()):