From a85c7354b4e560a8185d267517c771caab593b4b Mon Sep 17 00:00:00 2001 From: Erik Everson Date: Tue, 14 Jan 2025 14:36:50 -0800 Subject: [PATCH] A collection of small `ConfigureGUI` modifications (#68) * use the motor signal movement_finished() to update the Axis control displays * disable configuration controls while the probe drive is moving * if the status of one axis changes make the other AxisControlWidgets update * make the drive widget update its display when the configuration is changed * fix over indent * move all connected functions to MGWidget.configChanged into the _config_changed_handler() method * when _update_drive_control_widget() execute _refresh_drive_control() if the drive control widget does not have the valid MG --- .../gui/configure/motion_group_widget.py | 125 ++++++++++++++++-- 1 file changed, 116 insertions(+), 9 deletions(-) diff --git a/bapsf_motion/gui/configure/motion_group_widget.py b/bapsf_motion/gui/configure/motion_group_widget.py index 5833d7f7..6d1886e7 100644 --- a/bapsf_motion/gui/configure/motion_group_widget.py +++ b/bapsf_motion/gui/configure/motion_group_widget.py @@ -2,6 +2,7 @@ import asyncio import logging +import warnings from PySide6.QtCore import Qt, Signal, Slot, QSize from PySide6.QtGui import QDoubleValidator @@ -39,6 +40,9 @@ class AxisControlWidget(QWidget): axisLinked = Signal() axisUnlinked = Signal() + movementStarted = Signal(int) + movementStopped = Signal(int) + axisStatusChanged = Signal() def __init__(self, parent=None): super().__init__(parent) @@ -255,6 +259,10 @@ def link_axis(self, mg: MotionGroup, ax_index: int): self.axis_name_label.setText(self.axis.name) self.axis.motor.status_changed.connect(self._update_display_of_axis_status) + self.axis.motor.status_changed.connect(self.axisStatusChanged.emit) + self.axis.motor.movement_started.connect(self._emit_movement_started) + self.axis.motor.movement_finished.connect(self._emit_movement_finished) + self.axis.motor.movement_finished.connect(self._update_display_of_axis_status) self._update_display_of_axis_status() self.axisLinked.emit() @@ -263,17 +271,41 @@ def unlink_axis(self): if self.axis is not None: # self.axis.terminate(delay_loop_stop=True) self.axis.motor.status_changed.disconnect(self._update_display_of_axis_status) + self.axis.motor.status_changed.connect(self.axisStatusChanged.emit) + self.axis.motor.movement_started.connect(self._emit_movement_started) + self.axis.motor.movement_finished.connect(self._emit_movement_finished) + self.axis.motor.movement_finished.disconnect( + self._update_display_of_axis_status + ) self._mg = None self._axis_index = None self.axisUnlinked.emit() + def _emit_movement_started(self): + self.movementStarted.emit(self.axis_index) + + def _emit_movement_finished(self): + self.movementStopped.emit(self.axis_index) + def closeEvent(self, event): self.logger.info("Closing AxisControlWidget") + + if isinstance(self.axis, Axis): + self.axis.motor.status_changed.disconnect(self._update_display_of_axis_status) + self.axis.motor.status_changed.disconnect(self.axisStatusChanged.emit) + self.axis.motor.movement_started.connect(self._emit_movement_started) + self.axis.motor.movement_finished.connect(self._emit_movement_finished) + self.axis.motor.movement_finished.disconnect( + self._update_display_of_axis_status + ) + event.accept() class DriveControlWidget(QWidget): + movementStarted = Signal() + movementStopped = Signal() def __init__(self, parent=None): super().__init__(parent) @@ -474,6 +506,9 @@ def link_motion_group(self, mg): for ii, ax in enumerate(self.mg.drive.axes): acw = self._axis_control_widgets[ii] acw.link_axis(self.mg, ii) + acw.movementStarted.connect(self._drive_movement_started) + acw.movementStopped.connect(self._drive_movement_finished) + acw.axisStatusChanged.connect(self._update_all_axis_displays) acw.show() self.setEnabled(not self._mg.terminated) @@ -483,12 +518,42 @@ def unlink_motion_group(self): visible = True if ii == 0 else False acw.unlink_axis() + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=RuntimeWarning) + acw.movementStarted.disconnect(self._drive_movement_started) + acw.movementStopped.disconnect(self._drive_movement_finished) + acw.axisStatusChanged.disconnect(self._update_all_axis_displays) + acw.setVisible(visible) # self.mg.terminate(delay_loop_stop=True) self._mg = None self.setEnabled(False) + def _update_all_axis_displays(self): + for acw in self._axis_control_widgets: + if acw.isHidden(): + continue + elif acw.axis.is_moving: + continue + + acw._update_display_of_axis_status() + + @Slot(int) + def _drive_movement_started(self, axis_index): + self.movementStarted.emit() + + @Slot(int) + def _drive_movement_finished(self, axis_index): + if not isinstance(self.mg, MotionGroup) or not isinstance(self.mg.drive, Drive): + return + + is_moving = [ax.is_moving for ax in self.mg.drive.axes] + is_moving[axis_index] = False + if not any(is_moving): + self.movementStopped.emit() + def closeEvent(self, event): self.logger.info("Closing DriveControlWidget") event.accept() @@ -531,7 +596,6 @@ def __init__( "ips": deployed_ips, } - self._logger = gui_logger self._mg = None @@ -682,8 +746,8 @@ def __init__( # if MGWidget launched without a drive then use a default # drive (if defined) if ( - "drive" not in self.mg_config - and self.drive_defaults[0][0] != "Custom Drive" + "drive" not in self.mg_config + and self.drive_defaults[0][0] != "Custom Drive" ): self._mg_config["drive"] = _deepcopy_dict(self.drive_defaults[0][1]) @@ -734,12 +798,7 @@ def _connect_signals(self): self.mg_name_widget.editingFinished.connect(self._rename_motion_group) - self.configChanged.connect(self._update_toml_widget) - self.configChanged.connect(self._update_mg_name_widget) - self.configChanged.connect(self._validate_motion_group) - self.configChanged.connect(self._update_drive_dropdown) - self.configChanged.connect(self._update_mb_dropdown) - self.configChanged.connect(self._update_transform_dropdown) + self.configChanged.connect(self._config_changed_handler) self.drive_dropdown.currentIndexChanged.connect( self._drive_dropdown_new_selection @@ -751,6 +810,9 @@ def _connect_signals(self): self._transform_dropdown_new_selection ) + self.drive_control_widget.movementStarted.connect(self.disable_config_controls) + self.drive_control_widget.movementStopped.connect(self.enable_config_controls) + self.done_btn.clicked.connect(self.return_and_close) self.discard_btn.clicked.connect(self.close) @@ -987,6 +1049,22 @@ def _build_transform_defaults(self): return self._transform_defaults + def _config_changed_handler(self): + # Note: none of the methods executed here should cause a + # configChanged event + self._validate_motion_group() + + # now update displays + self._update_mg_name_widget() + self._update_toml_widget() + self._update_drive_dropdown() + self._update_mb_dropdown() + self._update_transform_dropdown() + + # updating the drive control widget should always be the last + # step + self._update_drive_control_widget() + def _populate_drive_dropdown(self): for item in self.drive_defaults: self.drive_dropdown.addItem(item[0]) @@ -1354,6 +1432,15 @@ def _update_toml_widget(self): def _update_mg_name_widget(self): self.mg_name_widget.setText(self.mg_config["name"]) + def _update_drive_control_widget(self): + if not self.drive_control_widget.isEnabled(): + return + + if self.drive_control_widget.mg is None: + self._refresh_drive_control() + else: + self.drive_control_widget._update_all_axis_displays() + def _rename_motion_group(self): self.logger.info("Renaming motion group") self.mg.config["name"] = self.mg_name_widget.text() @@ -1624,6 +1711,26 @@ def _transform_dropdown_new_selection(self, index): self._change_transform(tr_default_config) + def disable_config_controls(self): + self.drive_dropdown.setEnabled(False) + self.drive_btn.setEnabled(False) + + self.mb_dropdown.setEnabled(False) + self.mb_btn.setEnabled(False) + + self.transform_dropdown.setEnabled(False) + self.transform_btn.setEnabled(False) + + def enable_config_controls(self): + self.drive_dropdown.setEnabled(True) + self.drive_btn.setEnabled(True) + + self.mb_dropdown.setEnabled(True) + self.mb_btn.setEnabled(True) + + self.transform_dropdown.setEnabled(True) + self.transform_btn.setEnabled(True) + def return_and_close(self): config = _deepcopy_dict(self.mg.config) index = -1 if self._mg_index is None else self._mg_index