rxgraph: added in info pane
This commit is contained in:
parent
77ca4017cf
commit
1608136fde
|
@ -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>
|
||||
|
||||
|
||||
|
|
|
@ -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"];'%(
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue