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()
def pasteStorage(self): """Add the storage data from string.""" expr, ok = QInputDialog.getMultiLineText(self, "Storage", "Please input expression:") if not ok: return try: # Put the expression into parser to see if it is legal. parse_params(expr) except Exception as e: print(e) QMessageBox.warning(self, "Loading failed", "Your expression is in an incorrect format.") return name, ok = QInputDialog.getText(self, "Storage", "Please input name tag:") if not ok: return name_list = [ self.mechanism_storage.item(i).text() for i in range(self.mechanism_storage.count()) ] i = 0 name = name or f"Prototype_{i}" while name in name_list: name = f"Prototype_{i}" i += 1 self.__add_storage(name, expr)
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()
def on_expression_auto_clicked(self): """Auto configure the solutions.""" if not self.driver_list.count(): QMessageBox.information(self, "Auto configure", "Please setting the driver joint(s).") return reply = QMessageBox.question( self, "Auto configure", "This function can detect the structure " + "to configure the solutions.\n" + "The current settings will be cleared.") if ((reply != QMessageBox.Yes) or (not self.on_expression_clear_clicked())): return exprs = vpoints_configure( graph2vpoints(self.PreviewWindow.G, self.PreviewWindow.pos, self.PreviewWindow.cus, self.PreviewWindow.same), [ eval(item.text().replace('P', '')) for item in list_items(self.driver_list) ], self.PreviewWindow.status) for expr in exprs: self.__addSolution(*expr) self.__hasSolution() self.__setWarning(self.expression_list_label, not self.PreviewWindow.isAllLock()) self.PreviewWindow.update()
def refresh_proj(self, node: Optional[QTreeWidgetItem] = None): """Re-parse the file node.""" if node is None: node = self.tree_main.currentItem() if not node.text(1): QMessageBox.warning(self, "No path", "Can only refresh from valid path.") return self.__delete_node_data(node) node.takeChildren() parse(node, self.data) self.tree_main.setCurrentItem(node) _expand_recursive(node) code = int(node.text(2)) self.text_editor.setText(self.data[code]) self.data.set_saved(code, True) self.__add_macros() # File keeper if self.keeper is not None: self.keeper.stop() self.keeper = FileKeeper(LOADED_FILES, self) self.keeper.file_changed.connect(self.file_changed_warning) self.keeper.start()
def on_match_button_clicked(self): """Fitting function.""" l = len(self.path) if l==0: return index = list(range(l)) def polyfit(x: Sequence[float], y: Sequence[float], d: int): """Return a 2D fitting equation.""" coeffs = np.polyfit(x, y, d) #Fit values and mean. yhat = np.poly1d(coeffs)(x) ybar = np.sum(y)/len(y) return ( lambda t: sum(c * t**pow for pow, c in enumerate(reversed(coeffs))), np.sum((yhat - ybar)**2) / np.sum((y - ybar)**2) ) x_func, x_accuracy = polyfit(index, [x for x, y in self.path], 4) y_func, y_accuracy = polyfit(index, [y for x, y in self.path], 4) QMessageBox.information(self, "Curve fitting", "Accuracy:\nx: {:.02f}%\ny: {:.02f}%".format(x_accuracy, y_accuracy), QMessageBox.Ok, QMessageBox.Ok ) m = self.match_num.value() self.r_path = [(x_func(i/m*l), y_func(i/m*l)) for i in range(m)] self.accept()
def __read_slvs(self, file_name: str): """Read slvs format. + Choose a group. + Read the entities of the group. """ parser = SlvsParser(file_name) if not parser.isValid(): QMessageBox.warning(self, "Format error", "The format is not support.") return groups = parser.getGroups() if not groups: QMessageBox.warning(self, "Format error", "The model file is empty.") return group, ok = QInputDialog.getItem( self, "Solvespace groups", "Choose a group:\n" "(Please know that the group must contain a sketch only.)", ["@".join(g) for g in groups], 0, False) if not ok: return self.clear() self.DatabaseWidget.reset() print(f"Read from group: {group}") self.parseExpression(parser.parse(group.split('@')[0]))
def __replace_project(self): """Replace in project.""" self.__find_project() count = self.find_list.count() if count == 0: return if QMessageBox.question(self, "Replace in project", f"Replace all? ({count})") != QMessageBox.Yes: return replace_text = self.replace_bar.text() if not replace_text: if QMessageBox.question( self, "Empty replace text", f"Replace text is an empty string, still replace all?" ) != QMessageBox.No: return text, replace_text, flags = self.__search_option() used_code = set() for row in range(self.find_list.count()): code = int(self.find_list.item(row).toolTip().split(':')[0]) if code in used_code: continue self.data[code] = re.sub(text, replace_text, self.data[code], flags) used_code.add(code) self.__root_unsaved()
def __add_from_files(self): """Append atlas by text files.""" file_names = self.input_from("Edges data", ["Text File (*.txt)"], multiple=True) if not file_names: return read_data = [] for file_name in file_names: with open(file_name, 'r', encoding='utf-8') as f: for line in f: read_data.append(line) collections = [] for edges in read_data: try: collections.append(Graph(eval(edges))) except (SyntaxError, TypeError): QMessageBox.warning(self, "Wrong format", "Please check the edges text format.") return if not collections: return self.collections += collections self.__reload_atlas()
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)
def __find_project(self): """Find in all project.""" self.find_list.clear() self.find_list_node.clear() node_current = self.tree_main.currentItem() if node_current is None: return root = _get_root(node_current) text, _, flags = self.__search_option() def find_in_nodes(node: QTreeWidgetItem, last_name: str = ''): """Find the word in all nodes.""" last_name += node.text(0) if node.childCount(): last_name += '->' code = int(node.text(2)) doc = self.data[code] pattern = re.compile(text.encode('utf-8'), flags) for m in pattern.finditer(doc.encode('utf-8')): start, end = m.span() item = QListWidgetItem(last_name) item.setToolTip(f"{code}:{start}:{end}") self.find_list_node[code] = node self.find_list.addItem(item) for i in range(node.childCount()): find_in_nodes(node.child(i), last_name) find_in_nodes(root) find_in_nodes = None count = self.find_list.count() QMessageBox.information(self, "Find in project", f"Found {count} result.")
def on_mechanism_storage_paste_clicked(self): """Add the storage data from string.""" expr, ok = QInputDialog.getText(self, "Storage", "Please input expression:") if not ok: return try: #Put the expression into parser to see if it is legal. PMKS_parser.parse(expr) except: QMessageBox.warning(self, "Loading failed", "Your expression is in an incorrect format.") return name, ok = QInputDialog.getText(self, "Storage", "Please input name tag:") if not ok: return if not name: nameList = [ self.mechanism_storage.item(i).text() for i in range(self.mechanism_storage.count()) ] i = 0 while "Prototype_{}".format(i) in nameList: i += 1 name = "Prototype_{}".format(i) _addStorage(self, name, expr, clear=False)
def on_action_Import_PMKS_server_triggered(self): """Load PMKS URL and turn it to expression.""" URL, ok = QInputDialog.getText(self, "PMKS URL input", "Please input link string:") if not ok: return if not URL: QMessageBox.warning(self, "Loading failed", "Your link is in an incorrect format.") return try: for s in URL.split('?')[-1].split('&'): if 'mech=' in s: expr = s.replace('mech=', '').split('|') break textList = [s for s in expr if s not in ('', " ", '\n')] expression = [] while textList: item = textList.pop(0).split(',')[:-1] for i, e in enumerate(reversed(item)): if e in ['R', 'P', 'RP']: t = -(i + 1) break links = item[:t] item = item[t:] expression.append("J[{}, P[{}], L[{}]]".format( "{}:{}".format(item[0], item[-1]) if item[0] != 'R' else 'R', ", ".join((item[1], item[2])), ", ".join(links))) expression = "M[{}]".format(", ".join(expression)) except: QMessageBox.warning(self, "Loading failed", "Your link is in an incorrect format.") else: self.parseExpression(expression)
def parse_expression(self, expr: str): """Parse expression.""" try: args_list = parse_params(expr) except LarkError: QMessageBox.warning( self, "Loading failed", f"Your expression is in an incorrect format." ) else: for args in args_list: links = args[0].split(',') link_names = { vlink.name for vlink in self.entities_link.data() } for link_name in links: # If link name not exist. if link_name not in link_names: self.add_link(link_name, 'Blue') row_count = self.entities_point.rowCount() self.command_stack.beginMacro(f"Add {{Point{row_count}}}") self.command_stack.push(AddTable(self.entities_point)) self.command_stack.push(EditPointTable( row_count, self.entities_point, self.entities_link, args )) self.command_stack.endMacro()
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)
def on_Edges_to_altas_clicked(self): """Turn the text files into a atlas image. This opreation will load all edges to list widget first. """ fileNames = self.inputFrom( "Edges data", ["Text File (*.txt)"], multiple=True ) if not fileNames: return read_data = [] for fileName in fileNames: with open(fileName, 'r') as f: read_data += f.read().split('\n') answer = [] for edges in read_data: try: answer.append(Graph(eval(edges))) except: QMessageBox.warning(self, "Wrong format", "Please check the edges text format." ) return if not answer: return self.answer = answer self.on_reload_atlas_clicked() check_status = self.save_edges_auto.isChecked() self.save_edges_auto.setChecked(False) self.on_save_atlas_clicked() self.save_edges_auto.setChecked(check_status)
def __match(self): """Fitting function.""" length = len(self.path) if length == 0: return index = list(range(length)) def poly_fit(x: List[float], y: List[float], d: int): """Return a 2D fitting equation.""" coefficient = np.polyfit(x, y, d) # Fit values and mean. y_hat = np.poly1d(coefficient)(x) y_bar = np.sum(y) / len(y) def func(t: float) -> float: """Return y(x) function.""" return sum(c * t**p for p, c in enumerate(reversed(coefficient))) yh_yb = y_hat - y_bar y_yb = y - y_bar return func, np.sum(yh_yb * yh_yb) / np.sum(y_yb * y_yb) x_func, x_accuracy = poly_fit(index, [x for x, y in self.path], 4) y_func, y_accuracy = poly_fit(index, [y for x, y in self.path], 4) QMessageBox.information( self, "Curve fitting", f"Accuracy:\nx: {x_accuracy:.02f}%\ny: {y_accuracy:.02f}%") m = self.match_num.value() self.r_path = [(x_func(i / m * length), y_func(i / m * length)) for i in range(m)] self.accept()
def saveReplyBox(self, title: str, file_name: str): """Show message when successfully saved.""" size = QFileInfo(file_name).size() print("Size: " + (f"{size / 1024 / 1024:.02f} MB" if size / 1024 // 1024 else f"{size / 1024:.02f} KB")) QMessageBox.information(self, f"Initial Saved: {title}", f"Successfully saved:\n{file_name}") print(f"Initial saved: [\"{file_name}\"]")
def __save_picture_clipboard(self): """Capture the canvas image to clipboard.""" QApplication.clipboard().setPixmap(self.main_canvas.grab()) QMessageBox.information( self, "Captured!", "Canvas widget picture is copy to clipboard." )
def __about(self): """Kmol editor about.""" QMessageBox.about(self, "About Kmol Editor", '\n'.join(INFO + ( '', "Author: " + __author__, "Email: " + __email__, __copyright__, "License: " + __license__, )))
def saveReplyBox(self, title: str, file_name: str): """Show message when successfully saved.""" size = QFileInfo(file_name).size() print("Size: {}".format("{} MB".format(round(size / 1024 / 1024, 2)) if size / 1024 // 1024 else "{} KB".format(round(size / 1024, 2)))) QMessageBox.information(self, title, "Successfully converted:\n{}".format(file_name)) print("Successful saved: [\"{}\"]".format(file_name))
def loadCommitID(self, id: int): """Check the id is correct.""" try: commit = self.history_commit.where(CommitModel.id == id).get() except CommitModel.DoesNotExist: QMessageBox.warning(self, "Warning", "Commit ID is not exist.") except AttributeError: QMessageBox.warning(self, "Warning", "Nothing submitted.") else: self.loadCommit(commit)
def add_collection(self, edges: Sequence[Tuple[int, int]]): """Add collection by in put edges.""" graph = Graph(edges) error = self.__is_valid_graph(graph) if error: QMessageBox.warning(self, "Add Collection Error", f"Error: {error}") return self.collections.append(graph) self.unsaveFunc() self.__reload_atlas()
def on_Expression_auto_clicked(self): """Auto configure the solutions.""" if not self.Driver_list.count(): QMessageBox.information(self, "Auto configure", "Please setting the driver joint(s).") return reply = QMessageBox.question( self, "Auto configure", "This function can detect the structure to configure the solutions.\n" + "The current settings will be cleared.") if reply == QMessageBox.Yes and self.on_Expression_clear_clicked(): self.auto_configure_expression()
def refresh_proj(self): """Re-parse the file node.""" node = self.tree_main.currentItem() if not node.text(1): QMessageBox.warning( self, "No path", "Can only refresh from valid path." ) parse(node, self.data) self.tree_main.setCurrentItem(node) self.text_editor.setText(self.data[int(node.text(2))])
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.addPoint(e[0], e[1])
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)
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)
def __import_pmks_url(self): """Load PMKS URL and turn it to expression.""" url, ok = QInputDialog.getText( self, "PMKS URL input", "Please input link string:" ) if not ok: return if not url: QMessageBox.warning( self, "Loading failed", "Your link is in an incorrect format." ) return try: for s in url.split('?')[-1].split('&'): if 'mech=' in s: expr = s.replace('mech=', '').split('|') break else: raise ValueError text_list = [s for s in expr if s not in ('', " ", '\n')] expr.clear() while text_list: item = text_list.pop(0).split(',')[:-1] for i, e in enumerate(reversed(item)): if e in {'R', 'P', 'RP'}: t = -(i + 1) break else: raise ValueError links = item[:t] item = item[t:] type_text = f"{item[0]}:{item[-1]}" if item[0] != 'R' else 'R' links_text = ", ".join(links) expr.append(f"J[{type_text}, P[{item[1]}, {item[2]}], L[{links_text}]]") expr = "M[" + ", ".join(expr) + "]" except (ValueError, IndexError): QMessageBox.warning( self, "Loading failed", "Your link is in an incorrect format." ) else: self.parse_expression(expr)
def on_clear_button_clicked(self): if self.profile_name.text() == "No setting": return reply = QMessageBox.question(self, "Clear setting", "Do you want to clear the setting?") if reply == QMessageBox.Yes: self.__clearSettings()