3.9 机械臂 URDF 模型与可视化
一、本章目标
在前面的章节中,我们学习了 ROS2 的核心通信机制(Topic、Service、Action)以及 TF2 坐标系管理。但还有一个重要问题没有解决:
机器人的 3D 模型从哪里来?RViz 是怎么知道机械臂长什么样的?
想象以下场景:
- 你在 RViz 中看到一个漂亮的机械臂模型,可以实时显示各关节的位置
- 拖动滑块,机械臂模型跟着动,各个坐标系(TF)也实时更新
- 这一切是怎么实现的?
答案就是 URDF(Unified Robot Description Format) —— 统一机器人描述格式。
1.1 学习路线
第1步:理解 URDF 是什么、解决什么问题
↓
第2步:通过 2-link 简单示例掌握 URDF 基本语法
↓
第3步:了解 CAD 导出 URDF 的主流方法(实际工程中常用)
↓
第4步:分析 Episode 1 机械臂的完整 URDF 结构
↓
第5步:理解核心原理:URDF 如何变成 TF 坐标系并在 RViz 中显示
1.2 本章目标
完成本章后,你将能够:
- 理解 URDF 文件的结构和核心元素(
<link>、<joint>) - 看懂 CAD 软件导出的 URDF 文件
- 理解
robot_state_publisher如何将 URDF 转换为 TF 坐标系 - 理解 RViz 如何订阅话题来渲染机器人模型
1.3 与其他章节的关系
| 章节 | 内容 | 与本章的关系 |
|---|---|---|
| 第 4 章 Topic | 发布/订阅消息 | robot_state_publisher 订阅 /joint_states,发布 /tf |
| 第 5 章 自定义 Interface | 自定义消息类型 | /joint_states 使用 sensor_msgs/JointState |
| 第 8 章 TF2 | 手动发布坐标变换 | 本章的 TF 由 robot_state_publisher 自动发布 |
| 第 9 章 URDF(本章) | 机器人模型描述 | 定义机器人结构,驱动 TF 自动生成 |
核心理念:URDF 是机器人的"身份证",定义了它的外观和关节结构。
robot_state_publisher读取 URDF 后,自动帮你完成第 8 章中手动做的 TF 广播工作。
二、URDF 概述
2.1 什么是 URDF?
URDF(Unified Robot Description Format) 是一种基于 XML 的机器人描述格式,用于定义:
| 内容 | 说明 |
|---|---|
| 外观 | 机器人的 3D 模型(几何形状、颜色、纹理) |
| 结构 | 连杆(link)和关节(joint)的层级关系 |
| 物理属性 | 质量、惯性矩阵(用于动力学仿真) |
| 运动约束 | 关节类型、运动范围、速度限制 |
一句话总结:URDF 告诉 ROS2 "机器人长什么样、怎么连接、怎么动"。
2.2 URDF 的核心元素
URDF 文件由两个核心元素组成:Link(连杆) 和 Joint(关节)。
┌─────────────────────────────────────────────────────────────────┐
│ URDF 核心结构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ <robot name="my_robot"> │
│ │ │
│ ├── <link name="base_link"> ← 连杆:机器人的"骨骼" │
│ │ ├── <visual> ← 外观(RViz显示用) │
│ │ ├── <collision> ← 碰撞体(仿真碰撞检测用) │
│ │ └── <inertial> ← 惯性(动力学仿真用) │
│ │ │
│ ├── <link name="link1"> │
│ │ │
│ └── <joint name="joint1"> ← 关节:连接两个连杆 │
│ ├── <parent link="base_link"/> │
│ ├── <child link="link1"/> │
│ ├── <origin xyz="..." rpy="..."/> ← 相对位置 │
│ ├── <axis xyz="0 0 1"/> ← 转轴方向 │
│ └── <limit lower="..." upper="..."/> │
│ </robot> │
│ │
└─────────────────────────────────────────────────────────────────┘
Link vs Joint 类比:
| 概念 | 类比 | 说明 |
|---|---|---|
| Link(连杆) | 人体的骨骼 | 刚性部件,有形状、质量 |
| Joint(关节) | 人体的关节 | 连接两根骨骼,定义运动方式 |
2.3 关节类型
URDF 支持多种关节类型:
| 类型 | 说明 | 自由度 | 典型应用 |
|---|---|---|---|
revolute |
旋转关节(有限位) | 1 | 机械臂关节 |
continuous |
旋转关节(无限位) | 1 | 轮子 |
prismatic |
滑动关节 | 1 | 升降台 |
fixed |
固定连接 | 0 | 传感器安装 |
floating |
6 自由度浮动 | 6 | 飞行器 |
planar |
平面运动 | 2 | 2D 移动平台 |
机械臂最常用:
revolute(旋转关节)和fixed(固定连接,如末端工具)。
2.4 URDF 的树状结构
URDF 描述的机器人必须是树状结构:
- 有且只有一个根节点(通常是
base_link) - 每个 Link 只能有一个父 Joint
- 不允许闭环(并联机构如 Delta 机器人需使用 SDF 格式或 Gazebo 约束插件)
URDF 树状结构示例(6轴机械臂):
base_link (根节点)
│
└── joint1 (revolute)
│
└── link1
│
└── joint2 (revolute)
│
└── link2
│
└── joint3 (revolute)
│
└── link3
│
... (继续到 link6)
三、URDF 基础语法:3-Link 简单示例
在分析复杂的机械臂 URDF 之前,我们先用一个简单的 3-Link 示例来理解 URDF 的基本语法。
3.1 示例:一个简单的机械臂

我们要创建一个这样的机器人:
┌─────────────────────────────────────────────────────────────────┐
│ 3-Link 简单机械臂 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ╳ end_effector (坐标系) │
│ ↑ │
│ ┌───┴───┐ │
│ │link2 │ ← 小方块末端(可绕 Y 轴旋转) │
│ └───┬───┘ │
│ │ joint2 (revolute, Y轴旋转) │
│ ┌───┴───┐ │
│ │ │ │
│ │ link1 │ ← 长方体手臂(可绕 Z 轴旋转) │
│ │ (box) │ │
│ └───┬───┘ │
│ │ joint1 (revolute, Z轴旋转) │
│ ┌───┴────┐ │
│ │ │ │
│ │base_link│ ← 圆柱体底座(固定不动) │
│ │(cylinder)│ │
│ └────────┘ │
│ ═══════════════ 地面 │
│ │
└─────────────────────────────────────────────────────────────────┘
3.2 完整 URDF 代码
<?xml version="1.0"?>
<robot name="simple_3link_robot">
<!-- ==================== 材质定义 ==================== -->
<material name="blue">
<color rgba="0 0 0.8 1"/>
</material>
<material name="white">
<color rgba="1 1 1 1"/>
</material>
<!-- ==================== Link 1: 底座 ==================== -->
<link name="base_link">
<!-- 可视化外观 -->
<visual>
<origin xyz="0 0 0.05" rpy="0 0 0"/> <!-- 圆柱中心上移0.05m -->
<geometry>
<cylinder length="0.1" radius="0.05"/> <!-- 高0.1m,半径0.05m -->
</geometry>
<material name="blue"/>
</visual>
<!-- 碰撞体(通常与visual相同或简化) -->
<collision>
<origin xyz="0 0 0.05" rpy="0 0 0"/>
<geometry>
<cylinder length="0.1" radius="0.05"/>
</geometry>
</collision>
<!-- 惯性属性(仿真需要) -->
<inertial>
<origin xyz="0 0 0.05" rpy="0 0 0"/>
<mass value="1.0"/>
<inertia ixx="0.001" ixy="0" ixz="0" iyy="0.001" iyz="0" izz="0.001"/>
</inertial>
</link>
<!-- ==================== Link 2: 手臂 ==================== -->
<link name="link1">
<visual>
<origin xyz="0 0 0.15" rpy="0 0 0"/> <!-- 长方体中心在关节上方0.15m -->
<geometry>
<box size="0.04 0.04 0.3"/> <!-- 4cm x 4cm x 30cm 的长方体 -->
</geometry>
<material name="white"/>
</visual>
<collision>
<origin xyz="0 0 0.15" rpy="0 0 0"/>
<geometry>
<box size="0.04 0.04 0.3"/>
</geometry>
</collision>
<inertial>
<origin xyz="0 0 0.15" rpy="0 0 0"/>
<mass value="0.5"/>
<inertia ixx="0.004" ixy="0" ixz="0" iyy="0.004" iyz="0" izz="0.0001"/>
</inertial>
</link>
<!-- ==================== Joint 1: 连接底座和手臂 ==================== -->
<joint name="joint1" type="revolute">
<parent link="base_link"/> <!-- 父连杆 -->
<child link="link1"/> <!-- 子连杆 -->
<!-- 关节位置:在底座顶部(Z=0.1m) -->
<origin xyz="0 0 0.1" rpy="0 0 0"/>
<!-- 旋转轴:绕 Z 轴旋转 -->
<axis xyz="0 0 1"/>
<!-- 关节限位:-180° 到 +180° -->
<limit lower="-3.14159" upper="3.14159" effort="10" velocity="1.0"/>
</joint>
<!-- ==================== Link 3: 末端 ==================== -->
<link name="link2">
<visual>
<origin xyz="0 0 0.1" rpy="0 0 0"/> <!-- 小方块中心在关节上方0.1m -->
<geometry>
<box size="0.06 0.06 0.2"/> <!-- 6cm x 6cm x 20cm 的方块 -->
</geometry>
<material name="blue"/>
</visual>
<collision>
<origin xyz="0 0 0.1" rpy="0 0 0"/>
<geometry>
<box size="0.06 0.06 0.2"/>
</geometry>
</collision>
<inertial>
<origin xyz="0 0 0.1" rpy="0 0 0"/>
<mass value="0.3"/>
<inertia ixx="0.002" ixy="0" ixz="0" iyy="0.002" iyz="0" izz="0.001"/>
</inertial>
</link>
<!-- ==================== Joint 2: 连接手臂和末端 ==================== -->
<joint name="joint2" type="revolute">
<parent link="link1"/> <!-- 父连杆 -->
<child link="link2"/> <!-- 子连杆 -->
<!-- 关节位置:在 link1 顶部(Z=0.3m) -->
<origin xyz="0 0 0.3" rpy="0 0 0"/>
<!-- 旋转轴:绕 Y 轴旋转 -->
<axis xyz="0 1 0"/>
<!-- 关节限位:-180° 到 +180° -->
<limit lower="-3.14159" upper="3.14159" effort="10" velocity="1.0"/>
</joint>
<!-- ==================== Link 4: 末端坐标系 ==================== -->
<link name="end_effector">
<!-- 这是一个纯坐标系,不需要 visual,只用于 TF 显示 -->
</link>
<!-- ==================== Fixed Joint: 连接到末端坐标系 ==================== -->
<joint name="joint_end_effector" type="fixed">
<parent link="link2"/> <!-- 父连杆 -->
<child link="end_effector"/> <!-- 子连杆:末端坐标系 -->
<!-- 关节位置:在 link2 顶部(Z=0.2m) -->
<origin xyz="0 0 0.2" rpy="0 0 0"/>
</joint>
</robot>
3.3 运行 3-Link 示例
先别急着理解每一行代码,我们先把它跑起来,在 RViz 中看到效果!
步骤 1:保存 URDF 文件
将上面的 URDF 代码保存为文件:
# 创建目录
mkdir -p ~/ros2_ws/src/urdf_test
# 创建 URDF 文件(复制 3.2 节的完整代码)
nano ~/ros2_ws/src/urdf_test/simple_3link.urdf
步骤 2:启动 robot_state_publisher
# 终端 1:启动 robot_state_publisher
ros2 run robot_state_publisher robot_state_publisher ~/ros2_ws/src/urdf_test/simple_3link.urdf
步骤 3:启动 joint_state_publisher_gui
# 终端 2:启动关节滑块 GUI
ros2 run joint_state_publisher_gui joint_state_publisher_gui ~/ros2_ws/src/urdf_test/simple_3link.urdf
步骤 4:启动 RViz2
# 终端 3:启动 RViz
ros2 run rviz2 rviz2
步骤 5:配置 RViz 显示机器人
现在你应该能看到:
- 一个蓝色圆柱体底座(base_link)
- 一个白色长方体手臂(link1)
- 一个蓝色小方块末端(link2)
- 一个末端坐标系(end_effector)显示为 RGB 三轴
- 在
joint_state_publisher_gui窗口中拖动joint1和joint2滑块 - 机械臂会绕 Z 轴旋转!
快速验证命令
# 查看话题
ros2 topic list
# 输出
/clicked_point
/goal_pose
/initialpose
/joint_states
/parameter_events
/robot_description
/rosout
/tf
/tf_static
# 查看 joint_states
ros2 topic echo /joint_states
# 输出
---
header:
stamp:
sec: 1769566439
nanosec: 767450374
frame_id: ''
name:
- joint1
- joint2
position:
- -1.656874566
- -0.8633089320000003
velocity: []
effort: []
---
提示:如果 RViz 中看不到模型,检查 Fixed Frame 是否设置为
base_link,以及 RobotModel 的 Description Topic 是否正确。
现在我们来逐个理解 URDF 中的每个元素。
3.4 逐元素解析
3.4.1 <robot> 根元素
<robot name="simple_3link_robot">
...
</robot>
- 整个 URDF 的根元素
name属性定义机器人名称,用于在 ROS2 系统中唯一标识这个机器人模型
3.4.2 <material> 材质定义
<!-- ==================== 材质定义 ==================== -->
<!-- 材质通常在 URDF 文件开头定义,可被多个 Link 复用 -->
<material name="blue">
<color rgba="0 0 0.8 1"/> <!-- R G B Alpha,范围 0~1 -->
</material>
<material name="white">
<color rgba="1 1 1 1"/> <!-- 白色:R=1 G=1 B=1,完全不透明 -->
</material>
材质定义说明:
| 属性 | 说明 |
|---|---|
name |
材质名称,供后续引用 |
rgba |
颜色值:红(R) 绿(G) 蓝(B) 透明度(Alpha),范围 0~1 |
在 <visual> 中使用材质:
<link name="base_link">
<visual>
<geometry>
<cylinder length="0.1" radius="0.05"/>
</geometry>
<!-- 方式1:引用已定义的材质 -->
<material name="blue"/>
</visual>
</link>
<link name="link1">
<visual>
<geometry>
<box size="0.04 0.04 0.3"/>
</geometry>
<!-- 方式2:直接定义颜色(不推荐,不可复用) -->
<material name="">
<color rgba="1 1 1 1"/>
</material>
</visual>
</link>
最佳实践:在
<robot>标签后、第一个<link>前集中定义所有材质,便于统一管理和复用。
3.4.3 <link> 连杆
<link> 是 URDF 中描述机器人刚性部件的元素,包含三个主要子元素:
<link name="base_link">
<visual>...</visual> <!-- 可视化(必需) -->
<collision>...</collision> <!-- 碰撞检测(可选) -->
<inertial>...</inertial> <!-- 惯性属性(仿真需要) -->
</link>
| 子元素 | 用途 | 是否必需 |
|---|---|---|
<visual> |
RViz 显示用 | 是(否则看不到) |
<collision> |
物理仿真碰撞检测 | 仅仿真需要 |
<inertial> |
动力学仿真 | 仅仿真需要 |
3.4.3.1 <visual> 可视化
<visual>
<origin xyz="0 0 0.05" rpy="0 0 0"/> <!-- 几何体中心相对于 link 原点的位置 -->
<geometry>
<cylinder length="0.1" radius="0.05"/> <!-- 几何形状 -->
</geometry>
<material name="blue"/> <!-- 引用材质 -->
</visual>
支持的基本几何体:
| 几何体 | 语法 | 说明 |
|---|---|---|
| 圆柱 | <cylinder length="0.1" radius="0.05"/> |
高度、半径 |
| 长方体 | <box size="0.1 0.2 0.3"/> |
X Y Z 三个方向的尺寸 |
| 球体 | <sphere radius="0.05"/> |
半径 |
| 网格 | <mesh filename="package://pkg/meshes/file.stl"/> |
STL/DAE 文件 |
3.4.3.2 <collision> 碰撞体
<collision> 定义用于物理仿真中碰撞检测的几何体,通常与 <visual> 相同或简化:
<collision>
<origin xyz="0 0 0.05" rpy="0 0 0"/>
<geometry>
<cylinder length="0.1" radius="0.05"/>
</geometry>
</collision>
关键点:
- 用途:Gazebo 等物理仿真器中用于碰撞检测
- 简化策略:可以使用简化的几何体(如用圆柱近似复杂网格)以提高计算效率
- 本教程中:仅做可视化展示时可省略
3.4.3.3 <inertial> 惯性属性
<inertial> 定义连杆的质量和惯性参数,用于动力学仿真:
<inertial>
<origin xyz="0 0 0.05" rpy="0 0 0"/> <!-- 质心位置 -->
<mass value="1.0"/> <!-- 质量(kg) -->
<inertia <!-- 惯性张量 -->
ixx="0.001" ixy="0" ixz="0"
iyy="0.001" iyz="0" izz="0.001"/>
</inertial>
关键点:
- 质心位置(
origin):相对于 Link 原点 - 质量(
mass):单位为千克(kg) - 惯性张量(
inertia):6 个独立分量,描述物体绕不同轴的转动惯量 - 用途:Gazebo 动力学仿真、力矩计算
- 本教程中:仅做可视化展示时可省略
提示:CAD 软件导出的 URDF 会自动计算精确的惯性参数。手写 URDF 时,如果不做动力学仿真,可以填写近似值。
3.4.5 <origin> 位姿(通用元素)
<origin> 是 URDF 中的通用元素,用于定义相对位姿。它在不同上下文中有不同含义:
<origin xyz="0 0 0.1" rpy="0 0 0"/>
| 属性 | 说明 | 单位 |
|---|---|---|
xyz |
X Y Z 平移 | 米 |
rpy |
Roll Pitch Yaw 旋转 | 弧度 |
在不同上下文中的含义:
| 使用位置 | 相对关系 | 说明 |
|---|---|---|
<visual><origin> |
几何体中心相对于 Link 原点 | 定义可视化几何体如何摆放 |
<collision><origin> |
碰撞体中心相对于 Link 原点 | 定义碰撞体如何摆放 |
<inertial><origin> |
质心相对于 Link 原点 | 定义质心位置 |
<joint><origin> |
子 Link 原点相对于父 Link 原点 | 定义关节位置和姿态 |
示例对比:
<!-- 在 <visual> 中:定义几何体相对于 link 原点的位置 -->
<link name="base_link">
<visual>
<origin xyz="0 0 0.05" rpy="0 0 0"/> <!-- 圆柱中心在 link 原点上方 5cm -->
<geometry>
<cylinder length="0.1" radius="0.05"/>
</geometry>
</visual>
</link>
<!-- 在 <joint> 中:定义子 link 相对于父 link 的位置 -->
<joint name="joint1" type="revolute">
<parent link="base_link"/>
<child link="link1"/>
<origin xyz="0 0 0.1" rpy="0 0 0"/> <!-- link1 的原点在 base_link 原点上方 10cm -->
...
</joint>
注意:
rpy使用的是弧度,不是角度!90° = π/2 ≈ 1.5708rpy的旋转顺序:先 Roll(绕 X 轴)→ 再 Pitch(绕 Y 轴)→ 最后 Yaw(绕 Z 轴)
3.4.6 <joint> 关节
<joint name="joint1" type="revolute">
<parent link="base_link"/> <!-- 父连杆 -->
<child link="link1"/> <!-- 子连杆 -->
<origin xyz="0 0 0.1" rpy="0 0 0"/> <!-- 子 link 原点相对于父 link 原点的位置 -->
<axis xyz="0 0 1"/> <!-- 旋转轴方向 -->
<limit lower="-3.14" upper="3.14" effort="10" velocity="1.0"/> <!-- 限位 -->
</joint>
Joint 参数说明(以 joint1 为例):
| 元素 | joint1 实际值 | 说明 |
|---|---|---|
name |
"joint1" |
关节名称,在整个 URDF 中唯一标识这个关节 |
type |
revolute |
关节类型:旋转关节(有限位)其他类型:continuous(无限旋转)、prismatic(滑动)、fixed(固定) |
<parent> |
link="base_link" |
父连杆:joint1 连接在 base_link 上 |
<child> |
link="link1" |
子连杆:link1 是 joint1 的下级连杆 |
<origin> |
xyz="0 0 0.1" |
link1 的原点相对于 base_link 的原点的位置(单位:米)这里表示 link1 在 base_link 上方 0.1m |
rpy="0 0 0" |
link1 坐标系相对于 base_link 坐标系的旋转(单位:弧度)这里无旋转,两个坐标系方向一致 | |
<axis> |
xyz="0 0 1" |
关节旋转轴方向:正 Z 轴(在 link1 的坐标系下)表示 link1 绕垂直向上的轴旋转 |
<limit> |
lower="-3.14" |
关节角度下限:约 -180°(-π 弧度) |
upper="3.14" |
关节角度上限:约 +180°(π 弧度) | |
effort="10" |
最大力矩限制:10 N⋅m(仅动力学仿真需要) | |
velocity="1.0" |
最大速度限制:1.0 rad/s(仅动力学仿真需要) |
关键理解:
-
type决定关节行为:revolute:有限位的旋转关节(最常用于机械臂)continuous:无限旋转(如车轮)prismatic:直线滑动(如升降台)fixed:固定连接(如传感器安装)
-
<origin>的含义:定义子连杆坐标系相对于父连杆坐标系的初始位姿- 当关节角度为 0 时,子连杆按照这个 origin 定义的位置和姿态放置
- 当关节旋转时,子连杆绕
<axis>定义的轴旋转
-
<axis>的方向:在子连杆(link1)的坐标系下定义xyz="0 0 1"表示绕正 Z 轴旋转- 这个轴方向在 link1 坐标系中是固定的,会随 link1 一起运动
3.5 坐标系关系图解
┌─────────────────────────────────────────────────────────────────┐
│ 坐标系关系图解 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Z │
│ ↑ link2 坐标系原点 │
│ ┌───┼───┐ (在 joint2 位置) │
│ │ │ │ link2 (box) │
│ │ │ │ visual origin: (0,0,0.1) │
│ └───┼───┘ │
│ │ │
│ joint2 ─────────●───→ X joint origin: (0,0,0.3) │
│ (revolute) │ 表示 joint2 在 link1 上方 0.3m │
│ ┌──┴──┐ │
│ │ │ link1 (box) │
│ │ │ visual origin: (0,0,0.15) │
│ │ │ 表示 box 中心在 link1 原点上方 0.15m │
│ └──┬──┘ │
│ │ │
│ joint1 ────────●───→ X joint origin: (0,0,0.1) │
│ (revolute) │ 表示 joint1 在 base_link 上方 0.1m │
│ ┌─┴──┐ │
│ │ │ base_link (cylinder) │
│ │ ●──┼──→ X base_link 坐标系原点 │
│ │ │ │ visual origin: (0,0,0.05) │
│ └─┼──┘ 表示圆柱中心在原点上方 0.05m │
│ ═════════════╧═════════════════════════════════════════════ │
│ 地面 (Z=0) │
│ │
│ 【几何体尺寸说明】 │
│ • base_link: 圆柱 高0.1m × 半径0.05m │
│ • link1: 长方体 0.04m × 0.04m × 0.3m (高) │
│ • link2: 长方体 0.06m × 0.06m × 0.2m (高) │
│ │
└─────────────────────────────────────────────────────────────────┘
关键理解:
- Link 的原点 ≠ 几何体的中心,
<visual><origin>定义几何体中心相对于 link 原点的偏移 - Joint 的 origin 定义的是 child link 的原点相对于 parent link 原点的位置
- 当 joint 角度为 0 时,child link 按照 joint origin 定义的位姿放置
3.6 URDF 如何自动发布 TF?
在 8.ROS2 TF2 机械臂坐标系实战中,我们手动使用 TransformBroadcaster 发布 TF 变换。但在使用 URDF 时,这个过程是自动完成的。
3.6.1 核心概念:URDF 只是"图纸"
重要理解:URDF 文件本身不会发布任何 TF,它只是一个静态的 XML 文档,描述了机器人的结构。
3.6.2 robot_state_publisher:将 URDF 变成 TF 的"翻译器"
要让 URDF 变成活的 TF 树,需要 robot_state_publisher 节点:
启动命令回顾:
# 终端 1:启动 robot_state_publisher
ros2 run robot_state_publisher robot_state_publisher ~/ros2_ws/src/urdf_test/simple_3link.urdf
这行命令做了什么?
ros2 run robot_state_publisher robot_state_publisher:启动robot_state_publisher节点~/ros2_ws/src/urdf_test/simple_3link.urdf:传入 URDF 文件路径,告诉节点要加载哪个机器人模型
同时需要关节状态数据:
# 终端 2:启动关节滑块 GUI
ros2 run joint_state_publisher_gui joint_state_publisher_gui ~/ros2_ws/src/urdf_test/simple_3link.urdf
这行命令做了什么?
joint_state_publisher_gui:启动一个带图形界面的节点,提供滑块来手动设置关节角度- 作用:拖动滑块 → 改变关节角度 → 发布到
/joint_states→robot_state_publisher计算新 TF → RViz 更新显示 - 真实机械臂中:这个话题由机械臂驱动节点发布(如 Episode 1 的
robot_interface_node) - Randomize:随机设置所有关节角度
- Center:将所有关节归零
工作流程:
┌─────────────────────────────────────────────────────────────────┐
│ 从 URDF 到 TF:robot_state_publisher 的作用 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 步骤 1: 读取 URDF 文件 │
│ ┌──────────────────┐ │
│ │ simple_3link.urdf│ │
│ └────────┬─────────┘ │
│ │ 启动时加载 │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ robot_state_publisher 节点 │ │
│ │ (步骤2启动的节点) │ │
│ │ │ │
│ │ 解析 URDF,提取: │ │
│ │ • 所有 Link 的名称 │ │
│ │ • 所有 Joint 的类型、位置、轴向 │ │
│ │ • 构建机器人运动学树 │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 步骤 2: 订阅关节状态 │
│ ┌─────────────────────────────────────────┐ │
│ │ /joint_states 话题 │ │
│ │ (sensor_msgs/JointState) │ │
│ │ ← 由 joint_state_publisher_gui 发布 │ │
│ │ (步骤3启动的节点) │ │
│ │ │ │
│ │ name: ['joint1', 'joint2'] │ │
│ │ position: [0.5, -0.3] # 弧度 │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 步骤 3: 计算正运动学 │
│ ┌─────────────────────────────────────────┐ │
│ │ 根据 Joint 的 origin 和当前角度: │ │
│ │ │ │
│ │ T_base_to_link1 = T_joint1_origin │ │
│ │ × Rot(Z, θ1) │ │
│ │ │ │
│ │ T_link1_to_link2 = T_joint2_origin │ │
│ │ × Rot(Y, θ2) │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 步骤 4: 发布 TF 变换 │
│ ┌─────────────────────────────────────────┐ │
│ │ /tf 话题 │ │
│ │ (tf2_msgs/TFMessage) │ │
│ │ │ │
│ │ • base_link → link1 的变换 │ │
│ │ • link1 → link2 的变换 │ │
│ │ • link2 → end_effector 的变换 (fixed) │ │
│ └─────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 步骤 5: RViz 可视化 │
│ ┌─────────────────────────────────────────┐ │
│ │ RViz 订阅 /tf 和 /robot_description │ │
│ │ → 根据 TF 变换渲染机器人模型 │ │
│ └─────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
3.6.3 与第 8 章手动发布 TF 的对比
| 对比项 | 第 8 章:手动发布 TF | 第 9 章:URDF 自动发布 TF |
|---|---|---|
| TF 来源 | 代码中硬编码坐标变换 | URDF 文件中的 `` |
| 发布节点 | 自己编写的 Python/C++ 节点 | robot_state_publisher |
| 动态更新 | 需要手动计算新的变换并发布 | 订阅 /joint_states 自动计算 |
| 运动学计算 | 需要自己实现正运动学 | robot_state_publisher 内置 |
| 适用场景 | 额外的传感器、工具坐标系 | 机器人本体的关节坐标系 |
四、CAD 导出 URDF:工程实践中的主流方法
4.1 为什么不手写 URDF?
上一节的 3-link 示例大约 120 行,看起来还能手写。但实际的机械臂 URDF:
| 对比项 | 3-Link 示例 | 6 轴机械臂 |
|---|---|---|
| Link 数量 | 3 | 7(base + 6 link) |
| Joint 数量 | 2 | 6 |
| 几何体 | 基本形状 | 复杂 STL 网格 |
| 惯性参数 | 估算 | 需要精确计算 |
| 尺寸精度 | 随意 | 必须与实物一致 |
| 代码行数 | ~120 | ~400 |
结论:实际工程中,几乎没有人手写复杂的 URDF,而是从 CAD 软件导出。
4.2 主流 CAD 导出工具
- Blender URDF Exporter
- Fusion 360 URDF Exporter
- FusionSDF: Fusion 360 to SDF exporter
- SolidWorks URDF Exporter
- ExportURDF Library (Fusion360, OnShape, Solidworks)
4.4 导出后的标准目录结构
比如 Episode1 机械臂的 URDF 就是用 SolidWorks URDF Exporter 导出的, 导出后,通常会形成以下 ROS2 包结构:
episode1_urdf_1113/
├── config
│ └── gz_bridge.yaml # Gazebo-ROS2 话题桥接配置
├── episode1_urdf_1113
│ └── __init__.py
├── launch
│ ├── gz_simulator_launch.py # Gazebo 仿真启动
│ └── launch.py # RViz 可视化启动
├── meshes
│ ├── collision # 碰撞检测网格
│ │ ├── base_link.STL
│ │ ├── link1.STL
│ │ ├── link2.STL
│ │ ├── link3.STL
│ │ ├── link4.STL
│ │ ├── link5.STL
│ │ └── link6.STL
│ └── visual # 可视化网格
│ ├── base_link.STL
│ ├── link1.STL
│ ├── link2.STL
│ ├── link3.STL
│ ├── link4.STL
│ ├── link5.STL
│ └── link6.STL
├── package.xml # 功能包清单
├── resource
│ └── episode1_urdf_1113
├── setup.py # Python 包安装配置
├── urdf
│ ├── episode1_urdf_1113.urdf # URDF 模型(ROS2/RViz)
│ └── robot.sdf # SDF 模型(Gazebo)
└── world
└── depot.sdf # Gazebo 仿真场景
最佳实践:将 URDF 相关文件(urdf/、meshes/、launch/)放在独立的功能包中,便于复用和维护。
五、Episode 1 机械臂 URDF 解析
现在我们来分析 Episode 1 机械臂的实际 URDF 文件。
5.2 机械臂结构概览
| 属性 | 值 |
|---|---|
| 机器人名称 | episode1_urdf_1113 |
| Link 数量 | 7 个(base_link + link1~link6) |
| Joint 数量 | 6 个(joint1~joint6,全部为 revolute) |
| 自由度 | 6 DOF |
| 网格格式 | STL |
5.3 结构树状图
episode1_urdf_1113 (机器人)
│
└── base_link (底座)
│
└── joint1 (revolute, Z轴)
│
└── link1 (肩部)
│
└── joint2 (revolute, -Z轴)
│
└── link2 (大臂)
│
└── joint3 (revolute, -Z轴)
│
└── link3 (肘部)
│
└── joint4 (revolute, -Z轴)
│
└── link4 (小臂)
│
└── joint5 (revolute, -Z轴)
│
└── link5 (腕部)
│
└── joint6 (revolute, Z轴)
│
└── link6 (末端法兰)
5.4 Link 结构详解
以 link1 为例:
<link name="link1">
<!-- 惯性属性:用于动力学仿真 -->
<inertial>
<origin xyz="0.0129 0.00484 0.1429" rpy="0 0 0" /> <!-- 质心位置 -->
<mass value="0.623" /> <!-- 质量:0.623 kg -->
<inertia <!-- 惯性张量 -->
ixx="0.000787" ixy="3.99e-05" ixz="-0.000208"
iyy="0.00117" iyz="-3.07e-06"
izz="0.00135" />
</inertial>
<!-- 可视化:RViz 显示用 -->
<visual>
<origin xyz="0 0 0" rpy="0 0 0" />
<geometry>
<mesh filename="package://episode1_urdf_1113/meshes/visual/link1.STL" />
</geometry>
<material name="">
<color rgba="1 1 1 1" /> <!-- 白色 -->
</material>
</visual>
<!-- 碰撞体:物理仿真用 -->
<collision>
<origin xyz="0 0 0" rpy="0 0 0" />
<geometry>
<mesh filename="package://episode1_urdf_1113/meshes/visual/link1.STL" />
</geometry>
</collision>
</link>
Visual 参数说明(以 link1 为例)
| 元素 | link1 实际值 | 说明 |
|---|---|---|
<origin> |
xyz="0 0 0" |
网格中心与 link 原点重合(CAD 中已设置好) |
rpy="0 0 0" |
无旋转(网格已按正确姿态建模) | |
<geometry> |
<mesh filename="package://episode1_urdf_1113/meshes/visual/link1.STL" /> |
使用 STL 网格文件,包含从 SolidWorks 导出的精确 3D 模型 |
<material> |
<color rgba="1 1 1 1" /> |
白色(R=1 G=1 B=1 Alpha=1,完全不透明) |
为什么 Episode 1 的 origin 是 (0,0,0)?
注意到一个关键差异:
| 对比项 | CAD 导出的 URDF(Episode 1) | 手动创建的 URDF(3-Link 示例) |
|---|---|---|
<visual><origin> |
xyz="0 0 0" |
xyz="0 0 0.05"(需要偏移) |
| 几何体类型 | <mesh> STL 网格文件 |
<cylinder> 等基本几何体 |
| 原因 | STL 文件在 CAD 中建模时已定义好坐标 | 基本几何体默认以自身中心为原点 |
详细解释:
-
CAD 导出的网格文件(如 Episode 1):
- SolidWorks 等 CAD 软件在建模时,设计师会为每个零件设置坐标系原点
- 导出的 STL 文件包含的顶点坐标已经是相对于这个原点的
- 所以 URDF 中
<visual><origin>通常是(0,0,0),网格本身已经在正确位置
-
手动创建的基本几何体(如 3-Link 示例):
<cylinder>等几何体默认以其几何中心为原点- 如果想让圆柱底部在 link 原点(地面),需要通过
<origin>向上偏移半个高度 - 例如:高 0.1m 的圆柱,
origin xyz="0 0 0.05"使其底部在 Z=0
网格文件路径解析:package://episode1_urdf_1113/meshes/visual/link1.STL → 实际路径:~/ros2_ws/install/episode1_urdf_1113/share/episode1_urdf_1113/meshes/visual/link1.STL
5.5 Joint 结构详解
以 joint2 为例(最具代表性,因为它有复杂的 origin 变换):
<joint name="joint2" type="revolute">
<!-- 子连杆原点相对于父连杆原点的位置和姿态 -->
<origin
xyz="0.055 -0.02 0.166" <!-- 平移:X +5.5cm, Y -2cm, Z +16.6cm -->
rpy="1.5708 0 0" /> <!-- 旋转:绕 X 轴旋转 90°(π/2 弧度) -->
<parent link="link1" /> <!-- 父连杆 -->
<child link="link2" /> <!-- 子连杆 -->
<axis xyz="0 0 -1" /> <!-- 旋转轴:负 Z 轴方向 -->
<limit <!-- 关节限位 -->
lower="-1.5708" <!-- 下限:-90° -->
upper="1.5708" <!-- 上限:+90° -->
effort="0" <!-- 力矩限制(0 表示未设置) -->
velocity="0" /> <!-- 速度限制(0 表示未设置) -->
</joint>
Joint 参数说明(以 joint2 为例)
| 元素 | joint2 实际值 | 说明 |
|---|---|---|
type |
revolute |
关节类型:旋转关节(有限位) |
<parent> |
link="link1" |
父连杆:joint2 连接在 link1 上 |
<child> |
link="link2" |
子连杆:link2 是 joint2 的下级连杆 |
<origin> |
xyz="0.055 -0.02 0.166" |
link2 的原点相对于 link1 的原点的位置(单位:米) |
rpy="1.5708 0 0" |
link2 坐标系相对于 link1 坐标系的旋转(单位:弧度)这里是绕 X 轴旋转 90°(π/2 弧度) | |
<axis> |
xyz="0 0 -1" |
关节旋转轴方向:负 Z 轴(在 link2 的坐标系下) |
<limit> |
lower="-1.5708" |
关节角度下限:-90°(-π/2 弧度) |
upper="1.5708" |
关节角度上限:+90°(π/2 弧度) | |
effort="0" |
最大力矩限制(0 表示未设置,仅可视化时可忽略) | |
velocity="0" |
最大速度限制(0 表示未设置,仅可视化时可忽略) |
关键理解:
-
<origin>的含义:定义子连杆坐标系相对于父连杆坐标系的位置和姿态- 当关节角度为 0 时,link2 的坐标系按照这个 origin 定义的位置和姿态放置
- 当关节旋转时,link2 绕
<axis>定义的轴旋转
-
<axis>的方向:在子连杆(link2)的坐标系下定义xyz="0 0 -1"表示绕负 Z 轴旋转- 这个轴方向在 link2 坐标系中是固定的,会随 link2 一起运动
-
与 Visual 的 origin 区别:
<visual><origin>:定义几何体中心相对于 Link 原点的偏移<joint><origin>:定义子 Link 整个坐标系相对于父 Link 坐标系的变换
5.6 所有 Joint 参数汇总
| Joint | 父 → 子 | 位置 (xyz) | 旋转 (rpy) | 轴向 | 限位 (°) |
|---|---|---|---|---|---|
| joint1 | base→link1 | (0, 0, 0) | (0, 0, 0) | +Z | -180 ~ +160 |
| joint2 | link1→link2 | (0.055, -0.02, 0.166) | (90°, 0, 0) | -Z | -90 ~ +90 |
| joint3 | link2→link3 | (0, 0.2, 0) | (0, 0, 180°) | -Z | -83 ~ +80 |
| joint4 | link3→link4 | (0, -0.056, -0.02) | (-90°, 0, -90°) | -Z | -30 ~ +305 |
| joint5 | link4→link5 | (0, 0, -0.192) | (90°, -90°, 0) | -Z | -110 ~ +110 |
| joint6 | link5→link6 | (-0.055, 0, 0) | (90°, 0, -90°) | +Z | -30 ~ +305 |
注意:表中的角度是从弧度换算的近似值,实际 URDF 中使用弧度。