Skip to content

Fix joint transform for world-attached joints to use MJCF xform argument#1171

Merged
eric-heiden merged 4 commits into
newton-physics:mainfrom
eric-heiden:mjcf-world-xform
Dec 3, 2025
Merged

Fix joint transform for world-attached joints to use MJCF xform argument#1171
eric-heiden merged 4 commits into
newton-physics:mainfrom
eric-heiden:mjcf-world-xform

Conversation

@eric-heiden

@eric-heiden eric-heiden commented Dec 2, 2025

Copy link
Copy Markdown
Member

Description

Newton Migration Guide

Please ensure the migration guide for warp.sim users is up-to-date with the changes made in this PR.

  • The migration guide in docs/migration.rst is up-to date

Before your PR is "Ready for review"

  • Necessary tests have been added and new examples are tested (see newton/tests/test_examples.py)
  • Documentation is up-to-date
  • Code passes formatting and linting checks with pre-commit run -a

Summary by CodeRabbit

  • Bug Fixes

    • Corrected joint positioning and transforms when importing model files with non-identity transforms.
    • Fixed transform application for models imported with floating=False to respect the overall import transform.
  • Tests

    • Added test coverage validating transform handling consistency for joint and body states during model import operations.

✏️ Tip: You can customize this high-level summary in your review settings.

Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
…-xform-handling

Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
@coderabbitai

coderabbitai Bot commented Dec 2, 2025

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The changes modify how fixed-base joints are positioned when importing MJCF and URDF models with transforms applied. When converting floating joints to fixed-base (parent == -1), the code now computes a specialized parent_xform by combining the world_xform with the joint's local position to ensure the overall import transform is respected. Tests validate this behavior across both formats.

Changes

Cohort / File(s) Summary
Fixed-base joint transform handling
newton/_src/utils/import_mjcf.py
Replaced parent_xform source for fixed-base joints from _xform to world_xform. Added conditional logic to compute parent_xform_for_joint when parent == -1: applies world_xform * transform(joint_pos, identity) for world-parented joints to honor the import transform, while retaining body position/orientation logic for other cases.
MJCF import tests
newton/tests/test_import_mjcf.py
Added test_xform_with_floating_false to validate behavior with non-identity xform and floating=False. Verifies single FIXED joint creation, parent_xform correctness, and body state consistency after forward kinematics evaluation with quaternion sign equivalence handling.
URDF import and tests
newton/tests/test_import_urdf.py
Introduced default up_axis=Y when parsing URDF if not provided; removed hardcoded up_axis="Y" to enable explicit axis specification via kwargs. Added test_xform_with_floating_false mirroring MJCF test to verify xform application with fixed-base joints and imported warp (wp) for transform construction.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Correctness of parent_xform_for_joint computation logic and how world_xform is combined with joint-local transforms
  • Quaternion equivalence checks in tests and validation that body state matches expected transforms post-eval_fk
  • Consistency verification between MJCF and URDF implementations of the same feature
  • Ensure existing tests remain unaffected by the parent_xform source change

Possibly related PRs

Suggested reviewers

  • adenzler-nvidia

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately describes the main change: fixing how joint transforms are computed for world-attached joints to properly use the MJCF xform argument.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Dec 2, 2025

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
newton/tests/test_import_mjcf.py (1)

1091-1099: Consider extracting quaternion comparison to a helper function.

The quaternion comparison logic (checking both q and -q) is duplicated across this test and test_import_urdf.py. Consider extracting this to a shared utility in unittest_utils.py:

def assert_quaternion_equal(actual, expected, rtol=1e-5, atol=1e-5, msg=None):
    """Assert quaternions are equal, accounting for q == -q equivalence."""
    actual = np.asarray(actual)
    expected = np.asarray(expected)
    if not (np.allclose(actual, expected, rtol=rtol, atol=atol) or 
            np.allclose(actual, -expected, rtol=rtol, atol=atol)):
        raise AssertionError(msg or f"Quaternions not equal: {actual} vs {expected}")
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4eaa35 and f171595.

📒 Files selected for processing (3)
  • newton/_src/utils/import_mjcf.py (3 hunks)
  • newton/tests/test_import_mjcf.py (1 hunks)
  • newton/tests/test_import_urdf.py (4 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: adenzler-nvidia
Repo: newton-physics/newton PR: 552
File: newton/_src/solvers/mujoco/solver_mujoco.py:0-0
Timestamp: 2025-08-18T15:56:26.587Z
Learning: In Newton's MuJoCo solver, when transforming joint axes from Newton's internal frame to MuJoCo's expected frame, use wp.quat_rotate(joint_rot, axis) not wp.quat_rotate_inv(joint_rot, axis). The joint_rot represents rotation from joint-local to body frame, so forward rotation is correct.
Learnt from: gdaviet
Repo: newton-physics/newton PR: 631
File: newton/examples/mpm/example_anymal_c_walk_on_sand.py:164-169
Timestamp: 2025-08-25T11:07:47.818Z
Learning: In Newton's MuJoCo solver implementation, setting TARGET_POSITION mode on floating base DOFs (first 6 DOFs) does not overconstrain the base - the solver handles this appropriately and maintains expected behavior for floating robots.
📚 Learning: 2025-08-18T15:56:26.587Z
Learnt from: adenzler-nvidia
Repo: newton-physics/newton PR: 552
File: newton/_src/solvers/mujoco/solver_mujoco.py:0-0
Timestamp: 2025-08-18T15:56:26.587Z
Learning: In Newton's MuJoCo solver, when transforming joint axes from Newton's internal frame to MuJoCo's expected frame, use wp.quat_rotate(joint_rot, axis) not wp.quat_rotate_inv(joint_rot, axis). The joint_rot represents rotation from joint-local to body frame, so forward rotation is correct.

Applied to files:

  • newton/_src/utils/import_mjcf.py
  • newton/tests/test_import_urdf.py
📚 Learning: 2025-11-24T08:05:21.390Z
Learnt from: adenzler-nvidia
Repo: newton-physics/newton PR: 1107
File: newton/_src/solvers/mujoco/kernels.py:973-974
Timestamp: 2025-11-24T08:05:21.390Z
Learning: In Newton's MuJoCo solver integration (newton/_src/solvers/mujoco/), the mapping between Newton joints and MuJoCo joints is not 1-to-1. Instead, each Newton DOF maps to a distinct MuJoCo joint. This means that for multi-DOF joints (like D6 with 6 DOFs), there will be 6 corresponding MuJoCo joints, each with its own properties (margin, solimp, solref, etc.). The mapping is done via dof_to_mjc_joint array, ensuring each DOF's properties are written to its own MuJoCo joint without overwriting.

Applied to files:

  • newton/_src/utils/import_mjcf.py
📚 Learning: 2025-08-25T11:07:47.818Z
Learnt from: gdaviet
Repo: newton-physics/newton PR: 631
File: newton/examples/mpm/example_anymal_c_walk_on_sand.py:164-169
Timestamp: 2025-08-25T11:07:47.818Z
Learning: In Newton's MuJoCo solver implementation, setting TARGET_POSITION mode on floating base DOFs (first 6 DOFs) does not overconstrain the base - the solver handles this appropriately and maintains expected behavior for floating robots.

Applied to files:

  • newton/tests/test_import_mjcf.py
📚 Learning: 2025-08-20T03:30:16.037Z
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 584
File: newton/_src/utils/import_urdf.py:159-167
Timestamp: 2025-08-20T03:30:16.037Z
Learning: In the URDF importer (newton/_src/utils/import_urdf.py), cylinders are intentionally created using add_shape_capsule() instead of add_shape_cylinder() because cylinder collisions are not fully supported yet. This is a deliberate workaround until proper cylinder collision support is implemented.

Applied to files:

  • newton/tests/test_import_urdf.py
🧬 Code graph analysis (2)
newton/_src/utils/import_mjcf.py (2)
newton/_src/sim/builder.py (2)
  • add_joint_fixed (2331-2375)
  • key (391-393)
newton/_src/utils/import_utils.py (1)
  • transform (139-140)
newton/tests/test_import_mjcf.py (4)
newton/_src/utils/import_utils.py (1)
  • transform (139-140)
newton/_src/sim/builder.py (2)
  • ModelBuilder (70-5751)
  • add_mjcf (1302-1392)
newton/_src/sim/joints.py (1)
  • JointType (20-44)
newton/_src/sim/model.py (1)
  • state (517-554)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Run GPU Tests / Run GPU Unit Tests on AWS EC2
  • GitHub Check: Run GPU Benchmarks / Run GPU Benchmarks on AWS EC2
🔇 Additional comments (5)
newton/tests/test_import_urdf.py (2)

208-213: LGTM: Default up_axis handling improves test consistency.

The default up_axis="Y" provides a sensible default for URDF tests while still allowing explicit override via kwargs.


470-557: Well-structured test for xform handling with fixed base.

The test comprehensively validates:

  1. Joint count and type (FIXED)
  2. Joint parent transform matches the expected xform
  3. Body state after eval_fk matches expected transform
  4. Quaternion sign equivalence is correctly handled

The explicit up_axis="Z" to avoid axis transformation is a good practice documented inline.

newton/_src/utils/import_mjcf.py (2)

729-729: Correct fix: Using world_xform for fixed base joint.

This change ensures the fixed base joint correctly respects the import xform argument by using the pre-composed world_xform instead of the locally-computed _xform.


746-760: Correct implementation for world-attached joint transforms.

The conditional logic correctly handles both cases:

  • World-attached (parent == -1): Uses world_xform * transform(joint_pos, identity) which respects the import xform argument
  • Non-world parents: Preserves the original body-relative positioning

This is consistent with the fixed_base handling at line 729.

newton/tests/test_import_mjcf.py (1)

1040-1123: Comprehensive test for MJCF xform handling with floating=False.

The test validates:

  1. A freejoint body is converted to FIXED joint when floating=False
  2. The joint's parent_xform correctly combines the import xform with the body's local transform
  3. Body state after eval_fk matches the expected composed transform

The use of wp.quat_rpy for local_quat provides a more complex rotation scenario compared to the URDF test's simpler axis-angle rotation.

@eric-heiden eric-heiden merged commit d05d8c6 into newton-physics:main Dec 3, 2025
19 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Jan 16, 2026
3 tasks
eric-heiden added a commit to eric-heiden/newton that referenced this pull request Jan 28, 2026
…ent (newton-physics#1171)

Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
mmacklin pushed a commit to mmacklin/newton that referenced this pull request Apr 7, 2026
…ent (newton-physics#1171)

Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant