From 565ae33c171c5dc2b58d71b1d222ab60749a8010 Mon Sep 17 00:00:00 2001 From: Ryan Schultz Date: Sun, 26 Apr 2026 17:41:12 -0500 Subject: [PATCH] Closes #7691: Enable bim.measure_tool in edit mode Register Shift+M in the Mesh keymap so the measure tool can be invoked while in EDIT_MESH mode without requiring the Explore workspace tool. Append measure controls (type selector + clear button) to VIEW3D_HT_tool_header so they are visible in edit mode. Guard the object deselect in hotkey_S_M to OBJECT mode only, since deselecting objects has no meaning in edit mode. Fix snapping: obj.ray_cast() has no valid BVH tree for the active edit-mode mesh, so vertex/edge snapping was silently failing. Replace with a BMesh proximity pass via bmesh.from_edit_mesh() when context.mode == "EDIT_MESH". Generated with the assistance of an AI coding tool. --- .../bonsai/bim/module/project/__init__.py | 8 +++++++ .../bonsai/bim/module/project/workspace.py | 21 +++++++++++++++++-- src/bonsai/bonsai/tool/snap.py | 10 +++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/project/__init__.py b/src/bonsai/bonsai/bim/module/project/__init__.py index e83da49c5b2..530d4a06451 100644 --- a/src/bonsai/bonsai/bim/module/project/__init__.py +++ b/src/bonsai/bonsai/bim/module/project/__init__.py @@ -124,8 +124,16 @@ def register(): kmi.properties.should_save_as = False addon_keymaps.append((km, kmi)) + km = wm.keyconfigs.addon.keymaps.new(name="Mesh", space_type="VIEW_3D") + kmi = km.keymap_items.new("bim.explore_hotkey", "M", "PRESS", shift=True) + kmi.properties.hotkey = "S_M" + addon_keymaps.append((km, kmi)) + + bpy.types.VIEW3D_HT_tool_header.append(workspace.draw_measure_tool_header) + def unregister(): + bpy.types.VIEW3D_HT_tool_header.remove(workspace.draw_measure_tool_header) if not bpy.app.background: bpy.utils.unregister_tool(workspace.ExploreTool) del bpy.types.Scene.BIMProjectProperties diff --git a/src/bonsai/bonsai/bim/module/project/workspace.py b/src/bonsai/bonsai/bim/module/project/workspace.py index bd60f4975a4..48813351418 100644 --- a/src/bonsai/bonsai/bim/module/project/workspace.py +++ b/src/bonsai/bonsai/bim/module/project/workspace.py @@ -125,8 +125,9 @@ def hotkey_A_C(self): bpy.ops.bim.enable_culling("INVOKE_DEFAULT") def hotkey_S_M(self): - for obj in tool.Blender.get_selected_objects(): - obj.select_set(False) + if bpy.context.mode == "OBJECT": + for obj in tool.Blender.get_selected_objects(): + obj.select_set(False) measure_type = tool.Project.get_measure_tool_settings().measurement_type if measure_type == "FACE_AREA": bpy.ops.bim.measure_face_area_tool("INVOKE_DEFAULT") @@ -158,3 +159,19 @@ def hotkey_S_H(self) -> None: def hotkey_A_H(self) -> None: bpy.ops.bim.hide_queried_linked_element(unhide_all=True) + + +def draw_measure_tool_header(self, context: bpy.types.Context) -> None: + if context.mode != "EDIT_MESH": + return + prop = tool.Project.get_measure_tool_settings() + layout = self.layout + layout.separator() + row = layout.row(align=True) + row.label(text="", icon="EVENT_SHIFT") + row.label(text="", icon="EVENT_M") + op = row.operator("bim.explore_hotkey", text="Measure Tool", icon="CON_DISTLIMIT") + op.hotkey = "S_M" + row = layout.row(align=True) + row.prop(prop, "measurement_type", text="Measure Type", expand=True, icon_only=True, emboss=True) + row.operator("bim.clear_measurement", text="", icon="X") diff --git a/src/bonsai/bonsai/tool/snap.py b/src/bonsai/bonsai/tool/snap.py index 5755c02ba76..ba27df8277d 100644 --- a/src/bonsai/bonsai/tool/snap.py +++ b/src/bonsai/bonsai/tool/snap.py @@ -419,6 +419,16 @@ def select_plane_method(): closest_snap.append(point) detected_snaps = closest_snap + # Edit mode: obj.ray_cast() has no valid BVH for the active edit-mesh, so + # use BMesh proximity directly to snap to its current vertices and edges. + if context.mode == "EDIT_MESH" and context.edit_object: + edit_obj = context.edit_object + bm = bmesh.from_edit_mesh(edit_obj.data) + snap_points = tool.Raycast.ray_cast_by_proximity(context, event, edit_obj, custom_bmesh=bm.copy()) + for point in snap_points: + point["group"] = "Object" + detected_snaps.append(point) + # snap to cut geometry (e.g. in plan view) if CutDecorator.installed: cut_snaps = []