Skip to content

Update ModelBuilder API for creating articulations#1073

Merged
eric-heiden merged 63 commits into
newton-physics:mainfrom
eric-heiden:add-link
Dec 2, 2025
Merged

Update ModelBuilder API for creating articulations#1073
eric-heiden merged 63 commits into
newton-physics:mainfrom
eric-heiden:add-link

Conversation

@eric-heiden

@eric-heiden eric-heiden commented Nov 12, 2025

Copy link
Copy Markdown
Member

Description

  • Add add_link() method to create bodies within articulations
  • add_body() now creates an articulation, adds a link to it, and creates a free joint for this body. This makes it easier for common user scenarios where we just want to create a free-floating body
    • Implement new add_articulation(joints) method definition
  • Closes Update ModelBuilder API for creating articulations #1078

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

  • New Features

    • Articulation creation now accepts explicit joint lists, with validation for non-empty, ordered/contiguous indices, same-world membership, and simple tree structure; supports articulation-level custom attributes.
  • API Changes

    • Body creation renamed to add_link; joint-creation calls now return joint handles; add_articulation requires a joint list (optional key).
  • Workflow Updates

    • Build flow: add_link → create joints (capture handles) → add_articulation (deferred aggregation).
  • Tests / Docs

    • Tests and documentation updated to the link/joint/articulation flow.

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

…) for bodies within articulations

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

coderabbitai Bot commented Nov 12, 2025

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Introduces a link-first build flow and explicit articulation assembly: adds ModelBuilder.add_link, a validated ModelBuilder.add_articulation(joints: list[int], key: str|None = None, custom_attributes: dict|None = None), reworks add_joint/world checks, and adds a convenience add_body wrapper; importers, examples, docs, and tests updated to collect joints and defer articulation creation.

Changes

Cohort / File(s) Summary
Core API
newton/_src/sim/builder.py
Added `add_articulation(self, joints: list[int], key: str
Importers
newton/_src/utils/import_mjcf.py, newton/_src/utils/import_urdf.py, newton/_src/utils/import_usd.py
Replaced add_body() calls with add_link(). Joint creation now appends returned joint indices to per-articulation lists (e.g., joint_indices, articulation_joint_indices) rather than finalizing articulations immediately; deferred articulation creation via add_articulation(joints=..., key=..., custom_attributes=...). Adjusted disabled-joint handling and joint-target helpers to return/append indices.
Examples
newton/examples/basic/*
Replaced add_body() with add_link(), capture joint return values, and explicitly call add_articulation([...], key=...) after joints are defined.
Documentation
docs/concepts/articulations.rst, docs/concepts/custom_attributes.rst, docs/concepts/sites.rst, docs/concepts/collisions.rst
Updated prose and code examples to use add_link() and joint-first articulation construction (create joints, then add_articulation([joints])).
Tests — kinematics / IK / FK
newton/tests/test_kinematics.py, newton/tests/test_ik*.py, newton/tests/test_ik_fk_kernels.py, newton/tests/test_ik_lbfgs.py, newton/tests/test_joint_limits.py
Replaced add_body() with add_link(). Joint-creation calls now return handles; joints are collected and passed to add_articulation([...]). Many explicit add_joint_free() calls removed or adapted; added articulation validation checks in some tests.
Tests — control / dynamics / solvers
newton/tests/test_control_force.py, newton/tests/test_body_force.py, newton/tests/test_joint_controllers.py, newton/tests/test_joint_drive.py, newton/tests/test_mujoco_solver.py
Same API shifts: add_body()add_link(). Captured joint handles and registered articulations via add_articulation([j]). Adjusted test flows and removed some explicit free-joint additions.
Tests — collision / contact / sites / sensors
newton/tests/test_collision_pipeline.py, newton/tests/test_contact_matcher.py, newton/tests/test_rigid_contact.py, newton/tests/test_sites*.py, newton/tests/test_frame_transform_sensor.py
Replaced add_body() with add_link() where applicable. Removed or changed explicit add_joint_free() and articulation calls; some tests drop free-joint setup, others aggregate joints then call add_articulation(...).
Misc tests & validations
newton/tests/test_model.py, newton/tests/test_recorder.py, newton/tests/test_runtime_gravity.py, newton/tests/test_selection.py, newton/tests/test_equality_constraints.py, newton/tests/test_contact_matcher.py
Updated to link-centric construction, capturing joint handles and calling add_articulation([...]). Added or adjusted tests for articulation validation (contiguity, monotonicity, non-empty, world-consistency, single-parent-tree).

Sequence Diagram(s)

mermaid
sequenceDiagram
autonumber
participant Importer
participant ModelBuilder
participant WorldModel
Note over Importer,ModelBuilder: Importers collect joint ids and defer articulation creation
Importer->>ModelBuilder: add_link(...) (create link)
Importer->>ModelBuilder: add_joint_(parent,child,...) -> joint_id
Importer->>Importer: joint_indices.append(joint_id)
loop for each joint
Importer->>ModelBuilder: (repeat add_joint_
-> append)
end
Importer->>ModelBuilder: add_articulation(joints=joint_indices, key, custom_attributes)
ModelBuilder->>ModelBuilder: validate(joints: non-empty, contiguous, monotonic, same-world, single-parent-tree)
ModelBuilder->>WorldModel: register articulation (store articulation_start, articulation_key, custom_attributes)
ModelBuilder->>WorldModel: attach joints/bodies into articulation

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Points to focus during review:

  • newton/_src/sim/builder.py — articulation validation (contiguity, monotonicity, non-empty), same-world checks, single-parent-tree enforcement, and correct computation/storage of articulation_start and articulation_key.
  • Importers (import_mjcf.py, import_urdf.py, import_usd.py) — ensure all joint creation paths return/append indices, handle disabled joints/None consistently, preserve ordering/keys when calling add_articulation.
  • Tests — ensure joint handles are captured where required and that removals of explicit free-joint calls do not unintentionally change semantics in multi-world or floating-root scenarios.

Possibly related PRs

Suggested reviewers

  • mmacklin

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 74.42% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: updating the ModelBuilder API for creating articulations by introducing add_link() and refactoring add_articulation().
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #1078: introduces add_link(), refactors add_body() to create implicit articulations, implements add_articulation(joints) with contiguity/monotonic validation, and updates all call sites across codebase.
Out of Scope Changes check ✅ Passed All changes are directly related to the articulation API refactoring. Updates span core builder implementation, import utilities (MJCF, URDF, USD), examples, tests, and documentation—all necessary to support the new API.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent 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 ce4597e and d1672b1.

📒 Files selected for processing (1)
  • newton/tests/test_joint_limits.py (5 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-24T00:26:54.486Z
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 710
File: newton/_src/utils/import_usd.py:597-599
Timestamp: 2025-09-24T00:26:54.486Z
Learning: In the Newton ModelBuilder class, `joint_count`, `body_count`, `articulation_count`, and `shape_count` are implemented as property decorated methods, so they should be accessed as attributes (e.g., `builder.joint_count`) rather than called as methods (e.g., `builder.joint_count()`).

Applied to files:

  • newton/tests/test_joint_limits.py
📚 Learning: 2025-07-21T19:11:04.077Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 450
File: newton/sim/ik.py:1286-1296
Timestamp: 2025-07-21T19:11:04.077Z
Learning: In Newton's IK system, the JointLimitObjective class intentionally only handles joints where DOF count equals coordinate count (1-1 mapping). This is appropriate because joint limits are typically only meaningful for simple joint types like revolute and prismatic joints, not complex joints like ball or free joints where DOF count != coordinate count.

Applied to files:

  • newton/tests/test_joint_limits.py
🧬 Code graph analysis (1)
newton/tests/test_joint_limits.py (1)
newton/_src/sim/builder.py (3)
  • add_link (1750-1828)
  • add_joint_revolute (2041-2128)
  • add_articulation (1016-1109)
⏰ 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 (4)
newton/tests/test_joint_limits.py (4)

28-46: LGTM! Correct API migration pattern.

The test correctly uses the new link-first workflow: add_link() to create the body, captures the joint index from add_joint_revolute(), and explicitly registers the articulation with add_articulation([joint]).


48-66: LGTM! Correct API migration pattern.

The test correctly migrates to the new API, properly capturing the joint and registering the articulation.


68-100: LGTM! Correct API migration pattern for multiple builders.

Both builder instances correctly follow the new pattern, properly capturing joints and registering articulations.


102-135: LGTM! URDF import test is appropriate.

This test uses URDF import which handles model construction and articulation registration internally. No changes needed here.


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.

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
newton/tests/test_kinematics.py (6)

149-171: Inconsistent API usage: mixing add_articulation() with add_body().

Lines 155-157 call add_articulation() followed by add_body(). Based on the PR description, the new add_body() method creates a standalone body with its own internal articulation and free joint. This means:

  1. Line 155 creates an articulation named pendulum_{i}
  2. Lines 156-157 each call add_body(), which should create their own separate articulations
  3. Lines 158-171 attempt to join these bodies with revolute joints, but they're in different articulations

This pattern is inconsistent with test_fk_with_indices (lines 71-147) which correctly uses add_articulation() + add_link().

Apply this diff to fix the API usage:

     for i in range(2):
         builder.add_articulation(key=f"pendulum_{i}")
-        b1 = builder.add_body(xform=wp.transform(wp.vec3(i * 2.0, 0.0, 0.0), wp.quat_identity()))
-        b2 = builder.add_body(xform=wp.transform(wp.vec3(i * 2.0 + 1.0, 0.0, 0.0), wp.quat_identity()))
+        b1 = builder.add_link(xform=wp.transform(wp.vec3(i * 2.0, 0.0, 0.0), wp.quat_identity()))
+        b2 = builder.add_link(xform=wp.transform(wp.vec3(i * 2.0 + 1.0, 0.0, 0.0), wp.quat_identity()))
         builder.add_joint_revolute(

203-210: Same inconsistent API usage pattern.

This test has the same issue as test_ik_with_indices. Line 209 should use add_link() instead of add_body() since an articulation is created at line 208.

Apply this diff:

     builder.add_articulation()
-    b1 = builder.add_body()
+    b1 = builder.add_link()
     builder.add_joint_revolute(parent=-1, child=b1, axis=wp.vec3(0.0, 0.0, 1.0))

228-251: Same inconsistent API usage pattern in Isaac Lab test.

Lines 236-237 should use add_link() instead of add_body() since an articulation is created at line 235.

Apply this diff:

     for i in range(num_worlds):
         builder.add_articulation(key=f"env_{i}")
-        b1 = builder.add_body(xform=wp.transform(wp.vec3(i * 3.0, 0.0, 0.0), wp.quat_identity()))
-        b2 = builder.add_body(xform=wp.transform(wp.vec3(i * 3.0 + 1.0, 0.0, 0.0), wp.quat_identity()))
+        b1 = builder.add_link(xform=wp.transform(wp.vec3(i * 3.0, 0.0, 0.0), wp.quat_identity()))
+        b2 = builder.add_link(xform=wp.transform(wp.vec3(i * 3.0 + 1.0, 0.0, 0.0), wp.quat_identity()))
         builder.add_joint_revolute(

291-299: Same inconsistent API usage pattern in bounds checking test.

Line 298 should use add_link() instead of add_body() since an articulation is created at line 297.

Apply this diff:

     for _ in range(2):
         builder.add_articulation()
-        b1 = builder.add_body()
+        b1 = builder.add_link()
         builder.add_joint_revolute(parent=-1, child=b1, axis=wp.vec3(0.0, 0.0, 1.0))

317-339: Same inconsistent API usage pattern in IK mask test.

Lines 324-325 should use add_link() instead of add_body() since an articulation is created at line 323.

Apply this diff:

     for i in range(3):
         builder.add_articulation()
-        b1 = builder.add_body(xform=wp.transform(wp.vec3(i * 2.0, 0.0, 0.0), wp.quat_identity()))
-        b2 = builder.add_body(xform=wp.transform(wp.vec3(i * 2.0 + 1.0, 0.0, 0.0), wp.quat_identity()))
+        b1 = builder.add_link(xform=wp.transform(wp.vec3(i * 2.0, 0.0, 0.0), wp.quat_identity()))
+        b2 = builder.add_link(xform=wp.transform(wp.vec3(i * 2.0 + 1.0, 0.0, 0.0), wp.quat_identity()))
         builder.add_joint_revolute(

375-387: Same inconsistent API usage pattern.

Lines 379-380 should use add_link() instead of add_body() since an articulation is created at line 378.

Apply this diff:

     builder.add_articulation()
-    parent = builder.add_body(xform=wp.transform((0, 0, 0), wp.quat_identity()))
-    child = builder.add_body(xform=wp.transform((1, 0, 0), wp.quat_identity()))
+    parent = builder.add_link(xform=wp.transform((0, 0, 0), wp.quat_identity()))
+    child = builder.add_link(xform=wp.transform((1, 0, 0), wp.quat_identity()))
     builder.add_joint_revolute(
🧹 Nitpick comments (1)
newton/_src/utils/import_usd.py (1)

418-432: LGTM! Correct API usage in USD importer.

The change from builder.add_body() to builder.add_link() is correct since articulations are created before this function is called (e.g., lines 1168, 1177, 1186, 1264).

Consider renaming the local add_body function to avoid confusion with the ModelBuilder's add_body method. Something like add_body_to_builder or create_and_add_link would make it clearer that this is a helper function that calls builder.add_link().

📜 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 9233b83 and 2c4addb.

📒 Files selected for processing (9)
  • newton/_src/sim/builder.py (3 hunks)
  • newton/_src/utils/import_mjcf.py (1 hunks)
  • newton/_src/utils/import_urdf.py (1 hunks)
  • newton/_src/utils/import_usd.py (1 hunks)
  • newton/examples/basic/example_basic_joints.py (3 hunks)
  • newton/examples/basic/example_basic_pendulum.py (1 hunks)
  • newton/examples/cloth/example_usd_mesh.py (1 hunks)
  • newton/tests/test_joint_limits.py (4 hunks)
  • newton/tests/test_kinematics.py (1 hunks)
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-09-24T00:26:54.486Z
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 710
File: newton/_src/utils/import_usd.py:597-599
Timestamp: 2025-09-24T00:26:54.486Z
Learning: In the Newton ModelBuilder class, `joint_count`, `body_count`, `articulation_count`, and `shape_count` are implemented as property decorated methods, so they should be accessed as attributes (e.g., `builder.joint_count`) rather than called as methods (e.g., `builder.joint_count()`).

Applied to files:

  • newton/tests/test_joint_limits.py
  • newton/_src/sim/builder.py
  • newton/examples/basic/example_basic_joints.py
📚 Learning: 2025-07-21T19:11:04.077Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 450
File: newton/sim/ik.py:1286-1296
Timestamp: 2025-07-21T19:11:04.077Z
Learning: In Newton's IK system, the JointLimitObjective class intentionally only handles joints where DOF count equals coordinate count (1-1 mapping). This is appropriate because joint limits are typically only meaningful for simple joint types like revolute and prismatic joints, not complex joints like ball or free joints where DOF count != coordinate count.

Applied to files:

  • newton/tests/test_joint_limits.py
📚 Learning: 2025-08-12T17:58:16.929Z
Learnt from: nvlukasz
Repo: newton-physics/newton PR: 519
File: newton/tests/test_ik.py:25-26
Timestamp: 2025-08-12T17:58:16.929Z
Learning: In the Newton physics engine codebase, it's acceptable for tests to import and use private `_src` internal modules and functions when needed for testing purposes, even though this breaks the public API boundary. This is an intentional project decision for test code.

Applied to files:

  • newton/tests/test_kinematics.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/_src/utils/import_urdf.py
📚 Learning: 2025-08-12T18:07:17.267Z
Learnt from: nvlukasz
Repo: newton-physics/newton PR: 519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.

Applied to files:

  • newton/examples/cloth/example_usd_mesh.py
📚 Learning: 2025-08-12T18:07:17.267Z
Learnt from: nvlukasz
Repo: newton-physics/newton PR: 519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.267Z
Learning: In newton/_src/utils/import_usd.py, the PXR/USD modules are lazily loaded using try-except blocks within the functions (lines 109-111 and 1289-1291) rather than at module import time, making it safe to import parse_usd at the module level without causing import-time failures for users without USD dependencies.

Applied to files:

  • newton/examples/cloth/example_usd_mesh.py
🧬 Code graph analysis (9)
newton/examples/basic/example_basic_pendulum.py (1)
newton/_src/sim/builder.py (2)
  • add_link (1561-1639)
  • add_shape_box (3074-3108)
newton/tests/test_joint_limits.py (1)
newton/_src/sim/builder.py (2)
  • add_articulation (922-945)
  • add_link (1561-1639)
newton/_src/utils/import_usd.py (1)
newton/_src/sim/builder.py (1)
  • add_link (1561-1639)
newton/tests/test_kinematics.py (1)
newton/_src/sim/builder.py (1)
  • add_link (1561-1639)
newton/_src/utils/import_mjcf.py (1)
newton/_src/sim/builder.py (1)
  • add_link (1561-1639)
newton/_src/utils/import_urdf.py (1)
newton/_src/sim/builder.py (1)
  • add_link (1561-1639)
newton/_src/sim/builder.py (1)
newton/_src/utils/import_usd.py (1)
  • add_body (418-432)
newton/examples/basic/example_basic_joints.py (1)
newton/_src/sim/builder.py (3)
  • add_articulation (922-945)
  • key (354-356)
  • add_link (1561-1639)
newton/examples/cloth/example_usd_mesh.py (6)
newton/_src/geometry/types.py (1)
  • Mesh (116-300)
newton/_src/geometry/flags.py (1)
  • ParticleFlags (20-26)
newton/_src/solvers/style3d/collision/collision_legacy.py (1)
  • CollisionHandler (79-284)
newton/_src/sim/builder.py (5)
  • add_shape_mesh (3246-3282)
  • key (354-356)
  • finalize (4647-5035)
  • flags (180-186)
  • flags (189-194)
newton/_src/sim/style3d/builder_style3d.py (1)
  • add_aniso_cloth_grid (384-498)
newton/examples/__init__.py (2)
  • get_asset (34-35)
  • init (331-374)
⏰ 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). (1)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
🔇 Additional comments (9)
newton/examples/basic/example_basic_pendulum.py (1)

46-57: LGTM! Correct usage of the new articulation API.

The example correctly creates an articulation before adding links, and properly uses add_link() for bodies within the articulation.

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

566-570: LGTM! Correct API usage in MJCF importer.

The change from add_body() to add_link() is correct since an articulation is created at line 916 before bodies are parsed.

newton/_src/utils/import_urdf.py (1)

358-361: LGTM! Correct API usage in URDF importer.

The change from add_body() to add_link() is correct since an articulation is created at lines 276-279 before links are added.

newton/tests/test_joint_limits.py (2)

35-37: LGTM! Correct usage of the new articulation API.

The test correctly calls add_articulation() before add_link(), following the new API pattern.


148-156: No changes needed—test uses correct API pattern.

The test correctly uses the established pattern of add_body() followed by add_joint_d6(parent=-1, ...). The add_body() method explicitly creates a standalone body with an internal free joint, and then add_joint_d6(parent=-1, ...) adds a D6 joint from the world to that body. This exact pattern is consistently used across multiple test files (test_ik.py, test_control_force.py, test_body_force.py, test_ik_fk_kernels.py), confirming it is the intended API usage.

newton/tests/test_kinematics.py (1)

78-79: LGTM! Correct usage of the new articulation API.

The test correctly uses add_articulation() followed by add_link() for creating bodies within articulations.

newton/examples/basic/example_basic_joints.py (3)

64-70: LGTM! Correct usage of the new articulation API.

The revolute joint demo correctly creates an articulation before adding links.


96-102: LGTM! Correct usage of the new articulation API.

The prismatic joint demo correctly creates an articulation before adding links.


131-140: LGTM! Correct usage of the new articulation API.

The ball joint demo correctly creates an articulation before adding links.

Comment thread newton/examples/cloth/example_usd_mesh.py Outdated
Comment thread newton/examples/cloth/example_usd_mesh.py Outdated
eric-heiden and others added 5 commits November 13, 2025 18:06
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>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
newton/_src/utils/import_usd.py (2)

1258-1270: Critical: add_articulation() must be called before add_link().

This code violates the new API contract. The sequence is:

  1. Line 1261-1267: parse_body(..., add_body_to_builder=True) → calls local add_body() at line 464 → calls builder.add_link() at line 434
  2. Line 1268: builder.add_articulation(key) is called

According to the add_link() documentation: "Before calling this method, ensure that an articulation has been created using :meth:add_articulation."

Apply this diff to fix the ordering:

 # insert remaining bodies that were not part of any articulation so far
 for path, rigid_body_desc in body_specs.items():
     key = str(path)
+    # add articulation before creating the body/link
+    builder.add_articulation(key)
     body_id = parse_body(
         rigid_body_desc,
         stage.GetPrimAtPath(path),
         incoming_xform=incoming_world_xform,
         add_body_to_builder=True,
     )
-    # add articulation and free joint for this body
-    builder.add_articulation(key)
+    # add free joint for this body
     builder.add_joint_free(child=body_id)

434-439: Call site ordering issue confirmed—reorder add_articulation() before parse_body().

The change from add_body() to add_link() is correct. However, at lines 1261–1269, there is a critical ordering bug: parse_body(..., add_body_to_builder=True) is called at line 1261, which immediately invokes add_link(), but builder.add_articulation(key) is not called until line 1268. Since add_link() requires an existing articulation, this will fail at runtime.

Move builder.add_articulation(key) to line 1261 (before parse_body()) and shift the parse_body() call to what is now line 1262.

🧹 Nitpick comments (1)
newton/_src/sim/builder.py (1)

1618-1634: Clarify articulation requirement in docstring.

The docstring states "Before calling this method, ensure that an articulation has been created using :meth:add_articulation." However, this requirement is not strictly enforced since add_joint() (lines 1808-1810) automatically creates an articulation if none exists. Consider clarifying the docstring to indicate that:

  • An articulation should typically be created first for clarity
  • If you add a joint after this link, an articulation will be auto-created if needed
  • The requirement is primarily for organizational/clarity purposes rather than a hard constraint

Example clarification:

-        Before calling this method, ensure that an articulation has been created using :meth:`add_articulation`.
+        Typically, you should create an articulation using :meth:`add_articulation` before calling this method.
+        If you subsequently call a joint creation method (e.g., :meth:`add_joint_revolute`), an articulation
+        will be automatically created if none exists.
📜 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 2c4addb and 34154f2.

📒 Files selected for processing (5)
  • newton/_src/sim/builder.py (3 hunks)
  • newton/_src/utils/import_mjcf.py (1 hunks)
  • newton/_src/utils/import_usd.py (1 hunks)
  • newton/tests/test_joint_limits.py (4 hunks)
  • newton/tests/test_kinematics.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • newton/_src/utils/import_mjcf.py
  • newton/tests/test_kinematics.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-09-24T00:26:54.486Z
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 710
File: newton/_src/utils/import_usd.py:597-599
Timestamp: 2025-09-24T00:26:54.486Z
Learning: In the Newton ModelBuilder class, `joint_count`, `body_count`, `articulation_count`, and `shape_count` are implemented as property decorated methods, so they should be accessed as attributes (e.g., `builder.joint_count`) rather than called as methods (e.g., `builder.joint_count()`).

Applied to files:

  • newton/_src/sim/builder.py
  • newton/tests/test_joint_limits.py
📚 Learning: 2025-07-21T19:11:04.077Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 450
File: newton/sim/ik.py:1286-1296
Timestamp: 2025-07-21T19:11:04.077Z
Learning: In Newton's IK system, the JointLimitObjective class intentionally only handles joints where DOF count equals coordinate count (1-1 mapping). This is appropriate because joint limits are typically only meaningful for simple joint types like revolute and prismatic joints, not complex joints like ball or free joints where DOF count != coordinate count.

Applied to files:

  • newton/tests/test_joint_limits.py
🧬 Code graph analysis (3)
newton/_src/utils/import_usd.py (1)
newton/_src/sim/builder.py (1)
  • add_link (1618-1696)
newton/_src/sim/builder.py (1)
newton/_src/utils/import_usd.py (1)
  • add_body (430-444)
newton/tests/test_joint_limits.py (1)
newton/_src/sim/builder.py (2)
  • add_articulation (970-993)
  • add_link (1618-1696)
⏰ 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). (1)
  • GitHub Check: Run GPU Benchmarks / Run GPU Benchmarks on AWS EC2
🔇 Additional comments (2)
newton/tests/test_joint_limits.py (1)

34-36: LGTM! Correct usage of the new articulation API.

The tests correctly call add_articulation() before add_link(), which aligns with the refactored ModelBuilder API. The explicit articulation creation followed by link and joint setup properly demonstrates the new workflow for constructing articulated bodies.

Also applies to: 54-56, 74-76, 92-93

newton/_src/sim/builder.py (1)

1698-1757: Implementation looks good - achieves the PR objectives.

The new add_body method correctly implements the PR's goal of simplifying free-floating body creation by:

  1. Creating an articulation with an auto-generated key (line 1738)
  2. Creating a link via add_link (lines 1741-1749)
  3. Adding a free joint to connect the body to the world (lines 1752-1755)

The method signature matches add_link, making migration straightforward, and the docstring clearly explains the convenience wrapper behavior.

Note: Custom attributes are only passed to add_link (line 1748), not to the auto-created articulation or free joint. This is a reasonable design choice since users needing finer control can call add_articulation(), add_link(), and add_joint_free() explicitly.

eric-heiden and others added 20 commits November 14, 2025 09:35
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>
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>
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>
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>
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>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>
vreutskyy and others added 6 commits December 1, 2025 10:20
Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>
Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>
Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>
Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
newton/examples/basic/example_basic_joints.py (1)

64-91: Articulation wiring is correct, but dynamic demo links are now massless (kinematic).

The examples correctly migrate to the new API by:

  • Creating links via add_link(...),
  • Wiring fixed world anchors plus actuated joints,
  • Grouping them in articulations like [j_fixed_rev, j_revolute], [j_fixed_pri, j_prismatic], [j_fixed_ball, j_ball].

However, all links use the default mass=0.0:

  • b_rev, b_pri, and b_ball are treated as kinematic, so they won’t respond dynamically to gravity, undermining the “basic joints” motion demo.
  • Only a_ball is intended to be massless (per the comment “kinematic (massless) sphere as the parent anchor”).

To preserve the original behavior—static anchors and dynamic moving links—set positive masses on the moving links only, for example:

-        a_rev = builder.add_link(xform=wp.transform(p=wp.vec3(0.0, y, drop_z + upper_hz), q=wp.quat_identity()))
-        b_rev = builder.add_link(
+        a_rev = builder.add_link(xform=wp.transform(p=wp.vec3(0.0, y, drop_z + upper_hz), q=wp.quat_identity()))
+        b_rev = builder.add_link(
             xform=wp.transform(
                 p=wp.vec3(0.0, y, drop_z - cuboid_hz), q=wp.quat_from_axis_angle(wp.vec3(1.0, 0.0, 0.0), 0.15)
             ),
-            key="b_rev",
+            mass=1.0,
+            key="b_rev",
         )
@@
-        a_pri = builder.add_link(xform=wp.transform(p=wp.vec3(0.0, y, drop_z + upper_hz), q=wp.quat_identity()))
-        b_pri = builder.add_link(
+        a_pri = builder.add_link(xform=wp.transform(p=wp.vec3(0.0, y, drop_z + upper_hz), q=wp.quat_identity()))
+        b_pri = builder.add_link(
             xform=wp.transform(
                 p=wp.vec3(0.0, y, drop_z - cuboid_hz), q=wp.quat_from_axis_angle(wp.vec3(0.0, 1.0, 0.0), 0.12)
             ),
-            key="b_prismatic",
+            mass=1.0,
+            key="b_prismatic",
         )
@@
-        a_ball = builder.add_link(
+        a_ball = builder.add_link(
             xform=wp.transform(p=wp.vec3(0.0, y, drop_z + radius + cuboid_hz + z_offset), q=wp.quat_identity())
         )
-        b_ball = builder.add_link(
+        b_ball = builder.add_link(
             xform=wp.transform(
                 p=wp.vec3(0.0, y, drop_z + radius + z_offset), q=wp.quat_from_axis_angle(wp.vec3(1.0, 1.0, 0.0), 0.1)
             ),
-            key="b_ball",
+            mass=1.0,
+            key="b_ball",
         )

This keeps anchors kinematic while restoring meaningful motion for the hinge, slider, and ball demos.

Also applies to: 95-127, 136-170, 171-176

♻️ Duplicate comments (3)
newton/tests/test_mujoco_solver.py (3)

1707-1714: Multiple small test builders using add_link still miss required add_articulation(...) calls.

Several tests now construct bodies via add_link(...) and attach a single joint (revolute or prismatic) but never register an articulation:

  • test_no_shapes (lines 1707–1714)
  • test_separate_worlds_false_multi_world_validation’s template_builder (lines 1715–1721)
  • test_separate_worlds_false_single_world_works (lines 1745–1755)
  • test_joint_transform_composition (parent/child links and joints, lines 1762–1777, 1792–1794)
  • test_global_joint_solver_params (pendulum link + revolute joint, lines 1891–1905)
  • test_joint_frame_update (single revolute joint, lines 1977–1983)
  • test_noncontiguous_joint_indexing’s robot builder (links 0/1 and revolute joint, lines 2099–2103)

Per the new builder API, every set of joints created via add_link should be registered in an articulation using their indices. For each of these cases, please:

  1. Capture the joint index returned from add_joint_*.
  2. Call add_articulation([joint_idx]) (or a list of contiguous joint indices) before finalize().

For example, in test_no_shapes:

     def test_no_shapes(self):
         builder = newton.ModelBuilder()
-        b = builder.add_link(mass=1.0, com=wp.vec3(1.0, 2.0, 3.0), I_m=wp.mat33(np.eye(3)))
-        builder.add_joint_prismatic(-1, b)
+        b = builder.add_link(mass=1.0, com=wp.vec3(1.0, 2.0, 3.0), I_m=wp.mat33(np.eye(3)))
+        j = builder.add_joint_prismatic(-1, b)
+        builder.add_articulation([j])

Apply analogous changes in the other tests to keep all add_link-based bodies inside properly defined articulations.

Also applies to: 1715-1721, 1745-1755, 1762-1777, 1792-1794, 1891-1905, 1977-1983, 2099-2103


46-47: Add explicit articulations for the single-link MuJoCo option tests.

Both test_ls_parallel_option and test_tolerance_options now build a link via add_link(...) and a revolute joint to the world, but never register an articulation. Per the new ModelBuilder.add_link contract, callers are expected to create articulations explicitly after adding joints.

Capture the joint index and register a one-joint articulation in both tests, e.g.:

 def test_ls_parallel_option(self):
     # Create minimal model with proper inertia
     builder = newton.ModelBuilder()
-    body = builder.add_link(mass=1.0, com=wp.vec3(0.0, 0.0, 0.0), I_m=wp.mat33(np.eye(3)))
-    builder.add_joint_revolute(-1, body)
+    body = builder.add_link(mass=1.0, com=wp.vec3(0.0, 0.0, 0.0), I_m=wp.mat33(np.eye(3)))
+    j = builder.add_joint_revolute(-1, body)
+    builder.add_articulation([j])

Apply the same pattern in test_tolerance_options.

Also applies to: 62-63


2603-2611: Add an articulation for the 3-link custom-attributes builder.

In TestMuJoCoAttributes.test_custom_attributes_from_code, the builder now creates three links with three revolute joints:

b0 = builder.add_link()
builder.add_joint_revolute(-1, b0, ...)
...
b1 = builder.add_link()
builder.add_joint_revolute(b0, b1, ...)
...
b2 = builder.add_link()
builder.add_joint_revolute(b1, b2, ...)

but never registers an articulation, which is required when using add_link.

Capture the joint indices and create an articulation:

-        b0 = builder.add_link()
-        builder.add_joint_revolute(-1, b0, axis=(0.0, 0.0, 1.0))
+        b0 = builder.add_link()
+        j0 = builder.add_joint_revolute(-1, b0, axis=(0.0, 0.0, 1.0))
@@
-        b1 = builder.add_link()
-        builder.add_joint_revolute(b0, b1, axis=(0.0, 0.0, 1.0))
+        b1 = builder.add_link()
+        j1 = builder.add_joint_revolute(b0, b1, axis=(0.0, 0.0, 1.0))
@@
-        b2 = builder.add_link()
-        builder.add_joint_revolute(b1, b2, axis=(0.0, 0.0, 1.0))
+        b2 = builder.add_link()
+        j2 = builder.add_joint_revolute(b1, b2, axis=(0.0, 0.0, 1.0))
@@
-        model = builder.finalize()
+        builder.add_articulation([j0, j1, j2])
+        model = builder.finalize()

This aligns the test’s model-building path with the articulation-centric API.

🧹 Nitpick comments (3)
newton/_src/sim/builder.py (2)

1016-1110: Articulation validation logic looks solid; consider guarding against joint reuse across articulations

The new add_articulation() checks (non-empty, monotonic, contiguous, in-range, single-world, tree-structured) are consistent with how articulation_start is later interpreted in finalize() and should protect most misuse. One possible future enhancement would be to track which joint indices are already part of an articulation and reject overlapping or duplicated joints across multiple articulations, since the current implementation would silently allow a joint to be included in more than one articulation, which articulation_start semantics don’t really support.


1830-1890: add_body wrapper matches the “free-floating body” objective; consider optional articulation custom attrs

The new add_body() wrapper (link + free joint + articulation) aligns with the PR objective for creating standalone free‑floating bodies and the derived key scheme {key}_free_joint / {key}_articulation is clearly documented. If you anticipate users wanting ARTICULATION‑frequency custom attributes for these single‑body articulations, you might extend the signature with an optional articulation_custom_attributes and pass it through to add_articulation, but that’s a nice‑to‑have rather than a blocker.

newton/_src/utils/import_urdf.py (1)

464-466: Non-blocking: consider tightening exception types and messages, and XML hardening

The new/updated raises in parse_urdf currently use a generic Exception for unsupported joint types and fairly long ValueError messages. If you care about Ruff’s TRY00x hints, you could (1) switch the unsupported-type branch to a more specific ValueError and (2) move the verbose messages into helper functions or shorter strings. Separately, if URDF content may come from untrusted sources, consider swapping xml.etree.ElementTree for defusedxml.ElementTree in the fromstring/parse calls to address S314; otherwise it may be acceptable to treat these findings as waived for trusted inputs.

Also applies to: 565-565

📜 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 89a1c38 and 84609eb.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • newton/_src/sim/builder.py (5 hunks)
  • newton/_src/utils/import_urdf.py (8 hunks)
  • newton/examples/basic/example_basic_joints.py (6 hunks)
  • newton/examples/basic/example_basic_pendulum.py (1 hunks)
  • newton/tests/test_equality_constraints.py (2 hunks)
  • newton/tests/test_ik.py (3 hunks)
  • newton/tests/test_ik_fk_kernels.py (3 hunks)
  • newton/tests/test_ik_lbfgs.py (1 hunks)
  • newton/tests/test_mujoco_solver.py (21 hunks)
  • pyproject.toml (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • newton/tests/test_equality_constraints.py
  • newton/examples/basic/example_basic_pendulum.py
🧰 Additional context used
🧠 Learnings (20)
📓 Common learnings
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 710
File: newton/_src/utils/import_usd.py:597-599
Timestamp: 2025-09-24T00:26:54.486Z
Learning: In the Newton ModelBuilder class, `joint_count`, `body_count`, `articulation_count`, and `shape_count` are implemented as property decorated methods, so they should be accessed as attributes (e.g., `builder.joint_count`) rather than called as methods (e.g., `builder.joint_count()`).
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 710
File: newton/_src/utils/import_usd.py:597-599
Timestamp: 2025-09-24T00:26:54.486Z
Learning: In the Newton ModelBuilder class, `joint_count`, `body_count`, `articulation_count`, and `shape_count` are implemented as property decorated methods, so they should be accessed as attributes (e.g., `builder.joint_count`) rather than called as methods (e.g., `builder.joint_count()`).
📚 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/_src/utils/import_urdf.py
📚 Learning: 2025-09-22T21:03:39.624Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/_src/sim/ik/ik_lbfgs_optimizer.py:739-752
Timestamp: 2025-09-22T21:03:39.624Z
Learning: The L-BFGS optimizer in newton/_src/sim/ik/ik_lbfgs_optimizer.py currently intentionally only supports additive updates (assuming n_coords == n_dofs). Velocity space integration for joints with mismatched coordinate/DOF dimensions (like free/ball joints) is planned for future work and should not be flagged as an issue in current reviews.

Applied to files:

  • newton/_src/utils/import_urdf.py
  • newton/tests/test_ik.py
  • newton/tests/test_ik_lbfgs.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/_src/utils/import_urdf.py
📚 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_urdf.py
  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-09-22T21:03:18.367Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/_src/sim/ik/ik_lbfgs_optimizer.py:638-648
Timestamp: 2025-09-22T21:03:18.367Z
Learning: The L-BFGS optimizer in newton/_src/sim/ik/ik_lbfgs_optimizer.py currently only supports additive updates, meaning it assumes n_coords == n_dofs. Velocity space integration for joints with mismatched coordinate/DOF dimensions (like free/ball joints) is planned for future work.

Applied to files:

  • newton/_src/utils/import_urdf.py
  • newton/tests/test_ik_lbfgs.py
📚 Learning: 2025-09-24T00:26:54.486Z
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 710
File: newton/_src/utils/import_usd.py:597-599
Timestamp: 2025-09-24T00:26:54.486Z
Learning: In the Newton ModelBuilder class, `joint_count`, `body_count`, `articulation_count`, and `shape_count` are implemented as property decorated methods, so they should be accessed as attributes (e.g., `builder.joint_count`) rather than called as methods (e.g., `builder.joint_count()`).

Applied to files:

  • newton/tests/test_ik.py
  • newton/tests/test_mujoco_solver.py
  • newton/_src/sim/builder.py
  • newton/examples/basic/example_basic_joints.py
  • newton/tests/test_ik_fk_kernels.py
  • newton/tests/test_ik_lbfgs.py
📚 Learning: 2025-09-22T21:08:31.901Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/examples/ik/example_ik_franka.py:121-123
Timestamp: 2025-09-22T21:08:31.901Z
Learning: In the newton physics framework, when creating warp arrays for IK solver joint variables using wp.array(self.model.joint_q, shape=(1, coord_count)), the resulting array acts as a reference/pointer to the original model's joint coordinates, so updates from the IK solver automatically reflect in the model's joint_q buffer used for rendering.

Applied to files:

  • newton/tests/test_ik.py
  • newton/tests/test_mujoco_solver.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/tests/test_ik.py
  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-07-28T15:04:54.360Z
Learnt from: adenzler-nvidia
Repo: newton-physics/newton PR: 479
File: newton/solvers/mujoco/solver_mujoco.py:2147-2149
Timestamp: 2025-07-28T15:04:54.360Z
Learning: The ls_parallel option is specific to MuJoCo Warp and is not available in the pure MuJoCo mjOption structure. It should only be set on mjw_model.opt, not mj_model.opt.

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-07-28T15:04:54.360Z
Learnt from: adenzler-nvidia
Repo: newton-physics/newton PR: 479
File: newton/solvers/mujoco/solver_mujoco.py:2147-2149
Timestamp: 2025-07-28T15:04:54.360Z
Learning: In the Newton MuJoCo solver implementation, the ls_parallel option may not be available in the pure MuJoCo mjOption structure, despite being documented in newer MuJoCo versions. The user adenzler-nvidia confirmed that mj_model does not have this attribute in their implementation, so ls_parallel should only be set on mjw_model.opt.

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-09-25T16:14:22.033Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/_src/sim/ik/ik_objectives.py:0-0
Timestamp: 2025-09-25T16:14:22.033Z
Learning: In NVIDIA Warp's Newton physics library, wp.transform supports direct numerical indexing (e.g., body_tf[0], body_tf[1], body_tf[2] for position and body_tf[3], body_tf[4], body_tf[5], body_tf[6] for quaternion components) to access its elements. This is the standard API used throughout the codebase.

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-08-20T18:02:36.099Z
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 587
File: newton/_src/sim/builder.py:656-661
Timestamp: 2025-08-20T18:02:36.099Z
Learning: In Newton physics, collisions between shapes with body index -1 (world-attached/global shapes) are automatically skipped by the collision system, so no manual collision filter pairs need to be added between global shapes from different builders.

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-10-10T10:47:41.082Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 899
File: newton/tests/test_rigid_contact.py:214-268
Timestamp: 2025-10-10T10:47:41.082Z
Learning: In `newton/tests/test_rigid_contact.py`, using `add_shape_plane(width=0, length=0)` for an infinite plane works correctly with the collision system and does not cause degenerate AABB issues in the test scenario `test_shape_collisions_gjk_mpr_multicontact`.

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-09-22T21:08:31.901Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/examples/ik/example_ik_franka.py:121-123
Timestamp: 2025-09-22T21:08:31.901Z
Learning: In the newton physics framework, when creating warp arrays for IK solver joint variables using wp.array(self.model.joint_q, shape=(1, coord_count)), the resulting array acts as a reference/pointer to the original model's joint coordinates, so updates from the IK solver automatically reflect in the model's joint_q buffer used for rendering, despite the general warp documentation suggesting copies are made by default.

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-08-27T23:32:48.670Z
Learnt from: shi-eric
Repo: newton-physics/newton PR: 660
File: pyproject.toml:54-59
Timestamp: 2025-08-27T23:32:48.670Z
Learning: In the newton project's pyproject.toml, self-referential extras (e.g., `examples = ["newton[sim]", "newton[importers]", ...]`) are normal and encouraged for dependency management, rather than inlining the dependencies.

Applied to files:

  • pyproject.toml
📚 Learning: 2025-08-21T15:51:07.824Z
Learnt from: shi-eric
Repo: newton-physics/newton PR: 592
File: pyproject.toml:64-70
Timestamp: 2025-08-21T15:51:07.824Z
Learning: Self-referencing optional dependencies (e.g., "package[extra]") have been supported since pip 21.2 and are valid in pyproject.toml. However, build backends differ in metadata emission: some flatten dependencies while others preserve self-references. This can affect lockfile generators and tooling. Testing with the specific build backend and dependency tools is recommended to ensure expected behavior.

Applied to files:

  • pyproject.toml
📚 Learning: 2025-08-21T15:51:07.824Z
Learnt from: shi-eric
Repo: newton-physics/newton PR: 592
File: pyproject.toml:64-70
Timestamp: 2025-08-21T15:51:07.824Z
Learning: Since pip 21.2 (released 2021), Python packages can reference their own optional dependencies within other optional dependency groups using self-referencing syntax like "package-name[extra]" in pyproject.toml. This allows for clean dependency composition without inlining all dependencies.

Applied to files:

  • pyproject.toml
📚 Learning: 2025-08-27T23:33:32.527Z
Learnt from: shi-eric
Repo: newton-physics/newton PR: 660
File: pyproject.toml:28-28
Timestamp: 2025-08-27T23:33:32.527Z
Learning: In Python projects, it's normal and correct to have different warp-lang version specifications across files: pyproject.toml should specify minimum compatible versions using >= for flexibility, while specific environments like asv.conf.json can pin exact versions using == for reproducibility. These serve different purposes and should not be "aligned" to the same version.

Applied to files:

  • pyproject.toml
📚 Learning: 2025-07-31T22:37:26.351Z
Learnt from: shi-eric
Repo: newton-physics/newton PR: 495
File: pyproject.toml:64-70
Timestamp: 2025-07-31T22:37:26.351Z
Learning: The NVIDIA PyPI index at https://pypi.nvidia.com/ works correctly with uv and uses a direct package structure (e.g., https://pypi.nvidia.com/warp-lang/) rather than the standard PyPI simple index format (/simple/package-name/). While /simple/ paths return 404, the direct package URLs return 200 and uv handles this non-standard index format properly.

Applied to files:

  • pyproject.toml
🧬 Code graph analysis (3)
newton/_src/utils/import_urdf.py (2)
newton/_src/utils/import_utils.py (2)
  • sanitize_xml_content (152-166)
  • parse_custom_attributes (106-149)
newton/_src/sim/builder.py (6)
  • add_link (1750-1828)
  • ModelBuilder (70-5564)
  • key (387-389)
  • add_joint (1894-2039)
  • add_joint_revolute (2041-2128)
  • add_articulation (1016-1109)
newton/_src/sim/builder.py (1)
newton/_src/utils/import_usd.py (1)
  • add_body (430-444)
newton/tests/test_ik_fk_kernels.py (1)
newton/_src/sim/builder.py (3)
  • add_link (1750-1828)
  • joint_count (928-932)
  • add_articulation (1016-1109)
🪛 Ruff (0.14.6)
newton/_src/utils/import_urdf.py

111-111: Using xml to parse untrusted data is known to be vulnerable to XML attacks; use defusedxml equivalents

(S314)


339-339: Avoid specifying long messages outside the exception class

(TRY003)


464-466: Avoid specifying long messages outside the exception class

(TRY003)


565-565: Create your own exception

(TRY002)

newton/_src/sim/builder.py

1048-1048: Avoid specifying long messages outside the exception class

(TRY003)


1055-1057: Avoid specifying long messages outside the exception class

(TRY003)


1062-1066: Avoid specifying long messages outside the exception class

(TRY003)


1071-1073: Avoid specifying long messages outside the exception class

(TRY003)


1078-1081: Avoid specifying long messages outside the exception class

(TRY003)


1091-1094: Avoid specifying long messages outside the exception class

(TRY003)


1944-1944: Avoid specifying long messages outside the exception class

(TRY003)


1946-1949: Avoid specifying long messages outside the exception class

(TRY003)


1952-1952: Avoid specifying long messages outside the exception class

(TRY003)


1954-1957: Avoid specifying long messages outside the exception class

(TRY003)

⏰ 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 Benchmarks / Run GPU Benchmarks on AWS EC2
  • GitHub Check: Run GPU Tests / Run GPU Unit Tests on AWS EC2
🔇 Additional comments (15)
pyproject.toml (2)

120-123: Verify uv source configuration with platform markers.

The platform-specific torch source definitions use explicit indices with markers. Ensure this configuration is tested and verified to work correctly with uv and the target build environments. The indices are marked explicit = true, which is correct for PyTorch's non-standard index structure, but integration testing is recommended.

If you have a development environment, verify that uv sync or uv pip install newton[torch-cu12] correctly selects the appropriate torch wheel based on the platform.


65-72: torch==2.9.0+cu129 does not exist in official PyTorch releases — this dependency will fail on non-Windows platforms.

PyTorch 2.6.0 with CUDA 12.4 wheels are available and the Windows dependency is valid. However, PyTorch 2.9.0 official releases only provide CUDA 12.6, 12.8, and 13.0 builds — there is no cu129 variant. The Linux dependency on line 70 (torch==2.9.0+cu129) will not resolve and will cause installation failures.

Consider using one of the officially available CUDA versions for 2.9.0 (cu126, cu128, or cu130) or reverting to a torch version that supports cu129 if that CUDA variant is required.

⛔ Skipped due to learnings
Learnt from: shi-eric
Repo: newton-physics/newton PR: 592
File: pyproject.toml:64-70
Timestamp: 2025-08-21T15:51:07.824Z
Learning: Self-referencing optional dependencies (e.g., "package[extra]") have been supported since pip 21.2 and are valid in pyproject.toml. However, build backends differ in metadata emission: some flatten dependencies while others preserve self-references. This can affect lockfile generators and tooling. Testing with the specific build backend and dependency tools is recommended to ensure expected behavior.
Learnt from: shi-eric
Repo: newton-physics/newton PR: 660
File: pyproject.toml:54-59
Timestamp: 2025-08-27T23:32:48.670Z
Learning: In the newton project's pyproject.toml, self-referential extras (e.g., `examples = ["newton[sim]", "newton[importers]", ...]`) are normal and encouraged for dependency management, rather than inlining the dependencies.
newton/_src/sim/builder.py (2)

1750-1828: add_link implementation correctly mirrors existing body bookkeeping

add_link correctly initializes mass/inertia, inverse quantities, state (body_q, body_qd), keys, body_shapes, and body_world in a way that matches the existing body data layout, and it cleanly hooks into custom attributes with expected_frequency=BODY. This looks safe as a drop‑in for importers and multi‑body workflows that will later attach joints and articulations explicitly.


1941-1957: Verify current_world state management in all callers of add_joint()

The new parent/child world validation in add_joint() prevents cross-world joints by checking that bodies belong to current_world at joint creation time. Verify that all internal call sites—including helpers like add_free_joints_to_floating_bodies() and any importers—properly maintain current_world state and do not switch to a different world between body and joint creation, as this would now raise ValueError instead of succeeding silently.

newton/_src/utils/import_urdf.py (4)

106-112: Root element handling for file vs. string input is correct

Normalizing both file-based and string-based URDF inputs through urdf_root = ...getroot()/fromstring(...) and feeding that into all subsequent lookups (find, findall) is the right fix and keeps the parsing logic consistent regardless of source type.


276-283: Collecting joint_indices and switching to add_link cleanly supports the new articulation workflow

Initializing joint_indices once, populating it with every joint you actually create (base/floating/fixed plus all sorted URDF joints), and building links via builder.add_link(...) (with parsed BODY‑frequency custom attributes) matches the new “links first, joints next, articulation last” model. The link ordering logic using urdf_root.find(...) and the fallback to urdf_root.findall("link") also keeps behavior consistent for both joint‑ordered and unordered URDFs.

Also applies to: 334-343, 351-359


445-455: Joint creation order guarantees add_articulation preconditions; please keep that invariant in mind

All the joint creation branches now (a) capture the returned joint ID and (b) append it to joint_indices in the same order joints are created: base joint (if any), then the sorted URDF joints. Since ModelBuilder.add_joint* always allocates new joints with increasing indices, this guarantees that joint_indices is monotonically increasing and contiguous relative to the existing joint_type array, which is exactly what builder.add_articulation(joints=...) expects. This is correct as long as no other code interleaves joint creation between these calls; if future refactors introduce such interleaving, they’ll need to either keep appending their joint IDs into joint_indices or build a separate contiguous slice for each articulation.

Also applies to: 462-463, 468-483, 507-516, 518-526, 528-531, 545-562


567-577: URDF-level articulation creation matches the new post-add_articulation(joints) model

The final builder.add_articulation(...) call that uses the collected joint_indices, the URDF root’s name as the articulation key, and ARTICULATION‑frequency custom attributes parsed from urdf_root.attrib is exactly in line with the new API design. This gives URDF importers a clear, explicit articulation object and lets users attach articulation-scoped custom attributes via the usual add_custom_attribute + URDF attributes route.

newton/tests/test_mujoco_solver.py (4)

190-207: Articulated tree setup and articulation registration look correct.

The 3-link articulated tree now uses add_link for the bodies, captures all three joint indices (free + 2 revolute), and registers them in a single articulation via add_articulation([joint1, joint2, joint3]). Joint indices are contiguous and monotonically increasing, and world ownership is consistent, so this matches the documented articulation API.

Also applies to: 208-251


2411-2416: Mocap articulation wiring is consistent with the new builder API.

The mocap/platform setup now:

  • Creates two links via add_link (mocap_body, platform_body),
  • Connects them with two fixed joints (world→mocap, mocap→platform),
  • Registers both joints in a single articulation via add_articulation([mocap_joint, platform_joint]).

Joint indices are contiguous and world-consistent, so articulation creation matches the documented requirements and should keep newton_body_to_mocap_index and MuJoCo mocap arrays coherent.

Also applies to: 2419-2441, 2453-2455


2735-2749: Limit-margin from code: articulation wiring is correct.

test_limit_margin_from_code now:

  • Creates 3 links via add_link,
  • Captures their revolute joint indices (j0, j1, j2),
  • Registers a single 3-joint articulation with builder.add_articulation([j0, j1, j2]).

Joint indices are contiguous and in increasing order, so the articulation definition is valid and consistent with the new builder semantics.


2857-2867: Limit-margin runtime update: articulation definition is valid.

For test_limit_margin_runtime_update, the builder now:

  • Creates two links with revolute joints j0 and j1,
  • Calls builder.add_articulation([j0, j1]) before finalize().

This ensures the joint pair is grouped in a single articulation and keeps the runtime limit_margin updates well-defined over the articulation’s DOFs.

newton/tests/test_ik_fk_kernels.py (1)

79-83: _add_single_joint articulation wiring is correct and uses joint_count appropriately.

Switching to add_link(...) for the child body, capturing ji = builder.joint_count, and then conditionally creating a single-joint articulation via

if ji == builder.joint_count - 1:
    builder.add_articulation([ji])

is consistent with the new API. Given the helper is only used on a fresh ModelBuilder, ji will always be the index of the just-added joint, and the articulation definition is contiguous and monotonic. Using builder.joint_count as an attribute matches the documented property-based interface.

Also applies to: 92-93, 149-151

newton/tests/test_ik_lbfgs.py (1)

32-57: IK L-BFGS builder helpers correctly migrate to add_link + explicit articulations.

Across _build_two_link_planar, _build_free_plus_revolute, and _build_single_d6:

  • Links are now created with add_link(...) instead of add_body(...).
  • Joint indices (joint1, joint2, joint) are captured.
  • Articulations are registered explicitly via builder.add_articulation([...]) with contiguous, monotonically increasing joint lists.

This matches the new ModelBuilder API and keeps the test models’ articulation structure well-defined for the L-BFGS IK solver.

Also applies to: 36-42, 44-57, 56-57, 65-83, 73-80, 82-83, 91-101

newton/tests/test_ik.py (1)

39-71: IK convergence/Jacobian tests now build models with explicit articulations; wiring looks correct.

  • _build_two_link_planar, _build_free_plus_revolute, and _build_single_d6 have been migrated to add_link(...) plus explicit joint capture and add_articulation([joint_indices...]), with contiguous, monotonically increasing joint lists.
  • _fk_end_effector_positions’s updated signature to accept model (used only to document array shape) does not alter behavior and keeps FK post-processing consistent.

These changes align the IK tests with the new articulation-centric ModelBuilder API without introducing observable behavioral regressions.

Also applies to: 79-113, 121-135, 143-156

Comment thread newton/tests/test_mujoco_solver.py
vreutskyy and others added 2 commits December 1, 2025 12:39
Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
newton/tests/test_mujoco_solver.py (1)

1006-1038: Fix topology error in test_dof_passive_stiffness_damping_multiworld by avoiding add_body + extra joint.

The CI failure (test_dof_passive_stiffness_damping_multiworld → “Multiple joints lead to body 0”) is consistent with this setup:

  • template_builder.add_body(...) now creates:
    • A link.
    • A free joint from world to that link.
    • An articulation containing that free joint.
  • The subsequent add_joint_revolute(-1, pendulum, ...) adds a second joint to the same body, also from world.

When the template is replicated and converted to MuJoCo, you end up with multiple joints leading to the same body in a single articulation, which trips the new tree validation.

To restore the original intent (single hinged pendulum per world) and respect the new API, switch to add_link and make the revolute joint the only joint for that body, then register an articulation for it:

-        template_builder = newton.ModelBuilder()
-        SolverMuJoCo.register_custom_attributes(template_builder)
-
-        pendulum = template_builder.add_body(
-            mass=1.0,
-            com=wp.vec3(0.0, 0.0, 0.0),
-            I_m=wp.mat33(np.eye(3)),
-        )
+        template_builder = newton.ModelBuilder()
+        SolverMuJoCo.register_custom_attributes(template_builder)
+
+        # Single pendulum link attached to world by a revolute joint
+        pendulum = template_builder.add_link(
+            mass=1.0,
+            com=wp.vec3(0.0, 0.0, 0.0),
+            I_m=wp.mat33(np.eye(3)),
+        )
         template_builder.add_shape_box(
             body=pendulum,
             xform=wp.transform(),
             hx=0.05,
             hy=0.05,
             hz=0.05,
         )
-        template_builder.add_joint_revolute(
-            parent=-1,
-            child=pendulum,
-            axis=(0.0, 0.0, 1.0),
-            parent_xform=wp.transform(),
-            child_xform=wp.transform(),
-        )
+        joint = template_builder.add_joint_revolute(
+            parent=-1,
+            child=pendulum,
+            axis=(0.0, 0.0, 1.0),
+            parent_xform=wp.transform(),
+            child_xform=wp.transform(),
+        )
+        template_builder.add_articulation([joint])

This removes the extra free joint, keeps each pendulum body in a simple 1‑joint tree, and should eliminate the “Multiple joints lead to body 0” topology error during MuJoCo conversion.

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

1879-1909: Make remaining jointed setups explicit articulations for consistency.

Several tests now create jointed chains via add_link + joints but still rely on implicit articulation behavior:

  • test_joint_transform_composition (Lines 1879-1909): one free joint on parent_body plus a revolute joint between parent_body and child_body.
  • test_global_joint_solver_params (Lines 2001-2023): a single revolute pendulum joint to world.
  • test_joint_frame_update (Lines 2092-2108): one revolute joint to world.

Given the new builder contract, it would be safer and clearer to register articulations explicitly in these tests, e.g.:

@@ def test_joint_transform_composition(self):
-        # Add free joint to parent
-        builder.add_joint_free(parent_body)
-
-        # Add revolute joint between parent and child with specified transforms and axis
-        builder.add_joint_revolute(
+        # Add free joint to parent and revolute joint to child
+        j_free = builder.add_joint_free(parent_body)
+        j_rev = builder.add_joint_revolute(
             parent=parent_body,
             child=child_body,
             parent_xform=parent_xform,
             child_xform=child_xform,
             axis=(0.0, 0.0, 1.0),  # Revolute about Z
         )
+        builder.add_articulation([j_free, j_rev])
@@ def test_global_joint_solver_params(self):
-        pendulum = builder.add_link(
+        pendulum = builder.add_link(
             mass=mass,
             I_m=I_sphere,
         )
-
-        # Add joint with limits - attach to world (-1)
-        builder.add_joint_revolute(
+        # Add joint with limits - attach to world (-1) and create articulation
+        joint = builder.add_joint_revolute(
             parent=-1,  # World/ground
             child=pendulum,
@@
             limit_upper=np.pi / 2,  # Upper limit at 90 degrees
         )
+        builder.add_articulation([joint])
@@ def test_joint_frame_update(self):
-        body = builder.add_link(mass=1.0, I_m=wp.diag(wp.vec3(1.0, 1.0, 1.0)))
+        body = builder.add_link(mass=1.0, I_m=wp.diag(wp.vec3(1.0, 1.0, 1.0)))
@@
-        builder.add_joint_revolute(
+        joint = builder.add_joint_revolute(
             parent=-1,
             child=body,
@@
             axis=newton.Axis.X,
         )
+        builder.add_articulation([joint])

This brings these tests in line with the rest of the suite, removes reliance on any legacy implicit articulation behavior, and ensures future topology checks see well‑formed trees.

Also applies to: 2001-2023, 2092-2108

📜 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 84609eb and a092a7f.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • newton/_src/utils/import_mjcf.py (4 hunks)
  • newton/tests/test_model.py (9 hunks)
  • newton/tests/test_mujoco_solver.py (22 hunks)
🧰 Additional context used
🧠 Learnings (10)
📚 Learning: 2025-09-24T00:26:54.486Z
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 710
File: newton/_src/utils/import_usd.py:597-599
Timestamp: 2025-09-24T00:26:54.486Z
Learning: In the Newton ModelBuilder class, `joint_count`, `body_count`, `articulation_count`, and `shape_count` are implemented as property decorated methods, so they should be accessed as attributes (e.g., `builder.joint_count`) rather than called as methods (e.g., `builder.joint_count()`).

Applied to files:

  • newton/_src/utils/import_mjcf.py
  • newton/tests/test_model.py
  • newton/tests/test_mujoco_solver.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
  • newton/tests/test_mujoco_solver.py
📚 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_mujoco_solver.py
📚 Learning: 2025-08-12T17:58:16.929Z
Learnt from: nvlukasz
Repo: newton-physics/newton PR: 519
File: newton/tests/test_ik.py:25-26
Timestamp: 2025-08-12T17:58:16.929Z
Learning: In the Newton physics engine codebase, it's acceptable for tests to import and use private `_src` internal modules and functions when needed for testing purposes, even though this breaks the public API boundary. This is an intentional project decision for test code.

Applied to files:

  • newton/tests/test_model.py
📚 Learning: 2025-08-20T18:02:36.099Z
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 587
File: newton/_src/sim/builder.py:656-661
Timestamp: 2025-08-20T18:02:36.099Z
Learning: In Newton physics, collisions between shapes with body index -1 (world-attached/global shapes) are automatically skipped by the collision system, so no manual collision filter pairs need to be added between global shapes from different builders.

Applied to files:

  • newton/tests/test_model.py
  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-07-28T15:04:54.360Z
Learnt from: adenzler-nvidia
Repo: newton-physics/newton PR: 479
File: newton/solvers/mujoco/solver_mujoco.py:2147-2149
Timestamp: 2025-07-28T15:04:54.360Z
Learning: The ls_parallel option is specific to MuJoCo Warp and is not available in the pure MuJoCo mjOption structure. It should only be set on mjw_model.opt, not mj_model.opt.

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-07-28T15:04:54.360Z
Learnt from: adenzler-nvidia
Repo: newton-physics/newton PR: 479
File: newton/solvers/mujoco/solver_mujoco.py:2147-2149
Timestamp: 2025-07-28T15:04:54.360Z
Learning: In the Newton MuJoCo solver implementation, the ls_parallel option may not be available in the pure MuJoCo mjOption structure, despite being documented in newer MuJoCo versions. The user adenzler-nvidia confirmed that mj_model does not have this attribute in their implementation, so ls_parallel should only be set on mjw_model.opt.

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-10-10T10:47:41.082Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 899
File: newton/tests/test_rigid_contact.py:214-268
Timestamp: 2025-10-10T10:47:41.082Z
Learning: In `newton/tests/test_rigid_contact.py`, using `add_shape_plane(width=0, length=0)` for an infinite plane works correctly with the collision system and does not cause degenerate AABB issues in the test scenario `test_shape_collisions_gjk_mpr_multicontact`.

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-09-22T21:08:31.901Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/examples/ik/example_ik_franka.py:121-123
Timestamp: 2025-09-22T21:08:31.901Z
Learning: In the newton physics framework, when creating warp arrays for IK solver joint variables using wp.array(self.model.joint_q, shape=(1, coord_count)), the resulting array acts as a reference/pointer to the original model's joint coordinates, so updates from the IK solver automatically reflect in the model's joint_q buffer used for rendering.

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-09-22T21:08:31.901Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/examples/ik/example_ik_franka.py:121-123
Timestamp: 2025-09-22T21:08:31.901Z
Learning: In the newton physics framework, when creating warp arrays for IK solver joint variables using wp.array(self.model.joint_q, shape=(1, coord_count)), the resulting array acts as a reference/pointer to the original model's joint coordinates, so updates from the IK solver automatically reflect in the model's joint_q buffer used for rendering, despite the general warp documentation suggesting copies are made by default.

Applied to files:

  • newton/tests/test_mujoco_solver.py
🧬 Code graph analysis (2)
newton/_src/utils/import_mjcf.py (1)
newton/_src/sim/builder.py (5)
  • add_link (1750-1828)
  • key (387-389)
  • add_joint (1894-2039)
  • add_joint_fixed (2257-2301)
  • add_articulation (1016-1109)
newton/tests/test_model.py (1)
newton/_src/sim/builder.py (6)
  • add_link (1750-1828)
  • add_joint_fixed (2257-2301)
  • add_articulation (1016-1109)
  • add_joint_free (2303-2357)
  • add_body (1830-1890)
  • collapse_fixed_joints (2751-3045)
🪛 GitHub Actions: Pull Request
newton/tests/test_mujoco_solver.py

[error] 1050-1050: Test_dof_passive_stiffness_damping_multiworld failed due to topology error during MuJoCo solver conversion: Multiple joints lead to body 0.

🪛 Ruff (0.14.6)
newton/_src/utils/import_mjcf.py

709-711: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (8)
newton/_src/utils/import_mjcf.py (1)

655-659: MJCF importer articulation wiring looks consistent with new API.

  • Using builder.add_link(...) for bodies (Line 655) and collecting all joint handles into joint_indices at each add_joint_* call (Lines 690-707, 713-715, 723-729, 732-743) keeps joint indices monotone and contiguous by construction.
  • Initializing joint_indices = [] once (Line 1022) and creating a single articulation from it at the end (Lines 1132-1135) matches ModelBuilder.add_articulation’s expectations and avoids the empty-articulation case.
  • Dict-based base_joint now correctly uses child=link before adding the joint (Lines 701-707), aligning the joint child with the created link index.

I don’t see functional or topology issues here; this importer should produce a valid articulation for the imported MJCF model.

Also applies to: 690-707, 713-715, 723-729, 732-743, 1022-1023, 1132-1135

newton/tests/test_mujoco_solver.py (3)

45-49: Single‑joint test setups now follow the explicit articulation model.

Across test_ls_parallel_option, test_tolerance_options, test_no_shapes, and test_separate_worlds_false_single_world_works, the pattern:

  • link = builder.add_link(...)
  • joint = builder.add_joint_* (-1, link, ...)
  • builder.add_articulation([joint])

creates a minimal, well‑formed 1‑joint articulation that satisfies the contiguity/monotonicity constraints and matches the new ModelBuilder contract. This looks correct and keeps the tests aligned with the new API.

Also applies to: 62-67, 1821-1824, 1859-1865


189-253: Template builder articulations for MuJoCo property tests look structurally sound.

  • In TestMuJoCoSolverPropertiesBase.setUp (Lines 189-253) and test_jnt_solimp_conversion_and_updates (Lines 774-820), you now:
    • Keep the free box as a standalone body via add_body(...) (which owns its own free‑joint articulation).
    • Build a 3‑link tree via add_link(...) + add_joint_free/add_joint_revolute and register it once with add_articulation([joint1, joint2, joint3]).
  • In test_separate_worlds_false_multi_world_validation (Lines 1831-1836), the template builder similarly creates a link, a revolute joint to world, and a dedicated articulation.

In all three, joint indices are contiguous/monotone, the trees are valid (each child has a single parent), and the free body and articulated tree are cleanly separated into distinct articulations. This matches the new articulation semantics and should work well with the MuJoCo solver’s multi‑world replication.

Also applies to: 774-820, 1831-1836


2525-2571: Mocap, custom‑attribute, and limit‑margin tests correctly use explicit articulations.

  • In test_mocap_body_transform_updates_collision_geoms (Lines 2525-2571), grouping the world‑fixed mocap_joint and the platform_joint into a single articulation via builder.add_articulation([mocap_joint, platform_joint]) yields a clean fixed‑base tree for the mocap body and its welded descendant, while the dynamic ball remains a separate free‑body articulation via add_body(...). This matches the intended “fixed base articulation + dynamic body” setup.
  • In test_custom_attributes_from_code, test_limit_margin_from_code, and test_limit_margin_runtime_update (Lines 2719-2729, 2853-2866, 2975-2984), the link/joint chains are built with add_link + add_joint_revolute, and all relevant joints are registered once via add_articulation([...]). Joint indices are contiguous/monotone and the trees are simple chains, so they satisfy add_articulation’s validation.

These changes look correct and give MuJoCo a clear articulation structure to work with for attribute propagation.

Also applies to: 2719-2729, 2853-2866, 2975-2984

newton/tests/test_model.py (4)

119-137: Fixed‑joint collapse and builder replication tests are aligned with the new articulation model.

  • add_three_cubes now builds three links via add_link() and returns the fixed‑joint indices (Lines 119-137), which you then:
    • Use as a full articulation of only fixed joints.
    • Extend with a revolute joint for a mixed fixed/non‑fixed articulation.
    • Combine with a free joint for a third articulation (Lines 140-171).
  • The expected post‑collapse counts and contents (joint_count/body_count/articulation_start/joint_type/body_com/body_mass, Lines 174-185) are consistent with the described topology.
  • test_add_builder (Lines 566-571) now constructs both fixed‑base and floating‑base sub‑builders with explicit articulations (add_articulation([j0]), add_articulation([j1])), and the assertions on counts, worlds, parent/child indices, and transforms all match the intended two‑articulation configuration.

These tests give good coverage of articulation behavior before/after collapse_fixed_joints and through add_builder, and they look correct with the new API.

Also applies to: 140-171, 186-198, 566-571


316-335: World‑grouping expectations match updated add_body + articulation behavior.

  • In test_world_grouping, the global ground is still created via main_builder.add_body(...) in world -1, which now implies:
    • A free joint for the ground body.
    • A dedicated articulation for that joint.
  • Each create_world_builder() call (Lines 330-335) builds a 2‑link articulated body via add_link + add_joint_revolute and registers it with add_articulation([j1]).

With three cloned worlds plus the global ground, the revised expectations:

  • joint_count == 4 and articulation_count == 4 (Lines 370-371).
  • joint_world == [-1, 0, 1, 2] and articulation_world == [-1, 0, 1, 2] (Lines 405-415).

are consistent with one articulation per world (and one for the global ground). The grouping assertions across particles, bodies, shapes, joints, and articulations correctly reflect the new semantics.

Also applies to: 365-372, 405-415


461-544: Group‑aware fixed‑joint collapse scenario is well‑specified.

In test_collapse_fixed_joints_with_groups:

  • World 0 chain: three links with a revolute joint to world, followed by a fixed and then a revolute joint (Lines 461-489), grouped into an articulation (Lines 490-492).
  • World 1 chain: two links with a revolute joint to world and a revolute joint between bodies, plus its own articulation (Lines 495-517).
  • Global body: created via add_body(...) in world -1 (Lines 519-523), which adds a free‑joint articulation for the global body.

Pre‑collapse checks on body_world and joint_world (Lines 525-527) match this layout. After collapse_fixed_joints:

  • The expected reduced body_count == 5 and joint_count == 5 (Lines 538-540).
  • Updated body_world and joint_world arrays (Lines 542-543).

all line up with the comments describing which body/joint was removed and how the merged bodies are grouped. The follow‑up checks on the finalized model confirm that world groups are preserved correctly through collapse and finalize.

This is a solid regression test for group‑aware fixed‑joint collapsing with a global body still present.


612-713: New articulation and joint world‑validation tests closely mirror ModelBuilder’s contract.

The added tests:

  • test_articulation_validation_contiguous, test_articulation_validation_monotonic, test_articulation_validation_empty, test_articulation_validation_world_mismatch, and test_articulation_validation_tree_structure (Lines 612-713) exercise:
    • Non‑contiguous joint lists (gap in indices).
    • Non‑monotonic lists (wrong order).
    • Empty joint lists.
    • Cross‑world joint mixes.
    • Multiple‑parent situations for a single child body.
  • test_joint_world_validation (Lines 715-729) validates that joint creation fails when parent/child bodies don’t belong to the builder’s current world.

The asserted substrings in the error messages (“contiguous”, “monotonically increasing”, “no joints”, “world”, “multiple parents”) match the messages emitted by ModelBuilder.add_articulation and the new joint world checks. This suite gives good negative‑case coverage for the updated API invariants and should help catch future regressions.

Also applies to: 715-729

Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>

@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: 1

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

194-195: Template builder setup correctly separates free body and articulated tree

Using add_body for the free-floating box and add_link + add_joint_free/add_joint_revolute + add_articulation([joint1, joint2, joint3]) for the 3-link tree is consistent with the new API. Joint indices will be contiguous and monotonically increasing, so add_articulation validation should pass, and the multi-world replication logic sees a well-formed articulation.

You can optionally drop the now-unused commented-out tree_root_initial_* lines to reduce clutter.

Also applies to: 207-208, 210-213, 218-220, 228-235, 236-243, 244-250, 252-253

📜 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 a092a7f and 9ad4a07.

📒 Files selected for processing (1)
  • newton/tests/test_mujoco_solver.py (24 hunks)
🧰 Additional context used
🧠 Learnings (10)
📓 Common learnings
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 710
File: newton/_src/utils/import_usd.py:597-599
Timestamp: 2025-09-24T00:26:54.486Z
Learning: In the Newton ModelBuilder class, `joint_count`, `body_count`, `articulation_count`, and `shape_count` are implemented as property decorated methods, so they should be accessed as attributes (e.g., `builder.joint_count`) rather than called as methods (e.g., `builder.joint_count()`).
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 710
File: newton/_src/utils/import_usd.py:597-599
Timestamp: 2025-09-24T00:26:54.486Z
Learning: In the Newton ModelBuilder class, `joint_count`, `body_count`, `articulation_count`, and `shape_count` are implemented as property decorated methods, so they should be accessed as attributes (e.g., `builder.joint_count`) rather than called as methods (e.g., `builder.joint_count()`).
📚 Learning: 2025-09-24T00:26:54.486Z
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 710
File: newton/_src/utils/import_usd.py:597-599
Timestamp: 2025-09-24T00:26:54.486Z
Learning: In the Newton ModelBuilder class, `joint_count`, `body_count`, `articulation_count`, and `shape_count` are implemented as property decorated methods, so they should be accessed as attributes (e.g., `builder.joint_count`) rather than called as methods (e.g., `builder.joint_count()`).

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-07-28T15:04:54.360Z
Learnt from: adenzler-nvidia
Repo: newton-physics/newton PR: 479
File: newton/solvers/mujoco/solver_mujoco.py:2147-2149
Timestamp: 2025-07-28T15:04:54.360Z
Learning: The ls_parallel option is specific to MuJoCo Warp and is not available in the pure MuJoCo mjOption structure. It should only be set on mjw_model.opt, not mj_model.opt.

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-07-28T15:04:54.360Z
Learnt from: adenzler-nvidia
Repo: newton-physics/newton PR: 479
File: newton/solvers/mujoco/solver_mujoco.py:2147-2149
Timestamp: 2025-07-28T15:04:54.360Z
Learning: In the Newton MuJoCo solver implementation, the ls_parallel option may not be available in the pure MuJoCo mjOption structure, despite being documented in newer MuJoCo versions. The user adenzler-nvidia confirmed that mj_model does not have this attribute in their implementation, so ls_parallel should only be set on mjw_model.opt.

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-10-10T10:47:41.082Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 899
File: newton/tests/test_rigid_contact.py:214-268
Timestamp: 2025-10-10T10:47:41.082Z
Learning: In `newton/tests/test_rigid_contact.py`, using `add_shape_plane(width=0, length=0)` for an infinite plane works correctly with the collision system and does not cause degenerate AABB issues in the test scenario `test_shape_collisions_gjk_mpr_multicontact`.

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 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/tests/test_mujoco_solver.py
📚 Learning: 2025-09-22T21:08:31.901Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/examples/ik/example_ik_franka.py:121-123
Timestamp: 2025-09-22T21:08:31.901Z
Learning: In the newton physics framework, when creating warp arrays for IK solver joint variables using wp.array(self.model.joint_q, shape=(1, coord_count)), the resulting array acts as a reference/pointer to the original model's joint coordinates, so updates from the IK solver automatically reflect in the model's joint_q buffer used for rendering.

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-09-22T21:08:31.901Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/examples/ik/example_ik_franka.py:121-123
Timestamp: 2025-09-22T21:08:31.901Z
Learning: In the newton physics framework, when creating warp arrays for IK solver joint variables using wp.array(self.model.joint_q, shape=(1, coord_count)), the resulting array acts as a reference/pointer to the original model's joint coordinates, so updates from the IK solver automatically reflect in the model's joint_q buffer used for rendering, despite the general warp documentation suggesting copies are made by default.

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-08-20T18:02:36.099Z
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 587
File: newton/_src/sim/builder.py:656-661
Timestamp: 2025-08-20T18:02:36.099Z
Learning: In Newton physics, collisions between shapes with body index -1 (world-attached/global shapes) are automatically skipped by the collision system, so no manual collision filter pairs need to be added between global shapes from different builders.

Applied to files:

  • newton/tests/test_mujoco_solver.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/tests/test_mujoco_solver.py
🧬 Code graph analysis (1)
newton/tests/test_mujoco_solver.py (3)
newton/_src/sim/builder.py (9)
  • add_link (1750-1828)
  • add_joint_revolute (2041-2128)
  • add_articulation (1016-1109)
  • add_body (1830-1890)
  • add_shape_capsule (3430-3478)
  • add_joint_free (2303-2357)
  • add_joint_prismatic (2130-2215)
  • add_shape_box (3389-3428)
  • add_joint_fixed (2257-2301)
newton/_src/utils/import_usd.py (1)
  • add_body (430-444)
newton/_src/utils/import_utils.py (1)
  • transform (139-140)
⏰ 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 (11)
newton/tests/test_mujoco_solver.py (11)

46-48: LS/tolerance tests now correctly build a minimal articulation

Both test_ls_parallel_option and test_tolerance_options now create a single-link, single-revolute-joint articulation via add_linkadd_joint_revoluteadd_articulation([joint]). That matches the new articulation-first API and gives MuJoCo exactly one DOF as intended.

Also applies to: 63-65


776-777: Solimp test now builds a proper articulated chain

The solimplimit test’s template builder now uses add_link + add_joint_free + two revolute joints, and registers them together via add_articulation([joint1_idx, joint2_idx, joint3_idx]). That fixes the earlier missing-articulation pattern and ensures all DOFs in the tree participate in articulation-based MuJoCo mapping (via dof_to_mjc_joint).

Also applies to: 785-789, 791-795, 805-809, 819-820


1009-1013: Passive stiffness/damping multiworld test uses articulation-aware template

The pendulum template now consists of a single link, one revolute joint to world, and an articulation created with add_articulation([joint]). Replication across worlds will therefore produce clean, per-world articulations, which is exactly what the multiworld stiffness/damping checks assume.

Also applies to: 1021-1027, 1028-1033


1822-1824: Minimal prismatic model correctly migrated off add_body

test_no_shapes now uses add_link + add_joint_prismatic(-1, b) with add_articulation([j]), giving a single-DOF articulated body with no shapes. This matches the intent of the test and avoids the implicit free-joint articulation that add_body now creates.


1833-1836: Multi-world validation template builder now creates an articulation

The template used in test_separate_worlds_false_multi_world_validation now wires its single revolute joint into an articulation via add_articulation([joint]), which is important for exercising solver behavior under separate_worlds in an articulation-based model.


1862-1865: Single-world separate_worlds=False test aligned with new API

The single-world test now builds a one-link, one-revolute-joint articulation via add_linkadd_joint_revoluteadd_articulation([j]). That keeps the DOF count (mj_model.nv == 1) clear and consistent with articulation-based wiring.


2216-2222: Noncontiguous-joint indexing test builds a minimal articulation correctly

The robot fixture now uses add_link twice and a single revolute joint, then registers that joint via robot.add_articulation([0]). For this test—focused on selection and indexing of joints across worlds—that’s exactly what you need: one simple articulated chain per replicated robot.


2529-2537: Mocap/platform tree is now an explicit articulation

In the mocap test, you now:

  • Create mocap_body and platform_body via add_link.
  • Connect them with two fixed joints (mocap_joint, platform_joint).
  • Register an articulation with builder.add_articulation([mocap_joint, platform_joint]).

This cleanly models the fixed-base mocap tree as its own articulation, while the dynamic ball continues to use add_body (and its implicit articulation). That matches how the solver identifies and updates mocap bodies.

Also applies to: 2546-2558, 2571-2572


2720-2721: Custom-attribute chain from code wired as a single articulation

test_custom_attributes_from_code now builds a 3-link chain via add_link + revolute joints and groups all three joints in add_articulation([j0, j1, j2]). This is the right shape for testing joint/geom custom attributes (like condim) across an articulated chain.

Also applies to: 2723-2724, 2726-2727, 2729-2729


2854-2856: Limit-margin-from-code test now uses a well-formed 3‑joint articulation

The limit-margin test builds three links, three revolute joints (two with custom margins, one default), and registers them via builder.add_articulation([j0, j1, j2]). That guarantees all tested joints are part of the same articulation and show up consistently in both Newton and MuJoCo arrays.

Also applies to: 2858-2860, 2862-2864, 2866-2867


2976-2978: Runtime limit-margin update test uses a 2‑joint articulation

Similarly, the runtime update test now creates two links and revolute joints and wires them into a single articulation. This keeps model.mujoco.limit_margin and mjw_model.jnt_margin in 1‑1 correspondence for the DOFs under test, making the update assertions robust.

Also applies to: 2980-2982, 2984-2985

Comment thread newton/tests/test_mujoco_solver.py
Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
newton/tests/test_mujoco_solver.py (2)

2000-2023: Add an articulation for the pendulum joint in test_global_joint_solver_params

Here you build a single pendulum with add_link(...) and a world‑attached revolute joint but never call add_articulation. Under the new builder API, joints created this way are expected to be grouped into an articulation so articulation‑based code paths (including limit stiffness/damping conversion) see them consistently.

Recommend registering the joint explicitly:

-        # Add joint with limits - attach to world (-1)
-        builder.add_joint_revolute(
+        # Add joint with limits - attach to world (-1)
+        joint = builder.add_joint_revolute(
             parent=-1,  # World/ground
             child=pendulum,
             parent_xform=wp.transform(wp.vec3(0.0, 0.0, 0.0), wp.quat_identity()),
             child_xform=wp.transform(wp.vec3(0.0, 0.0, length), wp.quat_identity()),
             axis=newton.Axis.Y,
             limit_lower=0.0,  # Lower limit at 0 degrees
             limit_upper=np.pi / 2,  # Upper limit at 90 degrees
         )
+
+        # Register the articulation containing this joint
+        builder.add_articulation([joint])

This should make the soft vs. stiff limit behavior exercise the same articulation pipeline as the rest of the tests.


2091-2107: Also register the single revolute joint as an articulation in test_joint_frame_update

test_joint_frame_update builds a one‑link model with a world‑attached revolute joint but never calls add_articulation, even though the test inspects MuJoCo joint frames (jnt_pos, jnt_axis) that are normally derived from articulation wiring.

To align with the new API and avoid joints being treated as “unregistered”, consider:

-        body = builder.add_link(mass=1.0, I_m=wp.diag(wp.vec3(1.0, 1.0, 1.0)))
+        body = builder.add_link(mass=1.0, I_m=wp.diag(wp.vec3(1.0, 1.0, 1.0)))
@@
-        builder.add_joint_revolute(
+        joint = builder.add_joint_revolute(
             parent=-1,
             child=body,
             parent_xform=parent_xform,
             child_xform=child_xform,
             axis=newton.Axis.X,
         )
+
+        # Register articulation for this single revolute joint
+        builder.add_articulation([joint])

This keeps the test logic unchanged while ensuring the joint is handled through the same articulation‑based machinery as elsewhere.

♻️ Duplicate comments (1)
newton/tests/test_mujoco_solver.py (1)

1878-1927: Register the free + revolute joints as an articulation in test_joint_transform_composition

This test now creates a 2‑link mechanism with:

  • a free joint on the parent body, and
  • a revolute joint from parent to child,

but never calls add_articulation, so the pair may not be treated as a proper articulation by the MuJoCo wiring despite relying on DOF indexing (e.g., builder.joint_q[7]) and articulation‑based paths in the solver.

Consider explicitly registering both joints in a single articulation:

-        # Add free joint to parent
-        builder.add_joint_free(parent_body)
-
-        # Add revolute joint between parent and child with specified transforms and axis
-        builder.add_joint_revolute(
+        # Add free joint to parent
+        root_joint = builder.add_joint_free(parent_body)
+
+        # Add revolute joint between parent and child with specified transforms and axis
+        hinge_joint = builder.add_joint_revolute(
             parent=parent_body,
             child=child_body,
             parent_xform=parent_xform,
             child_xform=child_xform,
             axis=(0.0, 0.0, 1.0),  # Revolute about Z
         )
+
+        # Register articulation for the root free joint and the revolute joint
+        builder.add_articulation([root_joint, hinge_joint])

This keeps the intended DOF ordering (free joint first, revolute at index 7) while making the mechanism a first‑class articulation for the solver.

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

2214-2222: Avoid hard‑coding joint index when registering the robot articulation

Using a literal 0 in robot.add_articulation([0]) assumes this will always be the first (and only) joint on the robot builder. That’s true today, but it’s brittle if more joints get added later.

You can make the test more robust with a minor refactor:

-        robot.add_link()  # body 0
+        robot.add_link()  # body 0
@@
-        robot.add_link()  # body 1
+        robot.add_link()  # body 1
@@
-        robot.add_joint_revolute(parent=0, child=1, axis=(0, 0, 1))  # joint 0
-        robot.add_articulation([0])
+        joint = robot.add_joint_revolute(parent=0, child=1, axis=(0, 0, 1))
+        robot.add_articulation([joint])

This keeps the non‑contiguous indexing scenario intact while decoupling the test from implicit joint ordering.

📜 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 9ad4a07 and 0931062.

📒 Files selected for processing (1)
  • newton/tests/test_mujoco_solver.py (24 hunks)
🧰 Additional context used
🧠 Learnings (10)
📓 Common learnings
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 710
File: newton/_src/utils/import_usd.py:597-599
Timestamp: 2025-09-24T00:26:54.486Z
Learning: In the Newton ModelBuilder class, `joint_count`, `body_count`, `articulation_count`, and `shape_count` are implemented as property decorated methods, so they should be accessed as attributes (e.g., `builder.joint_count`) rather than called as methods (e.g., `builder.joint_count()`).
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 710
File: newton/_src/utils/import_usd.py:597-599
Timestamp: 2025-09-24T00:26:54.486Z
Learning: In the Newton ModelBuilder class, `joint_count`, `body_count`, `articulation_count`, and `shape_count` are implemented as property decorated methods, so they should be accessed as attributes (e.g., `builder.joint_count`) rather than called as methods (e.g., `builder.joint_count()`).
📚 Learning: 2025-09-24T00:26:54.486Z
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 710
File: newton/_src/utils/import_usd.py:597-599
Timestamp: 2025-09-24T00:26:54.486Z
Learning: In the Newton ModelBuilder class, `joint_count`, `body_count`, `articulation_count`, and `shape_count` are implemented as property decorated methods, so they should be accessed as attributes (e.g., `builder.joint_count`) rather than called as methods (e.g., `builder.joint_count()`).

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-07-28T15:04:54.360Z
Learnt from: adenzler-nvidia
Repo: newton-physics/newton PR: 479
File: newton/solvers/mujoco/solver_mujoco.py:2147-2149
Timestamp: 2025-07-28T15:04:54.360Z
Learning: The ls_parallel option is specific to MuJoCo Warp and is not available in the pure MuJoCo mjOption structure. It should only be set on mjw_model.opt, not mj_model.opt.

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-07-28T15:04:54.360Z
Learnt from: adenzler-nvidia
Repo: newton-physics/newton PR: 479
File: newton/solvers/mujoco/solver_mujoco.py:2147-2149
Timestamp: 2025-07-28T15:04:54.360Z
Learning: In the Newton MuJoCo solver implementation, the ls_parallel option may not be available in the pure MuJoCo mjOption structure, despite being documented in newer MuJoCo versions. The user adenzler-nvidia confirmed that mj_model does not have this attribute in their implementation, so ls_parallel should only be set on mjw_model.opt.

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-10-10T10:47:41.082Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 899
File: newton/tests/test_rigid_contact.py:214-268
Timestamp: 2025-10-10T10:47:41.082Z
Learning: In `newton/tests/test_rigid_contact.py`, using `add_shape_plane(width=0, length=0)` for an infinite plane works correctly with the collision system and does not cause degenerate AABB issues in the test scenario `test_shape_collisions_gjk_mpr_multicontact`.

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 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/tests/test_mujoco_solver.py
📚 Learning: 2025-09-22T21:08:31.901Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/examples/ik/example_ik_franka.py:121-123
Timestamp: 2025-09-22T21:08:31.901Z
Learning: In the newton physics framework, when creating warp arrays for IK solver joint variables using wp.array(self.model.joint_q, shape=(1, coord_count)), the resulting array acts as a reference/pointer to the original model's joint coordinates, so updates from the IK solver automatically reflect in the model's joint_q buffer used for rendering.

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-09-22T21:08:31.901Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/examples/ik/example_ik_franka.py:121-123
Timestamp: 2025-09-22T21:08:31.901Z
Learning: In the newton physics framework, when creating warp arrays for IK solver joint variables using wp.array(self.model.joint_q, shape=(1, coord_count)), the resulting array acts as a reference/pointer to the original model's joint coordinates, so updates from the IK solver automatically reflect in the model's joint_q buffer used for rendering, despite the general warp documentation suggesting copies are made by default.

Applied to files:

  • newton/tests/test_mujoco_solver.py
📚 Learning: 2025-08-20T18:02:36.099Z
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 587
File: newton/_src/sim/builder.py:656-661
Timestamp: 2025-08-20T18:02:36.099Z
Learning: In Newton physics, collisions between shapes with body index -1 (world-attached/global shapes) are automatically skipped by the collision system, so no manual collision filter pairs need to be added between global shapes from different builders.

Applied to files:

  • newton/tests/test_mujoco_solver.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/tests/test_mujoco_solver.py
⏰ 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 Benchmarks / Run GPU Benchmarks on AWS EC2
  • GitHub Check: Run GPU Tests / Run GPU Unit Tests on AWS EC2
🔇 Additional comments (8)
newton/tests/test_mujoco_solver.py (8)

46-48: Articulation setup for basic solver option tests looks correct

Using add_link(...), a world-attached revolute joint, and add_articulation([joint]) produces a minimal 1‑DOF articulation for both tests, which matches the new post‑add_articulation(joints) workflow and keeps the MuJoCo option checks focused on solver behavior rather than builder quirks.

Also applies to: 63-65


192-253: Multi‑world template builder now cleanly separates free body and articulated tree

Switching the free box to add_body(..., xform=...) and the 3‑link chain to add_link(...) + add_joint_* + add_articulation([joint1, joint2, joint3]) gives you exactly one free‑floating articulation plus one tree articulation per world. That aligns with the new API semantics and keeps body indices for the articulated chain contiguous and monotonic as required.


774-820: jnt_solimp test builder follows the new articulation workflow

The template builder now creates:

  • one free‑floating body via add_body(...), and
  • a 3‑link articulated chain via add_link + add_joint_free/add_joint_revolute + add_articulation([joint1_idx, joint2_idx, joint3_idx]).

This matches the post‑add_articulation(joints) design and ensures the DOF‑to‑MuJoCo‑joint mapping exercised later in the test is well‑defined across replicated worlds.


1006-1035: Pendulum template for passive stiffness/damping test is wired correctly

The updated template builder (add_link + world‑attached revolute + add_articulation([joint]), then replicated to multiple worlds) cleanly produces one articulated pendulum per world, which fits the assumptions in the passive stiffness/damping checks and the joint_mjc_dof_start/dof_to_mjc_joint mapping.


2523-2572: Mocap body + platform articulation wiring looks consistent with new API

Defining the mocap root and platform via add_link(...), connecting them with two FIXED joints, and then calling builder.add_articulation([mocap_joint, platform_joint]) cleanly encodes the fixed‑base mocap articulation. The dynamic ball is still created via add_body(...), which now naturally lives in its own free‑floating articulation. This matches the test’s intent and should keep newton_body_to_mocap_index and the mocap_pos/mocap_quat updates well‑defined.


2718-2730: Custom attribute test now uses an explicit 3‑joint articulation

Creating three links, wiring them with two internal revolute joints plus a world‑attached root revolute, and registering [j0, j1, j2] as a single articulation is a good fit for exercising mujoco:condim propagation. The articulation grouping matches how the solver will see the chain, so the condim checks on both the Newton model and mjw_model look sound.


2850-2867: limit_margin‑from‑code test correctly builds and registers a 3‑joint chain

The switch to add_link() plus add_joint_revolute(...) for b0, b1, and b2, followed by builder.add_articulation([j0, j1, j2]), makes this test’s articulation structure explicit and consistent with how limit_margin is stored and mapped to jnt_margin. The expected [0.01, 0.02, 0.0] pattern should remain stable under this wiring.


2970-2985: Runtime limit_margin update test uses consistent articulation wiring

Reusing the add_link + two revolute joints + add_articulation([j0, j1]) pattern here keeps the runtime update path (notify_model_changed(SolverNotifyFlags.JOINT_DOF_PROPERTIES)) aligned with the static limit_margin tests. The builder/solver setup now clearly encodes a 2‑joint articulation, so the before/after jnt_margin comparisons are testing exactly what they should.

vreutskyy and others added 4 commits December 1, 2025 16:14
Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>
Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
@eric-heiden eric-heiden merged commit 6b690f9 into newton-physics:main Dec 2, 2025
20 checks passed
JerryJiehanWang pushed a commit to JerryJiehanWang/newton that referenced this pull request Dec 2, 2025
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>
Signed-off-by: Eric Heiden <eheiden@nvidia.com>
Co-authored-by: Viktor Reutskyy <33062116+vreutskyy@users.noreply.github.com>
Co-authored-by: Viktor Reutskyy <vreutskyy@nvidia.com>
@coderabbitai coderabbitai Bot mentioned this pull request Jan 26, 2026
4 tasks
eric-heiden added a commit to eric-heiden/newton that referenced this pull request Jan 28, 2026
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>
Signed-off-by: Eric Heiden <eheiden@nvidia.com>
Co-authored-by: Viktor Reutskyy <33062116+vreutskyy@users.noreply.github.com>
Co-authored-by: Viktor Reutskyy <vreutskyy@nvidia.com>
@coderabbitai coderabbitai Bot mentioned this pull request Feb 6, 2026
4 tasks
mmacklin pushed a commit to mmacklin/newton that referenced this pull request Apr 7, 2026
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>
Signed-off-by: Eric Heiden <eheiden@nvidia.com>
Co-authored-by: Viktor Reutskyy <33062116+vreutskyy@users.noreply.github.com>
Co-authored-by: Viktor Reutskyy <vreutskyy@nvidia.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.

Update ModelBuilder API for creating articulations

2 participants