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.
#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 URDF | In 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 tensor | Three 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 file | The 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 radians | Limits in degrees |
effort | A drive's maxForce |
velocity | physxJoint:maxJointVelocity, in degrees per second |
| Damping, friction, mimic | Nothing 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:
| MuJoCo | Isaac Sim | |
|---|---|---|
| Physics engine | MuJoCo, from Google DeepMind, Apache 2.0 | PhysX, from NVIDIA, inside Omniverse |
| Runs on | Any CPU, a browser tab included; GPUs through MJX or MuJoCo Warp | An NVIDIA RTX GPU |
| Model format | MJCF, and it compiles URDF | OpenUSD, with importers for URDF and MJCF |
| Rendering | A plain OpenGL viewer | Ray-traced, with simulated cameras and lidar |
| Strongest at | Contact-rich control, fast iteration, research code | Sensors, synthetic data, large scenes, Isaac Lab training at scale |
| Getting started | pip install mujoco | A 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
- URDF to USD — the conversion, with what it cannot carry
- Robot models — a hundred robots, each downloadable as USD
- MuJoCo in the browser — the other simulator, with nothing to install