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 3 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,326 @@
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.

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

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, publisher and subscriber.
kscottz marked this conversation as resolved.
Show resolved Hide resolved
We also have methods to initialize them and a callback for the subscriber.
* On the QT side, we declare a label and a button, as well as a callback for the button.

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)
{
const auto layout = new QVBoxLayout(this);
label_ = new QLabel("[no data]");
kscottz marked this conversation as resolved.
Show resolved Hide resolved
button_ = new QPushButton("GO!");
layout->addWidget(label_);
kscottz marked this conversation as resolved.
Show resolved Hide resolved
layout->addWidget(button_);

QObject::connect(button_, &QPushButton::released, this, &DemoPanel::buttonActivated);
kscottz marked this conversation as resolved.
Show resolved Hide resolved
}

DemoPanel::~DemoPanel() = default;

void DemoPanel::onInitialize()
{
node_ptr_ = getDisplayContext()->getRosNodeAbstraction().lock();
kscottz marked this conversation as resolved.
Show resolved Hide resolved
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);
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
}

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()));
}

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)

* In the constructor, we add the two widgets to a layout object, and connect the callback to the button object.
kscottz marked this conversation as resolved.
Show resolved Hide resolved
* In the ``onInitialize`` method, we can access the ``DisplayContext`` which allows us access to the ROS interfaces.
We get a pointer to the actual node interface, and then create a standard publisher/subscriber.
* When we get a message from the subscription, we update the label.
* When the button is clicked, we publish a new message.

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, run
kscottz marked this conversation as resolved.
Show resolved Hide resolved

.. code-block:: bash

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

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


You can also see the results of pushing the button:
kscottz marked this conversation as resolved.
Show resolved Hide resolved

.. 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.
1 change: 1 addition & 0 deletions source/Tutorials/Intermediate/RViz/RViz-Main.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ RViz is a 3D visualizer for the Robot Operating System (ROS) framework.

RViz-User-Guide/RViz-User-Guide
RViz-Custom-Display/RViz-Custom-Display
RViz-Custom-Panel/RViz-Custom-Panel