feat: show AUTO CENTERING in the sample camera view while a loop centering runs
CI / lint (push) Skipped
CI / test (3.12) (push) Skipped
CI / test (3.13) (push) Skipped
CI / test-with-beamline-plugins (pxi_bec) (push) Skipped
CI / test-with-beamline-plugins (pxii_bec) (push) Skipped
CI / test-with-beamline-plugins (pxiii_bec) (push) Skipped
CI / lint (pull_request) Failing after 1m2s
CI / test (3.12) (pull_request) Failing after 1m5s
CI / test (3.13) (pull_request) Failing after 1m10s
CI / test (3.14) (pull_request) Failing after 1m19s
CI / test-with-beamline-plugins (pxi_bec) (pull_request) Failing after 1m19s
CI / test-with-beamline-plugins (pxiii_bec) (pull_request) Failing after 1m20s
CI / test-with-beamline-plugins (pxii_bec) (pull_request) Failing after 1m25s
CI / test-with-coverage (pull_request) Failing after 1m25s
CI / coverage-analysis (pull_request) Skipped
CI / lint (push) Skipped
CI / test (3.12) (push) Skipped
CI / test (3.13) (push) Skipped
CI / test-with-beamline-plugins (pxi_bec) (push) Skipped
CI / test-with-beamline-plugins (pxii_bec) (push) Skipped
CI / test-with-beamline-plugins (pxiii_bec) (push) Skipped
CI / lint (pull_request) Failing after 1m2s
CI / test (3.12) (pull_request) Failing after 1m5s
CI / test (3.13) (pull_request) Failing after 1m10s
CI / test (3.14) (pull_request) Failing after 1m19s
CI / test-with-beamline-plugins (pxi_bec) (pull_request) Failing after 1m19s
CI / test-with-beamline-plugins (pxiii_bec) (pull_request) Failing after 1m20s
CI / test-with-beamline-plugins (pxii_bec) (pull_request) Failing after 1m25s
CI / test-with-coverage (pull_request) Failing after 1m25s
CI / coverage-analysis (pull_request) Skipped
/status cannot tell an auto centering from a manual alignment move (both are busy + SampleAlignment, the combo the overlay deliberately hides), so the DAQ worker emits its own auto_centering flag: True while its own /alc/center_loop POST is in flight (the endpoint blocks until done) or while the automation progress reports the Center step running. The sample camera views pass it to build_busy_overlay_style, which paints a purple AUTO CENTERING wave text like the ROBOT MOUNTING/COOLING ones. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -1202,6 +1202,8 @@ class MainWindow(QMainWindow):
|
||||
self.daq.update.connect(self.sample_camera.update_daq_status)
|
||||
self.daq.update.connect(self.compact_sample_camera.update_daq_status)
|
||||
self.daq.update.connect(self.portrait_sample_camera.update_daq_status)
|
||||
for camera in (self.sample_camera, self.compact_sample_camera, self.portrait_sample_camera):
|
||||
self.daq.auto_centering.connect(camera.set_auto_centering)
|
||||
self.daq.update.connect(self.tell_samples.update_daq_status)
|
||||
self.daq.update.connect(self.ref_tools_panel.update_daq_status)
|
||||
if self.prediction_thread is not None:
|
||||
|
||||
@@ -84,6 +84,9 @@ class DAQWorker(QObject):
|
||||
http_error = Signal(str)
|
||||
status_message = Signal(str, bool)
|
||||
automation_progress = Signal(object)
|
||||
# True while a loop centering runs — /status alone cannot tell it from
|
||||
# a manual alignment move (see build_busy_overlay_style).
|
||||
auto_centering = Signal(bool)
|
||||
gui_sessions_loaded = Signal(list)
|
||||
gui_close_requested = Signal(int, int, str)
|
||||
recovery_action_completed = Signal(str)
|
||||
@@ -154,6 +157,10 @@ class DAQWorker(QObject):
|
||||
self._timer.start()
|
||||
self._counter = 0
|
||||
self._automation_progress_buffer = ""
|
||||
# Two centering sources OR-ed into auto_centering: our own
|
||||
# /alc/center_loop POST in flight, and the automation Center step.
|
||||
self._manual_centering = False
|
||||
self._automation_centering = False
|
||||
self._cleanup_done = False
|
||||
|
||||
self._last_auth_error_log_ts = 0.0
|
||||
@@ -747,7 +754,7 @@ class DAQWorker(QObject):
|
||||
"""
|
||||
if self._base_url is None:
|
||||
logger.info(f"POST /{url}: {body}")
|
||||
return
|
||||
return None
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self._base_url}/{url}"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self._token}".encode())
|
||||
@@ -755,6 +762,7 @@ class DAQWorker(QObject):
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
reply = self._net_manager.post(request, QByteArray(body.encode("utf-8")))
|
||||
reply.finished.connect(lambda: self.handle_req_response(reply))
|
||||
return reply
|
||||
|
||||
def generic_put(self, url: str, body: str = ""):
|
||||
"""
|
||||
@@ -844,7 +852,20 @@ class DAQWorker(QObject):
|
||||
|
||||
@Slot()
|
||||
def center_loop(self):
|
||||
self.generic_post("alc/center_loop")
|
||||
# The endpoint blocks server-side until centering ends, so the reply
|
||||
# doubles as the "done" edge for the camera overlay.
|
||||
reply = self.generic_post("alc/center_loop")
|
||||
if reply is None:
|
||||
return
|
||||
self._set_centering(manual=True)
|
||||
reply.finished.connect(lambda: self._set_centering(manual=False))
|
||||
|
||||
def _set_centering(self, *, manual: bool | None = None, automation: bool | None = None):
|
||||
if manual is not None:
|
||||
self._manual_centering = manual
|
||||
if automation is not None:
|
||||
self._automation_centering = automation
|
||||
self.auto_centering.emit(self._manual_centering or self._automation_centering)
|
||||
|
||||
@Slot()
|
||||
def force_session(self):
|
||||
@@ -1968,6 +1989,13 @@ class DAQWorker(QObject):
|
||||
progress = self._parse_automation_progress(progress_payload)
|
||||
self.automation_progress.emit(progress)
|
||||
self._emit_automation_progress_events(progress)
|
||||
self._set_centering(
|
||||
automation=not progress.finished
|
||||
and any(
|
||||
step.step == WorkflowStateKind.LOOP_CENTRE and step.status == StepStatus.RUNNING
|
||||
for step in progress.steps
|
||||
)
|
||||
)
|
||||
|
||||
current = progress.current_step or "Idle"
|
||||
if progress.finished:
|
||||
|
||||
@@ -17,6 +17,9 @@ from aare.gui.styles import (
|
||||
BUSY_PSI_RED,
|
||||
BUSY_PSI_RED_BORDER,
|
||||
BUSY_PSI_RED_DOT,
|
||||
BUSY_PURPLE,
|
||||
BUSY_PURPLE_BORDER,
|
||||
BUSY_PURPLE_DOT,
|
||||
BUSY_RED_BADGE,
|
||||
BUSY_RED_BORDER,
|
||||
BUSY_RED_DOT,
|
||||
@@ -174,6 +177,7 @@ def build_busy_overlay_style(
|
||||
is_busy: bool,
|
||||
tell_state: TellStateModel | None,
|
||||
session_state: SessionsStateEnum | None = None,
|
||||
auto_centering: bool = False,
|
||||
) -> BusyOverlayStyle | None:
|
||||
if session_state in {
|
||||
SessionsStateEnum.OwnedByElse,
|
||||
@@ -190,6 +194,24 @@ def build_busy_overlay_style(
|
||||
accent_dot=BUSY_YELLOW_DOT,
|
||||
subtext="Grab the baton if you need to interact with GUI",
|
||||
)
|
||||
|
||||
# Auto loop centering is busy + SampleAlignment in /status — the exact
|
||||
# combo the callers hide (manual omega moves look identical), so it
|
||||
# gets its own flag: the GUI knows it POSTed /alc/center_loop, or the
|
||||
# automation progress says the Center step is running. Checked before
|
||||
# is_busy because the callers pass is_busy=False during alignment.
|
||||
if auto_centering:
|
||||
return BusyOverlayStyle(
|
||||
text="AUTO CENTERING",
|
||||
badge_bg=BUSY_PURPLE,
|
||||
badge_fg=WHITE,
|
||||
overlay_fill=qcolor(BUSY_PURPLE, 190),
|
||||
overlay_border=qcolor(BUSY_PURPLE_BORDER, 235),
|
||||
overlay_text=qcolor(WHITE),
|
||||
accent_dot=BUSY_PURPLE_DOT,
|
||||
animate=True,
|
||||
)
|
||||
|
||||
if not is_busy:
|
||||
return None
|
||||
|
||||
|
||||
@@ -130,6 +130,7 @@ class SampleCameraImageLabel(QGraphicsView):
|
||||
self._last_grid_update_ts = 0.0
|
||||
self._grid_update_min_interval_s = 1.0 / 25.0
|
||||
self._tell_state = None
|
||||
self._auto_centering = False
|
||||
self._busy_overlay_style: BusyOverlayStyle | None = None
|
||||
|
||||
self._geom = geom
|
||||
@@ -295,6 +296,12 @@ class SampleCameraImageLabel(QGraphicsView):
|
||||
self._smoothed_target_point = None
|
||||
self.update()
|
||||
|
||||
@Slot(bool)
|
||||
def set_auto_centering(self, active: bool) -> None:
|
||||
# ponytail: only stored — the overlay style is rebuilt on the next
|
||||
# /status tick (500 ms), well within the seconds a centering takes.
|
||||
self._auto_centering = active
|
||||
|
||||
def _busy_overlay_text(self) -> str:
|
||||
tell_state = self._tell_state
|
||||
if tell_state is None:
|
||||
@@ -843,6 +850,7 @@ class SampleCameraImageLabel(QGraphicsView):
|
||||
is_busy=bool(s.busy) and s.state != BeamlineStateEnum.SampleAlignment,
|
||||
tell_state=s.tell_state,
|
||||
session_state=self._session_state,
|
||||
auto_centering=self._auto_centering,
|
||||
)
|
||||
if new_busy_style != self._busy_overlay_style:
|
||||
self._busy_overlay_style = new_busy_style
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
the badge pill (which reads as a button). These checks fail if the text
|
||||
renderer stops painting or the video view stops routing through it."""
|
||||
|
||||
from aarecommon.models.models import SessionsStateEnum
|
||||
from PySide6.QtCore import Qt
|
||||
from PySide6.QtGui import QPainter, QPixmap
|
||||
|
||||
@@ -34,3 +35,18 @@ def test_video_view_paints_busy_text(qtbot):
|
||||
assert view.grab().toImage() != idle, "busy style must change the rendered view"
|
||||
view.set_busy_overlay_style(None)
|
||||
assert view.grab().toImage() == idle, "clearing the style must restore the view"
|
||||
|
||||
|
||||
def test_auto_centering_flag_overrides_alignment_gate():
|
||||
# Callers pass is_busy=False during SampleAlignment; the flag must still
|
||||
# produce the overlay, and the viewing-mode badge must still win.
|
||||
style = build_busy_overlay_style(is_busy=False, tell_state=None, auto_centering=True)
|
||||
assert style is not None and style.text == "AUTO CENTERING"
|
||||
assert build_busy_overlay_style(is_busy=False, tell_state=None) is None
|
||||
viewing = build_busy_overlay_style(
|
||||
is_busy=False,
|
||||
tell_state=None,
|
||||
session_state=SessionsStateEnum.OwnedByElse,
|
||||
auto_centering=True,
|
||||
)
|
||||
assert viewing is not None and viewing.text.startswith("Viewing mode")
|
||||
|
||||
Reference in New Issue
Block a user