diff --git a/src/bonsai/bonsai/bim/module/cad/operator.py b/src/bonsai/bonsai/bim/module/cad/operator.py index 32dfde77e7..5978ddff20 100644 --- a/src/bonsai/bonsai/bim/module/cad/operator.py +++ b/src/bonsai/bonsai/bim/module/cad/operator.py @@ -25,6 +25,7 @@ import bonsai.tool as tool from mathutils import Vector, Matrix from math import pi, radians, sin, cos, sqrt import ifcopenshell.util.unit +from typing import Union messages = { @@ -622,7 +623,7 @@ class AddIfcArcIndexFillet(bpy.types.Operator): bl_idname = "bim.add_ifcarcindex_fillet" bl_label = "Add Arc Index Fillet" bl_options = {"REGISTER", "UNDO"} - radius: bpy.props.FloatProperty(name="Radius", default=0.1) + radius: bpy.props.FloatProperty(name="Radius", default=0.1, subtype="DISTANCE") @classmethod def poll(cls, context): @@ -644,7 +645,7 @@ class AddIfcArcIndexFillet(bpy.types.Operator): self.create_arc(context) return {"FINISHED"} - def has_selected_existing_arc(self, context): + def has_selected_existing_arc(self, context: bpy.types.Context) -> bool: obj = context.active_object bm = bmesh.from_edit_mesh(obj.data) verts = [v for v in bm.verts if v.select and not v.hide] @@ -662,8 +663,9 @@ class AddIfcArcIndexFillet(bpy.types.Operator): return True except: pass # Potentially fail if the vert has been removed in the previous operation + return False - def change_radius(self, context): + def change_radius(self, context: bpy.types.Context) -> None: obj = context.active_object bm = bmesh.from_edit_mesh(obj.data) edges = [e for e in bm.edges if e.select and not e.hide] diff --git a/src/bonsai/bonsai/bim/module/cad/workspace.py b/src/bonsai/bonsai/bim/module/cad/workspace.py index 3da0160b94..c5e39e4217 100644 --- a/src/bonsai/bonsai/bim/module/cad/workspace.py +++ b/src/bonsai/bonsai/bim/module/cad/workspace.py @@ -23,6 +23,7 @@ import bonsai.bim.module.type.prop as type_prop import ifcopenshell.util.unit from bpy.types import WorkSpaceTool from bonsai.bim.module.model.data import AuthoringData, RailingData, RoofData +from typing import Union # TODO duplicate code in cad/workspace and model/workspace @@ -329,7 +330,9 @@ def add_header_apply_button(layout, text, apply_operator, cancel_operator, ui_co row.label(text="Tools") -def add_layout_hotkey_operator(layout, text, hotkey, description, ui_context=""): +def add_layout_hotkey_operator( + layout: bpy.types.UILayout, text: str, hotkey: str, description: Union[str, None], ui_context: str = "" +) -> bpy.types.OperatorProperties: parts = hotkey.split("_") modifier, key = parts op_text = "" if ui_context == "TOOL_HEADER" else text diff --git a/src/bonsai/bonsai/bim/module/drawing/workspace.py b/src/bonsai/bonsai/bim/module/drawing/workspace.py index b10fe4c1a6..b5469ddd90 100644 --- a/src/bonsai/bonsai/bim/module/drawing/workspace.py +++ b/src/bonsai/bonsai/bim/module/drawing/workspace.py @@ -24,6 +24,7 @@ import bonsai.tool as tool from bonsai.bim.module.drawing.data import DecoratorData, AnnotationData from bonsai.bim.helper import prop_with_search from bpy.types import WorkSpaceTool +from typing import Union class LaunchAnnotationTypeManager(bpy.types.Operator): @@ -131,7 +132,9 @@ class AnnotationTool(WorkSpaceTool): AnnotationToolUI.draw(context, layout) -def add_layout_hotkey_operator(layout, text, hotkey, description): +def add_layout_hotkey_operator( + layout: bpy.types.UILayout, text: str, hotkey: str, description: Union[str, None] +) -> tuple[bpy.types.OperatorProperties, bpy.types.UILayout]: modifiers = { "A": "EVENT_ALT", "S": "EVENT_SHIFT", diff --git a/src/bonsai/bonsai/bim/module/model/polyline.py b/src/bonsai/bonsai/bim/module/model/polyline.py index 215a3505fe..0d09f87035 100644 --- a/src/bonsai/bonsai/bim/module/model/polyline.py +++ b/src/bonsai/bonsai/bim/module/model/polyline.py @@ -38,7 +38,7 @@ from math import pi, sin, cos, degrees from mathutils import Vector, Matrix from bonsai.bim.module.model.opening import FilledOpeningGenerator from bonsai.bim.module.model.decorator import PolylineDecorator -from typing import Optional +from typing import Optional, Union, Literal from lark import Lark, Transformer @@ -47,7 +47,7 @@ class PolylineOperator: """ """ @classmethod - def poll(cls, context): + def poll(cls, context: bpy.types.Context) -> bool: return context.space_data.type == "VIEW_3D" def __init__(self): @@ -103,7 +103,7 @@ class PolylineOperator: self.tool_state = tool.Polyline.create_tool_state() - def recalculate_inputs(self, context): + def recalculate_inputs(self, context: bpy.types.Context) -> Union[bool, None]: if self.number_input: is_valid, self.number_output = tool.Polyline.validate_input(self.number_output, self.input_type) self.input_ui.set_value(self.input_type, self.number_output) @@ -121,7 +121,7 @@ class PolylineOperator: tool.Blender.update_viewport() return is_valid - def choose_axis(self, event, x=True, y=True, z=False): + def choose_axis(self, event: bpy.types.Event, x: bool = True, y: bool = True, z: bool = False) -> None: if x: if not event.shift and event.value == "PRESS" and event.type == "X": self.tool_state.axis_method = "X" if self.tool_state.axis_method != event.type else None @@ -142,7 +142,7 @@ class PolylineOperator: PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) tool.Blender.update_viewport() - def choose_plane(self, event, x=True, y=True, z=True): + def choose_plane(self, event: bpy.types.Event, x: bool = True, y: bool = True, z: bool = True) -> None: if x: if event.shift and event.value == "PRESS" and event.type == "X": self.tool_state.use_default_container = False @@ -164,7 +164,7 @@ class PolylineOperator: self.tool_state.axis_method = None tool.Blender.update_viewport() - def handle_instructions(self, context, custom_instructions=""): + def handle_instructions(self, context: bpy.types.Context, custom_instructions: str = "") -> None: self.snap_info = f"""| Axis: {self.tool_state.axis_method} Plane: {self.tool_state.plane_method} @@ -172,7 +172,7 @@ class PolylineOperator: """ context.workspace.status_text_set(self.instructions + custom_instructions + self.snap_info) - def handle_lock_axis(self, context, event): + def handle_lock_axis(self, context: bpy.types.Context, event: bpy.types.Event) -> None: if event.value == "PRESS" and event.type == "A": self.tool_state.lock_axis = False if self.tool_state.lock_axis else True self.tool_state.snap_angle = self.input_ui.get_number_value("WORLD_ANGLE") @@ -191,7 +191,7 @@ class PolylineOperator: PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) tool.Blender.update_viewport() - def handle_keyboard_input(self, context, event): + def handle_keyboard_input(self, context: bpy.types.Context, event: bpy.types.Event) -> None: if self.tool_state.is_input_on and event.value == "PRESS" and event.type == "TAB": self.recalculate_inputs(context) @@ -269,7 +269,7 @@ class PolylineOperator: PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) tool.Blender.update_viewport() - def handle_inserting_polyline(self, context, event): + def handle_inserting_polyline(self, context: bpy.types.Context, event: bpy.types.Event) -> None: if not self.tool_state.is_input_on and event.value == "RELEASE" and event.type == "LEFTMOUSE": result = tool.Polyline.insert_polyline_point(self.input_ui, self.tool_state) if result: @@ -301,7 +301,7 @@ class PolylineOperator: PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) tool.Blender.update_viewport() - def handle_snap_selection(self, context, event): + def handle_snap_selection(self, context: bpy.types.Context, event: bpy.types.Event) -> None: if event.value == "PRESS" and event.type == "M": self.snapping_points = tool.Snap.modify_snapping_point_selection( self.snapping_points, lock_axis=self.tool_state.lock_axis @@ -310,7 +310,9 @@ class PolylineOperator: PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) tool.Blender.update_viewport() - def handle_cancelation(self, context, event): + def handle_cancelation( + self, context: bpy.types.Context, event: bpy.types.Event + ) -> Union[None, set[Literal["CANCELLED"]]]: if self.tool_state.is_input_on: if event.value == "RELEASE" and event.type in {"ESC"}: self.recalculate_inputs(context) @@ -329,7 +331,9 @@ class PolylineOperator: tool.Blender.update_viewport() return {"CANCELLED"} - def handle_mouse_move(self, context, event, should_round=False): + def handle_mouse_move( + self, context: bpy.types.Context, event: bpy.types.Event, should_round: bool = False + ) -> Union[None, set[Literal["RUNNING_MODAL"]]]: if not self.tool_state.is_input_on: if event.type == "MOUSEMOVE" or event.type == "INBETWEEN_MOUSEMOVE": self.mousemove_count += 1 @@ -368,13 +372,13 @@ class PolylineOperator: tool.Polyline.remove_last_polyline_point() tool.Blender.update_viewport() - def modal(self, context, event): + def modal(self, context: bpy.types.Context, event: bpy.types.Event) -> Union[set[str], None]: PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) tool.Blender.update_viewport() if event.type in {"MIDDLEMOUSE", "WHEELUPMOUSE", "WHEELDOWNMOUSE"}: return {"PASS_THROUGH"} - def invoke(self, context, event): + def invoke(self, context: bpy.types.Context, event: bpy.types.Event) -> None: PolylineDecorator.install(context) tool.Snap.clear_snapping_point() diff --git a/src/bonsai/bonsai/bim/module/model/workspace.py b/src/bonsai/bonsai/bim/module/model/workspace.py index c53d9662f9..a7366bf0c3 100644 --- a/src/bonsai/bonsai/bim/module/model/workspace.py +++ b/src/bonsai/bonsai/bim/module/model/workspace.py @@ -29,7 +29,7 @@ from bpy.types import WorkSpaceTool, Menu from bonsai.bim.module.model.data import AuthoringData, ItemData from bonsai.bim.module.system.data import PortData from bonsai.bim.module.model.prop import get_ifc_class -from typing import Optional +from typing import Optional, Union # TODO duplicate code in cad/workspace and model/workspace @@ -234,7 +234,9 @@ class CableTool(BimTool): ifc_element_type = "IfcCableSegmentType" -def add_layout_hotkey_operator(layout, text, hotkey, description, ui_context=""): +def add_layout_hotkey_operator( + layout: bpy.types.UILayout, text: str, hotkey: str, description: Union[str, None], ui_context: str = "" +) -> bpy.types.OperatorProperties: parts = hotkey.split("_") if hotkey else [] modifier, key = (parts + ["", ""])[:2] diff --git a/src/bonsai/bonsai/tool/model.py b/src/bonsai/bonsai/tool/model.py index 6e309ed7af..ab05aa8d79 100644 --- a/src/bonsai/bonsai/tool/model.py +++ b/src/bonsai/bonsai/tool/model.py @@ -26,10 +26,12 @@ import numpy as np import ifcopenshell import ifcopenshell.api import ifcopenshell.api.pset +import ifcopenshell.geom import ifcopenshell.util.element -import ifcopenshell.util.unit import ifcopenshell.util.placement import ifcopenshell.util.representation +import ifcopenshell.util.shape +import ifcopenshell.util.unit import bonsai.core.geometry import bonsai.core.tool import bonsai.tool as tool @@ -1739,6 +1741,7 @@ class Model(bonsai.core.tool.Model): if position is None: position = Matrix() position_i = position.inverted() + assert isinstance(position_i, Matrix) groups = {"IFCARCINDEX": [], "IFCCIRCLE": []} for i, group in enumerate(obj.vertex_groups): @@ -1781,7 +1784,7 @@ class Model(bonsai.core.tool.Model): loop_edges = list(bm.edges) # Create loops from edges - loops = [] + loops: list[list[bmesh.types.BMEdge]] = [] while loop_edges: edge = loop_edges.pop() loop = [edge] @@ -1802,7 +1805,7 @@ class Model(bonsai.core.tool.Model): tmp = ifcopenshell.file(schema=tool.Ifc.get().schema) - def is_in_group(v, group_name): + def is_in_group(v: bmesh.types.BMVert, group_name: str) -> bool: for group_index in groups[group_name]: if group_index in v[deform_layer]: return True @@ -1827,7 +1830,7 @@ class Model(bonsai.core.tool.Model): tmp.createIfcCircle(tmp.createIfcAxis2Placement2D(tmp.createIfcCartesianPoint(list(mid))), radius) ) else: - loop_verts = [] + loop_verts: list[bmesh.types.BMVert] = [] for i, edge in enumerate(loop): if i == 0 and len(loop) == 1: loop_verts.append(edge.verts[0]) @@ -1861,7 +1864,9 @@ class Model(bonsai.core.tool.Model): if tmp.schema != "IFC2X3" and any([is_in_group(v, "IFCARCINDEX") for v in loop_verts]): # We need to specify segments - coord_list = [list((position_i @ (v.co / unit_scale)).to_2d()) for v in loop_verts] + coord_list: list[list[float]] = [ + list((position_i @ (v.co / unit_scale)).to_2d()) for v in loop_verts + ] points = tmp.createIfcCartesianPointList2D(coord_list) i = 0 segments = [] diff --git a/src/bonsai/bonsai/tool/polyline.py b/src/bonsai/bonsai/tool/polyline.py index b73b80372f..60ac79606e 100644 --- a/src/bonsai/bonsai/tool/polyline.py +++ b/src/bonsai/bonsai/tool/polyline.py @@ -26,7 +26,7 @@ from dataclasses import dataclass from lark import Lark, Transformer from math import degrees, radians, sin, cos, tan from mathutils import Vector, Matrix -from typing import Optional +from typing import Optional, Union class Polyline(bonsai.core.tool.Polyline): @@ -98,15 +98,17 @@ class Polyline(bonsai.core.tool.Polyline): input_type: str = None @classmethod - def create_input_ui(cls, init_z=False, init_area=False): + def create_input_ui(cls, init_z: bool = False, init_area: bool = False) -> PolylineUI: return cls.PolylineUI(init_z=init_z, init_area=init_area) @classmethod - def create_tool_state(cls): + def create_tool_state(cls) -> ToolState: return cls.ToolState() @classmethod - def calculate_distance_and_angle(cls, context, input_ui, tool_state, should_round=False): + def calculate_distance_and_angle( + cls, context: bpy.types.Context, input_ui: PolylineUI, tool_state: ToolState, should_round: bool = False + ) -> None: try: polyline_data = context.scene.BIMPolylineProperties.insertion_polyline[0] @@ -192,7 +194,7 @@ class Polyline(bonsai.core.tool.Polyline): return @classmethod - def calculate_area(cls, context, input_ui): + def calculate_area(cls, context: bpy.types.Context, input_ui: PolylineUI) -> Union[PolylineUI, None]: try: polyline_data = context.scene.BIMPolylineProperties.insertion_polyline[0] polyline_points = polyline_data.polyline_points @@ -241,7 +243,7 @@ class Polyline(bonsai.core.tool.Polyline): return @classmethod - def calculate_x_y_and_z(cls, context, input_ui, tool_state): + def calculate_x_y_and_z(cls, context: bpy.types.Context, input_ui: PolylineUI, tool_state: ToolState) -> None: try: polyline_data = context.scene.BIMPolylineProperties.insertion_polyline[0] polyline_points = polyline_data.polyline_points @@ -305,7 +307,7 @@ class Polyline(bonsai.core.tool.Polyline): return @classmethod - def validate_input(cls, input_number, input_type): + def validate_input(cls, input_number, input_type) -> tuple[bool, str]: grammar_imperial = """ start: (FORMULA dim expr) | dim @@ -433,7 +435,7 @@ class Polyline(bonsai.core.tool.Polyline): return False, "0" @classmethod - def format_input_ui_units(cls, value, is_area=False): + def format_input_ui_units(cls, value: float, is_area: bool = False) -> str: unit_system = tool.Drawing.get_unit_system() if unit_system == "IMPERIAL": precision = bpy.context.scene.DocProperties.imperial_precision @@ -454,7 +456,7 @@ class Polyline(bonsai.core.tool.Polyline): ) @classmethod - def insert_polyline_point(cls, input_ui, tool_state=None): + def insert_polyline_point(cls, input_ui: PolylineUI, tool_state: Optional[ToolState] = None) -> Union[str, None]: x = input_ui.get_number_value("X") y = input_ui.get_number_value("Y") if input_ui.get_number_value("Z") is not None: @@ -521,7 +523,7 @@ class Polyline(bonsai.core.tool.Polyline): polyline_data.total_length = total_length @classmethod - def close_polyline(cls): + def close_polyline(cls) -> None: polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline polyline_points = polyline_data[0].polyline_points if polyline_data else [] if len(polyline_points) > 2: @@ -537,17 +539,17 @@ class Polyline(bonsai.core.tool.Polyline): polyline_point.position = first_point.position @classmethod - def clear_polyline(cls): + def clear_polyline(cls) -> None: bpy.context.scene.BIMPolylineProperties.insertion_polyline.clear() @classmethod - def remove_last_polyline_point(cls): + def remove_last_polyline_point(cls) -> None: polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline polyline_points = polyline_data[0].polyline_points if polyline_data else [] polyline_points.remove(len(polyline_points) - 1) @classmethod - def move_polyline_to_measure(cls, context, input_ui): + def move_polyline_to_measure(cls, context: bpy.types.Context, input_ui: PolylineUI) -> None: polyline_data = bpy.context.scene.BIMPolylineProperties.insertion_polyline polyline_points = polyline_data[0].polyline_points if polyline_data else [] measurement_data = bpy.context.scene.BIMPolylineProperties.measurement_polyline.add() diff --git a/src/ifcopenshell-python/ifcopenshell/api/project/create_file.py b/src/ifcopenshell-python/ifcopenshell/api/project/create_file.py index 5ce3592749..38da958ed5 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/project/create_file.py +++ b/src/ifcopenshell-python/ifcopenshell/api/project/create_file.py @@ -18,9 +18,10 @@ import datetime import ifcopenshell +import ifcopenshell.util.schema -def create_file(version: str = "IFC4") -> ifcopenshell.file: +def create_file(version: ifcopenshell.util.schema.IFC_SCHEMA = "IFC4") -> ifcopenshell.file: """Create a blank IFC model file object Create a new IFC file object based on the nominated schema version. The @@ -34,9 +35,7 @@ def create_file(version: str = "IFC4") -> ifcopenshell.file: :param version: The schema version of the IFC file. Choose from "IFC2X3", "IFC4", or "IFC4X3". If you have loaded in a custom schema, you may specify that schema identifier here too. - :type version: str, optional :return: The created IFC file object. - :rtype: ifcopenshell.file Example: diff --git a/src/ifcopenshell-python/ifcopenshell/file.py b/src/ifcopenshell-python/ifcopenshell/file.py index 228c0e9749..da7db0dcce 100644 --- a/src/ifcopenshell-python/ifcopenshell/file.py +++ b/src/ifcopenshell-python/ifcopenshell/file.py @@ -222,7 +222,7 @@ class file: def __init__( self, f: Optional[ifcopenshell_wrapper.file] = None, - schema: Optional[str] = None, + schema: Optional[ifcopenshell.util.schema.IFC_SCHEMA] = None, schema_version: Optional[tuple[int, int, int, int]] = None, ): """Create a new blank IFC model @@ -239,7 +239,6 @@ class file: or "IFC4X3". These refer to the ISO approved versions of IFC. Defaults to "IFC4" if not specified, which is currently recommended for all new projects. - :type schema: string :param schema_version: If you want to specify an exact version of IFC that may not be an ISO approved version, use this argument instead of ``schema``. IFC versions on technical.buildingsmart.org are @@ -248,7 +247,6 @@ class file: ADD2 TC1, which is the official version approved by ISO when people refer to "IFC4". Generally you should not use this argument unless you are testing non-ISO IFC releases. - :type schema_version: tuple[int, int, int, int] Example: