rcommander_refactor.py
Go to the documentation of this file.
00001 import roslib; roslib.load_manifest('rcommander')
00002 
00003 from PyQt4.QtGui import *
00004 from PyQt4.QtCore import *
00005 from rcommander_auto import Ui_RCommanderWindow
00006 
00007 import rospy
00008 import tf
00009 
00010 import sys
00011 
00012 #NodeBox 
00013 import graph
00014 
00015 import graph_view as gv
00016 import nodebox_gui as nbg
00017 import graph_model as gm
00018 import pr2_utils as pu
00019 import outcome_tool as ot
00020 
00021 def split(num, factor):
00022     num1 = int(round(num * factor))
00023     num2 = num - num1
00024     return [num1, num2]
00025 
00026 class FSMStackElement:
00027 
00028     def __init__(self, model, view, node):
00029         self.model = model
00030         self.view = view
00031         self.graph_node = None
00032         self.node = node
00033 
00034 class RCommander(QMainWindow, nbg.NodeBoxGUI):
00035 
00036     def __init__(self):
00037         QMainWindow.__init__(self)
00038         self.ui = Ui_RCommanderWindow()
00039         self.ui.setupUi(self)
00040         nbg.NodeBoxGUI.__init__(self, self.ui.graphicsSuperView)
00041 
00042         self.connect(self.ui.run_button,         SIGNAL('clicked()'), self.run_cb)
00043         self.connect(self.ui.add_button,         SIGNAL('clicked()'), self.add_cb)
00044         self.connect(self.ui.reset_button,       SIGNAL('clicked()'), self.reset_cb)
00045         self.connect(self.ui.save_button,        SIGNAL('clicked()'), self.save_cb)
00046         self.connect(self.ui.start_state_button, SIGNAL('clicked()'), self.start_state_cb)
00047 
00048         self.connect(self.ui.delete_button, SIGNAL('clicked()'), self.delete_cb)
00049         self.connect(self.ui.action_Run, SIGNAL('triggered(bool)'), self.run_sm_cb)
00050         self.connect(self.ui.action_stop, SIGNAL('triggered(bool)'), self.stop_sm_cb)
00051         self.connect(self.ui.actionNew, SIGNAL('triggered(bool)'), self.new_sm_cb)
00052         self.connect(self.ui.action_save, SIGNAL('triggered(bool)'), self.save_sm_cb)
00053         self.connect(self.ui.action_save_as, SIGNAL('triggered(bool)'), self.save_as_sm_cb)
00054         self.connect(self.ui.action_open, SIGNAL('triggered(bool)'), self.open_sm_cb)
00055         self.ui.splitter.setSizes(split(self.width(), .83))
00056 
00057         self.empty_container(self.ui.properties_tab)
00058         self.empty_container(self.ui.connections_tab)
00059         self.add_mode()
00060         self.disable_buttons()
00061 
00062         #create instance variables
00063         self.tabs = {}
00064         self.tool_dict = {}
00065 
00066         #name of currently selected tool that operates on graph
00067         #self.graph_model = None
00068         self.selected_tool = None
00069         self.selected_graph_tool = None 
00070         self.selected_node = None
00071         self.selected_edge = None
00072         self.fsm_stack = []
00073 
00074         #Setup animation timer
00075         self.status_bar_timer = QTimer()
00076         self.connect(self.status_bar_timer, SIGNAL('timeout()'), self.status_bar_check)
00077         self.status_bar_timer.start(100)
00078         
00079         #Connect to ROS & PR2 
00080         rospy.init_node('rcommander', anonymous=True)
00081         self.tf_listener = tf.TransformListener()
00082         self.pr2 = pu.PR2(self.tf_listener)
00083 
00084 
00085     def status_bar_check(self):
00086         if self.graph_model.sm_thread.has_key('run_sm'):
00087             sm_thread = self.graph_model.sm_thread['run_sm']
00088 
00089             if sm_thread.exception != None:
00090                 m = sm_thread.exception.message
00091                 self.statusBar().showMessage('%s: %s' % (sm_thread.exception.__class__, m), 15000)
00092                 self.graph_model.sm_thread.pop('run_sm')
00093                 self.graph_model.sm_thread.pop('preempted')
00094                 return
00095 
00096             if sm_thread.outcome != None:
00097                 self.statusBar().showMessage('Finished with outcome: %s' % sm_thread.outcome, 15000)
00098                 self.graph_model.sm_thread.pop('run_sm')
00099                 self.graph_model.sm_thread.pop('preempted')
00100                 return
00101 
00102             if not sm_thread.isAlive():
00103                 self.statusBar().showMessage('Error: SM thread unexpectedly died.', 15000)
00104                 self.graph_model.sm_thread.pop('run_sm')
00105                 self.graph_model.sm_thread.pop('preempted')
00106                 return
00107 
00108             if self.graph_model.sm_thread['preempted'] != None and (time.time() - self.graph_model.sm_thread['preempted'] > 5.):
00109                 rospy.loginfo('Thread took too long to terminate.  Escallating and using exception exit.')
00110                 self.graph_model.sm_thread['run_sm'].except_preempt()
00111                 rospy.loginfo('Thread terminated.')
00112                 self.graph_model.sm_thread.pop('run_sm')
00113                 self.graph_model.sm_thread.pop('preempted')
00114 
00115             rstring = 'Running...'
00116             if str(self.statusBar().currentMessage()) != rstring:
00117                 self.statusBar().showMessage(rstring, 1000)
00118 
00119 
00120     ####################################################################################################################
00121     # GUI logic
00122     ####################################################################################################################
00123     def _create_tab(self, tab_name):
00124         ntab = QWidget()
00125         ntab.setObjectName(tab_name)
00126         QHBoxLayout(ntab)
00127         self.ui.tools_box.addTab(ntab, tab_name)
00128         self.ui.tools_box.setTabText(self.ui.tools_box.indexOf(ntab), tab_name)
00129         self.tabs[tab_name] = ntab
00130 
00131     ##
00132     # Should only be called once during initialization
00133     #
00134     # @param list of [tab-name, tool-object] pairs
00135     def add_tools(self, tools_list):
00136         #add tools to the right tab, creating tabs if needed
00137         self.button_group_tab = QButtonGroup()
00138         for tab_name, tool in tools_list:
00139             if not self.tabs.has_key(tab_name):
00140                 self._create_tab(tab_name)
00141             tab_widget = self.tabs[tab_name]
00142             self.button_group_tab.addButton(tool.create_button(tab_widget))
00143             #self.tool_dict[tool.get_name()] = {'tool_obj': tool}
00144             self.tool_dict[tool.get_smach_class()] = {'tool_obj': tool}
00145 
00146         for tname in self.tabs.keys():
00147             self.tabs[tname].update()
00148 
00149         #Outcome tool is a specialized built in tool
00150         self.button_group_tab.addButton(self.ui.add_outcome_button)
00151         outcome_tool = ot.OutcomeTool(self.ui.add_outcome_button, self)
00152         #self.tool_dict[outcome_tool.get_name()] = {'tool_obj': outcome_tool}
00153         self.tool_dict[outcome_tool.get_smach_class()] = {'tool_obj': outcome_tool}
00154 
00155     def empty_container(self, pbox): 
00156         #pbox = self.ui.properties_tab
00157         formlayout = pbox.layout()
00158         for i in range(formlayout.count()):
00159             item = formlayout.itemAt(0)
00160             formlayout.removeItem(item)
00161         children = pbox.children()
00162         for c in children[1:]:
00163             formlayout.removeWidget(c)
00164             c.setParent(None)
00165         formlayout.invalidate()
00166         pbox.update()
00167 
00168     def set_selected_tool(self, tool_name):
00169         self.selected_tool = tool_name
00170 
00171     def run_state_machine(self, sm):
00172         if self.graph_model.sm_thread.has_key('run_sm'):
00173             raise RuntimeError('Only state machine execution thread maybe be active at a time.')
00174         self.graph_model.run(self.graph_model.document.get_name(), state_machine=sm)
00175 
00176     def check_current_document(self):
00177         if self.graph_model.document.modified:
00178             msg_box = QMessageBox()
00179             msg_box.setText('Current state machine has not been saved.')
00180             msg_box.setInformativeText('Do you want to save it first?')
00181             msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
00182             msg_box.setDefaultButton(QMessageBox.Cancel)
00183             ret = msg_box.exec_()
00184 
00185             if ret == QMessageBox.Cancel:
00186                 return False
00187 
00188             elif ret == QMessageBox.Yes:
00189                 return self.save_sm_cb()
00190 
00191         return True
00192 
00193     def disable_buttons(self):
00194         self.ui.run_button.setDisabled(True)
00195         self.ui.reset_button.setDisabled(True)
00196         self.ui.add_button.setDisabled(True)
00197         self.ui.save_button.setDisabled(True)
00198 
00199     def enable_buttons(self):
00200         self.ui.run_button.setDisabled(False)
00201         self.ui.reset_button.setDisabled(False)
00202         self.ui.add_button.setDisabled(False)
00203         self.ui.save_button.setDisabled(False)
00204 
00205     def deselect_tool_buttons(self):
00206         self.button_group_tab.setExclusive(False)
00207         button = self.button_group_tab.checkedButton()
00208         #print button
00209         if button != None:
00210             #print 'checked', button.isChecked(), button.isDown(), button.isCheckable(), button.text()
00211             button.setDown(False)
00212             button.setChecked(False)
00213             #print 'checked2', button.isChecked(), button.isDown()
00214         self.button_group_tab.setExclusive(True)
00215 
00216     def edit_mode(self):
00217         self.ui.add_button.hide()
00218         self.ui.save_button.show()
00219 
00220     def add_mode(self):
00221         self.ui.add_button.show()
00222         self.ui.save_button.hide()
00223 
00224     def empty_properties_box(self):
00225         self.empty_container(self.ui.properties_tab)
00226         self.empty_container(self.ui.connections_tab)
00227 
00228     ####################################################################################################################
00229     # Graph tools
00230     ####################################################################################################################
00231 
00232     def _reconnect_smach_states(self):
00233         for k in self.graph_model.smach_states:
00234             if hasattr(self.graph_model.smach_states[k], 'set_robot'):
00235                 self.graph_model.smach_states[k].set_robot(self.pr2)
00236 
00237     def connection_changed(self, node_name, outcome_name, new_outcome):
00238         self.graph_model.connection_changed(node_name, outcome_name, new_outcome)
00239         self.graph_model.document.modified = True
00240 
00241     def current_children_of(self, node_name):
00242         return self.graph_model.current_children_of(node_name)
00243 
00244     def connectable_nodes(self, node_name, outcome):
00245         return self.graph_model.connectable_nodes(node_name, outcome)
00246 
00247     def global_nodes(self, class_filter):
00248         return self.graph_model.global_nodes(class_filter)
00249 
00250     def set_selected_node(self, name):
00251         self.selected_node = name
00252 
00253     def set_selected_edge(self, n1, n2, label):
00254         if n1 == None:
00255             self.selected_edge = None
00256         else:
00257             self.selected_edge = self.graph_model.edge(n1, n2, label=label)
00258 
00259     ####################################################################################################################
00260     # All callbacks
00261     ####################################################################################################################
00262     def run_cb(self):
00263         if self.selected_tool == None:
00264             return
00265         try:
00266             tool_instance = self.tool_dict[self.selected_tool]['tool_obj']
00267             node = tool_instance.create_node(unique=False)
00268             singleton_sm = self.graph_model.create_singleton_statemachine(node)
00269             self.run_state_machine(singleton_sm)
00270         except RuntimeError, e:
00271             QMessageBox.information(self, str(self.objectName()), 'RuntimeError: ' + e.message)
00272     
00273     def add_cb(self):
00274         if self.selected_tool == None:
00275             return
00276         tool_instance = self.tool_dict[self.selected_tool]['tool_obj']
00277         if hasattr(tool_instance, 'set_child_node'):
00278             if self.selected_node == None:
00279                 QMessageBox.information(self, str(self.objectName()), 'Need to have another node selected to create an instance of this node.')
00280                 return
00281             else:
00282                 smach_state = self.graph_model.get_smach_state(self.selected_node)
00283                 tool_instance.set_child_node(smach_state)
00284 
00285         smach_node = tool_instance.create_node()
00286         self.graph_model.add_node(smach_node)
00287         if self.selected_node == None:
00288             self.node_cb(self.graph_model.node(smach_node.name))
00289         else:
00290             snode = self.graph_model.node(self.selected_node)
00291             if snode != None:
00292                 self.node_cb(snode)
00293             else:
00294                 self.nothing_cb(None)
00295                 #self.selected_node = None
00296 
00297         self.tool_dict[self.selected_tool]['tool_obj'].refresh_connections_box()
00298         self.graph_view.refresh()
00299         self.graph_model.document.modified = True
00300 
00301     def reset_cb(self):
00302         if self.selected_tool == None:
00303             return
00304         tool_instance = self.tool_dict[self.selected_tool]['tool_obj']
00305         tool_instance.reset()
00306         
00307     def save_cb(self):
00308         tool_instance = self.tool_dict[self.selected_tool]['tool_obj']
00309         #old_smach_node = self.graph_model.get_smach_state()
00310         old_node_name = tool_instance.get_current_node_name()
00311         # create a node with new settings
00312         smach_node = tool_instance.create_node(unique=False)
00313         # 'delete' old smach node
00314         self.graph_model.replace_node(smach_node, old_node_name)
00315         #print 'TRANS!', smach_node.vels
00316         #self.graph_model.set_smach_state(old_smach_node.get_name(), smach_node)
00317 
00318         # connection changes are made instantly (so don't worry about them)
00319         # only saving of internal node parameters must be implemented by client tools
00320         self.graph_model.document.modified = True
00321 
00322     def start_state_cb(self):
00323         if self.selected_node != None:
00324             try:
00325                 self.graph_model.set_start_state(self.selected_node)
00326             except RuntimeError, e:
00327                 QMessageBox.information(self, str(self.objectName()), 'RuntimeError: ' + e.message)
00328 
00329     def delete_cb(self):
00330         if self.selected_node != None:
00331             if self.selected_node != 'start':
00332                 self.graph_model.delete_node(self.selected_node)
00333                 self.set_selected_node(None)
00334                 self.graph_view.refresh()
00335             else:
00336                 print 'Can\'t delete start node!'
00337 
00338         #TODO rethink deleting of edges
00339         #if self.selected_edge != None:
00340         #    se = self.selected_edge
00341         #    self.set_selected_edge(None, None)
00342         #    self.graph_model.delete_edge(se)
00343         #    self.graph_view.refresh()
00344         self.graph_model.document.modified = True
00345         self.nothing_cb(None)
00346 
00347     def run_sm_cb(self, checked):
00348         #TODO Disable all buttons.
00349         #TODO Reflect state of running graph.
00350         if self.graph_model.get_start_state() == None:
00351             QMessageBox.information(self, str(self.objectName()), 'No start state set.  Select a state and click on \'Start State\' to set a new start state.')
00352         else:
00353             try:
00354                 self.run_state_machine(self.graph_model.create_state_machine())
00355             except RuntimeError, e:
00356                 QMessageBox.information(self, str(self.objectName()), 'RuntimeError: ' + e.message)
00357 
00358     def stop_sm_cb(self):
00359         if self.graph_model.sm_thread.has_key('run_sm'):
00360             self.graph_model.sm_thread['run_sm'].preempt()
00361             self.graph_model.sm_thread['preempted'] = time.time()
00362 
00363     def new_sm_cb(self):
00364         #prompt user to save if document has been modifid
00365         if not self.check_current_document():
00366             return
00367 
00368         self._set_model(gm.GraphModel())
00369         self.nothing_cb(None)
00370         #self.document = FSMDocument.new_document()
00371 
00372     def save_sm_cb(self):
00373         #print 'has real filename?', self.document.has_real_filename()
00374         if self.graph_model.document.has_real_filename():
00375             self.graph_model.save(self.graph_model.document.get_filename())
00376             return True
00377         else:
00378             return self.save_as_sm_cb()
00379 
00380     def save_as_sm_cb(self):
00381         #popup file dialog
00382         filename = str(QFileDialog.getSaveFileName(self, 'Save As', self.graph_model.document.get_filename()))
00383 
00384         #user canceled
00385         if len(filename) == 0:
00386             return False
00387 
00388         if pt.exists(filename):
00389             #Ask if want to over write
00390             msg_box = QMessageBox()
00391             msg_box.setText('There is already a file with this name.')
00392             msg_box.setInformativeText('Do you want to overwrite it?')
00393             msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
00394             msg_box.setDefaultButton(QMessageBox.Cancel)
00395             ret = msg_box.exec_()
00396             if ret == QMessageBox.No or ret == QMessageBox.Cancel:
00397                 return False
00398 
00399         self.graph_model.save(filename)
00400         self.graph_model.document.set_filename(filename)
00401         self.graph_model.document.real_filename = True
00402         self.graph_model.document.modified = False
00403         return True
00404 
00405     def open_sm_cb(self):
00406         #prompt user if current document has not been saved
00407         if not self.check_current_document():
00408             return
00409 
00410         dialog = QFileDialog(self, 'Open State Machine', '~')
00411         dialog.setFileMode(QFileDialog.Directory)
00412         dialog.setViewMode(QFileDialog.List)
00413         if dialog.exec_():
00414             filenames = dialog.selectedFiles()
00415             filename = str(filenames[0])
00416 
00417             #Set this a the new model
00418             self._set_model(gm.GraphModel.load(filename))
00419             self._reconnect_smach_states()
00420 
00421             #Reset state of GUI
00422             self.nothing_cb(None)
00423             #self.document = FSMDocument(filename, modified=False, real_filename=True)
00424 
00425     ####################################################################################################################
00426     # Graph Callbacks
00427     ####################################################################################################################
00428 
00429     def nothing_cb(self, pt):
00430         self.set_selected_node(None)
00431         self.set_selected_edge(None, None, None)
00432         self.empty_properties_box()
00433         self.add_mode()
00434         self.disable_buttons()
00435         self.deselect_tool_buttons()
00436 
00437     def node_cb(self, node):
00438         #print '================================= NODECB'
00439         self.set_selected_node(node.id)
00440         self.set_selected_edge(None, None, None)
00441         smach_state = self.graph_model.get_smach_state(node.id)
00442 
00443         #if smach_state.__class__ == get.GripperEventState:
00444         #    print 'remapping for', node.id, 'is', smach_state.remapping
00445 
00446         #tool = self.tool_dict[smach_state.tool_name]['tool_obj']
00447         tool = self.tool_dict[smach_state.__class__]['tool_obj']
00448         tool.button.setChecked(True)
00449 
00450         #print '??????????'
00451         tool.activate_cb(smach_state.get_name())
00452         #print '??????????'
00453 
00454         #self.set_selected_tool(smach_state.tool_name)
00455         self.set_selected_tool(smach_state.__class__)
00456 
00457         self.edit_mode()
00458         self.enable_buttons()
00459         tool.node_selected(smach_state)
00460 
00461         if smach_state.is_runnable():
00462             self.ui.run_button.setDisabled(False)
00463         else:
00464             self.ui.run_button.setDisabled(True)
00465         #print '--------------------------------- NODECB'
00466 
00467     def edge_cb(self, edge):
00468         self.set_selected_edge(edge.node1.id, edge.node2.id, edge.label)
00469         self.set_selected_node(None)
00470         self.disable_buttons()
00471 
00472     #Handler for double clicking on a node, descending a level
00473     def dclick_cb(self, node):
00474         snode = self.graph_model.get_smach_state(node.id)
00475         if gm.is_container(snode):
00476             self.fsm_stack.append(FSMStackElement(self.graph_model, self.graph_view, snode))
00477             self._set_model(snode.get_child())
00478             self._reconnect_smach_states()
00479             self.nothing_cb(None)
00480 
00481     #Handler for double clicing on circle, ascending a level
00482     def dclick_container_cb(self, fsm_stack_element):
00483 
00484         #Store current model
00485         last_fsm_el = self.fsm_stack[-1]
00486         #last_fsm_el.node.set_child(self.graph_model)
00487 
00488         ######
00489         #recreate the old node with this new model as a child
00490         #each node need a function that lets you recreate it
00491         # what to call this? recreate? update?
00492         #       input: old node
00493         #       output: new node
00494         new_smach_node = last_fsm_el.node.recreate(self.graph_model)
00495         
00496         #replace old node in the graph, reserving links which exist
00497         # replace_node (fix it so that it works with new nodes of the same name)
00498         # restore_consistency
00499         print 'new_smach_node', new_smach_node.get_name(), last_fsm_el.node.get_name()
00500         last_fsm_el.model.replace_node(new_smach_node, last_fsm_el.node.get_name())
00501 
00502         #Shorten the stack to the element selected
00503         self.fsm_stack = self.fsm_stack[:self.fsm_stack.index(fsm_stack_element)]
00504 
00505         #Load the element we're given
00506         self._set_model(fsm_stack_element.model, view=fsm_stack_element.view)
00507         self._reconnect_smach_states()
00508         self.nothing_cb(None)
00509 
00510     def _set_model(self, model, view=None):
00511         self.graph_model = model
00512         if view == None:
00513             self.graph_view = gv.GraphView(self.context, self.graph_model)
00514             self.graph_view.setup()
00515         else:
00516             self.graph_view = view
00517 
00518         self.graph_model.gve.events.click = self.node_cb
00519         self.graph_model.gve.events.click_edge = self.edge_cb
00520         self.graph_model.gve.events.click_nothing = self.nothing_cb
00521         self.graph_model.gve.events.dclick = self.dclick_cb
00522         self.graph_view.fsm_dclick_cb = self.dclick_container_cb
00523 
00524     ####################################################################################################################
00525     # Drawing code
00526     ####################################################################################################################
00527     def setup(self):
00528         graph._ctx = self.context
00529         self.context.speed(30.)
00530         self.context.size(700, 700)
00531         self._set_model(gm.GraphModel())
00532 
00533     def draw(self, properties_dict):
00534         properties_dict['selected_edge'] = self.selected_edge
00535         properties_dict['selected_node'] = self.selected_node
00536         properties_dict['width'        ] = self.ui.graphicsSuperView.viewport().width()
00537         properties_dict['height'       ] = self.ui.graphicsSuperView.viewport().height()
00538         properties_dict['name'         ] = self.graph_model.document.get_name()
00539         properties_dict['fsm_stack'    ] = self.fsm_stack
00540         self.graph_view.draw(properties_dict)
00541 
00542 
00543 if __name__ == '__main__':
00544     import plugins 
00545     import point_tool as ptl
00546     import state_machine_tool as smt
00547     import sleep_tool as st
00548 
00549     app = QApplication(sys.argv)
00550     rc = RCommander()
00551     app.connect(app, SIGNAL('lastWindowClosed()'), app.quit)
00552     app.connect(rc.ui.action_quit, SIGNAL('clicked()'), app.quit)
00553 
00554     #Load plugins
00555     tools_list = [['Graph', st.SleepTool(rc)], ['Graph', ptl.Point3DTool(rc)], ['Graph', smt.StateMachineTool(rc)]]
00556     plugin_clses = plugins.load_plugins()
00557     print 'found plugins', plugin_clses
00558     for tab_name, pcls in plugin_clses:
00559         tools_list.append([tab_name, pcls(rc)])
00560     rc.add_tools(tools_list)
00561 
00562     rc.show()
00563     sys.exit(app.exec_())
00564 
00565 
00566 
00567 
00568 
00569 
00570 
00571 
00572 
00573 
00574 
00575 
00576 
00577 
00578 
00579 
00580 
00581 
00582     #rc.add_tools([
00583     #              ['Manipulation', tt.TuckTool(rc)],
00584     #              ['Manipulation', lmt.LinearMoveTool(rc)],
00585     #              ['Manipulation', mat.SafeMoveArmTool(rc)],
00586     #              ['Manipulation', mt.JointSequenceTool(rc)],
00587     #              ['Manipulation', gt.GripperTool(rc)],
00588     #              ['Perception', ptl.Point3DTool(rc)],
00589     #              ['Perception', get.GripperEventTool(rc)],
00590     #              ['Navigation and Misc', nt.NavigateTool(rc)], 
00591     #              ['Navigation and Misc', spt.SpineTool(rc)],
00592     #              ['Navigation and Misc', smt.StateMachineTool(rc)],
00593     #              ['Navigation and Misc', st.SleepTool(rc)],
00594     #              ['Navigation and Misc', skt.SpeakTool(rc)]
00595     #              ])


rcommander
Author(s): Hai Nguyen (haidai@gmail.com)
autogenerated on Thu Nov 28 2013 11:46:34