tf_tree.py
Go to the documentation of this file.
1 # Software License Agreement (BSD License)
2 #
3 # Copyright (c) 2011, Willow Garage, Inc.
4 # All rights reserved.
5 #
6 # Redistribution and use in source and binary forms, with or without
7 # modification, are permitted provided that the following conditions
8 # are met:
9 #
10 # * Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
12 # * Redistributions in binary form must reproduce the above
13 # copyright notice, this list of conditions and the following
14 # disclaimer in the documentation and/or other materials provided
15 # with the distribution.
16 # * Neither the name of Willow Garage, Inc. nor the names of its
17 # contributors may be used to endorse or promote products derived
18 # from this software without specific prior written permission.
19 #
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 # POSSIBILITY OF SUCH DAMAGE.
32 
33 from __future__ import division
34 import os
35 
36 import rospy
37 import rospkg
38 
39 from tf2_msgs.srv import FrameGraph
40 import tf2_ros
41 
42 from python_qt_binding import loadUi
43 from python_qt_binding.QtCore import QFile, QIODevice, QObject, Qt, Signal
44 from python_qt_binding.QtGui import QIcon, QImage, QPainter
45 from python_qt_binding.QtWidgets import QFileDialog, QGraphicsScene, QWidget
46 from python_qt_binding.QtSvg import QSvgGenerator
47 from qt_dotgraph.pydotfactory import PydotFactory
48 # from qt_dotgraph.pygraphvizfactory import PygraphvizFactory
49 from qt_dotgraph.dot_to_qt import DotToQtGenerator
50 from rqt_graph.interactive_graphics_view import InteractiveGraphicsView
51 
52 from .dotcode_tf import RosTfTreeDotcodeGenerator
53 
54 
55 class RosTfTree(QObject):
56 
57  _deferred_fit_in_view = Signal()
58 
59  def __init__(self, context):
60  super(RosTfTree, self).__init__(context)
61  self.initialized = False
62 
63  self.setObjectName('RosTfTree')
64 
65  self._current_dotcode = None
66 
67  self._widget = QWidget()
68 
69  # factory builds generic dotcode items
71  # self.dotcode_factory = PygraphvizFactory()
72  # generator builds rosgraph
73  self.dotcode_generator = RosTfTreeDotcodeGenerator()
76 
77  # dot_to_qt transforms into Qt elements using dot layout
79 
80  rp = rospkg.RosPack()
81  ui_file = os.path.join(rp.get_path('rqt_tf_tree'), 'resource', 'RosTfTree.ui')
82  loadUi(ui_file, self._widget, {'InteractiveGraphicsView': InteractiveGraphicsView})
83  self._widget.setObjectName('RosTfTreeUi')
84  if context.serial_number() > 1:
85  self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number()))
86 
87  self._scene = QGraphicsScene()
88  self._scene.setBackgroundBrush(Qt.white)
89  self._widget.graphics_view.setScene(self._scene)
90 
91  self._widget.clear_buffer_push_button.setIcon(QIcon.fromTheme('edit-delete'))
92  self._widget.clear_buffer_push_button.pressed.connect(self._clear_buffer)
93 
94  self._widget.refresh_graph_push_button.setIcon(QIcon.fromTheme('view-refresh'))
95  self._widget.refresh_graph_push_button.pressed.connect(self._update_tf_graph)
96 
97  self._widget.highlight_connections_check_box.toggled.connect(self._redraw_graph_view)
98  self._widget.auto_fit_graph_check_box.toggled.connect(self._redraw_graph_view)
99  self._widget.fit_in_view_push_button.setIcon(QIcon.fromTheme('zoom-original'))
100  self._widget.fit_in_view_push_button.pressed.connect(self._fit_in_view)
101 
102  self._widget.load_dot_push_button.setIcon(QIcon.fromTheme('document-open'))
103  self._widget.load_dot_push_button.pressed.connect(self._load_dot)
104  self._widget.save_dot_push_button.setIcon(QIcon.fromTheme('document-save-as'))
105  self._widget.save_dot_push_button.pressed.connect(self._save_dot)
106  self._widget.save_as_svg_push_button.setIcon(QIcon.fromTheme('document-save-as'))
107  self._widget.save_as_svg_push_button.pressed.connect(self._save_svg)
108  self._widget.save_as_image_push_button.setIcon(QIcon.fromTheme('image-x-generic'))
109  self._widget.save_as_image_push_button.pressed.connect(self._save_image)
110 
111  self._deferred_fit_in_view.connect(self._fit_in_view,
112  Qt.QueuedConnection)
113  self._deferred_fit_in_view.emit()
114 
115  context.add_widget(self._widget)
116 
117  self._force_refresh = False
118 
119  def save_settings(self, plugin_settings, instance_settings):
120  instance_settings.set_value('auto_fit_graph_check_box_state',
121  self._widget.auto_fit_graph_check_box.isChecked())
122  instance_settings.set_value('highlight_connections_check_box_state',
123  self._widget.highlight_connections_check_box.isChecked())
124 
125  def restore_settings(self, plugin_settings, instance_settings):
126  self._widget.auto_fit_graph_check_box.setChecked(
127  instance_settings.value('auto_fit_graph_check_box_state', True) in [True, 'true'])
128  self._widget.highlight_connections_check_box.setChecked(
129  instance_settings.value('highlight_connections_check_box_state', True) in [True, 'true'])
130  self.initialized = True
131  self._refresh_tf_graph()
132 
133  def _clear_buffer(self):
134  self.tf2_buffer_.clear()
135 
136  def _update_tf_graph(self):
137  self._force_refresh = True
138  self._refresh_tf_graph()
139 
140  def _refresh_tf_graph(self):
141  if not self.initialized:
142  return
144 
145  def _generate_dotcode(self):
146  force_refresh = self._force_refresh
147  self._force_refresh = False
148  rospy.wait_for_service('~tf2_frames')
149  tf2_frame_srv = rospy.ServiceProxy('~tf2_frames', FrameGraph)
150  return self.dotcode_generator.generate_dotcode(dotcode_factory=self.dotcode_factory,
151  tf2_frame_srv=tf2_frame_srv,
152  force_refresh=force_refresh)
153 
154  def _update_graph_view(self, dotcode):
155  if dotcode == self._current_dotcode:
156  return
157  self._current_dotcode = dotcode
158  self._redraw_graph_view()
159 
160  def _generate_tool_tip(self, url):
161  return url
162 
164  self._scene.clear()
165 
166  if self._widget.highlight_connections_check_box.isChecked():
167  highlight_level = 3
168  else:
169  highlight_level = 1
170 
171  (nodes, edges) = self.dot_to_qt.dotcode_to_qt_items(self._current_dotcode,
172  highlight_level)
173 
174  for node_item in nodes.values():
175  self._scene.addItem(node_item)
176  for edge_items in edges.values():
177  for edge_item in edge_items:
178  edge_item.add_to_scene(self._scene)
179 
180  self._scene.setSceneRect(self._scene.itemsBoundingRect())
181  if self._widget.auto_fit_graph_check_box.isChecked():
182  self._fit_in_view()
183 
184  def _load_dot(self, file_name=None):
185  if file_name is None:
186  file_name, _ = QFileDialog.getOpenFileName(
187  self._widget,
188  self.tr('Open graph from file'),
189  None,
190  self.tr('DOT graph (*.dot)'))
191  if file_name is None or file_name == '':
192  return
193 
194  try:
195  fhandle = open(file_name, 'rb')
196  dotcode = fhandle.read()
197  fhandle.close()
198  except IOError:
199  return
200 
201  self._update_graph_view(dotcode)
202 
203  def _fit_in_view(self):
204  self._widget.graphics_view.fitInView(self._scene.itemsBoundingRect(),
205  Qt.KeepAspectRatio)
206 
207  def _save_dot(self):
208  file_name, _ = QFileDialog.getSaveFileName(self._widget,
209  self.tr('Save as DOT'),
210  'frames.dot',
211  self.tr('DOT graph (*.dot)'))
212  if file_name is None or file_name == '':
213  return
214 
215  file = QFile(file_name)
216  if not file.open(QIODevice.WriteOnly | QIODevice.Text):
217  return
218 
219  file.write(self._current_dotcode)
220  file.close()
221 
222  def _save_svg(self):
223  file_name, _ = QFileDialog.getSaveFileName(
224  self._widget,
225  self.tr('Save as SVG'),
226  'frames.svg',
227  self.tr('Scalable Vector Graphic (*.svg)'))
228  if file_name is None or file_name == '':
229  return
230 
231  generator = QSvgGenerator()
232  generator.setFileName(file_name)
233  generator.setSize((self._scene.sceneRect().size() * 2.0).toSize())
234 
235  painter = QPainter(generator)
236  painter.setRenderHint(QPainter.Antialiasing)
237  self._scene.render(painter)
238  painter.end()
239 
240  def _save_image(self):
241  file_name, _ = QFileDialog.getSaveFileName(
242  self._widget,
243  self.tr('Save as image'),
244  'frames.png',
245  self.tr('Image (*.bmp *.jpg *.png *.tiff)'))
246  if file_name is None or file_name == '':
247  return
248 
249  img = QImage((self._scene.sceneRect().size() * 2.0).toSize(),
250  QImage.Format_ARGB32_Premultiplied)
251  painter = QPainter(img)
252  painter.setRenderHint(QPainter.Antialiasing)
253  self._scene.render(painter)
254  painter.end()
255  img.save(file_name)
def _generate_tool_tip(self, url)
Definition: tf_tree.py:160
def _load_dot(self, file_name=None)
Definition: tf_tree.py:184
def _update_graph_view(self, dotcode)
Definition: tf_tree.py:154
def restore_settings(self, plugin_settings, instance_settings)
Definition: tf_tree.py:125
def __init__(self, context)
Definition: tf_tree.py:59
def save_settings(self, plugin_settings, instance_settings)
Definition: tf_tree.py:119


rqt_tf_tree
Author(s): Thibault Kruse
autogenerated on Mon Feb 4 2019 03:56:43