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.
This commit is contained in:
Bruno Perdigão
2024-09-08 15:47:51 -03:00
committed by Bruno Perdigão
parent 1a587dea7d
commit c06929e1c7
4 changed files with 375 additions and 492 deletions
@@ -0,0 +1,337 @@
# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2024 Bruno Perdigão <contact@brunopo.com>
#
# 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 <http://www.gnu.org/licenses/>.
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"}
+17 -245
View File
@@ -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:
+18 -244
View File
@@ -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"}
+3 -3
View File
@@ -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")