From 1608136fdeaf101e0baf9d5be99c4d76d5e60ba3 Mon Sep 17 00:00:00 2001 From: Ken Conley Date: Wed, 21 Apr 2010 00:11:43 +0000 Subject: [PATCH] rxgraph: added in info pane --- tools/rxgraph/manifest.xml | 5 ++- tools/rxgraph/src/rxgraph/dotcode.py | 9 ++++- tools/rxgraph/src/rxgraph/rxgraph.py | 54 ++++++++++++++++++++++++++-- tools/rxgraph/src/rxgraph/viewer.py | 41 +++++++++++---------- 4 files changed, 84 insertions(+), 25 deletions(-) diff --git a/tools/rxgraph/manifest.xml b/tools/rxgraph/manifest.xml index 8c897fd0..37205e50 100644 --- a/tools/rxgraph/manifest.xml +++ b/tools/rxgraph/manifest.xml @@ -1,7 +1,7 @@ - rxgraph + rxgraph is a command-line tool for visualizing a ROS computation graph. The rxgraph is a new package in ROS 1.1 that provides an updated version of the rxgraph tool that was originally distributed in the rosgraph package. Ken Conley @@ -13,6 +13,9 @@ + + + diff --git a/tools/rxgraph/src/rxgraph/dotcode.py b/tools/rxgraph/src/rxgraph/dotcode.py index 775c2826..a3de2474 100644 --- a/tools/rxgraph/src/rxgraph/dotcode.py +++ b/tools/rxgraph/src/rxgraph/dotcode.py @@ -110,7 +110,10 @@ def generate_namespaces(g, graph_mode, quiet=False): nt_nodes = [n for n in nt_nodes if not n in QUIET_NAMES] if nn_nodes or nt_nodes: namespaces = [roslib.names.namespace(n) for n in nn_nodes] - namespaces.extend([roslib.names.namespace(n) for n in nt_nodes]) + # an annoyance with the rosgraph library is that it + # prepends a space to topic names as they have to have + # different graph node namees from nodes. we have to strip here + namespaces.extend([roslib.names.namespace(n[1:]) for n in nt_nodes]) return list(set(namespaces)) @@ -151,6 +154,10 @@ def generate_dotcode(g, ns_filter, graph_mode, orientation, quiet=False): if quiet: nn_nodes = [n for n in nn_nodes if not n in QUIET_NAMES] nt_nodes = [n for n in nt_nodes if not n in QUIET_NAMES] + if ns_filter and ns_filter != '/': + nn_nodes = [n for n in nn_nodes if n.startswith(ns_filter) or n == name_filter] + nt_nodes = [n for n in nt_nodes if n[1:].startswith(ns_filter) or n[1:] == name_filter] + if nn_nodes or nt_nodes: nodes_str = '\n'.join([_generate_node_dotcode(n, g, quiet) for n in nn_nodes]) nodes_str += '\n'.join([' %s [shape=box,label="%s",URL="topic:%s"];'%( diff --git a/tools/rxgraph/src/rxgraph/rxgraph.py b/tools/rxgraph/src/rxgraph/rxgraph.py index c930390a..cbac98bc 100644 --- a/tools/rxgraph/src/rxgraph/rxgraph.py +++ b/tools/rxgraph/src/rxgraph/rxgraph.py @@ -54,6 +54,28 @@ import roslib.roslogging # have to import later than others due to xdot calling wxversion import wx +import roslib.scriptutil +import rostopic +import rosnode + +def get_info_text(selection_url): + if selection_url is None: + return '' + if selection_url.startswith('node:'): + try: + node_name = selection_url[5:] + master = roslib.scriptutil.get_master() + node_api = rosnode.get_api_uri(master, node_name) + return rosnode.get_node_info_description(node_name) + rosnode.get_node_connection_info_description(node_api) + except rosnode.ROSNodeException, e: + return "ERROR: %s"%str(e) + + elif selection_url.startswith('topic:'): + try: + return rostopic.get_info_text(selection_url[6:]) + except rostopic.ROSTopicException, e: + return "ERROR: %s"%str(e) + class DotUpdate(threading.Thread): """Thread to control update of dot file""" @@ -62,7 +84,20 @@ class DotUpdate(threading.Thread): self.viewer = viewer self.graph = graph self.output_file = None - + self.selection_url = None + self.selection_update = False + + def select_callback(self, target, event): + try: + url = target.url + if url: + self.selection_url = url + self.selection_update = True + except: + import traceback + traceback.print_exc() + pass + def run(self): viewer = self.viewer current_ns_filter = viewer.ns_filter @@ -72,12 +107,16 @@ class DotUpdate(threading.Thread): quiet = False orientation = rxgraph.dotcode.ORIENTATIONS[0] + info_text = '' g.set_master_stale(5.0) g.set_node_stale(5.0) GUPDATE_INTERVAL = 0.5 + INFO_UPDATE_INTERVAL = 10. + last_gupdate = time.time() - GUPDATE_INTERVAL + last_info_update = time.time() - GUPDATE_INTERVAL try: while not is_shutdown(): @@ -88,12 +127,20 @@ class DotUpdate(threading.Thread): if now - last_gupdate >= GUPDATE_INTERVAL: changed = g.update() last_gupdate = now + + if now - last_info_update >= INFO_UPDATE_INTERVAL or self.selection_update: + last_info_update = now + if self.selection_url is not None: + info_text = get_info_text(self.selection_url) graph_mode = NODE_TOPIC_GRAPH if viewer.topic_boxes else NODE_NODE_GRAPH changed |= viewer.ns_filter != current_ns_filter changed |= quiet != viewer.quiet changed |= graph_mode != last_graph_mode + if self.selection_update: + self.selection_update = False + changed = True quiet = viewer.quiet last_graph_mode = graph_mode @@ -107,6 +154,7 @@ class DotUpdate(threading.Thread): viewer.update_namespaces(namespaces) viewer.set_dotcode(dotcode) + viewer.set_info_text(info_text) # store dotcode if requested if output_file: @@ -172,7 +220,9 @@ def rxgraph_main(): frame = RxGraphViewerFrame() frame.set_dotcode(rxgraph.dotcode.INIT_DOTCODE) - DotUpdate(graph, frame, output_file=options.output_file).start() + updater = DotUpdate(graph, frame, output_file=options.output_file) + frame.register_select_cb(updater.select_callback) + updater.start() frame.Show() app.MainLoop() diff --git a/tools/rxgraph/src/rxgraph/viewer.py b/tools/rxgraph/src/rxgraph/viewer.py index 01e9d2a1..e87a38e0 100644 --- a/tools/rxgraph/src/rxgraph/viewer.py +++ b/tools/rxgraph/src/rxgraph/viewer.py @@ -78,7 +78,7 @@ class RxGraphViewerFrame(wx.Frame): self.topic_boxes = False self._needs_refresh = False - + self._new_info_text = None # setup UI vbox = wx.BoxSizer(wx.VERTICAL) @@ -106,16 +106,16 @@ class RxGraphViewerFrame(wx.Frame): toolbar.AddControl(wx.StaticText(toolbar,-1,"Path: ")) self._ns_combo = wx.ComboBox(toolbar, -1, style=wx.CB_DROPDOWN) - self._ns_combo .Bind(wx.EVT_COMBOBOX, self.set_path) + self._ns_combo .Bind(wx.EVT_COMBOBOX, self._ns_combo_event) self._ns_combo.Append('/') self._ns_combo.SetValue('/') self._namespaces = ['/'] # display options quiet_check = wx.CheckBox(toolbar, -1, label="Quiet") - quiet_check.Bind(wx.EVT_CHECKBOX, self.set_quiet_check) + quiet_check.Bind(wx.EVT_CHECKBOX, self._quiet_check_event) topic_check = wx.CheckBox(toolbar, -1, label="All topics") - topic_check.Bind(wx.EVT_CHECKBOX, self.set_topic_boxes) + topic_check.Bind(wx.EVT_CHECKBOX, self._topic_check_event) toolbar.AddControl(self._ns_combo) @@ -139,19 +139,18 @@ class RxGraphViewerFrame(wx.Frame): gv_vbox.Add(toolbar, 0, wx.EXPAND) gv_vbox.Add(self._widget, 1, wx.EXPAND) - # Create userdata widget borders = wx.LEFT | wx.RIGHT | wx.TOP border = 4 - self.ud_win = wx.ScrolledWindow(self.content_splitter, -1) - self.ud_gs = wx.BoxSizer(wx.VERTICAL) - self.ud_gs.Add(wx.StaticText(self.ud_win,-1,"Info:"),0, borders, border) - self.ud_txt = wx.TextCtrl(self.ud_win,-1,style=wx.TE_MULTILINE | wx.TE_READONLY) - self.ud_gs.Add(self.ud_txt,1,wx.EXPAND | borders, border) - self.ud_win.SetSizer(self.ud_gs) + self.info_win = wx.ScrolledWindow(self.content_splitter, -1) + self.info_sizer = wx.BoxSizer(wx.VERTICAL) + self.info_sizer.Add(wx.StaticText(self.info_win,-1,"Info:"),0, borders, border) + self.info_txt = wx.TextCtrl(self.info_win,-1,style=wx.TE_MULTILINE | wx.TE_READONLY) + self.info_sizer.Add(self.info_txt,1,wx.EXPAND | borders, border) + self.info_win.SetSizer(self.info_sizer) # Set content splitter - self.content_splitter.SplitVertically(viewer, self.ud_win, 512) + self.content_splitter.SplitVertically(viewer, self.info_win, 512) vbox.Add(self.content_splitter, 1, wx.EXPAND | wx.ALL) self.SetSizer(vbox) @@ -159,29 +158,29 @@ class RxGraphViewerFrame(wx.Frame): self.Bind(wx.EVT_IDLE,self.OnIdle) - # user callback for select - self._widget.register_select_callback(self.select_cb) - def register_select_cb(self, callback): self._widget.register_select_callback(callback) def OnIdle(self, event): + if self._new_info_text is not None: + self.info_txt.SetValue(self._new_info_text) + self._new_info_text = None if self._needs_refresh: self.Refresh() self._needs_refresh = False - def select_cb(self, *args): - print args[0].item.url - - def set_quiet_check(self, event): + def _quiet_check_event(self, event): self.quiet = event.Checked() - def set_topic_boxes(self, event): + def _topic_check_event(self, event): self.topic_boxes = event.Checked() - def set_path(self, event): + def _ns_combo_event(self, event): self.ns_filter = self._ns_combo.GetValue() self._needs_zoom = True + + def set_info_text(self, text): + self._new_info_text = text def update_namespaces(self, namespaces): # unfortunately this routine will not alphanumerically sort