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
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
00063 self.tabs = {}
00064 self.tool_dict = {}
00065
00066
00067
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
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
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
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
00133
00134
00135 def add_tools(self, tools_list):
00136
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
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
00150 self.button_group_tab.addButton(self.ui.add_outcome_button)
00151 outcome_tool = ot.OutcomeTool(self.ui.add_outcome_button, self)
00152
00153 self.tool_dict[outcome_tool.get_smach_class()] = {'tool_obj': outcome_tool}
00154
00155 def empty_container(self, pbox):
00156
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
00209 if button != None:
00210
00211 button.setDown(False)
00212 button.setChecked(False)
00213
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
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
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
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
00310 old_node_name = tool_instance.get_current_node_name()
00311
00312 smach_node = tool_instance.create_node(unique=False)
00313
00314 self.graph_model.replace_node(smach_node, old_node_name)
00315
00316
00317
00318
00319
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
00339
00340
00341
00342
00343
00344 self.graph_model.document.modified = True
00345 self.nothing_cb(None)
00346
00347 def run_sm_cb(self, checked):
00348
00349
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
00365 if not self.check_current_document():
00366 return
00367
00368 self._set_model(gm.GraphModel())
00369 self.nothing_cb(None)
00370
00371
00372 def save_sm_cb(self):
00373
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
00382 filename = str(QFileDialog.getSaveFileName(self, 'Save As', self.graph_model.document.get_filename()))
00383
00384
00385 if len(filename) == 0:
00386 return False
00387
00388 if pt.exists(filename):
00389
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
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
00418 self._set_model(gm.GraphModel.load(filename))
00419 self._reconnect_smach_states()
00420
00421
00422 self.nothing_cb(None)
00423
00424
00425
00426
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
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
00444
00445
00446
00447 tool = self.tool_dict[smach_state.__class__]['tool_obj']
00448 tool.button.setChecked(True)
00449
00450
00451 tool.activate_cb(smach_state.get_name())
00452
00453
00454
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
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
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
00482 def dclick_container_cb(self, fsm_stack_element):
00483
00484
00485 last_fsm_el = self.fsm_stack[-1]
00486
00487
00488
00489
00490
00491
00492
00493
00494 new_smach_node = last_fsm_el.node.recreate(self.graph_model)
00495
00496
00497
00498
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
00503 self.fsm_stack = self.fsm_stack[:self.fsm_stack.index(fsm_stack_element)]
00504
00505
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
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
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
00583
00584
00585
00586
00587
00588
00589
00590
00591
00592
00593
00594
00595