Note: Explains how to write your rqt plugin.. This tutorial assumes that you have completed the previous tutorials: rqt/Tutorials/Create your new rqt plugin.
(!) Please ask about problems and questions regarding this tutorial on answers.ros.org. Don't forget to include in your question the link to this page, the versions of your OS & ROS, and also add appropriate tags.

Writing a Python Plugin

Description: Shows how to write a plugin for rqt in Python.

Keywords: rqt tutorial

Tutorial Level: INTERMEDIATE

Next Tutorial: rqt/Tutorials/To show error or exception message to the users

Intro

This tutorial will show you how to write a Python plugin to integrate a custom user interface into rqt. You're expected to come from Create your new rqt plugin tutorial.

Complete example of all files in a package is available on github. For example rqt_bag.

The code

Create the src/rqt_mypkg/ inside the rqt_mypkg package directory.

% roscd rqt_mypkg
% mkdir -p src/rqt_mypkg

Create an empty __init__.py file under that folder in any way you prefer, for example:

% cd src/rqt_mypkg/
% touch __init__.py

Then in the same folder, create my_module.py file and paste all of the following code inside it:

   1 # when using rosbuild these lines are required to make sure that all dependent Python packages are on the PYTHONPATH:
   2 import roslib
   3 roslib.load_manifest('rqt_mypkg')

   1 import os
   2 import rospy
   3 import rospkg
   4 
   5 from qt_gui.plugin import Plugin
   6 from python_qt_binding import loadUi
   7 from python_qt_binding.QtWidgets import QWidget
   8 
   9 class MyPlugin(Plugin):
  10 
  11     def __init__(self, context):
  12         super(MyPlugin, self).__init__(context)
  13         # Give QObjects reasonable names
  14         self.setObjectName('MyPlugin')
  15 
  16         # Process standalone plugin command-line arguments
  17         from argparse import ArgumentParser
  18         parser = ArgumentParser()
  19         # Add argument(s) to the parser.
  20         parser.add_argument("-q", "--quiet", action="store_true",
  21                       dest="quiet",
  22                       help="Put plugin in silent mode")
  23         args, unknowns = parser.parse_known_args(context.argv())
  24         if not args.quiet:
  25             print 'arguments: ', args
  26             print 'unknowns: ', unknowns
  27 
  28         # Create QWidget
  29         self._widget = QWidget()
  30         # Get path to UI file which should be in the "resource" folder of this package
  31         ui_file = os.path.join(rospkg.RosPack().get_path('rqt_mypkg'), 'resource', 'MyPlugin.ui')
  32         # Extend the widget with all attributes and children from UI file
  33         loadUi(ui_file, self._widget)
  34         # Give QObjects reasonable names
  35         self._widget.setObjectName('MyPluginUi')
  36         # Show _widget.windowTitle on left-top of each plugin (when 
  37         # it's set in _widget). This is useful when you open multiple 
  38         # plugins at once. Also if you open multiple instances of your 
  39         # plugin at once, these lines add number to make it easy to 
  40         # tell from pane to pane.
  41         if context.serial_number() > 1:
  42             self._widget.setWindowTitle(self._widget.windowTitle() + (' (%d)' % context.serial_number()))
  43         # Add widget to the user interface
  44         context.add_widget(self._widget)
  45 
  46     def shutdown_plugin(self):
  47         # TODO unregister all publishers here
  48         pass
  49 
  50     def save_settings(self, plugin_settings, instance_settings):
  51         # TODO save intrinsic configuration, usually using:
  52         # instance_settings.set_value(k, v)
  53         pass
  54 
  55     def restore_settings(self, plugin_settings, instance_settings):
  56         # TODO restore intrinsic configuration, usually using:
  57         # v = instance_settings.value(k)
  58         pass
  59 
  60     #def trigger_configuration(self):
  61         # Comment in to signal that the plugin has a way to configure
  62         # This will enable a setting button (gear icon) in each dock widget title bar
  63         # Usually used to open a modal configuration dialog

This simple plugin only adds a single widget using add_widget. But a plugin can contribute multiple widgets by calling that method for each widget. The method can be called at any time to dynamically contribute more widgets. Internally each widget is embedded into a new QDockWidget which itself is added to the QMainWindow.

Adding the script file

Create the scripts/ folder inside the rqt_mypkg package directory.

% roscd rqt_mypkg
% mkdir scripts

Then in the same folder, create rqt_mypkg file and paste all of the following code inside it:

   1 #!/usr/bin/env python
   2 
   3 import sys
   4 
   5 from rqt_mypkg.my_module import MyPlugin
   6 from rqt_gui.main import Main
   7 
   8 plugin = 'rqt_mypkg'
   9 main = Main(filename=plugin)
  10 sys.exit(main.main(standalone=plugin))

Using qt_binding_helper

The python_qt_binding enables to write code which is agnostic to the used Python bindings for Qt. You should import all Qt modules with the python_qt_binding prefix instead of the PyQt4/PySide prefix. For more information about licensing regarding Qt-python binding, see wiki page of python_qt_binding package.

Using a UI file

The example uses an Qt Designer UI file to describe the widget tree. Instead of hand-coding the widget tree the python_qt_binding.loadUi function creates the widgets at runtime based on the description from the file. See this tutorial for more info.

For this tutorial, we will assume that a .ui file created from QT Designer is available.

Create the resource folder inside the rqt_mypkg package directory.

% roscd rqt_mypkg
% mkdir -p resource

In the same resource folder, create MyPlugin.ui file and paste all of the following code inside it:

   1 <?xml version="1.0" encoding="UTF-8"?>
   2 <ui version="4.0">
   3  <class>Form</class>
   4  <widget class="QWidget" name="Form">
   5   <property name="geometry">
   6    <rect>
   7     <x>0</x>
   8     <y>0</y>
   9     <width>400</width>
  10     <height>300</height>
  11    </rect>
  12   </property>
  13   <property name="windowTitle">
  14    <string>Form</string>
  15   </property>
  16   <widget class="QPushButton" name="Test">
  17    <property name="geometry">
  18     <rect>
  19      <x>120</x>
  20      <y>70</y>
  21      <width>98</width>
  22      <height>27</height>
  23     </rect>
  24    </property>
  25    <property name="text">
  26     <string>Original Name</string>
  27    </property>
  28   </widget>
  29  </widget>
  30  <resources/>
  31  <connections/>
  32 </ui>

trigger configuration

As a method shown in the code example above, each rqt plugin can have a configuration dialog (opened from gear-shape icon).

Examples from rqt_service_caller | rqt_plot.

Using rospy

The plugin should not call init_node as this is performed by rqt_gui_py. The plugin can use any rospy-specific functionality (like Publishers, Subscribers, Parameters). Just make sure to stop timers and publishers, unsubscribe from Topics etc in the shutdown_plugin method.

Due to restrictions in Qt, you cannot manipulate Qt widgets directly within ROS callbacks, because they are running in a different thread. In the ROS callback you can:

  • emit a Qt signal (which will bridge across the threads) and manipulate the widgets in the receiving slot

OR

  • only operate on non-widget entities like QAbstractItemModels

Once code is done

You can run your plugin from the rqt main window's 'plugin' menu or standalone like this:

$ rqt --standalone rqt_mypkg

For more options you may go back to Install & Run your plugin section in the previous tutorial.

Misc

python coding style for rqt

  • Follow PyStyleGuide in general.

  • rqt local custom is whether you follow 80-char-in-a-line rule from PEP8 is up to you when writing a plugin. Existing code should not be changed and must be consistent per package.

Also, python in rqt defines documenting rule (discussion):

  • PEP257 & PEP258 leave up the detailed format to each project

  • Most of the rqt plugins so far look like using Sphinx format

  • For attributing, both : and @ can be used. For example:

     @param argfoo: this is argfoo
    is equal to:
     :param argfoo: this is argfoo

Wiki: rqt/Tutorials/Writing a Python Plugin (last edited 2018-04-03 19:09:43 by HowardChen)