From 592c49af05cc3a88a1e2fa381a8274b9ce0c073f Mon Sep 17 00:00:00 2001 From: chrin Date: Tue, 6 Feb 2024 16:51:29 +0100 Subject: [PATCH] continued devl --- .gitignore | 5 + enumkind.py | 62 ++++++++++ guiheader.py | 293 +++++++++++++++++++++++++++++++++++++++++++++ sendelogproscan.py | 248 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 608 insertions(+) create mode 100644 .gitignore create mode 100644 enumkind.py create mode 100644 guiheader.py create mode 100644 sendelogproscan.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..70ca11c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.*~ +*.*-* +*.*+* +__pycache__ +__pycache__/*.* diff --git a/enumkind.py b/enumkind.py new file mode 100644 index 0000000..e159621 --- /dev/null +++ b/enumkind.py @@ -0,0 +1,62 @@ + +from enum import IntEnum + +class ElogPROSCAN: + def __init__(self): + self.eintrag = self.Eintrag(0) + self.system = self.System(0) + self.effekt = self.Effekt(0) + + class Eintrag(IntEnum): + PROBLEM = 0 + INFO = 1 + TUNING = 2 + OPERATION_CHANGE = 3 + BRIDGE = 4 + PIKETT = 5 + STATUS = 6 + INVENTORY = 7 + AK3 = 8 + TIPS = 9 + SHIFT_CHANGE = 10 + SHIFT_REPORT = 11 + + class System(IntEnum): + NONE = 0 + BEAMPROBE_COMMISSIOING = 1 + BEAMPROBE_RADIAL = 2 + DIAGNOSTICS_AND_STOPPER = 3 + EXTRACTION_ELEMENTS = 4 + GAS_SUPPLY = 5 + HF = 6 + INTERLOCK = 7 + ION_SOURCE = 8 + CONTROLS = 9 + CRYO_AND_MAGNET = 10 + COOLING = 11 + MAGNET_POWER_SUPPLIES = 12 + PASSG1 = 13 + PASSG2 = 14 + PASSG3 = 15 + PASSO2 = 16 + PASS_CONTROLROOM = 17 + PASS_FREI = 18 + PHASE_SLIT = 19 + PHASE_PROBE = 20 + PSYS = 21 + ACCEL_CIRCUIT = 22 + ACCEL_POWER_SUPPLIES = 23 + TRIMRODS = 24 + VACUUM = 25 + GENERAL = 26 + BEAM_LOSS = 27 + SU = 28 + OTHER = 29 + + + class Effekt(IntEnum): + NONE = 0 + BEAM_ABORT = 1 + DELAY = 2 + NO = 3 + OTHER = 4 diff --git a/guiheader.py b/guiheader.py new file mode 100644 index 0000000..c60e2cf --- /dev/null +++ b/guiheader.py @@ -0,0 +1,293 @@ +""" Gui header for PROSCAN +""" +from qtpy.QtCore import Qt, QTimer +from qtpy.QtGui import QFont +from qtpy.QtWidgets import ( + QApplication, QFrame, QGridLayout, QGroupBox, QHBoxLayout, QLabel, + QProxyStyle, QVBoxLayout, QWidget) + +from caqtwidgets.pvwidgets import CAQLabel +from pyqtacc.bdbase.enumkind import UserMode + +class GUIHeader(QWidget): + """ GUI Header Class + """ + def __init__(self, parent, user_mode=UserMode.OPERATION, extended=True): + super(GUIHeader, self).__init__() + self.parent = parent + self.appname = parent.appname + self.title = parent.title + self.settings = parent.settings + self.user_mode = user_mode + self.current_user_mode_name = user_mode.name + + self.extended = extended + self.cafe = parent.cafe + self.cyca = parent.cyca + + self.input_parameters = parent.input_parameters + self.input_labels = parent.input_labels + self.expert_parameters = parent.expert_parameters + self.expert_labels = parent.expert_labels + + self.header_groups = self.settings.data['header'] + self.header_status = [None] * len(self.header_groups) + self.header_row = {"COMET": 1, "G1": 2, "SH": 1, "G2": 2, "O2": 1, + "PIF": 0, "G3": 0, "MASTER": 1} + #self.header_row = {"COMET": 0, "G1": 0, "SH": 0, "G2": 0, "O2": 0, + # "PIF": 0, "G3": 0, "MASTER": 0} + + self.font_gui = QFont("sans serif") + self.font_gui.setPointSize(11) + self.font_pts10 = QFont("sans serif") + self.font_pts10.setPointSize(10) + self.widget_height = self.settings.data["StyleGuide"]["widgetHeight"] + self.extra_height = self.settings.data["StyleGuide"]["extraGroupHeight"] + + pvlist = [] + header_colors = [] + for sector in self.header_groups: + pvlist.append(self.settings.data[sector]["status"]) + header_colors.append(self.settings.data[sector]["colorObj"]) + + self.cafe.openPrepare() + self.cafe.open(pvlist) + self.cafe.openNowAndWait(0.4) + self.cafe.printDisconnected() + + self.station_width = 200 #default + self.station_height = 100 + self.hor_layout = QHBoxLayout() + self.grid_layout = QGridLayout() + self.beam_current_wgt_dict = {} + self.beam_current_wgt_group = {} + has_hbox_layout = False + + self.pframe_matrix = {"POST-COMET": {}, "G1": {}, "PRE-SH": {}, + "POST-SH": {}, "G2": {}, "PRE-O2": {}, + "O2": {}, "PRE-PIF": {}, "PRE-G3": {}} + + + def pframe(color="green"): + qframe = QFrame() + qframe.setStyleSheet("background-color: {0};".format(color)) + qframe.setFixedWidth(6) + qframe.setFixedHeight(6) + return qframe + + def draw_box(nheight: int = 10, nwidth: int = 8, area: str = None): + if area is None: + return + qw = QWidget() + qgrid = QGridLayout() + for i in range(0, nheight): + self.pframe_matrix[area][i] = {} + for j in range(0, nwidth): + self.pframe_matrix[area][i][j] = {} + pf = pframe('green') + qgrid.addWidget(pf, i, j, 1, 1, Qt.AlignCenter) + #if i==3 and j==3: + # pf.setVisible(False) + self.pframe_matrix[area][i][j] = True + + + qgrid.setSpacing(1) + qgrid.setContentsMargins(0, 0, 0, 0) + qgrid.setAlignment(Qt.AlignCenter) + qw.setLayout(qgrid) + return qw + + if has_hbox_layout: + for sector, pv, color in zip(self.header_groups, pvlist, + header_colors): + if sector == "MASTER": + qframe = QFrame() + qframe.setFixedWidth(200) + self.hor_layout.addWidget(qframe) + self.hor_layout.addWidget(self.beam_current_widget( + header=sector, pv= pv, color_obj=color)) + self.hor_layout.setSpacing(6) + self.hor_layout.setAlignment(Qt.AlignLeft) + self.hor_layout.setContentsMargins(5, 0, 5, 0) + else: + j = 0 + for (sector, pv, color) in ( + zip(self.header_groups, pvlist, header_colors)): + if sector == "MASTER": + qframe = QFrame() + qframe.setFixedWidth(100) + self.grid_layout.addWidget(qframe, self.header_row[sector], j, 2, 1) + j += 1 + + self.grid_layout.addWidget(self.beam_current_widget( + header=sector, pv= pv, color_obj=color), self.header_row[sector], j, 2, 1) + j += 1 + _align = Qt.AlignCenter + if sector in ["PIF"]: + qw = draw_box(nheight=10, nwidth=8, area="PRE-G3") + self.grid_layout.addWidget(qw, 0, j, 2, 1, _align) + j += 1 + elif sector in ["O2"]: + qw = draw_box(nheight=5, nwidth=16, area="PRE-PIF") + self.grid_layout.addWidget(qw, 0, j-1, 1, 1, _align) + qw = draw_box(nheight=10, nwidth=8, area="O2") + self.grid_layout.addWidget(qw, 0, j, 2, 1, _align) + j += 1 + elif sector in ["COMET"]: + qw = draw_box(nheight=10, nwidth=8, area="POST-COMET") + self.grid_layout.addWidget(qw, 1, j, 2, 1, _align) + j += 1 + elif sector in ["SH"]: + qw = draw_box(nheight=15, nwidth=10, area="POST-SH") + self.grid_layout.addWidget(qw, 0, j, 3, 1, _align) + j += 1 + elif sector in ["G1"]: + qw = draw_box(nheight=5,nwidth=16, area="G1") + self.grid_layout.addWidget(qw, 1, j-1, 1, 1, _align) + qw = draw_box(nheight=10, nwidth=8, area="PRE-SH") + self.grid_layout.addWidget(qw, 1, j, 2, 1, _align) + j += 1 + elif sector in ["G2"]: + + qw = draw_box(nheight=10, nwidth=16, area="G2") + self.grid_layout.addWidget(qw, 0, j-1, 2, 1, _align) + qw = draw_box(nheight=10, nwidth=8, area="PRE-O2") + self.grid_layout.addWidget(qw, 0, j, 2, 1, _align) + j += 1 + elif sector not in ["MASTER", "G3"]: + qw = draw_box() + self.grid_layout.addWidget(qw, 0, j, 2, 1, _align) + j += 1 + + if has_hbox_layout: + self.grid_layout.setSpacing(6) + else: + self.grid_layout.setSpacing(0) + self.grid_layout.setAlignment(Qt.AlignLeft) + self.grid_layout.setContentsMargins(5, 0, 5, 0) + + self.header_wgt = QGroupBox() + self.header_wgt.setObjectName(self.user_mode.name) + if has_hbox_layout: + self.header_wgt.setLayout(self.hor_layout) + self.header_wgt.setFixedHeight(110) + else: + self.header_wgt.setLayout(self.grid_layout) + self.header_wgt.setFixedHeight(160) + #self.header_wgt.setStyle(QProxyStyle()) + title = "PROSCAN {0}".format(self.user_mode.name) + if self.title: + title += ": {0}".format(self.title) + self.header_wgt.setTitle(title) + + print(self.pframe_matrix) + + self.target_beamline = None + + self.timer = QTimer() + #self.timer.timeout.connect(lambda: self.blink_target_beamline(None)) + self.timer.timeout.connect(self.blink_target_beamline) + self.timer.start(500) + self.timer_count = 0 + + self.master_to_wgt = {"Gantry 1": "G1", "Gantry 2": "G2", + "Gantry 3": "G3", "OPTIS2": "O2"} + + def blink_target_beamline(self): + target = self.target_beamline + if target is None: + return + self.target_beamline = target + self.timer_count += 1 + if self.timer_count < 2: + return + if self.timer_count % 2 == 0: + self.reset_beamline() + else: + self.set_target_beamline(reset=False) + if self.timer_count > 8: + self.timer.stop() + QApplication.processEvents() + + + def set_target_beamline(self, reset=True): + """ Select beamline target and modify color scheme accordinly + """ + print("set_target_beamline", self.target_beamline, flush=True) + master = self.beam_current_wgt_group[self.target_beamline] + master.setObjectName("ARAMIS") + master.style().polish(master) + + print("set_target_beamline//", self.target_beamline, flush=True) + + if reset: + self.reset_beamline() + + + def reset_beamline(self): + """ Reset QGroupBox for previous target to original colors + """ + print("reset_target_beamline", self.target_beamline, flush=True) + + master = self.beam_current_wgt_group[self.target_beamline] + + if "BEAMLINE" not in master.objectName(): + master.setObjectName("BEAMLINE") + master.style().polish(master) + print("reset_target_beamline/", self.target_beamline, flush=True) + + print("reset_target_beamline//", self.target_beamline, flush=True) + + def reset_operation_mode(self): + """ Reset header colors to application operation mode + """ + self.change_operation_mode(user_mode=self.user_mode) + + def change_operation_mode(self, user_mode=UserMode.OPERATION): + """ Different operation modes have different color schemes + """ + title_name = user_mode.name + dry_run_tags = ['HUSH', 'Dry'] + #print('title', self.header_wgt.title(), flush=True) + + if any([x in self.header_wgt.title() for x in dry_run_tags]): + title_name = 'DRY RUN' if user_mode.name == 'SIMULATION' \ + else user_mode.name + + + self.header_wgt.setObjectName(user_mode.name) + self.header_wgt.setTitle(self.header_wgt.title().replace( + self.current_user_mode_name, title_name)) + self.header_wgt.style().polish(self.header_wgt) + self.current_user_mode_name = title_name + + def beam_current_widget(self, header: str="", pv: str ="", + color_obj: str="MACHINE"): + """ QGroupBox encompassing beam current info + """ + station = QGroupBox() + station.setObjectName(color_obj.upper()) + station.setAlignment(Qt.AlignHCenter | Qt.AlignTop) + station.setFlat(False) + station.setTitle(header) + beam_current = CAQLabel(self, pv_name=pv, show_units=True) + beam_current.setFixedHeight(self.widget_height) + if header == "MASTER": + beam_current.setFixedWidth(140) + else: + beam_current.setFixedWidth(100) + + grid_layout = QGridLayout() + grid_layout.addWidget(beam_current, 0, 0, 1, 1, Qt.AlignCenter) + grid_layout.setVerticalSpacing(0) + grid_layout.setHorizontalSpacing(0) + grid_layout.setContentsMargins(2, 9, 2, 0) + + station.setLayout(grid_layout) + station.setFixedHeight(beam_current.height() + self.extra_height) + station.setFixedWidth(beam_current.width() + 15) + self.beam_current_wgt_dict[header] = beam_current + self.beam_current_wgt_group[header] = station + return station + + diff --git a/sendelogproscan.py b/sendelogproscan.py new file mode 100644 index 0000000..7611afc --- /dev/null +++ b/sendelogproscan.py @@ -0,0 +1,248 @@ +import inspect +import os +import socket +import time + +from qtpy.QtCore import Qt +from qtpy.QtWidgets import (QComboBox, QDialog, QFileDialog, QHBoxLayout, + QLabel, QLineEdit, QPushButton, QTextEdit, + QVBoxLayout) + +import elog # https://github.com/paulscherrerinstitute/py_elog +from pyqtacc.bdbase.enumkind import MsgSeverity + +from pyqtacc.bdbase.sendelogframe import QSendToELOGFrame + +_version = "1.0.0" +_pymodule = os.path.basename(__file__) +_appname, _appext = _pymodule.split(".") +_hostname = socket.gethostname().split(".")[0] + +def _line(): + """Macro to return the current line number. + + The current line number within the file is used when + reporting messages to the message logging window. + + Returns: + int: Current line number. + """ + return inspect.currentframe().f_back.f_lineno + +class QSendToELOG(QSendToELOGFrame): + """ Graphical interface to elog + """ + def __init__(self, parent, logbook=None, eintragIdx=1, + systemIdx=0, statusIdx=0, effektIdx=0, title=None, + message=None, attachFile=None, destination_folder=None): + + super().__init__(parent, logbook=logbook, title=title, message=message) + + #First check what is the logbook being requested? + #find layout items + self.layout_items = self.get_logbook_specific_items(self.logbook) + self.layout_to_widget_dict = {} + + + self.eintrag_idx = eintragIdx + self.system_idx = systemIdx + self.effekt_idx = effektIdx + + self.status_idx = statusIdx + + + #print("indices", self.eintrag_idx, self.system_idx, flush=True) + + self.eintrag = None + self.system = None + self.effekt = None + self.estatus = None + self.konsole = None + + + + #print("LAYOUT ITEMS: ", self.layout_items) + + self.sim_list = ["Sand", "test"] + + self.initialize_layout(self.logbook) + self.exec() + + def reset_layout(self): + def remove_wgt(wgt): + self.layout.removeItem(wgt) + while wgt.count(): + item = wgt.takeAt(0) + widget = item.widget() + widget.deleteLater() + + for layout in self.get_logbook_specific_items(self.logbook): + remove_wgt(self.layout_to_widget_dict[layout]) + + + def create_layout_widgets(self): + + + if not self.eintrag: + self.eintrag = QHBoxLayout() + self.eintrag.addWidget(QLabel('Eintrag: ')) + self.eintrag_items = QComboBox() + self.eintrag_items.setObjectName("Elog") + self.eintrag.addWidget(self.eintrag_items) + self.layout_to_widget_dict['Eintrag'] = self.eintrag + + if not self.system: + self.system = QHBoxLayout() + self.system.addWidget(QLabel('System: ')) + self.system_items = QComboBox() + self.system_items.setObjectName("Elog") + self.system.addWidget(self.system_items) + self.layout_to_widget_dict['System'] = self.system + + if not self.effekt: + self.effekt = QHBoxLayout() + self.effekt.addWidget(QLabel('Effekt: ')) + self.effekt_items = QComboBox() + self.effekt_items.setObjectName('Elog') + self.effekt.addWidget(self.effekt_items) + self.layout_to_widget_dict['Effekt'] = self.effekt + + if not self.estatus: + self.estatus = QHBoxLayout() + self.estatus.addWidget(QLabel('Status: ')) + self.estatus_items = QComboBox() + self.estatus_items.setObjectName("Elog") + self.estatus.addWidget(self.estatus_items) + self.layout_to_widget_dict['Status'] = self.estatus + + ''' + if not self.konsole: + self.konsole = QHBoxLayout() + self.konsole.addWidget(QLabel('Konsole: ')) + self.konsole_le = QLineEdit() + self.konsole_le.setObjectName('Elog') + self.konsole_le.setText(_hostname) + self.konsole_le.setFixedWidth(100) + self.konsole_le.setAlignment(Qt.AlignCenter) + self.konsole.addWidget(self.konsole_le) + self.layout_to_widget_dict['Konsole'] = self.konsole + ''' + + def initialize_layout(self, logbook): + + print("initialize_layout", self.layout_items, flush=True) + + #Decide on layout + self.create_layout_widgets() + + item_no = 2 # if "Proscan" in logbook else 2 + + if 'Eintrag' in self.layout_items: + self.eintrag_items.clear() + self.eintrag_items.addItems(list(self.parent.settings.data[ + 'ElogBooks'][logbook]['Required']['Eintrag'])) + self.eintrag_items.setCurrentIndex(self.eintrag_idx) + self.layout.insertLayout(item_no, self.eintrag) + if 'Effekt' in self.layout_items: + item_no += 1 + self.effekt_items.clear() + self.effekt_items.addItems(list(self.parent.settings.data[ + 'ElogBooks'][logbook]['Optional']['Effekt'])) + self.effekt_items.setCurrentIndex(self.effekt_idx) + self.layout.insertLayout(item_no, self.effekt) + if 'System' in self.layout_items: + item_no += 1 + self.system_items.clear() + self.system_items.addItems(list(self.parent.settings.data[ + 'ElogBooks'][logbook]['Optional']['System'])) + self.system_items.setCurrentIndex(self.system_idx) + self.layout.insertLayout(item_no, self.system) + if 'Status' in self.layout_items: + item_no += 1 + self.estatus_items.clear() + self.estatus_items.addItems(list(self.parent.settings.data[ + 'ElogBooks'][logbook]['Optional']['Status'])) + self.estatus_items.setCurrentIndex(self.status_idx) + self.layout.insertLayout(item_no, self.estatus) + + if 'Konsole' in self.layout_items: + item_no += 1 + self.layout.insertLayout(item_no, self.konsole) + + print("self.attachFile==>", self.parent.attach_files) + + self.attachFile = self.parent.attach_files + self.filesE.clear() + self.files_text = '' + + if self.attachFile is not None: + _attachFile = [] + if isinstance(self.attachFile, str): + _attachFile.append(self.attachFile) + elif isinstance(self.attachFile, list): + _attachFile = self.attachFile + for i, file in enumerate(_attachFile): + _attach_base = os.path.basename(file) + if i > 0: + self.files_text += "\n" + self.files_text += str(_attach_base) + self.filesE.setText(self.files_text) + self.fflag = True + + if any(substring.upper() in logbook.upper() \ + for substring in self.sim_list): + _bgcolor = "QComboBox {background: plum; color : black;}" + else: + _bgcolor = "QComboBox {background: lightblue; color : black;}" + self.elog_items.setStyleSheet(_bgcolor) + + #have to remove widgets within layout too! + #https://riverbankcomputing.com/pipermail/pyqt/2009-November/025214.html + def on_elog_change(self, idx): + + #print(self.elog_items.currentText(), "new=", idx) + #print(self.elog_items.itemText(idx)) + #print(self.logbook) + + new_logbook = self.elog_items.itemText(idx) + + #Meet the new logbook. Same as the old logbook + if new_logbook == self.logbook: + return + + self.layout_items = self.get_logbook_specific_items(new_logbook) + print("self.layout_items", self.layout_items) + + + if any(substring.upper() in new_logbook.upper() \ + for substring in self.sim_list): + _bgcolor = "QComboBox {background: plum; color : black;}" + else: + _bgcolor = "QComboBox {background: lightblue; color : black;}" + + self.reset_layout() + self.initialize_layout(new_logbook) + + self.elog_items.setStyleSheet(_bgcolor) + self.logbook = new_logbook + + def ok(self): + + el = self.elog_items.currentText() + + + if 'Eintrag' in self.layout_items: + self.attributes['Eintrag'] = self.eintrag_items.currentText() + if 'Effekt' in self.layout_items: + self.attributes['Effekt'] = self.effekt_items.currentText() + if 'System' in self.layout_items: + self.attributes['System'] = self.system_items.currentText() + + if 'Status' in self.layout_items: + self.attributes['Status'] = self.estatus_items.currentText() + if 'Konsole' in self.layout_items: + self.attributes['Konsole'] = self.konsole_le.text() + + QSendToELOGFrame.ok(self) + +