Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions xicam/Acquire/controllers/areadetector.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand All @@ -111,14 +117,34 @@ 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):
# 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.')

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('')

Expand Down
13 changes: 13 additions & 0 deletions xicam/Acquire/controlwidgets/runenginewidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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)

Expand All @@ -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()
Expand Down Expand Up @@ -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()

Expand All @@ -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):
Expand Down
8 changes: 8 additions & 0 deletions xicam/Acquire/runengine.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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()
Expand Down Expand Up @@ -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)
Expand Down