Пример #1
0
    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)
Пример #2
0
    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_()
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
Пример #4
0
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
Пример #5
0
    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_()