feat(flomni): warn on OSA collision risk when picking a foptz target
CI for csaxs_bec / test (push) Successful in 2m58s

The tomo-queue "move_foptz_with_shift" dialog now shows a live warning
when the entered foptz target would leave <=0 mm OSA clearance against a
normal OMNY pin (same collision check fosa_in() already enforces for
fosaz moves, surfaced here at data-entry time instead). Advisory only --
the queued action itself stays a plain absolute move, not gated on this.

Extracted the clearance formula out of FlomniOpticsMixin._osa_remaining_space
into a module-level compute_osa_remaining_space(foptz_val, fosaz_val) so
the GUI (a separate process with only RPC device access, no Flomni
instance to call a bound method on) can reuse the exact same formula
instead of duplicating it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NPZ9gzN2rt3wgiMnTKyrug
This commit is contained in:
x01dcandClaude Sonnet 5 committed 2026-10-01 15:38:56 +02:00
1 parent 437d9e7559
commit a5fcc73049
2 files changed
+64 -1

No files matched your search

@@ -9,6 +9,22 @@ class FlomniOpticsError(Exception):
pass
def compute_osa_remaining_space(foptz_val: float, fosaz_val: float) -> float:
"""Clearance (mm) between the OSA and a normal OMNY pin for a given
foptz/fosaz pair. <=0 means collision.
Module-level (not a method) so it can be imported and reused outside a
live Flomni session -- e.g. by the tomo-queue GUI (tomo_params.py),
which only has RPC device access (dev.foptz.readback.get()/
dev.fosaz.readback.get()), not a Flomni instance to call a bound method
on. FlomniOpticsMixin's own methods below are thin wrappers around this
for the two directions actually used in a live session (varying fosaz
at the current foptz, and vice versa) -- single source of truth for the
formula/constant either way.
"""
return 33 - foptz_val - fosaz_val
class FlomniOpticsMixin:
@staticmethod
def _get_user_param_safe(device, var):
@@ -21,7 +37,7 @@ class FlomniOpticsMixin:
"""Clearance (mm) between the OSA and a normal OMNY pin if fosaz were
at `fosaz_target`, at the *current* foptz reading. <=0 means collision."""
foptz_val = dev.foptz.readback.get()
return -fosaz_target + (33 - foptz_val)
return compute_osa_remaining_space(foptz_val, fosaz_target)
def _fheater_is_up(self) -> bool:
axis_id = dev.fheater._config["deviceConfig"].get("axis_Id")
@@ -2186,6 +2186,53 @@ class CommandJobBuilderDialog(QDialog):
label = f"{label} ({unit})"
self._field_form.addRow(f"{label}:", widget)
if action_name == "move_foptz_with_shift" and "foptz_target" in self._step_field_widgets:
self._build_osa_collision_warning()
def _build_osa_collision_warning(self) -> None:
"""Live, advisory-only OSA-collision warning for
move_foptz_with_shift's foptz_target field.
The move itself stays a plain absolute move (see
_queue_action_move_foptz_with_shift's docstring) -- this just
surfaces, at data-entry time, the same clearance check
FlomniOpticsMixin.fosa_in() enforces (by refusing) for fosaz moves,
so an operator can see a likely OSA collision before queuing a
foptz target, without the queue action itself blocking on it.
"""
from csaxs_bec.bec_ipython_client.plugins.flomni.flomni_optics_mixin import (
compute_osa_remaining_space,
)
lbl = QLabel()
lbl.setWordWrap(True)
lbl.setStyleSheet("color: #B8860B;")
lbl.setVisible(False)
self._field_form.addRow("", lbl)
foptz_widget, _field = self._step_field_widgets["foptz_target"]
def _update(_value=None):
fosaz = self._client.device_manager.devices.get("fosaz")
if fosaz is None:
return
try:
fosaz_val = fosaz.readback.get()
except Exception:
return
clearance = compute_osa_remaining_space(foptz_widget.value(), fosaz_val)
if clearance <= 0:
lbl.setText(
f"Warning: at this foptz target, OSA clearance would be "
f"{clearance:.2f} mm (<=0 = collision risk with a normal OMNY pin)."
)
lbl.setVisible(True)
else:
lbl.setVisible(False)
foptz_widget.valueChanged.connect(_update)
_update()
def _build_field_widget(self, field: dict) -> QWidget:
field_type = field.get("type")
if field_type == "float":