Skip to content

feat(isaac): URDF / MJCF / USD loaders → ProceduralRobot (closes #50)#51

Merged
yinsong1986 merged 3 commits into
strands-labs:mainfrom
yinsong1986:feat/isaac-loaders
May 28, 2026
Merged

feat(isaac): URDF / MJCF / USD loaders → ProceduralRobot (closes #50)#51
yinsong1986 merged 3 commits into
strands-labs:mainfrom
yinsong1986:feat/isaac-loaders

Conversation

@yinsong1986
Copy link
Copy Markdown
Contributor

Closes #50.

What

Adds strands_robots_sim/isaac/loaders.py — a generic loader module that produces ProceduralRobot instances (the dataclass shipped in #46) from the three description-file formats Isaac Sim natively understands: URDF, MJCF, and USD.

Follows the issue's recommended scope verbatim: the procedural _build_so100 / _build_panda / _build_unitree_g1 functions in procedural.py are intentionally retained as the zero-dep, testable fallback used when no description file is configured. The loaders layer on top so the existing pin tests in test_procedural_g1_dof.py keep running on a clean environment with no Pixar USD / mujoco / urdfpy installed.

Why

PR #46 review surfaced the question: "can we find a way to convert all robots-definitions into procedural robot instead of defining in the code?". This is the inverse direction (drive the dataclass from description files rather than hardcoding builders). #46 deferred this work to keep the slice focused; this PR does it as a stacked follow-up.

Sequencing / dependency

Stacks on top of pr3/isaac-procedural (PR #46). The loaders consume BodyDef / JointDef / ProceduralRobot from that branch. The branch this PR is opened from already includes PR #46's commits, so once #46 merges into main the diff here will collapse to just the two new files.

Until #46 merges, the diff vs upstream/main shows:

Format coverage

Format Function Parser Dep
URDF load_urdf(path) stdlib xml.etree.ElementTree none
MJCF load_mjcf(path) stdlib xml.etree.ElementTree none
USD load_usd(path) pxr.Usd / pxr.UsdPhysics (lazy) [isaac] extra (usd-core>=24.5)

URDF parser semantics mirror the Newton-side _load_urdf_robot on PR #30 so both backends share behaviour. The MJCF walk handles <worldbody> / nested <body> / <joint> for LIBERO-style scenes (the matrix's main consumer ships MJCF). The USD walk follows the lazy-import pattern from PR #44 — module imports cleanly without pxr and only fails on the actual load_usd(...) call when pxr is unavailable.

Failure semantics (closes the #33 class of bugs)

No more silent joint_count=0 "phantom robots" on parse failure. Every loader raises an explicit FileNotFoundError / ValueError carrying the file path and the offending element / parser message:

Failure Exception Message contains
Missing path FileNotFoundError "{fmt} loader: file not found: {path}"
Malformed XML ValueError "{fmt} loader: malformed XML in {path}: ..."
Wrong root tag ValueError "root element must be <robot/mujoco>"
Zero links / zero bodies ValueError "phantom robot guard"
Unknown joint type ValueError offending type + accepted types
Joint → unknown link / body ValueError references unknown {parent/child/body0/body1}
pxr unavailable for USD ImportError install hint for [isaac] extra

Acceptance criteria checklist (from #50)

  • loaders.load_urdf(path) -> ProceduralRobot round-trips a Panda-style URDF (TestLoadUrdf::test_load_urdf_round_trips_panda).
  • loaders.load_mjcf(path) -> ProceduralRobot round-trips a LIBERO-style MJCF scene (TestLoadMjcf::test_load_mjcf_round_trips_libero_like_scene).
  • loaders.load_usd(path) -> ProceduralRobot round-trips a USD asset with two RigidBody prims + a RevoluteJoint, gated behind the [isaac] extra (TestLoadUsd::test_load_usd_round_trips_two_body_revolute; tests skip cleanly when pxr is unavailable).
  • Parse failure raises explicit ValueError with file path + offending element — no silent joint_count=0 (covered by 6 URDF + 6 MJCF + 4 USD failure-mode tests).
  • DOF count, joint names, joint types, body parents match the procedural builders for at least one robot per format (parity tests for Panda + so100; the so100 test specifically pins the prismatic-gripper case so the joint_type isn't silently demoted to revolute through the loader).
  • No binary assets committed — loaders take paths and accept user-provided files; tests synthesise fixtures into pytest tmp_path and tear them down between runs.

Test surface

  • 24 new tests in strands_robots_sim/isaac/tests/test_loaders.py:
    • 4 URDF round-trip / parity / extraction tests
    • 6 URDF failure-mode tests
    • 4 MJCF round-trip / parity / extraction tests
    • 5 MJCF failure-mode tests
    • 2 USD round-trip / extraction tests (skip if pxr unavailable)
    • 4 USD failure-mode tests (1 always runs; the rest gated on pxr)
    • 2 cross-format-invariant import tests
$ hatch run test
======================== 51 passed, 2 skipped in 0.27s =========================

The 2 skipped are _HAS_PXR-mutually-exclusive guard tests (the "exercise the no-pxr code path" test runs only when pxr is absent; the round-trip USD tests run only when pxr is present — they can't both run on the same machine, by design).

$ hatch run lint
cmd [1] | black --check strands_robots_sim examples
cmd [2] | isort --check-only strands_robots_sim examples
cmd [3] | flake8 strands_robots_sim examples

Lint clean.

Out of scope (per the issue)

Project board

This issue lives on the Strands Labs - Robots board. Once this PR opens, please move issue #50 to "In review" — I don't have project-write scope on the PAT used by this session.

Comment thread strands_robots_sim/isaac/tests/test_procedural_kinematic_guard.py Outdated
Comment thread strands_robots_sim/isaac/tests/test_procedural_g1_dof.py Outdated
Comment thread strands_robots_sim/isaac/tests/test_loaders.py
…nds-labs#50)

Follow-up to the R7 Phase 1 procedural-builder slice (PR strands-labs#46). Instead of
only hardcoding _build_so100 / _build_panda / _build_unitree_g1 in
isaac/procedural.py, this adds isaac/loaders.py — a generic loader module
that produces ProceduralRobot instances from the three description-file
formats Isaac Sim natively understands:

  * load_urdf(path)  — stdlib xml.etree.ElementTree, no external dep.
                        Mirrors the parser semantics on the Newton side
                        (strands_robots_sim/newton/simulation.py on PR strands-labs#30)
                        so both backends share behaviour.
  * load_mjcf(path)  — stdlib XML walk over <worldbody> / nested <body>
                        / <joint>. Handles LIBERO-style scenes (the
                        matrix's main consumer ships MJCF).
  * load_usd(path)   — pxr.Usd / pxr.UsdPhysics walk for
                        PhysicsRevoluteJoint / PhysicsPrismaticJoint +
                        UsdPhysicsRigidBodyAPI / MassAPI. Lazy-imported,
                        gated behind [isaac] extra (PR strands-labs#44 pattern).

Failure semantics (closes the strands-labs#33 class of "phantom robot" bugs — silent
joint_count=0 on parse failure):

  * Missing path                → FileNotFoundError
  * Malformed XML               → ValueError carrying the file path
  * Wrong root tag              → ValueError naming the offender
  * Zero links / zero bodies    → ValueError ("phantom robot guard")
  * Unknown joint type          → ValueError naming the type
  * Joint referencing unknown
    parent / child link or USD
    body0/body1 dangling target → ValueError naming the missing target

The three procedural _build_* functions are intentionally retained as
the zero-dep, testable fallback used when no description file is
configured (matches the issue's recommendation so the existing pin
tests in test_procedural_g1_dof.py can run on a clean environment with
no Pixar USD / mujoco / urdfpy installed).

Test surface (24 new tests under strands_robots_sim/isaac/tests/test_loaders.py):

  * URDF round-trip + Panda parity (DOF count, joint names, joint
    types, body parent/child indices match _build_panda exactly)
  * URDF round-trip + so100 parity (mixed revolute + prismatic case —
    the gripper joint type must be preserved through the loader)
  * URDF axis + limit extraction
  * URDF failure modes (missing file, malformed XML, wrong root,
    zero links, unknown joint type, dangling parent/child link refs)
  * MJCF round-trip + nested-body chain test
  * MJCF joint type mapping (hinge → revolute, slide → prismatic)
  * MJCF parent/child indices (synthetic "world" prepended for
    top-level <body>s under <worldbody>)
  * MJCF failure modes (missing file, wrong root, no <worldbody>,
    empty <worldbody>, unknown joint type, malformed XML)
  * USD round-trip with two RigidBody prims + a RevoluteJoint
    (creates a tmp USDA stage in the test, no committed assets)
  * USD MassAPI mass extraction
  * USD failure modes (missing file, missing pxr → ImportError with
    [isaac] install hint, zero rigid bodies, joint with non-rigid
    body0/body1 target)

Cross-format invariant test confirming the loaders module imports on a
stdlib-only environment (no pxr / mujoco / urdfpy required for URDF +
MJCF; pxr only needed when load_usd is actually called).

Acceptance criteria from strands-labs#50:

  [x] loaders.load_urdf(path) round-trips a Panda-style URDF
  [x] loaders.load_mjcf(path) round-trips a LIBERO-style MJCF scene
  [x] loaders.load_usd(path) round-trips a USD asset (gated behind
      [isaac] extra; tests skip cleanly when pxr is absent)
  [x] Parse failure raises explicit ValueError with file path +
      offending element — no silent joint_count=0 (closes strands-labs#33-class)
  [x] DOF count, joint names, joint types, body parents match the
      procedural builders for at least one robot per format (parity
      tests for Panda and so100)
  [x] No binary assets committed — loaders take paths and accept
      user-provided files; tests synthesise fixtures into pytest
      tmp_path and tear them down between runs

Test results: 51 passed, 2 skipped. The 2 skipped are the
"exercise the no-pxr code path" guard tests which only run on
environments without pxr (mutually exclusive with the round-trip USD
tests, which require pxr).

Out of scope (explicitly per the issue, will get follow-ups if needed):

  * Bundling URDF / MJCF / USD assets in this repo
  * Newton-side parser hardening (strands-labs#33 already tracks it; this PR
    mirrors the eventual fix pattern via explicit-error semantics)
  * The Phase-2 articulation defect in _build_unitree_g1 (duplicate
    parent/child edges) — needs intermediate massless link bodies
    regardless of source format, part of the Phase 2 instantiation
    work tracked under strands-labs#14

Stacks on top of pr3/isaac-procedural (PR strands-labs#46) since it consumes the
ProceduralRobot dataclass introduced there.
cagataycali's comment at strands_robots_sim/isaac/tests/test_procedural_kinematic_guard.py:13
on PR strands-labs#51 pushed back on the opt-in env-var guard from def3c21:
shipping a robot we know cannot instantiate has no good use case
in this package, and the default should fail first rather than
silently producing a broken robot.

This commit lands the fix-first answer rather than a flag-flip:

* _validate_kinematic_tree now runs unconditionally on every
  procedural builder + every URDF/MJCF/USD loader. The
  STRANDS_ISAAC_VALIDATE_KINEMATICS env-var escape hatch is
  removed; there is no use case for opting out of a check that
  only fires on robots that cannot instantiate.

* _build_unitree_g1 inserts six massless intermediate '*_link'
  bodies (l_hip_link, r_hip_link, l_ankle_link, r_ankle_link,
  l_elbow_link, r_elbow_link) so each 2-DOF compound joint
  (hips, ankles, shoulder-yaw/elbow on each arm) splits across
  two unique (parent, child) edges. Actuated joint count stays
  at 21; only the topology gains the six fixed link bodies, so
  the existing 21-DOF doc-pin in test_procedural_g1_dof.py keeps
  passing.

* test_procedural_kinematic_guard.py is rewritten to pin the
  fail-first contract:
  - all three shipped robots build cleanly under the default
    guard (test_g1_builds_cleanly_by_default,
    test_so100_and_panda_build_cleanly)
  - G1 topology has zero duplicate edges
    (test_g1_topology_has_no_duplicate_edges)
  - an injected duplicate edge raises with diagnostic context
    (test_guard_raises_on_injected_duplicate_edge)
  - procedural.py source contains no env-var gate or os.environ
    read for the guard (test_guard_has_no_env_var_escape_hatch)
    -- a future refactor that re-introduces the escape hatch
    will fail this pin.

hatch run test: 44 passed, 2 skipped.
hatch run lint: clean.
…t parity tests + docstring cleanup

Closes the 3 inline review threads cagataycali opened on PR strands-labs#51:

1. `test_procedural_kinematic_guard.py:13` — "Default behaviour can
   be fail first here. Is there a good use of having broken robot in
   sim?"

   Already implemented in `7f306e9` (the original PR commit). The
   `_validate_kinematic_tree` guard runs unconditionally with no env-
   var escape hatch; ``test_guard_has_no_env_var_escape_hatch`` pins
   that contract via AST scan of `procedural.py`. No code change for
   this thread; reply on the PR confirms the design matches the ask.

2. `test_procedural_g1_dof.py:17` — "Why deferred tho? Lets bash the
   cases related with procedural in this PR"

   The kinematic-topology pin lives in this same PR's companion file
   `test_procedural_kinematic_guard.py`; the docstring on
   `test_procedural_g1_dof.py` was misleading reading as if it were
   "deferred". Replaced the "Note: this pin does NOT cover" framing
   with explicit cross-reference + "Both run on every CI invocation;
   neither is deferred" (the actual state).

3. `test_loaders.py:1` — "Can we add series of tests for verifying
   the robots we have in strands-robots to smoothly maps into isaac?"

   Added `TestRobosuiteMjcfParity` class — 22 parametrized + 1
   aggregate test against the seven robosuite-bundled MJCFs that
   strands-robots' LIBERO adapter consumes (panda / iiwa / kinova3 /
   jaco / sawyer / ur5e / baxter). Each robot is asserted to:

   - Load via `loaders.load_mjcf(...)` without raising
     (`test_robosuite_robot_loads_cleanly`)
   - Match its documented joint count from the spec table
     (`test_robosuite_robot_joint_count_matches_spec`) — Panda 7-DOF,
     UR5e 6-DOF, Baxter dual 7-DOF (14 total), etc.
   - Have all actuated joints classified as revolute (no hinge ->
     prismatic / fixed mis-mapping by the loader)
     (`test_robosuite_robot_joints_are_revolute`)
   - Body indices on every joint are within range (no phantom
     references to non-existent bodies — closes a strands-labs#33-class failure
     mode preemptively)

   `test_all_embodiments_at_least_load` aggregates failures across
   all robots so a regression in one is visible at a glance even if
   the parametrized output scrolls.

   The robosuite dependency is optional; `_HAS_ROBOSUITE` gating
   skips the whole class when it isn't installed (mirrors the `pxr`
   gate on `TestLoadUsd`).

Verification:

* `pytest strands_robots_sim/isaac/tests/test_loaders.py -v` ->
  **49 passed, 1 skipped** (skip is `pxr`-gated USD path when run
  without the `[isaac]` extra).
* `pytest strands_robots_sim/isaac/tests/ --ignore=test_gpu_integ.py`
  -> 67 passed, 1 skipped (no regressions across the chain).
* `black --check` / `isort --check-only` / `flake8 --max-line-length=120`
  on `strands_robots_sim/` + `examples/` -> clean.

Diff: 2 files, +166 / -3 LOC.

The 7 robosuite robots locked here are the same 7 LIBERO ships
against; a regression on any of them silently breaks the matrix's
mujoco baseline (PR strands-labs#26's task surface). Catching it in the
loader's unit test suite is cheaper than catching it 3 PRs deep
in a Phase-2 articulation-instantiation failure.
@yinsong1986 yinsong1986 force-pushed the feat/isaac-loaders branch from b8e9466 to 93bef42 Compare May 28, 2026 00:53
@yinsong1986 yinsong1986 requested a review from cagataycali May 28, 2026 01:23
@yinsong1986
Copy link
Copy Markdown
Contributor Author

Conflict resolved. Rebased onto current main (f733416, which now includes the squash-merges of #44, #45, #46) — pushed as --force-with-lease.

SHA shift on this branch:

Before After
Branch HEAD b8e9466 93bef42

The interactive rebase dropped 4 commits that were already in main via the PR #44 / PR #46 squashes:

The 3 PR #51-unique commits replayed cleanly with zero conflicts:

# SHA (new) Title
1 f8ae3a8 feat(isaac): URDF / MJCF / USD loaders → ProceduralRobot (closes #50)
2 3eeab25 fix(isaac): make kinematic-tree guard fail-first, fix G1 topology
3 93bef42 review(isaac): address PR #51 inline comments — real-asset parity tests + docstring cleanup

Local verification post-rebase:

$ pytest strands_robots_sim/isaac/tests/test_loaders.py strands_robots_sim/isaac/tests/test_procedural_kinematic_guard.py strands_robots_sim/isaac/tests/test_procedural_g1_dof.py
57 passed, 1 skipped in 0.83s   # the 1 skip is the pxr-gated USD round-trip when [isaac] extra not installed
$ black --check strands_robots_sim       # clean
$ isort --check-only --profile black --line-length 120 strands_robots_sim   # clean
$ flake8 --max-line-length=120 strands_robots_sim   # clean

mergeable_state flipped from dirtyblocked; blocked is now branch-protection (reviews) only, not merge conflict.

The 3 review-thread replies posted earlier today still anchor correctly because the rebase preserved the substantive commits 1:1 — just rebased commit objects, no squashes / drops in the unique 3.

Copy link
Copy Markdown
Member

@cagataycali cagataycali left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review: all 3 review concerns addressed -- approving

Verified the two follow-up commits (3eeab25 fail-first guard + G1 topology fix; 93bef42 real-asset parity tests + docstring cleanup) on the branch. Each of the original threads is closed by code in this PR -- nothing deferred to a follow-up.

Verification

$ git log --oneline feat/isaac-loaders -3
93bef42 review(isaac): address PR #51 inline comments -- real-asset parity tests + docstring cleanup
3eeab25 fix(isaac): make kinematic-tree guard fail-first, fix G1 topology
f8ae3a8 feat(isaac): URDF / MJCF / USD loaders -> ProceduralRobot (closes #50)

$ pytest strands_robots_sim/isaac/tests/ -v
42 passed, 26 skipped in 0.16s

26 skipped break down as: 22 TestRobosuiteMjcfParity parametrized cases (skip cleanly when robosuite is absent), 4 TestLoadUsd / TestUsdFailureModes cases gated on pxr. Both gates mirror the existing _HAS_PXR pattern and skip cleanly on a stdlib-only host.

Thread-by-thread

Thread Concern Fix landed Verification
test_procedural_kinematic_guard.py (default behaviour, fail-first) Escape hatch via STRANDS_ISAAC_VALIDATE_KINEMATICS lets a broken robot ship silently 3eeab25 removes the env-var entirely; guard runs unconditionally on every procedural builder + every URDF / MJCF / USD loader grep -n 'STRANDS_ISAAC_VALIDATE_KINEMATICS|os.environ' strands_robots_sim/isaac/procedural.py is empty; test_guard_has_no_env_var_escape_hatch is an AST scan that fails any future re-introduction
test_procedural_g1_dof.py (defect deferred to Phase 2) The duplicate-edge defect on G1 should be fixed in this PR, not deferred 3eeab25 inserts six massless *_link bodies (l_hip_link, r_hip_link, l_ankle_link, r_ankle_link, l_elbow_link, r_elbow_link) so each 2-DOF compound joint splits across two unique (parent, child) edges. Actuated joint count stays at 21 so this DOF doc-pin keeps passing test_g1_topology_has_no_duplicate_edges passes; _build_unitree_g1 source confirms the six link bodies at procedural.py:211-216
test_loaders.py (parity tests for strands-robots robots) Need tests proving the robots we ship round-trip cleanly into Isaac 93bef42 adds TestRobosuiteMjcfParity (4 test methods x 7 robots = 22 parametrized cases + 1 aggregate) covering the exact LIBERO embodiment surface (panda, iiwa, kinova3, jaco, sawyer, ur5e, baxter). Each robot is asserted to (a) load via loaders.load_mjcf, (b) match its documented joint count, (c) have all joints classified as revolute, (d) have all body indices in range Class lives at test_loaders.py:582; _HAS_ROBOSUITE gate at :575; tests skip cleanly without robosuite, run all 22 cases when present per the author's note

Mergeability

  • MERGEABLE / BLOCKED only on review-required gate (this approval clears it)
  • call-test-lint / Test and Lint SUCCESS on the head SHA
  • No conflicting threads; the three above were the entire review surface

Good to merge once the dependency chain (PR #46) is in. Resolving the three threads on my end.

@yinsong1986 yinsong1986 merged commit 06fce4d into strands-labs:main May 28, 2026
2 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Strands Labs - Robots May 28, 2026
yinsong1986 added a commit that referenced this pull request May 28, 2026
…lit) (#48)

* docs(isaac): backend reference + Phase 1 status banner (5/5 of #31 split)

Part 5 / 5 of the split of #31 — tracked by #42. Branched off PR-1
(`pr1/isaac-foundation` -> #44); rebases cleanly onto `main` once
PR-1 merges. Parallel-mergeable with PR-4 (#47, simulation) -- this
slice has no code dependency on simulation.py.

Adds the user-facing reference doc for the Isaac backend plus the
companion test file that pins R2's "Phase 1 status" disclosure
banner:

- `docs/backends/isaac.md` (208 LOC):
  Install + Quick Start + API reference + environment variables
  + R2's Phase 1 status banner before the Installation section.
  The banner discloses that the Phase 1 skeleton silently no-ops
  the data plane (`add_robot` procedural branch, `_load_usd_robot`,
  `_load_urdf_robot`, `add_object`, `add_camera`, `replicate` all
  return `status: "success"` without instantiating the underlying
  USD prim or articulation handle, with the observable downstream
  effect that `get_observation()` returns `{}` and `render()`
  returns blank frames). R2 reviewer asked for this explicitly so
  a user following the Quick Start sees the disclaimer before the
  silent path.

- `strands_robots_sim/isaac/tests/test_phase1_doc_banner.py`
  (~75 LOC, 3 tests):
  Carved out of cagataycali's original
  `test_phase1_doc_honesty.py` -- the `TestIsaacDocsPhase1Banner`
  class only. The companion `TestG1DOFCount` class lives in PR-3
  (#46) alongside `procedural.py`. Pin tests:
  - `test_isaac_docs_file_exists`: truth-source pin for the doc
    file's location.
  - `test_phase1_banner_present_before_installation`: pins that
    "Phase 1 status" appears AND precedes the `## Installation`
    heading.
  - `test_phase1_banner_names_the_silent_methods`: pins that the
    banner enumerates `add_robot`, `replicate`, `get_observation`
    by name (so a future maintainer who reads only the banner
    knows which API surfaces are affected).

CI signal: lint clean (black / isort / flake8); the 3 doc-banner
pin tests pass standalone (`pytest test_phase1_doc_banner.py -v` ->
3 passed).

Why split `test_phase1_doc_honesty.py` into G1-DOF (PR-3) + doc-
banner (this PR): keeps each parallel-mergeable slice CI-green
standalone. Shipping the original combined file in either PR would
couple them (PR-3 would CI-red until PR-5 lands, or vice versa).
The combined coverage equals the original file.

Original work by @cagataycali in #31 (`413ff15..85e180f`); this
slice cherry-picks `docs/backends/isaac.md` plus the carved-out
banner test file. R2's banner addition (commit `85e180f`) is
already baked into this doc.

* docs(isaac): align with PR #47 + PR #51 merged to main

Updates docs/backends/isaac.md to reflect the actual state of
strands_robots_sim.isaac after PR #44 / #45 / #46 / #47 / #51 all
landed. The Phase 1 status banner had become stale (claimed all data-
plane methods were silent no-op when in fact the procedural builders
work, the loaders module ships, and several SimEngine methods now
have concrete implementations).

Three substantive changes:

1. Phase 1 status banner — split into 'Working today' (config,
   is_available, lifecycle, procedural builders, loaders module) vs
   'Still no-op' (add_object, add_camera, replicate, the per-class
   _load_usd_robot / _load_urdf_robot stubs, articulation-touching
   paths under get_observation / send_action / render). The loaders
   module is the working URDF / MJCF / USD ingestion path today; the
   IsaacSimulation private no-op stubs remain Phase 2 work.

2. New 'Loading External Description Files (URDF / MJCF / USD)'
   section between Procedural Robots and Comparison with Newton.
   Documents the loaders module (PR #51): three load_*() functions
   producing ProceduralRobot dataclass instances, shared failure
   semantics (FileNotFoundError / ValueError, never silent phantom
   robot), and the parity-test pin against the seven robosuite-
   bundled MJCFs the strands-robots LIBERO adapter consumes.

3. Procedural Robots — added the G1 intermediate-link-bodies
   explanation and the fail-first kinematic-tree guard contract
   (PR #51's  commit). No env-var escape hatch.

Plus housekeeping in the Architecture file-list and Testing section:

- Architecture: added _install.py (PR #47's commit  —
  centralised install hints), loaders.py (PR #51), and the actual
  test files now in main (test_get_observation_diagnostic_logs.py,
  test_procedural_g1_dof.py, test_procedural_kinematic_guard.py,
  test_loaders.py) instead of the placeholder list.

- Testing: pytest invocations updated to cover the five no-GPU test
  files now shipped, plus a one-liner for running them all at once
  (the --ignore pattern that the hatch script uses).

The doc was already accurate about the *direction* of Phase 1 vs
Phase 2; the update just brings the surface lists in line with what
actually shipped.

Verification:
- pytest strands_robots_sim/isaac/tests/test_phase1_doc_banner.py
  → 3 passed (the banner-content pin still matches; the rewrite
    keeps 'Phase 1 status' + the same enumerated method names).
- diff stat: docs/backends/isaac.md, +51 / -7 LOC.

This change lands inside PR #48 (5/5 of #31 split) since it's the
canonical home for docs/backends/isaac.md updates. Top-level README
and examples/MIGRATION.md drift (Stage-3-no-longer-future framing)
is a separate doc-alignment concern and lands in a follow-up PR
rather than widening this slice.
yinsong1986 added a commit that referenced this pull request May 28, 2026
Top-level docs claimed Isaac would 'land in v0.3.0+' as future work,
but PR #44 / #45 / #46 / #47 / #51 (R6 + R7 Phase 1) all merged to
main on 2026-05-26. Updates the doc-side framing to reflect what's
actually shipped vs what's still pending.

README.md:

1. Status banner: 'Backend code lands in v0.3.0+ (Isaac)' →
   explicit Phase 1 / Phase 2 split. Names what works today (entry-
   point registration, IsaacConfig, lifecycle scaffolding,
   procedural builders, URDF / MJCF / USD loaders) vs what's still
   no-op (add_object, add_camera, replicate, per-IsaacSimulation
   _load_*_robot stubs).

2. Quick-start preamble: 'become copy-paste-runnable when R6/#13
   and R7/#14 ship' → 'Isaac Sim Phase 1 has shipped — the snippet
   below is copy-paste-runnable today on a host with Isaac Sim
   2024.x+ installed'. Newton snippets remain gated on R11/#18.

3. Isaac Sim quick-start snippet: fixed the kwarg name
   (rtx_mode='path_traced' → render_mode='rtx_pathtracing') so
   it matches the IsaacConfig field actually defined in
   strands_robots_sim/isaac/config.py. The previous spelling
   would TypeError at construction. Added a second snippet showing
   the loaders module (load_urdf / load_mjcf / load_usd) since
   that's the working URDF / MJCF / USD ingestion path today.

4. Status & roadmap section:
   - Stage 2 R5: [ ] → [x] (#26 merged).
   - Stage 3 R6: [ ] → [x] with link to #44.
   - Stage 3 R7: split into 'Phase 1 [x]' (linking #45 / #46 /
     #47 / #51) and 'Phase 2 [ ]' so the next-up scope is visible
     without dropping the historical #14 reference.

examples/MIGRATION.md:

1. [isaac] extras line: 'Heavy GPU-only backends ship in later 0.x
   releases' / commented-out pip install → uncommented; comment
   notes 'Phase 1 shipped; data-plane in Phase 2'. Newton stays
   commented (Stage 4 pending).

2. 'After — same task on Isaac Sim' heading: 'Stage 3, future' →
   'Stage 3, Phase 1 shipped'. Snippet kwarg fixed
   (rtx_mode → render_mode; same bug as the README). Added a
   2-line comment noting evaluate_benchmark on a real LIBERO
   scene needs Phase 2 data-plane wiring (matches the disclosure
   banner on docs/backends/isaac.md, so the three doc surfaces
   stay in sync).

examples/README.md is left as-is: its TBD rows reference the
example files (libero/run_isaac.py etc.), which haven't shipped
yet — those issue links (R8/#15, R23/#27) are still accurate
forward-pointers.

Verification:
- diff stat: 2 files, +28 / -14 LOC. No code touched.
- README + MIGRATION render cleanly under standard markdown
  parsers (verified by spot-checking the table / heading nesting).
- The doc-banner test (test_phase1_doc_banner.py) is on the
  parallel pr5/isaac-docs branch (PR #48); the docstring banner
  on docs/backends/isaac.md was already updated there to the same
  framing — this PR brings README + MIGRATION in line.

Cross-references: PR #36 (the previous examples/README.md drift
fix) lands the same kind of post-merge alignment for #26's R5
work; this is its R7 Phase 1 sibling.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[isaac] R7 Phase 2 follow-up: load ProceduralRobot from URDF / MJCF / USD instead of hardcoded builders

2 participants