dot_to_qt.py
Go to the documentation of this file.
1 #
2 # License: Yujin
3 #
4 
7 
8 """
9 .. module:: dot_to_qt
10  :platform: Unix
11  :synopsis: Repackaging of the limiting ROS qt_dotgraph.dot_to_qt module.
12 
13 Oh my spaghettified magnificence,
14 Bless my noggin with a tickle from your noodly appendages!
15 
16 """
17 
18 
21 
22 import codecs
23 
24 # import pydot
25 import pygraphviz
26 import pyparsing
27 
28 # work around for https://bugs.launchpad.net/ubuntu/+source/pydot/+bug/1321135
29 pyparsing._noncomma = "".join([c for c in pyparsing.printables if c != ","])
30 
31 
32 from python_qt_binding.QtCore import QPointF, QRectF
33 from python_qt_binding.QtGui import QColor
34 
35 from .edge_item import EdgeItem
36 from .node_item import NodeItem
37 
38 
39 POINTS_PER_INCH = 72
40 
41 
44 
45 
46 # hack required by pydot
47 def get_unquoted(item, name):
48  value = item.get(name)
49  if value is None:
50  return None
51  try:
52  return value.strip('"\n"')
53  except AttributeError:
54  # not part of the string family
55  return value
56 
57 # approximately, for workarounds (TODO: get this from dotfile somehow)
58 LABEL_HEIGHT = 30
59 
60 
63 
64 
65 # Class generating Qt Elements from doctcode
67 
68  def __init__(self):
69  pass
70 
71  def getNodeItemForSubgraph(self, subgraph, highlight_level):
72  # let pydot imitate pygraphviz api
73  attr = {}
74  for name in subgraph.get_attributes().iterkeys():
75  value = get_unquoted(subgraph, name)
76  attr[name] = value
77  obj_dic = subgraph.__getattribute__("obj_dict")
78  for name in obj_dic:
79  if name not in ['nodes', 'attributes', 'parent_graph'] and obj_dic[name] is not None:
80  attr[name] = get_unquoted(obj_dic, name)
81  elif name == 'nodes':
82  for key in obj_dic['nodes']['graph'][0]['attributes']:
83  attr[key] = get_unquoted(obj_dic['nodes']['graph'][0]['attributes'], key)
84  subgraph.attr = attr
85 
86  bb = subgraph.attr.get('bb', None)
87  if bb is None:
88  # no bounding box
89  return None
90  bb = bb.strip('"').split(',')
91  if len(bb) < 4:
92  # bounding box is empty
93  return None
94  bounding_box = QRectF(0, 0, float(bb[2]) - float(bb[0]), float(bb[3]) - float(bb[1]))
95  if 'lp' in subgraph.attr:
96  label_pos = subgraph.attr['lp'].strip('"').split(',')
97  else:
98  label_pos = (float(bb[0]) + (float(bb[2]) - float(bb[0])) / 2, float(bb[1]) + (float(bb[3]) - float(bb[1])) - LABEL_HEIGHT / 2)
99  bounding_box.moveCenter(QPointF(float(bb[0]) + (float(bb[2]) - float(bb[0])) / 2, -float(bb[1]) - (float(bb[3]) - float(bb[1])) / 2))
100  name = subgraph.attr.get('label', '')
101  color = QColor(subgraph.attr['color']) if 'color' in subgraph.attr else None
102  subgraph_nodeitem = NodeItem(highlight_level,
103  bounding_box,
104  label=name,
105  shape='box',
106  color=color,
107  label_pos=QPointF(float(label_pos[0]), -float(label_pos[1])))
108  bounding_box = QRectF(bounding_box)
109  # With clusters we have the problem that mouse hovers cannot
110  # decide whether to be over the cluster or a subnode. Using
111  # just the "title area" solves this. TODO: Maybe using a
112  # border region would be even better (multiple RectF)
113  bounding_box.setHeight(LABEL_HEIGHT)
114  subgraph_nodeitem.set_hovershape(bounding_box)
115  return subgraph_nodeitem
116 
117  def getNodeItemForNode(self, node, highlight_level):
118  """
119  returns a pyqt NodeItem object, or None in case of error or invisible style
120  """
121  # let pydot imitate pygraphviz api
122 # attr = {}
123 # for name in node.get_attributes().iterkeys():
124 # value = get_unquoted(node, name)
125 # attr[name] = value
126 # obj_dic = node.__getattribute__("obj_dict")
127 # for name in obj_dic:
128 # if name not in ['attributes', 'parent_graph'] and obj_dic[name] is not None:
129 # attr[name] = get_unquoted(obj_dic, name)
130 # node.attr = attr
131 
132  if 'style' in node.attr:
133  if node.attr['style'] == 'invis':
134  return None
135 
136  color = QColor(node.attr['color']) if 'color' in node.attr else None
137  name = None
138  if 'label' in node.attr:
139  name = node.attr['label']
140  elif 'name' in node.attr:
141  name = node.attr['name']
142  else:
143  print("Error, no label defined for node with attr: %s" % node.attr)
144  return None
145  if name is None:
146  # happens on Lucid pygraphviz version
147  print("Error, label is None for node %s, pygraphviz version may be too old." % node)
148  else:
149  # python2
150  # name = name.decode('string_escape')
151  # python3
152  name = codecs.escape_decode(name)[0].decode('utf-8')
153 
154  # decrease rect by one so that edges do not reach inside
155  bb_width = len(name) / 5
156  if 'width' in node.attr:
157  bb_width = node.attr['width']
158  bb_height = 1.0
159  if 'width' in node.attr:
160  bb_height = node.attr['height']
161  bounding_box = QRectF(0, 0, POINTS_PER_INCH * float(bb_width) - 1.0, POINTS_PER_INCH * float(bb_height) - 1.0)
162  pos = (0, 0)
163  if 'pos' in node.attr:
164  pos = node.attr['pos'].split(',')
165  bounding_box.moveCenter(QPointF(float(pos[0]), -float(pos[1])))
166 
167  node_item = NodeItem(highlight_level=highlight_level,
168  bounding_box=bounding_box,
169  label=name,
170  shape=node.attr.get('shape', 'ellipse'),
171  color=color,
172  tooltip=node.attr.get('tooltip', None)
173  # parent=None,
174  # label_pos=None
175  )
176  # node_item.setToolTip(self._generate_tool_tip(node.attr.get('URL', None)))
177  return node_item
178 
179  def addEdgeItem(self, edge, nodes, edges, highlight_level, same_label_siblings=False):
180  """
181  adds EdgeItem by data in edge to edges
182  :param same_label_siblings: if true, edges with same label will be considered siblings (collective highlighting)
183  """
184  # let pydot imitate pygraphviz api
185 # attr = {}
186 # for name in edge.get_attributes().iterkeys():
187 # value = get_unquoted(edge, name)
188 # attr[name] = value
189 # edge.attr = attr
190 
191  if 'style' in edge.attr:
192  if edge.attr['style'] == 'invis':
193  return
194  style = edge.attr.get('style', None)
195 
196  label = edge.attr.get('label', None)
197  label_pos = edge.attr.get('lp', None)
198  label_center = None
199  if label_pos is not None:
200  label_pos = label_pos.split(',')
201  label_center = QPointF(float(label_pos[0]), -float(label_pos[1]))
202 
203  # try pydot, fallback for pygraphviz
204  source_node = edge.get_source() if hasattr(edge, 'get_source') else edge[0]
205  destination_node = edge.get_destination() if hasattr(edge, 'get_destination') else edge[1]
206 
207  # create edge with from-node and to-node
208  edge_pos = "0,0"
209  if 'pos' in edge.attr:
210  edge_pos = edge.attr['pos']
211  if label is not None:
212  label = label.decode('string_escape')
213 
214  color = None
215  if 'colorR' in edge.attr and 'colorG' in edge.attr and 'colorB' in edge.attr:
216  r = edge.attr['colorR']
217  g = edge.attr['colorG']
218  b = edge.attr['colorB']
219  color = QColor(float(r), float(g), float(b))
220 
221  edge_item = EdgeItem(highlight_level=highlight_level,
222  spline=edge_pos,
223  label_center=label_center,
224  label=label,
225  from_node=nodes[source_node],
226  to_node=nodes[destination_node],
227  penwidth=int(edge.attr['penwidth']),
228  edge_color=color,
229  style=style)
230 
231  if same_label_siblings:
232  if label is None:
233  # for sibling detection
234  label = "%s_%s" % (source_node, destination_node)
235  # symmetrically add all sibling edges with same label
236  if label in edges:
237  for sibling in edges[label]:
238  edge_item.add_sibling_edge(sibling)
239  sibling.add_sibling_edge(edge_item)
240 
241  if label not in edges:
242  edges[label] = []
243  edges[label].append(edge_item)
244 
245  def dotcode_to_qt_items(self, dotcode, highlight_level, same_label_siblings=False):
246  """
247  takes dotcode, runs layout, and creates qt items based on the dot layout.
248  returns two dicts, one mapping node names to Node_Item, one mapping edge names to lists of Edge_Item
249  :param same_label_siblings: if true, edges with same label will be considered siblings (collective highlighting)
250  """
251  # layout graph
252  if dotcode is None:
253  return {}, {}
254 
255  # pydot - this function is very buggy and expensive, quickly > 1s!
256  # graph = pydot.graph_from_dot_data(dotcode.encode("ascii", "ignore"))
257  # let pydot imitate pygraphviz api
258  # graph.nodes_iter = graph.get_node_list
259  # graph.edges_iter = graph.get_edge_list
260  # graph.subgraphs_iter = graph.get_subgraph_list
261 
262  # pygraphviz
263  graph = pygraphviz.AGraph(
264  string=dotcode, # .encode("ascii", "ignore"),
265  strict=False,
266  directed=True
267  )
268  graph.layout(prog='dot')
269 
270  nodes = {}
271  for subgraph in graph.subgraphs_iter():
272  subgraph_nodeitem = self.getNodeItemForSubgraph(subgraph, highlight_level)
273  # skip subgraphs with empty bounding boxes
274  if subgraph_nodeitem is None:
275  continue
276 
277  subgraph.nodes_iter = subgraph.get_node_list
278  nodes[subgraph.get_name()] = subgraph_nodeitem
279  for node in subgraph.nodes_iter():
280  # hack required by pydot
281  if node.get_name() in ('graph', 'node', 'empty'):
282  continue
283  nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level)
284  for node in graph.nodes_iter():
285  # hack required by pydot
286  if node.get_name() in ('graph', 'node', 'empty'):
287  continue
288  nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level)
289 
290  edges = {}
291 
292  for subgraph in graph.subgraphs_iter():
293  subgraph.edges_iter = subgraph.get_edge_list
294  for edge in subgraph.edges_iter():
295  self.addEdgeItem(edge, nodes, edges,
296  highlight_level=highlight_level,
297  same_label_siblings=same_label_siblings)
298 
299  for edge in graph.edges_iter():
300  self.addEdgeItem(edge, nodes, edges,
301  highlight_level=highlight_level,
302  same_label_siblings=same_label_siblings)
303 
304  return nodes, edges
305 
306  def graph_to_qt_items(self, graph, highlight_level, same_label_siblings=False):
307  """
308  takes a pydot/pygraphviz graph and creates qt items based on the dot layout.
309  returns two dicts, one mapping node names to Node_Item, one mapping edge names to lists of Edge_Item
310  :param same_label_siblings: if true, edges with same label will be considered siblings (collective highlighting)
311  """
312  if graph is None:
313  return {}, {}
314 
315  # if pydot, let pydot imitate pygraphviz api
316  # graph.nodes_iter = graph.get_node_list
317  # graph.edges_iter = graph.get_edge_list
318  # graph.subgraphs_iter = graph.get_subgraph_list
319 
320  nodes = {}
321  for subgraph in graph.subgraphs_iter():
322  subgraph_nodeitem = self.getNodeItemForSubgraph(subgraph, highlight_level)
323  # skip subgraphs with empty bounding boxes
324  if subgraph_nodeitem is None:
325  continue
326 
327  subgraph.nodes_iter = subgraph.get_node_list
328  nodes[subgraph.get_name()] = subgraph_nodeitem
329  for node in subgraph.nodes_iter():
330  # hack required by pydot
331  if node.get_name() in ('graph', 'node', 'empty'):
332  continue
333  nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level)
334  for node in graph.nodes_iter():
335  # hack required by pydot
336  if node.get_name() in ('graph', 'node', 'empty'):
337  continue
338  nodes[node.get_name()] = self.getNodeItemForNode(node, highlight_level)
339 
340  edges = {}
341 
342  for subgraph in graph.subgraphs_iter():
343  subgraph.edges_iter = subgraph.get_edge_list
344  for edge in subgraph.edges_iter():
345  self.addEdgeItem(edge, nodes, edges,
346  highlight_level=highlight_level,
347  same_label_siblings=same_label_siblings)
348 
349  for edge in graph.edges_iter():
350  self.addEdgeItem(edge, nodes, edges,
351  highlight_level=highlight_level,
352  same_label_siblings=same_label_siblings)
353 
354  return nodes, edges
rqt_py_trees.qt_dotgraph.dot_to_qt.DotToQtGenerator.addEdgeItem
def addEdgeItem(self, edge, nodes, edges, highlight_level, same_label_siblings=False)
Definition: dot_to_qt.py:179
rqt_py_trees.qt_dotgraph.dot_to_qt.DotToQtGenerator.graph_to_qt_items
def graph_to_qt_items(self, graph, highlight_level, same_label_siblings=False)
Definition: dot_to_qt.py:306
rqt_py_trees.qt_dotgraph.dot_to_qt.DotToQtGenerator.getNodeItemForNode
def getNodeItemForNode(self, node, highlight_level)
Definition: dot_to_qt.py:117
rqt_py_trees.qt_dotgraph.dot_to_qt.DotToQtGenerator.dotcode_to_qt_items
def dotcode_to_qt_items(self, dotcode, highlight_level, same_label_siblings=False)
Definition: dot_to_qt.py:245
rqt_py_trees.qt_dotgraph.dot_to_qt.DotToQtGenerator.__init__
def __init__(self)
Definition: dot_to_qt.py:68
rqt_py_trees.qt_dotgraph.dot_to_qt.DotToQtGenerator
Classes.
Definition: dot_to_qt.py:66
rqt_py_trees.qt_dotgraph.edge_item.EdgeItem
Classes.
Definition: edge_item.py:36
rqt_py_trees.qt_dotgraph.node_item.NodeItem
Classes.
Definition: node_item.py:36
rqt_py_trees.qt_dotgraph.dot_to_qt.DotToQtGenerator.getNodeItemForSubgraph
def getNodeItemForSubgraph(self, subgraph, highlight_level)
Definition: dot_to_qt.py:71
rqt_py_trees.qt_dotgraph.dot_to_qt.get_unquoted
def get_unquoted(item, name)
Support.
Definition: dot_to_qt.py:47


rqt_py_trees
Author(s): Thibault Kruse, Michal Staniaszek, Daniel Stonier, Naveed Usmani
autogenerated on Wed Mar 2 2022 00:59:03