チートシート

URDFとMJCFのチートシート

リンク、関節、慣性、形状、制限、アクチュエータ、座標系についてURDFとMuJoCo MJCFの要素を並べて比較し、1つの小さなアームを両形式で記述して、相互変換の落とし穴をまとめます。

ROS 2MuJoCo最終確認 2026年11月14日· URDF as specified on the ROS wiki, MuJoCo 3.16 (checked against the 4.0 XML reference)

このシートは、ある形式のロボットモデルを別の形式に移したい人や、モデルを手で書く人のためのものです。ROSのロボット記述形式であるURDFの要素を、MuJoCo独自の形式であるMJCFに対応付けます。続いて、1つの小さなアームを両方の形式で示し、変換で変わる点を挙げます。要素名は2026年11月14日にROS URDF仕様とMuJoCo XMLリファレンスで確認しました。最初のモデルを手順に沿って作るには、MuJoCoでアームをシミュレーションするを参照してください。

要素の対応表

概念 URDF MJCF 注意点
ルート <robot name> <mujoco model>、ボディは<worldbody>の中 MJCFはオプション、デフォルト、アセット、アクチュエータ、センサも保持する
剛体 <link>、フラットなリスト <body>、入れ子 URDFは関節からツリーを構築する。MJCFは入れ子構造から構築する
関節 親リンクと子リンクの間の<joint> 子ボディ内の<joint> 関節を持たないボディは親に溶接(固定)される
関節の種類 revolute、continuous、prismatic、fixed、floating、planar hinge、slide、ball、free revoluteとcontinuousは、rangeの有無で区別したhingeになる。fixedは関節なしになる。floatingはfreeになる
関節軸 <axis xyz>、既定値 (1, 0, 0)、関節座標系で表す axis、既定値 (0, 0, 1)、ボディ座標系で表す 既定値が異なる。軸は必ず明示的に書く
関節の配置 関節の<origin xyz rpy>:親リンク座標系から子リンク座標系へ 子<body>のposとquat(またはeuler、axisangle) URDFのrpyは固定軸 x, y, z の順。MJCFではeulerseq="XYZ"に相当
位置の制限 <limit lower upper>、revoluteとprismaticでは必須 関節のrange。autolimits="true"(既定値)ではrangeを指定すると制限が有効になる <compiler angle="radian"/>がなければ、MJCFはhingeのrangeを度として読む
力と速度の制限 <limit effort velocity>、両方とも必須 力:関節のactuatorfrcrangeまたはアクチュエータのforcerange。関節速度の制限はない MuJoCoはURDFを読み込む際、URDFのeffortをactuatorfrcrangeに変換する
質量と慣性 <inertial>:<origin>、<mass value>、<inertia ixx ixy ixz iyy iyz izz> <inertial pos mass diaginertia>またはfullinertia(Ixx, Iyy, Izz, Ixy, Ixz, Iyz) 6つの数値の順序が異なる。<inertial>がない場合、MuJoCoはgeomから計算する
形状 <box size>は全長、<cylinder radius length>、<sphere radius>、<mesh> <geom type size>:boxのサイズは半分の長さ、cylinderとcapsuleは半径と半分の長さ、またはfromto MJCFに移すときは、boxとcylinderの長さをすべて半分にする
表示用と衝突用 <visual>と<collision>を別々に記述 geomは既定で衝突判定と描画の両方に使われる。表示専用のgeomにはcontype="0" conaffinity="0"を設定 MuJoCoは既定でURDFのvisualを破棄する(URDFではdiscardvisualがtrue)
関節の動力学 <dynamics damping friction> 関節のdamping、frictionloss、armature armature(出力軸換算のロータ慣性)にURDFの対応要素はない
アクチュエータ <actuator>と<mechanicalReduction>を含む<transmission>。ros2_controlでは<ros2_control>タグを使う <actuator>セクション:motor、position、velocity、generalなど。gear、ctrlrange、forcerangeを指定 URDFにはアクチュエータの動特性がない。アクチュエータモデルは利用者が決める
ツール座標系・センサ座標系 fixed関節につながる質量ゼロのリンク ボディ内の<site> MuJoCoはURDFを読み込む際、既定で固定関節のリンクを親に統合する(fusestatic)
センサ <sensor>(カメラ、レイ)はROS wikiで規定されている <sensor>セクション:jointpos、gyro、accelerometer、force、touch、rangefinderなど
接触 なし。すべての衝突ペアは利用側で判定する 親ボディと子ボディの組は除外される。ただし親がワールドに溶接されている場合は除外されない。その他のペアは<contact><exclude> 下のMJCFのexcludeを参照
単位 SI:メートル、キログラム、ラジアン 固定の単位はない。角度は既定で度 URDFに合わせるにはangle="radian"を設定し、SIを使う

1つのアーム、2つの形式

固定ベースの上に回転関節が2つ(肩とひじ)あり、どちらもy軸まわりに回転します。上腕は長さ 0.30 m、質量 1.0 kg です。前腕は長さ 0.25 m、質量 0.6 kg です。慣性は、それぞれの中心まわりの中実円柱の値です。ツール座標系は前腕の先端にあります。

URDF

<?xml version="1.0"?>
<robot name="two_link_arm">
  <link name="base_link">
    <visual>
      <origin xyz="0 0 0.05" rpy="0 0 0"/>
      <geometry><box size="0.2 0.2 0.1"/></geometry>
    </visual>
    <collision>
      <origin xyz="0 0 0.05" rpy="0 0 0"/>
      <geometry><box size="0.2 0.2 0.1"/></geometry>
    </collision>
    <inertial>
      <origin xyz="0 0 0.05" rpy="0 0 0"/>
      <mass value="2.0"/>
      <inertia ixx="0.008333" ixy="0" ixz="0" iyy="0.008333" iyz="0" izz="0.013333"/>
    </inertial>
  </link>

  <joint name="shoulder" type="revolute">
    <parent link="base_link"/>
    <child link="upper_arm"/>
    <origin xyz="0 0 0.1" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
    <limit lower="-1.57" upper="1.57" effort="20" velocity="2.0"/>
    <dynamics damping="0.5" friction="0.0"/>
  </joint>

  <link name="upper_arm">
    <visual>
      <origin xyz="0 0 0.15" rpy="0 0 0"/>
      <geometry><cylinder radius="0.03" length="0.3"/></geometry>
    </visual>
    <collision>
      <origin xyz="0 0 0.15" rpy="0 0 0"/>
      <geometry><cylinder radius="0.03" length="0.3"/></geometry>
    </collision>
    <inertial>
      <origin xyz="0 0 0.15" rpy="0 0 0"/>
      <mass value="1.0"/>
      <inertia ixx="0.007725" ixy="0" ixz="0" iyy="0.007725" iyz="0" izz="0.00045"/>
    </inertial>
  </link>

  <joint name="elbow" type="revolute">
    <parent link="upper_arm"/>
    <child link="forearm"/>
    <origin xyz="0 0 0.3" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
    <limit lower="-2.5" upper="2.5" effort="10" velocity="3.0"/>
    <dynamics damping="0.3" friction="0.0"/>
  </joint>

  <link name="forearm">
    <visual>
      <origin xyz="0 0 0.125" rpy="0 0 0"/>
      <geometry><cylinder radius="0.025" length="0.25"/></geometry>
    </visual>
    <collision>
      <origin xyz="0 0 0.125" rpy="0 0 0"/>
      <geometry><cylinder radius="0.025" length="0.25"/></geometry>
    </collision>
    <inertial>
      <origin xyz="0 0 0.125" rpy="0 0 0"/>
      <mass value="0.6"/>
      <inertia ixx="0.003219" ixy="0" ixz="0" iyy="0.003219" iyz="0" izz="0.000188"/>
    </inertial>
  </link>

  <joint name="tool_joint" type="fixed">
    <parent link="forearm"/>
    <child link="tool0"/>
    <origin xyz="0 0 0.25" rpy="0 0 0"/>
  </joint>
  <link name="tool0"/>

  <transmission name="shoulder_trans">
    <type>transmission_interface/SimpleTransmission</type>
    <joint name="shoulder">
      <hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>
    </joint>
    <actuator name="shoulder_motor">
      <mechanicalReduction>100</mechanicalReduction>
    </actuator>
  </transmission>

  <transmission name="elbow_trans">
    <type>transmission_interface/SimpleTransmission</type>
    <joint name="elbow">
      <hardwareInterface>hardware_interface/EffortJointInterface</hardwareInterface>
    </joint>
    <actuator name="elbow_motor">
      <mechanicalReduction>100</mechanicalReduction>
    </actuator>
  </transmission>
</robot>

MJCF

<mujoco model="two_link_arm">
  <compiler angle="radian" autolimits="true"/>
  <option timestep="0.002" gravity="0 0 -9.81"/>
  <default>
    <joint type="hinge" axis="0 1 0"/>
  </default>
  <worldbody>
    <geom name="floor" type="plane" size="1 1 0.1"/>
    <body name="base_link">
      <inertial pos="0 0 0.05" mass="2.0" diaginertia="0.008333 0.008333 0.013333"/>
      <geom type="box" pos="0 0 0.05" size="0.1 0.1 0.05"/>
      <body name="upper_arm" pos="0 0 0.1">
        <joint name="shoulder" range="-1.57 1.57" damping="0.5" armature="0.01"/>
        <inertial pos="0 0 0.15" mass="1.0" diaginertia="0.007725 0.007725 0.00045"/>
        <geom type="cylinder" fromto="0 0 0 0 0 0.3" size="0.03"/>
        <body name="forearm" pos="0 0 0.3">
          <joint name="elbow" range="-2.5 2.5" damping="0.3" armature="0.005"/>
          <inertial pos="0 0 0.125" mass="0.6" diaginertia="0.003219 0.003219 0.000188"/>
          <geom type="cylinder" fromto="0 0 0 0 0 0.25" size="0.025"/>
          <site name="tool0" pos="0 0 0.25"/>
        </body>
      </body>
    </body>
  </worldbody>
  <contact>
    <exclude body1="base_link" body2="upper_arm"/>
  </contact>
  <actuator>
    <position name="shoulder_pos" joint="shoulder" kp="50" kv="2" ctrlrange="-1.57 1.57" forcerange="-20 20"/>
    <position name="elbow_pos" joint="elbow" kp="30" kv="1" ctrlrange="-2.5 2.5" forcerange="-10 10"/>
  </actuator>
</mujoco>

1行ずつ比べると、違いは次のとおりです。MJCFのboxとcylinderのサイズは半分の長さで指定するか、fromtoで与えます。ベースのboxはpos="0 0 0.05"に置かれ、半分のサイズは 0.05 です。関節軸は<default>クラスで一度だけ書いています。<compiler angle="radian"/>があるため、制限値はラジアンです。ベースと上腕は溶接された親子なので、MuJoCoはこの2つの衝突を許してしまいます。<exclude>がそれを防ぎます。これがないと、肩はベースに押し付けられたまま動けず、目標に到達しません。

MJCFを読み込んでステップ実行する

このコードはモデルを読み込み、ツール位置を手で書いた順運動学と照合します。続いて、位置アクチュエータで両関節を目標へ動かします。

import numpy as np
import mujoco

MJCF = """
<mujoco model="two_link_arm">
  <compiler angle="radian" autolimits="true"/>
  <option timestep="0.002" gravity="0 0 -9.81"/>
  <default>
    <joint type="hinge" axis="0 1 0"/>
  </default>
  <worldbody>
    <geom name="floor" type="plane" size="1 1 0.1"/>
    <body name="base_link">
      <inertial pos="0 0 0.05" mass="2.0" diaginertia="0.008333 0.008333 0.013333"/>
      <geom type="box" pos="0 0 0.05" size="0.1 0.1 0.05"/>
      <body name="upper_arm" pos="0 0 0.1">
        <joint name="shoulder" range="-1.57 1.57" damping="0.5" armature="0.01"/>
        <inertial pos="0 0 0.15" mass="1.0" diaginertia="0.007725 0.007725 0.00045"/>
        <geom type="cylinder" fromto="0 0 0 0 0 0.3" size="0.03"/>
        <body name="forearm" pos="0 0 0.3">
          <joint name="elbow" range="-2.5 2.5" damping="0.3" armature="0.005"/>
          <inertial pos="0 0 0.125" mass="0.6" diaginertia="0.003219 0.003219 0.000188"/>
          <geom type="cylinder" fromto="0 0 0 0 0 0.25" size="0.025"/>
          <site name="tool0" pos="0 0 0.25"/>
        </body>
      </body>
    </body>
  </worldbody>
  <contact>
    <exclude body1="base_link" body2="upper_arm"/>
  </contact>
  <actuator>
    <position name="shoulder_pos" joint="shoulder" kp="50" kv="2" ctrlrange="-1.57 1.57" forcerange="-20 20"/>
    <position name="elbow_pos" joint="elbow" kp="30" kv="1" ctrlrange="-2.5 2.5" forcerange="-10 10"/>
  </actuator>
</mujoco>
"""

model = mujoco.MjModel.from_xml_string(MJCF)
data = mujoco.MjData(model)
data.qpos[:] = [0.5, -0.8]                       # shoulder, elbow (rad)
mujoco.mj_kinematics(model, data)
q1, q2 = data.qpos
analytic = np.array([0.3 * np.sin(q1) + 0.25 * np.sin(q1 + q2), 0.0,
                     0.1 + 0.3 * np.cos(q1) + 0.25 * np.cos(q1 + q2)])
print("tool0 from MuJoCo:", np.round(data.site("tool0").xpos, 4))
print("tool0 analytic:   ", np.round(analytic, 4))

mujoco.mj_resetData(model, data)
data.ctrl[:] = [0.8, -1.2]                       # position targets (rad)
for _ in range(int(3.0 / model.opt.timestep)):
    mujoco.mj_step(model, data)
print("joint angles after 3 s:", np.round(data.qpos, 3), "(gravity pulls them off target)")
print("total mass (kg):", round(float(model.body_mass.sum()), 3))

P制御のみの位置サーボでは、重力の影響で定常偏差が残ります。目標 0.8 rad に対して、肩は 0.843 rad で落ち着きます。追従性が重要な場合は、kpを上げるか、kvを加えるか、重力補償(最近のMuJoCoでは関節のactuatorgravcomp)を加えてください。

変換

方向 方法 変換後に確認すること
URDFからMuJoCo mujoco.MjModel.from_xml_path("arm.urdf")でURDFを直接読み込める。メッシュのパスやオプションの指定には、URDFに子要素<mujoco><compiler .../></mujoco>を追加する。mujoco.mj_saveLastXMLで保存するとMJCFが得られる fusestaticで統合されて消えたボディ、削除されたvisual、アクチュエータがないこと(追加する)、actuatorfrcrangeに移った関節のeffort、接触の除外設定
MJCFからURDF MuJoCoのドキュメントはURDFの読み込みを説明しているが、書き出しは説明していない。URDFは手で書くか、サードパーティ製ツールを使う ボールジョイントとテンドンにはURDFでの表現がない。free関節はfloatingになる。関節の等式拘束は<mimic>に一部しか対応しない。siteは固定関節のリンクになる
xacroからURDF ros2 run xacro xacro robot.urdf.xacro > robot.urdf どこかで読み込む前に、ROS 2 Jazzyに同梱されているcheck_urdfで結果を検査する

当サイトで上記のURDFをMuJoCo 3.16で読み込んだところ、base_linkはワールドボディに統合されました。visualのgeomは削除され(geomは3つ残る)、減衰の値は引き継がれ、関節のeffortはそれぞれ ±20 N m と ±10 N m の力の範囲になりました。

落とし穴

  • URDFの仕様では、revoluteとprismaticの関節に<limit>が必須です。ただし、すべてのパーサがこれを強制するわけではありません。urdf_parser_pyは<limit>のない回転関節を受け付けるので、ファイルは各自で確認してください。
  • 慣性は重心まわりの値で、正定値でなければなりません。動く関節の下に質量ゼロで慣性のないリンクがあると、シミュレータが不安定になります。
  • URDFの関節の<origin>は、子リンク座標系の位置と姿勢を決めます。visual、collision、inertialの<origin>タグは、リンク内でのオフセットです。これらを混同すると、質量は移動しても形状は移動しません。
  • MJCFのeulerはコンパイラのeulerseqに従います。既定値は動軸(intrinsic)のxyzで、URDFの固定軸の順序とは異なります。回転の表現を参照してください。
  • MJCFの四元数は (w, x, y, z) の順です。
  • 可能な限りプリミティブ形状で衝突判定を行い、詳細なメッシュは表示用にしてください。

関連ページ

誤りやバージョン変更に気づいたら 訂正の手順をご覧ください。