URDFとMJCFのチートシート
リンク、関節、慣性、形状、制限、アクチュエータ、座標系についてURDFとMuJoCo MJCFの要素を並べて比較し、1つの小さなアームを両形式で記述して、相互変換の落とし穴をまとめます。
このシートは、ある形式のロボットモデルを別の形式に移したい人や、モデルを手で書く人のためのものです。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) の順です。
- 可能な限りプリミティブ形状で衝突判定を行い、詳細なメッシュは表示用にしてください。
関連ページ
- MuJoCoでアームをシミュレーションすると2リンクアームのプロジェクト
- これらのファイルが記述する運動学についてはDHパラメータと指数積
- MuJoCoのプラットフォームページとシミュレーションのハブ
- MuJoCoモデリングガイド(URDF拡張に関する節を含む)