mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 07:51:47 +00:00
Fix parametric dimension anchor dot clicks: two-event modal + remove stale active-obj guard
ClickNearestDimensionAnchor was firing SetDimensionAnchor immediately on LMB PRESS and returning FINISHED, which caused Blender to re-deliver the RELEASE to view3d.select — deselecting the annotation mid-flight. Rewrite as a two-event modal: PRESS starts the modal, RELEASE fires SetDimensionAnchor and exits. SetDimensionAnchor also swallows any LMB RELEASE it receives to prevent view3d.select from stealing the active object after hand-off. The pre-click active-object guard (skip if dimension not active_object) caused dots to never turn blue: view3d.select was silently replacing the dimension with the plane underneath on every line-body click, so the dimension was never the active object at the time of the dot click. Removed — the operator now selects the dimension itself before going modal, making each dot click self-contained. RADIUS_PX reduced from 60 to 15 to match the gizmo disc visual size and prevent false triggers on line-body clicks near endpoints.
This commit is contained in:
@@ -2638,10 +2638,10 @@ class ExtrusionWidget(types.GizmoGroup):
|
|||||||
class GizmoAnchorHandle(bpy.types.Gizmo):
|
class GizmoAnchorHandle(bpy.types.Gizmo):
|
||||||
"""Visual-only dot at a parametric dimension vertex.
|
"""Visual-only dot at a parametric dimension vertex.
|
||||||
|
|
||||||
No draw_select/invoke — any draw_select entry puts the gizmo in Blender's
|
No draw_select/invoke — draw_select puts the gizmo in Blender's select buffer
|
||||||
select buffer, which causes the gizmo system to consume the click even
|
and causes the gizmo system to consume clicks even without an explicit invoke,
|
||||||
without an explicit invoke. All click handling is done by the
|
blocking ClickNearestDimensionAnchor from receiving them. All click handling
|
||||||
bim.click_nearest_dimension_anchor keymap operator.
|
is done by the bim.click_nearest_dimension_anchor keymap operator.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
bl_idname = "BIM_GT_anchor_handle"
|
bl_idname = "BIM_GT_anchor_handle"
|
||||||
|
|||||||
@@ -6710,6 +6710,13 @@ class SetDimensionAnchor(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
self._cycle_hover(context)
|
self._cycle_hover(context)
|
||||||
return {"RUNNING_MODAL"}
|
return {"RUNNING_MODAL"}
|
||||||
|
|
||||||
|
if event.type == "LEFTMOUSE" and event.value == "RELEASE":
|
||||||
|
# Swallow LMB RELEASE throughout the modal's lifetime.
|
||||||
|
# When ClickNearestDimensionAnchor hands off to this modal, Blender
|
||||||
|
# re-delivers that same RELEASE here; passing it through would let
|
||||||
|
# view3d.select steal the active object away from the annotation.
|
||||||
|
return {"RUNNING_MODAL"}
|
||||||
|
|
||||||
if event.type == "LEFTMOUSE" and event.value == "PRESS":
|
if event.type == "LEFTMOUSE" and event.value == "PRESS":
|
||||||
if self._phase == "PICK_VERTEX":
|
if self._phase == "PICK_VERTEX":
|
||||||
self._handle_vertex_pick(context, event)
|
self._handle_vertex_pick(context, event)
|
||||||
@@ -7753,16 +7760,20 @@ def _find_curve_in_item(item: ifcopenshell.entity_instance) -> Optional[ifcopens
|
|||||||
|
|
||||||
|
|
||||||
class ClickNearestDimensionAnchor(bpy.types.Operator):
|
class ClickNearestDimensionAnchor(bpy.types.Operator):
|
||||||
"""LMB fallback: fire SetDimensionAnchor when cursor is within RADIUS pixels of an anchor dot.
|
"""Click handler for dimension anchor dots.
|
||||||
|
|
||||||
The gizmo handles exact hits; this catches near-misses where the cursor
|
When the active dimension's anchor dots are visible, clicking within
|
||||||
is close to a dot but didn't land inside the gizmo hit shape.
|
RADIUS_PX of a dot activates SetDimensionAnchor for that dot.
|
||||||
|
|
||||||
|
Implemented as a two-event modal (PRESS consumed in invoke, RELEASE
|
||||||
|
consumed in modal) so SetDimensionAnchor starts on a clean slate —
|
||||||
|
no stray LMB events reach it that could trigger view3d.select.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
bl_idname = "bim.click_nearest_dimension_anchor"
|
bl_idname = "bim.click_nearest_dimension_anchor"
|
||||||
bl_label = "Click Nearest Dimension Anchor"
|
bl_label = "Click Nearest Dimension Anchor"
|
||||||
|
|
||||||
RADIUS_PX = 60
|
RADIUS_PX = 15
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
from bpy_extras.view3d_utils import location_3d_to_region_2d
|
||||||
@@ -7770,8 +7781,6 @@ class ClickNearestDimensionAnchor(bpy.types.Operator):
|
|||||||
if not tool.Ifc.get():
|
if not tool.Ifc.get():
|
||||||
return {"PASS_THROUGH"}
|
return {"PASS_THROUGH"}
|
||||||
|
|
||||||
# Always use the 3D viewport WINDOW region — context.region may be a header,
|
|
||||||
# sidebar, or toolbar depending on where the click landed in the area.
|
|
||||||
region = None
|
region = None
|
||||||
rv3d = None
|
rv3d = None
|
||||||
for area in context.screen.areas:
|
for area in context.screen.areas:
|
||||||
@@ -7791,7 +7800,6 @@ class ClickNearestDimensionAnchor(bpy.types.Operator):
|
|||||||
if not region or not rv3d:
|
if not region or not rv3d:
|
||||||
return {"PASS_THROUGH"}
|
return {"PASS_THROUGH"}
|
||||||
|
|
||||||
# Convert absolute mouse position to WINDOW region-local coordinates.
|
|
||||||
cx = event.mouse_x - region.x
|
cx = event.mouse_x - region.x
|
||||||
cy = event.mouse_y - region.y
|
cy = event.mouse_y - region.y
|
||||||
|
|
||||||
@@ -7802,8 +7810,6 @@ class ClickNearestDimensionAnchor(bpy.types.Operator):
|
|||||||
best_idx = -1
|
best_idx = -1
|
||||||
best_dist_sq = float("inf")
|
best_dist_sq = float("inf")
|
||||||
|
|
||||||
# Scan all visible dimension annotations — not just selected ones.
|
|
||||||
# view3d.select may deselect the annotation before this operator runs.
|
|
||||||
for obj in context.scene.objects:
|
for obj in context.scene.objects:
|
||||||
if obj.type != "CURVE":
|
if obj.type != "CURVE":
|
||||||
continue
|
continue
|
||||||
@@ -7837,15 +7843,35 @@ class ClickNearestDimensionAnchor(bpy.types.Operator):
|
|||||||
o.select_set(False)
|
o.select_set(False)
|
||||||
best_obj.select_set(True)
|
best_obj.select_set(True)
|
||||||
context.view_layer.objects.active = best_obj
|
context.view_layer.objects.active = best_obj
|
||||||
from bonsai.bim.module.drawing.gizmos import set_active_anchor
|
|
||||||
set_active_anchor(best_idx, best_obj)
|
# Store dot for the modal phase and go modal to consume the PRESS.
|
||||||
# Force viewport redraw so gizmo colors update before the modal starts.
|
# The modal will also consume the RELEASE before handing off to
|
||||||
for area in context.screen.areas:
|
# SetDimensionAnchor, so view3d.select never sees either event.
|
||||||
if area.type == "VIEW_3D":
|
self._best_obj = best_obj
|
||||||
area.tag_redraw()
|
self._best_idx = best_idx
|
||||||
break
|
context.window_manager.modal_handler_add(self)
|
||||||
bpy.ops.bim.set_dimension_anchor("INVOKE_DEFAULT", anchor_index=best_idx)
|
return {"RUNNING_MODAL"}
|
||||||
return {"FINISHED"}
|
|
||||||
|
def modal(self, context, event):
|
||||||
|
if event.type == "LEFTMOUSE" and event.value == "RELEASE":
|
||||||
|
# Both PRESS and RELEASE are now consumed. Start the anchor editor.
|
||||||
|
from bonsai.bim.module.drawing.gizmos import set_active_anchor
|
||||||
|
set_active_anchor(self._best_idx, self._best_obj)
|
||||||
|
for area in context.screen.areas:
|
||||||
|
if area.type == "VIEW_3D":
|
||||||
|
area.tag_redraw()
|
||||||
|
break
|
||||||
|
bpy.ops.bim.set_dimension_anchor("INVOKE_DEFAULT", anchor_index=self._best_idx)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
if event.type in ("ESC", "RIGHTMOUSE"):
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
# Consume intermediate LMB events; pass everything else through so
|
||||||
|
# viewport navigation (MMB, scroll) still works during the brief wait.
|
||||||
|
if event.type == "LEFTMOUSE":
|
||||||
|
return {"RUNNING_MODAL"}
|
||||||
|
return {"PASS_THROUGH"}
|
||||||
|
|
||||||
|
|
||||||
class DebugDimensionClicks(bpy.types.Operator):
|
class DebugDimensionClicks(bpy.types.Operator):
|
||||||
|
|||||||
Reference in New Issue
Block a user