2. XML 模型基础

好,咱们正式开始动手了。上一章聊了 MuJoCo 能干什么,这一章咱们直接扎进它的「骨架」——MJCF 格式。

说白了,MJCF 就是 MuJoCo 用来描述机器人世界的 XML 语言。你给它一个模型文件,它就知道:这个机器人长什么样、关节在哪、能怎么动、跟地面怎么摩擦。我刚开始接触 MuJoCo 时,第一反应是「又学一种新格式?」,但用顺手之后发现,这玩意儿比 URDF 清爽太多了。

核心观点:MJCF 不是简单的「数据描述」,它是 MuJoCo 的「世界观」。你写对了,仿真就稳;你写错了,仿真就飘。

2.1 MJCF 文件结构概览

一个标准的 MJCF 文件,长这样:

<mujoco model="my_robot">
  <compiler angle="radian" />
  <option gravity="0 0 -9.81" />
  <worldbody>
    <body name="base" pos="0 0 0">
      <geom type="box" size="0.1 0.1 0.05" />
      <joint name="base_joint" type="free" />
    </body>
  </worldbody>
</mujoco>

嗯,看着眼熟吧?根节点是 <mujoco>,里面包着编译器设置、物理参数、还有最重要的 <worldbody>。我个人习惯把 <worldbody> 想象成「舞台」,所有机器人零件都摆在这个舞台上。

2.2 四大核心元素:body、geom、joint、site

这四个元素,你写任何模型都绕不开。咱们一个一个拆。

2.2.1 body:机器人的「骨头」

<body> 代表一个刚体。它有位置、有姿态、可以挂子节点。你可以把它理解成机器人的「一块骨头」。

<body name="upper_arm" pos="0 0.2 0" euler="0 0 0">
  <!-- 子元素放这里 -->
</body>

注意,pos 是相对于父节点的偏移。我见过不少新手把 pos 写成世界坐标,结果模型飞出去老远。记住:body 的位置是相对的。

2.2.2 geom:机器人的「皮肉」

<geom> 定义几何形状。MuJoCo 支持 box、sphere、cylinder、capsule、mesh 等类型。它负责碰撞检测和视觉显示。

<geom type="cylinder" size="0.05 0.2" rgba="0.8 0.2 0.2 1" />

这里 size 的含义因类型而异:圆柱体是半径和高度,长方体是半边长。我踩过最大的坑就是——把 box 的 size 写成了全长,结果模型比预期大了一倍。

避坑指南:MuJoCo 的 size 参数,对于 box 类型是「半边长」。比如你要一个 0.2m 的立方体,size 要写 0.1 0.1 0.1,不是 0.2。

2.2.3 joint:机器人的「关节」

<joint> 定义自由度。没有 joint 的 body 就是一块死铁。joint 的类型有:

类型 说明 自由度 典型用途
free 自由浮动 6 (3平移+3旋转) 基座、无人机
ball 球铰 3 (旋转) 肩关节
hinge 旋转铰 1 肘关节、膝关节
slide 滑动副 1 直线导轨
<joint name="elbow" type="hinge" axis="0 0 1" range="-2.09 2.09" />

axis 是旋转轴,range 是角度限制(弧度)。我习惯把 hinge 关节的 axis 写成单位向量,这样不容易搞混方向。

2.2.4 site:机器人的「标记点」

<site> 是个轻量级元素。它不参与碰撞,只用来标记位置。比如你想知道「夹爪末端在哪」,就放一个 site。

<site name="grip_point" pos="0 0 0.15" size="0.01" />

site 在传感器、视觉渲染、IK 解算里特别有用。我个人习惯在每个机械臂的末端都加一个 site,方便调试。

2.3 从零搭建一个简单的机械臂

光说不练假把式。咱们搭一个 2 自由度的平面机械臂。它有两个连杆、两个旋转关节。

先画个结构图,帮你理清思路:

2-DOF 平面机械臂结构图 基座 连杆1 J1 连杆2 J2 末端 J1 控制连杆1旋转,J2 控制连杆2相对连杆1旋转

好,现在写 MJCF:

<mujoco model="simple_arm_2dof">
  <compiler angle="radian" />
  <option gravity="0 0 -9.81" />

  <worldbody>
    <!-- 地面 -->
    <geom name="floor" type="plane" size="1 1 0.1" rgba="0.8 0.8 0.8 1" />

    <!-- 基座 -->
    <body name="base" pos="0 0 0">
      <geom name="base_geom" type="box" size="0.08 0.08 0.04" rgba="0.2 0.4 0.8 1" />
      <joint name="base_joint" type="free" />

      <!-- 连杆1 -->
      <body name="link1" pos="0 0 0.04">
        <geom name="link1_geom" type="box" size="0.03 0.03 0.15" rgba="0.2 0.8 0.4 1" />
        <joint name="joint1" type="hinge" axis="0 0 1" pos="0 0 0" range="-2.5 2.5" />

        <!-- 连杆2 -->
        <body name="link2" pos="0 0 0.3">
          <geom name="link2_geom" type="box" size="0.03 0.03 0.15" rgba="0.8 0.2 0.4 1" />
          <joint name="joint2" type="hinge" axis="0 0 1" pos="0 0 0" range="-2.5 2.5" />

          <!-- 末端标记 -->
          <site name="tip" pos="0 0 0.15" />
        </body>
      </body>
    </body>
  </worldbody>
</mujoco>

小技巧:每个 body 的 pos 都是相对于父 body 的。link1 的 pos 是 0 0 0.04,意思是「在基座上方 0.04m 处」。link2 的 pos 是 0 0 0.3,意思是「在 link1 顶端上方 0.3m 处」。这样一层层叠上去,就搭出了机械臂。

2.4 模型调试小技巧

写完模型怎么验证?我一般用 MuJoCo 自带的 simulate 工具。命令行跑一下:

python -m mujoco.viewer --mjcf=simple_arm_2dof.xml

如果模型加载成功,你会看到一个 2 自由度的机械臂立在地面上。拖拽关节,看看运动对不对。

我曾经犯过一个低级错误——把 joint 的 axis 写成了 0 1 0,结果机械臂在水平面里乱转,完全不像「上下抬举」。后来检查才发现,轴方向搞反了。所以,写完模型一定要可视化验证,别偷懒。

2.5 本章小结

这一章咱们干了三件事:

  • 搞懂了 MJCF 的基本结构
  • 吃透了 body、geom、joint、site 四个核心元素
  • 从零搭了一个 2-DOF 机械臂模型