behaviour_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 
35 import argparse
36 import fnmatch
37 import functools
38 import py_trees
39 import py_trees_msgs.msg as py_trees_msgs
40 import os
41 import re
42 import rosbag
43 import rospkg
44 import rospy
45 import sys
46 import termcolor
47 import uuid_msgs.msg as uuid_msgs
48 
49 from . import visibility
50 
51 from .dotcode_behaviour import RosBehaviourTreeDotcodeGenerator
52 from .dynamic_timeline import DynamicTimeline
53 from .dynamic_timeline_listener import DynamicTimelineListener
54 from .timeline_listener import TimelineListener
55 from .qt_dotgraph.dot_to_qt import DotToQtGenerator
56 from .qt_dotgraph.pydotfactory import PydotFactory
57 from .qt_dotgraph.pygraphvizfactory import PygraphvizFactory
58 from rqt_bag.bag_timeline import BagTimeline
59 # from rqt_bag.bag_widget import BagGraphicsView
60 from rqt_graph.interactive_graphics_view import InteractiveGraphicsView
61 
62 from python_qt_binding import loadUi
63 from python_qt_binding.QtCore import QFile, QIODevice, QObject, Qt, Signal, QEvent, Slot
64 from python_qt_binding.QtGui import QIcon, QImage, QPainter, QKeySequence
65 from python_qt_binding.QtSvg import QSvgGenerator
66 try: # indigo
67  from python_qt_binding.QtGui import QFileDialog, QGraphicsView, QGraphicsScene, QWidget, QShortcut
68 except ImportError: # kinetic+ (pyqt5)
69  from python_qt_binding.QtWidgets import QFileDialog, QGraphicsView, QGraphicsScene, QWidget, QShortcut
70 
71 
72 class RosBehaviourTree(QObject):
73 
74  _deferred_fit_in_view = Signal()
75  _refresh_view = Signal()
76  _refresh_combo = Signal()
77  _message_changed = Signal()
78  _message_cleared = Signal()
79  _expected_type = py_trees_msgs.BehaviourTree()._type
80  _empty_topic = "No valid topics available"
81  _unselected_topic = "Not subscribing"
82  no_roscore_switch = "--no-roscore"
83 
84  class ComboBoxEventFilter(QObject):
85  """Event filter for the combo box. Will filter left mouse button presses,
86  calling a signal when they happen
87 
88  """
89  def __init__(self, signal):
90  """
91 
92  :param Signal signal: signal that is emitted when a left mouse button press happens
93  """
95  self.signal = signal
96 
97  def eventFilter(self, obj, event):
98  if event.type() == QEvent.MouseButtonPress and event.button() == Qt.LeftButton:
99  self.signal.emit()
100  return False
101 
102  def __init__(self, context):
103  super(RosBehaviourTree, self).__init__(context)
104  self.setObjectName('RosBehaviourTree')
105 
106  parser = argparse.ArgumentParser()
107  RosBehaviourTree.add_arguments(parser, False)
108  # if the context doesn't have an argv attribute then assume we're running with --no-roscore
109  if not hasattr(context, 'argv'):
110  args = sys.argv[1:]
111  # Can run the viewer with or without live updating. Running without is
112  # intended for viewing of bags only
113  self.live_update = False
114  else:
115  args = context.argv()
116  self.live_update = True
117 
118  parsed_args = parser.parse_args(args)
119 
120  self.context = context
121  self.initialized = False
122  self._current_dotcode = None # dotcode for the tree that is currently displayed
123  self._viewing_bag = False # true if a bag file is loaded
124  # True if next or previous buttons are pressed. Reset if the tree being
125  # viewed is the last one in the list.
126  self._browsing_timeline = False
127 
128  self._widget = QWidget()
129 
130  # factory builds generic dotcode items
131  self.dotcode_factory = PygraphvizFactory() # PydotFactory()
132  # self.dotcode_factory = PygraphvizFactory()
133  # generator builds rosgraph
135  self.current_topic = None
136  self.behaviour_sub = None
137  self._tip_message = None # message of the tip of the tree
138  self._default_topic = parsed_args.topic # topic parsed on the command line
139  self._saved_settings_topic = None # topic subscribed to by previous instance
140  self.visibility_level = py_trees.common.VisibilityLevel.DETAIL
141 
142  # dot_to_qt transforms into Qt elements using dot layout
144 
145  rp = rospkg.RosPack()
146  ui_file = os.path.join(rp.get_path('rqt_py_trees'), 'resource', 'RosBehaviourTree.ui')
147  loadUi(ui_file, self._widget, {'InteractiveGraphicsView': InteractiveGraphicsView})
148  self._widget.setObjectName('RosBehaviourTreeUi')
149  if hasattr(context, 'serial_number') and context.serial_number() > 1:
150  self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number()))
151 
152  self._scene = QGraphicsScene()
153  self._scene.setBackgroundBrush(Qt.white)
154  self._widget.graphics_view.setScene(self._scene)
155 
156  self._widget.highlight_connections_check_box.toggled.connect(self._redraw_graph_view)
157  self._widget.auto_fit_graph_check_box.toggled.connect(self._redraw_graph_view)
158  self._widget.fit_in_view_push_button.setIcon(QIcon.fromTheme('zoom-original'))
159  self._widget.fit_in_view_push_button.pressed.connect(self._fit_in_view)
160 
161  self._widget.load_bag_push_button.setIcon(QIcon.fromTheme('document-open'))
162  self._widget.load_bag_push_button.pressed.connect(self._load_bag)
163  self._widget.load_dot_push_button.setIcon(QIcon.fromTheme('document-open'))
164  self._widget.load_dot_push_button.pressed.connect(self._load_dot)
165  self._widget.save_dot_push_button.setIcon(QIcon.fromTheme('document-save-as'))
166  self._widget.save_dot_push_button.pressed.connect(self._save_dot)
167  self._widget.save_as_svg_push_button.setIcon(QIcon.fromTheme('document-save-as'))
168  self._widget.save_as_svg_push_button.pressed.connect(self._save_svg)
169  self._widget.save_as_image_push_button.setIcon(QIcon.fromTheme('image'))
170  self._widget.save_as_image_push_button.pressed.connect(self._save_image)
171 
172  for text in visibility.combo_to_py_trees:
173  self._widget.visibility_level_combo_box.addItem(text)
174  self._widget.visibility_level_combo_box.setCurrentIndex(self.visibility_level)
175  self._widget.visibility_level_combo_box.currentIndexChanged['QString'].connect(self._update_visibility_level)
176 
177  # set up the function that is called whenever the box is resized -
178  # ensures that the timeline is correctly drawn.
179  self._widget.resizeEvent = self._resize_event
180 
181  self._timeline = None
182  self._timeline_listener = None
183 
184  # Connect the message changed function of this object to a corresponding
185  # signal. This signal will be activated whenever the message being
186  # viewed changes.
187  self._message_changed.connect(self.message_changed)
188  self._message_cleared.connect(self.message_cleared)
189 
190  # Set up combo box for topic selection
191  # when the refresh_combo signal happens, update the combo topics available
192  self._refresh_combo.connect(self._update_combo_topics)
193  # filter events to catch the event which opens the combo box
195  self._widget.topic_combo_box.installEventFilter(self._combo_event_filter)
196  self._widget.topic_combo_box.activated.connect(self._choose_topic)
197  # Delay populating the combobox, let restore_settings() handle it
198  # self._update_combo_topics()
199 
200  # Set up navigation buttons
201  self._widget.previous_tool_button.pressed.connect(self._previous)
202  self._widget.previous_tool_button.setIcon(QIcon.fromTheme('go-previous'))
203  self._widget.next_tool_button.pressed.connect(self._next)
204  self._widget.next_tool_button.setIcon(QIcon.fromTheme('go-next'))
205  self._widget.first_tool_button.pressed.connect(self._first)
206  self._widget.first_tool_button.setIcon(QIcon.fromTheme('go-first'))
207  self._widget.last_tool_button.pressed.connect(self._last)
208  self._widget.last_tool_button.setIcon(QIcon.fromTheme('go-last'))
209 
210  # play, pause and stop buttons
211  self._widget.play_tool_button.pressed.connect(self._play)
212  self._widget.play_tool_button.setIcon(QIcon.fromTheme('media-playback-start'))
213  self._widget.stop_tool_button.pressed.connect(self._stop)
214  self._widget.stop_tool_button.setIcon(QIcon.fromTheme('media-playback-stop'))
215  # also connect the navigation buttons so that they stop the timer when
216  # pressed while the tree is playing.
217  self._widget.first_tool_button.pressed.connect(self._stop)
218  self._widget.previous_tool_button.pressed.connect(self._stop)
219  self._widget.last_tool_button.pressed.connect(self._stop)
220  self._widget.next_tool_button.pressed.connect(self._stop)
221 
222  # set up shortcuts for navigation (vim)
223  next_shortcut_vi = QShortcut(QKeySequence("l"), self._widget)
224  next_shortcut_vi.activated.connect(self._widget.next_tool_button.pressed)
225  previous_shortcut_vi = QShortcut(QKeySequence("h"), self._widget)
226  previous_shortcut_vi.activated.connect(self._widget.previous_tool_button.pressed)
227  first_shortcut_vi = QShortcut(QKeySequence("^"), self._widget)
228  first_shortcut_vi.activated.connect(self._widget.first_tool_button.pressed)
229  last_shortcut_vi = QShortcut(QKeySequence("$"), self._widget)
230  last_shortcut_vi.activated.connect(self._widget.last_tool_button.pressed)
231 
232  # shortcuts for emacs
233  next_shortcut_emacs = QShortcut(QKeySequence("Ctrl+f"), self._widget)
234  next_shortcut_emacs.activated.connect(self._widget.next_tool_button.pressed)
235  previous_shortcut_emacs = QShortcut(QKeySequence("Ctrl+b"), self._widget)
236  previous_shortcut_emacs.activated.connect(self._widget.previous_tool_button.pressed)
237  first_shortcut_emacs = QShortcut(QKeySequence("Ctrl+a"), self._widget)
238  first_shortcut_emacs.activated.connect(self._widget.first_tool_button.pressed)
239  last_shortcut_emacs = QShortcut(QKeySequence("Ctrl+e"), self._widget)
240  last_shortcut_emacs.activated.connect(self._widget.last_tool_button.pressed)
241 
242  # set up stuff for dotcode cache
244  self._dotcode_cache = {}
245  # cache is ordered on timestamps from messages, but earliest timestamp
246  # isn't necessarily the message that was viewed the longest time ago, so
247  # need to store keys
249 
250  # set up stuff for scene cache (dotcode cache doesn't seem to make much difference)
252  self._scene_cache = {}
254 
255  # Update the timeline buttons to correspond with a completely
256  # uninitialised state.
257  self._set_timeline_buttons(first_snapshot=False, previous_snapshot=False, next_snapshot=False, last_snapshot=False)
258 
259  self._deferred_fit_in_view.connect(self._fit_in_view,
260  Qt.QueuedConnection)
261  self._deferred_fit_in_view.emit()
262 
263  # This is used to store a timer which controls how fast updates happen when the play button is pressed.
264  self._play_timer = None
265 
266  # updates the view
267  self._refresh_view.connect(self._refresh_tree_graph)
268 
269  self._force_refresh = False
270 
271  if self.live_update:
272  context.add_widget(self._widget)
273  else:
274  self.initialized = True # this needs to be set for trees to be displayed
275  context.setCentralWidget(self._widget)
276 
277  if parsed_args.bag:
278  self._load_bag(parsed_args.bag)
279  elif parsed_args.latest_bag:
280  # if the latest bag is requested, load it from the default directory, or
281  # the one specified in the args
282  bag_dir = parsed_args.bag_dir or os.getenv('ROS_HOME', os.path.expanduser('~/.ros')) + '/behaviour_trees'
283  self.open_latest_bag(bag_dir, parsed_args.by_time)
284 
285  @Slot(str)
286  def _update_visibility_level(self, visibility_level):
287  """
288  We match the combobox index to the visibility levels defined in py_trees.common.VisibilityLevel.
289  """
290  self.visibility_level = visibility.combo_to_py_trees[visibility_level]
291  self._refresh_tree_graph()
292 
293  @staticmethod
294  def add_arguments(parser, group=True):
295  """Allows for the addition of arguments to the rqt_gui loading method
296 
297  :param bool group: If set to false, this indicates that the function is
298  being called from the rqt_py_trees script as opposed to the inside
299  of rqt_gui.main. We use this to ensure that the same arguments can
300  be passed with and without the --no-roscore argument set. If it is
301  set, the rqt_gui code is bypassed. We need to make sure that all the
302  arguments are displayed with -h.
303 
304  """
305  operate_object = parser
306  if group:
307  operate_object = parser.add_argument_group('Options for the rqt_py_trees viewer')
308 
309  operate_object.add_argument('bag', action='store', nargs='?', help='Load this bag when the viewer starts')
310  operate_object.add_argument('-l', '--latest-bag', action='store_true', help='Load the latest bag available in the bag directory. Bag files are expected to be under the bag directory in the following structure: year-month-day/behaviour_tree_hour-minute-second.bag. If this structure is not followed, the bag file which was most recently modified is used.')
311  operate_object.add_argument('--bag-dir', action='store', help='Specify the directory in which to look for bag files. The default is $ROS_HOME/behaviour_trees, if $ROS_HOME is set, or ~/.ros/behaviour_trees otherwise.')
312  operate_object.add_argument('-m', '--by-time', action='store_true', help='The latest bag is defined by the time at which the file was last modified, rather than the date and time specified in the filename.')
313  operate_object.add_argument(RosBehaviourTree.no_roscore_switch, action='store_true', help='Run the viewer without roscore. It is only possible to view bag files if this is set.')
314  operate_object.add_argument('--topic', action='store', type=str, default=None, help='Default topic to defer to [default:None]')
315 
316  def open_latest_bag(self, bag_dir, by_time=False):
317  """Open the latest bag in the given directory
318 
319  :param str bag_dir: the directory in which to look for bags
320  :param bool by_time: if true, the latest bag is the one with the latest
321  modification time, not the latest date-time specified by its filename
322 
323  """
324  if not os.path.isdir(bag_dir):
325  rospy.logwarn("Requested bag directory {0} is invalid. Latest bag will not be loaded.".format(bag_dir))
326  return
327 
328  files = []
329  for root, unused_dirnames, filenames in os.walk(bag_dir, topdown=True):
330  files.extend(fnmatch.filter(map(lambda p: os.path.join(root, p), filenames), '*.bag'))
331 
332  if not files:
333  rospy.logwarn("No files with extension .bag found in directory {0}".format(bag_dir))
334  return
335 
336  if not by_time:
337  # parse the file list with a regex to get only those which have the
338  # format year-month-day/behaviour_tree_hour-minute-second.bag
339  re_str = '.*\/\d{4}-\d{2}-\d{2}\/behaviour_tree_\d{2}-\d{2}-\d{2}.bag'
340  expr = re.compile(re_str)
341  valid = filter(lambda f: expr.match(f), files)
342 
343  # if no files match the regex, use modification time instead
344  if not valid:
345  by_time = True
346  else:
347  # dates are monotonically increasing, so the last one is the latest
348  latest_bag = sorted(valid)[-1]
349 
350  if by_time:
351  latest_bag = sorted(files, cmp=lambda x, y: cmp(os.path.getctime(x), os.path.getctime(y)))[-1]
352 
353  self._load_bag(latest_bag)
354 
356  """
357  Get the message in the list or bag that is being viewed that should be
358  displayed.
359  """
360  msg = None
361  if self._timeline_listener:
362  try:
363  msg = self._timeline_listener.msg
364  except KeyError:
365  pass
366 
367  return py_trees_msgs.BehaviourTree() if msg is None else msg
368 
369  def _choose_topic(self, index):
370  """Updates the topic that is subscribed to based on changes to the combo box
371  text. If the topic is unchanged, nothing will happnen. Otherwise, the
372  old subscriber will be unregistered, and a new one initialised for the
373  updated topic. If the selected topic corresponds to the unselected
374  topic, the subscriber will be unregistered and a new one will not be
375  created.
376 
377  """
378  selected_topic = self._widget.topic_combo_box.currentText()
379  if selected_topic != self._empty_topic and self.current_topic != selected_topic:
380  self.current_topic = selected_topic
381  # destroy the old timeline and clear the scene
382  if self._timeline:
383  self._timeline.handle_close()
384  self._widget.timeline_graphics_view.setScene(None)
385 
386  if selected_topic != self._unselected_topic:
387  # set up a timeline to track the messages coming from the subscriber
388  self._set_dynamic_timeline()
389 
391  """
392  Update the topics displayed in the combo box that the user can use to select
393  which topic they want to listen on for trees, filtered so that only
394  topics with the correct message type are shown.
395 
396  This method is triggered on startup and on user combobox interactions
397  """
398  # Only update topics if we're running with live update (i.e. with roscore)
399  if not self.live_update:
400  self._widget.topic_combo_box.setEnabled(False)
401  return
402 
403  self._widget.topic_combo_box.clear()
404  topic_list = rospy.get_published_topics()
405 
406  valid_topics = []
407  for topic_path, topic_type in topic_list:
408  if topic_type == RosBehaviourTree._expected_type:
409  valid_topics.append(topic_path)
410 
411  # Populate the combo-box
412  if self._default_topic is not None and self._default_topic not in valid_topics:
413  self._widget.topic_combo_box.addItem(self._default_topic)
414  self._widget.topic_combo_box.addItems(valid_topics)
415  if self._default_topic is None and self._saved_settings_topic is not None:
416  self._widget.topic_combo_box.addItem(self._saved_settings_topic)
417  if self._default_topic is None and not valid_topics and self._saved_settings_topic is None:
418  self._widget.topic_combo_box.addItem(RosBehaviourTree._empty_topic)
419 
420  # Initialise it with a topic, priority given to:
421  # 1) command line choice, i.e. self._default_topic whether
422  # regardless of whether it exists or not
423  # 2) the valid topic if only one exists
424  # 3) the valid topic that is also in saved settings if more than one exists
425  # 4) the first valid topic in the list
426  # 5) the saved topic if no valid topics exist
427  # 6) the empty topic if no valid topics exist and no saved topic exists
428  # For each choice except 6), set as current with a subscriber.
429  if self.current_topic is None:
430  if self._default_topic is not None:
431  topic = self._default_topic
432  elif len(valid_topics) == 1:
433  topic = valid_topics[0]
434  elif valid_topics:
435  if self._saved_settings_topic in valid_topics:
436  topic = self._saved_settings_topic
437  else:
438  topic = valid_topics[0]
439  elif self._saved_settings_topic is not None:
440  topic = self._saved_settings_topic
441  else:
442  topic = None
443  if topic is not None:
444  for index in range(self._widget.topic_combo_box.count()):
445  if topic == self._widget.topic_combo_box.itemText(index):
446  self._widget.topic_combo_box.setCurrentIndex(index)
447  self._choose_topic(topic)
448  break
449  return
450 
451  def _set_timeline_buttons(self, first_snapshot=None, previous_snapshot=None, next_snapshot=None, last_snapshot=None):
452  """
453  Allows timeline buttons to be enabled and disabled.
454  """
455  if first_snapshot is not None:
456  self._widget.first_tool_button.setEnabled(first_snapshot)
457  if previous_snapshot is not None:
458  self._widget.previous_tool_button.setEnabled(previous_snapshot)
459  if next_snapshot is not None:
460  self._widget.next_tool_button.setEnabled(next_snapshot)
461  if last_snapshot is not None:
462  self._widget.last_tool_button.setEnabled(last_snapshot)
463 
464  def _play(self):
465  """
466  Start a timer which will automatically call the next function every time its
467  duration is up. Only works if the current message is not the final one.
468  """
469  if not self._play_timer:
470  self._play_timer = rospy.Timer(rospy.Duration(1), self._timer_next)
471 
472  def _timer_next(self, timer):
473  """
474  Helper function for the timer so that it can call the next function without
475  breaking.
476  """
477  self._next()
478 
479  def _stop(self):
480  """Stop the play timer, if it exists.
481  """
482  if self._play_timer:
483  self._play_timer.shutdown()
484  self._play_timer = None
485 
486  def _first(self):
487  """Navigate to the first message. Activates the next and last buttons, disables
488  first and previous, and refreshes the view. Also changes the state to be
489  browsing the timeline.
490 
491  """
492  self._timeline.navigate_start()
493 
494  self._set_timeline_buttons(first_snapshot=False, previous_snapshot=False, next_snapshot=True, last_snapshot=True)
495  self._refresh_view.emit()
496  self._browsing_timeline = True
497 
498  def _previous(self):
499  """Navigate to the previous message. Activates the next and last buttons, and
500  refreshes the view. If the current message is the second message, then
501  the first and previous buttons are disabled. Changes the state to be
502  browsing the timeline.
503  """
504  # if already at the beginning, do nothing
505  if self._timeline._timeline_frame.playhead == self._timeline._get_start_stamp():
506  return
507 
508  # otherwise, go to the previous message
509  self._timeline.navigate_previous()
510  # now browsing the timeline
511  self._browsing_timeline = True
512  self._set_timeline_buttons(last_snapshot=True, next_snapshot=True)
513  # if now at the beginning, disable timeline buttons.
514  if self._timeline._timeline_frame.playhead == self._timeline._get_end_stamp():
515  self._set_timeline_buttons(next_snapshot=False, last_snapshot=False)
516 
517  self._refresh_view.emit()
518 
519  def _last(self):
520  """Navigate to the last message. Activates the first and previous buttons,
521  disables next and last, and refreshes the view. The user is no longer
522  browsing the timeline after this is called.
523 
524  """
525  self._timeline.navigate_end()
526 
527  self._set_timeline_buttons(first_snapshot=True, previous_snapshot=True, next_snapshot=False, last_snapshot=False)
528  self._refresh_view.emit()
529  self._browsing_timeline = False
530  self._new_messages = 0
531 
532  def _next(self):
533  """Navigate to the next message. Activates the first and previous buttons. If
534  the current message is the second from last, disables the next and last
535  buttons, and stops browsing the timeline.
536 
537  """
538  # if already at the end, do nothing
539  if self._timeline._timeline_frame.playhead == self._timeline._get_end_stamp():
540  return
541 
542  # otherwise, go to the next message
543  self._timeline.navigate_next()
544  self._set_timeline_buttons(first_snapshot=True, previous_snapshot=True)
545  # if now at the end, disable timeline buttons and shutdown the play timer if active
546  if self._timeline._timeline_frame.playhead == self._timeline._get_end_stamp():
547  self._set_timeline_buttons(next_snapshot=False, last_snapshot=False)
548  self._browsing_timeline = False
549  if self._play_timer:
550  self._play_timer.shutdown()
551 
552  self._refresh_view.emit()
553 
554  def save_settings(self, plugin_settings, instance_settings):
555  instance_settings.set_value('visibility_level', self.visibility_level)
556  instance_settings.set_value('auto_fit_graph_check_box_state',
557  self._widget.auto_fit_graph_check_box.isChecked())
558  instance_settings.set_value('highlight_connections_check_box_state',
559  self._widget.highlight_connections_check_box.isChecked())
560  combo_text = self._widget.topic_combo_box.currentText()
561  if combo_text not in [self._empty_topic, self._unselected_topic]:
562  instance_settings.set_value('combo_box_subscribed_topic', combo_text)
563 
564  def restore_settings(self, plugin_settings, instance_settings):
565  try:
566  self._widget.auto_fit_graph_check_box.setChecked(
567  instance_settings.value('auto_fit_graph_check_box_state', True) in [True, 'true'])
568  self._widget.highlight_connections_check_box.setChecked(
569  instance_settings.value('highlight_connections_check_box_state', True) in [True, 'true'])
570  self._saved_settings_topic = instance_settings.value('combo_box_subscribed_topic', None)
571  saved_visibility_level = instance_settings.value('visibility_level', 1)
572  except TypeError as e:
573  self._widget.auto_fit_graph_check_box.setChecked(True)
574  self._widget.highlight_connections_check_box.setChecked(True)
575  self._saved_settings_topic = None
576  saved_visibility_level = 1
577  rospy.logerr("Rqt PyTrees: incompatible qt app configuration found, try removing ~/.config/ros.org/rqt_gui.ini")
578  rospy.logerr("Rqt PyTrees: %s" % e)
579  self._widget.visibility_level_combo_box.setCurrentIndex(visibility.saved_setting_to_combo_index[saved_visibility_level])
580  self.initialized = True
581  self._update_combo_topics()
582  self._refresh_tree_graph()
583 
585  """Refresh the graph view by regenerating the dotcode from the current message.
586 
587  """
588  if not self.initialized:
589  return
591 
592  def _generate_dotcode(self, message):
593  """
594  Get the dotcode for the given message, checking the cache for dotcode that
595  was previously generated, and adding to the cache if it wasn't there.
596  Cache replaces LRU.
597 
598  Mostly stolen from rqt_bag.MessageLoaderThread
599 
600  :param py_trees_msgs.BehavoiurTree message
601  """
602  if message is None:
603  return ""
604 
605 
609  tip_id = None
610  self._tip_message = None
611  # reverse behaviour list - construction puts the root at the end (with
612  # visitor, at least)
613  for behaviour in reversed(message.behaviours):
614  # root has empty parent ID
615  if str(behaviour.parent_id) == str(uuid_msgs.UniqueID()):
616  # parent is the root behaviour, so
617  tip_id = behaviour.tip_id
618 
619  # Run through the behaviours and do a couple of things:
620  # - get the tip
621  # - protect against feedback messages with quotes (https://bitbucket.org/yujinrobot/gopher_crazy_hospital/issues/72/rqt_py_trees-fails-to-display-tree)
622  if self._tip_message is None:
623  for behaviour in message.behaviours:
624  if str(behaviour.own_id) == str(tip_id):
625  self._tip_message = behaviour.message
626  if '"' in behaviour.message:
627  print("%s" % termcolor.colored('[ERROR] found double quotes in the feedback message [%s]' % behaviour.message, 'red'))
628  behaviour.message = behaviour.message.replace('"', '')
629  print("%s" % termcolor.colored('[ERROR] stripped to stop from crashing, but do catch the culprit! [%s]' % behaviour.message, 'red'))
630 
631  key = str(message.header.stamp) # stamps are unique
632  if key in self._dotcode_cache:
633  return self._dotcode_cache[key]
634 
635  force_refresh = self._force_refresh
636  self._force_refresh = False
637 
638  visible_behaviours = visibility.filter_behaviours_by_visibility_level(message.behaviours, self.visibility_level)
639 
640  # cache miss
641  dotcode = self.dotcode_generator.generate_dotcode(dotcode_factory=self.dotcode_factory,
642  behaviours=visible_behaviours,
643  timestamp=message.header.stamp,
644  force_refresh=force_refresh
645  )
646  self._dotcode_cache[key] = dotcode
647  self._dotcode_cache_keys.append(key)
648 
649  if len(self._dotcode_cache) > self._dotcode_cache_capacity:
650  oldest = self._dotcode_cache_keys[0]
651  del self._dotcode_cache[oldest]
652  self._dotcode_cache_keys.remove(oldest)
653 
654  return dotcode
655 
656  def _update_graph_view(self, dotcode):
657  if dotcode == self._current_dotcode:
658  return
659  self._current_dotcode = dotcode
660  self._redraw_graph_view()
661 
663  key = str(self.get_current_message().header.stamp)
664  if key in self._scene_cache:
665  new_scene = self._scene_cache[key]
666  else: # cache miss
667  new_scene = QGraphicsScene()
668  new_scene.setBackgroundBrush(Qt.white)
669 
670  if self._widget.highlight_connections_check_box.isChecked():
671  highlight_level = 3
672  else:
673  highlight_level = 1
674 
675  # (nodes, edges) = self.dot_to_qt.graph_to_qt_items(self.dotcode_generator.graph,
676  # highlight_level)
677  # this function is very expensive
678  (nodes, edges) = self.dot_to_qt.dotcode_to_qt_items(self._current_dotcode,
679  highlight_level)
680 
681  for node_item in iter(nodes.values()):
682  new_scene.addItem(node_item)
683  for edge_items in iter(edges.values()):
684  for edge_item in edge_items:
685  edge_item.add_to_scene(new_scene)
686 
687  new_scene.setSceneRect(new_scene.itemsBoundingRect())
688 
689  # put the scene in the cache
690  self._scene_cache[key] = new_scene
691  self._scene_cache_keys.append(key)
692 
693  if len(self._scene_cache) > self._scene_cache_capacity:
694  oldest = self._scene_cache_keys[0]
695  del self._scene_cache[oldest]
696  self._scene_cache_keys.remove(oldest)
697 
698  # after construction, set the scene and fit to the view
699  self._scene = new_scene
700 
701  self._widget.graphics_view.setScene(self._scene)
702  self._widget.message_label.setText(self._tip_message)
703 
704  if self._widget.auto_fit_graph_check_box.isChecked():
705  self._fit_in_view()
706 
707  def _resize_event(self, event):
708  """Activated when the window is resized. Will re-fit the behaviour tree in the
709  window, and update the size of the timeline scene rectangle so that it
710  is correctly drawn.
711 
712  """
713  self._fit_in_view()
714  if self._timeline:
715  self._timeline.setSceneRect(0, 0, self._widget.timeline_graphics_view.width() - 2, max(self._widget.timeline_graphics_view.height() - 2, self._timeline._timeline_frame._history_bottom))
716 
717  def timeline_changed(self):
718  """Should be called whenever the timeline changes. At the moment this is only
719  used to ensure that the first and previous buttons are correctly
720  disabled when a new message coming in on the timeline pushes the
721  playhead to be at the first message
722 
723  """
724  if self._timeline._timeline_frame.playhead == self._timeline._get_start_stamp():
725  self._set_timeline_buttons(first_snapshot=False, previous_snapshot=False)
726  else:
727  self._set_timeline_buttons(first_snapshot=True, previous_snapshot=True)
728 
729  def message_changed(self):
730  """
731  This function should be called when the message being viewed changes. Will
732  change the current message and update the view. Also ensures that the
733  timeline buttons are correctly set for the current position of the
734  playhead on the timeline.
735  """
736  if self._timeline._timeline_frame.playhead == self._timeline._get_end_stamp():
737  self._set_timeline_buttons(last_snapshot=False, next_snapshot=False)
738  else:
739  self._set_timeline_buttons(last_snapshot=True, next_snapshot=True)
740 
741  if self._timeline._timeline_frame.playhead == self._timeline._get_start_stamp():
742  self._set_timeline_buttons(first_snapshot=False, previous_snapshot=False)
743  else:
744  self._set_timeline_buttons(first_snapshot=True, previous_snapshot=True)
745 
746  self._refresh_view.emit()
747 
748  def message_cleared(self):
749  """
750  This function should be called when the message being viewed was cleared.
751  Currently no situation where this happens?
752  """
753  pass
754 
755  def no_right_click_press_event(self, func):
756  """Decorator for ignoring right click events on mouse press
757  """
758  @functools.wraps(func)
759  def wrapper(event):
760  if event.type() == QEvent.MouseButtonPress and event.button() == Qt.RightButton:
761  event.ignore()
762  else:
763  func(event)
764  return wrapper
765 
767  """
768  Set the timeline to a dynamic timeline, listening to messages on the topic
769  selected in the combo box.
770  """
771  self._timeline = DynamicTimeline(self, publish_clock=False)
772  # connect timeline events so that the timeline will update when events happen
773  self._widget.timeline_graphics_view.mousePressEvent = self.no_right_click_press_event(self._timeline.on_mouse_down)
774  self._widget.timeline_graphics_view.mouseReleaseEvent = self._timeline.on_mouse_up
775  self._widget.timeline_graphics_view.mouseMoveEvent = self._timeline.on_mouse_move
776  self._widget.timeline_graphics_view.wheelEvent = self._timeline.on_mousewheel
777  self._widget.timeline_graphics_view.setScene(self._timeline)
778 
779  # Don't show scrollbars - the timeline adjusts to the size of the view
780  self._widget.timeline_graphics_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
781  self._widget.timeline_graphics_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
782  # Send a resize event so that the timeline knows the size of the view it's in
783  self._resize_event(None)
784 
785  self._timeline.add_topic(self.current_topic, py_trees_msgs.BehaviourTree)
786 
787  # Create a listener for the timeline which will call the emit function
788  # on the given signals when the message being viewed changes or is
789  # cleared. The message being viewed changing generally happens when the
790  # user moves the slider around.
792  # Need to add a listener to make sure that we can get information about
793  # messages that are on the topic that we're interested in.
794  self._timeline.add_listener(self.current_topic, self._timeline_listener)
795 
796  self._timeline.navigate_end()
797  self._timeline._redraw_timeline(None)
798  self._timeline.timeline_updated.connect(self.timeline_changed)
799 
800  def _set_bag_timeline(self, bag):
801  """Set the timeline of this object to a bag timeline, hooking the graphics view
802  into mouse and wheel functions of the timeline.
803 
804  """
805  self._timeline = BagTimeline(self, publish_clock=False)
806  # connect timeline events so that the timeline will update when events happen
807  self._widget.timeline_graphics_view.mousePressEvent = self.no_right_click_press_event(self._timeline.on_mouse_down)
808  self._widget.timeline_graphics_view.mouseReleaseEvent = self._timeline.on_mouse_up
809  self._widget.timeline_graphics_view.mouseMoveEvent = self._timeline.on_mouse_move
810  self._widget.timeline_graphics_view.wheelEvent = self._timeline.on_mousewheel
811  self._widget.timeline_graphics_view.setScene(self._timeline)
812 
813  # Don't show scrollbars - the timeline adjusts to the size of the view
814  self._widget.timeline_graphics_view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
815  self._widget.timeline_graphics_view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
816  # Send a resize event so that the timeline knows the size of the view it's in
817  self._resize_event(None)
818 
819  self._timeline.add_bag(bag)
820  # Create a listener for the timeline which will call the emit function
821  # on the given signals when the message being viewed changes or is
822  # cleared. The message being viewed changing generally happens when the
823  # user moves the slider around.
825  # Need to add a listener to make sure that we can get information about
826  # messages that are on the topic that we're interested in.
827  self._timeline.add_listener(self.current_topic, self._timeline_listener)
828  # Go to the first message in the timeline of the bag.
829  self._timeline.navigate_start()
830 
831  def _load_bag(self, file_name=None):
832  """Load a bag from file. If no file name is given, a dialogue will pop up and
833  the user will be asked to select a file. If the bag file selected
834  doesn't have any valid topic, nothing will happen. If there are valid
835  topics, we load the bag and add a timeline for managing it.
836 
837  """
838  if file_name is None:
839  file_name, _ = QFileDialog.getOpenFileName(
840  self._widget,
841  self.tr('Open trees from bag file'),
842  None,
843  self.tr('ROS bag (*.bag)'))
844  if file_name is None or file_name == "":
845  return
846 
847  rospy.loginfo("Reading bag from {0}".format(file_name))
848  bag = rosbag.Bag(file_name, 'r')
849  # ugh...
850  topics = list(bag.get_type_and_topic_info()[1].keys())
851  types = []
852  for i in range(0, len(bag.get_type_and_topic_info()[1].values())):
853  types.append(list(bag.get_type_and_topic_info()[1].values())[i][0])
854 
855  tree_topics = [] # only look at the first matching topic
856  for ind, tp in enumerate(types):
857  if tp == 'py_trees_msgs/BehaviourTree':
858  tree_topics.append(topics[ind])
859 
860  if len(tree_topics) == 0:
861  rospy.logerr('Requested bag did not contain any valid topics.')
862  return
863 
864  self.message_list = []
865  self._viewing_bag = True
866  rospy.loginfo('Reading behaviour trees from topic {0}'.format(tree_topics[0]))
867  for unused_topic, msg, unused_t in bag.read_messages(topics=[tree_topics[0]]):
868  self.message_list.append(msg)
869 
870  self.current_topic = tree_topics[0]
871  self._set_timeline_buttons(first_snapshot=True, previous_snapshot=True, next_snapshot=False, last_snapshot=False)
872  self._set_bag_timeline(bag)
873  self._refresh_view.emit()
874 
875  def _load_dot(self, file_name=None):
876  if file_name is None:
877  file_name, _ = QFileDialog.getOpenFileName(
878  self._widget,
879  self.tr('Open tree from DOT file'),
880  None,
881  self.tr('DOT graph (*.dot)'))
882  if file_name is None or file_name == '':
883  return
884 
885  try:
886  fhandle = open(file_name, 'rb')
887  dotcode = fhandle.read()
888  fhandle.close()
889  except IOError:
890  return
891  self._update_graph_view(dotcode)
892 
893  def _fit_in_view(self):
894  self._widget.graphics_view.fitInView(self._scene.itemsBoundingRect(),
895  Qt.KeepAspectRatio)
896 
897  def _save_dot(self):
898  file_name, _ = QFileDialog.getSaveFileName(self._widget,
899  self.tr('Save as DOT'),
900  'frames.dot',
901  self.tr('DOT graph (*.dot)'))
902  if file_name is None or file_name == '':
903  return
904 
905  dot_file = QFile(file_name)
906  if not dot_file.open(QIODevice.WriteOnly | QIODevice.Text):
907  return
908 
909  dot_file.write(self._current_dotcode)
910  dot_file.close()
911 
912  def _save_svg(self):
913  file_name, _ = QFileDialog.getSaveFileName(
914  self._widget,
915  self.tr('Save as SVG'),
916  'frames.svg',
917  self.tr('Scalable Vector Graphic (*.svg)'))
918  if file_name is None or file_name == '':
919  return
920 
921  generator = QSvgGenerator()
922  generator.setFileName(file_name)
923  generator.setSize((self._scene.sceneRect().size() * 2.0).toSize())
924 
925  painter = QPainter(generator)
926  painter.setRenderHint(QPainter.Antialiasing)
927  self._scene.render(painter)
928  painter.end()
929 
930  def _save_image(self):
931  file_name, _ = QFileDialog.getSaveFileName(
932  self._widget,
933  self.tr('Save as image'),
934  'frames.png',
935  self.tr('Image (*.bmp *.jpg *.png *.tiff)'))
936  if file_name is None or file_name == '':
937  return
938 
939  img = QImage((self._scene.sceneRect().size() * 2.0).toSize(),
940  QImage.Format_ARGB32_Premultiplied)
941  painter = QPainter(img)
942  painter.setRenderHint(QPainter.Antialiasing)
943  self._scene.render(painter)
944  painter.end()
945  img.save(file_name)
rqt_py_trees.behaviour_tree.RosBehaviourTree._update_combo_topics
def _update_combo_topics(self)
Definition: behaviour_tree.py:390
rqt_py_trees.behaviour_tree.RosBehaviourTree._next
def _next(self)
Definition: behaviour_tree.py:532
rqt_py_trees.behaviour_tree.RosBehaviourTree.dotcode_factory
dotcode_factory
Definition: behaviour_tree.py:131
rqt_py_trees.behaviour_tree.RosBehaviourTree.ComboBoxEventFilter.__init__
def __init__(self, signal)
Definition: behaviour_tree.py:89
rqt_py_trees.behaviour_tree.RosBehaviourTree._refresh_view
_refresh_view
Definition: behaviour_tree.py:75
rqt_py_trees.behaviour_tree.RosBehaviourTree._new_messages
_new_messages
Definition: behaviour_tree.py:530
rqt_py_trees.behaviour_tree.RosBehaviourTree.no_right_click_press_event
def no_right_click_press_event(self, func)
Definition: behaviour_tree.py:755
rqt_py_trees.behaviour_tree.RosBehaviourTree.initialized
initialized
Definition: behaviour_tree.py:121
qt_dotgraph::pygraphvizfactory::PygraphvizFactory
rqt_py_trees.behaviour_tree.RosBehaviourTree._save_dot
def _save_dot(self)
Definition: behaviour_tree.py:897
rosbag::Bag
rqt_py_trees.behaviour_tree.RosBehaviourTree._set_bag_timeline
def _set_bag_timeline(self, bag)
Definition: behaviour_tree.py:800
rqt_py_trees.behaviour_tree.RosBehaviourTree.visibility_level
visibility_level
Definition: behaviour_tree.py:140
rqt_py_trees.behaviour_tree.RosBehaviourTree._browsing_timeline
_browsing_timeline
Definition: behaviour_tree.py:126
rqt_py_trees.behaviour_tree.RosBehaviourTree._stop
def _stop(self)
Definition: behaviour_tree.py:479
rqt_py_trees.behaviour_tree.RosBehaviourTree.behaviour_sub
behaviour_sub
Definition: behaviour_tree.py:136
rqt_py_trees.dotcode_behaviour.RosBehaviourTreeDotcodeGenerator
Classes.
Definition: dotcode_behaviour.py:33
rqt_py_trees.behaviour_tree.RosBehaviourTree._save_image
def _save_image(self)
Definition: behaviour_tree.py:930
rqt_py_trees.behaviour_tree.RosBehaviourTree._timer_next
def _timer_next(self, timer)
Definition: behaviour_tree.py:472
rqt_py_trees.behaviour_tree.RosBehaviourTree.message_cleared
def message_cleared(self)
Definition: behaviour_tree.py:748
rqt_graph::interactive_graphics_view
rqt_py_trees.behaviour_tree.RosBehaviourTree._dotcode_cache_capacity
_dotcode_cache_capacity
Definition: behaviour_tree.py:243
rqt_py_trees.behaviour_tree.RosBehaviourTree.dot_to_qt
dot_to_qt
Definition: behaviour_tree.py:143
rqt_py_trees.behaviour_tree.RosBehaviourTree.ComboBoxEventFilter
Definition: behaviour_tree.py:84
rqt_py_trees.timeline_listener.TimelineListener
Definition: timeline_listener.py:5
rqt_py_trees.dynamic_timeline_listener.DynamicTimelineListener
Definition: dynamic_timeline_listener.py:6
qt_dotgraph::pygraphvizfactory
rqt_py_trees.behaviour_tree.RosBehaviourTree._timeline_listener
_timeline_listener
Definition: behaviour_tree.py:182
rqt_py_trees.behaviour_tree.RosBehaviourTree._empty_topic
string _empty_topic
Definition: behaviour_tree.py:80
rqt_bag::bag_timeline::BagTimeline
rqt_py_trees.behaviour_tree.RosBehaviourTree.message_changed
def message_changed(self)
Definition: behaviour_tree.py:729
rqt_py_trees.behaviour_tree.RosBehaviourTree._default_topic
_default_topic
Definition: behaviour_tree.py:138
rqt_bag::bag_timeline
rqt_py_trees.behaviour_tree.RosBehaviourTree.save_settings
def save_settings(self, plugin_settings, instance_settings)
Definition: behaviour_tree.py:554
rqt_py_trees.behaviour_tree.RosBehaviourTree.__init__
def __init__(self, context)
Definition: behaviour_tree.py:102
qt_dotgraph::dot_to_qt
rqt_py_trees.behaviour_tree.RosBehaviourTree._viewing_bag
_viewing_bag
Definition: behaviour_tree.py:123
rqt_py_trees.behaviour_tree.RosBehaviourTree._update_visibility_level
def _update_visibility_level(self, visibility_level)
Definition: behaviour_tree.py:286
rqt_py_trees.behaviour_tree.RosBehaviourTree._widget
_widget
Definition: behaviour_tree.py:128
rqt_py_trees.behaviour_tree.RosBehaviourTree._unselected_topic
string _unselected_topic
Definition: behaviour_tree.py:81
rqt_py_trees.behaviour_tree.RosBehaviourTree.dotcode_generator
dotcode_generator
Definition: behaviour_tree.py:134
rqt_py_trees.behaviour_tree.RosBehaviourTree._message_changed
_message_changed
Definition: behaviour_tree.py:77
rqt_py_trees.behaviour_tree.RosBehaviourTree._tip_message
_tip_message
Get the tip, from the perspective of the root.
Definition: behaviour_tree.py:137
rqt_py_trees.behaviour_tree.RosBehaviourTree._play_timer
_play_timer
Definition: behaviour_tree.py:264
rqt_py_trees.behaviour_tree.RosBehaviourTree.message_list
message_list
Definition: behaviour_tree.py:864
rqt_py_trees.behaviour_tree.RosBehaviourTree.context
context
Definition: behaviour_tree.py:120
rqt_py_trees.behaviour_tree.RosBehaviourTree._message_cleared
_message_cleared
Definition: behaviour_tree.py:78
rqt_py_trees.behaviour_tree.RosBehaviourTree.get_current_message
def get_current_message(self)
Definition: behaviour_tree.py:355
rqt_py_trees.behaviour_tree.RosBehaviourTree._first
def _first(self)
Definition: behaviour_tree.py:486
rqt_py_trees.behaviour_tree.RosBehaviourTree.timeline_changed
def timeline_changed(self)
Definition: behaviour_tree.py:717
rqt_py_trees.behaviour_tree.RosBehaviourTree.current_topic
current_topic
Definition: behaviour_tree.py:135
rqt_py_trees.behaviour_tree.RosBehaviourTree._update_graph_view
def _update_graph_view(self, dotcode)
Definition: behaviour_tree.py:656
rqt_py_trees.behaviour_tree.RosBehaviourTree._force_refresh
_force_refresh
Definition: behaviour_tree.py:269
rqt_py_trees.dynamic_timeline.DynamicTimeline
Definition: dynamic_timeline.py:56
rqt_py_trees.behaviour_tree.RosBehaviourTree._scene
_scene
Definition: behaviour_tree.py:152
rqt_py_trees.behaviour_tree.RosBehaviourTree._combo_event_filter
_combo_event_filter
Definition: behaviour_tree.py:194
rqt_py_trees.behaviour_tree.RosBehaviourTree._generate_dotcode
def _generate_dotcode(self, message)
Definition: behaviour_tree.py:592
rqt_py_trees.behaviour_tree.RosBehaviourTree._dotcode_cache
_dotcode_cache
Definition: behaviour_tree.py:244
qt_dotgraph::pydotfactory
rqt_py_trees.behaviour_tree.RosBehaviourTree._load_dot
def _load_dot(self, file_name=None)
Definition: behaviour_tree.py:875
rqt_py_trees.behaviour_tree.RosBehaviourTree._refresh_combo
_refresh_combo
Definition: behaviour_tree.py:76
rqt_py_trees.behaviour_tree.RosBehaviourTree._play
def _play(self)
Definition: behaviour_tree.py:464
qt_dotgraph::dot_to_qt::DotToQtGenerator
rqt_py_trees.behaviour_tree.RosBehaviourTree._dotcode_cache_keys
_dotcode_cache_keys
Definition: behaviour_tree.py:248
rqt_py_trees.behaviour_tree.RosBehaviourTree._saved_settings_topic
_saved_settings_topic
Definition: behaviour_tree.py:139
rqt_py_trees.behaviour_tree.RosBehaviourTree._set_dynamic_timeline
def _set_dynamic_timeline(self)
Definition: behaviour_tree.py:766
rqt_py_trees.behaviour_tree.RosBehaviourTree._refresh_tree_graph
def _refresh_tree_graph(self)
Definition: behaviour_tree.py:584
rqt_py_trees.behaviour_tree.RosBehaviourTree.live_update
live_update
Definition: behaviour_tree.py:113
rqt_py_trees.behaviour_tree.RosBehaviourTree._set_timeline_buttons
def _set_timeline_buttons(self, first_snapshot=None, previous_snapshot=None, next_snapshot=None, last_snapshot=None)
Definition: behaviour_tree.py:451
rqt_py_trees.behaviour_tree.RosBehaviourTree._scene_cache_keys
_scene_cache_keys
Definition: behaviour_tree.py:253
rqt_py_trees.behaviour_tree.RosBehaviourTree._load_bag
def _load_bag(self, file_name=None)
Definition: behaviour_tree.py:831
rqt_py_trees.behaviour_tree.RosBehaviourTree._choose_topic
def _choose_topic(self, index)
Definition: behaviour_tree.py:369
rqt_py_trees.behaviour_tree.RosBehaviourTree._previous
def _previous(self)
Definition: behaviour_tree.py:498
rqt_py_trees.behaviour_tree.RosBehaviourTree.ComboBoxEventFilter.eventFilter
def eventFilter(self, obj, event)
Definition: behaviour_tree.py:97
rqt_py_trees.behaviour_tree.RosBehaviourTree._redraw_graph_view
def _redraw_graph_view(self)
Definition: behaviour_tree.py:662
rqt_py_trees.behaviour_tree.RosBehaviourTree._scene_cache_capacity
_scene_cache_capacity
Definition: behaviour_tree.py:251
rqt_py_trees.behaviour_tree.RosBehaviourTree._fit_in_view
def _fit_in_view(self)
Definition: behaviour_tree.py:893
rqt_py_trees.behaviour_tree.RosBehaviourTree._resize_event
def _resize_event(self, event)
Definition: behaviour_tree.py:707
rqt_py_trees.behaviour_tree.RosBehaviourTree._scene_cache
_scene_cache
Definition: behaviour_tree.py:252
rqt_py_trees.behaviour_tree.RosBehaviourTree._timeline
_timeline
Definition: behaviour_tree.py:181
rqt_py_trees.behaviour_tree.RosBehaviourTree._deferred_fit_in_view
_deferred_fit_in_view
Definition: behaviour_tree.py:74
rqt_py_trees.behaviour_tree.RosBehaviourTree.add_arguments
def add_arguments(parser, group=True)
Definition: behaviour_tree.py:294
rqt_py_trees.behaviour_tree.RosBehaviourTree._save_svg
def _save_svg(self)
Definition: behaviour_tree.py:912
rqt_py_trees.behaviour_tree.RosBehaviourTree.open_latest_bag
def open_latest_bag(self, bag_dir, by_time=False)
Definition: behaviour_tree.py:316
rqt_py_trees.behaviour_tree.RosBehaviourTree
Definition: behaviour_tree.py:72
rqt_py_trees.behaviour_tree.RosBehaviourTree._current_dotcode
_current_dotcode
Definition: behaviour_tree.py:122
rqt_py_trees.behaviour_tree.RosBehaviourTree._last
def _last(self)
Definition: behaviour_tree.py:519
rqt_py_trees.behaviour_tree.RosBehaviourTree.ComboBoxEventFilter.signal
signal
Definition: behaviour_tree.py:95
rqt_py_trees.behaviour_tree.RosBehaviourTree.restore_settings
def restore_settings(self, plugin_settings, instance_settings)
Definition: behaviour_tree.py:564


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