From 9188dc60ab60ddf8f0a5518811ef56f05d9fe781 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Wed, 14 May 2025 11:46:20 +0500 Subject: [PATCH] typing --- .../bonsai/bim/module/drawing/operator.py | 33 ++++--- .../bonsai/bim/module/drawing/svgwriter.py | 95 +++++++++++-------- 2 files changed, 77 insertions(+), 51 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/drawing/operator.py b/src/bonsai/bonsai/bim/module/drawing/operator.py index 7d26f4b28d..9128d22e9c 100644 --- a/src/bonsai/bonsai/bim/module/drawing/operator.py +++ b/src/bonsai/bonsai/bim/module/drawing/operator.py @@ -410,10 +410,15 @@ class CreateDrawing(bpy.types.Operator): bpy.ops.render.render(write_still=True) else: previous_visibility: dict[str, bool] = {} - for obj in tool.Blender.get_object_bim_props(self.camera).collection.objects: - if bpy.context.view_layer.objects.get(obj.name): + collection = tool.Blender.get_object_bim_props(self.camera).collection + assert collection + # Hie annotations. + for obj in collection.objects: + if context.view_layer.objects.get(obj.name): previous_visibility[obj.name] = obj.hide_get() obj.hide_set(True) + + # Hide objects that shouldn't appear on render - empties, camera, grids, etc. for obj in context.visible_objects: if ( (not obj.data and not obj.instance_collection) @@ -442,15 +447,15 @@ class CreateDrawing(bpy.types.Operator): return svg_path def get_linework_contexts(self, ifc: ifcopenshell.file, target_view: str) -> LineworkContexts: - plan_body_target_contexts = [] - plan_body_model_contexts = [] - model_body_target_contexts = [] - model_body_model_contexts = [] + plan_body_target_contexts: list[int] = [] + plan_body_model_contexts: list[int] = [] + model_body_target_contexts: list[int] = [] + model_body_model_contexts: list[int] = [] - plan_annotation_target_contexts = [] - plan_annotation_model_contexts = [] - model_annotation_target_contexts = [] - model_annotation_model_contexts = [] + plan_annotation_target_contexts: list[int] = [] + plan_annotation_model_contexts: list[int] = [] + model_annotation_target_contexts: list[int] = [] + model_annotation_model_contexts: list[int] = [] for rep_context in ifc.by_type("IfcGeometricRepresentationContext"): if rep_context.is_a("IfcGeometricRepresentationSubContext"): @@ -522,8 +527,8 @@ class CreateDrawing(bpy.types.Operator): target_view: str, ) -> None: drawing_elements = drawing_elements.copy() - contexts: list[list[int]] = getattr(contexts, context_type) - for context in contexts: + contexts_: list[list[int]] = getattr(contexts, context_type) + for context in contexts_: with profile(f"Processing {context_type} context"): if not context or not drawing_elements: continue @@ -546,7 +551,7 @@ class CreateDrawing(bpy.types.Operator): tree.add_element(elem) drawing_elements -= processed - def generate_bisect_linework(self, context: bpy.types.Context, root): + def generate_bisect_linework(self, context: bpy.types.Context, root) -> None: camera_matrix_i = context.scene.camera.matrix_world.inverted() group = root.find("{http://www.w3.org/2000/svg}g") @@ -581,7 +586,7 @@ class CreateDrawing(bpy.types.Operator): path.attrib["d"] = d group.append(g) - def generate_wall_layers(self, context: bpy.types.Context, root): + def generate_wall_layers(self, context: bpy.types.Context, root) -> None: for el in root.findall(".//{http://www.w3.org/2000/svg}g[@{http://www.ifcopenshell.org/ns}guid]"): if "projection" in el.get("class", "").split(): continue diff --git a/src/bonsai/bonsai/bim/module/drawing/svgwriter.py b/src/bonsai/bonsai/bim/module/drawing/svgwriter.py index 0aaa66ef50..9b42b01c54 100644 --- a/src/bonsai/bonsai/bim/module/drawing/svgwriter.py +++ b/src/bonsai/bonsai/bim/module/drawing/svgwriter.py @@ -22,6 +22,7 @@ import bpy import math import bmesh import shutil +from bpy.types import SplineBezierPoints, SplinePoints import mathutils import xml.etree.ElementTree as ET import svgwrite @@ -37,7 +38,7 @@ from bonsai.bim.module.drawing.data import DrawingsData from bonsai.bim.module.drawing.data import DecoratorData from math import pi, ceil, atan, degrees, acos from mathutils import geometry, Vector -from typing import Optional, Self +from typing import Optional, Self, Union, Sequence, Callable from pathlib import Path @@ -59,6 +60,8 @@ class External(svgwrite.container.Group): class SvgWriter: + metadata: list[str] + def __init__(self): self.data_dir = None self.human_scale = "NTS" @@ -176,12 +179,20 @@ class SvgWriter: for child in root: self.svg.defs.add(External(child)) - def draw_annotations(self, annotations, precision, decimal_places): + def draw_annotations( + self, + annotations: list[ifcopenshell.entity_instance], + precision: Union[float, None], + decimal_places: Union[int, None], + ) -> Self: self.precision = precision self.decimal_places = decimal_places for element in annotations: obj = tool.Ifc.get_object(element) - if not obj or (object_type := ifcopenshell.util.element.get_predefined_type(element)) == "DRAWING": + if ( + not isinstance(obj, bpy.types.Object) + or (object_type := ifcopenshell.util.element.get_predefined_type(element)) == "DRAWING" + ): continue elif object_type == "GRID": self.draw_grid_annotation(obj) @@ -256,11 +267,12 @@ class SvgWriter: get_text, tag, dimension_data, text_position=text_position, angle=angle, class_str="SECTIONLEVEL" ) - def draw_stair_annotation(self, obj): + def draw_stair_annotation(self, obj: bpy.types.Object) -> None: x_offset = self.raw_width / 2 y_offset = self.raw_height / 2 matrix_world = obj.matrix_world classes = self.get_attribute_classes(obj) + assert isinstance(obj.data, bpy.types.Curve) for spline in obj.data.splines: points = self.get_spline_points(spline) projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points] @@ -284,7 +296,7 @@ class SvgWriter: ) ) - def draw_grid_annotation(self, obj): + def draw_grid_annotation(self, obj: bpy.types.Object) -> None: x_offset = self.raw_width / 2 y_offset = self.raw_height / 2 matrix_world = obj.matrix_world @@ -358,6 +370,7 @@ class SvgWriter: def get_attribute_classes(self, obj: bpy.types.Object) -> list[str]: element = tool.Ifc.get_entity(obj) + assert element global_id = "GlobalId-{}".format(element.GlobalId) predefined_type = "PredefinedType-" + tool.Drawing.canonicalise_class_name( str(ifcopenshell.util.element.get_predefined_type(element)) @@ -374,7 +387,7 @@ class SvgWriter: ) return classes - def draw_line_annotation(self, obj): + def draw_line_annotation(self, obj: bpy.types.Object) -> None: # TODO: properly scope these offsets x_offset = self.raw_width / 2 y_offset = self.raw_height / 2 @@ -397,7 +410,7 @@ class SvgWriter: elif isinstance(obj.data, bpy.types.Mesh): self.draw_edge_annotation(obj, classes) - def draw_edge_annotation(self, obj, classes): + def draw_edge_annotation(self, obj: bpy.types.Object, classes: list[str]) -> None: predefined_type = classes[2].split("-", 1)[1] x_offset = self.raw_width / 2 @@ -415,7 +428,7 @@ class SvgWriter: self.svg.line(start=start * self.svg_scale, end=end * self.svg_scale, class_=" ".join(classes)) ) - def draw_batting_annotation(): + def draw_batting_annotation() -> None: v0_global = matrix_world @ obj.data.vertices[0].co.xyz v1_global = matrix_world @ obj.data.vertices[1].co.xyz v0 = self.project_point_onto_camera(v0_global) @@ -481,41 +494,42 @@ class SvgWriter: ) self.svg.add(self.svg.polyline(points=points, class_=" ".join(classes), style=polyline_style)) - def draw_revision_cloud_annotation(): + def draw_revision_cloud_annotation() -> None: segment_width = 15.0 base_height = 1 width = 5 - def get_svg_half_circle(height, width): + def get_svg_half_circle(height: float, width: float) -> str: cp0 = f"0,-{height}" cp1 = f"{width},-{height}" end_point = f"{width},0" circle = f"c{cp0} {cp1} {end_point}" return circle - def get_revision_pattern(base_offset): + def get_revision_pattern(base_offset: Vector) -> str: pattern = f"m{base_offset.x},{base_offset.y}" pattern += " " + get_svg_half_circle(2 * base_height, width) pattern += " " + get_svg_half_circle(2.5 * base_height, width) pattern += " " + get_svg_half_circle(1.5 * base_height, width) return pattern - def get_scale(size, direction): + def get_scale(size: float, direction: Vector) -> str: original_edge = direction * size current_svg_segments = ceil(size / segment_width) * segment_width * direction scale = [1 if original_edge[i] == 0 else original_edge[i] / current_svg_segments[i] for i in range(2)] return "scale(%f, %f)" % (scale[0], scale[1]) - def poly_to_edges(poly): - edges = [] + def poly_to_edges(poly: Sequence[int]) -> list[tuple[int, int]]: + edges: list[tuple[int, int]] = [] n_verts = len(poly) - lats_index = n_verts - 1 + last_index = n_verts - 1 for i in range(len(poly)): - edge = [poly[i], (poly[i + 1]) if i != lats_index else poly[0]] + edge = (poly[i], (poly[i + 1]) if i != last_index else poly[0]) edges.append(edge) return edges element = tool.Ifc.get_entity(obj) + assert element safe_offset_x = 2.0 marker_width = segment_width + safe_offset_x * 2 market_height = 15.0 @@ -573,7 +587,7 @@ class SvgWriter: ) self.svg.add(polyline) - def draw_section_annotation(): + def draw_section_annotation() -> None: display_data = DecoratorData.get_section_markers_display_data(obj) connect_markers = display_data["connect_markers"] @@ -624,7 +638,7 @@ class SvgWriter: for edge in obj.data.edges: draw_simple_edge_annotation(*edge.vertices[:]) - def draw_leader_annotation(self, obj): + def draw_leader_annotation(self, obj: bpy.types.Object) -> None: self.draw_line_annotation(obj) spline = obj.data.splines[0] spline_points = spline.bezier_points if spline.bezier_points else spline.points @@ -634,7 +648,7 @@ class SvgWriter: position = Vector((0, 0, 0)) self.draw_text_annotation(obj, position) - def draw_section_annotation(self, obj): + def draw_section_annotation(self, obj: bpy.types.Object) -> None: x_offset = self.raw_width / 2 y_offset = self.raw_height / 2 @@ -688,7 +702,7 @@ class SvgWriter: ) ) - def draw_elevation_annotation(self, obj): + def draw_elevation_annotation(self, obj: bpy.types.Object) -> None: x_offset = self.raw_width / 2 y_offset = self.raw_height / 2 symbol_position = self.project_point_onto_camera(obj.location) @@ -927,7 +941,7 @@ class SvgWriter: symbol_transform = self.get_symbol_transform(svg_position_str) self.draw_symbol(svg_id, symbol_transform) - def draw_break_annotations(self, obj): + def draw_break_annotations(self, obj: bpy.types.Object) -> None: x_offset = self.raw_width / 2 y_offset = self.raw_height / 2 @@ -950,18 +964,20 @@ class SvgWriter: d = "M{}".format(d[1:]) path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes))) - def draw_plan_level_annotation(self, obj): + def draw_plan_level_annotation(self, obj: bpy.types.Object) -> None: offset = Vector([self.raw_width, self.raw_height]) / 2 matrix_world = obj.matrix_world classes = self.get_attribute_classes(obj) element = tool.Ifc.get_entity(obj) + assert element description = element.Description dimension_data = DecoratorData.get_dimension_data(obj) suppress_zero_inches = dimension_data["suppress_zero_inches"] base_offset_y = 1.0 + assert isinstance(obj.data, bpy.types.Curve) for spline in obj.data.splines: points = self.get_spline_points(spline) projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points] @@ -1000,7 +1016,8 @@ class SvgWriter: box_alignment=box_alignment, ) - def draw_angle_annotations(self, obj): + def draw_angle_annotations(self, obj: bpy.types.Object) -> None: + assert isinstance(obj.data, bpy.types.Curve) points = obj.data.splines[0].points # [1, 2, 3, 4, 5] -> [[1, 2, 3], [2, 3, 4], [3, 4, 5]] points_chunked = [points[i : i + 3] for i in range(len(points) - 2)] @@ -1009,18 +1026,18 @@ class SvgWriter: points_chunk = [obj.matrix_world @ p.co.xyz for p in points_chunk] self.draw_svg_3point_arc(obj, points_chunk) - def draw_svg_3point_arc(self, obj, angle_points): + def draw_svg_3point_arc(self, obj: bpy.types.Object, angle_points: list[Vector]) -> None: """`angle_points` are expected to be already in world space""" # This implementation uses an SVG arc, which means that it can only draw # arcs that are orthogonal to the view (e.g. not arcs in 3D). # Gosh this is bad code :( - def position_on_svg(p): + def position_on_svg(p: Vector) -> Vector: p = self.project_point_onto_camera(p) offset = Vector([self.raw_width / 2, self.raw_height / 2]) return (offset + p.xy * Vector((1, -1))) * self.svg_scale - def get_angle_value(): + def get_angle_value() -> float: """points should be in world space""" # calculate arc angle, need to make sure we do it in world space v0, v1, v2 = angle_points @@ -1104,10 +1121,11 @@ class SvgWriter: classes = self.get_attribute_classes(obj) path = self.svg.add(self.svg.path(d=d, class_=" ".join(classes))) - def draw_radius_annotations(self, obj): + def draw_radius_annotations(self, obj: bpy.types.Object) -> None: offset = Vector([self.raw_width, self.raw_height]) / 2 classes = self.get_attribute_classes(obj) element = tool.Ifc.get_entity(obj) + assert element tag = element.Description matrix_world = obj.matrix_world @@ -1141,7 +1159,7 @@ class SvgWriter: get_text, tag, dimension_data, text_position=text_position, class_str="RADIUS", box_alignment="center" ) - def draw_dimension_text(self, get_text, tag, dimension_data, **create_text_kwargs): + def draw_dimension_text(self, get_text: Callable[[], str], tag, dimension_data, **create_text_kwargs) -> None: prefix = dimension_data["text_prefix"] suffix = dimension_data["text_suffix"] show_description_only = dimension_data["show_description_only"] @@ -1166,12 +1184,13 @@ class SvgWriter: for text in text_tags: self.svg.add(text) - def draw_fall_annotations(self, obj): + def draw_fall_annotations(self, obj: bpy.types.Object) -> None: x_offset = self.raw_width / 2 y_offset = self.raw_height / 2 classes = self.get_attribute_classes(obj) element = tool.Ifc.get_entity(obj) matrix_world = obj.matrix_world + assert isinstance(obj.data, bpy.types.Curve) for spline in obj.data.splines: points = self.get_spline_points(spline) projected_points = [self.project_point_onto_camera(matrix_world @ p.co.xyz) for p in points] @@ -1236,13 +1255,14 @@ class SvgWriter: text_style = SvgWriter.get_box_alignment_parameters("center") self.svg.add(self.svg.text(tag, insert=tuple(text_position), class_="RADIUS", **text_style)) - def draw_diameter_annotations(self, obj): + def draw_diameter_annotations(self, obj: bpy.types.Object) -> None: classes = self.get_attribute_classes(obj) matrix_world = obj.matrix_world element = tool.Ifc.get_entity(obj) dimension_text = element.Description dimension_data = DecoratorData.get_dimension_data(obj) + assert isinstance(obj.data, bpy.types.Curve) for spline in obj.data.splines: points = self.get_spline_points(spline) for i, p in enumerate(points): @@ -1264,13 +1284,14 @@ class SvgWriter: custom_unit=dimension_data["custom_unit"], ) - def draw_dimension_annotations(self, obj): + def draw_dimension_annotations(self, obj: bpy.types.Object) -> None: classes = self.get_attribute_classes(obj) matrix_world = obj.matrix_world element = tool.Ifc.get_entity(obj) dimension_text = element.Description dimension_data = DecoratorData.get_dimension_data(obj) + assert isinstance(obj.data, bpy.types.Curve) for spline in obj.data.splines: points = self.get_spline_points(spline) for i in range(len(points) - 1): @@ -1289,7 +1310,7 @@ class SvgWriter: custom_unit=dimension_data["custom_unit"], ) - def draw_measureit_arch_dimension_annotations(self): + def draw_measureit_arch_dimension_annotations(self) -> None: try: import MeasureIt_ARCH.measureit_arch_external_utils @@ -1303,9 +1324,9 @@ class SvgWriter: def draw_dimension_annotation( self, - v0_global, - v1_global, - classes, + v0_global: Vector, + v1_global: Vector, + classes: list[str], dimension_text=None, text_format=lambda x: x, show_description_only=False, @@ -1314,7 +1335,7 @@ class SvgWriter: text_suffix="", fill_bg=False, custom_unit=None, - ): + ) -> None: offset = Vector([self.raw_width, self.raw_height]) / 2 v0 = self.project_point_onto_camera(v0_global) v1 = self.project_point_onto_camera(v1_global) @@ -1446,7 +1467,7 @@ class SvgWriter: Vector(self.camera_projection), ) - def get_spline_points(self, spline): + def get_spline_points(self, spline: bpy.types.Spline) -> Union[SplineBezierPoints, SplinePoints]: return spline.bezier_points if spline.bezier_points else spline.points def draw_polygon(self, polygon, position):