|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.|
|It is appreciated that problems/questions regarding this tutorial are asked on answers.ros.org. Don't forget to include in your question the link to this page, versions of your OS & ROS, and also add appropriate tags.|
Writing a Python PluginDescription: Shows how to write a plugin for rqt in Python.
Keywords: rqt tutorial
Tutorial Level: INTERMEDIATE
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.
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 4 from qt_gui.plugin import Plugin 5 from python_qt_binding import loadUi 6 from python_qt_binding.QtGui import QWidget 7 8 class MyPlugin(Plugin): 9 10 def __init__(self, context): 11 super(MyPlugin, self).__init__(context) 12 # Give QObjects reasonable names 13 self.setObjectName('MyPlugin') 14 15 # Process standalone plugin command-line arguments 16 from argparse import ArgumentParser 17 parser = ArgumentParser() 18 # Add argument(s) to the parser. 19 parser.add_argument("-q", "--quiet", action="store_true", 20 dest="quiet", 21 help="Put plugin in silent mode") 22 args, unknowns = parser.parse_known_args(context.argv()) 23 if not args.quiet: 24 print 'arguments: ', args 25 print 'unknowns: ', unknowns 26 27 # Create QWidget 28 self._widget = QWidget() 29 # Get path to UI file which is a sibling of this file 30 # in this example the .ui and .py file are in the same folder 31 ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), '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.
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.
As a method shown in the code example above, each rqt plugin can have a configuration dialog (opened from gear-shape icon).
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
only operate on non-widget entities like QAbstractItemModels
Once code is done
You may go back to Install & Run your plugin section in the previous tutorial to finish.
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):
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 argfoois equal to:
:param argfoo: this is argfoo