- name
- urdf-mjcf-to-usd-conversion
- description
- Convert URDF/MJCF to USD for Isaac Sim and Isaac Lab. Use when importing a new robot description.
- license
- Apache-2.0
- metadata
- {"author":"Renato Gasoto <info@nvidia.com>"}
# URDF / MJCF -> USD Conversion
## Purpose
Import URDF and MJCF robot descriptions to USD with modern importer APIs, instanceable meshes, and drive configuration for RL or teleop.
## Prerequisites
- Built Isaac Sim (`$ISAAC_SIM_DIR` or `_build/linux-x86_64/release`).
- NVIDIA GPU with a current driver (`nvidia-smi`).
- Shell env contract from `isaac-sim-orchestrator`: `$ISAAC_SIM_DIR`, `$ISAAC_LAB_DIR`, `$WORKSPACE_DIR`.
## Limitations
- Targets Isaac Sim 6 / Kit 110 unless a section states otherwise.
- Does not replace official NVIDIA documentation for unsupported edge cases.
## Troubleshooting
| Error / symptom | Cause | Solution |
|---|---|---|
| Extension or import not found | Wrong `$ISAAC_SIM_DIR` or stale build | Point env vars at `_build/linux-x86_64/release` or rebuild |
| Black or empty frames | Missing lights or non-RTX render mode | Add dome/key light; confirm RTX / PathTracing settings |
| Hang on stage load or first render | MDL compile or oversized stage | Follow isolation steps in `isaac-sim-troubleshooting` |
Two conversion paths and one export path.
| Path | When | Driver |
|---|---|---|
| 1. Full Isaac Sim import (CLI) | RL/Lab asset, scripted batch | `isaacsim.asset.importer.urdf` / `.mjcf` via `urdf_import.py` / `mjcf_import.py` |
| 2. Isaac Lab convert script | Isaac Lab-native config.yaml workflow | `$ISAAC_LAB_DIR/scripts/tools/convert_urdf.py` / `convert_mjcf.py` |
| Export | USD -> URDF round-trip | `isaacsim.asset.exporter.urdf` via `urdf_export.py` |
## Available Scripts
| Script | Purpose | Arguments |
|---|---|---|
| `scripts/urdf_importer_ros.py` | Import URDF from a live ROS 2 robot_state_publisher via URDFImporter | see script --help |
## Running scripts
From agent runtimes that expose skill execution helpers, invoke helpers with `run_script()`:
```python
run_script("scripts/urdf_importer_ros.py", args=["--help"])
```
From a built Isaac Sim tree, run the same file with `./python.sh` (Linux) or `python.bat` (Windows) from `_build/*/release`, or execute shell helpers directly when they do not require the simulator.
## XACRO inputs
The `URDFImporter` core does not parse XACRO. Two supported paths:
### Recommended — import directly from a running ROS 2 node
`isaacsim.ros2.urdf` adds a dedicated import path that queries the `robot_description` parameter on any node (typically `robot_state_publisher`) via the standard `GetParameters` service, resolves `package://` URLs, writes the URDF to a temp file, and feeds it to `URDFImporter`. The node is responsible for XACRO expansion, so this also covers launch-file-only distributions that never ship a static URDF.
UI: `File -> Import from ROS2 URDF Node` (opens an import window with the same collider / robot-type / mesh options as the standard URDF importer).
Python (preferred over the deprecated `URDFImportFromROS2Node` Kit command):
`import_urdf_from_ros(usd_out_path, merge_fixed_joints, fix_base, robot_type)` — subscribe to `robot_state_publisher`, resolve `package://` URLs, and import URDF when the description is received.
See [`scripts/urdf_importer_ros.py`](scripts/urdf_importer_ros.py).
Requires the `isaacsim.ros2.urdf` extension (depends on `isaacsim.ros2.bridge` for the ROS 2 runtime), a reachable node publishing `robot_description`, and Isaac Sim launched from a terminal where the robot workspace is sourced. The sourced workspace must include the robot description package and every package referenced by `package://` mesh/resource URLs; the bundled ROS 2 runtime does not provide robot-specific packages. The reader runs asynchronously — the callback fires once the `GetParameters` service replies.
### Fallback — offline xacro CLI
Use when there is no live ROS graph. Requires only the `xacro` package (`pip install xacro` or `apt install ros-$ROS_DISTRO-xacro`):
```bash
xacro robot.xacro > robot.urdf
xacro robot.xacro arm_id:=fr3 hand:=true > robot.urdf
# Inside a sourced ROS 2 workspace so package:// / $(find-pkg-share) work:
source /opt/ros/$ROS_DISTRO/setup.bash
source install/setup.bash
xacro $(ros2 pkg prefix --share my_robot_description)/urdf/robot.xacro \
> robot.urdf
```
Pass the resulting `.urdf` to Path 1 or Path 2.
## Path 1 — Isaac Sim importer (recommended for RL/Lab)
Modern public API: `isaacsim.asset.importer.urdf.URDFImporter` + `URDFImporterConfig` (dataclass) and the matching `isaacsim.asset.importer.mjcf` pair. The post-import `isaacsim.asset.transformer` runs by default and restructures the USD output (collects dependencies, runs registered rules for physics conversion, materials routing, etc.).
```python
from isaacsim.asset.importer.urdf import URDFImporter, URDFImporterConfig
config = URDFImporterConfig(
urdf_path="/path/robot.urdf",
usd_path="/path/out",
merge_fixed_joints=True,
fix_base=False,
collision_from_visuals=True,
collision_type="Convex Decomposition",
joint_drive_type="force",
joint_target_type="position",
override_joint_stiffness=800.0,
override_joint_damping=40.0,
robot_type="Manipulator", # robot-schema token
run_asset_transformer=True, # default True; applies transformer profile
run_multi_physics_conversion=True, # URDF -> PhysX/MuJoCo physics
)
output_usd = URDFImporter(config).import_urdf()
```
### `URDFImporterConfig` fields (defaults)
| Field | Default | Notes |
|---|---|---|
| `urdf_path`, `usd_path` | `None` | input/output |
| `merge_fixed_joints` | `False` | collapse fixed joints |
| `merge_mesh` | `False` | merge meshes per link |
| `debug_mode` | `False` | extra logging + intermediates |
| `collision_from_visuals` | `False` | derive collision geom from visuals |
| `collision_type` | `"Convex Hull"` | `Convex Hull` / `Convex Decomposition` / `Bounding Sphere` / `Bounding Cube` |
| `allow_self_collision` | `False` | leave off for training |
| `ros_package_paths` | `[]` | resolve `package://` URLs |
| `robot_type` | `"Default"` | robot-schema token; see below |
| `fix_base` | `False` | adds fixed joint world -> root; relocates `ArticulationRootAPI` |
| `link_density` | `None` | kg/m^3 fallback when URDF has no mass |
| `joint_drive_type` | `None` | `force` / `acceleration`; or `{regex: value}` per-joint |
| `joint_target_type` | `None` | `none` / `position` / `velocity`; or per-joint dict |
| `override_joint_stiffness` | `None` | Nm/rad (rev) or N/m (pris); or per-joint dict |
| `override_joint_damping` | `None` | Nm*s/rad / N*s/m; or per-joint dict |
| `run_asset_transformer` | `True` | run transformer profile post-import |
| `run_multi_physics_conversion` | `True` | URDF -> PhysX joint attr conversion |
### CLI (Isaac Sim)
`source/standalone_examples/api/isaacsim.asset.importer.urdf/urdf_import.py` auto-enables `omni.scene.optimizer.core` and `isaacsim.robot.schema`, then applies the config.
```bash
"$ISAAC_SIM_DIR/python.sh" \
"$ISAAC_SIM_DIR/source/standalone_examples/api/isaacsim.asset.importer.urdf/urdf_import.py" \
--urdf /path/robot.urdf \
--usd-path /path/out \
--merge-fixed-joints \
--fix-base \
--joint-drive-type force \
--joint-target-type position \
--collision-from-visuals --collision-type "Convex Decomposition" \
--robot-type Manipulator \
--ros-package my_pkg:/abs/path/to/my_pkg
# --no-run-asset-transformer to skip the transformer profile.
```
`--robot-type` choices come from `usd.schema.isaac.robot_schema.get_allowed_tokens(Attributes.ROBOT_TYPE)`: `Default`, `End Effector`, `Manipulator`, `Humanoid`, `Wheeled`, `Holonomic`, `Quadruped`, `Mobile Manipulators`, `Aerial`.
### MJCF import (parallel API)
```python
from isaacsim.asset.importer.mjcf import MJCFImporter, MJCFImporterConfig
config = MJCFImporterConfig(
mjcf_path="/path/robot.xml",
usd_path="/path/out",
import_scene=True, # include MJCF scene settings
merge_mesh=True,
robot_type="Quadruped",
override_gain_type="fixed", # MuJoCo actuator gain type
override_bias_type="affine", # MuJoCo actuator bias type
override_gain_prm=[kp, 0, 0, 0, 0, 0, 0, 0, 0, 0], # position control
override_bias_prm=[0, -kp, -kd, 0, 0, 0, 0, 0, 0, 0], # position control
)
output_usd = MJCFImporter(config).import_mjcf()
```
CLI: `source/standalone_examples/api/isaacsim.asset.importer.mjcf/mjcf_import.py` (mirrors URDF: `--mjcf`, `--usd-path`, `--import-scene`, `--robot-type`, `--override-gain-type`, `--override-bias-type`, etc.).
Legacy MJCF commands `MJCFCreateAsset` / `MJCFCreateImportConfig` are deprecated; use `MJCFImporter` directly.
> **Migration:** for the broader `omni.importer.mjcf` / `omni.importer.urdf` → `isaacsim.asset.importer.*` rename map, see [Renaming Extensions](https://docs.isaacsim.omniverse.nvidia.com/latest/migration_guides/isaac_sim_4_5/extensions_renaming.html).
### Asset Transformer (what `run_asset_transformer=True` runs)
`isaacsim.asset.transformer` executes ordered USD rule pipelines. Default post-import rules include `UrdfToMjcPhysxConversionRule` / `MjcToPhysxConversionRule` and material routing. To run manually on an existing USD:
```python
from isaacsim.asset.transformer import AssetTransformerManager, RuleProfile
manager = AssetTransformerManager()
profile = RuleProfile.from_json("path/to/profile.json")
report = manager.run("input.usd", profile, package_root="/tmp/out")
```
CLI: `source/standalone_examples/api/isaacsim.asset.transformer/run_asset_transformer.py` (`--input`, `--profile`, `--output`).
### Robot Schema applied during import
The importer applies the modern `usd.schema.isaac.robot_schema`:
- `IsaacRobotAPI` on the robot root prim (stores `robot_type`, ordered link/joint relations, named-pose container).
- `IsaacLinkAPI` on rigid links.
- `IsaacJointAPI` on joints.
- `IsaacSiteAPI` on sites (replaces deprecated `IsaacReferencePointAPI`).
- `IsaacNamedPose` prims for named poses; manage via the `isaacsim.robot.poser` module (see `manipulation-ik`).
Validate after import:
```python
from pxr import Usd
from usd.schema.isaac.robot_schema import Classes, get_allowed_tokens, Attributes
stage = Usd.Stage.Open("/path/out/robot.usd")
robot = next(p for p in stage.Traverse() if p.HasAPI(Classes.ROBOT_API))
print(robot.GetAttribute(Attributes.ROBOT_TYPE).Get())
```
## Path 2 — Isaac Lab `convert_urdf.py` / `convert_mjcf.py` (config.yaml)
For Isaac Lab-native workflows you have a `config.yaml` per robot under `assets/isaaclab/Robots/<Robot>/`:
```yaml
asset_path: /path/to/robot.urdf # pre-expand XACRO first
usd_file_name: robot_name.usd
force_usd_conversion: true
make_instanceable: true # critical for parallel envs
import_inertia_tensor: true # use URDF inertia
merge_fixed_joints: true
self_collision: false
fix_base: false
default_drive_type: none # "none" | "position" | "velocity"
default_drive_stiffness: 0.0
default_drive_damping: 0.0
link_density: 0.0
convex_decompose_mesh: false
```
Run:
```bash
cd "$ISAAC_LAB_DIR"
./isaaclab.sh -p scripts/tools/convert_urdf.py \
--config /path/MyRobot/config.yaml --output /path/MyRobot/
# MJCF equivalent:
./isaaclab.sh -p scripts/tools/convert_mjcf.py \
--config /path/MyRobot/config.yaml --output /path/MyRobot/
```
### Drive presets
| Use case | Drive config |
|---|---|
| RL training (agent controls torques) | `default_drive_type: none`, stiffness `0.0`, damping `0.0` |
| Position-controlled teleop | `default_drive_type: position`, stiffness `800.0`, damping `40.0` |
| Assembly / manipulation objects (AutoMate) | `default_drive_type: force`, `joint_drive.target_type: position`, `gains: {stiffness: 100, damping: 1}`, `collider_type: convex_hull` |
### `make_instanceable: true`
GPU mesh instancing. Without it, 4096 envs * full mesh = VRAM blow-up. With it, parallel envs share one mesh in VRAM. Always set for RL.
### `fix_base` by robot type
| Robot type | `fix_base` |
|---|---|
| Manipulator arm (table/wall mounted) | `true` |
| Mobile robot (wheels) | `false` |
| Humanoid / legged | `false` |
View on GitHub