rxgraph: added in info pane

This commit is contained in:
Ken Conley 2010-04-21 00:11:43 +00:00
parent 77ca4017cf
commit 1608136fde
4 changed files with 84 additions and 25 deletions

View File

@ -1,7 +1,7 @@
<package>
<description brief="rxgraph">
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.
</description>
<author>Ken Conley</author>
@ -13,6 +13,9 @@
<depend package="xdot"/>
<depend package="roslib"/>
<depend package="rostopic"/>
<depend package="rosnode"/>
</package>

View File

@ -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"];'%(

View File

@ -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()

View File

@ -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