2. URDF基础标签:<robot>、<link>、<joint>
写URDF就像搭乐高。你得先有零件(link),再用关节(joint)把它们拼起来,最后用<robot>把整个模型包住。我刚开始接触ROS时,觉得这玩意儿不就是个XML嘛,后来踩了不少坑才明白——每个标签的细节都藏着设计哲学。
这一章,咱们就把最核心的三个标签掰开揉碎。你想想看,一个机器人模型动起来,全靠它们配合。我会把我在项目里摔过的跟头、总结的窍门都抖出来。
2.1 <robot> —— 模型的根
说白了,<robot>就是整个URDF文件的根元素。所有link和joint都得包在里面。它有两个常用属性:name(必填)和 xmlns(一般写死)。
<robot name="my_robot" xmlns:xacro="http://www.ros.org/wiki/xacro">
<link name="base_link" />
<joint name="base_to_wheel" type="continuous"> ... </joint>
</robot>
💡 我的习惯: name 用蛇形命名,比如
two_wheel_robot。别用空格或驼峰,后面写launch文件时容易出幺蛾子。
2.2 <link> —— 机器人的骨头
每个link代表一个刚体零件。你可以给它加视觉、碰撞和惯性属性。我见过新手只写一个空link,结果仿真时物理引擎直接崩了——因为缺少惯性数据。
一个典型的link长这样:
<link name="base_link">
<visual>
<geometry>
<box size="0.4 0.3 0.1" />
</geometry>
<origin xyz="0 0 0" rpy="0 0 0" />
<material name="blue">
<color rgba="0 0.4 0.8 1" />
</material>
</visual>
<collision>
<geometry>
<box size="0.4 0.3 0.1" />
</geometry>
</collision>
<inertial>
<mass value="2.0" />
<inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.01" />
</inertial>
</link>
| 子标签 | 作用 | 是否必填 |
|---|---|---|
| visual | 可视化外观,显示在Rviz里 | 推荐 |
| collision | 碰撞检测模型,通常比visual简单 | 推荐 |
| inertial | 惯性参数,质量+惯性张量 | 物理仿真必填 |
⚠️ 我曾经踩过的坑: 把collision做得和visual一样精细,结果Gazebo卡成PPT。后来我学乖了——碰撞模型用基本几何体或者简化网格,能省下大量计算资源。
2.3 <joint> —— 让零件动起来
joint连接两个link,并定义它们的相对运动。URDF支持6种关节类型,但最常用的是 revolute、continuous 和 fixed。
<joint name="left_wheel_joint" type="continuous">
<parent link="base_link" />
<child link="left_wheel" />
<origin xyz="0.2 -0.15 0" rpy="0 0 0" />
<axis xyz="0 1 0" />
</joint>
关节的核心属性:
- name —— 唯一标识,别重名。
- type —— 关节类型:fixed, revolute, continuous, prismatic, floating, planar。
- parent / child —— 父子link,构成运动树。
- origin —— 子link相对于父link的位姿。
- axis —— 旋转或平移轴(对fixed无效)。
🔧 我个人的经验: 写joint时,origin 的 rpy 很容易搞反。记住:rpy 是子坐标系在父坐标系中的旋转,不是关节旋转角度。关节角度由 joint state 发布器控制。
2.4 三个标签的协作关系
一个机器人模型就是一棵树。<robot>是根,link是节点,joint是树枝。我习惯先画草图,标出每个link的坐标系原点,再写代码。你想想看,如果坐标系对不上,TF树就会乱成一团。
下面这个例子展示了一个简单的两轮底盘:
<robot name="mini_dash">
<link name="chassis">
<visual>
<geometry><box size="0.3 0.2 0.05" /></geometry>
</visual>
</link>
<link name="right_wheel">
<visual>
<geometry><cylinder radius="0.04" length="0.02" /></geometry>
</visual>
</link>
<joint name="right_joint" type="continuous">
<parent link="chassis" />
<child link="right_wheel" />
<origin xyz="0.1 -0.12 0" />
<axis xyz="0 1 0" />
</joint>
</robot>
嗯,这里要注意:joint的 origin 是子link坐标系的原点相对父link的位置。我见过有人把轮子的位置写在link的visual里,结果关节一动,轮子飞出去了——因为joint的origin才是真正的连接点。
📌 避坑指南: 如果你用
fixed 关节连接两个link,记得检查是否真的需要两个link。有时候一个link加两个visual更省事。但为了TF树清晰,我通常还是分成两个link。