1238 lines
35 KiB
Python
Executable File
1238 lines
35 KiB
Python
Executable File
#!/usr/bin/env python
|
||
# *-----------------------------------------------------------------------*
|
||
# | |
|
||
# | Copyright (c) 2024 by Paul Scherrer Institute (http://www.psi.ch) |
|
||
# | |
|
||
# | Author Thierry Zamofing (thierry.zamofing@psi.ch) |
|
||
# *-----------------------------------------------------------------------*
|
||
|
||
"""
|
||
SwissFEL Athos spectrometer
|
||
EPICS simulation motor PVs:
|
||
|
||
~/Documents/prj/SwissFEL/test_ioc/MotorSim/iocBoot/SATES30-RIXS/SATES30-RIXS.cmd
|
||
caQtDM -attach ~/Documents/prj/SwissFEL/test_ioc/MotorSim/iocBoot/SATES30-RIXS/SATES30-RIXS.ui
|
||
|
||
bitmask for modes:
|
||
0x01:
|
||
0x02:
|
||
0x04:
|
||
0x08:
|
||
0x10:
|
||
0x20:
|
||
0x40:
|
||
0x80:
|
||
"""
|
||
|
||
|
||
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QSlider, QLineEdit,\
|
||
QCheckBox, QHBoxLayout, QVBoxLayout, QGroupBox, QGridLayout, QComboBox, QMessageBox
|
||
from PyQt5.QtGui import QPainter, QColor, QPen, QBrush, QPolygon, QTransform
|
||
from PyQt5.QtCore import QPoint, QPointF, Qt
|
||
from numpy.matlib import empty
|
||
|
||
from pyqtgraph.Qt import QtCore, QtGui
|
||
import PyQt5.QtGui as QtGui
|
||
import PyQt5.QtCore as QtCore
|
||
import PyQt5.QtWidgets as QtW
|
||
from PyQt5.uic import loadUiType
|
||
import numpy as np
|
||
from RIXSgeometry import VLSgrating
|
||
from RIXScfg import Config
|
||
from RIXSgirder import WndGirderRIXS
|
||
|
||
import sys, logging, copy, re
|
||
import epics
|
||
|
||
if sys.version_info[0] < 3 or sys.version_info[1] < 6:
|
||
print(f"Must be using Python 3.6 or newer. Try e.g. /opt/gfa/python-3.8/latest/bin/python /sf/furka/bin/RIXSconfig")
|
||
|
||
_log=logging.getLogger(__name__)
|
||
|
||
def obj_info(obj,p=''):
|
||
print(f"{p}obj_info:{obj}")
|
||
try:
|
||
pos=obj.pos()
|
||
print(f"{p}pos:({pos.x():.6g},{pos.y():.6g})") # in coordinate value on the scene (no change by zooming)
|
||
except AttributeError:
|
||
pass
|
||
try:
|
||
sz=obj.size()
|
||
print(f"{p}size:({sz.x():.6g},{sz.y():.6g})") # in coordinate value on the scene (no change by zooming)
|
||
except AttributeError:
|
||
pass
|
||
try:
|
||
for k, v in (('Viewport', obj.viewport()), ('Window', obj.window())):
|
||
print(
|
||
f"{p}{k} (x,y)(w,h):({v.x():.6g},{v.y():.6g})({v.width():.6g},{v.height():.6g})") # in coordinate value on the scene (no change by zooming)
|
||
except AttributeError:
|
||
pass
|
||
try:
|
||
scnPos=obj.scenePos()
|
||
print(f"{p}scenePos:({scnPos.x():.6g},{scnPos.y():.6g})") # in pixel on the scene (changes by zooming)
|
||
except AttributeError:
|
||
pass
|
||
try:
|
||
if type(obj)==QTransform:
|
||
t=obj
|
||
else:
|
||
t=obj.transform()
|
||
print(f"{p}QTransform:{t.m11():8.5g} {t.m12():8.5g} {t.m13():8.5g}")
|
||
print(f"{p} {t.m21():8.5g} {t.m22():8.5g} {t.m23():8.5g}")
|
||
print(f"{p} {t.m31():8.5g} {t.m32():8.5g} {t.m33():8.5g}")
|
||
except AttributeError:
|
||
pass
|
||
|
||
#Ui_MainWindow, QMainWindow = loadUiType("graphEx1.ui")
|
||
#class RIXSgrating(QMainWindow, Ui_MainWindow):
|
||
# def __init__(self, ):
|
||
# super(RIXSgrating, self).__init__()
|
||
# self.setupUi(self)
|
||
|
||
|
||
class WndMainRIXS(QWidget):
|
||
def __init__(self):
|
||
super().__init__()
|
||
|
||
self._param={
|
||
'ofs':(500, 500), # location of drawings
|
||
'difrBeamPaint':4, # mode to plot the difracted beam
|
||
}
|
||
self.initUI()
|
||
|
||
def initUI(self):
|
||
self.setGeometry(100, 100, 350, 350)
|
||
app=QApplication.instance()
|
||
dev=app._dev
|
||
self.setWindowTitle(f'{dev._name} main')
|
||
self._wDevs=w=QWidget(self)
|
||
w.move(10, 10)
|
||
lv=QVBoxLayout(w)
|
||
#lv.addWidget(w)
|
||
|
||
for pb in ('Visualize','Vars'):
|
||
w=QPushButton(pb)
|
||
#w.clicked.connect(self.OnOpenChildWnd
|
||
w.clicked.connect(lambda dummy,pb=pb: self.OnOpenChildWnd(pb))
|
||
lv.addWidget(w)
|
||
|
||
w=QGroupBox(dev._name, self,objectName=dev._name)
|
||
lv.addWidget(w)
|
||
lg=QGridLayout(w)
|
||
row=0
|
||
|
||
t='energy'
|
||
w=QLabel(t);
|
||
lg.addWidget(w, row, 0)
|
||
w=QLineEdit(t, objectName=t);
|
||
lg.addWidget(w, row, 1)
|
||
w.returnPressed.connect(lambda w=w:self.OnEnergyChanged(w))
|
||
w=QLabel('out of\nspecs',objectName='oos');w.setStyleSheet("background-color:yellow;color:red;font-weight: bold;font-size: small;")
|
||
#https://stackoverflow.com/questions/10794532/how-to-make-a-qt-widget-invisible-without-changing-the-position-of-the-other-qt
|
||
sp=w.sizePolicy();sp.setRetainSizeWhenHidden(True);w.setSizePolicy(sp)#avoid resizing if not visible
|
||
w.setVisible(False)
|
||
lg.addWidget(w, row, 2)
|
||
|
||
row+=1;
|
||
key, rng, tk, pos=('energy', (200, 2000), 50, 60)
|
||
w=QSlider(QtCore.Qt.Horizontal, objectName=key)
|
||
w.setFixedWidth(200);
|
||
w.setMinimum(rng[0]);
|
||
w.setMaximum(rng[1])
|
||
w.setTickPosition(QSlider.TicksBelow);
|
||
w.setTickInterval(tk)
|
||
#lv.addWidget(w)
|
||
lg.addWidget(w, row, 0, 1, 2)
|
||
w.valueChanged.connect(lambda val, w=w:self.OnEnergyChanged(w, val))
|
||
|
||
row+=1
|
||
t='probe'
|
||
w=QLabel(t);
|
||
lg.addWidget(w, row, 0)
|
||
w=QComboBox(objectName=t);
|
||
lg.addWidget(w, row, 1)
|
||
for i, tx in enumerate(tuple(dev._probes.keys())):
|
||
w.insertItem(i, tx)
|
||
w.currentIndexChanged.connect(lambda val, w=w:self.OnEnergyChanged(w,val))
|
||
w=QCheckBox('fix', objectName=t)
|
||
w.clicked.connect(lambda val, w=w:self.OnEnergyChanged(w, val))
|
||
lg.addWidget(w, row, 2)
|
||
row+=1
|
||
|
||
w=QPushButton('sync motors')
|
||
w.clicked.connect(self.btnSyncMotors)
|
||
lv.addWidget(w)
|
||
self.show()
|
||
|
||
def btnSyncMotors(self):
|
||
#reads the epics motor and updates the vizualization
|
||
_log.info('')
|
||
|
||
app=QApplication.instance()
|
||
dev=app._dev
|
||
geo=dev._geo
|
||
pv_rbv=dev._pvDict['rbv']
|
||
wGrp=self.findChild(QGroupBox,dev._name)
|
||
wLe=wGrp.findChild(QLineEdit, 'energy')
|
||
#wLe=self.findChild(QLineEdit, 'energy')
|
||
wLe.blockSignals(True)
|
||
wLe.setText('<unknown>')
|
||
wLe.blockSignals(False)
|
||
m=dict()
|
||
for k,v in pv_rbv.items():
|
||
if v.connected:
|
||
m[k]=v.get()
|
||
else:
|
||
_log.warning(f'pv {k} not connected')
|
||
break
|
||
dev.motor2geometry(m)
|
||
geo.update(m)
|
||
dev.setGeo2Paint(dev._geo)
|
||
try:
|
||
wndCld=app._wndVisualize
|
||
except AttributeError as e:
|
||
pass
|
||
else:
|
||
wndCld.updateDevice(dev)
|
||
try:
|
||
wndCld=app._wndVars
|
||
except AttributeError as e:
|
||
pass
|
||
else:
|
||
wndCld.updateDevice(dev)
|
||
|
||
def OnOpenChildWnd(self,wnd):
|
||
app=QApplication.instance()
|
||
if wnd=='Visualize':
|
||
try:
|
||
wnd=app._wndVisualize
|
||
except AttributeError as e:
|
||
app._wndVisualize=WndVisualize()
|
||
else:
|
||
wnd.show()
|
||
elif wnd=='Vars':
|
||
try:
|
||
wnd=app._wndVars
|
||
except AttributeError as e:
|
||
app._wndVars=WndVars()
|
||
else:
|
||
wnd.show()
|
||
else:
|
||
_log.error(f'unknown window{wnd}')
|
||
|
||
def OnEnergyChanged(self,w,*args):
|
||
#_log.debug(f'OnEnergyChanged: {w} {args}')
|
||
wGrp=w.parent()
|
||
app=QApplication.instance()
|
||
#wCb=self._wdGrpEnergy.findChild(QComboBox, 'Grating')
|
||
wLeEnergy=wGrp.findChild(QLineEdit, 'energy')
|
||
wSlEnergy=wGrp.findChild(QSlider, 'energy')
|
||
wFix=wGrp.findChild(QCheckBox, 'probe')
|
||
t=type(w)
|
||
if t==QLineEdit:
|
||
try:
|
||
e=float(w.text())
|
||
except ValueError as e:
|
||
return
|
||
wSlEnergy.blockSignals(True)
|
||
wSlEnergy.setValue(int(e))
|
||
wSlEnergy.blockSignals(False)
|
||
elif t==QComboBox:
|
||
_log.info(f'cb:{args}')
|
||
app=QApplication.instance()
|
||
try:
|
||
e=float(wLeEnergy.text())
|
||
except ValueError as e:
|
||
return
|
||
elif t==QSlider:
|
||
e=args[0]
|
||
wLeEnergy.setText(f'{e:.4g}')
|
||
elif t==QCheckBox:
|
||
_log.info(f'fix:{args}')
|
||
else:
|
||
raise(TypeError(f'wrong type: {t}'))
|
||
try: e # is there an energy to set?
|
||
except UnboundLocalError as er: pass
|
||
else:
|
||
self.setEnergy(e,wFix.isChecked(),wGrp)
|
||
|
||
|
||
def setEnergy(self,energy,fix,wGrp):
|
||
app=QApplication.instance()
|
||
dev=app._dev
|
||
vlsg=app._vlsg
|
||
|
||
wcbProbe=wGrp.findChild(QComboBox, 'probe')
|
||
wLbOOS=wGrp.findChild(QLabel, 'oos')
|
||
k=wcbProbe.currentText()
|
||
probe=dev._probes[k]
|
||
try:
|
||
eMin,eMax=Config._lutProbes[probe['probe']]['e']
|
||
except KeyError as e:
|
||
eMin=eMax=0
|
||
if fix:
|
||
wLbOOS.setVisible(energy<eMin or energy>eMax)
|
||
else:
|
||
if energy<eMin or energy>eMax:
|
||
probe=None
|
||
_log.info('try to find a better probe')
|
||
for idx,p in enumerate(dev._probes.values()):
|
||
try:
|
||
eMin, eMax=Config._lutProbes[p['probe']]['e']
|
||
except KeyError as e:
|
||
#_log.error(f'no energy range for probe: {i}')
|
||
continue
|
||
if energy>=eMin and energy<=eMax:
|
||
wcbProbe.blockSignals(True)
|
||
wcbProbe.setCurrentIndex(idx)
|
||
wcbProbe.blockSignals(False)
|
||
probe=p
|
||
break
|
||
if probe is None:
|
||
wLbOOS.setVisible(True)
|
||
_log.warning(f'found no probe for energy {energy}')
|
||
return
|
||
else:
|
||
wLbOOS.setVisible(False)
|
||
try:
|
||
vlsg.setup(probe['probe'])
|
||
except KeyError as e:
|
||
_log.error(f'can not find probe: {repr(e)}')
|
||
return
|
||
geo=vlsg.energy2geometry(energy,probe)
|
||
dev.setGeo2Paint(geo)
|
||
try:
|
||
dev.geometry2motor()
|
||
except ValueError as e:
|
||
_log.warning(f'{e}')
|
||
|
||
try:
|
||
wndCld=app._wndVisualize
|
||
except AttributeError as e:
|
||
pass
|
||
else:
|
||
wndCld.updateDevice(dev)
|
||
try:
|
||
wndCld=app._wndVars
|
||
except AttributeError as e:
|
||
pass
|
||
else:
|
||
wndCld.updateDevice(dev)
|
||
|
||
|
||
class RIXSdevice():
|
||
_lutDifrBeamPaint=( # (number of difr beam,draw mode,alpha,width)
|
||
(4, 2, 190, 0),
|
||
(4, 1, 200, 0),
|
||
(32, 1, 120, 0),
|
||
(8, 1, 196, 0),
|
||
(32, 0, 120, 3),
|
||
(32, 2, 255, 0),
|
||
)
|
||
|
||
_prefix='SATES30-RIXS:'
|
||
_motors=(
|
||
('mt', 'Mask translation'),
|
||
('gtz', 'Grating translation along Z-axis (along beam)'),
|
||
('gty1', 'Grating translation along Y-axis (height) Wedge leveller 1'),
|
||
('gty2', 'Grating translation along Y-axis (height) Wedge leveller 2'),
|
||
('grx', 'Grating rotation around X-axis'),
|
||
('gtx', 'Grating translation along X-axis'),
|
||
('dtz', 'Detector Translation along Z-axis'),
|
||
('dty1', 'Detector Translation along Y-axis'),
|
||
('dty2', 'Detector Translation along Y-axis'),
|
||
('drx', 'Detector Rotation around X-axis (ɣ) '),
|
||
)
|
||
|
||
def __init__(self,name,**kwargs):
|
||
self._name=name
|
||
self._paint=p={
|
||
'mode':4, #difraction beam paint mode
|
||
'ofs':(500, 500), # location of vlsg
|
||
'szG':(200, 5), # size VLS grating
|
||
'szD':(150, 5), # size detector
|
||
'sclTrf':(2**(6/2), 2**(-6/2)), # scaling transfformation [angle, distance]
|
||
}
|
||
p.update(kwargs)
|
||
self._geo=g={
|
||
'r1':2000, # distance probe grating
|
||
'r2':3500, # distance grating detector
|
||
'aa':88, # grating angle
|
||
'bb':87, # reflection angle
|
||
'cc':22, # detector angle
|
||
}
|
||
self.setGeo2Paint(g)
|
||
self.connectEPICS()
|
||
|
||
def connectEPICS(self):
|
||
_log.info('connect PVs')
|
||
self._pvDict=pvd={'rbv':dict()}
|
||
pv_rbv=pvd['rbv']
|
||
self._pvConnected=0
|
||
for m,v in self._motors:
|
||
pvn=self._prefix+'MOT_'+m.upper()+'.RBV'
|
||
pv=epics.get_pv(pvn,connection_callback=self.OnConnectionChange,callback=self.OnValueChange)
|
||
#pv=epics.get_pv(pvn)
|
||
pv_rbv[m]=pv
|
||
_log.info(f'{pv}')
|
||
print(len(pv_rbv))
|
||
#for pv in pv_rbv.values(): #need to do it is 2 steps.
|
||
# pv.connection_callbacks.append(self.OnConnectionChange)
|
||
# idx=pv.add_callback(self.OnValueChange)
|
||
# pv.run_callback(idx)
|
||
|
||
def OnConnectionChange(self, pvname=None, conn=None, **kws):
|
||
pvc=self._pvConnected
|
||
if conn:
|
||
pvc+=1
|
||
else:
|
||
if pvc>0: pvc-=1
|
||
pv_rbv=self._pvDict['rbv']
|
||
_log.info(f'PV connection {pvc}/{len(pv_rbv)}: {pvname} {conn}')
|
||
self._pvConnected=pvc
|
||
|
||
app=QApplication.instance()
|
||
#wGrp=app._wndVisualize._wdGrpDraw
|
||
#key=self._pv2key[pvname]
|
||
#wLb=wGrp.findChild(QLabel, key)
|
||
#if not conn:
|
||
# v=f"<font color='#a00000'>{key}</font>"
|
||
# wLb.setText(v)
|
||
#else:
|
||
# v=f"<font color='#00a000'>{key}</font>"
|
||
# wLb.setText(v)
|
||
|
||
def OnValueChange(self, pvname, value, **kw):
|
||
#_log.info(kw) #{
|
||
# 'pvname': 'SATES30-ARES:MOT_2TRY.RBV', 'value': 102.507, 'char_value': '102.5070', 'status': 0, 'ftype': 20,
|
||
# 'chid': 26100744, 'host': 'localhost:5064', 'count': 1, 'access': 'read-only', 'write_access': False, 'read_access': True,
|
||
# 'severity': 0, 'timestamp': 1724915455.46745, 'posixseconds': 1724915455.0, 'nanoseconds': 467450100, 'precision': 4,
|
||
# 'units': 'deg', 'enum_strs': None, 'upper_disp_limit': 0.0, 'lower_disp_limit': 0.0, 'upper_alarm_limit': nan,
|
||
#'lower_alarm_limit': nan, 'lower_warning_limit': nan, 'upper_warning_limit': nan, 'upper_ctrl_limit': 0.0,
|
||
#'lower_ctrl_limit': 0.0, 'nelm': 1, 'type': 'time_double', 'typefull': 'time_double',
|
||
#'cb_info': (1, <PV 'SATES30-ARES:MOT_2TRY.RBV', count=1, type=time_double, access=read-only>)
|
||
#}
|
||
_log.info(f"PV val:{pvname}:{value}")
|
||
app=QApplication.instance()
|
||
self.motor2geometry()
|
||
self.setGeo2Paint(self._geo)
|
||
try:
|
||
wndCld=app._wndVisualize
|
||
except AttributeError as e:
|
||
pass
|
||
else:
|
||
wndCld.updateDevice(self)
|
||
try:
|
||
wndCld=app._wndVars
|
||
except AttributeError as e:
|
||
pass
|
||
else:
|
||
#wnd.update_label(pvname, value, **kw) # would not update the geometry
|
||
wndCld.updateDevice(self)
|
||
|
||
|
||
#pv.add_callback('RBV', self.update_label)
|
||
#_log.warning(f"can't handle PV: {pvname}:{value}")
|
||
# return
|
||
#self.vis_update(key,value,'008000')
|
||
|
||
def setGeo2Paint(self,geo):
|
||
self._geo=geo
|
||
p=self._paint
|
||
sclA,sclD=p['sclTrf']
|
||
p.update({
|
||
'r1':geo['r1']*sclD,
|
||
'r2':geo['r2']*sclD,
|
||
'aa':(90-geo['aa'])*sclA,
|
||
'bb':(90-geo['bb'])*sclA,
|
||
'cc':geo['cc'],
|
||
})
|
||
|
||
def geometry2motor(self):
|
||
# returns raw motor positions
|
||
# offset detector plane to deflected beam: 34deg
|
||
geo=self._geo
|
||
r1,r2,aa,bb,cc=geo['r1'],geo['r2'],geo['aa'],geo['bb'],geo['cc']
|
||
mt=gtz=gty1=gty2=grx=gtx=dtz=dty1=dty2=drx=None
|
||
degArm=90-aa+90-bb
|
||
radArm=np.deg2rad(degArm)
|
||
gtz=r1
|
||
#grx=np.sin(np.deg2rad(90-aa))*200 # TODO: check angle to distance conversion
|
||
grx=aa-90
|
||
dtz=r1+np.cos(radArm)*r2
|
||
dty1=dty2=np.sin(radArm)*r2
|
||
#drx=np.sin(np.deg2rad(90-aa+90-bb+cc-34))*200 # TODO: check angle to distance conversion
|
||
drx=90-aa+90-bb+cc
|
||
dd=cc-34 # angle of bellow to detector
|
||
|
||
geo.update({
|
||
'mt':mt,
|
||
'gtz':gtz,
|
||
'gty1':gty1,
|
||
'gty2': gty2,
|
||
'grx':grx,
|
||
'gtx':gtx,
|
||
'dtz':dtz,
|
||
'dty1':dty1,
|
||
'dty2':dty2,
|
||
'drx':drx})
|
||
|
||
if degArm>10:
|
||
raise(ValueError('angle arm > 10°'))
|
||
elif degArm<1:
|
||
raise(ValueError('angle arm < 1°'))
|
||
elif abs(dd)>15:
|
||
raise(ValueError('angle bellow to detector > 15°'))
|
||
|
||
def motor2geometry(self,m=None):
|
||
# reads the motor position from the pv and updates the geometry
|
||
# IT DOES NOT touch the motor fields in _geometry !
|
||
if m is None: #dictionary is empty
|
||
pv_rbv=self._pvDict['rbv']
|
||
if len(pv_rbv)<10:
|
||
_log.info('not yet populated')
|
||
return
|
||
m=dict()
|
||
for k, v in pv_rbv.items():
|
||
if v.connected and v._args['value'] is not None:
|
||
# v._args['value'] is not None is needed as connected sometimes is not save enough
|
||
m[k]=v.get()
|
||
else:
|
||
_log.warning(f'pv {k} not connected or no value')
|
||
return
|
||
def mot2geo(mt,gtz,gty1,gty2,grx,gtx,dtz,dty1,dty2,drx):
|
||
geo=self._geo
|
||
#degArm=90-aa+90-bb
|
||
#radArm=np.deg2rad(degArm)
|
||
#gtz=r1
|
||
#grx=90-aa
|
||
#dtz=np.cos(radArm)*r2
|
||
#dty1=dty2=np.sin(radArm)*r2
|
||
#drx=90-aa+90-bb+cc-34
|
||
#dd=cc-34 # angle of bellow to detector
|
||
|
||
r1=gtz
|
||
x=(dtz-gtz)
|
||
y=(dty1+dty2)/2
|
||
r2=(x**2+y**2)**.5
|
||
radArm=np.arctan2(y,x)
|
||
degArm=np.rad2deg(radArm)
|
||
|
||
#aa=90-np.rad2deg(np.arcsin(grx/200)) # TODO: check angle to distance conversion
|
||
aa=90+grx
|
||
#degArm=90-aa+90-bb
|
||
bb=90-aa+90-degArm
|
||
#drx=90-aa+90-bb+cc-34
|
||
#cc=drx-90+aa-90+bb+34
|
||
#cc=np.rad2deg(np.arcsin(drx/200))-degArm+34 # TODO: check angle to distance conversion
|
||
cc=drx-degArm
|
||
|
||
geo.update({
|
||
'r1':r1,
|
||
'r2':r2,
|
||
'aa':aa,
|
||
'bb':bb,
|
||
'cc':cc,
|
||
})
|
||
mot2geo(**m)
|
||
|
||
|
||
|
||
|
||
|
||
def containsPoint(self,point):
|
||
try:
|
||
pg=self._polygon
|
||
except AttributeError:
|
||
return False
|
||
return pg.containsPoint(point,Qt.OddEvenFill)
|
||
|
||
@staticmethod
|
||
def plotOrig(qp):
|
||
penR=QPen(QtCore.Qt.red, 5, QtCore.Qt.SolidLine)
|
||
penG=QPen(QtCore.Qt.green, 5, QtCore.Qt.SolidLine)
|
||
pOrig=qp.pen()
|
||
qp.setPen(penR)
|
||
qp.drawLine(-20, 0, 20, 0)
|
||
qp.drawLine( 20, 0, 16, 2)
|
||
qp.setPen(penG)
|
||
qp.drawLine(0, -20, 0, 20)
|
||
qp.drawLine(0, 20, 2, 16)
|
||
qp.setPen(pOrig)
|
||
|
||
def paint(self,qp):
|
||
# qp QPainter to paint on
|
||
# ofs caanter to draw
|
||
# scl scaling for x and y translation of coordinate systems
|
||
# paintMode: mode how to paint the diffraction beam
|
||
p=self._paint
|
||
r1=p['r1']
|
||
r2=p['r2']
|
||
aa=p['aa']
|
||
bb=p['bb']
|
||
cc=p['cc']
|
||
szG=p['szG']
|
||
szD=p['szD']
|
||
ofs=p['ofs']
|
||
|
||
w=int(max(szG[0], szD[0])/2)
|
||
ofs=(max(ofs[0], w+r1), max(w+r2*np.sin((aa+bb)*np.pi/180), ofs[1]))
|
||
|
||
# --- prepare transformations ---
|
||
# tf0: grating center not rotated
|
||
# tfa: grating center rotated angle -aa
|
||
# tfab: grating center rotated angle -aa-bb
|
||
# tfc: detector center rotated angle -cc
|
||
|
||
tf0=QTransform()
|
||
tf0.translate(ofs[0], ofs[1])
|
||
tfa=copy.copy(tf0) # center
|
||
tfa.rotate(-aa)
|
||
tfab=copy.copy(tf0) # center
|
||
tfab.rotate(-aa-bb)
|
||
tfc=copy.copy(tfab)
|
||
tfc.translate(r2,0).rotate(-cc)
|
||
|
||
penBk=QPen(QtCore.Qt.black, 0, QtCore.Qt.SolidLine)
|
||
penWt=QPen(QtCore.Qt.white, 1, QtCore.Qt.SolidLine)
|
||
penYl=QPen(QtCore.Qt.yellow, 1, QtCore.Qt.SolidLine)
|
||
penRd=QPen(QtCore.Qt.red, 1, QtCore.Qt.SolidLine)
|
||
|
||
# --- visualize ---
|
||
#qp.setRenderHints(QPainter.HighQualityAntialiasing)
|
||
|
||
pl=[
|
||
QPoint(*tf0.map (-r1-w, -w)),
|
||
QPoint(*tf0.map (-r1-w, +w)),
|
||
QPoint(*tf0.map ( 0, +w)),
|
||
QPoint(*tfab.map( 0, +w)),
|
||
QPoint(*tfab.map( r2+w, +w)),
|
||
QPoint(*tfab.map( r2+w, -w)),
|
||
QPoint(*tf0.map ( -w,-w)),
|
||
]
|
||
self._polygon=QPolygon(pl)
|
||
|
||
qp.setBrush(QColor(0, 0, 0))
|
||
# plot black background
|
||
qp.setTransform(tf0)
|
||
qp.drawRect(-r1-w, -w, r1+w, 2*w)
|
||
qp.drawEllipse(-w, -w, 2*w, 2*w)
|
||
qp.setTransform(tfab)
|
||
qp.drawRect(0, -w, r2+w, 2*w)
|
||
|
||
# plot beam path
|
||
qp.setTransform(tf0)
|
||
qp.setCompositionMode(QtGui.QPainter.CompositionMode_Lighten)
|
||
qp.setPen(penWt)
|
||
qp.setBrush(QColor(255, 80, 0, 128))
|
||
|
||
qp.drawEllipse(-r1-20, -10, 20, 20) # target
|
||
qp.drawLine(-r1, 0, 0, 0) # central beam1
|
||
qp.rotate(-aa)
|
||
tf1=qp.transform()
|
||
qp.drawRect(int(-szG[0]/2), 0, szG[0], szG[1]) # grating
|
||
qp.rotate(-bb)
|
||
tf2=qp.transform()
|
||
qp.drawLine(0, 0, int(r2+szD[0]/2), 0) # central beam2
|
||
qp.translate(r2, 0)
|
||
qp.rotate(-cc)
|
||
tf3=qp.transform()
|
||
qp.drawRect(int(-szD[0]/2), 0, szD[0], szD[1]) # detector
|
||
|
||
# beam target to vlsg
|
||
qp.setTransform(QtGui.QTransform())
|
||
p0=QPointF(*tf0.map(-r1-10, 10))
|
||
p1=QPointF(*tf1.map(-szG[0]/2, 0))
|
||
qp.drawLine(p0, p1)
|
||
p0=QPointF(*tf0.map(-r1-10, -10))
|
||
p1=QPointF(*tf1.map(szG[0]/2, 0))
|
||
qp.drawLine(p0, p1)
|
||
|
||
# beam vlsg to detector
|
||
# n=32;mode=0;alpha=120;width=3
|
||
n, mode, alpha, width=self._lutDifrBeamPaint[p['mode']]
|
||
scl=256/n
|
||
p0=QPointF(*tf1.map(-szG[0]/2, 0))
|
||
p1=QPointF(*tf1.map(szG[0]/2, 0))
|
||
|
||
if mode==0:
|
||
pen=QtGui.QPen(QtCore.Qt.black, width, QtCore.Qt.SolidLine)
|
||
col=QtGui.QColor()
|
||
for i in range(n):
|
||
p2=QPointF(*tf3.map(szD[0]/2-szD[0]*i/(n-1), 0))
|
||
col.setHsv(int(i*scl), 255, 255, alpha)
|
||
pen.setColor(col)
|
||
qp.setPen(pen)
|
||
qp.drawLine(p0, p2)
|
||
qp.drawLine(p1, p2)
|
||
if mode==1:
|
||
col=QtGui.QColor()
|
||
qp.setPen(penBk)
|
||
for i in range(n):
|
||
p2=QPointF(*tf3.map(szD[0]/2-szD[0]*i/(n-1), 0))
|
||
col.setHsv(int(i*scl), 255, 255, alpha)
|
||
qp.setBrush(col)
|
||
qp.drawPolygon(p0, p1, p2)
|
||
if mode==2:
|
||
col=QtGui.QColor()
|
||
qp.setPen(penBk)
|
||
for i in range(n):
|
||
p2=QPointF(*tf3.map(szD[0]/2-szD[0]*(i+1)/n, 0))
|
||
p3=QPointF(*tf3.map(szD[0]/2-szD[0]*i/n, 0))
|
||
col.setHsv(int(i*scl), 255, 255, alpha)
|
||
qp.setBrush(col)
|
||
qp.drawPolygon(p0, p1, p2, p3)
|
||
|
||
qp.setCompositionMode(QtGui.QPainter.CompositionMode_SourceOver)
|
||
|
||
# draw central beam (if needed)
|
||
if mode!=0:
|
||
qp.setTransform(tf0)
|
||
qp.setPen(QtGui.QPen(QtGui.QColor(0x757780), 2, QtCore.Qt.SolidLine))
|
||
qp.drawLine(-r1, 0, 0, 0) # central beam1
|
||
qp.setTransform(tf2)
|
||
qp.drawLine(0, 0, int(r2+szD[0]/2), 0) # central beam2
|
||
|
||
# draw dimensions
|
||
g=self._geo
|
||
vr1=g['r1']
|
||
vr2=g['r2']
|
||
vaa=90-g['aa']
|
||
vbb=90-g['bb']
|
||
vcc=g['cc']
|
||
|
||
qp.setTransform(QtGui.QTransform())
|
||
qp.setPen(penRd)
|
||
p0=QPointF(*tf0.map(-r1-10, +10))
|
||
p1=QPointF(*tf0.map(-r1-10, 0+w))
|
||
qp.drawLine(p0, p1)
|
||
p2=QPointF(*tf0.map(0, 0))
|
||
p3=QPointF(*tf0.map(0, 0+w))
|
||
qp.drawLine(p2, p3)
|
||
p4=(p1+p3)/2 # text pos
|
||
p5=QPointF(*tf2.map(0, 0+w))
|
||
qp.drawLine(p2, p5)
|
||
p6=QPointF(*tf2.map(r2, 0))
|
||
p7=QPointF(*tf2.map(r2, 0+w))
|
||
qp.drawLine(p6, p7)
|
||
p8=(p5+p7)/2 # text pos
|
||
|
||
p9=QPointF(*tf1.map(0, -w))
|
||
qp.drawLine(p2, p9)
|
||
s=50
|
||
qp.drawArc(int(p2.x())-s, int(p2.y())-s, 2*s, 2*s, 180*16, int(aa*16))
|
||
s1=s*np.cos(aa*np.pi/180);
|
||
s2=s*np.sin(aa*np.pi/180)
|
||
qp.drawArc(int(p2.x())-s, int(p2.y())-s, 2*s, 2*s, int(aa*16), int(bb*16))
|
||
|
||
qp.drawArc(int(p6.x())-s, int(p6.y())-s, 2*s, 2*s, int((aa+bb)*16), int(cc*16))
|
||
p10=QPointF(*tf2.map(r2+20, 0)) # text pos
|
||
|
||
qp.setPen(penYl)
|
||
sclTrf=p['sclTrf']
|
||
qp.drawText(p0+QPointF(20-w, 10-w), f'scl(ang;{sclTrf[0]:0.2g},dst:{sclTrf[1]:0.2g})')
|
||
|
||
qp.drawText(p4+QPointF(-40, -10), f'R1={vr1:.5g}mm')
|
||
# qp.setTransform(QtGui.QTransform())
|
||
qp.translate(p8);
|
||
qp.rotate(-aa-bb)
|
||
qp.drawText(-40, -10, f'R2={vr2:.5g}mm')
|
||
qp.setTransform(QtGui.QTransform())
|
||
qp.translate(p2);
|
||
qp.rotate(-aa)
|
||
qp.drawText(20-int(szG[0]/2), 20, f'aa={vaa:.5g}°')
|
||
qp.drawText(20, 20, f'bb={vbb:.5g}°')
|
||
qp.setTransform(QtGui.QTransform())
|
||
qp.translate(p10);
|
||
qp.rotate(-aa-bb)
|
||
qp.drawText(0, 20, f'cc={vcc:.5g}°')
|
||
|
||
#mouse move polygon
|
||
#qp.setTransform(QTransform())
|
||
#qp.setPen(penRd)
|
||
#qp.drawPolygon(self._polygon)
|
||
#origin crosses
|
||
#for tf in (tf0,tfa,tfab,tfc):#,tfg,tfs):#,tfc,tfs):
|
||
# qp.setTransform(tf);self.plotOrig(qp)
|
||
|
||
class WndVisualize(QWidget):
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.initUI()
|
||
|
||
def initUI(self):
|
||
self.setGeometry(560, 100, 1300, 800)
|
||
self.setWindowTitle('Visualize')
|
||
app=QApplication.instance()
|
||
dev=app._dev
|
||
self._wdGrpDraw=w=QGroupBox(dev._name,self)
|
||
w.move(10,10)
|
||
row=0
|
||
lg=QGridLayout(w)
|
||
pDev=dev._paint
|
||
for key, rng, tk in (
|
||
('r1', (50, 800), 50),
|
||
('r2', (50, 800), 50),
|
||
('aa', (0, 90), 5),
|
||
('bb', (0, 90), 5),
|
||
('cc', (0, 180), 5),
|
||
('mode',(0,len(RIXSdevice._lutDifrBeamPaint)-1),1),
|
||
('sclA', ( -8, 8), 1),
|
||
('sclD', (-8, 8), 1),):
|
||
|
||
wLb=QLabel(key)
|
||
wSl=QSlider(QtCore.Qt.Horizontal,objectName=key)
|
||
wSl.setFixedWidth(200);wSl.setMinimum(rng[0]);wSl.setMaximum(rng[1])
|
||
if key.startswith('scl'):
|
||
if key[-1]=='A':
|
||
v=pDev['sclTrf'][0]
|
||
else:
|
||
v=pDev['sclTrf'][1]
|
||
v=int(round(np.log2(v)*2))
|
||
else:
|
||
v=pDev[key]
|
||
wSl.setValue(v)
|
||
wSl.setTickPosition(QSlider.TicksBelow);wSl.setTickInterval(tk)
|
||
wSl.valueChanged.connect(lambda val,key=key: self.sldChanged(key,val))
|
||
|
||
lg.addWidget(wLb, row, 0)
|
||
lg.addWidget(wSl, row, 1);row+=1
|
||
self.show()
|
||
|
||
def destroy(self, destroyWindow, destroySubWindows): #overloaded function
|
||
_log.info('destroy')
|
||
|
||
def closeEvent(self, event): #overloaded function
|
||
_log.info('closeEvent')
|
||
|
||
def sldChanged(self,key,val,*args,**kwargs):
|
||
app=QApplication.instance()
|
||
dev=app._dev
|
||
p=dev._paint
|
||
if key.startswith('scl'):
|
||
wGrp=self._wdGrpDraw
|
||
if key[-1]=='A':
|
||
p['sclTrf']=(2**(val/2),p['sclTrf'][1])
|
||
else:
|
||
p['sclTrf']=(p['sclTrf'][0],2**(val/2))
|
||
dev.setGeo2Paint(dev._geo)
|
||
else:
|
||
p[key]=val
|
||
g=dev._geo
|
||
if key in ('r1','r2'):
|
||
sclD=p['sclTrf'][1]
|
||
g[key]=val/sclD
|
||
elif key in ('aa', 'bb'):
|
||
sclA=p['sclTrf'][0]
|
||
g[key]=90-val/sclA
|
||
else:
|
||
g[key]=val
|
||
self.update()
|
||
|
||
def mouseReleaseEvent(self, a0):
|
||
try:
|
||
del self._mouseDrag
|
||
except AttributeError:
|
||
pass
|
||
|
||
def mousePressEvent(self, a0):
|
||
app=QApplication.instance()
|
||
mousePos=a0.pos()
|
||
print(a0.type)
|
||
if a0.type()!=QtGui.QMouseEvent.MouseButtonPress:
|
||
return
|
||
wGrp=self._wdGrpDraw
|
||
#if wGrp.underMouse(): #draging sliders?
|
||
if wGrp.geometry().contains(mousePos):
|
||
self._mouseDrag={'obj':wGrp, 'start':mousePos}
|
||
return
|
||
dev=app._dev
|
||
if dev.containsPoint(mousePos):
|
||
self._devSel=dev
|
||
wGrp=self._wdGrpDraw
|
||
wGrp.setTitle(dev._name)
|
||
devP=dev._paint
|
||
for wSl in wGrp.findChildren(QSlider):
|
||
#_log.info(wSl)
|
||
key=wSl.objectName()
|
||
if key.startswith('scl'):
|
||
if key[-1]=='A':
|
||
v=devP['sclTrf'][0]
|
||
else:
|
||
v=devP['sclTrf'][1]
|
||
v=int(round(np.log2(v)*2))
|
||
else:
|
||
v=devP[key]
|
||
wSl.setValue(v)
|
||
|
||
self._mouseDrag={'obj':dev,'start':(mousePos,dev._paint['ofs'])}
|
||
try:
|
||
_log.info(f'{self._mouseDrag}')
|
||
except AttributeError:
|
||
_log.info(f'no object to drag')
|
||
|
||
|
||
def mouseMoveEvent(self, a0):
|
||
try:
|
||
md=self._mouseDrag
|
||
except AttributeError:
|
||
return
|
||
obj=md['obj']
|
||
s=md['start']
|
||
if obj==self._wdGrpDraw:
|
||
p=a0.pos()
|
||
md['start']=p
|
||
p=obj.geometry().topLeft()+p-s
|
||
obj.move(p)
|
||
return
|
||
p=a0.pos()
|
||
ofs=QPoint(*s[1])+p-s[0]
|
||
_log.info(f'{p} {ofs}')
|
||
obj._paint['ofs']=(ofs.x(),ofs.y())
|
||
self.update()
|
||
|
||
def paintEvent(self, e):
|
||
qp = QPainter()
|
||
qp.begin(self)
|
||
qp.setRenderHints(QPainter.HighQualityAntialiasing)
|
||
app=QApplication.instance()
|
||
dev=app._dev
|
||
app._dev.paint(qp)
|
||
qp.end()
|
||
|
||
def updateDevice(self, dev):
|
||
#_log.info(f'WndVis')
|
||
self._devSel=dev
|
||
devP=dev._paint
|
||
wGrp=self._wdGrpDraw
|
||
wGrp.setTitle(dev._name)
|
||
for wSl in wGrp.findChildren(QSlider):
|
||
# _log.info(wSl)
|
||
key=wSl.objectName()
|
||
if key.startswith('scl'):
|
||
if key[-1]=='A':
|
||
v=devP['sclTrf'][0]
|
||
else:
|
||
v=devP['sclTrf'][1]
|
||
v=int(round(np.log2(v)*2))
|
||
else:
|
||
v=devP[key]
|
||
wSl.blockSignals(True)
|
||
wSl.setValue(v)
|
||
wSl.blockSignals(False)
|
||
self.update()
|
||
|
||
def OnEnergyChanged(self,w,*args):
|
||
#_log.debug(f'OnEnergyChanged: {w} {args}')
|
||
app=QApplication.instance()
|
||
#wCb=self._wdGrpEnergy.findChild(QComboBox, 'Grating')
|
||
wLe=self._wdGrpEnergy.findChild(QLineEdit, 'Energy')
|
||
wSl=self._wdGrpEnergy.findChild(QSlider, 'energy')
|
||
t=type(w)
|
||
if t==QLineEdit:
|
||
try:
|
||
e=float(w.text())
|
||
except ValueError as e:
|
||
return
|
||
wSl.blockSignals(True)
|
||
wSl.setValue(int(e))
|
||
wSl.blockSignals(False)
|
||
elif t==QComboBox:
|
||
app=QApplication.instance()
|
||
vlsg=app._vlsg
|
||
vlsg.setup(w.currentText())
|
||
try:
|
||
e=float(wLe.text())
|
||
except ValueError as e:
|
||
return
|
||
elif t==QSlider:
|
||
e=args[0]
|
||
wLe.setText(f'{e:.4g}')
|
||
else:
|
||
raise(TypeError(f'wrong type: {t}'))
|
||
self.setEnergy(e)
|
||
|
||
def OnEvent(self,*args,**kwargs):
|
||
#test event
|
||
print(f'OnEvent: {args} ,{kwargs}')
|
||
|
||
def setEnergy(self,val):
|
||
app=QApplication.instance()
|
||
wGrating=self._wdGrpEnergy.findChild(QComboBox, 'Grating')
|
||
wEnergy=self._wdGrpEnergy.findChild(QLineEdit, 'Energy')
|
||
wEnergy.setText(f'{val:.4g}')
|
||
|
||
vlsg=app._vlsg
|
||
vlsg.setup(wGrating.currentText())
|
||
self._vlsg_geo=geo=vlsg.energy2geometry(val)
|
||
|
||
grp=self._wdGrpGeometry
|
||
for k,v in geo.items():
|
||
w=grp.findChild(QLineEdit, k)
|
||
w.setText(f'{v:.6g}')
|
||
|
||
self._vlsg_raw=raw,err=vlsg.geometry2raw(**geo)
|
||
if err is not None:
|
||
_log.error(str(err))
|
||
self._vlsg_raw=raw
|
||
grp=self._wdGrpRaw
|
||
for k,v in raw.items():
|
||
w=grp.findChild(QLineEdit, k)
|
||
if v is None:
|
||
w.setText('(null)')
|
||
else:
|
||
w.setText(f'{v:.6g}')
|
||
self.update()
|
||
|
||
|
||
class WndVars(QWidget):
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.initUI()
|
||
|
||
def initUI(self):
|
||
app=QApplication.instance()
|
||
dev=app._dev
|
||
self.setGeometry(100, 500, 550, 550)
|
||
self.setWindowTitle('Vars')
|
||
|
||
self._wdGrpGeometry=w=QGroupBox("Geometry",self)
|
||
w.move(10,10)
|
||
l=QGridLayout(w)
|
||
lut={'aa':'\u03B1', 'bb':'\u03B2', 'cc':'\u03B3'}
|
||
for i,t in enumerate(('r1','r2','aa','bb','cc',)):
|
||
tl=lut.get(t,t)
|
||
w=QLabel(tl)
|
||
l.addWidget(w, i,0)
|
||
w=QLineEdit(t,objectName=t)
|
||
l.addWidget(w, i,1)
|
||
|
||
self._wdGrpMotor=w=QGroupBox("Motors",self)
|
||
w.move(200,10)
|
||
#w.setFixedSize(400,42+32*14)
|
||
w.setFixedWidth(330)
|
||
l=QGridLayout(w)
|
||
|
||
prefix=RIXSdevice._prefix
|
||
motors=RIXSdevice._motors
|
||
pv_rbv=dev._pvDict['rbv']
|
||
for i,v in enumerate(motors):
|
||
m,desc=v
|
||
wCb=QCheckBox(m,objectName=m)
|
||
l.addWidget(wCb, i,0)
|
||
pv=pv_rbv[m]
|
||
if pv.connected:
|
||
v=pv.get(as_string=True)
|
||
else:
|
||
v=m
|
||
w=QLineEdit(v,objectName=m)
|
||
w.setToolTip(desc)
|
||
l.addWidget(w, i,1)
|
||
|
||
v=f"<font color='#808080'>{m}</font>"
|
||
w=QLabel(v,objectName=m)
|
||
l.addWidget(w, i, 2)
|
||
#pv=pvd[rcNm]
|
||
|
||
w=QPushButton('move motors')
|
||
w.clicked.connect(self.btnMoveMotors)
|
||
l.addWidget(w, i+1, 1)
|
||
|
||
|
||
|
||
self.show()
|
||
|
||
def updateDevice(self, dev):
|
||
grpGeo=self._wdGrpGeometry
|
||
grpMot=self._wdGrpMotor
|
||
|
||
geo=dev._geo
|
||
for k, v in geo.items():
|
||
wLe=grpGeo.findChild(QLineEdit, k)
|
||
if wLe is not None:
|
||
wLe.setText(f'{v:.6g}')
|
||
else:
|
||
self.update_label(k,v)
|
||
self.update()
|
||
|
||
def update_label(self,k,v,**kwargs):
|
||
#_log.info(f'{k}:{v}:{kwargs}')
|
||
app=QApplication.instance()
|
||
dev=app._dev
|
||
if kwargs:
|
||
rbv=v
|
||
key=re.search('_(.*)\.',k).group(1).lower()
|
||
val=dev._geo.get(key,None)
|
||
#_log.debug(f'monitoring: {key}:{val}:{rbv}')
|
||
else:
|
||
key=k
|
||
val=v
|
||
pv_rbv=dev._pvDict['rbv']
|
||
pv=pv_rbv[k]
|
||
if pv.connected:
|
||
rbv=pv.get()
|
||
else:
|
||
rbv=None
|
||
#_log.debug(f'change: {key}:{val}:{rbv}')
|
||
grpMot=self._wdGrpMotor
|
||
wLe=grpMot.findChild(QLineEdit, key)
|
||
if wLe is not None:
|
||
wCb=grpMot.findChild(QCheckBox, key)
|
||
wLb=grpMot.findChild(QLabel, key)
|
||
#TODO:
|
||
if val is None:
|
||
v=f"<font color='#606060'>{rbv}</font>"
|
||
else:
|
||
wLe.setText(f'{val:.3f}') # {v:.6g}
|
||
if rbv is None:
|
||
v=f"<font color='#606060'>{rbv}</font>"
|
||
else:
|
||
d=abs(val-rbv)
|
||
b=bool(d>0.001)
|
||
wCb.setChecked(b)
|
||
if b:
|
||
c='ff8080'
|
||
else:
|
||
c='60a060'
|
||
v=f"<font color='#{c}'>{rbv:.5g} ({d:+.5g})</font>"
|
||
wLb.setText(v)
|
||
|
||
def btnMoveMotors(self):
|
||
app=QApplication.instance()
|
||
dev=app._dev
|
||
grpMot=self._wdGrpMotor
|
||
lg=grpMot.layout()
|
||
row=lg.rowCount()
|
||
cnt=0
|
||
for r in range(row-1): # -1 because last one is a button
|
||
wCb=lg.itemAtPosition(r, 0)
|
||
if wCb is None: continue
|
||
if wCb.widget().isChecked(): cnt+=1
|
||
if cnt==0: return
|
||
btn=QMessageBox.question(
|
||
self,
|
||
"Motion",
|
||
f"Really want to move {cnt} motors ?",
|
||
buttons=QMessageBox.Yes|QMessageBox.No,
|
||
defaultButton=QMessageBox.No,
|
||
)
|
||
if btn!=QMessageBox.Yes: return
|
||
_log.info('for safty reason the program will not move')
|
||
return
|
||
|
||
geo=dev._geo
|
||
try:
|
||
pv_val=dev._pvDict['val']
|
||
except KeyError as e:
|
||
pv_val=dev._pvDict['val']=dict()
|
||
|
||
for r in range(row-1): # -1 because last one is a button
|
||
wCb=lg.itemAtPosition(r, 0)
|
||
if wCb is None:
|
||
continue
|
||
wCb=wCb.widget()
|
||
if wCb.isChecked():
|
||
m=wCb.objectName()
|
||
try:
|
||
pv=pv_val[m]
|
||
except KeyError as e:
|
||
pvn=RIXSdevice._prefix+'MOT_'+m.upper()+'.VAL'
|
||
pv=pv_val[m]=epics.get_pv(pvn)#,connection_callback=self.OnConnectionChange,callback=self.OnValueChange)
|
||
pv.put(geo[m])
|
||
|
||
|
||
# ->>>> https://pyepics.github.io/pyepics/devices.html >>> USE THIS
|
||
#[satesf-cons-03 ~]$ /opt/gfa/python-3.8/latest/bin/ipython3
|
||
#import CaChannel
|
||
#import epics
|
||
|
||
#capv=CaChannel.CaChannel('SATOP11-PSAS079:MOT_DDF_X.VAL') -> THIS IS REALLY BASIC...
|
||
#capv.state()
|
||
#m=epics.Motor('SATOP11-PSAS079:MOT_DDF_X')
|
||
#pv=pvm.PV('RBV')
|
||
#dev=epics.Device('SATOP11-PSAS079:MOT_DDF_X.', attrs=('VAL', 'RBV', 'DESC'))
|
||
#dev.RBV
|
||
|
||
class SimEpicsDevice(): #behaves similar to epics.Device
|
||
def __init__(self, prefix='', attrs=None,
|
||
nonpvs=None, delim='', timeout=None,
|
||
mutable=True, aliases=None, with_poll=True):
|
||
self._prefix=prefix
|
||
self._pvs={}
|
||
for k in attrs:
|
||
self._pvs[k]=0
|
||
|
||
def __getattr__(self, attr):
|
||
#if self._pvs:
|
||
if '_pvs' in self.__dict__:
|
||
return self._pvs[attr]
|
||
else:
|
||
_log.debug(attr)
|
||
raise AttributeError
|
||
|
||
def __setattr__(self, attr, val):
|
||
if attr in ('_pvs','_prefix'):
|
||
self.__dict__[attr]=val
|
||
elif attr in self._pvs:
|
||
self._pvs[attr]= val
|
||
if attr=='VAL' and 'RBV' in self._pvs:
|
||
self._pvs['RBV']=val
|
||
else:
|
||
self.__dict__[attr]=val
|
||
|
||
def __repr__(self):
|
||
"string representation"
|
||
attr=[]
|
||
for k,v in self._pvs.items():
|
||
attr.append(f'{k}: {v}')
|
||
s=f"<Device: {self._prefix} ( "+', '.join(attr)+' )'
|
||
return s
|
||
|
||
|
||
# if attr in self._pvs:
|
||
# return self.get(attr)
|
||
# elif attr in self.__dict__:
|
||
# return self.__dict__[attr]
|
||
|
||
if __name__ == '__main__':
|
||
import argparse
|
||
logging.basicConfig(level=logging.DEBUG, format='%(levelname)s:%(module)s:%(lineno)d:%(funcName)s:%(message)s ')
|
||
|
||
|
||
def main():
|
||
epilog=__doc__ # +'\nExamples:'+''.join(map(lambda s:cmd+s, exampleCmd))+'\n'
|
||
parser=argparse.ArgumentParser(epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||
parser.add_argument('--mode', '-m', type=lambda x:int(x, 0), help='mode (see bitmasks) default=0x%(default)x', default=1)
|
||
parser.add_argument("--sim", "-s", type=lambda x: int(x,0), help="simulate devices (see bitmasks) default=0x%(default)x", default=0x01)
|
||
args=parser.parse_args()
|
||
_log.info('Arguments:{}'.format(args.__dict__))
|
||
|
||
app=QApplication(sys.argv)
|
||
app._args=args
|
||
#devUS=RIXSdevice('RIXS us', ofs=(750, 200), g=-100, s=-1500, sy=-20, cy=70)
|
||
#devDS=RIXSdevice('RIXS ds', ofs=(930, 630))
|
||
sCfg=Config._spectrometer
|
||
pCfg=Config._lutProbes
|
||
app._dev=dev=RIXSdevice('Furka-RIXS',**sCfg['vis'])
|
||
dev._probes=sCfg['probes']
|
||
|
||
vlsg=VLSgrating()
|
||
app._vlsg=vlsg
|
||
|
||
k=pCfg.keys()
|
||
for v in dev._probes.values():
|
||
try:
|
||
p=v['probe']
|
||
except:
|
||
pass
|
||
else:
|
||
if p not in k:
|
||
_log.error(f'probe:{p} not found in {tuple(k)}')
|
||
|
||
if args.mode&0x01:
|
||
app._wndRIXS=WndMainRIXS()
|
||
if args.mode&0x02:
|
||
app._wndGirder=WndGirderRIXS()
|
||
sys.exit(app.exec_())
|
||
|
||
main() |