From c06929e1c77ad0f23e231b5998ab32657c916961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Sun, 8 Sep 2024 15:47:51 -0300 Subject: [PATCH] Major refactor in polyline tool to use a base class for other operators. Polyline Wall and Measure tool now share most of the logic by inheriting from a base class PolylineOperator. This should facilitate the implementation of other tools that use polyline. --- .../bonsai/bim/module/model/polyline.py | 337 ++++++++++++++++++ src/bonsai/bonsai/bim/module/model/wall.py | 262 +------------- .../bonsai/bim/module/project/operator.py | 262 +------------- src/bonsai/bonsai/tool/snap.py | 6 +- 4 files changed, 375 insertions(+), 492 deletions(-) create mode 100644 src/bonsai/bonsai/bim/module/model/polyline.py diff --git a/src/bonsai/bonsai/bim/module/model/polyline.py b/src/bonsai/bonsai/bim/module/model/polyline.py new file mode 100644 index 0000000000..89986961aa --- /dev/null +++ b/src/bonsai/bonsai/bim/module/model/polyline.py @@ -0,0 +1,337 @@ +# Bonsai - OpenBIM Blender Add-on +# Copyright (C) 2024 Bruno Perdigão +# +# This file is part of Bonsai. +# +# Bonsai is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Bonsai is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Bonsai. If not, see . + +import bpy +import copy +import math +import bmesh +import ifcopenshell +import ifcopenshell.api +import ifcopenshell.util.unit +import ifcopenshell.util.element +import ifcopenshell.util.placement +import ifcopenshell.util.representation +import ifcopenshell.util.type +import mathutils.geometry +import bonsai.core.type +import bonsai.core.root +import bonsai.core.geometry +import bonsai.core.model as core +import bonsai.tool as tool +from bonsai.bim.ifc import IfcStore +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 lark import Lark, Transformer + + +class PolylineOperator: + # TODO Fill doc strings + """ """ + + @classmethod + def poll(cls, context): + return context.space_data.type == "VIEW_3D" + + def __init__(self): + self.mousemove_count = 0 + self.action_count = 0 + self.visible_objs = [] + self.objs_2d_bbox = [] + self.number_options = { + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + " ", + ".", + "+", + "-", + "*", + "/", + "'", + '"', + "=", + } + self.number_input = [] + self.number_output = "" + self.number_is_negative = False + self.input_options = ["D", "A", "X", "Y"] + self.input_type = None + self.input_type = None + self.input_value_xy = [None, None] + self.input_ui = tool.Polyline.create_input_ui() + self.is_typing = False + self.snap_angle = None + self.snapping_points = [] + self.instructions = """TAB: Cycle Input + M: Modify Snap Point + C: Close + Backspace: Remove + X Y: Axis + Shift: Lock axis +""" + self.tool_state = tool.Polyline.create_tool_state() + + def recalculate_inputs(self, context): + 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) + if not is_valid: + self.report({"WARNING"}, "The number typed is not valid.") + return is_valid + else: + if self.input_type in {"X", "Y"}: + tool.Polyline.calculate_distance_and_angle(context, self.input_ui, self.tool_state) + elif self.input_type in {"D", "A"}: + tool.Polyline.calculate_x_y_and_z(context, self.input_ui, self.tool_state) + tool.Polyline.calculate_distance_and_angle(context, self.input_ui, self.tool_state) + else: + self.input_ui.set_value(self.input_type, self.number_output) + tool.Blender.update_viewport() + return is_valid + + def choose_axis(self, event, x=True, y=True, z=False): + if x: + if event.value == "PRESS" and event.type == "X": + self.tool_state.axis_method = "X" if self.tool_state.axis_method != event.type else None + PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) + tool.Blender.update_viewport() + + if y: + if event.value == "PRESS" and event.type == "Y": + self.tool_state.axis_method = "Y" if self.tool_state.axis_method != event.type else None + PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) + tool.Blender.update_viewport() + if z: + if event.value == "PRESS" and event.type == "Z": + self.tool_state.axis_method = "Z" if self.tool_state.axis_method != event.type else None + 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): + if x: + if event.shift and event.value == "PRESS" and event.type == "X": + self.tool_state.use_default_container = False + self.tool_state.plane_method = "YZ" + self.tool_state.axis_method = None + tool.Blender.update_viewport() + + if y: + if event.shift and event.value == "PRESS" and event.type == "Y": + self.tool_state.use_default_container = False + self.tool_state.plane_method = "XZ" + self.tool_state.axis_method = None + tool.Blender.update_viewport() + + if z: + if event.shift and event.value == "PRESS" and event.type == "Z": + self.tool_state.use_default_container = False + self.tool_state.plane_method = "XY" + self.tool_state.axis_method = None + tool.Blender.update_viewport() + + def handle_keyboard_input(self, context, event): + + if self.tool_state.is_input_on and event.value == "PRESS" and event.type == "TAB": + self.recalculate_inputs(context) + index = self.input_options.index(self.input_type) + size = len(self.input_options) + self.input_type = self.input_options[((index + 1) % size)] + self.tool_state.input_type = self.input_options[((index + 1) % size)] + self.tool_state.mode = "Select" + self.is_typing = False + self.number_input = self.input_ui.get_formatted_value(self.input_type) + self.number_input = list(self.number_input) + self.number_output = "".join(self.number_input) + self.input_ui.set_value(self.input_type, self.number_output) + PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) + tool.Blender.update_viewport() + + if not self.tool_state.is_input_on and event.value == "RELEASE" and event.type == "TAB": + self.recalculate_inputs(context) + self.tool_state.mode = "Select" + self.tool_state.is_input_on = True + self.input_type = "D" + self.tool_state.input_type = "D" + self.is_typing = False + self.number_input = self.input_ui.get_formatted_value(self.input_type) + self.number_input = list(self.number_input) + self.number_output = "".join(self.number_input) + self.input_ui.set_value(self.input_type, self.number_output) + PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) + tool.Blender.update_viewport() + + if not self.tool_state.is_input_on and event.ascii in self.number_options: + self.recalculate_inputs(context) + self.tool_state.mode = "Edit" + self.tool_state.is_input_on = True + self.input_type = "D" + self.tool_state.input_type = "D" + PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) + tool.Blender.update_viewport() + + if event.value == "RELEASE" and event.type in {"D", "A"}: + self.recalculate_inputs(context) + self.tool_state.mode = "Edit" + self.tool_state.is_input_on = True + self.input_type = event.type + self.tool_state.input_type = event.type + self.input_ui.set_value(self.input_type, "") + PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) + tool.Blender.update_viewport() + + if self.input_type in self.input_options: + if (event.ascii in self.number_options) or (event.value == "RELEASE" and event.type == "BACK_SPACE"): + if not self.tool_state.mode == "Edit" and not (event.ascii == "=" or event.type == "BACK_SPACE"): + self.number_input = [] + + if event.type == "BACK_SPACE": + if len(self.number_input) <= 1: + self.number_input = [] + else: + self.number_input.pop(-1) + elif event.ascii == "=": + if self.number_input[0] == "=": + self.number_input.pop(0) + else: + self.number_input.insert(0, "=") + else: + self.number_input.append(event.ascii) + + if not self.number_input: + self.number_output = "0" + + self.tool_state.mode = "Edit" + self.is_typing = True + self.number_output = "".join(self.number_input) + self.input_ui.set_value(self.input_type, self.number_output) + PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) + tool.Blender.update_viewport() + + def handle_inserting_polyline(self, context, event): + if event.value == "RELEASE" and event.type == "LEFTMOUSE": + tool.Snap.insert_polyline_point(self.input_ui) + tool.Blender.update_viewport() + + if event.value == "PRESS" and event.type == "C": + tool.Snap.close_polyline() + PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) + tool.Blender.update_viewport() + + if ( + self.tool_state.is_input_on + and event.value == "RELEASE" + and event.type in {"RET", "NUMPAD_ENTER", "RIGHTMOUSE"} + ): + is_valid = self.recalculate_inputs(context) + if is_valid: + tool.Snap.insert_polyline_point(self.input_ui) + self.tool_state.mode = "Mouse" + self.tool_state.is_input_on = False + self.input_type = None + self.tool_state.input_type = None + self.number_input = [] + self.number_output = "" + PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) + tool.Blender.update_viewport() + + def handle_snap_selection(self, context, event): + if event.value == "PRESS" and event.type == "M": + self.snapping_points = tool.Snap.modify_snapping_point_selection(self.snapping_points) + tool.Polyline.calculate_distance_and_angle(context, self.input_ui, self.tool_state) + PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) + tool.Blender.update_viewport() + + def handle_cancelation(self, context, event): + if self.tool_state.is_input_on: + if event.value == "RELEASE" and event.type in {"ESC"}: + self.recalculate_inputs(context) + self.tool_state.mode = "Mouse" + self.tool_state.is_input_on = False + self.input_type = None + self.tool_state.input_type = None + PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) + tool.Blender.update_viewport() + else: + if event.value == "RELEASE" and event.type in {"ESC"}: + self.tool_state.axis_method = None + PolylineDecorator.uninstall() + tool.Snap.clear_polyline() + tool.Blender.update_viewport() + return {"CANCELLED"} + + def handle_mouse_move(self, context, event): + if not self.tool_state.is_input_on: + if event.type == "MOUSEMOVE" or event.type == "INBETWEEN_MOUSEMOVE": + self.mousemove_count += 1 + self.tool_state.mode = "Mouse" + self.tool_state.is_input_on = False + self.input_type = None + self.tool_state.input_type = None + PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) + tool.Snap.clear_snapping_ref() + tool.Blender.update_viewport() + else: + self.mousemove_count = 0 + + if self.mousemove_count == 2: + self.objs_2d_bbox = [] + for obj in self.visible_objs: + self.objs_2d_bbox.append(tool.Raycast.get_on_screen_2d_bounding_boxes(context, obj)) + + if self.mousemove_count > 3: + detected_snaps = tool.Snap.detect_snapping_points(context, event, self.objs_2d_bbox, self.tool_state) + self.snapping_points = tool.Snap.select_snapping_points(context, event, self.tool_state, detected_snaps) + tool.Polyline.calculate_distance_and_angle(context, self.input_ui, self.tool_state) + tool.Blender.update_viewport() + return {"RUNNING_MODAL"} + + if event.value == "RELEASE" and event.type == "BACK_SPACE": + tool.Snap.remove_last_polyline_point() + tool.Blender.update_viewport() + + + def invoke(self, context, event): + PolylineDecorator.install(context) + tool.Snap.clear_snapping_point() + + self.tool_state.use_default_container = False + self.tool_state.axis_method = None + self.tool_state.plane_method = None + self.tool_state.mode = "Mouse" + tool.Snap.set_tool_state(self.tool_state) + self.visible_objs = tool.Raycast.get_visible_objects(context) + for obj in self.visible_objs: + self.objs_2d_bbox.append(tool.Raycast.get_on_screen_2d_bounding_boxes(context, obj)) + detected_snaps = tool.Snap.detect_snapping_points(context, event, self.objs_2d_bbox, self.tool_state) + self.snapping_points = tool.Snap.select_snapping_points(context, event, self.tool_state, detected_snaps) + tool.Polyline.calculate_distance_and_angle(context, self.input_ui, self.tool_state) + + PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) + tool.Blender.update_viewport() + context.window_manager.modal_handler_add(self) + # return {"RUNNING_MODAL"} diff --git a/src/bonsai/bonsai/bim/module/model/wall.py b/src/bonsai/bonsai/bim/module/model/wall.py index 3b6a1d2ee0..0244f4a173 100644 --- a/src/bonsai/bonsai/bim/module/model/wall.py +++ b/src/bonsai/bonsai/bim/module/model/wall.py @@ -38,6 +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 bonsai.bim.module.model.polyline import PolylineOperator from typing import Optional from lark import Lark, Transformer @@ -284,7 +285,7 @@ def recalculate_dumb_wall_origin(wall, new_origin=None): child.matrix_parent_inverse = wall.matrix_world.inverted() -class DrawPolylineWall(bpy.types.Operator): +class DrawPolylineWall(bpy.types.Operator, PolylineOperator): bl_idname = "bim.draw_polyline_wall" bl_label = "Draw Polyline Wall" bl_options = {"REGISTER", "UNDO"} @@ -294,68 +295,7 @@ class DrawPolylineWall(bpy.types.Operator): return context.space_data.type == "VIEW_3D" def __init__(self): - self.mousemove_count = 0 - self.action_count = 0 - self.visible_objs = [] - self.objs_2d_bbox = [] - self.number_options = { - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - " ", - ".", - "+", - "-", - "*", - "/", - "'", - '"', - "=", - } - self.number_input = [] - self.number_output = "" - self.number_is_negative = False - self.input_options = ["D", "A", "X", "Y"] - self.input_type = None - self.input_type = None - self.input_value_xy = [None, None] - self.input_ui = tool.Polyline.create_input_ui() - self.is_typing = False - self.snap_angle = None - self.snapping_points = [] - self.instructions = """TAB: Cycle Input - M: Modify Snap Point - C: Close - Backspace: Remove - X Y: Axis - Shift: Lock axis -""" - self.tool_state = tool.Polyline.create_tool_state() - - def recalculate_inputs(self, context): - 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) - if not is_valid: - self.report({"WARNING"}, "The number typed is not valid.") - return is_valid - else: - if self.input_type in {"X", "Y"}: - tool.Polyline.calculate_distance_and_angle(context, self.input_ui, self.tool_state) - elif self.input_type in {"D", "A"}: - tool.Polyline.calculate_x_y_and_z(context, self.input_ui, self.tool_state) - tool.Polyline.calculate_distance_and_angle(context, self.input_ui, self.tool_state) - else: - self.input_ui.set_value(self.input_type, self.number_output) - tool.Blender.update_viewport() - return is_valid + super().__init__() # TODO This is creating a hack in generate function from DumbWallGenerator # Come up with a better solution @@ -384,131 +324,14 @@ class DrawPolylineWall(bpy.types.Operator): DumbWallJoiner().join_V(wall1["obj"], wall2["obj"]) def modal(self, context, event): + if event.type in {"MIDDLEMOUSE", "WHEELUPMOUSE", "WHEELDOWNMOUSE"}: + return {"PASS_THROUGH"} - if not self.tool_state.is_input_on: - if event.type == "MOUSEMOVE" or event.type == "INBETWEEN_MOUSEMOVE": - self.mousemove_count += 1 - self.tool_state.mode = "Mouse" - self.tool_state.is_input_on = False - self.input_type = None - self.tool_state.input_type = None - PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) - tool.Snap.clear_snapping_ref() - tool.Blender.update_viewport() - else: - self.mousemove_count = 0 + self.handle_mouse_move(context, event) - if self.mousemove_count == 2: - self.objs_2d_bbox = [] - for obj in self.visible_objs: - self.objs_2d_bbox.append(tool.Raycast.get_on_screen_2d_bounding_boxes(context, obj)) + self.choose_axis(event) - if self.mousemove_count > 3: - detected_snaps = tool.Snap.detect_snapping_points(context, event, self.objs_2d_bbox, self.tool_state) - self.snapping_points = tool.Snap.select_snapping_points(context, event, self.tool_state, detected_snaps) - print(self.snapping_points) - tool.Polyline.calculate_distance_and_angle(context, self.input_ui, self.tool_state) - tool.Blender.update_viewport() - return {"RUNNING_MODAL"} - - if event.value == "RELEASE" and event.type == "BACK_SPACE": - tool.Snap.remove_last_polyline_point() - tool.Blender.update_viewport() - - if event.value == "RELEASE" and event.type == "LEFTMOUSE": - tool.Snap.insert_polyline_point(self.input_ui) - tool.Blender.update_viewport() - - if event.value == "PRESS" and event.type == "X": - self.tool_state.axis_method = "X" if self.tool_state.axis_method != event.type else None - PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) - tool.Blender.update_viewport() - - if event.value == "PRESS" and event.type == "Y": - self.tool_state.axis_method = "Y" if self.tool_state.axis_method != event.type else None - PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) - tool.Blender.update_viewport() - - if event.value == "PRESS" and event.type == "C": - tool.Snap.close_polyline() - PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) - tool.Blender.update_viewport() - - if self.tool_state.is_input_on and event.value == "PRESS" and event.type == "TAB": - self.recalculate_inputs(context) - index = self.input_options.index(self.input_type) - size = len(self.input_options) - self.input_type = self.input_options[((index + 1) % size)] - self.tool_state.input_type = self.input_options[((index + 1) % size)] - self.tool_state.mode = "Select" - self.is_typing = False - self.number_input = self.input_ui.get_formatted_value(self.input_type) - self.number_input = list(self.number_input) - self.number_output = "".join(self.number_input) - self.input_ui.set_value(self.input_type, self.number_output) - PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) - tool.Blender.update_viewport() - - if not self.tool_state.is_input_on and event.value == "RELEASE" and event.type == "TAB": - self.recalculate_inputs(context) - self.tool_state.mode = "Select" - self.tool_state.is_input_on = True - self.input_type = "D" - self.tool_state.input_type = "D" - self.is_typing = False - self.number_input = self.input_ui.get_formatted_value(self.input_type) - self.number_input = list(self.number_input) - self.number_output = "".join(self.number_input) - self.input_ui.set_value(self.input_type, self.number_output) - PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) - tool.Blender.update_viewport() - - if not self.tool_state.is_input_on and event.ascii in self.number_options: - self.recalculate_inputs(context) - self.tool_state.mode = "Edit" - self.tool_state.is_input_on = True - self.input_type = "D" - self.tool_state.input_type = "D" - PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) - tool.Blender.update_viewport() - - if event.value == "RELEASE" and event.type in {"D", "A"}: - self.recalculate_inputs(context) - self.tool_state.mode = "Edit" - self.tool_state.is_input_on = True - self.input_type = event.type - self.tool_state.input_type = event.type - self.input_ui.set_value(self.input_type, "") - PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) - tool.Blender.update_viewport() - - if self.input_type in self.input_options: - if (event.ascii in self.number_options) or (event.value == "RELEASE" and event.type == "BACK_SPACE"): - if not self.tool_state.mode == "Edit" and not (event.ascii == "=" or event.type == "BACK_SPACE"): - self.number_input = [] - - if event.type == "BACK_SPACE": - if len(self.number_input) <= 1: - self.number_input = [] - else: - self.number_input.pop(-1) - elif event.ascii == "=": - if self.number_input[0] == "=": - self.number_input.pop(0) - else: - self.number_input.insert(0, "=") - else: - self.number_input.append(event.ascii) - - if not self.number_input: - self.number_output = "0" - - self.tool_state.mode = "Edit" - self.is_typing = True - self.number_output = "".join(self.number_input) - self.input_ui.set_value(self.input_type, self.number_output) - PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) - tool.Blender.update_viewport() + self.handle_snap_selection(context, event) if not self.tool_state.is_input_on and event.value == "RELEASE" and event.type in {"RET", "NUMPAD_ENTER", "RIGHTMOUSE"}: self.create_walls_from_polyline(context) @@ -517,72 +340,21 @@ class DrawPolylineWall(bpy.types.Operator): tool.Blender.update_viewport() return {"FINISHED"} - if self.tool_state.is_input_on and event.value == "RELEASE" and event.type in {"RET", "NUMPAD_ENTER", "RIGHTMOUSE"}: - is_valid = self.recalculate_inputs(context) - if is_valid: - tool.Snap.insert_polyline_point(self.input_ui) - self.tool_state.mode = "Mouse" - self.tool_state.is_input_on = False - self.input_type = None - self.tool_state.input_type = None - self.number_input = [] - self.number_output = "" - PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) - tool.Blender.update_viewport() + self.handle_keyboard_input(context, event) - if event.value == "PRESS" and event.type == "M": - self.snapping_points = tool.Snap.modify_snapping_point_selection(self.snapping_points) - tool.Polyline.calculate_distance_and_angle(context, self.input_ui, self.tool_state) - PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) - tool.Blender.update_viewport() + self.handle_inserting_polyline(context, event) - if event.type in {"MIDDLEMOUSE", "WHEELUPMOUSE", "WHEELDOWNMOUSE"}: - return {"PASS_THROUGH"} + result = self.handle_cancelation(context, event) + if result is not None: + return result - if self.tool_state.is_input_on: - if event.value == "RELEASE" and event.type in {"ESC"}: - self.recalculate_inputs(context) - self.tool_state.mode = "Mouse" - self.tool_state.is_input_on = False - self.input_type = None - self.tool_state.input_type = None - PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) - tool.Blender.update_viewport() - else: - if event.value == "RELEASE" and event.type in {"ESC"}: - self.tool_state.axis_method = None - PolylineDecorator.uninstall() - tool.Snap.clear_polyline() - tool.Blender.update_viewport() - return {"CANCELLED"} - - print(">>> ", self.tool_state.mode) return {"RUNNING_MODAL"} def invoke(self, context, event): - if context.space_data.type == "VIEW_3D": - PolylineDecorator.install(context) - tool.Snap.clear_snapping_point() - - self.tool_state.use_default_container = True - self.tool_state.axis_method = None - self.tool_state.plane_method = "XY" - self.tool_state.mode = "Mouse" - tool.Snap.set_tool_state(self.tool_state) - self.visible_objs = tool.Raycast.get_visible_objects(context) - for obj in self.visible_objs: - self.objs_2d_bbox.append(tool.Raycast.get_on_screen_2d_bounding_boxes(context, obj)) - detected_snaps = tool.Snap.detect_snapping_points(context, event, self.objs_2d_bbox, self.tool_state) - self.snapping_points = tool.Snap.select_snapping_points(context, event, self.tool_state, detected_snaps) - tool.Polyline.calculate_distance_and_angle(context, self.input_ui, self.tool_state) - - PolylineDecorator.update(event, self.tool_state, self.input_ui, self.snapping_points[0]) - tool.Blender.update_viewport() - context.window_manager.modal_handler_add(self) - return {"RUNNING_MODAL"} - else: - self.report({"WARNING"}, "Active space must be a View3d") - return {"CANCELLED"} + super().invoke(context, event) + self.tool_state.use_default_container = True + self.tool_state.plane_method = "XY" + return {"RUNNING_MODAL"} class DumbWallAligner: diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index dff315ec52..4eef869eff 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -53,6 +53,7 @@ from ifcopenshell.geom import ShapeElementType from bonsai.bim.module.project.data import LinksData from bonsai.bim.module.project.decorator import ProjectDecorator, ClippingPlaneDecorator from bonsai.bim.module.model.decorator import PolylineDecorator +from bonsai.bim.module.model.polyline import PolylineOperator from typing import Union @@ -2268,7 +2269,7 @@ if bpy.app.version >= (4, 1, 0): return True -class MeasureTool(bpy.types.Operator): +class MeasureTool(bpy.types.Operator, PolylineOperator): bl_idname = "bim.measure_tool" bl_options = {"REGISTER", "UNDO"} bl_label = "Measure Tool" @@ -2278,42 +2279,9 @@ class MeasureTool(bpy.types.Operator): return context.space_data.type == "VIEW_3D" def __init__(self): - self.mousemove_count = 0 - self.action_count = 0 - self.visible_objs = [] - self.objs_2d_bbox = [] - self.number_options = { - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - " ", - ".", - "+", - "-", - "*", - "/", - "'", - '"', - "=", - } - self.number_input = [] - self.number_output = "" - self.number_is_negative = False - self.is_input_on = False - self.input_options = ["D", "A", "X", "Y", "Z"] - self.input_type = None - self.input_value_xy = [None, None] + super().__init__() self.input_ui = tool.Polyline.create_input_ui(init_z=True) - self.is_typing = False - self.snap_angle = None - self.snapping_points = [] + self.input_options = ["D", "A", "X", "Y", "Z"] self.instructions = """TAB: Cycle Input M: Modify Snap Point C: Close @@ -2323,228 +2291,34 @@ class MeasureTool(bpy.types.Operator): Shift: Lock axis """ - def recalculate_inputs(self, context): - 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) - if not is_valid: - self.report({"WARNING"}, "The number typed is not valid.") - return is_valid - else: - if self.input_type in {"X", "Y", "Z"}: - tool.Polyline.calculate_distance_and_angle(context, self.is_input_on, self.input_ui) - elif self.input_type in {"D", "A"}: - tool.Polyline.calculate_x_y_and_z(context, self.input_ui) - tool.Polyline.calculate_distance_and_angle(context, self.is_input_on, self.input_ui) - else: - self.input_ui.set_value(self.input_type, self.number_output) - tool.Blender.update_viewport() - return is_valid - def modal(self, context, event): + if event.type in {"MIDDLEMOUSE", "WHEELUPMOUSE", "WHEELDOWNMOUSE"}: + return {"PASS_THROUGH"} - if not self.is_input_on: - if event.type == "MOUSEMOVE" or event.type == "INBETWEEN_MOUSEMOVE": - self.mousemove_count += 1 - self.is_input_on = False - self.input_type = None - PolylineDecorator.set_input_ui(self.input_ui, self.input_type) - tool.Snap.clear_snapping_ref() - tool.Blender.update_viewport() - else: - self.mousemove_count = 0 + self.handle_mouse_move(context, event) - if self.mousemove_count == 2: - self.objs_2d_bbox = [] - for obj in self.visible_objs: - self.objs_2d_bbox.append(tool.Raycast.get_on_screen_2d_bounding_boxes(context, obj)) + self.choose_axis(event, z=True) - if self.mousemove_count > 3: - detected_snaps = tool.Snap.detect_snapping_points(context, event, self.objs_2d_bbox) - self.snapping_points = tool.Snap.select_snapping_points(context, event, detected_snaps) - PolylineDecorator.set_mouse_position(event) - tool.Polyline.calculate_distance_and_angle(context, self.is_input_on, self.input_ui) - tool.Blender.update_viewport() - return {"RUNNING_MODAL"} + self.choose_plane(event) - if event.value == "RELEASE" and event.type == "BACK_SPACE": - tool.Snap.remove_last_polyline_point() - tool.Blender.update_viewport() + self.handle_snap_selection(context, event) - if event.value == "RELEASE" and event.type == "LEFTMOUSE": - tool.Snap.insert_polyline_point(self.input_ui) - tool.Blender.update_viewport() - - if event.value == "PRESS" and event.type == "X": - tool.Snap.set_snap_axis_method("X") - tool.Blender.update_viewport() - - if event.value == "PRESS" and event.type == "Y": - tool.Snap.set_snap_axis_method("Y") - tool.Blender.update_viewport() - - if event.value == "PRESS" and event.type == "Z": - tool.Snap.set_snap_axis_method("Z") - tool.Blender.update_viewport() - - if event.value == "PRESS" and event.type == "C": - tool.Snap.close_polyline() - PolylineDecorator.set_input_ui(self.input_ui, self.input_type) - tool.Blender.update_viewport() - - if self.is_input_on and event.value == "PRESS" and event.type == "TAB": - self.recalculate_inputs(context) - index = self.input_options.index(self.input_type) - size = len(self.input_options) - self.input_type = self.input_options[((index + 1) % size)] - self.is_typing = False - self.number_input = self.input_ui.get_formatted_value(self.input_type) - self.number_input = list(self.number_input) - self.number_output = "".join(self.number_input) - self.input_ui.set_value(self.input_type, self.number_output) - PolylineDecorator.set_input_ui(self.input_ui, self.input_type) - tool.Blender.update_viewport() - - if not self.is_input_on and event.value == "RELEASE" and event.type == "TAB": - self.recalculate_inputs(context) - self.is_input_on = True - self.input_type = "D" - self.is_typing = False - self.number_input = self.input_ui.get_formatted_value(self.input_type) - self.number_input = list(self.number_input) - self.number_output = "".join(self.number_input) - self.input_ui.set_value(self.input_type, self.number_output) - PolylineDecorator.set_input_ui(self.input_ui, self.input_type) - tool.Blender.update_viewport() - - if not self.is_input_on and event.ascii in self.number_options: - self.recalculate_inputs(context) - self.is_input_on = True - self.input_type = "D" - PolylineDecorator.set_input_ui(self.input_ui, self.input_type) - tool.Blender.update_viewport() - - if event.value == "RELEASE" and event.type in {"D", "A"}: - self.recalculate_inputs(context) - self.is_input_on = True - self.input_type = event.type - self.input_ui.set_value(self.input_type, "") - PolylineDecorator.set_input_ui(self.input_ui, self.input_type) - tool.Blender.update_viewport() - - if self.input_type in self.input_options: - if (event.ascii in self.number_options) or (event.value == "RELEASE" and event.type == "BACK_SPACE"): - if not self.is_typing and not (event.ascii == "=" or event.type == "BACK_SPACE"): - self.number_input = [] - - if event.type == "BACK_SPACE": - if len(self.number_input) <= 1: - self.number_input = [] - else: - self.number_input.pop(-1) - elif event.ascii == "=": - if self.number_input[0] == "=": - self.number_input.pop(0) - else: - self.number_input.insert(0, "=") - else: - self.number_input.append(event.ascii) - - if not self.number_input: - self.number_output = "0" - - self.is_typing = True - self.number_output = "".join(self.number_input) - self.input_ui.set_value(self.input_type, self.number_output) - PolylineDecorator.set_input_ui(self.input_ui, self.input_type) - tool.Blender.update_viewport() - - if not self.is_input_on and event.value == "RELEASE" and event.type in {"RET", "NUMPAD_ENTER", "RIGHTMOUSE"}: + if not self.tool_state.is_input_on and event.value == "RELEASE" and event.type in {"RET", "NUMPAD_ENTER", "RIGHTMOUSE"}: PolylineDecorator.uninstall() tool.Snap.clear_polyline() tool.Blender.update_viewport() return {"FINISHED"} - if self.is_input_on and event.value == "RELEASE" and event.type in {"RET", "NUMPAD_ENTER", "RIGHTMOUSE"}: - is_valid = self.recalculate_inputs(context) - if is_valid: - tool.Snap.insert_polyline_point(self.input_ui) - self.is_input_on = False - self.input_type = None - self.number_input = [] - self.number_output = "" - PolylineDecorator.set_input_ui(self.input_ui, self.input_type) - tool.Blender.update_viewport() + self.handle_keyboard_input(context, event) - if event.value == "PRESS" and event.type == "M": - self.snapping_points = tool.Snap.modify_snapping_point_selection(self.snapping_points) - PolylineDecorator.set_mouse_position(event) - tool.Polyline.calculate_distance_and_angle(context, self.is_input_on, self.input_ui) - tool.Blender.update_viewport() + self.handle_inserting_polyline(context, event) - if event.shift and event.value == "PRESS" and event.type == "X": - tool.Snap.set_use_default_container(False) - PolylineDecorator.set_use_default_container(False) - tool.Snap.cycle_snap_plane_method("YZ") - tool.Snap.set_snap_axis_method(None) - tool.Blender.update_viewport() - - if event.shift and event.value == "PRESS" and event.type == "Y": - tool.Snap.set_use_default_container(False) - PolylineDecorator.set_use_default_container(False) - tool.Snap.cycle_snap_plane_method("XZ") - tool.Snap.set_snap_axis_method(None) - tool.Blender.update_viewport() - - if event.shift and event.value == "PRESS" and event.type == "Z": - tool.Snap.set_use_default_container(False) - PolylineDecorator.set_use_default_container(False) - tool.Snap.cycle_snap_plane_method("XY") - tool.Snap.set_snap_axis_method(None) - tool.Blender.update_viewport() - - if event.type in {"MIDDLEMOUSE", "WHEELUPMOUSE", "WHEELDOWNMOUSE"}: - return {"PASS_THROUGH"} - - if self.is_input_on: - if event.value == "RELEASE" and event.type in {"ESC"}: - self.recalculate_inputs(context) - self.is_input_on = False - self.input_type = None - PolylineDecorator.set_input_ui(self.input_ui, self.input_type) - tool.Blender.update_viewport() - else: - if event.value == "RELEASE" and event.type in {"ESC"}: - tool.Snap.set_snap_plane_method(None) - tool.Snap.set_snap_axis_method(None) - PolylineDecorator.uninstall() - tool.Snap.clear_polyline() - tool.Blender.update_viewport() - return {"CANCELLED"} + result = self.handle_cancelation(context, event) + if result is not None: + return result return {"RUNNING_MODAL"} def invoke(self, context, event): - if context.space_data.type == "VIEW_3D": - PolylineDecorator.install(context) - tool.Snap.clear_snapping_point() - tool.Snap.set_use_default_container(False) - PolylineDecorator.set_use_default_container(False) - tool.Polyline.set_use_default_container(False) - tool.Snap.set_snap_plane_method(None) - tool.Snap.set_snap_axis_method(None) - PolylineDecorator.set_instructions(self.instructions) - PolylineDecorator.set_input_ui(self.input_ui, self.input_type) - self.visible_objs = tool.Raycast.get_visible_objects(context) - for obj in self.visible_objs: - self.objs_2d_bbox.append(tool.Raycast.get_on_screen_2d_bounding_boxes(context, obj)) - detected_snaps = tool.Snap.detect_snapping_points(context, event, self.objs_2d_bbox) - self.snapping_points = tool.Snap.select_snapping_points(context, event, detected_snaps) - PolylineDecorator.set_mouse_position(event) - tool.Polyline.calculate_distance_and_angle(context, self.is_input_on, self.input_ui) - tool.Blender.update_viewport() - context.window_manager.modal_handler_add(self) - return {"RUNNING_MODAL"} - else: - self.report({"WARNING"}, "Active space must be a View3d") - return {"CANCELLED"} + super().invoke(context, event) + return {"RUNNING_MODAL"} diff --git a/src/bonsai/bonsai/tool/snap.py b/src/bonsai/bonsai/tool/snap.py index 137bad0b52..ff9a9d5b41 100644 --- a/src/bonsai/bonsai/tool/snap.py +++ b/src/bonsai/bonsai/tool/snap.py @@ -118,10 +118,10 @@ class Snap(bonsai.core.tool.Snap): def insert_polyline_point(cls, input_ui): x = input_ui.get_number_value("X") y = input_ui.get_number_value("Y") - try: + if input_ui.get_number_value("Z") is not None: z = input_ui.get_number_value("Z") - except: - z = Vector((0, 0, 0)) + else: + z = 0 d = input_ui.get_formatted_value("D") a = input_ui.get_formatted_value("A")