Advertisement
130s

rqt_topic_topic_widget_enhance_13Mar31.py

Mar 31st, 2013
286
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.22 KB | None | 0 0
  1. #!/usr/bin/env python
  2.  
  3. # 3/31/2013 Isaac Saito. Related to this thread on github
  4. # https://github.com/ros-visualization/rqt_common_plugins/issues/62
  5.  
  6. # Copyright (c) 2011, Dorian Scholz, TU Darmstadt
  7. # All rights reserved.
  8. #
  9. # Redistribution and use in source and binary forms, with or without
  10. # modification, are permitted provided that the following conditions
  11. # are met:
  12. #
  13. # * Redistributions of source code must retain the above copyright
  14. # notice, this list of conditions and the following disclaimer.
  15. # * Redistributions in binary form must reproduce the above
  16. # copyright notice, this list of conditions and the following
  17. # disclaimer in the documentation and/or other materials provided
  18. # with the distribution.
  19. # * Neither the name of the TU Darmstadt nor the names of its
  20. # contributors may be used to endorse or promote products derived
  21. # from this software without specific prior written permission.
  22. #
  23. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  24. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  25. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  26. # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  27. # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  28. # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  29. # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  30. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  31. # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  32. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  33. # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  34. # POSSIBILITY OF SUCH DAMAGE.
  35.  
  36. from __future__ import division
  37. import cProfile
  38. import os
  39. import sys
  40. import threading
  41.  
  42. from python_qt_binding import loadUi
  43. from python_qt_binding.QtCore import Qt, QTimer, Slot
  44. from python_qt_binding.QtGui import QHeaderView, QIcon, QMenu, QTreeWidgetItem, QWidget
  45.  
  46. import roslib
  47. import rospy
  48. import rospkg
  49.  
  50. from rqt_topic.topic_info import TopicInfo
  51.  
  52.  
  53. class TopicInfoGenerationThread(threading.Thread):
  54. def __init__(self, parent, existing_topicinfos, topic_name,
  55. topic_type, new_topics):
  56. super(TopicInfoGenerationThread, self).__init__()
  57. self._parent = parent
  58. self._existing_topicinfos = existing_topicinfos
  59. self._topic_name = topic_name
  60. self._topic_type = topic_type
  61. self._new_topics = new_topics
  62.  
  63. def run(self):
  64. if ((self._topic_name not in self._existing_topicinfos) or
  65. (self._existing_topicinfos[self._topic_name]['type'] !=
  66. self._topic_type)):
  67. # create new TopicInfo
  68. topic_info = TopicInfo(self._topic_name)
  69. # if successful, add it to the dict and tree view
  70. if topic_info._topic_name:
  71. topic_item = self._parent._recursive_create_widget_items(
  72. self._parent.topics_tree_widget,
  73. self._topic_name,
  74. self._topic_type,
  75. topic_info.message_class())
  76. self._new_topics[self._topic_name] = {
  77. 'item': topic_item,
  78. 'info': topic_info,
  79. 'type': self._topic_type,
  80. }
  81. else:
  82. # if topic has been seen before, copy it to new dict and remove
  83. # it from the old one
  84. self._new_topics[self._topic_name] = \
  85. self._existing_topicinfos[self._topic_name]
  86. del self._existing_topicinfos[self._topic_name]
  87.  
  88.  
  89. class TopicWidget(QWidget):
  90. """
  91. main class inherits from the ui window class
  92. """
  93. _column_names = ['topic', 'type', 'bandwidth', 'rate', 'value']
  94.  
  95. def __init__(self, plugin):
  96. super(TopicWidget, self).__init__()
  97. rp = rospkg.RosPack()
  98. ui_file = os.path.join(rp.get_path('rqt_topic'), 'resource',
  99. 'TopicWidget.ui')
  100. loadUi(ui_file, self)
  101. self._plugin = plugin
  102. self.topics_tree_widget.sortByColumn(0, Qt.AscendingOrder)
  103. header = self.topics_tree_widget.header()
  104. header.setResizeMode(QHeaderView.ResizeToContents)
  105. header.customContextMenuRequested.connect(self.handle_header_view_customContextMenuRequested)
  106. header.setContextMenuPolicy(Qt.CustomContextMenu)
  107.  
  108. self._current_topic_list = []
  109. self._topics = {}
  110. self._tree_items = {}
  111. self._column_index = {}
  112. for column_name in self._column_names:
  113. self._column_index[column_name] = len(self._column_index)
  114.  
  115. self.refresh_topics()
  116. #cProfile.runctx('self.refresh_topics()', globals(), locals())
  117. # init and start update timer
  118. self._timer_refresh_topics = QTimer(self)
  119. self._timer_refresh_topics.timeout.connect(self.refresh_topics)
  120. self._timer_refresh_topics.start(1000)
  121.  
  122. @Slot()
  123. def refresh_topics(self):
  124. # refresh tree view items
  125. topic_list = rospy.get_published_topics()
  126. if self._current_topic_list != topic_list:
  127. self._current_topic_list = topic_list
  128.  
  129. # start new topic dict
  130. new_topics = {}
  131.  
  132. for topic_name, topic_type in topic_list:
  133. # if topic is new or has changed its type
  134.  
  135. # Create TopicInfo in different thread
  136. topic_generator = TopicInfoGenerationThread(
  137. self, self._topics, topic_name,
  138. topic_type, new_topics)
  139. topic_generator.start()
  140.  
  141. rospy.loginfo('refresh_topics widget thread ended.')
  142.  
  143. # clean up old topics
  144. for topic_name in self._topics.keys():
  145. self._topics[topic_name]['info'].stop_monitoring()
  146. index = self.topics_tree_widget.indexOfTopLevelItem(self._topics[topic_name]['item'])
  147. self.topics_tree_widget.takeTopLevelItem(index)
  148. del self._topics[topic_name]
  149.  
  150. # switch to new topic dict
  151. self._topics = new_topics
  152.  
  153. self._update_topics_data()
  154.  
  155. def _update_topics_data(self):
  156. for topic in self._topics.values():
  157. topic_info = topic['info']
  158. if topic_info.monitoring:
  159. # update rate
  160. rate, _, _, _ = topic_info.get_hz()
  161. rate_text = '%1.2f' % rate if rate != None else 'unknown'
  162.  
  163. # update bandwidth
  164. bytes_per_s, _, _, _ = topic_info.get_bw()
  165. if bytes_per_s is None:
  166. bandwidth_text = 'unknown'
  167. elif bytes_per_s < 1000:
  168. bandwidth_text = '%.2fB/s' % bytes_per_s
  169. elif bytes_per_s < 1000000:
  170. bandwidth_text = '%.2fKB/s' % (bytes_per_s / 1000.)
  171. else:
  172. bandwidth_text = '%.2fMB/s' % (bytes_per_s / 1000000.)
  173.  
  174. # update values
  175. value_text = ''
  176. self.update_value(topic_info._topic_name, topic_info.last_message)
  177.  
  178. else:
  179. rate_text = ''
  180. bandwidth_text = ''
  181. value_text = 'not monitored'
  182.  
  183. self._tree_items[topic_info._topic_name].setText(self._column_index['rate'], rate_text)
  184. self._tree_items[topic_info._topic_name].setText(self._column_index['bandwidth'], bandwidth_text)
  185. self._tree_items[topic_info._topic_name].setText(self._column_index['value'], value_text)
  186.  
  187. def update_value(self, topic_name, message):
  188. if hasattr(message, '__slots__') and hasattr(message, '_slot_types'):
  189. for slot_name in message.__slots__:
  190. self.update_value(topic_name + '/' + slot_name, getattr(message, slot_name))
  191.  
  192. elif type(message) in (list, tuple) and (len(message) > 0) and hasattr(message[0], '__slots__'):
  193. for index, slot in enumerate(message):
  194. if topic_name + '[%d]' % index in self._tree_items:
  195. self.update_value(topic_name + '[%d]' % index, slot)
  196. else:
  197. base_type_str, _ = self._extract_array_info(self._tree_items[topic_name].text(self._column_index['type']))
  198. self._recursive_create_widget_items(self._tree_items[topic_name], topic_name + '[%d]' % index, base_type_str, slot)
  199.  
  200. else:
  201. if topic_name in self._tree_items:
  202. self._tree_items[topic_name].setText(self._column_index['value'], repr(message))
  203.  
  204. def _extract_array_info(self, type_str):
  205. array_size = None
  206. if '[' in type_str and type_str[-1] == ']':
  207. type_str, array_size_str = type_str.split('[', 1)
  208. array_size_str = array_size_str[:-1]
  209. if len(array_size_str) > 0:
  210. array_size = int(array_size_str)
  211. else:
  212. array_size = 0
  213.  
  214. return type_str, array_size
  215.  
  216. def _recursive_create_widget_items(self, parent, topic_name, type_name,
  217. message):
  218. if parent is self.topics_tree_widget:
  219. # show full topic name with preceding namespace on toplevel item
  220. topic_text = topic_name
  221. else:
  222. topic_text = topic_name.split('/')[-1]
  223. if '[' in topic_text:
  224. topic_text = topic_text[topic_text.index('['):]
  225. item = QTreeWidgetItem(parent)
  226. item.setText(self._column_index['topic'], topic_text)
  227. item.setText(self._column_index['type'], type_name)
  228. item.setData(0, Qt.UserRole, topic_name)
  229. self._tree_items[topic_name] = item
  230. if hasattr(message, '__slots__') and hasattr(message, '_slot_types'):
  231. for slot_name, type_name in zip(message.__slots__,
  232. message._slot_types):
  233. self._recursive_create_widget_items(item, topic_name + '/' + \
  234. slot_name, type_name, getattr(message, slot_name))
  235.  
  236. else:
  237. base_type_str, array_size = self._extract_array_info(type_name)
  238. try:
  239. base_instance = roslib.message.get_message_class(base_type_str)()
  240. except ValueError:
  241. base_instance = None
  242. if array_size is not None and hasattr(base_instance, '__slots__'):
  243. for index in range(array_size):
  244. self._recursive_create_widget_items(
  245. item,
  246. topic_name + '[%d]' % index, base_type_str, base_instance)
  247. return item
  248.  
  249. @Slot('QPoint')
  250. def handle_header_view_customContextMenuRequested(self, pos):
  251. header = self.topics_tree_widget.header()
  252.  
  253. # show context menu
  254. menu = QMenu(self)
  255. action_toggle_auto_resize = menu.addAction('Toggle Auto-Resize')
  256. action = menu.exec_(header.mapToGlobal(pos))
  257.  
  258. # evaluate user action
  259. if action is action_toggle_auto_resize:
  260. if header.resizeMode(0) == QHeaderView.ResizeToContents:
  261. header.setResizeMode(QHeaderView.Interactive)
  262. else:
  263. header.setResizeMode(QHeaderView.ResizeToContents)
  264.  
  265. @Slot('QPoint')
  266. def on_topics_tree_widget_customContextMenuRequested(self, pos):
  267. item = self.topics_tree_widget.itemAt(pos)
  268. if item is None:
  269. return
  270.  
  271. # show context menu
  272. menu = QMenu(self)
  273. action_moggle_monitoring = menu.addAction(QIcon.fromTheme('search'), 'Toggle Monitoring')
  274. action_item_expand = menu.addAction(QIcon.fromTheme('zoom-in'), 'Expand All Children')
  275. action_item_collapse = menu.addAction(QIcon.fromTheme('zoom-out'), 'Collapse All Children')
  276. action = menu.exec_(self.topics_tree_widget.mapToGlobal(pos))
  277.  
  278. # evaluate user action
  279. if action is action_moggle_monitoring:
  280. root_item = item
  281. while root_item.parent() is not None:
  282. root_item = root_item.parent()
  283. root_topic_name = root_item.data(0, Qt.UserRole)
  284. self._topics[root_topic_name]['info'].toggle_monitoring()
  285.  
  286. elif action in (action_item_expand, action_item_collapse):
  287. expanded = (action is action_item_expand)
  288.  
  289. def recursive_set_expanded(item):
  290. item.setExpanded(expanded)
  291. for index in range(item.childCount()):
  292. recursive_set_expanded(item.child(index))
  293. recursive_set_expanded(item)
  294.  
  295. def shutdown_plugin(self):
  296. for topic in self._topics.values():
  297. topic['info'].stop_monitoring()
  298. self._timer_refresh_topics.stop()
  299.  
  300.  
  301. if __name__ == '__main__':
  302. # main should be used only for debug purpose.
  303. # This moveites this QWidget as a standalone rqt gui.
  304. from rqt_gui.main import Main
  305.  
  306. main = Main()
  307. sys.exit(main.main(sys.argv, standalone='rqt_topic'))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement