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 _reversed_graph(graph: Graph) -> Graph: """Edges will become nodes.""" graph_ = Graph([]) nodes = dict(edges_view(graph)) for i, (l1, l2) in nodes.items(): for j, edge in nodes.items(): if i == j: continue if (l1 in edge) or (l2 in edge): graph_.add_edge(i, j) return graph_
def __init__( self, get_solutions: Callable[[], str], parent: QWidget ): """Input parameters and attributes. + A function should return a tuple of function expression. format: ("PLAP[P1,a0,L0,P2](P3)", "PLLP[P1,a0,L0,P2](P3)", ...) + Origin graph + Customize points: Dict[str, int] + Multiple joints: Dict[int, int] + Positions: Dict[int, Tuple[float, float]] + Joint status: Dict[int, bool] + Name dict: Dict['P0', 'A'] """ super(PreviewCanvas, self).__init__(parent) self.showSolutions = True self.get_solutions = get_solutions self.G = Graph([]) self.cus: Dict[str, int] = {} self.same: Dict[int, int] = {} self.pos: Dict[int, Tuple[float, float]] = {} self.status = {} # Additional attributes. self.grounded = -1 self.Driver = -1 self.Target = -1 self.clear()
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 clear(self): """Clear the attributes.""" self.G = Graph([]) self.cus.clear() self.same.clear() self.pos.clear() self.status.clear() self.grounded = -1 self.Driver = -1 self.Target = -1 self.update()
def __from_profile(self): """Show up the dialog to load structure data.""" dlg = CollectionsDialog(self.collections, self.get_configure, self.unsave_func, self.configure_canvas.monochrome, self) dlg.show() if not dlg.exec(): dlg.deleteLater() return # Add customize joints params = dlg.params graph = Graph(params['Graph']) expression: str = params['Expression'] pos_list = parse_pos(expression) cus: Dict[int, int] = params['cus'] same: Dict[int, int] = params['same'] for node, ref in sorted(same.items()): pos_list.insert(node, pos_list[ref]) pos: Dict[int, _Coord] = dict(enumerate(pos_list)) if not self.set_graph(graph, pos): dlg.deleteLater() return self.profile_name.setText(dlg.name) dlg.deleteLater() del dlg self.configure_canvas.cus = cus self.configure_canvas.same = same # Grounded setting placement: Set[int] = set(params['Placement']) links: List[Set[int]] = [set() for _ in range(len(graph.nodes))] for joint, link in edges_view(graph): for node in link: links[node].add(joint) for row, link in enumerate(links): if placement == link - set(same): self.__set_grounded(row) break # Driver, Target input_list: List[Tuple[int, int]] = params['input'] self.driver_list.addItems(f"(P{b}, P{d})" for b, d in input_list) self.configure_canvas.set_driver(input_list) _set_warning(self.driver_label, self.driver_list.count() == 0) target_list: Dict[int, Sequence[_Coord]] = params['Target'] self.configure_canvas.set_target(target_list) self.target_list.addItems(f"P{n}" for n in target_list) _set_warning(self.target_label, self.target_list.count() == 0) # Expression self.expr_show.setText(params['Expression'])
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 __is_valid_graph(self, g: Graph) -> str: """Test graph and return True if it is valid.""" if not g.edges: return "is an empty graph" if not g.is_connected(): return "is not a close chain" if not is_planar(g): return "is not a planar chain" if g.has_cut_link(): return "has cut link" try: external_loop_layout(g, True) except ValueError as error: return str(error) for h in self.collections: if g.is_isomorphic(h): return f"is isomorphic with: {h.edges}" return ""
def getGraph(self) -> List[Tuple[int, int]]: """Return edges data for NetworkX graph class. + VLinks will become graph nodes. """ vpoints = self.EntitiesPoint.dataTuple() vlinks = self.EntitiesLink.dataTuple() graph = Graph([]) # links name for RP joint. k = len(vlinks) used_point = set() # Link names will change to index number. for i, vlink in enumerate(vlinks): for p in vlink.points: if p in used_point: continue for m, vlink_ in enumerate(vlinks): if not ((i != m) and (p in vlink_.points)): continue if vpoints[p].type != VPoint.RP: graph.add_edge(i, m) continue graph.add_edge(i, k) graph.add_edge(k, m) k += 1 used_point.add(p) return [edge for n, edge in edges_view(graph)]
def _four_bar_loops(graph: Graph) -> Iterator[Tuple[int, int, int, int]]: """A generator to find out the four bar loops.""" result = set() vertexes = {v: k for k, v in edges_view(graph)} def loop_set(n: int, n1: int, n2: int, n3: int) -> Tuple[int, int, int, int]: """Return a loop set.""" return ( vertexes[tuple(sorted((n, n1)))], vertexes[tuple(sorted((n1, n2)))], vertexes[tuple(sorted((n2, n3)))], vertexes[tuple(sorted((n, n3)))], ) for node in graph.nodes: if node in result: continue nb1s = graph.neighbors(node) # node not in nb1s for nb1 in nb1s: if nb1 in result: continue nb2s = graph.neighbors(nb1) # node can not in nb2s for nb2 in nb2s: if (nb2 == node) or (nb2 in result): continue nb3s = graph.neighbors(nb2) # node can not in nb3s for nb3 in nb3s: if (nb3 in (node, nb1)) or (nb3 in result): continue if node in graph.neighbors(nb3): loop = [node, nb1, nb2, nb3] result.update(loop) yield loop_set(*loop)
def __load_data_base(self): """Show up the dialog to load structure data.""" dlg = CollectionsDialog(self.collections, self.getCollection, self) dlg.show() if not dlg.exec_(): return self.profile_name = dlg.name() params = dlg.params() # Add customize joints. graph = Graph(params['Graph']) self.setGraph(graph, params['pos']) self.PreviewWindow.cus = params['cus'] self.PreviewWindow.same = params['same'] # Grounded setting. drivers = set(params['Driver']) followers = set(params['Follower']) for row, link in enumerate(graph.nodes): points = {f'P{n}' for n, edge in edges_view(graph) if link in edge} if (drivers | followers) <= points: self.__set_ground(row) break # Driver, Follower, Target for expr in params['Expression'].split(';'): if str_before(expr, '[') != 'PLAP': continue base = str_between(expr, '[', ']').split(',')[0] self.__find_follower_to_remove(base) rotator = str_between(expr, '(', ')') self.driver_list.addItem(f"({base}, {rotator})") _set_warning(self.driver_label, not self.driver_list.count()) self.target_list.addItems(list(params['Target'])) _set_warning(self.target_label, not self.target_list.count() > 0) # Constraints self.constraint_list.addItems( [", ".join(c) for c in params['constraints']]) # Expression if params['Expression']: for expr in params['Expression'].split(';'): func = str_before(expr, '[') target = str_between(expr, '(', ')') params = str_between(expr, '[', ']').split(',') params.insert(0, func) params.append(target) self.__add_solution(*params) self.PreviewWindow.setStatus(target, True) _set_warning(self.expression_list_label, not self.PreviewWindow.isAllLock())
def test_topologic(self): """Testing 'topologic' libraries. + 'topo' function. + 'Graph' class. """ from core.libs import Graph G = Graph([(0, 1), (0, 4), (1, 5), (2, 3), (2, 4), (3, 5), (4, 5)]) H = Graph([(0, 2), (0, 4), (1, 3), (1, 4), (2, 5), (3, 5), (4, 5)]) I = Graph([(0, 1), (0, 2), (1, 4), (2, 5), (3, 4), (3, 5), (4, 5)]) self.assertTrue(G.is_isomorphic(H)) self.assertFalse(G.is_isomorphic(I)) answer, time = topo([4, 2], degenerate=True) self.assertEqual(len(answer), 2)
def from_profile(self, params: Dict[str, Any]): """Simple load by dict object.""" # Add customize joints. graph = Graph(params['Graph']) self.setGraph(graph, params['pos']) self.cus = params['cus'] self.same = params['same'] # Grounded setting. driver = set(params['Driver']) follower = set(params['Follower']) for row, link in enumerate(graph.nodes): points = set(f'P{n}' for n, edge in edges_view(graph) if link in edge) if (driver | follower) <= points: self.setGrounded(row) break # Expression if params['Expression']: for expr in params['Expression'].split(';'): self.setStatus(io.str_between(expr, '(', ')'), True) self.update()
def __init__(self, parent: QWidget): """Input parameters and attributes. + Origin graph + Customize points: Dict[str, int] + Multiple joints: Dict[int, int] + Positions: Dict[int, Tuple[float, float]] + Joint status: Dict[int, bool] + Name dict: Dict['P0', 'A'] """ super(PreviewCanvas, self).__init__(parent) self.G = Graph([]) self.cus: Dict[int, int] = {} self.same: Dict[int, int] = {} self.pos: Dict[int, _Coord] = {} self.status = {} # Additional attributes. self.grounded = -1 self.driver: Set[int] = set() self.target: Set[int] = set() self.clear()
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())
def from_profile(self, params: Dict[str, Any]): """Simple load by dict object.""" # Customize points and multiple joints graph = Graph(params['Graph']) expression: str = params['Expression'] pos_list = parse_pos(expression) self.cus: Dict[int, int] = params['cus'] self.same: Dict[int, int] = params['same'] for node, ref in sorted(self.same.items()): pos_list.insert(node, pos_list[ref]) pos: Dict[int, _Coord] = dict(enumerate(pos_list)) self.set_graph(graph, pos) # Grounded setting placement: Set[int] = set(params['Placement']) links: List[Set[int]] = [set() for _ in range(len(graph.nodes))] for joint, link in edges_view(graph): for node in link: links[node].add(joint) for row, link in enumerate(links): if placement == link - set(self.same): self.set_grounded(row) break # Driver setting input_list: List[Tuple[int, int]] = params['input'] self.driver.clear() self.driver.update(pair[0] for pair in input_list) # Target setting target: Dict[int, Sequence[_Coord]] = params['Target'] self.target.clear() self.target.update(target) self.update()
def to_graph(graph: Graph, width: int, engine: Union[str, Dict[int, Tuple[float, float]]], node_mode: bool = False, except_node: int = None) -> QIcon: """Draw a generalized chain graph.""" pos: Pos = engine_picker(graph, engine, node_mode) width_bound = -float('inf') for x, y in pos.values(): if abs(x) > width_bound: width_bound = x if abs(y) > width_bound: width_bound = y width_bound *= 2.2 image = QImage(QSize(int(width_bound), int(width_bound)), QImage.Format_ARGB32_Premultiplied) image.fill(Qt.transparent) painter = QPainter(image) painter.translate(image.width() / 2, image.height() / 2) pen = QPen() r = width_bound / 50 pen.setWidth(int(r)) painter.setPen(pen) # Draw edges. if node_mode: for l1, l2 in graph.edges: if except_node in {l1, l2}: pen.setColor(color_qt('Gray')) else: pen.setColor(color_qt('Black')) painter.setPen(pen) painter.drawLine(QPointF(pos[l1][0], -pos[l1][1]), QPointF(pos[l2][0], -pos[l2][1])) else: painter.setBrush(QBrush(QColor(226, 219, 190, 150))) for link in graph.nodes: if link == except_node: pen.setColor(color_qt('Gray')) else: pen.setColor(color_qt('Black')) painter.setPen(pen) painter.drawPolygon(*convex_hull([(pos[n][0], -pos[n][1]) for n, edge in edges_view(graph) if link in edge], as_qpoint=True)) # Draw nodes. for k, (x, y) in pos.items(): if node_mode: color = color_num(len(list(graph.neighbors(k))) - 1) if k == except_node: color.setAlpha(150) else: if except_node in dict(edges_view(graph))[k]: color = color_qt('Green') else: color = color_qt('Blue') pen.setColor(color) painter.setPen(pen) painter.setBrush(QBrush(color)) painter.drawEllipse(QPointF(x, -y), r, r) painter.end() icon = QIcon(QPixmap.fromImage(image).scaledToWidth(width)) return icon
def to_graph( g: Graph, width: int, engine: Union[str, Pos], node_mode: bool, show_label: bool, monochrome: bool, *, except_node: Optional[int] = None ) -> QIcon: """Draw a generalized chain graph.""" pos: Pos = engine_picker(g, engine, node_mode) if not pos: pixmap = QPixmap(width, width) pixmap.fill(Qt.transparent) return QIcon(pixmap) width_bound = -float('inf') for x, y in pos.values(): if abs(x) > width_bound: width_bound = x if abs(y) > width_bound: width_bound = y width_bound *= 2.5 image = QImage( QSize(int(width_bound), int(width_bound)), QImage.Format_ARGB32_Premultiplied ) image.fill(Qt.transparent) painter = QPainter(image) painter.translate(image.width() / 2, image.height() / 2) pen = QPen() r = int(width_bound / 50) pen.setWidth(r) painter.setPen(pen) _font.setPixelSize(r * 6) painter.setFont(_font) # Draw edges. if node_mode: for l1, l2 in g.edges: if except_node in {l1, l2}: pen.setColor(Qt.gray) else: pen.setColor(Qt.black) painter.setPen(pen) painter.drawLine( QPointF(pos[l1][0], -pos[l1][1]), QPointF(pos[l2][0], -pos[l2][1]) ) else: if monochrome: color = QColor(Qt.darkGray) else: color = QColor(226, 219, 190) color.setAlpha(150) painter.setBrush(QBrush(color)) for link in g.nodes: if link == except_node: pen.setColor(Qt.gray) else: pen.setColor(Qt.black) painter.setPen(pen) painter.drawPolygon(*convex_hull([ (pos[n][0], -pos[n][1]) for n, edge in edges_view(g) if link in edge ], as_qpoint=True)) # Draw nodes. for k, (x, y) in pos.items(): if node_mode: color = color_num(len(list(g.neighbors(k))) - 1) if k == except_node: color.setAlpha(150) else: if monochrome: color = Qt.black elif except_node in dict(edges_view(g))[k]: color = color_qt('Green') else: color = color_qt('Blue') pen.setColor(color) painter.setPen(pen) painter.setBrush(QBrush(color)) point = QPointF(x, -y) painter.drawEllipse(point, r, r) if show_label: pen.setColor(Qt.darkMagenta) painter.setPen(pen) painter.drawText(point, str(k)) painter.end() return QIcon(QPixmap.fromImage(image).scaledToWidth(width))
def get_graph(self) -> Tuple[ Graph, List[int], List[Tuple[int, int]], Dict[int, Tuple[float, float]], Dict[int, int], Dict[int, int] ]: """Generalization Algorithm Return edges data, grounded list, variable list and multiple joints. VLinks will become graph nodes. """ vpoints = list(self.entities_point.data()) vlinks = list(self.entities_link.data()) link_names = [vlink.name for vlink in vlinks] input_pair = set() for b, d, _ in self.inputs_widget.input_pairs(): input_pair.update({b, d}) # links name for RP joint. k = len(vlinks) graph = Graph([]) grounded_list = [] pos = {} same = {} used_point = set() mapping = {} # Link names will change to index number. for i, vlink in enumerate(vlinks): for p in vlink.points: if p in used_point: continue vpoint = vpoints[p] base_num = len(graph.edges) mapping[p] = base_num pos[base_num] = (vpoint.x, vpoint.y) for link_name in vpoint.links: if vlink.name == link_name: continue m = link_names.index(link_name) grounded = 'ground' in {vlink.name, link_name} ref_num = len(graph.edges) if ref_num != base_num: pos[ref_num] = (vpoint.x, vpoint.y) if vpoint.type == VJoint.RP: graph.add_edge(i, k) if grounded: grounded_list.append(len(graph.edges)) graph.add_edge(k, m) k += 1 else: if ref_num != base_num: same[ref_num] = base_num graph.add_edge(i, m) if grounded and ref_num not in same: grounded_list.append(ref_num) used_point.add(p) counter = len(graph.edges) cus = {} for vpoint in vpoints: if len(vpoint.links) == 1: cus[counter] = link_names.index(vpoint.links[0]) counter += 1 return ( graph, grounded_list, [(mapping[b], mapping[d]) for b, d, _ in self.inputs_widget.input_pairs()], pos, cus, same, )