《URDF 机器人建模:连杆、关节与可视化》
当机器人拥有多个零部件时,只靠一张外观图无法让 ROS 2 理解“哪个部件连接在哪里、能够怎样运动、每个坐标系之间是什么关系”。URDF 用 XML 把这些结构信息写成机器可读取的模型:刚体由 link 表示,连接关系由 joint 表示,robot_state_publisher 再根据关节状态生成 TF,RViz2 负责把模型和坐标系显示出来。
本文以 ROS 2 Humble 为基准,从一个最小连杆开始,逐步完成固定三连杆模型和可转动机械臂模型,并将它们整理成可构建的 urdf_demo 功能包。完成后,你不仅能够看懂 visual、collision、inertial、origin、axis 和 limit,还能够使用 check_urdf、TF 与 RViz2 建立一套可重复的建模验证流程。
1. 版本、环境与最终成果
1.1 推荐环境
本文采用以下环境作为可复现基线:
| 项目 | 推荐值 | 说明 |
|---|---|---|
| 操作系统 | Ubuntu 22.04 | ROS 2 Humble 的主要支持平台 |
| ROS 2 | Humble Hawksbill | 建议安装 ros-humble-desktop |
| 构建工具 | colcon |
使用 ament_python 功能包 |
| 可视化工具 | RViz2 | 显示 RobotModel 与 TF |
Jetson Orin Nano 使用 Ubuntu 22.04 与 ROS 2 Humble 时,可以直接沿用本文命令。其他 ROS 2 发行版的 URDF 核心语法基本相同,但包名、RViz2 属性或启动方式可能有小幅差异;若实际环境不是 Humble,应以对应发行版的官方文档为准。
先确认基础环境:
source /opt/ros/humble/setup.bash
printenv ROS_DISTRO
ros2 --help
colcon --help
printenv ROS_DISTRO 应输出 humble。如果 ros2 或 colcon 找不到,先完成 ROS 2 Desktop 与开发工具安装,不要在环境尚未正确加载时继续排查 URDF。
本文使用以下依赖,joint-state-publisher-gui依赖会在10.5小节用到,务必检查并安装:
sudo apt update
sudo apt install -y \
liburdfdom-tools \
ros-humble-joint-state-publisher-gui \
ros-humble-robot-state-publisher \
ros-humble-rviz2
其中,liburdfdom-tools 提供 check_urdf,其余包分别负责 TF、测试关节状态和模型显示。固定关节模型不需要关节状态 GUI。
1.2 本文最终文件
最终功能包包含三个模型:
| 文件 | 用途 | 是否包含可动关节 |
|---|---|---|
urdf/single_link.urdf |
理解最小 URDF 结构 | 否 |
urdf/simple_demo.urdf |
固定三连杆完整示例 | 否 |
urdf/articulated_demo.urdf |
验证 revolute、axis 和 limit |
是 |
同时提供 rviz_view.launch.py、joint_view.launch.py 和 rviz/urdf.rviz。文章最后会给出标准构建、运行、验证流程,配套目录中的 example/urdf_demo 可直接放入 ROS 2 工作空间的 src 目录。
2. URDF 在 ROS 2 中解决什么问题
2.1 URDF 是结构描述,不是控制程序
URDF 是 Unified Robot Description Format,即统一机器人描述格式。它是一种 XML 文档,用来声明机器人的连杆、关节、几何外观、碰撞形状、质量和惯性等静态信息。URDF 能回答以下问题:
-
机器人有哪些刚体部件?
-
哪个连杆是父连杆,哪个是子连杆?
-
两个连杆在零位时如何安装?
-
关节能绕哪根轴旋转,或沿哪根轴移动?
-
RViz2 应显示什么形状和颜色?
-
仿真器进行碰撞与动力学计算时应使用哪些参数?
URDF 不能替代驱动节点、控制器、传感器驱动或运动规划器。电机是否真的转动、编码器如何读取、速度闭环如何计算,都需要其他 ROS 2 节点完成。更准确地说,URDF 是机器人“结构与坐标关系的数字说明书”。
完整链路可以概括为:URDF 文本被写入 robot_description 参数和同名话题,robot_state_publisher 读取模型拓扑,并结合 /joint_states 中的关节位置计算 TF;RViz2 的 RobotModel 显示项读取机器人描述,同时根据 TF 将每个连杆放到正确位置。固定关节的变换不会变化,通常发布到 /tf_static;可动关节的变换会随关节状态发布到 /tf。
2.2 实体机器人与模型边界
实体机器人通常包含机械结构、驱动系统、控制器、计算平台和传感器。URDF 主要描述机械结构,以及传感器相对机身的安装位置。例如,相机节点负责发布图像,URDF 则描述 camera_link 位于 base_link 前方多少米、朝向如何;雷达驱动负责发布扫描数据,URDF 则描述 laser_link 的安装高度和姿态。
若 RViz2 中相机点云整体偏移,问题可能是相机固定关节的 origin;若外形正确但不能运动,应检查 /joint_states 和关节名称,而不是 visual。
2.3 URDF 的树形约束
一个标准 URDF 模型是一棵树,而不是任意图结构:
-
必须有且只有一个根连杆。
-
除根连杆外,每个连杆只能有一个父关节。
-
每个关节连接一个父连杆和一个子连杆。
-
link名称和joint名称在各自范围内必须唯一。 -
普通 URDF 不能直接描述闭环机构。
如果同一个子连杆被两个关节指向,或关节首尾形成环,check_urdf 通常会报告无法构建树。闭环机构需要专门处理,不能把物理闭环直接写进普通 URDF。
3. 连杆 link:描述一个刚体
3.1 link 的三个核心维度
link 表示一个理想刚体。最常见的内部元素是 visual、collision 和 inertial:
| 元素 | 主要消费者 | 作用 | 是否影响 RViz2 外观 |
|---|---|---|---|
visual |
RViz2、Gazebo 渲染器 | 定义可见几何和材质 | 是 |
collision |
碰撞检测、物理仿真 | 定义碰撞几何 | 默认不显示 |
inertial |
动力学与物理仿真 | 定义质量、质心和惯性 | 否 |
对只在 RViz2 中显示的最小模型,可以先只写 visual。但是进入 Gazebo、MoveIt 碰撞检测或动力学仿真后,collision 和 inertial 不能长期缺省。视觉模型追求真实,碰撞模型追求计算稳定和高效,二者没有必要完全相同。
3.2 visual、geometry 与 material
下面是一个完整的可视元素:
<visual>
<origin xyz="0 0 0.1" rpy="0 0 0"/>
<geometry>
<box size="0.4 0.3 0.2"/>
</geometry>
<material name="blue">
<color rgba="0.1 0.3 0.8 1.0"/>
</material>
</visual>
geometry 支持四类常用形状:
| 几何体 | 关键属性 | 单位或含义 |
|---|---|---|
box |
size="x y z" |
三个方向的边长,单位为米 |
cylinder |
radius、length |
半径与沿自身 Z 轴的长度 |
sphere |
radius |
球半径,单位为米 |
mesh |
filename、scale |
网格 URI 与三轴缩放 |
rgba 的四个数字依次是红、绿、蓝、透明度,范围通常为 0 到 1。透明度为 1 表示完全不透明。重复使用的材质宜声明在 <robot> 下,再通过 <material name="blue"/> 引用,避免每个连杆重复写颜色。
3.3 为什么 visual 和 collision 应分开
真实机器人外壳可能包含倒角、孔位、线缆和曲面。如果把高面数网格同时用于碰撞检测,仿真和规划速度会明显下降。常见工程做法是:
-
visual使用精细网格或组合几何,保证外观接近实物。 -
collision使用盒体、圆柱、球体或低面数凸包,保证计算稳定。 -
两者分别设置自己的
origin,但都相对同一个连杆坐标系定义。
上图中的 frame 是连杆坐标系,visual geometry 是渲染形状,collision geometry 是碰撞包络。即使两个几何体外形不同,只要相对坐标正确,它们仍属于同一个 link。排查碰撞错位时,应在 RViz2 中启用 RobotModel 的 Collision 显示,不能只看视觉外观。
3.4 inertial 的含义
典型惯性配置如下:
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="1.0"/>
<inertia
ixx="0.01" ixy="0" ixz="0"
iyy="0.01" iyz="0" izz="0.01"/>
</inertial>
origin 表示惯性坐标系位姿,平移通常对应质心;mass 单位为千克;inertia 是对称惯性矩阵的六个独立元素。质量必须大于零,矩阵应物理可实现。本文固定模型的数值用于结构示范,真实仿真时应重新计算。
3.5 连杆坐标系比外形中心更重要
link 本身没有尺寸,坐标系才是它真正的参考。几何体中心不必与连杆原点重合。例如机械臂连杆常把原点放在关节轴心,再把一根长度为 0.6 m 的盒体沿 X 轴平移 0.3 m。这样盒体从关节处向外延伸,而下一级关节可以直接放在 xyz="0.6 0 0"。
建模时应先确定坐标系,再放几何体,否则容易出现轴心偏移和 TF 难以解释的问题。
4. 关节 joint:连接父连杆与子连杆
4.1 一个关节包含什么
joint 不表示实体零件,而表示两个连杆之间的运动学约束。下面是一个带限位的旋转关节:
<joint name="shoulder_joint" type="revolute">
<parent link="base_link"/>
<child link="arm_link"/>
<origin xyz="0 0 0.2" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
<limit lower="-1.5708" upper="1.5708"
effort="10" velocity="1.0"/>
</joint>
各字段的职责如下:
| 字段 | 含义 | 常见错误 |
|---|---|---|
parent |
指定父连杆 | 名称与 link name 不一致 |
child |
指定子连杆 | 子连杆被多个关节重复使用 |
origin |
零位时子坐标系相对父坐标系的安装位姿 | 把几何偏移误写成关节偏移 |
axis |
关节运动轴,表达在关节坐标系中 | 轴向与预期旋转方向不符 |
limit |
位置、速度和力矩限制 | revolute 或 prismatic 遗漏必需限制 |
关节值为零时,origin 决定父、子连杆坐标系的基础变换;关节值变化时,再绕 axis 旋转或沿 axis 平移。理解这一点后,才能区分“部件安装在哪里”和“部件围绕什么运动”。
4.2 常用关节类型
| 类型 | 自由度 | 是否需要 limit |
典型用途 |
|---|---|---|---|
fixed |
0 | 否 | 机身与传感器固定安装 |
revolute |
单轴旋转 | 是 | 舵机、带机械限位的关节 |
continuous |
单轴连续旋转 | 不设位置上下限 | 车轮、无限旋转轴 |
prismatic |
单轴直线移动 | 是 | 升降台、直线模组 |
planar |
平面运动 | 特殊 | 少量二维机构 |
floating |
六自由度 | 特殊 | 少量自由体模型 |
fixed 只声明固定安装关系,不需要 axis 与 limit。continuous 需要 axis,但没有角度上下限。revolute 与 prismatic 通常必须给出 lower、upper、effort 和 velocity。位置限制的单位取决于运动类型:旋转关节使用弧度,直线关节使用米;velocity 对旋转关节为 rad/s,对直线关节为 m/s。
4.3 固定关节实例
固定三连杆模型中的右腿由以下关节连接:
<joint name="base_to_right_leg" type="fixed">
<parent link="base_link"/>
<child link="right_leg"/>
<origin xyz="0 -0.22 0.25" rpy="0 0 0"/>
</joint>
这表示 right_leg 坐标系相对 base_link 沿 Y 轴移动 -0.22 m、沿 Z 轴移动 0.25 m,并保持相同朝向。它不表示右腿几何体的中心位置;右腿几何体还会在自身 visual/origin 中继续偏移和旋转。
4.4 固定模型与可动模型的数据差异
只有固定关节时,robot_state_publisher 读取 URDF 后就能确定所有连杆之间的变换,不需要额外关节状态。存在 revolute、continuous 或 prismatic 时,仅有 URDF 还不够,因为系统不知道当前角度或位移;此时必须由驱动、控制器、仿真器或 joint_state_publisher_gui 发布 sensor_msgs/msg/JointState。
JointState.name 中的名称必须与 URDF 的 joint name 完全一致。若 URDF 写的是 shoulder_joint,发布端却写成 shoulder,模型文件可以正常解析,但 TF 不会按预期更新。
5. origin、坐标轴与模型树
5.1 xyz 和 rpy
origin 的基本格式是:
<origin xyz="x y z" rpy="roll pitch yaw"/>
xyz 单位为米。rpy 依次表示绕 X、Y、Z 轴的滚转、俯仰和偏航角,单位为弧度。常用换算如下:
| 角度 | 弧度近似值 |
|---|---|
| 30° | 0.5236 |
| 45° | 0.7854 |
| 90° | 1.5708 |
| 180° | 3.14159 |
ROS 坐标约定通常遵循 REP-103:X 向前、Y 向左、Z 向上。URDF 语法不会强制你遵循该方向,但整个项目保持一致能显著降低传感器、导航和 TF 集成成本。
5.2 两层 origin 不要混淆
同一个子连杆常同时出现两类 origin:
-
joint/origin:定义子连杆坐标系相对父连杆坐标系的安装位置。 -
visual/origin、collision/origin、inertial/origin:定义各自属性相对当前连杆坐标系的位置。
以右腿为例,joint/origin 先把 right_leg 坐标系放到基座右侧和上方;visual/origin 再把长方体中心沿自身 Z 轴移动 -0.3 m,并绕 Y 轴旋转约 90°。最终外形位置是两级变换叠加的结果。若把这两个偏移全部塞进关节,坐标系会落在几何中心之外,后续传感器安装和子关节定位会变得难以维护。
5.3 三连杆模型的树结构
固定三连杆模型有一个根连杆和两个分支:
base_link
├── base_to_left_leg -> left_leg
└── base_to_right_leg -> right_leg
左右腿关节的 X、Z 坐标相同,Y 坐标分别为 0.22 和 -0.22,由此形成镜像布局。树图是排查 URDF 的第一工具:每增加一个关节,都应能明确指出它的父连杆、子连杆、零位安装关系和运动轴。如果只能描述“它看起来在这里”,却说不清坐标树,模型通常还没有真正完成。
5.4 先定零位,再定运动范围
对于可动关节,建模顺序建议固定为:
-
确定父连杆坐标系。
-
用
joint/origin放置关节轴心和关节坐标系。 -
用
axis指定正方向。 -
在关节值为零时摆正子连杆。
-
最后填写
lower、upper、velocity和effort。
工程中应统一 CAD 装配零位、编码器零点与软件零位,并通过驱动偏置或标定处理硬件误差,不要反复修改 URDF 迁就当前读数。
6. 创建功能包与最小模型
6.1 获取完整示例
完整工程:GitHub 仓库(按住Ctrl同时鼠标单击链接即可跳转Git页面)任选一种方式获取。
下载 ZIP: 打开 ZIP 压缩包 后执行:
mkdir -p ~/ros2_ws/src
unzip ~/Downloads/URDF_demo-main.zip -d ~/ros2_ws/src
Git 克隆:
mkdir -p ~/ros2_ws/src
git -C ~/ros2_ws/src clone --depth 1 \
https://github.com/2303886347/URDF_demo.git
两种目录中的 urdf_demo 均可被 colcon 发现,根据示例压缩包或git界面的readme文档操作编译功能包后,可直接跳到第 12 章。
6.2 从零创建功能包
在 ~/ros2_ws/src 创建功能包:
source /opt/ros/humble/setup.bash
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
ros2 pkg create urdf_demo \
--build-type ament_python \
--license Apache-2.0
mkdir -p urdf_demo/urdf
mkdir -p urdf_demo/launch
mkdir -p urdf_demo/rviz
本文使用 ament_python 和 setup.py 安装模型资源。URDF 不是可执行程序,无需执行 chmod +x。
完成后的核心目录如下:
ros2_ws/
└── src/
└── urdf_demo/
├── launch/
│ ├── joint_view.launch.py
│ └── rviz_view.launch.py
├── resource/
│ └── urdf_demo
├── rviz/
│ └── urdf.rviz
├── urdf/
│ ├── articulated_demo.urdf
│ ├── simple_demo.urdf
│ └── single_link.urdf
├── urdf_demo/
│ └── __init__.py
├── package.xml
├── setup.cfg
└── setup.py
6.3 最小可解析 URDF
第一个示例只解决一件事:确认 XML、robot、link、visual 和基础几何体的最小组合。文件路径为 ~/ros2_ws/src/urdf_demo/urdf/single_link.urdf,完整内容如下:
<?xml version="1.0"?>
<robot name="single_link_demo">
<material name="blue">
<color rgba="0.1 0.3 0.8 1.0"/>
</material>
<link name="base_link">
<visual>
<origin xyz="0 0 0.1" rpy="0 0 0"/>
<geometry>
<box size="0.4 0.3 0.2"/>
</geometry>
<material name="blue"/>
</visual>
</link>
</robot>
运行前先做结构验证:
check_urdf ~/ros2_ws/src/urdf_demo/urdf/single_link.urdf
预期能看到类似以下结果:
robot name is: single_link_demo
---------- Successfully Parsed XML ---------------
root Link: base_link has 0 child(ren)
这个模型足以在 RViz2 中显示,但没有 collision 和 inertial,因此它只适合作为显示与语法练习,不是完整物理仿真模型。最重要的注意事项是:check_urdf 成功只证明 XML 和树结构可解析,不证明尺寸、坐标方向、碰撞外形和惯性参数符合真实机器人。
6.4 XML 编写习惯
建议把每个元素的职责保持清晰:
-
所有名称只使用 ASCII 字母、数字和下划线。
-
数值显式写成米和弧度,不在属性中附加单位字符。
-
XML 属性使用英文半角引号,避免中文引号。
-
每个开始标签都要有对应结束标签,自闭合标签以
/>结束。 -
robot name、文件名和功能包名使用能表达含义的名称。
编辑器中的自动格式化和 XML 高亮很有帮助,但不要依赖编辑器替代 check_urdf。复制代码时尤其要检查 - 是否被替换成特殊短横线、引号是否变成排版引号。
7. 完成固定三连杆模型
7.1 示例目标与文件位置
这一主线示例将一个蓝色圆柱基座与两条白色长方体腿连接起来,用来完整演示材质、visual、collision、inertial 和 fixed 关节。文件路径为 ~/ros2_ws/src/urdf_demo/urdf/simple_demo.urdf。
7.2 simple_demo.urdf 完整代码
<?xml version="1.0"?>
<robot name="simple_demo">
<material name="blue">
<color rgba="0 0 0.8 1"/>
</material>
<material name="white">
<color rgba="1 1 1 1"/>
</material>
<link name="base_link">
<visual>
<geometry>
<cylinder length="0.6" radius="0.2"/>
</geometry>
<material name="blue"/>
</visual>
<collision>
<geometry>
<cylinder length="0.6" radius="0.2"/>
</geometry>
</collision>
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="1.0"/>
<inertia
ixx="0.01" ixy="0" ixz="0"
iyy="0.01" iyz="0" izz="0.01"/>
</inertial>
</link>
<link name="right_leg">
<visual>
<origin xyz="0 0 -0.3" rpy="0 1.5708 0"/>
<geometry>
<box size="0.6 0.1 0.2"/>
</geometry>
<material name="white"/>
</visual>
<collision>
<origin xyz="0 0 -0.3" rpy="0 1.5708 0"/>
<geometry>
<box size="0.6 0.1 0.2"/>
</geometry>
</collision>
<inertial>
<origin xyz="0 0 -0.3" rpy="0 0 0"/>
<mass value="0.2"/>
<inertia
ixx="0.002" ixy="0" ixz="0"
iyy="0.002" iyz="0" izz="0.002"/>
</inertial>
</link>
<link name="left_leg">
<visual>
<origin xyz="0 0 -0.3" rpy="0 1.5708 0"/>
<geometry>
<box size="0.6 0.1 0.2"/>
</geometry>
<material name="white"/>
</visual>
<collision>
<origin xyz="0 0 -0.3" rpy="0 1.5708 0"/>
<geometry>
<box size="0.6 0.1 0.2"/>
</geometry>
</collision>
<inertial>
<origin xyz="0 0 -0.3" rpy="0 0 0"/>
<mass value="0.2"/>
<inertia
ixx="0.002" ixy="0" ixz="0"
iyy="0.002" iyz="0" izz="0.002"/>
</inertial>
</link>
<joint name="base_to_right_leg" type="fixed">
<parent link="base_link"/>
<child link="right_leg"/>
<origin xyz="0 -0.22 0.25" rpy="0 0 0"/>
</joint>
<joint name="base_to_left_leg" type="fixed">
<parent link="base_link"/>
<child link="left_leg"/>
<origin xyz="0 0.22 0.25" rpy="0 0 0"/>
</joint>
</robot>
保存后执行:
check_urdf ~/ros2_ws/src/urdf_demo/urdf/simple_demo.urdf
预期根连杆为 base_link,并显示两个子链。check_urdf 输出中的树应与第 5 章的结构一致。正式构建与 RViz2 启动命令统一放在第 12 章,避免在每节重复。
8. 逐段理解三连杆模型
8.1 全局材质与基座
blue 和 white 定义在 <robot> 的直接子级,因此多个 visual 可以按名称复用。base_link 的圆柱没有额外 origin,其中心与 base_link 原点重合,圆柱自身 Z 轴方向的长度为 0.6 m,半径为 0.2 m。
如果希望 base_link 原点位于圆柱底面而不是中心,应把圆柱的 visual/origin 和 collision/origin 都改成 xyz="0 0 0.3",并同步考虑惯性坐标系。不要只移动 visual,否则外观看起来正确,碰撞体和质心仍停留在原位置。
8.2 腿部几何为什么既平移又旋转
box size="0.6 0.1 0.2" 默认在 X 方向最长。rpy="0 1.5708 0" 使盒体绕 Y 轴旋转约 90°,长轴因此转到接近 Z 方向;xyz="0 0 -0.3" 再把盒体中心下移 0.3 m。结果是腿部从自身连杆原点向下延伸。
这正体现了“连杆原点不等于外形中心”。关节连接到的是 right_leg 或 left_leg 坐标系,不是盒体中心。未来如果要在腿末端增加脚部关节,可以基于清晰的腿部坐标系继续定位,而不用从渲染网格中心反推位置。
8.3 左右对称只改变 Y 符号
右腿关节使用 xyz="0 -0.22 0.25",左腿使用 xyz="0 0.22 0.25"。在 X 向前、Y 向左、Z 向上的约定下,正 Y 位于机器人左侧,负 Y 位于机器人右侧。两侧使用相同的腿部几何和局部偏移,仅关节 Y 坐标符号相反,因此天然形成镜像。
如果模型左右颠倒,首先检查坐标约定和 Y 符号,不要急于通过修改相机视角掩盖问题。RViz2 视角可以旋转,但 TF 的左右关系不会因此改变。
8.4 可视化结果应观察什么
正确结果不是“窗口里出现了一个物体”这么简单,还应逐项确认:
-
蓝色圆柱体和两条白色长方体均可见。
-
两条腿相对基座左右对称,没有重叠到同一侧。
-
RViz2 的 Fixed Frame 为
base_link,Global Status 为 OK。 -
RobotModel 不报告
No transform或No link。 -
TF 显示中存在
base_link、left_leg、right_leg。
原图反映的是模型的三维显示效果,不应把它当作物理仿真结果。RViz2 不计算重力、摩擦、碰撞响应或电机动力学;即使惯性参数不合理,模型也可能在 RViz2 中正常显示。
8.5 本例惯性参数的边界
示例采用简化惯性值。进入 Gazebo 前,应按真实几何和质量计算惯量,确认质心、惯性坐标系与几何方向一致。建议先验证视觉和 TF,再加入碰撞体,最后依据 CAD 或测量数据填写质量和惯量。
9. 从 fixed 进阶到可动关节
9.1 示例解决的问题
固定模型能够验证连杆树,却无法直观看出 axis、limit 和 /joint_states 的作用。本节创建一个简化机械臂:圆柱基座通过 revolute 关节连接长方体机械臂,末端再通过 fixed 关节连接工具。joint_state_publisher_gui 提供肩关节滑块,拖动时 RViz2 中的机械臂应同步转动。
文件路径为 ~/ros2_ws/src/urdf_demo/urdf/articulated_demo.urdf。
9.2 articulated_demo.urdf 完整代码
<?xml version="1.0"?>
<robot name="articulated_demo">
<material name="blue">
<color rgba="0.1 0.3 0.8 1.0"/>
</material>
<material name="orange">
<color rgba="0.95 0.45 0.05 1.0"/>
</material>
<material name="green">
<color rgba="0.1 0.65 0.35 1.0"/>
</material>
<link name="base_link">
<visual>
<origin xyz="0 0 0.1" rpy="0 0 0"/>
<geometry>
<cylinder radius="0.18" length="0.2"/>
</geometry>
<material name="blue"/>
</visual>
<collision>
<origin xyz="0 0 0.1" rpy="0 0 0"/>
<geometry>
<cylinder radius="0.18" length="0.2"/>
</geometry>
</collision>
<inertial>
<origin xyz="0 0 0.1" rpy="0 0 0"/>
<mass value="2.0"/>
<inertia
ixx="0.02287" ixy="0" ixz="0"
iyy="0.02287" iyz="0" izz="0.03240"/>
</inertial>
</link>
<link name="arm_link">
<visual>
<origin xyz="0.3 0 0" rpy="0 0 0"/>
<geometry>
<box size="0.6 0.1 0.1"/>
</geometry>
<material name="orange"/>
</visual>
<collision>
<origin xyz="0.3 0 0" rpy="0 0 0"/>
<geometry>
<box size="0.6 0.1 0.1"/>
</geometry>
</collision>
<inertial>
<origin xyz="0.3 0 0" rpy="0 0 0"/>
<mass value="0.5"/>
<inertia
ixx="0.00083" ixy="0" ixz="0"
iyy="0.01542" iyz="0" izz="0.01542"/>
</inertial>
</link>
<joint name="shoulder_joint" type="revolute">
<parent link="base_link"/>
<child link="arm_link"/>
<origin xyz="0 0 0.2" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
<limit lower="-1.5708" upper="1.5708"
effort="10" velocity="1.0"/>
</joint>
<link name="tool_link">
<visual>
<origin xyz="0.1 0 0" rpy="0 1.5708 0"/>
<geometry>
<cylinder radius="0.06" length="0.2"/>
</geometry>
<material name="green"/>
</visual>
<collision>
<origin xyz="0.1 0 0" rpy="0 1.5708 0"/>
<geometry>
<cylinder radius="0.06" length="0.2"/>
</geometry>
</collision>
<inertial>
<origin xyz="0.1 0 0" rpy="0 1.5708 0"/>
<mass value="0.2"/>
<inertia
ixx="0.00085" ixy="0" ixz="0"
iyy="0.00085" iyz="0" izz="0.00036"/>
</inertial>
</link>
<joint name="arm_to_tool" type="fixed">
<parent link="arm_link"/>
<child link="tool_link"/>
<origin xyz="0.6 0 0" rpy="0 0 0"/>
</joint>
</robot>
先验证结构:
check_urdf ~/ros2_ws/src/urdf_demo/urdf/articulated_demo.urdf
预期树结构为:
base_link
└── shoulder_joint -> arm_link
└── arm_to_tool -> tool_link
9.3 肩关节的零位、轴与限位
shoulder_joint 位于基座上表面附近,即 xyz="0 0 0.2"。axis="0 1 0" 表示绕关节坐标系的 Y 轴运动。lower="-1.5708" 和 upper="1.5708" 将范围限制在约 -90° 到 90°。当 GUI 滑块从 0 向正方向移动时,机械臂按右手定则绕正 Y 轴旋转。
arm_link 的盒体中心位于自身 X 轴正方向 0.3 m,因此盒体近端恰好位于肩关节附近;arm_to_tool 再放在 X 轴 0.6 m 处,即机械臂末端。这个布局展示了一条可维护的坐标链:关节原点放轴心,连杆几何沿自身轴延伸,下一级关节放在连杆长度处。
9.4 验证时应观察什么
运行 joint_view.launch.py 后,joint_state_publisher_gui 应只显示一个名为 shoulder_joint 的滑块。拖动滑块时:
-
base_link保持不动。 -
arm_link围绕肩部 Y 轴旋转。 -
tool_link因固定在arm_link末端而一起运动。 -
/joint_states中的name为shoulder_joint。 -
base_link到arm_link的 TF 随角度变化。
最重要的注意事项是不要同时启动两个互相竞争的关节状态来源。真实驱动、控制器和 joint_state_publisher_gui 若同时发布同名关节,RViz2 可能出现跳变;GUI 只应作为没有真实硬件状态时的测试工具。
10. 让资源随功能包安装
10.1 为什么不能只把文件放进 src
直接运行源码路径下的 URDF 看似可行,但安装后的 Launch 文件通常通过 ROS 2 包索引查找 share/urdf_demo。如果 setup.py 没有安装 urdf、launch 和 rviz 目录,colcon build 可以成功,启动时却会报告文件不存在。
目标是让以下命令返回安装空间路径:
ros2 pkg prefix --share urdf_demo
预期结果类似 ~/ros2_ws/install/urdf_demo/share/urdf_demo,该目录下应能看到三个资源子目录。
10.2 完整 setup.py
文件路径为 ~/ros2_ws/src/urdf_demo/setup.py:
import os
from glob import glob
from setuptools import find_packages, setup
package_name = 'urdf_demo'
setup(
name=package_name,
version='0.1.0',
packages=find_packages(exclude=['test']),
data_files=[
(
os.path.join(
'share', 'ament_index', 'resource_index', 'packages'
),
['resource/' + package_name],
),
(os.path.join('share', package_name), ['package.xml']),
(
os.path.join('share', package_name, 'launch'),
glob(os.path.join('launch', '*.launch.py')),
),
(
os.path.join('share', package_name, 'rviz'),
glob(os.path.join('rviz', '*.rviz')),
),
(
os.path.join('share', package_name, 'urdf'),
glob(os.path.join('urdf', '*.*')),
),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='tutorial',
maintainer_email='tutorial@example.com',
description='URDF link, joint, TF, and RViz2 tutorial examples.',
license='Apache-2.0',
tests_require=['pytest'],
entry_points={
'console_scripts': [],
},
)
glob 在构建时收集匹配的文件,os.path.join 生成安装目标路径。新增 URDF 或 Launch 文件后必须重新构建,安装空间才会包含它。--symlink-install 对 Python 与资源开发很方便,但不能把“忘记构建”变成“永远自动安装新文件”。
10.3 完整 package.xml
文件路径为 ~/ros2_ws/src/urdf_demo/package.xml:
<?xml version="1.0"?>
<package format="3">
<name>urdf_demo</name>
<version>0.1.0</version>
<description>URDF link, joint, TF, and RViz2 tutorial examples.</description>
<maintainer email="tutorial@example.com">tutorial</maintainer>
<license>Apache-2.0</license>
<buildtool_depend>ament_python</buildtool_depend>
<exec_depend>ament_index_python</exec_depend>
<exec_depend>joint_state_publisher_gui</exec_depend>
<exec_depend>launch</exec_depend>
<exec_depend>launch_ros</exec_depend>
<exec_depend>robot_state_publisher</exec_depend>
<exec_depend>rviz2</exec_depend>
<export>
<build_type>ament_python</build_type>
</export>
</package>
依赖按运行期实际使用声明。功能包没有自定义 rclpy 节点,因此不需要为了“这是 ROS 2 包”而额外依赖 rclpy。如果以后添加 Python 节点,再把对应依赖加入 package.xml。
10.4 固定模型 Launch 文件
文件路径为 ~/ros2_ws/src/urdf_demo/launch/rviz_view.launch.py:
from pathlib import Path
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
package_share = Path(get_package_share_directory('urdf_demo'))
urdf_path = package_share / 'urdf' / 'simple_demo.urdf'
rviz_path = package_share / 'rviz' / 'urdf.rviz'
robot_description = urdf_path.read_text(encoding='utf-8')
return LaunchDescription([
Node(
package='robot_state_publisher',
executable='robot_state_publisher',
name='robot_state_publisher',
parameters=[{'robot_description': robot_description}],
output='screen',
),
Node(
package='rviz2',
executable='rviz2',
name='rviz2',
arguments=['-d', str(rviz_path)],
output='screen',
),
])
这里从安装后的包共享目录读取 URDF,而不是写死 ~/ros2_ws/src。因此功能包移动到另一台机器或另一个工作空间后仍能工作。固定模型不启动 joint_state_publisher_gui,因为两个关节的变换都可直接由 URDF 确定。
10.5 可动模型 Launch 文件
文件路径为 ~/ros2_ws/src/urdf_demo/launch/joint_view.launch.py:
from pathlib import Path
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
package_share = Path(get_package_share_directory('urdf_demo'))
urdf_path = package_share / 'urdf' / 'articulated_demo.urdf'
rviz_path = package_share / 'rviz' / 'urdf.rviz'
robot_description = urdf_path.read_text(encoding='utf-8')
return LaunchDescription([
Node(
package='joint_state_publisher_gui',
executable='joint_state_publisher_gui',
name='joint_state_publisher_gui',
parameters=[{'robot_description': robot_description}],
output='screen',
),
Node(
package='robot_state_publisher',
executable='robot_state_publisher',
name='robot_state_publisher',
parameters=[{'robot_description': robot_description}],
output='screen',
),
Node(
package='rviz2',
executable='rviz2',
name='rviz2',
arguments=['-d', str(rviz_path)],
output='screen',
),
])
两个状态相关节点读取同一份 robot_description,保证 GUI 展示的关节名称与状态发布器解析的模型一致。对于真实机器人,应将 GUI 替换成实际驱动或 ros2_control 控制器,不应让 Launch 文件长期依赖人工滑块。
11. 在 RViz2 中显示模型与 TF
11.1 RViz 配置要点
配套工程已经提供 rviz/urdf.rviz。其中最关键的设置是:Global Options 的 Fixed Frame 为 base_link;RobotModel 的 Description Source 为 Topic;Description Topic 为 /robot_description;Durability Policy 为 Transient Local;同时启用 TF 显示。
Transient Local 很重要。robot_state_publisher 会发布机器人描述,RViz2 若稍晚启动,需要以兼容的 QoS 获取已保存的描述;若使用不合适的 Volatile 配置,可能出现节点都正常但 RobotModel 偶发为空的竞态。
如果不使用配套配置,可手动执行以下操作:
-
启动 RViz2 后,将 Fixed Frame 改为
base_link。 -
点击 Add,添加 RobotModel。
-
将 Description Source 设为 Topic。
-
将 Description Topic 设为
/robot_description。 -
再添加 TF,观察连杆名称、坐标轴和树结构。
-
使用 File → Save Config As 保存到功能包的
rviz/urdf.rviz。
Fixed Frame 不应写成默认的 map,也不要写成 /base_link。如果全局状态提示 Fixed Frame 不存在,优先检查 robot_state_publisher 是否启动以及模型根连杆是否确实命名为 base_link。
11.2 RobotModel 与 TF 各自证明什么
RobotModel 正常说明视觉资源和 TF 可用;TF 树正确说明父子关系与状态链路基本成立。碰撞体和惯性仍需单独验收。
12. 标准构建、运行与验证流程
12.1 安装依赖并构建
在工作空间根目录执行:
source /opt/ros/humble/setup.bash
cd ~/ros2_ws
colcon build --symlink-install --packages-select urdf_demo
source install/setup.bash
构建后检查安装资源:
ros2 pkg prefix --share urdf_demo
find install/urdf_demo/share/urdf_demo -maxdepth 2 -type f | sort
结果中应包含 launch、rviz、urdf 和 package.xml。若源码中有文件而安装目录没有,检查 setup.py 的 data_files,然后重新构建并重新 source。
12.2 启动固定模型
ros2 launch urdf_demo rviz_view.launch.py
预期 RViz2 显示蓝色基座和两条白色腿,TF 树以 base_link 为根。另开终端并加载工作空间后验证:
source ~/ros2_ws/install/setup.bash
ros2 node list
ros2 topic info /robot_description --verbose
ros2 run tf2_ros tf2_echo base_link right_leg
tf2_echo 的平移应接近 0 -0.22 0.25,旋转应为单位旋转。由于模型全部使用固定关节,不出现 /joint_states 也不属于错误。
12.3 启动可动模型
先关闭上一套 Launch,再执行:
ros2 launch urdf_demo joint_view.launch.py
拖动 shoulder_joint 滑块,同时检查:
ros2 topic echo /joint_states --once
ros2 run tf2_ros tf2_echo base_link arm_link
/joint_states 应包含 shoulder_joint,TF 的旋转值应随滑块变化。如果 GUI 有滑块但模型不动,核对关节名称、robot_state_publisher 节点和 /joint_states 发布者。
推荐始终遵循“先 check_urdf,再构建,再启动,再查 TF,最后看 RViz2”的顺序。这样能把 XML 错误、安装错误、状态发布错误和显示配置错误分层定位,而不是在 RViz2 空白窗口里盲目尝试。
13. 网格、碰撞、惯性与复用
13.1 加载 mesh 资源
真实外观可使用包 URI:
<geometry>
<mesh filename="package://urdf_demo/meshes/base.stl"
scale="0.001 0.001 0.001"/>
</geometry>
package:// 比绝对路径可移植。scale="0.001 0.001 0.001" 常用于把毫米网格换算为米,但不能看到 STL 就机械套用;应确认 CAD 导出单位。新增 meshes 目录后,还要在 setup.py 中加入对应安装规则。
13.2 工程化原则
高面数视觉网格应配低复杂度碰撞体;惯性应来自 CAD、称重和公式,而不是照抄其他机器人;重复轮子、腿或传感器安装结构可在模型稳定后迁移到 Xacro 宏。Xacro 能减少重复,但不会修复错误坐标,因此先用普通 URDF 理清一条链,再做参数化复用。
14. 常用元素与命令速查
| 项目 | 作用 | 关键点 |
|---|---|---|
link |
声明刚体 | 名称唯一 |
joint |
连接父子连杆 | 模型保持树形 |
origin |
声明相对位姿 | 米、弧度 |
axis |
声明运动轴 | 位于关节坐标系 |
limit |
声明运动约束 | revolute、prismatic 必查 |
check_urdf |
解析 XML 和树结构 | 不验证物理真实性 |
robot_state_publisher |
由模型和关节状态发布 TF | 固定关节发布静态变换 |
| RobotModel | 在 RViz2 显示 URDF | 依赖描述与 TF |
常用命令集中如下:
check_urdf path/to/model.urdf
colcon build --symlink-install --packages-select urdf_demo
ros2 launch urdf_demo rviz_view.launch.py
ros2 launch urdf_demo joint_view.launch.py
ros2 topic info /robot_description --verbose
ros2 topic echo /joint_states --once
ros2 run tf2_ros tf2_echo base_link right_leg
15. 常见问题
15.1 常见问题
check_urdf 报 XML 错误。 检查标签闭合、半角引号和属性拼写,并确认没有把 Markdown 围栏复制进 URDF 文件。
提示找不到子连杆或存在多个根。 检查 parent、child 是否与 link name 完全一致,是否有孤立连杆,或同一子连杆是否被多个关节连接。
RViz2 中 RobotModel 为空。 检查 Fixed Frame、/robot_description、RobotModel 的 Topic 与 QoS,再检查资源是否安装。不要只反复重启 RViz2。
模型出现但位置错误。 分开检查 joint/origin 和 visual/origin。前者放置连杆坐标系,后者放置几何体;注意米、弧度和 Y 轴正负号。
可动关节不动。 检查 /joint_states、关节名称、axis 和 limit。固定关节不会出现在 GUI 滑块中,这是正常行为。
源码修改后效果不变。 确认 Launch 读取的是安装空间文件,重新构建并执行 source ~/ros2_ws/install/setup.bash。













