Robot Description Formats Compared: URDF vs SDF vs MJCF vs OpenUSD for Digital Twins

Robot Description Formats Compared: URDF vs SDF vs MJCF vs OpenUSD for Digital Twins

URDF vs SDF vs MJCF vs OpenUSD: Robot Description Formats for Digital Twins

Every robot simulation starts with a file that says what the robot is, and that file quietly decides what the simulation can ever know. The URDF vs SDF question is the one most teams ask first, but it is only half the map: MJCF and OpenUSD now sit alongside them, and each was designed to answer a different question. URDF describes a kinematic tree for ROS. SDF describes a world for Gazebo. MJCF describes a contact-rich dynamical system for MuJoCo. OpenUSD describes a scene that many tools, and many physics engines, can share.

This matters now because the center of gravity is moving. GPU-parallel simulators, the Newton physics engine under the Linux Foundation, and Isaac Sim’s USD-first pipeline mean the “authoritative” robot description is increasingly a scene graph, not a ROS-era XML file. Choosing wrong means a conversion step that silently loses inertia, sensors, or closed-loop constraints.

You will leave with a precise account of what each format can and cannot express, the same two-link arm written in all four, the converter paths that exist, and a decision matrix.

What this covers: the design intent of each format, a same-robot code comparison, converter fidelity and loss points, the state of Newton and MuJoCo Warp, failure modes, and a practical recommendation for a digital twin pipeline.

Context and Background

A robot description format is a data model plus a serialization. The data model answers a short list of questions. What are the rigid bodies and their mass properties? How are they connected, and with which joint types and limits? What do they look like, and what do they collide with? What sensors and actuators are attached? What does the surrounding world contain? Formats differ mostly in how many of those questions they answer natively and how many they push into extensions or into the simulator itself.

The incumbent is the Unified Robot Description Format, URDF, born in the ROS ecosystem. It is an XML schema built around <link> and <joint> elements and it is the lingua franca of ROS tooling: robot_state_publisher, MoveIt, RViz, and most vendor-supplied robot packages ship one. Its strength is ubiquity. Its weakness is that it was scoped to describe a single robot as a tree, not a world, not a closed mechanism, and not a sensor suite.

The Simulation Description Format, SDFormat or SDF, came out of the Gazebo (formerly Gazebo Classic, then Ignition) project to fix those gaps. The specification at sdformat.org documents a root hierarchy of world, model, actor, and light elements, with a published specification version of 1.12 among the revisions. SDF is therefore a superset in intent: it can describe an entire simulated world including physics settings and plugins, not only one robot.

MJCF, the MuJoCo XML format, was designed for a different priority. MuJoCo is a physics engine built for model-based control and reinforcement learning, so MJCF exposes the physics: contact parameters, tendons, equality constraints, actuator dynamics, and solver options sit in the file as first-class citizens. The MuJoCo XML reference lists top-level sections for options, assets, the body tree, actuators, sensors, contact pairs, equality constraints, tendons, and keyframes.

OpenUSD, Pixar’s Universal Scene Description, comes from film and visual effects. It is a scene graph with layered composition, variants, and references, and it is not robotics-specific. Physics arrives through the UsdPhysics schema, which defines rigid bodies, collision shapes, materials, mass properties, a set of joint types, and an articulation root. NVIDIA built Isaac Sim around it, and the Newton engine’s announcement describes building on OpenUSD for modeling robots and environments.

The reason this comparison is worth a full post is that the four formats are not interchangeable representations of one truth. They are lossy projections of a richer robot model, and each projection drops different information. A digital twin that must stay synchronized with a real machine needs to know exactly what is being dropped. For the broader question of when a model is a simulation and when it is a twin, see our analysis of digital twin vs simulation architecture decisions. The authoritative primary references for this post are the MuJoCo XML reference and the SDFormat specification.

Why a format is a commitment

Choosing a description format is closer to choosing a database schema than choosing a file extension. Downstream tools bind to it. Controllers reference joint names. Perception pipelines reference sensor frames. Reinforcement learning environments reference body indices. Changing the format after those bindings exist means re-validating every one of them, which is why teams that start with whatever the vendor shipped often end up maintaining a fragile converter for years.

The Reference Model: What a Robot Description Must Carry

A robot description format must carry five layers: kinematics (bodies and joints), dynamics (mass, inertia, damping, actuators), geometry (visual and collision), sensing, and the environment. URDF covers the first three partly, SDF covers all five, MJCF covers dynamics deepest, and OpenUSD covers geometry and scene composition deepest.

Four robot description formats mapped onto the five layers a digital twin needs: URDF vs SDF vs MJCF vs OpenUSD

Figure 1: Coverage of the five description layers by each format. Filled coverage is native; extensions and simulator-specific plugins are discussed in the text.

Figure 1 reads as a coverage map. The key observation is that no format is complete, and the gaps are in different places. Treat the figure as a qualitative summary of native schema support, not a score: a format can express a layer through vendor extensions and still be a poor carrier for it.

Layer one and two: kinematics and dynamics

Kinematics is the topology and joint geometry. URDF encodes a tree: every link except the root has exactly one parent joint. This is the single most consequential design decision in URDF, because it makes the format trivially easy to parse and to feed to forward-kinematics libraries, and it makes closed kinematic loops inexpressible in the base schema. A parallel gripper, a four-bar linkage, a delta robot, or a Stewart platform cannot be written as a pure URDF tree. Practitioners work around it by cutting the loop and adding a simulator-specific closing constraint, which immediately couples the file to one simulator.

SDF relaxes this. A model can contain joints that connect links into loops, and the specification’s frame semantics let poses be expressed relative to named frames rather than only to a parent link. That second feature matters more than it sounds: in URDF every origin is relative to the parent joint frame, which makes hand-editing and offsetting error-prone, while SDF lets you say “this sensor sits 10 cm along the x axis of that frame”.

MJCF also uses a body tree, nested <body> elements with <joint> children, but closes loops through <equality> constraints such as connect and weld. This is a different philosophy: the topology is a tree, and loop closure is a soft or hard constraint the solver enforces. For control and learning this is attractive because the constraint solver is the same machinery used for contacts.

OpenUSD represents joints as prims in the schema, UsdPhysics provides fixed, revolute, prismatic, spherical, and distance joints with limits and drives, and the articulation root API marks a subtree for reduced-coordinate simulation. Because joints are relationships between prims rather than nesting, a loop is expressible. Whether a given solver honors it is a separate matter.

Dynamics is where the formats diverge most. URDF carries mass, a center of mass offset, and a 3×3 inertia tensor per link, plus joint damping, friction, effort and velocity limits. It does not carry contact stiffness, solver parameters, or actuator models beyond a coarse transmission block. SDF carries inertials, surface friction, and per-world physics configuration. MJCF can derive inertia automatically from geometry through compiler options, and exposes actuator types such as motors, position servos, and muscles. UsdPhysics carries mass properties and material friction and restitution, and leaves engine-specific parameters to schema extensions such as PhysX or MuJoCo-specific API schemas.

Layer three: geometry

All four formats separate visual from collision geometry, and all four can reference mesh files. The differences are in what happens next. URDF meshes are referenced by package:// URIs that depend on ROS package resolution, a persistent source of portability bugs. SDF uses model:// URIs and a resource path. MJCF declares meshes in an <asset> block and refers to them by name, with the compiler able to compute inertia from them. OpenUSD references assets through layered composition and can embed or reference meshes, materials, and textures with full physically based shading, which is why it is the format of choice when a twin must also render convincingly.

Layer four and five: sensors and environment

URDF has no native sensor schema. Cameras, lidars, and IMUs are attached as links, and the sensor behavior lives in a <gazebo> extension block or in a ROS driver node. This is a limitation, not a bug: URDF was meant to describe the robot, and the sensor was the driver’s concern. SDF has a native <sensor> element with camera, lidar, IMU, contact, and other types, so a Gazebo model carries its sensing definition with it. MJCF has a <sensor> section for simulated measurements such as joint positions, touch, accelerometers, and force-torque, tied directly to the physics state. UsdPhysics deliberately excludes sensors; the schema documentation describes itself as solver-agnostic and notes it leaves out sensors, fluids, and soft bodies, so sensors in the USD world come from application-level schemas such as those used by Isaac Sim.

For the environment, URDF has nothing: there is no world element. SDF has world natively, and OpenUSD’s scene graph is itself the world. MJCF can place bodies in a <worldbody> and define floors, lights, and cameras, but it is a model file for an engine, not a scene interchange format.

The same dynamics, four dialects

It helps to see the shared content. A single revolute joint with an inertia tensor, a damping coefficient, and a limit exists in all four formats. What changes is the vocabulary: <joint type="revolute"> with <limit lower upper effort velocity> in URDF, <joint type="revolute"> with <axis><limit> children in SDF, <joint type="hinge" range="..."> in MJCF, and a PhysicsRevoluteJoint prim with physics:lowerLimit and physics:upperLimit attributes in USD. That is the part converters can translate faithfully. The loss lives in everything around it.

The fastest way to see the differences is to write the same robot four times. The arm below has a fixed base, a shoulder joint rotating about the z axis, a 0.5 m upper link of 2.0 kg, an elbow joint about the same axis, and a 0.4 m forearm of 1.0 kg. All mass values are illustrative placeholders chosen for the example, not measurements of a real arm. Inertia tensors are computed for thin rods using the standard formula for a solid cylinder approximated as a slender rod.

Pipeline showing a two-link arm authored in URDF and converted to SDF, MJCF and OpenUSD with loss points marked

Figure 2: The conversion graph for the example arm. Edges are labelled with the converter and the information typically lost on that edge.

URDF

URDF is the shortest and the most widely understood. Note that the file declares links and joints only. There is no world, no sensor, and the physics parameters are minimal.

<robot name="two_link_arm">
  <link name="base"/>
  <link name="upper">
    <inertial>
      <origin xyz="0.25 0 0"/>
      <mass value="2.0"/>
      <inertia ixx="0.001" iyy="0.042" izz="0.042" ixy="0" ixz="0" iyz="0"/>
    </inertial>
    <visual>
      <origin xyz="0.25 0 0" rpy="0 1.5708 0"/>
      <geometry><cylinder radius="0.03" length="0.5"/></geometry>
    </visual>
    <collision>
      <origin xyz="0.25 0 0" rpy="0 1.5708 0"/>
      <geometry><cylinder radius="0.03" length="0.5"/></geometry>
    </collision>
  </link>
  <link name="fore">
    <inertial>
      <origin xyz="0.2 0 0"/>
      <mass value="1.0"/>
      <inertia ixx="0.0005" iyy="0.0136" izz="0.0136" ixy="0" ixz="0" iyz="0"/>
    </inertial>
    <visual>
      <origin xyz="0.2 0 0" rpy="0 1.5708 0"/>
      <geometry><cylinder radius="0.025" length="0.4"/></geometry>
    </visual>
  </link>
  <joint name="shoulder" type="revolute">
    <parent link="base"/><child link="upper"/>
    <origin xyz="0 0 0.1"/>
    <axis xyz="0 0 1"/>
    <limit lower="-2.9" upper="2.9" effort="50" velocity="3.0"/>
    <dynamics damping="0.1" friction="0.02"/>
  </joint>
  <joint name="elbow" type="revolute">
    <parent link="upper"/><child link="fore"/>
    <origin xyz="0.5 0 0"/>
    <axis xyz="0 0 1"/>
    <limit lower="-2.5" upper="2.5" effort="30" velocity="4.0"/>
    <dynamics damping="0.05" friction="0.01"/>
  </joint>
</robot>

Three things are worth noticing. The joint origin is relative to the parent link frame, so moving the base requires no edits downstream. The inertial block is optional in the sense that tools will accept a file without it, which is the cause of a large class of “robot explodes in simulation” bugs when an importer substitutes defaults. And the forearm has no collision element, which many simulators will interpret as “this link never collides”, a silent behavior difference between engines.

SDF

SDF can hold the same content and then keep going. The model below adds a world wrapper, a physics block, and a sensor on the forearm, which URDF could not carry natively. Joint poses use the frame semantics of SDF, here the default of a pose relative to the child link.

<sdf version="1.9">
  <world name="lab">
    <physics name="default" type="ode">
      <max_step_size>0.001</max_step_size>
    </physics>
    <model name="two_link_arm">
      <static>false</static>
      <link name="base">
        <pose>0 0 0.1 0 0 0</pose>
      </link>
      <link name="upper">
        <pose>0 0 0.1 0 0 0</pose>
        <inertial>
          <pose>0.25 0 0 0 0 0</pose>
          <mass>2.0</mass>
          <inertia><ixx>0.001</ixx><iyy>0.042</iyy><izz>0.042</izz></inertia>
        </inertial>
        <collision name="c"><pose>0.25 0 0 0 1.5708 0</pose>
          <geometry><cylinder><radius>0.03</radius><length>0.5</length></cylinder></geometry>
        </collision>
      </link>
      <link name="fore">
        <pose>0.5 0 0.1 0 0 0</pose>
        <inertial>
          <pose>0.2 0 0 0 0 0</pose>
          <mass>1.0</mass>
          <inertia><ixx>0.0005</ixx><iyy>0.0136</iyy><izz>0.0136</izz></inertia>
        </inertial>
        <sensor name="tip_imu" type="imu">
          <pose>0.4 0 0 0 0 0</pose>
          <update_rate>200</update_rate>
        </sensor>
      </link>
      <joint name="shoulder" type="revolute">
        <parent>base</parent><child>upper</child>
        <axis><xyz>0 0 1</xyz>
          <limit><lower>-2.9</lower><upper>2.9</upper>
            <effort>50</effort><velocity>3.0</velocity></limit>
          <dynamics><damping>0.1</damping><friction>0.02</friction></dynamics>
        </axis>
      </joint>
      <joint name="elbow" type="revolute">
        <parent>upper</parent><child>fore</child>
        <axis><xyz>0 0 1</xyz>
          <limit><lower>-2.5</lower><upper>2.5</upper>
            <effort>30</effort><velocity>4.0</velocity></limit>
        </axis>
      </joint>
    </model>
  </world>
</sdf>

The SDF version is longer because it is doing more. The sensor is part of the description rather than an adjunct, and the physics step is declared next to the model. The cost is that link poses are explicit rather than implied by joint origins, so a naive converter must compute them by composing transforms down the tree. The specification version in the header must match what the parser supports; the sdformat.org site documents version 1.12 among the revisions, and the 1.9 in the header here is an illustrative value that I did not verify against a specific Gazebo release. Check the version your simulator expects in its release notes.

MJCF

MJCF is the most compact of the physical descriptions because the compiler does work for you. The hinge joints, body nesting, and an actuator are declared together, and inertia can be derived from geometry rather than typed in.

<mujoco model="two_link_arm">
  <compiler angle="radian" inertiafromgeom="auto"/>
  <option timestep="0.002" integrator="implicitfast"/>
  <worldbody>
    <body name="upper" pos="0 0 0.1">
      <joint name="shoulder" type="hinge" axis="0 0 1"
             range="-2.9 2.9" damping="0.1" frictionloss="0.02"/>
      <geom type="capsule" fromto="0 0 0 0.5 0 0" size="0.03" mass="2.0"/>
      <body name="fore" pos="0.5 0 0">
        <joint name="elbow" type="hinge" axis="0 0 1"
               range="-2.5 2.5" damping="0.05" frictionloss="0.01"/>
        <geom type="capsule" fromto="0 0 0 0.4 0 0" size="0.025" mass="1.0"/>
        <site name="tip" pos="0.4 0 0"/>
      </body>
    </body>
  </worldbody>
  <actuator>
    <motor joint="shoulder" ctrlrange="-50 50"/>
    <motor joint="elbow" ctrlrange="-30 30"/>
  </actuator>
  <sensor>
    <jointpos joint="shoulder"/>
    <jointpos joint="elbow"/>
    <accelerometer site="tip"/>
  </sensor>
</mujoco>

Here the physics is the file. Actuators and sensors reference joints and sites by name, and the whole model compiles to a flat set of arrays the engine steps directly. Notice what is absent: there is no notion of a ROS package, no world metadata, no rendering material beyond defaults. MJCF is a model-compilation format, which is exactly why it is excellent for control research and awkward as a general interchange file.

OpenUSD (USDA)

In OpenUSD the arm is a hierarchy of prims with API schemas applied. The physical content is expressed through UsdPhysics attributes, and the same stage can carry materials, lights, cameras, and a factory around the arm.

#usda 1.0
(defaultPrim = "arm", metersPerUnit = 1, upAxis = "Z")

def Xform "arm" (prepend apiSchemas = ["PhysicsArticulationRootAPI"])
{
    def Xform "upper" (prepend apiSchemas = ["PhysicsRigidBodyAPI", "PhysicsMassAPI"])
    {
        float physics:mass = 2.0
        double3 xformOp:translate = (0, 0, 0.1)
        uniform token[] xformOpOrder = ["xformOp:translate"]
        def Capsule "geom" (prepend apiSchemas = ["PhysicsCollisionAPI"]) {
            double radius = 0.03
            double height = 0.5
            uniform token axis = "X"
        }
    }
    def PhysicsRevoluteJoint "shoulder"
    {
        rel physics:body0 = </arm/world_anchor>
        rel physics:body1 = </arm/upper>
        uniform token physics:axis = "Z"
        float physics:lowerLimit = -166.0
        float physics:upperLimit = 166.0
    }
}

The fragment is abbreviated: the forearm, elbow, and anchor are omitted for length, but they follow the identical pattern. Two details trip people up. UsdPhysics expresses revolute limits in degrees, not radians, so the -2.9 rad shoulder limit becomes roughly -166 degrees. And joint bodies are referenced by relationship paths, so renaming a prim without updating relationships breaks the joint silently. The first is a standard conversion hazard; the second is a consequence of USD being a general scene graph.

Comparison of what each file actually knows

Sensors, closed loops and contact fidelity compared across URDF, SDF, MJCF and OpenUSD robot simulation formats

Figure 3: Capability profile across the four formats. The axes are native schema support, not the best that a motivated team can achieve with plugins.

Capability URDF SDF MJCF OpenUSD (UsdPhysics)
Kinematic tree Native Native Native Native via prims and joints
Closed loops No, tree only Yes Via equality constraints Expressible, solver dependent
World description No Yes Partial Yes, it is a scene graph
Native sensors No Yes Yes, physics-coupled No in UsdPhysics
Actuator models Coarse transmission Plugins Rich, native Joint drives
Derived inertia No No Yes, from geometry Mass API, density option
Rendering fidelity Basic materials Moderate Basic Very high
Composition and variants xacro macros include and nesting include Layers, references, variants

The table is a qualitative reading of the specifications, and it should not be turned into a numeric score. A team with a mature Gazebo plugin library can get sensors into URDF; a team with Isaac Sim’s sensor schemas can get them into USD. The question is whether the capability travels with the file.

Composition: xacro versus USD layers

Real robots are rarely one flat file. URDF’s answer is xacro, an XML macro language processed before the URDF reaches the parser. It handles parameters, conditional blocks, and includes, which is how a single description produces left and right arms or alternative end effectors. The catch is that the macro expansion happens outside the format, so what a simulator sees is the expanded output, and any provenance of the macro is lost.

SDF added native composition. Models can be included and nested, and the specification’s frame semantics let a nested model be attached to a named frame of the parent. OpenUSD is far ahead here: layers, references, and variant sets let a stage hold several robot configurations and switch between them non-destructively, and an override layer can change a mass or a friction coefficient for a specific project without touching the source asset. For a digital twin, that matters: a calibration layer can adjust the vendor-supplied model to match the measured physical machine, and the layer is a reviewable artifact.

A note on the ROS ecosystem

ROS tools consume URDF. If your pipeline includes MoveIt for planning, robot_state_publisher for transforms, and ros2_control for hardware interfaces, URDF is not optional, regardless of what your simulator prefers. This is why the practical pattern for ROS-centric teams is to keep URDF as the single editable source and generate the other formats from it. The risk of that pattern is covered below. For context on how robots reach the field once the simulation is trusted, our analysis of Agility Robotics Digit RaaS deployment shows what the operational side of a humanoid fleet looks like.

Converters, Pipelines, and the Newton Question

Converters exist for most edges between the four formats, but none is lossless in both directions. The reliable pattern is one authoring source, generated outputs, and a regression test that compares mass, inertia, joint limits, and a forward-kinematics pose across every generated copy.

The practical conversion tools are these. Isaac Sim ships a URDF importer extension that converts a URDF into USD; its documentation describes options for generating collision geometry from visual meshes (convex hull, convex decomposition, bounding sphere, or bounding cube), choosing a robot type, anchoring the base as fixed or mobile, merging meshes, and enabling self-collision, and the importer is open source. The same documentation set references a separate MJCF importer. The sdformat library from the Gazebo project parses SDF and includes tooling to convert from URDF to SDF, which is what Gazebo does internally when you spawn a URDF. MuJoCo can load a URDF directly through its compiler, with caveats about what it discards, and the MuJoCo Menagerie repository provides more than 80 curated MJCF models (humanoids, quadrupeds, arms, and hands from vendors such as Unitree, Franka, Universal Robots, and KUKA) so that you can start from a vetted model rather than converting your own.

Decision flow for choosing URDF, SDF, MJCF or OpenUSD as the authoring format for a robot digital twin

Figure 4: A decision flow for selecting the authoring format. The branches are driven by closed loops, sensor ownership, and renderer requirements.

What a URDF to MJCF conversion loses

The MJCF compiler can read URDF, and the translation covers links, joints, inertials, and meshes. What does not survive is anything URDF never knew: contact solver parameters, actuator models, equality constraints, and sensors. After conversion you must author those by hand, and you must check defaults. A well-known pitfall is that MuJoCo may merge bodies connected by fixed joints during compilation and can ignore the inertia of links it considers degenerate, unless the compiler is told otherwise. The mitigation is to compare total mass and each link’s inertia against the source after loading.

Menagerie takes a pragmatic stance on this. Its README describes a four-tier quality grading for models: A+ for values from proper system identification, A for realistic values without formal identification, B for stable but with some unrealistic parameters, and C for conditionally stable models that could be improved. Each model directory documents how its MJCF file was produced. The lesson for twin builders is that a converted model is an unvalidated model until someone has compared its dynamics to hardware.

What a URDF to USD conversion loses

The Isaac Sim importer converts to USD with PhysX schema attributes applied, and a documented quirk is that link, joint, and mesh names are sanitized because USD identifiers cannot contain special characters; names beginning with an underscore get a prefix. That sounds trivial until a controller config references a joint by its original name. The robust fix is a name mapping table generated at conversion time and used by every downstream consumer. What is gained in conversion is a scene: lighting, materials, environment, sensors from application schemas. What is lost is the ROS-specific semantics, such as transmissions and the package:// resolution, which the importer resolves into concrete asset paths.

What a URDF to SDF conversion loses

URDF to SDF is the most faithful of the three, because SDF is a conceptual superset and the Gazebo project invested in this path. Losses occur in extension content: <gazebo> blocks in the URDF carry reference to Gazebo-specific properties, and the converter maps those it understands into native SDF elements. Anything else is dropped or retained as unrecognized extension data. The reverse direction, SDF to URDF, is where information is destroyed: worlds, closed-loop joints, native sensors, and nested models have no URDF equivalent, so an SDF exported to URDF is a reduced view and should never become the source of truth.

Newton, MuJoCo Warp, and why the converter map is changing

The most important recent development is not a new format but a new engine. Newton is an open-source GPU-accelerated physics engine for robotics, initiated by Disney Research, Google DeepMind, and NVIDIA, and contributed to the Linux Foundation, with the contribution announced on 29 September 2025. The announcement describes it as Apache 2.0 licensed, built on NVIDIA Warp, and building on OpenUSD for data modeling of robots and environments. It includes MuJoCo Warp, a GPU-parallel reimplementation of the MuJoCo backbone, and the repository describes MuJoCo Warp as Newton’s primary backend.

Status needs care. The launch coverage described the project as alpha with expected API instability. When I checked the Newton documentation for this article, the docs reported a development version of 1.7.0.dev0 and listed three solvers, MuJoCo, Kamino, and VBD, with documentation licensed CC-BY-4.0. I was not able to confirm a stable release date or whether the project has declared a beta or 1.0 milestone, and one release-page fetch returned dates that cannot be right for a project announced in 2025, so I have not relied on it. If you plan to depend on Newton in production, read its current release notes first.

The relevance to robot description is that Newton consumes URDF, MJCF, and USD, and the documentation references URDF examples and USD parsing. If that holds in your version, it removes the need to commit to a single simulator-specific format. It does not remove the fidelity question: converting once into Newton’s internal model is still a lossy projection, and the engine’s solver choice changes behavior for the same input. The MuJoCo project itself continues to ship quickly; its release page showed version 3.14.0 dated 22 September 2026 at the time of my check, with changes including experimental penetration-free flex contact, archive resource providers for .mjz and zip assets, preserved frame elements when saving MJCF, and corrections to weld constraint torque and tendon force accounting that affect force and torque sensor accuracy. Treat those version numbers as a snapshot taken on the date of writing.

Where co-simulation fits

A robot description covers the mechanical plant. A digital twin of a real cell often also needs power electronics, thermal behavior, or a PLC model, and those arrive through the Functional Mock-up Interface rather than through URDF or SDF. If your twin needs to co-simulate a physics engine with such models, our FMI 3 co-simulation tutorial with FMPy covers the exchange mechanics, and the companion second FMI 3 FMPy walkthrough extends it. The robot description is the plant geometry and rigid-body dynamics; FMI is how the plant meets everything else.

A reproducible regression test

The single most valuable engineering habit across all of this is an automated equivalence check. After generating each format from the authoring source, load every copy in its own engine and compare a small set of invariants: total mass, the inertia of each link about its center of mass, the joint limit values after unit conversion, and the world pose of a named frame at three joint configurations. Tolerances should be tight for geometry, around floating-point precision, and looser for dynamics, where integrator differences are legitimate. A discrepancy beyond tolerance means the converter dropped or reinterpreted something. Run this in continuous integration whenever the source model changes. It costs an afternoon to build and prevents weeks of debugging a controller against a model that quietly disagrees with the one it was tuned on.

Trade-offs, Gotchas, and What Goes Wrong

The common failures are unit and convention mismatches, silently missing inertia, loop constraints dropped by converters, and sensor definitions that did not travel with the file. Each is cheap to prevent and expensive to find late.

Unit conventions bite first. URDF and SDF use radians for angles and meters for lengths. UsdPhysics expresses revolute limits in degrees. MJCF defaults to degrees unless the compiler is set to radians, as in the example above. A shoulder limit of 2.9 passed through a converter that assumes the wrong unit becomes a joint that can barely move or one that can wrap more than a turn. Check the compiler angle attribute and the stage metadata, and put the expected limits in the regression test.

Inertia is the second source of silent error. URDF tools often accept a link without an inertial block and substitute defaults; some simulators then treat the link as having negligible mass, others as unit mass. Both produce plausible but wrong behavior, and both pass a visual check. Require that every link either declares an inertial or is explicitly marked massless, and fail the build otherwise. Inertia tensors that violate the triangle inequality, where one principal moment exceeds the sum of the other two, are physically impossible, and engines respond with warnings, clamping, or instability. A validator should check them.

Closed loops are the third. When a mechanism has a loop and the authoring source is URDF, the closing constraint lives in a simulator-specific side file. Converting the URDF to another format moves the tree but not the constraint, and the mechanism simply falls apart in the target engine. If your robot has parallel linkages, author in a format that can express them, MJCF or SDF, or in USD with a solver that honors the joint set, and generate a URDF tree only for the ROS tools that need it.

Fourth, sensors. A twin that relies on a simulated lidar, depth camera, or IMU must record where sensor definitions live. If they are in Gazebo extension blocks, they will not appear in a USD import. If they are in MJCF sensor entries, they describe state measurements, not raycast perception. Document the ownership and test it: for each sensor, the regression suite should assert that a measurement exists in each target and that its frame matches.

Fifth, the temptation of a single universal format. OpenUSD is the most capable container, but UsdPhysics is deliberately minimal and solver-agnostic, so engine-specific behavior rides on extension schemas that are not portable across engines. A USD asset tuned for one solver is not a neutral twin. Equally, MJCF’s physics expressiveness is a trap if you intend to move engines, because the tuned parameters are meaningful only to MuJoCo’s contact model.

Finally, the model is not the machine. Even a perfectly converted description is only as good as its identification. A catalog mass and a CAD-derived inertia can differ from the as-built robot by enough to destabilize a model-based controller. A twin needs a calibration step and a way to store the calibrated parameters, and layered override files in USD are well suited to it, but the discipline is organizational, not format-driven.

Practical Recommendations

Pick the authoring format by the constraint that is hardest to retrofit, then generate the rest. In most digital twin projects that constraint is one of three things: ROS integration, closed-loop mechanisms, or photorealistic scene and sensor simulation.

If the robot is a serial arm or a mobile base running ROS 2 with MoveIt, author in URDF with xacro, and generate MJCF and USD from it with a regression suite. If the robot has closed loops or heavy contact, such as hands, legged robots with parallel actuators, or delta mechanisms, author in MJCF or SDF and export a URDF tree for ROS tools only. If the twin needs a factory scene, synthetic camera data, and multiple robots with swappable configurations, make OpenUSD the scene authority and keep a physics-ready robot asset as a referenced layer. If you are targeting GPU-parallel reinforcement learning, evaluate Newton and MuJoCo Warp, but pin versions and expect API movement until the project publishes stable guarantees.

A short checklist for any of these paths:

  • Choose one authoring source of truth and mark every other copy as generated.
  • Require an inertial block on every link and validate the inertia tensors.
  • Record unit conventions per format and test limit values after conversion.
  • Store the sensor ownership table: which file or layer defines each sensor.
  • Build a CI check comparing mass, inertia, joint limits, and a frame pose in every target engine.
  • Keep calibrated parameters in an override layer, not edited into the vendor model.
  • Pin simulator and converter versions, and re-run the check on each upgrade.

Frequently Asked Questions

What is the main difference in URDF vs SDF?

URDF describes one robot as a kinematic tree and omits worlds, native sensors, and closed loops. SDF describes entire simulated worlds, with models, physics settings, sensors, lights, and composition by inclusion, and it can express closed kinematic loops. URDF dominates ROS tooling such as MoveIt and RViz, while SDF is native to Gazebo. Most Gazebo workflows convert URDF into SDF when spawning a robot, so teams often author URDF and let the simulator convert.

Can URDF represent closed kinematic chains?

Not in the base schema. Every link except the root has exactly one parent joint, so a four-bar linkage or parallel gripper cannot be written as pure URDF. The usual workaround is to cut the loop and add a closing constraint in a simulator-specific extension or side file, which couples the model to that simulator. SDF, MJCF equality constraints, and USD joint relationships can express loops more directly.

Is MJCF better than URDF for reinforcement learning?

MJCF exposes contact parameters, actuator models, tendons, and equality constraints in the file, and the compiler can derive inertia from geometry, so tuning and training in MuJoCo is more direct. URDF is better for ROS integration. Many teams train on MJCF or a USD asset and deploy against a URDF-driven ROS stack, using regression tests to keep the copies consistent.

Does OpenUSD replace URDF for digital twins?

Not as a replacement, since ROS tooling still consumes URDF. OpenUSD is a strong scene authority for twins that need rendering, environments, and layered configuration. UsdPhysics provides rigid bodies, joints, and articulations but is solver-agnostic and excludes sensors, so engine-specific and sensor behavior comes from extensions. Most production pipelines keep URDF and USD side by side.

What is Newton and how does it relate to MuJoCo?

Newton is an open-source GPU-accelerated physics engine for robotics, initiated by Disney Research, Google DeepMind, and NVIDIA, and hosted by the Linux Foundation under Apache 2.0. It includes MuJoCo Warp, a GPU implementation of MuJoCo, as its main backend, and builds on OpenUSD. At announcement it was alpha; check current release notes for maturity before depending on it.

How do I convert URDF to USD for Isaac Sim?

Use the Isaac Sim URDF importer extension, which converts a URDF to USD with physics schemas applied. You can choose collision generation, robot type, base anchoring, mesh merging, and self-collision. Names with special characters are sanitized, so keep a mapping table for controller configs. Afterward, verify mass, inertia, and joint limits against the source, remembering that UsdPhysics uses degrees for revolute limits.

Further Reading

By Riju — about

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *