class QtRappManager(Plugin): _update_rapps_signal = Signal() def __init__(self, context): self._context = context super(QtRappManager, self).__init__(context) self._init_ui(context) self._init_events() self._init_variables() self.spin() def _init_ui(self, context): self._widget = QWidget() self.setObjectName('QtRappManger') rospack = rospkg.RosPack() ui_file = os.path.join(rospack.get_path('rocon_qt_app_manager'), 'ui', 'qt_rapp_manager.ui') self._widget.setObjectName('QtRappManger') loadUi(ui_file, self._widget, {}) if context.serial_number() > 1: self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number())) context.add_widget(self._widget) # Set view mode self._widget.rapp_grid.setViewMode(QListView.IconMode) # self._widget.rapp_grid.setViewMode(QListView.ListMode) def _init_events(self): # combo box change event self._widget.namespace_cbox.currentIndexChanged.connect( self._change_namespace) # Rapp single click event self._widget.rapp_grid.clicked.connect(self._rapp_single_click) # Rapp double click event self._widget.rapp_grid.doubleClicked.connect(self._rapp_double_click) def _init_variables(self): self.initialised = False # init self._qt_rapp_manager_info = QtRappManagerInfo(self._refresh_rapps) self._update_rapps_signal.connect(self._update_rapp_list) self._rapp_view_model = QStandardItemModel() self._widget.rapp_grid.setModel(self._rapp_view_model) self._widget.rapp_grid.setWrapping(True) self._widget.rapp_grid.setIconSize(QSize(60, 60)) self._widget.rapp_grid.setSpacing(10) self._selected_rapp = None def spin(self): self._get_appmanager_namespaces() def _cleanup_rapps(self): self._rapp_view_model.clear() def _get_appmanager_namespaces(self): namespaces = self._qt_rapp_manager_info._get_namespaces() for namespace in namespaces: ns = namespace[:namespace.find('list_rapps')] self._widget.namespace_cbox.addItem(ns) def _exit(self): pass ################################################################### # Events ################################################################### def _change_namespace(self, event): self._qt_rapp_manager_info.select_rapp_manager( self._widget.namespace_cbox.currentText()) def _refresh_rapps(self): """ Updates from qt_app_manager_info. """ self._update_rapps_signal.emit() def _update_rapp_list(self): """ Rapp manager namespace event """ self._cleanup_rapps() available_rapps = self._qt_rapp_manager_info.get_available_rapps() running_rapps = self._qt_rapp_manager_info.get_running_rapps() for r, v in available_rapps.items(): if r in running_rapps.keys(): item = QRappItem(v, running=True) else: item = QRappItem(v, running=False) self._rapp_view_model.appendRow(item) def _rapp_single_click(self, index): qrapp = self._rapp_view_model.item(index.row()) rapp = qrapp.getRapp() self._create_rapp_dialog(rapp) def _create_rapp_dialog(self, rapp): is_running = self._qt_rapp_manager_info.is_running_rapp(rapp) self._selected_rapp = rapp self._dialog = QtRappDialog(self._widget, rapp, self._qt_rapp_manager_info.start_rapp, self._qt_rapp_manager_info.stop_rapp, is_running) self._dialog.show() def _rapp_double_click(self, item): running_rapps = self._qt_rapp_manager_info.get_running_rapps() if len(running_rapps) > 0: names = [r['display_name'] for r in running_rapps.values()] show_message(self._widget, "Error", "Rapp %s are already running" % names) else: self._start_rapp() def _start_rapp(self): result = self._qt_rapp_manager_info.start_rapp( self._selected_rapp['name'], [], self._selected_rapp['public_parameters']) show_message(self._widget, str(result.started), result.message) self._selected_rapp = None return result def _stop_rapp(self): result = self._qt_rapp_manager_info.stop_rapp() show_message(self._widget, str(result.stopped), result.message) self._selected_rapp = None return result
class QtRappManager(Plugin): _update_rapps_signal = Signal() def __init__(self, context): self._context = context super(QtRappManager, self).__init__(context) self._init_ui(context) self._init_events() self._init_variables() self.spin() def _init_ui(self, context): self._widget = QWidget() self.setObjectName('QtRappManger') rospack = rospkg.RosPack() ui_file = os.path.join(rospack.get_path('rocon_qt_app_manager'), 'ui', 'qt_rapp_manager.ui') self._widget.setObjectName('QtRappManger') loadUi(ui_file, self._widget, {}) if context.serial_number() > 1: self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number())) context.add_widget(self._widget) # Set view mode self._widget.rapp_grid.setViewMode(QListView.IconMode) #self._widget.rapp_grid.setViewMode(QListView.ListMode) def _init_events(self): #combo box change event self._widget.namespace_cbox.currentIndexChanged.connect( self._change_namespace) # Rapp single click event self._widget.rapp_grid.clicked.connect(self._rapp_single_click) # Rapp double click event self._widget.rapp_grid.doubleClicked.connect(self._rapp_double_click) def _init_variables(self): self.initialised = False #init self._qt_rapp_manager_info = QtRappManagerInfo(self._refresh_rapps) self._update_rapps_signal.connect(self._update_rapp_list) self._rapp_view_model = QStandardItemModel() self._widget.rapp_grid.setModel(self._rapp_view_model) self._widget.rapp_grid.setWrapping(True) self._widget.rapp_grid.setIconSize(QSize(60, 60)) self._widget.rapp_grid.setSpacing(10) self._selected_rapp = None def spin(self): self._get_appmanager_namespaces() def _cleanup_rapps(self): self._rapp_view_model.clear() pass def _get_appmanager_namespaces(self): namespaces = self._qt_rapp_manager_info._get_namespaces() for namespace in namespaces: ns = namespace[:namespace.find('list_rapps')] self._widget.namespace_cbox.addItem(ns) def _exit(self): pass ################################################################### # Events ################################################################### def _change_namespace(self, event): self._qt_rapp_manager_info.select_rapp_manager( self._widget.namespace_cbox.currentText()) def _refresh_rapps(self): """ Updates from qt_app_manager_info. """ self._update_rapps_signal.emit() def _update_rapp_list(self): """ Rapp manager namespace event """ self._cleanup_rapps() available_rapps = self._qt_rapp_manager_info.get_available_rapps() running_rapps = self._qt_rapp_manager_info.get_running_rapps() rapp_items = [] for r, v in available_rapps.items(): if r in running_rapps.keys(): item = QRappItem(v, running=True) else: item = QRappItem(v, running=False) self._rapp_view_model.appendRow(item) def _rapp_single_click(self, index): qrapp = self._rapp_view_model.item(index.row()) rapp = qrapp.getRapp() self._create_rapp_dialog(rapp) def _create_rapp_dialog(self, rapp): is_running = self._qt_rapp_manager_info.is_running_rapp(rapp) self._selected_rapp = rapp self._dialog = QtRappDialog(self._widget, rapp, self._qt_rapp_manager_info.start_rapp, self._qt_rapp_manager_info.stop_rapp, is_running) self._dialog.show() def _rapp_double_click(self, item): running_rapps = self._qt_rapp_manager_info.get_running_rapps() if len(running_rapps) > 0: names = [r['display_name'] for r in running_rapps.values()] show_message(self._widget, "Error", "Rapp %s are already running" % names) else: self._start_rapp() def _start_rapp(self): result = self._qt_rapp_manager_info.start_rapp( self._selected_rapp['name'], [], self._selected_rapp['public_parameters']) show_message(self._widget, str(result.started), result.message) self._selected_rapp = None return result def _stop_rapp(self): result = self._qt_rapp_manager_info.stop_rapp() show_message(self._widget, str(result.stopped), result.message) self._selected_rapp = None return result ######################################## # Legacy ######################################## def _select_rapp_tree_item(self, item): if not item in self.rapps.keys(): print "HAS NO KEY" else: self.current_rapp = self.rapps[item] self._widget.rapp_info_text.clear() rapp_info = self.qt_rapp_manager_info._get_rapp_info(self.current_rapp) self._widget.rapp_info_text.appendHtml(rapp_info) self._update_rapp_parameter_layout(self.current_rapp) self._update_implementation_tree(self.current_rapp) def _update_rapp_parameter_layout(self, rapp): parameters_layout = self._widget.rapp_parameter_layout clear_layout(parameters_layout) for param in rapp['public_parameters']: one_param_layout = create_label_textedit_pair( param.key, param.value) parameters_layout.addLayout(one_param_layout) def _update_implementation_tree(self, rapp): self._widget.implementation_tree_widget.clear() self.selected_impl = None self.impls = {} for impl in rapp['implementations']: impl_item = QTreeWidgetItem( self._widget.implementation_tree_widget) impl_item.setText(0, impl) self.impls[impl_item] = impl def _select_implementation_tree_item(self, item): if not item in self.impls.keys(): print "HAS NO KEY" else: self.selected_impl = self.impls[item] def _get_public_parameters(self): public_parameters = {} parameters_layout = self._widget.rapp_parameter_layout for i in reversed(range(parameters_layout.count())): item = parameters_layout.itemAt(i) key_label = item.itemAt(0).widget() value_textbox = item.itemAt(1).widget() public_parameters[key_label.text()] = str( value_textbox.toPlainText()) return public_parameters
class RqtSrVisualServoing(Plugin): def __init__(self, context): super(RqtSrVisualServoing, self).__init__(context) # Give QObjects reasonable names self.setObjectName('RqtSrVisualServoing') # # Process standalone plugin command-line arguments # from argparse import ArgumentParser # parser = ArgumentParser() # # Add argument(s) to the parser. # parser.add_argument("-q", "--quiet", action="store_true", # dest="quiet", # help="Put plugin in silent mode") # args, unknowns = parser.parse_known_args(context.argv()) # if not args.quiet: # print 'arguments: ', args # print 'unknowns: ', unknowns # Create QWidget self.ui = QWidget() # Get path to UI file which is in our packages resource directory rp = rospkg.RosPack() self.ui_file = os.path.join(rp.get_path(PKG), 'resource', 'gui.ui') # Extend the widget with all attributes and children from UI file loadUi(self.ui_file, self.ui) # Give QObjects reasonable names self.ui.setObjectName('RqtSrVisualServoingUi') # Show ui.windowTitle on left-top of each plugin (when it's set in ui). # This is useful when you open multiple plugins at once. Also if you # open multiple instances of your plugin at once, these lines add # number to make it easy to tell from pane to pane. if context.serial_number() > 1: self.ui.setWindowTitle(self.ui.windowTitle() + (' (%d)' % context.serial_number())) # Wire up the buttons self.ui.startBtn.clicked.connect( self.start_clicked ) self.ui.stopBtn.clicked.connect( self.stop_clicked ) # Annoyingly setting the icon theme in designer only works in designer, # we have to set it again here for it to work at run time self.ui.startBtn.setIcon(QIcon.fromTheme('media-playback-start')) self.ui.stopBtn.setIcon(QIcon.fromTheme('media-playback-stop')) # Add widget to the user interface context.add_widget(self.ui) # Setup a model for the feedback and link to the view. self.feedback_model = QStandardItemModel(0,2) self.feedback_model.setHorizontalHeaderLabels(['Name','Value']) self.ui.feedbackView.setModel(self.feedback_model) self.ui.connect( self.ui, SIGNAL('feedback(QString)'), self.update_feedback ) # ROS setup self.last_feedback = None self.client = actionlib.SimpleActionClient('visual_servo', VisualServoingAction) msg = "" if self.client.wait_for_server(rospy.Duration(2.0)): msg = "Found action server, servoing appears to be running" rospy.loginfo(msg) else: msg = "Can't find action server, servoing not running" rospy.logerr(msg) self.ui.statusValue.setText(msg) def shutdown_plugin(self): # TODO unregister all publishers here pass def save_settings(self, plugin_settings, instance_settings): # TODO save intrinsic configuration, usually using: # instance_settings.set_value(k, v) pass def restore_settings(self, plugin_settings, instance_settings): # TODO restore intrinsic configuration, usually using: # v = instance_settings.value(k) pass #def trigger_configuration(self): # Comment in to signal that the plugin has a way to configure it # Usually used to open a configuration dialog def start_clicked(self): goal = VisualServoingActionGoal() self.client.send_goal(goal, feedback_cb = self._feedback_cb) self.ui.statusValue.setText("Starting") def stop_clicked(self): self.client.cancel_all_goals() self.ui.statusValue.setText("Stopped") self.feedback_model.clear() def _feedback_cb(self, feedback): # We can't update the UI in this thread so stash the data and emit a # signal that can be traped by the main thread and update the ui. self.last_feedback = feedback self.ui.emit( SIGNAL('feedback(QString)'), "" ) def update_feedback(self, data): """Listen for feedback signals and update the interface.""" fb = self.last_feedback self.ui.statusValue.setText(str(self.client.get_goal_status_text())) # Update the feedback model, which triggers the view to update m = self.feedback_model m.setHorizontalHeaderLabels(['Name','Value']) m.setItem(0,0,QStandardItem('distance')) m.setItem(0,1,QStandardItem(str(fb.distance))) m.setItem(1,0,QStandardItem('object_pose.position.x')) m.setItem(1,1,QStandardItem(str(fb.object_pose.position.x))) m.setItem(2,0,QStandardItem('object_pose.position.y')) m.setItem(2,1,QStandardItem(str(fb.object_pose.position.y))) m.setItem(3,0,QStandardItem('object_pose.position.z')) m.setItem(3,1,QStandardItem(str(fb.object_pose.position.z))) m.setItem(4,0,QStandardItem('object_pose.orientation.x')) m.setItem(4,1,QStandardItem(str(fb.object_pose.orientation.x))) m.setItem(5,0,QStandardItem('object_pose.orientation.y')) m.setItem(5,1,QStandardItem(str(fb.object_pose.orientation.y))) m.setItem(6,0,QStandardItem('object_pose.orientation.z')) m.setItem(6,1,QStandardItem(str(fb.object_pose.orientation.z))) m.setItem(7,0,QStandardItem('object_pose.orientation.w')) m.setItem(7,1,QStandardItem(str(fb.object_pose.orientation.w))) m.setItem(8,0,QStandardItem('grasp_pose.position.x')) m.setItem(8,1,QStandardItem(str(fb.grasp_pose.position.x))) m.setItem(9,0,QStandardItem('grasp_pose.position.y')) m.setItem(9,1,QStandardItem(str(fb.grasp_pose.position.y))) m.setItem(10,0,QStandardItem('grasp_pose.position.z')) m.setItem(10,1,QStandardItem(str(fb.grasp_pose.position.z))) m.setItem(11,0,QStandardItem('grasp_pose.orientation.x')) m.setItem(11,1,QStandardItem(str(fb.grasp_pose.orientation.x))) m.setItem(12,0,QStandardItem('grasp_pose.orientation.y')) m.setItem(12,1,QStandardItem(str(fb.grasp_pose.orientation.y))) m.setItem(13,0,QStandardItem('grasp_pose.orientation.z')) m.setItem(13,1,QStandardItem(str(fb.grasp_pose.orientation.z))) m.setItem(14,0,QStandardItem('grasp_pose.orientation.w')) m.setItem(14,1,QStandardItem(str(fb.grasp_pose.orientation.w))) self.ui.feedbackView.resizeColumnsToContents()
class LaunchWidget(QDialog): # To be connected to PluginContainerWidget sig_sysmsg = Signal(str) def __init__(self, parent): """ @type parent: LaunchMain """ super(LaunchWidget, self).__init__() self._parent = parent self._config = None self._settings = QSettings('ros', 'rqt_launch') self._settings.sync() self._package_update = False self._launchfile_update = False # TODO: should be configurable from gui self._port_roscore = 11311 self._run_id = None self._rospack = rospkg.RosPack() ui_file = os.path.join( self._rospack.get_path('rqt_launch'), 'resource', 'launch_widget.ui' ) loadUi(ui_file, self) # row=0 allows any number of rows to be added. self._datamodel = QStandardItemModel(0, 1) master_uri = rosenv.get_master_uri() rospy.loginfo('LaunchWidget master_uri={}'.format(master_uri)) self._delegate = NodeDelegate(master_uri, self._rospack) self._treeview.setModel(self._datamodel) self._treeview.setItemDelegate(self._delegate) # NodeController used in controller class for launch operation. self._node_controllers = [] self._pushbutton_load_params.clicked.connect(self._parent.load_params) self._pushbutton_start_all.clicked.connect(self._parent.start_all) self._pushbutton_stop_all.clicked.connect(self._parent.stop_all) self._pushbutton_refresh.clicked.connect( self._update_pkgs_contain_launchfiles ) # Bind package selection with .launch file selection. self._combobox_pkg.currentIndexChanged[str].connect( self._refresh_launchfiles ) # Bind a launch file selection with launch GUI generation. self._combobox_launchfile_name.currentIndexChanged[str].connect( self._load_launchfile_slot ) self._update_pkgs_contain_launchfiles() def _load_launchfile_slot(self, launchfile_name): # Not yet sure why, but everytime combobox.currentIndexChanged occurs, # this method gets called twice with launchfile_name=='' in 1st call. if ( launchfile_name is None or launchfile_name == '' or self._launchfile_update ): return self._settings.setValue('launchfile_name', launchfile_name) self._settings.sync() _config = None rospy.logdebug( '_load_launchfile_slot launchfile_name={}'.format(launchfile_name) ) try: _config = self._create_launchconfig( launchfile_name, self._port_roscore ) # TODO: folder_name_launchfile should be able to specify arbitrarily # _create_launchconfig takes 3rd arg for it. except IndexError as e: msg = 'IndexError={} launchfile={}'.format(e, launchfile_name) rospy.logerr(msg) self.sig_sysmsg.emit(msg) return except RLException as e: msg = 'RLException={} launchfile={}'.format(e, launchfile_name) rospy.logerr(msg) self.sig_sysmsg.emit(msg) return self._create_widgets_for_launchfile(_config) def _create_launchconfig( self, launchfile_name, port_roscore, folder_name_launchfile='launch' ): """ @rtype: ROSLaunchConfig @raises RLException: raised by roslaunch.config.load_config_default. """ pkg_name = self._combobox_pkg.currentText() try: launchfile = os.path.join( self._rospack.get_path(pkg_name), folder_name_launchfile, launchfile_name, ) except IndexError as e: raise RLException('IndexError: {}'.format(e)) try: launch_config = roslaunch.config.load_config_default( [launchfile], port_roscore ) except rospkg.common.ResourceNotFound as e: raise RLException('ResourceNotFound: {}'.format(e)) except RLException as e: raise e return launch_config def _create_widgets_for_launchfile(self, config): self._config = config # Delete old nodes' GUIs. del self._node_controllers[:] self._delegate.clear_node_widgets() # reset the data model self._datamodel.clear() self._datamodel.setColumnCount(1) self._datamodel.setRowCount(len(self._config.nodes)) # Loop per xml element for i, node in enumerate(self._config.nodes): proxy = NodeProxy(self._run_id, self._config.master.uri, node) nodewidget_index = self._datamodel.index(i, 0, QModelIndex()) node_widget = self._delegate.create_node_widget( nodewidget_index, proxy.config, StatusIndicator() ) # TODO: use tree view delegates correctly instead of # empty QStandardItemModel self._datamodel.setItem(i, QStandardItem()) self._treeview.setIndexWidget(nodewidget_index, node_widget) node_controller = NodeController(proxy, node_widget) self._node_controllers.append(node_controller) rospy.logdebug( 'loop #%d proxy.config.namespace=%s ' + 'proxy.config.name=%s', i, proxy.config.namespace, proxy.config.name, ) self._parent.set_node_controllers(self._node_controllers) def _update_pkgs_contain_launchfiles(self): """ Inspired by rqt_msg.MessageWidget._update_pkgs_contain_launchfiles """ self._package_update = True packages = sorted( [ pkg_tuple[0] for pkg_tuple in RqtRoscommUtil.iterate_packages('launch') ] ) self._package_list = packages rospy.logdebug('pkgs={}'.format(self._package_list)) previous_package = self._settings.value('package', '') self._combobox_pkg.clear() self._combobox_pkg.addItems(self._package_list) if previous_package in self._package_list: index = self._package_list.index(previous_package) else: index = 0 self._package_update = False self._combobox_pkg.setCurrentIndex(index) def _refresh_launchfiles(self, package=None): """ Inspired by rqt_msg.MessageWidget._refresh_msgs """ if package is None or len(package) == 0 or self._package_update: return self._launchfile_update = True previous_launchfile = self._settings.value('launchfile_name', '') self._settings.setValue('package', package) self._settings.sync() self._launchfile_instances = [] # Launch[] # TODO: RqtRoscommUtil.list_files's 2nd arg 'subdir' should NOT be # hardcoded later. _launch_instance_list = RqtRoscommUtil.list_files(package, 'launch') rospy.logdebug( '_refresh_launches package={} instance_list={}'.format( package, _launch_instance_list ) ) self._launchfile_instances = [ x.split('/')[1] for x in _launch_instance_list ] self._combobox_launchfile_name.clear() self._combobox_launchfile_name.addItems(self._launchfile_instances) if previous_launchfile in self._launchfile_instances: index = self._launchfile_instances.index(previous_launchfile) else: index = 0 self._launchfile_update = False self._combobox_launchfile_name.setCurrentIndex(index) def load_parameters(self): """ Loads all global parameters into roscore. """ run_id = ( self._run_id if self._run_id is not None else roslaunch.rlutil.get_or_generate_uuid(None, True) ) runner = roslaunch.ROSLaunchRunner(run_id, self._config) runner._load_parameters() msg = 'Loaded %d parameters to parameter server.' % len( self._config.params ) self.sig_sysmsg.emit(msg) rospy.logdebug(msg) def save_settings(self, plugin_settings, instance_settings): # instance_settings.set_value('splitter', self._splitter.saveState()) pass def restore_settings(self, plugin_settings, instance_settings): # if instance_settings.contains('splitter'): # self._splitter.restoreState(instance_settings.value('splitter')) # else: # self._splitter.setSizes([100, 100, 200]) pass
class HandEyeCalibration(Plugin): PLUGIN_TITLE = ' Intel OTC Robotics: Hand-Eye Calibration' def __init__(self, context): super(HandEyeCalibration, self).__init__(context) self.context = context self.node = context.node self.widget = QWidget() self.widget.setObjectName(self.PLUGIN_TITLE) self.widget.setWindowTitle(self.PLUGIN_TITLE) # Data self.Tsamples = [] # Toolbar _, path_pkg = get_resource('packages', 'handeye_dashboard') print("{}".format(path_pkg)) self.snapshot_action = QAction(QIcon.fromTheme('camera-photo'), 'Take a snapshot', self.widget) path = path_pkg + '/share/handeye_dashboard/images/capture.png' self.calibrate_action = QAction(QIcon(QPixmap.fromImage(QImage(path))), 'Get the camera/robot transform', self.widget) self.clear_action = QAction(QIcon.fromTheme('edit-clear'), 'Clear the record data.', self.widget) path = path_pkg + '/share/handeye_dashboard/images/UR5.png' self.execut_action = QAction(QIcon(QPixmap.fromImage(QImage(path))), 'EStart the publishing the TF.', self.widget) self.toolbar = QToolBar() self.toolbar.addAction(self.snapshot_action) self.toolbar.addAction(self.calibrate_action) self.toolbar.addAction(self.clear_action) self.toolbar.addAction(self.execut_action) # Toolbar0 self.l0 = QLabel(self.widget) self.l0.setText("Camera-Mount-Type: ") self.l0.setFixedWidth(150) self.l0.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) self.combobox = QComboBox(self.widget) self.combobox.addItem('attached on robot') self.combobox.addItem('fixed beside robot') self.toolbar0 = QToolBar() self.toolbar0.addWidget(self.l0) self.toolbar0.addWidget(self.combobox) # Toolbar1 self.l1 = QLabel(self.widget) self.l1.setText("Camera-Frame: ") self.l1.setFixedWidth(150) self.l1.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) self.camera_frame = QLineEdit(self.widget) self.camera_frame.setText("camera_link") self.toolbar1 = QToolBar() self.toolbar1.addWidget(self.l1) self.toolbar1.addWidget(self.camera_frame) # Toolbar2 self.l2 = QLabel(self.widget) self.l2.setText("Object-Frame: ") self.l2.setFixedWidth(150) self.l2.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) self.object_frame = QLineEdit(self.widget) self.object_frame.setText("calib_board") self.toolbar2 = QToolBar() self.toolbar2.addWidget(self.l2) self.toolbar2.addWidget(self.object_frame) # Toolbar3 self.l3 = QLabel(self.widget) self.l3.setText("Robot-Base-Frame: ") self.l3.setFixedWidth(150) self.l3.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) self.base_frame = QLineEdit(self.widget) self.base_frame.setText("base") self.toolbar3 = QToolBar() self.toolbar3.addWidget(self.l3) self.toolbar3.addWidget(self.base_frame) # Toolbar4 self.l4 = QLabel(self.widget) self.l4.setText("End-Effector-Frame: ") self.l4.setFixedWidth(150) self.l4.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) self.endeffector_frame = QLineEdit(self.widget) self.endeffector_frame.setText("tool0") self.toolbar4 = QToolBar() self.toolbar4.addWidget(self.l4) self.toolbar4.addWidget(self.endeffector_frame) # Toolbar5 self.l5 = QLabel(self.widget) self.l5.setText("Sample-Number: ") self.l5.setFixedWidth(150) self.l5.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) self.le5 = QLineEdit(self.widget) self.le5.setValidator(QIntValidator()) self.le5.setText('10') self.le5.setReadOnly(True) self.toolbar5 = QToolBar() self.toolbar5.addWidget(self.l5) self.toolbar5.addWidget(self.le5) # TreeView self.treeview = QTreeView() self.treeview.setAlternatingRowColors(True) self.model = QStandardItemModel(self.treeview) self.treeview.setModel(self.model) self.treeview.setHeaderHidden(True) # TextEdit self.textedit = QTextEdit(self.widget) self.textedit.setReadOnly(True) # Layout self.layout = QVBoxLayout() self.layout.addWidget(self.toolbar0) self.layout.addWidget(self.toolbar1) self.layout.addWidget(self.toolbar2) self.layout.addWidget(self.toolbar3) self.layout.addWidget(self.toolbar4) self.layout.addWidget(self.toolbar5) self.layout.addWidget(self.toolbar) self.layoutH = QHBoxLayout() self.layoutH.addWidget(self.treeview) self.layoutH.addWidget(self.textedit) self.layout.addLayout(self.layoutH) self.widget.setLayout(self.layout) # Add the widget to the user interface if context.serial_number() > 1: self.widget.setWindowTitle(self.widget.windowTitle() + (' (%d)' % context.serial_number())) context.add_widget(self.widget) # Make the connections self.snapshot_action.triggered.connect(self.take_snapshot) self.calibrate_action.triggered.connect(self.calibration) self.clear_action.triggered.connect(self.clear) self.execut_action.triggered.connect(self.execution) # Package path self.path_pkg = path_pkg # Set up TF self.cli = self.node.create_client(HandeyeTF, 'handeye_tf_service') while not self.cli.wait_for_service(timeout_sec=1.0): self.node.get_logger().info( 'service not available, waiting again...') self.req = HandeyeTF.Request() def clear(self): # >>> Clear the recorded samples self.textedit.append('Clearing the recorded data ...') self.textedit.clear() self.Tsamples = [] self.model.clear() def get_tf_transform(self, frame_id, child_frame_id): self.req.transform.header.frame_id = frame_id self.req.transform.child_frame_id = child_frame_id self.req.publish.data = False future = self.cli.call_async(self.req) rclpy.spin_until_future_complete(self.node, future) transform = TransformStamped() try: result = future.result() except Exception as e: self.node.get_logger().info('Service call failed %r' % (e, )) else: transform = result.tf_lookup_result return transform def publish_tf_transform(self, transform_to_publish): self.req.publish.data = True self.req.transform = transform_to_publish future = self.cli.call_async(self.req) rclpy.spin_until_future_complete(self.node, future) try: future.result() except Exception as e: self.node.get_logger().info('Service call failed %r' % (e, )) else: self.node.get_logger().info( 'Send the camera-robot transform :\n\tfrom `{}` to `{}`.'. format(self.req.transform.header.frame_id, self.req.transform.child_frame_id)) def take_snapshot(self): # >>> Take the snapshot self.textedit.append('Taking snapshot ...') # Get the transform from `tool0` to `base_link` T = self.get_tf_transform(self.base_frame.text(), self.endeffector_frame.text()) bTe = np.zeros((4, 4)) q = [ T.transform.rotation.w, T.transform.rotation.x, T.transform.rotation.y, T.transform.rotation.z ] bTe = br.quaternion.to_transform(q) bTe[:3, 3] = np.array([ T.transform.translation.x, T.transform.translation.y, T.transform.translation.z ]) self.textedit.append('Lookup transform: from `{}` to `{}`.'.format( self.base_frame.text(), self.endeffector_frame.text())) self.node.get_logger().info(bcolors.OKGREEN + 'bTe:' + bcolors.ENDC + '\n{}'.format(bTe)) # Get the transform from `calib_board` to `camera_link` T = self.get_tf_transform(self.camera_frame.text(), self.object_frame.text()) cTo = np.zeros((4, 4)) q = [ T.transform.rotation.w, T.transform.rotation.x, T.transform.rotation.y, T.transform.rotation.z ] cTo = br.quaternion.to_transform(q) cTo[:3, 3] = np.array([ T.transform.translation.x, T.transform.translation.y, T.transform.translation.z ]) self.textedit.append('Lookup transform: from `{}` to `{}`.'.format( self.camera_frame.text(), self.object_frame.text())) self.node.get_logger().info(bcolors.OKGREEN + 'cTo:' + bcolors.ENDC + '\n{}'.format(cTo)) parent = QStandardItem('Snapshot {}'.format(len(self.Tsamples))) child_1 = QStandardItem('bTe:\n{}\n{}\n{}\n{}'.format( bTe[0, :], bTe[1, :], bTe[2, :], bTe[3, :])) child_2 = QStandardItem('cTo:\n{}\n{}\n{}\n{}'.format( cTo[0, :], cTo[1, :], cTo[2, :], cTo[3, :])) parent.appendRow(child_1) parent.appendRow(child_2) self.model.appendRow(parent) self.Tsamples.append((bTe, cTo)) self.le5.setText(str(len(self.Tsamples))) def calibration(self): # >>> Compute the calibration self.textedit.append('Making the calibration ...') if len(self.Tsamples) == 0: self.textedit.append( 'No transform recorded, please take snapshots.') return # save samples to `dataset.json` file save_samples_to_file(self.Tsamples) import handeye if self.combobox.currentIndex() == 0: solver_cri = handeye.calibrator.HandEyeCalibrator(setup='Moving') if self.combobox.currentIndex() == 1: solver_cri = handeye.calibrator.HandEyeCalibrator(setup='Fixed') for sample in self.Tsamples: solver_cri.add_sample(sample[0], sample[1]) try: bTc = solver_cri.solve(method=handeye.solver.Daniilidis1999) # save the calibration result to 'camera-robot.json' file file_output = '/tmp/' + 'camera-robot.json' with open(file_output, 'w') as f: json.dump(bTc.tolist(), f) except Exception: self.textedit.append("Failed to solve the hand-eye calibration.") def execution(self): # >>> Publish the camera-robot transform self.textedit.append('Publishing the camera TF ...') file_input = '/tmp/' + 'camera-robot.json' with open(file_input, 'r') as f: datastore = json.load(f) to_frame = self.camera_frame.text() if self.combobox.currentIndex() == 0: from_frame = self.endeffector_frame.text() if self.combobox.currentIndex() == 1: from_frame = self.base_frame.text() bTc = np.array(datastore) static_transformStamped = TransformStamped() static_transformStamped.header.stamp = ROSClock().now().to_msg() static_transformStamped.header.frame_id = from_frame static_transformStamped.child_frame_id = to_frame static_transformStamped.transform.translation.x = bTc[0, 3] static_transformStamped.transform.translation.y = bTc[1, 3] static_transformStamped.transform.translation.z = bTc[2, 3] q = br.transform.to_quaternion(bTc) static_transformStamped.transform.rotation.x = q[1] static_transformStamped.transform.rotation.y = q[2] static_transformStamped.transform.rotation.z = q[3] static_transformStamped.transform.rotation.w = q[0] self.publish_tf_transform(static_transformStamped) output_string = "camera-robot pose:\n" output_string += " Translation: [{}, {}, {}]\n".format( bTc[0, 3], bTc[1, 3], bTc[2, 3]) output_string += " Rotation: in Quaternion [{}, {}, {}, {}]".format( q[0], q[1], q[2], q[3]) file_path = '/tmp/' + 'camera-robot.txt' with open(file_path, 'w') as f: f.write(output_string) def shutdown_plugin(self): """ Unregister subscribers when the plugin shutdown """ pass def save_settings(self, plugin_settings, instance_settings): # Nothing to be done here pass def restore_settings(self, plugin_settings, instance_settings): # Nothing to be done here pass
class OutputDialog(QDialog): def __init__(self, mode, dotgraph, dotcode_factory, dotparser, param_manager, models_desc_file_path, nodename='', parent=None): super(OutputDialog, self).__init__(parent) if mode not in ['add', 'edit']: raise Exception('Wrong mode for Ouput Dialog') self.setWindowTitle("Add a output pipe") self.setGeometry(300, 300, 450, 250) # self._widget = QDialog() ui_file = os.path.join(rospkg.RosPack().get_path('rqt_vino_plugin'), 'resource', 'add_output_dialog.ui') loadUi(ui_file, self) self.dotgraph = dotgraph self.dotparser = dotparser self.dotcode_factory = dotcode_factory self.available_infers_list = param_manager.parse_inferlist_file( models_desc_file_path) self.connect_from_listmodel = QStandardItemModel() self.output_type_combobox.currentTextChanged.connect( self._update_display) self.update_output_types() if mode == 'add': self.buttonBox.accepted.connect(self._create_node_in_graph) elif mode == 'edit': self.nodename = nodename self.buttonBox.accepted.connect(self._edit_node_in_graph) def update_output_types(self, outputlist_file=None): self.output_type_combobox.clear() self.output_type_combobox.addItem('ImageWindow') self.output_type_combobox.addItem('Rviz') self.output_type_combobox.addItem('Rostopic') def get_infer_desc(self, infer_name): for infer in self.available_infers_list: if infer['infer_name'] == infer_name: return infer def _update_display(self, output_name): self.output_name_display_lineEdit.setText(output_name) self.update_connect_to_list(output_name) def update_connect_to_list(self, output_name): self.connect_from_listmodel.clear() for node in self.dotgraph.get_node_list(): if node.get('nodetype') == 'input': pass elif node.get('nodetype') == 'output': pass elif node.get('nodetype') == 'infer': if output_name in self.get_infer_desc( node.get_name())['connect_to']: pipeline_item = QStandardItem() pipeline_item.setText(node.get_name()) pipeline_item.setCheckable(True) pipeline_item.setCheckState(False) self.connect_from_listmodel.appendRow(pipeline_item) self.connect_from_listview.setModel(self.connect_from_listmodel) def _create_node_in_graph(self): output_node_name = str(self.output_type_combobox.currentText()) #Create Node self.dotparser.parse_dotgraph_from_outputs(self.dotcode_factory, self.dotgraph, [output_node_name]) #Create connection from input node to infer node for i in range(self.connect_from_listview.model().rowCount()): node_name = self.connect_from_listmodel.item(i).text() check_state = self.connect_from_listmodel.item(i).checkState() if check_state: connects = {node_name: [output_node_name]} self.dotparser.parse_dotgraph_from_connects( self.dotcode_factory, self.dotgraph, connects) def _edit_node_in_graph(self): self.dotcode_factory.del_node_from_graph(self.dotgraph, self.nodename) self._create_node_in_graph()
class VinoGraph(Plugin): def __init__(self, context): super(VinoGraph, self).__init__(context) # Give QObjects reasonable names self.setObjectName('VinoGraph') # Process standalone plugin command-line arguments from argparse import ArgumentParser parser = ArgumentParser() # Add argument(s) to the parser. parser.add_argument("-q", "--quiet", action="store_true", dest="quiet", help="Put plugin in silent mode") args, unknowns = parser.parse_known_args(context.argv()) if not args.quiet: print 'arguments: ', args print 'unknowns: ', unknowns # Create QWidget self._widget = QWidget() # Get path to UI file which should be in the "resource" folder of this package ui_file = os.path.join(rospkg.RosPack().get_path('rqt_vino_plugin'), 'resource', 'rqt_vino_plugin.ui') # Extend the widget with all attributes and children from UI file loadUi(ui_file, self._widget, {'InteractiveGraphicsView': InteractiveGraphicsView}) # Give QObjects reasonable names self._widget.setObjectName('VinoGraphUi') # Show _widget.windowTitle on left-top of each plugin (when # it's set in _widget). This is useful when you open multiple QListView # plugins at once. Also if you open multiple instances of your # plugin at once, these lines add number to make it easy to # tell from pane to pane. if context.serial_number() > 1: self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number())) #A dict which stores pipeline name and dotgraph pair self._dotgraphs = dict() #which dotgraph currently drawing on the scence self._current_dotcode = None self._current_pipeline_name = '' #Pydot self.dotcode_factory = VinoPydotFactory() self.dot_to_qt = DotToQtGenerator() self.param_manager = ParamManagerWrapper() #Binding scene canvas self._scene = QGraphicsScene() self._scene.setBackgroundBrush(Qt.white) self._widget.graphics_view.setScene(self._scene) self._widget.graphics_view.setClickNodeCallback(self._edit_node) #QListview of pipelines self._listmodel = QStandardItemModel() self._widget.pipeline_name_listview.clicked.connect( self._display_choosed_pipeline) #self._widget.pipeline_name_listview.itemRenamed.connect(self._rename_pipeline) #Load pipelines from yaml file self._widget.load_pipeline_push_button.clicked.connect( self._load_pipeline) #Create a pipeline self._widget.create_pipeline_push_button.clicked.connect( self._create_pipeline) #Add input to pipeline graph self._widget.add_input_push_button.clicked.connect(self._add_input) #Add infer self._widget.add_inference_push_button.clicked.connect(self._add_infer) self._widget.add_output_push_button.clicked.connect(self._add_output) self._widget.save_pipeline_push_button.clicked.connect( self._save_pipeline) self.models_desc_file_path = os.path.join( rospkg.RosPack().get_path('vino_param_lib'), 'param', 'models.yaml') # Add widget to the user interface context.add_widget(self._widget) def shutdown_plugin(self): # TODO unregister all publishers here pass def save_settings(self, plugin_settings, instance_settings): # TODO save intrinsic configuration, usually using: # instance_settings.set_value(k, v) pass def restore_settings(self, plugin_settings, instance_settings): # TODO restore intrinsic configuration, usually using: # v = instance_settings.value(k) pass def _refresh_rosgraph(self): self._widget.pipeline_name_listview.setModel(self._listmodel) self._widget.pipeline_name_listview.show() self._redraw_graph_view() # if not self.initialized: # return # self._update_graph_view(self._generate_dotcode()) def _rename_pipeline(self, item, col): pass def _display_choosed_pipeline(self, index): pipeline_name = self._listmodel.data(index) if pipeline_name in self._dotgraphs.iterkeys(): self._current_pipeline_name = pipeline_name self._refresh_rosgraph() def _load_pipeline(self): self._dotgraphs.clear() self._listmodel.clear() file_path, _ = QFileDialog.getOpenFileName( self._widget, "QFileDialog.getOpenFileName()", "", "*.yaml") self._dotgraphs = generate_dotcode_from_yaml_file( self.dotcode_factory, self.param_manager, str(file_path)) for pipeline_name, pipeline_dotgraph in self._dotgraphs.items(): pipeline_item = QStandardItem() pipeline_item.setText(pipeline_name) self._listmodel.appendRow(pipeline_item) self._current_pipeline_name = self._dotgraphs.iterkeys().next() self._refresh_rosgraph() #self._display_choosed_pipeline(pipeline_item) def _redraw_graph_view(self): self._scene.clear() if self._dotgraphs == None: return self._current_dotcode = self.dotcode_factory.create_dot( self._dotgraphs[self._current_pipeline_name]) highlight_level = 2 (nodes, edges) = self.dot_to_qt.dotcode_to_qt_items( self._current_dotcode, highlight_level=highlight_level, same_label_siblings=False, scene=self._scene) self._scene.setSceneRect(self._scene.itemsBoundingRect()) #if self._widget.auto_fit_graph_check_box.isChecked(): self._fit_in_view() def _add_input(self): dlg = InputDialog('add', self._dotgraphs[self._current_pipeline_name], self.dotcode_factory, dotparser, self.param_manager, parent=self._widget) dlg.exec_() self._refresh_rosgraph() def _add_infer(self): if self._current_pipeline_name == '': return dlg = InferenceDialog('add', self._dotgraphs[self._current_pipeline_name], self.dotcode_factory, dotparser, self.param_manager, self.models_desc_file_path, parent=self._widget) dlg.exec_() self._refresh_rosgraph() def _edit_node(self, nodename=''): if self._current_pipeline_name == '': return selected_node = self._dotgraphs[self._current_pipeline_name].get_node( nodename.encode('utf-8'))[0] selected_node_type = selected_node.get('nodetype') if selected_node_type == 'input': self._edit_input(nodename) elif selected_node_type == 'infer': self._edit_infer(nodename) elif selected_node_type == 'output': self._edit_output(nodename) def _edit_infer(self, nodename=''): dlg = InferenceDialog('edit', self._dotgraphs[self._current_pipeline_name], self.dotcode_factory, dotparser, self.param_manager, self.models_desc_file_path, nodename=nodename, parent=self._widget) dlg.exec_() self._refresh_rosgraph() def _edit_input(self, nodename=''): dlg = InputDialog('edit', self._dotgraphs[self._current_pipeline_name], self.dotcode_factory, dotparser, self.param_manager, nodename=nodename, parent=self._widget) dlg.exec_() self._refresh_rosgraph() def _edit_output(self, nodename=''): dlg = OutputDialog('edit', self._dotgraphs[self._current_pipeline_name], self.dotcode_factory, dotparser, self.param_manager, models_desc_file_path=self.models_desc_file_path, nodename=nodename, parent=self._widget) dlg.exec_() self._refresh_rosgraph() def _add_output(self): dlg = OutputDialog('add', self._dotgraphs[self._current_pipeline_name], self.dotcode_factory, dotparser, self.param_manager, models_desc_file_path=self.models_desc_file_path, parent=self._widget) dlg.exec_() self._refresh_rosgraph() def _fit_in_view(self): self._widget.graphics_view.fitInView(self._scene.itemsBoundingRect(), Qt.KeepAspectRatio) def _create_pipeline(self): pipeline_name, okPressed = QInputDialog.getText( self._widget, "Create a new pipeline", "new pipeline name:", QLineEdit.Normal, "") if okPressed != True or pipeline_name == '': #Empty name return pipeline_item = QStandardItem() pipeline_item.setText(pipeline_name) self._listmodel.appendRow(pipeline_item) new_dotgraph = dotparser.generate_dotcode_from_empty( self.dotcode_factory, pipeline_name) self._dotgraphs.update(new_dotgraph) self._current_pipeline_name = pipeline_name self._refresh_rosgraph() def _save_pipeline(self): # print(self._dotgraphs ) self.dotcode_factory.parse_nodes( self._dotgraphs[self._current_pipeline_name])
class InferenceDialog(QDialog): def __init__(self, mode, dotgraph , dotcode_factory, dotparser , param_manager, models_desc_file_path, nodename='', parent = None): super(InferenceDialog, self).__init__(parent) if mode not in ['add','edit']: raise Exception('wrong mode for InferenceDialog') self.nodename = nodename self.setWindowTitle("Dialog") self.setGeometry(300,300,450,250) # self._widget = QDialog() ui_file = os.path.join(rospkg.RosPack().get_path('rqt_vino_plugin'), 'resource', 'add_inference_dialog.ui') loadUi(ui_file, self) self.dotgraph = dotgraph self.dotparser = dotparser self.dotcode_factory = dotcode_factory self.available_infers_list = param_manager.parse_inferlist_file(models_desc_file_path) # self.node_name_lineedit.textEdited.connect(self._edit_infer_name) #self.node_infer_name_combobox.activated.connect(self.update_infer_names) self.node_infer_combobox.currentTextChanged.connect(self._select_infer_type) self.connect_from_listmodel = QStandardItemModel()#(self.connect_from_listview) self.connect_to_listmodel = QStandardItemModel()#(self.connect_to_listview) # self.connect_from_listmodel.itemChanged.connect(self._connect_from_changed) #self.connect_to_listview.setModel(self.connect_to_listmodel) self.update_infer_names() self.update_connect_from_list() if mode == 'add': self.buttonBox.accepted.connect(self._create_node_in_graph) elif mode == 'edit': self.buttonBox.accepted.connect(self._edit_node_in_graph) def update_infer_names(self): self.node_infer_combobox.clear() for infer in self.available_infers_list: self.node_infer_combobox.addItem(infer['infer_name']) def get_infer_desc(self,infer_name): for infer in self.available_infers_list: if infer['infer_name'] == infer_name: return infer def get_model_desc(self,infer_name,model_type): for model_desc in self.get_infer_desc(infer_name)['available_models']: if model_desc['name'] == model_type: return model_desc def update_connect_to_list(self, infer_name): self.connect_from_listmodel.clear() self.connect_to_listmodel.clear() for node in self.dotgraph.get_node_list(): pipeline_item = QStandardItem() pipeline_item.setText(node.get_name()) pipeline_item.setCheckable(True) pipeline_item.setCheckState(False) if node.get_name() == infer_name.encode('utf-8'): continue if node.get('nodetype') == 'input': self.connect_from_listmodel.appendRow(pipeline_item) # self.connect_from_listview.addItem(node.get_name()) elif node.get('nodetype') == 'output': self.connect_to_listmodel.appendRow(pipeline_item) # self.connect_to_listview.addItem(node.get_name()) elif node.get('nodetype') == 'infer': if infer_name in self.get_infer_desc(infer_name)['connect_from']: self.connect_from_listmodel.appendRow(pipeline_item) elif infer_name in self.get_infer_desc(infer_name)['connect_to']: self.connect_to_listmodel.appendRow(pipeline_item) self.connect_from_listview.setModel(self.connect_from_listmodel) self.connect_to_listview.setModel(self.connect_to_listmodel) def update_connect_from_list(self): pass def update_model_type(self,infer_name): self.node_model_combobox.clear() # infer = self.get_infer_desc(infer_name) for model in self.get_infer_desc(infer_name)['available_models']: self.node_model_combobox.addItem(model['name']) # self.node_model_combobox.addItem("MobileNetSSD") # self.node_model_combobox.addItem("YoloV2") def _select_infer_type(self,infer_name): self.node_engine_combobox.clear() infer_name = str(infer_name) for infer in self.available_infers_list: if infer['infer_name'] == infer_name: for engine in infer['available_engine']: self.node_engine_combobox.addItem(engine) self.update_model_type(infer_name) self.update_connect_to_list(infer_name) def _edit_infer_name(self,infer_name): self.node_name_display_lineEdit.setText(infer_name) def _create_node_in_graph(self): infer_node_name = str(self.node_infer_combobox.currentText()) print('ava_list',self.available_infers_list) infer_node_engine = self.node_engine_combobox.currentText() print(self.get_infer_desc(infer_node_name)) infer_node_model_type = self.node_model_combobox.currentText() infer_node_label = self.get_model_desc(infer_node_name, infer_node_model_type)['label'] infer_node_model = self.get_model_desc(infer_node_name, infer_node_model_type)['model'] #Create Node self.dotparser.parse_dotgraph_from_infers(self.dotcode_factory,self.dotgraph, [{'name': infer_node_name, 'engine':infer_node_engine, 'model':infer_node_model, 'label':infer_node_label, 'batch':1}]) #To-do Implement batch configure #Create connection from input node to infer node for i in range(self.connect_from_listview.model().rowCount()): node_name = self.connect_from_listmodel.item(i).text() check_state = self.connect_from_listmodel.item(i).checkState() if check_state: connects = { node_name: [infer_node_name] } self.dotparser.parse_dotgraph_from_connects(self.dotcode_factory,self.dotgraph,connects) #Create connection from infer node to output node for i in range(self.connect_to_listview.model().rowCount()): node_name = self.connect_to_listmodel.item(i).text() check_state = self.connect_to_listmodel.item(i).checkState() if check_state: connects = { infer_node_name :[node_name] } self.dotparser.parse_dotgraph_from_connects(self.dotcode_factory,self.dotgraph,connects) def _edit_node_in_graph(self): self.dotcode_factory.del_node_from_graph(self.dotgraph,self.nodename) self._create_node_in_graph()
class InteractionsChooserUI(): def __init__(self, rocon_master_uri='localhost', host_name='localhost', with_rqt=False): self.with_rqt = with_rqt self.widget = QWidget() self.pairings_view_model = QStandardItemModel() self.interactions_view_model = QStandardItemModel() self.interactions_remocon = InteractionsRemocon( rocon_master_uri, host_name) self.interactions_remocon.connect( [self.update_group_combobox, self.refresh_grids]) self.interactions_remocon.connect( [self.update_pairings_group_combobox, self.refresh_grids]) self.default_group = "All" rospack = rospkg.RosPack() ui_file = os.path.join(rospack.get_path('rocon_remocon'), 'ui', 'interactions_chooser.ui') loadUi(ui_file, self.widget, {}) # create a few directories for caching icons and ... utils.setup_home_dirs() self._init_ui() self._init_events() def shutdown(self): self.interactions_remocon.shutdown() @Slot() def update_group_combobox(self): """ The underyling ros part of the remocon might get fresh data about the group list. Connect to this slot to update the combobox in the ui. """ new_groups = copy.copy( self.interactions_remocon.interactions_table.groups()) new_groups = [g for g in new_groups if g != "Hidden"] # did the underlying groups change - if so, update the combobox current_group = self.widget.interactions_group_combobox.currentText() current_size = self.widget.interactions_group_combobox.count() target_group = current_group if current_size != 1 else self.default_group current_group_list = [ self.widget.interactions_group_combobox.itemText(i) for i in range(self.widget.interactions_group_combobox.count()) ] if set(current_group_list) != set(['All'] + new_groups): self.widget.interactions_group_combobox.clear() self.widget.interactions_group_combobox.addItems(['All'] + new_groups) index = self.widget.interactions_group_combobox.findText( target_group) if index != -1: self.widget.interactions_group_combobox.setCurrentIndex(index) self.refresh_grids() @Slot() def update_pairings_group_combobox(self): """ The underyling ros part of the remocon might get fresh data about the group list. Connect to this slot to update the combobox in the ui. """ new_groups = copy.copy( self.interactions_remocon.pairings_table.groups()) # did the underlying groups change - if so, update the combobox current_group = self.widget.pairings_group_combobox.currentText() current_size = self.widget.pairings_group_combobox.count() target_group = current_group if current_size != 1 else self.default_pairings_group current_group_list = [ self.widget.pairings_group_combobox.itemText(i) for i in range(self.widget.pairings_group_combobox.count()) ] if set(current_group_list) != set(['All'] + new_groups): self.widget.pairings_group_combobox.clear() self.widget.pairings_group_combobox.addItems(['All'] + new_groups) index = self.widget.pairings_group_combobox.findText(target_group) if index != -1: self.widget.pairings_group_combobox.setCurrentIndex(index) self.refresh_grids() @Slot() def refresh_grids(self): """ This just does a complete redraw of the interactions with the currently selected role. It's a bit brute force doing this every time the interactions' 'state' changes, but this suffices for now. """ self.pairings_view_model.clear() self.interactions_view_model.clear() active_pairing = copy.copy(self.interactions_remocon.active_pairing) group = self.widget.pairings_group_combobox.currentText() for p in self.interactions_remocon.pairings_table.sorted(): if group != "All" and p.group != group: continue is_running = False enabled = False if active_pairing is not None and p.name == active_pairing.name: is_running = True enabled = True elif active_pairing is None: enabled = True # enabled = not p.requires_interaction item = icon.QModelIconItem(p, enabled=enabled, running=is_running) self.pairings_view_model.appendRow(item) group = self.widget.interactions_group_combobox.currentText() for i in self.interactions_remocon.interactions_table.sorted(): if group != "All" and i.group != group: continue if i.hidden: continue extra_tooltip_info = "" if i.required_pairings: extra_tooltip_info += " Requires " for required_pairing in i.required_pairings: extra_tooltip_info += "'" + required_pairing + "', " extra_tooltip_info = extra_tooltip_info.rstrip(', ') if not i.bringup_pairing: extra_tooltip_info += " to be running" extra_tooltip_info += "." item = icon.QModelIconItem( i, enabled=self._is_interaction_enabled(i), running=self._is_interaction_running(i), extended_tooltip_info=extra_tooltip_info) self.interactions_view_model.appendRow(item) def _init_ui(self): self.widget.pairings_grid.setViewMode(QListView.IconMode) self.widget.pairings_grid.setModel(self.pairings_view_model) self.widget.pairings_grid.setWordWrap(True) self.widget.pairings_grid.setWrapping(True) # really need to get away from listview, or subclass it if we want to control better how many lines of text show up # self.widget.pairings_grid.setTextElideMode(Qt.ElideNone) self.widget.pairings_grid.setIconSize(QSize(60, 60)) self.widget.pairings_grid.setSpacing(10) self.widget.interactions_grid.setViewMode(QListView.IconMode) self.widget.interactions_grid.setModel(self.interactions_view_model) self.widget.interactions_grid.setWordWrap(True) self.widget.interactions_grid.setWrapping(True) self.widget.interactions_grid.setIconSize(QSize(60, 60)) self.widget.interactions_grid.setSpacing(10) for ns in self.interactions_remocon.namespaces: self.widget.namespace_checkbox.addItem(ns) self.refresh_grids() self.widget.pairings_group_combobox.addItems( ['All'] + self.interactions_remocon.pairings_table.groups()) self.widget.interactions_group_combobox.addItems( ['All'] + self.interactions_remocon.interactions_table.groups()) # TODO namespace checkbox to self.interactions_remocon.active_namespace ############################################################################## # Private ############################################################################## def _init_events(self): self.widget.namespace_checkbox.currentIndexChanged.connect( self._event_change_namespace) self.widget.pairings_grid.clicked.connect(self._pairing_single_click) self.widget.interactions_grid.clicked.connect( self._interaction_single_click) self.widget.button_stop_all_interactions.clicked.connect( self.interactions_remocon.stop_all_interactions) self.widget.pairings_group_combobox.currentIndexChanged.connect( self.refresh_grids) self.widget.interactions_group_combobox.currentIndexChanged.connect( self.refresh_grids) def _event_change_namespace(self): rospy.logwarn( "Remocon : changing interaction managers is currently not supported." ) def _pairing_single_click(self, index): pairing_item = self.pairings_view_model.item(index.row()) pairing = pairing_item.implementation self._create_pairing_dialog(pairing) def _create_pairing_dialog(self, pairing): active_pairing = copy.copy(self.interactions_remocon.active_pairing) if active_pairing is not None: is_running = (active_pairing.name == pairing.name) is_enabled = is_running else: is_enabled = True # not pairing.requires_interaction is_running = False self.selected_pairing = pairing self.dialog = PairingDialog(self.widget, pairing, self.interactions_remocon.start_pairing, self.interactions_remocon.stop_pairing, is_enabled, is_running) self.dialog.show() def _interaction_single_click(self, index): interaction_item = self.interactions_view_model.item(index.row()) interaction = interaction_item.implementation self._create_interaction_dialog(interaction) def _create_interaction_dialog(self, interaction): self.selected_interaction = interaction self.dialog = InteractionDialog( self.widget, interaction, self.interactions_remocon.start_interaction, self.interactions_remocon.stop_interaction, self._is_interaction_enabled(interaction), self._is_interaction_running(interaction), self._is_interaction_permitted_new_launches(interaction)) self.dialog.show() def _is_interaction_permitted_new_launches(self, interaction): current_number_of_launches = len( self.interactions_remocon.launched_interactions.get_launch_details( interaction.hash)) if interaction.max == -1 or current_number_of_launches < interaction.max: return True else: return False def _is_interaction_enabled(self, interaction): active_pairing = copy.copy(self.interactions_remocon.active_pairing) enabled = True if interaction.required_pairings: if active_pairing and active_pairing.name not in interaction.required_pairings: enabled = False elif active_pairing is None: if not interaction.bringup_pairing: enabled = False else: available_required_pairings = [ name for name in interaction.required_pairings if self.interactions_remocon.pairings_table.find(name) is not None ] if not available_required_pairings: enabled = False return enabled def _is_interaction_running(self, interaction): return True if self.interactions_remocon.launched_interactions.get_launch_details( interaction.hash) else False