RViz 自定义 Panel
本教程面向希望在 RViz 中以二维形式显示数据、或与数据进行交互的用户。
在本教程中,你将学习如何在 RViz 中完成以下三件事:
- 创建一个新的 Qt Panel。
- 创建一个话题订阅者(Subscriber),用于监控指定话题上的消息,并将其显示在 Panel 中。
- 创建一个话题发布者(Publisher),使得 Panel 中的按钮按下后能向 ROS 话题发布消息。
本教程的所有代码可以在 此仓库 中找到。
基础模板代码
Section titled “基础模板代码”以下是 demo_panel.hpp 的内容:
#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_OBJECTpublic: explicit DemoPanel(QWidget * parent = 0); ~DemoPanel() override;};} // namespace rviz_panel_tutorial
#endif // RVIZ_PANEL_TUTORIAL__DEMO_PANEL_HPP_- 我们继承自 rviz_common::Panel 类。
- 由于一些不在本教程讨论范围内的原因,你需要在类中添加
Q_OBJECT宏,Qt 的 GUI 机制才能正常工作。 - 暂时只声明构造函数和析构函数,它们的实现放在 cpp 文件中。
demo_panel.cpp:
#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)- 重写构造函数和析构函数并非必需,但后续我们会在其中添加更多逻辑。
- 为了让 RViz 能发现我们的插件,需要在代码中添加这个
PLUGINLIB调用(以及下面的其他配置)。
package.xml
Section titled “package.xml”我们需要在 package.xml 中添加以下依赖:
<depend>pluginlib</depend><depend>rviz_common</depend>rviz_common_plugins.xml
Section titled “rviz_common_plugins.xml”<library path="demo_panel"> <class type="rviz_panel_tutorial::DemoPanel" base_class_type="rviz_common::Panel"> <description></description> </class></library>-
这是标准的
pluginlib代码。library的path是我们将在 CMake 中指定的库名称。class应与上面的PLUGINLIB调用保持一致。
-
描述内容稍后补充。
CMakeLists.txt
Section titled “CMakeLists.txt”在标准模板基础上,添加以下内容:
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>)target_link_libraries(demo_panel PUBLIC pluginlib::pluginlib rviz_common::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)-
为了生成正确的 Qt 文件,我们需要:
- 开启
CMAKE_AUTOMOC。 - 通过调用
qt5_wrap_cpp包装每个包含Q_OBJECT的头文件。 - 将
MOC_FILES与其他 cpp 文件一起包含到库中。
- 开启
-
其余大量代码用于保证插件机制正常运作。 其中,
pluginlib_export_plugin_description_file调用尤为关键——RViz 正是通过它来发现你的新插件的。
编译代码,source 你的工作空间,然后运行 rviz2。
顶部菜单栏中应该有一个「Panels」菜单。 从中选择「Add New Panel」。
{/截图:Add New Panel 对话框/}
此时会弹出一个对话框,列出 ROS 环境中所有可用的 Panel,按所属 ROS 包分组显示。 双击名称或选中后点击 OK,即可创建 Panel 的新实例。
这会在 RViz 窗口中添加一个新 Panel,不过目前只有一个带 Panel 名称的标题栏,还没有实际内容。
{/截图:RViz 窗口显示新的简单 Panel/}
填充 Panel 内容
Section titled “填充 Panel 内容”接下来为 Panel 添加一些基本的 ROS/Qt 交互功能。
整体思路是:从 RViz 内部访问 ROS 节点,用该节点同时实现话题的订阅与发布。
我们用订阅者来监听 /input 话题,并将收到的 String 消息显示在组件中;
用发布者将按钮点击事件映射为向 /output 话题发布的消息。
更新 demo_panel.hpp,包含以下头文件和类体。
#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_OBJECTpublic: 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- ROS 相关部分:我们声明了一个抽象节点指针,用它来与 ROS 生态系统交互。
订阅者用于从 ROS 获取信息并在 RViz 中使用;发布者则用于从 RViz 向 ROS 发布消息或事件。
此外还有用于初始化 ROS 组件的
onInitialize方法,以及订阅者回调topicCallback。 - Qt 相关部分:声明了一个标签(Label)和一个按钮(Button),以及按钮的回调
buttonActivated。
更新 demo_panel.cpp 为以下内容:
#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){ // 创建标签和按钮,垂直显示(VBox 中的 V 表示垂直) const auto layout = new QVBoxLayout(this); // 为按钮创建标签和按钮 label_ = new QLabel("[no data]"); button_ = new QPushButton("GO!"); // 将这些元素添加到 GUI 布局中 layout->addWidget(label_); layout->addWidget(button_);
// 将按钮释放的事件连接到我们的回调, // 这样按下按钮就会调用 buttonActivated 回调。 QObject::connect(button_, &QPushButton::released, this, &DemoPanel::buttonActivated);}
DemoPanel::~DemoPanel() = default;
void DemoPanel::onInitialize(){ // 获取抽象的 ROS Node 并 // 在此过程中锁定它以供独占使用,直到方法结束。 node_ptr_ = getDisplayContext()->getRosNodeAbstraction().lock();
// 获取熟悉的 rclcpp::Node 指针用于创建订阅者/发布者 // (与普通的 rclcpp 代码一样) rclcpp::Node::SharedPtr node = node_ptr_->get_raw_node();
// 为输出创建一个 String 发布者 publisher_ = node->create_publisher<std_msgs::msg::String>("/output", 10);
// 创建一个 String 订阅者并将其绑定到此类内部的 topicCallback。 subscription_ = node->create_subscription<std_msgs::msg::String>("/input", 10, std::bind(&DemoPanel::topicCallback, this, std::placeholders::_1));}
// 当订阅者收到消息时,此回调被触发,// 然后我们将其数据复制到组件的标签中void DemoPanel::topicCallback(const std_msgs::msg::String & msg){ label_->setText(QString(msg.data.c_str()));}
// 当组件的按钮被按下时,此回调被触发,// 然后我们在我们的 Topic 上发布一条新消息。void DemoPanel::buttonActivated(){ 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)使用 ROS 测试
Section titled “使用 ROS 测试”再次编译并启动 RViz2,加载你的 Panel。 现在你应该能在 Panel 中看到标签和按钮了。
{/截图:RViz Panel 的默认状态/}
要更新标签内容,只需在 /input 话题上发布一条消息:
ros2 topic pub /input std_msgs/msg/String "{data: 'Please be kind.'}"由于组件订阅了该话题,消息到达时就会触发回调,从而更新标签文本。
{/截图:RViz Panel 显示自定义字符串消息/}
按下按钮会发布一条消息,你可以用以下命令通过 echo /output 话题来查看:
ros2 topic echo /output接下来做一些清理工作。 这一步并非必需,但能让界面更美观、更易用。
首先,更新 rviz_common_plugins.xml 中插件的描述。
此外,我们在 icons/classes/DemoPanel.png 处为插件添加一个图标。
文件夹路径是硬编码的,文件名应与插件声明中的名称一致(若未指定则为类名)。
然后在 CMake 中安装该图片文件:
install(FILES icons/classes/DemoPanel.png DESTINATION share/${PROJECT_NAME}/icons/classes)现在添加 Panel 时,应该能看到图标和描述了。
{/截图:Add New Panel 对话框,显示自定义图标和描述/}
Panel 标题栏也会显示更新后的图标。
{/截图:RViz Panel 显示自定义图标/}