00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033 from __future__ import division
00034 import os
00035
00036
00037 import rospy
00038 import rospkg
00039
00040 from tf2_msgs.srv import FrameGraph
00041 import tf2_ros
00042
00043 from python_qt_binding import loadUi
00044 from python_qt_binding.QtCore import QFile, QIODevice, QObject, Qt, Signal
00045 from python_qt_binding.QtGui import QFileDialog, QGraphicsScene, QIcon, QImage, QPainter, QWidget
00046 from python_qt_binding.QtSvg import QSvgGenerator
00047 from qt_dotgraph.pydotfactory import PydotFactory
00048
00049 from qt_dotgraph.dot_to_qt import DotToQtGenerator
00050 from rqt_graph.interactive_graphics_view import InteractiveGraphicsView
00051
00052 from .dotcode_tf import RosTfTreeDotcodeGenerator
00053
00054
00055 class RosTfTree(QObject):
00056
00057 _deferred_fit_in_view = Signal()
00058
00059 def __init__(self, context):
00060 super(RosTfTree, self).__init__(context)
00061 self.initialized = False
00062
00063 self.setObjectName('RosTfTree')
00064
00065 self._current_dotcode = None
00066
00067 self._widget = QWidget()
00068
00069
00070 self.dotcode_factory = PydotFactory()
00071
00072
00073 self.dotcode_generator = RosTfTreeDotcodeGenerator()
00074 self.tf2_buffer_ = tf2_ros.Buffer()
00075 self.tf2_listener_ = tf2_ros.TransformListener(self.tf2_buffer_)
00076
00077
00078 self.dot_to_qt = DotToQtGenerator()
00079
00080 rp = rospkg.RosPack()
00081 ui_file = os.path.join(rp.get_path('rqt_tf_tree'), 'resource', 'RosTfTree.ui')
00082 loadUi(ui_file, self._widget, {'InteractiveGraphicsView': InteractiveGraphicsView})
00083 self._widget.setObjectName('RosTfTreeUi')
00084 if context.serial_number() > 1:
00085 self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number()))
00086
00087 self._scene = QGraphicsScene()
00088 self._scene.setBackgroundBrush(Qt.white)
00089 self._widget.graphics_view.setScene(self._scene)
00090
00091 self._widget.refresh_graph_push_button.setIcon(QIcon.fromTheme('view-refresh'))
00092 self._widget.refresh_graph_push_button.pressed.connect(self._update_tf_graph)
00093
00094 self._widget.highlight_connections_check_box.toggled.connect(self._redraw_graph_view)
00095 self._widget.auto_fit_graph_check_box.toggled.connect(self._redraw_graph_view)
00096 self._widget.fit_in_view_push_button.setIcon(QIcon.fromTheme('zoom-original'))
00097 self._widget.fit_in_view_push_button.pressed.connect(self._fit_in_view)
00098
00099 self._widget.load_dot_push_button.setIcon(QIcon.fromTheme('document-open'))
00100 self._widget.load_dot_push_button.pressed.connect(self._load_dot)
00101 self._widget.save_dot_push_button.setIcon(QIcon.fromTheme('document-save-as'))
00102 self._widget.save_dot_push_button.pressed.connect(self._save_dot)
00103 self._widget.save_as_svg_push_button.setIcon(QIcon.fromTheme('document-save-as'))
00104 self._widget.save_as_svg_push_button.pressed.connect(self._save_svg)
00105 self._widget.save_as_image_push_button.setIcon(QIcon.fromTheme('image'))
00106 self._widget.save_as_image_push_button.pressed.connect(self._save_image)
00107
00108 self._deferred_fit_in_view.connect(self._fit_in_view,
00109 Qt.QueuedConnection)
00110 self._deferred_fit_in_view.emit()
00111
00112 context.add_widget(self._widget)
00113
00114 self._force_refresh = False
00115
00116 def save_settings(self, plugin_settings, instance_settings):
00117 instance_settings.set_value('auto_fit_graph_check_box_state',
00118 self._widget.auto_fit_graph_check_box.isChecked())
00119 instance_settings.set_value('highlight_connections_check_box_state',
00120 self._widget.highlight_connections_check_box.isChecked())
00121
00122 def restore_settings(self, plugin_settings, instance_settings):
00123 self._widget.auto_fit_graph_check_box.setChecked(
00124 instance_settings.value('auto_fit_graph_check_box_state', True) in [True, 'true'])
00125 self._widget.highlight_connections_check_box.setChecked(
00126 instance_settings.value('highlight_connections_check_box_state', True) in [True, 'true'])
00127 self.initialized = True
00128 self._refresh_tf_graph()
00129
00130 def _update_tf_graph(self):
00131 self._force_refresh = True
00132 self._refresh_tf_graph()
00133
00134 def _refresh_tf_graph(self):
00135 if not self.initialized:
00136 return
00137 self._update_graph_view(self._generate_dotcode())
00138
00139 def _generate_dotcode(self):
00140 force_refresh = self._force_refresh
00141 self._force_refresh = False
00142 rospy.wait_for_service('~tf2_frames')
00143 tf2_frame_srv = rospy.ServiceProxy('~tf2_frames', FrameGraph)
00144 return self.dotcode_generator.generate_dotcode(dotcode_factory=self.dotcode_factory,
00145 tf2_frame_srv=tf2_frame_srv,
00146 force_refresh=force_refresh)
00147
00148 def _update_graph_view(self, dotcode):
00149 if dotcode == self._current_dotcode:
00150 return
00151 self._current_dotcode = dotcode
00152 self._redraw_graph_view()
00153
00154 def _generate_tool_tip(self, url):
00155 return url
00156
00157 def _redraw_graph_view(self):
00158 self._scene.clear()
00159
00160 if self._widget.highlight_connections_check_box.isChecked():
00161 highlight_level = 3
00162 else:
00163 highlight_level = 1
00164
00165 (nodes, edges) = self.dot_to_qt.dotcode_to_qt_items(self._current_dotcode,
00166 highlight_level)
00167
00168 for node_item in nodes.itervalues():
00169 self._scene.addItem(node_item)
00170 for edge_items in edges.itervalues():
00171 for edge_item in edge_items:
00172 edge_item.add_to_scene(self._scene)
00173
00174 self._scene.setSceneRect(self._scene.itemsBoundingRect())
00175 if self._widget.auto_fit_graph_check_box.isChecked():
00176 self._fit_in_view()
00177
00178 def _load_dot(self, file_name=None):
00179 if file_name is None:
00180 file_name, _ = QFileDialog.getOpenFileName(
00181 self._widget,
00182 self.tr('Open graph from file'),
00183 None,
00184 self.tr('DOT graph (*.dot)'))
00185 if file_name is None or file_name == '':
00186 return
00187
00188 try:
00189 fhandle = open(file_name, 'rb')
00190 dotcode = fhandle.read()
00191 fhandle.close()
00192 except IOError:
00193 return
00194
00195 self._update_graph_view(dotcode)
00196
00197 def _fit_in_view(self):
00198 self._widget.graphics_view.fitInView(self._scene.itemsBoundingRect(),
00199 Qt.KeepAspectRatio)
00200
00201 def _save_dot(self):
00202 file_name, _ = QFileDialog.getSaveFileName(self._widget,
00203 self.tr('Save as DOT'),
00204 'frames.dot',
00205 self.tr('DOT graph (*.dot)'))
00206 if file_name is None or file_name == '':
00207 return
00208
00209 file = QFile(file_name)
00210 if not file.open(QIODevice.WriteOnly | QIODevice.Text):
00211 return
00212
00213 file.write(self._current_dotcode)
00214 file.close()
00215
00216 def _save_svg(self):
00217 file_name, _ = QFileDialog.getSaveFileName(
00218 self._widget,
00219 self.tr('Save as SVG'),
00220 'frames.svg',
00221 self.tr('Scalable Vector Graphic (*.svg)'))
00222 if file_name is None or file_name == '':
00223 return
00224
00225 generator = QSvgGenerator()
00226 generator.setFileName(file_name)
00227 generator.setSize((self._scene.sceneRect().size() * 2.0).toSize())
00228
00229 painter = QPainter(generator)
00230 painter.setRenderHint(QPainter.Antialiasing)
00231 self._scene.render(painter)
00232 painter.end()
00233
00234 def _save_image(self):
00235 file_name, _ = QFileDialog.getSaveFileName(
00236 self._widget,
00237 self.tr('Save as image'),
00238 'frames.png',
00239 self.tr('Image (*.bmp *.jpg *.png *.tiff)'))
00240 if file_name is None or file_name == '':
00241 return
00242
00243 img = QImage((self._scene.sceneRect().size() * 2.0).toSize(),
00244 QImage.Format_ARGB32_Premultiplied)
00245 painter = QPainter(img)
00246 painter.setRenderHint(QPainter.Antialiasing)
00247 self._scene.render(painter)
00248 painter.end()
00249 img.save(file_name)