Universal Scene Description  ·  .usda

OpenUSD for robots

OpenUSD has no robot element. A robot in USD is ordinary geometry with physics applied to it — and that is exactly what Isaac Sim reads. Here is what one arm looks like as a stage, what a URDF becomes on its way into Isaac Sim, and how the result was checked against Pixar's own library.

What OpenUSD is

Universal Scene Description is the format Pixar built to assemble film scenes out of layers that many people edit at once. It was open-sourced in 2016, is now standardised through the Alliance for OpenUSD, and comes in three encodings: text (.usda), binary (.usdc) and a zipped package (.usdz). NVIDIA built Omniverse on it, and Isaac Sim — its robot simulator — opens nothing else.

What USD contributes is composition: a stage references other files, overrides parts of them and layers changes on top. What makes a stage a robot is a set of schemas, UsdPhysics, applied to ordinary prims: this one is a rigid body with a mass, that one collides, this joint relates those two bodies.

The same arm, as a USD stage

The two-link arm from the URDF walkthrough, exactly as the converter writes it. Pixar's library opens it, and UsdPhysics finds two bodies, a joint and an articulation in it.

two-link-arm.usda Download it
#usda 1.0
(
    defaultPrim = "two_link_arm"
    doc = "Converted by urdfbase"
    kilogramsPerUnit = 1
    metersPerUnit = 1
    upAxis = "Z"
)

def Xform "two_link_arm" (
    prepend apiSchemas = ["PhysicsArticulationRootAPI"]
)
{
    def Xform "base" (
        prepend apiSchemas = ["PhysicsRigidBodyAPI", "PhysicsMassAPI"]
    )
    {
        float physics:mass = 2
        float3 physics:diagonalInertia = (0.00412, 0.00412, 0.0049)
        def Cylinder "visual_0"
        {
            double radius = 0.07
            double height = 0.1
            uniform token axis = "Z"
        }
        def Cylinder "collision_0" (
            prepend apiSchemas = ["PhysicsCollisionAPI"]
        )
        {
            double radius = 0.07
            double height = 0.1
            uniform token axis = "Z"
            uniform token purpose = "guide"
        }
    }

    def Xform "arm" (
        prepend apiSchemas = ["PhysicsRigidBodyAPI", "PhysicsMassAPI"]
    )
    {
        double3 xformOp:translate = (0, 0, 0.05)
        uniform token[] xformOpOrder = ["xformOp:translate"]
        float physics:mass = 0.8
        point3f physics:centerOfMass = (0.15, 0, 0)
        float3 physics:diagonalInertia = (0.000213, 0.00611, 0.00611)
        def Cube "visual_0"
        {
            double size = 1
            double3 xformOp:translate = (0.15, 0, 0)
            double3 xformOp:scale = (0.3, 0.04, 0.04)
            uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:scale"]
        }
        def Cube "collision_0" (
            prepend apiSchemas = ["PhysicsCollisionAPI"]
        )
        {
            double size = 1
            uniform token purpose = "guide"
            double3 xformOp:translate = (0.15, 0, 0)
            double3 xformOp:scale = (0.3, 0.04, 0.04)
            uniform token[] xformOpOrder = ["xformOp:translate", "xformOp:scale"]
        }
    }

    def Scope "joints"
    {
        def PhysicsRevoluteJoint "shoulder" (
            prepend apiSchemas = ["PhysicsDriveAPI:angular", "PhysxJointAPI"]
        )
        {
            rel physics:body0 = </two_link_arm/base>
            rel physics:body1 = </two_link_arm/arm>
            point3f physics:localPos0 = (0, 0, 0.05)
            quatf physics:localRot0 = (1, 0, 0, 0)
            point3f physics:localPos1 = (0, 0, 0)
            quatf physics:localRot1 = (1, 0, 0, 0)
            uniform token physics:axis = "Z"
            float physics:lowerLimit = -89.9543738355
            float physics:upperLimit = 89.9543738355
            float drive:angular:physics:maxForce = 12
            float physxJoint:maxJointVelocity = 114.591559026
        }
    }
}
  • The stage states its units. metersPerUnit = 1, kilogramsPerUnit = 1, upAxis = "Z". USD's fallbacks are centimetres and Y up, which is how a robot arrives a hundred times too big and lying on its side.
  • A link is an Xform with physics applied. The rigid-body and mass schemas carry the mass, the centre of mass and the inertia; the shapes underneath are plain geometry, and the collision ones say so with PhysicsCollisionAPI.
  • A joint is a prim of its own that points at two bodies and says where the joint sits in each. Limits are in degrees, so the URDF's ±1.57 rad arrives as ±89.95°.
  • The motor's rating is a drive. The 12 N·m effort becomes the drive's maxForce; the top speed goes into PhysX's own attribute, in degrees per second.

What did not cross, as the converter reported it:

  • joint shoulder … damping and friction belong to a drive in USD and are not written here

What a URDF becomes

Nearly every piece of a URDF has a place in a USD stage, and the few that have none are said out loud. This is the mapping the URDF to USD converter follows:

In the URDFIn the USD stage
<robot>An Xform with PhysicsArticulationRootAPI: PhysX simulates everything under it as one articulation
<link>An Xform with PhysicsRigidBodyAPI and PhysicsMassAPI, placed with a translate and an orient
The inertia tensorThree principal moments in diagonalInertia and the turn into them as principalAxes — the full tensor, not just its diagonal
<visual>A shape prim: Cube, Sphere, Cylinder, Capsule or Mesh
<collision>The same, with PhysicsCollisionAPI and purpose "guide", so it collides and is not rendered
A mesh fileThe mesh itself, written into the stage — USD cannot load an STL or an OBJ
<joint>A PhysicsRevoluteJoint, PrismaticJoint or FixedJoint naming its two bodies, with the joint frame on each side
Limits in radiansLimits in degrees
effortA drive's maxForce
velocityphysxJoint:maxJointVelocity, in degrees per second
Damping, friction, mimicNothing in core USD — listed as lost

How it was checked

A converter that checks its output with its own reader proves only that the two agree — this site's first USD writer produced files that its own reader loved and Pixar's library refused to open. So the check is somebody else's code. Every robot in the catalogue — 104 of them on 4 October 2026 — was converted and then:

  • opened with Pixar's usd-core, the library every USD tool is built on, Isaac Sim included;
  • read with UsdPhysics' own parser — the routine physics engines use to find bodies, joints and articulations — which found every body and joint the robot has;
  • compared link by link with Drake loading the same robot as URDF: every link where the URDF puts it, and every mesh where its link puts it. The one exception is Cassie's pair of free-floating linkage rods, which Drake places at the world's origin rather than where the joint holds them.

Into Isaac Sim, with or without Isaac

If Isaac Sim is installed, its own URDF importer is the native route: it also sets up drive gains, instancing and the articulation settings Isaac Lab expects. This converter is for the times Isaac is not to hand — on a laptop without an RTX card, in a build, for a colleague who only needs to look — and for the other direction, from a stage back to URDF or MJCF.

Unitree robots are the usual case. Isaac Lab ships configurations for the A1, Go1, Go2, H1 and G1; the same robots open in the catalogue here and download as a single .usda with their meshes inside, which is a file you can read, diff and change.

MuJoCo or Isaac Sim?

They answer different questions, and the format each one reads follows from that:

MuJoCoIsaac Sim
Physics engineMuJoCo, from Google DeepMind, Apache 2.0PhysX, from NVIDIA, inside Omniverse
Runs onAny CPU, a browser tab included; GPUs through MJX or MuJoCo WarpAn NVIDIA RTX GPU
Model formatMJCF, and it compiles URDFOpenUSD, with importers for URDF and MJCF
RenderingA plain OpenGL viewerRay-traced, with simulated cameras and lidar
Strongest atContact-rich control, fast iteration, research codeSensors, synthetic data, large scenes, Isaac Lab training at scale
Getting startedpip install mujocoA large install and a supported GPU driver

Plenty of teams use both: train in one, check in the other, and find the bugs that live in the gap between two physics engines. Moving a robot between them is the MJCF to USD conversion and back.

Common questions

What is OpenUSD?

Universal Scene Description: the format Pixar built to compose film scenes from layers, open-sourced in 2016 and now standardised through the Alliance for OpenUSD. It is text (.usda), binary (.usdc) or a zipped package (.usdz), and NVIDIA's Omniverse and Isaac Sim are built on it.

How do I import a URDF into Isaac Sim?

Isaac Sim's own URDF importer does it when Isaac is installed, and also sets up drive gains and its own instancing. Without Isaac, the URDF to USD converter writes a stage with every mesh inside it, which Isaac Sim opens as it is.

Why does my robot open a hundred times too big, or lying on its side?

Units and axes. USD's own fallbacks are centimetres and Y up, and a stage that does not declare metersPerUnit = 1 and upAxis = "Z" gets them. Every stage written here declares both, and kilograms too.

Can I get a Unitree G1 or Go2 into Isaac Sim?

Isaac Lab ships ready configurations for Unitree's A1, Go1, Go2, H1 and G1. For a file you can read and change — or a robot Isaac Lab does not ship — open it from the catalogue and download it as USD: one .usda, meshes included.

Can USD go back to URDF?

Yes, from the text form: the USD to URDF converter reads rigid bodies, joints and collision shapes and turns degrees back into radians. A binary .usdc has to be turned into text first, with Pixar's usdcat -o robot.usda.

Should I use MuJoCo or Isaac Sim?

MuJoCo when the question is control or contact and you want to iterate quickly, on any machine; Isaac Sim when you need its sensors and rendering, or Isaac Lab's scale on NVIDIA GPUs. Many teams use both, training in one and checking in the other — which is what the converters between MJCF, URDF and USD are for.

Where to go next