| name | usd-articulation |
| description | Validate and build multi-arm robot articulations in USD for Isaac Sim 6 / Lab. Covers programmatic assembly (chassis + arms + `FixedJoint`s), `ArticulationRootAPI` placement, the modern Isaac Robot Schema overlay (`IsaacRobotAPI`, `IsaacLinkAPI`, `IsaacJointAPI`, `IsaacSiteAPI`, `IsaacNamedPose`, `KinematicChain`), `PopulateRobotSchemaFromArticulation` for retrofitting existing USDs, flatten-before-deploy, and the validation checklist (articulation roots, joint connections, robot-schema integrity, floating arms). Use when creating multi-limb robots in USD, retrofitting an existing articulation with the Robot Schema, debugging "floating arms" or disconnected geometry, or flattening articulations for deployment to Isaac Lab.
|
USD Articulation — Multi-Arm Robots
Two layers stack on a robot USD:
- Physics:
UsdPhysics.ArticulationRootAPI, UsdPhysics.Joint, UsdPhysics.RigidBodyAPI, PhysxSchema.PhysxArticulationAPI.
- Robot Schema (
usd.schema.isaac.robot_schema): semantic overlay used by isaacsim.robot.poser, the importers, manipulators examples, and any tool that walks "this robot's links / joints / named poses".
Rule: visual plausibility is not mechanical correctness. If it doesn't articulate, it doesn't exist.
Multi-arm assembly (correct pattern)
from pxr import Usd, UsdGeom, UsdPhysics, Sdf, Gf
chassis = stage.DefinePrim("/World/Robot", "Xform")
chassis.GetReferences().AddReference(CHASSIS_USD)
left = stage.DefinePrim("/World/Robot/LeftArm", "Xform")
left.GetReferences().AddReference(ARM_USD)
UsdGeom.Xformable(left).AddTranslateOp().Set(Gf.Vec3d(0.0, 0.6, 0.5))
joint = UsdPhysics.FixedJoint.Define(stage, "/World/Robot/LeftArmAttachment")
joint.CreateBody0Rel().SetTargets([Sdf.Path("/World/Robot/.../Chassis")])
joint.CreateBody1Rel().SetTargets([Sdf.Path("/World/Robot/LeftArm/.../BaseMount")])
arm_prim = stage.GetPrimAtPath("/World/Robot/LeftArm/...")
arm_prim.RemoveAPI(UsdPhysics.ArticulationRootAPI)
What does not work
- Referencing the same arm USD twice via sublayer composition.
- Using
over "Geometry" to add arms in a parts layer.
- Assuming visual presence equals physical attachment.
Robot Schema overlay (Kit 110)
Apply the modern Isaac Robot Schema on top of the physics layer. The URDF/MJCF importers do this automatically; for hand-authored or retrofitted USDs apply it manually.
from pxr import Usd
from usd.schema.isaac.robot_schema import (
Classes, Attributes,
ApplyRobotAPI, ApplyLinkAPI, ApplyJointAPI, ApplySiteAPI,
CreateNamedPose, CreateSurfaceGripper,
PopulateRobotSchemaFromArticulation, GenerateRobotLinkTree, GetAllNamedPoses,
)
stage = Usd.Stage.Open("/path/robot.usd")
robot_prim = stage.GetPrimAtPath("/World/Robot")
ApplyRobotAPI(robot_prim)
robot_prim.GetAttribute(Attributes.ROBOT_TYPE).Set("Mobile Manipulators")
for link in (link_prims):
ApplyLinkAPI(link)
for joint in (joint_prims):
ApplyJointAPI(joint)
ApplySiteAPI(grasp_frame_prim)
PopulateRobotSchemaFromArticulation(stage, robot_prim)
| Schema | Applied to | Role |
|---|
IsaacRobotAPI (Classes.ROBOT_API) | robot root | robot_type, ordered link/joint relations, named-pose container |
IsaacLinkAPI (Classes.LINK_API) | each rigid link | mass/visual aux for tools that walk the chain |
IsaacJointAPI (Classes.JOINT_API) | each joint | semantic joint metadata |
IsaacSiteAPI (Classes.SITE_API) | grasp / mount / reference frames | named frames for IK targets, mounts, sensors |
IsaacNamedPose (Classes.NAMED_POSE) | pose container prims | stored joint configurations (see manipulation-ik) |
Classes.SURFACE_GRIPPER | end-effector site | author surface gripper via CreateSurfaceGripper |
Classes.ATTACHMENT_POINT_API | site or link | attachment points used by accessory tooling |
Validation checklist
Before trusting any multi-arm robot:
- Exactly 1
UsdPhysics.ArticulationRootAPI on the chassis, nowhere else.
FixedJoints connect each arm BaseMount to the chassis.
- Joint count matches:
N arm joints per arm + chassis joints.
IsaacRobotAPI present on the root; robot_type set to a valid token.
ROBOT_LINKS / ROBOT_JOINTS relations populated (call PopulateRobotSchemaFromArticulation if not).
- Each grasp frame carries
IsaacSiteAPI (not deprecated IsaacReferencePointAPI).
- Render from four angles (front, side, 3/4, top).
- Send to a vision LLM with a binary question ("Arms attached? PASS/FAIL").
Validation snippet (self-contained)
Run via $ISAAC_SIM_DIR/python.sh. Enforces exactly one ArticulationRootAPI, prints joints and connected bodies, flags arms whose BaseMount is not bound to the chassis via a FixedJoint, and reports the Robot Schema state.
import sys
from pxr import Usd, UsdPhysics, Sdf
from usd.schema.isaac.robot_schema import Classes, Attributes, GetAllNamedPoses
stage = Usd.Stage.Open(sys.argv[1])
art_roots = [p.GetPath() for p in stage.Traverse() if p.HasAPI(UsdPhysics.ArticulationRootAPI)]
print(f"ArticulationRootAPI count: {len(art_roots)} -- {art_roots}")
assert len(art_roots) == 1, "must be exactly 1 articulation root"
joints = [p for p in stage.Traverse() if p.IsA(UsdPhysics.Joint)]
chassis_paths = {str(art_roots[0])}
attached_arms = set()
for j in joints:
j_api = UsdPhysics.Joint(j)
b0 = j_api.GetBody0Rel().GetTargets()
b1 = j_api.GetBody1Rel().GetTargets()
if j.IsA(UsdPhysics.FixedJoint) and any(str(t).startswith(p) for t in b0 for p in chassis_paths):
attached_arms.update(str(t) for t in b1)
print(f"Joints: {len(joints)} | FixedJoint-attached children: {sorted(attached_arms)}")
robots = [p for p in stage.Traverse() if p.HasAPI(Classes.ROBOT_API)]
print(f"IsaacRobotAPI count: {len(robots)}")
for r in robots:
rt = r.GetAttribute(Attributes.ROBOT_TYPE).Get()
poses = GetAllNamedPoses(stage, r)
print(f" {r.GetPath()}: robot_type={rt!r} named_poses={list(poses)}")
Extend with whatever arm/leg path patterns your asset uses. Binary goal: exactly one root, every limb connected to the chassis via a FixedJoint or articulated joint chain, and the Robot Schema overlay present.
Common failures
| Symptom | Cause | Fix |
|---|
| Arms render but float | No FixedJoint to chassis | add FixedJoint |
| Multiple articulation roots | Arm USD has its own root | RemoveAPI(UsdPhysics.ArticulationRootAPI) from arms |
| Training works but arms independent | Separate articulation trees | single root + FixedJoints |
RobotPoser.solve_ik() errors out | missing IsaacRobotAPI / link relations | ApplyRobotAPI + PopulateRobotSchemaFromArticulation |
Importer applied IsaacReferencePointAPI | older asset | re-import with current Isaac Sim, or migrate to IsaacSiteAPI (deprecation warning) |
robot_type attribute value rejected | typo or stale token | pick from get_allowed_tokens(Attributes.ROBOT_TYPE) |