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.
This commit is contained in:
Bruno Perdigão
2024-10-03 21:45:26 -03:00
parent 864cc10b24
commit 9b5f20e1dc
6 changed files with 149 additions and 23 deletions
+109 -16
View File
@@ -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"]:
@@ -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)
@@ -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()
@@ -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")
@@ -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)
+3 -3
View File
@@ -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