wip: digital twin
This commit is contained in:
@@ -661,11 +661,11 @@ class MoverPanel(QWidget):
|
||||
|
||||
# FE Slits
|
||||
mot = self.dev.sldi_gapx
|
||||
self.sldi_gapx = MoveWidget(motor=mot, label='GAPX', unit='mm', decimals=2)
|
||||
self.sldi_gapx = MoveWidget(motor=mot, label='GAPX', unit='mm', decimals=2, deadband=0.01)
|
||||
self.mover_widgets.append(self.sldi_gapx)
|
||||
|
||||
mot = self.dev.sldi_gapy
|
||||
self.sldi_gapy = MoveWidget(motor=mot, label='GAPY', unit='mm', decimals=2)
|
||||
self.sldi_gapy = MoveWidget(motor=mot, label='GAPY', unit='mm', decimals=2, deadband=0.01)
|
||||
self.mover_widgets.append(self.sldi_gapy)
|
||||
|
||||
self.sldi_mov_group = Group(
|
||||
@@ -678,19 +678,19 @@ class MoverPanel(QWidget):
|
||||
|
||||
# Collimating mirror
|
||||
mot = self.dev.cm_trx
|
||||
self.cm_trx = MoveWidget(motor=mot, label='TRX', unit='mm', decimals=2)
|
||||
self.cm_trx = MoveWidget(motor=mot, label='TRX', unit='mm', decimals=2, deadband=0.01)
|
||||
self.mover_widgets.append(self.cm_trx)
|
||||
|
||||
mot = self.dev.cm_try
|
||||
self.cm_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2)
|
||||
self.cm_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2, deadband=0.01)
|
||||
self.mover_widgets.append(self.cm_try)
|
||||
|
||||
mot = self.dev.cm_bnd
|
||||
self.cm_bnd = MoveWidget(motor=mot, label='BENDER', unit='km', decimals=2)
|
||||
self.cm_bnd = MoveWidget(motor=mot, label='BENDER', unit='km', decimals=2, deadband=0.2)
|
||||
self.mover_widgets.append(self.cm_bnd)
|
||||
|
||||
mot = self.dev.cm_rotx
|
||||
self.cm_rotx = MoveWidget(motor=mot, label='PITCH', unit='mrad', decimals=3)
|
||||
self.cm_rotx = MoveWidget(motor=mot, label='PITCH', unit='mrad', decimals=3, deadband=0.01)
|
||||
self.mover_widgets.append(self.cm_rotx)
|
||||
|
||||
self.cm_mov_group = Group(
|
||||
@@ -705,15 +705,15 @@ class MoverPanel(QWidget):
|
||||
|
||||
# Monochromator
|
||||
mot = self.dev.mo1_bragg
|
||||
self.mo1_bragg_angle = MoveWidget(motor=mot, label='Bragg Angle', unit='deg', decimals=3)
|
||||
self.mo1_bragg_angle = MoveWidget(motor=mot, label='Bragg Angle', unit='deg', decimals=3, deadband=0.01)
|
||||
self.mover_widgets.append(self.mo1_bragg_angle)
|
||||
|
||||
mot = self.dev.mo1_trx
|
||||
self.mo1_trx = MoveWidget(motor=mot, label='TRX', unit='mm', decimals=2)
|
||||
self.mo1_trx = MoveWidget(motor=mot, label='TRX', unit='mm', decimals=2, deadband=0.01)
|
||||
self.mover_widgets.append(self.mo1_trx)
|
||||
|
||||
mot = self.dev.mo1_try
|
||||
self.mo1_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2)
|
||||
self.mo1_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2, deadband=0.01)
|
||||
self.mover_widgets.append(self.mo1_try)
|
||||
|
||||
self.mo1_mov_group = Group(
|
||||
@@ -727,7 +727,7 @@ class MoverPanel(QWidget):
|
||||
|
||||
# OP Slits 1
|
||||
mot = self.dev.sl1_centery
|
||||
self.sl1_centery = MoveWidget(motor=mot, label='CENTERY', unit='mm', decimals=2)
|
||||
self.sl1_centery = MoveWidget(motor=mot, label='CENTERY', unit='mm', decimals=2, deadband=0.1)
|
||||
self.mover_widgets.append(self.sl1_centery)
|
||||
|
||||
self.sl1_mov_group = Group(
|
||||
@@ -739,7 +739,7 @@ class MoverPanel(QWidget):
|
||||
|
||||
# OP Beam Monitor 1
|
||||
mot = self.dev.bm1_try
|
||||
self.bm1_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2)
|
||||
self.bm1_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2, deadband=0.1)
|
||||
self.mover_widgets.append(self.bm1_try)
|
||||
|
||||
self.bm1_mov_group = Group(
|
||||
@@ -751,19 +751,19 @@ class MoverPanel(QWidget):
|
||||
|
||||
# Focusing Mirror
|
||||
mot = self.dev.fm_trx
|
||||
self.fm_trx = MoveWidget(motor=mot, label='TRX', unit='mm', decimals=2)
|
||||
self.fm_trx = MoveWidget(motor=mot, label='TRX', unit='mm', decimals=2, deadband=0.01)
|
||||
self.mover_widgets.append(self.fm_trx)
|
||||
|
||||
mot = self.dev.fm_try
|
||||
self.fm_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2)
|
||||
self.fm_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2, deadband=0.01)
|
||||
self.mover_widgets.append(self.fm_try)
|
||||
|
||||
mot = self.dev.fm_bnd
|
||||
self.fm_bnd = MoveWidget(motor=mot, label='BENDER', unit='km', decimals=2)
|
||||
self.fm_bnd = MoveWidget(motor=mot, label='BENDER', unit='km', decimals=2, deadband=0.2)
|
||||
self.mover_widgets.append(self.fm_bnd)
|
||||
|
||||
mot = self.dev.fm_rotx
|
||||
self.fm_rotx = MoveWidget(motor=mot, label='PITCH', unit='mrad', decimals=3)
|
||||
self.fm_rotx = MoveWidget(motor=mot, label='PITCH', unit='mrad', decimals=3, deadband=0.01)
|
||||
self.mover_widgets.append(self.fm_rotx)
|
||||
|
||||
self.fm_mov_group = Group(
|
||||
@@ -778,7 +778,7 @@ class MoverPanel(QWidget):
|
||||
|
||||
# OP Slits 2
|
||||
mot = self.dev.sl2_centery
|
||||
self.sl2_centery = MoveWidget(motor=mot, label='CENTERY', unit='mm', decimals=2)
|
||||
self.sl2_centery = MoveWidget(motor=mot, label='CENTERY', unit='mm', decimals=2, deadband=0.1)
|
||||
self.mover_widgets.append(self.sl2_centery)
|
||||
|
||||
self.sl2_mov_group = Group(
|
||||
@@ -790,7 +790,7 @@ class MoverPanel(QWidget):
|
||||
|
||||
# OP Beam Monitor 2
|
||||
mot = self.dev.bm2_try
|
||||
self.bm2_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2)
|
||||
self.bm2_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2, deadband=0.1)
|
||||
self.mover_widgets.append(self.bm2_try)
|
||||
|
||||
self.bm2_mov_group = Group(
|
||||
@@ -802,15 +802,15 @@ class MoverPanel(QWidget):
|
||||
|
||||
# Optical Table
|
||||
mot = self.dev.ot_try
|
||||
self.ot_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2)
|
||||
self.ot_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2, deadband=0.2)
|
||||
self.mover_widgets.append(self.ot_try)
|
||||
|
||||
mot = self.dev.ot_rotx
|
||||
self.ot_rotx = MoveWidget(motor=mot, label='ROTX', unit='mrad', decimals=3)
|
||||
self.ot_rotx = MoveWidget(motor=mot, label='ROTX', unit='mrad', decimals=3, deadband=0.05)
|
||||
self.mover_widgets.append(self.ot_rotx)
|
||||
|
||||
mot = self.dev.ot_es1_trz
|
||||
self.ot_es1_trz = MoveWidget(motor=mot, label='ES1 TRZ', unit='mm', decimals=0)
|
||||
self.ot_es1_trz = MoveWidget(motor=mot, label='ES1 TRZ', unit='mm', decimals=0, deadband=5)
|
||||
self.mover_widgets.append(self.ot_es1_trz)
|
||||
|
||||
self.ot_mov_group = Group(
|
||||
@@ -950,7 +950,7 @@ class SurfacePlots(QWidget):
|
||||
self.colors = [(79, 163, 224), (240, 128, 60)]
|
||||
self.text_color = (255, 255, 255)
|
||||
else: # dark theme
|
||||
self.color_impenetrable = (220, 220, 220)
|
||||
self.color_impenetrable = (180, 180, 180)
|
||||
self.colors = [(26, 111, 173), (212, 83, 10)]
|
||||
self.text_color = (0, 0, 0)
|
||||
|
||||
@@ -1091,26 +1091,26 @@ class SideviewPlot(QWidget):
|
||||
self.colors = [(79, 163, 224), (240, 128, 60)]
|
||||
self.text_color = (255, 255, 255)
|
||||
else: # dark theme
|
||||
self.color_impenetrable = (220, 220, 220)
|
||||
self.color_impenetrable = (180, 180, 180)
|
||||
self.colors = [(26, 111, 173), (212, 83, 10)]
|
||||
self.text_color = (0, 0, 0)
|
||||
|
||||
for idx, scene in enumerate(self.data):
|
||||
if scene in 'assistant':
|
||||
brush = QBrush(QColor(*self.colors[idx], 255), Qt.DiagCrossPattern)
|
||||
pen = pg.mkPen(QColor(*self.colors[idx], 255), width=1, style=Qt.DashLine)
|
||||
pen = pg.mkPen(QColor(*self.colors[idx], 255), width=3, style=Qt.DashLine)
|
||||
else:
|
||||
brush = QBrush(QColor(*self.colors[idx], 255))
|
||||
pen = pg.mkPen(QColor(*self.colors[idx], 255), width=1)
|
||||
pen = pg.mkPen(QColor(*self.colors[idx], 255), width=3)
|
||||
self.plots[scene].setPen(pen)
|
||||
self.plots[scene].setBrush(brush)
|
||||
|
||||
for wall in self.walls:
|
||||
wall.setPen(pg.mkPen(color=self.color_impenetrable, width=2))
|
||||
wall.setPen(pg.mkPen(color=self.color_impenetrable, width=3))
|
||||
wall.setBrush(pg.QtGui.QBrush(pg.QtGui.QColor(*self.color_impenetrable))) # pylint: disable=E1101
|
||||
|
||||
for pipe in self.pipes:
|
||||
pipe.setPen(pg.mkPen(color=self.color_impenetrable, width=2))
|
||||
pipe.setPen(pg.mkPen(color=self.color_impenetrable, width=3))
|
||||
|
||||
def plot_vacuum_pipes(self):
|
||||
pipes = pipe_geometries()
|
||||
@@ -1141,7 +1141,7 @@ if __name__ == "__main__":
|
||||
from bec_widgets.utils.colors import apply_theme
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
apply_theme("light")
|
||||
apply_theme("dark")
|
||||
dispatcher = BECDispatcher(gui_id="digital_twin")
|
||||
win = DigitalTwin()
|
||||
win.show()
|
||||
|
||||
@@ -22,47 +22,6 @@ class Status:
|
||||
MOVING = "moving" # blue mdi.loading (spinning)
|
||||
ERROR = "error" # red mdi.alert-circle
|
||||
|
||||
# class StatusIcon(qta.IconWidget):
|
||||
# """
|
||||
# Displays a status icon using qtawesome Material Design Icons.
|
||||
# Handles its own spin animation for the MOVING state via qta.Spin.
|
||||
# """
|
||||
|
||||
# ICON_SIZE = 36
|
||||
|
||||
# # Map each status to an (icon_name, color) pair
|
||||
# _ICON_MAP = {
|
||||
# Status.IN_POSITION: ("mdi.check-circle-outline", "#27ae60"),
|
||||
# Status.NOT_IN_POSITION: ("mdi.close-circle-outline", "#e6d922"),
|
||||
# Status.ERROR: ("mdi.alert-outline", "#e74c3c"),
|
||||
# Status.MOVING: ("mdi.gamepad-circle-outline", "#2980b9"),
|
||||
# }
|
||||
|
||||
# def __init__(self, parent=None):
|
||||
# super().__init__(parent=parent)
|
||||
# self._status = None
|
||||
# self._spin_anim = qta.Spin(self, autostart=True)
|
||||
# self.setFixedSize(self.ICON_SIZE, self.ICON_SIZE)
|
||||
# self.set_status(Status.NOT_IN_POSITION)
|
||||
|
||||
# def set_status(self, status: str):
|
||||
# if status == self._status:
|
||||
# return
|
||||
# self._status = status
|
||||
|
||||
# icon_name, color = self._ICON_MAP[status]
|
||||
|
||||
# if status == Status.MOVING:
|
||||
# icon = qta.icon(icon_name, color=color, animation=self._spin_anim)
|
||||
# self._spin_anim.start()
|
||||
# else:
|
||||
# self._spin_anim.stop()
|
||||
# icon = qta.icon(icon_name, color=color)
|
||||
|
||||
# self.setIcon(icon)
|
||||
# self.setIconSize(QSize(self.ICON_SIZE, self.ICON_SIZE))
|
||||
|
||||
|
||||
class StatusIcon(QWidget):
|
||||
"""
|
||||
Displays a status icon using bec_qthemes Material Design Icons.
|
||||
@@ -160,41 +119,6 @@ class MotionWorker(QObject):
|
||||
# time.sleep(0.1)
|
||||
self.finished.emit(True)
|
||||
|
||||
# def run(self):
|
||||
# """Simulate motion: move in small steps with realistic deceleration."""
|
||||
# distance = abs(self._target - self._current)
|
||||
# direction = 1 if self._target > self._current else -1
|
||||
# speed = max(0.5, distance / 3.0) # units/second (simulated)
|
||||
# step_time = 0.05 # seconds per step
|
||||
|
||||
# pos = self._current
|
||||
# while not self._stop_flag.is_set():
|
||||
# remaining = abs(self._target - pos)
|
||||
# if remaining < 0.01:
|
||||
# pos = self._target
|
||||
# self.position_changed.emit(round(pos, 4))
|
||||
# self.finished.emit(True)
|
||||
# return
|
||||
|
||||
# # Decelerate near the end
|
||||
# effective_speed = speed * min(1.0, remaining / (distance * 0.2 + 0.001))
|
||||
# effective_speed = max(0.05, effective_speed)
|
||||
# step = effective_speed * step_time * direction
|
||||
# if abs(step) > remaining:
|
||||
# step = remaining * direction
|
||||
|
||||
# pos += step
|
||||
# pos += random.gauss(0, 0.002) # tiny simulated encoder noise
|
||||
# if pos > 20: # Simulated error if above 20 mm
|
||||
# self.error.emit(True)
|
||||
# return
|
||||
# self.position_changed.emit(round(pos, 4))
|
||||
# time.sleep(step_time)
|
||||
|
||||
# # Stopped by user
|
||||
# self.position_changed.emit(round(pos, 4))
|
||||
# self.finished.emit(False)
|
||||
|
||||
class MoveWidget(QWidget):
|
||||
"""
|
||||
One motor stage control group containing:
|
||||
@@ -204,13 +128,12 @@ class MoveWidget(QWidget):
|
||||
- Start / Stop button
|
||||
"""
|
||||
|
||||
DEADBAND = 0.02 # mm — positions within this tolerance are "in position"
|
||||
|
||||
def __init__(self, motor, label: str = '', unit=None, decimals=3):
|
||||
def __init__(self, motor, label: str = '', unit=None, decimals=3, deadband=0.0):
|
||||
super().__init__()
|
||||
self.fb = 0.0
|
||||
self.target = 0
|
||||
self.motor = motor
|
||||
self.deadband = deadband
|
||||
self._status = Status.IN_POSITION
|
||||
self._thread: QThread | None = None
|
||||
self._worker: MotionWorker | None = None
|
||||
@@ -320,7 +243,7 @@ class MoveWidget(QWidget):
|
||||
"""Re-evaluate in-position status whenever the target value changes."""
|
||||
if self._status in (Status.ERROR, Status.MOVING):
|
||||
return
|
||||
if abs(self.fb - self.target) <= self.DEADBAND:
|
||||
if abs(self.fb - self.target) <= self.deadband:
|
||||
self._set_status(Status.IN_POSITION)
|
||||
else:
|
||||
self._set_status(Status.NOT_IN_POSITION)
|
||||
@@ -333,7 +256,7 @@ class MoveWidget(QWidget):
|
||||
|
||||
def _start_motion(self):
|
||||
target = self.target
|
||||
if abs(target - self.fb) <= self.DEADBAND:
|
||||
if abs(target - self.fb) <= self.deadband:
|
||||
self._set_status(Status.IN_POSITION)
|
||||
return
|
||||
|
||||
@@ -373,7 +296,7 @@ class MoveWidget(QWidget):
|
||||
|
||||
def _on_motion_finished(self, reached: bool):
|
||||
target = self.target
|
||||
if abs(self.fb - target) <= self.DEADBAND:
|
||||
if abs(self.fb - target) <= self.deadband:
|
||||
self._set_status(Status.IN_POSITION)
|
||||
else:
|
||||
self._set_status(Status.NOT_IN_POSITION)
|
||||
@@ -396,53 +319,3 @@ class MoveWidget(QWidget):
|
||||
if self._thread:
|
||||
self._thread.quit()
|
||||
self._thread.wait(2000) # max 2 s grace period
|
||||
|
||||
|
||||
# # ---------------------------------------------------------------------------
|
||||
# # Main window
|
||||
# # ---------------------------------------------------------------------------
|
||||
# class MainWindow(QMainWindow):
|
||||
# def __init__(self):
|
||||
# super().__init__()
|
||||
# self.setWindowTitle("Motor Stage Controller")
|
||||
# self.setMinimumWidth(620)
|
||||
|
||||
# central = QWidget()
|
||||
# self.setCentralWidget(central)
|
||||
# layout = QVBoxLayout(central)
|
||||
# layout.setContentsMargins(16, 16, 16, 16)
|
||||
# layout.setSpacing(12)
|
||||
|
||||
# # Title
|
||||
# title = QLabel("Motor Stage Controller")
|
||||
# layout.addWidget(title)
|
||||
|
||||
# sep = QFrame()
|
||||
# sep.setFrameShape(QFrame.HLine)
|
||||
# layout.addWidget(sep)
|
||||
|
||||
# # Three example stage groups
|
||||
# self.stages: list[StageGroupWidget] = []
|
||||
# for axis in ("X Axis", "Y Axis", "Z Axis"):
|
||||
# stage = StageGroupWidget(axis)
|
||||
# self.stages.append(stage)
|
||||
# layout.addWidget(stage)
|
||||
|
||||
# # Set different initial positions for demo variety
|
||||
# self.stages[0].spin_value.setValue(25.0)
|
||||
# self.stages[1].spin_value.setValue(-10.5)
|
||||
# self.stages[2].spin_value.setValue(5.75)
|
||||
|
||||
|
||||
# def closeEvent(self, event):
|
||||
# """Stop all motion threads before closing."""
|
||||
# for stage in self.stages:
|
||||
# stage.shutdown()
|
||||
# event.accept()
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# app = QApplication(sys.argv)
|
||||
# # app.setStyle("Fusion")
|
||||
# window = MainWindow()
|
||||
# window.show()
|
||||
# sys.exit(app.exec())
|
||||
|
||||
Reference in New Issue
Block a user