diff --git a/epics_widgets/MotorTweak.py b/epics_widgets/MotorTweak.py
index c5407b1..fe239d8 100644
--- a/epics_widgets/MotorTweak.py
+++ b/epics_widgets/MotorTweak.py
@@ -18,7 +18,6 @@ SPMG_PAUSE = 1
SPMG_MOVE = 2
SPMG_GO = 3
-
class MotorTweak(QWidget, Ui_MotorTweak):
event_val = pyqtSignal(str, dict)
event_readback = pyqtSignal(str, float, dict)
@@ -335,11 +334,7 @@ class MotorTweak(QWidget, Ui_MotorTweak):
qp.end()
def _draw_limits(self, qp):
- try:
- m = self.motor
- except AttributeError:
- _log.warning('Motor not connected')
- return
+ m = self.motor
width, height = self.size().width(), self.size().height()
pad = 5
rounding = 2
diff --git a/epics_widgets/SimMotorTweak.py b/epics_widgets/SimMotorTweak.py
new file mode 100644
index 0000000..112f238
--- /dev/null
+++ b/epics_widgets/SimMotorTweak.py
@@ -0,0 +1,309 @@
+#!/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')
+logger = logging.getLogger(__name__)
+logger.setLevel(logging.INFO)
+
+class SimMotor:
+ def __init__(self,motor_base, short_name):
+ self._llm = -10
+ self._hlm = 10
+ self._prec = 5
+ self._units = 'mm'
+ self._pos = 3.1415
+ self._short_name=short_name
+
+class SimMotorTweak(QWidget, Ui_MotorTweak):
+ event_val = pyqtSignal(str, dict)
+ event_readback = 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, motor_base, short_name=None, *args, **kwargs):
+ self.label.setToolTip('{} => {}'.format(motor_base, motor_base))
+ self.motor=SimMotor(motor_base, short_name)
+
+ twv=0.1
+
+ self.set_motor_validator()
+ self._drive_val.setText(str(self.motor._pos))
+ self._drive_val.returnPressed.connect(self.move_motor_to_position)
+
+ 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(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._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):
+ lineedit = self._drive_val
+ min, max = self.motor._llm, self.motor._hlm
+ if min == max:
+ min = -1e6
+ max = 1e6
+ lineedit.setValidator(QDoubleValidator(min, max, 5, lineedit))
+
+
+ def move_relative(self, dist, delay=None):
+ target = self.motor._pos + dist
+ if delay:
+ sleep(delay)
+
+ def get_position(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_motor_to_position(self, drive=None, wait=False, assert_position=False):
+
+ if assert_position:
+ wait=True
+ if drive is None:
+ logger.debug('{} abs target from widget'.format(self.short_name))
+ drive = float(self._drive_val.text())
+ logger.debug('{} abs move => {}'.format(self.short_name, drive))
+ self._pos=drive
+
+ def emit_signals(self, **kw):
+ field = kw['source_field']
+ if field == 'VAL':
+ self.event_val.emit(self._pvname, kw)
+ elif field == 'RBV':
+ self.event_readback.emit(self._pvname, kw)
+ elif field == 'LVIO':
+ self.event_soft_limit.emit(self._pvname, kw)
+ elif field == 'HLS':
+ self.event_high_hard_limit.emit(self._pvname, kw)
+ self.event_axis_fault.emit(self._pvname, kw)
+ elif field == 'LVIO':
+ self.event_low_hard_limit.emit(self._pvname, kw)
+ self.event_axis_fault.emit(self._pvname, kw)
+ elif field == 'STAT':
+ self.event_axis_fault.emit(self._pvname, 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)
+ logger.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']
+ logger.debug('updating VAL = {}'.format(v))
+ self._drive_val.setText(v)
+
+ def update_label(self, **kwargs):
+ self.label.setText(self._templates[self._label_style].format(rbv=self._pv_readback.get()))
+ self.tweak_forward.setToolTip('tweak forward by {:.3f} {}'.format(self._pv_tweak_val.get(), self._units))
+ self.tweak_reverse.setToolTip('tweak reverse by {:.3f} {}'.format(self._pv_tweak_val.get(), self._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)
+ logger.debug('prec before %d', prec)
+ prec, ok = QInputDialog.getInt(self, msg, msg, prec, 0, 10)
+ logger.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):
+ source = self._templates_source
+ target = self._templates
+
+ for k in source:
+ target[k] = source[k].format(
+ short_name=self.motor._short_name,
+ precision=self.motor._prec,
+ units=self.motor._units)
+ self.label.setText(target[self._label_style].format(rbv=self.motor._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)
diff --git a/swissmx.py b/swissmx.py
index dba64fe..aab5467 100755
--- a/swissmx.py
+++ b/swissmx.py
@@ -77,7 +77,7 @@ TASK_HELICAL = "helical"
TASK_EMBL = "embl"
-#import PrelocatedCoordinatesModel #ZAC: orig. code
+import PrelocatedCoordinatesModel
#from EmblModule import EmblWidget #ZAC: orig. code
#from HelicalTable import HelicalTableWidget #ZAC: orig. code
#from Wigis import Spinner, Checkbox #ZAC: orig. code
@@ -105,12 +105,15 @@ from CustomROI import BeamMark
#from epics_widgets import zoom #ZAC: orig. code
from epics_widgets.MotorTweak import MotorTweak
from epics_widgets.SmaractMotorTweak import SmaractMotorTweak
+from epics_widgets.SimMotorTweak import SimMotorTweak
+
from matplotlib import pyplot
import numpy as np
import pyqtgraph as pg
import pyqtgraph.exporters
-pg.setConfigOption("antialias", True)
+# use antialias for draw lines, interpret image data as row-major instead of col-major
+pg.setConfigOptions(antialias=True,imageAxisOrder='row-major')
import app_utils
from app_config import settings, appsconf, option, toggle_option, simulated
@@ -222,6 +225,8 @@ class Main(QMainWindow, Ui_MainWindow):
super(Main, self).__init__()
self.setupUi(self)
+ app=QApplication.instance()
+
if os.getenv("DEVELOPMENT_VERSION"):
title = "[DEVELOPMENT] SwissMX - Solid support scanning for FEL sources"
else:
@@ -251,13 +256,13 @@ class Main(QMainWindow, Ui_MainWindow):
self.microscope_page.layout().addWidget(self.glw)
self.glw.show()
- self.vb = self.glw.addViewBox(enableMenu=False)
+ self.vb = self.glw.addViewBox(invertY=True)#,enableMenu=False)
self.img = pg.ImageItem()
self.img._swissmx_name = "microscope_image_item"
# self.graphicsView.setCentralItem(self.vb)
self.glw.scene().sigMouseMoved.connect(self.mouse_move_event)
self.glw.scene().sigMouseClicked.connect(self.mouse_click_event)
- self.vb.setAspectLocked()
+ self.vb.setAspectLocked(True)
self.vb.setBackgroundColor((120, 90, 90))
self.vb.addItem(self.img)
@@ -330,9 +335,9 @@ class Main(QMainWindow, Ui_MainWindow):
#self.create_helical_widgets() #ZAC: orig. code
self.center_piece_update(0) # start camera updater
- #curzoom = qoptic_zoom.get_position(cached=False) #ZAC: orig. code
- #_log.debug(f"starting app with zoom at {curzoom}")
- #self.zoom_changed_cb(curzoom)
+ curzoom = app._zoom.get()
+ _log.debug(f"starting app with zoom at {curzoom}")
+ self.zoom_changed_cb(curzoom)
self._tabs_daq_methods.currentChanged.connect(self.switch_task)
self.switch_task()
@@ -806,7 +811,7 @@ class Main(QMainWindow, Ui_MainWindow):
exp_tab.layout().addStretch()
# DAQ Methods Tabs
- #self.build_daq_methods_prelocated_tab() #ZAC: orig. code
+ self.build_daq_methods_prelocated_tab() #ZAC: orig. code
self.build_daq_methods_grid_tab()
self.build_sample_selection_tab()
@@ -1037,17 +1042,20 @@ class Main(QMainWindow, Ui_MainWindow):
self._lb_coords.setText("")
def mouse_move_event(self, pos):
+ app = QApplication.instance()
self._mouse_pos = pos
task = self.active_task()
xy = self.img.mapFromScene(pos)
- z = qoptic_zoom.get_position()
+ z = app._zoom.get()
+ _log.debug('mouse_pos:{} scene_pos:{} zoom:{}'.format(pos,xy,z))
+ #TODO: implement mouse handling
# if self._ppm_toolbox._force_ppm > 0:
# ppm = self._ppm_toolbox._force_ppm
# else:
try:
ppm = self.ppm_fitter(z)
except:
- ppm = 0 # do not move if we don't know ppm
+ ppm = 1
x, y = xy.x(), xy.y()
try:
bx, by = self.get_beam_mark_on_camera_xy()
@@ -1055,12 +1063,13 @@ class Main(QMainWindow, Ui_MainWindow):
bx, by = 500, 500
dx = (x - bx) / ppm
dy = -(y - by) / ppm
-
- fx = self.tweakers["fast_x"].motor.get_position()
- fy = self.tweakers["fast_y"].motor.get_position()
-
- fx += dx
- fy += dy
+ try:
+ fx = self.tweakers["fast_x"].motor.get_position()
+ fy = self.tweakers["fast_y"].motor.get_position()
+ fx += dx
+ fy += dy
+ except:
+ fx=fy=0
self._lb_coords.setText(
"\u23FA{:>}:{:>6.0f} {:<.0f}"
@@ -1073,10 +1082,11 @@ class Main(QMainWindow, Ui_MainWindow):
"PPM", ppm,
"Distance to beam", 1000 * dx, 1000 * dy,
"Stage", fx, fy,
- )
+ )
)
def mouse_click_event(self, event):
+ app=QApplication.instance()
fx_motor, fy_motor, bx_motor, bz_motor, omega_motor = self.get_gonio_tweakers()
_log.debug("{}".format(event))
_log.debug(" pos {}".format(event.pos()))
@@ -1090,13 +1100,13 @@ class Main(QMainWindow, Ui_MainWindow):
event.ignore()
return
pos = event.scenePos()
- zoom_level = qoptic_zoom.get_position()
+ zoom_level = app._zoom.get()
_log.debug(" zoom {}".format(zoom_level))
try:
ppm = self.ppm_fitter(zoom_level)
except:
- ppm = 0 # do not move if we don't know ppm
+ ppm = 1 # 0: do not move if we don't know ppm
dblclick = event.double()
shft = Qt.ShiftModifier & event.modifiers()
ctrl = Qt.ControlModifier & event.modifiers()
@@ -1110,11 +1120,11 @@ class Main(QMainWindow, Ui_MainWindow):
pixel_dist = np.linalg.norm(a - b)
_log.debug("distance to beam: {:.1f} pixels".format(pixel_dist))
- omega = omega_motor.motor.get_position()
+ omega = omega_motor.get_position()
_log.debug("omega at {:.03f} degrees".format(omega))
- fx = fx_motor.motor.get_position()
- fy = fy_motor.motor.get_position()
+ fx = fx_motor.get_position()
+ fy = fy_motor.get_position()
dx = (x - bx) / ppm
dy = -(y - by) / ppm
fx += dx
@@ -1210,7 +1220,8 @@ class Main(QMainWindow, Ui_MainWindow):
pass
def get_beam_mark_on_camera_xy(self):
- z = qoptic_zoom.get_position()
+ app=QApplication.instance()
+ z = app._zoom.get()
try:
bx = self.beamx_fitter(z)
by = self.beamy_fitter(z)
@@ -1250,6 +1261,7 @@ class Main(QMainWindow, Ui_MainWindow):
self._escape_current_state = "ManualSampleExchange"
def escape_goToSampleAlignment(self):
+ app=QApplication.instance()
self._escape_current_state = "busy"
steps = [
@@ -1258,7 +1270,7 @@ class Main(QMainWindow, Ui_MainWindow):
]
if option(CRYOJET_MOTION_ENABLED):
steps.extend([lambda: self.move_cryojet_nozzle("in")])
- steps.extend([lambda: self.move_post_tube("out"), lambda: backlight.move("in")])
+ steps.extend([lambda: self.move_post_tube("out"), lambda: app._backlight.move("in")])
self.escape_run_steps(steps, "Transitioning to Sample Alignment")
self._escape_current_state = "SampleAlignment"
@@ -1435,6 +1447,9 @@ class Main(QMainWindow, Ui_MainWindow):
dx = settings.value("collimator/dx", 1e10, type=float)
dy = settings.value("collimator/dy", 1e10, type=float)
+ _log.info('Skip Zac''s orig. code')
+ return
+ # ZAC: orig. code
if 1e9 < x_pos + y_pos + dx + dy:
raise IncompleteConfiguration("COLLIMATOR configuration is incomplete!")
@@ -1704,6 +1719,9 @@ class Main(QMainWindow, Ui_MainWindow):
)
def move_post_tube(self, dir):
+ _log.info('Skip Zac''s orig. code')
+ return
+ # ZAC: orig. code
try:
tandem_twv = float(self._post_tandem_tweak_val.text())
except:
@@ -1809,11 +1827,19 @@ class Main(QMainWindow, Ui_MainWindow):
dsy.move_relative(-tandem_twv)
def get_tweaker(self, pv, alias=None, label=None, mtype="epics_motor", layout=None, **kwargs):
+ app = QApplication.instance()
+ sim=app._args.sim
if mtype == "epics_motor":
- m = MotorTweak()
+ if sim&0x10:
+ m=SimMotorTweak()
+ else:
+ m = MotorTweak()
else:
- m = SmaractMotorTweak()
- #m.connect_motor(pv, label, **kwargs) #ZAC: orig. code
+ if sim&0x20:
+ m=SimMotorTweak()
+ else:
+ m = SmaractMotorTweak()
+ m.connect_motor(pv, label, **kwargs)
self.tweakers[alias] = m
return m
@@ -1825,7 +1851,7 @@ class Main(QMainWindow, Ui_MainWindow):
else:
m = SmaractMotorTweak()
layout.addWidget(m)
- #m.connect_motor(pv, label)#ZAC: orig. code
+ m.connect_motor(pv, label)
self.tweakers[alias] = m
def done_sliding(self):
@@ -3217,6 +3243,7 @@ def main():
# set app icon
app = QApplication(sys.argv)
+ app._args=args
if args.sim&0x01:
app._backlight = backlight.Backlight(None)
@@ -3232,7 +3259,7 @@ def main():
app._zoom = zoom.QopticZoom()
if args.sim&0x08:
app._camera = camera.epics_cam(None)
- app._camera.sim_gen(mode=0)
+ app._camera.sim_gen(mode=1)
app._camera.run()
else:
app._camera = camera.epics_cam()