class MainWindowBase(QMainWindow, Ui_MainWindow, metaclass=QABCMeta): """External UI settings.""" @abstractmethod def __init__(self): super(MainWindowBase, self).__init__() self.setupUi(self) self.setAttribute(Qt.WA_DeleteOnClose) # Initialize custom UI self.__undo_redo() self.__appearance() self.__free_move() self.__options() self.__zoom() self.__point_context_menu() self.__link_context_menu() self.__canvas_context_menu() # Environment path self.env = "" if ARGUMENTS.c: self.set_locate(QFileInfo(ARGUMENTS.c).canonicalFilePath()) else: desktop = QStandardPaths.writableLocation( QStandardPaths.DesktopLocation) self.set_locate(self.settings.value("ENV", desktop, type=str)) def show(self): """Overridden function to zoom the canvas's size after startup.""" super(MainWindowBase, self).show() self.main_canvas.zoom_to_fit() def set_locate(self, locate: str): """Set environment variables.""" if locate == self.env: # If no changed. return self.env = locate logger.debug(f"~Set workplace to: [\"{self.env}\"]") def __undo_redo(self): """Undo list settings. + Undo stack. + Undo view widget. + Hot keys. """ self.command_stack = QUndoStack(self) self.command_stack.setUndoLimit(self.undo_limit_option.value()) self.undo_limit_option.valueChanged.connect( self.command_stack.setUndoLimit) self.command_stack.indexChanged.connect(self.command_reload) self.undo_view = QUndoView(self.command_stack) self.undo_view.setEmptyLabel("~ Start Pyslvs") self.undo_redo_layout.addWidget(self.undo_view) self.action_redo = self.command_stack.createRedoAction(self, "Redo") self.action_undo = self.command_stack.createUndoAction(self, "Undo") self.action_redo.setShortcuts([ QKeySequence("Ctrl+Shift+Z"), QKeySequence("Ctrl+Y"), ]) self.action_redo.setStatusTip("Backtracking undo action.") self.action_redo.setIcon(QIcon(QPixmap(":/icons/redo.png"))) self.action_undo.setShortcut("Ctrl+Z") self.action_undo.setStatusTip("Recover last action.") self.action_undo.setIcon(QIcon(QPixmap(":/icons/undo.png"))) self.menu_edit.addAction(self.action_undo) self.menu_edit.addAction(self.action_redo) def __appearance(self): """Start up and initialize custom widgets.""" # Version label self.version_label.setText(__version__) # Entities tables self.entities_tab.tabBar().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) # 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(): """Distinguish table by tab index.""" tables: List[BaseTableWidget] = [ self.entities_point, self.entities_link, self.entities_expr, ] 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 = DynamicCanvas(self) select_tips = QLabel(self, Qt.ToolTip) self.entities_tab.currentChanged.connect( self.main_canvas.set_selection_mode) @Slot(QPoint, str) def show_select_tips(pos: QPoint, text: str): 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_set_selection(selections: Sequence[int], key_detect: bool): """Distinguish table by tab index.""" tables: List[BaseTableWidget] = [ self.entities_point, self.entities_link, self.entities_expr, ] tables[self.entities_tab.currentIndex()].set_selections( selections, key_detect) self.main_canvas.selected.connect(table_set_selection) self.entities_point.row_selection_changed.connect( self.main_canvas.set_selection) @Slot() def table_clear_selection(): """Distinguish table by tab index.""" tables: List[BaseTableWidget] = [ self.entities_point, self.entities_link, self.entities_expr, ] tables[self.entities_tab.currentIndex()].clearSelection() self.main_canvas.noselected.connect(table_clear_selection) 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.addAction(clean_selection_action) self.main_canvas.free_moved.connect(self.set_free_move) self.main_canvas.alt_add.connect(self.q_add_normal_point) 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_splitter.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_set_selection(selections: Sequence[int], _: bool): """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_set_selection) self.main_canvas.noselected.connect(self.inputs_widget.clear_selection) self.inputs_widget.update_preview_button.clicked.connect( self.main_canvas.update_preview_path) # Number and type synthesis self.structure_synthesis = StructureSynthesis(self) self.synthesis_tab_widget.addTab(self.structure_synthesis, self.structure_synthesis.windowIcon(), "Structural") # Synthesis collections self.collection_tab_page = Collections(self) self.synthesis_tab_widget.addTab(self.collection_tab_page, self.collection_tab_page.windowIcon(), "Collections") self.structure_synthesis.addCollection = ( self.collection_tab_page.structure_widget.add_collection) # 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") @Slot() def set_design_progress(): """Synthesis progress bar.""" pos = self.synthesis_tab_widget.currentIndex() if pos == 1: pos += self.collection_tab_page.tab_widget.currentIndex() elif pos == 2: pos += 1 self.synthesis_progress.setValue(pos) self.synthesis_tab_widget.currentChanged.connect(set_design_progress) self.collection_tab_page.tab_widget.currentChanged.connect( set_design_progress) # File widget settings self.database_widget = DatabaseWidget(self) self.vc_layout.addWidget(self.database_widget) self.database_widget.commit_add.clicked.connect(self.commit) self.database_widget.branch_add.clicked.connect(self.commit_branch) self.action_stash.triggered.connect(self.database_widget.stash) # YAML editor self.yaml_editor = YamlEditor(self) # Console dock will hide when startup self.console_widget.hide() # Connect to GUI button self.console_disconnect_button.setEnabled(not ARGUMENTS.debug_mode) self.console_connect_button.setEnabled(ARGUMENTS.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) @Slot() def new_main_window(): """Start a new window.""" run = self.__class__() run.show() self.action_new_window.triggered.connect(new_main_window) def __free_move(self): """Menu of free move mode.""" free_move_mode_menu = QMenu(self) def free_move_mode_func(j: int, icon_qt: QIcon): @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 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(QKeySequence(f"Ctrl+{i + 1}")) action.setShortcutContext(Qt.WindowShortcut) action.setStatusTip(tip) free_move_mode_menu.addAction(action) if i == 0: self.free_move_disable = action self.free_move_button.setMenu(free_move_mode_menu) # "Link adjust" function self.link_free_move_confirm.clicked.connect( self.main_canvas.emit_free_move_all) def __options(self): """Signal connection for option widgets. + Spin boxes + Combo boxes + Check boxes """ # While value change, update the canvas widget. self.settings = QSettings( QStandardPaths.writableLocation(QStandardPaths.HomeLocation) + '/.pyslvs.ini', QSettings.IniFormat, self) self.zoom_bar.valueChanged.connect(self.main_canvas.set_zoom) self.line_width_option.valueChanged.connect( self.main_canvas.set_link_width) self.path_width_option.valueChanged.connect( self.main_canvas.set_path_width) self.font_size_option.valueChanged.connect( self.main_canvas.set_font_size) 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) self.selection_radius_option.valueChanged.connect( self.main_canvas.set_selection_radius) self.link_trans_option.valueChanged.connect( self.main_canvas.set_transparency) self.margin_factor_option.valueChanged.connect( self.main_canvas.set_margin_factor) self.joint_size_option.valueChanged.connect( self.main_canvas.set_joint_size) self.zoom_by_option.currentIndexChanged.connect( self.main_canvas.set_zoom_by) self.snap_option.valueChanged.connect(self.main_canvas.set_snap) self.background_option.textChanged.connect( self.main_canvas.set_background) self.background_opacity_option.valueChanged.connect( self.main_canvas.set_background_opacity) self.background_scale_option.valueChanged.connect( self.main_canvas.set_background_scale) self.background_offset_x_option.valueChanged.connect( self.main_canvas.set_background_offset_x) self.background_offset_y_option.valueChanged.connect( self.main_canvas.set_background_offset_y) self.monochrome_option.toggled.connect( self.main_canvas.set_monochrome_mode) self.monochrome_option.toggled.connect( self.collection_tab_page.configure_widget.configure_canvas. set_monochrome_mode) self.monochrome_option.toggled.connect( self.dimensional_synthesis.preview_canvas.set_monochrome_mode) # Resolve after change current kernel. self.planar_solver_option.addItems(kernel_list) self.path_preview_option.addItems(kernel_list + ("Same as solver kernel", )) self.planar_solver_option.currentIndexChanged.connect(self.solve) self.path_preview_option.currentIndexChanged.connect(self.solve) self.settings_reset.clicked.connect(self.reset_options) def __zoom(self): """Zoom functions. + 'zoom to fit' function connections. + Zoom text buttons """ self.action_zoom_to_fit.triggered.connect(self.main_canvas.zoom_to_fit) self.ResetCanvas.clicked.connect(self.main_canvas.zoom_to_fit) zoom_menu = QMenu(self) def zoom_level(value: int): """Return a function that set the specified zoom value.""" @Slot() def func(): self.zoom_bar.setValue(value) return func for level in range( self.zoom_bar.minimum() - self.zoom_bar.minimum() % 100 + 100, 500 + 1, 100): action = QAction(f'{level}%', self) action.triggered.connect(zoom_level(level)) zoom_menu.addAction(action) action = QAction("customize", self) action.triggered.connect(self.customize_zoom) zoom_menu.addAction(action) self.zoom_button.setMenu(zoom_menu) def __point_context_menu(self): """EntitiesPoint context menu + Add /////// + New Link + Edit + Grounded + Multiple joint - Point0 - Point1 - ... + Copy table data + Copy coordinate + Clone ------- + Delete """ self.entities_point_widget.customContextMenuRequested.connect( self.point_context_menu) self.pop_menu_point = QMenu(self) self.pop_menu_point.setSeparatorsCollapsible(True) self.action_point_context_add = QAction("&Add", self) self.action_point_context_add.triggered.connect(self.new_point) self.pop_menu_point.addAction(self.action_point_context_add) # New Link self.pop_menu_point.addAction(self.action_new_link) self.action_point_context_edit = QAction("&Edit", self) self.action_point_context_edit.triggered.connect(self.edit_point) self.pop_menu_point.addAction(self.action_point_context_edit) self.action_point_context_lock = QAction("&Grounded", self) self.action_point_context_lock.setCheckable(True) self.action_point_context_lock.triggered.connect(self.lock_points) self.pop_menu_point.addAction(self.action_point_context_lock) self.pop_menu_point_merge = QMenu(self) self.pop_menu_point_merge.setTitle("Multiple joint") self.pop_menu_point.addMenu(self.pop_menu_point_merge) self.action_point_context_copydata = QAction("&Copy table data", self) self.action_point_context_copydata.triggered.connect( self.copy_points_table) self.pop_menu_point.addAction(self.action_point_context_copydata) self.action_point_context_copy_coord = QAction("&Copy coordinate", self) self.action_point_context_copy_coord.triggered.connect(self.copy_coord) self.pop_menu_point.addAction(self.action_point_context_copy_coord) self.action_point_context_clone = QAction("C&lone", self) self.action_point_context_clone.triggered.connect(self.clone_point) self.pop_menu_point.addAction(self.action_point_context_clone) self.pop_menu_point.addSeparator() self.action_point_context_delete = QAction("&Delete", self) self.action_point_context_delete.triggered.connect( self.delete_selected_points) self.pop_menu_point.addAction(self.action_point_context_delete) def __link_context_menu(self): """EntitiesLink context menu + Add + Edit + Merge links - Link0 - Link1 - ... + Copy table data + Release + Constrain ------- + Delete """ self.entities_link_widget.customContextMenuRequested.connect( self.link_context_menu) self.pop_menu_link = QMenu(self) self.pop_menu_link.setSeparatorsCollapsible(True) self.action_link_context_add = QAction("&Add", self) self.action_link_context_add.triggered.connect(self.new_link) self.pop_menu_link.addAction(self.action_link_context_add) self.action_link_context_edit = QAction("&Edit", self) self.action_link_context_edit.triggered.connect(self.edit_link) self.pop_menu_link.addAction(self.action_link_context_edit) self.pop_menu_link_merge = QMenu(self) self.pop_menu_link_merge.setTitle("Merge links") self.pop_menu_link.addMenu(self.pop_menu_link_merge) self.action_link_context_copydata = QAction("&Copy table data", self) self.action_link_context_copydata.triggered.connect( self.copy_links_table) self.pop_menu_link.addAction(self.action_link_context_copydata) self.action_link_context_release = QAction("&Release", self) self.action_link_context_release.triggered.connect(self.release_ground) self.pop_menu_link.addAction(self.action_link_context_release) self.action_link_context_constrain = QAction("C&onstrain", self) self.action_link_context_constrain.triggered.connect( self.constrain_link) self.pop_menu_link.addAction(self.action_link_context_constrain) self.pop_menu_link.addSeparator() self.action_link_context_delete = QAction("&Delete", self) self.action_link_context_delete.triggered.connect( self.delete_selected_links) self.pop_menu_link.addAction(self.action_link_context_delete) def __canvas_context_menu(self): """MainCanvas context menus, switch the actions when selection mode changed. + Actions set of points. + Actions set of links. """ self.main_canvas.setContextMenuPolicy(Qt.CustomContextMenu) self.main_canvas.customContextMenuRequested.connect( self.canvas_context_menu) """ Actions set of points: + Add /////// + New Link + Add [fixed] + Add [target path] /////// + Edit + Grounded + Multiple joint - Point0 - Point1 - ... + Clone + Copy coordinate ------- + Delete """ self.pop_menu_canvas_p = QMenu(self) self.pop_menu_canvas_p.setSeparatorsCollapsible(True) self.action_canvas_context_add = QAction("&Add", self) self.action_canvas_context_add.triggered.connect(self.add_normal_point) self.pop_menu_canvas_p.addAction(self.action_canvas_context_add) # New Link self.pop_menu_canvas_p.addAction(self.action_new_link) self.action_canvas_context_grounded_add = QAction( "Add [grounded]", self) self.action_canvas_context_grounded_add.triggered.connect( self.add_fixed_point) self.pop_menu_canvas_p.addAction( self.action_canvas_context_grounded_add) self.action_canvas_context_path = QAction("Add [target path]", self) self.action_canvas_context_path.triggered.connect( self.add_target_point) self.pop_menu_canvas_p.addAction(self.action_canvas_context_path) # The following actions will be shown when points selected. self.pop_menu_canvas_p.addAction(self.action_point_context_edit) self.pop_menu_canvas_p.addAction(self.action_point_context_lock) self.pop_menu_canvas_p.addMenu(self.pop_menu_point_merge) self.pop_menu_canvas_p.addAction(self.action_point_context_copy_coord) self.pop_menu_canvas_p.addAction(self.action_point_context_clone) self.pop_menu_canvas_p.addSeparator() self.pop_menu_canvas_p.addAction(self.action_point_context_delete) """ Actions set of links: + Add /////// + Add [target path] /////// + Edit + Merge links - Link0 - Link1 - ... + Release / Constrain ------- + Delete """ self.pop_menu_canvas_l = QMenu(self) self.pop_menu_canvas_l.setSeparatorsCollapsible(True) self.pop_menu_canvas_l.addAction(self.action_link_context_add) self.pop_menu_canvas_l.addAction(self.action_link_context_edit) self.pop_menu_canvas_l.addMenu(self.pop_menu_link_merge) self.pop_menu_canvas_l.addAction(self.action_link_context_constrain) self.pop_menu_canvas_l.addSeparator() self.pop_menu_canvas_l.addAction(self.action_link_context_delete) @Slot(int, name='on_entities_tab_currentChanged') def __set_selection_mode(self, index: int): """Connect selection signal for main canvas.""" # Set selection from click table items. tables: List[BaseTableWidget] = [ self.entities_point, self.entities_link, self.entities_expr, ] try: for table in tables: table.row_selection_changed.disconnect() except TypeError: pass 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 tables: table.clearSelection() self.inputs_widget.clear_selection() @abstractmethod def command_reload(self, index: int) -> None: ... @abstractmethod def new_point(self) -> None: ... @abstractmethod def add_normal_point(self) -> None: ... @abstractmethod def add_fixed_point(self) -> None: ... @abstractmethod def edit_point(self) -> None: ... @abstractmethod def delete_selected_points(self) -> None: ... @abstractmethod def lock_points(self) -> None: ... @abstractmethod def new_link(self) -> None: ... @abstractmethod def edit_link(self) -> None: ... @abstractmethod def delete_selected_links(self) -> None: ... @abstractmethod def constrain_link(self) -> None: ... @abstractmethod def release_ground(self) -> None: ... @abstractmethod def add_target_point(self) -> None: ... @abstractmethod def set_free_move( self, args: Sequence[Tuple[int, Tuple[float, float, float]]]) -> None: ... @abstractmethod def q_add_normal_point(self, x: float, y: float) -> None: ... @abstractmethod def set_mouse_pos(self, x: float, y: float) -> None: ... @abstractmethod def solve(self) -> None: ... @abstractmethod def resolve(self) -> None: ... @abstractmethod def commit(self, is_branch: bool = False) -> None: ... @abstractmethod def commit_branch(self) -> None: ... @abstractmethod def enable_mechanism_actions(self) -> None: ... @abstractmethod def clone_point(self) -> None: ... @abstractmethod def copy_coord(self) -> None: ... @abstractmethod def copy_points_table(self) -> None: ... @abstractmethod def copy_links_table(self) -> None: ... @abstractmethod def canvas_context_menu(self, point: QPoint) -> None: ... @abstractmethod def link_context_menu(self, point: QPoint) -> None: ... @abstractmethod def customize_zoom(self) -> None: ... @abstractmethod def reset_options(self) -> None: ... @abstractmethod def preview_path(self, auto_preview: List[List[_Coord]], slider_auto_preview: Dict[int, List[_Coord]], vpoints: Sequence[VPoint]) -> None: ... @abstractmethod def reload_canvas(self) -> None: ... @abstractmethod def output_to(self, format_name: str, format_choose: Sequence[str]) -> str: ... @abstractmethod def right_input(self) -> bool: ... @abstractmethod def set_coords_as_current(self) -> None: ... @abstractmethod def dof(self) -> int: ... @abstractmethod def save_reply_box(self, title: str, file_name: str) -> None: ... @abstractmethod def input_from(self, format_name: str, format_choose: Sequence[str], multiple: bool = False) -> str: ... @abstractmethod def get_graph( self ) -> Tuple[Graph, List[int], List[Tuple[int, int]], Dict[int, _Coord], Dict[int, int], Dict[int, int]]: ... @abstractmethod def get_configure(self) -> Dict[str, Any]: ... @abstractmethod def workbook_no_save(self) -> None: ... @abstractmethod def workbook_saved(self) -> bool: ... @abstractmethod def merge_result(self, expr: str, path: Sequence[Sequence[_Coord]]) -> None: ... @abstractmethod def check_file_changed(self) -> bool: ... @abstractmethod def get_storage(self) -> Dict[str, str]: ... @abstractmethod def add_empty_links(self, link_color: Dict[str, str]) -> None: ... @abstractmethod def parse_expression(self, expr: str) -> None: ... @abstractmethod def add_multiple_storage(self, exprs: Sequence[Tuple[str, str]]) -> None: ... @abstractmethod def clear(self) -> None: ... @abstractmethod def add_points( self, p_attr: Sequence[Tuple[float, float, str, str, int, float]]) -> None: ...
class MainWindowUiInterface(QMainWindow, Ui_MainWindow, metaclass=QAbcMeta): """External UI settings.""" def __init__(self): super(MainWindowUiInterface, self).__init__() self.setupUi(self) self.setAttribute(Qt.WA_DeleteOnClose) self.env = "" self.setLocate( QFileInfo(ARGUMENTS.c).canonicalFilePath() if ARGUMENTS.c else QStandardPaths.writableLocation(QStandardPaths.DesktopLocation)) # Undo stack stream. self.CommandStack = QUndoStack(self) # Initialize custom UI. self.__undo_redo() self.__appearance() self.__free_move() self.__options() self.__zoom() self.__point_context_menu() self.__link_context_menu() self.__canvas_context_menu() def show(self): """Overridden function to zoom the canvas's size after startup.""" super(MainWindowUiInterface, self).show() self.MainCanvas.zoomToFit() def setLocate(self, locate: str): """Set environment variables.""" if locate == self.env: # If no changed. return self.env = locate print(f"~Set workplace to: [\"{self.env}\"]") def __undo_redo(self): """Undo list settings. + Undo stack. + Undo view widget. + Hot keys. """ self.CommandStack.setUndoLimit(self.undolimit_option.value()) self.undolimit_option.valueChanged.connect( self.CommandStack.setUndoLimit) self.CommandStack.indexChanged.connect(self.commandReload) self.undoView = QUndoView(self.CommandStack) self.undoView.setEmptyLabel("~ Start Pyslvs") self.UndoRedoLayout.addWidget(self.undoView) self.action_Redo = self.CommandStack.createRedoAction(self, "Redo") self.action_Undo = self.CommandStack.createUndoAction(self, "Undo") self.action_Redo.setShortcuts([ QKeySequence("Ctrl+Shift+Z"), QKeySequence("Ctrl+Y"), ]) self.action_Redo.setStatusTip("Backtracking undo action.") self.action_Redo.setIcon(QIcon(QPixmap(":/icons/redo.png"))) self.action_Undo.setShortcut("Ctrl+Z") self.action_Undo.setStatusTip("Recover last action.") self.action_Undo.setIcon(QIcon(QPixmap(":/icons/undo.png"))) self.menu_Edit.addAction(self.action_Undo) self.menu_Edit.addAction(self.action_Redo) def __appearance(self): """Start up and initialize custom widgets.""" # Version label self.version_label.setText(f"v{_major}.{_minor}.{_build} ({_label})") # Entities tables. self.EntitiesTab.tabBar().setStatusTip( "Switch the tabs to change to another view mode.") self.EntitiesPoint = PointTableWidget(self.EntitiesPoint_widget) self.EntitiesPoint.cellDoubleClicked.connect(self.editPoint) self.EntitiesPoint.deleteRequest.connect(self.deletePoints) self.EntitiesPoint_layout.addWidget(self.EntitiesPoint) self.EntitiesLink = LinkTableWidget(self.EntitiesLink_widget) self.EntitiesLink.cellDoubleClicked.connect(self.editLink) self.EntitiesLink.deleteRequest.connect(self.deleteLinks) self.EntitiesLink_layout.addWidget(self.EntitiesLink) self.EntitiesExpr = ExprTableWidget(self.EntitiesExpr_widget) self.EntitiesExpr.reset.connect(self.link_free_move_widget.setEnabled) self.EntitiesExpr.free_move_request.connect(self.setLinkFreeMove) self.EntitiesExpr_layout.insertWidget(0, self.EntitiesExpr) # Link free mode slide bar. self.link_free_move_slider.valueChanged.connect( self.link_free_move_spinbox.setValue) self.link_free_move_spinbox.valueChanged.connect( self.link_free_move_slider.setValue) self.link_free_move_slider.rangeChanged.connect( self.link_free_move_spinbox.setRange) # 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.") @pyqtSlot() def table_select_all(): """Distinguish table by tab index.""" tables = (self.EntitiesPoint, self.EntitiesLink, self.EntitiesExpr) tables[self.EntitiesTab.currentIndex()].selectAll() select_all_button.clicked.connect(table_select_all) self.EntitiesTab.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.MainCanvas = DynamicCanvas(self) self.EntitiesTab.currentChanged.connect( self.MainCanvas.setSelectionMode) @pyqtSlot(tuple, bool) def table_set_selection(selections: Tuple[int], key_detect: bool): """Distinguish table by tab index.""" tables = (self.EntitiesPoint, self.EntitiesLink, self.EntitiesExpr) tables[self.EntitiesTab.currentIndex()].setSelections( selections, key_detect) self.MainCanvas.selected.connect(table_set_selection) self.EntitiesPoint.rowSelectionChanged.connect( self.MainCanvas.setSelection) @pyqtSlot() def table_clear_selection(): """Distinguish table by tab index.""" tables = (self.EntitiesPoint, self.EntitiesLink, self.EntitiesExpr) tables[self.EntitiesTab.currentIndex()].clearSelection() self.MainCanvas.noselected.connect(table_clear_selection) 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.addAction(clean_selection_action) self.MainCanvas.free_moved.connect(self.setFreeMove) self.MainCanvas.alt_add.connect(self.qAddNormalPoint) self.MainCanvas.doubleclick_edit.connect(self.editPoint) self.MainCanvas.zoom_changed.connect(self.ZoomBar.setValue) self.MainCanvas.tracking.connect(self.setMousePos) self.canvasSplitter.insertWidget(0, self.MainCanvas) self.canvasSplitter.setSizes([600, 10, 30]) # Selection label on status bar right side. selection_label = SelectionLabel(self) self.EntitiesPoint.selectionLabelUpdate.connect( selection_label.updateSelectPoint) self.MainCanvas.browse_tracking.connect( selection_label.updateMousePosition) self.status_bar.addPermanentWidget(selection_label) # FPS label on status bar right side. fps_label = FPSLabel(self) self.MainCanvas.fps_updated.connect(fps_label.updateText) self.status_bar.addPermanentWidget(fps_label) # Inputs widget. self.InputsWidget = InputsWidget(self) self.inputs_tab_layout.addWidget(self.InputsWidget) self.free_move_button.toggled.connect( self.InputsWidget.variableValueReset) self.InputsWidget.aboutToResolve.connect(self.resolve) @pyqtSlot(tuple, bool) def inputs_set_selection(selections: Tuple[int], _: bool): """Distinguish table by tab index.""" self.InputsWidget.clearSelection() if self.EntitiesTab.currentIndex() == 0: self.InputsWidget.setSelection(selections) self.MainCanvas.selected.connect(inputs_set_selection) self.MainCanvas.noselected.connect(self.InputsWidget.clearSelection) self.InputsWidget.update_preview_button.clicked.connect( self.MainCanvas.updatePreviewPath) # Number and type synthesis. self.StructureSynthesis = StructureSynthesis(self) self.SynthesisTab.addTab(self.StructureSynthesis, self.StructureSynthesis.windowIcon(), "Structural") # Synthesis collections self.CollectionTabPage = Collections(self) self.SynthesisTab.addTab(self.CollectionTabPage, self.CollectionTabPage.windowIcon(), "Collections") self.StructureSynthesis.addCollection = ( self.CollectionTabPage.StructureWidget.addCollection) # Dimensional synthesis self.DimensionalSynthesis = DimensionalSynthesis(self) self.MainCanvas.set_target_point.connect( self.DimensionalSynthesis.setPoint) self.SynthesisTab.addTab(self.DimensionalSynthesis, self.DimensionalSynthesis.windowIcon(), "Dimensional") # File widget settings. self.DatabaseWidget = DatabaseWidget(self) self.SCMLayout.addWidget(self.DatabaseWidget) self.DatabaseWidget.commit_add.clicked.connect(self.commit) self.DatabaseWidget.branch_add.clicked.connect(self.commit_branch) self.action_stash.triggered.connect(self.DatabaseWidget.stash) # YAML editor. self.YamlEditor = YamlEditor(self) # Console dock will hide when startup. self.ConsoleWidget.hide() # Connect to GUI button switching. self.console_disconnect_button.setEnabled(not ARGUMENTS.debug_mode) self.console_connect_button.setEnabled(ARGUMENTS.debug_mode) # Splitter stretch factor. self.MainSplitter.setStretchFactor(0, 4) self.MainSplitter.setStretchFactor(1, 15) self.MechanismPanelSplitter.setSizes([500, 200]) self.synthesis_splitter.setSizes([100, 500]) # Enable mechanism menu actions when shows. self.menu_Mechanism.aboutToShow.connect(self.enableMechanismActions) # Start a new window. @pyqtSlot() def new_main_window(): run = self.__class__() run.show() self.action_new_window.triggered.connect(new_main_window) def __free_move(self): """Menu of free move mode.""" free_move_mode_menu = QMenu(self) def free_move_mode_func(j: int, icon_qt: QIcon): @pyqtSlot() def func(): self.free_move_button.setIcon(icon_qt) self.MainCanvas.setFreeMove(j) self.EntitiesTab.setCurrentIndex(0) self.InputsWidget.variable_stop.click() return func 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(QKeySequence(f"Ctrl+{i + 1}")) action.setShortcutContext(Qt.WindowShortcut) action.setStatusTip(tip) free_move_mode_menu.addAction(action) if i == 0: self.free_move_disable = action self.free_move_button.setMenu(free_move_mode_menu) # Link free move by expression table. self.link_free_move_slider.sliderReleased.connect( self.MainCanvas.emit_free_move_all) def __options(self): """Signal connection for option widgets. + Spin boxes + Combo boxes + Check boxes """ # While value change, update the canvas widget. self.settings = QSettings('Kmol', 'Pyslvs') self.ZoomBar.valueChanged.connect(self.MainCanvas.setZoom) self.linewidth_option.valueChanged.connect( self.MainCanvas.setLinkWidth) self.pathwidth_option.valueChanged.connect( self.MainCanvas.setPathWidth) self.fontsize_option.valueChanged.connect(self.MainCanvas.setFontSize) self.action_show_point_mark.toggled.connect( self.MainCanvas.setPointMark) self.action_show_dimensions.toggled.connect( self.MainCanvas.setShowDimension) self.selectionradius_option.valueChanged.connect( self.MainCanvas.setSelectionRadius) self.linktrans_option.valueChanged.connect( self.MainCanvas.setTransparency) self.marginfactor_option.valueChanged.connect( self.MainCanvas.setMarginFactor) self.jointsize_option.valueChanged.connect( self.MainCanvas.setJointSize) self.zoomby_option.currentIndexChanged.connect( self.MainCanvas.setZoomBy) self.snap_option.valueChanged.connect(self.MainCanvas.setSnap) self.background_option.textChanged.connect( self.MainCanvas.setBackground) self.background_opacity_option.valueChanged.connect( self.MainCanvas.setBackgroundOpacity) self.background_scale_option.valueChanged.connect( self.MainCanvas.setBackgroundScale) self.background_offset_x_option.valueChanged.connect( self.MainCanvas.setBackgroundOffsetX) self.background_offset_y_option.valueChanged.connect( self.MainCanvas.setBackgroundOffsetY) # Resolve after change current kernel. self.planarsolver_option.addItems(kernel_list) self.pathpreview_option.addItems(kernel_list + ("Same as solver kernel", )) self.planarsolver_option.currentIndexChanged.connect(self.solve) self.pathpreview_option.currentIndexChanged.connect(self.solve) self.settings_reset.clicked.connect(self.resetOptions) def __zoom(self): """Zoom functions. + 'zoom to fit' function connections. + Zoom text buttons """ self.action_zoom_to_fit.triggered.connect(self.MainCanvas.zoomToFit) self.ResetCanvas.clicked.connect(self.MainCanvas.zoomToFit) zoom_menu = QMenu(self) def zoom_level(value: int): """Return a function that set the specified zoom value.""" @pyqtSlot() def func(): return self.ZoomBar.setValue(value) return func for level in range( self.ZoomBar.minimum() - self.ZoomBar.minimum() % 100 + 100, 500 + 1, 100): action = QAction(f'{level}%', self) action.triggered.connect(zoom_level(level)) zoom_menu.addAction(action) action = QAction("customize", self) action.triggered.connect(self.customizeZoom) zoom_menu.addAction(action) self.zoom_button.setMenu(zoom_menu) def __point_context_menu(self): """EntitiesPoint context menu + Add /////// + New Link + Edit + Grounded + Multiple joint - Point0 - Point1 - ... + Copy table data + Clone ------- + Delete """ self.EntitiesPoint_widget.customContextMenuRequested.connect( self.point_context_menu) self.pop_menu_point = QMenu(self) self.pop_menu_point.setSeparatorsCollapsible(True) self.action_point_context_add = QAction("&Add", self) self.action_point_context_add.triggered.connect(self.newPoint) self.pop_menu_point.addAction(self.action_point_context_add) # New Link self.pop_menu_point.addAction(self.action_new_link) self.action_point_context_edit = QAction("&Edit", self) self.action_point_context_edit.triggered.connect(self.editPoint) self.pop_menu_point.addAction(self.action_point_context_edit) self.action_point_context_lock = QAction("&Grounded", self) self.action_point_context_lock.setCheckable(True) self.action_point_context_lock.triggered.connect(self.lockPoints) self.pop_menu_point.addAction(self.action_point_context_lock) self.pop_menu_point_merge = QMenu(self) self.pop_menu_point_merge.setTitle("Multiple joint") self.pop_menu_point.addMenu(self.pop_menu_point_merge) self.action_point_context_copydata = QAction("&Copy table data", self) self.action_point_context_copydata.triggered.connect( self.copyPointsTable) self.pop_menu_point.addAction(self.action_point_context_copydata) self.action_point_context_copyCoord = QAction("&Copy coordinate", self) self.action_point_context_copyCoord.triggered.connect(self.copyCoord) self.pop_menu_point.addAction(self.action_point_context_copyCoord) self.action_point_context_copyPoint = QAction("C&lone", self) self.action_point_context_copyPoint.triggered.connect(self.clonePoint) self.pop_menu_point.addAction(self.action_point_context_copyPoint) self.pop_menu_point.addSeparator() self.action_point_context_delete = QAction("&Delete", self) self.action_point_context_delete.triggered.connect(self.deletePoints) self.pop_menu_point.addAction(self.action_point_context_delete) def __link_context_menu(self): """EntitiesLink context menu + Add + Edit + Merge links - Link0 - Link1 - ... + Copy table data + Release / Constrain ------- + Delete """ self.EntitiesLink_widget.customContextMenuRequested.connect( self.link_context_menu) self.pop_menu_link = QMenu(self) self.pop_menu_link.setSeparatorsCollapsible(True) self.action_link_context_add = QAction("&Add", self) self.action_link_context_add.triggered.connect(self.newLink) self.pop_menu_link.addAction(self.action_link_context_add) self.action_link_context_edit = QAction("&Edit", self) self.action_link_context_edit.triggered.connect(self.editLink) self.pop_menu_link.addAction(self.action_link_context_edit) self.pop_menu_link_merge = QMenu(self) self.pop_menu_link_merge.setTitle("Merge links") self.pop_menu_link.addMenu(self.pop_menu_link_merge) self.action_link_context_copydata = QAction("&Copy table data", self) self.action_link_context_copydata.triggered.connect( self.copyLinksTable) self.pop_menu_link.addAction(self.action_link_context_copydata) self.action_link_context_release = QAction("&Release", self) self.action_link_context_release.triggered.connect(self.releaseGround) self.pop_menu_link.addAction(self.action_link_context_release) self.action_link_context_constrain = QAction("C&onstrain", self) self.action_link_context_constrain.triggered.connect( self.constrainLink) self.pop_menu_link.addAction(self.action_link_context_constrain) self.pop_menu_link.addSeparator() self.action_link_context_delete = QAction("&Delete", self) self.action_link_context_delete.triggered.connect(self.deleteLinks) self.pop_menu_link.addAction(self.action_link_context_delete) def __canvas_context_menu(self): """MainCanvas context menus, switch the actions when selection mode changed. + Actions set of points. + Actions set of links. """ self.MainCanvas.setContextMenuPolicy(Qt.CustomContextMenu) self.MainCanvas.customContextMenuRequested.connect( self.canvas_context_menu) """ Actions set of points: + Add /////// + New Link + Add [fixed] + Add [target path] /////// + Edit + Grounded + Multiple joint - Point0 - Point1 - ... + Clone + Copy coordinate ------- + Delete """ self.pop_menu_canvas_p = QMenu(self) self.pop_menu_canvas_p.setSeparatorsCollapsible(True) self.action_canvas_context_add = QAction("&Add", self) self.action_canvas_context_add.triggered.connect(self.addNormalPoint) self.pop_menu_canvas_p.addAction(self.action_canvas_context_add) # New Link self.pop_menu_canvas_p.addAction(self.action_new_link) self.action_canvas_context_grounded_add = QAction( "Add [grounded]", self) self.action_canvas_context_grounded_add.triggered.connect( self.addFixedPoint) self.pop_menu_canvas_p.addAction( self.action_canvas_context_grounded_add) self.action_canvas_context_path = QAction("Add [target path]", self) self.action_canvas_context_path.triggered.connect(self.addTargetPoint) self.pop_menu_canvas_p.addAction(self.action_canvas_context_path) # The following actions will be shown when points selected. self.pop_menu_canvas_p.addAction(self.action_point_context_edit) self.pop_menu_canvas_p.addAction(self.action_point_context_lock) self.pop_menu_canvas_p.addMenu(self.pop_menu_point_merge) self.pop_menu_canvas_p.addAction(self.action_point_context_copyCoord) self.pop_menu_canvas_p.addAction(self.action_point_context_copyPoint) self.pop_menu_canvas_p.addSeparator() self.pop_menu_canvas_p.addAction(self.action_point_context_delete) """ Actions set of links: + Add /////// + Add [target path] /////// + Edit + Merge links - Link0 - Link1 - ... + Release / Constrain ------- + Delete """ self.pop_menu_canvas_l = QMenu(self) self.pop_menu_canvas_l.setSeparatorsCollapsible(True) self.pop_menu_canvas_l.addAction(self.action_link_context_add) self.pop_menu_canvas_l.addAction(self.action_link_context_edit) self.pop_menu_canvas_l.addMenu(self.pop_menu_link_merge) self.pop_menu_canvas_l.addAction(self.action_link_context_constrain) self.pop_menu_canvas_l.addSeparator() self.pop_menu_canvas_l.addAction(self.action_link_context_delete) @abstractmethod def commandReload(self, index: int) -> None: ... @abstractmethod def newPoint(self) -> None: ... @abstractmethod def addNormalPoint(self) -> None: ... @abstractmethod def addFixedPoint(self) -> None: ... @abstractmethod def editPoint(self) -> None: ... @abstractmethod def deletePoints(self) -> None: ... @abstractmethod def lockPoints(self) -> None: ... @abstractmethod def newLink(self) -> None: ... @abstractmethod def editLink(self) -> None: ... @abstractmethod def deleteLinks(self) -> None: ... @abstractmethod def constrainLink(self) -> None: ... @abstractmethod def releaseGround(self) -> None: ... @abstractmethod def addTargetPoint(self) -> None: ... @abstractmethod def setLinkFreeMove(self, enable: bool) -> None: ... @abstractmethod def setFreeMove( self, args: Sequence[Tuple[int, Tuple[float, float, float]]]) -> None: ... @abstractmethod def qAddNormalPoint(self, x: float, y: float) -> None: ... @abstractmethod def setMousePos(self, x: float, y: float) -> None: ... @abstractmethod def solve(self) -> None: ... @abstractmethod def resolve(self) -> None: ... @abstractmethod def commit(self, is_branch: bool = False) -> None: ... @abstractmethod def commit_branch(self) -> None: ... @abstractmethod def enableMechanismActions(self) -> None: ... @abstractmethod def clonePoint(self) -> None: ... @abstractmethod def copyCoord(self) -> None: ... @abstractmethod def copyPointsTable(self) -> None: ... @abstractmethod def copyLinksTable(self) -> None: ... @abstractmethod def canvas_context_menu(self, point: QPoint) -> None: ... @abstractmethod def link_context_menu(self, point: QPoint) -> None: ... @abstractmethod def customizeZoom(self) -> None: ... @abstractmethod def resetOptions(self) -> None: ...