Skip to content

image.png

  • Container:容器相关
  • Actions:动作相关, 比如查看动作类型
    • Action Type Browser:查看 action 对应的消息类型
  • Configuration:配置相关
    • Parameter Reconfigure:配置动态参数
  • Intorspection:节点相关,
    • Node Graph:查看节点图
  • Logging:日志相关
    • bag:数据录制与回放
    • Console:查看日志
  • Miscellaneours Tools:杂项
    • Python console
    • Shell
  • Services:服务相关
    • Service Caller:调用服务
    • Service Type Browser:查看 service 对应的消息类型
  • Topics:话题相关
    • Message Publisher:发布话题
    • Messga Type Browser:查看 action 对应的消息类型
    • Topic Monitor:查看当前可用的 topic 消息
  • Visualization 视觉相关
    • Image View:查看图像
    • Plot:绘制 topic 曲线图

rqt 框架架构详解

概述

rqt 是 ROS 2 的一个可视化框架,用于构建图形化工具(GUI plugins)。它采用插件化架构,允许用户通过安装不同的 rqt 插件来扩展功能(如 rqt_graphrqt_topicrqt_msg 等)。

rqt 的核心设计思想:

  1. 分层架构:从底层 Qt 框架逐层向上,最终支持 Python 和 C++ 插件。
  2. 插件发现与加载:通过包管理系统自动发现和动态加载插件。
  3. ROS 集成:通过 rclpy/roscpp 将 ROS 功能集成到 GUI 中。
  4. 线程隔离:使用独立的 spinner 线程驱动 ROS 事件循环,避免阻塞 GUI 主线程。

plantuml
@startuml
!theme plain
skinparam backgroundColor #FEFEFE
skinparam classBackgroundColor #F0F0F0
skinparam classBorderColor #333333
skinparam arrowColor #333333

title rqt 框架架构图 (ROS 2 Humble)

' ============== 顶层包定义 ==============
package "qt_gui (Qt 框架基础层)" {
    interface "PluginProvider" as IPluginProvider {
        +discover()
        +load()
        +unload()
    }

    interface "PluginContext" as IPluginContext {
        +serial_number()
    }

    interface "Plugin" as IPlugin {
    }

    class "CompositePluginProvider" as CompositePluginProvider {
        -plugin_providers
        +discover()
        +load()
        +unload()
    }

    class "Main" as QtMain {
        +main()
        +create_application()
    }
}

package "python_qt_binding (Qt Python 绑定)" {
    class "QWidget" as QWidget
    class "QThread" as QThread
    class "QCompleter" as QCompleter
    class "QTreeView" as QTreeView
}

package "rclpy (ROS 2 Python 客户端库)" {
    class "Node" as RclpyNode {
        +create_node()
        +destroy_node()
        +get_node_names_and_namespaces()
    }

    class "MultiThreadedExecutor" as MultiThreadedExecutor {
        +add_node()
        +spin_once()
    }
}

' ============== rqt_gui 层 ==============
package "rqt_gui (rqt GUI 框架主层)" {
    class "Main" as RqtMain {
        -plugin_providers
        +_add_plugin_providers()
        +_add_reload_paths()
    }

    class "RosPluginProvider" as RosPluginProvider {
        #_export_tag
        #_base_class_type
        #_plugin_descriptors
        +discover()
        +load()
        +_find_plugins()
        +_parse_plugin_xml()
    }

    class "RospkgPluginProvider" as RospkgPluginProvider {
        +_find_plugins()
    }

    class "Ros2PluginContext" as Ros2PluginContext {
        +node: rclpy.Node
    }
}

' ============== rqt_gui_py 层 ==============
package "rqt_gui_py (rqt Python 插件支持)" {
    class "Plugin" as RqtPlugin {
        +shutdown_plugin()
    }

    class "RosPyPluginProvider" as RosPyPluginProvider {
        -_node: rclpy.Node
        -_spinner: RclpySpinner
        -_node_initialized: bool
        +load()
        +unload()
        +_init_node()
        +_destroy_node()
    }

    class "RclpySpinner" as RclpySpinner {
        -_node: rclpy.Node
        -_abort: bool
        +run()
        +quit()
    }
}

' ============== rqt_py_common 工具层 ==============
package "rqt_py_common (rqt Python 工具库)" {
    class "RqtRoscommUtil" as RqtRoscommUtil {
        +get_node_names()
        +get_publisher_names()
        +get_subscriber_names()
    }

    class "MessageTreeWidget" as MessageTreeWidget {
        +setModel()
    }

    class "TreeModelCompleter" as TreeModelCompleter {
        +setModel()
    }

    class "TopicCompleter" as TopicCompleter {
        +updateModel()
    }

    class "PluginContainerWidget" as PluginContainerWidget {
        +QWidget child
    }

    class "MessageTreeModel" as MessageTreeModel {
        +expandAll()
        +collapseAll()
    }

    class "TopicTreeModel" as TopicTreeModel {
    }

    class "TopicDict" as TopicDict {
        +get()
    }

    class "MessageHelpers" as MessageHelpers {
        +get_message_class()
    }

    class "TopicHelpers" as TopicHelpers {
        +get_topic_type()
    }

    class "IniHelper" as IniHelper {
        +write()
        +read()
    }
}

' ============== rqt_gui_cpp 层(可选) ==============
package "rqt_gui_cpp (rqt C++ 插件支持,可选)" {
    class "NodeletPluginProvider" as NodeletPluginProvider {
        +discover()
        +load()
    }

    class "RoscppPluginProvider" as RoscppPluginProvider {
        +discover()
        +load()
    }
}

' ============== rqt meta 包 ==============
package "rqt (meta 包,依赖整合)" {
    note "依赖以上所有包,提供统一的安装与启动入口" as RqtMetaNote
}

' ============== 具体插件示例(虚拟) ==============
package "具体插件示例 (rqt_topic, rqt_msg 等)" {
    class "TopicPlugin" as TopicPlugin
    class "MessagePlugin" as MessagePlugin
}

' ============== 依赖和继承关系 ==============

' qt_gui 基础层关系
IPluginProvider <|-- CompositePluginProvider
IPluginProvider <|-- RosPluginProvider
IPluginContext <|-- Ros2PluginContext
IPlugin <|-- RqtPlugin
QtMain --> CompositePluginProvider : uses

' rqt_gui 继承关系
RosPluginProvider <|-- RospkgPluginProvider
RqtMain --|> QtMain : extends
RqtMain --> RosPluginProvider : aggregates
RqtMain --> RospkgPluginProvider : registers

' rqt_gui_py 与 rqt_gui 的关系
CompositePluginProvider <|-- RosPyPluginProvider
RosPyPluginProvider --> IPluginProvider
RosPyPluginProvider --> Ros2PluginContext : creates
RosPyPluginProvider --> RospkgPluginProvider : uses
RosPyPluginProvider --> RclpySpinner : creates

' rclpy 集成
Ros2PluginContext --> RclpyNode : holds
RosPyPluginProvider --> RclpyNode : creates
RclpySpinner --> RclpyNode : spins
RclpySpinner --|> QThread : extends
RclpySpinner --> MultiThreadedExecutor : uses

' Qt 集成
RqtPlugin --|> IPlugin : implements
RqtPlugin --> Ros2PluginContext : uses
MessageTreeWidget --|> QTreeView : extends
TreeModelCompleter --|> QCompleter : extends
PluginContainerWidget --|> QWidget : extends

' rqt_py_common 工具与 rqt_gui_py 的关系
RosPyPluginProvider --> RqtRoscommUtil : queries via
RqtRoscommUtil --> RclpyNode : queries

' 消息处理工具链
TopicCompleter --|> TreeModelCompleter : extends
TopicCompleter --> MessageHelpers : uses
MessageHelpers --> TopicHelpers : works with
MessageTreeWidget --> MessageTreeModel : uses
MessageTreeModel --> TopicTreeModel : may contain
TopicDict --> MessageTreeModel : provides data
IniHelper --> PluginContainerWidget : persists state

' 具体插件与框架的关系
TopicPlugin --|> RqtPlugin : extends
MessagePlugin --|> RqtPlugin : extends
TopicPlugin --> RqtRoscommUtil : uses
TopicPlugin --> PluginContainerWidget : inherits UI
MessagePlugin --> MessageTreeWidget : uses
MessagePlugin --> TopicCompleter : uses

' C++ 插件支持(可选)
NodeletPluginProvider --|> IPluginProvider
RoscppPluginProvider --|> IPluginProvider

note right of RqtMain
  应用程序入口
  初始化插件提供者
  注册 Python/C++ 插件
end note

note right of RosPyPluginProvider
  Python 插件生命周期管理
  ROS node 创建与销毁
  后台 spinner 线程管理
end note

note right of RclpySpinner
  在独立 Qt 线程中运行
  以多线程 executor 驱动 rclpy node
  支持 GUI 与 ROS 并发处理
end note

note right of RqtRoscommUtil
  提供图查询接口
  查询节点、话题、服务等
  通过 rclpy Node 访问
end note

note bottom of "rqt_py_common (rqt Python 工具库)"
  通用工具与 UI 组件
  支持 topic\/message\/parameter 查询
  提供自动补全、树形显示等功能
end note


@enduml

各模块详解

1. qt_gui (Qt 框架基础层)

来源:独立的 Qt GUI 框架包(非 ROS 特定)

核心接口

  • PluginProvider(虚基类):负责插件的发现、加载、卸载
  • PluginContext:传递给每个插件,提供与框架交互的能力。
  • Plugin:插件要实现的基类。
  • CompositePluginProvider:可管理多个子 PluginProvider 的复合提供者。
  • Main:应用程序入口点,管理主窗口、菜单、工具栏等。

职责:提供通用的 GUI 框架与插件系统,与 ROS 无关。


2. python_qt_binding (Qt Python 绑定层)

来源:ROS 官方 Qt Python 绑定包

提供

  • QWidgetQThreadQCompleterQTreeView 等 Qt 类的 Python 包装。
  • 支持在运行时选择 PyQt 5 或 PySide 2(通过环境变量 QT_BINDING)。

职责:统一 Qt 与 Python 的接口,隐藏具体 PyQt/PySide 差异。


3. rclpy (ROS 2 Python 客户端库)

来源:ROS 2 官方 Python 客户端库

核心类

  • Node:ROS 节点对象。方法包括:

    • get_node_names_and_namespaces():查询 ROS 图中的所有节点。
    • get_publisher_names_and_types_by_node(node_name, namespace):查询节点的 publisher。
    • get_subscriber_names_and_types_by_node(...) 等。
    • create_subscription(), create_publisher(), create_client(), create_service() 等。
  • MultiThreadedExecutor:多线程执行器,驱动 node 的回调。

    • add_node(node):注册 node。
    • spin_once(timeout_sec):执行一次迭代(处理回调)。

职责:提供 ROS 2 的 Python 客户端 API,供 rqt 查询 ROS 图和访问 ROS 功能。


4. rqt_gui (rqt GUI 框架主层)

位置rqt_gui/ 目录

核心类

4.1 Main

python
class Main(Base):  # Base = qt_gui.main.Main
    def main(argv, standalone, plugin_argument_provider, help_text):
        # 初始化 Qt 应用
        # 注册插件提供者(Python、C++)
        # 启动主窗口

职责

  • 继承 qt_gui.main.Main,扩展 rqt 特定的初始化逻辑。
  • 通过 _add_plugin_providers() 注册 Python 和 C++ 插件提供者。
  • 调用 rclpy.utilities.remove_ros_args() 过滤掉 ROS 特有的命令行参数(如 __ns, __node)。
  • 设置应用图标、帮助文本等。

启动命令示例

bash
ros2 run rqt_gui rqt_gui

4.2 RosPluginProvider 类 (抽象)

python
class RosPluginProvider(PluginProvider):
    def discover(discovery_data):
        # 查找 plugin.xml 文件
        # 解析 XML 提取插件信息(class name, type, icon等)
        # 返回 PluginDescriptor 列表
    
    def load(plugin_id, plugin_context):
        # 动态导入插件模块
        # 实例化插件类
        # 传入 plugin_context 初始化

职责

  • 基于 ROS 包系统(ament/rospkg)自动发现插件。
  • 解析 plugin.xml 清单文件,提取插件元数据(名称、类型、图标等)。
  • 动态加载和卸载插件。

plugin. xml 示例

xml
<library path="lib/python_builtin_plugins">
  <class name="Topic Publisher" type="rqt_publisher::Publisher" base_class_type="rqt_gui_py::Plugin">
    <description>Publishes ROS topics</description>
    <qtgui>
      <icon type="theme">folder</icon>
      <label>Topic Publisher</label>
    </qtgui>
  </class>
</library>

4.3 RospkgPluginProvider 类 (子类)

python
class RospkgPluginProvider(RosPluginProvider):
    def _find_plugins(export_tag, discovery_data):
        # 使用 rospkg/ament 查找所有包中的特定 export tag
        # 返回 [(package_name, plugin_xml_path), ...]

职责:实现 _find_plugins() 的包查询部分,通过 rospkg 枚举所有 ROS 包并查找它们的 plugin.xml

4.4 Ros2PluginContext

python
class Ros2PluginContext(PluginContext):
    def __init__(handler, node):
        super().__init__(handler)
        self.node = node  # ← 暴露给插件的 rclpy.Node 对象

职责

  • 继承 qt_gui.plugin_context.PluginContext
  • 关键:将 rclpy node 对象传递给插件,使插件能访问 ROS 功能。

5. rqt_gui_py (rqt Python 插件支持层)

位置rqt_gui_py/ 目录

核心类

5.1 Plugin 基类

python
class Plugin(Base):  # Base = qt_gui.plugin.Plugin
    def __init__(context):
        super().__init__(context)
    
    def shutdown_plugin():
        """清理资源:取消订阅、停止定时器等"""
        pass

职责

  • 为 Python 插件提供标准接口。
  • 提醒用户实现 shutdown_plugin() 以正确清理资源。
  • 不调用 rclpy.init()(由框架统一管理)。

典型使用

python
from rqt_gui_py.plugin import Plugin

class MyTopicPlugin(Plugin):
    def __init__(self, context):
        super().__init__(context)
        self.node = context.node  # ← 通过 context 获取 rclpy.Node
        # 使用 self.node 创建 publisher/subscriber 等
    
    def shutdown_plugin(self):
        # 清理
        self.node.destroy_node()

5.2 RosPyPluginProvider

python
class RosPyPluginProvider(CompositePluginProvider):
    def __init__():
        # 包装 RospkgPluginProvider,寻找 rqt_gui_py::Plugin
        # 初始化 _node = None, _spinner = None
    
    def load(plugin_id, plugin_context):
        self._init_node()  # ← 懒初始化
        ros_plugin_context = Ros2PluginContext(handler=..., node=self._node)
        # 调用父类加载
        return super().load(plugin_id, ros_plugin_context)
    
    def _init_node():
        if not self._node_initialized:
            name = 'rqt_gui_py_node_%d' % os.getpid()
            rclpy.init()
            self._node = rclpy.create_node(name)
            self._spinner = RclpySpinner(self._node)
            self._spinner.start()  # ← 启动后台线程
            self._node_initialized = True

职责

  • 管理 Python 插件的生命周期。
  • 关键:在第一个 Python 插件加载时创建全局 rclpy node(懒初始化)。
  • 启动 RclpySpinner 后台线程驱动 ROS 事件处理。
  • node 名称格式:rqt_gui_py_node_<PID>

5.3 RclpySpinner 类 (QThread 子类)

python
class RclpySpinner(QThread):
    def __init__(node):
        super().__init__()
        self._node = node
        self._abort = False
    
    def run():
        """在独立线程中执行"""
        executor = MultiThreadedExecutor()
        executor.add_node(self._node)
        while rclpy.ok() and not self._abort:
            executor.spin_once(timeout_sec=1.0)
    
    def quit():
        self._abort = True
        super().quit()

职责

  • 关键设计:在独立的 Qt 线程中驱动 rclpy node,避免阻塞 GUI 线程。
  • 使用 MultiThreadedExecutor 支持 node 的多个回调并发执行。
  • spin_once(timeout_sec=1.0) 每次等待最多 1 秒以处理回调。
  • 支持优雅关闭(quit() 设置 _abort 标志)。

时序图

GUI 主线程                      RclpySpinner 线程
┌────────────────┐             ┌──────────────────┐
│ Main event loop│             │ run() in QThread │
│ - GUI events   │             ├──────────────────┤
│ - User input   │◄──────────►│ executor.spin()  │
│   ...          │             │ - ROS callbacks  │
└────────────────┘             │ - Timers         │
                               │ - Subscriptions  │
                               └──────────────────┘

6. rqt_gui_cpp (rqt C++ 插件支持层,可选)

位置rqt_gui_cpp/ 目录

核心类

6.1 NodeletPluginProvider

  • 用于发现和加载基于 Nodelet 架构的 C++ 插件。
  • Nodelet:在同一进程中运行多个轻量级"节点"的 ROS C++ 组件加载框架。

6.2 RoscppPluginProvider

  • 用于发现和加载基于 roscpp(ROS C++ 客户端库)的 C++ 插件。

职责:为 C++ 插件提供与 Python 等价的插件发现与加载机制(通过 CMake 集成)。


7. rqt_py_common (rqt Python 工具库)

位置rqt_py_common/src/rqt_py_common/ 目录

这是一个公共工具包,为各种 rqt 插件提供重用代码。

核心模块

7.1 rqt_roscomm_util.pyRqtRoscommUtil

功能:访问 ROS 图信息(通过 rclpy node)。

API 示例

python
class RqtRoscommUtil:
    @staticmethod
    def get_node_names(node):
        # 返回图中所有节点名
        
    @staticmethod
    def get_publisher_names(node, remote_node_name):
        # 返回指定节点的 publisher (name, type) 列表
        
    @staticmethod
    def get_subscriber_names(node, remote_node_name):
        # 返回指定节点的 subscriber 列表
        
    # 类似的还有 get_service_names, get_action_names 等

使用场景:在插件中查询 ROS 图拓扑。

7.2 message_helpers.pyMessageHelpers

功能:ROS 消息类型的反射与加载。

API 示例

python
class MessageHelpers:
    @staticmethod
    def get_message_class(message_type_name):
        """根据字符串名加载消息类
        示例:'std_msgs/String' → std_msgs.msg.String
        """

7.3 topic_helpers.pyTopicHelpers

功能:话题类型信息查询与字段遍历。

API 示例

python
class TopicHelpers:
    @staticmethod
    def get_topic_type(node, topic_name):
        # 返回话题的消息类型名
        
    @staticmethod
    def get_type_class(type_name):
        # 加载并返回类型的 Python 类

7.4 UI 组件类

MessageTreeWidget

  • 继承 QTreeView
  • 以树形展示 ROS 消息的字段结构。
  • 支持展开/折叠字段。

MessageTreeModel

  • MessageTreeWidget 提供数据模型。
  • 递归构建消息字段树。

TopicCompleter

  • 继承 TreeModelCompleter(而它继承 QCompleter)。
  • 为输入框提供话题名自动补全。
  • 实时查询 ROS 图并更新补全列表。

TreeModelCompleter

  • 基于树形模型的通用补全器。

PluginContainerWidget

  • 继承 QWidget
  • 为插件 UI 提供容器,包含消息/日志显示区域等。

TopicDictTopicTreeModel**:

  • 管理话题信息的缓存与更新。

7.5 ini_helper.pyIniHelper

功能:插件配置持久化(INI 文件格式)。

API 示例

python
class IniHelper:
    @staticmethod
    def write(settings, key, value):
        # 写入配置项
        
    @staticmethod
    def read(settings, key, default=None):
        # 读取配置项

使用场景:保存用户在插件中的设置(窗口大小、选中的话题等)。


关键设计模式

1. 插件发现与动态加载(Strategy Pattern)

ROS 包系统 

RospkgPluginProvider._find_plugins()

遍历所有包,找 plugin.xml 文件

RosPluginProvider._parse_plugin_xml()

提取 <class type="..."> 信息,生成 PluginDescriptor

用户在 GUI 中点击菜单加载插件

RosPluginProvider.load(plugin_id, context)

__builtin__.__import__() 动态导入模块

实例化插件类,传入 context

插件开始运行

2. ROS 事件循环隔离(Thread Pattern)

GUI 主线程                    RclpySpinner 线程
运行 Qt event loop      ←→    运行 ROS executor loop

优势:
- GUI 响应性不受 ROS 回调阻塞
- ROS 回调可以安全地更新 GUI(通过 Qt 信号)

3. 上下文注入(Dependency Injection Pattern)

RosPyPluginProvider.load()

创建 Ros2PluginContext(handler, node=self._node)

传给插件 Plugin.__init__(context)

插件通过 context.node 访问 ROS 功能

避免全局变量,便于测试与多插件隔离

4. 工具库共享(Utility Pattern)

rqt_py_common 提供:
- RqtRoscommUtil:图查询
- MessageHelpers/TopicHelpers:类型反射
- UI 组件:MessageTreeWidget 等

多个具体插件重用这些工具
示例:
  rqt_topic, rqt_publisher, rqt_msg 都使用
  MessageTreeWidget 来显示消息字段

完整的插件加载流程

用户启动 rqt:
$ ros2 run rqt_gui rqt_gui

1. 进入点:rqt_gui/main.py::Main().main()

2. 继承链:Main(rqt_gui) → Main(qt_gui)

3. qt_gui.main.Main 初始化 Qt 应用、主窗口

4. rqt_gui.Main._add_plugin_providers()
   - 注册 RospkgPluginProvider('qt_gui', 'qt_gui_py::Plugin')
   - 注册 RospkgPluginProvider('rqt_gui', 'rqt_gui_py::PluginProvider')
   - 如果有 C++ 支持,注册 NodeletPluginProvider / RoscppPluginProvider

5. 对每个 PluginProvider.discover()
   - 遍历 ROS 包,查找对应 export_tag 的 plugin.xml
   - 解析并返回 PluginDescriptor 列表

6. GUI 显示 Plugins 菜单,列出所有发现的插件

7. 用户点击菜单项加载插件 → Main 调用 load(plugin_id, context)

8. 首次加载 Python 插件 → RosPyPluginProvider._init_node()
   - rclpy.init()
   - self._node = rclpy.create_node('rqt_gui_py_node_<PID>')
   - self._spinner = RclpySpinner(self._node)
   - self._spinner.start()  ← 启动后台线程

9. RosPyPluginProvider.load(plugin_id, plugin_context)
   - 创建 Ros2PluginContext(handler, node)
   - 调用父类 load,执行 RosPluginProvider 的加载逻辑

10. RosPluginProvider.load()
    - 通过 PluginDescriptor 得到模块名、类名
    - sys.path.append(library_path)
    - module = __builtin__.__import__(module_name, [...])
    - class_ref = getattr(module, class_name)
    - instance = class_ref(ros_plugin_context)  ← 传入 context
    - return instance

11. 插件对象现在活跃
    - 访问 context.node → 进行 ROS 通信
    - 在后台,RclpySpinner 线程驱动 node 的回调执行

12. 用户关闭插件 → 调用 unload(plugin_instance)
    - 触发 plugin.shutdown_plugin()
    - 插件清理资源

13. 所有插件关闭 → 应用退出
    - RosPyPluginProvider.shutdown()
    - self._spinner.quit()  → 停止线程
    - self._node.destroy_node()
    - rclpy.try_shutdown()

常见的具体插件示例

rqt_graph

  • 显示 ROS 计算图(节点、话题、服务的依赖关系)。
  • 使用 rqt_py_common.rqt_ros_graph 模块获取图数据。
  • 使用 graphviz 或类似库绘制。

rqt_topic

  • 订阅并显示 ROS 话题消息。
  • 使用 rqt_py_common.message_tree_widget 显示消息字段。
  • 使用 RqtRoscommUtil 列出可用话题。

rqt_publisher

  • 发布 ROS 话题。
  • rqt_py_common.topic_completer 自动补全话题名。
  • rqt_py_common.message_tree_widget 编辑消息字段。

rqt_msg / rqt_srv / rqt_action

  • 浏览 ROS 消息/服务/动作定义。
  • 使用消息反射工具加载类型定义。

总结

模块 职责 关键类
qt_gui Qt 框架抽象层 Main, PluginProvider, PluginContext, Plugin
python_qt_binding Qt 与 Python 绑定 QWidget, QThread, QCompleter, ...
rclpy ROS 2 Python 客户端 Node, MultiThreadedExecutor
rqt_gui ROS 框架集成入口 Main, RosPluginProvider, RospkgPluginProvider, Ros2PluginContext
rqt_gui_py Python 插件运行时 Plugin, RosPyPluginProvider, RclpySpinner
rqt_gui_cpp C++ 插件运行时(可选) NodeletPluginProvider, RoscppPluginProvider
rqt_py_common 工具库 RqtRoscommUtil, MessageHelpers, TopicHelpers, UI 组件

核心要点

  1. 插件系统采用发现、描述、加载、执行 的标准模式。
  2. ROS 事件循环通过 RclpySpinner 后台线程 与 Qt GUI 主线程隔离。
  3. 所有 Python 插件共享一个 全局 rclpy node,由 RosPyPluginProvider 管理。
  4. 插件通过 Ros 2 PluginContext 获得 node 对象,进行 ROS 通信。
  5. rqt_py_common 提供高级工具库,使编写插件更简便。

基于 VitePress 构建