[Newton] Add MuJoCo tendon support to Newton physics replication#5433
[Newton] Add MuJoCo tendon support to Newton physics replication#5433hujc7 wants to merge 1 commit intoisaac-sim:developfrom
Conversation
ea456bc to
500ab8a
Compare
500ab8a to
99f7d7b
Compare
There was a problem hiding this comment.
🤖 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)
SchemaResolverMjcvalidation 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.
|
@greptileai Review |
Greptile SummaryThis PR adds MuJoCo tendon support to Newton physics replication by calling Confidence Score: 5/5Safe to merge — logic is sound, well-commented, and backed by 16 new unit tests; only minor P2 documentation and test-style suggestions remain. No P0 or P1 findings. The previous thread concern about startswith path-prefix false-positives is already addressed in the current code with the trailing-slash guard. Remaining findings are documentation and defensive-coding style, both P2. No files require special attention. The test helpers in test_rename_builder_labels.py have a minor inconsistency with production defensive coding for constraint_mimic attributes. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["newton_physics_replicate()"] --> B["_build_newton_builder_from_mapping()"]
B --> C["Create main builder\n(no register_custom_attributes)"]
C --> D["main_builder.add_usd(\n ignore_paths=['/World/envs'] + sources\n)"]
B --> E["For each src_path in sources"]
E --> F["Create proto builder p"]
F --> G["SolverMuJoCo.register_custom_attributes(p)\nregisters mujoco:* custom frequencies"]
G --> H["_scope_custom_frequencies(p, src_path)\npatches usd_prim_filter with prefix+'/'\nfor cross-source isolation"]
H --> I["p.add_usd(stage, root_path=src_path)\ntraversal scoped to src_path only"]
I --> J["protos[src_path] = p"]
J --> E
B --> K["For each env col\nbegin_world / add_builder / end_world"]
K --> L["N x T tendon entries in main builder\n(N envs x T tendons per proto)"]
L --> M["Return builder"]
M --> N["_rename_builder_labels()\nPass 1: built-in label arrays\nPass 2: string custom-attr columns\nwith references=world companion"]
N --> O["NewtonManager.set_builder(builder)"]
Reviews (3): Last reviewed commit: "Apply ruff format to test_rename_builder..." | Re-trigger Greptile |
| continue | ||
| orig = freq.usd_prim_filter |
There was a problem hiding this comment.
Path-prefix false-positive in scope filter
startswith(_path) without a trailing / will match sibling USD paths that share a common string prefix. For example, if root_path="/World/src", a MjcTendon prim at /World/src2/tendon_coupling would incorrectly pass the filter, causing that prim to be resolved against the wrong proto's joint_label — exactly the zombie-tendon-entry problem the filter is meant to prevent.
The fix is to append "/" to the prefix check:
| continue | |
| orig = freq.usd_prim_filter | |
| freq.usd_prim_filter = lambda prim, ctx, _orig=orig, _path=root_path: ( | |
| str(prim.GetPath()).startswith(_path + "/") and _orig(prim, ctx) | |
| ) |
| """Plain builder has no MJC frequencies; _scope_custom_frequencies must not raise.""" | ||
| b = _plain_builder() | ||
| _scope_custom_frequencies(b, self.ROOT_A) | ||
|
|
||
| def test_heterogeneous_plan_no_cross_contamination(self): | ||
| """Each proto only accumulates tendon entries from its own source subtree. | ||
|
|
||
| Simulates the heterogeneous scenario: two proto builders, each scoped to | ||
| a different source path. After both receive tendon entries representing | ||
| their own prims (as the scoped traversal would inject), neither contains | ||
| entries from the other source. | ||
| """ |
There was a problem hiding this comment.
Test gap: path-prefix ambiguity not covered
TestScopeMjcTendonFilters tests a path from ROOT_B being correctly rejected by a filter scoped to ROOT_A, but both roots are chosen so neither is a string prefix of the other (source_a / source_b). The latent bug in the production filter — where startswith without a trailing "/" also matches a sibling path like /World/envs/env_0/source_aa/tendon when scoped to /World/envs/env_0/source_a — is not exercised. A test like the following would expose the issue:
def test_filter_rejects_prim_at_sibling_with_shared_string_prefix(self):
b = self._scoped_builder("/World/envs/env_0/source_a")
prim = _FakePrim("/World/envs/env_0/source_aa/tendon")
freq = b.custom_frequencies[_TENDON_FREQ]
self.assertFalse(freq.usd_prim_filter(prim, {}))There was a problem hiding this comment.
🤖 Isaac Lab Review Bot
Summary
Previous concern about shared resolver instances remains unaddressed. The code is identical to the last review — proto_resolvers is still instantiated once at line 97 and shared across all proto builders.
Implementation Verdict
Minor fixes needed — Move proto_resolvers = [SchemaResolverMjc(), SchemaResolverNewton(), SchemaResolverPhysx()] inside the for src_path in sources: loop (after line 103) to ensure each proto builder gets fresh resolver instances, or add a comment confirming Newton resolvers are stateless.
CI Status
Pre-commit is failing — this needs to be resolved before merge.
There was a problem hiding this comment.
🤖 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.
|
@greptileai Review |
There was a problem hiding this comment.
🤖 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.
a6a2a7c to
8a39a8f
Compare
There was a problem hiding this comment.
🤖 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.
There was a problem hiding this comment.
🤖 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.
There was a problem hiding this comment.
🤖 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.
There was a problem hiding this comment.
🤖 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.
There was a problem hiding this comment.
🤖 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.
There was a problem hiding this comment.
🤖 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.
There was a problem hiding this comment.
🤖 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.
80ed8df to
6cea8f7
Compare
There was a problem hiding this comment.
🤖 Isaac Lab Review Bot
Summary
Follow-up review on commit 6cea8f71. No code changes since the previous review — the implementation remains correct and well-tested. The MuJoCo tendon support for Newton physics replication is ready to ship.
CI Status
Several CI jobs are failing but appear unrelated to this PR:
Check changelog fragments: failure— likely needs a changelog fragment file (standard PR process issue)Installation Tests: failure— needs investigation but not caused by this PR's changes- Pre-commit passed ✅
Implementation Verdict
Ship it — pending CI infrastructure fixes and changelog fragment addition.
6cea8f7 to
70302de
Compare
There was a problem hiding this comment.
🤖 Isaac Lab Review Bot
Summary
Follow-up review on commit 70302def. The changelog fragment has been added (jichuanh-newton-replicate-tendon-fix.rst), addressing the previous CI concern about missing changelog fragments. No functional code changes since the last review — the implementation remains correct and comprehensive.
CI Status
Check changelog fragments: success✅ — Previous concern addressedCheck for Broken Links: failure❌ — Unrelated to this PR (likely a docs infrastructure issue)- Most jobs still pending; pre-commit passed ✅
Implementation Verdict
Ship it — The changelog is well-written with clear "Added" and "Fixed" sections that accurately describe the changes. Ready to merge once CI passes.
70302de to
d8383ad
Compare
There was a problem hiding this comment.
🤖 Isaac Lab Review Bot
Summary
Follow-up review on commit d8383ad0. No code changes since the last review — this appears to be a CI re-run or merge commit. The implementation remains correct and comprehensive.
CI Status
Check changelog fragments: success✅pre-commit: success✅Build Wheel: success✅Check for Broken Links: failure❌ — Unrelated to this PR (docs infrastructure issue)- Most other jobs still pending
Implementation Verdict
Ship it — All previous concerns have been addressed. The changelog is present and accurate, the implementation is sound, and tests are thorough. Ready to merge once remaining CI jobs pass.
d8383ad to
54d0eca
Compare
There was a problem hiding this comment.
🤖 Isaac Lab Review Bot
Summary
Follow-up review on commit 54d0eca5. No substantive code changes since the previous review — the implementation remains correct and well-tested. All previous concerns were addressed in earlier iterations.
CI Status
All jobs still pending. Pre-commit and Build Wheel passed previously; waiting on isaaclab_newton tests which are the relevant jobs for this PR.
Implementation Verdict
Ship it — Ready to merge once CI completes successfully.
The Newton 1.2 release includes the upstream tendon-scoping fix (newton-physics/newton#2659), which scopes ``parse_usd``'s custom- frequency walk to ``root_path`` natively. Bumping the Newton pin removes the cross-source ``MjcTendon`` contamination IsaacLab previously had to work around at the framework layer. Pin bumps: * ``source/isaaclab/setup.py``: ``warp-lang==1.12.0`` → ``>=1.13.0``, ``mujoco==3.6.0`` → ``==3.8.0``, ``mujoco-warp==3.6.0`` → ``==3.8.0.1``. * ``source/isaaclab_newton/setup.py``: same mujoco / mujoco-warp bumps; Newton pin → ``v1.2.0rc1``. * ``source/isaaclab_visualizers/setup.py``: 3 × Newton pin → ``v1.2.0rc1``. Code adapts: * ``source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py`` and ``source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer_kernels.py``: ``wp.math.transform_to_matrix`` → ``wp.transform_to_matrix``. The ``wp.math`` namespace was removed in ``warp-lang`` 1.13. Per-env label rename: * ``_rename_builder_labels`` now walks string-typed custom-attribute columns whose frequency declares a ``references="world"`` companion (e.g. ``mujoco:tendon_label``), rewriting the per-row source-path prefix to the destination world root. ``constraint_mimic`` joins the built-in label pass. The prefix match uses a path-separator boundary so source paths that share a string prefix (``/Sources/protoA`` vs ``/Sources/protoAB``) do not cross- contaminate. Newton's ``add_builder`` copies labels verbatim and tracks env identity in ``*_world`` int columns; the IL rename is the structural translation between Newton's labeling model and IL's per-env USD-path model. Both passes (built-in label arrays and string custom-attribute columns) remain necessary after the Newton bump. Adds ``test_rename_builder_labels.py`` with 10 cases covering the two passes, multi-source prefix-overlap regression, sparse env ids, multi-frequency, multiple string columns at one frequency, and empty-values pass-through.
54d0eca to
fce4ebd
Compare
There was a problem hiding this comment.
🤖 Isaac Lab Review Bot
Summary
Follow-up review on commit fce4ebdf. The diff shows the same implementation as the previous review - no new changes since the last "Ship it" verdict. The dependency bumps (Newton v1.2.0rc1, warp-lang>=1.13.0, mujoco 3.8.0), the _rename_builder_labels fix with boundary-terminated prefix matching, the wp.transform_to_matrix API update, and the comprehensive test suite are all unchanged.
CI Status
Most jobs still pending. Pre-commit passed ✅. The "Check changelog fragments" failure needs attention - verify the changelog fragment format meets project requirements (the .rst extension and location look correct, but the CI may have specific validation rules).
Implementation Verdict
Ship it — Code is ready. Address the changelog fragment CI failure (likely a formatting or validation issue) and merge once remaining CI jobs pass.
Summary
SchemaResolverMjcto proto-builderschema_resolverssoMjcTendonprims (e.g. Shadow Hand fixed tendons) are parsed correctly during Newton physics replication.SchemaResolverMjcon the main builder caused two interlocking failures:SchemaResolverMjc.validate_custom_attributes()raises unlessSolverMuJoCo.register_custom_attributes()is called first.register_custom_attributesregisters MJC custom frequencies, triggering Newton's stage-wide traversal (independent ofignore_paths). On the main builder, this traversal findsMjcTendonprims under/World/envs/...and tries to resolve their joint paths against the main builder's emptyjoint_label, producing 128 "unknown joint path" warnings and silently dropping all tendons.SchemaResolverMjcfrom the main builder'sschema_resolvers. The main builder loads only scene-level prims (ground, lights) with noMjcTendonprims. Proto builders include it withregister_custom_attributescalled first, resolving tendons correctly against each proto's populatedjoint_label.add_builderthen propagates N×T entries across N environments — handled natively bySolverMuJoCoviatendon_worldfiltering.Test plan
test/cloner/test_mjc_tendon_cloner.pypass (no Isaac Sim required)tendon_worldattributeadd_buildercalls is Newton's expected stateSchemaResolverMjc.validate_custom_attributesraises/passes appropriatelySolverMuJoCono longer produces "unknown joint path" warnings at startup