wip: digital twin
This commit is contained in:
@@ -5,6 +5,7 @@ Digital Twin: Custom BEC widget to support the beamline alignment.
|
||||
import sys
|
||||
import numpy as np
|
||||
from bec_lib import bec_logger
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
|
||||
# pylint: disable=E0611
|
||||
from qtpy.QtWidgets import (
|
||||
@@ -22,6 +23,7 @@ from qtpy.QtCore import (
|
||||
from qtpy.QtGui import (
|
||||
QColor,
|
||||
QBrush,
|
||||
QCloseEvent,
|
||||
)
|
||||
import pyqtgraph as pg
|
||||
|
||||
@@ -33,8 +35,8 @@ from debye_bec.bec_widgets.widgets.qt_widgets import (
|
||||
ComboBox,
|
||||
Group,
|
||||
NumberIndicator,
|
||||
Mover,
|
||||
)
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.move_widget import MoveWidget
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.calc_positions import calc_positions
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.calc_sideview import calc_sideview
|
||||
from debye_bec.bec_widgets.widgets.digital_twin.calc_surfaces import calc_surfaces
|
||||
@@ -77,14 +79,14 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
self.sideview_plot = SideviewPlot()
|
||||
self.surface_plots = SurfacePlots()
|
||||
self.positions = PositionsPanel()
|
||||
# self.mover = MoverPanel(dev=self.dev)
|
||||
self.mover = MoverPanel(self.dev)
|
||||
|
||||
self.root_layout.addWidget(self.input, stretch=1, alignment=Qt.AlignTop) # type: ignore
|
||||
self.plot_layout.addWidget(self.sideview_plot) # type: ignore
|
||||
self.plot_layout.addWidget(self.surface_plots) # type: ignore
|
||||
self.root_layout.addWidget(self.plot_widget, stretch=1, alignment=Qt.AlignTop) # type: ignore
|
||||
self.root_layout.addWidget(self.positions, stretch=1, alignment=Qt.AlignTop) # type: ignore
|
||||
# self.root_layout.addWidget(self.mover, stretch=1, alignment=Qt.AlignTop) # type: ignore
|
||||
# self.root_layout.addWidget(self.positions, stretch=1, alignment=Qt.AlignTop) # type: ignore
|
||||
self.root_layout.addWidget(self.mover, stretch=1, alignment=Qt.AlignTop)
|
||||
|
||||
self.setLayout(self.root_layout)
|
||||
self.setWindowTitle("Digital Twin")
|
||||
@@ -112,13 +114,14 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
|
||||
# Timer: update plot every 1 second
|
||||
self._timer = QTimer(self)
|
||||
self._timer.setInterval(1000)
|
||||
self._timer.setInterval(100)
|
||||
self._timer.timeout.connect(self.calc_reality)
|
||||
self._timer.start()
|
||||
|
||||
def apply_theme(self, theme):
|
||||
self.sideview_plot.apply_theme(theme)
|
||||
self.surface_plots.apply_theme(theme)
|
||||
self.mover.apply_theme(theme)
|
||||
|
||||
@SafeSlot()
|
||||
def calc_assistant(self, *args, **kwargs):
|
||||
@@ -203,38 +206,60 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
return config
|
||||
|
||||
def get_reality_config(self):
|
||||
if abs(self.dev.mo1_trx.read()['mo1_trx']['value']) > 5:
|
||||
mo1_trx = self.dev.mo1_trx.read(cached=True)['mo1_trx']['value']
|
||||
if abs(mo1_trx) > 5:
|
||||
mo1_mode = 'Monochromatic'
|
||||
else:
|
||||
mo1_mode = 'Pinkbeam'
|
||||
mo1_bragg = self.dev.mo1_bragg.read()
|
||||
sldi_gapx = self.dev.sldi_gapx.read()['sldi_gapx']['value']
|
||||
sldi_gapy = self.dev.sldi_gapy.read()['sldi_gapy']['value']
|
||||
mo1_bragg = self.dev.mo1_bragg.read(cached=True)
|
||||
sldi_gapx = self.dev.sldi_gapx.read(cached=True)['sldi_gapx']['value']
|
||||
sldi_gapy = self.dev.sldi_gapy.read(cached=True)['sldi_gapy']['value']
|
||||
h_acc, v_acc = sldi_gap_to_acc(sldi_gapx, sldi_gapy)
|
||||
cm_trx = -self.dev.cm_trx.read()['cm_trx']['value']
|
||||
cm_trx = -self.dev.cm_trx.read(cached=True)['cm_trx']['value']
|
||||
cm_stripe = cm_trx_to_stripe(cm_trx)
|
||||
cm_pitch = -self.dev.cm_rotx.read()['cm_rotx']['value'] * 1e-3
|
||||
fm_trx = -self.dev.fm_trx.read()['fm_trx']['value']
|
||||
cm_pitch = self.dev.cm_rotx.read(cached=True)['cm_rotx']['value']
|
||||
fm_trx = -self.dev.fm_trx.read(cached=True)['fm_trx']['value']
|
||||
fm_stripe = fm_trx_to_stripe(fm_trx)
|
||||
fm_pitch = -self.dev.fm_rotx.read()['fm_rotx']['value'] * 1e-3
|
||||
fm_pitch = self.dev.fm_rotx.read(cached=True)['fm_rotx']['value']
|
||||
fm_pitch_real = 2 * cm_pitch - fm_pitch
|
||||
smpl = self.dev.ot_es1_trz.read(cached=True)['ot_es1_trz']['value']
|
||||
config = { # Config in SI units!
|
||||
'energy' : mo1_bragg['mo1_bragg']['value'],
|
||||
'h_acc' : h_acc,
|
||||
'v_acc' : v_acc,
|
||||
'cm_pitch' : cm_pitch,
|
||||
'cm_pitch' : -cm_pitch * 1e-3,
|
||||
'cm_stripe' : cm_stripe,
|
||||
'cm_trx' : cm_trx,
|
||||
'mo1_mode' : mo1_mode,
|
||||
'mo1_xtal' : mo1_bragg['mo1_bragg_crystal_current_xtal_string']['value'],
|
||||
'mo1_bragg' : mo1_bragg['mo1_bragg_angle']['value']/180*np.pi,
|
||||
'fm_pitch' : fm_pitch_real,
|
||||
'fm_pitch' : -fm_pitch_real * 1e-3,
|
||||
'fm_stripe' : fm_stripe,
|
||||
'fm_trx' : fm_trx,
|
||||
'fm_gain_height' : 1,
|
||||
'smpl' : self.dev.ot_es1_trz.read()['ot_es1_trz']['value'],
|
||||
'smpl' : smpl,
|
||||
}
|
||||
# logger.info(f'Config created: {config}')
|
||||
self.mover.sldi_gapx.set_feedback(sldi_gapx)
|
||||
self.mover.sldi_gapy.set_feedback(sldi_gapy)
|
||||
self.mover.cm_trx.set_feedback(cm_trx)
|
||||
self.mover.cm_try.set_feedback(self.dev.cm_try.read(cached=True)['cm_try']['value'])
|
||||
self.mover.cm_bnd.set_feedback(self.dev.cm_bnd_radius.read(cached=True)['cm_bnd_radius']['value'])
|
||||
self.mover.cm_rotx.set_feedback(cm_pitch)
|
||||
self.mover.mo1_bragg_angle.set_feedback(mo1_bragg['mo1_bragg_angle']['value'])
|
||||
self.mover.mo1_trx.set_feedback(mo1_trx)
|
||||
self.mover.mo1_try.set_feedback(self.dev.mo1_try.read(cached=True)['mo1_try']['value'])
|
||||
self.mover.sl1_centery.set_feedback(self.dev.sl1_centery.read(cached=True)['sl1_centery']['value'])
|
||||
self.mover.bm1_try.set_feedback(self.dev.bm1_try.read(cached=True)['bm1_try']['value'])
|
||||
self.mover.fm_trx.set_feedback(fm_trx)
|
||||
self.mover.fm_try.set_feedback(self.dev.fm_try.read(cached=True)['fm_try']['value'])
|
||||
self.mover.fm_bnd.set_feedback(self.dev.fm_bnd_radius.read(cached=True)['fm_bnd_radius']['value'])
|
||||
self.mover.fm_rotx.set_feedback(fm_pitch)
|
||||
self.mover.sl2_centery.set_feedback(self.dev.sl2_centery.read(cached=True)['sl2_centery']['value'])
|
||||
self.mover.bm2_try.set_feedback(self.dev.bm2_try.read(cached=True)['bm2_try']['value'])
|
||||
self.mover.ot_try.set_feedback(self.dev.ot_try.read(cached=True)['ot_try']['value'])
|
||||
self.mover.ot_rotx.set_feedback(self.dev.ot_rotx.read(cached=True)['ot_rotx']['value'])
|
||||
self.mover.ot_es1_trz.set_feedback(smpl)
|
||||
return config
|
||||
|
||||
def update_fm_mode(self):
|
||||
@@ -330,18 +355,39 @@ class DigitalTwin(BECWidget, QWidget):
|
||||
self.positions.ot_rotx.setValue(out['ot_rotx']['value'])
|
||||
self.positions.ot_es1_trz.setValue(out['ot_es1_trz']['value'])
|
||||
|
||||
self.mover.sldi_gapx.set_target(out['sldi_gapx']['value'])
|
||||
self.mover.sldi_gapy.set_target(out['sldi_gapy']['value'])
|
||||
self.mover.cm_trx.set_target(out['cm_trx']['value'])
|
||||
self.mover.cm_try.set_target(out['cm_try']['value'])
|
||||
self.mover.cm_bnd.set_target(out['cm_bnd_radius']['value'])
|
||||
self.mover.cm_rotx.set_target(out['cm_rotx']['value'])
|
||||
self.mover.mo1_bragg_angle.set_target(out['mo1_bragg_angle']['value'])
|
||||
self.mover.mo1_trx.set_target(out['mo1_trx']['value'])
|
||||
self.mover.mo1_try.set_target(out['mo1_try']['value'])
|
||||
self.mover.sl1_centery.set_target(out['sl1_centery']['value'])
|
||||
self.mover.bm1_try.set_target(out['bm1_try']['value'])
|
||||
self.mover.fm_trx.set_target(out['fm_trx']['value'])
|
||||
self.mover.fm_try.set_target(out['fm_try']['value'])
|
||||
self.mover.fm_bnd.set_target(out['fm_bnd_radius']['value'])
|
||||
self.mover.fm_rotx.set_target(out['fm_rotx']['value'])
|
||||
self.mover.sl2_centery.set_target(out['sl2_centery']['value'])
|
||||
self.mover.bm2_try.set_target(out['bm2_try']['value'])
|
||||
self.mover.ot_try.set_target(out['ot_try']['value'])
|
||||
self.mover.ot_rotx.set_target(out['ot_rotx']['value'])
|
||||
self.mover.ot_es1_trz.set_target(out['ot_es1_trz']['value'])
|
||||
|
||||
def calc_mo1_bragg_angle(self):
|
||||
"""
|
||||
Calculates bragg angle in rad
|
||||
"""
|
||||
xtal = self.input.mo1_xtal.currentText()
|
||||
if xtal in 'Si(111)':
|
||||
d_spacing = self.dev.mo1_bragg.crystal.d_spacing_si111.get()
|
||||
d_spacing = self.dev.mo1_bragg.crystal.d_spacing_si111.read(cached=True)['mo1_bragg_crystal_d_spacing_si111']['value']
|
||||
elif xtal in 'Si(311)':
|
||||
d_spacing = self.dev.mo1_bragg.crystal.d_spacing_si311.get()
|
||||
d_spacing = self.dev.mo1_bragg.crystal.d_spacing_si311.read(cached=True)['mo1_bragg_crystal_d_spacing_si311']['value']
|
||||
else:
|
||||
raise Exception(f'Invalid xtal selection: {xtal}')
|
||||
cm_pitch = -self.dev.cm_rotx.read()['cm_rotx']['value'] * 1e-3
|
||||
cm_pitch = -self.dev.cm_rotx.read(cached=True)['cm_rotx']['value'] * 1e-3
|
||||
mo1_mode = self.input.mo1_mode.currentText()
|
||||
energy = self.input.energy.value()
|
||||
theta, theta_cor = mo1_bragg_angle(mo1_mode, d_spacing, energy, cm_pitch)
|
||||
@@ -607,19 +653,20 @@ class MoverPanel(QWidget):
|
||||
|
||||
def __init__(self, dev, parent=None):
|
||||
super().__init__(parent)
|
||||
self.dev = dev
|
||||
self._layout = QVBoxLayout(self)
|
||||
self._layout.setSizeConstraint(QLayout.SetFixedSize) # type: ignore
|
||||
|
||||
self.mover_widgets = []
|
||||
self.dev = dev
|
||||
|
||||
# FE Slits
|
||||
mot = self.dev.sldi_gapx
|
||||
egu = self.dev.sldi_gapx.egu()
|
||||
prec = self.dev.sldi_gapx.precision
|
||||
self.sldi_gapx = Mover(mot, egu, prec)
|
||||
self.sldi_gapx = MoveWidget(motor=mot, label='GAPX', unit='mm', decimals=2)
|
||||
self.mover_widgets.append(self.sldi_gapx)
|
||||
|
||||
mot = self.dev.sldi_gapy
|
||||
egu = self.dev.sldi_gapy.egu()
|
||||
prec = self.dev.sldi_gapy.precision
|
||||
self.sldi_gapy = Mover(mot, egu, prec)
|
||||
self.sldi_gapy = MoveWidget(motor=mot, label='GAPY', unit='mm', decimals=2)
|
||||
self.mover_widgets.append(self.sldi_gapy)
|
||||
|
||||
self.sldi_mov_group = Group(
|
||||
'FE Slits',
|
||||
@@ -629,16 +676,174 @@ class MoverPanel(QWidget):
|
||||
]
|
||||
)
|
||||
|
||||
# Assemble complete assitant group
|
||||
# Collimating mirror
|
||||
mot = self.dev.cm_trx
|
||||
self.cm_trx = MoveWidget(motor=mot, label='TRX', unit='mm', decimals=2)
|
||||
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.mover_widgets.append(self.cm_try)
|
||||
|
||||
mot = self.dev.cm_bnd
|
||||
self.cm_bnd = MoveWidget(motor=mot, label='BENDER', unit='km', decimals=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.mover_widgets.append(self.cm_rotx)
|
||||
|
||||
self.cm_mov_group = Group(
|
||||
'Collimating Mirror',
|
||||
[
|
||||
self.cm_trx,
|
||||
self.cm_try,
|
||||
self.cm_bnd,
|
||||
self.cm_rotx,
|
||||
]
|
||||
)
|
||||
|
||||
# Monochromator
|
||||
mot = self.dev.mo1_bragg
|
||||
self.mo1_bragg_angle = MoveWidget(motor=mot, label='Bragg Angle', unit='deg', decimals=3)
|
||||
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.mover_widgets.append(self.mo1_trx)
|
||||
|
||||
mot = self.dev.mo1_try
|
||||
self.mo1_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2)
|
||||
self.mover_widgets.append(self.mo1_try)
|
||||
|
||||
self.mo1_mov_group = Group(
|
||||
'Monochromator',
|
||||
[
|
||||
self.mo1_bragg_angle,
|
||||
self.mo1_trx,
|
||||
self.mo1_try,
|
||||
]
|
||||
)
|
||||
|
||||
# OP Slits 1
|
||||
mot = self.dev.sl1_centery
|
||||
self.sl1_centery = MoveWidget(motor=mot, label='CENTERY', unit='mm', decimals=2)
|
||||
self.mover_widgets.append(self.sl1_centery)
|
||||
|
||||
self.sl1_mov_group = Group(
|
||||
'OP Slits 1',
|
||||
[
|
||||
self.sl1_centery,
|
||||
]
|
||||
)
|
||||
|
||||
# OP Beam Monitor 1
|
||||
mot = self.dev.bm1_try
|
||||
self.bm1_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2)
|
||||
self.mover_widgets.append(self.bm1_try)
|
||||
|
||||
self.bm1_mov_group = Group(
|
||||
'OP Beam Monitor 1',
|
||||
[
|
||||
self.bm1_try,
|
||||
]
|
||||
)
|
||||
|
||||
# Focusing Mirror
|
||||
mot = self.dev.fm_trx
|
||||
self.fm_trx = MoveWidget(motor=mot, label='TRX', unit='mm', decimals=2)
|
||||
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.mover_widgets.append(self.fm_try)
|
||||
|
||||
mot = self.dev.fm_bnd
|
||||
self.fm_bnd = MoveWidget(motor=mot, label='BENDER', unit='km', decimals=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.mover_widgets.append(self.fm_rotx)
|
||||
|
||||
self.fm_mov_group = Group(
|
||||
'Focusing Mirror',
|
||||
[
|
||||
self.fm_trx,
|
||||
self.fm_try,
|
||||
self.fm_bnd,
|
||||
self.fm_rotx,
|
||||
]
|
||||
)
|
||||
|
||||
# OP Slits 2
|
||||
mot = self.dev.sl2_centery
|
||||
self.sl2_centery = MoveWidget(motor=mot, label='CENTERY', unit='mm', decimals=2)
|
||||
self.mover_widgets.append(self.sl2_centery)
|
||||
|
||||
self.sl2_mov_group = Group(
|
||||
'OP Slits 2',
|
||||
[
|
||||
self.sl2_centery,
|
||||
]
|
||||
)
|
||||
|
||||
# OP Beam Monitor 2
|
||||
mot = self.dev.bm2_try
|
||||
self.bm2_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=2)
|
||||
self.mover_widgets.append(self.bm2_try)
|
||||
|
||||
self.bm2_mov_group = Group(
|
||||
'OP Beam Monitor 2',
|
||||
[
|
||||
self.bm2_try,
|
||||
]
|
||||
)
|
||||
|
||||
# Optical Table
|
||||
mot = self.dev.ot_try
|
||||
self.ot_try = MoveWidget(motor=mot, label='TRY', unit='mm', decimals=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.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.mover_widgets.append(self.ot_es1_trz)
|
||||
|
||||
self.ot_mov_group = Group(
|
||||
'Optical Table',
|
||||
[
|
||||
self.ot_try,
|
||||
self.ot_rotx,
|
||||
self.ot_es1_trz,
|
||||
]
|
||||
)
|
||||
|
||||
# Assemble complete mover group
|
||||
self.mover_group = Group(
|
||||
'Mover',
|
||||
[
|
||||
self.sldi_mov_group,
|
||||
self.cm_mov_group,
|
||||
self.mo1_mov_group,
|
||||
self.sl1_mov_group,
|
||||
self.bm1_mov_group,
|
||||
self.fm_mov_group,
|
||||
self.sl2_mov_group,
|
||||
self.bm2_mov_group,
|
||||
self.ot_mov_group,
|
||||
]
|
||||
)
|
||||
|
||||
self._layout .addWidget(self.mover_group)
|
||||
self._layout .addStretch()
|
||||
|
||||
def apply_theme(self, theme):
|
||||
for widget in self.mover_widgets:
|
||||
widget.apply_theme(theme)
|
||||
|
||||
class SurfacePlots(QWidget):
|
||||
"""Plot widget with two curves and legend."""
|
||||
|
||||
@@ -0,0 +1,448 @@
|
||||
import time
|
||||
import random
|
||||
import threading
|
||||
|
||||
# import qtawesome as qta
|
||||
from bec_qthemes import material_icon
|
||||
from bec_widgets.utils.colors import get_accent_colors
|
||||
from bec_lib import bec_logger
|
||||
|
||||
from qtpy.QtCore import Qt, QThread, Signal, QObject, Property, QPropertyAnimation
|
||||
from qtpy.QtWidgets import (
|
||||
QGroupBox, QHBoxLayout, QVBoxLayout, QLabel, QPushButton,
|
||||
QDoubleSpinBox, QFrame, QWidget, QApplication
|
||||
)
|
||||
from qtpy.QtGui import QTransform
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
class Status:
|
||||
IN_POSITION = "in_position" # green mdi.check-circle
|
||||
NOT_IN_POSITION = "not_in_position" # orange mdi.close-circle
|
||||
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.
|
||||
Handles its own spin animation for the MOVING state via QPropertyAnimation.
|
||||
"""
|
||||
|
||||
ICON_SIZE = 20
|
||||
|
||||
_ICON_MAP = {
|
||||
Status.IN_POSITION: ("check_circle", "#27ae60"),
|
||||
Status.NOT_IN_POSITION: ("cancel", "#e6d922"),
|
||||
Status.ERROR: ("warning", "#e74c3c"),
|
||||
Status.MOVING: ("cycle", "#2980b9"),
|
||||
}
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
self._status = None
|
||||
self._rotation = 0.0
|
||||
|
||||
self._label = QLabel(self)
|
||||
self._label.setFixedSize(self.ICON_SIZE, self.ICON_SIZE)
|
||||
self._label.setAlignment(Qt.AlignCenter)
|
||||
self.setFixedSize(self.ICON_SIZE, self.ICON_SIZE)
|
||||
|
||||
self._spin_anim = QPropertyAnimation(self, b"rotation")
|
||||
self._spin_anim.setStartValue(0)
|
||||
self._spin_anim.setEndValue(360)
|
||||
self._spin_anim.setDuration(1000)
|
||||
self._spin_anim.setLoopCount(-1) # Loop indefinitely
|
||||
|
||||
self.set_status(Status.NOT_IN_POSITION)
|
||||
|
||||
def get_rotation(self):
|
||||
return self._rotation
|
||||
|
||||
def set_rotation(self, angle):
|
||||
self._rotation = angle
|
||||
if self._current_pixmap_base is not None:
|
||||
cx = self._current_pixmap_base.width() / 2
|
||||
cy = self._current_pixmap_base.height() / 2
|
||||
t = QTransform().translate(cx, cy).rotate(angle).translate(-cx, -cy)
|
||||
self._label.setPixmap(self._current_pixmap_base.transformed(t, Qt.SmoothTransformation))
|
||||
|
||||
rotation = Property(float, get_rotation, set_rotation)
|
||||
|
||||
def set_status(self, status: str):
|
||||
if status == self._status:
|
||||
return
|
||||
self._status = status
|
||||
|
||||
icon_name, color = self._ICON_MAP[status]
|
||||
icon = material_icon(icon_name, size=(self.ICON_SIZE, self.ICON_SIZE), color=color, convert_to_pixmap=True)
|
||||
self._current_pixmap_base = icon
|
||||
|
||||
if status == Status.MOVING:
|
||||
self._spin_anim.start()
|
||||
else:
|
||||
self._spin_anim.stop()
|
||||
self._label.setPixmap(icon)
|
||||
|
||||
class MotionWorker(QObject):
|
||||
"""
|
||||
Simulates moving a stage from current_pos to target_pos.
|
||||
Emits position_changed and finished signals.
|
||||
"""
|
||||
position_changed = Signal(float)
|
||||
error = Signal(bool) # True = error
|
||||
finished = Signal(bool) # True = reached target, False = stopped
|
||||
|
||||
def __init__(self, motor, target_pos: float):
|
||||
super().__init__()
|
||||
self.motor = motor
|
||||
self.name = motor.dotted_name
|
||||
self._target = target_pos
|
||||
self._stop_flag = threading.Event()
|
||||
|
||||
def stop(self):
|
||||
self._stop_flag.set()
|
||||
|
||||
def run(self):
|
||||
logger.info(f'Would run motor {self.name}')
|
||||
simulated_run_time = 3
|
||||
start = time.time()
|
||||
while (time.time() - start) < simulated_run_time:
|
||||
if self._stop_flag.is_set():
|
||||
break
|
||||
time.sleep(0.01)
|
||||
|
||||
# self.motor.move(self._target, relative=False)
|
||||
# while self.motor.motor_is_moving.get():
|
||||
# if self._stop_flag.is_set():
|
||||
# self.motor.motor_stop()
|
||||
# self.position_changed.emit(self.motor.read[self.name]['value'])
|
||||
# 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:
|
||||
- Value spinbox (target position)
|
||||
- Feedback label (current position)
|
||||
- Status icon (qtawesome)
|
||||
- Start / Stop button
|
||||
"""
|
||||
|
||||
DEADBAND = 0.02 # mm — positions within this tolerance are "in position"
|
||||
|
||||
def __init__(self, motor, label: str = '', unit=None, decimals=3):
|
||||
super().__init__()
|
||||
self.fb = 0.0
|
||||
self.target = 0
|
||||
self.motor = motor
|
||||
self._status = Status.IN_POSITION
|
||||
self._thread: QThread | None = None
|
||||
self._worker: MotionWorker | None = None
|
||||
|
||||
self.text_color = (0, 0, 0)
|
||||
|
||||
self.unit = unit
|
||||
self.decimals = decimals
|
||||
|
||||
# self._set_status(Status.IN_POSITION)
|
||||
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
|
||||
# Name
|
||||
self.label = QLabel(label)
|
||||
self.label.setFixedWidth(100)
|
||||
self.label.setContentsMargins(0, 0, 10, 0)
|
||||
self.label.setWordWrap(True)
|
||||
layout.addWidget(self.label)
|
||||
|
||||
# Target
|
||||
self.target_label = QLabel('-')
|
||||
self.target_label.setFixedWidth(100)
|
||||
layout.addWidget(self.target_label)
|
||||
|
||||
# Feedback
|
||||
self.fb_label = QLabel('-')
|
||||
self.fb_label.setFixedWidth(100)
|
||||
layout.addWidget(self.fb_label)
|
||||
|
||||
# Status icon
|
||||
self.status_icon = StatusIcon()
|
||||
self.status_icon.setFixedWidth(30)
|
||||
self.status_icon.setContentsMargins(0, 0, 10, 0)
|
||||
layout.addWidget(self.status_icon)
|
||||
|
||||
# Start / Stop button
|
||||
self.btn_action = QPushButton("▶ Move")
|
||||
self.btn_action.setFixedWidth(90)
|
||||
self.btn_action.setFixedHeight(20)
|
||||
self.btn_action.clicked.connect(self._on_button_clicked)
|
||||
layout.addWidget(self.btn_action)
|
||||
|
||||
self._apply_button_style("start")
|
||||
|
||||
self.apply_theme()
|
||||
|
||||
def apply_theme(self, theme=None):
|
||||
if theme is None:
|
||||
app = QApplication.instance()
|
||||
theme = app.theme.theme # type: ignore
|
||||
|
||||
if theme == "light":
|
||||
self.text_color = {'target': (79, 163, 224), 'fb': (240, 128, 60)}
|
||||
else: # dark theme
|
||||
self.text_color = {'target': (26, 111, 173), 'fb': (212, 83, 10)}
|
||||
r, g, b = self.text_color['target']
|
||||
self.target_label.setStyleSheet(f'QLabel {{color: rgb({r}, {g}, {b})}}')
|
||||
r, g, b = self.text_color['fb']
|
||||
self.fb_label.setStyleSheet(f'QLabel {{color: rgb({r}, {g}, {b})}}')
|
||||
|
||||
def set_target(self, target):
|
||||
self.target = target
|
||||
text = f'{target:.{int(self.decimals)}f}'
|
||||
if self.unit is not None:
|
||||
text = text + ' ' + self.unit
|
||||
self.target_label.setText(text)
|
||||
self._on_target_or_fb_changed()
|
||||
|
||||
def set_feedback(self, fb):
|
||||
if self._status != Status.MOVING:
|
||||
self.fb = fb
|
||||
text = f'{fb:.{int(self.decimals)}f}'
|
||||
if self.unit is not None:
|
||||
text = text + ' ' + self.unit
|
||||
self.fb_label.setText(text)
|
||||
self._on_target_or_fb_changed()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Button style helpers
|
||||
# ------------------------------------------------------------------
|
||||
def _apply_button_style(self, mode: str):
|
||||
if mode == "start":
|
||||
self.btn_action.setText("▶ Move")
|
||||
self.btn_action.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().success.name()}; color: white;}}"
|
||||
)
|
||||
else: # stop
|
||||
self.btn_action.setText("■ Stop")
|
||||
self.btn_action.setStyleSheet(
|
||||
f"QPushButton {{background-color: {get_accent_colors().emergency.name()}; color: white;}}"
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Status management
|
||||
# ------------------------------------------------------------------
|
||||
def _set_status(self, status: str):
|
||||
self._status = status
|
||||
self.status_icon.set_status(status)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Motion control
|
||||
# ------------------------------------------------------------------
|
||||
def _on_target_or_fb_changed(self):
|
||||
"""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:
|
||||
self._set_status(Status.IN_POSITION)
|
||||
else:
|
||||
self._set_status(Status.NOT_IN_POSITION)
|
||||
|
||||
def _on_button_clicked(self):
|
||||
if self._thread and self._thread.isRunning():
|
||||
self._stop_motion()
|
||||
else:
|
||||
self._start_motion()
|
||||
|
||||
def _start_motion(self):
|
||||
target = self.target
|
||||
if abs(target - self.fb) <= self.DEADBAND:
|
||||
self._set_status(Status.IN_POSITION)
|
||||
return
|
||||
|
||||
self._set_status(Status.MOVING)
|
||||
self._apply_button_style("stop")
|
||||
|
||||
self._worker = MotionWorker(self.motor, target)
|
||||
self._thread = QThread()
|
||||
self._worker.moveToThread(self._thread)
|
||||
|
||||
# Wire signals
|
||||
self._thread.started.connect(self._worker.run)
|
||||
self._worker.position_changed.connect(self._on_position_changed)
|
||||
self._worker.error.connect(self._on_error)
|
||||
self._worker.error.connect(self._thread.quit)
|
||||
self._worker.finished.connect(self._on_motion_finished)
|
||||
self._worker.finished.connect(self._thread.quit)
|
||||
self._thread.finished.connect(self._cleanup_thread)
|
||||
|
||||
self._thread.start()
|
||||
|
||||
def _on_error(self):
|
||||
self._set_status(Status.ERROR)
|
||||
self._apply_button_style("start")
|
||||
|
||||
def _stop_motion(self):
|
||||
if self._worker:
|
||||
self._worker.stop()
|
||||
# UI will update via finished signal
|
||||
|
||||
def _on_position_changed(self, pos: float):
|
||||
self.fb = pos
|
||||
text = f'{pos:.{int(self.decimals)}f}'
|
||||
if self.unit is not None:
|
||||
text = text + ' ' + self.unit
|
||||
self.fb_label.setText(text)
|
||||
|
||||
def _on_motion_finished(self, reached: bool):
|
||||
target = self.target
|
||||
if abs(self.fb - target) <= self.DEADBAND:
|
||||
self._set_status(Status.IN_POSITION)
|
||||
else:
|
||||
self._set_status(Status.NOT_IN_POSITION)
|
||||
self._apply_button_style("start")
|
||||
|
||||
def _cleanup_thread(self):
|
||||
if self._thread:
|
||||
self._thread.deleteLater()
|
||||
self._thread = None
|
||||
if self._worker:
|
||||
self._worker.deleteLater()
|
||||
self._worker = None
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Called on application close — stop motion safely
|
||||
# ------------------------------------------------------------------
|
||||
def shutdown(self):
|
||||
if self._worker:
|
||||
self._worker.stop()
|
||||
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())
|
||||
@@ -15,32 +15,6 @@ class Group(QGroupBox):
|
||||
for widget in widgets:
|
||||
self.layout.addWidget(widget) # type: ignore
|
||||
|
||||
# class TextIndicator(QWidget):
|
||||
# def __init__(self, label, unit=None, highlight=False):
|
||||
# super().__init__()
|
||||
# layout = QHBoxLayout(self)
|
||||
# layout.setContentsMargins(10, 0, 0, 0)
|
||||
# layout.setSpacing(0)
|
||||
# self.label = QLabel(label)
|
||||
# self.label.setFixedWidth(150)
|
||||
# layout.addWidget(self.label)
|
||||
# self.value = QLabel('-')
|
||||
# self.value.setFixedWidth(160)
|
||||
# layout.addWidget(self.value)
|
||||
# self.unit = unit
|
||||
# self.highlight = highlight
|
||||
# if highlight:
|
||||
# font = QFont()
|
||||
# font.setBold(True)
|
||||
# font.setPointSize(14)
|
||||
# self.label.setFont(font)
|
||||
# self.value.setFont(font)
|
||||
|
||||
# def set_text(self, text):
|
||||
# if self.unit is not None:
|
||||
# text = text + ' ' + self.unit
|
||||
# self.value.setText(text)
|
||||
|
||||
class NumberIndicator(QWidget):
|
||||
def __init__(self, label='', unit=None, highlight=False, decimals=3):
|
||||
super().__init__()
|
||||
@@ -80,38 +54,6 @@ class NumberIndicator(QWidget):
|
||||
text = text + ' ' + self.unit
|
||||
self.val.setText(text)
|
||||
|
||||
# class InputTextField(QWidget):
|
||||
# def __init__(self, topic, label):
|
||||
# super().__init__()
|
||||
# self.topic = topic
|
||||
# layout = QHBoxLayout(self)
|
||||
# layout.setContentsMargins(10, 0, 0, 0)
|
||||
# layout.setSpacing(0)
|
||||
# self.label = QLabel(label)
|
||||
# self.label.setFixedWidth(140)
|
||||
# self.label.setContentsMargins(0, 0, 10, 0)
|
||||
# self.label.setWordWrap(True)
|
||||
# layout.addWidget(self.label)
|
||||
# self.val = QLineEdit()
|
||||
# self.val.setPlaceholderText('0')
|
||||
# # self.val.setFixedWidth(140)
|
||||
# layout.addWidget(self.val)
|
||||
|
||||
# def set_text(self, text):
|
||||
# self.val.setText(text)
|
||||
|
||||
# def has_focus(self) -> bool:
|
||||
# return self.val.hasFocus()
|
||||
|
||||
# def text(self) -> str:
|
||||
# return self.val.text()
|
||||
|
||||
# def set_on_return(self, func):
|
||||
# """Connect a function to the Enter/Return key press."""
|
||||
# self.val.returnPressed.connect(
|
||||
# partial(func, self.val, self.topic, lambda: self.val.text())
|
||||
# )
|
||||
|
||||
class InputNumberField(QWidget):
|
||||
def __init__(self, identifier='', label='', unit=None, prefix=None, init=0.0, decimals=1, single_step=0.1, ll=-1e6, hl=1e6):
|
||||
super().__init__()
|
||||
@@ -186,70 +128,96 @@ class ComboBox(QWidget):
|
||||
def setDisabled(self, disable):
|
||||
self.value.setDisabled(disable)
|
||||
|
||||
class Mover(QWidget):
|
||||
def __init__(self, dev, egu, prec):
|
||||
super().__init__()
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(10, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
self.position = QLabel('-')
|
||||
self.position.setFixedWidth(150)
|
||||
layout.addWidget(self.position)
|
||||
self.led = QLabel()
|
||||
self.led.setFixedWidth(30)
|
||||
self.led.setStyleSheet("background-color: 0, 0, 0; border: 1px solid black;")
|
||||
layout.addWidget(self.led)
|
||||
self.start = QPushButton('Move')
|
||||
self.start.setStyleSheet("color: black; background-color: green;")
|
||||
self.start.setFixedWidth(80)
|
||||
self.stop = QPushButton('Stop')
|
||||
self.stop.setStyleSheet("color: black; background-color: firebrick;")
|
||||
self.stop.setFixedWidth(80)
|
||||
layout.addWidget(self.start)
|
||||
layout.addWidget(self.stop)
|
||||
self.dev = dev
|
||||
self.unit = egu
|
||||
self.decimals = prec
|
||||
# class Mover(QWidget):
|
||||
# def __init__(self, dev, egu, prec):
|
||||
# super().__init__()
|
||||
# layout = QHBoxLayout(self)
|
||||
# layout.setContentsMargins(10, 0, 0, 0)
|
||||
# layout.setSpacing(0)
|
||||
# self.position = QLabel('-')
|
||||
# self.position.setFixedWidth(150)
|
||||
# layout.addWidget(self.position)
|
||||
# self.led = QLabel()
|
||||
# self.led.setFixedWidth(30)
|
||||
# self.led.setStyleSheet("background-color: 0, 0, 0; border: 1px solid black;")
|
||||
# layout.addWidget(self.led)
|
||||
# self.start = QPushButton('Move')
|
||||
# self.start.setStyleSheet("color: black; background-color: green;")
|
||||
# self.start.setFixedWidth(80)
|
||||
# self.stop = QPushButton('Stop')
|
||||
# self.stop.setStyleSheet("color: black; background-color: firebrick;")
|
||||
# self.stop.setFixedWidth(80)
|
||||
# layout.addWidget(self.start)
|
||||
# layout.addWidget(self.stop)
|
||||
# self.dev = dev
|
||||
# self.unit = egu
|
||||
# self.decimals = prec
|
||||
|
||||
def led_set_status(self, status):
|
||||
if status in 'out':
|
||||
self.led.setStyleSheet("background-color: 255, 0, 0; border: 1px solid black;")
|
||||
elif status in 'moving':
|
||||
self.led.setStyleSheet("background-color: 255, 255, 0; border: 1px solid black;")
|
||||
elif status in 'in':
|
||||
self.led.setStyleSheet("background-color: 0, 255, 0; border: 1px solid black;")
|
||||
# def led_set_status(self, status):
|
||||
# if status in 'out':
|
||||
# self.led.setStyleSheet("background-color: 255, 0, 0; border: 1px solid black;")
|
||||
# elif status in 'moving':
|
||||
# self.led.setStyleSheet("background-color: 255, 255, 0; border: 1px solid black;")
|
||||
# elif status in 'in':
|
||||
# self.led.setStyleSheet("background-color: 0, 255, 0; border: 1px solid black;")
|
||||
|
||||
def position_setValue(self, number):
|
||||
text = f'{number:.{int(self.decimals)}f}'
|
||||
if self.unit is not None:
|
||||
text = text + ' ' + self.unit
|
||||
self.position.setText(text)
|
||||
# def position_setValue(self, number):
|
||||
# text = f'{number:.{int(self.decimals)}f}'
|
||||
# if self.unit is not None:
|
||||
# text = text + ' ' + self.unit
|
||||
# self.position.setText(text)
|
||||
|
||||
def start_clicked_connect(self, func):
|
||||
"""Connect a function to the start button press."""
|
||||
self.start.clicked.connect(
|
||||
partial(func, dev=self.dev)
|
||||
)
|
||||
# def start_clicked_connect(self, func):
|
||||
# """Connect a function to the start button press."""
|
||||
# self.start.clicked.connect(
|
||||
# partial(func, dev=self.dev)
|
||||
# )
|
||||
|
||||
def stop_clicked_connect(self, func):
|
||||
"""Connect a function to the stop button press."""
|
||||
self.stop.clicked.connect(
|
||||
partial(func, dev=self.dev)
|
||||
)
|
||||
# def stop_clicked_connect(self, func):
|
||||
# """Connect a function to the stop button press."""
|
||||
# self.stop.clicked.connect(
|
||||
# partial(func, dev=self.dev)
|
||||
# )
|
||||
|
||||
def start_setEnabled(self, enable):
|
||||
self.start.setEnabled(enable)
|
||||
if enable:
|
||||
self.start.setStyleSheet("color: black; background-color: green;")
|
||||
else:
|
||||
self.start.setStyleSheet("color: black; background-color: grey;")
|
||||
# def start_setEnabled(self, enable):
|
||||
# self.start.setEnabled(enable)
|
||||
# if enable:
|
||||
# self.start.setStyleSheet("color: black; background-color: green;")
|
||||
# else:
|
||||
# self.start.setStyleSheet("color: black; background-color: grey;")
|
||||
|
||||
def stop_setEnabled(self, enable):
|
||||
self.stop.setEnabled(enable)
|
||||
if enable:
|
||||
self.stop.setStyleSheet("color: black; background-color: firebrick;")
|
||||
else:
|
||||
self.stop.setStyleSheet("color: black; background-color: grey;")
|
||||
# def stop_setEnabled(self, enable):
|
||||
# self.stop.setEnabled(enable)
|
||||
# if enable:
|
||||
# self.stop.setStyleSheet("color: black; background-color: firebrick;")
|
||||
# else:
|
||||
# self.stop.setStyleSheet("color: black; background-color: grey;")
|
||||
|
||||
# class TextIndicator(QWidget):
|
||||
# def __init__(self, label, unit=None, highlight=False):
|
||||
# super().__init__()
|
||||
# layout = QHBoxLayout(self)
|
||||
# layout.setContentsMargins(10, 0, 0, 0)
|
||||
# layout.setSpacing(0)
|
||||
# self.label = QLabel(label)
|
||||
# self.label.setFixedWidth(150)
|
||||
# layout.addWidget(self.label)
|
||||
# self.value = QLabel('-')
|
||||
# self.value.setFixedWidth(160)
|
||||
# layout.addWidget(self.value)
|
||||
# self.unit = unit
|
||||
# self.highlight = highlight
|
||||
# if highlight:
|
||||
# font = QFont()
|
||||
# font.setBold(True)
|
||||
# font.setPointSize(14)
|
||||
# self.label.setFont(font)
|
||||
# self.value.setFont(font)
|
||||
|
||||
# def set_text(self, text):
|
||||
# if self.unit is not None:
|
||||
# text = text + ' ' + self.unit
|
||||
# self.value.setText(text)
|
||||
|
||||
# class Button(QWidget):
|
||||
# def __init__(self, label, label_button):
|
||||
@@ -297,4 +265,36 @@ class Mover(QWidget):
|
||||
|
||||
# def apply_color(self, val):
|
||||
# color = self.colors[self.states.index(val)]
|
||||
# self.led.setStyleSheet(f"background-color: {color}; border: 1px solid black;")
|
||||
# self.led.setStyleSheet(f"background-color: {color}; border: 1px solid black;")
|
||||
|
||||
# class InputTextField(QWidget):
|
||||
# def __init__(self, topic, label):
|
||||
# super().__init__()
|
||||
# self.topic = topic
|
||||
# layout = QHBoxLayout(self)
|
||||
# layout.setContentsMargins(10, 0, 0, 0)
|
||||
# layout.setSpacing(0)
|
||||
# self.label = QLabel(label)
|
||||
# self.label.setFixedWidth(140)
|
||||
# self.label.setContentsMargins(0, 0, 10, 0)
|
||||
# self.label.setWordWrap(True)
|
||||
# layout.addWidget(self.label)
|
||||
# self.val = QLineEdit()
|
||||
# self.val.setPlaceholderText('0')
|
||||
# # self.val.setFixedWidth(140)
|
||||
# layout.addWidget(self.val)
|
||||
|
||||
# def set_text(self, text):
|
||||
# self.val.setText(text)
|
||||
|
||||
# def has_focus(self) -> bool:
|
||||
# return self.val.hasFocus()
|
||||
|
||||
# def text(self) -> str:
|
||||
# return self.val.text()
|
||||
|
||||
# def set_on_return(self, func):
|
||||
# """Connect a function to the Enter/Return key press."""
|
||||
# self.val.returnPressed.connect(
|
||||
# partial(func, self.val, self.topic, lambda: self.val.text())
|
||||
# )
|
||||
|
||||
Reference in New Issue
Block a user