class NumberAndTypeSynthesis(QWidget, Ui_Form): """Number and type synthesis widget.""" def __init__(self, parent): super(NumberAndTypeSynthesis, self).__init__(parent) self.setupUi(self) self.outputTo = parent.outputTo self.saveReplyBox = parent.saveReplyBox self.inputFrom = parent.inputFrom self.splitter.setStretchFactor(0, 2) self.splitter.setStretchFactor(1, 15) self.answer = [] self.save_edges_auto_label.setStatusTip(self.save_edges_auto.statusTip()) self.NL_input.valueChanged.connect(self.adjust_NJ_NL_dof) self.NJ_input.valueChanged.connect(self.adjust_NJ_NL_dof) self.graph_engine.addItems(EngineList) self.graph_engine.setCurrentIndex(2) self.graph_link_as_node.clicked.connect(self.on_reload_atlas_clicked) self.graph_engine.currentIndexChanged.connect( self.on_reload_atlas_clicked ) self.Topologic_result.customContextMenuRequested.connect( self.Topologic_result_context_menu ) """Context menu + Add to collections + Copy edges + Copy image """ self.popMenu_topo = QMenu(self) self.add_collection = QAction( QIcon(QPixmap(":/icons/collections.png")), "Add to collections", self ) self.copy_edges = QAction("Copy edges", self) self.copy_image = QAction("Copy image", self) self.popMenu_topo.addActions([ self.add_collection, self.copy_edges, self.copy_image ]) self.jointDataFunc = parent.Entities_Point.data self.linkDataFunc = parent.Entities_Link.data self.clear() def clear(self): """Clear all sub-widgets.""" self.answer.clear() self.Expression_edges.clear() self.Expression_number.clear() self.Topologic_result.clear() self.time_label.setText("") self.NL_input.setValue(0) self.NJ_input.setValue(0) self.NL_input_old_value = 0 self.NJ_input_old_value = 0 self.DOF.setValue(1) @pyqtSlot() def on_ReloadMechanism_clicked(self): """Reload button: Auto-combine the mechanism from the workbook.""" jointData = self.jointDataFunc() linkData = self.linkDataFunc() if jointData and linkData: self.Expression_edges.setText(str(v_to_graph(jointData, linkData))) else: self.Expression_edges.setText("") keep_dof_checked = self.keep_dof.isChecked() self.keep_dof.setChecked(False) self.NL_input.setValue( sum(len(vlink.points)>1 for vlink in linkData)+ sum( len(vpoint.links)-1 for vpoint in jointData if (vpoint.type == 2) and (len(vpoint.links) > 1) ) ) self.NJ_input.setValue(sum( (len(vpoint.links)-1 + int(vpoint.type == 2)) for vpoint in jointData if len(vpoint.links)>1 )) self.keep_dof.setChecked(keep_dof_checked) def adjust_NJ_NL_dof(self): """Update NJ and NL values. If user don't want to keep the DOF: Change the DOF then exit. """ if not self.keep_dof.isChecked(): self.DOF.setValue( 3 * (self.NL_input.value() - 1) - 2 * self.NJ_input.value() ) return """Prepare the input value. + N2: Get the user's adjusted value. + NL_func: Get the another value of parameters (N1) by degrees of freedom formula. + is_above: Is value increase or decrease? """ if self.sender() == self.NJ_input: N2 = self.NJ_input.value() NL_func = lambda: float(((self.DOF.value() + 2*N2) / 3) + 1) is_above = N2 > self.NJ_input_old_value else: N2 = self.NL_input.value() NL_func = lambda: float((3*(N2-1) - self.DOF.value()) / 2) is_above = N2 > self.NL_input_old_value N1 = NL_func() while not N1.is_integer(): N2 += 1 if is_above else -1 N1 = NL_func() if (N1 == 0) or (N2 == 0): break """Return the result values. + Value of widgets. + Setting old value record. """ if self.sender() == self.NL_input: self.NJ_input.setValue(N1) self.NL_input.setValue(N2) self.NJ_input_old_value = N1 self.NL_input_old_value = N2 else: self.NJ_input.setValue(N2) self.NL_input.setValue(N1) self.NJ_input_old_value = N2 self.NL_input_old_value = N1 @pyqtSlot() def on_Combine_number_clicked(self): """Show number of links with different number of joints.""" self.Expression_number.clear() NS_result = NumberSynthesis(self.NL_input.value(), self.NJ_input.value()) if type(NS_result) == str: item = QListWidgetItem(NS_result) item.links = None self.Expression_number.addItem(item) else: for result in NS_result: item = QListWidgetItem(", ".join( "NL{} = {}".format(i+2, result[i]) for i in range(len(result)) )) item.links = result self.Expression_number.addItem(item) self.Expression_number.setCurrentRow(0) @pyqtSlot() def on_Combine_type_clicked(self): """Type synthesis. If there has no data of number synthesis, execute number synthesis first. """ row = self.Expression_number.currentRow() if not row>-1: self.on_Combine_number_clicked() row = self.Expression_number.currentRow() if self.Expression_number.currentItem() is None: return answer = self.combineType(row) if answer: self.answer = answer self.on_reload_atlas_clicked() @pyqtSlot() def on_Combine_type_all_clicked(self): """Type synthesis - find all. If the data of number synthesis has multiple results, execute type synthesis one by one. """ if not self.Expression_number.currentRow()>-1: self.on_Combine_number_clicked() if self.Expression_number.currentItem().links is None: return answers = [] break_point = False for row in range(self.Expression_number.count()): answer = self.combineType(row) if answer: answers += answer else: break_point = True break if not answers: return if break_point: reply = QMessageBox.question(self, "Type synthesis - abort", "Do you want to keep the results?" ) if reply != QMessageBox.Yes: return self.answer = answers self.on_reload_atlas_clicked() def combineType(self, row: int): """Combine and show progress dialog.""" item = self.Expression_number.item(row) progdlg = QProgressDialog( "Analysis of the topology...", "Cancel", 0, 100, self ) progdlg.setWindowTitle("Type synthesis - ({})".format(item.text())) progdlg.setMinimumSize(QSize(500, 120)) progdlg.setModal(True) progdlg.show() #Call in every loop. def stopFunc(): QCoreApplication.processEvents() progdlg.setValue(progdlg.value() + 1) return progdlg.wasCanceled() def setjobFunc(job, maximum): progdlg.setLabelText(job) progdlg.setValue(0) progdlg.setMaximum(maximum+1) answer, time = topo( item.links, not self.graph_degenerate.isChecked(), setjobFunc, stopFunc ) self.time_label.setText("{}[min] {:.2f}[s]".format( int(time // 60), time % 60 )) progdlg.setValue(progdlg.maximum()) if answer: return [Graph(G.edges) for G in answer] @pyqtSlot() @pyqtSlot(str) def on_reload_atlas_clicked(self, p0=None): """Reload the atlas. Regardless there has any old data.""" self.engine = self.graph_engine.currentText().split(" - ")[1] self.Topologic_result.clear() if self.answer: progdlg = QProgressDialog( "Drawing atlas...", "Cancel", 0, len(self.answer), self ) progdlg.setWindowTitle("Type synthesis") progdlg.resize(400, progdlg.height()) progdlg.setModal(True) progdlg.show() for i, G in enumerate(self.answer): QCoreApplication.processEvents() if progdlg.wasCanceled(): return if self.drawAtlas(i, G): progdlg.setValue(i+1) else: break progdlg.setValue(progdlg.maximum()) def drawAtlas(self, i: int, G: Graph) -> bool: """Draw atlas and return True if done.""" item = QListWidgetItem("No. {}".format(i + 1)) try: item.setIcon(graph( G, self.Topologic_result.iconSize().width(), self.engine, self.graph_link_as_node.isChecked() )) except EngineError as e: QMessageBox.warning(self, str(e), "Please install and make sure Graphviz is working." ) return False else: item.setToolTip(str(G.edges)) self.Topologic_result.addItem(item) return True def atlas_image(self, row: int =None) -> QImage: """Capture a result item icon to image.""" w = self.Topologic_result if row is None: item = w.currentItem() else: item = w.item(row) return item.icon().pixmap(w.iconSize()).toImage() @pyqtSlot(QPoint) def Topologic_result_context_menu(self, point): """Context menu for the type synthesis results.""" index = self.Topologic_result.currentIndex().row() self.add_collection.setEnabled(index>-1) self.copy_edges.setEnabled(index>-1) self.copy_image.setEnabled(index>-1) action = self.popMenu_topo.exec_(self.Topologic_result.mapToGlobal(point)) if not action: return clipboard = QApplication.clipboard() if action==self.add_collection: self.addCollection(self.answer[index].edges) elif action==self.copy_edges: clipboard.setText(str(self.answer[index].edges)) elif action==self.copy_image: #Turn the transparent background to white. image1 = self.atlas_image() image2 = QImage(image1.size(), image1.format()) image2.fill(QColor(Qt.white).rgb()) painter = QPainter(image2) painter.drawImage(QPointF(0, 0), image1) painter.end() pixmap = QPixmap() pixmap.convertFromImage(image2) clipboard.setPixmap(pixmap) @pyqtSlot() def on_Expression_copy_clicked(self): """Copy expression button.""" string = self.Expression_edges.text() if string: QApplication.clipboard().setText(string) self.Expression_edges.selectAll() @pyqtSlot() def on_Expression_add_collection_clicked(self): """Add this expression to collections widget.""" string = self.Expression_edges.text() if string: self.addCollection(eval(string)) @pyqtSlot() def on_save_atlas_clicked(self): """Saving all the atlas to image file. We should turn transparent background to white first. Then using QImage class to merge into one image. """ fileName = "" lateral = 0 if self.save_edges_auto.isChecked(): lateral, ok = QInputDialog.getInt(self, "Atlas", "The number of lateral:", 5, 1, 10 ) if not ok: return fileName = self.outputTo("Atlas image", Qt_images) if fileName: reply = QMessageBox.question(self, "Type synthesis", "Do you want to Re-synthesis?", (QMessageBox.Yes | QMessageBox.YesToAll | QMessageBox.Cancel), QMessageBox.YesToAll ) if reply == QMessageBox.Yes: self.on_Combine_type_clicked() elif reply == QMessageBox.YesToAll: self.on_Combine_type_all_clicked() count = self.Topologic_result.count() if not count: return if not lateral: lateral, ok = QInputDialog.getInt(self, "Atlas", "The number of lateral:", 5, 1, 10 ) if not ok: return if not fileName: fileName = self.outputTo("Atlas image", Qt_images) if not fileName: return width = self.Topologic_result.iconSize().width() image_main = QImage( QSize( lateral * width if count>lateral else count * width, ((count // lateral) + bool(count % lateral)) * width ), self.atlas_image(0).format() ) image_main.fill(QColor(Qt.white).rgb()) painter = QPainter(image_main) for row in range(count): image = self.atlas_image(row) painter.drawImage(QPointF( row % lateral * width, row // lateral * width ), image) painter.end() pixmap = QPixmap() pixmap.convertFromImage(image_main) pixmap.save(fileName, format=QFileInfo(fileName).suffix()) self.saveReplyBox("Atlas", fileName) @pyqtSlot() def on_save_edges_clicked(self): """Saving all the atlas to text file.""" fileName = "" if self.save_edges_auto.isChecked(): fileName = self.outputTo( "Atlas edges expression", ["Text file (*.txt)"] ) if not fileName: return reply = QMessageBox.question(self, "Type synthesis", "Do you want to Re-synthesis?", (QMessageBox.Yes | QMessageBox.YesToAll | QMessageBox.Cancel), QMessageBox.YesToAll ) if reply == QMessageBox.Yes: self.on_Combine_type_clicked() elif reply == QMessageBox.YesToAll: self.on_Combine_type_all_clicked() count = self.Topologic_result.count() if not count: return if not fileName: fileName = self.outputTo( "Atlas edges expression", ["Text file (*.txt)"] ) if not fileName: return with open(fileName, 'w') as f: f.write('\n'.join(str(G.edges) for G in self.answer)) self.saveReplyBox("edges expression", fileName) @pyqtSlot() 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)
class StructureSynthesis(QWidget, Ui_Form): """Number and type synthesis widget. Calculate the combinations of mechanism family and show the atlas. """ def __init__(self, parent: 'mw.MainWindow'): """Reference names: + IO functions from main window. + Table data from PMKS expression. + Graph data function from main window. """ super(StructureSynthesis, self).__init__(parent) self.setupUi(self) self.save_edges_auto_label.setStatusTip( self.save_edges_auto.statusTip()) # Function references self.outputTo = parent.outputTo self.saveReplyBox = parent.saveReplyBox self.inputFrom = parent.inputFrom self.jointDataFunc = parent.EntitiesPoint.dataTuple self.linkDataFunc = parent.EntitiesLink.dataTuple self.getGraph = parent.getGraph # Splitters self.splitter.setStretchFactor(0, 2) self.splitter.setStretchFactor(1, 15) # Answer list. self.answer: List[Graph] = [] # Signals self.NL_input.valueChanged.connect(self.__adjust_structure_data) self.NJ_input.valueChanged.connect(self.__adjust_structure_data) self.graph_engine.addItems(engines) self.structure_list.customContextMenuRequested.connect( self.__topologic_result_context_menu) """Context menu + Add to collections + Copy edges + Copy image """ self.pop_menu_topo = QMenu(self) self.add_collection = QAction( QIcon(QPixmap(":/icons/collections.png")), "Add to collections", self) self.copy_edges = QAction("Copy edges", self) self.copy_image = QAction("Copy image", self) self.pop_menu_topo.addActions( [self.add_collection, self.copy_edges, self.copy_image]) self.NL_input_old_value = 0 self.NJ_input_old_value = 0 self.clear() def clear(self): """Clear all sub-widgets.""" self.answer.clear() self.edges_text.clear() self.l_a_list.clear() self.__clear_structure_list() self.NL_input.setValue(0) self.NJ_input.setValue(0) self.NL_input_old_value = 0 self.NJ_input_old_value = 0 self.DOF.setValue(1) @pyqtSlot(name='on_structure_list_clear_button_clicked') def __clear_structure_list(self): """Clear the structure list.""" self.structure_list.clear() self.time_label.setText("") @pyqtSlot(name='on_from_mechanism_button_clicked') def __from_mechanism(self): """Reload button: Auto-combine the mechanism from the workbook.""" joint_data = self.jointDataFunc() link_data = self.linkDataFunc() if joint_data and link_data: graph = Graph(self.getGraph()) self.edges_text.setText(str(graph.edges)) else: graph = Graph([]) self.edges_text.setText("") keep_dof_checked = self.keep_dof.isChecked() self.keep_dof.setChecked(False) self.NL_input.setValue( sum(len(vlink.points) > 1 for vlink in link_data) + sum( len(vpoint.links) - 2 for vpoint in joint_data if (vpoint.type == VPoint.RP) and (len(vpoint.links) > 1))) self.NJ_input.setValue( sum((len(vpoint.links) - 1 + int(vpoint.type == VPoint.RP)) for vpoint in joint_data if (len(vpoint.links) > 1))) self.keep_dof.setChecked(keep_dof_checked) # Auto synthesis. if not graph.edges: return self.l_a_list.setCurrentRow( compare_assortment(tuple(l_a(graph)), self.__l_a_synthesis())) self.c_l_a_list.setCurrentRow( compare_assortment(tuple(c_l_a(graph)), self.__c_l_a_synthesis())) def __adjust_structure_data(self): """Update NJ and NL values. If user don't want to keep the DOF: Change the DOF then exit. """ if not self.keep_dof.isChecked(): self.DOF.setValue(3 * (self.NL_input.value() - 1) - 2 * self.NJ_input.value()) return """Prepare the input value. + N2: Get the user's adjusted value. + NL_func: Get the another value of parameters (N1) by degrees of freedom formula. + is_above: Is value increase or decrease? """ if self.sender() == self.NJ_input: n2 = self.NJ_input.value() def nl_func() -> float: return ((self.DOF.value() + 2 * n2) / 3) + 1 is_above = n2 > self.NJ_input_old_value else: n2 = self.NL_input.value() def nl_func() -> float: return (3 * (n2 - 1) - self.DOF.value()) / 2 is_above = n2 > self.NL_input_old_value n1 = nl_func() while not n1.is_integer(): n2 += 1 if is_above else -1 n1 = nl_func() if (n1 == 0) or (n2 == 0): break """Return the result values. + Value of widgets. + Setting old value record. """ if self.sender() == self.NL_input: self.NJ_input.setValue(n1) self.NL_input.setValue(n2) self.NJ_input_old_value = n1 self.NL_input_old_value = n2 else: self.NJ_input.setValue(n2) self.NL_input.setValue(n1) self.NJ_input_old_value = n2 self.NL_input_old_value = n1 @pyqtSlot(name='on_number_synthesis_button_clicked') def __l_a_synthesis(self) -> List[Tuple[int, ...]]: """Synthesis of link assortments.""" self.l_a_list.clear() self.c_l_a_list.clear() try: results = number_synthesis(self.NL_input.value(), self.NJ_input.value()) except Exception as e: item = QListWidgetItem(str(e)) self.l_a_list.addItem(item) return [] else: for result in results: self.l_a_list.addItem( QListWidgetItem(", ".join(f"NL{i + 2} = {result[i]}" for i in range(len(result))))) self.l_a_list.setCurrentRow(0) return results @pyqtSlot(int, name='on_l_a_list_currentRowChanged') def __c_l_a_synthesis(self, index: int = 0) -> List[Tuple[int, ...]]: """Synthesis of contracted link assortments.""" self.c_l_a_list.clear() item = self.l_a_list.item(index) if item is None: return [] results = contracted_link(_link_assortments(item.text())) for c_j in results: self.c_l_a_list.addItem( QListWidgetItem(", ".join(f"Nc{i + 1} = {c_j[i]}" for i in range(len(c_j))))) self.c_l_a_list.setCurrentRow(0) return results def __set_time_count(self, t: float, count: int): """Set time and count digit to label.""" self.time_label.setText(f"{t:.04f} s ({count})") @pyqtSlot(name='on_structure_synthesis_button_clicked') def __structure_synthesis(self): """Structural synthesis - find by contracted links.""" self.__clear_structure_list() row = self.l_a_list.currentRow() if row == -1: self.__l_a_synthesis() self.__c_l_a_synthesis() item_l_a: QListWidgetItem = self.l_a_list.currentItem() item_c_l_a: QListWidgetItem = self.c_l_a_list.currentItem() try: job_l_a = _link_assortments(item_l_a.text()) job_c_l_a = _link_assortments(item_c_l_a.text()) except ValueError: return self.__structural_combine([(job_l_a, job_c_l_a)], 1) @pyqtSlot(name='on_structure_synthesis_links_button_clicked') def __structure_synthesis_links(self): """Structural synthesis - find by links.""" self.__clear_structure_list() row = self.l_a_list.currentRow() if row == -1: self.__l_a_synthesis() self.__c_l_a_synthesis() item_l_a: QListWidgetItem = self.l_a_list.currentItem() try: job_l_a = _link_assortments(item_l_a.text()) except ValueError: return jobs = contracted_link(job_l_a) def jobs_iterator( _l_a: Sequence[int], _jobs: Sequence[Sequence[int]] ) -> Iterator[Tuple[Sequence[int], Sequence[int]]]: for _c_l_a in _jobs: yield _l_a, _c_l_a self.__structural_combine(jobs_iterator(job_l_a, jobs), len(jobs)) @pyqtSlot(name='on_structure_synthesis_all_button_clicked') def __structure_synthesis_all(self): """Structural synthesis - find all.""" self.__clear_structure_list() if self.l_a_list.currentRow() == -1: self.__l_a_synthesis() item: QListWidgetItem = self.c_l_a_list.currentItem() try: _link_assortments(item.text()) except ValueError: return job_count = 0 jobs = [] for row in range(self.l_a_list.count()): item: QListWidgetItem = self.l_a_list.item(row) job_l_a = _link_assortments(item.text()) job_c_l_as = contracted_link(job_l_a) job_count += len(job_c_l_as) jobs.append((job_l_a, job_c_l_as)) def jobs_iterator( _jobs: Sequence[Tuple[Sequence[int], Sequence[Sequence[int]]]] ) -> Iterator[Tuple[Sequence[int], Sequence[int]]]: for _l_a, _c_l_as in _jobs: for _c_l_a in _c_l_as: yield _l_a, _c_l_a self.__structural_combine(jobs_iterator(jobs), job_count) def __structural_combine(self, jobs: Iterable[Tuple[Sequence[int], Sequence[int]]], job_count: int): """Structural combine by iterator.""" dlg = SynthesisProgressDialog("Structural Synthesis", "", job_count, self) dlg.show() answers = [] break_point = False t0 = 0. c0 = 0 for job_l_a, job_c_l_a in jobs: answer, t1 = topo(job_l_a, job_c_l_a, self.graph_degenerate.currentIndex(), dlg.stop_func) dlg.next() if answer is not None: answers.extend(answer) t0 += t1 c0 += len(answer) else: break_point = True break if not answers: return if break_point: reply = QMessageBox.question(self, "Type synthesis - abort", "Do you want to keep the results?") if reply != QMessageBox.Yes: return # Save the answer list. self.answer = answers self.__set_time_count(t0, c0) self.__reload_atlas() @pyqtSlot(name='on_graph_link_as_node_clicked') @pyqtSlot(name='on_reload_atlas_clicked') @pyqtSlot(int, name='on_graph_engine_currentIndexChanged') def __reload_atlas(self, *_: int): """Reload the atlas.""" scroll_bar: QScrollBar = self.structure_list.verticalScrollBar() scroll_pos = scroll_bar.sliderPosition() self.structure_list.clear() if not self.answer: return dlg = SynthesisProgressDialog("Type synthesis", "Drawing atlas...", len(self.answer), self) dlg.show() for i, G in enumerate(self.answer): QCoreApplication.processEvents() if dlg.wasCanceled(): return if self.__draw_atlas(i, G): dlg.setValue(i + 1) else: break dlg.setValue(dlg.maximum()) scroll_bar.setSliderPosition(scroll_pos) def __draw_atlas(self, i: int, g: Graph) -> bool: """Draw atlas and return True if done.""" item = QListWidgetItem(f"No. {i + 1}") item.setIcon( to_graph(g, self.structure_list.iconSize().width(), self.graph_engine.currentText(), self.graph_link_as_node.isChecked())) item.setToolTip(f"Edge Set: {list(g.edges)}\n" f"Link Assortments: {l_a(g)}\n" f"Contracted Link Assortments: {c_l_a(g)}") self.structure_list.addItem(item) return True def __atlas_image(self, row: int = None) -> QImage: """Capture a result item icon to image.""" w = self.structure_list if row is None: item = w.currentItem() else: item = w.item(row) return item.icon().pixmap(w.iconSize()).toImage() @pyqtSlot(QPoint) def __topologic_result_context_menu(self, point): """Context menu for the type synthesis results.""" index = self.structure_list.currentIndex().row() self.add_collection.setEnabled(index > -1) self.copy_edges.setEnabled(index > -1) self.copy_image.setEnabled(index > -1) action = self.pop_menu_topo.exec_( self.structure_list.mapToGlobal(point)) if not action: return clipboard = QApplication.clipboard() if action == self.add_collection: self.addCollection(self.answer[index].edges) elif action == self.copy_edges: clipboard.setText(str(self.answer[index].edges)) elif action == self.copy_image: # Turn the transparent background to white. image1 = self.__atlas_image() image2 = QImage(image1.size(), image1.format()) image2.fill(QColor(Qt.white).rgb()) painter = QPainter(image2) painter.drawImage(QPointF(0, 0), image1) painter.end() pixmap = QPixmap() pixmap.convertFromImage(image2) clipboard.setPixmap(pixmap) @pyqtSlot(name='on_expr_copy_clicked') def __copy_expr(self): """Copy expression button.""" string = self.edges_text.text() if string: QApplication.clipboard().setText(string) self.edges_text.selectAll() @pyqtSlot(name='on_expr_add_collection_clicked') def __add_collection(self): """Add this expression to collections widget.""" string = self.edges_text.text() if string: self.addCollection(eval(string)) @pyqtSlot(name='on_save_atlas_clicked') def __save_atlas(self): """Saving all the atlas to image file. We should turn transparent background to white first. Then using QImage class to merge into one image. """ file_name = "" lateral = 0 if self.save_edges_auto.isChecked(): lateral, ok = QInputDialog.getInt(self, "Atlas", "The number of lateral:", 5, 1, 10) if not ok: return file_name = self.outputTo("Atlas image", qt_image_format) if file_name: reply = QMessageBox.question( self, "Type synthesis", "Do you want to Re-synthesis?", (QMessageBox.Yes | QMessageBox.YesToAll | QMessageBox.Cancel), QMessageBox.Yes) if reply == QMessageBox.Yes: self.__structure_synthesis() elif reply == QMessageBox.YesToAll: self.__structure_synthesis_all() count = self.structure_list.count() if not count: return if not lateral: lateral, ok = QInputDialog.getInt(self, "Atlas", "The number of lateral:", 5, 1, 10) if not ok: return if not file_name: file_name = self.outputTo("Atlas image", qt_image_format) if not file_name: return width = self.structure_list.iconSize().width() image_main = QImage( QSize(lateral * width if count > lateral else count * width, ((count // lateral) + bool(count % lateral)) * width), self.__atlas_image(0).format()) image_main.fill(QColor(Qt.white).rgb()) painter = QPainter(image_main) for row in range(count): image = self.__atlas_image(row) painter.drawImage( QPointF(row % lateral * width, row // lateral * width), image) painter.end() pixmap = QPixmap() pixmap.convertFromImage(image_main) pixmap.save(file_name, format=QFileInfo(file_name).suffix()) self.saveReplyBox("Atlas", file_name) @pyqtSlot(name='on_save_edges_clicked') def __save_edges(self): """Saving all the atlas to text file.""" file_name = "" if self.save_edges_auto.isChecked(): file_name = self.outputTo("Atlas edges expression", ["Text file (*.txt)"]) if not file_name: return reply = QMessageBox.question( self, "Type synthesis", "Do you want to Re-synthesis?", (QMessageBox.Yes | QMessageBox.YesToAll | QMessageBox.Cancel), QMessageBox.Yes) if reply == QMessageBox.Yes: self.__structure_synthesis() elif reply == QMessageBox.YesToAll: self.__structure_synthesis_all() count = self.structure_list.count() if not count: return if not file_name: file_name = self.outputTo("Atlas edges expression", ["Text file (*.txt)"]) if not file_name: return with open(file_name, 'w') as f: f.write('\n'.join(str(G.edges) for G in self.answer)) self.saveReplyBox("edges expression", file_name) @pyqtSlot(name='on_edges2atlas_button_clicked') def __edges2atlas(self): """Turn the text files into a atlas image. This operation will load all edges to list widget first. """ file_names = self.inputFrom("Edges data", ["Text file (*.txt)"], multiple=True) if not file_names: return read_data = [] for file_name in file_names: with open(file_name) as f: for line in f: read_data.append(line) answer = [] for edges in read_data: try: g = Graph(eval(edges)) except (SyntaxError, TypeError): QMessageBox.warning(self, "Wrong format", "Please check text format.") else: answer.append(g) if not answer: QMessageBox.information(self, "No data", "The graph data is empty.") return self.answer = answer self.__reload_atlas() self.save_edges_auto.setChecked(False) self.__save_atlas() self.save_edges_auto.setChecked(self.save_edges_auto.isChecked())