#!/usr/bin/env python # *-----------------------------------------------------------------------* # | | # | Copyright (c) 2022 by Paul Scherrer Institute (http://www.psi.ch) | # | | # | Author Thierry Zamofing (thierry.zamofing@psi.ch) | # *-----------------------------------------------------------------------* from PyQt5.QtWidgets import QWidget, QLabel, QPushButton, QApplication from PyQt5.QtGui import QPainter, QColor, QBrush from pyqtgraph.Qt import QtCore, QtGui import numpy as np import PyQt5.QtGui as QtGui import PyQt5.QtCore as QtCore import PyQt5.QtWidgets as QtW from PyQt5.uic import loadUiType import numpy as np import geometry import sys import logging 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)==QtGui.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 class RIXSgirder(QWidget): def __init__(self): super().__init__() self._param={'ctr':(200, 300), # location of big chamber 'aa':45, # RIXS arm angle 'cc':45, # UNSUSED } self.initUI() def initUI(self): self.setGeometry(300, 300, 850, 800) self.setWindowTitle('RIXS girder') #label = QLabel('Python', self) #label.move(50,50) #b1 = QPushButton("Button1",self) #b1.move(400,150) w=QtGui.QGroupBox("Drawing",self) w.move(10,10) l=QtGui.QVBoxLayout(w) sld={} for key,rng,tk,pos in (('aa',(0,180),5,20),('cc',(0,180),5,40),): sl=QtGui.QSlider(QtCore.Qt.Horizontal) sl.setFixedWidth(200);sl.setMinimum(rng[0]);sl.setMaximum(rng[1]) sl.setValue(self._param[key]) sl.setTickPosition(QtGui.QSlider.TicksBelow);sl.setTickInterval(tk) l.addWidget(sl) sl.valueChanged.connect(lambda val,key=key: self.sldChanged(key,val)) sld[key]=sl self.show() def sldChanged(self,key,val,*args,**kwargs): print(key,val) self._param[key]=val self.update() def paintEvent(self, e): p=self._param ctr=p['ctr'] aa=p['aa'] qp = QPainter() qp.begin(self) qp.translate(ctr[0],ctr[1]) qp.setPen(QtGui.QPen(QtCore.Qt.black, 2, QtCore.Qt.SolidLine)) qp.setBrush(QColor(255, 80, 0, 128)) rCmb=100 # radius of chamber rTrg=10 # radius of target qp.drawEllipse(-rCmb, -rCmb, 2*rCmb, 2*rCmb) # big chamber qp.drawEllipse(-rTrg, -rTrg, 2*rTrg, 2*rTrg) # target qp.drawLine(0,-rCmb-50,0,-rTrg) #beam qp.rotate(-aa) qp.drawRect(-50, 100, 100, 300) # girder qp.drawEllipse(-50, 100+150, 20, 20) #pusher left qp.drawEllipse( 50-20, 100+150, 20, 20) #pusher right qp.drawEllipse(-50, 100+250, 20, 20) #air pad left qp.drawEllipse( 50-20, 100+250, 20, 20) #air pad right qp.end() #Ui_MainWindow, QMainWindow = loadUiType("graphEx1.ui") #class RIXSgrating(QMainWindow, Ui_MainWindow): # def __init__(self, ): # super(RIXSgrating, self).__init__() # self.setupUi(self) class RIXSgrating(QWidget): def __init__(self): super().__init__() self._param={'ctr':(500,500), # location of vlsg 'r1':314,#distance probe grating 'r2':447,#distance grating detector 'aa':18, #grating angle 'bb':18, #reflection angle 'cc':58, #detector angle 'szG':(200,5), #size VLS grating 'szD':(150,5), #size detector 'difrBeamPaint':4, # mode to plot the difracted beam } self._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), ) # n=4;mode=2;alpha=190 # n=4;mode=1;alpha=200 # n=32;mode=1;alpha=120 # n=8;mode=1;alpha=196 n=32; mode=0; alpha=120; width=3 # n=32;mode=2;alpha=255 self.initUI() def initUI(self): #p=self.palette() #p.setColor(self.backgroundRole(), QtCore.Qt.black) #self.setPalette(p) self.setGeometry(300, 300, 1050, 700) self.setWindowTitle('RIXS grating') #w.move(400,100) #w.setFixedSize(200,200) self._wdGrpEnergy=w=QtGui.QGroupBox("Energy",self) w.move(10,10) #w.setFixedSize(200,42+32*2) lv=QtGui.QVBoxLayout(w) w=QtGui.QWidget() #QGroupBox("E2",self) lv.addWidget(w) lg=QtGui.QGridLayout(w) i=0;t='Energy' w=QLabel(t); lg.addWidget(w, i,0) w=QtGui.QLineEdit(t,objectName=t); lg.addWidget(w, i,1) w.returnPressed.connect(lambda w=w:self.OnEnergyChanged(w)) i+=1;t='Grating' w=QLabel(t); lg.addWidget(w, i,0) w=QtGui.QComboBox(objectName=t); lg.addWidget(w, i,1) for i,t in enumerate(geometry.VLSgrating.vlsgTypes): w.insertItem(i,t) w.currentIndexChanged.connect(lambda val,w=w:self.OnEnergyChanged(w,val)) key,rng,tk,pos=('energy',(200,1500),50,60) w=QtGui.QSlider(QtCore.Qt.Horizontal,objectName=key) w.setFixedWidth(200);w.setMinimum(rng[0]);w.setMaximum(rng[1]) #w.setValue(self._param[key]) w.setTickPosition(QtGui.QSlider.TicksBelow);w.setTickInterval(tk) lv.addWidget(w) #sl.valueChanged.connect(lambda val,key=key: self.sldChanged(key,val)) w.valueChanged.connect(lambda val,w=w:self.OnEnergyChanged(w,val)) self._wdGrpGeometry=w=QtGui.QGroupBox("Geometry",self) w.move(10,160) #w.setFixedSize(200,42+32*4) l=QtGui.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=QtGui.QLineEdit(t,objectName=t) l.addWidget(w, i,1) self._wdGrpRaw=w=QtGui.QGroupBox("Motors",self) w.move(250,10) #w.setFixedSize(200,42+32*4) l=QtGui.QGridLayout(w) 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 (ɣ) '), ) for i,m in enumerate(motors): tl,inf=m t=tl.lower() w=QLabel(tl) l.addWidget(w, i,0) w=QtGui.QLineEdit(t,objectName=t) w.setToolTip(inf) l.addWidget(w, i,1) w=QtGui.QPushButton('move all motors') w.clicked.connect(self.btnMoveAllMotors) l.addWidget(w, i+1, 1) self._wdGrpDraw=w=QtGui.QGroupBox("Drawing",self) w.move(470,10) l=QtGui.QVBoxLayout(w) sld={} 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), ('difrBeamPaint',(0,len(self._lutDifrBeamPaint)-1),1),): sl=QtGui.QSlider(QtCore.Qt.Horizontal,objectName=key) sl.setFixedWidth(200);sl.setMinimum(rng[0]);sl.setMaximum(rng[1]) sl.setValue(self._param[key]) sl.setTickPosition(QtGui.QSlider.TicksBelow);sl.setTickInterval(tk) l.addWidget(sl) sl.valueChanged.connect(lambda val,key=key: self.sldChanged(key,val)) sld[key]=sl self.show() def OnEnergyChanged(self,w,*args): #_log.debug(f'OnEnergyChanged: {w} {args}') app=QApplication.instance() #wCb=self._wdGrpEnergy.findChild(QtGui.QComboBox, 'Grating') wLe=self._wdGrpEnergy.findChild(QtGui.QLineEdit, 'Energy') wSl=self._wdGrpEnergy.findChild(QtGui.QSlider, 'energy') t=type(w) if t==QtGui.QLineEdit: try: e=float(w.text()) except ValueError as e: return wSl.blockSignals(True) wSl.setValue(int(e)) wSl.blockSignals(False) elif t==QtGui.QComboBox: app=QApplication.instance() vlsg=app._vlsg vlsg.setup(w.currentText()) try: e=float(wLe.text()) except ValueError as e: return elif t==QtGui.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() p=self._param wGrating=self._wdGrpEnergy.findChild(QtGui.QComboBox, 'Grating') wEnergy=self._wdGrpEnergy.findChild(QtGui.QLineEdit, 'Energy') wEnergy.setText(f'{val:.4g}') vlsg=app._vlsg vlsg.setup(wGrating.currentText()) self._vlsg_geo=geo=vlsg.energy2geometry(val) p['r1']=geo['r1']/8 p['r2']=geo['r2']/8 p['aa']=(90-geo['aa'])*5 p['bb']=(90-geo['bb'])*5 p['cc']=geo['cc'] grp=self._wdGrpDraw for k,v in p.items(): w=grp.findChild(QtGui.QSlider, k) if w is not None: w.blockSignals(True) w.setValue(int(v)) w.blockSignals(False) grp=self._wdGrpGeometry for k,v in geo.items(): w=grp.findChild(QtGui.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(QtGui.QLineEdit, k) if v is None: w.setText('(null)') else: w.setText(f'{v:.6g}') self.update() def sldChanged(self,key,val,*args,**kwargs): #print(key,val) try: self._vlsg_geo except AttributeError: pass p=self._param p[key]=val self.update() def btnMoveAllMotors(self): p=self._param wGr=self._wdGrpEnergy.findChild(QtGui.QComboBox, 'Grating') wEn=self._wdGrpEnergy.findChild(QtGui.QLineEdit, 'Energy') try: mot=self._vlsg_raw except AttributeError: _log.error('no current energy set') return dlg=DlgMoveMotors() dlg.initUI('prefix',mot) #dlg.initUI('prefix',self._vlsg_raw) if dlg.exec(): print("Success!") else: print("Cancel!") #print(f'grating:{wGr.currentText()} energy:{wEn.text()}') #print(f'geometry:{self._vlsg_geo}') #print(f'raw motors:{self._vlsg_raw}') def paintEvent(self, e): p=self._param r1=int(p['r1']) r2=int(p['r2']) aa=p['aa'] bb=p['bb'] cc=p['cc'] szG=p['szG'] szD=p['szD'] ctr=p['ctr'] qp = QPainter() qp.begin(self) qp.setRenderHints(QPainter.HighQualityAntialiasing) #plot black background w=int(max(szG[0],szD[0])/2) ctr=(max(ctr[0],w+r1),max(w+r2*np.sin((aa+bb)*np.pi/180),ctr[1])) qp.translate(ctr[0],ctr[1]) qp.setBrush(QColor(0, 0, 0)) qp.drawRect(-r1-w, -w, r1+w, 2*w) #qp.drawEllipse(-r1-w, -w, 2*w, 2*w) qp.drawEllipse(-w, -w, 2*w, 2*w) qp.rotate(180-aa-bb) qp.drawRect(-r2-w, -w, r2+w, 2*w) #qp.drawEllipse(-r2-w, -w, 2*w, 2*w) #plot beam path qp.setTransform(QtGui.QTransform()) qp.translate(ctr[0],ctr[1]) qp.setCompositionMode(QtGui.QPainter.CompositionMode_Lighten) tf0=qp.transform() qp.setPen(QtGui.QPen(QtCore.Qt.white, 1, QtCore.Qt.SolidLine)) 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=QtCore.QPointF(*tf0.map(-r1-10, 10)) p1=QtCore.QPointF(*tf1.map(-szG[0]/2, 0)) qp.drawLine(p0,p1) p0=QtCore.QPointF(*tf0.map(-r1-10, -10)) p1=QtCore.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['difrBeamPaint']] scl=256/n p0=QtCore.QPointF(*tf1.map(-szG[0]/2, 0)) p1=QtCore.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=QtCore.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(QtGui.QPen(QtCore.Qt.black, 0, QtCore.Qt.SolidLine)) for i in range(n): p2=QtCore.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(QtGui.QPen(QtCore.Qt.black, 0, QtCore.Qt.SolidLine)) for i in range(n): p2=QtCore.QPointF(*tf3.map(szD[0]/2-szD[0]*(i+1)/n, 0)) p3=QtCore.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 try: d=self._vlsg_geo except AttributeError: vr1=r1; vr2=r2; vaa=aa; vbb=bb; vcc=cc else: vr1=d['r1'] vr2=d['r2'] vaa=90-d['aa'] vbb=90-d['bb'] vcc=d['cc'] qp.setTransform(QtGui.QTransform()) pen=QtGui.QPen(QtCore.Qt.red, 1, QtCore.Qt.SolidLine) penTxt=QtGui.QPen(QtCore.Qt.yellow, 1, QtCore.Qt.SolidLine) qp.setPen(pen) p0=QtCore.QPointF(*tf0.map(-r1-10, +10)) p1=QtCore.QPointF(*tf0.map(-r1-10, 0+w)) qp.drawLine(p0,p1) p2=QtCore.QPointF(*tf0.map(0, 0)) p3=QtCore.QPointF(*tf0.map(0, 0+w)) qp.drawLine(p2,p3) p4=(p1+p3)/2 #text pos p5=QtCore.QPointF(*tf2.map(0, 0+w)) qp.drawLine(p2,p5) p6=QtCore.QPointF(*tf2.map(r2, 0)) p7=QtCore.QPointF(*tf2.map(r2, 0+w)) qp.drawLine(p6,p7) p8=(p5+p7)/2 #text pos p9=QtCore.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=QtCore.QPointF(*tf2.map(r2+20, 0)) #text pos qp.setPen(penTxt) qp.drawText(p4+QtCore.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}°') #for i,p in enumerate((p0,p1,p2,p3,p4,p5,p6,p7,p8,p9)): # qp.drawText(p,f'P{i}') #p5=QtCore.QPointF(*tf1.map(-r2-10, 50+w)) qp.end() #https://www.pythonguis.com/tutorials/pyqt-dialogs/ class DlgMoveMotors(QtGui.QDialog): def __init__(self): super().__init__() #self.initUI() def initUI(self,prefix,motDst): self._motDst=motDst self.setWindowTitle("Move Motors:") #QBtn=QtGui.QDialogButtonBox.Ok|QtGui.QDialogButtonBox.Cancel #self.buttonBox=QtGui.QDialogButtonBox(QBtn) #self.buttonBox.accepted.connect(self.accept) #self.buttonBox.rejected.connect(self.reject) self.buttonBox=QtGui.QDialogButtonBox() btn=self.buttonBox.addButton('move all',QtGui.QDialogButtonBox.ActionRole) btn.clicked.connect(self.OnMove) self.buttonBox.addButton('stop all',QtGui.QDialogButtonBox.ActionRole) self.layout=QtGui.QVBoxLayout() message=QLabel("Something happened, is that OK?") self.layout.addWidget(message) self._wdGrpRaw=w=QtGui.QGroupBox("Motors", self) self.layout.addWidget(w) self._layoutGrid=lg=QtGui.QGridLayout(w) # dev=epics.Device('SATES30-RX:MOT_ABL.', attrs=('VAL', 'RBV', 'DESC')) app=QApplication.instance() devs=app._devs prefix='SATES30-RX:' for i,(k,v) in enumerate(motDst.items()): m='MOT_'+k.upper() #devs[k]=dev=epics.Device(prefix+m+'.', attrs=('RBV', 'DESC'), timeout=.5) # for safty: removed 'VAL', try: dev=devs[k] except KeyError: devs[k]=dev=SimDevice(prefix+m+'.', attrs=('VAL', 'RBV', 'DESC')) w=QtGui.QCheckBox(m,objectName=k) lg.addWidget(w, i, 0) if v is None: #v="
(null)"
v="(null)"
else:
rbv=dev.RBV
d=v-rbv
if d>0:
c='ff8080'
else:
c='60a060'
v=f"{v:.5g} (old:{rbv:.5g} {d:.5g})"
w.setChecked(True)
w=QLabel(v)
lg.addWidget(w, i, 1)
self.layout.addWidget(self.buttonBox)
self.setLayout(self.layout)
def OnMove(self):
app=QApplication.instance()
devs=app._devs
motDst=self._motDst
lg=self._layoutGrid
col=lg.columnCount()
for i,(k,v) in enumerate(motDst.items()):
cb=lg.itemAt(i*col).widget()
if cb.isChecked():
if v is not None:
dev=devs[k]
#print(dev)
dev.VAL=v # move motor
print(f'{cb.objectName()} {dev}')
# ->>>> 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('SATES30-RX:MOT_ABL.VAL') -> THIS IS REALLY BASIC...
#capv.state()
#m=epics.Motor('SATES30-RX:MOT_ABL')
#pv=pvm.PV('RBV')
#dev=epics.Device('SATES30-RX:MOT_ABL.', attrs=('VAL', 'RBV', 'DESC'))
#dev.RBV
class SimDevice():
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"