Files
OpticsTools/OpticsTools.py

239 lines
9.3 KiB
Python

import sys
import json
import webbrowser
import subprocess
from argparse import ArgumentParser
from PyQt5 import QtWidgets,QtGui,QtCore
from ui.OpticsToolsGui import Ui_OpticsGUI
from plot import OpticsPlot
from model import Model
from machine import Machine
from reference import ReferenceManager
from sandbox import Sandbox
from matchmaker import MatchMaker
class OpticsTools(QtWidgets.QMainWindow, QtCore.QObject, Ui_OpticsGUI):
sigStatus = QtCore.pyqtSignal(str)
def __init__(self,phase=0, office= 1):
super(OpticsTools, self).__init__()
self.setupUi(self)
office = office == 1
if phase > 0:
office = True
self.version = '1.1.1'
self.setWindowIcon(QtGui.QIcon("rsc/iconoptics.png"))
self.plot = OpticsPlot(parent=self)
self.plot.show()
# initialize online model
self.model = Model(phase=phase,parent=self)
# initialize modeling
self.match = MatchMaker(signal=self.sigStatus)
self.UIMatchOpticsSelect.clear()
for key in self.match.matchlist.keys():
self.UIMatchOpticsSelect.addItem(key)
if phase == 0:
self.UIMatchOpticsSelect.setCurrentIndex(0)
else:
self.UIMatchOpticsSelect.setCurrentIndex(1)
self.updateMatchingCase()
self.machine = Machine(parent = True, office = office)
self.machine.initPVs(self.model.getElements())
self.sandbox = Sandbox(parent = self, machine = self.machine)
self.sandbox.updateSandbox()
self.reference = ReferenceManager(parent=self)
self.reference.initReferencePoints(self.match)
self.updateMatchVariables()
title = "SwissFEL Optics Tools - Lattice %s (Phase %d)" % (self.model.getLatticeVersion(),phase)
if office:
title += " - offline"
self.setWindowTitle(title)
# events handling
self.UIMatchOpticsSelect.currentIndexChanged.connect(self.updateMatchingCase)
self.UIMatchSelected.clicked.connect(self.doMatch)
self.actionOpen_2.triggered.connect(self.loadSettings)
self.actionSave.triggered.connect(self.saveSettings)
self.UIUpdateFromMachine.clicked.connect(self.fullUpdate)
self.actionHelp.triggered.connect(self.openGit)
self.actionAbout.triggered.connect(self.about)
self.actionOpenScriptEditor.triggered.connect(self.editMatchingScript)
self.sigStatus.connect(self.status)
def doMatch(self):
"""
match the lattice for the given matching scripts. These can be selected by the individual check boxed.
The matching is in the order: Injector -> Athos -> Porthos -> Aramis.
The online model is updated with the updated match values.
:return: None
"""
injector = self.UIMatchInjector.isChecked()
aramis = self.UIMatchAramis.isChecked()
athos = self.UIMatchAthos.isChecked()
porthos = self.UIMatchPorthos.isChecked()
if self.UIInitAllMagnets.isChecked():
self.model.initializeMagnets()
if self.UIModifyKnobs.isChecked():
vars = self.getMatchVariables()
else:
vars = None
twiss = self.match.match(self.model.om, variables = vars, Injector = injector,Athos = athos, Aramis = aramis, Porthos = porthos)
self.updateMatchResult(self.match.matchresult)
energy = self.model.calcEnergyProfile(twiss)
#enfore the writing of a new lattice so that the magnet settings are in the new lattice
self.model.forceLat = True
self.plot.newData(twiss, energy)
if self.UISaveMatchSettings.isChecked():
fileName = self.match.scriptdir+'/settings.json'
self.saveSettingsdirect(fileName)
def updateMatchResult(self,result):
self.UIReportMatchResult.clear()
for ele in result:
label = '%s (%6.2e)' % (ele['Location'],ele['Error'])
listitem = QtWidgets.QListWidgetItem(label)
color = QtGui.QColor(100, 255, 100) # white
if ele['Error'] > 1e-5:
color = QtGui.QColor(255, 255, 100)
if ele['Error'] > 1:
color = QtGui.QColor(255, 100, 100)
listitem.setBackground(color)
self.UIReportMatchResult.addItem(listitem)
def updateMatchingCase(self):
"""
Update the check box for selecting the different matching steps and initial settings if reference file is present
:return: None
"""
target = self.UIMatchOpticsSelect.currentText()
self.match.initScripts(target)
self.updateMatchingCaseScript(self.UIMatchInjector,self.match.scriptInjector)
self.updateMatchingCaseScript(self.UIMatchAthos, self.match.scriptAthos)
self.updateMatchingCaseScript(self.UIMatchAramis, self.match.scriptAramis)
self.updateMatchingCaseScript(self.UIMatchPorthos, self.match.scriptPorthos)
def updateMatchingCaseScript(self,widget,state):
"""
Generalized routine to select and enable checkbox widgets
:param widget: checkbox widget
:param state: True or False
:return: None
"""
widget.setChecked(state)
widget.setEnabled(state)
def updateMatchVariables(self):
nrow = len(self.match.variables.keys())
self.UIMatchKnobs.setRowCount(nrow)
for irow,key in enumerate(self.match.variables.keys()):
self.UIMatchKnobs.setItem(irow, 0, QtWidgets.QTableWidgetItem(key))
self.UIMatchKnobs.setItem(irow, 1, QtWidgets.QTableWidgetItem('%f' % self.match.variables[key]['Value']))
self.UIMatchKnobs.item(irow, 0).setToolTip(self.match.variables[key]['Description'])
self.UIMatchKnobs.resizeColumnsToContents()
def getMatchVariables(self):
variables={}
nrow = self.UIMatchKnobs.rowCount()
for irow in range(nrow):
name = str(self.UIMatchKnobs.item(irow,0).text())
val = float(str(self.UIMatchKnobs.item(irow, 1).text()))
variables[name]={'Val':val}
return variables
def closeEvent(self, event):
self.plot.close()
event.accept()
def about(self):
QtWidgets.QMessageBox.about(self, "Optics Tool",
"Version:%s\nContact: Sven Reiche\nEmail: sven.reiche@psi.ch" % self.version)
def openGit(self):
webbrowser.open("https://gitea.psi.ch/reiche/opticstool")
def editMatchingScript(self):
options = QtWidgets.QFileDialog.Options()
options |= QtWidgets.QFileDialog.DontUseNativeDialog
fileName, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open Matching Script File",
"Scripts/switchyard.madx",
"MadX Files (*.madx)", options=options)
if not fileName:
return
subprocess.Popen(["emacs", fileName])
##################################################
def saveSettings(self):
options = QtWidgets.QFileDialog.Options()
options |= QtWidgets.QFileDialog.DontUseNativeDialog
fileName, _ = QtWidgets.QFileDialog.getSaveFileName(self, "Save Settings",
"Settings/newSetting.json",
"Json Files (*.json)", options=options)
if not fileName:
return
self.saveSettingdirect(fileName)
def loadSettings(self):
options = QtWidgets.QFileDialog.Options()
options |= QtWidgets.QFileDialog.DontUseNativeDialog
fileName, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open Settings",
"Settings/ReferenceSetting.json",
"Json Files (*.json)", options=options)
if not fileName:
return
self.loadSettingsdirect(fileName)
def loadSettingsdirect(self,fileName):
with open(fileName, 'r', encoding='utf-8') as f:
settings = json.load(f)
self.model.loadSettings(settings)
self.status('Reference loaded')
def saveSettingsdirect(self,fileName):
settings = self.model.getSettings()
with open(fileName, 'w', encoding='utf-8') as f:
json.dump(settings, f, ensure_ascii=False, indent=4)
####################################################
def fullUpdate(self):
machine = self.machine.getMachineStatus()
self.model.updateFromMachine(machine)
self.sandbox.updateSandbox()
self.status('Machine Settings')
@QtCore.pyqtSlot(str)
def status(self,msg):
self.UIStatus.setText(msg)
# --------------------------------
# Main routine
if __name__ == '__main__':
QtWidgets.QApplication.setStyle(QtWidgets.QStyleFactory.create("plastique"))
parser = ArgumentParser()
parser.add_argument('-phase', type=int, help='Select Phase of the Lattice', default=0)
parser.add_argument('-offline', type=int, help='Excludes any connection to control system', default=1)
args = parser.parse_args()
app = QtWidgets.QApplication(sys.argv)
main = OpticsTools(phase = args.phase, office = args.offline)
if main:
main.show()
sys.exit(app.exec_())