Skip to content

Commit

Permalink
Merge branch 'main' into enforce_excluded_movement
Browse files Browse the repository at this point in the history
  • Loading branch information
rocco8773 committed Jan 15, 2025
2 parents 1ad317d + f894e38 commit f33b4d6
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 56 deletions.
33 changes: 22 additions & 11 deletions bapsf_motion/gui/configure/motion_builder_overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
)
from bapsf_motion.motion_builder import MotionBuilder
from bapsf_motion.motion_builder.layers import layer_registry
from bapsf_motion.motion_builder.exclusions import exclusion_registry
from bapsf_motion.motion_builder.exclusions import exclusion_registry, GovernExclusion
from bapsf_motion.utils import _deepcopy_dict
from bapsf_motion.utils import units as u

Expand Down Expand Up @@ -124,10 +124,7 @@ def __init__(self, mg: MotionGroup, parent: "mgw.MGWidget" = None):
def _connect_signals(self):
super()._connect_signals()

self.configChanged.connect(self.update_canvas)
self.configChanged.connect(self.update_exclusion_list_box)
self.configChanged.connect(self.update_layer_list_box)
self.configChanged.connect(self._validate_mb)
self.configChanged.connect(self._config_changed_handler)

self.add_ex_btn.clicked.connect(self._exclusion_configure_new)
self.remove_ex_btn.clicked.connect(self._exclusion_remove_from_mb)
Expand Down Expand Up @@ -612,6 +609,16 @@ def _define_params_field_widget(self, ex_or_ly, _type):

# -- WIDGET INTERACTION FUNCTIONALITY --

def _config_changed_handler(self):
# Note: none of the methods executed here should cause a
# configChanged event
self._validate_mb()

# now update displays
self.update_exclusion_list_box()
self.update_layer_list_box()
self.update_canvas()

def _exclusion_configure_new(self):
if not self._params_widget.isHidden():
self._hide_and_clear_params_widget()
Expand All @@ -621,6 +628,13 @@ def _exclusion_configure_new(self):
_available = self.exclusion_registry.get_names_by_dimensionality(
self.dimensionality
)
if self.mb.exclusions and isinstance(self.mb.exclusions[0], GovernExclusion):
# remove govern exclusion since we can only have one defined
for name in tuple(_available):
ex = self.exclusion_registry.get_exclusion(name)
if issubclass(ex, GovernExclusion):
_available.remove(name)

self._refresh_params_combo_box(_available)
self.params_combo_box.setObjectName("exclusion")

Expand Down Expand Up @@ -969,12 +983,9 @@ def update_canvas(self):
self.logger.info(f"MB config = {self.mb.config}")

self.mpl_canvas.figure.clear()
ax = self.mpl_canvas.figure.add_subplot(111)
self.mb.mask.plot(
x=self.mb.mask.dims[0],
y=self.mb.mask.dims[1],
ax=ax,
)
ax = self.mpl_canvas.figure.gca()
xdim, ydim = self.mb.mspace_dims
self.mb.mask.plot(x=xdim, y=ydim, ax=ax)

pts = self.mb.motion_list
if pts is not None:
Expand Down
143 changes: 132 additions & 11 deletions bapsf_motion/gui/configure/motion_group_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import asyncio
import logging
import warnings

from PySide6.QtCore import Qt, Signal, Slot, QSize
from PySide6.QtGui import QDoubleValidator
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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()
Expand Down Expand Up @@ -521,6 +586,10 @@ def __init__(
deployed_ips = []
if isinstance(self._parent.rm, RunManager):
for mg in self._parent.rm.mgs.values():
if mg_config is not None and dict_equal(mg_config, mg.config):
# assume we are editing an existing motion group
continue

deployed_mg_names.append(mg.config["name"])

for axis in mg.drive.axes:
Expand All @@ -531,7 +600,6 @@ def __init__(
"ips": deployed_ips,
}


self._logger = gui_logger

self._mg = None
Expand Down Expand Up @@ -682,8 +750,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])

Expand Down Expand Up @@ -724,6 +792,11 @@ def __init__(

self._initial_mg_config = _deepcopy_dict(self._mg_config)

if "name" not in self._mg_config or self._mg_config["name"] == "":
self._mg_config["name"] = "A New MG"
self.logger.info(f"starting _mg_config: {self._mg_config}")
self._update_mg_name_widget()

self._spawn_motion_group()
self._refresh_drive_control()

Expand All @@ -734,12 +807,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
Expand All @@ -751,6 +819,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)

Expand Down Expand Up @@ -987,6 +1058,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])
Expand Down Expand Up @@ -1255,7 +1342,6 @@ def mg_config(self) -> Union[Dict[str, Any], "MotionGroupConfig"]:
return self._mg_config
elif self._mg_config is None:
name = self.mg_name_widget.text()
name = "A New MG" if name == "" else name
self._mg_config = {"name": name}

return self._mg_config
Expand Down Expand Up @@ -1354,6 +1440,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()
Expand Down Expand Up @@ -1414,12 +1509,17 @@ def _validate_motion_group(self):

if not isinstance(self.mg.mb, MotionBuilder):
self.mb_btn.set_invalid()
self.mb_btn.setToolTip("Motion space needs to be defined.")
self.done_btn.setEnabled(False)
else:
if "layer" not in self.mg.mb.config:
self.mb_btn.set_invalid()
self.mb_btn.setToolTip(
"A point layer needs to be defined to generate a motion list."
)
else:
self.mb_btn.set_valid()
self.mb_btn.setToolTip("")

if not isinstance(self.mg.transform, BaseTransform):
self.transform_btn.set_invalid()
Expand All @@ -1442,8 +1542,8 @@ def _validate_motion_group(self):
self.done_btn.setEnabled(False)

def _validate_motion_group_name(self) -> bool:
self.logger.info("Validating motion group name")
mg_name = self.mg_name_widget.text()
self.logger.info(f"Validating motion group name '{mg_name}'.")

# clear previous tooltips and actions
self.mg_name_widget.setToolTip("")
Expand Down Expand Up @@ -1483,6 +1583,7 @@ def _validate_drive(self) -> bool:
self.mb_dropdown.setEnabled(False)
self.mb_btn.setEnabled(False)
self.mb_btn.set_invalid()
self.mb_btn.setToolTip("Motion space needs to be defined.")

self.transform_dropdown.setEnabled(False)
self.transform_btn.setEnabled(False)
Expand Down Expand Up @@ -1624,6 +1725,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
Expand Down
6 changes: 4 additions & 2 deletions bapsf_motion/gui/widgets/buttons.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,10 @@ def __init__(self, color: str = "#2980b9", parent=None):

class GearValidButton(StyleButton):
def __init__(self, parent=None):
self._valid_color = "#499C54" # rgb(14, 212, 0)
self._invalid_color = "#C75450" # rgb(13, 88, 0)
# self._valid_color = "#499C54" # rgb(14, 212, 0)
# self._invalid_color = "#C75450" # rgb(13, 88, 0)
self._valid_color = "#3498DB" # rgb(52, 152, 219) blue
self._invalid_color = "#FF5733" # rgb(242, 94, 62) orange

self._valid_icon = qta.icon("fa.gear", color=self._valid_color)
self._invalid_icon = qta.icon("fa.gear", color=self._invalid_color)
Expand Down
Loading

0 comments on commit f33b4d6

Please sign in to comment.