#!/usr/bin/env python # *-----------------------------------------------------------------------* # | | # | Copyright (c) 2022 by Paul Scherrer Institute (http://www.psi.ch) | # | Based on Zac great first implementation | # | Author Thierry Zamofing (thierry.zamofing@psi.ch) | # *-----------------------------------------------------------------------* # simulated motor tweaks ui: # same interface as MotorTweak and SmaractMotorTweaks, but mimes just a simulated motor import logging from time import sleep from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtGui import QPainter, QBrush, QColor, QPainterPath, QPen, QDoubleValidator from PyQt5.QtWidgets import QMenu, QInputDialog, QAction from PyQt5.uic import loadUiType from epics.ca import pend_event Ui_MotorTweak, QWidget = loadUiType('epics_widgets/MotorTweak.ui') _log = logging.getLogger(__name__) #logger.setLevel(logging.INFO) class SimMotor: def __init__(self,rec_name, short_name): self._llm = -10 self._hlm = 10 self._prec = 5 self._twv = 0.1 self._units = 'mm' self._pos = 3.1415 self._short_name=short_name self._rec_name=rec_name class SimMotorTweak(QWidget, Ui_MotorTweak): event_val = pyqtSignal(str, dict) event_rbv = pyqtSignal(str, dict) event_soft_limit = pyqtSignal(str, dict) event_high_hard_limit = pyqtSignal(str, dict) event_low_hard_limit = pyqtSignal(str, dict) event_axis_fault = pyqtSignal(str, dict) def __init__(self, parent=None): super(SimMotorTweak, self).__init__(parent=parent) self.setupUi(self) self._wheel_tweaks = True self._auto_labels = True self._locked = False self._label_style = 'basic' self._templates_source = { 'basic': '{short_name} {{rbv:.{precision}f}} {units}', 'small': '{short_name} {{rbv:.{precision}f}} {units}', '2 lines': '{short_name}
{{rbv:.{precision}f}} {units}', 'busy': '{short_name} {{rbv:.{precision}f}} {units}' } self._templates = {} def connect_motor(self, rec_name, short_name=None, *args, **kwargs): self.label.setToolTip('{} => {}'.format(rec_name, short_name)) self._motor=m=SimMotor(rec_name, short_name) self.set_motor_validator() self._drive_val.setText(str(m._pos)) self._drive_val.returnPressed.connect(self.move_abs) self.jog_forward.hide() self.jog_reverse.hide() tweak_min = kwargs.get("tweak_min", 0.0001) tweak_max = kwargs.get("tweak_max", 20.0) self._tweak_val.setText(str(m._twv)) self._tweak_val.setValidator(QDoubleValidator(tweak_min, tweak_max, 4, self._tweak_val)) #self._tweak_val.editingFinished.connect(lambda m=self._pv_tweak_val: m.put(self._tweak_val.text())) self.tweak_forward.clicked.connect(lambda m: self.move_rel(float(self._tweak_val.text()))) self.tweak_reverse.clicked.connect(lambda m: self.move_rel(-float(self._tweak_val.text()))) #self.tweak_forward.clicked.connect(lambda m: self._pv_tweak_f.put(1)) #self.tweak_reverse.clicked.connect(lambda m: self._pv_tweak_r.put(1)) self.bind_wheel() self.update_label_template() #self._pv_readback.add_callback(self.update_label) #self._pv_status.add_callback(self.set_val_on_status_change) # self._pv_drive.add_callback(self.set_val) #self._pv_drive.add_callback(self.emit_signals, source_field='VAL') #self._pv_readback.add_callback(self.emit_signals, source_field='RBV') #self._pv_hlm.add_callback(self.emit_signals, source_field='HLS') #self._pv_llm.add_callback(self.emit_signals, source_field='LLS') # m.set_callback('HLM', self.set_motor_validator) def set_motor_validator(self, **kwargs): m=self._motor lineedit = self._drive_val min, max = m._llm, m._hlm if min == max: min = -1e6 max = 1e6 lineedit.setValidator(QDoubleValidator(min, max, 5, lineedit)) def move_rel(self, dist, delay=None): m=self._motor m._pos += dist if delay: sleep(delay) _log.debug('{} rel move => {}'.format(m._short_name, dist)) self.update_label() self.emit_signals(source_field='VAL') def get_rbv(self): return self._motor._pos def get_val(self): return self._motor._pos def is_homed(self): return True def get_status(self, as_string=False): """ States: 0 Stopped 1 Stepping 2 Scanning 3 Holding 4 Targeting 5 Move Delay 6 Calibrating 7 Finding Ref 8 Locked :return: """ return 0 def wait(self): pend_event(.01) while not self.is_done(): pend_event(0.2) def is_done(self): return True # in (0, 3) def move_abs(self, drive=None, wait=False, assert_position=False): m=self._motor if assert_position: wait=True if drive is None: _log.debug('{} abs target from widget'.format(m._short_name)) drive = float(self._drive_val.text()) _log.debug('{} abs move => {}'.format(m._short_name, drive)) m._pos=drive self.update_label() self.emit_signals(source_field='VAL') def emit_signals(self, **kw): m=self._motor field = kw['source_field'] if field == 'VAL': self.event_val.emit(m._rec_name, kw) elif field == 'RBV': self.event_rbv.emit(m._rec_name, kw) elif field == 'LVIO': self.event_soft_limit.emit(m._rec_name, kw) elif field == 'HLS': self.event_high_hard_limit.emit(m._rec_name, kw) self.event_axis_fault.emit(m._rec_name, kw) elif field == 'LVIO': self.event_low_hard_limit.emit(m._rec_name, kw) self.event_axis_fault.emit(m._rec_name, kw) elif field == 'STAT': self.event_axis_fault.emit(m._rec_name, kw) def set_val_on_status_change(self, **kw): ''' 0 Stopped 1 Stepping 2 Scanning 3 Holding 4 Targeting 5 Move Delay 6 Calibrating 7 Finding Ref 8 Locked :param kw: :return: ''' status = kw['char_value'] if status in ('Holding', 'Stopped'): v = self._pv_readback.get(as_string=True) _log.debug('updating VAL on status change to holding/stopped = {}'.format(v)) self._drive_val.setText(v) def set_val(self, **kw): v = kw['char_value'] _log.debug('updating VAL = {}'.format(v)) self._drive_val.setText(v) def update_label(self, **kwargs): m=self._motor self.label.setText(self._templates[self._label_style].format(rbv=m._pos)) self._drive_val.setText(f'{m._pos:.3f}') twv=float(self._tweak_val.text()) self.tweak_forward.setToolTip('tweak forward by {:.3f} {}'.format(twv, m._units)) self.tweak_reverse.setToolTip('tweak reverse by {:.3f} {}'.format(twv, m._units)) def tweak_event(self, event): sign = event.angleDelta().y() if sign < 0: self._pv_tweak_r.put(1) else: self._pv_tweak_f.put(1) def bind_wheel(self): self.tweak_forward.wheelEvent = self.tweak_event self.tweak_reverse.wheelEvent = self.tweak_event def contextMenuEvent(self, event): name = self._pv_name.get() unit = self._units prec = self._prec menu = QMenu(self) menu.setTitle(self.short_name) lockmotor = QAction('lock motor', menu, checkable=True) lockmotor.setChecked(self._locked) menu.addAction(lockmotor) autolabelsAction = QAction('auto', menu, checkable=True) autolabelsAction.setChecked(self._auto_labels) menu.addAction(autolabelsAction) wheel_tweaks = QAction('Mouse wheel tweaks motor', menu, checkable=True) wheel_tweaks.setChecked(self._wheel_tweaks) menu.addAction(wheel_tweaks) # stopmotorAction = QAction('Stopped', menu, checkable=True) # stopmotorAction.setChecked(SPMG_STOP == m.stop_go) # menu.addAction(stopmotorAction) changeprecAction = menu.addAction("Change Precision") changetweakstepAction = menu.addAction("Tweak step {:.3f} {}".format(self._pv_tweak_val.get(), unit)) tozeroAction = menu.addAction("move to Zero") action = menu.exec_(self.mapToGlobal(event.pos())) if action == lockmotor: self._locked = not self._locked if self._locked: self._controlbox.setDisabled(True) else: self._controlbox.setDisabled(False) elif action == changeprecAction: msg = 'Precision for motor {}'.format(name) _log.debug('prec before %d', prec) prec, ok = QInputDialog.getInt(self, msg, msg, prec, 0, 10) _log.debug('prec after (%d) %d', ok, prec) if ok: self._prec = prec elif action == changetweakstepAction: tv = self._pv_tweak_val.get() msg = 'Tweak step for motor {} at {} {}'.format(name, tv, unit) tv, ok = QInputDialog.getDouble(self, msg, msg, tv, pow(10, -prec), 10.0, prec) if ok: self._pv_tweak_val.put(tv) elif action == tozeroAction: self._pv_drive.put(0.0) elif action == autolabelsAction: self._auto_labels = not self._auto_labels elif action == wheel_tweaks: self._wheel_tweaks = not self._wheel_tweaks self.bind_wheel() self.update_label_template() def resizeEvent(self, event): pass def update_label_template(self): m=self._motor source = self._templates_source target = self._templates for k in source: target[k] = source[k].format( short_name=m._short_name, precision=m._prec, units=m._units) self.label.setText(target[self._label_style].format(rbv=m._pos)) def paintEvent(self, e): qp = QPainter() qp.begin(self) qp.setRenderHint(QPainter.Antialiasing) self._draw_limits(qp) qp.end() def _draw_limits(self, qp): pass # width, height = self.size().width(), self.size().height() # pad = 5 # rounding = 2 # size = 10 # if m.HLS: # x, y, w, h = width - size, pad, size, height - 2 * pad # elif m.LLS: # x, y, w, h = 0, pad, size, height - 2 * pad # else: # return # color = QColor('indianred') # qp.setBrush(QBrush(color, Qt.SolidPattern)) # qp.setPen(QPen(color)) # path = QPainterPath() # path.setFillRule(Qt.WindingFill) # path.addRoundedRect(x, y, w, h, rounding, rounding) # qp.drawPath(path)