背景
在 RViz 中有许多类型的数据已经有现有的可视化。然而,如果有一种消息类型尚未有插件来显示它,那么有两种选择可以在 RViz 中查看它。
将消息转换为另一种类型,例如
visualization_msgs/Marker
。编写自定义 RViz 显示。
使用第一个选项,会有更多的网络流量和数据表示的限制。它也快速且灵活。后一种选择在本教程中进行了说明。虽然需要一些工作,但可以带来更丰富的可视化效果。
本教程的所有代码都可以在此存储库 https://github.com/MetroRobots/rviz_plugin_tutorial 中找到。为了查看本教程中编写的插件的增量进展,存储库有不同的分支( step2
, step3
……),每个分支都可以在您进行时编译和运行。
cxy@ubuntu2404-cxy:~/ros2_ws$ colcon build --packages-select rviz_plugin_tutorial_msgs
Starting >>> rviz_plugin_tutorial_msgs
Finished <<< rviz_plugin_tutorial_msgs [24.6s]
Summary: 1 package finished [24.9s]
cxy@ubuntu2404-cxy:~/ros2_ws$ . install/setup.bash
cxy@ubuntu2404-cxy:~/ros2_ws$ colcon build --packages-select rviz_plugin_tutorial
Starting >>> rviz_plugin_tutorial
Finished <<< rviz_plugin_tutorial [25.8s]
Summary: 1 package finished [26.8s]
cxy@ubuntu2404-cxy:~/ros2_ws$ . install/setup.bash
cxy@ubuntu2404-cxy:~/ros2_ws$ ros2 run rviz2 rviz2
[INFO] [1720966890.438943145] [rviz2]: Stereo is NOT SUPPORTED
[INFO] [1720966890.439067206] [rviz2]: OpenGl version: 4.6 (GLSL 4.6)
[INFO] [1720966890.559429080] [rviz2]: Stereo is NOT SUPPORTED
Step By Step
点 2D 消息
我们将使用 rviz_plugin_tutorial_msgs
包中定义的玩具消息: Point2D.msg
:
std_msgs/Header header
float64 x
float64 y
基本插件的样板代码
系好安全带,这里有很多代码。您可以使用分支名称 step1
查看此代码的完整版本。
头文件
以下是 point_display.hpp
的内容
// 如果没有定义 RVIZ_PLUGIN_TUTORIAL__POINT_DISPLAY_HPP_,则定义它
#ifndef RVIZ_PLUGIN_TUTORIAL__POINT_DISPLAY_HPP_
#define RVIZ_PLUGIN_TUTORIAL__POINT_DISPLAY_HPP_
// 包含 rviz_common 的消息过滤显示库
#include <rviz_common/message_filter_display.hpp>
// 包含 rviz_plugin_tutorial_msgs 的 Point2D 消息库
#include <rviz_plugin_tutorial_msgs/msg/point2_d.hpp>
// 定义 rviz_plugin_tutorial 命名空间
namespace rviz_plugin_tutorial
{
// 定义 PointDisplay 类,它是 rviz_common::MessageFilterDisplay 类的公共派生类
class PointDisplay
: public rviz_common::MessageFilterDisplay<rviz_plugin_tutorial_msgs::msg::Point2D>
{
// 使用 Qt 的元对象系统
Q_OBJECT
protected:
// 重写 processMessage 方法,该方法用于处理 Point2D 消息
void processMessage(const rviz_plugin_tutorial_msgs::msg::Point2D::ConstSharedPtr msg) override;
};
} // 结束 rviz_plugin_tutorial 命名空间的定义
// 结束 RVIZ_PLUGIN_TUTORIAL__POINT_DISPLAY_HPP_ 的定义
#endif // RVIZ_PLUGIN_TUTORIAL__POINT_DISPLAY_HPP_
我们正在实现 MessageFilterDisplay 类,该类可用于任何带有
std_msgs/Header
的消息。该类使用我们的
Point2D
消息类型进行模板化。由于本教程范围之外的原因,您需要在其中使用
Q_OBJECT
宏才能使 GUI 的 QT 部分正常工作。processMessage
是唯一需要实现的方法,我们将在 cpp 文件中实现。
源文件
point_display.cpp
// 包含 rviz_plugin_tutorial 的 PointDisplay 库
#include <rviz_plugin_tutorial/point_display.hpp>
// 包含 rviz_common 的日志库
#include <rviz_common/logging.hpp>
// 定义 rviz_plugin_tutorial 命名空间
namespace rviz_plugin_tutorial
{
// 定义 PointDisplay 类的 processMessage 方法,该方法用于处理 Point2D 消息
void PointDisplay::processMessage(const rviz_plugin_tutorial_msgs::msg::Point2D::ConstSharedPtr msg)
{
// 使用 RVIZ_COMMON_LOG_INFO_STREAM 宏打印接收到的消息的帧ID
RVIZ_COMMON_LOG_INFO_STREAM("We got a message with frame " << msg->header.frame_id);
}
} // 结束 rviz_plugin_tutorial 命名空间的定义
// 包含 pluginlib 的类列表宏库
#include <pluginlib/class_list_macros.hpp>
// 使用 PLUGINLIB_EXPORT_CLASS 宏导出 PointDisplay 类,使其成为 rviz_common::Display 的插件
PLUGINLIB_EXPORT_CLASS(rviz_plugin_tutorial::PointDisplay, rviz_common::Display)
记录不是严格必要的,但有助于调试。
为了让 RViz 找到我们的插件,我们需要在代码中使用这个
PLUGINLIB
调用(以及下面的其他内容)。
package.xml
我们需要在我们的 package.xml 中包含以下三个依赖项:
<depend>pluginlib</depend>
<depend>rviz_common</depend>
<depend>rviz_plugin_tutorial_msgs</depend>
rviz_common_plugins.xml
<library path="point_display">
<class type="rviz_plugin_tutorial::PointDisplay" base_class_type="rviz_common::Display">
<description></description>
</class>
</library>
这是标准
pluginlib
代码。库
path
是我们将在 CMake 中分配的库的名称。类应与上面的
PLUGINLIB
调用匹配。
我们稍后会回到描述部分,我保证。
CMakeLists.txt
将以下几行添加到标准样板的顶部。
// 寻找ament_cmake_ros包,这是ROS2的构建系统
find_package(ament_cmake_ros REQUIRED)
// 寻找pluginlib包,这是ROS的插件库
find_package(pluginlib REQUIRED)
// 寻找rviz_common包,这是RViz的通用库
find_package(rviz_common REQUIRED)
// 寻找rviz_plugin_tutorial_msgs包,这是我们自定义的消息类型库
find_package(rviz_plugin_tutorial_msgs REQUIRED)
// 开启CMake的自动MOC功能,用于处理Qt的元对象编译
set(CMAKE_AUTOMOC ON)
// 使用Qt5的MOC预处理器处理头文件,生成MOC文件
qt5_wrap_cpp(MOC_FILES
include/rviz_plugin_tutorial/point_display.hpp
)
// 添加一个库,名为point_display,源文件包括point_display.cpp和上面生成的MOC文件
add_library(point_display src/point_display.cpp ${MOC_FILES})
// 设置point_display库的公共包含目录
target_include_directories(point_display PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
// 设置point_display库的依赖项
ament_target_dependencies(point_display
pluginlib
rviz_common
rviz_plugin_tutorial_msgs
)
// 安装point_display库,包括静态库、动态库和可执行文件
install(TARGETS point_display
EXPORT export_rviz_plugin_tutorial
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)
// 安装include目录下的所有头文件
install(DIRECTORY include/
DESTINATION include
)
// 安装rviz_common_plugins.xml文件
install(FILES rviz_common_plugins.xml
DESTINATION share/${PROJECT_NAME}
)
// 导出include目录
ament_export_include_directories(include)
// 导出rviz_plugin_tutorial目标
ament_export_targets(export_rviz_plugin_tutorial)
// 导出插件描述文件
pluginlib_export_plugin_description_file(rviz_common rviz_common_plugins.xml)
要生成正确的 Qt 文件,我们需要
打开
CMAKE_AUTOMOC
。通过调用
qt5_wrap_cpp
包装每个包含Q_OBJECT
的标题。将
MOC_FILES
包含在库中,与我们的其他 cpp 文件一起。
请注意,如果您不包装头文件,在尝试在运行时加载插件时,可能会收到类似以下内容的错误消息:
[rviz2]: PluginlibFactory: The plugin for class 'rviz_plugin_tutorial::PointDisplay' failed to load. Error: Failed to load library /home/ros/ros2_ws/install/rviz_plugin_tutorial/lib/libpoint_display.so. Make sure that you are calling the PLUGINLIB_EXPORT_CLASS macro in the library code, and that names are consistent between this macro and your XML. Error string: Could not load library LoadLibrary error: /home/ros/ros2_ws/install/rviz_plugin_tutorial/lib/libpoint_display.so: undefined symbol: _ZTVN20rviz_plugin_tutorial12PointDisplayE, at /tmp/binarydeb/ros-foxy-rcutils-1.1.4/src/shared_library.c:84
许多其他代码确保插件部分正常工作。即,调用
pluginlib_export_plugin_description_file
对于让 RViz 找到您的新插件至关重要。
测试一下
编译你的代码并运行 rviz2
。你应该能够通过点击左下角的 Add
,然后选择你的包/插件来添加你的新插件。
最初,显示将处于错误状态,因为您尚未分配主题。
如果我们将主题 /point
放入,它应该加载正常但不会显示任何内容。
您可以使用以下命令发布消息:
ros2 topic pub /point rviz_plugin_tutorial_msgs/msg/Point2D "{header: {frame_id: map}, x: 1, y: 2}" -r 0.5
这应该会导致“我们收到了一条消息”的日志出现在 RViz 的 stdout
中。
实际可视化
您可以查看此步骤的完整版本,分支名称为 step2
。
首先,您需要在 CMakeLists.txt
和 package.xml
中添加对包 rviz_rendering
的依赖。
我们需要在头文件中添加三行:
在 rviz_rendering 包中有很多选项可以用于构建可视化对象。这里我们使用一个简单的形状。
在类里,我们将添加一个新的
protected
虚拟方法:void onInitialize() override;
我们还添加了一个指向我们形状对象的指针:
std::unique_ptr<rviz_rendering::Shape> point_shape_;
然后在 cpp 文件中,我们定义 onInitialize
方法:
// 定义PointDisplay类的onInitialize方法
void PointDisplay::onInitialize()
{
// 调用MFDClass类的onInitialize方法
MFDClass::onInitialize();
// 创建一个新的rviz_rendering::Shape对象,类型为Cube
// 并将其赋值给point_shape_成员变量
point_shape_ =
std::make_unique<rviz_rendering::Shape>(rviz_rendering::Shape::Type::Cube, scene_manager_,
scene_node_);
}
MFDClass
被别名化为模板化父类以方便使用。形状对象必须在
onInitialize
方法中构建,而不是在构造函数中构建,因为否则scene_manager_
和scene_node_
将无法准备好。
我们还更新了我们的 processMessage
方法:
// 定义PointDisplay类的processMessage方法
void PointDisplay::processMessage(const rviz_plugin_tutorial_msgs::msg::Point2D::ConstSharedPtr msg)
{
// 打印接收到的消息的frame_id
RVIZ_COMMON_LOG_INFO_STREAM("We got a message with frame " << msg->header.frame_id);
// 定义位置和方向变量
Ogre::Vector3 position;
Ogre::Quaternion orientation;
// 如果无法从消息的header获取变换,则打印错误信息
if (!context_->getFrameManager()->getTransform(msg->header, position, orientation)) {
RVIZ_COMMON_LOG_DEBUG_STREAM("Error transforming from frame '" << msg->header.frame_id <<
"' to frame '" << qPrintable(fixed_frame_) << "'");
}
// 设置场景节点的位置和方向
scene_node_->setPosition(position);
scene_node_->setOrientation(orientation);
// 定义点的位置
Ogre::Vector3 point_pos;
point_pos.x = msg->x;
point_pos.y = msg->y;
// 设置点的位置
point_shape_->setPosition(point_pos);
}
我们需要为我们的消息获取适当的框架,并相应地转换
scene_node_
。这确保了可视化不会总是相对于固定框架出现。实际的可视化内容在最后四行:我们将可视化的位置设置为与消息的位置匹配。
结果应如下所示:
如果该位置没有出现该框,可能是因为:
此时您未发布该主题
消息在过去 2 秒内尚未发布。
您没有正确设置 RViz 中的主题。
有选择真好
如果您想允许用户自定义可视化的不同属性,则需要添加 rviz_common::Property 对象。https://github.com/ros2/rviz/tree/ros2/rviz_common/include/rviz_common/properties
您可以查看此步骤的完整版本,分支名称为 step3
。
标题更新
包含颜色属性的头文件: #include <rviz_common/properties/color_property.hpp>
。颜色只是您可以设置的众多属性之一。
添加 updateStyle
的原型,每当通过 Qt 的 SIGNAL/SLOT 框架更改 GUI 时调用它
private Q_SLOTS:
void updateStyle();
添加一个新属性来存储属性本身: std::unique_ptr<rviz_common::properties::ColorProperty> color_property_;
Cpp 更新
#include <rviz_common/properties/parse_color.hpp>
- 包含将属性转换为 OGRE 颜色的辅助函数。为了我们的
onInitialize
,我们添加
color_property_ = std::make_unique<rviz_common::properties::ColorProperty>(
"Point Color", QColor(36, 64, 142), "Color to draw the point.", this, SLOT(updateStyle()));
updateStyle();
这将构建具有其名称、默认值、描述和回调的对象。
我们直接调用
updateStyle
,以便在属性更改之前就设置颜色。然后我们定义回调函数。
void PointDisplay::updateStyle()
{
Ogre::ColourValue color = rviz_common::properties::qtToOgre(color_property_->getColor());
point_shape_->setColor(color);
}
结果应如下所示:
哦,粉红色的!
状态报告
您可以查看此步骤的完整版本,分支名称为 step4
。
您还可以设置显示状态。作为一个任意示例,让我们在 x 坐标为负时显示警告,为什么不呢?在 processMessage
:
if (msg->x < 0) {
setStatus(StatusProperty::Warn, "Message",
"I will complain about points with negative x values.");
} else {
setStatus(StatusProperty::Ok, "Message", "OK");
}
我们假设之前的
using rviz_common::properties::StatusProperty;
声明。将状态视为键/值对,其中键是某个字符串(这里我们使用
"Message"
),值是状态级别(错误/警告/正常)和描述(其他一些字符串)。
清理
现在是时候清理一下了。这使得事情看起来更好看,也更容易使用,但不是严格要求的。您可以查看此步骤的完整版本,分支名称为 step5
。
首先,我们更新插件声明。
<library path="point_display">
<class name="Point2D" type="rviz_plugin_tutorial::PointDisplay" base_class_type="rviz_common::Display">
<description>Tutorial to display a point</description>
<message_type>rviz_plugin_tutorial_msgs/msg/Point2D</message_type>
</class>
</library>
我们将
name
字段添加到class
标签。这会更改在 RViz 中显示的名称。在代码中,将其称为PointDisplay
是有意义的,但在 RViz 中,我们希望简化。我们将实际文本放入描述中。不要偷懒。
通过在此处声明特定的消息类型,当您尝试按主题添加显示时,它将为该类型的主题建议此插件。
我们还在 icons/classes/Point2D.png
添加了插件的图标。文件夹是硬编码的,文件名应与插件声明中的名称(或未指定时的类名)匹配。[图标来源]
我们需要在 CMake 中安装镜像文件。
install(FILES icons/classes/Point2D.png
DESTINATION share/${PROJECT_NAME}/icons/classes
)
现在,当您添加显示时,它应该显示一个图标和描述。
以下是按主题添加时的显示:
最后,这是标准界面中的图标:
注意,如果更改插件名称,先前的 RViz 配置将不再有效。
附录:
point_display.hpp
// 防止头文件被重复包含
#ifndef RVIZ_PLUGIN_TUTORIAL__POINT_DISPLAY_HPP_
#define RVIZ_PLUGIN_TUTORIAL__POINT_DISPLAY_HPP_
// 包含所需的头文件
#include <memory>
#include <rviz_common/message_filter_display.hpp>
#include <rviz_common/properties/color_property.hpp>
#include <rviz_plugin_tutorial_msgs/msg/point2_d.hpp>
#include <rviz_rendering/objects/shape.hpp>
// 定义rviz_plugin_tutorial命名空间
namespace rviz_plugin_tutorial
{
// 定义PointDisplay类,继承自MessageFilterDisplay
class PointDisplay
: public rviz_common::MessageFilterDisplay<rviz_plugin_tutorial_msgs::msg::Point2D>
{
Q_OBJECT // 使用Qt的信号和槽机制
private Q_SLOTS:
// 定义私有槽函数updateStyle
void updateStyle();
protected:
// 重写onInitialize函数
void onInitialize() override;
// 重写processMessage函数,处理接收到的消息
void processMessage(const rviz_plugin_tutorial_msgs::msg::Point2D::ConstSharedPtr msg) override;
// 定义unique_ptr类型的point_shape_和color_property_成员变量
std::unique_ptr<rviz_rendering::Shape> point_shape_;
std::unique_ptr<rviz_common::properties::ColorProperty> color_property_;
};
} // 结束rviz_plugin_tutorial命名空间的定义
// 结束防止头文件被重复包含的预处理器指令
#endif // RVIZ_PLUGIN_TUTORIAL__POINT_DISPLAY_HPP_
point_display.cpp
// 包含所需的头文件
#include <rviz_plugin_tutorial/point_display.hpp>
#include <rviz_common/properties/parse_color.hpp>
#include <rviz_common/logging.hpp>
// 定义rviz_plugin_tutorial命名空间
namespace rviz_plugin_tutorial
{
// 使用rviz_common::properties::StatusProperty
using rviz_common::properties::StatusProperty;
// 定义PointDisplay类的onInitialize函数
void PointDisplay::onInitialize()
{
// 调用父类的onInitialize函数
MFDClass::onInitialize();
// 创建一个新的Shape对象,类型为Cube
point_shape_ =
std::make_unique<rviz_rendering::Shape>(rviz_rendering::Shape::Type::Cube, scene_manager_,
scene_node_);
// 创建一个新的ColorProperty对象,用于设置点的颜色
color_property_ = std::make_unique<rviz_common::properties::ColorProperty>(
"Point Color", QColor(36, 64, 142), "Color to draw the point.", this, SLOT(updateStyle()));
// 更新样式
updateStyle();
}
// 定义PointDisplay类的processMessage函数,处理接收到的消息
void PointDisplay::processMessage(const rviz_plugin_tutorial_msgs::msg::Point2D::ConstSharedPtr msg)
{
// 打印接收到的消息的帧ID
RVIZ_COMMON_LOG_INFO_STREAM("We got a message with frame " << msg->header.frame_id);
// 定义位置和方向变量
Ogre::Vector3 position;
Ogre::Quaternion orientation;
// 获取消息的转换
if (!context_->getFrameManager()->getTransform(msg->header, position, orientation)) {
// 如果获取转换失败,打印错误信息
RVIZ_COMMON_LOG_DEBUG_STREAM("Error transforming from frame '" << msg->header.frame_id <<
"' to frame '" << qPrintable(fixed_frame_) << "'");
}
// 设置场景节点的位置和方向
scene_node_->setPosition(position);
scene_node_->setOrientation(orientation);
// 如果x值小于0,设置状态为警告
if (msg->x < 0) {
setStatus(StatusProperty::Warn, "Message",
"I will complain about points with negative x values.");
} else {
// 否则,设置状态为OK
setStatus(StatusProperty::Ok, "Message", "OK");
}
// 设置点的位置
Ogre::Vector3 point_pos;
point_pos.x = msg->x;
point_pos.y = msg->y;
point_shape_->setPosition(point_pos);
}
// 定义PointDisplay类的updateStyle函数,更新样式
void PointDisplay::updateStyle()
{
// 获取颜色属性的颜色值,并设置到点的形状上
Ogre::ColourValue color = rviz_common::properties::qtToOgre(color_property_->getColor());
point_shape_->setColor(color);
}
} // 结束rviz_plugin_tutorial命名空间的定义
// 包含pluginlib的宏定义
#include <pluginlib/class_list_macros.hpp>
// 导出PointDisplay类,使其可以作为rviz_common::Display的插件
PLUGINLIB_EXPORT_CLASS(rviz_plugin_tutorial::PointDisplay, rviz_common::Display)
CMakeLists.txt
// 设置CMake的最低版本要求为3.5
cmake_minimum_required(VERSION 3.5)
// 定义项目名称为rviz_plugin_tutorial
project(rviz_plugin_tutorial)
// 默认使用C99标准
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()
// 默认使用C++17标准
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
endif()
// 如果编译器是GNU或Clang,添加编译选项
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
// 寻找必要的包
find_package(ament_cmake REQUIRED)
find_package(ament_cmake_ros REQUIRED)
find_package(pluginlib REQUIRED)
find_package(rviz_common REQUIRED)
find_package(rviz_plugin_tutorial_msgs REQUIRED)
find_package(rviz_rendering REQUIRED)
// 开启CMake的自动MOC功能
set(CMAKE_AUTOMOC ON)
// 使用Qt的MOC预处理一些头文件
qt5_wrap_cpp(MOC_FILES
include/rviz_plugin_tutorial/point_display.hpp
)
// 添加一个库
add_library(point_display src/point_display.cpp ${MOC_FILES})
// 设置库的包含目录
target_include_directories(point_display PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
// 设置库的依赖
ament_target_dependencies(point_display
pluginlib
rviz_common
rviz_plugin_tutorial_msgs
rviz_rendering
)
// 安装库
install(TARGETS point_display
EXPORT export_rviz_plugin_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}
)
// 安装图标文件
install(FILES icons/classes/Point2D.png
DESTINATION share/${PROJECT_NAME}/icons/classes
)
// 导出包含目录
ament_export_include_directories(include)
// 导出目标
ament_export_targets(export_rviz_plugin_tutorial)
// 导出插件描述文件
pluginlib_export_plugin_description_file(rviz_common rviz_common_plugins.xml)
// 如果开启了测试,寻找测试依赖
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
ament_lint_auto_find_test_dependencies()
endif()
// 打包
ament_package()
package.xml
<!-- 设置XML版本为1.0 -->
<?xml version="1.0"?>
<!-- 设置XML模式 -->
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<!-- 定义包的格式为3 -->
<package format="3">
<!-- 设置包的名称为rviz_plugin_tutorial -->
<name>rviz_plugin_tutorial</name>
<!-- 设置包的版本为0.0.0 -->
<version>0.0.0</version>
<!-- 设置包的描述 -->
<description>A tutorial how to create a new plugin for rviz</description>
<!-- 设置包的维护者 -->
<maintainer email="davidvlu@gmail.com">David V. Lu!!</maintainer>
<!-- 设置包的许可证为BSD -->
<license>BSD</license>
<!-- 设置构建工具依赖为ament_cmake -->
<buildtool_depend>ament_cmake</buildtool_depend>
<!-- 设置包的依赖 -->
<depend>pluginlib</depend>
<depend>rviz_common</depend>
<depend>rviz_plugin_tutorial_msgs</depend>
<depend>rviz_rendering</depend>
<!-- 设置测试依赖 -->
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<!-- 导出构建类型 -->
<export>
<build_type>ament_cmake</build_type>
</export>
<!-- 结束包的定义 -->
</package>
rviz_common_plugins.xml
<!-- 定义库的路径为point_display -->
<library path="point_display">
<!-- 定义一个类,名称为Point2D,类型为rviz_plugin_tutorial::PointDisplay,基类类型为rviz_common::Display -->
<class name="Point2D" type="rviz_plugin_tutorial::PointDisplay" base_class_type="rviz_common::Display">
<!-- 设置类的描述 -->
<description>Tutorial to display a point</description>
<!-- 设置类的消息类型 -->
<message_type>rviz_plugin_tutorial_msgs/msg/Point2D</message_type>
<!-- 结束类的定义 -->
</class>
<!-- 结束库的定义 -->
</library>