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,phase=phase) self.UIMatchOpticsSelect.clear() for key in self.match.matchlist.keys(): self.UIMatchOpticsSelect.addItem(key) self.UIMatchOpticsSelect.setCurrentIndex(0) 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) self.model.checkMagnetLimit() 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) if not self.match.settings is None: self.loadSettingsdirect(self.match.settings) 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) self.match.referencePoints.clear() self.match.referencePoints['swissfel$start'] = {'Twiss': self.model.startTwiss,'Label': 'Start'} self.reference.initReferencePoints(self.match) def loadSettingsdirect(self,fileName): with open(fileName, 'r', encoding='utf-8') as f: settings = json.load(f) status=self.model.loadSettings(settings) if status: print('Reference loaded from file %s' % fileName) self.status('Reference file loaded') else: print('Reference cannot be loaded from file %s' % fileName) print('Missing or mismatched phase of settings file with model') self.status('Phase Mismatch in Setting') 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_())