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
Contents
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 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:
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