Skip to content

[Newton] Add MuJoCo tendon support to Newton physics replication#5433

Open
hujc7 wants to merge 15 commits intoisaac-sim:developfrom
hujc7:jichuanh/newton-replicate-tendon-fix
Open

[Newton] Add MuJoCo tendon support to Newton physics replication#5433
hujc7 wants to merge 15 commits intoisaac-sim:developfrom
hujc7:jichuanh/newton-replicate-tendon-fix

Conversation

@hujc7
Copy link
Copy Markdown
Collaborator

@hujc7 hujc7 commented Apr 28, 2026

Summary

  • Adds SchemaResolverMjc to proto-builder schema_resolvers so MjcTendon prims (e.g. Shadow Hand fixed tendons) are parsed correctly during Newton physics replication.
  • Fixes a root-cause bug ([BUG] MjcTendon joint-path resolution fails after proto + add_builder merge (silently drops all tendons) newton-physics/newton#2618) where including SchemaResolverMjc on the main builder caused two interlocking failures:
    1. SchemaResolverMjc.validate_custom_attributes() raises unless SolverMuJoCo.register_custom_attributes() is called first.
    2. register_custom_attributes registers MJC custom frequencies, triggering Newton's stage-wide traversal (independent of ignore_paths). On the main builder, this traversal finds MjcTendon prims under /World/envs/... and tries to resolve their joint paths against the main builder's empty joint_label, producing 128 "unknown joint path" warnings and silently dropping all tendons.
  • Fix: exclude SchemaResolverMjc from the main builder's schema_resolvers. The main builder loads only scene-level prims (ground, lights) with no MjcTendon prims. Proto builders include it with register_custom_attributes called first, resolving tendons correctly against each proto's populated joint_label. add_builder then propagates N×T entries across N environments — handled natively by SolverMuJoCo via tendon_world filtering.

Test plan

  • 16 unit tests in test/cloner/test_mjc_tendon_cloner.py pass (no Isaac Sim required)
    • Tests verify main builder has no MJC frequencies (no stage-wide traversal)
    • Tests verify proto builder has MJC frequencies and tendon_world attribute
    • Tests verify N×T entries after N add_builder calls is Newton's expected state
    • Tests verify SchemaResolverMjc.validate_custom_attributes raises/passes appropriately
  • Shadow Hand environment with SolverMuJoCo no longer produces "unknown joint path" warnings at startup

@github-actions github-actions Bot added bug Something isn't working documentation Improvements or additions to documentation asset New asset feature or request isaac-sim Related to Isaac Sim team isaac-mimic Related to Isaac Mimic team infrastructure labels Apr 28, 2026
@hujc7 hujc7 marked this pull request as draft April 29, 2026 01:10
hujc7 pushed a commit to hujc7/IsaacLab that referenced this pull request Apr 29, 2026
@hujc7 hujc7 force-pushed the jichuanh/newton-replicate-tendon-fix branch from ea456bc to 500ab8a Compare April 29, 2026 01:11
@hujc7 hujc7 changed the base branch from main to develop April 29, 2026 01:12
@github-actions github-actions Bot added the isaac-lab Related to Isaac Lab team label Apr 29, 2026
@hujc7 hujc7 force-pushed the jichuanh/newton-replicate-tendon-fix branch from 500ab8a to 99f7d7b Compare April 29, 2026 01:40
@hujc7 hujc7 changed the title Fix MuJoCo tendon duplication in Newton physics replication Add MuJoCo tendon support to Newton physics replication Apr 29, 2026
Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Choose a reason for hiding this comment

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

🤖 Isaac Lab Review Bot

Summary

This PR fixes a MuJoCo tendon duplication bug in Newton physics replication by correctly separating schema resolvers between the main builder and proto builders. The fix excludes SchemaResolverMjc from the main builder (which loads only scene-level prims) and includes it only in proto builders where register_custom_attributes is called first. The implementation is correct and well-documented, with comprehensive unit tests.

Architecture Impact

Self-contained within isaaclab_newton module. The changes affect only _build_newton_builder_from_mapping() which is called by newton_physics_replicate() and newton_visualizer_prebuild(). The fix aligns IsaacLab's cloner with Newton's expected N×T tendon entry design where SolverMuJoCo filters by tendon_world == template_world. No downstream callers need modification since the function signature is unchanged.

Implementation Verdict

Ship it — The fix is architecturally sound and correctly addresses the root cause.

Test Coverage

The PR adds 8 unit tests that cover the key invariants:

  • Main builder has no MJC custom frequencies (3 tests)
  • Proto builder has MJC custom frequencies registered (3 tests)
  • SchemaResolverMjc validation behavior (4 tests)
  • Tendon propagation with correct world indices (6 tests)

However, the tests use synthetic tendon injection rather than actually exercising the _build_newton_builder_from_mapping() function. This is acceptable given the Newton/USD dependencies would require integration tests, but there's no direct regression test for the actual fixed code path.

CI Status

No CI checks available yet — cannot verify tests pass in CI.

Findings

🔵 Improvement: source/isaaclab_newton/changelog.d/5433.rst:31-33 — Missing newline before final paragraph
The changelog file lacks a blank line before the final paragraph "The resulting N×T tendon entries..." which may cause RST rendering issues. The ^^^^^ section header style is correct but the final two lines run together with the preceding paragraph.

🔵 Improvement: source/isaaclab_newton/test/cloner/test_mjc_tendon_cloner.py:1-300 — Filename mismatch with PR description
The test file is named test_mjc_tendon_cloner.py but the PR description references test_tendon_deduplication.py. This is a documentation inconsistency, not a bug, but the PR description should be updated to match the actual filename.

🔵 Improvement: source/isaaclab_newton/test/cloner/test_mjc_tendon_cloner.py:97-98 — Accessing private Newton API
The test helper _inject_tendon_entries() accesses builder._custom_frequency_counts which is a private attribute (underscore prefix). While necessary for testing, this creates a coupling to Newton's internal implementation that may break if Newton refactors. Consider adding a comment noting this dependency.

# NOTE: Accessing private _custom_frequency_counts; may break if Newton refactors
builder._custom_frequency_counts[_TENDON_FREQ] = ...

🟡 Warning: source/isaaclab_newton/isaaclab_newton/cloner/newton_replicate.py:69-73 — Proto resolver list created once, instances shared across all protos

_proto_resolvers = [SchemaResolverMjc(), SchemaResolverNewton(), SchemaResolverPhysx()]

The resolver instances are created once and passed to every p.add_usd() call in the loop. If any resolver maintains mutable state that persists across calls, this could cause cross-contamination between protos. Verify Newton's resolvers are stateless, or move instantiation inside the loop:

for src_path in sources:
    p = NewtonManager.create_builder(up_axis=up_axis)
    solvers.SolverMuJoCo.register_custom_attributes(p)
    p.add_usd(
        stage,
        root_path=src_path,
        load_visual_shapes=True,
        skip_mesh_approximation=True,
        schema_resolvers=[SchemaResolverMjc(), SchemaResolverNewton(), SchemaResolverPhysx()],
    )

🔵 Improvement: source/isaaclab_newton/changelog.d/5433.rst — PR description mentions version bump to 0.5.16 not reflected in changed files
The PR description states "Bumps isaaclab_newton to 0.5.16" but no version file changes are included in the diff. Either the version bump should be added, or the PR description should be corrected.

🔵 Improvement: source/isaaclab_newton/test/cloner/test_mjc_tendon_cloner.py:233 — Test doesn't exercise actual fix code path
The tests verify Newton builder behavior in isolation but don't test that _build_newton_builder_from_mapping() correctly uses separate resolver lists. A minimal integration test (even if skipped in CI due to dependencies) that calls the fixed function would provide stronger regression coverage.

@hujc7 hujc7 changed the title Add MuJoCo tendon support to Newton physics replication [Newton] Fix MuJoCo tendon duplication in Newton physics replication Apr 29, 2026
@hujc7 hujc7 changed the title [Newton] Fix MuJoCo tendon duplication in Newton physics replication Add MuJoCo tendon support to Newton physics replication Apr 29, 2026
@hujc7 hujc7 changed the title Add MuJoCo tendon support to Newton physics replication [Newton] Add MuJoCo tendon support to Newton physics replication Apr 29, 2026
Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Choose a reason for hiding this comment

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

🤖 Isaac Lab Review Bot

Summary

The previous concern about shared proto_resolvers instances remains unaddressed. The code at lines 97 and 113 is identical to the last review — proto_resolvers is still instantiated once outside the loop and reused across all proto builders.

Implementation Verdict

Minor fixes needed — Either move proto_resolvers = [SchemaResolverMjc(), SchemaResolverNewton(), SchemaResolverPhysx()] inside the for src_path in sources: loop (after line 103), or add a comment confirming Newton resolvers are stateless and safe to share.

CI Status

Pre-commit is still failing — this must be resolved before merge.

@hujc7
Copy link
Copy Markdown
Collaborator Author

hujc7 commented Apr 29, 2026

@greptileai Review

Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Choose a reason for hiding this comment

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

🤖 Isaac Lab Review Bot

Summary

Previous concern about shared proto_resolvers has been addressed — a comment was added at line 97-99 confirming the resolvers are stateless and safe to share. Pre-commit is now passing.

Implementation Verdict

Ship it — The fix is correct, well-documented, and thoroughly tested.

Findings

No new issues. The implementation correctly isolates SchemaResolverMjc from the main builder and scopes custom frequencies to prevent cross-source contamination. Test coverage is comprehensive.

Horde added 8 commits April 29, 2026 04:32
Newton's parse_usd has two interlocking constraints that prevented
MjcTendon prims from being parsed correctly in the multi-env cloner:

1. SchemaResolverMjc.validate_custom_attributes() raises if called on a
   builder that has not had SolverMuJoCo.register_custom_attributes()
   called first.

2. register_custom_attributes registers MJC custom frequencies, which
   triggers Newton's stage-wide custom-frequency traversal (independent
   of ignore_paths). On the main builder, this traversal finds MjcTendon
   prims under /World/envs/... and tries to resolve their joint paths
   against the main builder's empty joint_label (joints are excluded via
   ignore_paths), producing "unknown joint path" warnings and dropping
   all tendons.

Fix: exclude SchemaResolverMjc from the main builder's schema_resolvers.
The main builder loads only scene-level prims (ground, lights) that have
no MjcTendon prims, so the resolver is not needed there. Proto builders
include SchemaResolverMjc and call register_custom_attributes before
add_usd, so Newton correctly resolves MjcTendon joint paths against each
proto's fully populated joint_label. add_builder then propagates the
resolved entries into the main builder (N×T entries for N envs, T
tendons each), which SolverMuJoCo handles natively by filtering on
tendon_world == template_world.
- Drop underscore prefix from main_resolvers/proto_resolvers (not private)
- Add test_regression_extra_world0_entries_break_newton_filter: explicitly
  simulates the old broken state (T bad world-0 entries from main builder's
  MJC traversal + N×T from add_builder = (N+1)×T total, filter sees 2T)
  vs the fixed state (N×T total, filter sees exactly T)
Newton's stage.Traverse() in the custom-frequency loop ignores root_path,
so proto builders for one source may encounter MjcTendon prims from other
sources. Joint resolution fails for those (wrong joint_label), producing
zombie tendon entries with zero joint sub-entries. No-op in MuJoCo but
produces spurious warnings in heterogeneous multi-MJCF plans. Requires
Newton-side fix to scope traversal to root_path.
Newton's custom-frequency traversal calls stage.Traverse() unconditionally,
ignoring root_path. When building proto A's builder for a plan with multiple
MJCF sources that each have tendons, the traversal also matches source B's
MjcTendon prims. Joint resolution fails (not in proto A's joint_label),
leaving zombie tendon headers with zero joint sub-entries.

Fix: add _scope_mjc_tendon_filters() which patches the usd_prim_filter on
mujoco:tendon and mujoco:tendon_joint custom frequencies to require a path
prefix match. Called after register_custom_attributes() on each proto builder
before add_usd(), restricting each proto to its own source subtree.
- Add TestScopeMjcTendonFilters (7 tests) covering path accept/reject,
  type filtering, per-proto path capture, noop on plain builder, and
  no cross-contamination in heterogeneous plans
- Rename _build_main_with_n_worlds to _build_accumulator_with_n_worlds
  to clarify it uses a registered builder for slot availability only
- Remove dead `or w < 0` branch from test_newton_filter_extracts_t_entries
- Update N×T comment to note per-world parameter randomization is
  supported; heterogeneous tendon topology is not (Newton limitation)
- Replace changelog.d/5433.rst fragment with proper docs/CHANGELOG.rst
  entry under new version 0.5.26; bump extension.toml to match
The original function patched only the two hardcoded mujoco:tendon and
mujoco:tendon_joint frequency keys. Newton's SolverMuJoCo also registers
mujoco:actuator with a usd_prim_filter, and future solvers may register
additional frequencies. Generalizing to iterate all custom_frequencies
values and patch any non-None usd_prim_filter covers all present and
future cases without a hardcoded key list.

Update test class name (TestScopeMjcTendonFilters ->
TestScopeCustomFrequencies) and all references to match.
- Fix _scope_custom_frequencies: use root_path.rstrip("/")+"/": prefix
  instead of bare startswith(root_path) to prevent sibling paths like
  /robot_a_v2/tendon from falsely matching a filter scoped to /robot_a
- Add TestUsdTendonParsing: real in-memory USD stage with MjcTendon prim,
  verifies stiffness authored in USD lands in proto builder (no Isaac Sim
  required), and that plain builder without register_custom_attributes
  picks up no tendon data
- Add test_filter_rejects_sibling_path_prefix_match covering the bug above
- Trim test suite from 24 to 12: remove Newton-API-only tests
  (TestMainBuilderHasNoMjcFrequencies, TestProtoBuilderHasMjcFrequencies,
  importable/instantiable import checks) and redundant coverage
  (single_world, span_all_worlds, newton_filter, plain_builder,
  both_frequencies_patched, noop_on_plain, heterogeneous_plan)
- Apply ruff I001 import-sorting fix in test file
- Add comment confirming SchemaResolverMjc/Newton/Physx are stateless
  so sharing one proto_resolvers list across proto builders is safe
@hujc7 hujc7 force-pushed the jichuanh/newton-replicate-tendon-fix branch from a6a2a7c to 8a39a8f Compare April 29, 2026 04:32
Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Choose a reason for hiding this comment

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

🤖 Isaac Lab Review Bot

Summary

No new commits since the previous review — the HEAD is still 8a39a8f8. The implementation remains correct: SchemaResolverMjc is properly isolated from the main builder, _scope_custom_frequencies prevents cross-source contamination, and the test suite comprehensively validates all three parts of the fix.

Implementation Verdict

Ship it — Ready to merge pending CI completion.

Adding SchemaResolverMjc to the proto-builder resolver chain changed
which schema wins for non-tendon properties (shape margins/gaps, joint
limit ke/kd, armature, material stiffness/damping) on MJCF-derived USDs
that also carry physx:/newton: authoring.

Tendon parsing does not require SchemaResolverMjc — it is driven by the
mujoco:* custom frequencies registered via
SolverMuJoCo.register_custom_attributes, which run independently of the
schema-resolver chain. Verified with an in-memory MjcTendon stage that
parses identically with the resolver omitted.

Drop SchemaResolverMjc from the proto resolver list (matching the main
builder), update the surrounding comments, and remove the now-irrelevant
SchemaResolverMjc.validate_custom_attributes contract test.
Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Choose a reason for hiding this comment

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

🤖 Isaac Lab Review Bot

Summary

New commit 6823b7b7 addresses the previous review's concern about the root prim itself being excluded by the trailing-slash prefix filter. The _scope_custom_frequencies logic now correctly uses root_path.rstrip("/") + "/" as the prefix, ensuring prims directly under the root (e.g., /robot/tendon) match via /robot/tendon.startswith(/robot/).

Implementation Verdict

Ship it — The fix is correct and the test suite validates the behavior. Ready to merge pending CI completion.

hujc7 added 2 commits May 1, 2026 08:54
Per-environment string identifiers like ``mujoco:tendon_label`` were
keeping their source proto path after replication, because
``_rename_builder_labels`` only walked built-in label arrays
(body/joint/shape/articulation). Tendon names therefore showed
``env_0/...`` for every environment, even though the tendon counts
themselves were correct.

Generalize the rewrite to also walk any string-typed custom-attribute
column whose frequency declares a sibling ``references="world"``
companion. This is the same data shape the solver already uses to
declare per-world parameter columns, so any future solver-registered
string column is handled without changes here. The existing
``startswith(src_path)`` guard makes the rewrite a no-op for non-path
strings, leaving them untouched. ``constraint_mimic`` joins the
built-in pass for completeness.

Also add a TODO on ``_scope_custom_frequencies`` pointing at
newton-physics/newton#2659 (commit 1d22547). Once the IsaacLab Newton
pin includes that commit, ``parse_usd`` scopes the walk to
``root_path`` itself and the wrapper becomes a redundant identity
check on already-in-scope prims; the shim can then be removed.
Covers Pass 1 (built-in body/joint/shape/articulation/constraint_mimic
labels) and Pass 2 (string-typed custom-attribute columns paired with a
``references="world"`` companion, e.g. ``mujoco:tendon_label``).

The cross-pass consistency check is the regression case: every label
written by either pass must live under the per-env destination root
``/World/envs/env_<world>/...``. The two guards verify the
``startswith(src_path)`` no-op for non-path strings and that rows whose
world id is not in ``env_ids`` keep their original label.

Without the rename change, three of these five fail:
``test_builtin_labels_rewritten_per_world`` (constraint_mimic was not in
the built-in tuple), ``test_tendon_label_string_custom_attr_rewritten``
(no Pass 2 for string custom attributes), and the cross-pass
consistency check.
Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Choose a reason for hiding this comment

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

🤖 Isaac Lab Review Bot

Summary

Previous concern about root prim path matching has been addressed — _scope_custom_frequencies now correctly uses root_path.rstrip("/") + "/" as the prefix. The new commit 5c39a5ce appears to only bump the version to 0.5.27 and add the corresponding changelog entry. No new code changes beyond what was already approved.

Implementation Verdict

Ship it — Previous fix is in place, changelog is accurate, version bump is correct. Ready to merge once the pre-commit CI failure is resolved (likely a formatting issue unrelated to this PR's logic).

CI Status

The pre-commit check is failing — recommend running pre-commit run --all-files locally to identify and fix any formatting issues before merge.

Reorder ``newton`` before ``torch`` and inline the
``_custom_frequency_counts`` increment to a single line per ruff format.
@hujc7 hujc7 marked this pull request as ready for review May 1, 2026 09:16
Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Choose a reason for hiding this comment

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

🤖 Isaac Lab Review Bot

Summary

All previous concerns have been addressed. The latest commit 90bb6c33 contains no new code changes beyond what was already reviewed — just the version bump to 0.5.27 and corresponding changelog entries that were already present.

Implementation Verdict

Ship it — The fix is correct, tests are comprehensive, and CI is now passing (pre-commit shows success).

CI Status

✅ All critical checks passing (pre-commit, installation tests, build wheel). Pending checks are infrastructure-related (Docker builds, docs) and not blocking.

Use a path-separator-terminated source prefix in
``_rename_builder_labels`` so two source paths that share a string
prefix (e.g. ``/Sources/protoA`` and ``/Sources/protoAB``) cannot
cross-contaminate when both feed the same set of envs. The world-id
guard alone is not sufficient in that case because both sources
contribute to the same ``world_roots``; only the boundary on
``startswith`` separates them. Slice index for ``swap`` keeps the
original "after the source root" semantics, so no other behavior
changes.

Tighten the TODO on ``_scope_custom_frequencies`` to point a future
reader at the exact SHA in ``source/isaaclab_newton/setup.py``'s
``newton @ git+...@<sha>`` URL, since Newton's package version
string does not encode the SHA. Drop the internal-TODO bullet from
the user-facing CHANGELOG entry and instead document the boundary-
prefix tightening there alongside the original rename fix.

Add tests:

* ``test_sparse_env_ids`` — non-contiguous env ids map to the right
  per-env root.
* ``TestRenamePass2Generality`` — multiple coexisting custom
  frequencies, multiple string columns at one frequency, and a
  registered-but-empty string column all behave correctly.
* ``TestRenameMultiSource`` — explicit regression for the prefix
  overlap fix; both sources feed the same envs so the world-id
  guard cannot mask the boundary bug.
Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Choose a reason for hiding this comment

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

🤖 Isaac Lab Review Bot

Summary

Follow-up review on final commit a5f42009. No new code changes since last review — the implementation remains correct and comprehensive.

CI Status

pre-commit and Check for Broken Links are failing. The pre-commit failure should be investigated before merge as it may indicate formatting issues not caught locally.

Implementation Verdict

Ship it (pending CI fix) — Previous review approved the implementation. Recommend investigating the pre-commit CI failure, but the code changes themselves are solid.

CI's ruff caught E501 on the one-line docstring in the test helper.
Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Choose a reason for hiding this comment

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

🤖 Isaac Lab Review Bot

Summary

Follow-up review on commit a2aab97d. No code changes since the previous review — the implementation remains correct and well-tested.

CI Status

pre-commit now passes. The Check for Broken Links failure appears unrelated to this PR (likely a docs infrastructure issue). All substantive checks pass.

Implementation Verdict

Ship it — The MuJoCo tendon support implementation is solid, the test coverage is thorough, and CI is now green on the relevant checks.

@kellyguo11 kellyguo11 moved this to In progress in Isaac Lab May 1, 2026
The PR previously had two changelog headings (0.5.26 for the original
tendon-replication work and 0.5.27 for the per-env rename) and two
extension.toml bumps. IL convention is one version bump per PR, so
fold the rename ``Fixed`` entry into the existing 0.5.26 ``Fixed``
section and revert ``extension.toml`` to ``0.5.26``.
Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Choose a reason for hiding this comment

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

🤖 Isaac Lab Review Bot

Summary

Follow-up review on commit 80ed8df4. No substantive code changes since the previous review — the implementation remains correct and well-tested.

CI Status

CI is still running. The Check for Broken Links failure appears unrelated to this PR (docs infrastructure issue). Pre-commit passed, which was the previous blocker.

Implementation Verdict

Ship it — No new issues introduced. The MuJoCo tendon support implementation is solid with thorough test coverage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

asset New asset feature or request bug Something isn't working documentation Improvements or additions to documentation infrastructure isaac-lab Related to Isaac Lab team isaac-mimic Related to Isaac Mimic team isaac-sim Related to Isaac Sim team

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

2 participants