1033 lines
29 KiB
Python
Executable File
1033 lines
29 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
|
||
|
||
bitmask for simulation:
|
||
0x01: EPICS motors
|
||
0x02:
|
||
0x04:
|
||
0x08:
|
||
0x10:
|
||
0x20:
|
||
0x40:
|
||
0x80:
|
||
|
||
"""
|
||
|
||
|
||
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QSlider, QLineEdit,\
|
||
QCheckBox, QHBoxLayout, QVBoxLayout, QGroupBox, QGridLayout, QComboBox
|
||
from PyQt5.QtGui import QPainter, QColor, QPen, QBrush, QPolygon, QTransform
|
||
from PyQt5.QtCore import QPoint, QPointF, Qt
|
||
|
||
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 ESF_RIXSgeometry import VLSgrating
|
||
from ESF_RIXSconfig import Config
|
||
from ESF_RIXSgirder import WndGirderRIXS
|
||
|
||
import sys, logging, copy
|
||
import epics
|
||
|
||
_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)
|
||
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
|
||
self.show()
|
||
|
||
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.setGeometry(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),
|
||
)
|
||
|
||
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.setGeometry(g)
|
||
|
||
|
||
def setGeometry(self,geo):
|
||
self._geo=geo
|
||
p=self._paint
|
||
sclA,sclD=p['sclTrf']
|
||
p.update({
|
||
'r1':int(geo['r1']*sclD),
|
||
'r2':int(geo['r2']*sclD),
|
||
'aa':int((90-geo['aa'])*sclA),
|
||
'bb':int((90-geo['bb'])*sclA),
|
||
'cc':int(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=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
|
||
|
||
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 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.setGeometry(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):
|
||
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()
|
||
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='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 (ɣ) '),
|
||
|
||
)
|
||
self._epicsDevDct=eDevDct=dict()
|
||
simMot=app._args.sim&0x01
|
||
for i,m in enumerate(motors):
|
||
tl,inf=m
|
||
t=tl.lower()
|
||
wCb=QCheckBox(t,objectName=t)
|
||
l.addWidget(wCb, i,0)
|
||
w=QLineEdit(t,objectName=t)
|
||
w.setToolTip(inf)
|
||
l.addWidget(w, i,1)
|
||
|
||
v=f"<font color='#808080'>{t}</font>"
|
||
w=QLabel(v,objectName=t)
|
||
l.addWidget(w, i, 2)
|
||
|
||
rcNm=prefix+'MOT_'+tl+'.'
|
||
if simMot:
|
||
eDevDct[t]=eDev=SimEpicsDevice(rcNm, attrs=('VAL', 'RBV', 'DESC'))
|
||
else:
|
||
eDevDct[t]=eDev=epics.Device(rcNm, attrs=('VAL', 'RBV', 'DESC'), timeout=.5) # for safty: removed 'VAL',
|
||
eDev.add_callback('RBV', self.update_label)
|
||
|
||
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(key=k,val=v)
|
||
self.update()
|
||
|
||
def update_label(self, **kwargs):
|
||
#_log.info(f'update_label...{kwargs}')
|
||
try:
|
||
k=kwargs['pvname'].split(':',1)[1].split('.',1)[0].split('_',1)[1].lower()
|
||
except KeyError as e:
|
||
k=kwargs['key']
|
||
v=kwargs['val']
|
||
else:
|
||
_log.debug(f'monitoring: {k}')
|
||
app=QApplication.instance()
|
||
v=None
|
||
dev=app._dev
|
||
try:
|
||
v=dev._geo[k]
|
||
except (KeyError,AttributeError) as e:
|
||
pass
|
||
grpMot=self._wdGrpMotor
|
||
wLe=grpMot.findChild(QLineEdit, k)
|
||
if wLe is not None:
|
||
_log.debug(f'{k}:{v}')
|
||
wCb=grpMot.findChild(QCheckBox, k)
|
||
wLb=grpMot.findChild(QLabel, k)
|
||
eDev=self._epicsDevDct[k]
|
||
rbv=eDev.RBV
|
||
if v is None:
|
||
v=f"<font color='#606060'>{rbv:.5g}</font>"
|
||
else:
|
||
wLe.setText(f'{v:.3f}') # {v:.6g}
|
||
d=abs(v-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):
|
||
grpMot=self._wdGrpMotor
|
||
lg=grpMot.layout()
|
||
col=lg.columnCount()
|
||
eDevs=self._epicsDevDct
|
||
|
||
for k,eDev in eDevs.items():
|
||
wCb=grpMot.findChild(QCheckBox, k)
|
||
if wCb.isChecked():
|
||
wLe=grpMot.findChild(QLineEdit, k)
|
||
v=float(wLe.text())
|
||
if eDev is not None:
|
||
_log.info(f'move motor {k} to {v}')
|
||
if type(eDev)==SimEpicsDevice:
|
||
eDev.VAL=v # move motor #comment out for safety
|
||
self.update_label(key=k,val=v)
|
||
else:
|
||
#eDev.VAL=v # move motor #comment out for safety
|
||
pass
|
||
|
||
# ->>>> 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() |