def _setupMenu(self): self.menubar = QMenuBar(self) self.layout().setMenuBar(self.menubar) self.menu = self.menubar.addMenu('Options') self.act_saveas = self.menu.addAction('Save as...') self.act_saveas.triggered.connect(self._showSaveAsPopup) self._undo_stack = QUndoStack(self) self.act_undo = self._undo_stack.createUndoAction(self, 'Undo') self.act_undo.setShortcut(QKeySequence.Undo) self.menu.addAction(self.act_undo) self.act_redo = self._undo_stack.createRedoAction(self, 'Redo') self.act_redo.setShortcut(QKeySequence.Redo) self.menu.addAction(self.act_redo)
def __init__(self): super(OpenBurnApplication, self).__init__() self.motor = OpenBurnMotor() self.current_design_filename: str = None self.undo_stack = QUndoStack(self) self.propellant_db = PropellantDatabase('user/propellants.json') self.settings = SettingsDatabase('user/settings.json')
def __undo_redo(self) -> None: """Undo list settings. + Undo stack. + Undo view widget. + Hot keys. """ self.command_stack = QUndoStack(self) self.command_stack.setUndoLimit(self.prefer.undo_limit_option) self.command_stack.indexChanged.connect(self.command_reload) action_redo = self.command_stack.createRedoAction(self, "Redo") action_undo = self.command_stack.createUndoAction(self, "Undo") action_redo.setShortcuts(["Ctrl+Shift+Z", "Ctrl+Y"]) action_redo.setStatusTip("Backtracking undo action.") action_redo.setIcon(QIcon(QPixmap(":/icons/redo.png"))) action_undo.setShortcut("Ctrl+Z") action_undo.setStatusTip("Recover last action.") action_undo.setIcon(QIcon(QPixmap(":/icons/undo.png"))) self.menu_edit.addActions([action_undo, action_redo])
def __undo_redo(self) -> None: """Undo list settings. + Undo stack. + Undo view widget. + Hot keys. """ self.cmd_stack = QUndoStack(self) self.cmd_stack.setUndoLimit(self.prefer.undo_limit_option) self.cmd_stack.indexChanged.connect(self.command_reload) redo = self.cmd_stack.createRedoAction(self, "Redo") undo = self.cmd_stack.createUndoAction(self, "Undo") redo.setShortcuts(["Ctrl+Shift+Z", "Ctrl+Y"]) redo.setStatusTip("Backtracking undo action.") redo.setIcon(QIcon(QPixmap("icons:redo.png"))) undo.setShortcut("Ctrl+Z") undo.setStatusTip("Recover last action.") undo.setIcon(QIcon(QPixmap("icons:undo.png"))) self.menu_edit.insertActions(self.action_new_point, [undo, redo]) self.menu_edit.insertSeparator(self.action_new_point)
def __init__(self, parent=None, prefix=''): """Initialize object.""" super().__init__(parent) self.setWindowTitle('Booster Energy Ramping') self.setObjectName('BOApp') cor = get_appropriate_color(section='BO') self.setWindowIcon(qta.icon( 'mdi.escalator', scale_factor=1.5, color=cor)) self.prefix = prefix self.ramp_config = None self._undo_stack = QUndoStack(self) self._tunecorr_configname = 'BO.V05.04.M0' self._chromcorr_configname = 'BO.V05.04.M0' self._setupUi() self._connSignals() self._addActions() self.setFocusPolicy(Qt.StrongFocus)
class RampMain(SiriusMainWindow): """Main window of Booster Ramp Control HLA.""" loadSignal = Signal(ramp.BoosterRamp) def __init__(self, parent=None, prefix=''): """Initialize object.""" super().__init__(parent) self.setWindowTitle('Booster Energy Ramping') self.setObjectName('BOApp') cor = get_appropriate_color(section='BO') self.setWindowIcon(qta.icon( 'mdi.escalator', scale_factor=1.5, color=cor)) self.prefix = prefix self.ramp_config = None self._undo_stack = QUndoStack(self) self._tunecorr_configname = 'BO.V05.04.M0' self._chromcorr_configname = 'BO.V05.04.M0' self._setupUi() self._connSignals() self._addActions() self.setFocusPolicy(Qt.StrongFocus) def _setupUi(self): cw = QWidget(self) glay = QGridLayout(cw) glay.setHorizontalSpacing(10) glay.setVerticalSpacing(10) lab = QLabel('<h3>Booster Energy Ramping</h3>', cw) lab.setStyleSheet(""" min-height:1.55em; max-height: 1.55em; qproperty-alignment: 'AlignVCenter | AlignRight'; background-color: qlineargradient(spread:pad, x1:1, y1:0.0227273, x2:0, y2:0, stop:0 rgba(173, 190, 207, 255), stop:1 rgba(213, 213, 213, 255));""") glay.addWidget(lab, 0, 0, 1, 2) self.settings = Settings( self, self.prefix, self.ramp_config, self._tunecorr_configname, self._chromcorr_configname) self.setMenuBar(self.settings) self.status_and_commands = StatusAndCommands( self, self.prefix, self.ramp_config) glay.addWidget(self.status_and_commands, 1, 1) self.config_parameters = ConfigParameters( self, self.prefix, self.ramp_config, self._undo_stack, self._tunecorr_configname, self._chromcorr_configname) self.config_parameters.setObjectName('ConfigParameters') glay.addWidget(self.config_parameters, 1, 0) glay.setColumnStretch(0, 15) glay.setColumnStretch(1, 1) self.setCentralWidget(cw) def _connSignals(self): self.settings.newConfigNameSignal.connect(self._receiveNewConfigName) self.settings.loadSignal.connect(self._emitLoadSignal) self.settings.opticsSettingsSignal.connect( self._handleUpdateOpticsAdjustSettings) self.settings.plotUnitSignal.connect( self.config_parameters.getPlotUnits) self.settings.newNormConfigsSignal.connect(self._receiveNewNormConfigs) self.settings.newTIConfigSignal.connect(self._receiveNewTIConfig) self.settings.newRFConfigSignal.connect(self._receiveNewRFConfig) self.config_parameters.dip_ramp.updateDipoleRampSignal.connect( self._verifySync) self.config_parameters.dip_ramp.updateDipoleRampSignal.connect( self.config_parameters.mult_ramp.updateTable) self.config_parameters.dip_ramp.updateDipoleRampSignal.connect( self.config_parameters.mult_ramp.updateGraph) self.config_parameters.dip_ramp.updateDipoleRampSignal.connect( self.config_parameters.rf_ramp.updateGraph) self.config_parameters.dip_ramp.updateDipoleRampSignal.connect( self.status_and_commands.update_ps_params) self.config_parameters.dip_ramp.updateDipoleRampSignal.connect( self.status_and_commands.update_ti_params) self.config_parameters.dip_ramp.applyChanges2MachineSignal.connect( self.status_and_commands.apply_changes) self.config_parameters.mult_ramp.updateMultipoleRampSignal.connect( self._verifySync) self.config_parameters.mult_ramp.updateMultipoleRampSignal.connect( self.status_and_commands.update_ps_params) self.config_parameters.mult_ramp.applyChanges2MachineSignal.connect( self.status_and_commands.apply_changes) self.config_parameters.mult_ramp.plotUnitSignal.connect( self.config_parameters.getPlotUnits) self.config_parameters.rf_ramp.updateRFRampSignal.connect( self._verifySync) self.config_parameters.rf_ramp.updateRFRampSignal.connect( self.status_and_commands.update_rf_params) self.config_parameters.rf_ramp.updateRFRampSignal.connect( self.status_and_commands.update_ti_params) self.config_parameters.rf_ramp.applyChanges2MachineSignal.connect( self.status_and_commands.apply_changes) self.status_and_commands.inj_eje_times.connect( self.config_parameters.dip_ramp.updateInjEjeTimes) self.loadSignal.connect(self.settings.getRampConfig) self.loadSignal.connect(self.config_parameters.handleLoadRampConfig) self.loadSignal.connect(self.status_and_commands.handleLoadRampConfig) def _addActions(self): self.act_undo = self._undo_stack.createUndoAction(self, 'Undo') self.act_undo.setIcon(qta.icon('mdi.undo')) self.act_undo.setShortcut(QKeySequence.Undo) self.settings.config_menu.addAction(self.act_undo) self.act_redo = self._undo_stack.createRedoAction(self, 'Redo') self.act_redo.setIcon(qta.icon('mdi.redo')) self.act_redo.setShortcut(QKeySequence.Redo) self.settings.config_menu.addAction(self.act_redo) @Slot(str) def _receiveNewConfigName(self, new_config_name): if self.ramp_config is None or \ self.ramp_config.name != new_config_name or \ self.ramp_config.name == '**New Configuration**': self.ramp_config = ramp.BoosterRamp(new_config_name, auto_update=True) self._undo_stack.clear() self._emitLoadSignal() @Slot(dict) def _receiveNewNormConfigs(self, norm_configs): old_norm_configs = _dcopy( self.config_parameters.mult_ramp.normalized_configs) self.ramp_config.ps_normalized_configs_set(norm_configs) self.loadSignal.emit(self.ramp_config) self._verifySync() new_norm_configs = _dcopy( self.config_parameters.mult_ramp.normalized_configs) self.config_parameters.mult_ramp.stackUndoMultipoleTableCommand( description='reconstruct normalized configs from waveforms', old=old_norm_configs, new=new_norm_configs) @Slot(dict) def _receiveNewTIConfig(self, params): for param, value in params.items(): attr_name = 'ti_params_'+param setattr(self.ramp_config, attr_name, value) self.loadSignal.emit(self.ramp_config) self._verifySync() @Slot(dict) def _receiveNewRFConfig(self, params): for param, value in params.items(): if 'duration' in param: continue attr_name = 'rf_ramp_'+param setattr(self.ramp_config, attr_name, value) self.ramp_config.rf_ramp_rampup_start_time = params['bottom_duration'] self.ramp_config.rf_ramp_rampup_stop_time = \ params['bottom_duration'] + \ params['rampup_duration'] self.ramp_config.rf_ramp_rampdown_start_time = \ params['bottom_duration'] + \ params['rampup_duration'] + \ params['top_duration'] self.ramp_config.rf_ramp_rampdown_stop_time = \ params['bottom_duration'] + \ params['rampup_duration'] + \ params['top_duration'] + \ params['rampdown_duration'] self.loadSignal.emit(self.ramp_config) self._verifySync() def _emitLoadSignal(self): try: if self.ramp_config.exist(): self.ramp_config.load() except _ConfigDBException as err: QMessageBox.critical(self, 'Error', str(err), QMessageBox.Ok) else: self.loadSignal.emit(self.ramp_config) finally: self._verifySync() def _verifySync(self): """Verify sync status related to ConfServer.""" if self.ramp_config is not None: if not self.ramp_config.synchronized: pal = self.config_parameters.palette() pal.setColor(QPalette.Text, Qt.red) self.config_parameters.setPalette(pal) self.config_parameters.setToolTip('There are unsaved changes') else: pal = self.config_parameters.palette() pal.setColor(QPalette.Text, Qt.black) self.config_parameters.setPalette(pal) self.config_parameters.setToolTip('') def closeEvent(self, ev): """Reimplement closeEvent to avoid forgeting saving changes.""" if self.ramp_config is None: ev.accept() super().closeEvent(ev) elif not self.ramp_config.synchronized: accept = self.settings.verifyUnsavedChanges() if accept: ev.accept() super().closeEvent(ev) else: ev.ignore() @Slot(str, str) def _handleUpdateOpticsAdjustSettings(self, tune_cname, chrom_cname): self._tunecorr_configname = tune_cname self._chromcorr_configname = chrom_cname self.config_parameters.updateOpticsAdjustSettings( tune_cname, chrom_cname)
class MainWindowBase(MainWindowABC, ABC): """External UI settings.""" vpoint_list: List[VPoint] vlink_list: List[VLink] __tables: Sequence[BaseTableWidget] @abstractmethod def __init__(self): super(MainWindowBase, self).__init__() # Environment path self.env = "" # Alignment mode self.alignment_mode = 0 # Entities list self.vpoint_list = [] self.vlink_list = [VLink(VLink.FRAME, 'White', (), color_rgb)] # Condition list of context menus self.context = _Context() # Preference self.prefer = Preferences() # Set path from command line home_dir = QDir.home() self.settings = QSettings(home_dir.absoluteFilePath(".pyslvs.ini"), QSettings.IniFormat, self) if ARGUMENTS.c: self.set_locate(QDir(ARGUMENTS.c).absolutePath()) else: home_dir.cd("Desktop") env = self.settings.value("ENV", home_dir.absolutePath()) self.set_locate(str(env)) # Initialize custom UI self.__undo_redo() self.__appearance() self.__alignment() self.__free_move() self.__options() self.__context_menu() def env_path(self) -> str: """Return environment path.""" return self.env def set_locate(self, locate: str) -> None: """Set environment variables.""" if locate == self.env or not QDir(locate).exists(): return self.env = locate logger.debug(f"~Set workplace to: \"{self.env}\"") def __undo_redo(self) -> None: """Undo list settings. + Undo stack. + Undo view widget. + Hot keys. """ self.command_stack = QUndoStack(self) self.command_stack.setUndoLimit(self.prefer.undo_limit_option) self.command_stack.indexChanged.connect(self.command_reload) action_redo = self.command_stack.createRedoAction(self, "Redo") action_undo = self.command_stack.createUndoAction(self, "Undo") action_redo.setShortcuts(["Ctrl+Shift+Z", "Ctrl+Y"]) action_redo.setStatusTip("Backtracking undo action.") action_redo.setIcon(QIcon(QPixmap(":/icons/redo.png"))) action_undo.setShortcut("Ctrl+Z") action_undo.setStatusTip("Recover last action.") action_undo.setIcon(QIcon(QPixmap(":/icons/undo.png"))) self.menu_edit.addActions([action_undo, action_redo]) def __appearance(self) -> None: """Start up and initialize custom widgets.""" # Entities tables tab_bar = self.entities_tab.tabBar() tab_bar.setStatusTip("Switch the tabs to change to another view mode.") self.entities_point = PointTableWidget(self.entities_point_widget) self.entities_point.cellDoubleClicked.connect(self.edit_point) self.entities_point.delete_request.connect(self.delete_selected_points) self.entities_point_layout.addWidget(self.entities_point) self.entities_link = LinkTableWidget(self.entities_link_widget) self.entities_link.cellDoubleClicked.connect(self.edit_link) self.entities_link.delete_request.connect(self.delete_selected_links) self.entities_link_layout.addWidget(self.entities_link) self.entities_expr = ExprTableWidget(self.EntitiesExpr_widget) self.entities_expr_layout.insertWidget(0, self.entities_expr) self.__tables = ( self.entities_point, self.entities_link, self.entities_expr, ) # Select all button on the Point and Link tab as corner widget select_all_button = QPushButton() select_all_button.setIcon(QIcon(QPixmap(":/icons/select_all.png"))) select_all_button.setToolTip("Select all") select_all_button.setStatusTip("Select all item of point table.") @Slot() def table_select_all() -> None: """Distinguish table by tab index.""" self.__tables[self.entities_tab.currentIndex()].selectAll() select_all_button.clicked.connect(table_select_all) self.entities_tab.setCornerWidget(select_all_button) select_all_action = QAction("Select all point", self) select_all_action.triggered.connect(table_select_all) select_all_action.setShortcut("Ctrl+A") select_all_action.setShortcutContext(Qt.WindowShortcut) self.addAction(select_all_action) # QPainter canvas window self.main_canvas = MainCanvas(self) self.entities_tab.currentChanged.connect( self.main_canvas.set_selection_mode) select_tips = QLabel(self, Qt.ToolTip) @Slot(QPoint, str) def show_select_tips(pos: QPoint, text: str) -> None: select_tips.setText(text) select_tips.move(pos - QPoint(0, select_tips.height())) select_tips.show() self.main_canvas.selected_tips.connect(show_select_tips) self.main_canvas.selected_tips_hide.connect(select_tips.hide) @Slot(tuple, bool) def table_selection(selection: Sequence[int], check_key: bool) -> None: """Distinguish table by tab index.""" index = self.entities_tab.currentIndex() self.__tables[index].set_selections(selection, check_key) self.main_canvas.selected.connect(table_selection) self.entities_point.row_selection_changed.connect( self.main_canvas.set_selection) @Slot() def table_clear_selection() -> None: """Clear the selection of specific table by tab index.""" index = self.entities_tab.currentIndex() self.__tables[index].clearSelection() clean_selection_action = QAction("Clean selection", self) clean_selection_action.triggered.connect(table_clear_selection) clean_selection_action.setShortcut("Esc") clean_selection_action.setShortcutContext(Qt.WindowShortcut) self.main_canvas.no_selected.connect(table_clear_selection) self.addAction(clean_selection_action) self.main_canvas.free_moved.connect(self.set_free_move) self.main_canvas.alt_add.connect(self.add_point_by_pos) self.main_canvas.doubleclick_edit.connect(self.edit_point) self.main_canvas.zoom_changed.connect(self.zoom_bar.setValue) self.main_canvas.tracking.connect(self.set_mouse_pos) self.canvas_layout.insertWidget(0, self.main_canvas) self.canvas_splitter.setSizes([600, 10, 30]) # Selection label on status bar right side selection_label = SelectionLabel(self) self.entities_point.selectionLabelUpdate.connect( selection_label.update_select_point) self.main_canvas.browse_tracking.connect( selection_label.update_mouse_position) self.status_bar.addPermanentWidget(selection_label) # FPS label on status bar right side fps_label = FPSLabel(self) self.main_canvas.fps_updated.connect(fps_label.update_text) self.status_bar.addPermanentWidget(fps_label) # Inputs widget self.inputs_widget = InputsWidget(self) self.inputs_tab_layout.addWidget(self.inputs_widget) self.free_move_button.toggled.connect( self.inputs_widget.variable_value_reset) self.inputs_widget.about_to_resolve.connect(self.resolve) @Slot(tuple, bool) def inputs_selection(selections: Sequence[int], _=None) -> None: """Distinguish table by tab index.""" self.inputs_widget.clear_selection() if self.entities_tab.currentIndex() == 0: self.inputs_widget.set_selection(selections) self.main_canvas.selected.connect(inputs_selection) self.main_canvas.no_selected.connect( self.inputs_widget.clear_selection) self.inputs_widget.update_preview_button.clicked.connect( self.main_canvas.update_preview_path) # Synthesis collections self.collections = Collections(self) # Number and type synthesis self.structure_synthesis = StructureSynthesis(self) for widget, name in [ (self.structure_synthesis, "Structural"), (self.collections, "Collections"), ]: # type: QWidget, str self.synthesis_tab_widget.addTab(widget, widget.windowIcon(), name) # Dimensional synthesis self.dimensional_synthesis = DimensionalSynthesis(self) self.main_canvas.set_target_point.connect( self.dimensional_synthesis.set_point) self.synthesis_tab_widget.addTab( self.dimensional_synthesis, self.dimensional_synthesis.windowIcon(), "Dimensional") # Same options of structure previews as_node1 = self.collections.structure_widget.graph_link_as_node as_node2 = self.structure_synthesis.graph_link_as_node as_node1.toggled.connect(as_node2.setChecked) as_node2.toggled.connect(as_node1.setChecked) show_label1 = self.collections.structure_widget.graph_show_label show_label2 = self.structure_synthesis.graph_show_label show_label1.toggled.connect(show_label2.setChecked) show_label2.toggled.connect(show_label1.setChecked) # File widget settings self.project_widget = ProjectWidget(self) self.project_layout.addWidget(self.project_widget) # Zooming and console dock will hide when startup self.zoom_widget.hide() self.console_widget.hide() # Connect to GUI button debug_mode = ARGUMENTS.debug_mode self.console_disconnect_button.setEnabled(not debug_mode) self.console_connect_button.setEnabled(debug_mode) # Splitter stretch factor self.main_splitter.setStretchFactor(0, 4) self.main_splitter.setStretchFactor(1, 15) self.mechanism_panel_splitter.setSizes([500, 200]) # Enable mechanism menu actions when shows self.menu_mechanism.aboutToShow.connect(self.enable_mechanism_actions) # New main window function self.action_new_window.triggered.connect(self.new) def __alignment(self) -> None: """Menu of alignment function.""" def switch_icon(m: int, icon_name: str) -> Callable[[], None]: @Slot() def func() -> None: self.alignment_mode = m self.alignment_button.setIcon(QIcon(QPixmap(icon_name))) return func menu = QMenu(self) for i, (text, icon) in enumerate([ ("Vertical alignment", "vertical_align"), ("Horizontal alignment", "horizontal_align"), ]): icon = f":/icons/{icon}.png" action = QAction(QIcon(QPixmap(icon)), text, self) action.triggered.connect(switch_icon(i, icon)) menu.addAction(action) self.alignment_button.setMenu(menu) self.alignment_button.clicked.connect(self.point_alignment) def __free_move(self) -> None: """Menu of free move mode.""" def free_move_mode_func(j: int, icon_qt: QIcon) -> Callable[[], None]: @Slot() def func() -> None: self.free_move_button.setIcon(icon_qt) self.main_canvas.set_free_move(j) self.entities_tab.setCurrentIndex(0) self.inputs_widget.variable_stop.click() return func menu = QMenu(self) for i, (text, icon, tip) in enumerate([ ("View mode", "free_move_off", "Disable free move mode."), ("Translate mode", "translate", "Edit by 2 DOF moving."), ("Rotate mode", "rotate", "Edit by 1 DOF moving."), ("Reflect mode", "reflect", "Edit by flip axis."), ]): action = QAction(QIcon(QPixmap(f":/icons/{icon}.png")), text, self) action.triggered.connect(free_move_mode_func(i, action.icon())) action.setShortcut(f"Ctrl+{i + 1}") action.setShortcutContext(Qt.WindowShortcut) action.setStatusTip(tip) menu.addAction(action) if i == 0: self.free_move_disable = action self.free_move_button.setMenu(menu) def __options(self) -> None: """Signal connection for option widgets. + Spin boxes + Combo boxes + Check boxes """ # While value change, update the canvas widget self.zoom_bar.valueChanged.connect(self.main_canvas.set_zoom) self.action_show_point_mark.toggled.connect( self.main_canvas.set_point_mark) self.action_show_dimensions.toggled.connect( self.main_canvas.set_show_dimension) def __action(self, name: Union[str, QAction], slot: Optional[Callable[..., None]] = None, enable: Optional[_Enable] = None, *, cast_to: Type[_N]) -> _N: """New action or menu.""" is_menu = cast_to is QMenu if isinstance(name, QAction): menu: Optional[QMenu] = None action = name elif is_menu: menu = QMenu(name, self) action = menu.menuAction() else: menu = None action = QAction(name, self) if slot is not None: action.triggered.connect(slot) if enable is not None: for target in self.context[enable]: if isinstance(target, QMenu): if is_menu: target.addMenu(menu) else: target.addAction(action) elif isinstance(target, list): target.append(action) else: raise ValueError("not a list or menu") if is_menu: return cast(QMenu, menu) else: return action def __context_menu(self) -> None: """Context menu settings.""" self.entities_point_widget.customContextMenuRequested.connect( self.point_context_menu) self.pop_point = QMenu(self) self.entities_link_widget.customContextMenuRequested.connect( self.link_context_menu) self.pop_link = QMenu(self) self.main_canvas.setContextMenuPolicy(Qt.CustomContextMenu) self.main_canvas.customContextMenuRequested.connect( self.canvas_context_menu) self.pop_canvas_p = QMenu(self) self.pop_canvas_l = QMenu(self) for enable, menu in ( (_Enable.T_P, self.pop_point), (_Enable.T_L, self.pop_link), (_Enable.C_P, self.pop_canvas_p), (_Enable.C_L, self.pop_canvas_l), ): menu.setSeparatorsCollapsible(True) self.context[enable] = menu # EntitiesPoint two_menus_p = _Enable.T_P | _Enable.C_P two_menus_l = _Enable.T_L | _Enable.C_L self.__action("&Add", self.new_point, _Enable.T_P | _Enable.P_NO, cast_to=QAction) self.__action("&Add", self.add_normal_point, _Enable.C_P | _Enable.P_NO, cast_to=QAction) self.__action("Add to [ground]", self.add_fixed_point, _Enable.C_P | _Enable.P_NO, cast_to=QAction) self.action_c_add_target = self.__action("Add &Target Point", self.add_target_point, _Enable.C_P | _Enable.C_L | _Enable.P_NO | _Enable.L_NO, cast_to=QAction) self.__action(self.action_new_link, enable=two_menus_p | two_menus_l | _Enable.P_MUL | _Enable.L_NO, cast_to=QAction) self.__action("&Edit", self.edit_point, two_menus_p | _Enable.P_ONE, cast_to=QAction) self.action_p_lock = self.__action("&Grounded", self.lock_points, two_menus_p | _Enable.P_ANY, cast_to=QAction) self.action_p_lock.setCheckable(True) self.pop_point_m = self.__action("Multiple joint", enable=two_menus_p | _Enable.P_MUL, cast_to=QMenu) self.__action("&Copy Table Data", self.copy_points_table, _Enable.T_P | _Enable.P_ONE, cast_to=QAction) self.__action("Copy Coordinate", self.copy_coord, _Enable.T_P | _Enable.P_ONE, cast_to=QAction) self.__action("C&lone", self.clone_point, two_menus_p | _Enable.P_ONE, cast_to=QAction) self.pop_point.addSeparator() self.pop_canvas_p.addSeparator() self.__action("&Delete", self.delete_selected_points, two_menus_p | _Enable.P_ANY, cast_to=QAction) # EntitiesLink self.__action("&Edit", self.edit_link, two_menus_l | _Enable.L_ONE, cast_to=QAction) self.pop_link_m = self.__action("Merge Links", enable=two_menus_l | _Enable.L_MUL, cast_to=QMenu) self.__action("&Copy Table Data", self.copy_links_table, _Enable.T_L | _Enable.L_ONE, cast_to=QAction) self.__action("&Release", self.release_ground, two_menus_l | _Enable.L_ONE | _Enable.L_GND, cast_to=QAction) self.__action("C&onstrain", self.constrain_link, two_menus_l | _Enable.L_ONE | _Enable.L_N_GND, cast_to=QAction) self.pop_link.addSeparator() self.pop_canvas_l.addSeparator() self.__action("Remove &Redundant Links", self.delete_redundant_links, _Enable.T_L, cast_to=QAction) self.__action("&Delete", self.delete_selected_links, two_menus_l | _Enable.L_ANY, cast_to=QAction) @Slot(int, name='on_entities_tab_currentChanged') def __set_selection_mode(self, index: int) -> None: """Connect selection signal for main canvas.""" # Set selection from click table items try: for table in self.__tables: table.row_selection_changed.disconnect() except TypeError: pass self.__tables[index].row_selection_changed.connect( self.main_canvas.set_selection) # Double click signal try: self.main_canvas.doubleclick_edit.disconnect() except TypeError: pass if index == 0: self.main_canvas.doubleclick_edit.connect(self.edit_point) elif index == 1: self.main_canvas.doubleclick_edit.connect(self.edit_link) # Clear all selections for table in self.__tables: table.clearSelection() self.inputs_widget.clear_selection()
class BONormEdit(SiriusMainWindow): """Widget to perform optics adjust in normalized configurations.""" normConfigChanged = Signal(float, dict) def __init__(self, parent=None, prefix='', ramp_config=None, norm_config=None, time=None, energy=None, magnets=dict(), conn_sofb=None, tunecorr_configname=None, chromcorr_configname=None): """Initialize object.""" super().__init__(parent) self.setWindowTitle('Edit Normalized Configuration') self.setObjectName('BOApp') self.prefix = prefix self.ramp_config = ramp_config self.norm_config = _dcopy(norm_config) self.time = time self.energy = energy self._aux_magnets = magnets self._conn_sofb = conn_sofb self._tunecorr = BOTuneCorr(tunecorr_configname) self._chromcorr = BOChromCorr(chromcorr_configname) self._reference = _dcopy(norm_config) self._currChrom = self._estimateChrom(use_ref=True) self._deltas = { 'kicks': dict(), 'factorH': 0.0, 'factorV': 0.0, 'tuneX': 0.0, 'tuneY': 0.0, 'chromX': self._currChrom[0], 'chromY': self._currChrom[1], } self._setupUi() self._setupMenu() self.verifySync() # ---------- setup/build layout ---------- def _setupUi(self): self.label_description = QLabel( '<h2>'+self.norm_config['label']+'</h2>', self) self.label_description.setAlignment(Qt.AlignCenter) self.label_time = QLabel('<h2>T = '+str(self.time)+'ms</h2>', self) self.label_time.setAlignment(Qt.AlignCenter) self.strengths = self._setupStrengthWidget() self.orbit = self._setupOrbitWidget() self.tune = self._setupTuneWidget() self.chrom = self._setupChromWidget() self.bt_apply = QPushButton(qta.icon('fa5s.angle-right'), '', self) self.bt_apply.setToolTip('Apply Changes to Machine') self.bt_apply.setStyleSheet('icon-size: 30px 30px;') self.bt_apply.clicked.connect(self._updateRampConfig) cw = QWidget() lay = QGridLayout() lay.setVerticalSpacing(10) lay.setHorizontalSpacing(10) lay.addWidget(self.label_description, 0, 0, 1, 2) lay.addWidget(self.label_time, 1, 0, 1, 2) lay.addWidget(self.strengths, 2, 0, 4, 1) lay.addWidget(self.orbit, 2, 1) lay.addWidget(self.tune, 3, 1) lay.addWidget(self.chrom, 4, 1) lay.addWidget(self.bt_apply, 5, 1) lay.setColumnStretch(0, 2) lay.setColumnStretch(1, 2) lay.setRowStretch(0, 2) lay.setRowStretch(1, 2) lay.setRowStretch(2, 8) lay.setRowStretch(3, 8) lay.setRowStretch(4, 8) lay.setRowStretch(5, 1) cw.setLayout(lay) cw.setStyleSheet(""" QGroupBox::title{ subcontrol-origin: margin; subcontrol-position: top center; padding: 0 2px 0 2px;}""") cw.setFocusPolicy(Qt.StrongFocus) self.setCentralWidget(cw) def _setupMenu(self): self.menubar = QMenuBar(self) self.layout().setMenuBar(self.menubar) self.menu = self.menubar.addMenu('Options') self.act_saveas = self.menu.addAction('Save as...') self.act_saveas.triggered.connect(self._showSaveAsPopup) self._undo_stack = QUndoStack(self) self.act_undo = self._undo_stack.createUndoAction(self, 'Undo') self.act_undo.setShortcut(QKeySequence.Undo) self.menu.addAction(self.act_undo) self.act_redo = self._undo_stack.createRedoAction(self, 'Redo') self.act_redo.setShortcut(QKeySequence.Redo) self.menu.addAction(self.act_redo) def _setupStrengthWidget(self): scrollarea = QScrollArea() self.nconfig_data = QWidget() flay_configdata = QFormLayout() psnames = self._get_PSNames() self._map_psnames2wigdets = dict() for ps in psnames: ps = SiriusPVName(ps) if ps in ramp.BoosterRamp.PSNAME_DIPOLES: ps_value = QLabel(str(self.norm_config[ps])+' GeV', self) flay_configdata.addRow(QLabel(ps + ': ', self), ps_value) else: ps_value = QDoubleSpinBoxPlus(self.nconfig_data) ps_value.setDecimals(6) ps_value.setMinimum(-10000) ps_value.setMaximum(10000) ps_value.setValue(self.norm_config[ps]) ps_value.valueChanged.connect(self._handleStrenghtsSet) if ps.dev in {'QD', 'QF', 'QS'}: unit_txt = '1/m' elif ps.dev in {'SD', 'SF'}: unit_txt = '1/m²' else: unit_txt = 'urad' label_unit = QLabel(unit_txt, self) label_unit.setStyleSheet("min-width:2.5em; max-width:2.5em;") hb = QHBoxLayout() hb.addWidget(ps_value) hb.addWidget(label_unit) flay_configdata.addRow(QLabel(ps + ': ', self), hb) ps_value.setObjectName(ps) ps_value.setStyleSheet("min-height:1.29em; max-height:1.29em;") self._map_psnames2wigdets[ps] = ps_value self.nconfig_data.setObjectName('data') self.nconfig_data.setStyleSheet(""" #data{background-color: transparent;}""") self.nconfig_data.setLayout(flay_configdata) scrollarea.setWidget(self.nconfig_data) self.cb_checklims = QCheckBox('Set limits according to energy', self) self.cb_checklims.setChecked(False) self.cb_checklims.stateChanged.connect(self._handleStrengtsLimits) self.bt_graph = QPushButton(qta.icon('mdi.chart-line'), '', self) self.bt_graph.clicked.connect(self._show_kicks_graph) gbox = QGroupBox() gbox.setObjectName('strengths') gbox.setStyleSheet('#strengths{min-width:20em;}') glay = QGridLayout() glay.addWidget(scrollarea, 0, 0, 1, 2) glay.addWidget(self.cb_checklims, 1, 0, alignment=Qt.AlignLeft) glay.addWidget(self.bt_graph, 1, 1, alignment=Qt.AlignRight) gbox.setLayout(glay) return gbox def _setupOrbitWidget(self): self.bt_get_kicks = QPushButton('Get Kicks from SOFB', self) self.bt_get_kicks.clicked.connect(self._handleGetKicksFromSOFB) label_correctH = QLabel('Correct H', self, alignment=Qt.AlignRight | Qt.AlignVCenter) self.sb_correctH = QDoubleSpinBoxPlus(self) self.sb_correctH.setValue(self._deltas['factorH']) self.sb_correctH.setDecimals(1) self.sb_correctH.setMinimum(-10000) self.sb_correctH.setMaximum(10000) self.sb_correctH.setSingleStep(0.1) self.sb_correctH.setObjectName('factorH') self.sb_correctH.editingFinished.connect(self._handleCorrFactorsSet) labelH = QLabel('%', self) label_correctV = QLabel('Correct V', self, alignment=Qt.AlignRight | Qt.AlignVCenter) self.sb_correctV = QDoubleSpinBoxPlus(self) self.sb_correctV.setValue(self._deltas['factorV']) self.sb_correctV.setDecimals(1) self.sb_correctV.setMinimum(-10000) self.sb_correctV.setMaximum(10000) self.sb_correctV.setSingleStep(0.1) self.sb_correctV.setObjectName('factorV') self.sb_correctV.editingFinished.connect(self._handleCorrFactorsSet) labelV = QLabel('%', self) self.bt_update_ref_orbit = QPushButton('Update reference', self) self.bt_update_ref_orbit.clicked.connect( _part(self._updateReference, 'corrs')) gbox = QGroupBox('Orbit', self) lay = QGridLayout() lay.addWidget(self.bt_get_kicks, 0, 0, 1, 4) lay.addWidget(label_correctH, 1, 0) lay.addWidget(self.sb_correctH, 1, 2) lay.addWidget(labelH, 1, 3) lay.addWidget(label_correctV, 2, 0) lay.addWidget(self.sb_correctV, 2, 2) lay.addWidget(labelV, 2, 3) lay.addWidget(self.bt_update_ref_orbit, 3, 2, 1, 2) lay.setColumnStretch(0, 16) lay.setColumnStretch(1, 1) lay.setColumnStretch(2, 14) lay.setColumnStretch(3, 2) gbox.setLayout(lay) return gbox def _setupTuneWidget(self): for cord in ['X', 'Y']: setattr(self, 'label_deltaTune'+cord, QLabel('Δν<sub>'+cord+'</sub>: ')) lab = getattr(self, 'label_deltaTune'+cord) lab.setStyleSheet("min-width:1.55em; max-width:1.55em;") setattr(self, 'sb_deltaTune'+cord, QDoubleSpinBoxPlus(self)) sb = getattr(self, 'sb_deltaTune'+cord) sb.setDecimals(6) sb.setMinimum(-5) sb.setMaximum(5) sb.setSingleStep(0.0001) sb.setObjectName('tune'+cord) sb.editingFinished.connect(self._handleDeltaTuneSet) label_KL = QLabel('<h4>ΔKL [1/m]</h4>', self) label_KL.setStyleSheet("""min-height:1.55em; max-height:1.55em; qproperty-alignment: AlignCenter;""") self.l_deltaKLQF = QLabel('', self) self.l_deltaKLQD = QLabel('', self) self.bt_update_ref_deltaKL = QPushButton('Update reference', self) self.bt_update_ref_deltaKL.clicked.connect( _part(self._updateReference, 'quads')) gbox = QGroupBox('Tune', self) lay = QGridLayout() lay.addWidget(self.label_deltaTuneX, 1, 0) lay.addWidget(self.sb_deltaTuneX, 1, 1) lay.addWidget(self.label_deltaTuneY, 1, 3) lay.addWidget(self.sb_deltaTuneY, 1, 4) lay.addWidget(label_KL, 3, 0, 1, 5) lay.addWidget(QLabel('QF: '), 4, 0) lay.addWidget(self.l_deltaKLQF, 4, 1) lay.addWidget(QLabel('QD: '), 4, 3) lay.addWidget(self.l_deltaKLQD, 4, 4) lay.addWidget(self.bt_update_ref_deltaKL, 6, 3, 1, 2) lay.setVerticalSpacing(6) lay.setColumnStretch(0, 2) lay.setColumnStretch(1, 4) lay.setColumnStretch(2, 1) lay.setColumnStretch(3, 2) lay.setColumnStretch(4, 4) lay.setRowStretch(0, 1) lay.setRowStretch(1, 2) lay.setRowStretch(2, 1) lay.setRowStretch(3, 2) lay.setRowStretch(4, 2) lay.setRowStretch(5, 1) lay.setRowStretch(6, 2) gbox.setLayout(lay) return gbox def _setupChromWidget(self): for cord in ['X', 'Y']: setattr(self, 'label_Chrom'+cord, QLabel('ξ<sub>'+cord+'</sub>: ')) lab = getattr(self, 'label_Chrom'+cord) lab.setStyleSheet("min-width:1.55em; max-width:1.55em;") setattr(self, 'sb_Chrom'+cord, QDoubleSpinBoxPlus(self)) sb = getattr(self, 'sb_Chrom'+cord) sb.setDecimals(6) sb.setMinimum(-5) sb.setMaximum(5) sb.setSingleStep(0.0001) sb.setObjectName('chrom'+cord) sb.setValue(self._deltas['chrom'+cord]) sb.editingFinished.connect(self._handleChromSet) label_SL = QLabel('<h4>ΔSL [1/m<sup>2</sup>]</h4>', self) label_SL.setStyleSheet("""min-height:1.55em; max-height:1.55em; qproperty-alignment: AlignCenter;""") self.l_deltaSLSF = QLabel('', self) self.l_deltaSLSD = QLabel('', self) self.bt_update_ref_deltaSL = QPushButton('Update reference', self) self.bt_update_ref_deltaSL.clicked.connect( _part(self._updateReference, 'sexts')) gbox = QGroupBox('Chromaticity', self) lay = QGridLayout() lay.addWidget(self.label_ChromX, 1, 0) lay.addWidget(self.sb_ChromX, 1, 1) lay.addWidget(self.label_ChromY, 1, 3) lay.addWidget(self.sb_ChromY, 1, 4) lay.addWidget(label_SL, 3, 0, 1, 5) lay.addWidget(QLabel('SF: '), 4, 0) lay.addWidget(self.l_deltaSLSF, 4, 1) lay.addWidget(QLabel('SD: '), 4, 3) lay.addWidget(self.l_deltaSLSD, 4, 4) lay.addWidget(self.bt_update_ref_deltaSL, 6, 3, 1, 2) lay.setVerticalSpacing(6) lay.setColumnStretch(0, 2) lay.setColumnStretch(1, 4) lay.setColumnStretch(2, 1) lay.setColumnStretch(3, 2) lay.setColumnStretch(4, 4) lay.setRowStretch(0, 1) lay.setRowStretch(1, 2) lay.setRowStretch(2, 1) lay.setRowStretch(3, 2) lay.setRowStretch(4, 2) lay.setRowStretch(5, 1) lay.setRowStretch(6, 2) gbox.setLayout(lay) return gbox # ---------- server communication ---------- def _save(self, name): try: nconf = ramp.BoosterNormalized() nconf.value = self.norm_config nconf.save(new_name=name) except _ConfigDBException as err: QMessageBox.critical(self, 'Error', str(err), QMessageBox.Ok) def _showSaveAsPopup(self): self._saveAsPopup = _SaveConfigDialog('bo_normalized', self) self._saveAsPopup.configname.connect(self._save) self._saveAsPopup.open() def verifySync(self): if self.ramp_config is None: return if not self.ramp_config.verify_ps_normalized_synchronized( self.time, value=self.norm_config): self.label_time.setStyleSheet('color: red;') self.label_description.setStyleSheet('color: red;') self.setToolTip("There are unsaved changes") else: self.label_time.setStyleSheet('color: black;') self.label_description.setStyleSheet('color: black;') self.setToolTip("") # ---------- strengths ---------- def _handleStrenghtsSet(self, new_value): psname = self.sender().objectName() self._stack_command( self.sender(), self.norm_config[psname], new_value, message='set '+psname+' strength to {}'.format(new_value)) self.norm_config[psname] = new_value self.verifySync() def _handleStrengtsLimits(self, state): psnames = list(self.norm_config.keys()) psnames.remove('BO-Fam:PS-B-1') psnames.remove('BO-Fam:PS-B-2') psnames.remove('label') if state: for ps in psnames: ps_value = self.nconfig_data.findChild( QDoubleSpinBoxPlus, name=ps) ma = _MASearch.conv_psname_2_psmaname(ps) aux = self._aux_magnets[ma] currs = (aux.current_min, aux.current_max) lims = aux.conv_current_2_strength( currents=currs, strengths_dipole=self.energy) ps_value.setMinimum(min(lims)) ps_value.setMaximum(max(lims)) else: for ps in psnames: ps_value = self.nconfig_data.findChild( QDoubleSpinBoxPlus, name=ps) ps_value.setMinimum(-10000) ps_value.setMaximum(10000) def _updateStrenghtsWidget(self, pstype): psnames = self._get_PSNames(pstype) wid2change = psnames if psnames else list(self.norm_config.keys()) for wid in wid2change: value = self.norm_config[wid] self._map_psnames2wigdets[wid].setValue(value) # ---------- orbit ---------- def _updateCorrKicks(self): for psname, dkick in self._deltas['kicks'].items(): corr_factor = self._deltas['factorV'] if 'CV' in psname \ else self._deltas['factorH'] corr_factor /= 100 self.norm_config[psname] = self._reference[psname] + \ dkick*corr_factor def _handleGetKicksFromSOFB(self): if not self._conn_sofb.connected: QMessageBox.warning( self, 'Not Connected', 'There are not connected PVs!', QMessageBox.Ok) return dkicks = self._conn_sofb.get_deltakicks() if not dkicks: QMessageBox.warning( self, 'Could not get kicks', 'Could not get kicks from SOFB!', QMessageBox.Ok) return self._deltas['kicks'] = dkicks self._updateCorrKicks() self._updateStrenghtsWidget('corrs') self.verifySync() def _handleCorrFactorsSet(self): widget = self.sender() factor = widget.objectName() dim = ' vertical ' if factor == 'factorV' else ' horizantal ' new_value = widget.value() self._stack_command( widget, self._deltas[factor], new_value, message='set'+dim+'orbit correction factor to {}'.format( new_value)) self._deltas[factor] = new_value self._updateCorrKicks() self._updateStrenghtsWidget('corrs') self.verifySync() def _resetOrbitChanges(self): self._deltas['kicks'] = dict() self._deltas['factorH'] = 0.0 self._deltas['factorV'] = 0.0 self.sb_correctH.setValue(0.0) self.sb_correctV.setValue(0.0) # ---------- tune ---------- def _handleDeltaTuneSet(self): widget = self.sender() tune = widget.objectName() dim = ' vertical ' if tune == 'tuneY' else ' horizantal ' new_value = widget.value() self._stack_command( widget, self._deltas[tune], new_value, message='set'+dim+'delta tune to {}'.format( new_value)) self._deltas[tune] = new_value self._updateDeltaKL() def _updateDeltaKL(self): self._deltaKL = self._tunecorr.calculate_deltaKL( [self._deltas['tuneX'], self._deltas['tuneY']]) self.l_deltaKLQF.setText('{: 4f}'.format(self._deltaKL[0])) self.l_deltaKLQD.setText('{: 4f}'.format(self._deltaKL[1])) self.norm_config['BO-Fam:PS-QF'] = \ self._reference['BO-Fam:PS-QF'] + self._deltaKL[0] self.norm_config['BO-Fam:PS-QD'] = \ self._reference['BO-Fam:PS-QD'] + self._deltaKL[1] self._updateStrenghtsWidget('quads') self.verifySync() def _resetTuneChanges(self): self.sb_deltaTuneX.setValue(0) self.sb_deltaTuneY.setValue(0) self._deltaKL = [0.0, 0.0] self.l_deltaKLQF.setText('{: 6f}'.format(self._deltaKL[0])) self.l_deltaKLQD.setText('{: 6f}'.format(self._deltaKL[1])) # ---------- chromaticity ---------- def _estimateChrom(self, use_ref=False): nom_SL = self._chromcorr.nominal_intstrengths.flatten() if use_ref: curr_SL = _np.array([self._reference['BO-Fam:PS-SF'], self._reference['BO-Fam:PS-SD']]) else: curr_SL = _np.array([self.norm_config['BO-Fam:PS-SF'], self.norm_config['BO-Fam:PS-SD']]) delta_SL = curr_SL - nom_SL return self._chromcorr.calculate_Chrom(delta_SL) def _handleChromSet(self): widget = self.sender() chrom = widget.objectName() dim = ' vertical ' if chrom == 'chromY' else ' horizantal ' new_value = widget.value() self._stack_command( widget, self._deltas[chrom], new_value, message='set'+dim+'chromaticity to {}'.format( new_value)) self._deltas[chrom] = new_value self._updateDeltaSL() def _updateDeltaSL(self): desired_Chrom = _np.array([self._deltas['chromX'], self._deltas['chromY']]) deltas = desired_Chrom - self._currChrom self._deltaSL = self._chromcorr.calculate_deltaSL( [deltas[0], deltas[1]]) self.l_deltaSLSF.setText('{: 4f}'.format(self._deltaSL[0])) self.l_deltaSLSD.setText('{: 4f}'.format(self._deltaSL[1])) self.norm_config['BO-Fam:PS-SF'] = \ self._reference['BO-Fam:PS-SF'] + self._deltaSL[0] self.norm_config['BO-Fam:PS-SD'] = \ self._reference['BO-Fam:PS-SD'] + self._deltaSL[1] self._updateStrenghtsWidget('sexts') self.verifySync() def _resetChromChanges(self): self._currChrom = self._estimateChrom(use_ref=True) self.sb_ChromX.setValue(self._currChrom[0]) self.sb_ChromY.setValue(self._currChrom[1]) self._deltaSL = [0.0, 0.0] self.l_deltaSLSF.setText('{: 6f}'.format(self._deltaSL[0])) self.l_deltaSLSD.setText('{: 6f}'.format(self._deltaSL[1])) # ---------- update methods ---------- def _updateReference(self, pstype): psnames = self._get_PSNames(pstype) for ps in psnames: self._reference[ps] = self.norm_config[ps] if pstype == 'corrs': self._resetOrbitChanges() elif pstype == 'quads': self._resetTuneChanges() elif pstype == 'sexts': self._resetChromChanges() else: self._resetOrbitChanges() self._resetTuneChanges() self._resetChromChanges() self.verifySync() def _updateRampConfig(self): if self.norm_config is not None: self.normConfigChanged.emit(self.time, _dcopy(self.norm_config)) def updateTime(self, time): """Update norm config time.""" self.time = time self.label_time.setText('<h2>T = '+str(time)+'ms</h2>') self.energy = self.ramp_config.ps_waveform_interp_energy(time) self._handleStrengtsLimits(self.cb_checklims.checkState()) self.verifySync() def updateLabel(self, label): """Update norm config label.""" self.norm_config['label'] = label self.label_description.setText('<h2>'+label+'</h2>') self.verifySync() @Slot(str, str) def updateSettings(self, tunecorr_configname, chromcorr_configname): self._tunecorr = BOTuneCorr(tunecorr_configname) self._chromcorr = BOChromCorr(chromcorr_configname) self._updateDeltaKL() self._estimateChrom(use_ref=True) self._updateDeltaSL() # ---------- handle undo redo stack ---------- def _stack_command(self, widget, old_value, new_value, message): global _flag_stack_next_command, _flag_stacking if _flag_stack_next_command and (old_value != new_value): _flag_stacking = True command = _UndoRedoSpinbox(widget, old_value, new_value, message) self._undo_stack.push(command) else: _flag_stack_next_command = True # ---------- helper methods ---------- def _get_PSNames(self, pstype=None): psnames = list() if pstype == 'corrs': psnames = _PSSearch.get_psnames({'sec': 'BO', 'dev': 'C(V|H)'}) elif pstype == 'quads': psnames = ['BO-Fam:PS-QF', 'BO-Fam:PS-QD'] elif pstype == 'sexts': psnames = ['BO-Fam:PS-SF', 'BO-Fam:PS-SD'] else: psnames = _PSSearch.get_psnames({'sec': 'BO', 'sub': 'Fam'}) psnames.extend(_PSSearch.get_psnames({'sec': 'BO', 'dev': 'QS'})) psnames.extend(_PSSearch.get_psnames({'sec': 'BO', 'dev': 'CH'})) psnames.extend(_PSSearch.get_psnames({'sec': 'BO', 'dev': 'CV'})) return psnames def _show_kicks_graph(self): strenghts_dict = _dcopy(self.norm_config) strenghts_dict.pop('label') graph = _ShowCorrectorKicks(self, self.time, strenghts_dict) graph.show()