Implements docs/plans/xrayeye-vertical-lock.md: once the height-centering
step fixes the sample's vertical position against the FZP crosshair, every
selection box drawn for the remaining angle-alignment steps (1-5) no longer
needs manual vertical placement -- it snaps to the crosshair's y position on
draw and again on every drag/resize.
- OMNY_XRayEye: new _vertical_lock_enabled state, lock_vertical_center()/
unlock_vertical_center() RPC methods, _snap_roi_vertical() helper wired
into _style_new_roi() (both roiAdded and sigRegionChangeFinished). Snap is
idempotent (no-op once already at the target y) so it can't loop back into
itself via its own set_position() call.
- Also added a "ROI vertical lock" toggle in the control panel so an
operator can release/re-engage the lock manually mid-run -- e.g. to freely
draw/measure an unrelated box without aborting the alignment. Both the
toggle and the RPC methods stay in sync (blockSignals pattern already used
elsewhere in this file for on_live_view_enabled).
- x_ray_eye_align.py: lock_vertical_center() right after _height_centered
flips to True; unlock_vertical_center() at the start of every fresh run
(so a run never inherits a stale lock) and in align()'s finally block
(so the lock never outlives a run, including on error/interruption).
FZP box (step 0) and the height-centering box itself remain fully free --
the lock isn't engaged yet at that point. LamNI/OMNY have their own
near-identical scripts but are explicitly out of scope (per the plan); the
widget change is inert by default.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lx3KffiFyyDMKT8vvPENUW