From 11b5e5180ab24692ce2ce86029b51a8b3acb5ccc Mon Sep 17 00:00:00 2001 From: Andrey Gruzinov Date: Sat, 15 Jun 2024 19:41:54 +0200 Subject: [PATCH 01/19] Disabled view of Shutterless --- mxcubeqt/widgets/acquisition_widget.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mxcubeqt/widgets/acquisition_widget.py b/mxcubeqt/widgets/acquisition_widget.py index 27776e84b..651dc455c 100644 --- a/mxcubeqt/widgets/acquisition_widget.py +++ b/mxcubeqt/widgets/acquisition_widget.py @@ -498,9 +498,11 @@ def init_limits(self): self.set_tunable_energy(HWR.beamline.tunable_wavelength) has_shutter_less = HWR.beamline.detector.has_shutterless() - self.acq_widget_layout.shutterless_cbx.setEnabled(has_shutter_less) + self.acq_widget_layout.shutterless_cbx.setEnabled(False) self.acq_widget_layout.shutterless_cbx.setChecked(has_shutter_less) + + if HWR.beamline.disable_num_passes: num_passes = self.acq_widget_layout.findChild( qt_import.QLineEdit, "num_passes_ledit" From de9217f3da42c4d589c69aabbcad0caf1de3ac71 Mon Sep 17 00:00:00 2001 From: Andrey Gruzinov Date: Thu, 27 Jun 2024 12:46:28 +0200 Subject: [PATCH 02/19] Added Additional robot actions to the sample changer --- mxcubeqt/bricks/desy/p11_simple_brick.py | 58 +++++++++++++++++------- 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/mxcubeqt/bricks/desy/p11_simple_brick.py b/mxcubeqt/bricks/desy/p11_simple_brick.py index ef55e24cc..673acbf70 100644 --- a/mxcubeqt/bricks/desy/p11_simple_brick.py +++ b/mxcubeqt/bricks/desy/p11_simple_brick.py @@ -34,7 +34,12 @@ from mxcubeqt.base_components import BaseWidget from mxcubeqt.utils import colors, qt_import from mxcubeqt.utils import sample_changer_helper as sc_helper -from mxcubeqt.bricks.sample_changer_brick import SampleChangerBrick, BasketView, VialView, StatusView +from mxcubeqt.bricks.sample_changer_brick import ( + SampleChangerBrick, + BasketView, + VialView, + StatusView, +) from mxcubecore import HardwareRepository as HWR @@ -44,12 +49,10 @@ class P11SCStatusView(qt_import.QWidget): - # statusMsgChangedSignal = qt_import.pyqtSignal(str, qt_import.QColor) # resetSampleChangerSignal = qt_import.pyqtSignal() def __init__(self, parent, brick): - qt_import.QWidget.__init__(self, parent) self._parent = brick @@ -94,7 +97,6 @@ def __init__(self, parent, brick): # Qt signal/slot connections ------------------------------------------ - def set_expert_mode(self, expert): pass @@ -104,7 +106,6 @@ def setStatusMsg(self, status): self.setToolTip(status) def setState(self, state): - color = sc_helper.SC_STATE_COLOR.get(state, None) if color is None: @@ -121,10 +122,10 @@ def setState(self, state): def setIcons(self, *args): pass + class P11SimpleBrick(SampleChangerBrick): def __init__(self, *args): - - super(P11SimpleBrick,self).__init__(*args) + super(P11SimpleBrick, self).__init__(*args) self._powered_on = None self.state = sc_helper.SampleChangerState.Ready @@ -140,18 +141,18 @@ def __init__(self, *args): self.double_click_loads_cbox.hide() self.current_basket_view.hide() - #self.current_sample_view.hide() + # self.current_sample_view.hide() if HWR.beamline.sample_changer is not None: - #self.connect( - #HWR.beamline.sample_changer, - #"runningStateChanged", - #self._updatePathRunning, - #) + # self.connect( + # HWR.beamline.sample_changer, + # "runningStateChanged", + # self._updatePathRunning, + # ) self.connect( HWR.beamline.sample_changer, "powerStateChanged", - self._update_power_state + self._update_power_state, ) self._powered_on = HWR.beamline.sample_changer.is_powered() @@ -159,7 +160,7 @@ def __init__(self, *args): def build_status_view(self, container): return P11SCStatusView(container, self) - #return StatusView(container) + # return StatusView(container) def build_operations_widget(self): self.buttons_layout = qt_import.QHBoxLayout() @@ -168,16 +169,25 @@ def build_operations_widget(self): self.load_button = qt_import.QPushButton("Load", self) self.unload_button = qt_import.QPushButton("Unload", self) self.wash_button = qt_import.QPushButton("Wash", self) + self.home_button = qt_import.QPushButton("Home position", self) + self.cool_button = qt_import.QPushButton("Cool position", self) + self.deice_button = qt_import.QPushButton("Deice", self) self.abort_button = qt_import.QPushButton("Abort", self) self.load_button.clicked.connect(self.load_selected_sample) self.unload_button.clicked.connect(self.unload_sample) self.wash_button.clicked.connect(self.wash_sample) + self.home_button.clicked.connect(self.home_robot) + self.cool_button.clicked.connect(self.cool_robot) + self.deice_button.clicked.connect(self.deice_robot) self.abort_button.clicked.connect(self.abort_mounting) self.buttons_layout.addWidget(self.load_button) self.buttons_layout.addWidget(self.unload_button) self.buttons_layout.addWidget(self.wash_button) + self.buttons_layout.addWidget(self.home_button) + self.buttons_layout.addWidget(self.cool_button) + self.buttons_layout.addWidget(self.deice_button) self.operation_buttons_layout.addLayout(self.buttons_layout) self.operation_buttons_layout.addWidget(self.abort_button) self.operations_widget.setLayout(self.operation_buttons_layout) @@ -217,6 +227,9 @@ def _update_buttons(self): self.load_button.setEnabled(False) self.unload_button.setEnabled(False) self.wash_button.setEnabled(False) + self.home_button.setEnabled(False) + self.cool_button.setEnabled(False) + self.deice_button.setEnabled(False) self.abort_button.setEnabled(False) abort_color = colors.LIGHT_GRAY elif ready: @@ -245,8 +258,8 @@ def load_selected_sample(self): logging.getLogger("GUI").info("Loading sample: %s / %s" % (basket, vial)) if basket is not None and vial is not None: - sample_loc = "%d:%d" % (basket, vial) - HWR.beamline.sample_changer.load(sample_loc, wait=False) + sample_loc = "%d:%d" % (basket, vial) + HWR.beamline.sample_changer.load(sample_loc, wait=False) def unload_sample(self): logging.getLogger("GUI").info("Unloading sample") @@ -256,6 +269,17 @@ def wash_sample(self): logging.getLogger("GUI").info("Washing sample") HWR.beamline.sample_changer.wash() + def home_robot(self): + logging.getLogger("GUI").info("Home robot") + HWR.beamline.sample_changer.home() + + def cool_robot(self): + logging.getLogger("GUI").info("Cool robot") + HWR.beamline.sample_changer.cool() + + def deice_robot(self): + logging.getLogger("GUI").info("Deice robot") + HWR.beamline.sample_changer.deice() def abort_mounting(self): HWR.beamline.sample_changer._do_abort() From eaf698b974a3cb2c7271f37f1aab106a0257dd12 Mon Sep 17 00:00:00 2001 From: Andrey Gruzinov Date: Thu, 27 Jun 2024 12:46:59 +0200 Subject: [PATCH 03/19] Black --- mxcubeqt/bricks/desy/digital_zoom_brick.py | 11 ++++++----- mxcubeqt/bricks/desy/p11_deice_brick.py | 1 - mxcubeqt/bricks/desy/p11_proposal_brick.py | 8 +++++--- mxcubeqt/bricks/desy/value_state_brick.py | 1 - 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/mxcubeqt/bricks/desy/digital_zoom_brick.py b/mxcubeqt/bricks/desy/digital_zoom_brick.py index b60500da6..8d041a3f7 100644 --- a/mxcubeqt/bricks/desy/digital_zoom_brick.py +++ b/mxcubeqt/bricks/desy/digital_zoom_brick.py @@ -30,7 +30,6 @@ class DigitalZoomBrick(BaseWidget): - STATE_COLORS = ( colors.LIGHT_YELLOW, # INITIALIZING colors.LIGHT_GREEN, # ON @@ -129,16 +128,19 @@ def setToolTip(self, name=None, state=None): self.label.setToolTip(tip) def motor_state_changed(self, state): - # self.positions_combo.setEnabled(self.motor_hwobj.is_ready()) if self.motor_hwobj.is_ready: colors.set_widget_color( - self.positions_combo, colors.LIGHT_GREEN, qt_import.QPalette.Button, + self.positions_combo, + colors.LIGHT_GREEN, + qt_import.QPalette.Button, ) else: colors.set_widget_color( - self.positions_combo, colors.LIGHT_GRAY, qt_import.QPalette.Button, + self.positions_combo, + colors.LIGHT_GRAY, + qt_import.QPalette.Button, ) # self.setToolTip(state=state) @@ -242,7 +244,6 @@ def position_selected(self, index): self.previous_position_button.setEnabled(index >= 0) def predefined_position_changed(self, position, offset): - if self.positions: for index, item in enumerate(self.positions): if position.name == item.name: diff --git a/mxcubeqt/bricks/desy/p11_deice_brick.py b/mxcubeqt/bricks/desy/p11_deice_brick.py index 029e8d981..723020a5c 100644 --- a/mxcubeqt/bricks/desy/p11_deice_brick.py +++ b/mxcubeqt/bricks/desy/p11_deice_brick.py @@ -32,7 +32,6 @@ class P11DeiceBrick(DuoStateBrick): - STATES = { "unknown": (colors.LIGHT_GRAY, True, True, False, False), "disabled": (colors.LIGHT_GRAY, False, False, False, False), diff --git a/mxcubeqt/bricks/desy/p11_proposal_brick.py b/mxcubeqt/bricks/desy/p11_proposal_brick.py index 5e75a356b..c15d79222 100644 --- a/mxcubeqt/bricks/desy/p11_proposal_brick.py +++ b/mxcubeqt/bricks/desy/p11_proposal_brick.py @@ -24,6 +24,7 @@ from mxcubeqt.bricks.proposal_brick import ProposalBrick, ProposalGUIEvent from mxcubecore import HardwareRepository as HWR + class P11ProposalBrick(ProposalBrick): def __init__(self, *args): super(P11ProposalBrick, self).__init__(*args) @@ -89,7 +90,6 @@ def run(self): qt_import.QApplication.postEvent(self, start_server_event) def p11_login_as_proposal(self): - if HWR.beamline.lims.simulated_proposal == 1: proposal_code = HWR.beamline.lims.simulated_prop_code proposal_number = HWR.beamline.lims.simulated_prop_number @@ -103,11 +103,13 @@ def p11_login_as_proposal(self): ) self._do_login_as_proposal( - proposal_code, proposal_number, None, HWR.beamline.lims.beamline_name, + proposal_code, + proposal_number, + None, + HWR.beamline.lims.beamline_name, ) def show_selected_proposal(self, proposal): - beamtime_id = HWR.beamline.session.get_current_beamtime_id() prop_number = str(proposal["number"]) prop_code = str(proposal["code"]) diff --git a/mxcubeqt/bricks/desy/value_state_brick.py b/mxcubeqt/bricks/desy/value_state_brick.py index f573a4383..c30bf26a9 100644 --- a/mxcubeqt/bricks/desy/value_state_brick.py +++ b/mxcubeqt/bricks/desy/value_state_brick.py @@ -32,7 +32,6 @@ class ValueStateBrick(BaseWidget): - STATE_COLORS = ( colors.LIGHT_RED, # ALARM colors.LIGHT_YELLOW, # WARNING From cbbbd48a04cdb2597c4d124cf86a655a9f7f963a Mon Sep 17 00:00:00 2001 From: Andrey Gruzinov Date: Tue, 3 Sep 2024 13:03:51 +0200 Subject: [PATCH 04/19] Fixing explicit int conversion in python > 3.10 --- mxcubeqt/bricks/progress_bar_brick.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mxcubeqt/bricks/progress_bar_brick.py b/mxcubeqt/bricks/progress_bar_brick.py index 931307f78..9835ac55c 100644 --- a/mxcubeqt/bricks/progress_bar_brick.py +++ b/mxcubeqt/bricks/progress_bar_brick.py @@ -76,7 +76,7 @@ def step_progress(self, step, msg=None): # f self.use_dialog: # BaseWidget.set_progress_dialog_step(step) # lse: - self.progress_bar.setValue(step) + self.progress_bar.setValue(int(step)) self.setEnabled(True) # BaseWidget.set_progress_bar_step(step) From efe9f226da3dba7038e71673ac324eb97cad3ccc Mon Sep 17 00:00:00 2001 From: Andrey Gruzinov Date: Tue, 10 Sep 2024 12:30:03 +0200 Subject: [PATCH 05/19] Removed unused brick --- mxcubeqt/bricks/desy/p11_deice_brick.py | 675 ------------------------ 1 file changed, 675 deletions(-) delete mode 100644 mxcubeqt/bricks/desy/p11_deice_brick.py diff --git a/mxcubeqt/bricks/desy/p11_deice_brick.py b/mxcubeqt/bricks/desy/p11_deice_brick.py deleted file mode 100644 index 723020a5c..000000000 --- a/mxcubeqt/bricks/desy/p11_deice_brick.py +++ /dev/null @@ -1,675 +0,0 @@ -# -# Project: MXCuBE -# https://github.com/mxcube -# -# This file is part of MXCuBE software. -# -# MXCuBE is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# MXCuBE is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with MXCuBE. If not, see . - -import re - -from mxcubeqt.utils import colors, icons, qt_import -from mxcubeqt.base_components import BaseWidget - -from mxcubeqt.utils import colors, icons, qt_import -from mxcubeqt.bricks.proposal_brick import DuoStateBrick, ProposalGUIEvent -from mxcubecore import HardwareRepository as HWR -import logging -import os - -__category__ = "DESY" - - -class P11DeiceBrick(DuoStateBrick): - STATES = { - "unknown": (colors.LIGHT_GRAY, True, True, False, False), - "disabled": (colors.LIGHT_GRAY, False, False, False, False), - "noperm": (colors.LIGHT_GRAY, False, False, False, False), - "error": (colors.LIGHT_RED, False, False, False, False), - "out": (colors.LIGHT_GREEN, True, False, False, True), - "closed": (colors.LIGHT_GREEN, True, True, False, True), - "moving": (colors.LIGHT_YELLOW, False, False, None, None), - "in": (colors.LIGHT_GREEN, False, True, True, False), - "opened": (colors.LIGHT_GREEN, True, True, True, False), - "automatic": (colors.WHITE, True, True, False, False), - } - - def __init__(self, *args): - BaseWidget.__init__(self, *args) - - # Hardware objects ---------------------------------------------------- - self.wrapper_hwobj = None - - # Internal values ----------------------------------------------------- - self.__expertMode = False - - # Properties ---------------------------------------------------------- - self.add_property("mnemonic", "string", "") - self.add_property("forceNoControl", "boolean", False) - self.add_property("expertModeControlOnly", "boolean", False) - self.add_property("icons", "string", "") - self.add_property("in", "string", "in") - self.add_property("out", "string", "out") - self.add_property("setin", "string", "Set in") - self.add_property("setout", "string", "Set out") - self.add_property("username", "string", "") - - # Signals ------------------------------------------------------------- - - # Slots --------------------------------------------------------------- - self.define_slot("allowControl", ()) - - # Graphic elements ---------------------------------------------------- - self.main_gbox = qt_import.QGroupBox("none", self) - self.main_gbox.setAlignment(qt_import.Qt.AlignCenter) - self.state_ledit = qt_import.QLineEdit("unknown", self.main_gbox) - - self.buttons_widget = qt_import.QWidget(self.main_gbox) - self.set_in_button = qt_import.QPushButton("Set in", self.buttons_widget) - self.set_in_button.setCheckable(True) - self.set_out_button = qt_import.QPushButton("Set out", self.buttons_widget) - self.set_out_button.setCheckable(True) - - # Layout -------------------------------------------------------------- - _buttons_widget_hlayout = qt_import.QHBoxLayout(self.buttons_widget) - _buttons_widget_hlayout.addWidget(self.set_in_button) - _buttons_widget_hlayout.addWidget(self.set_out_button) - _buttons_widget_hlayout.setSpacing(0) - _buttons_widget_hlayout.setContentsMargins(0, 0, 0, 0) - - _main_gbox_vlayout = qt_import.QVBoxLayout(self.main_gbox) - _main_gbox_vlayout.addWidget(self.state_ledit) - _main_gbox_vlayout.addWidget(self.buttons_widget) - _main_gbox_vlayout.setSpacing(2) - _main_gbox_vlayout.setContentsMargins(4, 4, 4, 4) - - _main_vlayout = qt_import.QVBoxLayout(self) - _main_vlayout.addWidget(self.main_gbox) - _main_vlayout.setSpacing(0) - _main_vlayout.setContentsMargins(0, 0, 0, 0) - - # SizePolicies -------------------------------------------------------- - - # Qt signal/slot connections ------------------------------------------ - self.set_in_button.toggled.connect(self.set_in) - self.set_out_button.toggled.connect(self.set_out) - - # Other --------------------------------------------------------------- - self.state_ledit.setAlignment(qt_import.Qt.AlignCenter) - self.state_ledit.setToolTip("Shows the current control state") - self.state_ledit.setFrame(False) - bold_font = self.state_ledit.font() - bold_font.setBold(True) - self.state_ledit.setFont(bold_font) - self.state_ledit.setFixedHeight(24) - - self.set_in_button.setToolTip("Changes the control state") - self.set_out_button.setToolTip("Changes the control state") - - self.instance_synchronize("state_ledit") - - def setExpertMode(self, expert): - self.__expertMode = expert - self.buttons_widget.show() - - if not expert and self["expertModeControlOnly"]: - self.buttons_widget.hide() - - def set_in(self, state): - if state: - self.set_in_button.setEnabled(False) - self.wrapper_hwobj.setIn() - else: - self.set_in_button.blockSignals(True) - # self.set_in_button.setState(QtGui.QPushButton.On) - self.set_in_button.setDown(True) - self.set_in_button.blockSignals(False) - - def set_out(self, state): - if state: - self.set_out_button.setEnabled(False) - self.wrapper_hwobj.setOut() - else: - self.set_out_button.blockSignals(True) - self.set_out_button.setDown(False) - # self.set_out_button.setState(QtGui.QPushButton.On) - self.set_out_button.blockSignals(False) - - def updateLabel(self, label): - self.main_gbox.setTitle(label) - - def stateChanged(self, state, state_label=""): - self.setEnabled(True) - state = str(state) - try: - color = self.STATES[state][0] - except KeyError: - state = "unknown" - color = self.STATES[state][0] - if color is None: - color = colors.GROUP_BOX_GRAY - - colors.set_widget_color(self.state_ledit, color, qt_import.QPalette.Base) - # self.state_ledit.setPaletteBackgroundColor(QColor(color)) - if len(state_label) > 0: - self.state_ledit.setText("%s" % state_label) - else: - label_str = state - if state == "in": - prop_label = self["in"].strip() - if len(prop_label.strip()): - label_str = prop_label - if state == "out": - prop_label = self["out"].strip() - if prop_label: - label_str = prop_label - self.state_ledit.setText("%s" % label_str) - - if state in self.STATES: - in_enable = self.STATES[state][1] - out_enable = self.STATES[state][2] - else: - in_enable = False - out_enable = False - - self.set_in_button.setEnabled(in_enable) - self.set_out_button.setEnabled(out_enable) - - if state in self.STATES: - in_state = self.STATES[state][3] - out_state = self.STATES[state][4] - else: - in_state = True - out_state = False - if in_state is not None: - self.set_in_button.blockSignals(True) - self.set_in_button.setChecked(in_state) - self.set_in_button.blockSignals(False) - if out_state is not None: - self.set_out_button.blockSignals(True) - self.set_out_button.setChecked(out_state) - self.set_out_button.blockSignals(False) - - """ - if state=='in': - self.duoStateBrickMovingSignal.emit(False) - self.duoStateBrickInSignal.emit(True) - elif state=='out': - self.duoStateBrickMovingSignal.emit(False) - self.duoStateBrickOutSignal.emit(True) - elif state=='moving': - self.duoStateBrickMovingSignal.emit(True) - elif state=='error' or state=='unknown' or state=='disabled': - self.duoStateBrickMovingSignal.emit(False) - self.duoStateBrickInSignal.emit(False) - self.duoStateBrickOutSignal.emit(False) - """ - - def allowControl(self, enable): - if self["forceNoControl"]: - return - if enable: - self.buttons_widget.show() - else: - self.buttons_widget.hide() - - def property_changed(self, property_name, old_value, new_value): - if property_name == "mnemonic": - if self.wrapper_hwobj is not None: - self.wrapper_hwobj.duoStateChangedSignal.disconnect(self.stateChanged) - - h_obj = self.get_hardware_object(new_value) - if h_obj is not None: - self.wrapper_hwobj = WrapperHO(h_obj) - self.main_gbox.show() - - if self["username"] == "": - self["username"] = self.wrapper_hwobj.username - - help_text = self["setin"] + " the " + self["username"].lower() - self.set_in_button.setToolTip(help_text) - help_text = self["setout"] + " the " + self["username"].lower() - self.set_out_button.setToolTip(help_text) - self.main_gbox.setTitle(self["username"]) - self.wrapper_hwobj.duoStateChangedSignal.connect(self.stateChanged) - self.wrapper_hwobj.get_state() - else: - self.wrapper_hwobj = None - # self.main_gbox.hide() - elif property_name == "expertModeControlOnly": - if new_value: - if self.__expertMode: - self.buttons_widget.show() - else: - self.buttons_widget.hide() - else: - self.buttons_widget.show() - elif property_name == "forceNoControl": - if new_value: - self.buttons_widget.hide() - else: - self.buttons_widget.show() - elif property_name == "icons": - # w = self.fontMetrics().width("Set out") - icons_list = new_value.split() - try: - self.set_in_button.setIcon(icons.load_icon(icons_list[0])) - except IndexError: - self.set_in_button.setText(self["setin"]) - # self.set_in_button.setMinimumWidth(w) - try: - self.set_out_button.setIcon(icons.load_icon(icons_list[1])) - except IndexError: - self.set_out_button.setText(self["setout"]) - # self.set_out_button.setMinimumWidth(w) - - # elif property_name=='in': - # if self.wrapper_hwobj is not None: - # self.stateChanged(self.wrapper_hwobj.get_state()) - - # elif property_name=='out': - # if self.wrapper_hwobj is not None: - # self.stateChanged(self.wrapper_hwobj.get_state()) - - elif property_name == "setin": - # w=self.fontMetrics().width("Set out") - icons_list = self["icons"] - try: - i = icons_list[0] - except IndexError: - self.set_in_button.setText(new_value) - # self.set_in_button.setMinimumWidth(w) - help_text = new_value + " the " + self["username"].lower() - self.set_in_button.setToolTip(help_text) - self.set_in_button.setText(self["setin"]) - - elif property_name == "setout": - # w=self.fontMetrics().width("Set out") - icons_list = self["icons"].split() - try: - i = icons_list[1] - except IndexError: - self.set_out_button.setText(new_value) - # self.set_out_button.setMinimumWidth(w) - help_text = new_value + " the " + self["username"].lower() - self.set_out_button.setToolTip(help_text) - self.set_out_button.setText(self["setout"]) - - elif property_name == "username": - if new_value == "": - if self.wrapper_hwobj is not None: - name = self.wrapper_hwobj.username - if name != "": - self["username"] = name - return - help_text = self["setin"] + " the " + new_value.lower() - self.set_in_button.setToolTip(help_text) - help_text = self["setout"] + " the " + new_value.lower() - self.set_out_button.setToolTip(help_text) - self.main_gbox.setTitle(self["username"]) - - else: - BaseWidget.property_changed(self, property_name, old_value, new_value) - - -### -# Wrapper around different hardware objects, to make them have the -# same behavior to the brick -### - - -class WrapperHO(qt_import.QObject): - DEVICE_MAP = { - "Device": "Procedure", - "SOLEILGuillotine": "Shutter", - "SoleilSafetyShutter": "Shutter", - "TangoShutter": "Shutter", - "ShutterEpics": "Shutter", - "MD2v4_FastShutter": "Shutter", - "TempShutter": "Shutter", - "EMBLSafetyShutter": "Shutter", - "MDFastShutter": "Shutter", - "WagoPneu": "WagoPneu", - "Shutter": "WagoPneu", - "SpecMotorWSpecPositions": "WagoPneu", - "Procedure": "WagoPneu", - } - - WAGO_STATE = {"in": "in", "out": "out", "unknown": "unknown"} - - SHUTTER_STATE = { - "fault": "error", - "opened": "in", - "noperm": "noperm", - "closed": "out", - "unknown": "unknown", - "moving": "moving", - "automatic": "automatic", - "disabled": "disabled", - "error": "error", - } - DOOR_INTERLOCK_STATE = { - "locked": "out", - "unlocked": "disabled", - "locked_active": "out", - "locked_inactive": "disabled", - "error": "error", - } - - MOTOR_WPOS = ("out", "in") - MOTOR_WSTATE = ("disabled", "error", None, "moving", "moving", "moving") - - STATES = ( - "unknown", - "disabled", - "closed", - "error", - "out", - "moving", - "in", - "automatic", - "noperm", - ) - - duoStateChangedSignal = qt_import.pyqtSignal(str, str) - - def __init__(self, hardware_obj): - qt_import.QObject.__init__(self) - - # self.setIn = new.instancemethod(lambda self: None, self) - self.setIn = lambda self: None - self.setOut = self.setIn - # self.get-State = new.instancemethod(lambda self: "unknown", self) - self.get_state = lambda self: "unknown" - self.dev = hardware_obj - try: - sClass = str(self.dev.__class__) - i, j = re.search("'.*'", sClass).span() - except BaseException: - dev_class = sClass - else: - dev_class = sClass[i + 1 : j - 1] - self.devClass = dev_class.split(".")[-1] - - self.devClass = WrapperHO.DEVICE_MAP.get(self.devClass, "Shutter") - - initFunc = getattr(self, "init%s" % self.devClass) - initFunc() - self.setIn = getattr(self, "setIn%s" % self.devClass) - self.setOut = getattr(self, "setOut%s" % self.devClass) - self.get_state = getattr(self, "getState%s" % self.devClass) - - def __getstate__(self): - dict = self.__dict__.copy() - del dict["setIn"] - del dict["setOut"] - del dict["getState"] - return dict - - def __setstate__(self, dict): - self.__dict__ = dict.copy() - try: - # Python2 - import new - - self.setIn = new.instancemethod(lambda self: None, self) - self.setOut = self.setIn - self.get_state = new.instancemethod(lambda self: "unknown", self) - except ImportError: - import types - - self.setIn = types.MethodType(lambda self: None, self) - self.setOut = self.setIn - self.get_state = types.MethodType(lambda self: "unknown", self) - - def userName(self): - return self.dev.username - - # WagoPneu HO methods - def initWagoPneu(self): - self.dev.connect(self.dev, "wagoStateChanged", self.stateChangedWagoPneu) - - def setInWagoPneu(self): - self.duoStateChangedSignal.emit("moving") - self.dev.wagoIn() - - def setOutWagoPneu(self): - self.duoStateChangedSignal.emit("moving") - self.dev.wagoOut() - - def stateChangedWagoPneu(self, state): - try: - state = WrapperHO.WAGO_STATE[state] - except KeyError: - state = "error" - self.duoStateChangedSignal.emit(state) - - def getStateWagoPneu(self): - state = self.dev.getWagoState() - try: - state = WrapperHO.WAGO_STATE[state] - except KeyError: - state = "error" - return state - - # Shutter HO methods - def initShutter(self): - self.dev.connect(self.dev, "shutterStateChanged", self.stateChangedShutter) - - def setInShutter(self): - self.dev.openShutter() - - def setOutShutter(self): - self.dev.closeShutter() - - def stateChangedShutter(self, state, state_label=None): - state = WrapperHO.SHUTTER_STATE.get(state, "unknown") - if not state_label: - state_label = "" - self.duoStateChangedSignal.emit(state, state_label) - - def getStateShutter(self): - state = self.dev.getShutterState() - try: - state = WrapperHO.SHUTTER_STATE[state] - except KeyError: - state = "error" - return state - - # SpecMotorWSpecPositions HO methods - def initSpecMotorWSpecPositions(self): - self.positions = None - self.dev.connect( - self.dev, - "predefinedPositionChanged", - self.position_changed_spec_motor_wspec_positions, - ) - self.dev.connect( - self.dev, "stateChanged", self.stateChangedSpecMotorWSpecPositions - ) - self.dev.connect( - self.dev, - "newPredefinedPositions", - self.new_predefined_spec_motor_wspec_positions, - ) - - def setInSpecMotorWSpecPositions(self): - if self.positions is not None: - self.dev.moveToPosition(self.positions[1]) - - def setOutSpecMotorWSpecPositions(self): - if self.positions is not None: - self.dev.moveToPosition(self.positions[0]) - - def stateChangedSpecMotorWSpecPositions(self, state): - # logging.info("stateChangedSpecMotorWSpecPositions %s" % state) - try: - state = WrapperHO.MOTOR_WSTATE[state] - except IndexError: - state = "error" - if state is not None: - self.duoStateChangedSignal.emit(state) - - def position_changed_spec_motor_wspec_positions(self, pos_name, pos): - if self.dev.get_state() != self.dev.READY: - return - state = "error" - if self.positions is not None: - for i in range(len(self.positions)): - if pos_name == self.positions[i]: - state = WrapperHO.MOTOR_WPOS[i] - self.duoStateChangedSignal.emit(state) - - def get_state_spec_motor_wspec_positions(self): - if self.positions is None: - return "error" - curr_pos = self.dev.get_current_position_name() - if curr_pos is None: - state = self.dev.get_state() - try: - state = WrapperHO.MOTOR_WSTATE[state] - except IndexError: - state = "error" - return state - else: - for i in range(len(self.positions)): - if curr_pos == self.positions[i]: - return WrapperHO.MOTOR_WPOS[i] - return "error" - - def new_predefined_spec_motor_wspec_positions(self): - self.positions = self.dev.get_predefined_positions_list() - self.position_changed_spec_motor_wspec_positions( - self.dev.get_current_position_name(), self.dev.get_value() - ) - - # Procedure HO methods - def init_procedure(self): - cmds = self.dev.get_commands() - - self.set_in_cmd = None - self.set_out_cmd = None - - try: - channel = self.dev.get_channel_object("dev_state") - except KeyError: - channel = None - self.stateChannel = channel - if self.stateChannel is not None: - self.state_dict = { - "OPEN": "in", - "CLOSED": "out", - "ERROR": "error", - "1": "in", - "0": "out", - } - self.stateChannel.connect_signal("update", self.channel_update) - else: - self.state_dict = {} - - for cmd in cmds: - if cmd.name() == "set in": - self.set_in_cmd = cmd - if self.stateChannel is not None: - self.set_in_cmd.connect_signal( - "commandReplyArrived", self.procedureSetInEnded - ) - self.set_in_cmd.connect_signal( - "commandBeginWaitReply", self.procedure_started - ) - self.set_in_cmd.connect_signal( - "commandFailed", self.procedure_aborted - ) - self.set_in_cmd.connect_signal( - "commandAborted", self.procedure_aborted - ) - elif cmd.name() == "set out": - self.set_out_cmd = cmd - if self.stateChannel is not None: - self.set_out_cmd.connect_signal( - "commandReplyArrived", self.procedure_set_out_ended - ) - self.set_out_cmd.connect_signal( - "commandBeginWaitReply", self.procedure_started - ) - self.set_out_cmd.connect_signal( - "commandFailed", self.procedure_aborted - ) - self.set_out_cmd.connect_signal( - "commandAborted", self.procedure_aborted - ) - - def channel_update(self, value): - try: - key = self.dev.statekey - except AttributeError: - pass - else: - try: - state = value[key] - except TypeError: - state = "error" - try: - state = self.state_dict[state] - except KeyError: - pass - self.duoStateChangedSignal.emit(state) - - def set_in_procedure(self): - if self.set_in_cmd is not None: - self.set_in_cmd() - - def set_out_procedure(self): - if self.set_out_cmd is not None: - self.set_out_cmd() - - """ - def stateChangedProcedure(self,state): - pass - """ - - def get_state_procedure(self): - if self.stateChannel is not None: - try: - state = self.stateChannel.get_value() - except BaseException: - state = "error" - else: - try: - key = self.dev.statekey - except AttributeError: - pass - else: - try: - state = state[key] - except TypeError: - state = "error" - try: - state = self.state_dict[state] - except KeyError: - pass - return state - return "unknown" - - def procedureSetInEnded(self, *args): - self.duoStateChangedSignal.emit("in") - - def procedure_set_out_ended(self, *args): - self.duoStateChangedSignal.emit("out") - - def procedure_started(self, *args): - self.duoStateChangedSignal.emit("moving") - - def procedure_aborted(self, *args): - self.duoStateChangedSignal.emit("error") From 489a93505d0e4b4b571168cb77e827f433b5f656 Mon Sep 17 00:00:00 2001 From: Andrey Gruzinov Date: Fri, 13 Sep 2024 13:57:42 +0200 Subject: [PATCH 06/19] Add changesto handle limits according to the changes in P11Transmission in PR#966 mxcubecore --- mxcubeqt/widgets/acquisition_ssx_widget.py | 12 +++++++----- mxcubeqt/widgets/acquisition_widget.py | 10 +++++++++- mxcubeqt/widgets/acquisition_widget_simple.py | 10 +++++++++- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/mxcubeqt/widgets/acquisition_ssx_widget.py b/mxcubeqt/widgets/acquisition_ssx_widget.py index f7fbdb8d2..fab973104 100644 --- a/mxcubeqt/widgets/acquisition_ssx_widget.py +++ b/mxcubeqt/widgets/acquisition_ssx_widget.py @@ -339,18 +339,20 @@ def update_energy_limits(self, limits): def update_transmission_limits(self, limits): """ - Updates transmission limits - :param limits: list of two floats - :return: None + Updates transmission limits. + + Args: + limits (tuple): A tuple containing the minimum and maximum transmission limits. """ - if limits: + if limits and limits[0] is not None and limits[1] is not None: self.transmission_validator.setBottom(limits[0]) self.transmission_validator.setTop(limits[1]) self.acq_widget_layout.transmission_ledit.setToolTip( "Transmission limits %0.2f : %0.2f %%\n" % (limits[0], limits[1]) - + "2 digits precision." ) self._acquisition_mib.validate_all() + else: + self.acq_widget_layout.transmission_ledit.setToolTip(r"Invalid transmission limits: {}".format(limits)) def update_resolution_limits(self, limits): """ diff --git a/mxcubeqt/widgets/acquisition_widget.py b/mxcubeqt/widgets/acquisition_widget.py index 651dc455c..2b731bd5b 100644 --- a/mxcubeqt/widgets/acquisition_widget.py +++ b/mxcubeqt/widgets/acquisition_widget.py @@ -662,7 +662,13 @@ def update_energy_limits(self, limits): self._acquisition_mib.validate_all() def update_transmission_limits(self, limits): - if limits: + """ + Updates the transmission limits in the validator. + + Args: + limits (tuple): A tuple containing the minimum and maximum transmission limits. + """ + if limits and limits[0] is not None and limits[1] is not None: self.transmission_validator.setBottom(limits[0]) self.transmission_validator.setTop(limits[1]) self.acq_widget_layout.transmission_ledit.setToolTip( @@ -670,6 +676,8 @@ def update_transmission_limits(self, limits): + "2 digits precision." ) self._acquisition_mib.validate_all() + else: + self.acq_widget_layout.transmission_ledit.setToolTip("Invalid transmission limits received: {}".format(limits)) def update_resolution_limits(self, limits): if limits: diff --git a/mxcubeqt/widgets/acquisition_widget_simple.py b/mxcubeqt/widgets/acquisition_widget_simple.py index e67060b2e..15ec06211 100644 --- a/mxcubeqt/widgets/acquisition_widget_simple.py +++ b/mxcubeqt/widgets/acquisition_widget_simple.py @@ -294,13 +294,21 @@ def update_energy_limits(self, limits): self._acquisition_mib.validate_all() def update_transmission_limits(self, limits): - if limits: + """ + Updates the transmission limits in the validator. + + Args: + limits (tuple): A tuple containing the minimum and maximum transmission limits. + """ + if limits and limits[0] is not None and limits[1] is not None: self.transmission_validator.setBottom(limits[0]) self.transmission_validator.setTop(limits[1]) self.acq_widget_layout.transmission_ledit.setToolTip( "Transmission limits %0.3f : %0.3f" % (limits[0], limits[1]) ) self._acquisition_mib.validate_all() + else: + self.acq_widget_layout.transmission_ledit.setToolTip(r"Invalid transmission limits received: {}".format(limits)) def update_resolution_limits(self, limits): if limits: From 7e072d425edb91a2950cb782fe32a0d4e24795fd Mon Sep 17 00:00:00 2001 From: Andrey Gruzinov Date: Fri, 13 Sep 2024 14:29:32 +0200 Subject: [PATCH 07/19] Update of p11 proposal brick to display the proposal in case the ispyb is not available. --- mxcubeqt/bricks/desy/p11_proposal_brick.py | 71 ++++++++++++++++------ 1 file changed, 53 insertions(+), 18 deletions(-) diff --git a/mxcubeqt/bricks/desy/p11_proposal_brick.py b/mxcubeqt/bricks/desy/p11_proposal_brick.py index c15d79222..41f66558b 100644 --- a/mxcubeqt/bricks/desy/p11_proposal_brick.py +++ b/mxcubeqt/bricks/desy/p11_proposal_brick.py @@ -90,24 +90,59 @@ def run(self): qt_import.QApplication.postEvent(self, start_server_event) def p11_login_as_proposal(self): - if HWR.beamline.lims.simulated_proposal == 1: - proposal_code = HWR.beamline.lims.simulated_prop_code - proposal_number = HWR.beamline.lims.simulated_prop_number - else: - proposal_code = HWR.beamline.session.get_current_proposal_code() - proposal_number = HWR.beamline.session.get_current_proposal_number() - - logging.getLogger("HWR").debug(" PROPOSAL BRICK - code is %s" % proposal_code) - logging.getLogger("HWR").debug( - " PROPOSAL BRICK - number is %s" % proposal_number - ) - - self._do_login_as_proposal( - proposal_code, - proposal_number, - None, - HWR.beamline.lims.beamline_name, - ) + try: + if HWR.beamline.lims.simulated_proposal == 1: + proposal_code = HWR.beamline.lims.simulated_prop_code + proposal_number = HWR.beamline.lims.simulated_prop_number + else: + proposal_code = HWR.beamline.session.get_current_proposal_code() + proposal_number = HWR.beamline.session.get_current_proposal_number() + + logging.getLogger("HWR").debug( + " PROPOSAL BRICK - code is %s" % proposal_code + ) + logging.getLogger("HWR").debug( + " PROPOSAL BRICK - number is %s" % proposal_number + ) + + # Fetch proposal data + prop = HWR.beamline.lims.get_proposal(proposal_code, proposal_number) + + # Check for ISPyB connection error + if prop["status"]["code"] == "error": + self.message_widget.setText("ISPyB is not connected.") + self.message_widget.show() + + # Display the available proposal info + self.show_selected_proposal(prop["Proposal"]) + + except Exception as e: + # Catch any errors and display them as warnings but proceed + logging.getLogger("HWR").error(f"Error logging in as proposal: {str(e)}") + self.message_widget.setText(f"Error: {str(e)}") + self.message_widget.show() + self.show_selected_proposal( + {"code": proposal_code, "number": proposal_number} + ) + + def show_selected_proposal(self, proposal): + """ + Display the selected proposal information even if incomplete. + """ + try: + beamtime_id = HWR.beamline.session.get_current_beamtime_id() + prop_code = str(proposal.get("code", "Unknown")) + prop_number = str(proposal.get("number", "Unknown")) + + prop_info = f"ID: {prop_code}-{prop_number} - BT_ID: {beamtime_id}" + + self.proposal_info.setText(prop_info) + self.proposal_info.show() + + except KeyError as e: + logging.getLogger("HWR").error(f"Missing proposal information: {str(e)}") + self.proposal_info.setText(f"ID: {prop_code}-Unknown - BT_ID: Unknown") + self.proposal_info.show() def show_selected_proposal(self, proposal): beamtime_id = HWR.beamline.session.get_current_beamtime_id() From 94a21a05bc9ae3600ac4a34c1b3686c84f628722 Mon Sep 17 00:00:00 2001 From: Andrey Gruzinov Date: Fri, 13 Sep 2024 15:50:43 +0200 Subject: [PATCH 08/19] Black --- mxcubeqt/bricks/desy/digital_zoom_brick.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/mxcubeqt/bricks/desy/digital_zoom_brick.py b/mxcubeqt/bricks/desy/digital_zoom_brick.py index 8d041a3f7..c61e306e8 100644 --- a/mxcubeqt/bricks/desy/digital_zoom_brick.py +++ b/mxcubeqt/bricks/desy/digital_zoom_brick.py @@ -132,15 +132,11 @@ def motor_state_changed(self, state): if self.motor_hwobj.is_ready: colors.set_widget_color( - self.positions_combo, - colors.LIGHT_GREEN, - qt_import.QPalette.Button, + self.positions_combo, colors.LIGHT_GREEN, qt_import.QPalette.Button ) else: colors.set_widget_color( - self.positions_combo, - colors.LIGHT_GRAY, - qt_import.QPalette.Button, + self.positions_combo, colors.LIGHT_GRAY, qt_import.QPalette.Button ) # self.setToolTip(state=state) From 3273fff04b19826dfefa0c9123590b18d8078335 Mon Sep 17 00:00:00 2001 From: Andrey Gruzinov Date: Fri, 13 Sep 2024 15:51:59 +0200 Subject: [PATCH 09/19] Added changes that are reflecting handling of NState instead of MultyNState (PR#966 mxcubecore) --- mxcubeqt/bricks/multi_state_brick.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/mxcubeqt/bricks/multi_state_brick.py b/mxcubeqt/bricks/multi_state_brick.py index e3ed53f36..052dd1fb7 100644 --- a/mxcubeqt/bricks/multi_state_brick.py +++ b/mxcubeqt/bricks/multi_state_brick.py @@ -63,9 +63,7 @@ class MultiStateBrick(BaseWidget): - units = { - "micron": u"\u03BC", - } + units = {"micron": u"\u03BC"} def __init__(self, *args): @@ -132,17 +130,13 @@ def property_changed(self, property_name, old_value, new_value): elif property_name == "mnemonic": if self.multi_hwobj is not None: self.disconnect(self.multi_hwobj, "stateChanged", self.state_changed) - self.disconnect( - self.multi_hwobj, "valueChanged", self.value_changed, - ) + self.disconnect(self.multi_hwobj, "valueChanged", self.value_changed) self.multi_hwobj = self.get_hardware_object(new_value) if self.multi_hwobj is not None: self.connect(self.multi_hwobj, "stateChanged", self.state_changed) - self.connect( - self.multi_hwobj, "valueChanged", self.value_changed, - ) + self.connect(self.multi_hwobj, "valueChanged", self.value_changed) self.fill_positions() if self.multibutton: @@ -161,7 +155,8 @@ def switch_multibutton_mode(self, mode): def get_position_label(self, posidx): pos = self.positions[posidx] - unit = self.multi_hwobj.get_property_value_by_index(posidx, "unit") + # Access the unit directly from self.multi_hwobj._positions + unit = self.multi_hwobj._positions.get(pos, {}).get("unit", "") label = str(pos) if unit: label += self.units.get(unit, unit) @@ -225,17 +220,21 @@ def value_changed(self, value=None): self.multi_position_combo.blockSignals(True) if value is None: - value = self.multi_hwobj.get_value() + value = ( + self.multi_hwobj.get_value() + ) # Ensure this returns an index or similar - if value >= 0 and value < len(self.positions): + # Ensure value is an integer index, not a dict or object + if isinstance(value, int) and 0 <= value < len(self.positions): self.multi_position_combo.setCurrentIndex(value) else: self.multi_position_combo.setCurrentIndex(-1) self.multi_position_combo.blockSignals(False) - # value is index + # Disable the current button for button in self.multibuttons: button.setEnabled(True) - self.multibuttons[value].setEnabled(False) + if isinstance(value, int) and value < len(self.multibuttons): + self.multibuttons[value].setEnabled(False) From c5ae78f4650d4720adfc7389fa4cc5c52d9b4287 Mon Sep 17 00:00:00 2001 From: Andrey Gruzinov Date: Fri, 20 Sep 2024 23:57:41 +0200 Subject: [PATCH 10/19] added the changes corresponding to the refactoring in core PR#966 --- mxcubeqt/bricks/multi_state_brick.py | 111 ++++++++++++++++++++------- 1 file changed, 82 insertions(+), 29 deletions(-) diff --git a/mxcubeqt/bricks/multi_state_brick.py b/mxcubeqt/bricks/multi_state_brick.py index 052dd1fb7..806919424 100644 --- a/mxcubeqt/bricks/multi_state_brick.py +++ b/mxcubeqt/bricks/multi_state_brick.py @@ -63,7 +63,7 @@ class MultiStateBrick(BaseWidget): - units = {"micron": u"\u03BC"} + units = {"micron": "\u03BC"} def __init__(self, *args): @@ -115,34 +115,37 @@ def __init__(self, *args): def property_changed(self, property_name, old_value, new_value): if property_name == "label": - if new_value == "": + if new_value != "": self.label.setText(new_value) else: - self.label.hide() + self.label.show() + elif property_name == "title": if new_value != "": self.main_gbox.setTitle(new_value) + elif property_name == "multibutton": if new_value != "": self.multibutton = new_value - if len(self.positions): + if self.positions is not None and len(self.positions): self.switch_multibutton_mode(self.multibutton) - elif property_name == "mnemonic": - if self.multi_hwobj is not None: - self.disconnect(self.multi_hwobj, "stateChanged", self.state_changed) - self.disconnect(self.multi_hwobj, "valueChanged", self.value_changed) + elif property_name == "mnemonic": + # Get the new hardware object directly without using callbacks or signals self.multi_hwobj = self.get_hardware_object(new_value) if self.multi_hwobj is not None: - self.connect(self.multi_hwobj, "stateChanged", self.state_changed) - self.connect(self.multi_hwobj, "valueChanged", self.value_changed) - + # Set up the motor positions and buttons self.fill_positions() + if self.multibutton: self.switch_multibutton_mode(True) - self.state_changed() + + # Manually check the state of the motor and update the UI + self.state_changed(self.multi_hwobj.get_state()) + else: + # Fall back to the base class method for other properties BaseWidget.property_changed(self, property_name, old_value, new_value) def switch_multibutton_mode(self, mode): @@ -190,51 +193,101 @@ def fill_positions(self): self.value_changed() def change_value(self, index): - if index >= 0: - self.multi_hwobj.set_value(index) + """Change the hardware object to the selected value.""" + # Check if positions is a list or dictionary + if isinstance(self.positions, list): + # If positions is a list, use the index directly + if 0 <= index < len(self.positions): + position_name = self.positions[index] + self.multi_hwobj.set_value(position_name) + else: + self.log.error(f"Invalid index: {index}.") + elif isinstance(self.positions, dict): + # If positions is a dictionary, convert keys to a list and use the index + keys_list = list(self.positions.keys()) + if 0 <= index < len(keys_list): + position_name = keys_list[index] + self.multi_hwobj.set_value(position_name) + else: + self.log.error(f"Invalid index: {index}.") + else: + self.log.error(f"Invalid type for positions: {type(self.positions)}") def state_changed(self, state=None): - + """Update the UI based on the current state of the hardware.""" if state is None: state = self.multi_hwobj.get_state() if state == HardwareObjectState.READY: color = colors.LIGHT_GREEN + self.label.setText("Motor Ready") self.setEnabled(True) elif state == HardwareObjectState.BUSY: color = colors.LIGHT_YELLOW + self.label.setText("Motor Busy") self.setEnabled(False) else: color = colors.LIGHT_GRAY + self.label.setText("Motor Not Ready") self.setEnabled(False) + # Update colors in the UI colors.set_widget_color( self.multi_position_combo, color, qt_import.QPalette.Button ) - - for but in self.multibuttons: - colors.set_widget_color(but, color, qt_import.QPalette.Button) + for button in self.multibuttons: + colors.set_widget_color(button, color, qt_import.QPalette.Button) def value_changed(self, value=None): - + """This method is called when the hardware object's value changes.""" self.multi_position_combo.blockSignals(True) if value is None: - value = ( - self.multi_hwobj.get_value() - ) # Ensure this returns an index or similar - - # Ensure value is an integer index, not a dict or object - if isinstance(value, int) and 0 <= value < len(self.positions): - self.multi_position_combo.setCurrentIndex(value) + # Get the current value from the hardware object + value = self.multi_hwobj.get_value() + + # Check if positions exist and handle both list and dict cases + if isinstance(self.positions, dict): + positions_list = list(self.positions.keys()) + elif isinstance(self.positions, list): + positions_list = self.positions else: - self.multi_position_combo.setCurrentIndex(-1) + self.log.error(f"Invalid type for positions: {type(self.positions)}") + positions_list = [] + + # Handle string positions and index values + if isinstance(value, str): + if value in positions_list: + # Set the current index to the matching string position + self.multi_position_combo.setCurrentIndex(positions_list.index(value)) + self.label.setText(f"Current Position: {value}") + else: + self.log.error(f"Unknown position: {value}") + self.label.setText("Unknown Position") + self.multi_position_combo.setCurrentIndex(-1) + else: + try: + # If value is an index, convert it to an integer + value = int(value) + except (ValueError, TypeError): + self.log.error( + f"Invalid value type: {value}. Expected an integer or string." + ) + value = -1 + + if 0 <= value < len(positions_list): + # Set the current index based on the integer value + self.multi_position_combo.setCurrentIndex(value) + self.label.setText(f"Current Position: {positions_list[value]}") + else: + self.multi_position_combo.setCurrentIndex(-1) + self.label.setText("Unknown Position") self.multi_position_combo.blockSignals(False) - # Disable the current button + # Update button states for button in self.multibuttons: button.setEnabled(True) - if isinstance(value, int) and value < len(self.multibuttons): + if isinstance(value, int) and 0 <= value < len(self.multibuttons): self.multibuttons[value].setEnabled(False) From 5879cf820d64e881f022154ff5cd0d193dc209bb Mon Sep 17 00:00:00 2001 From: Andrey Gruzinov Date: Thu, 26 Sep 2024 16:49:14 +0200 Subject: [PATCH 11/19] Fix for mockup start --- mxcubeqt/bricks/aperture_brick.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mxcubeqt/bricks/aperture_brick.py b/mxcubeqt/bricks/aperture_brick.py index cc7f390b1..3daee4f32 100644 --- a/mxcubeqt/bricks/aperture_brick.py +++ b/mxcubeqt/bricks/aperture_brick.py @@ -134,7 +134,7 @@ def init_aperture(self): aperture_size_list = HWR.beamline.beam.aperture.get_diameter_size_list() self.aperture_diameter_combo.clear() for aperture_size in aperture_size_list: - self.aperture_diameter_combo.addItem("%d%s" % (aperture_size, unichr(956))) + self.aperture_diameter_combo.addItem("%s%s" % (aperture_size, unichr(956))) aperture_position_list = HWR.beamline.beam.aperture.get_position_list() self.aperture_position_combo.clear() From 6179a13612733525de6dc82944cb2a30b6307094 Mon Sep 17 00:00:00 2001 From: Andrey Gruzinov Date: Thu, 26 Sep 2024 16:56:18 +0200 Subject: [PATCH 12/19] Fixes for the python dropping explicit type conversion - issue in mxcube designer mode --- mxcubeqt/gui_builder.py | 2 +- mxcubeqt/utils/gui_display.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/mxcubeqt/gui_builder.py b/mxcubeqt/gui_builder.py index 76db7a48d..c676d89e7 100644 --- a/mxcubeqt/gui_builder.py +++ b/mxcubeqt/gui_builder.py @@ -1262,7 +1262,7 @@ def __init__(self, *args, **kwargs): self.log_window.setWindowTitle("Log window") sw = qt_import.QApplication.desktop().screen().width() sh = qt_import.QApplication.desktop().screen().height() - self.log_window.resize(qt_import.QSize(sw * 0.8, sh * 0.2)) + self.log_window.resize(qt_import.QSize(int(sw * 0.8), int(sh * 0.2))) self.property_editor_window = PropertyEditorWindow(None) self.gui_preview_window = GUIPreviewWindow(None) self.configuration = self.gui_editor_window.configuration diff --git a/mxcubeqt/utils/gui_display.py b/mxcubeqt/utils/gui_display.py index 5d7c8dd8f..942d57db4 100644 --- a/mxcubeqt/utils/gui_display.py +++ b/mxcubeqt/utils/gui_display.py @@ -477,18 +477,18 @@ def paintEvent(self, event): if self.orientation == "horizontal": height = self.height() / 2 - painter.drawLine(0, height, self.width(), height) - painter.drawLine(0, height, 5, height - 5) - painter.drawLine(0, height, 5, height + 5) - painter.drawLine(self.width(), height, self.width() - 5, height - 5) - painter.drawLine(self.width(), height, self.width() - 5, height + 5) + painter.drawLine(0, int(height), int(self.width()), int(height)) + painter.drawLine(0,int(height), 5, int(height) - 5) + painter.drawLine(0, int(height), 5, int(height) + 5) + painter.drawLine(self.width(), int(height), self.width() - 5, int(height) - 5) + painter.drawLine(self.width(), int(height), self.width() - 5, int(height) + 5) else: width = self.width() / 2 - painter.drawLine(self.width() / 2, 0, self.width() / 2, self.height()) - painter.drawLine(width, 0, width - 5, 5) - painter.drawLine(width, 0, width + 5, 5) - painter.drawLine(width, self.height(), width - 5, self.height() - 5) - painter.drawLine(width, self.height(), width + 5, self.height() - 5) + painter.drawLine(int(self.width() / 2), 0,int(self.width() / 2),int( self.height())) + painter.drawLine(int(width), 0, int(width) - 5, 5) + painter.drawLine(int(width), 0, int(width) + 5, 5) + painter.drawLine(int(width), self.height(), int(width) - 5, self.height() - 5) + painter.drawLine(int(width), self.height(), int(width) + 5, self.height() - 5) class CustomFrame(qt_import.QFrame): def __init__(self, *args, **kwargs): From a654e647bc188746bd7036231a6485e6cb90ea1e Mon Sep 17 00:00:00 2001 From: Andrey Gruzinov Date: Thu, 26 Sep 2024 17:06:51 +0200 Subject: [PATCH 13/19] Black --- mxcubeqt/bricks/door_interlock_brick.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/mxcubeqt/bricks/door_interlock_brick.py b/mxcubeqt/bricks/door_interlock_brick.py index 3e1483f7b..bd1d0972a 100644 --- a/mxcubeqt/bricks/door_interlock_brick.py +++ b/mxcubeqt/bricks/door_interlock_brick.py @@ -84,12 +84,8 @@ def __init__(self, *args): self.state_label.setToolTip("Shows the current door state") self.unlock_door_button.setToolTip("Unlocks the doors") - self.connect( - HWR.beamline.hutch_interlock, - "valueChanged", - self.value_changed - ) - + self.connect(HWR.beamline.hutch_interlock, "valueChanged", self.value_changed) + def unlock_doors(self): self.unlock_door_button.setEnabled(False) HWR.beamline.hutch_interlock.unlock() From 537f8e12be4ce3e5c1b9c3647caada1815c5eb57 Mon Sep 17 00:00:00 2001 From: Andrey Gruzinov Date: Thu, 26 Sep 2024 17:07:59 +0200 Subject: [PATCH 14/19] Fixing proper logging --- mxcubeqt/bricks/multi_state_brick.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/mxcubeqt/bricks/multi_state_brick.py b/mxcubeqt/bricks/multi_state_brick.py index 806919424..e45f9c8cd 100644 --- a/mxcubeqt/bricks/multi_state_brick.py +++ b/mxcubeqt/bricks/multi_state_brick.py @@ -56,7 +56,6 @@ from mxcubecore.BaseHardwareObjects import HardwareObjectState - __credits__ = ["MXCuBE collaboration"] __license__ = "LGPLv3+" __category__ = "General" @@ -201,7 +200,7 @@ def change_value(self, index): position_name = self.positions[index] self.multi_hwobj.set_value(position_name) else: - self.log.error(f"Invalid index: {index}.") + logging.error(f"Invalid index: {index}.") elif isinstance(self.positions, dict): # If positions is a dictionary, convert keys to a list and use the index keys_list = list(self.positions.keys()) @@ -209,9 +208,9 @@ def change_value(self, index): position_name = keys_list[index] self.multi_hwobj.set_value(position_name) else: - self.log.error(f"Invalid index: {index}.") + logging.error(f"Invalid index: {index}.") else: - self.log.error(f"Invalid type for positions: {type(self.positions)}") + logging.error(f"Invalid type for positions: {type(self.positions)}") def state_changed(self, state=None): """Update the UI based on the current state of the hardware.""" @@ -252,7 +251,7 @@ def value_changed(self, value=None): elif isinstance(self.positions, list): positions_list = self.positions else: - self.log.error(f"Invalid type for positions: {type(self.positions)}") + logging.error(f"Invalid type for positions: {type(self.positions)}") positions_list = [] # Handle string positions and index values @@ -262,7 +261,7 @@ def value_changed(self, value=None): self.multi_position_combo.setCurrentIndex(positions_list.index(value)) self.label.setText(f"Current Position: {value}") else: - self.log.error(f"Unknown position: {value}") + logging.error(f"Unknown position: {value}") self.label.setText("Unknown Position") self.multi_position_combo.setCurrentIndex(-1) else: @@ -270,7 +269,7 @@ def value_changed(self, value=None): # If value is an index, convert it to an integer value = int(value) except (ValueError, TypeError): - self.log.error( + logging.error( f"Invalid value type: {value}. Expected an integer or string." ) value = -1 From e56b3294a31cbf4e34152f0ce2270cb990a0c97e Mon Sep 17 00:00:00 2001 From: Andrey Gruzinov Date: Fri, 27 Sep 2024 14:24:09 +0200 Subject: [PATCH 15/19] Updated the mach info brick --- mxcubeqt/bricks/machine_info_brick.py | 191 ++++++++++---------------- 1 file changed, 74 insertions(+), 117 deletions(-) diff --git a/mxcubeqt/bricks/machine_info_brick.py b/mxcubeqt/bricks/machine_info_brick.py index 284c80326..350982223 100644 --- a/mxcubeqt/bricks/machine_info_brick.py +++ b/mxcubeqt/bricks/machine_info_brick.py @@ -22,7 +22,9 @@ from mxcubeqt.widgets.matplot_widget import TwoAxisPlotWidget from mxcubecore import HardwareRepository as HWR +import logging +from PyQt5.QtWidgets import QLineEdit STATES = {"unknown": colors.GRAY, "ready": colors.LIGHT_BLUE, "error": colors.LIGHT_RED} @@ -31,142 +33,97 @@ __license__ = "LGPLv3+" __category__ = "General" +from mxcubeqt.base_components import BaseWidget +from mxcubeqt.utils import qt_import +import logging +from PyQt5.QtCore import QMetaObject, Qt +from PyQt5.QtWidgets import ( + QApplication, + QWidget, + QLabel, + QVBoxLayout, + QFormLayout, + QGroupBox, +) + class MachineInfoBrick(BaseWidget): - """Brick to display information about synchrotron and beamline""" + """Brick to display information about synchrotron and beamline as simple text lines.""" def __init__(self, *args): - """Main init""" + super().__init__(*args) - BaseWidget.__init__(self, *args) + self.current_label_text = qt_import.QLabel(self) + self.current_label = qt_import.QLabel(self) - # Internal values ----------------------------------------------------- - self.graphics_initialized = False - self.value_label_list = [] + self.message_label_text = qt_import.QLabel(self) + self.message_label = qt_import.QLabel(self) - # Properties (name, type, default value, comment)---------------------- - self.add_property( - "maxPlotPoints", "integer", 100, comment="Maximal number of plot points" - ) + self.lifetime_label_text = qt_import.QLabel(self) + self.lifetime_label = qt_import.QLabel(self) - # Signals ------------------------------------------------------------- + self.energy_label_text = qt_import.QLabel(self) + self.energy_label = qt_import.QLabel(self) - # Slots --------------------------------------------------------------- + # Set default values + self.current_label_text.setText("Machine current") + self.current_label.setText("N/A mA") + self.current_label.setStyleSheet("background-color: #6cb2f8;") + self.current_label.setAlignment(Qt.AlignCenter) - # Graphic elements ---------------------------------------------------- + self.message_label_text.setText("Machine state") + self.message_label.setStyleSheet("background-color: #6cb2f8;") + self.message_label.setText("N/A") + self.message_label.setAlignment(Qt.AlignCenter) - # Layout -------------------------------------------------------------- - self.main_vlayout = qt_import.QVBoxLayout(self) - self.main_vlayout.setSpacing(1) - self.main_vlayout.setContentsMargins(2, 2, 2, 2) + self.lifetime_label_text.setText("Lifetime") + self.lifetime_label.setText("N/A hours") + self.lifetime_label.setStyleSheet("background-color: #6cb2f8;") + self.lifetime_label.setAlignment(Qt.AlignCenter) - # SizePolicies -------------------------------------------------------- + self.energy_label_text.setText("Energy") + self.energy_label.setText("N/A GeV") + self.energy_label.setStyleSheet("background-color: #6cb2f8;") + self.energy_label.setAlignment(Qt.AlignCenter) - # Other --------------------------------------------------------------- - self.setToolTip("Main information about the beamline") + # Add labels to layout + self.main_layout = qt_import.QVBoxLayout(self) - def run(self): - """Method called when user changes a property in the gui builder""" - if HWR.beamline.machine_info is not None: - self.setEnabled(True) - self.connect(HWR.beamline.machine_info, "valuesChanged", self.set_value) - else: - self.setEnabled(False) + self.main_layout.addWidget(self.current_label_text) + self.main_layout.addWidget(self.current_label) - def set_value(self, values_dict): - """Slot connected to the valuesChanged signal - At first time initializes gui by adding necessary labels. - If the gui is initialized then update labels with values - """ - if not self.graphics_initialized: - for item in values_dict.values(): - temp_widget = CustomInfoWidget(self) - temp_widget.init_info(item, self["maxPlotPoints"]) - self.value_label_list.append(temp_widget) - self.main_vlayout.addWidget(temp_widget) - self.graphics_initialized = True - for index, value in enumerate(values_dict.values()): - self.value_label_list[index].update_info(value) - - -class CustomInfoWidget(qt_import.QWidget): - """Custom information widget""" + self.main_layout.addWidget(self.message_label_text) + self.main_layout.addWidget(self.message_label) - def __init__(self, *args): - qt_import.QWidget.__init__(self, *args) + self.main_layout.addWidget(self.lifetime_label_text) + self.main_layout.addWidget(self.lifetime_label) - self.value_plot = None + self.main_layout.addWidget(self.energy_label_text) + self.main_layout.addWidget(self.energy_label) - self.title_label = qt_import.QLabel(self) - self.value_widget = qt_import.QWidget(self) - self.value_label = qt_import.QLabel(self.value_widget) - self.value_label.setAlignment(qt_import.Qt.AlignCenter) - self.history_button = qt_import.QPushButton( - icons.load_icon("LineGraph"), "", self.value_widget + def update_labels_in_ui_thread(self, current_value): + QMetaObject.invokeMethod( + self.current_label, "setText", Qt.QueuedConnection, f"{current_value} mA" ) - self.history_button.hide() - self.history_button.setFixedWidth(22) - self.history_button.setFixedHeight(22) - - _value_widget_hlayout = qt_import.QHBoxLayout(self.value_widget) - _value_widget_hlayout.addWidget(self.value_label) - _value_widget_hlayout.addWidget(self.history_button) - _value_widget_hlayout.setSpacing(2) - _value_widget_hlayout.setContentsMargins(0, 0, 0, 0) - - self.main_vlayout = qt_import.QVBoxLayout(self) - self.main_vlayout.addWidget(self.title_label) - self.main_vlayout.addWidget(self.value_widget) - self.main_vlayout.setSpacing(1) - self.main_vlayout.setContentsMargins(0, 0, 0, 0) - - self.history_button.clicked.connect(self.open_history_view) - - def init_info(self, info_dict, max_plot_points=None): - self.title_label.setText(info_dict.get("title", "???")) - self.history_button.setVisible(info_dict.get("history", False)) - font = self.value_label.font() - if info_dict.get("font"): - font.setPointSize(info_dict.get("font")) - if info_dict.get("bold"): - font.setBold(True) - self.value_label.setFont(font) - - if info_dict.get("align") == "left": - self.value_label.setAlignment(qt_import.Qt.AlignLeft) - elif info_dict.get("align") == "right": - self.value_label.setAlignment(qt_import.Qt.AlignRight) - elif info_dict.get("align") == "center": - self.value_label.setAlignment(qt_import.Qt.AlignHCenter) - elif info_dict.get("align") == "justify": - self.value_label.setAlignment(qt_import.Qt.AlignJustify) - - if info_dict.get("history"): - self.history_button.show() - self.value_plot = TwoAxisPlotWidget(self, realtime_plot=True) - self.value_plot.hide() - self.main_vlayout.addWidget(self.value_plot) - self.value_plot.set_tight_layout() - self.value_plot.clear() - self.value_plot.set_max_plot_point(max_plot_points) - # self.value_plot.set_y_axis_limits([0, None]) - self.update_info(info_dict) - - def update_info(self, info_dict): - if info_dict.get("value_str"): - self.value_label.setText(info_dict.get("value_str")) - else: - self.value_label.setText(str(info_dict.get("value"))) - if info_dict.get("in_range") is None: - colors.set_widget_color(self.value_label, colors.GRAY) - elif info_dict.get("in_range") == True: - colors.set_widget_color(self.value_label, colors.LIGHT_BLUE) + def run(self): + """Connect the signal from the hardware object.""" + if HWR.beamline.machine_info is not None: + HWR.beamline.machine_info.valuesChanged.connect(self.set_value) else: - colors.set_widget_color(self.value_label, colors.LIGHT_RED) - value = info_dict.get("value") - if type(value) in (int, float) and self.value_plot: - self.value_plot.add_new_plot_value(value) + logging.error("Machine info object not available") - def open_history_view(self): - self.value_plot.setVisible(not self.value_plot.isVisible()) + def set_value(self, values_dict): + """Update the labels in the MachineInfoBrick with machine values.""" + logging.info(f"Received machine info values: {values_dict}") + + current_value = values_dict.get("current", {}).get("value", "N/A") + lifetime_value = values_dict.get("lifetime", {}).get("value", "N/A") + energy_value = values_dict.get("energy", {}).get("value", "N/A") + message_value = values_dict.get("message", {}).get("value", "N/A") + + # Update the UI labels + self.current_label.setText(f"{current_value} mA") + self.lifetime_label.setText(f"{lifetime_value} hours") + self.energy_label.setText(f"{energy_value} GeV") + self.message_label.setText(f"{message_value}") From 90d9cc82bb35ddda822398e232e64405593cffde Mon Sep 17 00:00:00 2001 From: Andrey Gruzinov Date: Fri, 27 Sep 2024 14:51:53 +0200 Subject: [PATCH 16/19] Added category DESY in the respective bricks:wq --- mxcubeqt/bricks/desy/p11_proposal_brick.py | 5 +++++ mxcubeqt/bricks/desy/p11_sample_changer_brick.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/mxcubeqt/bricks/desy/p11_proposal_brick.py b/mxcubeqt/bricks/desy/p11_proposal_brick.py index 41f66558b..519093a0e 100644 --- a/mxcubeqt/bricks/desy/p11_proposal_brick.py +++ b/mxcubeqt/bricks/desy/p11_proposal_brick.py @@ -17,6 +17,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with MXCuBE. If not, see . +__credits__ = ["MXCuBE collaboration"] +__license__ = "LGPLv3+" +__category__ = "DESY" + + import logging import os diff --git a/mxcubeqt/bricks/desy/p11_sample_changer_brick.py b/mxcubeqt/bricks/desy/p11_sample_changer_brick.py index d41ef1313..aa9c8be99 100644 --- a/mxcubeqt/bricks/desy/p11_sample_changer_brick.py +++ b/mxcubeqt/bricks/desy/p11_sample_changer_brick.py @@ -17,6 +17,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with MXCuBE. If not, see . +__credits__ = ["MXCuBE collaboration"] +__license__ = "LGPLv3+" +__category__ = "DESY" + + from mxcubeqt.bricks.cats_simple_brick import CatsSimpleBrick From 686779e78c4abf21574f127d277d5e658a700189 Mon Sep 17 00:00:00 2001 From: Andrey Gruzinov Date: Fri, 27 Sep 2024 14:52:40 +0200 Subject: [PATCH 17/19] Added P11 specific mach_info_brick --- .../bricks/desy/p11_machine_info_brick.py | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 mxcubeqt/bricks/desy/p11_machine_info_brick.py diff --git a/mxcubeqt/bricks/desy/p11_machine_info_brick.py b/mxcubeqt/bricks/desy/p11_machine_info_brick.py new file mode 100644 index 000000000..bc0592447 --- /dev/null +++ b/mxcubeqt/bricks/desy/p11_machine_info_brick.py @@ -0,0 +1,129 @@ +# +# Project: MXCuBE +# https://github.com/mxcube +# +# This file is part of MXCuBE software. +# +# MXCuBE is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# MXCuBE is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with MXCuBE. If not, see . + +from mxcubeqt.base_components import BaseWidget +from mxcubeqt.utils import icons, colors, qt_import +from mxcubeqt.widgets.matplot_widget import TwoAxisPlotWidget + +from mxcubecore import HardwareRepository as HWR +import logging + +from PyQt5.QtWidgets import QLineEdit + +STATES = {"unknown": colors.GRAY, "ready": colors.LIGHT_BLUE, "error": colors.LIGHT_RED} + + +__credits__ = ["MXCuBE collaboration"] +__license__ = "LGPLv3+" +__category__ = "DESY" + +from mxcubeqt.base_components import BaseWidget +from mxcubeqt.utils import qt_import +import logging +from PyQt5.QtCore import QMetaObject, Qt +from PyQt5.QtWidgets import ( + QApplication, + QWidget, + QLabel, + QVBoxLayout, + QFormLayout, + QGroupBox, +) + + +class P11MachineInfoBrick(BaseWidget): + """Brick to display information about synchrotron and beamline as simple text lines.""" + + def __init__(self, *args): + super().__init__(*args) + + self.current_label_text = qt_import.QLabel(self) + self.current_label = qt_import.QLabel(self) + + self.message_label_text = qt_import.QLabel(self) + self.message_label = qt_import.QLabel(self) + + self.lifetime_label_text = qt_import.QLabel(self) + self.lifetime_label = qt_import.QLabel(self) + + self.energy_label_text = qt_import.QLabel(self) + self.energy_label = qt_import.QLabel(self) + + # Set default values + self.current_label_text.setText("Machine current") + self.current_label.setText("N/A mA") + self.current_label.setStyleSheet("background-color: #6cb2f8;") + self.current_label.setAlignment(Qt.AlignCenter) + + self.message_label_text.setText("Machine state") + self.message_label.setStyleSheet("background-color: #6cb2f8;") + self.message_label.setText("N/A") + self.message_label.setAlignment(Qt.AlignCenter) + + self.lifetime_label_text.setText("Lifetime") + self.lifetime_label.setText("N/A hours") + self.lifetime_label.setStyleSheet("background-color: #6cb2f8;") + self.lifetime_label.setAlignment(Qt.AlignCenter) + + self.energy_label_text.setText("Energy") + self.energy_label.setText("N/A GeV") + self.energy_label.setStyleSheet("background-color: #6cb2f8;") + self.energy_label.setAlignment(Qt.AlignCenter) + + # Add labels to layout + self.main_layout = qt_import.QVBoxLayout(self) + + self.main_layout.addWidget(self.current_label_text) + self.main_layout.addWidget(self.current_label) + + self.main_layout.addWidget(self.message_label_text) + self.main_layout.addWidget(self.message_label) + + self.main_layout.addWidget(self.lifetime_label_text) + self.main_layout.addWidget(self.lifetime_label) + + self.main_layout.addWidget(self.energy_label_text) + self.main_layout.addWidget(self.energy_label) + + def update_labels_in_ui_thread(self, current_value): + QMetaObject.invokeMethod( + self.current_label, "setText", Qt.QueuedConnection, f"{current_value} mA" + ) + + def run(self): + """Connect the signal from the hardware object.""" + if HWR.beamline.machine_info is not None: + HWR.beamline.machine_info.valuesChanged.connect(self.set_value) + else: + logging.error("Machine info object not available") + + def set_value(self, values_dict): + """Update the labels in the MachineInfoBrick with machine values.""" + logging.info(f"Received machine info values: {values_dict}") + + current_value = values_dict.get("current", {}).get("value", "N/A") + lifetime_value = values_dict.get("lifetime", {}).get("value", "N/A") + energy_value = values_dict.get("energy", {}).get("value", "N/A") + message_value = values_dict.get("message", {}).get("value", "N/A") + + # Update the UI labels + self.current_label.setText(f"{current_value} mA") + self.lifetime_label.setText(f"{lifetime_value} hours") + self.energy_label.setText(f"{energy_value} GeV") + self.message_label.setText(f"{message_value}") From 569cd0defdb33d9232c5e75dd1f70975f0de9920 Mon Sep 17 00:00:00 2001 From: Andrey Gruzinov Date: Fri, 27 Sep 2024 15:09:51 +0200 Subject: [PATCH 18/19] Restored the original repo version that was committed after changes to create P11 mach info brick:wq --- mxcubeqt/bricks/machine_info_brick.py | 190 ++++++++++++++++---------- 1 file changed, 116 insertions(+), 74 deletions(-) diff --git a/mxcubeqt/bricks/machine_info_brick.py b/mxcubeqt/bricks/machine_info_brick.py index 350982223..c4604d93e 100644 --- a/mxcubeqt/bricks/machine_info_brick.py +++ b/mxcubeqt/bricks/machine_info_brick.py @@ -22,9 +22,7 @@ from mxcubeqt.widgets.matplot_widget import TwoAxisPlotWidget from mxcubecore import HardwareRepository as HWR -import logging -from PyQt5.QtWidgets import QLineEdit STATES = {"unknown": colors.GRAY, "ready": colors.LIGHT_BLUE, "error": colors.LIGHT_RED} @@ -33,97 +31,141 @@ __license__ = "LGPLv3+" __category__ = "General" -from mxcubeqt.base_components import BaseWidget -from mxcubeqt.utils import qt_import -import logging -from PyQt5.QtCore import QMetaObject, Qt -from PyQt5.QtWidgets import ( - QApplication, - QWidget, - QLabel, - QVBoxLayout, - QFormLayout, - QGroupBox, -) - class MachineInfoBrick(BaseWidget): - """Brick to display information about synchrotron and beamline as simple text lines.""" + """Brick to display information about synchrotron and beamline""" def __init__(self, *args): - super().__init__(*args) + """Main init""" - self.current_label_text = qt_import.QLabel(self) - self.current_label = qt_import.QLabel(self) + BaseWidget.__init__(self, *args) - self.message_label_text = qt_import.QLabel(self) - self.message_label = qt_import.QLabel(self) + # Internal values ----------------------------------------------------- + self.graphics_initialized = False + self.value_label_list = [] - self.lifetime_label_text = qt_import.QLabel(self) - self.lifetime_label = qt_import.QLabel(self) + # Properties (name, type, default value, comment)---------------------- + self.add_property( + "maxPlotPoints", "integer", 100, comment="Maximal number of plot points" + ) - self.energy_label_text = qt_import.QLabel(self) - self.energy_label = qt_import.QLabel(self) + # Signals ------------------------------------------------------------- - # Set default values - self.current_label_text.setText("Machine current") - self.current_label.setText("N/A mA") - self.current_label.setStyleSheet("background-color: #6cb2f8;") - self.current_label.setAlignment(Qt.AlignCenter) + # Slots --------------------------------------------------------------- - self.message_label_text.setText("Machine state") - self.message_label.setStyleSheet("background-color: #6cb2f8;") - self.message_label.setText("N/A") - self.message_label.setAlignment(Qt.AlignCenter) + # Graphic elements ---------------------------------------------------- - self.lifetime_label_text.setText("Lifetime") - self.lifetime_label.setText("N/A hours") - self.lifetime_label.setStyleSheet("background-color: #6cb2f8;") - self.lifetime_label.setAlignment(Qt.AlignCenter) + # Layout -------------------------------------------------------------- + self.main_vlayout = qt_import.QVBoxLayout(self) + self.main_vlayout.setSpacing(1) + self.main_vlayout.setContentsMargins(2, 2, 2, 2) - self.energy_label_text.setText("Energy") - self.energy_label.setText("N/A GeV") - self.energy_label.setStyleSheet("background-color: #6cb2f8;") - self.energy_label.setAlignment(Qt.AlignCenter) + # SizePolicies -------------------------------------------------------- - # Add labels to layout - self.main_layout = qt_import.QVBoxLayout(self) + # Other --------------------------------------------------------------- + self.setToolTip("Main information about the beamline") - self.main_layout.addWidget(self.current_label_text) - self.main_layout.addWidget(self.current_label) + def run(self): + """Method called when user changes a property in the gui builder""" + if HWR.beamline.machine_info is not None: + self.setEnabled(True) + self.connect(HWR.beamline.machine_info, "valuesChanged", self.set_value) + else: + self.setEnabled(False) - self.main_layout.addWidget(self.message_label_text) - self.main_layout.addWidget(self.message_label) + def set_value(self, values_dict): + """Slot connected to the valuesChanged signal + At first time initializes gui by adding necessary labels. + If the gui is initialized then update labels with values + """ + if not self.graphics_initialized: + for item in values_dict.values(): + temp_widget = CustomInfoWidget(self) + temp_widget.init_info(item, self["maxPlotPoints"]) + self.value_label_list.append(temp_widget) + self.main_vlayout.addWidget(temp_widget) + self.graphics_initialized = True + for index, value in enumerate(values_dict.values()): + self.value_label_list[index].update_info(value) + + +class CustomInfoWidget(qt_import.QWidget): + """Custom information widget""" - self.main_layout.addWidget(self.lifetime_label_text) - self.main_layout.addWidget(self.lifetime_label) + def __init__(self, *args): + qt_import.QWidget.__init__(self, *args) - self.main_layout.addWidget(self.energy_label_text) - self.main_layout.addWidget(self.energy_label) + self.value_plot = None - def update_labels_in_ui_thread(self, current_value): - QMetaObject.invokeMethod( - self.current_label, "setText", Qt.QueuedConnection, f"{current_value} mA" + self.title_label = qt_import.QLabel(self) + self.value_widget = qt_import.QWidget(self) + self.value_label = qt_import.QLabel(self.value_widget) + self.value_label.setAlignment(qt_import.Qt.AlignCenter) + self.history_button = qt_import.QPushButton( + icons.load_icon("LineGraph"), "", self.value_widget ) + self.history_button.hide() + self.history_button.setFixedWidth(22) + self.history_button.setFixedHeight(22) + + _value_widget_hlayout = qt_import.QHBoxLayout(self.value_widget) + _value_widget_hlayout.addWidget(self.value_label) + _value_widget_hlayout.addWidget(self.history_button) + _value_widget_hlayout.setSpacing(2) + _value_widget_hlayout.setContentsMargins(0, 0, 0, 0) + + self.main_vlayout = qt_import.QVBoxLayout(self) + self.main_vlayout.addWidget(self.title_label) + self.main_vlayout.addWidget(self.value_widget) + self.main_vlayout.setSpacing(1) + self.main_vlayout.setContentsMargins(0, 0, 0, 0) + + self.history_button.clicked.connect(self.open_history_view) + + def init_info(self, info_dict, max_plot_points=None): + self.title_label.setText(info_dict.get("title", "???")) + self.history_button.setVisible(info_dict.get("history", False)) + font = self.value_label.font() + if info_dict.get("font"): + font.setPointSize(info_dict.get("font")) + if info_dict.get("bold"): + font.setBold(True) + self.value_label.setFont(font) + + if info_dict.get("align") == "left": + self.value_label.setAlignment(qt_import.Qt.AlignLeft) + elif info_dict.get("align") == "right": + self.value_label.setAlignment(qt_import.Qt.AlignRight) + elif info_dict.get("align") == "center": + self.value_label.setAlignment(qt_import.Qt.AlignHCenter) + elif info_dict.get("align") == "justify": + self.value_label.setAlignment(qt_import.Qt.AlignJustify) + + if info_dict.get("history"): + self.history_button.show() + self.value_plot = TwoAxisPlotWidget(self, realtime_plot=True) + self.value_plot.hide() + self.main_vlayout.addWidget(self.value_plot) + self.value_plot.set_tight_layout() + self.value_plot.clear() + self.value_plot.set_max_plot_point(max_plot_points) + # self.value_plot.set_y_axis_limits([0, None]) + self.update_info(info_dict) + + def update_info(self, info_dict): + if info_dict.get("value_str"): + self.value_label.setText(info_dict.get("value_str")) + else: + self.value_label.setText(str(info_dict.get("value"))) - def run(self): - """Connect the signal from the hardware object.""" - if HWR.beamline.machine_info is not None: - HWR.beamline.machine_info.valuesChanged.connect(self.set_value) + if info_dict.get("in_range") is None: + colors.set_widget_color(self.value_label, colors.GRAY) + elif info_dict.get("in_range") == True: + colors.set_widget_color(self.value_label, colors.LIGHT_BLUE) else: - logging.error("Machine info object not available") + colors.set_widget_color(self.value_label, colors.LIGHT_RED) + value = info_dict.get("value") + if type(value) in (int, float) and self.value_plot: + self.value_plot.add_new_plot_value(value) - def set_value(self, values_dict): - """Update the labels in the MachineInfoBrick with machine values.""" - logging.info(f"Received machine info values: {values_dict}") - - current_value = values_dict.get("current", {}).get("value", "N/A") - lifetime_value = values_dict.get("lifetime", {}).get("value", "N/A") - energy_value = values_dict.get("energy", {}).get("value", "N/A") - message_value = values_dict.get("message", {}).get("value", "N/A") - - # Update the UI labels - self.current_label.setText(f"{current_value} mA") - self.lifetime_label.setText(f"{lifetime_value} hours") - self.energy_label.setText(f"{energy_value} GeV") - self.message_label.setText(f"{message_value}") + def open_history_view(self): From 564784dbbb94c64b2b49f4350891c33dbe2644d0 Mon Sep 17 00:00:00 2001 From: Andrey Gruzinov Date: Tue, 1 Oct 2024 10:39:09 +0200 Subject: [PATCH 19/19] Added change of color in p11 mach info brick if values are out of range:wq --- .../bricks/desy/p11_machine_info_brick.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/mxcubeqt/bricks/desy/p11_machine_info_brick.py b/mxcubeqt/bricks/desy/p11_machine_info_brick.py index bc0592447..1ac42e9b2 100644 --- a/mxcubeqt/bricks/desy/p11_machine_info_brick.py +++ b/mxcubeqt/bricks/desy/p11_machine_info_brick.py @@ -127,3 +127,24 @@ def set_value(self, values_dict): self.lifetime_label.setText(f"{lifetime_value} hours") self.energy_label.setText(f"{energy_value} GeV") self.message_label.setText(f"{message_value}") + + # Check if machine current is <= 99, change color + if current_value != "N/A" and float(current_value) <= 99: + self.set_all_labels_color("salmon") + else: + self.reset_all_labels_color() + + def set_all_labels_color(self, color): + """Set all label backgrounds to the specified color.""" + self.current_label.setStyleSheet(f"background-color: {color};") + self.message_label.setStyleSheet(f"background-color: {color};") + self.lifetime_label.setStyleSheet(f"background-color: {color};") + self.energy_label.setStyleSheet(f"background-color: {color};") + + def reset_all_labels_color(self): + """Reset all label backgrounds to their default color.""" + default_color = "#6cb2f8" + self.current_label.setStyleSheet(f"background-color: {default_color};") + self.message_label.setStyleSheet(f"background-color: {default_color};") + self.lifetime_label.setStyleSheet(f"background-color: {default_color};") + self.energy_label.setStyleSheet(f"background-color: {default_color};")