$search
00001 #! /usr/bin/python 00002 #*********************************************************** 00003 #* Software License Agreement (BSD License) 00004 #* 00005 #* Copyright (c) 2009, Willow Garage, Inc. 00006 #* All rights reserved. 00007 #* 00008 #* Redistribution and use in source and binary forms, with or without 00009 #* modification, are permitted provided that the following conditions 00010 #* are met: 00011 #* 00012 #* * Redistributions of source code must retain the above copyright 00013 #* notice, this list of conditions and the following disclaimer. 00014 #* * Redistributions in binary form must reproduce the above 00015 #* copyright notice, this list of conditions and the following 00016 #* disclaimer in the documentation and/or other materials provided 00017 #* with the distribution. 00018 #* * Neither the name of Willow Garage, Inc. nor the names of its 00019 #* contributors may be used to endorse or promote products derived 00020 #* from this software without specific prior written permission. 00021 #* 00022 #* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 00023 #* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 00024 #* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 00025 #* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 00026 #* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 00027 #* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 00028 #* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 00029 #* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 00030 #* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 00031 #* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 00032 #* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 00033 #* POSSIBILITY OF SUCH DAMAGE. 00034 #* 00035 #* Author: Eitan Marder-Eppstein 00036 #*********************************************************** 00037 import roslib; roslib.load_manifest('tf2_visualization') 00038 import rospy 00039 00040 import os 00041 import sys 00042 import yaml 00043 00044 import wxversion 00045 WXVER = '2.8' 00046 if wxversion.checkInstalled(WXVER): 00047 wxversion.select(WXVER) 00048 else: 00049 print >> sys.stderr, 'This application requires wxPython version %s' % WXVER 00050 sys.exit(1) 00051 00052 import wx 00053 import wx.richtext 00054 import xdot 00055 import threading 00056 00057 class FrameViewerPanel(wx.Panel): 00058 def __init__(self, parent, tf_interface, auto_update_list=True): 00059 wx.Panel.__init__(self, parent, -1) 00060 00061 self.tf_interface = tf_interface 00062 self.namespace = ('local', False) 00063 self.tf_namespaces = [] 00064 self.loaded_files = [] 00065 self.need_dot_zoom = True 00066 self.keep_running = True 00067 self.ns_list_lock = threading.RLock() 00068 00069 #Create a main pane 00070 vbox = wx.BoxSizer(wx.VERTICAL) 00071 00072 #Create a splitter 00073 self.content_splitter = wx.SplitterWindow(self, -1, style=wx.SP_LIVE_UPDATE) 00074 self.content_splitter.SetMinimumPaneSize(24) 00075 self.content_splitter.SetSashGravity(0.85) 00076 00077 #Create a viewer panel 00078 viewer = wx.Panel(self.content_splitter, -1) 00079 00080 #Create a graph panel 00081 graph_viewer = wx.Panel(viewer, -1) 00082 gv_vbox = wx.BoxSizer(wx.VERTICAL) 00083 graph_viewer.SetSizer(gv_vbox) 00084 00085 viewer_box = wx.BoxSizer() 00086 viewer_box.Add(graph_viewer,1,wx.EXPAND | wx.ALL, 4) 00087 viewer.SetSizer(viewer_box) 00088 00089 #Construct toolbar 00090 toolbar = wx.ToolBar(graph_viewer, -1) 00091 toolbar.AddControl(wx.StaticText(toolbar, -1, "Graph: ")) 00092 self.namespaces = wx.ComboBox(toolbar, 1, value='local', choices=['local'], size=(-1, -1), style=wx.CB_DROPDOWN) 00093 self.namespaces.Editable = False 00094 toolbar.AddControl(self.namespaces) 00095 self.Bind(wx.EVT_COMBOBOX, self.on_select_ns, id=1) 00096 00097 if auto_update_list: 00098 self.namespace_list_thread = threading.Thread(target=self._update_namespace_list) 00099 self.namespace_list_thread.start() 00100 00101 toolbar.Realize(); 00102 00103 #Create graph_view widget 00104 self.widget = xdot.wxxdot.WxDotWindow(graph_viewer, -1) 00105 self.widget.zoom_to_fit() 00106 00107 gv_vbox.Add(toolbar, 0, wx.EXPAND) 00108 gv_vbox.Add(self.widget, 1, wx.EXPAND) 00109 00110 nb = wx.Notebook(self.content_splitter, -1, style=wx.NB_TOP | wx.WANTS_CHARS) 00111 nb_box = wx.BoxSizer() 00112 nb_box.Add(nb, 1, wx.EXPAND | wx.ALL, 4) 00113 00114 #Create an info pane 00115 borders = wx.LEFT | wx.RIGHT | wx.TOP 00116 border = 4 00117 self.info_win = wx.ScrolledWindow(nb, -1) 00118 self.info_sizer = wx.BoxSizer(wx.VERTICAL) 00119 self.info_txt = wx.TextCtrl(self.info_win, -1, style=wx.TE_MULTILINE | wx.TE_READONLY) 00120 self.info_sizer.Add(self.info_txt, 1, wx.EXPAND | borders, border) 00121 self.info_win.SetSizer(self.info_sizer) 00122 00123 #Create a tf echo pane 00124 echo_panel = wx.Panel(nb, -1) 00125 echo_box = wx.BoxSizer(wx.VERTICAL) 00126 echo_panel.SetSizer(echo_box) 00127 wx.StaticText(echo_panel, -1, "Target: ", pos=(5, 15)) 00128 self.target_frame = wx.ComboBox(echo_panel, 2, value='Select Target', choices=['Select Target'], pos=(60, 10), size=(200, -1), style=wx.CB_DROPDOWN) 00129 self.target_frame.SetEditable(False) 00130 self.Bind(wx.EVT_COMBOBOX, self.on_select_target, id=2) 00131 00132 wx.StaticText(echo_panel, -1, "Source: ", pos=(5, 55)) 00133 self.source_frame = wx.ComboBox(echo_panel, 3, value='Select Source', choices=['Select Source'], pos=(60, 50), size=(200, -1), style=wx.CB_DROPDOWN) 00134 self.source_frame.SetEditable(False) 00135 self.Bind(wx.EVT_COMBOBOX, self.on_select_source, id=3) 00136 00137 self.echo_txt = wx.TextCtrl(echo_panel, -1, style=wx.TE_MULTILINE | wx.TE_READONLY, pos=(5, 90), size=(255,500)) 00138 00139 #Add our panels to the notebook 00140 nb.AddPage(self.info_win, "Info") 00141 nb.AddPage(echo_panel, "TF Echo") 00142 00143 # Set content splitter 00144 #self.content_splitter.SplitHorizontally(nb, viewer, 120) 00145 00146 vbox.Add(self.content_splitter, 1, wx.EXPAND | wx.ALL) 00147 self.SetSizer(vbox) 00148 self.Center() 00149 00150 self.content_splitter.SplitVertically(nb, viewer, 300) 00151 00152 self.timer = wx.Timer(self) 00153 00154 self.Bind(wx.EVT_TIMER, lambda event: self.update_tf_data(), self.timer) 00155 self.widget.register_select_callback(self.select_cb) 00156 00157 self.timer.Start(1000) 00158 00159 def update_file_list(self, file): 00160 if file: 00161 filename = file.rstrip('.tf')+'.tf' 00162 self.loaded_files.append(filename) 00163 self.refresh_list() 00164 self.namespaces.SetValue(filename) 00165 cmd = wx.CommandEvent(wx.EVT_COMBOBOX.evtType[0]) 00166 cmd.SetEventObject(self.namespaces) 00167 cmd.SetId(self.namespaces.GetId()) 00168 self.namespaces.GetEventHandler().ProcessEvent(cmd) 00169 00170 def reset_frame_lists(self): 00171 self.source_frame.SetItems(['Select Source']) 00172 self.source_frame.SetValue('Select Source') 00173 self.target_frame.SetItems(['Select Target']) 00174 self.target_frame.SetValue('Select Target') 00175 self.set_echo_text("") 00176 00177 def update_tf_data(self): 00178 self.tf_interface.update_data(*self.namespace) 00179 00180 dotcode = self.tf_interface.get_dot() 00181 frame_list = self.tf_interface.get_frame_list() 00182 00183 if self.namespace[0] == 'local': 00184 self.source_frame.SetItems(frame_list) 00185 self.target_frame.SetItems(frame_list) 00186 else: 00187 self.reset_frame_lists() 00188 00189 self.set_info_text(yaml.dump(self.tf_interface.get_info(), default_flow_style=False)) 00190 self.set_dotcode(dotcode) 00191 00192 self.check_echo() 00193 self.refresh_list() 00194 00195 def set_dotcode(self, dotcode): 00196 if not dotcode: 00197 return 00198 00199 self.widget.set_dotcode(dotcode, None) 00200 00201 #We'll only zoom to fit the first time we get data 00202 if self.need_dot_zoom: 00203 self.need_dot_zoom = False 00204 self.widget.zoom_to_fit() 00205 00206 wx.CallAfter(self.Refresh) 00207 00208 def set_info_text(self, text): 00209 self.info_txt.Value = text 00210 self.Refresh() 00211 00212 def set_echo_text(self, text): 00213 self.echo_txt.Value = text 00214 self.Refresh() 00215 00216 ## Event handling 00217 00218 def select_cb(self, target, event): 00219 if event.ButtonDown(wx.MOUSE_BTN_LEFT) and target.url is not None: 00220 self.tf_interface.set_detail(target.url) 00221 00222 def _update_namespace_list(self): 00223 r = rospy.Rate(0.5) 00224 while not rospy.is_shutdown() and self.keep_running: 00225 tf_namespaces = self.tf_interface.find_tf_namespaces() 00226 with self.ns_list_lock: 00227 self.tf_namespaces = tf_namespaces 00228 r.sleep() 00229 00230 def refresh_list(self): 00231 with self.ns_list_lock: 00232 self.namespaces.Items = ['local'] + self.tf_namespaces + self.loaded_files 00233 00234 def on_select_ns(self, event): 00235 value = event.EventObject.Value 00236 #we need to check if we're looking up a namespace or just reading a file 00237 if value == self.namespace[0]: 00238 return 00239 00240 with self.ns_list_lock: 00241 if value == 'local' or self.tf_namespaces.count(value) > 0: 00242 self.namespace = (value, False) 00243 else: 00244 self.namespace = (value, True) 00245 00246 self.tf_interface.clear_detail() 00247 self.reset_frame_lists() 00248 self.need_dot_zoom = True 00249 00250 def on_select_target(self, event): 00251 print "target" 00252 print type(event) 00253 print event.EventObject.Value 00254 00255 def on_select_source(self, event): 00256 print "source" 00257 print type(event) 00258 print event.EventObject.Value 00259 00260 def check_echo(self): 00261 target = self.target_frame.GetValue() 00262 source = self.source_frame.GetValue() 00263 if source != 'Select Source' and target != 'Select Target': 00264 self.set_echo_text(self.tf_interface.get_echo_string(target, source)) 00265