【ROS2】中级:RViz-构建自定义 RViz 显示

news2024/11/13 23:00:45

 背景 

在 RViz 中有许多类型的数据已经有现有的可视化。然而,如果有一种消息类型尚未有插件来显示它,那么有两种选择可以在 RViz 中查看它

  1. 将消息转换为另一种类型,例如 visualization_msgs/Marker 。

  2. 编写自定义 RViz 显示。

使用第一个选项,会有更多的网络流量和数据表示的限制。它也快速且灵活。后一种选择在本教程中进行了说明。虽然需要一些工作,但可以带来更丰富的可视化效果。

本教程的所有代码都可以在此存储库 https://github.com/MetroRobots/rviz_plugin_tutorial 中找到。为了查看本教程中编写的插件的增量进展,存储库有不同的分支( step2 , step3 ……),每个分支都可以在您进行时编译和运行。

93b9388f7b6ad90c18d7a49540ba211d.png

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

4f07c7cef75a626853d32e590a344d80.png

f15a2ce975353f08f1c4497b3e1456d7.png

Step By Step

 点 2D 消息

我们将使用 rviz_plugin_tutorial_msgs 包中定义的玩具消息: Point2D.msg :

std_msgs/Header header
float64 x
float64 y

基本插件的样板代码

系好安全带,这里有很多代码。您可以使用分支名称 step1 查看此代码的完整版本。

69529cb93af05b89215ce2da5ae940f6.png

头文件 

以下是 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 ,然后选择你的包/插件来添加你的新插件。

de44b6aeeb5fbf1e61297fe2b359b8c6.png

最初,显示将处于错误状态,因为您尚未分配主题。

7679fbe52cfb4af6330bb69bb2cac16c.png

如果我们将主题 /point 放入,它应该加载正常但不会显示任何内容。

e1ae54a591d5e8f879d23ecbbe1671e4.png

您可以使用以下命令发布消息:

ros2 topic pub /point rviz_plugin_tutorial_msgs/msg/Point2D "{header: {frame_id: map}, x: 1, y: 2}" -r 0.5

068d2d5dd9cb015bc474961106b0c3b0.png

这应该会导致“我们收到了一条消息”的日志出现在 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_ 。这确保了可视化不会总是相对于固定框架出现。

  • 实际的可视化内容在最后四行:我们将可视化的位置设置为与消息的位置匹配。

结果应如下所示: 

c9c60c749d08d58b8122761f7038f0e1.png

如果该位置没有出现该框,可能是因为:

  • 此时您未发布该主题

  • 消息在过去 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);
}

结果应如下所示: 

60a2f9b3c1e954d248a007121b8cb539.png

 哦,粉红色的!

8312e0bf633914ccbe464dedafb399a4.png

状态报告

您可以查看此步骤的完整版本,分支名称为 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" ),值是状态级别(错误/警告/正常)和描述(其他一些字符串)。

37579be5ff0295c646ef52c84ca06c52.png

ad9cc4b85ba7a2f79f1d06338bf1223a.png

清理

现在是时候清理一下了。这使得事情看起来更好看,也更容易使用,但不是严格要求的。您可以查看此步骤的完整版本,分支名称为 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
)

现在,当您添加显示时,它应该显示一个图标和描述。

c02c2a136592abf0c3fbf13756700ecf.png

以下是按主题添加时的显示:

ac9b694be5a7db6fd69ac324832dbaa5.png

a00d4828a1cd72b7bfd3f605d444af89.png

最后,这是标准界面中的图标:

adc5b2f3cd1bd8f1ceb77223aa1977ad.png

注意,如果更改插件名称,先前的 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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1926797.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

成为CMake砖家(5): VSCode CMake Tools 插件基本使用

大家好&#xff0c;我是白鱼。 之前提到过&#xff0c;白鱼的主力 编辑器/IDE 是 VSCode&#xff0c; 也提到过使用 CMake Language Support 搭配 dotnet 执行 CMakeLists.txt 语法高亮。 对于阅读 CMakeLists.txt 脚本&#xff0c; 这足够了。 而在 C/C 开发过程中&#xff…

数据结构(单链表(2))

单链表的实现 SList.h 由于代码中已有大量注释&#xff0c;所以该文章主要起到补充说明作用。 &#xfeff;#pragma once #include<stdio.h> #include<stdlib.h> #include<assert.h>//定义链表&#xff08;结点&#xff09;的结构typedef int SLTDataType…

MySQL in 太多过慢的 3 种解决方案

文章目录 解决方案一&#xff1a;使用 JOIN 替代 IN示例&#xff1a; 解决方案二&#xff1a;分批处理 IN 子句示例&#xff1a; 解决方案三&#xff1a;使用临时表示例&#xff1a; 总结 &#x1f389;欢迎来到Java学习路线专栏~探索Java中的静态变量与实例变量 ☆* o(≧▽≦)…

力扣刷题之978.最长湍流子数组

题干要求&#xff1a; 给定一个整数数组 arr &#xff0c;返回 arr 的 最大湍流子数组的长度 。 如果比较符号在子数组中的每个相邻元素对之间翻转&#xff0c;则该子数组是 湍流子数组 。 更正式地来说&#xff0c;当 arr 的子数组 A[i], A[i1], ..., A[j] 满足仅满足下列条…

基于用户鼠标移动的规律可以对用户身份进行连续验证的方法

概述 论文地址&#xff1a;https://arxiv.org/abs/2403.03828 本文重点论述了高效可靠的用户身份验证方法在计算机安全领域的重要性。它研究了使用鼠标移动动态作为连续身份验证新方法的可能性。具体来说&#xff0c;本文分析了用户在两个不同游戏场景–团队要塞和聚能桥–中…

关于Kafka Topic分区和Replication分配的策略

文章目录 1. Topic多分区2. 理想的策略3. 实际的策略4. 如何自定义策略 1. Topic多分区 如图&#xff0c;是一个多分区Topic在Kafka集群中可能得分配情况。 P0-RL代表分区0&#xff0c;Leader副本。 这个Topic是3分区2副本的配置。分区尽量均匀分在不同的Broker上&#xff0c…

自动驾驶-2D目标检测

yolo及yolo的变体 anchor boxes (锚框) intersection over union 并集交集 用于计算两个边界框的差异程度 bounding box predictions 边界框预测 non maximum suppression非极大值抑制 为了分离这些边界框并为每个对象获得单个边界框&#xff0c;我们使用IOU。这种获取单…

Ubuntu 安装 XRDP,替代系统自带RDP远程桌面

起因&#xff0c;Ubuntu的自带RDP远程桌面很好用&#xff0c;但很傻卵&#xff0c;必须登录。 而设置了自动登录也不能解开KEYRING&#xff0c;必须必须必须用GUI手动登录。 &#xff08;我远程我用头给你坐机子面前开显示器先登录&#xff1f;&#xff1f;&#xff09; 比起VN…

vue3 快速入门 (二) : 实现第一个Vue网页,并在手机上浏览

1. 最简单的一个VUE网页 首先&#xff0c;我们可以看我的这篇文章 : vue3 快速入门 (一) : 环境配置与搭建 完成环境搭建。 接着就可以来实现我们的第一个Vue网页了。 本文环境 Vue版本 : 3.4.29Node.js版本 : v20.15.0系统 : Windows11 64位IDE : VsCode 1.1 基础模板 vu…

使用OpenCV寻找图像中的轮廓

引言 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的计算机视觉和机器学习软件库。它提供了大量的视觉处理功能&#xff0c;包括图像和视频捕获、特征检测与匹配、图像变换、图像分割、颜色空间转换等。在图像处理中&#xff0c;寻找图像中的…

Gocator Acquisition for Cognex VisionPro(LMI相机图像获取)

概述 VisionPro 是个很强大的视觉软件, 我们很乐意我们的客户在VisionPro 环境中使用Gocator产品。 实现方法 在 VisionPro 环境下配置 Gocator 产品两种方法: ● 方法一: 创建一个 QuickBuild Job,在 Job 编辑器添加 Job Script,插入 Gocator 的 SDK,编辑简 单脚本就 OK。 …

基于MATHCAD的傅里叶级数模拟和方波图像绘制

一、MATHCAD软件简介 MATHCAD是一款功能强大的数学计算软件&#xff0c;它允许用户以类似手写公式的方式输入数学表达式&#xff0c;并即时显示计算结果和图形。在工程研究和学术写作的世界里&#xff0c;MathCAD以其强大的符号运算能力和直观的数学书写体验脱颖而出。MATHCAD…

防火墙nat基础实验

一&#xff0c;实验拓扑&#xff1a; 二&#xff0c;实验需求&#xff1a; 1&#xff0c;办公区设备可以通过电信链路和移动链路上网(多对多的NAT&#xff0c;并且需要保留一个公网IP不能用来转换) 2&#xff0c;分公司设备可以通过总公司的移动链路和电信链路访问到Dmz区的ht…

以数据编织,重构数据管理新范式

大数据产业创新服务媒体 ——聚焦数据 改变商业 人工智能几乎统一了全球最顶尖科技公司的认知&#xff1a;这个时代&#xff0c;除了AI&#xff0c;没有第二条路可走。 人工智能的技术逻辑颇有一种“暴力美学”&#xff0c;它依托于海量大数据和超高算力的训练和推理&#xff…

MySQL里的累计求和

在MySQL中&#xff0c;你可以使用SUM()函数来进行累计求和。如果你想要对一个列进行累计求和&#xff0c;可以使用OVER()子句与ORDER BY子句结合&#xff0c;进行窗口函数的操作。 以下是一个简单的例子&#xff0c;假设我们有一个名为sales的表&#xff0c;它有两个列&#x…

Redis 三大高可用模式:主从、哨兵、集群

一、引言 Redis&#xff0c;作为一种开源的、基于内存的数据结构存储系统&#xff0c;被广泛应用于各种场景&#xff0c;包括缓存、消息队列、短期存储等。 单一实例的工作模式通常无法保证Redis的可用性和拓展性&#xff0c;Redis提供了三种分布式方案&#xff1a; 主从模式…

【精品资料】智慧党建信息化建设方案(32页PPT)

引言&#xff1a;随着信息技术的快速发展&#xff0c;传统党建模式面临着信息传递不及时、党员教育管理手段单一、党组织活动参与度不高等挑战。智慧党建作为数字化转型的重要方向&#xff0c;能够有效解决上述问题&#xff0c;推动党建工作向更高质量发展。 方案介绍&#xff…

MySQL高级面试点

Explain语句结果中各个字段分别代表什么 id&#xff1a;查询语句没出现一个select关键字&#xff0c;MySQL就会给他分配一个唯一id select_type&#xff1a; select关键字对应哪个查询的类型 simple&#xff1a;简单的查询 不包含任何子查询 primary&#xff1a;查询中如果…

SparkStreaming--scala

文章目录 第1关&#xff1a;QueueStream代码 第2关&#xff1a;File Streams代码 第1关&#xff1a;QueueStream 任务描述 本关任务&#xff1a;编写一个清洗QueueStream数据的SparkStreaming程序。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;1.如何使用S…

<数据集>光伏板缺陷识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;2400张 标注数量(xml文件个数)&#xff1a;2400 标注数量(txt文件个数)&#xff1a;2400 标注类别数&#xff1a;4 标注类别名称&#xff1a;[Crack,Grid,Spot] 序号类别名称图片数框数1Crack8688922Grid8248843S…