一、Plugin Development 插件开发
1.Extension Points 扩展点
Rvizz中有不同类型的插件,每个插件都必须具有相应的基本类型,才能被RViz识别。
plugin type | base type |
---|---|
Display | rviz_common::Display |
Panel | rviz_common::Panel |
Tool | rviz_common::Tool |
Frames transformation library | rviz_common::transformation::FrameTransformer |
View Controller | rviz_common::ViewController |
2.Plugin Basics插件基础知识
创建一个工作空间
您需要将你的程序与rviz_common链接,可能还需要与rviz_rendering和rviz_default_plugins链接。
要让插件加载程序找到您的插件,你需要调用PLUGINILIB_EXPORT_CLASS宏。例如,如果你的插件名为“myplugin::plugin”,基类为rviz_common::Display,则需要在.cpp文件中包含以下内容:
#include <pluginlib/class_list_macros.hpp> PLUGINLIB_EXPORT_CLASS(myplugin::Plugin, rviz_common::Display)
您需要针对pluginlib包进行链接和编译。其他可能相关的CMake选项(请参阅rviz_default_plugins CMakeLists):
#Causes the visibility macros to use dllexport rather than dllimport (for Windows, when your plugin should be used as library)
target_compile_definitions(rviz_default_plugins PRIVATE "RVIZ_DEFAULT_PLUGINS_BUILDING_LIBRARY")
你需要编写一个plugindescription.xml文件,其中包含pluginlib在运行时加载插件所需的信息。有关示例,请参见rviz_default_plugins/plugins_description.xml(语法与旧rviz相同)
导出插件描述文件
pluginlib_export_plugin_description_file(rviz_common plugins_description.xml)
这个描述文件能够确保你的插件运行时能够被rviz_common找到。
3.Writing a display plugin编写一个display插件
为了编写一个display插件,需要从rviz_common::Display或rviz_common::RosTopicDisplay 派生。
3.1 在没有ros订阅的情况下编写display
在不订阅ros的情况下编写display时,从rviz_common::display
派生.
- Display类提供了一个
Ogre::SceneNode
,可用于添加视觉对象。 - 它提供了设置和删除状态消息的方便方法
- 当你需要任何Ogre或SceneNode设置时,请覆盖
onInitialize()
方法。
3.2 使用订阅编写显示display
当你编写一个topic类型的display,从rviz_common::RosTopicDisplay派生。RosTopicDisplay以该显示器的消息类型为模板:
- RosTopicDisplay来自Display,因此包含上一节中描述的所有功能
- 它处理对其模板类型的topic的订阅和取消订阅
- 它提供了两个属性:一个属性用于选择主题,另一个属性用来选择QoS配置文件(“服务质量”,当前为“可靠”或“不可靠”)
- 它提供了接收消息和进行初始检查的方便方法。覆盖processMessage以添加接收新消息时的行为
3.3 提供Ogre媒体文件
可以将自己的meshes、scripts等添加到RViz中。要使它们在运行时可用,请使用在rviz_rendering中定义的cmake宏register_rviz_ogre_media_exports。例如,如果您想注册位于项目源文件夹中的文件夹test_folder/scripts,请编写
register_rviz_ogre_media_exports(DIRECTORIES "test_folder/scripts")
注意:如果要导出文件夹层次结构,则需要分别导出每个文件夹。导出文件夹中的文件夹不会自动可用。
3.4 编写一个只能使用一个transformation插件的显示器
一些默认显示器(如TF显示器或LaserScan显示器)只能使用tf2作为转换框架。同样,作为一名开发人员,你可能已经编写了自己的转换插件,无法与每个显示器一起工作,或者您自己的显示器无法与每个转换插件一起工作。类rviz_default_plugins::transformation::TransformerGuard可以帮助您处理这些可能性。
如下所述,每个转换插件都实现基类rviz_common::transformation::FrameTransformer。TransformerGuard类是基于此实现的类型模板化的,它将确保如果当前使用的转换器是不同的类型,则其所属的display将被禁用。
在编写应该只与特定转换插件一起使用的display时,可以添加TransformerGuard的模板实例作为display成员。这个类的构造函数有两个参数:一个指向拥有它的显示器的原始指针,以及它应该使用的转换器的名称(正确设置错误状态所需的名称)。理想情况下,TransformerGuard对象在显示器的构造函数中初始化。
除此之外,TransformerGuard类还提供了checkTransformer()方法,如果当前usd转换器是允许的类型,则返回true,否则返回false。每当您希望显示器根据当前使用的转换插件的类型表现不同时,显示器都可以使用此方法。
关于如何完成这一切的具体示例,你可以在rviz_default_plugins中查看TF或LaserScan显示器,如前所述,该显示器仅适用于tf2。
4. Writing a panel plugin 编写一个panel插件
要编写自定义面板,从rviz_common::panel派生。
5. Writing a tool plugin 编写一个tool插件
- 编写一个tool插件,从rviz_common::Tool派生
- 快捷键可以通过shortcut_key_来设置
- 通过将函数getPropertyContainer()的返回值作为相关属性的父级传递,可以将属性添加到“工具属性”面板中(有关示例,请参见rviz_default_plugins::tools::MeasureTool)
- 用于插件的自定义光标、图标等可通过rviz_common/load_resource获得
6. Writing a view controller plugin 编写视图控制器插件
- 为了编写一个独立于tf帧的自定义视图控制器,请从rviz_common::ViewController派生
- 如果视图控制器应该能够跟踪场景中的tf帧,请从rviz_common::FramePositionTrackingViewController派生,它已经包含了跟踪目标帧的便利功能
- 如果您的自定义视图控制器环绕焦点运行,那么从rviz_default_plugins:OrbitViewController派生也可能是有益的
7. Writing a transformation library plugin 编写转换库插件
- 要编写转换器插件,必须实现rviz_common::transformation::FrameTransformer类(请参阅头文件中包含的API文档)
- 如果您的插件还需要额外的功能,或者您想提供对库本身某些部分的直接访问,那么您也可以实现rviz_common::transformation::TransformerLibraryConnector的子类(例如,请参阅rviz_default_plugins::transformation::TFWrapper)
二、Overview of RViz API RViz API概述
1. rviz_rendering
rviz_rendering包应该包含所有涉及渲染的功能:
- 要添加到场景图中的视觉效果和对象,如子文件夹对象中的箭头、形状或文本对象(其中许多对象是从文件夹ogre_helpers中移植的)
- 渲染窗口,包括一些暴露其内部的函数(RenderWindowOgreAdapter)。如果可能的话,不要使用RenderWindowOgreAdapter,因为它将来可能会被弃用和删除
- 便利功能去工作使用materials(material_manager.hpp)或其他与orge相关的功能(例如viewport_projection_filter.hpp中的光线跟踪)
- 用于测试的便利类,允许设置一个工作的grge环境和场景图内省的助手
2. rviz_common
rviz_common包包含大量可用于插件开发的rviz:
- 主应用程序和rendering队列(不暴露)
- 插件开发的主要入口点-panels、view controllers、displays和tools的基类
- 使用位于rviz_common/properties中的各种视图(如display panel)中的属性的便捷类
- 可选类型的主要类SelectionHandlers(位于rviz_common/interaction中)
- ROS 2的接入点。目前,RViz只使用一个节点,可以通过ros_integration访问。未来,可能需要进一步的改变,以将ROS 2访问完全抽象为ROS_integration
3. rviz_default_plugins
rviz_default_plugins包含rviz附带的所有插件(view controllers, tools, displays and in the future, panels) 其中大多数是从rviz的default_plugin斯文件夹移植的)。
- 当开发简单的插件时,没有必要在这个包中使用任何东西。
- 当开发类似于现有插件的更复杂的插件时,使用甚至从这个包中包含的类派生可能是有益的,以简化您的开发。
4. rviz_visual_testing_framework
rviz_visual_testing_framework包含编写插件可视化测试的主干。如果您想编写自动屏幕截图测试,那么只需要将此包用作测试依赖项。有关进一步的帮助,请参阅软件包中的文档。
5. 头文件内容
rviz_common/view_manager
这个头文件是ROS中的一个视图管理器,主要用于管理rviz中的视图。
它包含了一些重要的类和函数,如ViewManager、ViewController、ViewManager::getCurrent()等。
ViewManager是视图管理器的主要类,它负责管理所有的视图,并提供了一些方法来添加、删除、获取视图等。
ViewController是视图控制器的基类,它定义了一些基本的方法和属性,如getName()、getType()等。
ViewManager::getCurrent()是一个静态方法,用于获取当前的视图管理器。除此之外,rviz_common/view_manager还包含了一些其他的类和函数,
如ViewManager::getViewController()、ViewManager::getViews()等,它们都是视图管理器中非常重要的组成部分。
三、实际问题
1. 将一个QT工程项目编写成rviz的Panel插件(ROS1代码)
要将一个QT工程项目编写成rviz的Panel插件,需要按照以下步骤进行操作:
-
创建一个QT工程项目,并在其中添加一个QT界面,用于显示rviz的Panel插件。
-
在QT工程项目中添加rviz的依赖库,以便能够使用rviz的API。
-
在QT工程项目中创建一个继承自rviz::Panel的类,用于实现rviz的Panel插件的功能。
-
在该类中实现rviz::Panel的虚函数,包括onInitialize()、load()、save()和refresh()等函数,以便能够正确地加载和保存插件的状态,并在需要时刷新插件的显示。
-
在QT界面中添加一个QVBoxLayout布局,并将rviz::Panel的实例添加到该布局中,以便能够正确地显示rviz的Panel插件。
-
在QT工程项目中添加一个CMakeLists.txt文件,并在其中添加必要的编译选项和链接选项,以便能够正确地编译和链接rviz的Panel插件。
-
在QT工程项目中添加一个package.xml文件,并在其中添加必要的依赖项和描述信息,以便能够正确地打包和安装rviz的Panel插件。
以下是一个示例代码,展示了如何将一个QT工程项目编写成rviz的Panel插件:
#include <rviz/panel.h>
#include <QVBoxLayout>class MyPanel : public rviz::Panel
{
public:MyPanel(QWidget* parent = 0) : rviz::Panel(parent){QVBoxLayout* layout = new QVBoxLayout;setLayout(layout);}virtual void onInitialize(){// TODO: Initialize the panel here.}virtual void load(const rviz::Config& config){// TODO: Load the panel's state from the given config object.}virtual void save(rviz::Config config) const{// TODO: Save the panel's state to the given config object.}virtual void refresh(){// TODO: Refresh the panel's display here.}
};#include <pluginlib/class_list_macros.h>
PLUGINLIB_EXPORT_CLASS(MyPanel, rviz::Panel)
在CMakeLists.txt文件中,需要添加以下内容:
find_package(rviz REQUIRED)
include_directories(${rviz_INCLUDE_DIRS})
link_directories(${rviz_LIBRARY_DIRS})add_library(my_panel src/my_panel.cpp)
target_link_libraries(my_panel ${rviz_LIBRARIES})install(TARGETS my_panelARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION})install(FILES plugin_description.xmlDESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})
在package.xml文件中,需要添加以下内容:
<build_depend>rviz</build_depend>
<run_depend>rviz</run_depend><export><rviz plugin="${prefix}/plugin_description.xml"/>
</export>
最后,使用catkin_make命令编译和安装rviz的Panel插件即可。
2. 同一个type的rviz的panel插件通过按钮控件实现跳转
要实现同一个type的rviz的panel插件通过按钮控件实现跳转,可以按照以下步骤进行:
- 在panel插件的构造函数中创建按钮控件,并设置其回调函数,如下所示:
var button = new ROSLIB.Button({divID : 'my-button',class : 'my-button-class',text : 'My Button'
});button.on('click', function() {// 跳转到其他panel
});
- 在回调函数中,可以使用rviz的PanelManager对象的load方法来加载其他panel,如下所示:
var panelManager = new ROSLIB.Rviz.PanelManager({ros : ros,rootObject : rootObject,loader : loader
});panelManager.loadPanel('my_panel_type', {my_panel_options : 'my_panel_options_value'
});
其中,'my_panel_type’是要跳转到的panel的type,'my_panel_options’是要传递给panel的参数。
- 最后,需要在panel插件的HTML文件中添加按钮控件的HTML代码,如下所示:
<div id="my-button" class="my-button-class"></div>
这样就可以实现同一个type的rviz的panel插件通过按钮控件实现跳转了。
3. 在编写rviz的Panel插件时,编译后的lib链接库没有生成的原因
可能有很多,以下是一些可能的解决方法:
- 确认CMakeLists.txt文件中是否正确添加了生成链接库的代码,例如:
add_library(my_panel_plugin src/my_panel_plugin.cpp)
target_link_libraries(my_panel_plugin ${catkin_LIBRARIES} ${QT_LIBRARIES} ${Boost_LIBRARIES})
- 确认是否正确设置了插件的元数据,例如:
#include <pluginlib/class_list_macros.h>
#include <my_panel_plugin/my_panel_plugin.h>PLUGINLIB_EXPORT_CLASS(my_panel_plugin::MyPanelPlugin, rviz::Panel)
- 确认是否正确设置了插件的安装路径,例如:
install(TARGETS my_panel_pluginARCHIVE DESTINATION libLIBRARY DESTINATION libRUNTIME DESTINATION bin)
- 确认是否正确设置了rviz的插件路径,例如:
export RVIZ_PLUGIN_PATH=$RVIZ_PLUGIN_PATH:/path/to/my_panel_plugin
如果以上方法都无法解决问题,可以尝试使用调试工具(例如gdb)来查看链接库生成的过程中是否出现了错误。
4. 设置rviz的panel插件弹出的大小
要设置rviz的panel插件弹出的大小,可以通过在插件的构造函数中设置固定大小或者使用布局管理器来自适应大小。以下是两种方法的示例代码:
- 在构造函数中设置固定大小
MyPanel::MyPanel(QWidget* parent) : rviz::Panel(parent)
{// 设置固定大小为300x200setFixedSize(300, 200);
}
- 使用布局管理器自适应大小
MyPanel::MyPanel(QWidget* parent) : rviz::Panel(parent)
{// 创建一个垂直布局管理器QVBoxLayout* layout = new QVBoxLayout(this);// 添加需要显示的控件QLabel* label = new QLabel("Hello World", this);layout->addWidget(label);// 设置布局管理器setLayout(layout);
}
在第二种方法中,布局管理器会根据添加的控件自动调整大小,从而实现自适应大小的效果。你可以根据实际需求选择其中一种方法来设置rviz的panel插件弹出的大小。
5. 在rviz的自定义panel中调用另一个panel
您可以通过以下步骤在rviz的自定义panel中调用另一个panel:
- 首先,您需要在您的自定义panel中包含另一个panel的头文件。例如,如果您想要调用名为"my_other_panel"的panel,您需要在您的自定义panel中包含以下头文件:
#include <my_other_panel.h>
- 接下来,您需要创建一个指向另一个panel的指针。您可以在您的自定义panel的构造函数中完成这个步骤。例如:
MyOtherPanel* other_panel_;
other_panel_ = new MyOtherPanel();
- 然后,您可以在您的自定义panel中创建一个按钮或其他交互元素,以便用户可以调用另一个panel。例如:
QPushButton* button = new QPushButton("Call Other Panel");
connect(button, SIGNAL(clicked()), this, SLOT(callOtherPanel()));
- 最后,您需要实现一个槽函数,该函数将调用另一个panel。例如:
void MyCustomPanel::callOtherPanel()
{other_panel_->show();
}
这将显示另一个panel,并允许用户与其交互。请注意,您需要确保在您的自定义panel中正确管理另一个panel的生命周期,以避免内存泄漏或其他问题。
rviz的自定义panel中通过一个panel调用另外一个panel在rviz的页面中显示
您可以通过以下步骤在rviz的自定义panel中调用另一个panel并在rviz页面中显示:
-
首先,您需要在您的rviz插件包中创建两个自定义panel,例如PanelA和PanelB。
-
在PanelA的头文件中,包含PanelB的头文件,并创建一个指向PanelB的指针:
#include "panel_b.h"
...
PanelB* panel_b_;
- 在PanelA的构造函数中,初始化PanelB指针并将其添加到PanelA中:
panel_b_ = new PanelB();
layout->addWidget(panel_b_);
- 在PanelA中创建一个按钮或其他交互元素,并在其槽函数中调用PanelB的show()函数:
void PanelA::onButtonClicked()
{panel_b_->show();
}
- 在PanelB的构造函数中,设置PanelB的布局和其他元素,并将其隐藏:
setLayout(layout);
setVisible(false);
- 最后,在PanelB的show()函数中,将PanelB设置为可见并将其添加到rviz的窗口中:
void PanelB::show()
{setVisible(true);rviz::Panel::show();
}
通过以上步骤,您可以在rviz的自定义panel中调用另一个panel并在rviz页面中显示。
实现rviz2中的panel插件在调用另外一个插件时在rviz2的界面中显示而不是单独弹出一个程序界面,需要在panel插件中使用rviz2提供的API来创建一个子窗口,并将另外一个插件的内容嵌入到该子窗口中。
具体实现步骤如下:
- 在panel插件的构造函数中创建一个子窗口,代码如下:
QVBoxLayout* layout = new QVBoxLayout;
QWidget* widget = new QWidget;
widget->setLayout(layout);
sub_window_ = new rviz_common::PanelDockWidget(tr("Sub Window"), this);
sub_window_->setWidget(widget);
- 在panel插件中使用rviz2提供的API来获取另外一个插件的内容,并将其添加到子窗口中,代码如下:
rviz_common::PanelFactory* factory = context_->getPanelFactory("AnotherPlugin");
if (factory)
{rviz_common::Panel* panel = factory->createPanel();layout->addWidget(panel);
}
- 在panel插件的析构函数中释放子窗口和其中的内容,代码如下:
delete sub_window_;
完整的代码示例如下:
#include <QVBoxLayout>
#include <QWidget>
#include <rviz_common/panel_dock_widget.hpp>
#include <rviz_common/panel_factory.hpp>
#include <rviz_common/panel.hpp>class MyPanel : public rviz_common::Panel
{
public:MyPanel(QWidget* parent = nullptr) : rviz_common::Panel(parent){QVBoxLayout* layout = new QVBoxLayout;QWidget* widget = new QWidget;widget->setLayout(layout);sub_window_ = new rviz_common::PanelDockWidget(tr("Sub Window"), this);sub_window_->setWidget(widget);rviz_common::PanelFactory* factory = context_->getPanelFactory("AnotherPlugin");if (factory){rviz_common::Panel* panel = factory->createPanel();layout->addWidget(panel);}}~MyPanel(){delete sub_window_;}private:rviz_common::PanelDockWidget* sub_window_;
};
需要注意的是,要在panel插件中使用另外一个插件的内容,需要先在CMakeLists.txt中添加对该插件的依赖,例如:
find_package(rviz_common REQUIRED)
rviz_common_plugin(AnotherPlugin)
同时,在package.xml中也需要添加对该插件的依赖,例如:
<depend>rviz_plugin_tutorials</depend>
如何使用rviz2提供的API?
rviz2是ROS2中的可视化工具,提供了一些API供开发者使用。以下是使用rviz2 API的步骤:
- 导入rviz2 API
在你的ROS2包中,需要导入rviz2 API。在CMakeLists.txt文件中添加以下代码:
find_package(rviz2 REQUIRED)
在package.xml文件中添加以下代码:
<depend>rviz2</depend>
- 创建rviz2节点
在你的ROS2节点中,需要创建一个rviz2节点。以下是一个示例代码:
#include "rclcpp/rclcpp.hpp"
#include "rviz_common/node.hpp"int main(int argc, char **argv)
{rclcpp::init(argc, argv);auto node = std::make_shared<rviz_common::Node>("my_rviz2_node");rclcpp::spin(node);rclcpp::shutdown();return 0;
}
- 创建rviz2可视化组件
在rviz2节点中,需要创建一个可视化组件。以下是一个示例代码:
#include "rclcpp/rclcpp.hpp"
#include "rviz_common/node.hpp"
#include "rviz_common/display.hpp"
#include "rviz_common/display_context.hpp"
#include "rviz_common/display_factory.hpp"
#include "rviz_common/ros_integration/ros_node_abstraction.hpp"int main(int argc, char **argv)
{rclcpp::init(argc, argv);auto node = std::make_shared<rviz_common::Node>("my_rviz2_node");auto display_context = std::make_shared<rviz_common::DisplayContext>(node);auto display_factory = std::make_shared<rviz_common::DisplayFactory>();auto ros_node_abstraction = std::make_shared<rviz_common::RosNodeAbstraction>(node);auto display = display_factory->make("rviz_common/PointCloud2Display");display->initialize(display_context);display->setNode(ros_node_abstraction);display_context->addDisplay(display);rclcpp::spin(node);rclcpp::shutdown();return 0;
}
- 运行rviz2节点
在终端中运行rviz2节点:
ros2 run my_package my_rviz2_node
- 在rviz2中显示可视化组件
打开rviz2,点击“Add”按钮,选择你创建的可视化组件,即可在rviz2中显示可视化组件。
以上是使用rviz2 API的基本步骤。你可以根据自己的需求,使用rviz2 API创建更复杂的可视化组件。
调用close后的显示问题
在rviz的panel插件调用close后,留下状态条的问题可能是由于插件没有正确释放资源导致的。解决方法可以尝试在插件的close函数中添加释放资源的代码,例如:
void MyPanel::close()
{// 释放资源delete my_resource_;// 调用父类的close函数Panel::close();
}
如果仍然存在状态条的问题,可以尝试在插件的destructor中添加释放资源的代码,例如:
MyPanel::~MyPanel()
{// 释放资源delete my_resource_;
}
另外,也可以尝试在rviz的配置文件中将该插件的auto_save属性设置为false,以避免状态条的出现。具体方法是在配置文件中找到该插件的配置项,添加auto_save: false属性,例如:
MyPanel:Type: my_package/MyPanelName: My Panelauto_save: false
以上是一些可能的解决方法,具体需要根据实际情况进行调试和优化。
如果您需要彻底关闭当前panel页面,可以考虑使用rviz的removePanel函数。该函数可以将指定的panel从rviz中彻底删除,包括释放资源和销毁QWidget对象。具体方法是在插件的代码中调用rviz::PanelFactory::removePanel函数,例如:
void MyPanel::close()
{// 调用rviz::PanelFactory::removePanel函数rviz::PanelFactory::removePanel(this);// 调用父类的close函数Panel::close();
}
需要注意的是,使用removePanel函数会彻底删除该panel,因此需要确保在调用该函数之前已经保存了该panel的状态和数据,以便下次需要时可以恢复。另外,如果您的插件需要在关闭时执行一些其他的操作,例如保存数据或发送信号,可以在调用removePanel函数之前添加相应的代码。
彻底关闭Panel插件
rviz2中的自定义Panel在调用close后需要手动点击Panels状态栏才能彻底关闭Panel插件,有什么方法可以在调用close函数后自动彻底关闭当前插件?不要使用hide方法,那个是不可行的。C++代码 请详细回答
可以在自定义Panel的构造函数中添加一个回调函数,用于监听Panel的关闭事件。当Panel关闭时,回调函数会被触发,可以在回调函数中添加代码来彻底关闭Panel插件。
以下是示例代码:
#include <rviz_common/panel.h>class MyPanel : public rviz_common::Panel
{
public:MyPanel(QWidget* parent = nullptr) : rviz_common::Panel(parent){// 添加关闭事件监听器connect(this, &MyPanel::closeRequested, this, &MyPanel::onClose);}private:void onClose(){// 在这里添加关闭Panel插件的代码// ...}
};
在上面的代码中,我们在MyPanel的构造函数中添加了一个关闭事件监听器,当Panel关闭时,onClose函数会被触发。在onClose函数中,可以添加代码来彻底关闭Panel插件。
如何添加关闭Panel插件的代码 ?
在onClose函数中,可以调用rviz_common::Panel::close()函数来关闭Panel插件。但是,这个函数只会隐藏Panel,而不会彻底关闭它。为了彻底关闭Panel插件,我们需要手动删除Panel对象。
以下是示例代码:
void onClose()
{// 关闭Panelrviz_common::Panel::close();// 删除Panel对象delete this;
}
在上面的代码中,我们先调用rviz_common::Panel::close()函数来关闭Panel,然后再调用delete this来删除Panel对象。这样就可以彻底关闭Panel插件了。