2. URDF核心标签详解:<link>、<visual>、<collision> 与 <inertial>

上一章我们搭好了 URDF 的基本骨架,知道了 link 和 joint 是机器人的「骨头」和「关节」。但光有骨头可不够——你得告诉 ROS:这个零件长什么样?撞到东西怎么办?重心在哪?

说白了,<link> 标签才是真正定义「一个刚体」全部物理属性的地方。我刚开始学的时候,以为随便写个 box 就能跑,结果一仿真机器人直接飞了……嗯,后来才明白,惯性参数没配好,物理引擎会给你「惊喜」。

URDF <link> 核心结构 <link> <visual> <collision> <inertial> <geometry> <origin> <material> <geometry> <origin> <material> <mass> <inertia> <origin>

2.1 <link> 标签——机器人的「骨头」

每个 link 代表一个刚体。你可以把它想象成一块铁疙瘩——不会变形,不会自己动。在 URDF 里,link 必须有一个名字,而且名字要唯一。

<link name="base_link">
    <!-- 这里放 visual, collision, inertial -->
</link>

我个人习惯把 base_link 作为机器人的根坐标系。你想想看,如果连根都没有,后面的关节往哪挂?

2.2 <visual> 属性——让机器人「看得见」

<visual> 定义的是「看起来什么样」。它不影响物理碰撞,只影响显示。说白了就是给机器人穿衣服。

<link name="arm_link">
    <visual>
        <origin xyz="0 0 0.5" rpy="0 0 0"/>
        <geometry>
            <cylinder length="1.0" radius="0.1"/>
        </geometry>
        <material name="blue">
            <color rgba="0 0 1 1"/>
        </material>
    </visual>
</link>

这里有个坑:<origin> 的 rpy 是绕固定轴旋转的,顺序是 Z-Y-X。我在项目里见过有人把 rpy 写反了,结果机械臂在 Rviz 里扭成了麻花……

💡 我的经验: 几何体尽量用 cylinder 和 box,mesh 文件虽然好看,但会拖慢加载速度。调试阶段用简单几何体,最后再换 mesh。

2.3 <collision> 属性——让机器人「有边界」

<collision> 是给碰撞检测用的。它和 <visual> 结构几乎一样,但几何体可以更简化。为什么?因为碰撞检测算的是「有没有碰到」,不需要精细到每个螺丝孔。

<collision>
    <origin xyz="0 0 0.5" rpy="0 0 0"/>
    <geometry>
        <cylinder length="1.0" radius="0.12"/>
    </geometry>
</collision>

注意看,我把 radius 从 0.1 加到了 0.12。这是故意的——给碰撞包络留一点余量,防止因为视觉误差导致「看起来没碰到,实际上卡住了」。我曾经在产线上遇到过这种情况,机器人抓取时老是报碰撞,后来发现 collision 和 visual 完全一样,稍微放大一点就好了。

⚠️ 警告: 不要省略 <collision>!如果你不写,MoveIt! 会默认用 <visual> 的几何体做碰撞检测,但那样计算量会翻倍,而且容易出 bug。

2.4 <inertial> 惯性参数配置——让机器人「有重量」

这是最容易忽略、也最容易出问题的地方。没有惯性参数,机器人就像纸糊的一样——仿真里一碰就飞,或者根本动不了。

<inertial>
    <mass value="2.0"/>
    <inertia ixx="0.01" ixy="0.0" ixz="0.0"
             iyy="0.01" iyz="0.0"
             izz="0.01"/>
    <origin xyz="0 0 0.25" rpy="0 0 0"/>
</inertial>

这里有几个关键点:

  • mass:质量,单位 kg。别写太大也别写太小,真实机器人多少就写多少。
  • inertia:惯性张量,6 个独立分量(ixx, ixy, ixz, iyy, iyz, izz)。对于简单几何体,可以用公式估算。
  • origin:质心位置。默认是 link 坐标系原点,但实际质心往往不在那里。

你可能会问:「惯性张量怎么算?」嗯,对于圆柱体,绕轴线的转动惯量是 ½ m r²,垂直轴线的是 ¼ m r² + 1/12 m l²。我一般写个小脚本算,或者直接用 SolidWorks 导出。

🔧 实战小贴士: 如果你不确定惯性参数,可以先给一个近似值。但一定要保证惯性张量是正定的(所有对角线元素 > 0,且满足三角不等式)。否则 Gazebo 会直接崩溃,别问我怎么知道的……

2.5 完整示例:一个带视觉、碰撞和惯性的 link

<link name="shoulder_link">
    <visual>
        <origin xyz="0 0 0.3" rpy="0 0 0"/>
        <geometry>
            <box size="0.2 0.2 0.6"/>
        </geometry>
        <material name="red">
            <color rgba="0.8 0.2 0.1 1"/>
        </material>
    </visual>
    <collision>
        <origin xyz="0 0 0.3" rpy="0 0 0"/>
        <geometry>
            <box size="0.22 0.22 0.62"/>
        </geometry>
    </collision>
    <inertial>
        <mass value="3.5"/>
        <inertia ixx="0.05" ixy="0.0" ixz="0.0"
                 iyy="0.05" iyz="0.0"
                 izz="0.02"/>
        <origin xyz="0 0 0.3" rpy="0 0 0"/>
    </inertial>
</link>

这个 shoulder_link 是一个长方体,视觉尺寸 0.2×0.2×0.6,碰撞稍微大一圈,质量 3.5kg,质心在几何中心。你看,其实没那么复杂。

2.6 常见错误与避坑

  • 忘记写 <inertial>:ROS 不会报错,但 Gazebo 会直接忽略这个 link,导致仿真异常。
  • 惯性张量全零:物理引擎会除零崩溃。我见过有人从网上复制代码,ixx=0 没改,结果一运行就段错误。
  • collision 比 visual 小:这会导致「视觉上没碰到,但碰撞检测已经触发了」,反过来也可能漏检。建议 collision 比 visual 大 5%~10%。
  • origin 的 rpy 乱写:尤其是旋转顺序搞错。记住:URDF 里 rpy 是绕固定轴 Z-Y-X 旋转。
📌 检查方法: 用 check_urdf 命令可以快速验证语法。再用 urdf_to_graphiz 生成结构图,一眼就能看出 link 和 joint 的关系对不对。

好了,这一章的内容就是这些。<link> 标签虽然基础,但它是整个 URDF 的基石。下一章我们会聊关节——让这些刚体真正动起来。