def __init__(self, timeline, parent, topic): MessageView.__init__(self, timeline, topic) self._parent = parent self._stamp = None self._name = parent.objectName() self.toolbar = QToolBar() self._first_action = QAction(QIcon.fromTheme('go-first'), '', self.toolbar) self._first_action.triggered.connect(self.navigate_first) self.toolbar.addAction(self._first_action) self._prev_action = QAction(QIcon.fromTheme('go-previous'), '', self.toolbar) self._prev_action.triggered.connect(self.navigate_previous) self.toolbar.addAction(self._prev_action) self._next_action = QAction(QIcon.fromTheme('go-next'), '', self.toolbar) self._next_action.triggered.connect(self.navigate_next) self.toolbar.addAction(self._next_action) self._last_action = QAction(QIcon.fromTheme('go-last'), '', self.toolbar) self._last_action.triggered.connect(self.navigate_last) self.toolbar.addAction(self._last_action) parent.layout().addWidget(self.toolbar)
def _define_menu(self): self._menu = QMenu(self._gl_view) layer_menu = QMenu('Layers', self._gl_view) for layer in self._mapDrawer.get_layers(): layer_menu.addAction(layer.menu_action) self._menu.addMenu(layer_menu) self._menu.addSeparator() self._view2dAction = QAction(self._gl_view.tr("2D View"), self._gl_view, triggered=self._set_default_view) self._menu.addAction(self._view2dAction) self._view3dAction = QAction(self._gl_view.tr("3D View"), self._gl_view, triggered=self._set_3d_view) self._menu.addAction(self._view3dAction) self._menu.addSeparator() #self._rotateSubAction = QAction(self._gl_view.tr("Rotate with Sub"), self, checkable=True, # triggered=self._rotate_with_sub) #self._menu.addAction(self._rotateSubAction) self._lockOnSubAction = QAction(self._gl_view.tr("Lock on Sub"), self, checkable=True, triggered=self._lock_on_sub) self._menu.addAction(self._lockOnSubAction) self._menu.addSeparator() self._menu.addAction(QAction('Reset Path', self, triggered=self._mapDrawer.reset_path)) setLocationAction = QAction('Set Location Target', self._gl_view, triggered=self._set_location_action) self._menu.addAction(setLocationAction)
class Layer: def __init__(self, name,parent_widget): self._name = name self._is_active = True self.menu_action = QAction(self._name, parent_widget, triggered=self.toggle_active, checkable=True, checked=True) def toggle_active(self): self._is_active = self._is_active != True def set_active(self,active): self._is_active = active def draw(self): if self._is_active: self._draw() # Overrided by chlidren class def _draw(self): None def save_settings(self, plugin_settings, instance_settings): instance_settings.set_value(self._name + '_is_active', str(self._is_active)) #view_matrix_string = repr(self._gl_view.get_view_matrix()) #instance_settings.set_value('view_matrix', view_matrix_string) def restore_settings(self, plugin_settings, instance_settings): is_active = instance_settings.value(self._name + '_is_active') if is_active is not None : self._is_active = is_active == 'True' self.menu_action.setChecked(self._is_active)
def _rightclick_menu(self, event): """ :type event: QEvent """ # QTreeview.selectedIndexes() returns 0 when no node is selected. # This can happen when after booting no left-click has been made yet # (ie. looks like right-click doesn't count). These lines are the # workaround for that problem. selected = self._messages_tree.selectedIndexes() if len(selected) == 0: return menu = QMenu() text_action = QAction(self.tr('View Text'), menu) menu.addAction(text_action) raw_action = QAction(self.tr('View Raw'), menu) menu.addAction(raw_action) remove_action = QAction(self.tr('Remove message'), menu) menu.addAction(remove_action) action = menu.exec_(event.globalPos()) if action == raw_action or action == text_action: rospy.logdebug('_rightclick_menu selected={}'.format(selected)) selected_type = selected[1].data() if selected_type[-2:] == '[]': selected_type = selected_type[:-2] browsetext = None try: if (self._mode == rosmsg.MODE_MSG or self._mode == rosaction.MODE_ACTION): browsetext = rosmsg.get_msg_text(selected_type, action == raw_action) elif self._mode == rosmsg.MODE_SRV: browsetext = rosmsg.get_srv_text(selected_type, action == raw_action) else: raise except rosmsg.ROSMsgException as e: QMessageBox.warning( self, self.tr('Warning'), self.tr('The selected item component ' + 'does not have text to view.')) if browsetext is not None: self._browsers.append( TextBrowseDialog(browsetext, self._rospack)) self._browsers[-1].show() if action == remove_action: self._messages_tree.model().removeRow(selected[0].row())
def _add_perspective_action(self, name): if self._menu_manager is not None: # create action action = QAction(name, self._menu_manager.menu) action.setCheckable(True) self._perspective_mapper.setMapping(action, name) action.triggered.connect(self._perspective_mapper.map) # add action to menu self._menu_manager.add_item(action) # enable remove-action if self._menu_manager.count_items() > 1: self._remove_action.setEnabled(True)
def _update_remove_topic_menu(self): def make_remove_topic_function(x): return lambda: self.remove_topic(x) self._remove_topic_menu.clear() for topic_name in sorted(self._rosdata.keys()): action = QAction(topic_name, self._remove_topic_menu) action.triggered.connect(make_remove_topic_function(topic_name)) self._remove_topic_menu.addAction(action) if len(self._rosdata) > 1: all_action = QAction('All', self._remove_topic_menu) all_action.triggered.connect(self.clean_up_subscribers) self._remove_topic_menu.addAction(all_action)
def __init__(self, parent=None): super(PublisherTreeWidget, self).__init__(parent) self.setModel(PublisherTreeModel(self)) self._action_remove_publisher = QAction(QIcon.fromTheme('list-remove'), 'Remove Selected', self) self._action_remove_publisher.triggered[bool].connect( self._handle_action_remove_publisher) self._action_publish_once = QAction( QIcon.fromTheme('media-playback-start'), 'Publish Selected Once', self) self._action_publish_once.triggered[bool].connect( self._handle_action_publish_once) self.setItemDelegateForColumn( self.model()._column_index['rate'], SpinBoxDelegate(min_value=0, max_value=1000000, decimals=2))
def _define_menu(self): self._menu = QMenu(self.imageFrame) configureAction = QAction("Configure ...", self.imageFrame, triggered=self.configure_filterchain_action) self._menu.addAction(configureAction) self._menu.addSeparator() self._recordAction = QAction("Record ...", self.imageFrame, triggered=self.record_execution_action) self._menu.addAction(self._recordAction) self._stop_recordAction = QAction("Stop Record", self.imageFrame, triggered=self.stop_record_execution_action) self._stop_recordAction.setEnabled(False) self._menu.addAction(self._stop_recordAction) self._menu.addSeparator() deleteAction = QAction(self.imageFrame.tr("Delete this execution"), self.imageFrame, triggered=self.delete_current_execution) self._menu.addAction(deleteAction)
def add_plugin_prefix(self, plugin_descriptor): action_attributes = plugin_descriptor.action_attributes() action = QAction(action_attributes['label'], self._plugin_menu_manager.menu) self._enrich_action(action, action_attributes) self._plugin_mapper.setMapping(action, plugin_descriptor.plugin_id()) action.triggered.connect(self._plugin_mapper.map) self._plugin_menu_manager.add_prefix(action)
def __init__(self, parent=None): super(LaunchTreeWidget, self).__init__(parent) self.setModel(LaunchTreeModel(self)) self._action_remove_publisher = QAction(QIcon.fromTheme('remove'), 'Remove Selected', self) self._action_remove_publisher.triggered[bool].connect( self._handle_action_remove_publisher)
def _gl_view_mouseReleaseEvent(self, event): if event.button() == Qt.RightButton: menu = QMenu(self._gl_view) action = QAction(self._gl_view.tr("Reset view"), self._gl_view) menu.addAction(action) action.triggered.connect(self._set_default_view) menu.exec_(self._gl_view.mapToGlobal(event.pos()))
def __init__(self, menu_bar, plugin_manager): super(PluginMenu, self).__init__() self.setObjectName('PluginMenu') plugin_menu = menu_bar.addMenu(menu_bar.tr('&Plugins')) running_menu = menu_bar.addMenu(menu_bar.tr('&Running')) self._plugin_menu_manager = MenuManager(plugin_menu) self._plugin_mapper = QSignalMapper(plugin_menu) self._plugin_mapper.mapped[str].connect(self.load_plugin_signal) self._running_menu_manager = MenuManager(running_menu) action = QAction(' Hidden action to work around QTBUG-52582', self._running_menu_manager.menu) action.setVisible(False) self._running_menu_manager.add_item(action) self._running_mapper = QSignalMapper(running_menu) self._running_mapper.mapped[str].connect(self.unload_plugin_signal) self._instances = {}
def addDockWidget(self, dock_widget): # remove action for same dock widget if exists self.removeDockWidget(dock_widget) icon = dock_widget.windowIcon() if icon.isNull(): icon = QIcon.fromTheme('folder') title = dock_widget.windowTitle() action = QAction(icon, title, self) # truncate label if necessary if len(title) > MinimizedDockWidgetsToolbar.max_label_length: action.setToolTip(title) action.setIconText(title[0:MinimizedDockWidgetsToolbar.max_label_length] + '...') self._signal_mapper.setMapping(action, dock_widget) action.triggered.connect(self._signal_mapper.map) self._dock_widgets[dock_widget] = action self.addAction(action) self.show()
def __init__(self, parent=None): super(MessageTreeWidget, self).__init__(parent) self.setDragEnabled(True) self.sortByColumn(0, Qt.AscendingOrder) try: setSectionResizeMode = self.header().setSectionResizeMode # Qt5 except AttributeError: setSectionResizeMode = self.header().setResizeMode # Qt4 setSectionResizeMode(QHeaderView.ResizeToContents) self.header().setContextMenuPolicy(Qt.CustomContextMenu) self.header().customContextMenuRequested.connect( self.handle_header_view_customContextMenuRequested) self._action_item_expand = QAction(QIcon.fromTheme('zoom-in'), 'Expand Selected', self) self._action_item_expand.triggered.connect(self._handle_action_item_expand) self._action_item_collapse = QAction(QIcon.fromTheme('zoom-out'), 'Collapse Selected', self) self._action_item_collapse.triggered.connect(self._handle_action_item_collapse) self.customContextMenuRequested.connect(self.handle_customContextMenuRequested)
def add_instance(self, plugin_descriptor, instance_id): action_attributes = plugin_descriptor.action_attributes() action = QAction(self._get_instance_label(str(instance_id)), self._running_menu_manager.menu) base_path = plugin_descriptor.attributes().get('plugin_path') self._enrich_action(action, action_attributes, base_path) self._running_mapper.setMapping(action, str(instance_id)) action.triggered.connect(self._running_mapper.map) self._running_menu_manager.add_item(action) self._instances[instance_id] = action
def _update_remove_topic_menu(self): def make_remove_topic_function(x): return lambda: self.remove_topic(x) self._remove_topic_menu.clear() for topic_name in sorted(self._rosdata.keys()): action = QAction(topic_name, self._remove_topic_menu) action.triggered.connect(make_remove_topic_function(topic_name)) self._remove_topic_menu.addAction(action) self.remove_topic_button.setMenu(self._remove_topic_menu)
def add_menu_item(self, menu_name, labels): menu_text = menu_name if self.label[labels[0]].isHidden(): menu_text = "Show " + menu_text else: menu_text = "Hide " + menu_text action = QAction(menu_text, self._widget) action.triggered.connect(partial(self.toggle_labels, labels)) self._widget.addAction(action) self.actions[menu_name] = action
def add_plugin(self, plugin_descriptor): base_path = plugin_descriptor.attributes().get('plugin_path') menu_manager = self._plugin_menu_manager # create submenus for group in plugin_descriptor.groups(): label = group['label'] if menu_manager.contains_menu(label): submenu = menu_manager.get_menu(label) else: submenu = QMenu(label, menu_manager.menu) menu_action = submenu.menuAction() self._enrich_action(menu_action, group, base_path) menu_manager.add_item(submenu) menu_manager = MenuManager(submenu) # create action action_attributes = plugin_descriptor.action_attributes() action = QAction(action_attributes['label'], menu_manager.menu) self._enrich_action(action, action_attributes, base_path) self._plugin_mapper.setMapping(action, plugin_descriptor.plugin_id()) action.triggered.connect(self._plugin_mapper.map) not_available = plugin_descriptor.attributes().get('not_available') if not_available: action.setEnabled(False) action.setStatusTip( self.tr('Plugin is not available: %s') % not_available) # add action to menu menu_manager.add_item(action)
def add_instance(self, plugin_descriptor, instance_id): action_attributes = plugin_descriptor.action_attributes() # create action label = self.tr('Close:') + ' ' + action_attributes['label'] if instance_id.serial_number != 1: label = label + ' (%s)' % str(instance_id.serial_number) action = QAction(label, self._running_menu_manager.menu) base_path = plugin_descriptor.attributes().get('plugin_path') self._enrich_action(action, action_attributes, base_path) self._running_mapper.setMapping(action, str(instance_id)) action.triggered.connect(self._running_mapper.map) self._running_menu_manager.add_item(action) self._instances[instance_id] = action
def addDockWidget(self, dock_widget): # remove action for same dock widget if exists self.removeDockWidget(dock_widget) icon = dock_widget.windowIcon() if icon.isNull(): icon = QIcon.fromTheme('folder') title = dock_widget.windowTitle() action = QAction(icon, title, self) # truncate label if necessary if len(title) > MinimizedDockWidgetsToolbar.max_label_length: action.setToolTip(title) action.setIconText( title[0:MinimizedDockWidgetsToolbar.max_label_length] + '...') self._signal_mapper.setMapping(action, dock_widget) action.triggered.connect(self._signal_mapper.map) self._dock_widgets[dock_widget] = action self.addAction(action) self.show()
class VisionMainWidget(QWidget): def __init__(self): super(VisionMainWidget, self).__init__() try: rospy.wait_for_service('/provider_vision/get_information_list', timeout=2) rospy.wait_for_service('/provider_vision/get_filterchain_from_execution', timeout=2) rospy.wait_for_service('/provider_vision/get_media_from_execution', timeout=2) rospy.wait_for_service('/provider_vision/execute_cmd', timeout=2) except rospy.ROSException: rospy.loginfo('Services unavailable') ui_file = os.path.join(rospkg.RosPack().get_path('rqt_vision'), 'resource', 'mainwidget.ui') loadUi(ui_file, self) self.setWindowTitle('Vision UI') self._current_execution = None self._current_execution_subscriber = None self._current_execution_subscriber_result = None self._is_recording = False self._video_writer = None self._cv_image = None self._widget = None self.bridge = CvBridge() self.connect(self, SIGNAL("result_change(QString)"), self._handle_result) ##### Service self._srv_get_information_list = rospy.ServiceProxy('/provider_vision/get_information_list', get_information_list) self._srv_get_filterchain_from_execution = rospy.ServiceProxy('/provider_vision/get_filterchain_from_execution', get_filterchain_from_execution) self._srv_get_media_from_execution = rospy.ServiceProxy('/provider_vision/get_media_from_execution', get_media_from_execution) self._srv_execute_cmd = rospy.ServiceProxy('/provider_vision/execute_cmd', execute_cmd) ### self.image_frame_mouse_release_event_original = self.imageFrame.mouseReleaseEvent self.imageFrame.mouseReleaseEvent = self.image_frame_mouse_release_event self.refresh_button.clicked[bool].connect(self.fill_execution_list) self.current_execution.currentIndexChanged[int].connect(self.current_execution_index_changed) self.image_paint_event_original = self.imageFrame.paintEvent self.imageFrame.paintEvent = self.image_paint_event self.fill_execution_list() self._define_menu() def _define_menu(self): self._menu = QMenu(self.imageFrame) configureAction = QAction("Configure ...", self.imageFrame, triggered=self.configure_filterchain_action) self._menu.addAction(configureAction) self._menu.addSeparator() self._recordAction = QAction("Record ...", self.imageFrame, triggered=self.record_execution_action) self._menu.addAction(self._recordAction) self._stop_recordAction = QAction("Stop Record", self.imageFrame, triggered=self.stop_record_execution_action) self._stop_recordAction.setEnabled(False) self._menu.addAction(self._stop_recordAction) self._menu.addSeparator() deleteAction = QAction(self.imageFrame.tr("Delete this execution"), self.imageFrame, triggered=self.delete_current_execution) self._menu.addAction(deleteAction) def fill_execution_list(self): self._refresh_clean() self._current_execution = None self.current_execution.clear() execution_string = self._srv_get_information_list(1) execution_list = execution_string.list.split(';') if len(execution_list) == 0: return self._current_execution = execution_list[0] for execution in execution_list: if len(execution) > 0: self.current_execution.addItem(execution) def current_execution_index_changed(self, index): self._refresh_clean() if self._current_execution_subscriber is not None: self._current_execution_subscriber.unregister() self._current_execution_subscriber_result.unregister() new_execution = self.current_execution.itemText(index) self._current_execution = new_execution self._filterchain = self._srv_get_filterchain_from_execution(self._current_execution) self._current_execution_subscriber = rospy.Subscriber('/provider_vision/' + new_execution + '_image', SensorImage, self.current_execution_callback) self._current_execution_subscriber_result = rospy.Subscriber('/provider_vision/' + new_execution + '_result', VisionTarget, self.current_execution_result_callback) def _refresh_clean(self): if self._current_execution_subscriber is not None: self._current_execution_subscriber.unregister() self._current_execution_subscriber_result.unregister() self.result_text.setText('') self._cv_image = None self.imageFrame.update() def current_execution_callback(self, img): try: cv_image = self.bridge.imgmsg_to_cv2(img, desired_encoding="rgb8") height, width, channel = cv_image.shape bytesPerLine = 3 * width self._cv_image = QImage(cv_image.data, width, height, bytesPerLine, QImage.Format_RGB888) if self._is_recording: if self._video_writer is None: four_cc = cv2.cv.CV_FOURCC(*'HFYU') self._video_writer = cv2.VideoWriter(self._recording_file_name, four_cc, float(15), (width,height)) temp = cv2.cvtColor(cv_image,cv2.cv.CV_BGR2RGB) self._video_writer.write(temp) except CvBridgeError as e: print(e) self.imageFrame.update() def image_paint_event(self, data): self.image_paint_event_original(data) if self._cv_image is None: return; img = self._cv_image painter = QPainter(self.imageFrame) painter.drawImage(data.rect(), img) painter.end() def current_execution_result_callback(self, visionTarget): result = 'X:{}, Y:{}, width:{:.2f}, height:{:.2f}, angle:{:.2f}, desc_1:{}, desc_2:{}'.format(visionTarget.x, visionTarget.y, visionTarget.width, visionTarget.height, visionTarget.angle, visionTarget.desc_1, visionTarget.desc_2) self.emit(SIGNAL("result_change(QString)"), result) def _handle_result(self,result): self.result_text.setText(result) def image_frame_mouse_release_event(self,event): if event.button() == Qt.RightButton: self._menu.exec_(self.imageFrame.mapToGlobal(event.pos())) def delete_current_execution(self): if self._current_execution is None : return media = self._srv_get_media_from_execution(self._current_execution) self._srv_execute_cmd(self._current_execution,self._filterchain.list,media.list,2) self.fill_execution_list() def configure_filterchain_action(self): self._widget = ConfigureFilterchainWidget(self._current_execution, self._filterchain) self._widget.show() def shutdown_plugin(self): if self._current_execution_subscriber is not None: self._current_execution_subscriber.unregister() self._current_execution_subscriber_result.unregister() if self._widget is not None: self._widget.close() def record_execution_action(self): Tk().withdraw() filename = asksaveasfilename(defaultextension='.avi') self._recording_file_name = filename if len(self._recording_file_name) > 0: self._is_recording = True self._recordAction.setEnabled(False) self._stop_recordAction.setEnabled(True) def stop_record_execution_action(self): self._is_recording = False time.sleep(0.1) self._video_writer.release() self._video_writer = None self._recordAction.setEnabled(True) self._stop_recordAction.setEnabled(False)
def main(self, argv=None, standalone=None, plugin_argument_provider=None, plugin_manager_settings_prefix=''): if argv is None: argv = sys.argv # extract --args and everything behind manually since argparse can not handle that arguments = argv[1:] # extract plugin specific args when not being invoked in standalone mode programmatically if not standalone: plugin_args = [] if '--args' in arguments: index = arguments.index('--args') plugin_args = arguments[index + 1:] arguments = arguments[0:index + 1] parser = ArgumentParser(os.path.basename(Main.main_filename), add_help=False) self.add_arguments(parser, standalone=bool(standalone), plugin_argument_provider=plugin_argument_provider) self._options = parser.parse_args(arguments) self._options.multi_process = False # not supported anymore if standalone: # rerun parsing to separate common arguments from plugin specific arguments parser = ArgumentParser(os.path.basename(Main.main_filename), add_help=False) self.add_arguments(parser, standalone=bool(standalone)) self._options, plugin_args = parser.parse_known_args(arguments) self._options.plugin_args = plugin_args # set default values for options not available in standalone mode if standalone: self._options.freeze_layout = False self._options.lock_perspective = False self._options.hide_title = False self._options.perspective = None self._options.perspective_file = None self._options.standalone_plugin = standalone self._options.list_perspectives = False self._options.list_plugins = False self._options.command_pid = None self._options.command_start_plugin = None self._options.command_switch_perspective = None self._options.embed_plugin = None self._options.embed_plugin_serial = None self._options.embed_plugin_address = None # check option dependencies try: if self._options.plugin_args and not self._options.standalone_plugin and not self._options.command_start_plugin and not self._options.embed_plugin: raise RuntimeError( 'Option --args can only be used together with either --standalone, --command-start-plugin or --embed-plugin option' ) if self._options.freeze_layout and not self._options.lock_perspective: raise RuntimeError( 'Option --freeze_layout can only be used together with the --lock_perspective option' ) list_options = (self._options.list_perspectives, self._options.list_plugins) list_options_set = [ opt for opt in list_options if opt is not False ] if len(list_options_set) > 1: raise RuntimeError( 'Only one --list-* option can be used at a time') command_options = (self._options.command_start_plugin, self._options.command_switch_perspective) command_options_set = [ opt for opt in command_options if opt is not None ] if len(command_options_set) > 0 and not self._dbus_available: raise RuntimeError( 'Without DBus support the --command-* options are not available' ) if len(command_options_set) > 1: raise RuntimeError( 'Only one --command-* option can be used at a time (except --command-pid which is optional)' ) if len(command_options_set ) == 0 and self._options.command_pid is not None: raise RuntimeError( 'Option --command_pid can only be used together with an other --command-* option' ) embed_options = (self._options.embed_plugin, self._options.embed_plugin_serial, self._options.embed_plugin_address) embed_options_set = [ opt for opt in embed_options if opt is not None ] if len(command_options_set) > 0 and not self._dbus_available: raise RuntimeError( 'Without DBus support the --embed-* options are not available' ) if len(embed_options_set) > 0 and len(embed_options_set) < len( embed_options): raise RuntimeError( 'Missing option(s) - all \'--embed-*\' options must be set' ) if len(embed_options_set) > 0 and self._options.clear_config: raise RuntimeError( 'Option --clear-config can only be used without any --embed-* option' ) groups = (list_options_set, command_options_set, embed_options_set) groups_set = [opt for opt in groups if len(opt) > 0] if len(groups_set) > 1: raise RuntimeError( 'Options from different groups (--list, --command, --embed) can not be used together' ) perspective_options = (self._options.perspective, self._options.perspective_file) perspective_options_set = [ opt for opt in perspective_options if opt is not None ] if len(perspective_options_set) > 1: raise RuntimeError( 'Only one --perspective-* option can be used at a time') if self._options.perspective_file is not None and not os.path.isfile( self._options.perspective_file): raise RuntimeError( 'Option --perspective-file must reference existing file') except RuntimeError as e: print(str(e)) #parser.parse_args(['--help']) # calling --help will exit return 1 # set implicit option dependencies if self._options.standalone_plugin is not None: self._options.lock_perspective = True # create application context containing various relevant information from .application_context import ApplicationContext context = ApplicationContext() context.qtgui_path = self._qtgui_path context.options = self._options if self._dbus_available: from dbus import DBusException, Interface, SessionBus # non-special applications provide various dbus interfaces if self._dbus_available: context.provide_app_dbus_interfaces = len(groups_set) == 0 context.dbus_base_bus_name = 'org.ros.qt_gui' if context.provide_app_dbus_interfaces: context.dbus_unique_bus_name = context.dbus_base_bus_name + '.pid%d' % os.getpid( ) # provide pid of application via dbus from .application_dbus_interface import ApplicationDBusInterface _dbus_server = ApplicationDBusInterface( context.dbus_base_bus_name) # determine host bus name, either based on pid given on command line or via dbus application interface if any other instance is available if len(command_options_set) > 0 or len(embed_options_set) > 0: host_pid = None if self._options.command_pid is not None: host_pid = self._options.command_pid else: try: remote_object = SessionBus().get_object( context.dbus_base_bus_name, '/Application') except DBusException: pass else: remote_interface = Interface( remote_object, context.dbus_base_bus_name + '.Application') host_pid = remote_interface.get_pid() if host_pid is not None: context.dbus_host_bus_name = context.dbus_base_bus_name + '.pid%d' % host_pid # execute command on host application instance if len(command_options_set) > 0: if self._options.command_start_plugin is not None: try: remote_object = SessionBus().get_object( context.dbus_host_bus_name, '/PluginManager') except DBusException: (rc, msg) = (1, 'unable to communicate with GUI instance "%s"' % context.dbus_host_bus_name) else: remote_interface = Interface( remote_object, context.dbus_base_bus_name + '.PluginManager') (rc, msg) = remote_interface.start_plugin( self._options.command_start_plugin, ' '.join(self._options.plugin_args)) if rc == 0: print('qt_gui_main() started plugin "%s" in GUI "%s"' % (msg, context.dbus_host_bus_name)) else: print( 'qt_gui_main() could not start plugin "%s" in GUI "%s": %s' % (self._options.command_start_plugin, context.dbus_host_bus_name, msg)) return rc elif self._options.command_switch_perspective is not None: remote_object = SessionBus().get_object( context.dbus_host_bus_name, '/PerspectiveManager') remote_interface = Interface( remote_object, context.dbus_base_bus_name + '.PerspectiveManager') remote_interface.switch_perspective( self._options.command_switch_perspective) print( 'qt_gui_main() switched to perspective "%s" in GUI "%s"' % (self._options.command_switch_perspective, context.dbus_host_bus_name)) return 0 raise RuntimeError('Unknown command not handled') # choose selected or default qt binding setattr(sys, 'SELECT_QT_BINDING', self._options.qt_binding) from python_qt_binding import QT_BINDING from python_qt_binding.QtCore import qDebug, qInstallMessageHandler, QSettings, Qt, QtCriticalMsg, QtDebugMsg, QtFatalMsg, QTimer, QtWarningMsg from python_qt_binding.QtGui import QIcon from python_qt_binding.QtWidgets import QAction, QMenuBar from .about_handler import AboutHandler from .composite_plugin_provider import CompositePluginProvider from .container_manager import ContainerManager from .help_provider import HelpProvider from .icon_loader import get_icon from .main_window import MainWindow from .minimized_dock_widgets_toolbar import MinimizedDockWidgetsToolbar from .perspective_manager import PerspectiveManager from .plugin_manager import PluginManager # TODO PySide2 segfaults when invoking this custom message handler atm if QT_BINDING != 'pyside': def message_handler(type_, context, msg): colored_output = 'TERM' in os.environ and 'ANSI_COLORS_DISABLED' not in os.environ cyan_color = '\033[36m' if colored_output else '' red_color = '\033[31m' if colored_output else '' reset_color = '\033[0m' if colored_output else '' if type_ == QtDebugMsg and self._options.verbose: print(msg, file=sys.stderr) elif type_ == QtWarningMsg: print(cyan_color + msg + reset_color, file=sys.stderr) elif type_ == QtCriticalMsg: print(red_color + msg + reset_color, file=sys.stderr) elif type_ == QtFatalMsg: print(red_color + msg + reset_color, file=sys.stderr) sys.exit(1) qInstallMessageHandler(message_handler) app = self.create_application(argv) self._check_icon_theme_compliance() settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'ros.org', self._settings_filename) if len(embed_options_set) == 0: if self._options.clear_config: settings.clear() main_window = MainWindow() if self._options.on_top: main_window.setWindowFlags(Qt.WindowStaysOnTopHint) main_window.statusBar() def sigint_handler(*args): qDebug('\nsigint_handler()') main_window.close() signal.signal(signal.SIGINT, sigint_handler) # the timer enables triggering the sigint_handler timer = QTimer() timer.start(500) timer.timeout.connect(lambda: None) if not self._options.lock_perspective: menu_bar = main_window.menuBar() file_menu = menu_bar.addMenu(menu_bar.tr('&File')) action = QAction(file_menu.tr('&Quit'), file_menu) action.setIcon(QIcon.fromTheme('application-exit')) action.triggered.connect(main_window.close) file_menu.addAction(action) else: menu_bar = None else: app.setQuitOnLastWindowClosed(False) main_window = None menu_bar = None self._add_plugin_providers() # setup plugin manager plugin_provider = CompositePluginProvider(self.plugin_providers) plugin_manager = PluginManager( plugin_provider, settings, context, settings_prefix=plugin_manager_settings_prefix) if self._options.list_plugins: # output available plugins print('\n'.join(sorted(plugin_manager.get_plugins().values()))) return 0 help_provider = HelpProvider() plugin_manager.plugin_help_signal.connect( help_provider.plugin_help_request) # setup perspective manager if main_window is not None: perspective_manager = PerspectiveManager(settings, context) if self._options.list_perspectives: # output available perspectives print('\n'.join(sorted(perspective_manager.perspectives))) return 0 else: perspective_manager = None if main_window is not None: container_manager = ContainerManager(main_window, plugin_manager) plugin_manager.set_main_window(main_window, menu_bar, container_manager) if not self._options.freeze_layout: minimized_dock_widgets_toolbar = MinimizedDockWidgetsToolbar( container_manager, main_window) main_window.addToolBar(Qt.BottomToolBarArea, minimized_dock_widgets_toolbar) plugin_manager.set_minimized_dock_widgets_toolbar( minimized_dock_widgets_toolbar) if menu_bar is not None: perspective_menu = menu_bar.addMenu(menu_bar.tr('P&erspectives')) perspective_manager.set_menu(perspective_menu) # connect various signals and slots if perspective_manager is not None and main_window is not None: # signal changed perspective to update window title perspective_manager.perspective_changed_signal.connect( main_window.perspective_changed) # signal new settings due to changed perspective perspective_manager.save_settings_signal.connect( main_window.save_settings) perspective_manager.restore_settings_signal.connect( main_window.restore_settings) perspective_manager.restore_settings_without_plugin_changes_signal.connect( main_window.restore_settings) if perspective_manager is not None and plugin_manager is not None: perspective_manager.save_settings_signal.connect( plugin_manager.save_settings) plugin_manager.save_settings_completed_signal.connect( perspective_manager.save_settings_completed) perspective_manager.restore_settings_signal.connect( plugin_manager.restore_settings) perspective_manager.restore_settings_without_plugin_changes_signal.connect( plugin_manager.restore_settings_without_plugins) if plugin_manager is not None and main_window is not None: # signal before changing plugins to save window state plugin_manager.plugins_about_to_change_signal.connect( main_window.save_setup) # signal changed plugins to restore window state plugin_manager.plugins_changed_signal.connect( main_window.restore_state) # signal save settings to store plugin setup on close main_window.save_settings_before_close_signal.connect( plugin_manager.close_application) # signal save and shutdown called for all plugins, trigger closing main window again plugin_manager.close_application_signal.connect( main_window.close, type=Qt.QueuedConnection) if main_window is not None and menu_bar is not None: about_handler = AboutHandler(context.qtgui_path, main_window) help_menu = menu_bar.addMenu(menu_bar.tr('&Help')) action = QAction(help_menu.tr('&About'), help_menu) action.setIcon(QIcon.fromTheme('help-about')) action.triggered.connect(about_handler.show) help_menu.addAction(action) # set initial size - only used without saved configuration if main_window is not None: main_window.resize(600, 450) main_window.move(100, 100) # ensure that qt_gui/src is in sys.path src_path = os.path.realpath( os.path.join(os.path.dirname(__file__), '..')) if src_path not in sys.path: sys.path.append(src_path) # load specific plugin plugin = None plugin_serial = None if self._options.embed_plugin is not None: plugin = self._options.embed_plugin plugin_serial = self._options.embed_plugin_serial elif self._options.standalone_plugin is not None: plugin = self._options.standalone_plugin plugin_serial = 0 if plugin is not None: plugins = plugin_manager.find_plugins_by_name(plugin) if len(plugins) == 0: print('qt_gui_main() found no plugin matching "%s"' % plugin) print('try passing the option "--force-discover"') return 1 elif len(plugins) > 1: print( 'qt_gui_main() found multiple plugins matching "%s"\n%s' % (plugin, '\n'.join(plugins.values()))) return 1 plugin = list(plugins.keys())[0] qDebug('QtBindingHelper using %s' % QT_BINDING) plugin_manager.discover() if self._options.reload_import: qDebug( 'ReloadImporter() automatically reload all subsequent imports') from .reload_importer import ReloadImporter _reload_importer = ReloadImporter() self._add_reload_paths(_reload_importer) _reload_importer.enable() # switch perspective if perspective_manager is not None: if plugin: perspective_manager.set_perspective( plugin, hide_and_without_plugin_changes=True) elif self._options.perspective_file: perspective_manager.import_perspective_from_file( self._options.perspective_file, perspective_manager.HIDDEN_PREFIX + os.path.basename(self._options.perspective_file)) else: perspective_manager.set_perspective(self._options.perspective) # load specific plugin if plugin: plugin_manager.load_plugin(plugin, plugin_serial, self._options.plugin_args) running = plugin_manager.is_plugin_running(plugin, plugin_serial) if not running: return 1 if self._options.standalone_plugin: # use icon of standalone plugin (if available) for application plugin_descriptor = plugin_manager.get_plugin_descriptor( plugin) action_attributes = plugin_descriptor.action_attributes() if 'icon' in action_attributes and action_attributes[ 'icon'] is not None: base_path = plugin_descriptor.attributes().get( 'plugin_path') try: icon = get_icon( action_attributes['icon'], action_attributes.get('icontype', None), base_path) except UserWarning: pass else: app.setWindowIcon(icon) if main_window is not None: main_window.show() if sys.platform == 'darwin': main_window.raise_() return app.exec_()
def main(self, argv=None, standalone=None, plugin_argument_provider=None, plugin_manager_settings_prefix=''): if argv is None: argv = sys.argv # extract --args and everything behind manually since argparse can not handle that arguments = argv[1:] # extract plugin specific args when not being invoked in standalone mode programmatically if not standalone: plugin_args = [] if '--args' in arguments: index = arguments.index('--args') plugin_args = arguments[index + 1:] arguments = arguments[0:index + 1] parser = ArgumentParser(os.path.basename(Main.main_filename), add_help=False) self.add_arguments(parser, standalone=bool(standalone), plugin_argument_provider=plugin_argument_provider) self._options = parser.parse_args(arguments) if standalone: # rerun parsing to separate common arguments from plugin specific arguments parser = ArgumentParser(os.path.basename(Main.main_filename), add_help=False) self.add_arguments(parser, standalone=bool(standalone)) self._options, plugin_args = parser.parse_known_args(arguments) self._options.plugin_args = plugin_args # set default values for options not available in standalone mode if standalone: self._options.freeze_layout = False self._options.lock_perspective = False self._options.multi_process = False self._options.perspective = None self._options.perspective_file = None self._options.standalone_plugin = standalone self._options.list_perspectives = False self._options.list_plugins = False self._options.command_pid = None self._options.command_start_plugin = None self._options.command_switch_perspective = None self._options.embed_plugin = None self._options.embed_plugin_serial = None self._options.embed_plugin_address = None # check option dependencies try: if self._options.plugin_args and not self._options.standalone_plugin and not self._options.command_start_plugin and not self._options.embed_plugin: raise RuntimeError('Option --args can only be used together with either --standalone, --command-start-plugin or --embed-plugin option') if self._options.freeze_layout and not self._options.lock_perspective: raise RuntimeError('Option --freeze_layout can only be used together with the --lock_perspective option') list_options = (self._options.list_perspectives, self._options.list_plugins) list_options_set = [opt for opt in list_options if opt is not False] if len(list_options_set) > 1: raise RuntimeError('Only one --list-* option can be used at a time') command_options = (self._options.command_start_plugin, self._options.command_switch_perspective) command_options_set = [opt for opt in command_options if opt is not None] if len(command_options_set) > 0 and not self._dbus_available: raise RuntimeError('Without DBus support the --command-* options are not available') if len(command_options_set) > 1: raise RuntimeError('Only one --command-* option can be used at a time (except --command-pid which is optional)') if len(command_options_set) == 0 and self._options.command_pid is not None: raise RuntimeError('Option --command_pid can only be used together with an other --command-* option') embed_options = (self._options.embed_plugin, self._options.embed_plugin_serial, self._options.embed_plugin_address) embed_options_set = [opt for opt in embed_options if opt is not None] if len(command_options_set) > 0 and not self._dbus_available: raise RuntimeError('Without DBus support the --embed-* options are not available') if len(embed_options_set) > 0 and len(embed_options_set) < len(embed_options): raise RuntimeError('Missing option(s) - all \'--embed-*\' options must be set') if len(embed_options_set) > 0 and self._options.clear_config: raise RuntimeError('Option --clear-config can only be used without any --embed-* option') groups = (list_options_set, command_options_set, embed_options_set) groups_set = [opt for opt in groups if len(opt) > 0] if len(groups_set) > 1: raise RuntimeError('Options from different groups (--list, --command, --embed) can not be used together') perspective_options = (self._options.perspective, self._options.perspective_file) perspective_options_set = [opt for opt in perspective_options if opt is not None] if len(perspective_options_set) > 1: raise RuntimeError('Only one --perspective-* option can be used at a time') if self._options.perspective_file is not None and not os.path.isfile(self._options.perspective_file): raise RuntimeError('Option --perspective-file must reference existing file') except RuntimeError as e: print(str(e)) #parser.parse_args(['--help']) # calling --help will exit return 1 # set implicit option dependencies if self._options.standalone_plugin is not None: self._options.lock_perspective = True # create application context containing various relevant information from .application_context import ApplicationContext context = ApplicationContext() context.qtgui_path = self._qtgui_path context.options = self._options if self._dbus_available: from dbus import DBusException, Interface, SessionBus # non-special applications provide various dbus interfaces if self._dbus_available: context.provide_app_dbus_interfaces = len(groups_set) == 0 context.dbus_base_bus_name = 'org.ros.qt_gui' if context.provide_app_dbus_interfaces: context.dbus_unique_bus_name = context.dbus_base_bus_name + '.pid%d' % os.getpid() # provide pid of application via dbus from .application_dbus_interface import ApplicationDBusInterface _dbus_server = ApplicationDBusInterface(context.dbus_base_bus_name) # determine host bus name, either based on pid given on command line or via dbus application interface if any other instance is available if len(command_options_set) > 0 or len(embed_options_set) > 0: host_pid = None if self._options.command_pid is not None: host_pid = self._options.command_pid else: try: remote_object = SessionBus().get_object(context.dbus_base_bus_name, '/Application') except DBusException: pass else: remote_interface = Interface(remote_object, context.dbus_base_bus_name + '.Application') host_pid = remote_interface.get_pid() if host_pid is not None: context.dbus_host_bus_name = context.dbus_base_bus_name + '.pid%d' % host_pid # execute command on host application instance if len(command_options_set) > 0: if self._options.command_start_plugin is not None: try: remote_object = SessionBus().get_object(context.dbus_host_bus_name, '/PluginManager') except DBusException: (rc, msg) = (1, 'unable to communicate with GUI instance "%s"' % context.dbus_host_bus_name) else: remote_interface = Interface(remote_object, context.dbus_base_bus_name + '.PluginManager') (rc, msg) = remote_interface.start_plugin(self._options.command_start_plugin, ' '.join(self._options.plugin_args)) if rc == 0: print('qt_gui_main() started plugin "%s" in GUI "%s"' % (msg, context.dbus_host_bus_name)) else: print('qt_gui_main() could not start plugin "%s" in GUI "%s": %s' % (self._options.command_start_plugin, context.dbus_host_bus_name, msg)) return rc elif self._options.command_switch_perspective is not None: remote_object = SessionBus().get_object(context.dbus_host_bus_name, '/PerspectiveManager') remote_interface = Interface(remote_object, context.dbus_base_bus_name + '.PerspectiveManager') remote_interface.switch_perspective(self._options.command_switch_perspective) print('qt_gui_main() switched to perspective "%s" in GUI "%s"' % (self._options.command_switch_perspective, context.dbus_host_bus_name)) return 0 raise RuntimeError('Unknown command not handled') # choose selected or default qt binding setattr(sys, 'SELECT_QT_BINDING', self._options.qt_binding) from python_qt_binding import QT_BINDING from python_qt_binding.QtCore import qDebug, qInstallMessageHandler, QSettings, Qt, QtCriticalMsg, QtDebugMsg, QtFatalMsg, QTimer, QtWarningMsg from python_qt_binding.QtGui import QIcon from python_qt_binding.QtWidgets import QAction, QMenuBar from .about_handler import AboutHandler from .composite_plugin_provider import CompositePluginProvider from .container_manager import ContainerManager from .help_provider import HelpProvider from .icon_loader import get_icon from .main_window import MainWindow from .minimized_dock_widgets_toolbar import MinimizedDockWidgetsToolbar from .perspective_manager import PerspectiveManager from .plugin_manager import PluginManager # TODO PySide2 segfaults when invoking this custom message handler atm if QT_BINDING != 'pyside': def message_handler(type_, context, msg): colored_output = 'TERM' in os.environ and 'ANSI_COLORS_DISABLED' not in os.environ cyan_color = '\033[36m' if colored_output else '' red_color = '\033[31m' if colored_output else '' reset_color = '\033[0m' if colored_output else '' if type_ == QtDebugMsg and self._options.verbose: print(msg, file=sys.stderr) elif type_ == QtWarningMsg: print(cyan_color + msg + reset_color, file=sys.stderr) elif type_ == QtCriticalMsg: print(red_color + msg + reset_color, file=sys.stderr) elif type_ == QtFatalMsg: print(red_color + msg + reset_color, file=sys.stderr) sys.exit(1) qInstallMessageHandler(message_handler) app = self.create_application(argv) self._check_icon_theme_compliance() settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'ros.org', self._settings_filename) if len(embed_options_set) == 0: if self._options.clear_config: settings.clear() main_window = MainWindow() if self._options.on_top: main_window.setWindowFlags(Qt.WindowStaysOnTopHint) main_window.statusBar() def sigint_handler(*args): qDebug('\nsigint_handler()') main_window.close() signal.signal(signal.SIGINT, sigint_handler) # the timer enables triggering the sigint_handler timer = QTimer() timer.start(500) timer.timeout.connect(lambda: None) menu_bar = main_window.menuBar() file_menu = menu_bar.addMenu(menu_bar.tr('&File')) action = QAction(file_menu.tr('&Quit'), file_menu) action.setIcon(QIcon.fromTheme('application-exit')) action.triggered.connect(main_window.close) file_menu.addAction(action) else: app.setQuitOnLastWindowClosed(False) main_window = None menu_bar = None self._add_plugin_providers() # setup plugin manager plugin_provider = CompositePluginProvider(self.plugin_providers) plugin_manager = PluginManager(plugin_provider, settings, context, settings_prefix=plugin_manager_settings_prefix) if self._options.list_plugins: # output available plugins print('\n'.join(sorted(plugin_manager.get_plugins().values()))) return 0 help_provider = HelpProvider() plugin_manager.plugin_help_signal.connect(help_provider.plugin_help_request) # setup perspective manager if main_window is not None: perspective_manager = PerspectiveManager(settings, context) if self._options.list_perspectives: # output available perspectives print('\n'.join(sorted(perspective_manager.perspectives))) return 0 else: perspective_manager = None if main_window is not None: container_manager = ContainerManager(main_window, plugin_manager) plugin_manager.set_main_window(main_window, menu_bar if not self._options.lock_perspective else None, container_manager) if not self._options.freeze_layout: minimized_dock_widgets_toolbar = MinimizedDockWidgetsToolbar(container_manager, main_window) main_window.addToolBar(Qt.BottomToolBarArea, minimized_dock_widgets_toolbar) plugin_manager.set_minimized_dock_widgets_toolbar(minimized_dock_widgets_toolbar) if menu_bar is not None and not self._options.lock_perspective: perspective_menu = menu_bar.addMenu(menu_bar.tr('P&erspectives')) perspective_manager.set_menu(perspective_menu) # connect various signals and slots if perspective_manager is not None and main_window is not None: # signal changed perspective to update window title perspective_manager.perspective_changed_signal.connect(main_window.perspective_changed) # signal new settings due to changed perspective perspective_manager.save_settings_signal.connect(main_window.save_settings) perspective_manager.restore_settings_signal.connect(main_window.restore_settings) perspective_manager.restore_settings_without_plugin_changes_signal.connect(main_window.restore_settings) if perspective_manager is not None and plugin_manager is not None: perspective_manager.save_settings_signal.connect(plugin_manager.save_settings) plugin_manager.save_settings_completed_signal.connect(perspective_manager.save_settings_completed) perspective_manager.restore_settings_signal.connect(plugin_manager.restore_settings) perspective_manager.restore_settings_without_plugin_changes_signal.connect(plugin_manager.restore_settings_without_plugins) if plugin_manager is not None and main_window is not None: # signal before changing plugins to save window state plugin_manager.plugins_about_to_change_signal.connect(main_window.save_setup) # signal changed plugins to restore window state plugin_manager.plugins_changed_signal.connect(main_window.restore_state) # signal save settings to store plugin setup on close main_window.save_settings_before_close_signal.connect(plugin_manager.close_application) # signal save and shutdown called for all plugins, trigger closing main window again plugin_manager.close_application_signal.connect(main_window.close, type=Qt.QueuedConnection) if main_window is not None and menu_bar is not None: about_handler = AboutHandler(context.qtgui_path, main_window) help_menu = menu_bar.addMenu(menu_bar.tr('&Help')) action = QAction(file_menu.tr('&About'), help_menu) action.setIcon(QIcon.fromTheme('help-about')) action.triggered.connect(about_handler.show) help_menu.addAction(action) # set initial size - only used without saved configuration if main_window is not None: main_window.resize(600, 450) main_window.move(100, 100) # ensure that qt_gui/src is in sys.path src_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) if src_path not in sys.path: sys.path.append(src_path) # load specific plugin plugin = None plugin_serial = None if self._options.embed_plugin is not None: plugin = self._options.embed_plugin plugin_serial = self._options.embed_plugin_serial elif self._options.standalone_plugin is not None: plugin = self._options.standalone_plugin plugin_serial = 0 if plugin is not None: plugins = plugin_manager.find_plugins_by_name(plugin) if len(plugins) == 0: print('qt_gui_main() found no plugin matching "%s"' % plugin) return 1 elif len(plugins) > 1: print('qt_gui_main() found multiple plugins matching "%s"\n%s' % (plugin, '\n'.join(plugins.values()))) return 1 plugin = plugins.keys()[0] qDebug('QtBindingHelper using %s' % QT_BINDING) plugin_manager.discover() if self._options.reload_import: qDebug('ReloadImporter() automatically reload all subsequent imports') from .reload_importer import ReloadImporter _reload_importer = ReloadImporter() self._add_reload_paths(_reload_importer) _reload_importer.enable() # switch perspective if perspective_manager is not None: if plugin: perspective_manager.set_perspective(plugin, hide_and_without_plugin_changes=True) elif self._options.perspective_file: perspective_manager.import_perspective_from_file(self._options.perspective_file, perspective_manager.HIDDEN_PREFIX + '__cli_perspective_from_file') else: perspective_manager.set_perspective(self._options.perspective) # load specific plugin if plugin: plugin_manager.load_plugin(plugin, plugin_serial, self._options.plugin_args) running = plugin_manager.is_plugin_running(plugin, plugin_serial) if not running: return 1 if self._options.standalone_plugin: # use icon of standalone plugin (if available) for application plugin_descriptor = plugin_manager.get_plugin_descriptor(plugin) action_attributes = plugin_descriptor.action_attributes() if 'icon' in action_attributes and action_attributes['icon'] is not None: base_path = plugin_descriptor.attributes().get('plugin_path') try: icon = get_icon(action_attributes['icon'], action_attributes.get('icontype', None), base_path) except UserWarning: pass else: app.setWindowIcon(icon) if main_window is not None: main_window.show() if sys.platform == 'darwin': main_window.raise_() return app.exec_()
def set_menu(self, menu): self._menu_manager = MenuManager(menu) self._perspective_mapper = QSignalMapper(menu) self._perspective_mapper.mapped[str].connect(self.switch_perspective) # generate menu create_action = QAction('&Create perspective...', self._menu_manager.menu) create_action.setIcon(QIcon.fromTheme('list-add')) create_action.triggered.connect(self._on_create_perspective) self._menu_manager.add_suffix(create_action) self._remove_action = QAction('&Remove perspective...', self._menu_manager.menu) self._remove_action.setEnabled(False) self._remove_action.setIcon(QIcon.fromTheme('list-remove')) self._remove_action.triggered.connect(self._on_remove_perspective) self._menu_manager.add_suffix(self._remove_action) self._menu_manager.add_suffix(None) import_action = QAction('&Import...', self._menu_manager.menu) import_action.setIcon(QIcon.fromTheme('document-open')) import_action.triggered.connect(self._on_import_perspective) self._menu_manager.add_suffix(import_action) export_action = QAction('&Export...', self._menu_manager.menu) export_action.setIcon(QIcon.fromTheme('document-save-as')) export_action.triggered.connect(self._on_export_perspective) self._menu_manager.add_suffix(export_action) # add perspectives to menu for name in self.perspectives: if not name.startswith(self.HIDDEN_PREFIX): self._add_perspective_action(name)
class PerspectiveManager(QObject): """Manager for perspectives associated with specific sets of `Settings`.""" perspective_changed_signal = Signal(str) save_settings_signal = Signal(Settings, Settings) restore_settings_signal = Signal(Settings, Settings) restore_settings_without_plugin_changes_signal = Signal(Settings, Settings) HIDDEN_PREFIX = '@' def __init__(self, settings, application_context): super(PerspectiveManager, self).__init__() self.setObjectName('PerspectiveManager') self._qtgui_path = application_context.qtgui_path self._settings_proxy = SettingsProxy(settings) self._global_settings = Settings(self._settings_proxy, 'global') self._perspective_settings = None self._create_perspective_dialog = None self._menu_manager = None self._perspective_mapper = None # get perspective list from settings self.perspectives = self._settings_proxy.value('', 'perspectives', []) if is_string(self.perspectives): self.perspectives = [self.perspectives] self._current_perspective = None self._remove_action = None self._callback = None self._callback_args = [] if application_context.provide_app_dbus_interfaces: from .perspective_manager_dbus_interface import PerspectiveManagerDBusInterface self._dbus_server = PerspectiveManagerDBusInterface( self, application_context) def set_menu(self, menu): self._menu_manager = MenuManager(menu) self._perspective_mapper = QSignalMapper(menu) self._perspective_mapper.mapped[str].connect(self.switch_perspective) # generate menu create_action = QAction('&Create perspective...', self._menu_manager.menu) create_action.setIcon(QIcon.fromTheme('list-add')) create_action.triggered.connect(self._on_create_perspective) self._menu_manager.add_suffix(create_action) self._remove_action = QAction('&Remove perspective...', self._menu_manager.menu) self._remove_action.setEnabled(False) self._remove_action.setIcon(QIcon.fromTheme('list-remove')) self._remove_action.triggered.connect(self._on_remove_perspective) self._menu_manager.add_suffix(self._remove_action) self._menu_manager.add_suffix(None) import_action = QAction('&Import...', self._menu_manager.menu) import_action.setIcon(QIcon.fromTheme('document-open')) import_action.triggered.connect(self._on_import_perspective) self._menu_manager.add_suffix(import_action) export_action = QAction('&Export...', self._menu_manager.menu) export_action.setIcon(QIcon.fromTheme('document-save-as')) export_action.triggered.connect(self._on_export_perspective) self._menu_manager.add_suffix(export_action) # add perspectives to menu for name in self.perspectives: if not name.startswith(self.HIDDEN_PREFIX): self._add_perspective_action(name) def set_perspective(self, name, hide_and_without_plugin_changes=False): if name is None: name = self._settings_proxy.value('', 'current-perspective', 'Default') elif hide_and_without_plugin_changes: name = self.HIDDEN_PREFIX + name self.switch_perspective( name, save_before=not hide_and_without_plugin_changes, without_plugin_changes=hide_and_without_plugin_changes) @Slot(str) @Slot(str, bool) @Slot(str, bool, bool) def switch_perspective(self, name, settings_changed=True, save_before=True, without_plugin_changes=False): if save_before and self._global_settings is not None and self._perspective_settings is not None: self._callback = self._switch_perspective self._callback_args = [name, settings_changed, save_before] self.save_settings_signal.emit(self._global_settings, self._perspective_settings) else: self._switch_perspective(name, settings_changed, save_before, without_plugin_changes) def _switch_perspective(self, name, settings_changed, save_before, without_plugin_changes=False): # convert from unicode name = str(name.replace('/', '__')) qDebug( 'PerspectiveManager.switch_perspective() switching to perspective "%s"' % name) if self._current_perspective is not None and self._menu_manager is not None: self._menu_manager.set_item_checked(self._current_perspective, False) self._menu_manager.set_item_disabled(self._current_perspective, False) # create perspective if necessary if name not in self.perspectives: self._create_perspective(name, clone_perspective=False) # update current perspective self._current_perspective = name if self._menu_manager is not None: self._menu_manager.set_item_checked(self._current_perspective, True) self._menu_manager.set_item_disabled(self._current_perspective, True) if not self._current_perspective.startswith(self.HIDDEN_PREFIX): self._settings_proxy.set_value('', 'current-perspective', self._current_perspective) self._perspective_settings = self._get_perspective_settings( self._current_perspective) # emit signals self.perspective_changed_signal.emit( self._current_perspective.lstrip(self.HIDDEN_PREFIX)) if settings_changed: if not without_plugin_changes: self.restore_settings_signal.emit(self._global_settings, self._perspective_settings) else: self.restore_settings_without_plugin_changes_signal.emit( self._global_settings, self._perspective_settings) def save_settings_completed(self): if self._callback is not None: callback = self._callback callback_args = self._callback_args self._callback = None self._callback_args = [] callback(*callback_args) def _get_perspective_settings(self, perspective_name): return Settings(self._settings_proxy, 'perspective/%s' % perspective_name) def _on_create_perspective(self): name = self._choose_new_perspective_name() if name is not None: clone_perspective = self._create_perspective_dialog.clone_checkbox.isChecked( ) self._create_perspective(name, clone_perspective) self.switch_perspective(name, settings_changed=not clone_perspective, save_before=False) def _choose_new_perspective_name(self, show_cloning=True): # input dialog for new perspective name if self._create_perspective_dialog is None: ui_file = os.path.join(self._qtgui_path, 'resource', 'perspective_create.ui') self._create_perspective_dialog = loadUi(ui_file) # custom validator preventing forward slashs class CustomValidator(QValidator): def __init__(self, parent=None): super(CustomValidator, self).__init__(parent) def fixup(self, value): value = value.replace('/', '') def validate(self, value, pos): if value.find('/') != -1: pos = value.find('/') return (QValidator.Invalid, value, pos) if value == '': return (QValidator.Intermediate, value, pos) return (QValidator.Acceptable, value, pos) self._create_perspective_dialog.perspective_name_edit.setValidator( CustomValidator()) # set default values self._create_perspective_dialog.perspective_name_edit.setText('') self._create_perspective_dialog.clone_checkbox.setChecked(True) self._create_perspective_dialog.clone_checkbox.setVisible(show_cloning) # show dialog and wait for it's return value return_value = self._create_perspective_dialog.exec_() if return_value == self._create_perspective_dialog.Rejected: return name = str(self._create_perspective_dialog.perspective_name_edit.text( )).lstrip(self.HIDDEN_PREFIX) if name == '': QMessageBox.warning( self._menu_manager.menu, self.tr('Empty perspective name'), self.tr('The name of the perspective must be non-empty.')) return if name in self.perspectives: QMessageBox.warning( self._menu_manager.menu, self.tr('Duplicate perspective name'), self.tr('A perspective with the same name already exists.')) return return name def _create_perspective(self, name, clone_perspective=True): # convert from unicode name = str(name) if name.find('/') != -1: raise RuntimeError( 'PerspectiveManager._create_perspective() name must not contain forward slashs (/)' ) qDebug('PerspectiveManager._create_perspective(%s, %s)' % (name, clone_perspective)) # add to list of perspectives self.perspectives.append(name) self._settings_proxy.set_value('', 'perspectives', self.perspectives) # save current settings if self._global_settings is not None and self._perspective_settings is not None: self._callback = self._create_perspective_continued self._callback_args = [name, clone_perspective] self.save_settings_signal.emit(self._global_settings, self._perspective_settings) else: self._create_perspective_continued(name, clone_perspective) def _create_perspective_continued(self, name, clone_perspective): # clone settings if clone_perspective: new_settings = self._get_perspective_settings(name) keys = self._perspective_settings.all_keys() for key in keys: value = self._perspective_settings.value(key) new_settings.set_value(key, value) # add and switch to perspective if not name.startswith(self.HIDDEN_PREFIX): self._add_perspective_action(name) def _add_perspective_action(self, name): if self._menu_manager is not None: # create action action = QAction(name, self._menu_manager.menu) action.setCheckable(True) self._perspective_mapper.setMapping(action, name) action.triggered.connect(self._perspective_mapper.map) # add action to menu self._menu_manager.add_item(action) # enable remove-action if self._menu_manager.count_items() > 1: self._remove_action.setEnabled(True) def _on_remove_perspective(self): # input dialog to choose perspective to be removed names = list(self.perspectives) names.remove(self._current_perspective) name, return_value = QInputDialog.getItem( self._menu_manager.menu, self._menu_manager.tr('Remove perspective'), self._menu_manager.tr('Select the perspective'), names, 0, False) # convert from unicode name = str(name) if return_value == QInputDialog.Rejected: return self._remove_perspective(name) def _remove_perspective(self, name): if name not in self.perspectives: raise UserWarning('unknown perspective: %s' % name) qDebug('PerspectiveManager._remove_perspective(%s)' % str(name)) # remove from list of perspectives self.perspectives.remove(name) self._settings_proxy.set_value('', 'perspectives', self.perspectives) # remove settings settings = self._get_perspective_settings(name) settings.remove('') # remove from menu self._menu_manager.remove_item(name) # disable remove-action if self._menu_manager.count_items() < 2: self._remove_action.setEnabled(False) def _on_import_perspective(self): file_name, _ = QFileDialog.getOpenFileName( self._menu_manager.menu, self.tr('Import perspective from file'), None, self.tr('Perspectives (*.perspective)')) if file_name is None or file_name == '': return perspective_name = os.path.basename(file_name) suffix = '.perspective' if perspective_name.endswith(suffix): perspective_name = perspective_name[:-len(suffix)] if perspective_name in self.perspectives: perspective_name = self._choose_new_perspective_name(False) if perspective_name is None: return self.import_perspective_from_file(file_name, perspective_name) def import_perspective_from_file(self, path, perspective_name): # create clean perspective if perspective_name in self.perspectives: self._remove_perspective(perspective_name) self._create_perspective(perspective_name, clone_perspective=False) # read perspective from file file_handle = open(path, 'r') #data = eval(file_handle.read()) data = json.loads(file_handle.read()) self._convert_values(data, self._import_value) new_settings = self._get_perspective_settings(perspective_name) self._set_dict_on_settings(data, new_settings) self.switch_perspective(perspective_name, settings_changed=True, save_before=True) def _set_dict_on_settings(self, data, settings): """Set dictionary key-value pairs on Settings instance.""" keys = data.get('keys', {}) for key in keys: settings.set_value(key, keys[key]) groups = data.get('groups', {}) for group in groups: sub = settings.get_settings(group) self._set_dict_on_settings(groups[group], sub) def _on_export_perspective(self): file_name, _ = QFileDialog.getSaveFileName( self._menu_manager.menu, self.tr('Export perspective to file'), self._current_perspective + '.perspective', self.tr('Perspectives (*.perspective)')) if file_name is None or file_name == '': return # trigger save of perspective before export self._callback = self._on_export_perspective_continued self._callback_args = [file_name] self.save_settings_signal.emit(self._global_settings, self._perspective_settings) def _on_export_perspective_continued(self, file_name): # convert every value data = self._get_dict_from_settings(self._perspective_settings) self._convert_values(data, self._export_value) # write perspective data to file file_handle = open(file_name, 'w') file_handle.write(json.dumps(data, indent=2, separators=(',', ': '))) file_handle.close() def _get_dict_from_settings(self, settings): """Convert data of Settings instance to dictionary.""" keys = {} for key in settings.child_keys(): keys[str(key)] = settings.value(key) groups = {} for group in settings.child_groups(): sub = settings.get_settings(group) groups[str(group)] = self._get_dict_from_settings(sub) return {'keys': keys, 'groups': groups} def _convert_values(self, data, convert_function): keys = data.get('keys', {}) for key in keys: keys[key] = convert_function(keys[key]) groups = data.get('groups', {}) for group in groups: self._convert_values(groups[group], convert_function) def _import_value(self, value): import QtCore # @UnusedImport if value['type'] == 'repr': return eval(value['repr']) elif value['type'] == 'repr(QByteArray.hex)': return QByteArray.fromHex(eval(value['repr(QByteArray.hex)'])) raise RuntimeError( 'PerspectiveManager._import_value() unknown serialization type (%s)' % value['type']) def _export_value(self, value): data = {} if value.__class__.__name__ == 'QByteArray': hex_value = value.toHex() data['repr(QByteArray.hex)'] = self._strip_qt_binding_prefix( hex_value, repr(hex_value)) data['type'] = 'repr(QByteArray.hex)' # add pretty print for better readability characters = '' for i in range(1, value.size(), 2): try: character = value.at(i) # output all non-control characters if character >= ' ' and character <= '~': characters += character else: characters += ' ' except UnicodeDecodeError: characters += ' ' data['pretty-print'] = characters else: data['repr'] = self._strip_qt_binding_prefix(value, repr(value)) data['type'] = 'repr' # verify that serialized data can be deserialized correctly reimported = self._import_value(data) if reimported != value: raise RuntimeError( 'PerspectiveManager._export_value() stored value can not be restored (%s)' % type(value)) return data def _strip_qt_binding_prefix(self, obj, data): """Strip binding specific prefix from type string.""" parts = obj.__class__.__module__.split('.') if len(parts) > 1 and parts[1] == 'QtCore': prefix = '.'.join(parts[:2]) data = data.replace(prefix, 'QtCore', 1) return data
def _rightclick_menu(self, event): """ :type event: QEvent """ # QTreeview.selectedIndexes() returns 0 when no node is selected. # This can happen when after booting no left-click has been made yet # (ie. looks like right-click doesn't count). These lines are the # workaround for that problem. selected = self._messages_tree.selectedIndexes() if len(selected) == 0: return menu = QMenu() text_action = QAction(self.tr('View Text'), menu) menu.addAction(text_action) remove_action = QAction(self.tr('Remove message'), menu) menu.addAction(remove_action) action = menu.exec_(event.globalPos()) if action == text_action: self._logger.debug('_rightclick_menu selected={}'.format(selected)) selected_type = selected[1].data() # We strip any array information for loading the python classes selected_type_bare = selected_type if selected_type_bare.find('[') >= 0: selected_type_bare = selected_type_bare[:selected_type_bare. find('[')] # We use the number of '/' to determine of the selected type is a msg, action, srv, # or primitive type. # NOTE (mlautman - 2/4/19) this heuristic seems brittle and should be removed selected_type_bare_tokens_len = len(selected_type_bare.split('/')) # We only want the base class so we transform eg. pkg1/my_srv/Request -> pkg1/my_srv if selected_type_bare_tokens_len > 2: selected_type_bare = "/".join( selected_type_bare.split('/')[:2]) browsetext = None # If the type does not have '/'s then we treat it as a primitive type if selected_type_bare_tokens_len == 1: browsetext = selected_type # if the type has a single '/' then we treat it as a msg type elif selected_type_bare_tokens_len == 2: msg_class = get_message_class(selected_type_bare) browsetext = get_message_text_from_class(msg_class) # If the type has two '/'s then we treat it as a srv or action type elif selected_type_bare_tokens_len == 3: if self._mode == message_helpers.SRV_MODE: msg_class = get_service_class(selected_type_bare) browsetext = get_service_text_from_class(msg_class) elif self._mode == message_helpers.ACTION_MODE: msg_class = get_action_class(selected_type_bare) browsetext = get_action_text_from_class(msg_class) else: self._logger.warn("Unrecognized value for self._mode: {} " "for selected_type: {}".format( self._mode, selected_type)) else: self._logger.warn( "Invalid selected_type: {}".format(selected_type)) if browsetext is not None: self._browsers.append(TextBrowseDialog(browsetext)) self._browsers[-1].show() if action == remove_action: self._messages_tree.model().removeRow(selected[0].row())
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))), 'Start the publishing the TF.', self.widget) self.path_red_icon = path_pkg + '/share/handeye_dashboard/images/red_circle_icon.png' self.path_green_icon = path_pkg + '/share/handeye_dashboard/images/green_circle_icon.png' self.parameter_action = QAction( QIcon(QPixmap.fromImage(QImage(self.path_red_icon))), 'Connect to parameter service.', self.widget) self.tf_action = QAction( QIcon(QPixmap.fromImage(QImage(self.path_red_icon))), 'Connect to tf service.', 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) self.parameter_label = QLabel(self.widget) self.parameter_label.setText("Parameter service: ") self.toolbar.addWidget(self.parameter_label) self.toolbar.addAction(self.parameter_action) self.tf_label = QLabel(self.widget) self.tf_label.setText("tf service: ") self.toolbar.addWidget(self.tf_label) self.toolbar.addAction(self.tf_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) self.parameter_action.triggered.connect(self.parameter_connect) self.tf_action.triggered.connect(self.tf_connect) # Package path self.path_pkg = path_pkg # Set up TF service client self.client_tf = self.node.create_client(HandeyeTF, 'handeye_tf_service') self.tf_action.setIcon(QIcon(QPixmap.fromImage(QImage(self.path_red_icon))) \ if not self.client_tf.wait_for_service(timeout_sec=0.5) \ else QIcon(QPixmap.fromImage(QImage(self.path_green_icon)))) self.req = HandeyeTF.Request() # Set up parameter service client self.client_param = self.node.create_client( SetParameters, '/grasp_modbus_server/set_parameters') self.parameter_action.setIcon(QIcon(QPixmap.fromImage(QImage(self.path_red_icon))) \ if not self.client_param.wait_for_service(timeout_sec=0.5) \ else QIcon(QPixmap.fromImage(QImage(self.path_green_icon)))) def tf_connect(self): self.client_tf = self.node.create_client(HandeyeTF, 'handeye_tf_service') self.tf_action.setIcon(QIcon(QPixmap.fromImage(QImage(self.path_red_icon))) \ if not self.client_tf.wait_for_service(timeout_sec=0.5) \ else QIcon(QPixmap.fromImage(QImage(self.path_green_icon)))) def parameter_connect(self): self.client_param = self.node.create_client( SetParameters, '/grasp_modbus_server/set_parameters') self.parameter_action.setIcon(QIcon(QPixmap.fromImage(QImage(self.path_red_icon))) \ if not self.client_param.wait_for_service(timeout_sec=0.5) \ else QIcon(QPixmap.fromImage(QImage(self.path_green_icon)))) 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.client_tf.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.client_tf.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): # Set calibration state to success req = SetParameters.Request() param = Parameter() param.name = "calibration_state" param.value.type = ParameterType.PARAMETER_INTEGER param.value.integer_value = 4 req.parameters.append(param) future = self.client_param.call_async(req) rclpy.spin_until_future_complete(self.node, future) # >>> 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
def __init__(self, name,parent_widget): self._name = name self._is_active = True self.menu_action = QAction(self._name, parent_widget, triggered=self.toggle_active, checkable=True, checked=True)
class NavigationMapWidget(QWidget): def __init__(self, plugin): super(NavigationMapWidget, self).__init__() rp = rospkg.RosPack() try: rospy.wait_for_service('/proc_control/set_global_target', timeout=2) except rospy.ROSException: False ui_file = os.path.join(rp.get_path('rqt_navigation_map'), 'resource', 'mainWidget.ui') loadUi(ui_file, self) self._plugin = plugin self._topic_name = None self._odom_subscriber = None # create GL view self._gl_view = GLWidget() self._gl_view.unsetCursor() self._mapDrawer = MapDrawer(self) self._position = (0.0, 0.0, 0.0) self._mapDrawer.set_position(self._position) self._orientation = quaternion_about_axis(45.0, (0.0, 0.0, 1.0)) self._mapDrawer.set_orientation(self._orientation,45) # add GL view to widget layout self.layout().addWidget(self._gl_view) self._rotate_with_sub_activated = False self._lock_on_sub_activated = False self._yaw = 0 self._odom_subscriber = rospy.Subscriber('/proc_navigation/odom', Odometry, self._odom_callback) self.position_target_subscriber = rospy.Subscriber('/proc_control/current_target', PositionTarget, self._position_target_callback) self.set_global_target = rospy.ServiceProxy('/proc_control/set_global_target', SetPositionTarget) self._define_menu() def _define_menu(self): self._menu = QMenu(self._gl_view) layer_menu = QMenu('Layers', self._gl_view) for layer in self._mapDrawer.get_layers(): layer_menu.addAction(layer.menu_action) self._menu.addMenu(layer_menu) self._menu.addSeparator() self._view2dAction = QAction(self._gl_view.tr("2D View"), self._gl_view, triggered=self._set_default_view) self._menu.addAction(self._view2dAction) self._view3dAction = QAction(self._gl_view.tr("3D View"), self._gl_view, triggered=self._set_3d_view) self._menu.addAction(self._view3dAction) self._menu.addSeparator() #self._rotateSubAction = QAction(self._gl_view.tr("Rotate with Sub"), self, checkable=True, # triggered=self._rotate_with_sub) #self._menu.addAction(self._rotateSubAction) self._lockOnSubAction = QAction(self._gl_view.tr("Lock on Sub"), self, checkable=True, triggered=self._lock_on_sub) self._menu.addAction(self._lockOnSubAction) self._menu.addSeparator() self._menu.addAction(QAction('Reset Path', self, triggered=self._mapDrawer.reset_path)) setLocationAction = QAction('Set Location Target', self._gl_view, triggered=self._set_location_action) self._menu.addAction(setLocationAction) def _position_target_callback(self, target): self._mapDrawer.drawTarget(target.X, target.Y, target.Z) def _odom_callback(self, odom_data): vehicle_position_x = odom_data.pose.pose.position.x vehicle_position_y = odom_data.pose.pose.position.y vehicle_position_z = odom_data.pose.pose.position.z self._position = (vehicle_position_x, vehicle_position_y, vehicle_position_z) self._mapDrawer.set_position(self._position) self._yaw = odom_data.pose.pose.orientation.z self._orientation = quaternion_about_axis(math.radians(self._yaw), (0.0, 0.0, 1.0)) self._mapDrawer.set_orientation(self._orientation,self._yaw) def save_settings(self, plugin_settings, instance_settings): self._mapDrawer.save_settings(plugin_settings,instance_settings) view_matrix_string = repr(self._gl_view.get_view_matrix()) instance_settings.set_value('view_matrix', view_matrix_string) instance_settings.set_value('lock_on_sub_activated', str(self._lock_on_sub_activated)) def restore_settings(self, plugin_settings, instance_settings): self._mapDrawer.restore_settings(plugin_settings,instance_settings) view_matrix_string = instance_settings.value('view_matrix') try: view_matrix = eval(view_matrix_string) except Exception: view_matrix = None if view_matrix is not None: self._gl_view.set_view_matrix(view_matrix) else: self._set_default_view() lock_on_sub = instance_settings.value('lock_on_sub_activated') == 'True' if lock_on_sub is None: print 'Nothing stored for lock_on_sub' else: self._lock_on_sub(lock_on_sub) self._lockOnSubAction.setChecked(lock_on_sub) #rotate_with_sub = instance_settings.value('rotate_with_sub_activated') == 'True' #if rotate_with_sub is None: # print 'Nothing stored for lock_on_sub' #else: # self._rotate_with_sub(rotate_with_sub) # self._rotateSubAction.setChecked(rotate_with_sub) def _set_default_view(self): self._gl_view.makeCurrent() self._gl_view.reset_view() self._gl_view.translate((0, 0, -800)) def _set_3d_view(self): self._gl_view.makeCurrent() self._gl_view.reset_view() self._gl_view.rotate((0, 0, 1), 0) self._gl_view.rotate((1, 0, 0), -75) self._gl_view.translate((-100, -100, -500)) def _rotate_with_sub(self, checked): self._rotate_with_sub_activated = checked self._mapDrawer.set_rotate_with_sub_activated(checked) def _lock_on_sub(self, checked): self._lock_on_sub_activated = checked self._mapDrawer.set_lock_on_sub_activated(checked) self._view2dAction.setEnabled(not checked) self._view3dAction.setEnabled(not checked) def _gl_view_mouseReleaseEvent(self, event): if event.button() == Qt.RightButton: self._event_3dPoint = event self._menu.exec_(self._gl_view.mapToGlobal(event.pos())) def _set_location_action(self): x_cursor = self._event_3dPoint.pos().x() y_cursor = self._gl_view.height() - self._event_3dPoint.pos().y() x, y, z = self._gl_view.unproject_mouse_on_scene(QPoint(x_cursor, y_cursor)) position_x = x / self._mapDrawer.resolution_meters position_y = y / self._mapDrawer.resolution_meters position_z = z / self._mapDrawer.resolution_meters self._mapDrawer.drawTarget(position_x, position_y, position_z) rospy.loginfo('Set Target selected at (%.2f, %.2f)', position_x, position_y) self.set_global_target(X=position_x, Y=position_y, Z=position_z, ROLL=0, PITCH=0, YAW=self._yaw) def shutdown_plugin(self): print 'Shutting down'
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()