Source code for QInstrument.instruments.PriorScientific.Proscan.widget
from __future__ import annotations
import logging
from pathlib import Path
from qtpy import QtCore
from QInstrument.lib.QInstrumentWidget import QInstrumentWidget
from QInstrument.instruments.PriorScientific.Proscan.instrument import QProscan
logger = logging.getLogger(__name__)
_NORMAL_STYLE = 'background: lightyellow;'
_LIMIT_STYLE = 'background: #FF6B6B;'
[docs]
class QProscanWidget(QInstrumentWidget):
'''Control widget for the Prior Scientific Proscan stage controller.
Displays the current XY and Z position, provides a joystick for
continuous XY motion, a rotary encoder for Z focus, and spinboxes
for speed, acceleration, and step-size settings.
Position displays turn red when the corresponding axis limit switch
is active.
'''
UIFILE = str(Path(__file__).parent / 'ProscanWidget.ui')
INSTRUMENT = QProscan
HARDWARE_DOMINANT = True
def __init__(self, *args,
interval: int | None = None, **kwargs) -> None:
'''Initialize the widget.
Parameters
----------
interval : int or None, optional
Poll interval in milliseconds for position and limit updates.
Overrides :attr:`QProscan.POLL_INTERVAL`. Default: 200.
'''
super().__init__(*args, **kwargs)
self._interval = int(interval or 200)
self._prev_limits = None
self.joystick.setRange(-200., 200.)
def _connectSignals(self) -> None:
super()._connectSignals()
self.joystick.positionChanged.connect(self._updateVelocity)
self.joystick.stepped.connect(self._onStep)
self.zdial.stepUp.connect(self.device.stepUp)
self.zdial.stepDown.connect(self.device.stepDown)
self.stop.clicked.connect(self.device.stop)
self.set_origin.clicked.connect(self.device.set_origin)
self.advancedToggle.toggled.connect(self._onAdvancedToggled)
self.advanced.setVisible(False)
self.device.positionChanged.connect(self._onPositionChanged)
self.device.limitsChanged.connect(self._onLimitsChanged)
def _firstShow(self) -> None:
super()._firstShow()
self.device.POLL_INTERVAL = self._interval
QtCore.QMetaObject.invokeMethod(
self.device, 'startPolling',
QtCore.Qt.ConnectionType.QueuedConnection)
@QtCore.Slot(bool)
def _onAdvancedToggled(self, checked: bool) -> None:
'''Show or hide the advanced settings panel.'''
self.advanced.setVisible(checked)
self.advancedToggle.setText(
'▾ Advanced Settings' if checked else '▸ Advanced Settings')
self.window().adjustSize()
[docs]
def showEvent(self, event: object) -> None:
super().showEvent(event)
self.window().adjustSize()
@QtCore.Slot(object)
def _onPositionChanged(self, pos: list[int]) -> None:
'''Update the XYZ position displays.'''
try:
x, y, z = pos
except (TypeError, ValueError):
return
self.x.display(x)
self.y.display(y)
self.z.display(z)
@QtCore.Slot(object)
def _onLimitsChanged(self, limits: object) -> None:
'''Update limit-switch indicator styles and log new activations.'''
if limits and not self._prev_limits:
logger.warning(
f'Limit switch active: X={limits[0]}, '
f'Y={limits[1]}, Z={limits[2]}')
self._prev_limits = limits
x_hit, y_hit, z_hit, _ = limits or (False, False, False, False)
self.x.setStyleSheet(_LIMIT_STYLE if x_hit else _NORMAL_STYLE)
self.y.setStyleSheet(_LIMIT_STYLE if y_hit else _NORMAL_STYLE)
self.z.setStyleSheet(_LIMIT_STYLE if z_hit else _NORMAL_STYLE)
@QtCore.Slot(str)
def _onStep(self, direction: str) -> None:
'''Execute one discrete step in *direction*.'''
methods = {
'left': self.device.stepLeft,
'right': self.device.stepRight,
'up': self.device.stepForward,
'down': self.device.stepBackward,
}
methods[direction]()
@QtCore.Slot(object)
def _updateVelocity(self, velocity: object) -> None:
'''Forward joystick position to the stage as a velocity command.'''
logger.debug(f'velocity: {velocity}')
self.device.set_velocity(velocity)
__all__ = ['QProscanWidget']
if __name__ == '__main__':
QProscanWidget.example()