From 0b5666084a0dc12a85c775a802cba18e0e4e1d37 Mon Sep 17 00:00:00 2001 From: x12sa Date: Thu, 9 Jul 2026 12:54:14 +0200 Subject: [PATCH] fix(flomni): warn on invalid beamline states before manual xray-eye frame; fix sample-remove slot handling xrayeye_update_frame() now checks all configured beamline states and, if any are not valid, warns the user (the frame may be empty when the beam is down) and asks before taking a frame. The automated alignment path is unaffected. ftransfer_sample_change(): clarify the remove-only prompt to "sample currently in the sample stage", and include tray slot 20 in the empty-slot scan (range(1, 20) -> range(1, 21)) in both the remove and mount branches, matching check_position_is_valid's 0-20 range. --- .../plugins/flomni/flomni.py | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py b/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py index f211c9b..84e610d 100644 --- a/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py +++ b/csaxs_bec/bec_ipython_client/plugins/flomni/flomni.py @@ -878,7 +878,7 @@ class FlomniSampleTransferMixin: raise FlomniError("There is no sample in the sample stage to remove. Aborting.") empty_slots = [] - for j in range(1, 20): + for j in range(1, 21): if not dev.flomni_samples.is_sample_slot_used(j): empty_slots.append(j) if not empty_slots: @@ -887,7 +887,10 @@ class FlomniSampleTransferMixin: print(f"The following slots are empty: {empty_slots}.") while True: - user_input = input(f"Where shall I put the sample? Default: [{empty_slots[0]}] ") + user_input = input( + "Where shall I put the sample currently in the sample stage? " + f"Default: [{empty_slots[0]}] " + ) if user_input.strip() == "": user_input = empty_slots[0] @@ -934,7 +937,7 @@ class FlomniSampleTransferMixin: # continue # if val.get("value") == 0: # empty_slots.append(int(name.split("flomni_samples_sample_placed_sample")[1])) - for j in range(1, 20): + for j in range(1, 21): if not dev.flomni_samples.is_sample_slot_used(j): empty_slots.append(j) if not empty_slots: @@ -1801,7 +1804,45 @@ class Flomni( umv(dev.fsamx, fsamx_in) raise exc + def _check_beamline_states_valid(self): + """Return (all_valid, invalid_labels). + + Reads every configured beamline state (bec.beamline_states) and + collects the ones whose status is not "valid" (i.e. "invalid", + "warning" or "unknown"). Used to warn before taking a manual X-ray + eye frame: if e.g. the beam is down, the frame would come back empty + and the user is left confused about why they see nothing. + + Returns all_valid=True (with an empty list) if the beamline-state + machinery isn't available at all, so this never blocks on a setup + that doesn't use beamline states. + """ + bec = builtins.__dict__.get("bec") + manager = getattr(bec, "beamline_states", None) if bec is not None else None + if manager is None: + return True, [] + + bad = [] + for name in list(getattr(manager, "_states", {})): + try: + status = manager.get_status_by_name(name) + except Exception: # pylint: disable=broad-except + continue + if status is not None and status != "valid": + bad.append(f"{name}: {status}") + return (len(bad) == 0), bad + def xrayeye_update_frame(self, keep_shutter_open=False): + all_valid, bad_states = self._check_beamline_states_valid() + if not all_valid: + print( + "Attention: not all beamline states are valid:\n " + + "\n ".join(bad_states) + + "\nThe beam may be down, so the frame could come back empty." + ) + if not self.OMNYTools.yesno("Take a frame anyway?", "n"): + print("Stopping.") + return self.align.update_frame(keep_shutter_open) def xrayeye_alignment_start(self, keep_shutter_open=False):