From be8b325a83e1a41eceb5e9350b68a6a98134353d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Sat, 7 Sep 2024 15:23:43 -0300 Subject: [PATCH] WIP - Polyline tool refactor --- .../bonsai/bim/module/model/decorator.py | 48 +++++++------- src/bonsai/bonsai/bim/module/model/wall.py | 66 +++++++++++-------- src/bonsai/bonsai/tool/polyline.py | 62 ++++++++--------- src/bonsai/bonsai/tool/snap.py | 50 +++++++------- 4 files changed, 116 insertions(+), 110 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/decorator.py b/src/bonsai/bonsai/bim/module/model/decorator.py index b14878a690..790cd9740b 100644 --- a/src/bonsai/bonsai/bim/module/model/decorator.py +++ b/src/bonsai/bonsai/bim/module/model/decorator.py @@ -301,7 +301,8 @@ class ProfileDecorator: class PolylineDecorator: is_installed = False handlers = [] - mouse_pos = None + event = None + input_type = None input_ui = None angle_snap_mat = None @@ -333,30 +334,27 @@ class PolylineDecorator: cls.is_installed = False @classmethod - def set_mouse_position(cls, event): - cls.mouse_pos = event.mouse_region_x, event.mouse_region_y + def update(cls, event, tool_state, input_ui): + cls.event = event + cls.tool_state = tool_state + cls.input_ui = input_ui @classmethod - def set_input_ui(cls, input_ui, input_type): + def set_input_ui(cls, input_ui): cls.input_ui = input_ui - cls.input_type = input_type @classmethod def set_angle_axis_line(cls, start, end): cls.axis_start = start cls.axis_end = end - @classmethod - def set_axis_rectangle(cls, corners): - cls.axis_rectangle = [*corners] + # @classmethod + # def set_axis_rectangle(cls, corners): + # cls.axis_rectangle = [*corners] @classmethod - def set_use_default_container(cls, value=False): - cls.use_default_container = value - - @classmethod - def set_instructions(cls, instructions): - cls.instructions = instructions + def set_tool_state(cls, tool_state): + cls.tool_state = tool_state @classmethod def set_snap_info(cls, snap_info): @@ -379,6 +377,8 @@ class PolylineDecorator: "Z": "Z coord:", "AREA": "Area: ", } + mouse_pos = self.event.mouse_region_x, self.event.mouse_region_y + self.addon_prefs = tool.Blender.get_addon_preferences() self.font_id = 0 @@ -392,18 +392,18 @@ class PolylineDecorator: new_line = 20 for i, (key, field_name) in enumerate(texts.items()): - if key != self.input_type: + if key != self.tool_state.input_type: formatted_value = self.input_ui.get_formatted_value(key) else: formatted_value = self.input_ui.get_text_value(key) if formatted_value is None: continue - if key == self.input_type: + if key == self.tool_state.input_type: blf.color(self.font_id, *color_highlight) else: blf.color(self.font_id, *color) - blf.position(self.font_id, self.mouse_pos[0] + offset, self.mouse_pos[1] - (new_line * i), 0) + blf.position(self.font_id, mouse_pos[0] + offset, mouse_pos[1] - (new_line * i), 0) blf.draw(self.font_id, field_name + formatted_value) @@ -452,10 +452,10 @@ class PolylineDecorator: color = self.addon_prefs.decorations_colour blf.color(self.font_id, *color) - text_w, text_h = blf.dimensions(0, self.instructions) + text_w, text_h = blf.dimensions(0, self.tool_state.instructions) position = (region.width / 2) - (text_w / 2) blf.position(self.font_id, position, 10, 0) - blf.draw(self.font_id, self.instructions) + blf.draw(self.font_id, self.tool_state.instructions) text_w, text_h = blf.dimensions(0, self.snap_info) position = (region.width / 2) - (text_w / 2) @@ -494,7 +494,7 @@ class PolylineDecorator: default_container_elevation = tool.Ifc.get_object(tool.Root.get_default_container()).location.z projection_point = [] - if self.use_default_container: + if self.tool_state.use_default_container: # When a point is above the plane it projects the point # to the plane and creates a line if snap_prop.snap_type != "Plane" and snap_prop.z != 0: @@ -520,10 +520,10 @@ class PolylineDecorator: self.line_shader.uniform_float("lineWidth", 0.75) self.draw_batch("LINES", [self.axis_start, self.axis_end], decorator_color_unselected, [(0, 1)]) - try: - self.draw_batch("TRIS", self.axis_rectangle, (1, 1, 1, 0.1), [(0, 1, 3), (0, 2, 3)]) - except: - pass + # try: + # self.draw_batch("TRIS", self.axis_rectangle, (1, 1, 1, 0.1), [(0, 1, 3), (0, 2, 3)]) + # except: + # pass # Area highlight # if "AREA" in list(self.input_panel.keys()): # TODO Change to input_ui diff --git a/src/bonsai/bonsai/bim/module/model/wall.py b/src/bonsai/bonsai/bim/module/model/wall.py index cb273177e5..50cd35de0f 100644 --- a/src/bonsai/bonsai/bim/module/model/wall.py +++ b/src/bonsai/bonsai/bim/module/model/wall.py @@ -325,6 +325,7 @@ class DrawPolylineWall(bpy.types.Operator): self.is_input_on = 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 @@ -337,6 +338,7 @@ class DrawPolylineWall(bpy.types.Operator): X Y: Axis Shift: Lock axis """ + self.tool_state = tool.Polyline.create_tool_state() def recalculate_inputs(self, context): if self.number_input: @@ -347,10 +349,10 @@ class DrawPolylineWall(bpy.types.Operator): return is_valid else: if self.input_type in {"X", "Y"}: - tool.Polyline.calculate_distance_and_angle(context, self.is_input_on, self.input_ui) + 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) - tool.Polyline.calculate_distance_and_angle(context, self.is_input_on, self.input_ui) + 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() @@ -388,8 +390,10 @@ class DrawPolylineWall(bpy.types.Operator): if event.type == "MOUSEMOVE" or event.type == "INBETWEEN_MOUSEMOVE": self.mousemove_count += 1 self.is_input_on = False + self.tool_state.is_input_on = False self.input_type = None - PolylineDecorator.set_input_ui(self.input_ui, self.input_type) + self.tool_state.input_type = None + PolylineDecorator.update(event, self.tool_state, self.input_ui) tool.Snap.clear_snapping_ref() tool.Blender.update_viewport() else: @@ -401,10 +405,9 @@ class DrawPolylineWall(bpy.types.Operator): 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) + 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, detected_snaps) - PolylineDecorator.set_mouse_position(event) - tool.Polyline.calculate_distance_and_angle(context, self.is_input_on, self.input_ui) + tool.Polyline.calculate_distance_and_angle(context, self.input_ui, self.tool_state) tool.Blender.update_viewport() return {"RUNNING_MODAL"} @@ -426,7 +429,7 @@ class DrawPolylineWall(bpy.types.Operator): if event.value == "PRESS" and event.type == "C": tool.Snap.close_polyline() - PolylineDecorator.set_input_ui(self.input_ui, self.input_type) + PolylineDecorator.update(event, self.tool_state, self.input_ui) tool.Blender.update_viewport() if self.is_input_on and event.value == "PRESS" and event.type == "TAB": @@ -434,39 +437,48 @@ class DrawPolylineWall(bpy.types.Operator): 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.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) + PolylineDecorator.update(event, self.tool_state, self.input_ui) 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.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.set_input_ui(self.input_ui, self.input_type) + PolylineDecorator.update(event, self.tool_state, self.input_ui) 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.tool_state.is_input_on = True self.input_type = "D" - PolylineDecorator.set_input_ui(self.input_ui, self.input_type) + self.tool_state.input_type = "D" + # PolylineDecorator.set_input_ui(self.input_ui) + # PolylineDecorator.set_tool_state(self.tool_state) + PolylineDecorator.update(event, self.tool_state, self.input_ui) tool.Blender.update_viewport() if event.value == "RELEASE" and event.type in {"D", "A"}: self.recalculate_inputs(context) self.is_input_on = True + 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.set_input_ui(self.input_ui, self.input_type) + PolylineDecorator.update(event, self.tool_state, self.input_ui) tool.Blender.update_viewport() if self.input_type in self.input_options: @@ -493,7 +505,7 @@ class DrawPolylineWall(bpy.types.Operator): 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) + PolylineDecorator.update(event, self.tool_state, self.input_ui) tool.Blender.update_viewport() if not self.is_input_on and event.value == "RELEASE" and event.type in {"RET", "NUMPAD_ENTER", "RIGHTMOUSE"}: @@ -508,16 +520,18 @@ class DrawPolylineWall(bpy.types.Operator): if is_valid: tool.Snap.insert_polyline_point(self.input_ui) self.is_input_on = False + self.tool_state.is_input_on = False self.input_type = None + self.tool_state.input_type = None self.number_input = [] self.number_output = "" - PolylineDecorator.set_input_ui(self.input_ui, self.input_type) + PolylineDecorator.update(event, self.tool_state, self.input_ui) tool.Blender.update_viewport() 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.Polyline.calculate_distance_and_angle(context, self.input_ui, self.tool_state) + PolylineDecorator.update(event, self.tool_state, self.input_ui) tool.Blender.update_viewport() if event.type in {"MIDDLEMOUSE", "WHEELUPMOUSE", "WHEELDOWNMOUSE"}: @@ -527,8 +541,10 @@ class DrawPolylineWall(bpy.types.Operator): if event.value == "RELEASE" and event.type in {"ESC"}: self.recalculate_inputs(context) self.is_input_on = False + self.tool_state.is_input_on = False self.input_type = None - PolylineDecorator.set_input_ui(self.input_ui, self.input_type) + self.tool_state.input_type = None + PolylineDecorator.update(event, self.tool_state, self.input_ui) tool.Blender.update_viewport() else: if event.value == "RELEASE" and event.type in {"ESC"}: @@ -544,21 +560,19 @@ class DrawPolylineWall(bpy.types.Operator): if context.space_data.type == "VIEW_3D": PolylineDecorator.install(context) tool.Snap.clear_snapping_point() - tool.Snap.set_use_default_container(True) - tool.Snap.clear_snap_angle() - PolylineDecorator.set_use_default_container(True) - tool.Polyline.set_use_default_container(True) + + self.tool_state.use_default_container = True + tool.Snap.set_tool_state(self.tool_state) + tool.Snap.set_snap_axis_method(None) tool.Snap.set_snap_plane_method("XY") - PolylineDecorator.set_instructions(self.instructions) - PolylineDecorator.set_input_ui(self.input_ui, self.input_type) + PolylineDecorator.update(event, self.tool_state, self.input_ui) 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) + 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, detected_snaps) - PolylineDecorator.set_mouse_position(event) - tool.Polyline.calculate_distance_and_angle(context, self.is_input_on, self.input_ui) + tool.Polyline.calculate_distance_and_angle(context, self.input_ui, self.tool_state) tool.Blender.update_viewport() context.window_manager.modal_handler_add(self) diff --git a/src/bonsai/bonsai/tool/polyline.py b/src/bonsai/bonsai/tool/polyline.py index f107d7ece5..5b43a9fd7e 100644 --- a/src/bonsai/bonsai/tool/polyline.py +++ b/src/bonsai/bonsai/tool/polyline.py @@ -83,6 +83,26 @@ class PolylineUI: return format_distance(value * factor, precision=precision, suppress_zero_inches=True, in_unit_length=True) +@dataclass +class ToolState: + use_default_container: bool = None + snap_angle: float = None + is_input_on: bool = None + # angle_axis_start: Vector + # angle_axis_end: Vector + axis_method: str = None + plane_method: str = None + instructions: str = """TAB: Cycle Input + M: Modify Snap Point + C: Close + Backspace: Remove + X Y: Axis + Shift: Lock axis +""" + snap_info: str = None + # input_state: str = None | "Select" | "Edit" + input_type: str = None + class Polyline(bonsai.core.tool.Polyline): @@ -91,37 +111,11 @@ class Polyline(bonsai.core.tool.Polyline): return PolylineUI(init_z=init_z, init_area=init_area) @classmethod - def set_mouse_position(cls, event): - cls.mouse_pos = event.mouse_region_x, event.mouse_region_y + def create_tool_state(cls): + return ToolState() @classmethod - def set_angle_axis_line(cls, start, end): - cls.axis_start = start - cls.axis_end = end - - @classmethod - def set_axis_rectangle(cls, corners): - cls.axis_rectangle = [*corners] - - @classmethod - def set_use_default_container(cls, value=False): - cls.use_default_container = value - - @classmethod - def set_plane(cls, plane_origin, plane_normal): - cls.plane_origin = plane_origin - cls.plane_normal = plane_normal - - @classmethod - def set_instructions(cls, instructions): - cls.instructions = instructions - - @classmethod - def set_snap_info(cls, snap_info): - cls.snap_info = snap_info - - @classmethod - def calculate_distance_and_angle(cls, context, is_input_on, input_ui): + def calculate_distance_and_angle(cls, context, input_ui, tool_state): try: polyline_data = context.scene.BIMModelProperties.polyline_point @@ -138,8 +132,8 @@ class Polyline(bonsai.core.tool.Polyline): else: last_point = Vector((0, 0, 0)) - if is_input_on: - if cls.use_default_container: + if tool_state.is_input_on: + if tool_state.use_default_container: snap_vector = Vector( (input_ui.get_number_value("X"), input_ui.get_number_value("Y"), default_container_elevation) ) @@ -148,7 +142,7 @@ class Polyline(bonsai.core.tool.Polyline): (input_ui.get_number_value("X"), input_ui.get_number_value("Y"), input_ui.get_number_value("Z")) ) else: - if cls.use_default_container: + if tool_state.use_default_container: snap_vector = Vector((snap_prop.x, snap_prop.y, default_container_elevation)) else: snap_vector = Vector((snap_prop.x, snap_prop.y, snap_prop.z)) @@ -229,7 +223,7 @@ class Polyline(bonsai.core.tool.Polyline): return @classmethod - def calculate_x_y_and_z(cls, context, input_ui): + def calculate_x_y_and_z(cls, context, input_ui, tool_state): try: polyline_data = context.scene.BIMModelProperties.polyline_point default_container_elevation = tool.Ifc.get_object(tool.Root.get_default_container()).location.z @@ -242,7 +236,7 @@ class Polyline(bonsai.core.tool.Polyline): snap_prop = context.scene.BIMModelProperties.snap_mouse_point[0] snap_vector = Vector((snap_prop.x, snap_prop.y, snap_prop.z)) - if cls.use_default_container: + if tool_state.use_default_container: snap_vector = Vector((snap_prop.x, snap_prop.y, default_container_elevation)) else: snap_vector = Vector((snap_prop.x, snap_prop.y, snap_prop.z)) diff --git a/src/bonsai/bonsai/tool/snap.py b/src/bonsai/bonsai/tool/snap.py index f0668f2320..3164fccabc 100644 --- a/src/bonsai/bonsai/tool/snap.py +++ b/src/bonsai/bonsai/tool/snap.py @@ -27,15 +27,14 @@ from lark import Lark, Transformer class Snap(bonsai.core.tool.Snap): - mouse_pos = None snap_angle = None - use_default_container = False + tool_state = None snap_plane_method = None snap_axis_method = None @classmethod - def set_use_default_container(cls, value=True): - cls.use_default_container = value + def set_tool_state(cls, tool_state): + cls.tool_state = tool_state @classmethod def set_snap_plane_method(cls, value=True): @@ -145,7 +144,7 @@ class Snap(bonsai.core.tool.Snap): a = input_ui.get_formatted_value("A") snap_vertex = bpy.context.scene.BIMModelProperties.snap_mouse_point[0] - if cls.use_default_container: + if cls.tool_state.use_default_container: z = tool.Ifc.get_object(tool.Root.get_default_container()).location.z if x is None and y is None: @@ -289,11 +288,10 @@ class Snap(bonsai.core.tool.Snap): return sorted_intersections[0], "Mix" @classmethod - def detect_snapping_points(cls, context, event, objs_2d_bbox): - region = context.region + def detect_snapping_points(cls, context, event, objs_2d_bbox, tool_state): rv3d = context.region_data space = context.space_data - cls.mouse_pos = event.mouse_region_x, event.mouse_region_y + mouse_pos = event.mouse_region_x, event.mouse_region_y detected_snaps = [] snap_threshold = 0.3 @@ -322,7 +320,7 @@ class Snap(bonsai.core.tool.Snap): plane_normal = view_direction.normalized() if cls.snap_plane_method == "XY" or (not cls.snap_plane_method and cls.snap_axis_method in {"X", "Y"}): - if cls.use_default_container: + if cls.tool_state.use_default_container: plane_origin = Vector((0, 0, elevation)) elif not last_polyline_point: plane_origin = Vector((0, 0, 0)) @@ -342,7 +340,7 @@ class Snap(bonsai.core.tool.Snap): return plane_origin, plane_normal - def cast_rays_and_get_best_object(objs_to_raycast): + def cast_rays_and_get_best_object(objs_to_raycast, mouse_pos): best_length_squared = 1.0 best_obj = None best_hit = None @@ -352,13 +350,13 @@ class Snap(bonsai.core.tool.Snap): hit, normal, face_index = tool.Raycast.obj_ray_cast(context, event, obj) if hit is None: # Tried original mouse position. Now it will try the offsets. - original_mouse_pos = cls.mouse_pos + original_mouse_pos = mouse_pos for value in mouse_offset: - cls.mouse_pos = tuple(x + y for x, y in zip(original_mouse_pos, value)) - hit, normal, face_index = tool.Raycast.obj_ray_cast(context, event, obj, cls.mouse_pos) + mouse_pos = tuple(x + y for x, y in zip(original_mouse_pos, value)) + hit, normal, face_index = tool.Raycast.obj_ray_cast(context, event, obj, mouse_pos) if hit: break - cls.mouse_pos = original_mouse_pos + mouse_pos = original_mouse_pos if hit is not None: hit_world = obj.original.matrix_world @ hit @@ -380,14 +378,14 @@ class Snap(bonsai.core.tool.Snap): objs_to_raycast = [] for obj, bbox_2d in objs_2d_bbox: if obj.type == "MESH" and bbox_2d: - if tool.Raycast.intersect_mouse_2d_bounding_box(cls.mouse_pos, bbox_2d, offset): + if tool.Raycast.intersect_mouse_2d_bounding_box(mouse_pos, bbox_2d, offset): if space.local_view: if obj.local_view_get(context.space_data): objs_to_raycast.append(obj) else: objs_to_raycast.append(obj) # Obj - snap_obj, hit, face_index = cast_rays_and_get_best_object(objs_to_raycast) + snap_obj, hit, face_index = cast_rays_and_get_best_object(objs_to_raycast, mouse_pos) if hit is not None: detected_snaps.append({"Object": (snap_obj, hit, face_index)}) @@ -423,29 +421,29 @@ class Snap(bonsai.core.tool.Snap): rot_intersection = None if not cls.snap_plane_method: if cls.snap_axis_method == "X": - cls.snap_angle = 180 + tool_state.snap_angle = 180 if cls.snap_axis_method == "Y": - cls.snap_angle = 90 + tool_state.snap_angle = 90 if cls.snap_axis_method == "Z": - cls.snap_angle = 90 + tool_state.snap_angle = 90 if cls.snap_axis_method: # Doesn't update snap_angle so that it keeps in the same axis - rot_intersection, _, axis_start, axis_end = cls.snap_on_axis(intersection, cls.snap_angle) + rot_intersection, _, axis_start, axis_end = cls.snap_on_axis(intersection, tool_state.snap_angle) if cls.snap_plane_method: if cls.snap_plane_method in {"XY", "XZ"} and cls.snap_axis_method == "X": - cls.snap_angle = 180 + tool_state.snap_angle = 180 if cls.snap_plane_method in {"XY", "YZ"} and cls.snap_axis_method == "Y": - cls.snap_angle = 90 + tool_state.snap_angle = 90 if cls.snap_plane_method in {"YZ"} and cls.snap_axis_method == "Z": - cls.snap_angle = 180 + tool_state.snap_angle = 180 if cls.snap_plane_method in {"XZ"} and cls.snap_axis_method == "Z": - cls.snap_angle = 90 + tool_state.snap_angle = 90 if event.shift or cls.snap_axis_method: # Doesn't update snap_angle so that it keeps in the same axis - rot_intersection, _, axis_start, axis_end = cls.snap_on_axis(intersection, cls.snap_angle) + rot_intersection, _, axis_start, axis_end = cls.snap_on_axis(intersection, tool_state.snap_angle) else: - rot_intersection, cls.snap_angle, axis_start, axis_end = cls.snap_on_axis(intersection, None) + rot_intersection, tool_state.snap_angle, axis_start, axis_end = cls.snap_on_axis(intersection, None) if rot_intersection: detected_snaps.append({"Axis": (rot_intersection, axis_start, axis_end)})