From b057f8945986283e031724fe1c3125baedc4d410 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Fri, 4 Oct 2024 17:10:13 -0300 Subject: [PATCH] Measure tool - single and area measurement now persist on screen. Also, the measure polyline properties now stores the area and the total length that will be used by the decorator in later development. --- .../bonsai/bim/module/model/decorator.py | 2 +- src/bonsai/bonsai/bim/module/model/prop.py | 4 +- .../bonsai/bim/module/project/decorator.py | 109 +++++++++++++++--- .../bonsai/bim/module/project/operator.py | 2 +- src/bonsai/bonsai/tool/polyline.py | 28 ++--- src/bonsai/bonsai/tool/snap.py | 22 +++- 6 files changed, 134 insertions(+), 33 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/decorator.py b/src/bonsai/bonsai/bim/module/model/decorator.py index 95b038e68b..f7e80b4802 100644 --- a/src/bonsai/bonsai/bim/module/model/decorator.py +++ b/src/bonsai/bonsai/bim/module/model/decorator.py @@ -573,7 +573,7 @@ class PolylineDecorator: 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] - formatted_value = self.input_ui.format_input_ui_units(context, value) + formatted_value = tool.Polyline.format_input_ui_units(context, value) text = f"{prefix}: {formatted_value}" text_length = blf.dimensions(self.font_id, text) self.draw_text_background(context, dim_text_coords, text_length) diff --git a/src/bonsai/bonsai/bim/module/model/prop.py b/src/bonsai/bonsai/bim/module/model/prop.py index b88c843cad..ef1480bbdf 100644 --- a/src/bonsai/bonsai/bim/module/model/prop.py +++ b/src/bonsai/bonsai/bim/module/model/prop.py @@ -751,8 +751,10 @@ class PolylinePoint(PropertyGroup): class MeasurePolyline(PropertyGroup): - polyline_point: bpy.props.CollectionProperty(type=PolylinePoint) + polyline_points: bpy.props.CollectionProperty(type=PolylinePoint) measurement_type: bpy.props.StringProperty(name="Measurement Type") + area: bpy.props.StringProperty(name="Measured Area") + total_length: bpy.props.StringProperty(name="Total Length") class BIMPolylineProperties(PropertyGroup): diff --git a/src/bonsai/bonsai/bim/module/project/decorator.py b/src/bonsai/bonsai/bim/module/project/decorator.py index c3ead90565..674d859978 100644 --- a/src/bonsai/bonsai/bim/module/project/decorator.py +++ b/src/bonsai/bonsai/bim/module/project/decorator.py @@ -42,6 +42,11 @@ def toggle_decorations_on_load(*args): # as queried object is linked from separate .blend file. +def transparent_color(color, alpha=0.1): + color = [i for i in color] + color[3] = alpha + return color + class ProjectDecorator: installed = None @@ -239,6 +244,45 @@ class MeasureDecorator: shader.uniform_float("color", color) batch.draw(shader) + def calculate_measurement_x_y_and_z(self, context, measurement_prop): + + 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_text_background(self, context, coords_dim, text_dim): padding = 5 theme = context.preferences.themes.items()[0][1] @@ -255,6 +299,7 @@ class MeasureDecorator: def draw_measurements(self, context): region = context.region rv3d = region.data + self.addon_prefs = tool.Blender.get_addon_preferences() self.font_id = 1 self.shader = gpu.shader.from_builtin("UNIFORM_COLOR") @@ -263,36 +308,56 @@ class MeasureDecorator: 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) + + blf.color(self.font_id, *color) measure_data = context.scene.BIMPolylineProperties.measurement_polyline for data in measure_data: - measurement_prop = data.polyline_point + measurement_prop = data.polyline_points + measure_type = data.measurement_type for i in range(len(measurement_prop)): if i == 0: continue - pos_dim = (Vector(measurement_prop[i].position) + Vector(measurement_prop[i - 1].position)) / 2 - coords_dim = view3d_utils.location_3d_to_region_2d(region, rv3d, pos_dim) + dim_text_pos = (Vector(measurement_prop[i].position) + Vector(measurement_prop[i - 1].position)) / 2 + dim_text_coords = view3d_utils.location_3d_to_region_2d(region, rv3d, dim_text_pos) formatted_value = measurement_prop[i].dim - blf.position(self.font_id, coords_dim[0], coords_dim[1], 0) + blf.position(self.font_id, dim_text_coords[0], dim_text_coords[1], 0) text = "d: " + formatted_value - text_dim = blf.dimensions(self.font_id, text) - self.draw_text_background(context, coords_dim, text_dim) + text_length = blf.dimensions(self.font_id, text) + self.draw_text_background(context, dim_text_coords, text_length) blf.draw(self.font_id, text) if i == 1: continue - pos_angle = measurement_prop[i - 1].position - coords_angle = view3d_utils.location_3d_to_region_2d(region, rv3d, pos_angle) - blf.position(self.font_id, coords_angle[0], coords_angle[1], 0) + angle_text_pos = measurement_prop[i - 1].position + angle_text_coords = view3d_utils.location_3d_to_region_2d(region, rv3d, angle_text_pos) + blf.position(self.font_id, angle_text_coords[0], angle_text_coords[1], 0) text = "a: " + measurement_prop[i].angle - text_dim = blf.dimensions(self.font_id, text) - self.draw_text_background(context, coords_angle, text_dim) + text_length = blf.dimensions(self.font_id, text) + self.draw_text_background(context, angle_text_coords, text_length) blf.draw(self.font_id, text) - blf.disable(self.font_id, blf.SHADOW) + + if measure_type == "SINGLE": + axis_line, axis_line_center = self.calculate_measurement_x_y_and_z(context, measurement_prop) + for i, dim_text_pos in enumerate(axis_line_center): + dim_text_coords = view3d_utils.location_3d_to_region_2d(region, rv3d, dim_text_pos) + blf.position(self.font_id, dim_text_coords[0], dim_text_coords[1], 0) + value = round((axis_line[i][1] - axis_line[i][0]).length, 4) + direction = axis_line[i][1] - axis_line[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] + formatted_value = tool.Polyline.format_input_ui_units(context, value) + text = f"{prefix}: {formatted_value}" + text_length = blf.dimensions(self.font_id, text) + self.draw_text_background(context, dim_text_coords, text_length) + blf.draw(self.font_id, text) + + blf.disable(self.font_id, blf.SHADOW) + def __call__(self, context): @@ -321,7 +386,7 @@ class MeasureDecorator: measure_data = context.scene.BIMPolylineProperties.measurement_polyline for data in measure_data: - polyline_data = data.polyline_point + polyline_data = data.polyline_points polyline_points = [] polyline_edges = [] @@ -332,6 +397,22 @@ class MeasureDecorator: for i in range(len(polyline_points) - 1): polyline_edges.append([i, i + 1]) + # Lines for X, Y, Z of single measure + measure_type = data.measurement_type + if measure_type == "SINGLE": + axis, _ = self.calculate_measurement_x_y_and_z(context, polyline_data) + 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)]) + + # Area highlight + area = data.area + if area: + if float(area) > 0: + tris = self.calculate_polygon(polyline_points) + self.draw_batch("TRIS", polyline_points, transparent_color(decorator_color_special), tris) + # Draw polyline with selected points self.line_shader.uniform_float("lineWidth", 2.0) self.draw_batch("POINTS", polyline_points, decorator_color_special) diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index ae685a65ea..9838033d03 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -2421,7 +2421,7 @@ class MeasureTool(bpy.types.Operator, PolylineOperator): ) or single_mode: context.workspace.status_text_set(text=None) PolylineDecorator.uninstall() - tool.Snap.move_polyline_to_measure() + tool.Snap.move_polyline_to_measure(context, self.input_ui) tool.Snap.clear_polyline() MeasureDecorator.install(context) tool.Blender.update_viewport() diff --git a/src/bonsai/bonsai/tool/polyline.py b/src/bonsai/bonsai/tool/polyline.py index cd6f13fc84..adcae9675d 100644 --- a/src/bonsai/bonsai/tool/polyline.py +++ b/src/bonsai/bonsai/tool/polyline.py @@ -71,20 +71,8 @@ class Polyline(bonsai.core.tool.Polyline): value = float(self.get_text_value(attribute_name)) return f"{value:.2f}" else: - return self.format_input_ui_units(context, value) + return Polyline.format_input_ui_units(context, value) - def format_input_ui_units(cls, context, value): - unit_system = tool.Drawing.get_unit_system() - if unit_system == "IMPERIAL": - precision = context.scene.DocProperties.imperial_precision - factor = 3.28084 - else: - precision = None - factor = 1 - if context.scene.unit_settings.length_unit == "MILLIMETERS": - factor = 1000 - - return format_distance(value * factor, precision=precision, suppress_zero_inches=True, in_unit_length=True) @dataclass class ToolState: @@ -500,3 +488,17 @@ class Polyline(bonsai.core.tool.Polyline): return True, str(result) except: return False, "0" + + @classmethod + def format_input_ui_units(cls, context, value): + unit_system = tool.Drawing.get_unit_system() + if unit_system == "IMPERIAL": + precision = context.scene.DocProperties.imperial_precision + factor = 3.28084 + else: + precision = None + factor = 1 + if context.scene.unit_settings.length_unit == "MILLIMETERS": + factor = 1000 + + return format_distance(value * factor, precision=precision, suppress_zero_inches=True, in_unit_length=True) diff --git a/src/bonsai/bonsai/tool/snap.py b/src/bonsai/bonsai/tool/snap.py index b30b8977b1..455e741c09 100644 --- a/src/bonsai/bonsai/tool/snap.py +++ b/src/bonsai/bonsai/tool/snap.py @@ -157,13 +157,13 @@ class Snap(bonsai.core.tool.Snap): polyline_data.remove(len(polyline_data) - 1) @classmethod - def move_polyline_to_measure(cls): + def move_polyline_to_measure(cls, context, input_ui): polyline_data = bpy.context.scene.BIMPolylineProperties.polyline_points measurement_data = bpy.context.scene.BIMPolylineProperties.measurement_polyline.add() measurement_type = bpy.context.scene.MeasureToolSettings.measure_type measurement_data.measurement_type = measurement_type for point in polyline_data: - measurement_point = measurement_data.polyline_point.add() + measurement_point = measurement_data.polyline_points.add() measurement_point.x = point.x measurement_point.y = point.y measurement_point.z = point.z @@ -171,6 +171,22 @@ class Snap(bonsai.core.tool.Snap): measurement_point.angle = point.angle measurement_point.position = point.position + # Add total length + total_length = 0 + for i, point in enumerate(measurement_data.polyline_points): + if i == 0: + continue + dim = float(tool.Polyline.validate_input(point.dim, "D")[1]) + total_length += dim + total_length = tool.Polyline.format_input_ui_units(context, total_length) + measurement_data.total_length = total_length + + # Add area + area = input_ui.get_number_value("AREA") + if area: + area = tool.Polyline.format_input_ui_units(context, area) + measurement_data.area = area + @classmethod def snap_on_axis(cls, intersection, tool_state, lock_angle=False): def create_axis_line_data(rot_mat, origin): @@ -396,7 +412,7 @@ class Snap(bonsai.core.tool.Snap): # Measure measure_data = context.scene.BIMPolylineProperties.measurement_polyline for measure in measure_data: - measure_points = measure.polyline_point + measure_points = measure.polyline_points snap_points = tool.Raycast.ray_cast_to_measure(context, event, measure_points) if snap_points: detected_snaps.append({"Polyline": snap_points})