1
2
3
4
5
6
7
8
9
10 import re
11 import copy
12 import rocon_utilities
13 import rosgraph.impl.graph
14 import roslib
15
16
17
18
19
20
21 GATEWAY_GATEWAY_GRAPH = 'gateway_gateway'
22
23 GATEWAY_PULLED_GRAPH = 'gateway_pulled'
24
25 GATEWAY_FLIPPED_GRAPH = 'gateway_flipped'
26
27
29 if patternlist is None or len(patternlist) == 0:
30 return False
31 for pattern in patternlist:
32 if str(name).strip() == pattern:
33 return True
34 if re.match("^[a-zA-Z0-9_/]+$", pattern) is None:
35 if re.match(str(pattern), name.strip()) is not None:
36 return True
37 return False
38
39
41 - def __init__(self, incoming=None, outgoing=None):
42 self.incoming = incoming or []
43 self.outgoing = outgoing or []
44
45
47
50
51 - def _add_edge(self, edge, dotcode_factory, dotgraph, is_topic=False):
52 if is_topic:
53 dotcode_factory.add_edge_to_graph(dotgraph, edge.start, edge.end, label=edge.label, url='topic:%s' % edge.label)
54 else:
55 dotcode_factory.add_edge_to_graph(dotgraph, edge.start, edge.end, label=edge.label)
56
57 - def _add_node(self, node, rosgraphinst, dotcode_factory, dotgraph):
58 if node in rosgraphinst.bad_nodes:
59 bn = rosgraphinst.bad_nodes[node]
60 if bn.type == rosgraph.impl.graph.BadNode.DEAD:
61 dotcode_factory.add_node_to_graph(dotgraph,
62 nodename=node,
63 shape="doublecircle",
64 url=node,
65 color="red")
66 else:
67 dotcode_factory.add_node_to_graph(dotgraph,
68 nodename=node,
69 shape="doublecircle",
70 url=node,
71 color="orange")
72 else:
73 dotcode_factory.add_node_to_graph(dotgraph,
74 nodename=node,
75
76
77 shape='ellipse',
78 url=rocon_utilities.gateway_basename(node),
79
80 )
81
83 label = rosgraph.impl.graph.node_topic(node)
84 dotcode_factory.add_node_to_graph(dotgraph,
85 nodename=label,
86 nodelabel=label,
87 shape='box',
88 url="topic:%s" % label)
89
91 """
92 Determine the namespaces of the nodes being displayed
93 """
94 namespaces = []
95 if graph_mode == GATEWAY_GATEWAY_GRAPH:
96 nodes = graph.gateway_nodes
97 namespaces = list(set([roslib.names.namespace(n) for n in nodes]))
98
99 elif graph_mode == GATEWAY_PULLED_GRAPH or \
100 graph_mode == GATEWAY_FLIPPED_GRAPH:
101 gateway_nodes = graph.gateway_nodes
102 connection_nodes = graph.flipped_nodes
103 if gateway_nodes or connection_nodes:
104 namespaces = [roslib.names.namespace(n) for n in gateway_nodes]
105
106
107
108 namespaces.extend([roslib.names.namespace(n[1:]) for n in connection_nodes])
109
110 return list(set(namespaces))
111
113 nodenames = [str(n).strip() for n in nodes]
114
115 return [e for e in edges if e.start.strip() in nodenames and e.end.strip() in nodenames]
116
118 '''remove topic graphnodes without connected ROS nodes'''
119 removal_nodes = []
120 for n in connection_nodes:
121 keep = False
122 for e in edges:
123 if (e.start.strip() == str(n).strip() or e.end.strip() == str(n).strip()):
124 keep = True
125 break
126 if not keep:
127 removal_nodes.append(n)
128 for n in removal_nodes:
129 connection_nodes.remove(n)
130 return connection_nodes
131
133 '''splits a string after each comma, and treats tokens with leading dash as exclusions.
134 Adds .* as inclusion if no other inclusion option was given'''
135 includes = []
136 excludes = []
137 for name in ns_filter.split(','):
138 if name.strip().startswith('-'):
139 excludes.append(name.strip()[1:])
140 else:
141 includes.append(name.strip())
142 if includes == [] or includes == ['/'] or includes == ['']:
143 includes = ['.*']
144 return includes, excludes
145
147 '''returns a dict mapping node name to edge objects partitioned in incoming and outgoing edges'''
148 node_connections = {}
149 for edge in edges:
150 if not edge.start in node_connections:
151 node_connections[edge.start] = NodeConnections()
152 if not edge.end in node_connections:
153 node_connections[edge.end] = NodeConnections()
154 node_connections[edge.start].outgoing.append(edge)
155 node_connections[edge.end].incoming.append(edge)
156 return node_connections
157
158 - def _filter_leaves(self,
159 nodes_in,
160 edges_in,
161 node_connections,
162 hide_single_connection_topics,
163 hide_dead_end_topics):
164 '''
165 removes certain ending topic nodes and their edges from list of nodes and edges
166
167 @param hide_single_connection_topics: if true removes topics that are only published/subscribed by one node
168 @param hide_dead_end_topics: if true removes topics having only publishers
169 '''
170 if not hide_dead_end_topics and not hide_single_connection_topics:
171 return nodes_in, edges_in
172
173 nodes = copy.copy(nodes_in)
174 edges = copy.copy(edges_in)
175 removal_nodes = []
176 for n in nodes:
177 if n in node_connections:
178 node_edges = []
179 has_out_edges = False
180 node_edges.extend(node_connections[n].outgoing)
181 if len(node_connections[n].outgoing) > 0:
182 has_out_edges = True
183 node_edges.extend(node_connections[n].incoming)
184 if ((hide_single_connection_topics and len(node_edges) < 2) or
185 (hide_dead_end_topics and not has_out_edges)):
186 removal_nodes.append(n)
187 for e in node_edges:
188 if e in edges:
189 edges.remove(e)
190 for n in removal_nodes:
191 nodes.remove(n)
192 return nodes, edges
193
194 - def generate_dotgraph(self,
195 rosgraphinst,
196 ns_filter,
197 topic_filter,
198 graph_mode,
199 dotcode_factory,
200 show_all_advertisements=False,
201 hide_dead_end_topics=False,
202 cluster_namespaces_level=0,
203 orientation='LR',
204 rank='same',
205 ranksep=0.2,
206 rankdir='TB',
207 simplify=True,
208 ):
209 """
210 See generate_dotcode
211 """
212 includes, excludes = self._split_filter_string(ns_filter)
213 connection_includes, connection_excludes = self._split_filter_string(topic_filter)
214
215 gateway_nodes = []
216 connection_nodes = []
217
218 if graph_mode == GATEWAY_GATEWAY_GRAPH:
219 gateway_nodes = rosgraphinst.gateway_nodes
220 gateway_nodes = [n for n in gateway_nodes if matches_any(n, includes) and not matches_any(n, excludes)]
221 edges = rosgraphinst.gateway_edges
222 edges = [e for e in edges if matches_any(e.label, connection_includes) and not matches_any(e.label, connection_excludes)]
223
224 elif graph_mode == GATEWAY_PULLED_GRAPH or graph_mode == GATEWAY_FLIPPED_GRAPH:
225
226 if graph_mode == GATEWAY_PULLED_GRAPH:
227 edges = [e for e in rosgraphinst.pulled_edges]
228 connection_nodes = rosgraphinst.pulled_nodes
229 else:
230 edges = [e for e in rosgraphinst.flipped_edges]
231 connection_nodes = rosgraphinst.flipped_nodes
232 gateway_nodes = rosgraphinst.gateway_nodes
233
234 gateway_nodes = [n for n in gateway_nodes if matches_any(n, includes) and not matches_any(n, excludes)]
235 connection_nodes = [n for n in connection_nodes if matches_any(n, connection_includes) and not matches_any(n, connection_excludes)]
236
237 hide_unused_advertisements = not show_all_advertisements
238 if graph_mode == GATEWAY_PULLED_GRAPH and hide_unused_advertisements:
239 node_connections = self._get_node_edge_map(edges)
240 connection_nodes, edges = self._filter_leaves(connection_nodes,
241 edges,
242 node_connections,
243 hide_unused_advertisements,
244 hide_dead_end_topics)
245
246
247
248
249
250
251
252
253
254 edges = self._filter_orphaned_edges(edges, list(gateway_nodes) + list(connection_nodes))
255 connection_nodes = self._filter_orphaned_topics(connection_nodes, edges)
256
257
258
259 dotgraph = dotcode_factory.get_graph(rank=rank,
260 ranksep=ranksep,
261 simplify=simplify,
262 rankdir=orientation)
263
264 namespace_clusters = {}
265 for n in (connection_nodes or []):
266
267 if (cluster_namespaces_level > 0 and
268 str(n).count('/') > 1 and
269 len(str(n).split('/')[1]) > 0):
270 namespace = str(n).split('/')[1]
271 if namespace not in namespace_clusters:
272 namespace_clusters[namespace] = dotcode_factory.add_subgraph_to_graph(dotgraph, namespace, rank=rank, rankdir=orientation, simplify=simplify)
273 self._add_topic_node(n, dotcode_factory=dotcode_factory, dotgraph=namespace_clusters[namespace])
274 else:
275 self._add_topic_node(n, dotcode_factory=dotcode_factory, dotgraph=dotgraph)
276
277
278
279 if gateway_nodes is not None:
280 for n in gateway_nodes:
281 if (cluster_namespaces_level > 0 and
282 str(n).count('/') >= 1 and
283 len(str(n).split('/')[1]) > 0 and
284 str(n).split('/')[1] in namespace_clusters):
285 namespace = str(n).split('/')[1]
286 self._add_node(n, rosgraphinst=rosgraphinst, dotcode_factory=dotcode_factory, dotgraph=namespace_clusters[namespace])
287 else:
288 self._add_node(n, rosgraphinst=rosgraphinst, dotcode_factory=dotcode_factory, dotgraph=dotgraph)
289
290 for e in edges:
291 self._add_edge(e, dotcode_factory, dotgraph=dotgraph, is_topic=(graph_mode == GATEWAY_GATEWAY_GRAPH))
292
293 return dotgraph
294
295 - def generate_dotcode(self,
296 rosgraphinst,
297 ns_filter,
298 topic_filter,
299 graph_mode,
300 dotcode_factory,
301 show_all_advertisements=False,
302 hide_dead_end_topics=False,
303 cluster_namespaces_level=0,
304 orientation='LR',
305 rank='same',
306 ranksep=0.2,
307 rankdir='TB',
308 simplify=True,
309 ):
310 """
311 @param rosgraphinst: RosGraph instance
312 @param ns_filter: nodename filter
313 @type ns_filter: string
314 @param topic_filter: topicname filter
315 @type ns_filter: string
316 @param graph_mode str: GATEWAY_GATEWAY_GRAPH | GATEWAY_PULLED_GRAPH | GATEWAY_FLIPPED_GRAPH
317 @type graph_mode: str
318 @param orientation: rankdir value (see ORIENTATIONS dict)
319 @type dotcode_factory: object
320 @param dotcode_factory: abstract factory manipulating dot language objects
321 @param hide_single_connection_topics: if true remove topics with just one connection
322 @param hide_dead_end_topics: if true remove topics with publishers only
323 @param cluster_namespaces_level: if > 0 places box around members of same namespace (TODO: multiple namespace layers)
324 @return: dotcode generated from graph singleton
325 @rtype: str
326 """
327 dotgraph = self.generate_dotgraph(rosgraphinst=rosgraphinst,
328 ns_filter=ns_filter,
329 topic_filter=topic_filter,
330 graph_mode=graph_mode,
331 dotcode_factory=dotcode_factory,
332 show_all_advertisements=show_all_advertisements,
333 hide_dead_end_topics=hide_dead_end_topics,
334 cluster_namespaces_level=cluster_namespaces_level,
335 orientation=orientation,
336 rank=rank,
337 ranksep=ranksep,
338 rankdir=rankdir,
339 simplify=simplify,
340 )
341 dotcode = dotcode_factory.create_dot(dotgraph)
342 return dotcode
343