Skip to content

Commit 783b7f6

Browse files
DLukscottz
andauthored
Rviz Panel Tutorial (#4869)
Signed-off-by: David V. Lu!! <[email protected]> Signed-off-by: Katherine Scott <[email protected]> Co-authored-by: Katherine Scott <[email protected]>
1 parent 61b0fd6 commit 783b7f6

File tree

8 files changed

+352
-0
lines changed

8 files changed

+352
-0
lines changed
Lines changed: 351 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,351 @@
1+
Building a Custom RViz Panel
2+
============================
3+
4+
This tutorial is for people who would like to work within the RViz environment to either display or interact with some data in a two-dimensional environment.
5+
6+
In this tutorial you will learn how to do three things within RViz:
7+
8+
* Create a new QT panel within RViz.
9+
* Create a topic subscriber within RViz that can monitor messages published on that topic and display them within the RViz panel.
10+
* Create a topic publisher such button presses within RViz publish to an output topic in ROS.
11+
12+
All of the code for this tutorial can be found in `this repository <https://github.com/MetroRobots/rviz_panel_tutorial>`__.
13+
14+
Boilerplate Code
15+
----------------
16+
17+
Header File
18+
^^^^^^^^^^^
19+
20+
Here are the contents of ``demo_panel.hpp``
21+
22+
.. code-block:: c++
23+
24+
#ifndef RVIZ_PANEL_TUTORIAL__DEMO_PANEL_HPP_
25+
#define RVIZ_PANEL_TUTORIAL__DEMO_PANEL_HPP_
26+
27+
#include <rviz_common/panel.hpp>
28+
29+
namespace rviz_panel_tutorial
30+
{
31+
class DemoPanel
32+
: public rviz_common::Panel
33+
{
34+
Q_OBJECT
35+
public:
36+
explicit DemoPanel(QWidget * parent = 0);
37+
~DemoPanel() override;
38+
};
39+
} // namespace rviz_panel_tutorial
40+
41+
#endif // RVIZ_PANEL_TUTORIAL__DEMO_PANEL_HPP_
42+
43+
* We're extending the `rviz_common::Panel <https://github.com/ros2/rviz/blob/9a94bdf2f5f92ccdac4037c9268b95940845d609/rviz_common/include/rviz_common/panel.hpp#L46>`__ class.
44+
* `For reasons outside the scope of this tutorial <https://doc.qt.io/qt-5/moc.html>`__, you need the ``Q_OBJECT`` macro in there to get the QT parts of the GUI to work.
45+
* We start by declaring just a constructor and destructor, implemented in the cpp file.
46+
47+
Source File
48+
^^^^^^^^^^^
49+
50+
``demo_panel.cpp``
51+
52+
.. code-block:: c++
53+
54+
#include <rviz_panel_tutorial/demo_panel.hpp>
55+
56+
namespace rviz_panel_tutorial
57+
{
58+
DemoPanel::DemoPanel(QWidget* parent) : Panel(parent)
59+
{
60+
}
61+
62+
DemoPanel::~DemoPanel() = default;
63+
} // namespace rviz_panel_tutorial
64+
65+
#include <pluginlib/class_list_macros.hpp>
66+
PLUGINLIB_EXPORT_CLASS(rviz_panel_tutorial::DemoPanel, rviz_common::Panel)
67+
68+
* Overriding the constructor and deconstructor are not strictly necessary, but we can do more with them later.
69+
* In order for RViz to find our plugin, we need this ``PLUGINLIB`` invocation in our code (as well as other things below).
70+
71+
package.xml
72+
^^^^^^^^^^^
73+
74+
We need the following dependencies in our package.xml:
75+
76+
.. code-block:: xml
77+
78+
<depend>pluginlib</depend>
79+
<depend>rviz_common</depend>
80+
81+
rviz_common_plugins.xml
82+
^^^^^^^^^^^^^^^^^^^^^^^
83+
84+
.. code-block:: xml
85+
86+
<library path="demo_panel">
87+
<class type="rviz_panel_tutorial::DemoPanel" base_class_type="rviz_common::Panel">
88+
<description></description>
89+
</class>
90+
</library>
91+
92+
* This is standard ``pluginlib`` code.
93+
94+
* The library ``path`` is the name of the library we'll assign in the CMake.
95+
* The class should match the ``PLUGINLIB`` invocation from above.
96+
97+
* We'll come back to the description later, I promise.
98+
99+
CMakeLists.txt
100+
^^^^^^^^^^^^^^
101+
102+
Add the following lines to the top of the standard boilerplate.
103+
104+
.. code-block:: cmake
105+
106+
find_package(ament_cmake_ros REQUIRED)
107+
find_package(pluginlib REQUIRED)
108+
find_package(rviz_common REQUIRED)
109+
110+
set(CMAKE_AUTOMOC ON)
111+
qt5_wrap_cpp(MOC_FILES
112+
include/rviz_panel_tutorial/demo_panel.hpp
113+
)
114+
115+
add_library(demo_panel src/demo_panel.cpp ${MOC_FILES})
116+
target_include_directories(demo_panel PUBLIC
117+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
118+
$<INSTALL_INTERFACE:include>
119+
)
120+
ament_target_dependencies(demo_panel
121+
pluginlib
122+
rviz_common
123+
)
124+
install(TARGETS demo_panel
125+
EXPORT export_rviz_panel_tutorial
126+
ARCHIVE DESTINATION lib
127+
LIBRARY DESTINATION lib
128+
RUNTIME DESTINATION bin
129+
)
130+
install(DIRECTORY include/
131+
DESTINATION include
132+
)
133+
install(FILES rviz_common_plugins.xml
134+
DESTINATION share/${PROJECT_NAME}
135+
)
136+
ament_export_include_directories(include)
137+
ament_export_targets(export_rviz_panel_tutorial)
138+
pluginlib_export_plugin_description_file(rviz_common rviz_common_plugins.xml)
139+
140+
141+
* To generate the proper Qt files, we need to
142+
143+
* Turn ``CMAKE_AUTOMOC`` on.
144+
* Wrap the headers by calling ``qt5_wrap_cpp`` with each header that has ``Q_OBJECT`` in it.
145+
* Include the ``MOC_FILES`` in the library alongside our other cpp files.
146+
147+
* A lot of the other code ensures that the plugin portion works.
148+
Namely, calling ``pluginlib_export_plugin_description_file`` is essential to getting RViz to find your new plugin.
149+
150+
Testing it out
151+
^^^^^^^^^^^^^^
152+
153+
Compile your code, source your workspace and run ``rviz2``.
154+
155+
In the top Menu bar, there should be a "Panels" menu.
156+
Select "Add New Panel" from that menu.
157+
158+
.. image:: images/Select0.png
159+
:target: images/Select0.png
160+
:alt: screenshot of Add New Panel dialog
161+
162+
A dialog will pop up showing all the panels accessible in your ROS environment, grouped into folders based on their ROS package.
163+
Create a new instance of your panel by either double clicking on its name, or selecting it and clicking OK.
164+
165+
This should create a new panel in your RViz window, albeit with nothing but a title bar with the name of your panel.
166+
167+
.. image:: images/RViz0.png
168+
:target: images/RViz0.png
169+
:alt: screenshot of the whole RViz window showing the new simple panel
170+
171+
Filling in the Panel
172+
--------------------
173+
We're going to update our panel with some very basic ROS/QT interaction.
174+
What we will do, roughly, is access the ROS node from within RViz that can both subscribe and publish to ROS topics.
175+
We will use our subscriber to monitor an ``/input`` topic within ROS and display the published ``String`` values in the widget.
176+
We use our publisher to map button presses within RViz to messages published on a ROS topic named ``/output`` .
177+
178+
Updated Header File
179+
^^^^^^^^^^^^^^^^^^^
180+
181+
Update ``demo_panel.hpp`` to include the following includes and class Body.
182+
183+
.. code-block:: c++
184+
185+
#include <rviz_common/panel.hpp>
186+
#include <rviz_common/ros_integration/ros_node_abstraction_iface.hpp>
187+
#include <std_msgs/msg/string.hpp>
188+
#include <QLabel>
189+
#include <QPushButton>
190+
191+
namespace rviz_panel_tutorial
192+
{
193+
class DemoPanel : public rviz_common::Panel
194+
{
195+
Q_OBJECT
196+
public:
197+
explicit DemoPanel(QWidget * parent = 0);
198+
~DemoPanel() override;
199+
200+
void onInitialize() override;
201+
202+
protected:
203+
std::shared_ptr<rviz_common::ros_integration::RosNodeAbstractionIface> node_ptr_;
204+
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
205+
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
206+
207+
void topicCallback(const std_msgs::msg::String & msg);
208+
209+
QLabel* label_;
210+
QPushButton* button_;
211+
212+
private Q_SLOTS:
213+
void buttonActivated();
214+
};
215+
} // namespace rviz_panel_tutorial
216+
217+
* On the ROS side, we declare an abstract node pointer, which we will use to create interfaces to the wider ROS ecosystem.
218+
We have a subscriber which will allow us to take information from ROS and use it in RViz.
219+
The publisher allows us to publish information/events from within RViz and make them available in ROS.
220+
We also have methods an initialization method for setting up the ROS components (``onInitialize``) and a callback for the subscriber (``topicCallback``).
221+
* On the QT side, we declare a label and a button, as well as a callback for the button (``buttonActivated``).
222+
223+
Updated Source File
224+
^^^^^^^^^^^^^^^^^^^
225+
226+
Update ``demo_panel.cpp`` to have the following contents:
227+
228+
.. code-block:: c++
229+
230+
#include <rviz_panel_tutorial/demo_panel.hpp>
231+
#include <QVBoxLayout>
232+
#include <rviz_common/display_context.hpp>
233+
234+
namespace rviz_panel_tutorial
235+
{
236+
237+
DemoPanel::DemoPanel(QWidget* parent) : Panel(parent)
238+
{
239+
// Create a label and a button, displayed vertically (the V in VBox means vertical)
240+
const auto layout = new QVBoxLayout(this);
241+
// Create a button and a label for the button
242+
label_ = new QLabel("[no data]");
243+
button_ = new QPushButton("GO!");
244+
// Add those elements to the GUI layout
245+
layout->addWidget(label_);
246+
layout->addWidget(button_);
247+
248+
// Connect the event of when the button is released to our callback,
249+
// so pressing the button results in the buttonActivated callback being called.
250+
QObject::connect(button_, &QPushButton::released, this, &DemoPanel::buttonActivated);
251+
}
252+
253+
DemoPanel::~DemoPanel() = default;
254+
255+
void DemoPanel::onInitialize()
256+
{
257+
// Access the abstract ROS Node and
258+
// in the process lock it for exclusive use until the method is done.
259+
node_ptr_ = getDisplayContext()->getRosNodeAbstraction().lock();
260+
261+
// Get a pointer to the familiar rclcpp::Node for making subscriptions/publishers
262+
// (as per normal rclcpp code)
263+
rclcpp::Node::SharedPtr node = node_ptr_->get_raw_node();
264+
265+
// Create a String publisher for the output
266+
publisher_ = node->create_publisher<std_msgs::msg::String>("/output", 10);
267+
268+
// Create a String subscription and bind it to the topicCallback inside this class.
269+
subscription_ = node->create_subscription<std_msgs::msg::String>("/input", 10, std::bind(&DemoPanel::topicCallback, this, std::placeholders::_1));
270+
}
271+
272+
// When the subscriber gets a message, this callback is triggered,
273+
// and then we copy its data into the widget's label
274+
void DemoPanel::topicCallback(const std_msgs::msg::String & msg)
275+
{
276+
label_->setText(QString(msg.data.c_str()));
277+
}
278+
279+
// When the widget's button is pressed, this callback is triggered,
280+
// and then we publish a new message on our topic.
281+
void DemoPanel::buttonActivated()
282+
{
283+
auto message = std_msgs::msg::String();
284+
message.data = "Button clicked!";
285+
publisher_->publish(message);
286+
}
287+
288+
} // namespace rviz_panel_tutorial
289+
290+
#include <pluginlib/class_list_macros.hpp>
291+
292+
PLUGINLIB_EXPORT_CLASS(rviz_panel_tutorial::DemoPanel, rviz_common::Panel)
293+
294+
Testing with ROS
295+
^^^^^^^^^^^^^^^^
296+
Compile and launch RViz2 with your panel again. You should see your label and button in the panel now.
297+
298+
.. image:: images/RViz1.png
299+
:target: images/RViz1.png
300+
:alt: screenshot of the RViz panel in its default state
301+
302+
To change the label, we simply have to publish a message on the ``/input`` topic, which you can do with this command:
303+
304+
.. code-block:: bash
305+
306+
ros2 topic pub /input std_msgs/msg/String "{data: 'Please be kind.'}"
307+
308+
Since the widget is subscribed to this topic, it will trigger the callback and change the text of the label.
309+
310+
.. image:: images/RViz2.png
311+
:target: images/RViz2.png
312+
:alt: screenshot of the RViz panel with custom string message displayed
313+
314+
315+
Pressing the button will publish a message, which you can see by echoing the ``/output`` topic, like with this command.
316+
317+
.. code-block:: bash
318+
319+
ros2 topic echo /output
320+
321+
322+
Cleanup
323+
-------
324+
325+
Now its time to clean it up a bit.
326+
This makes things look nicer and be a little easier to use, but aren't strictly required.
327+
328+
First, you should update the description of your plugin in ``rviz_common_plugins.xml``
329+
330+
We also add an icon for the plugin at ``icons/classes/DemoPanel.png``.
331+
The folder is hardcoded, and the filename should match the name from the plugin declaration (or the name of the class if not specified).
332+
333+
We need to install the image file in the CMake.
334+
335+
.. code-block:: cmake
336+
337+
install(FILES icons/classes/DemoPanel.png
338+
DESTINATION share/${PROJECT_NAME}/icons/classes
339+
)
340+
341+
Now when you add the panel, it should show up with an icon and description.
342+
343+
.. image:: images/Select1.png
344+
:target: images/Select1.png
345+
:alt: screenshot of Add New Panel dialog with our custom icon and description
346+
347+
The panel will also have an updated icon.
348+
349+
.. image:: images/RViz3.png
350+
:target: images/RViz3.png
351+
:alt: screenshot of the RViz panel with custom icon
133 KB
Loading
5.68 KB
Loading
6.2 KB
Loading
5.97 KB
Loading
31.5 KB
Loading
37.4 KB
Loading

source/Tutorials/Intermediate/RViz/RViz-Main.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ RViz is a 3D visualizer for the Robot Operating System (ROS) framework.
1212

1313
RViz-User-Guide/RViz-User-Guide
1414
RViz-Custom-Display/RViz-Custom-Display
15+
RViz-Custom-Panel/RViz-Custom-Panel
1516
Marker-Display-types/Marker-Display-types

0 commit comments

Comments
 (0)