def __init__( self, collections: Dict[str, Any], get_collection: Callable[[], Dict[str, Any]], parent: QWidget ): """We put the 'collections' (from iteration widget) reference here.""" super(CollectionsDialog, self).__init__(parent) self.setupUi(self) self.setWindowFlags( self.windowFlags() & ~Qt.WindowContextHelpButtonHint | Qt.WindowMaximizeButtonHint ) self.collections = collections self.getCollection = get_collection # Current profile name. self.__name_loaded = "" def get_solutions_func() -> Tuple[str, ...]: """Return solutions to preview canvas.""" try: return self.collections[self.__name_loaded]['Expression'] except KeyError: if self.__name_loaded == "Four bar linkage mechanism": return _mech_params_4_bar['Expression'] elif self.__name_loaded == "Eight bar linkage mechanism": return _mech_params_8_bar['Expression'] elif self.__name_loaded == "Ball lifter linkage mechanism": return _mech_params_ball_lifter['Expression'] else: return () self.PreviewCanvas = PreviewCanvas(get_solutions_func, self) self.preview_layout.addWidget(self.PreviewCanvas) self.show_solutions.clicked.connect(self.PreviewCanvas.setShowSolutions) for name in self.collections: self.collections_list.addItem(name) # Splitter self.main_splitter.setSizes([200, 200]) self.sub_splitter.setSizes([100, 200]) # Signals self.common_list.currentTextChanged.connect(self.__choose_common) self.common_list.itemDoubleClicked.connect(self.__load_common) self.common_load.clicked.connect(self.__load_common) self.collections_list.currentTextChanged.connect(self.__choose_collections) self.collections_list.currentTextChanged.connect(self.__can_open) self.collections_list.itemDoubleClicked.connect(self.__load_collections) self.buttonBox.accepted.connect(self.__load_collections) self.__has_collection() self.__can_open()
def __init__(self, parent): super(CollectionsDialog, self).__init__(parent) self.setupUi(self) self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint | Qt.WindowMaximizeButtonHint) self.collections = parent.collections self.name_loaded = "" def get_solutions_func() -> Tuple[str]: """Return solutions to preview canvas.""" try: return self.collections[self.name_loaded]['Expression'] except KeyError: if self.name_loaded == "Four bar linkage mechanism": return mech_params_4Bar['Expression'] elif self.name_loaded == "Eight bar linkage mechanism": return mech_params_8Bar['Expression'] elif self.name_loaded == "Ball lifter linkage mechanism": return mech_params_BallLifter['Expression'] else: return tuple() self.PreviewCanvas = PreviewCanvas(get_solutions_func, self) self.preview_layout.addWidget(self.PreviewCanvas) self.show_solutions.clicked.connect( self.PreviewCanvas.setShowSolutions) for name in self.collections: self.collections_list.addItem(name) #Splitter self.main_splitter.setSizes([200, 200]) #Signals self.common_list.currentTextChanged.connect(self.__chooseCommon) self.common_list.itemClicked.connect(self.__chooseCommon) self.common_load.clicked.connect(self.__loadCommon) self.common_list.itemDoubleClicked.connect(self.__loadCommon) self.collections_list.currentTextChanged.connect( self.__chooseCollections) self.collections_list.itemClicked.connect(self.__chooseCollections) self.buttonBox.accepted.connect(self.__loadCollections) self.collections_list.itemDoubleClicked.connect(self.__loadCollections) self.collections_list.currentRowChanged.connect(self.__canOpen) self.__hasCollection() self.__canOpen()
def __init__(self, parent: MainWindowBase): """Reference names: + Iteration collections. + Result data. + Main window function references. """ super(DimensionalSynthesis, self).__init__(parent) self.setupUi(self) self.mech_params: Dict[str, Any] = {} self.path: Dict[int, List[_Coord]] = {} # Some reference of 'collections' self.collections = parent.collection_tab_page.configure_widget.collections self.get_collection = parent.get_configure self.input_from = parent.input_from self.workbook_no_save = parent.workbook_no_save self.merge_result = parent.merge_result self.update_ranges = parent.main_canvas.update_ranges self.set_solving_path = parent.main_canvas.set_solving_path # Data and functions self.mechanism_data: List[Dict[str, Any]] = [] self.alg_options: Dict[str, Union[int, float]] = {} self.alg_options.update(defaultSettings) self.alg_options.update(DifferentialPrams) self.__set_algorithm_default() self.preview_canvas = PreviewCanvas(self) self.preview_layout.addWidget(self.preview_canvas) # Splitter self.main_splitter.setStretchFactor(0, 100) self.main_splitter.setStretchFactor(1, 10) self.up_splitter.setSizes([80, 100]) self.down_splitter.setSizes([20, 80]) # Table widget column width header = self.parameter_list.horizontalHeader() header.setSectionResizeMode(QHeaderView.ResizeToContents) self.clear()
def __init__(self, parent): super(DimensionalSynthesis, self).__init__(parent) self.setupUi(self) self.mech_params = {} self.path = {} #A pointer reference of 'collections'. self.collections = parent.CollectionTabPage.CollectionsTriangularIteration.collections #Data and functions. self.mechanism_data = [] self.inputFrom = parent.inputFrom self.unsaveFunc = parent.workbookNoSave self.mergeResult = parent.mergeResult self.updateRanges = parent.MainCanvas.updateRanges self.setSolvingPath = parent.MainCanvas.setSolvingPath self.Settings = deepcopy(defaultSettings) self.__setAlgorithmToDefault() def get_solutions_func(): """For preview canvas.""" try: return self.mech_params['Expression'] except KeyError: return () self.PreviewCanvas = PreviewCanvas(get_solutions_func, self) self.preview_layout.addWidget(self.PreviewCanvas) self.show_solutions.clicked.connect( self.PreviewCanvas.setShowSolutions) #Splitter self.up_splitter.setSizes([80, 100]) #Table widget column width. self.ground_joints.setColumnWidth(0, 50) self.ground_joints.setColumnWidth(1, 80) self.ground_joints.setColumnWidth(2, 70) self.ground_joints.setColumnWidth(3, 70) self.ground_joints.setColumnWidth(4, 80) #Default value of algorithm parameters. self.type0.clicked.connect(self.__setAlgorithmToDefault) self.type1.clicked.connect(self.__setAlgorithmToDefault) self.type2.clicked.connect(self.__setAlgorithmToDefault) #Signals self.Result_list.clicked.connect(self.__hasResult) self.path_clear.clicked.connect(self.__clearPath)
def __init__(self, parent): super(DimensionalSynthesis, self).__init__(parent) self.setupUi(self) self.mechanismParams = {} self.path = {} #A pointer reference of 'collections'. self.collections = parent.CollectionTabPage.CollectionsTriangularIteration.collections #Data and functions. self.mechanism_data = [] self.inputFrom = parent.inputFrom self.unsaveFunc = parent.workbookNoSave self.Settings = deepcopy(defaultSettings) self.algorithmParams_default() #Canvas def get_solutions_func(): try: return replace_by_dict(self.mechanismParams) except KeyError: return tuple() self.PreviewCanvas = PreviewCanvas(get_solutions_func, self) self.preview_layout.addWidget(self.PreviewCanvas) self.show_solutions.clicked.connect(self.PreviewCanvas.setShowSolutions) #Splitter self.up_splitter.setSizes([80, 100]) #Table widget column width. self.ground_joints.setColumnWidth(0, 50) self.ground_joints.setColumnWidth(1, 80) self.ground_joints.setColumnWidth(2, 70) self.ground_joints.setColumnWidth(3, 70) self.ground_joints.setColumnWidth(4, 80) #Default value of algorithm parameters. self.type0.clicked.connect(self.algorithmParams_default) self.type1.clicked.connect(self.algorithmParams_default) self.type2.clicked.connect(self.algorithmParams_default) #Signals self.Result_list.clicked.connect(self.hasResult) self.clear_button.clicked.connect(self.clear_settings) self.clear()
def __init__(self, parent): super(CollectionsDialog, self).__init__(parent) self.setupUi(self) self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) self.collections = parent.collections self.name_loaded = "" #Canvas def get_solutions_func(): try: return replace_by_dict(self.collections[self.name_loaded]) except KeyError: if self.name_loaded=="Four bar linkage mechanism": return replace_by_dict(mechanismParams_4Bar) elif self.name_loaded=="Eight bar linkage mechanism": return replace_by_dict(mechanismParams_8Bar) elif self.name_loaded=="Ball lifter linkage mechanism": return replace_by_dict(mechanismParams_BallLifter) else: return tuple() self.PreviewCanvas = PreviewCanvas(get_solutions_func, self) self.preview_layout.addWidget(self.PreviewCanvas) self.show_solutions.clicked.connect(self.PreviewCanvas.setShowSolutions) for name in self.collections: self.collections_list.addItem(name) #Splitter self.main_splitter.setSizes([200, 200]) #Signals self.common_list.currentTextChanged.connect(self.choose_common) self.common_list.itemClicked.connect(self.choose_common) self.common_load.clicked.connect(self.load_common) self.common_list.itemDoubleClicked.connect(self.load_common) self.collections_list.currentTextChanged.connect(self.choose_collections) self.collections_list.itemClicked.connect(self.choose_collections) self.buttonBox.accepted.connect(self.load_collections) self.collections_list.itemDoubleClicked.connect(self.load_collections) self.collections_list.currentRowChanged.connect(self.canOpen) self.hasCollection() self.canOpen()
def __init__( self, collections: Dict[str, Any], get_collection: Callable[[], Dict[str, Any]], unsave_func: Callable[[], None], monochrome: bool, parent: QWidget ): """We put the 'collections' (from iteration widget) reference here.""" super(CollectionsDialog, self).__init__(parent) self.setupUi(self) self.setWindowFlags( self.windowFlags() & ~Qt.WindowContextHelpButtonHint | Qt.WindowMaximizeButtonHint ) self.collections = collections self.get_collection = get_collection self.unsave_func = unsave_func # Current profile name self.name = "" self.params: Dict[str, Any] = {} self.preview_canvas = PreviewCanvas(self) self.preview_layout.addWidget(self.preview_canvas) self.preview_canvas.set_monochrome_mode(monochrome) self.common_list.addItems(collection_list) self.collections_list.addItems(self.collections) # Splitter self.main_splitter.setSizes([200, 200]) self.sub_splitter.setSizes([100, 200]) self.__has_collection() self.__can_open()
class CollectionsDialog(QDialog, Ui_Dialog): """Option dialog. Load the settings after closed. Any add, rename, delete operations will be apply immediately """ def __init__( self, collections: Dict[str, Any], get_collection: Callable[[], Dict[str, Any]], parent: QWidget ): """We put the 'collections' (from iteration widget) reference here.""" super(CollectionsDialog, self).__init__(parent) self.setupUi(self) self.setWindowFlags( self.windowFlags() & ~Qt.WindowContextHelpButtonHint | Qt.WindowMaximizeButtonHint ) self.collections = collections self.getCollection = get_collection # Current profile name. self.__name_loaded = "" def get_solutions_func() -> Tuple[str, ...]: """Return solutions to preview canvas.""" try: return self.collections[self.__name_loaded]['Expression'] except KeyError: if self.__name_loaded == "Four bar linkage mechanism": return _mech_params_4_bar['Expression'] elif self.__name_loaded == "Eight bar linkage mechanism": return _mech_params_8_bar['Expression'] elif self.__name_loaded == "Ball lifter linkage mechanism": return _mech_params_ball_lifter['Expression'] else: return () self.PreviewCanvas = PreviewCanvas(get_solutions_func, self) self.preview_layout.addWidget(self.PreviewCanvas) self.show_solutions.clicked.connect(self.PreviewCanvas.setShowSolutions) for name in self.collections: self.collections_list.addItem(name) # Splitter self.main_splitter.setSizes([200, 200]) self.sub_splitter.setSizes([100, 200]) # Signals self.common_list.currentTextChanged.connect(self.__choose_common) self.common_list.itemDoubleClicked.connect(self.__load_common) self.common_load.clicked.connect(self.__load_common) self.collections_list.currentTextChanged.connect(self.__choose_collections) self.collections_list.currentTextChanged.connect(self.__can_open) self.collections_list.itemDoubleClicked.connect(self.__load_collections) self.buttonBox.accepted.connect(self.__load_collections) self.__has_collection() self.__can_open() def __can_open(self): """Set the button box to enable when data is already.""" self.buttonBox.button(QDialogButtonBox.Open).setEnabled( self.collections_list.currentRow() > -1 ) def __has_collection(self): """Set the buttons to enable when user choose a data.""" has_collection = bool(self.collections) for button in [ self.rename_button, self.copy_button, self.delete_button ]: button.setEnabled(has_collection) def name(self) -> str: return self.__name_loaded def params(self) -> Dict[str, Any]: return self.__mech_params @pyqtSlot(name='on_rename_button_clicked') def __rename(self): """Show up a string input to change the data name.""" row = self.collections_list.currentRow() if not row > -1: return name, ok = QInputDialog.getText( self, "Profile name", "Please enter the profile name:" ) if not ok: return if not name: QMessageBox.warning( self, "Profile name", "Can not use blank string to rename." ) return item = self.collections_list.item(row) self.collections[name] = self.collections.pop(item.text()) item.setText(name) @pyqtSlot(name='on_copy_button_clicked') def __copy(self): """Ask a name to copy a data.""" row = self.collections_list.currentRow() if not row > -1: return name, ok = QInputDialog.getText( self, "Profile name", "Please enter a new profile name:" ) if not ok: return if not name: QMessageBox.warning( self, "Profile name", "Can not use blank string to rename." ) return name_old = self.collections_list.item(row).text() self.collections[name] = self.collections[name_old].copy() self.collections_list.addItem(name) @pyqtSlot(name='on_delete_button_clicked') def __delete(self): """Delete a data.""" row = self.collections_list.currentRow() if not row > -1: return reply = QMessageBox.question( self, "Delete", "Do you want to delete this structure?" ) if reply != QMessageBox.Yes: return item = self.collections_list.takeItem(row) del self.collections[item.text()] self.PreviewCanvas.clear() self.__has_collection() @pyqtSlot(str) @pyqtSlot(QListWidgetItem) def __choose_common(self, p0: Union[str, QListWidgetItem, None] = None): """Update preview canvas for common data.""" item = self.common_list.currentItem() if not item: return self.__name_loaded = item.text() if self.__name_loaded == "Four bar linkage mechanism": self.__mech_params = deepcopy(_mech_params_4_bar) elif self.__name_loaded == "Eight bar linkage mechanism": self.__mech_params = deepcopy(_mech_params_8_bar) elif self.__name_loaded == "Ball lifter linkage mechanism": self.__mech_params = deepcopy(_mech_params_ball_lifter) self.PreviewCanvas.from_profile(self.__mech_params) @pyqtSlot(str) @pyqtSlot(QListWidgetItem) def __choose_collections(self, p0: Union[str, QListWidgetItem, None] = None): """Update preview canvas for a workbook data.""" item = self.collections_list.currentItem() if not item: return self.__name_loaded = item.text() self.__mech_params = deepcopy(self.collections[self.__name_loaded]) self.PreviewCanvas.from_profile(self.__mech_params) @pyqtSlot(name='on_workbook_button_clicked') def __from_canvas(self): """Get a collection data from current mechanism.""" try: collection = self.getCollection() except ValueError as e: QMessageBox.warning(self, "Mechanism not support.", str(e)) else: num = 0 name = f"mechanism{num}" while name in self.collections: name = f"mechanism{num}" num += 1 self.collections[name] = collection.copy() self.collections_list.addItem(name) @pyqtSlot() @pyqtSlot(QListWidgetItem) def __load_common(self, p0: Optional[QListWidgetItem] = None): """Load a common data and close.""" self.__choose_common() self.accept() @pyqtSlot() @pyqtSlot(QListWidgetItem) def __load_collections(self, p0: Optional[QListWidgetItem] = None): """Load a workbook data and close.""" self.__choose_collections() self.accept()
class DimensionalSynthesis(QWidget, Ui_Form): """Dimensional synthesis widget.""" fixPointRange = pyqtSignal(dict) pathChanged = pyqtSignal(dict) mergeResult = pyqtSignal(int, tuple) def __init__(self, parent): super(DimensionalSynthesis, self).__init__(parent) self.setupUi(self) self.mechanismParams = {} self.path = {} #A pointer reference of 'collections'. self.collections = parent.CollectionTabPage.CollectionsTriangularIteration.collections #Data and functions. self.mechanism_data = [] self.inputFrom = parent.inputFrom self.unsaveFunc = parent.workbookNoSave self.Settings = deepcopy(defaultSettings) self.algorithmParams_default() #Canvas def get_solutions_func(): try: return replace_by_dict(self.mechanismParams) except KeyError: return tuple() self.PreviewCanvas = PreviewCanvas(get_solutions_func, self) self.preview_layout.addWidget(self.PreviewCanvas) self.show_solutions.clicked.connect(self.PreviewCanvas.setShowSolutions) #Splitter self.up_splitter.setSizes([80, 100]) #Table widget column width. self.ground_joints.setColumnWidth(0, 50) self.ground_joints.setColumnWidth(1, 80) self.ground_joints.setColumnWidth(2, 70) self.ground_joints.setColumnWidth(3, 70) self.ground_joints.setColumnWidth(4, 80) #Default value of algorithm parameters. self.type0.clicked.connect(self.algorithmParams_default) self.type1.clicked.connect(self.algorithmParams_default) self.type2.clicked.connect(self.algorithmParams_default) #Signals self.Result_list.clicked.connect(self.hasResult) self.clear_button.clicked.connect(self.clear_settings) self.clear() def clear(self): """Clear all sub-widgets.""" self.mechanism_data.clear() self.Result_list.clear() self.clear_settings() self.hasResult() def clear_settings(self): """Clear sub-widgets that contain the setting.""" self.on_path_clear_clicked(ask=False) self.path.clear() self.mechanismParams.clear() self.PreviewCanvas.clear() self.Settings.clear() self.Settings = deepcopy(defaultSettings) self.profile_name.setText("No setting") self.type2.setChecked(True) self.ground_joints.setRowCount(0) self.target_points.clear() self.Expression.clear() self.Link_Expression.clear() self.updateRange() self.isGenerate() def loadResults(self, mechanism_data: List[Dict[str, Any]] ): """Append results of workbook database to memory.""" for e in mechanism_data: self.mechanism_data.append(e) self.add_result(e) def currentPathChanged(self): """Call the canvas to update to current target path.""" self.pathChanged.emit({ name: tuple(path) for name, path in self.path.items() }) self.isGenerate() def currentPath(self) -> List[Tuple[float, float]]: """Return the pointer of current target path.""" item = self.target_points.currentItem() if item: return self.path[item.text()] else: return [] @pyqtSlot(str) def on_target_points_currentTextChanged(self, text=None): """Switch to the current target path.""" self.path_list.clear() for x, y in self.currentPath(): self.path_list.addItem("({:.04f}, {:.04f})".format(x, y)) self.currentPathChanged() @pyqtSlot() def on_path_clear_clicked(self, ask: bool =True): """Clear the current target path.""" if ask: reply = QMessageBox.question(self, "Clear path", "Are you sure to clear the current path?" ) if reply != QMessageBox.Yes: return self.currentPath().clear() self.path_list.clear() self.currentPathChanged() @pyqtSlot() def on_path_copy_clicked(self): """Copy the current path coordinates to clipboard.""" QApplication.clipboard().setText('\n'.join( "{},{}".format(x, y) for x, y in self.currentPath() )) @pyqtSlot() def on_path_paste_clicked(self): """Paste path data from clipboard.""" self.readPathFromCSV(charSplit(";|,|\n", QApplication.clipboard().text())) @pyqtSlot() def on_importCSV_clicked(self): """Paste path data from a text file.""" fileName = self.inputFrom( "Path data", ["Text File (*.txt)", "CSV File (*.csv)"] ) if not fileName: return data = [] with open(fileName, newline='') as stream: reader = csv.reader(stream, delimiter=' ', quotechar='|') for row in reader: data += ' '.join(row).split(',') self.readPathFromCSV(data) def readPathFromCSV(self, data: List[str]): """Trun STR to FLOAT then add them to current target path.""" try: data = [ (round(float(data[i]), 4), round(float(data[i + 1]), 4)) for i in range(0, len(data), 2) ] except: QMessageBox.warning(self, "File error", "Wrong format.\nIt should be look like this:" + "\n0.0,0.0[\\n]" * 3 ) else: for e in data: self.add_point(e[0], e[1]) @pyqtSlot() def on_importXLSX_clicked(self): """Paste path data from a Excel file.""" fileName = self.inputFrom( "Excel file", ["Microsoft Office Excel (*.xlsx *.xlsm *.xltx *.xltm)"] ) if not fileName: return wb = openpyxl.load_workbook(fileName) ws = wb.get_sheet_by_name(wb.get_sheet_names()[0]) data = [] #Keep finding until there is no value. i = 1 while True: x = ws.cell(row=i, column=1).value y = ws.cell(row=i, column=2).value if x == None or y == None: break try: data.append((round(float(x), 4), round(float(y), 4))) except: QMessageBox.warning(self, "File error", "Wrong format.\n" + "The datasheet seems to including non-digital cell." ) break i += 1 for x, y in data: self.add_point(x, y) @pyqtSlot() def on_pathAdjust_clicked(self): """Show up path adjust dialog and get back the changes of current target path. """ dlg = Path_adjust_show(self) dlg.show() if not dlg.exec_(): return self.on_path_clear_clicked() for e in dlg.r_path: self.add_point(e[0], e[1]) self.currentPathChanged() def add_point(self, x: float, y: float): """Add path data to list widget and current target path. """ x = round(x, 4) y = round(y, 4) self.currentPath().append((x, y)) self.path_list.addItem("({:.04f}, {:.04f})".format(x, y)) self.currentPathChanged() @pyqtSlot() def on_close_path_clicked(self): """Add a the last point same as first point.""" currentPath = self.currentPath() if (self.path_list.count() > 1) and (currentPath[0] != currentPath[-1]): self.add_point(*currentPath[0]) @pyqtSlot() def on_point_up_clicked(self): """Target point move up.""" row = self.path_list.currentRow() if not ((row > 0) and (self.path_list.count() > 1)): return path = self.currentPath() path.insert(row - 1, (path[row][0], path[row][1])) del path[row + 1] x, y = self.path_list.currentItem().text()[1:-1].split(", ") self.path_list.insertItem(row - 1, "({}, {})".format(x, y)) self.path_list.takeItem(row + 1) self.path_list.setCurrentRow(row - 1) self.currentPathChanged() @pyqtSlot() def on_point_down_clicked(self): """Target point move down.""" row = self.path_list.currentRow() if not ( (row < self.path_list.count() - 1) and (self.path_list.count() > 1) ): return path = self.currentPath() path.insert(row + 2, (path[row][0], path[row][1])) del path[row] x, y = self.path_list.currentItem().text()[1:-1].split(", ") self.path_list.insertItem(row+2, "({}, {})".format(x, y)) self.path_list.takeItem(row) self.path_list.setCurrentRow(row+1) self.currentPathChanged() @pyqtSlot() def on_point_delete_clicked(self): """Delete a target point.""" row = self.path_list.currentRow() if not row > -1: return del self.currentPath()[row] self.path_list.takeItem(row) self.currentPathChanged() def isGenerate(self): """Set button enable if all the data are already.""" self.pointNum.setText( "<html><head/><body><p><span style=\"font-size:12pt; color:#00aa00;\">" + str(self.path_list.count()) + "</span></p></body></html>" ) n = bool(self.mechanismParams) and (self.path_list.count() > 1) self.pathAdjust.setEnabled(n) self.generate_button.setEnabled(n) @pyqtSlot() def on_generate_button_clicked(self): """Start synthesis.""" #Check if the number of target points are same. leng = -1 for path in self.path.values(): if leng<0: leng = len(path) if len(path)!=leng: QMessageBox.warning(self, "Target Error", "The length of target paths should be the same." ) return #Get the algorithm type. if self.type0.isChecked(): type_num = AlgorithmType.RGA elif self.type1.isChecked(): type_num = AlgorithmType.Firefly elif self.type2.isChecked(): type_num = AlgorithmType.DE #Deep copy it so the pointer will not the same. mechanismParams = deepcopy(self.mechanismParams) mechanismParams['Target'] = deepcopy(self.path) for key in ('Driver', 'Follower'): for name in mechanismParams[key]: row = name_in_table(self.ground_joints, name) mechanismParams[key][name] = ( self.ground_joints.cellWidget(row, 2).value(), self.ground_joints.cellWidget(row, 3).value(), self.ground_joints.cellWidget(row, 4).value() ) for name in ['IMax', 'IMin', 'LMax', 'LMin', 'FMax', 'FMin', 'AMax', 'AMin']: mechanismParams[name] = self.Settings[name] setting = {'report': self.Settings['report']} if 'maxGen' in self.Settings: setting['maxGen'] = self.Settings['maxGen'] elif 'minFit' in self.Settings: setting['minFit'] = self.Settings['minFit'] elif 'maxTime' in self.Settings: setting['maxTime'] = self.Settings['maxTime'] setting.update(self.Settings['algorithmPrams']) #Start progress dialog. dlg = Progress_show( type_num, mechanismParams, setting, self ) dlg.show() if not dlg.exec_(): return for m in dlg.mechanisms: self.mechanism_data.append(m) self.add_result(m) self.setTime(dlg.time_spand) self.unsaveFunc() QMessageBox.information(self, "Dimensional Synthesis", "Your tasks is all completed.", QMessageBox.Ok ) print("Finished.") def setTime(self, time: float): """Set the time label.""" self.timeShow.setText( "<html><head/><body><p><span style=\"font-size:16pt\">" + "{}[min] {:.02f}[s]".format(int(time // 60), time % 60) + "</span></p></body></html>" ) def add_result(self, result: Dict[str, Any]): """Add result items, except add to the list.""" item = QListWidgetItem(result['Algorithm']) interrupt = result['interrupted'] if interrupt=='False': item.setIcon(QIcon(QPixmap(":/icons/task-completed.png"))) elif interrupt=='N/A': item.setIcon(QIcon(QPixmap(":/icons/question-mark.png"))) else: item.setIcon(QIcon(QPixmap(":/icons/interrupted.png"))) text = "{} ({})".format( result['Algorithm'], "No interrupt." if interrupt=='False' else "Interrupt at {}".format(interrupt) ) if interrupt == 'N/A': text += "\n※Completeness is not clear." item.setToolTip(text) self.Result_list.addItem(item) @pyqtSlot() def on_deleteButton_clicked(self): """Delete a result.""" row = self.Result_list.currentRow() if not row>-1: return reply = QMessageBox.question(self, "Delete", "Delete this result from list?" ) if reply != QMessageBox.Yes: return del self.mechanism_data[row] self.Result_list.takeItem(row) self.unsaveFunc() self.hasResult() @pyqtSlot() def hasResult(self): """Set enable if there has any result.""" for button in [ self.mergeButton, self.deleteButton, self.Result_load_settings, self.Result_chart, self.Result_clipboard ]: button.setEnabled(self.Result_list.currentRow()>-1) @pyqtSlot(QModelIndex) def on_Result_list_doubleClicked(self, index): """Double click result item can show up preview dialog.""" row = self.Result_list.currentRow() if not row>-1: return dlg = PreviewDialog(self.mechanism_data[row], self.get_path(row), self) dlg.show() @pyqtSlot() def on_mergeButton_clicked(self): """Merge mechanism into main canvas.""" row = self.Result_list.currentRow() if not row>-1: return reply = QMessageBox.question(self, "Merge", "Merge this result to your canvas?" ) if reply == QMessageBox.Yes: self.mergeResult.emit(row, self.get_path(row)) def get_path(self, row: int): """Using result data to generate paths of mechanism.""" Result = self.mechanism_data[row] expr_angles, expr_links, expr_points = triangle_class(Result['Expression']) if len(expr_angles)>1: return tuple() ''' expr_angles: ('a0', ...) expr_links: ('L0', 'L1', 'L2', ...) expr_points: ('A', 'B', 'C', 'D', 'E', ...) ''' Paths = tuple([] for i in range(len(expr_points))) for a in range(360 + 1): data_dict = {e: Result[e] for e in expr_links} data_dict.update({e: Result[e] for e in Result['Driver']}) data_dict.update({e: Result[e] for e in Result['Follower']}) data_dict.update({expr_angles[0]: radians(a)}) expr_parser(Result['Expression'], data_dict) for i, e in enumerate(expr_points): x, y = data_dict[e] if x!=nan: Paths[i].append((x, y)) return tuple( tuple(path) if len(set(path))>1 else () for path in Paths ) @pyqtSlot() def on_Result_chart_clicked(self): """Show up the chart dialog.""" dlg = ChartDialog("Convergence Value", self.mechanism_data, self) dlg.show() @pyqtSlot() def on_Result_clipboard_clicked(self): """Copy pretty print result as text.""" QApplication.clipboard().setText( pprint.pformat(self.mechanism_data[self.Result_list.currentRow()]) ) @pyqtSlot() def on_save_button_clicked(self): """Save as new profile to collection widget.""" if not self.mechanismParams: return name, ok = QInputDialog.getText(self, "Profile name", "Please enter the profile name:" ) if not ok: return i = 0 while (name not in self.collections) and (not name): name = "Structure_{}".format(i) mechanismParams = deepcopy(self.mechanismParams) for key in [ 'Driver', 'Follower', 'Target' ]: for name in mechanismParams[key]: mechanismParams[key][name] = None self.collections[name] = mechanismParams self.unsaveFunc() @pyqtSlot() def on_load_profile_clicked(self): """Load profile from collections dialog.""" dlg = CollectionsDialog(self) dlg.show() if not dlg.exec_(): return self.clear_settings() self.mechanismParams = dlg.mechanismParams self.profile_name.setText(dlg.name_loaded) self.Expression.setText(self.mechanismParams['Expression']) self.Link_Expression.setText(self.mechanismParams['Link_Expression']) self.set_profile() self.isGenerate() def set_profile(self): """Set profile to sub-widgets.""" params = self.mechanismParams self.path.clear() self.target_points.clear() for name in sorted(params['Target']): self.target_points.addItem(name) path = params['Target'][name] if path: self.path[name] = path.copy() else: self.path[name] = [] if self.target_points.count(): self.target_points.setCurrentRow(0) gj = {} for key in ('Driver', 'Follower'): gj.update(params[key]) self.ground_joints.setRowCount(0) self.ground_joints.setRowCount(len(gj)) def spinbox(v, prefix=False): s = QDoubleSpinBox(self) s.setMinimum(-1000000.0) s.setMaximum(1000000.0) s.setSingleStep(10.0) s.setValue(v) if prefix: s.setPrefix("±") return s nd = {k: int(v.replace('P', '')) for k, v in params['name_dict'].items()} for row, name in enumerate(sorted(gj)): coord = gj[name] self.ground_joints.setItem(row, 0, QTableWidgetItem(name)) self.ground_joints.setItem(row, 1, QTableWidgetItem('Driver' if name in params['Driver'] else 'Follower') ) self.ground_joints.setCellWidget(row, 2, spinbox(coord[0] if coord else params['pos'][nd[name]][0]) ) self.ground_joints.setCellWidget(row, 3, spinbox(coord[1] if coord else params['pos'][nd[name]][1]) ) self.ground_joints.setCellWidget(row, 4, spinbox(coord[2] if coord else 50., True) ) for row in range(self.ground_joints.rowCount()): for column in range(2, 5): self.ground_joints.cellWidget(row, column).valueChanged.connect(self.updateRange) self.updateRange() self.PreviewCanvas.from_profile(self.mechanismParams) @pyqtSlot() def on_Result_load_settings_clicked(self): """Load settings from a result.""" self.hasResult() row = self.Result_list.currentRow() if not row>-1: return self.clear_settings() Result = self.mechanism_data[row] if Result['Algorithm'] == str(AlgorithmType.RGA): self.type0.setChecked(True) elif Result['Algorithm'] == str(AlgorithmType.Firefly): self.type1.setChecked(True) elif Result['Algorithm'] == str(AlgorithmType.DE): self.type2.setChecked(True) self.profile_name.setText("External setting") #External setting. self.Expression.setText(Result['Expression']) self.Link_Expression.setText(Result['Link_Expression']) #Copy to mechanism params. self.mechanismParams.clear() for key in [ 'Driver', 'Follower', 'Target' ]: self.mechanismParams[key] = Result[key].copy() for key in [ 'Link_Expression', 'Expression', 'constraint', 'Graph', 'name_dict', 'pos', 'cus', 'same' ]: self.mechanismParams[key] = Result[key] self.set_profile() self.setTime(Result['time']) settings = Result['settings'] self.Settings = { 'report': settings['report'], 'IMax': Result['IMax'], 'IMin': Result['IMin'], 'LMax': Result['LMax'], 'LMin': Result['LMin'], 'FMax': Result['FMax'], 'FMin': Result['FMin'], 'AMax': Result['AMax'], 'AMin': Result['AMin'] } if 'maxGen' in settings: self.Settings['maxGen'] = settings['maxGen'] elif 'minFit' in settings: self.Settings['minFit'] = settings['minFit'] elif 'maxTime' in settings: self.Settings['maxTime'] = settings['maxTime'] algorithmPrams = settings.copy() del algorithmPrams['report'] self.Settings['algorithmPrams'] = algorithmPrams def algorithmParams_default(self): """Set the algorithm settings to default.""" if self.type0.isChecked(): self.Settings['algorithmPrams'] = GeneticPrams.copy() elif self.type1.isChecked(): self.Settings['algorithmPrams'] = FireflyPrams.copy() elif self.type2.isChecked(): self.Settings['algorithmPrams'] = DifferentialPrams.copy() @pyqtSlot() def on_advanceButton_clicked(self): """Get the settings from advance dialog.""" if self.type0.isChecked(): type_num = AlgorithmType.RGA elif self.type1.isChecked(): type_num = AlgorithmType.Firefly elif self.type2.isChecked(): type_num = AlgorithmType.DE dlg = Options_show(type_num, self.Settings) dlg.show() if not dlg.exec_(): return tablePL = lambda row: dlg.PLTable.cellWidget(row, 1).value() self.Settings = { 'report': dlg.report.value(), 'IMax': tablePL(0), 'IMin': tablePL(1), 'LMax': tablePL(2), 'LMin': tablePL(3), 'FMax': tablePL(4), 'FMin': tablePL(5), 'AMax': tablePL(6), 'AMin': tablePL(7) } if dlg.maxGen_option.isChecked(): self.Settings['maxGen'] = dlg.maxGen.value() elif dlg.minFit_option.isChecked(): self.Settings['minFit'] = dlg.minFit.value() elif dlg.maxTime_option.isChecked(): #Three spinbox value translate to second. self.Settings['maxTime'] = ( dlg.maxTime_h.value() * 3600 + dlg.maxTime_m.value() * 60 + dlg.maxTime_s.value() ) tableAP = lambda row: dlg.APTable.cellWidget(row, 1).value() popSize = dlg.popSize.value() if type_num == AlgorithmType.RGA: self.Settings['algorithmPrams'] = { 'nPop': popSize, 'pCross': tableAP(0), 'pMute': tableAP(1), 'pWin': tableAP(2), 'bDelta': tableAP(3) } elif type_num == AlgorithmType.Firefly: self.Settings['algorithmPrams'] = { 'n': popSize, 'alpha': tableAP(0), 'betaMin': tableAP(1), 'gamma': tableAP(2), 'beta0': tableAP(3) } elif type_num == AlgorithmType.DE: self.Settings['algorithmPrams'] = { 'NP': popSize, 'strategy': tableAP(0), 'F': tableAP(1), 'CR': tableAP(2) } @pyqtSlot(float) def updateRange(self, p0=None): """Update range values to main canvas.""" def t(x, y): item = self.ground_joints.item(x, y) if item: return item.text() else: return self.ground_joints.cellWidget(x, y).value() self.fixPointRange.emit({ t(row, 0): (t(row, 2), t(row, 3), t(row, 4)) for row in range(self.ground_joints.rowCount()) }) @pyqtSlot() def on_Expression_copy_clicked(self): """Copy profile expression.""" text = self.Expression.text() if text: QApplication.clipboard().setText(text) @pyqtSlot() def on_Link_Expression_copy_clicked(self): """Copy profile linkage expression.""" text = self.Link_Expression.text() if text: QApplication.clipboard().setText(text)
class CollectionsDialog(QDialog, Ui_Dialog): """Option dialog. Load the settings after closed. """ def __init__(self, parent): super(CollectionsDialog, self).__init__(parent) self.setupUi(self) self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint | Qt.WindowMaximizeButtonHint) self.collections = parent.collections self.name_loaded = "" def get_solutions_func() -> Tuple[str]: """Return solutions to preview canvas.""" try: return self.collections[self.name_loaded]['Expression'] except KeyError: if self.name_loaded == "Four bar linkage mechanism": return mech_params_4Bar['Expression'] elif self.name_loaded == "Eight bar linkage mechanism": return mech_params_8Bar['Expression'] elif self.name_loaded == "Ball lifter linkage mechanism": return mech_params_BallLifter['Expression'] else: return tuple() self.PreviewCanvas = PreviewCanvas(get_solutions_func, self) self.preview_layout.addWidget(self.PreviewCanvas) self.show_solutions.clicked.connect( self.PreviewCanvas.setShowSolutions) for name in self.collections: self.collections_list.addItem(name) #Splitter self.main_splitter.setSizes([200, 200]) #Signals self.common_list.currentTextChanged.connect(self.__chooseCommon) self.common_list.itemClicked.connect(self.__chooseCommon) self.common_load.clicked.connect(self.__loadCommon) self.common_list.itemDoubleClicked.connect(self.__loadCommon) self.collections_list.currentTextChanged.connect( self.__chooseCollections) self.collections_list.itemClicked.connect(self.__chooseCollections) self.buttonBox.accepted.connect(self.__loadCollections) self.collections_list.itemDoubleClicked.connect(self.__loadCollections) self.collections_list.currentRowChanged.connect(self.__canOpen) self.__hasCollection() self.__canOpen() def __canOpen(self): """Set the button box to enable when data is already.""" self.buttonBox.button(QDialogButtonBox.Open).setEnabled( self.collections_list.currentRow() > -1) def __hasCollection(self): """Set the buttons to enable when user choose a data.""" hasCollection = bool(self.collections) for button in [ self.rename_button, self.copy_button, self.delete_button ]: button.setEnabled(hasCollection) @pyqtSlot() def on_rename_button_clicked(self): """Show up a string input to change the data name.""" row = self.collections_list.currentRow() if not row > -1: return name, ok = QInputDialog.getText(self, "Profile name", "Please enter the profile name:") if not ok: return if not name: QMessageBox.warning(self, "Profile name", "Can not use blank string to rename.") return item = self.collections_list.item(row) self.collections[name] = self.collections.pop(item.text()) item.setText(name) @pyqtSlot() def on_copy_button_clicked(self): """Ask a name to copy a data.""" row = self.collections_list.currentRow() if not row > -1: return name, ok = QInputDialog.getText(self, "Profile name", "Please enter a new profile name:") if not ok: return if not name: QMessageBox.warning(self, "Profile name", "Can not use blank string to rename.") return name_old = self.collections_list.item(row).text() self.collections[name] = self.collections[name_old].copy() self.collections_list.addItem(name) @pyqtSlot() def on_delete_button_clicked(self): """Delete a data.""" row = self.collections_list.currentRow() if not row > -1: return reply = QMessageBox.question(self, "Delete", "Do you want to delete this structure?") if reply != QMessageBox.Yes: return item = self.collections_list.takeItem(row) del self.collections[item.text()] self.PreviewCanvas.clear() self.__hasCollection() @pyqtSlot(str) @pyqtSlot(QListWidgetItem) def __chooseCommon(self, p0=None): """Update preview canvas for common data.""" text = self.common_list.currentItem().text() if not text: return self.name_loaded = text if text == "Four bar linkage mechanism": self.mech_params = deepcopy(mech_params_4Bar) elif text == "Eight bar linkage mechanism": self.mech_params = deepcopy(mech_params_8Bar) elif self.name_loaded == "Ball lifter linkage mechanism": self.mech_params = deepcopy(mech_params_BallLifter) self.PreviewCanvas.from_profile(self.mech_params) @pyqtSlot(str) @pyqtSlot(QListWidgetItem) def __chooseCollections(self, p0=None): """Update preview canvas for a workbook data.""" text = self.collections_list.currentItem().text() if not text: return self.name_loaded = text self.mech_params = self.collections[self.name_loaded] self.PreviewCanvas.from_profile(self.mech_params) @pyqtSlot() @pyqtSlot(QListWidgetItem) def __loadCommon(self, p0=None): """Load a common data and close.""" self.__chooseCommon(self.common_list.currentItem().text()) self.accept() @pyqtSlot() @pyqtSlot(QListWidgetItem) def __loadCollections(self, p0=None): """Load a workbook data and close.""" self.__chooseCollections(self.collections_list.currentItem().text()) self.accept()
class CollectionsDialog(QDialog, Ui_Dialog): def __init__(self, parent): super(CollectionsDialog, self).__init__(parent) self.setupUi(self) self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) self.collections = parent.collections self.name_loaded = "" #Canvas def get_solutions_func(): try: return replace_by_dict(self.collections[self.name_loaded]) except KeyError: if self.name_loaded=="Four bar linkage mechanism": return replace_by_dict(mechanismParams_4Bar) elif self.name_loaded=="Eight bar linkage mechanism": return replace_by_dict(mechanismParams_8Bar) elif self.name_loaded=="Ball lifter linkage mechanism": return replace_by_dict(mechanismParams_BallLifter) else: return tuple() self.PreviewCanvas = PreviewCanvas(get_solutions_func, self) self.preview_layout.addWidget(self.PreviewCanvas) self.show_solutions.clicked.connect(self.PreviewCanvas.setShowSolutions) for name in self.collections: self.collections_list.addItem(name) #Splitter self.main_splitter.setSizes([200, 200]) #Signals self.common_list.currentTextChanged.connect(self.choose_common) self.common_list.itemClicked.connect(self.choose_common) self.common_load.clicked.connect(self.load_common) self.common_list.itemDoubleClicked.connect(self.load_common) self.collections_list.currentTextChanged.connect(self.choose_collections) self.collections_list.itemClicked.connect(self.choose_collections) self.buttonBox.accepted.connect(self.load_collections) self.collections_list.itemDoubleClicked.connect(self.load_collections) self.collections_list.currentRowChanged.connect(self.canOpen) self.hasCollection() self.canOpen() def canOpen(self): self.buttonBox.button(QDialogButtonBox.Open).setEnabled(self.collections_list.currentRow()>-1) def hasCollection(self): hasCollection = bool(self.collections) for button in [self.rename_button, self.copy_button, self.delete_button]: button.setEnabled(hasCollection) @pyqtSlot() def on_rename_button_clicked(self): row = self.collections_list.currentRow() if row>-1: name, ok = QInputDialog.getText(self, "Profile name", "Please enter the profile name:") if ok: if not name: QMessageBox.warning(self, "Profile name", "Can not use blank string to rename.") return item = self.collections_list.item(row) self.collections[name] = self.collections.pop(item.text()) item.setText(name) @pyqtSlot() def on_copy_button_clicked(self): row = self.collections_list.currentRow() if row>-1: name, ok = QInputDialog.getText(self, "Profile name", "Please enter a new profile name:") if ok: if not name: QMessageBox.warning(self, "Profile name", "Can not use blank string to rename.") return name_old = self.collections_list.item(row).text() self.collections[name] = self.collections[name_old].copy() self.collections_list.addItem(name) @pyqtSlot() def on_delete_button_clicked(self): row = self.collections_list.currentRow() if row>-1: reply = QMessageBox.question(self, "Delete", "Do you want to delete this structure?", (QMessageBox.Yes | QMessageBox.No), QMessageBox.Yes) if reply==QMessageBox.Yes: item = self.collections_list.takeItem(row) del self.collections[item.text()] self.PreviewCanvas.clear() self.hasCollection() @pyqtSlot(str) @pyqtSlot(QListWidgetItem) def choose_common(self, p0=None): text = self.common_list.currentItem().text() if text: self.name_loaded = text if text=="Four bar linkage mechanism": self.mechanismParams = deepcopy(mechanismParams_4Bar) elif text=="Eight bar linkage mechanism": self.mechanismParams = deepcopy(mechanismParams_8Bar) elif self.name_loaded=="Ball lifter linkage mechanism": self.mechanismParams = deepcopy(mechanismParams_BallLifter) self.PreviewCanvas.from_profile(self.mechanismParams) @pyqtSlot(str) @pyqtSlot(QListWidgetItem) def choose_collections(self, p0=None): text = self.collections_list.currentItem().text() if text: self.name_loaded = text self.mechanismParams = self.collections[self.name_loaded] self.PreviewCanvas.from_profile(self.mechanismParams) @pyqtSlot() @pyqtSlot(QListWidgetItem) def load_common(self, p0=None): self.choose_common(self.common_list.currentItem().text()) self.accept() @pyqtSlot() @pyqtSlot(QListWidgetItem) def load_collections(self, p0=None): self.choose_collections(self.collections_list.currentItem().text()) self.accept() @pyqtSlot(bool) def on_switch_name_clicked(self, checked): if checked: self.PreviewCanvas.setNameDict({}) else: self.PreviewCanvas.setNameDict(self.mechanismParams['name_dict'])
class CollectionsDialog(QDialog, Ui_Dialog): """Option dialog. Load the settings after closed. Any add, rename, delete operations will be apply immediately """ def __init__( self, collections: Dict[str, Any], get_collection: Callable[[], Dict[str, Any]], unsave_func: Callable[[], None], monochrome: bool, parent: QWidget ): """We put the 'collections' (from iteration widget) reference here.""" super(CollectionsDialog, self).__init__(parent) self.setupUi(self) self.setWindowFlags( self.windowFlags() & ~Qt.WindowContextHelpButtonHint | Qt.WindowMaximizeButtonHint ) self.collections = collections self.get_collection = get_collection self.unsave_func = unsave_func # Current profile name self.name = "" self.params: Dict[str, Any] = {} self.preview_canvas = PreviewCanvas(self) self.preview_layout.addWidget(self.preview_canvas) self.preview_canvas.set_monochrome_mode(monochrome) self.common_list.addItems(collection_list) self.collections_list.addItems(self.collections) # Splitter self.main_splitter.setSizes([200, 200]) self.sub_splitter.setSizes([100, 200]) self.__has_collection() self.__can_open() @Slot(str, name='on_collections_list_currentTextChanged') def __can_open(self, _=None): """Set the button box to enable when data is already.""" self.button_box.button(QDialogButtonBox.Open).setEnabled( self.collections_list.currentRow() > -1 ) def __has_collection(self): """Set the buttons to enable when user choose a data.""" has_collection = bool(self.collections) for button in [ self.rename_button, self.copy_button, self.delete_button ]: button.setEnabled(has_collection) @Slot(name='on_rename_button_clicked') def __rename(self): """Show up a string input to change the data name.""" row = self.collections_list.currentRow() if not row > -1: return name, ok = QInputDialog.getText( self, "Profile name", "Please enter the profile name:" ) if not ok: return if not name: QMessageBox.warning( self, "Profile name", "Can not use blank string to rename." ) return item = self.collections_list.item(row) self.collections[name] = self.collections.pop(item.text()) item.setText(name) self.unsave_func() @Slot(name='on_copy_button_clicked') def __copy(self): """Ask a name to copy a data.""" row = self.collections_list.currentRow() if not row > -1: return name, ok = QInputDialog.getText( self, "Profile name", "Please enter a new profile name:" ) if not ok: return if not name: QMessageBox.warning( self, "Profile name", "Can not use blank string to rename." ) return name_old = self.collections_list.item(row).text() self.collections[name] = self.collections[name_old].copy() self.collections_list.addItem(name) self.unsave_func() @Slot(name='on_delete_button_clicked') def __delete(self): """Delete a data.""" row = self.collections_list.currentRow() if not row > -1: return if QMessageBox.question( self, "Delete", "Do you want to delete this structure?" ) != QMessageBox.Yes: return item = self.collections_list.takeItem(row) self.collections.pop(item.text()) self.preview_canvas.clear() self.__has_collection() self.unsave_func() @Slot(QListWidgetItem, name='on_common_list_itemClicked') def __choose_common(self, _=None): """Update preview canvas for common data.""" item = self.common_list.currentItem() if not item: return self.name = item.text() self.params = deepcopy(collection_list[self.name]) self.preview_canvas.from_profile(self.params) @Slot(QListWidgetItem, name='on_collections_list_itemClicked') def __choose_collections(self, _=None): """Update preview canvas for a workbook data.""" item = self.collections_list.currentItem() if not item: return self.name = item.text() self.params = deepcopy(self.collections[self.name]) self.preview_canvas.from_profile(self.params) @Slot(name='on_workbook_button_clicked') def __from_canvas(self): """Get a collection data from current mechanism.""" try: collection = self.get_collection() except ValueError as error: QMessageBox.warning(self, "Mechanism not support.", str(error)) return num = 0 name = f"mechanism{num}" while name in self.collections: name = f"mechanism{num}" num += 1 self.collections[name] = collection.copy() self.collections_list.addItem(name) self.unsave_func() self.__has_collection() @Slot(name='on_common_load_clicked') @Slot(QListWidgetItem, name='on_common_list_itemDoubleClicked') def __load_common(self, _=None): """Load a common data and close.""" self.__choose_common() self.accept() @Slot(name='on_button_box_accepted') @Slot(QListWidgetItem, name='on_collections_list_itemDoubleClicked') def __load_collections(self, _=None): """Load a workbook data and close.""" self.__choose_collections() self.accept()
class DimensionalSynthesis(QWidget, Ui_Form): """Dimensional synthesis widget. User can run the dimensional synthesis here. """ def __init__(self, parent: MainWindowBase): """Reference names: + Iteration collections. + Result data. + Main window function references. """ super(DimensionalSynthesis, self).__init__(parent) self.setupUi(self) self.mech_params: Dict[str, Any] = {} self.path: Dict[int, List[_Coord]] = {} # Some reference of 'collections' self.collections = parent.collection_tab_page.configure_widget.collections self.get_collection = parent.get_configure self.input_from = parent.input_from self.workbook_no_save = parent.workbook_no_save self.merge_result = parent.merge_result self.update_ranges = parent.main_canvas.update_ranges self.set_solving_path = parent.main_canvas.set_solving_path # Data and functions self.mechanism_data: List[Dict[str, Any]] = [] self.alg_options: Dict[str, Union[int, float]] = {} self.alg_options.update(defaultSettings) self.alg_options.update(DifferentialPrams) self.__set_algorithm_default() self.preview_canvas = PreviewCanvas(self) self.preview_layout.addWidget(self.preview_canvas) # Splitter self.main_splitter.setStretchFactor(0, 100) self.main_splitter.setStretchFactor(1, 10) self.up_splitter.setSizes([80, 100]) self.down_splitter.setSizes([20, 80]) # Table widget column width header = self.parameter_list.horizontalHeader() header.setSectionResizeMode(QHeaderView.ResizeToContents) self.clear() def clear(self): """Clear all sub-widgets.""" self.mechanism_data.clear() self.result_list.clear() self.__clear_settings() self.__has_result() def __clear_settings(self): """Clear sub-widgets that contain the setting.""" self.__clear_path(ask=False) self.path.clear() self.mech_params.clear() self.preview_canvas.clear() self.alg_options.clear() self.alg_options.update(defaultSettings) self.alg_options.update(DifferentialPrams) self.profile_name.clear() self.type2.setChecked(True) self.parameter_list.setRowCount(0) self.target_points.clear() self.expression_string.clear() self.update_range() self.__able_to_generate() @Slot(name='on_clear_button_clicked') def __user_clear(self): if not self.profile_name.text(): return if QMessageBox.question( self, "Clear setting", "Do you want to clear the setting?") == QMessageBox.Yes: self.__clear_settings() def load_results(self, mechanism_data: Sequence[Dict[str, Any]]): """Append results of workbook database to memory.""" for e in mechanism_data: self.mechanism_data.append(e) self.__add_result(e) def __current_path_changed(self): """Call the canvas to update to current target path.""" self.set_solving_path( {f"P{name}": tuple(path) for name, path in self.path.items()}) self.__able_to_generate() def current_path(self) -> List[_Coord]: """Return the pointer of current target path.""" item = self.target_points.currentItem() if item is None: return [] return self.path[int(item.text().replace('P', ''))] @Slot(str, name='on_target_points_currentTextChanged') def __set_target(self, _: str): """Switch to the current target path.""" self.path_list.clear() for x, y in self.current_path(): self.path_list.addItem(f"({x:.04f}, {y:.04f})") self.__current_path_changed() @Slot(name='on_path_clear_clicked') def __clear_path(self, *, ask: bool = True): """Clear the current target path.""" if ask: if QMessageBox.question(self, "Clear path", "Are you sure to clear the current path?" ) != QMessageBox.Yes: return self.current_path().clear() self.path_list.clear() self.__current_path_changed() @Slot(name='on_path_copy_clicked') def __copy_path(self): """Copy the current path coordinates to clipboard.""" QApplication.clipboard().setText('\n'.join( f"{x},{y}" for x, y in self.current_path())) @Slot(name='on_path_paste_clicked') def __paste_path(self): """Paste path data from clipboard.""" self.__read_path_from_csv( char_split(r"[;,\n]", QApplication.clipboard().text())) @Slot(name='on_import_csv_button_clicked') def __import_csv(self): """Paste path data from a text file.""" file_name = self.input_from( "Path data", ["Text file (*.txt)", "Comma-Separated Values (*.csv)"]) if not file_name: return data = [] with open(file_name, 'r', encoding='utf-8', newline='') as f: for row in csv.reader(f, delimiter=' ', quotechar='|'): data += " ".join(row).split(',') self.__read_path_from_csv(data) def __read_path_from_csv(self, raw_data: List[str]): """Turn string to float then add them to current target path.""" try: data = [(round(float(raw_data[i]), 4), round(float(raw_data[i + 1]), 4)) for i in range(0, len(raw_data), 2)] except (IndexError, ValueError): QMessageBox.warning( self, "File error", "Wrong format.\nIt should be look like this:" + ("\n0.0,0.0[\\n]" * 3)) else: for x, y in data: self.add_point(x, y) @Slot(name='on_import_xlsx_button_clicked') def __import_xlsx(self): """Paste path data from a Excel file.""" file_name = self.input_from( "Excel file", ["Microsoft Office Excel (*.xlsx *.xlsm *.xltx *.xltm)"]) if not file_name: return wb = load_workbook(file_name) ws = wb.get_sheet_by_name(wb.get_sheet_names()[0]) data = [] # Keep finding until there is no value. i = 1 while True: x = ws.cell(row=i, column=1).value y = ws.cell(row=i, column=2).value if None in {x, y}: break try: data.append((round(float(x), 4), round(float(y), 4))) except (IndexError, AttributeError): QMessageBox.warning( self, "File error", "Wrong format.\n" "The data sheet seems to including non-digital cell.") break i += 1 for x, y in data: self.add_point(x, y) @Slot(name='on_path_adjust_button_clicked') def __adjust_path(self): """Show up path adjust dialog and get back the changes of current target path. """ dlg = PathAdjustDialog(self) dlg.show() if not dlg.exec(): dlg.deleteLater() return self.__clear_path(ask=False) for e in dlg.r_path: self.add_point(e[0], e[1]) dlg.deleteLater() self.__current_path_changed() def add_point(self, x: float, y: float): """Add path data to list widget and current target path.""" x = round(x, 4) y = round(y, 4) self.current_path().append((x, y)) self.path_list.addItem(f"({x:.04f}, {y:.04f})") self.path_list.setCurrentRow(self.path_list.count() - 1) self.__current_path_changed() @Slot(float, float) def set_point(self, x: float, y: float): """Set the coordinate of current target path.""" if not self.edit_target_point_button.isChecked(): return for i, (cx, cy) in enumerate(self.current_path()): if hypot(x - cx, y - cy) < 5: index = i self.path_list.setCurrentRow(index) break else: return self.current_path()[index] = (x, y) self.path_list.item(index).setText(f"({x:.04f}, {y:.04f})") self.__current_path_changed() @Slot(name='on_close_path_clicked') def __close_path(self): """Add a the last point same as first point.""" current_path = self.current_path() if (self.path_list.count() > 1) and (current_path[0] != current_path[-1]): self.add_point(*current_path[0]) @Slot(name='on_point_up_clicked') def __move_up_point(self): """Target point move up.""" row = self.path_list.currentRow() if not ((row > 0) and (self.path_list.count() > 1)): return path = self.current_path() path.insert(row - 1, (path[row][0], path[row][1])) del path[row + 1] x, y = self.path_list.currentItem().text()[1:-1].split(", ") self.path_list.insertItem(row - 1, f"({x}, {y})") self.path_list.takeItem(row + 1) self.path_list.setCurrentRow(row - 1) self.__current_path_changed() @Slot(name='on_point_down_clicked') def __move_down_point(self): """Target point move down.""" row = self.path_list.currentRow() if not ((row < self.path_list.count() - 1) and (self.path_list.count() > 1)): return path = self.current_path() path.insert(row + 2, (path[row][0], path[row][1])) del path[row] x, y = self.path_list.currentItem().text()[1:-1].split(", ") self.path_list.insertItem(row + 2, f"({x}, {y})") self.path_list.takeItem(row) self.path_list.setCurrentRow(row + 1) self.__current_path_changed() @Slot(name='on_point_delete_clicked') def __delete_point(self): """Delete a target point.""" row = self.path_list.currentRow() if not row > -1: return del self.current_path()[row] self.path_list.takeItem(row) self.__current_path_changed() def __able_to_generate(self): """Set button enable if all the data are already.""" self.pointNum.setText( "<p><span style=\"font-size:12pt;" f"color:#00aa00;\">{self.path_list.count()}</span></p>") n = bool(self.mech_params and (self.path_list.count() > 1) and self.expression_string.text()) self.path_adjust_button.setEnabled(n) self.synthesis_button.setEnabled(n) @Slot(name='on_synthesis_button_clicked') def __synthesis(self): """Start synthesis.""" # Check if the number of target points are same. length = -1 for path in self.path.values(): if length < 0: length = len(path) if len(path) != length: QMessageBox.warning( self, "Target Error", "The length of target paths should be the same.") return # Get the algorithm type. if self.type0.isChecked(): type_num = AlgorithmType.RGA elif self.type1.isChecked(): type_num = AlgorithmType.Firefly else: type_num = AlgorithmType.DE # Deep copy it so the pointer will not the same. mech_params = deepcopy(self.mech_params) mech_params['Expression'] = parse_vpoints( mech_params.pop('Expression', [])) mech_params['Target'] = deepcopy(self.path) def name_in_table(target_name: str) -> int: """Find a target_name and return the row from the table.""" for r in range(self.parameter_list.rowCount()): if self.parameter_list.item(r, 0).text() == target_name: return r return -1 placement: Dict[int, Tuple[float, float, float]] = mech_params['Placement'] for name in placement: row = name_in_table(f"P{name}") placement[name] = ( self.parameter_list.cellWidget(row, 2).value(), self.parameter_list.cellWidget(row, 3).value(), self.parameter_list.cellWidget(row, 4).value(), ) # Start progress dialog. dlg = ProgressDialog(type_num, mech_params, self.alg_options, self) dlg.show() if not dlg.exec(): dlg.deleteLater() return mechanisms = dlg.mechanisms mechanisms_plot: List[Dict[str, Any]] = [] for data in mechanisms: mechanisms_plot.append({ 'time_fitness': data.pop('time_fitness'), 'Algorithm': data['Algorithm'], }) self.mechanism_data.append(data) self.__add_result(data) self.__set_time(dlg.time_spend) self.workbook_no_save() dlg.deleteLater() dlg = ChartDialog("Convergence Data", mechanisms_plot, self) dlg.show() dlg.exec() dlg.deleteLater() def __set_time(self, time: float): """Set the time label.""" self.timeShow.setText( "<html><head/><body><p><span style=\"font-size:16pt\">" f"{time // 60}[min] {time % 60:.02f}[s]" "</span></p></body></html>") def __add_result(self, result: Dict[str, Any]): """Add result items, except add to the list.""" item = QListWidgetItem(result['Algorithm']) interrupt = result['interrupted'] if interrupt == 'False': interrupt_icon = "task_completed.png" elif interrupt == 'N/A': interrupt_icon = "question.png" else: interrupt_icon = "interrupted.png" item.setIcon(QIcon(QPixmap(f":/icons/{interrupt_icon}"))) if interrupt == 'False': interrupt_text = "No interrupt." else: interrupt_text = f"Interrupt at: {interrupt}" text = f"{result['Algorithm']} ({interrupt_text})" if interrupt == 'N/A': text += "\n※Completeness is unknown." item.setToolTip(text) self.result_list.addItem(item) @Slot(name='on_delete_button_clicked') def __delete_result(self): """Delete a result.""" row = self.result_list.currentRow() if not row > -1: return if QMessageBox.question( self, "Delete", "Delete this result from list?") != QMessageBox.Yes: return self.mechanism_data.pop(row) self.result_list.takeItem(row) self.workbook_no_save() self.__has_result() @Slot(QModelIndex, name='on_result_list_clicked') def __has_result(self, *_): """Set enable if there has any result.""" enable = self.result_list.currentRow() > -1 for button in (self.merge_button, self.delete_button, self.result_load_settings, self.result_clipboard): button.setEnabled(enable) @Slot(QModelIndex, name='on_result_list_doubleClicked') def __show_result(self, _: QModelIndex): """Double click result item can show up preview dialog.""" row = self.result_list.currentRow() if not row > -1: return dlg = PreviewDialog(self.mechanism_data[row], self.__get_path(row), self) dlg.show() dlg.exec() dlg.deleteLater() @Slot(name='on_merge_button_clicked') def __merge_result(self): """Merge mechanism into main canvas.""" row = self.result_list.currentRow() if not row > -1: return if QMessageBox.question( self, "Merge", "Add the result expression into storage?") == QMessageBox.Yes: expression: str = self.mechanism_data[row]['Expression'] self.merge_result(expression, self.__get_path(row)) def __get_path(self, row: int) -> List[List[_Coord]]: """Using result data to generate paths of mechanism.""" result = self.mechanism_data[row] expression: str = result['Expression'] same: Dict[int:int] = result['same'] inputs: List[Tuple[int, int]] = result['input'] input_list = [] for b, d in inputs: for i in range(b): if i in same: b -= 1 for i in range(d): if i in same: d -= 1 input_list.append((b, d)) vpoints = parse_vpoints(expression) expr = vpoints_configure(vpoints, input_list) b, d = input_list[0] base_angle = vpoints[b].slope_angle(vpoints[d]) path = [[] for _ in range(len(vpoints))] for angle in range(360 + 1): try: result_list = expr_solving( expr, {i: f"P{i}" for i in range(len(vpoints))}, vpoints, [base_angle + angle] + [0] * (len(input_list) - 1)) except ValueError: nan = float('nan') for i in range(len(vpoints)): path[i].append((nan, nan)) else: for i in range(len(vpoints)): coord = result_list[i] if type(coord[0]) == tuple: path[i].append(coord[1]) else: path[i].append(coord) return path @Slot(name='on_result_clipboard_clicked') def __copy_result_text(self): """Copy pretty print result as text.""" QApplication.clipboard().setText( pprint.pformat(self.mechanism_data[self.result_list.currentRow()])) @Slot(name='on_save_profile_clicked') def __save_profile(self): """Save as new profile to collection widget.""" if not self.mech_params: return name, ok = QInputDialog.getText(self, "Profile name", "Please enter the profile name:") if not ok: return i = 0 while (not name) and (name not in self.collections): name = f"Structure_{i}" i += 1 mech_params = deepcopy(self.mech_params) for key in ('Placement', 'Target'): for mp in mech_params[key]: mech_params[key][mp] = None self.collections[name] = mech_params self.workbook_no_save() @Slot(name='on_load_profile_clicked') def __load_profile(self): """Load profile from collections dialog.""" dlg = CollectionsDialog(self.collections, self.get_collection, self.workbook_no_save, self.preview_canvas.monochrome, self) dlg.show() if not dlg.exec(): dlg.deleteLater() return params = dlg.params name = dlg.name dlg.deleteLater() del dlg # Check the profile if not (params['Target'] and params['input'] and params['Placement']): QMessageBox.warning(self, "Invalid profile", "The profile is not configured yet.") return self.__set_profile(name, params) def __set_profile(self, profile_name: str, params: Dict[str, Any]): """Set profile to sub-widgets.""" self.__clear_settings() self.mech_params = deepcopy(params) expression: str = self.mech_params['Expression'] self.expression_string.setText(expression) target: Dict[int, List[_Coord]] = self.mech_params['Target'] for name in sorted(target): self.target_points.addItem(f"P{name}") path = target[name] if path: self.path[name] = path.copy() else: self.path[name] = [] if self.target_points.count(): self.target_points.setCurrentRow(0) # Parameter of link length and input angle. link_list = set() for vlink in parse_vlinks(expression): if len(vlink.points) < 2: continue a = vlink.points[0] b = vlink.points[1] link_list.add(f"P{a}<->P{b}") for c in vlink.points[2:]: for d in (a, b): link_list.add(f"P{c}<->P{d}") link_count = len(link_list) angle_list = set() input_list: List[Tuple[int, int]] = self.mech_params['input'] for b, d in input_list: angle_list.add(f"P{b}->P{d}") angle_count = len(angle_list) self.parameter_list.setRowCount(0) placement: Dict[int, Tuple[float, float, float]] = self.mech_params['Placement'] self.parameter_list.setRowCount( len(placement) + link_count + angle_count) def spinbox(v: float, *, minimum: float = 0., maximum: float = 9999., prefix: bool = False) -> QDoubleSpinBox: double_spinbox = QDoubleSpinBox() double_spinbox.setMinimum(minimum) double_spinbox.setMaximum(maximum) double_spinbox.setSingleStep(10.0) double_spinbox.setValue(v) if prefix: double_spinbox.setPrefix("±") return double_spinbox # Position. pos_list = parse_pos(expression) same: Dict[int, int] = self.mech_params['same'] for node, ref in sorted(same.items()): pos_list.insert(node, pos_list[ref]) pos: Dict[int, _Coord] = dict(enumerate(pos_list)) row = 0 for name in sorted(placement): coord = placement[name] self.parameter_list.setItem(row, 0, QTableWidgetItem(f"P{name}")) self.parameter_list.setItem(row, 1, QTableWidgetItem('Placement')) x, y = pos[name] for i, s in enumerate([ spinbox(coord[0] if coord else x, minimum=-9999.), spinbox(coord[1] if coord else y, minimum=-9999.), spinbox(coord[2] if coord else 25., prefix=True), ]): s.valueChanged.connect(self.update_range) self.parameter_list.setCellWidget(row, i + 2, s) row += 1 # Default value of upper and lower. for name in ('upper', 'lower'): if name not in self.mech_params: self.mech_params[name] = [0.] * (link_count + angle_count) upper_list: List[float] = self.mech_params['upper'] lower_list: List[float] = self.mech_params['lower'] def set_by_center( index: int, get_range: Callable[[], float]) -> Callable[[float], None]: """Return a slot function use to set limit value by center.""" @Slot(float) def func(value: float): range_value = get_range() upper_list[index] = value + range_value lower_list[index] = value - range_value return func def set_by_range( index: int, get_value: Callable[[], float]) -> Callable[[float], None]: """Return a slot function use to set limit value by range.""" @Slot(float) def func(value: float): center = get_value() upper_list[index] = center + value lower_list[index] = center - value return func for i, name in enumerate(sorted(link_list) + sorted(angle_list)): name_item = QTableWidgetItem(name) name_item.setToolTip(name) self.parameter_list.setItem(row, 0, name_item) if name in link_list: type_name = "Link" else: type_name = "Input" self.parameter_list.setItem(row, 1, QTableWidgetItem(type_name)) # Set values (it will be same if not in the 'mech_params'). upper = upper_list[i] if upper == 0.: upper = 100. if name in link_list else 360. lower = lower_list[i] if lower == 0. and name in link_list: lower = 0. upper_list[i] = upper lower_list[i] = lower # Spin box. error_range = (upper - lower) / 2 default_value = error_range + lower if name in link_list: s1 = spinbox(default_value) else: s1 = spinbox(default_value, maximum=360.) self.parameter_list.setCellWidget(row, 2, s1) s2 = spinbox(error_range, prefix=True) self.parameter_list.setCellWidget(row, 4, s2) # Signal connections. s1.valueChanged.connect(set_by_center(i, s2.value)) s2.valueChanged.connect(set_by_range(i, s1.value)) row += 1 self.preview_canvas.from_profile(self.mech_params) self.update_range() self.profile_name.setText(profile_name) # Default value of algorithm option. if 'settings' in self.mech_params: self.alg_options.update(self.mech_params['settings']) else: self.__set_algorithm_default() self.__able_to_generate() @Slot(name='on_result_load_settings_clicked') def __load_result_settings(self): """Load settings from a result.""" self.__has_result() row = self.result_list.currentRow() if not row > -1: return self.__clear_settings() result = self.mechanism_data[row] if result['Algorithm'] == str(AlgorithmType.RGA): self.type0.setChecked(True) elif result['Algorithm'] == str(AlgorithmType.Firefly): self.type1.setChecked(True) elif result['Algorithm'] == str(AlgorithmType.DE): self.type2.setChecked(True) # Copy to mechanism params. self.__set_profile("External setting", result) self.__set_time(result['time']) # Load settings. self.alg_options.clear() self.alg_options.update(result['settings']) @Slot(name='on_type0_clicked') @Slot(name='on_type1_clicked') @Slot(name='on_type2_clicked') def __set_algorithm_default(self): """Set the algorithm settings to default.""" self.alg_options.clear() self.alg_options.update(defaultSettings) if self.type0.isChecked(): self.alg_options.update(GeneticPrams) elif self.type1.isChecked(): self.alg_options.update(FireflyPrams) elif self.type2.isChecked(): self.alg_options.update(DifferentialPrams) @Slot(name='on_advance_button_clicked') def __show_advance(self): """Get the settings from advance dialog.""" if self.type0.isChecked(): type_num = AlgorithmType.RGA elif self.type1.isChecked(): type_num = AlgorithmType.Firefly else: type_num = AlgorithmType.DE dlg = AlgorithmOptionDialog(type_num, self.alg_options, self) dlg.show() if not dlg.exec(): dlg.deleteLater() return self.alg_options['report'] = dlg.report.value() self.alg_options.pop('max_gen', None) self.alg_options.pop('min_fit', None) self.alg_options.pop('max_time', None) if dlg.max_gen_option.isChecked(): self.alg_options['max_gen'] = dlg.max_gen.value() elif dlg.min_fit_option.isChecked(): self.alg_options['min_fit'] = dlg.min_fit.value() elif dlg.max_time_option.isChecked(): # Three spinbox value translate to second. self.alg_options['max_time'] = (dlg.max_time_h.value() * 3600 + dlg.max_time_m.value() * 60 + dlg.max_time_s.value()) else: raise ValueError("invalid option") def from_table(row: int) -> Union[int, float]: """Get algorithm data from table.""" return dlg.alg_table.cellWidget(row, 1).value() pop_size = dlg.pop_size.value() if type_num == AlgorithmType.RGA: self.alg_options['nPop'] = pop_size for i, tag in enumerate(('pCross', 'pMute', 'pWin', 'bDelta')): self.alg_options[tag] = from_table(i) elif type_num == AlgorithmType.Firefly: self.alg_options['n'] = pop_size for i, tag in enumerate(('alpha', 'beta_min', 'gamma', 'beta0')): self.alg_options[tag] = from_table(i) elif type_num == AlgorithmType.DE: self.alg_options['NP'] = pop_size for i, tag in enumerate(('strategy', 'F', 'CR')): self.alg_options[tag] = from_table(i) dlg.deleteLater() @Slot() def update_range(self): """Update range values to main canvas.""" def t(x: int, y: int) -> Union[str, float]: item = self.parameter_list.item(x, y) if item is None: return self.parameter_list.cellWidget(x, y).value() else: return item.text() self.update_ranges({ t(row, 0): (t(row, 2), t(row, 3), t(row, 4)) for row in range(self.parameter_list.rowCount()) if t(row, 1) == 'Placement' }) @Slot(name='on_expr_copy_clicked') def __copy_expr(self): """Copy profile expression.""" text = self.expression_string.text() if text: QApplication.clipboard().setText(text)