From 9b5f20e1dc98adc9e69cac842ddd52c6b2e690f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Thu, 3 Oct 2024 21:45:26 -0300 Subject: [PATCH] Initial implementation of measure tool modes: single, polyline and area. - Single: draws a single measurement that shows the lines and dimensions for x, y and z. - Polyline: it's how it was already working - Area: show the area value in the input panel and creates the polygon shape that represents the area. Only works for coplanar points. The mode can be chosen by clicking the option icon in the workspace menu. --- .../bonsai/bim/module/model/decorator.py | 125 +++++++++++++++--- .../bonsai/bim/module/project/__init__.py | 3 + .../bonsai/bim/module/project/operator.py | 14 +- src/bonsai/bonsai/bim/module/project/prop.py | 10 ++ .../bonsai/bim/module/project/workspace.py | 14 +- src/bonsai/bonsai/tool/polyline.py | 6 +- 6 files changed, 149 insertions(+), 23 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/decorator.py b/src/bonsai/bonsai/bim/module/model/decorator.py index 5f53effcd6..b059d9a6bd 100644 --- a/src/bonsai/bonsai/bim/module/model/decorator.py +++ b/src/bonsai/bonsai/bim/module/model/decorator.py @@ -319,6 +319,9 @@ class PolylineDecorator: handler = cls() cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_snap_point, (context,), "WINDOW", "POST_PIXEL")) cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_measurements, (context,), "WINDOW", "POST_PIXEL")) + cls.handlers.append( + SpaceView3D.draw_handler_add(handler.draw_measurement_axis, (context,), "WINDOW", "POST_VIEW") + ) cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_input_ui, (context,), "WINDOW", "POST_PIXEL")) cls.handlers.append( SpaceView3D.draw_handler_add(handler.draw_product_preview, (context,), "WINDOW", "POST_VIEW") @@ -350,14 +353,50 @@ class PolylineDecorator: cls.axis_start = start cls.axis_end = end - # @classmethod - # def set_axis_rectangle(cls, corners): - # cls.axis_rectangle = [*corners] - @classmethod def set_tool_state(cls, tool_state): cls.tool_state = tool_state + def calculate_measurement_x_y_and_z(self, context): + measurement_prop = context.scene.BIMPolylineProperties.polyline_point + + if len(measurement_prop) == 0 or len(measurement_prop) > 2: + return None, None + + start = measurement_prop[0] + if len(measurement_prop) == 1: + end = context.scene.BIMPolylineProperties.snap_mouse_point[0] + else: + end = measurement_prop[1] + + x_axis = (Vector((start.x, start.y, start.z)), Vector((end.x, start.y, start.z))) + y_axis = (Vector((end.x, start.y, start.z)), Vector((end.x, end.y, start.z))) + z_axis = (Vector((end.x, end.y, start.z)), Vector((end.x, end.y, end.z))) + x_middle = (x_axis[1] + x_axis[0]) / 2 + y_middle = (y_axis[1] + y_axis[0]) / 2 + z_middle = (z_axis[1] + z_axis[0]) / 2 + + return (x_axis, y_axis, z_axis), (x_middle, y_middle, z_middle) + + def calculate_polygon(self, points): + bm = bmesh.new() + + new_verts = [bm.verts.new(v) for v in points] + new_edges = [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(points) - 1)] + + bm.verts.index_update() + bm.edges.index_update() + + new_faces = bmesh.ops.contextual_create(bm, geom=bm.edges) + + bm.verts.index_update() + bm.edges.index_update() + tris = [[loop.vert.index for loop in triangles] for triangles in bm.calc_loop_triangles()] + + bm.free() + + return tris + def draw_batch(self, shader_type, content_pos, color, indices=None): shader = self.line_shader if shader_type == "LINES" else self.shader batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices) @@ -424,7 +463,6 @@ class PolylineDecorator: bm.verts.index_update() bm.edges.index_update() edges = [[v.index for v in e.verts] for e in bm.edges] - print(edges) tris = [[l.vert.index for l in loop] for loop in bm.calc_loop_triangles()] all_edges.extend(edges) all_tris.extend(tris) @@ -491,6 +529,7 @@ class PolylineDecorator: def draw_measurements(self, context): region = context.region rv3d = region.data + measure_type = context.scene.MeasureToolSettings.measure_type measurement_prop = context.scene.BIMPolylineProperties.polyline_point self.addon_prefs = tool.Blender.get_addon_preferences() @@ -526,8 +565,65 @@ class PolylineDecorator: text_dim = blf.dimensions(self.font_id, text) self.draw_text_background(context, coords_angle, text_dim) blf.draw(self.font_id, text) + + if measure_type == "SINGLE": + axis, axis_middle = self.calculate_measurement_x_y_and_z(context) + for i, measurement in enumerate(axis_middle): + coords_measurement = view3d_utils.location_3d_to_region_2d(region, rv3d, measurement) + blf.position(self.font_id, coords_measurement[0], coords_measurement[1], 0) + value = round((axis[i][1] - axis[i][0]).length, 4) + direction = axis[i][1] - axis[i][0] + if (i == 0 and direction.x < 0) or (i == 1 and direction.y < 0) or (i == 2 and direction.z < 0): + value = -value + prefix = "xyz"[i] + text = f"{prefix}: {str(value)}" + text_dim = blf.dimensions(self.font_id, text) + self.draw_text_background(context, coords_measurement, text_dim) + blf.draw(self.font_id, text) + blf.disable(self.font_id, blf.SHADOW) + def draw_measurement_axis(self, context): + self.addon_prefs = tool.Blender.get_addon_preferences() + decorator_color = self.addon_prefs.decorations_colour + decorator_color_special = self.addon_prefs.decorator_color_special + decorator_color_selected = self.addon_prefs.decorator_color_selected + decorator_color_error = self.addon_prefs.decorator_color_error + decorator_color_unselected = self.addon_prefs.decorator_color_unselected + decorator_color_background = self.addon_prefs.decorator_color_background + theme = context.preferences.themes.items()[0][1] + decorator_color_object_active = (*theme.view_3d.object_active, 1) # unwrap color values and adds alpha=1 + decorator_color_x_axis = (*theme.user_interface.axis_x, 1) + decorator_color_y_axis = (*theme.user_interface.axis_y, 1) + decorator_color_z_axis = (*theme.user_interface.axis_z, 1) + + gpu.state.blend_set("ALPHA") + self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR") + self.line_shader.bind() # required to be able to change uniforms of the shader + # POLYLINE_UNIFORM_COLOR specific uniforms + self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height)) + + # general shader + self.shader = gpu.shader.from_builtin("UNIFORM_COLOR") + gpu.state.point_size_set(6) + + self.font_id = 1 + font_size = tool.Blender.scale_font_size(12) + blf.size(self.font_id, font_size) + blf.enable(self.font_id, blf.SHADOW) + blf.shadow(self.font_id, 6, 0, 0, 0, 1) + color = self.addon_prefs.decorations_colour + + blf.color(self.font_id, *color) + + measure_type = context.scene.MeasureToolSettings.measure_type + if measure_type == "SINGLE": + axis, _ = self.calculate_measurement_x_y_and_z(context) + x_axis, y_axis, z_axis = axis + self.draw_batch("LINES", [*x_axis], decorator_color_x_axis, [(0, 1)]) + self.draw_batch("LINES", [*y_axis], decorator_color_y_axis, [(0, 1)]) + self.draw_batch("LINES", [*z_axis], decorator_color_z_axis, [(0, 1)]) + def draw_snap_point(self, context): self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR") self.line_shader.bind() # required to be able to change uniforms of the shader @@ -648,18 +744,15 @@ class PolylineDecorator: self.line_shader.uniform_float("lineWidth", 0.75) self.draw_batch("LINES", [self.axis_start, self.axis_end], axis_color, [(0, 1)]) - # 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 - # if self.input_panel["AREA"] and float(self.input_panel["AREA"]) > 0: # TODO Change to input_ui - # edges = [] - # for i in range(1, len(polyline_points) - 1): - # edges.append((0, i, i + 1)) - # self.draw_batch("TRIS", polyline_points, (0, 1, 0, 0.1), edges) + try: + has_area = self.input_ui.init_area + except: + has_area = False + if has_area: + if self.input_ui.get_number_value("AREA") > 0: + tris = self.calculate_polygon(polyline_points) + self.draw_batch("TRIS", polyline_points, transparent_color(decorator_color_special), tris) # Mouse points if snap_prop.snap_type in ["Plane", "Axis", "Mix"]: diff --git a/src/bonsai/bonsai/bim/module/project/__init__.py b/src/bonsai/bonsai/bim/module/project/__init__.py index bb15b2513d..5c7e27d7cd 100644 --- a/src/bonsai/bonsai/bim/module/project/__init__.py +++ b/src/bonsai/bonsai/bim/module/project/__init__.py @@ -65,6 +65,7 @@ classes = ( prop.FilterCategory, prop.Link, prop.BIMProjectProperties, + prop.MeasureToolSettings, ui.BIM_MT_new_project, ui.BIM_MT_project, ui.BIM_PT_new_project_wizard, @@ -92,6 +93,7 @@ def register(): if not bpy.app.background: bpy.utils.register_tool(workspace.ExploreTool, after={"builtin.transform"}, separator=True, group=False) bpy.types.Scene.BIMProjectProperties = bpy.props.PointerProperty(type=prop.BIMProjectProperties) + bpy.types.Scene.MeasureToolSettings = bpy.props.PointerProperty(type=prop.MeasureToolSettings) bpy.app.handlers.load_post.append(decorator.toggle_decorations_on_load) bpy.types.TOPBAR_MT_file_import.append(ui.file_import_menu) bpy.types.TOPBAR_MT_file.prepend(ui.file_menu) @@ -115,6 +117,7 @@ def unregister(): if not bpy.app.background: bpy.utils.unregister_tool(workspace.ExploreTool) del bpy.types.Scene.BIMProjectProperties + del bpy.types.Scene.MeasureToolSettings bpy.app.handlers.load_post.remove(decorator.toggle_decorations_on_load) bpy.types.TOPBAR_MT_file.remove(ui.file_menu) bpy.types.TOPBAR_MT_file_context_menu.remove(ui.file_menu) diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index 7ce93f02e8..570fe0bd69 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -2372,13 +2372,18 @@ class MeasureTool(bpy.types.Operator, PolylineOperator): bl_label = "Measure Tool" bl_options = {"REGISTER", "UNDO"} + measure_type: bpy.props.StringProperty() + @classmethod def poll(cls, context): return context.space_data.type == "VIEW_3D" def __init__(self): super().__init__() - self.input_ui = tool.Polyline.create_input_ui(init_z=True) + if self.measure_type == 'AREA': + self.input_ui = tool.Polyline.create_input_ui(init_z=True, init_area=True) + else: + self.input_ui = tool.Polyline.create_input_ui(init_z=True) self.input_options = ["D", "A", "X", "Y", "Z"] self.instructions = """TAB: Cycle Input D: Distance Input @@ -2405,11 +2410,15 @@ class MeasureTool(bpy.types.Operator, PolylineOperator): self.handle_snap_selection(context, event) + single_mode = False + if self.measure_type == "SINGLE" and len(context.scene.BIMPolylineProperties.polyline_point) >= 2: + single_mode = True + if ( not self.tool_state.is_input_on and event.value == "RELEASE" and event.type in {"RET", "NUMPAD_ENTER", "RIGHTMOUSE"} - ): + ) or single_mode: context.workspace.status_text_set(text=None) PolylineDecorator.uninstall() tool.Snap.move_polyline_to_measure() @@ -2421,6 +2430,7 @@ class MeasureTool(bpy.types.Operator, PolylineOperator): self.handle_keyboard_input(context, event) self.handle_inserting_polyline(context, event) + tool.Polyline.calculate_area(context, self.input_ui) if event.type == "E": context.scene.BIMPolylineProperties.measure_polyline.clear() diff --git a/src/bonsai/bonsai/bim/module/project/prop.py b/src/bonsai/bonsai/bim/module/project/prop.py index 7b1a7c8fac..23f7258ed6 100644 --- a/src/bonsai/bonsai/bim/module/project/prop.py +++ b/src/bonsai/bonsai/bim/module/project/prop.py @@ -229,3 +229,13 @@ class BIMProjectProperties(PropertyGroup): def get_library_element_index(self, lib_element): return next((i for i in range(len(self.library_elements)) if self.library_elements[i] == lib_element)) + + +class MeasureToolSettings(PropertyGroup): + measure_type_items = [ + ("SINGLE", "SINGLE", "Single", "FIXED_SIZE",1), + ("POLYLINE", "POLYLINE", "Polyline", "DRIVER_ROTATIONAL_DIFFERENCE", 2), + ("AREA", "AREA", "Area", "OUTLINER_DATA_LIGHTPROBE", 3), + ] + + measure_type: bpy.props.EnumProperty(items=measure_type_items, default="POLYLINE") diff --git a/src/bonsai/bonsai/bim/module/project/workspace.py b/src/bonsai/bonsai/bim/module/project/workspace.py index 7ebbd3b164..6cfe2f86fc 100644 --- a/src/bonsai/bonsai/bim/module/project/workspace.py +++ b/src/bonsai/bonsai/bim/module/project/workspace.py @@ -58,9 +58,16 @@ class ExploreTool(bpy.types.WorkSpaceTool): row = layout.row(align=True) row.label(text="", icon="EVENT_ALT") row.label(text="Disable Culling" if LinksData.enable_culling else "Enable Culling", icon="EVENT_C") + + prop = context.scene.MeasureToolSettings row = layout.row(align=True) row.label(text="", icon="EVENT_SHIFT") - row.label(text="Measure Tool", icon="EVENT_M") + row.label(text="", icon="EVENT_M") + row = layout.row(align=True) + op = row.operator("bim.explore_hotkey", text="Measure Tool", icon="CON_DISTLIMIT") + op.hotkey = "S_M" + row = layout.row(align=True) + row.prop(prop, "measure_type", text="Measure Type", expand=True, icon_only=True, emboss=True) class ExploreHotkey(bpy.types.Operator): @@ -94,4 +101,7 @@ class ExploreHotkey(bpy.types.Operator): bpy.ops.bim.enable_culling("INVOKE_DEFAULT") def hotkey_S_M(self): - bpy.ops.bim.measure_tool("INVOKE_DEFAULT") + for obj in tool.Blender.get_selected_objects(): + obj.select_set(False) + measure_type = bpy.context.scene.MeasureToolSettings.measure_type + bpy.ops.bim.measure_tool("INVOKE_DEFAULT", measure_type=measure_type) diff --git a/src/bonsai/bonsai/tool/polyline.py b/src/bonsai/bonsai/tool/polyline.py index 38d6651f8b..532d795d1b 100644 --- a/src/bonsai/bonsai/tool/polyline.py +++ b/src/bonsai/bonsai/tool/polyline.py @@ -45,7 +45,7 @@ class Polyline(bonsai.core.tool.Polyline): if self.init_z: self._Z = "" if self.init_area: - self._AREA = "" + self._AREA = "0" def set_value(self, attribute_name, value): value = str(value) @@ -222,8 +222,8 @@ class Polyline(bonsai.core.tool.Polyline): else: area = 0 - if input_ui.get_text_value("A") is not None: - input_ui.set_value("A", area) + if input_ui.get_text_value("AREA") is not None: + input_ui.set_value("AREA", area) return @classmethod