Update ModelBuilder API for creating articulations#1073
Conversation
…) for bodies within articulations Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
📝 WalkthroughWalkthroughIntroduces 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
Sequence Diagram(s)mermaid Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Points to focus during review:
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used🧠 Learnings (2)📚 Learning: 2025-09-24T00:26:54.486ZApplied to files:
📚 Learning: 2025-07-21T19:11:04.077ZApplied to files:
🧬 Code graph analysis (1)newton/tests/test_joint_limits.py (1)
⏰ 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)
🔇 Additional comments (4)
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. Comment |
There was a problem hiding this comment.
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 byadd_body(). Based on the PR description, the newadd_body()method creates a standalone body with its own internal articulation and free joint. This means:
- Line 155 creates an articulation named
pendulum_{i}- Lines 156-157 each call
add_body(), which should create their own separate articulations- 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 usesadd_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 useadd_link()instead ofadd_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 ofadd_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 ofadd_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 ofadd_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 ofadd_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()tobuilder.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_bodyfunction to avoid confusion with the ModelBuilder'sadd_bodymethod. Something likeadd_body_to_builderorcreate_and_add_linkwould make it clearer that this is a helper function that callsbuilder.add_link().
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 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.pynewton/_src/sim/builder.pynewton/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()toadd_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()toadd_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()beforeadd_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 byadd_joint_d6(parent=-1, ...). Theadd_body()method explicitly creates a standalone body with an internal free joint, and thenadd_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 byadd_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.
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>
There was a problem hiding this comment.
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 beforeadd_link().This code violates the new API contract. The sequence is:
- Line 1261-1267:
parse_body(..., add_body_to_builder=True)→ calls localadd_body()at line 464 → callsbuilder.add_link()at line 434- Line 1268:
builder.add_articulation(key)is calledAccording 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—reorderadd_articulation()beforeparse_body().The change from
add_body()toadd_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 invokesadd_link(), butbuilder.add_articulation(key)is not called until line 1268. Sinceadd_link()requires an existing articulation, this will fail at runtime.Move
builder.add_articulation(key)to line 1261 (beforeparse_body()) and shift theparse_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 sinceadd_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
📒 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.pynewton/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()beforeadd_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_bodymethod correctly implements the PR's goal of simplifying free-floating body creation by:
- Creating an articulation with an auto-generated key (line 1738)
- Creating a link via
add_link(lines 1741-1749)- 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 calladd_articulation(),add_link(), andadd_joint_free()explicitly.
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>
Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>
Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>
There was a problem hiding this comment.
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, andb_ballare treated as kinematic, so they won’t respond dynamically to gravity, undermining the “basic joints” motion demo.- Only
a_ballis 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 usingadd_linkstill miss requiredadd_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’stemplate_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’srobotbuilder (links 0/1 and revolute joint, lines 2099–2103)Per the new builder API, every set of joints created via
add_linkshould be registered in an articulation using their indices. For each of these cases, please:
- Capture the joint index returned from
add_joint_*.- Call
add_articulation([joint_idx])(or a list of contiguous joint indices) beforefinalize().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_optionandtest_tolerance_optionsnow build a link viaadd_link(...)and a revolute joint to the world, but never register an articulation. Per the newModelBuilder.add_linkcontract, 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 articulationsThe new
add_articulation()checks (non-empty, monotonic, contiguous, in-range, single-world, tree-structured) are consistent with howarticulation_startis later interpreted infinalize()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, whicharticulation_startsemantics don’t really support.
1830-1890: add_body wrapper matches the “free-floating body” objective; consider optional articulation custom attrsThe 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}_articulationis clearly documented. If you anticipate users wanting ARTICULATION‑frequency custom attributes for these single‑body articulations, you might extend the signature with an optionalarticulation_custom_attributesand pass it through toadd_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 hardeningThe new/updated raises in
parse_urdfcurrently use a genericExceptionfor unsupported joint types and fairly longValueErrormessages. If you care about Ruff’s TRY00x hints, you could (1) switch the unsupported-type branch to a more specificValueErrorand (2) move the verbose messages into helper functions or shorter strings. Separately, if URDF content may come from untrusted sources, consider swappingxml.etree.ElementTreefordefusedxml.ElementTreein thefromstring/parsecalls 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
⛔ Files ignored due to path filters (1)
uv.lockis 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.pynewton/tests/test_ik.pynewton/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.pynewton/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.pynewton/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.pynewton/tests/test_mujoco_solver.pynewton/_src/sim/builder.pynewton/examples/basic/example_basic_joints.pynewton/tests/test_ik_fk_kernels.pynewton/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.pynewton/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.pynewton/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 syncoruv 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_linkcorrectly initializes mass/inertia, inverse quantities, state (body_q,body_qd), keys,body_shapes, andbody_worldin a way that matches the existing body data layout, and it cleanly hooks into custom attributes withexpected_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 tocurrent_worldat joint creation time. Verify that all internal call sites—including helpers likeadd_free_joints_to_floating_bodies()and any importers—properly maintaincurrent_worldstate and do not switch to a different world between body and joint creation, as this would now raiseValueErrorinstead of succeeding silently.newton/_src/utils/import_urdf.py (4)
106-112: Root element handling for file vs. string input is correctNormalizing 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: Collectingjoint_indicesand switching toadd_linkcleanly supports the new articulation workflowInitializing
joint_indicesonce, populating it with every joint you actually create (base/floating/fixed plus all sorted URDF joints), and building links viabuilder.add_link(...)(with parsed BODY‑frequency custom attributes) matches the new “links first, joints next, articulation last” model. The link ordering logic usingurdf_root.find(...)and the fallback tourdf_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 guaranteesadd_articulationpreconditions; please keep that invariant in mindAll the joint creation branches now (a) capture the returned joint ID and (b) append it to
joint_indicesin the same order joints are created: base joint (if any), then the sorted URDF joints. SinceModelBuilder.add_joint*always allocates new joints with increasing indices, this guarantees thatjoint_indicesis monotonically increasing and contiguous relative to the existingjoint_typearray, which is exactly whatbuilder.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 intojoint_indicesor 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)modelThe final
builder.add_articulation(...)call that uses the collectedjoint_indices, the URDF root’snameas the articulation key, and ARTICULATION‑frequency custom attributes parsed fromurdf_root.attribis 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 usualadd_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_linkfor the bodies, captures all three joint indices (free + 2 revolute), and registers them in a single articulation viaadd_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_indexand 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_codenow:
- 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
j0andj1,- Calls
builder.add_articulation([j0, j1])beforefinalize().This ensures the joint pair is grouped in a single articulation and keeps the runtime
limit_marginupdates well-defined over the articulation’s DOFs.newton/tests/test_ik_fk_kernels.py (1)
79-83:_add_single_jointarticulation wiring is correct and usesjoint_countappropriately.Switching to
add_link(...)for the child body, capturingji = builder.joint_count, and then conditionally creating a single-joint articulation viaif 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,jiwill always be the index of the just-added joint, and the articulation definition is contiguous and monotonic. Usingbuilder.joint_countas 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 toadd_link+ explicit articulations.Across
_build_two_link_planar,_build_free_plus_revolute, and_build_single_d6:
- Links are now created with
add_link(...)instead ofadd_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
ModelBuilderAPI 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_d6have been migrated toadd_link(...)plus explicit joint capture andadd_articulation([joint_indices...]), with contiguous, monotonically increasing joint lists._fk_end_effector_positions’s updated signature to acceptmodel(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
Signed-off-by: Viktor Reutskyy <vreutskyy@nvidia.com>
There was a problem hiding this comment.
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 intest_dof_passive_stiffness_damping_multiworldby avoidingadd_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_linkand 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 onparent_bodyplus a revolute joint betweenparent_bodyandchild_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
⛔ Files ignored due to path filters (1)
uv.lockis 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.pynewton/tests/test_model.pynewton/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.pynewton/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.pynewton/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.pynewton/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 intojoint_indicesat eachadd_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) matchesModelBuilder.add_articulation’s expectations and avoids the empty-articulation case.- Dict-based
base_jointnow correctly useschild=linkbefore 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, andtest_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
ModelBuildercontract. 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) andtest_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_revoluteand register it once withadd_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‑fixedmocap_jointand theplatform_jointinto a single articulation viabuilder.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 viaadd_body(...). This matches the intended “fixed base articulation + dynamic body” setup.- In
test_custom_attributes_from_code,test_limit_margin_from_code, andtest_limit_margin_runtime_update(Lines 2719-2729, 2853-2866, 2975-2984), the link/joint chains are built withadd_link+add_joint_revolute, and all relevant joints are registered once viaadd_articulation([...]). Joint indices are contiguous/monotone and the trees are simple chains, so they satisfyadd_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_cubesnow builds three links viaadd_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_jointsand throughadd_builder, and they look correct with the new API.Also applies to: 140-171, 186-198, 566-571
316-335: World‑grouping expectations match updatedadd_body+ articulation behavior.
- In
test_world_grouping, the global ground is still created viamain_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 viaadd_link+add_joint_revoluteand registers it withadd_articulation([j1]).With three cloned worlds plus the global ground, the revised expectations:
joint_count == 4andarticulation_count == 4(Lines 370-371).joint_world == [-1, 0, 1, 2]andarticulation_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_worldandjoint_world(Lines 525-527) match this layout. Aftercollapse_fixed_joints:
- The expected reduced
body_count == 5andjoint_count == 5(Lines 538-540).- Updated
body_worldandjoint_worldarrays (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 mirrorModelBuilder’s contract.The added tests:
test_articulation_validation_contiguous,test_articulation_validation_monotonic,test_articulation_validation_empty,test_articulation_validation_world_mismatch, andtest_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_articulationand 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
There was a problem hiding this comment.
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 treeUsing
add_bodyfor the free-floating box andadd_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, soadd_articulationvalidation 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
📒 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 articulationBoth
test_ls_parallel_optionandtest_tolerance_optionsnow create a single-link, single-revolute-joint articulation viaadd_link→add_joint_revolute→add_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 chainThe solimplimit test’s template builder now uses
add_link+add_joint_free+ two revolute joints, and registers them together viaadd_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 (viadof_to_mjc_joint).Also applies to: 785-789, 791-795, 805-809, 819-820
1009-1013: Passive stiffness/damping multiworld test uses articulation-aware templateThe 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 offadd_body
test_no_shapesnow usesadd_link+add_joint_prismatic(-1, b)withadd_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 thatadd_bodynow creates.
1833-1836: Multi-world validation template builder now creates an articulationThe template used in
test_separate_worlds_false_multi_world_validationnow wires its single revolute joint into an articulation viaadd_articulation([joint]), which is important for exercising solver behavior underseparate_worldsin an articulation-based model.
1862-1865: Single-worldseparate_worlds=Falsetest aligned with new APIThe single-world test now builds a one-link, one-revolute-joint articulation via
add_link→add_joint_revolute→add_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 correctlyThe robot fixture now uses
add_linktwice and a single revolute joint, then registers that joint viarobot.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 articulationIn the mocap test, you now:
- Create
mocap_bodyandplatform_bodyviaadd_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_codenow builds a 3-link chain viaadd_link+ revolute joints and groups all three joints inadd_articulation([j0, j1, j2]). This is the right shape for testing joint/geom custom attributes (likecondim) 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 articulationThe 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 articulationSimilarly, the runtime update test now creates two links and revolute joints and wires them into a single articulation. This keeps
model.mujoco.limit_marginandmjw_model.jnt_marginin 1‑1 correspondence for the DOFs under test, making the update assertions robust.Also applies to: 2980-2982, 2984-2985
There was a problem hiding this comment.
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 intest_global_joint_solver_paramsHere you build a single pendulum with
add_link(...)and a world‑attached revolute joint but never calladd_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 intest_joint_frame_update
test_joint_frame_updatebuilds a one‑link model with a world‑attached revolute joint but never callsadd_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 intest_joint_transform_compositionThis 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 articulationUsing a literal
0inrobot.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
📒 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 correctUsing
add_link(...), a world-attached revolute joint, andadd_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 treeSwitching the free box to
add_body(..., xform=...)and the 3‑link chain toadd_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 workflowThe 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 correctlyThe 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 thejoint_mjc_dof_start/dof_to_mjc_jointmapping.
2523-2572: Mocap body + platform articulation wiring looks consistent with new APIDefining the mocap root and platform via
add_link(...), connecting them with two FIXED joints, and then callingbuilder.add_articulation([mocap_joint, platform_joint])cleanly encodes the fixed‑base mocap articulation. The dynamic ball is still created viaadd_body(...), which now naturally lives in its own free‑floating articulation. This matches the test’s intent and should keepnewton_body_to_mocap_indexand the mocap_pos/mocap_quat updates well‑defined.
2718-2730: Custom attribute test now uses an explicit 3‑joint articulationCreating 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 exercisingmujoco:condimpropagation. The articulation grouping matches how the solver will see the chain, so the condim checks on both the Newton model andmjw_modellook sound.
2850-2867: limit_margin‑from‑code test correctly builds and registers a 3‑joint chainThe switch to
add_link()plusadd_joint_revolute(...)forb0,b1, andb2, followed bybuilder.add_articulation([j0, j1, j2]), makes this test’s articulation structure explicit and consistent with howlimit_marginis stored and mapped tojnt_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 wiringReusing 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/afterjnt_margincomparisons are testing exactly what they should.
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>
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>
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>
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>
Description
add_link()method to create bodies within articulationsadd_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 bodyadd_articulation(joints)method definitionNewton Migration Guide
Please ensure the migration guide for warp.sim users is up-to-date with the changes made in this PR.
docs/migration.rstis up-to dateBefore your PR is "Ready for review"
newton/tests/test_examples.py)pre-commit run -aSummary by CodeRabbit
New Features
API Changes
Workflow Updates
Tests / Docs
✏️ Tip: You can customize this high-level summary in your review settings.