From 643877cb13a30bfdd764246d07658dc6365e65e1 Mon Sep 17 00:00:00 2001 From: gnzng Date: Tue, 8 Jul 2025 15:04:00 -0700 Subject: [PATCH 1/3] Add Stop button functionality to RunEngineWidget and implement stop signal in QRunEngine --- xicam/Acquire/controlwidgets/runenginewidget.py | 13 +++++++++++++ xicam/Acquire/runengine.py | 8 ++++++++ 2 files changed, 21 insertions(+) diff --git a/xicam/Acquire/controlwidgets/runenginewidget.py b/xicam/Acquire/controlwidgets/runenginewidget.py index 37888ef..fc86941 100644 --- a/xicam/Acquire/controlwidgets/runenginewidget.py +++ b/xicam/Acquire/controlwidgets/runenginewidget.py @@ -33,7 +33,9 @@ def __init__(self, *args, **kwargs): self.runbutton = QPushButton('Run') self.pausebutton = QPushButton('Pause') self.resumebutton = QPushButton('Resume') + self.stopbutton = QPushButton('Stop') self.abortbutton = QPushButton('Abort') + self.stopbutton.setStyleSheet('background-color:orange;color:white;font-weight:bold;') self.abortbutton.setStyleSheet('background-color:red;color:white;font-weight:bold;') # Layout @@ -52,6 +54,7 @@ def __init__(self, *args, **kwargs): self.runlayout.addWidget(self.runbutton) self.runlayout.addWidget(self.pausebutton) self.runlayout.addWidget(self.resumebutton) + self.runlayout.addWidget(self.stopbutton) self.runlayout.addWidget(self.abortbutton) self.runwidget.setLayout(self.runlayout) self.splitter.addWidget(self.runwidget) @@ -67,6 +70,7 @@ def __init__(self, *args, **kwargs): self.copybutton.clicked.connect(self.copy) self.runbutton.clicked.connect(self.run) self.abortbutton.clicked.connect(self.abort) + self.stopbutton.clicked.connect(self.stop) self.pausebutton.clicked.connect(self.pause) self.resumebutton.clicked.connect(self.resume) @@ -76,6 +80,7 @@ def __init__(self, *args, **kwargs): self.RE.sigFinish.connect(self._finished) self.RE.sigStart.connect(self._started) self.RE.sigAbort.connect(self._aborted) + self.RE.sigStop.connect(self._stopped) # Run model self.runmodel = QStandardItemModel() @@ -142,6 +147,9 @@ def run(self): def abort(self): self.RE.abort('Aborted by Xi-cam user.') + def stop(self): + self.RE.stop('Stopped by Xi-cam user.') + def pause(self): self.RE.pause() @@ -160,16 +168,21 @@ def _paused(self): def _started(self): self.abortbutton.setEnabled(True) + self.stopbutton.setEnabled(True) self.pausebutton.setEnabled(True) self._resumed() def _finished(self): self.abortbutton.setEnabled(False) + self.stopbutton.setEnabled(False) self.pausebutton.setEnabled(False) def _aborted(self): self._finished() + def _stopped(self): + self._finished() + class MDVWithButtons(QWidget): def __init__(self, mdv, *args, **kwargs): diff --git a/xicam/Acquire/runengine.py b/xicam/Acquire/runengine.py index 2802929..8d8d22d 100644 --- a/xicam/Acquire/runengine.py +++ b/xicam/Acquire/runengine.py @@ -59,6 +59,7 @@ class PrioritizedPlan: class QRunEngine(QObject): sigDocumentYield = Signal(str, dict) sigAbort = Signal() # TODO: wireup me + sigStop = Signal() # Signal for graceful stop sigException = Signal(Exception) sigFinish = Signal() sigStart = Signal() @@ -74,6 +75,7 @@ def __init__(self, **kwargs): self.sigFinish.connect(self._check_if_ready) self.sigAbort.connect(self._check_if_ready) + self.sigStop.connect(self._check_if_ready) self.sigException.connect(self._check_if_ready) self.queue = PriorityQueue() @@ -147,6 +149,12 @@ def abort(self, reason=''): self.RE.abort(reason=reason) self.sigAbort.emit() + def stop(self, reason=''): + """Gracefully stop the running plan and mark it as successful.""" + if self.RE.state == 'running': + self.RE.stop() + self.sigStop.emit() + def pause(self, defer=False): if self.RE.state != 'paused': self.RE.request_pause(defer) From 8c59fa1d29da502e1b4a487bea5ef6b329fd79ea Mon Sep 17 00:00:00 2001 From: gnzng Date: Tue, 8 Jul 2025 15:33:09 -0700 Subject: [PATCH 2/3] Adding stop button to areadetector panel --- xicam/Acquire/controllers/areadetector.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/xicam/Acquire/controllers/areadetector.py b/xicam/Acquire/controllers/areadetector.py index a22147a..2d9c53c 100644 --- a/xicam/Acquire/controllers/areadetector.py +++ b/xicam/Acquire/controllers/areadetector.py @@ -75,6 +75,10 @@ def __init__(self, device, preprocess_enabled=True, maxfps=4): # Note: typical acquire_button.clicked.connect(self.acquire) acquire_layout.addWidget(acquire_button) + self.stop_button = QPushButton('Stop') + self.stop_button.clicked.connect(self.stop) + acquire_layout.addWidget(self.stop_button) + self.abort_button = QPushButton('Abort') self.abort_button.clicked.connect(self.abort) self._ready() # prepare the appropriate abort btn check state and styling @@ -93,6 +97,8 @@ def __init__(self, device, preprocess_enabled=True, maxfps=4): # Note: typical # Connect relevant RE signals to update abort btn check state and styling depending on the RE state self.RE.sigStart.connect(self._started) self.RE.sigReady.connect(self._ready) + self.RE.sigStop.connect(self._ready) + self.RE.sigAbort.connect(self._ready) # WIP # self.lutCheck = QCheckBox() @@ -111,14 +117,21 @@ def __init__(self, device, preprocess_enabled=True, maxfps=4): # Note: typical def acquire(self): self.RE(count(self.coupled_devices), **self.metadata) + def stop(self): + self.RE.stop('Acquisition stopped by Xi-cam user.') + def abort(self): self.RE.abort('Acquisition aborted by Xi-cam user.') def _started(self): + self.stop_button.setEnabled(True) + self.stop_button.setStyleSheet('background-color:orange;color:white;font-weight:bold;') self.abort_button.setEnabled(True) self.abort_button.setStyleSheet('background-color:red;color:white;font-weight:bold;') def _ready(self): + self.stop_button.setEnabled(False) + self.stop_button.setStyleSheet('') self.abort_button.setEnabled(False) self.abort_button.setStyleSheet('') From 84afaf0dfa99fb87052f6e387ad4420be41ae992 Mon Sep 17 00:00:00 2001 From: gnzng Date: Tue, 8 Jul 2025 16:01:27 -0700 Subject: [PATCH 3/3] trying stopping first, and then then stopping. --- xicam/Acquire/controllers/areadetector.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/xicam/Acquire/controllers/areadetector.py b/xicam/Acquire/controllers/areadetector.py index 2d9c53c..71207d5 100644 --- a/xicam/Acquire/controllers/areadetector.py +++ b/xicam/Acquire/controllers/areadetector.py @@ -118,7 +118,20 @@ def acquire(self): self.RE(count(self.coupled_devices), **self.metadata) def stop(self): - self.RE.stop('Acquisition stopped by Xi-cam user.') + # Enhanced graceful stop to prevent file corruption + # Pause first to allow current plan step to complete and reach cleanup blocks + if self.RE.state == 'running': + try: + self.RE.request_pause(defer=False) + import time + time.sleep(0.1) # Brief moment for pause to take effect + if self.RE.state == 'paused': + self.RE.stop() # Stop from paused state for cleaner shutdown + except Exception as e: + print(f"Error during graceful stop: {e}") + self.RE.stop('Acquisition stopped by Xi-cam user.') + else: + self.RE.stop('Acquisition stopped by Xi-cam user.') def abort(self): self.RE.abort('Acquisition aborted by Xi-cam user.')