Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rviz Panel Tutorial #4869

Open
wants to merge 21 commits into
base: rolling
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
dbfd3eb
Rviz Panel Tutorial
DLu Nov 15, 2024
99703f8
Update source/Tutorials/Intermediate/RViz/RViz-Custom-Panel/RViz-Cust…
DLu Nov 18, 2024
0a11ad5
Add link to code, images, edits
DLu Nov 18, 2024
1234a9d
PR Feedack response for Kat
DLu Nov 21, 2024
be64144
Update source/Tutorials/Intermediate/RViz/RViz-Custom-Panel/RViz-Cust…
kscottz Nov 21, 2024
fa8d2d3
Update source/Tutorials/Intermediate/RViz/RViz-Custom-Panel/RViz-Cust…
kscottz Nov 21, 2024
c68f06e
Update source/Tutorials/Intermediate/RViz/RViz-Custom-Panel/RViz-Cust…
kscottz Nov 21, 2024
3ebfde1
Update source/Tutorials/Intermediate/RViz/RViz-Custom-Panel/RViz-Cust…
kscottz Nov 21, 2024
3173b6f
Update source/Tutorials/Intermediate/RViz/RViz-Custom-Panel/RViz-Cust…
kscottz Nov 21, 2024
e26fd4a
Update source/Tutorials/Intermediate/RViz/RViz-Custom-Panel/RViz-Cust…
kscottz Nov 21, 2024
c18a452
Update source/Tutorials/Intermediate/RViz/RViz-Custom-Panel/RViz-Cust…
kscottz Nov 21, 2024
21fad13
Update source/Tutorials/Intermediate/RViz/RViz-Custom-Panel/RViz-Cust…
kscottz Nov 21, 2024
ef1ae71
Update source/Tutorials/Intermediate/RViz/RViz-Custom-Panel/RViz-Cust…
kscottz Nov 21, 2024
f78d5d3
Update source/Tutorials/Intermediate/RViz/RViz-Custom-Panel/RViz-Cust…
kscottz Nov 21, 2024
affc0ab
Merge branch 'rolling' into rviz_panel
kscottz Nov 22, 2024
efd2b3b
Apply suggestions from code review
kscottz Nov 22, 2024
a6d233f
Apply suggestions from code review
kscottz Nov 22, 2024
97a3a35
Update source/Tutorials/Intermediate/RViz/RViz-Custom-Panel/RViz-Cust…
kscottz Nov 23, 2024
ed25f68
Update source/Tutorials/Intermediate/RViz/RViz-Main.rst
kscottz Nov 23, 2024
4b39fed
Update source/Tutorials/Intermediate/RViz/RViz-Main.rst
kscottz Nov 25, 2024
d5756dd
Update source/Tutorials/Intermediate/RViz/RViz-Main.rst
kscottz Nov 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,362 @@
Building a Custom RViz Panel
============================

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.
In this tutorial you will learn how to do three things within RViz:

* Create a new QT panel within RViz.
* Create a topic subscriber within RViz that can monitor messages published on that topic and print them to RViz panel.
* Create a topic publisher that maps button presses within RViz to an output topic within your ROS system.
In this tutorial you will learn how to do three things within RViz:
kscottz marked this conversation as resolved.
Show resolved Hide resolved

* Create a new QT panel within RViz.
* Create a topic subscriber within RViz that can monitor messages published on that topic and display them within the RViz panel.
* Create a topic publisher such button presses within RViz publish to an output topic in ROS.

kscottz marked this conversation as resolved.
Show resolved Hide resolved
All of the code for this tutorial can be found in `this repository <https://github.com/MetroRobots/rviz_panel_tutorial>`__.

Boilerplate Code
----------------

Header File
^^^^^^^^^^^

Here are the contents of ``demo_panel.hpp``

.. code-block:: c++

#ifndef RVIZ_PANEL_TUTORIAL__DEMO_PANEL_HPP_
#define RVIZ_PANEL_TUTORIAL__DEMO_PANEL_HPP_

#include <rviz_common/panel.hpp>

namespace rviz_panel_tutorial
{
class DemoPanel
: public rviz_common::Panel
{
Q_OBJECT
public:
explicit DemoPanel(QWidget * parent = 0);
~DemoPanel() override;
};
} // namespace rviz_panel_tutorial

#endif // RVIZ_PANEL_TUTORIAL__DEMO_PANEL_HPP_

* We're extending the `rviz_common::Panel <https://github.com/ros2/rviz/blob/9a94bdf2f5f92ccdac4037c9268b95940845d609/rviz_common/include/rviz_common/panel.hpp#L46>`__ class.
* `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.
* We start by declaring just a constructor and destructor, implemented in the cpp file.

Source File
^^^^^^^^^^^

``demo_panel.cpp``

.. code-block:: c++

#include <rviz_panel_tutorial/demo_panel.hpp>

namespace rviz_panel_tutorial
{
DemoPanel::DemoPanel(QWidget* parent) : Panel(parent)
{
}

DemoPanel::~DemoPanel() = default;
} // namespace rviz_panel_tutorial

#include <pluginlib/class_list_macros.hpp>
PLUGINLIB_EXPORT_CLASS(rviz_panel_tutorial::DemoPanel, rviz_common::Panel)

* Overriding the constructor and deconstructor are not strictly necessary, but we can do more with them later.
* In order for RViz to find our plugin, we need this ``PLUGINLIB`` invocation in our code (as well as other things below).

package.xml
^^^^^^^^^^^

We need the following dependencies in our package.xml:

.. code-block:: xml

<depend>pluginlib</depend>
<depend>rviz_common</depend>

rviz_common_plugins.xml
^^^^^^^^^^^^^^^^^^^^^^^

.. code-block:: xml

<library path="demo_panel">
<class type="rviz_panel_tutorial::DemoPanel" base_class_type="rviz_common::Panel">
<description></description>
</class>
</library>

* This is standard ``pluginlib`` code.

* The library ``path`` is the name of the library we'll assign in the CMake.
* The class should match the ``PLUGINLIB`` invocation from above.

* We'll come back to the description later, I promise.

CMakeLists.txt
^^^^^^^^^^^^^^

Add the following lines to the top of the standard boilerplate.

.. code-block:: cmake

find_package(ament_cmake_ros REQUIRED)
find_package(pluginlib REQUIRED)
find_package(rviz_common REQUIRED)

set(CMAKE_AUTOMOC ON)
qt5_wrap_cpp(MOC_FILES
include/rviz_panel_tutorial/demo_panel.hpp
)

add_library(demo_panel src/demo_panel.cpp ${MOC_FILES})
target_include_directories(demo_panel PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
ament_target_dependencies(demo_panel
pluginlib
rviz_common
)
install(TARGETS demo_panel
EXPORT export_rviz_panel_tutorial
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)
install(DIRECTORY include/
DESTINATION include
)
install(FILES rviz_common_plugins.xml
DESTINATION share/${PROJECT_NAME}
)
ament_export_include_directories(include)
ament_export_targets(export_rviz_panel_tutorial)
pluginlib_export_plugin_description_file(rviz_common rviz_common_plugins.xml)


* To generate the proper Qt files, we need to

* Turn ``CMAKE_AUTOMOC`` on.
* Wrap the headers by calling ``qt5_wrap_cpp`` with each header that has ``Q_OBJECT`` in it.
* Include the ``MOC_FILES`` in the library alongside our other cpp files.

* A lot of the other code ensures that the plugin portion works.
Namely, calling ``pluginlib_export_plugin_description_file`` is essential to getting RViz to find your new plugin.

Testing it out
^^^^^^^^^^^^^^

Compile your code, source your workspace and run ``rviz2``.

In the top Menu bar, there should be a "Panels" menu.
Select "Add New Panel" from that menu.

.. image:: images/Select0.png
:target: images/Select0.png
:alt: screenshot of Add New Panel dialog

A dialog will pop up showing all the panels accessible in your ROS environment, grouped into folders based on their ROS package.
Create a new instance of your panel by either double clicking on its name, or selecting it and clicking OK.

This should create a new panel in your RViz window, albeit with nothing but a title bar with the name of your panel.

.. image:: images/RViz0.png
:target: images/RViz0.png
:alt: screenshot of the whole RViz window showing the new simple panel

Filling in the Panel
--------------------
We're going to update our panel with some very basic ROS/QT interaction.
kscottz marked this conversation as resolved.
Show resolved Hide resolved
What we will do, roughly, is create something like a ROS node within RViz that can both subscribe and publish to ROS topics.
kscottz marked this conversation as resolved.
Show resolved Hide resolved
We will use our subscriber to monitor an '\input` topic within ROS and print published `String` values to the screen.
kscottz marked this conversation as resolved.
Show resolved Hide resolved
We use our publisher to map button presses within RViz to messages published on a ROS topic named `\output` .
kscottz marked this conversation as resolved.
Show resolved Hide resolved
What we will do, roughly, is access the ROS node from within RViz that can both subscribe and publish to ROS topics.
kscottz marked this conversation as resolved.
Show resolved Hide resolved
We will use our subscriber to monitor an ``/input`` topic within ROS and display the published ``String`` values in the widget.
kscottz marked this conversation as resolved.
Show resolved Hide resolved
We use our publisher to map button presses within RViz to messages published on a ROS topic named ``/output`` .
kscottz marked this conversation as resolved.
Show resolved Hide resolved

Updated Header File
^^^^^^^^^^^^^^^^^^^

Update ``demo_panel.hpp`` to include the following includes and class Body.

.. code-block:: c++

#include <rviz_common/panel.hpp>
#include <rviz_common/ros_integration/ros_node_abstraction_iface.hpp>
#include <std_msgs/msg/string.hpp>
#include <QLabel>
#include <QPushButton>

namespace rviz_panel_tutorial
{
class DemoPanel : public rviz_common::Panel
{
Q_OBJECT
public:
explicit DemoPanel(QWidget * parent = 0);
~DemoPanel() override;

void onInitialize() override;

protected:
std::shared_ptr<rviz_common::ros_integration::RosNodeAbstractionIface> node_ptr_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;

void topicCallback(const std_msgs::msg::String & msg);

QLabel* label_;
QPushButton* button_;

private Q_SLOTS:
void buttonActivated();
};
} // namespace rviz_panel_tutorial

* On the ROS side, we declare an abstract node pointer, which we will use to create interfaces to the wider ROS ecosystem.
We have a subscriber which will allow us to take information from ROS and use it in RViz.
The publisher allows us to publish information/events from within RViz and make them available in ROS.
We also have methods an initialization method for setting up the ROS components (``onInitialize``) and a callback for the subscriber (``topicCallback``).
* On the QT side, we declare a label and a button, as well as a callback for the button (``buttonActivated``).

Updated Source File
^^^^^^^^^^^^^^^^^^^

Update ``demo_panel.cpp`` to have the following contents:

.. code-block:: c++

#include <rviz_panel_tutorial/demo_panel.hpp>
#include <QVBoxLayout>
#include <rviz_common/display_context.hpp>

namespace rviz_panel_tutorial
{

DemoPanel::DemoPanel(QWidget* parent) : Panel(parent)
{
// Create a label and a button, displayed vertically (the V in VBox means vertical)
const auto layout = new QVBoxLayout(this);
// Create a button and a label for the button
label_ = new QLabel("[no data]");
kscottz marked this conversation as resolved.
Show resolved Hide resolved
button_ = new QPushButton("GO!");
// Add the button to the GUI layout
layout->addWidget(label_);
kscottz marked this conversation as resolved.
Show resolved Hide resolved
layout->addWidget(button_);

// Connect the event of when the button is released to our callback,
// so pressing the button results in the callback being called.
// Create a callback such that when our button is released the buttonActivated callback is called.
kscottz marked this conversation as resolved.
Show resolved Hide resolved
QObject::connect(button_, &QPushButton::released, this, &DemoPanel::buttonActivated);
kscottz marked this conversation as resolved.
Show resolved Hide resolved
}

DemoPanel::~DemoPanel() = default;

void DemoPanel::onInitialize()
{
// Access the abstract ROS Node and
// in the process lock it for exclusive use until the method is done.
// on init add a thread lock to our ROS node.
kscottz marked this conversation as resolved.
Show resolved Hide resolved
node_ptr_ = getDisplayContext()->getRosNodeAbstraction().lock();
kscottz marked this conversation as resolved.
Show resolved Hide resolved

// Get a pointer to the familiar rclcpp::Node for making subscriptions/publishers
// (as per normal rclcpp code)
// grab a shared pointer from our target node from which we'll get our data
rclcpp::Node::SharedPtr node = node_ptr_->get_raw_node();
kscottz marked this conversation as resolved.
Show resolved Hide resolved
publisher_ = node->create_publisher<std_msgs::msg::String>("/output", 10);
// In our target node, create a subscription, of type String, on topic input, and bind it to the demoCallback inside this class.
kscottz marked this conversation as resolved.
Show resolved Hide resolved
subscription_ = node->create_subscription<std_msgs::msg::String>("/input", 10, std::bind(&DemoPanel::topicCallback, this, std::placeholders::_1));
kscottz marked this conversation as resolved.
Show resolved Hide resolved
}

// When the subscriber gets a message, this callback is triggered,
// and then we copy its data into the widget's label
// When our node interface gets a message on its input topic this callback
// gets called and we copy the message text to our button label.
void DemoPanel::topicCallback(const std_msgs::msg::String & msg)
kscottz marked this conversation as resolved.
Show resolved Hide resolved
{
label_->setText(QString(msg.data.c_str()));
}

// When the widget's button is pressed, this callback is triggered,
// and then we publish a new message on our topic.
// When the user clicks the button on our panel this function will send a
kscottz marked this conversation as resolved.
Show resolved Hide resolved
// a "Button clicked!" message on the /output topic.
void DemoPanel::buttonActivated()
kscottz marked this conversation as resolved.
Show resolved Hide resolved
{
auto message = std_msgs::msg::String();
message.data = "Button clicked!";
publisher_->publish(message);
}

} // namespace rviz_panel_tutorial

#include <pluginlib/class_list_macros.hpp>

PLUGINLIB_EXPORT_CLASS(rviz_panel_tutorial::DemoPanel, rviz_common::Panel)

Testing with ROS
^^^^^^^^^^^^^^^^
Compile and launch RViz2 with your panel again. You should see your label and button in the panel now.

.. image:: images/RViz1.png
:target: images/RViz1.png
:alt: screenshot of the RViz panel in its default state

To change the label, we simply have to publish a message on the ``/input`` topic, which you can do with this command:

.. code-block:: bash

ros2 topic pub /input std_msgs/msg/String "{data: 'Please be kind.'}"

Since the widget is subscribed to this topic, it will trigger the callback and change the text of the label.

.. image:: images/RViz2.png
:target: images/RViz2.png
:alt: screenshot of the RViz panel with custom string message displayed


Pressing the button will publish a message, which you can see by echoing the ``/output`` topic, like with this command.

.. code-block:: bash

ros2 topic echo /output


Cleanup
-------

Now its time to clean it up a bit.
This makes things look nicer and be a little easier to use, but aren't strictly required.

First, you should update the description of your plugin in ``rviz_common_plugins.xml``

We also add an icon for the plugin at ``icons/classes/DemoPanel.png``.
The folder is hardcoded, and the filename should match the name from the plugin declaration (or the name of the class if not specified).

We need to install the image file in the CMake.

.. code-block:: cmake

install(FILES icons/classes/DemoPanel.png
DESTINATION share/${PROJECT_NAME}/icons/classes
)

Now when you add the panel, it should show up with an icon and description.

.. image:: images/Select1.png
:target: images/Select1.png
:alt: screenshot of Add New Panel dialog with our custom icon and description

The panel will also have an updated icon.

.. image:: images/RViz3.png
:target: images/RViz3.png
:alt: screenshot of the RViz panel with custom icon
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion source/Tutorials/Intermediate/RViz/RViz-Main.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ RViz is a 3D visualizer for the Robot Operating System (ROS) framework.

RViz-User-Guide/RViz-User-Guide
RViz-Custom-Display/RViz-Custom-Display
Marker-Display-types/Marker-Display-types
RViz-Custom-Panel/RViz-Custom-Panel
Marker-Display-types/Marker-Display-types
kscottz marked this conversation as resolved.
Show resolved Hide resolved
kscottz marked this conversation as resolved.
Show resolved Hide resolved
Loading