Skip to content

Public API refactor#519

Merged
nvlukasz merged 11 commits into
newton-physics:mainfrom
nvlukasz:public-api-refactor-v2
Aug 14, 2025
Merged

Public API refactor#519
nvlukasz merged 11 commits into
newton-physics:mainfrom
nvlukasz:public-api-refactor-v2

Conversation

@nvlukasz

@nvlukasz nvlukasz commented Aug 8, 2025

Copy link
Copy Markdown
Member

Description

Code refactor to improve the public API clarity, discoverability, and consistency.

  • The bulk of the code is moved to newton/_src, thus it is considered private. I left the structure mostly unchanged there, for now.
  • newton/__init__.py defines all the public entry points at the top level (newton.*).
  • There are a few public submodules, with their public APIs defined in newton/ik.py, newton/solvers.py, etc. These define public entry points like newton.ik.* or newton.solvers.*. Here, we simply import the symbols from the _src tree that we wish to expose publicly. This way, there is no risk of "leaking" unwanted symbols from the private _src tree, and adding things to the public API becomes a deliberate act.
  • Some things were renamed for consistency, e.g. all solvers start with Solver*.
  • Integer constants like JOINT_TYPE_* were converted to IntEnum or IntFlag, as appropriate.

Note that this is still a work in progress, but the basic structure is there. Still to do:

  • Expose more symbols as needed.
  • Update the docs and generate_api.py.
  • Do another verification pass over everything.

Newton Migration Guide

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

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

Before your PR is "Ready for review"

  • All commits are signed-off to indicate that your contribution adheres to the Developer Certificate of Origin requirements
  • Necessary tests have been added and new examples are tested (see newton/tests/test_examples.py)
  • Documentation is up-to-date
  • Code passes formatting and linting checks with pre-commit run -a

Summary by CodeRabbit

  • New Features

    • Added top-level modules (ik, selection, sensors, solvers, utils, viewer); new IK-prefixed IK objectives; remesh utility and recorder/renderer exports.
  • Refactor

    • Public API reorganized: solver classes now named Solver*, renderers named Renderer* (moved to viewer); many numeric constants replaced by enums/flags (JointType, JointMode, EqType, ShapeFlags, ParticleFlags); imports streamlined.
  • Documentation

    • API docs, migration guide, quickstart, examples, and tests updated to match renames and relocations.
  • Chores

    • Removed legacy factory helpers/constants; importer functions now accept mesh_maxhullvert.

@nvlukasz nvlukasz requested a review from mmacklin August 8, 2025 21:04
@coderabbitai

coderabbitai Bot commented Aug 8, 2025

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Reorganizes public API into internal _src packages, replaces many numeric constants with IntEnum flag types, renames solvers and renderers (Solver*/Renderer*), converts particle/shape flag arrays to int32, updates import paths across code, docs, examples, and tests. No algorithmic changes.

Changes

Cohort / File(s) Change Summary
Top-level façades & re-exports
newton/__init__.py, newton/utils.py, newton/geometry.py, newton/ik.py, newton/selection.py, newton/sensors.py, newton/solvers.py
Add/adjust top-level re-exports from _src modules; expose subsystems (geometry, ik, selection, sensors, solvers, utils, viewer); add __version__; remove legacy factory functions and many old numeric constants.
_src geometry & flags
newton/_src/geometry/__init__.py, newton/_src/geometry/flags.py, newton/_src/geometry/types.py, newton/_src/geometry/kernels.py, newton/_src/geometry/raycast.py
Introduce _src geometry package; expose SDF/GeoType/Mesh and broad-phase classes; add ParticleFlags/ShapeFlags IntEnum; update kernels to use enums and wp.int32; fix relative imports.
Removed legacy geometry modules
- newton/geometry/__init__.py, - newton/geometry/flags.py
Delete legacy public geometry module and flag constants; replaced by _src implementations and thin newton.geometry façade.
Sim enums & joint types
newton/_src/sim/joints.py, newton/_src/sim/__init__.py
Replace JOINT_/EQ_/JOINT_MODE_ numeric constants with IntEnum types: JointType, JointMode, EqType; update exports and call sites.
Model / Builder / Articulation
newton/_src/sim/builder.py, newton/_src/sim/model.py, newton/_src/sim/articulation.py, newton/_src/sim/state.py, newton/_src/sim/sew.py
Migrate to relative imports; replace raw constants with enums/flags; update docstrings/type hints; finalize particle/shape flags as int32; adjust solver-name references.
Solvers consolidation & renames
newton/_src/solvers/__init__.py, newton/_src/solvers/*, newton/solvers.py
Consolidate solver package under _src.solvers; rename public solver classes to Solver*; add SolverNotifyFlags; update __all__ and thin public newton.solvers.
Solver kernels & dtype changes
newton/_src/solvers/*/kernels.py, newton/_src/solvers/*/solver_*.py, newton/_src/solvers/flags.py
Switch to enum-based joint/flag usage; change particle_flags arrays wp.uint32wp.int32; update bitwise checks to use ParticleFlags/ShapeFlags; use relative imports.
Viewer / renderer relocation & renames
newton/_src/utils/render.py, newton/_src/utils/__init__.py, usages → newton.viewer
Rename/move renderers: SimRendererOpenGLRendererOpenGL, SimRendererUsdRendererUsd; update aliases, signatures, and example usages; expose newton.viewer.
Importers & utils façade
newton/_src/utils/import_urdf.py, newton/_src/utils/import_mjcf.py, newton/_src/utils/import_usd.py, newton/utils.py
Move importers to _src relative modules; replace constants with enums; add mesh_maxhullvert parameter to parse functions; re-export spatial/math and importer utilities via newton.utils.
IK subsystem moves & renames
newton/_src/sim/ik.py, newton/ik.py
Move IK API to newton.ik; rename objectives with IK* prefix and JacobianModeIKJacobianMode; update defaults and imports.
Examples & tests updates
newton/examples/*, newton/tests/*
Update examples and tests to new top-level exports and Solver* names; replace legacy constants with JointType/JointMode/ShapeFlags/ParticleFlags; move eval_fk/eval_ik to top-level newton; adjust renderer/recorder imports and constructor args.
Docs / API generation
docs/api/*.rst, docs/generate_api.py, docs/index.rst, docs/print_api.py
Add Sphinx pages for newton and submodules; remove/replace legacy pages; expand MODULES; generate_api now cleans output and sorts symbols; add API introspection helper.
Recorder & contact sensor façades
newton/_src/utils/recorder.py, newton/_src/utils/contact_sensor.py, newton/sensors.py
Consolidate recorder/contact_sensor imports; re-export BasicRecorder/ModelAndStateRecorder; add newton.sensors façade exposing ContactSensor and populate_contacts.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Model
  participant Solver
  participant State

  User->>Solver: instantiate Solver*(model)
  User->>State: prepare state0, control
  User->>Solver: solver.step(state0, state1, control, None, dt)
  Solver->>Model: read model metadata
  Solver->>State: update state1 (internal integration/constraints)
  Solver-->>User: step returns (state1 updated)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • mmacklin
  • eric-heiden
  • shi-eric

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🔭 Outside diff range comments (13)
newton/tests/test_recorder.py (1)

107-121: Add playback round-trip assertions in test_model_and_state_recorder

After you compare the serialized state blobs, instantiate a fresh state and call new_recorder.playback for each frame to verify that Warp arrays are restored with the correct structured dtypes and on the intended device.

• File: newton/tests/test_recorder.py
• Location: inside test_model_and_state_recorder, immediately after the existing history comparisons

Suggested diff:

         for original_state_data, loaded_state_data in zip(recorder.history, new_recorder.history):
             _compare_serialized_data(test, original_state_data, loaded_state_data)
+
+        # Round-trip playback: verify dtype & device restoration
+        for i in range(len(states)):
+            restored_state = model.state()
+            new_recorder.playback(restored_state, i)
+            # body_q should be a wp.transform array on the correct device
+            test.assertIsInstance(restored_state.body_q, wp.array)
+            test.assertEqual(restored_state.body_q.dtype, wp.transform.dtype)
+            test.assertEqual(restored_state.body_q.device, device)
+            # body_qd should be a wp.spatial_vector array on the correct device
+            test.assertEqual(restored_state.body_qd.dtype, wp.spatial_vector.dtype)
+            test.assertEqual(restored_state.body_qd.device, device)
newton/_src/utils/recorder.py (1)

176-205: Playback cannot reliably restore wp dtypes; nested collections aren’t serialized; empty arrays dropped.

  • record() stores raw numpy arrays, losing dtype info (e.g., wp.transform, wp.spatial_vector).
  • playback() reconstructs wp arrays without dtype, risking incorrect types.
  • _serialize_value() returns collections as-is (no recursion), so nested wp arrays aren’t handled.
  • Zero-length wp arrays are discarded (returned None).

Recommend serializing wp arrays with dtype metadata and recursively handling collections; mirror that in deserialization and use it in record()/playback().

Here’s a concrete refactor:

@@
 class ModelAndStateRecorder:
@@
-    def _serialize_value(self, value):
+    def _serialize_value(self, value):
@@
-        if value is None:
-            return None
-
-        if isinstance(value, (int, float, bool, str, list, dict, set, tuple)):
-            return value
-        elif isinstance(value, np.ndarray):
-            return value
-        elif isinstance(value, wp.array):
-            if value.size > 0:
-                return {
-                    "__type__": "wp.array",
-                    "data": value.numpy(),
-                }
-        return None
+        if value is None:
+            return None
+        # Primitives
+        if isinstance(value, (int, float, bool, str, bytes, bytearray, complex)):
+            return value
+        # Numpy arrays are pickle-able as-is
+        if isinstance(value, np.ndarray):
+            return value
+        # Warp arrays: always serialize (even if size==0) with dtype metadata
+        if isinstance(value, wp.array):
+            dtype_name = getattr(value.dtype, "__name__", str(value.dtype))
+            return {"__type__": "wp.array", "dtype": dtype_name, "data": value.numpy()}
+        # Collections (recursive)
+        if isinstance(value, dict):
+            return {k: self._serialize_value(v) for k, v in value.items()}
+        if isinstance(value, list):
+            return [self._serialize_value(v) for v in value]
+        if isinstance(value, tuple):
+            return tuple(self._serialize_value(v) for v in value)
+        if isinstance(value, set):
+            return {self._serialize_value(v) for v in value}
+        # Fallback: not serializable
+        return None
@@
-    def _deserialize_and_restore_value(self, value, device):
+    def _deserialize_and_restore_value(self, value, device):
@@
-        if isinstance(value, dict) and "__type__" in value:
-            type_name = value["__type__"]
-            obj_data = value["data"]
-
-            if type_name == "wp.array":
-                return wp.array(obj_data, device=device)
-        if isinstance(value, np.ndarray):
-            return value
-        return value
+        # Rehydrate serialized wp arrays
+        if isinstance(value, dict) and "__type__" in value:
+            type_name = value.get("__type__")
+            if type_name == "wp.array":
+                obj_data = value.get("data")
+                dtype_name = value.get("dtype")
+                # Map dtype name back to wp dtype if possible
+                dtype = getattr(wp, dtype_name, None) if isinstance(dtype_name, str) else None
+                if dtype is not None:
+                    return wp.array(obj_data, dtype=dtype, device=device)
+                return wp.array(obj_data, device=device)
+        # Collections (recursive)
+        if isinstance(value, dict):
+            return {k: self._deserialize_and_restore_value(v, device) for k, v in value.items()}
+        if isinstance(value, list):
+            return [self._deserialize_and_restore_value(v, device) for v in value]
+        if isinstance(value, tuple):
+            return tuple(self._deserialize_and_restore_value(v, device) for v in value)
+        if isinstance(value, set):
+            return {self._deserialize_and_restore_value(v, device) for v in value}
+        # Numpy arrays and primitives
+        return value
@@
-    def record(self, state: State):
+    def record(self, state: State):
@@
-        state_data = {}
-        for name, value in state.__dict__.items():
-            if isinstance(value, wp.array):
-                state_data[name] = value.numpy()
-        self.history.append(state_data)
+        state_data = {}
+        for name, value in state.__dict__.items():
+            if isinstance(value, wp.array) and value is not None:
+                state_data[name] = self._serialize_value(value)
+        self.history.append(state_data)
@@
-    def playback(self, state: State, frame_id: int):
+    def playback(self, state: State, frame_id: int):
@@
-        for name, value_np in state_data.items():
-            if hasattr(state, name):
-                value_wp = wp.array(value_np, device=device)
-                setattr(state, name, value_wp)
+        for name, serialized in state_data.items():
+            if hasattr(state, name):
+                value_wp = self._deserialize_and_restore_value(serialized, device)
+                if isinstance(value_wp, wp.array):
+                    setattr(state, name, value_wp)

This preserves dtype during round-trips, handles empty arrays, and supports nested structures.

Also applies to: 206-229, 230-242, 253-276

newton/tests/test_equality_constraints.py (1)

43-56: Add MuJoCo availability guard in TestEqualityConstraints

The new test instantiates newton.solvers.SolverMuJoCo and calls newton.eval_fk, which will error in environments without MuJoCo. To keep CI green, skip this entire test class when MuJoCo isn’t installed.

• File: newton/tests/test_equality_constraints.py
Wrap the class with an availability check:

@@
 import newton
+try:
+    import mujoco  # noqa: F401
+    _HAS_MUJOCO = True
+except ImportError:
+    _HAS_MUJOCO = False
@@
-class TestEqualityConstraints(unittest.TestCase):
+@unittest.skipUnless(_HAS_MUJOCO, "MuJoCo not available")
+class TestEqualityConstraints(unittest.TestCase):
     def setUp(self):
         …
docs/concepts/articulations.rst (1)

21-66: Mismatch between documentation and JointType enum: missing COMPOUND/UNIVERSAL members

The docs still list legacy JOINT_COMPOUND and JOINT_UNIVERSAL, but the JointType enum in newton/_src/sim/joints.py only defines:

  • PRISMATIC
  • REVOLUTE
  • BALL
  • FIXED
  • FREE
  • DISTANCE
  • D6

You must choose one of the following fixes:

  • Add COMPOUND and UNIVERSAL to the JointType enum (with the correct integer values) so that JointType.COMPOUND and JointType.UNIVERSAL are valid, then update the docs to use those enum members.
  • Remove or update the two entries in docs/concepts/articulations.rst for compound and universal joints to match the supported joint types.

Files to update:

  • docs/concepts/articulations.rst (remove or rename lines 46–51)
  • newton/_src/sim/joints.py (if adding new enum members)
newton/_src/solvers/style3d/kernels.py (1)

206-217: Potential Warp-kernel mis-compile around flag test

if not particle_flags[tid] & ParticleFlags.ACTIVE:
  1. not converts the bitwise result to Python bool; Warp’s Python-to-CUDA translation has historically been brittle with Python boolean operators inside kernels.
  2. The array was switched to wp.int32, but many call-sites still pass wp.uint32. A silent dtype mismatch will raise at launch time.

Safer, explicit form:

flag = particle_flags[tid] & ParticleFlags.ACTIVE
if flag == 0:
    ...

and keep particle_flags as the same unsigned type used elsewhere (or update all call-sites).

newton/_src/solvers/euler/particles.py (1)

88-96: Guard against division by zero when particles coincide

If two active particles occupy the same position, d can be 0 and n/d will NaN. Add a small epsilon check before normalizing.

-            err = d - radius - particle_radius[index]
+            err = d - radius - particle_radius[index]
@@
-            if err <= k_cohesion:
-                n = n / d
+            if err <= k_cohesion:
+                # avoid normalization when particles coincide
+                if d <= 1.0e-9:
+                    continue
+                n = n / d
newton/_src/solvers/featherstone/solver_featherstone.py (1)

500-503: Hard-coded DOF=18 assertions break general models

These asserts should use self.dof_count, not 18. As-is, any non-18-DOF articulation will assert.

-                            assert H_tiled.shape == (model.articulation_count, 18, 18)
-                            assert L_tiled.shape == (model.articulation_count, 18, 18)
-                            assert R_tiled.shape == (model.articulation_count, 18)
+                            assert H_tiled.shape == (model.articulation_count, self.dof_count, self.dof_count)
+                            assert L_tiled.shape == (model.articulation_count, self.dof_count, self.dof_count)
+                            assert R_tiled.shape == (model.articulation_count, self.dof_count)

If articulations can differ in structure, also validate/guard the earlier assumption “all articulations have the same structure”.

newton/_src/solvers/solver.py (1)

49-49: Gravity term masked incorrectly for particles

Using wp.step(-inv_mass) applies gravity when inv_mass <= 0 (immobile), which is inverted. Match the rigid-body path and gate with nonzero(inv_mass).

-    v1 = v0 + (f0 * inv_mass + gravity * wp.step(-inv_mass)) * dt
+    v1 = v0 + (f0 * inv_mass + gravity * wp.nonzero(inv_mass)) * dt

Optionally: wp.step(inv_mass) also works if step(0)=0 on your Warp version; nonzero mirrors integrate_rigid_body.

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

132-138: Case mapping bug: 'meshSimplification' will never match after .lower()

approximation.lower() yields 'meshsimplification', but the mapping key is 'meshSimplification'. This silently disables the intended remeshing path.

     approximation_to_remeshing_method = {
         "convexdecomposition": "coacd",
         "convexhull": "convex_hull",
         "boundingsphere": "bounding_sphere",
         "boundingcube": "bounding_box",
-        "meshSimplification": "quadratic",
+        "meshsimplification": "quadratic",
     }
newton/examples/example_selection_articulations.py (1)

117-125: Fix RendererOpenGL up_axis type (should be Axis/None, not str)

RendererOpenGL expects up_axis as an Axis-like enum (or None), not a string. Passing str(up_axis) can lead to misconfiguration.

Apply this diff:

-            self.renderer = newton.viewer.RendererOpenGL(
-                path=stage_path,
-                model=self.model,
-                scaling=2.0,
-                up_axis=str(up_axis),
-                screen_width=1280,
-                screen_height=720,
-                camera_pos=(0, 4, 30),
-            )
+            self.renderer = newton.viewer.RendererOpenGL(
+                path=stage_path,
+                model=self.model,
+                scaling=2.0,
+                up_axis=up_axis,
+                screen_width=1280,
+                screen_height=720,
+                camera_pos=(0, 4, 30),
+            )
newton/examples/example_selection_materials.py (1)

88-96: Pass Axis enum, not string, to RendererOpenGL

RendererOpenGL expects an Axis enum (or None), not str(up_axis). Passing a string can break axis handling and camera orientation.

Apply:

-                up_axis=str(up_axis),
+                up_axis=up_axis,
newton/_src/solvers/mujoco/solver_mujoco.py (2)

941-959: Possible illegal in-place element write on vec10f

actuator_bias[worldid, actuator_idx][1] = -kp (and similar writes) mutates a single element of a vector stored inside a Warp array.
Warp only supports arr[idx].x = … style field writes; indexed writes ([...]) are not currently allowed and will raise a code-gen error. Safest pattern:

bias   = actuator_bias[worldid, actuator_idx]
bias[1] = -kp
bias[2] = -kv
actuator_bias[worldid, actuator_idx] = bias

Apply the same pattern for actuator_gain.


1871-1877: Graph-colour allocation limited to 32 collision groups

if color < 32: adds every new colour bit into a 32-bit mask.
When more than 32 colour classes are produced, shapes silently fall into the “collide with everything” bucket, defeating the filter. Consider:

  • upgrading to a 64-bit mask, or
  • detecting overflow and raising a clear error.

This avoids extremely hard-to-trace contact explosions on big scenes.

♻️ Duplicate comments (1)
newton/_src/sim/articulation.py (1)

569-640: Repeat of the if-chain / shadowing pattern in IK kernel

The same comments about type shadowing and the missing elifs apply here. Consider the same refactor for consistency.

🧹 Nitpick comments (69)
newton/tests/test_recorder.py (1)

24-24: Using internal _src import in tests is acceptable here; consider planning a public re-export.

Per team practice, testing private APIs is fine. Longer term, expose BasicRecorder/ModelAndStateRecorder via the public API to avoid test fragility on internal paths.

I can draft the public re-export (e.g., newton.utils.recorder or newton.recorder) and update tests. Want me to open an issue/PR?

newton/tests/test_broad_phase.py (1)

22-22: Private-API import in tests is acceptable.

Swap to _src path is fine and keeps the test compiling with the refactor.

Consider adding a TODO to switch back to the public façade once the constant is re-exported there, to avoid future private-API coupling.

newton/_src/geometry/types.py (1)

40-41: Resolve outstanding FIXME before merge

The new comment highlights uncertainty about exposing MESH_MAXHULLVERT. Since the constructor already accepts maxhullvert, keeping a module-level mutable constant can be confusing. Consider:

  1. Removing the constant entirely and hard-coding the default in the Mesh signature, or
  2. Promoting the constant to the public API and documenting how users should adjust it.

Clarifying this before the public refactor freezes will avoid future breaking changes.

newton/tests/test_raycast.py (1)

20-27: GeoType re-export confirmed; consider public exports for raycast

  • Verified that GeoType is listed in newton/__init__.py (lines 28 & 64), so importing it from newton is correct.
  • All five ray_intersect_* functions live in newton/_src/geometry/raycast.py (defs at lines 41, 85, 133, 224, 294). Testing them directly is fine, but you may want to add public re-exports (for example under newton.geometry.raycast or newton.raycast) to avoid private _src imports in the long term.

resolve_review_comment

docs/conventions.rst (1)

84-90: Use consistent “Solver” naming and add cross-refs*

Align with the new naming scheme in this list and link the MuJoCo solver:

-* **Standard Newton solvers** (XPBD, Euler, SolverMuJoCo etc.)  
+* **Standard Newton solvers** (SolverXPBD, SolverSemiImplicit, :class:`newton.solvers.SolverMuJoCo`, etc.)  
@@
-  convert from this convention to MuJoCo's mixed-frame format when using the SolverMuJoCo.
+  convert from this convention to MuJoCo's mixed-frame format when using :class:`newton.solvers.SolverMuJoCo`.
newton/_src/solvers/featherstone/__init__.py (1)

16-20: Re-export rename LGTM; consider a temporary compat alias

The rename to SolverFeatherstone matches the new convention. If you want a smoother migration for internal imports, add a soft alias (optional):

 from .solver_featherstone import SolverFeatherstone
 
 __all__ = [
     "SolverFeatherstone",
 ]
+
+# Optional: backward-compat alias (remove in a future release)
+# from warnings import warn as _warn
+# _warn("FeatherstoneSolver is deprecated; use SolverFeatherstone", DeprecationWarning, stacklevel=2)
+FeatherstoneSolver = SolverFeatherstone
+# __all__.append("FeatherstoneSolver")  # only if you want it in star-imports
newton/_src/solvers/euler/__init__.py (1)

16-20: Rename looks good; consider adding a temporary alias for smoother migration.

To reduce churn for downstreams, optionally provide a deprecated alias:

 from .solver_euler import SolverSemiImplicit
 
 __all__ = [
     "SolverSemiImplicit",
 ]
+
+# Optional backwards-compat alias (deprecate in a future release)
+SemiImplicitSolver = SolverSemiImplicit
+# If you want to expose it, also:
+# __all__.append("SemiImplicitSolver")
newton/_src/solvers/vbd/__init__.py (1)

16-20: Rename to SolverVBD looks good; consider a temporary alias.

Optionally provide an alias for downstream compatibility:

 from .solver_vbd import SolverVBD
 
 __all__ = [
     "SolverVBD",
 ]
+
+# Optional backwards-compat alias (deprecate in a future release)
+VBDSolver = SolverVBD
+# If you want to expose it, also:
+# __all__.append("VBDSolver")
newton/_src/solvers/euler/solver_euler.py (2)

84-92: Type-hint control as optional

control may be None at call sites and is handled as such inside. Reflect this in the signature for clarity and typing tools.

-    def step(
+    def step(
         self,
         state_in: State,
         state_out: State,
-        control: Control,
+        control: Control | None,
         contacts: Contacts,
         dt: float,
     ):

138-139: Remove or gate dead code for muscles

if False: is dead code. Either delete it or guard with a feature flag to keep intent clear.

-            # muscles
-            if False:
-                eval_muscle_forces(model, state_in, control, body_f)
+            # muscles (enable when muscle dynamics are supported)
+            # if self.enable_muscles:
+            #     eval_muscle_forces(model, state_in, control, body_f)
docs/api/newton_geometry.rst (1)

63-70: Prefer documenting enum classes over duplicating numeric values

Using ParticleFlags.ACTIVE and ShapeFlags.* is clearer. To avoid numeric drift, consider documenting the enum classes directly (e.g., with .. autoclass:: ParticleFlags and .. autoclass:: ShapeFlags plus :members:) instead of hardcoding numeric values in a list-table.

newton/selection.py (1)

16-20: Add a brief module docstring for Sphinx discoverability

Consider adding a top-level docstring describing this as the public interface for selection utilities and briefly summarizing ArticulationView usage.

Example:

"""Public selection utilities.

Exposes:
- ArticulationView: Select and manipulate subsets of articulations, joints, and links in a Model.
"""
newton/_src/utils/import_urdf.py (1)

27-31: Relative imports align with the new private layout; consider simplifying to a single module import for consistency.

Right now both the module and a symbol are imported from geometry:

  • from .. import geometry
  • from ..geometry import Mesh

To reduce ambiguity and ease refactors, prefer importing the module only and referring to geometry.Mesh at use sites.

Apply this diff:

-from .. import geometry
-from ..core import Axis, AxisType, quat_between_axes
-from ..core.types import Transform
-from ..geometry import Mesh
-from ..sim import ModelBuilder
+from .. import geometry
+from ..core import Axis, AxisType, quat_between_axes
+from ..core.types import Transform
+from ..sim import ModelBuilder

And update Mesh constructions below (also in Lines 220 and 232):

-                        m_mesh = Mesh(m_vertices, m_faces, maxhullvert=geometry.MESH_MAXHULLVERT)
+                        m_mesh = geometry.Mesh(m_vertices, m_faces, maxhullvert=geometry.MESH_MAXHULLVERT)
newton/tests/test_ik.py (2)

25-25: Using the private IK module in tests is acceptable here; consider adding a public-API smoke test.

Per team practice, testing private APIs is OK. To guard the public surface, add a tiny smoke test that imports from newton.ik and instantiates the renamed classes.

I can draft an additional test to import newton.ik and construct IKPositionObjective, IKRotationObjective, and IKJointLimitObjective to ensure they’re re-exported.


278-280: Prefer keyword arguments for readability and future-proofing.

These two calls use positional args where there are many parameters. Using keywords reduces ordering mistakes and helps future refactors.

Apply this diff as an example:

-        pos_obj = ik.IKPositionObjective(0, wp.vec3(0.0, 0.0, 0.0), pos_targets, n_problems, 6, 0)
-        rot_obj = ik.IKRotationObjective(0, wp.quat_identity(), rot_targets, n_problems, 6, 3)
+        pos_obj = ik.IKPositionObjective(
+            link_index=0,
+            link_offset=wp.vec3(0.0, 0.0, 0.0),
+            target_positions=pos_targets,
+            n_problems=n_problems,
+            total_residuals=6,
+            residual_offset=0,
+        )
+        rot_obj = ik.IKRotationObjective(
+            link_index=0,
+            link_offset_rotation=wp.quat_identity(),
+            target_rotations=rot_targets,
+            n_problems=n_problems,
+            total_residuals=6,
+            residual_offset=3,
+        )

Also applies to: 419-421

docs/guide/quickstart.rst (1)

36-37: Keep terminology consistent

The sentence still mentions “OpenGLRenderer”. Everywhere else the class is now called RendererOpenGL; consider updating the prose to avoid reader confusion.

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

23-24: Circular alias can confuse public API

SimRenderer is now just an alias of RendererUsd, but exporting all three names (RendererOpenGL, RendererUsd, and SimRenderer) may mislead newcomers into thinking they are three distinct back-ends.
Consider deprecating SimRenderer (e.g., with warnings.warn("SimRenderer is deprecated …")) or dropping it from __all__ in the next major release.


114-116: __all__ order nit

Purely stylistic: keep list alphabetically sorted to avoid merge conflicts (RendererUsd before RendererOpenGL).

newton/examples/example_mujoco.py (3)

247-256: Variable shadowing can obscure intent

Parameter solver is reused as a local variable passed into SolverMuJoCo(solver=solver, …).
Because it masks the earlier imported module namespace (newton.solvers), future edits may accidentally shadow more attributes.
Renaming the arg to solver_backend (or similar) mitigates this risk.

-        solver = solver if solver is not None else ROBOT_CONFIGS[robot]["solver"]
+        solver_backend = solver if solver is not None else ROBOT_CONFIGS[robot]["solver"]
 ...
-        self.solver = newton.solvers.SolverMuJoCo(
+        self.solver = newton.solvers.SolverMuJoCo(
             self.model,
             use_mujoco=use_mujoco,
-            solver=solver_backend,
+            solver=solver_backend,

259-260: Missing explicit import for newton.viewer

Unlike newton.utils, the viewer sub-module is not imported at the file top.
If newton/__init__.py ever stops auto-exposing it, this line will break. A one-liner import keeps the script robust:

import newton.viewer

360-362: Help string stale after rename

The CLI help still references MuJoCo viewer “when SolverMuJoCo is active.” Fine, but you renamed the flag earlier; just sanity-check that other docs/examples spell the class the same way (SolverMuJoCo).

newton/_src/solvers/xpbd/solver_xpbd.py (1)

18-20: Relative import depth fragile

from ...core.types import override relies on the package remaining exactly three levels deep.
If xpbd gets nested further (xpbd.v2 etc.) the import will break.
Using absolute import (from newton._src.core.types import override) avoids this class of errors.

newton/_src/solvers/featherstone/kernels.py (1)

128-175: Consider renaming the type parameter and typing it as JointType

type shadows the Python builtin and is annotated as plain int, yet throughout the file it is compared against newton.JointType.* (an IntEnum).
Changing the signature to something like

def jcalc_transform(
    joint_type: newton.JointType,
    ...
):

(and similarly for the other helpers) removes the shadowing, documents the expected domain, and lets static checkers catch mismatched callers. Warp kernels accept IntEnum values transparently (IntEnum inherits int), so this is a zero-cost refactor.

Also applies to: 239-299, 420-469, 523-536

newton/tests/test_import_urdf.py (1)

176-182: Mock path couples the test to a private module location

patch("newton._src.utils.import_urdf._download_file", …) reaches into the private _src tree.
If the implementation is later relocated, the test will break while public behaviour is unchanged. Patching via the public symbol exposed through newton.utils.import_urdf (after importing it) would decouple the test from internal layout.

with patch(f"{parse_urdf.__module__}._download_file", side_effect=mock_mesh_download):
    ...
newton/_src/solvers/implicit_mpm/solver_implicit_mpm.py (1)

1164-1169: Avoid shadowing the collider variable inside the list-comprehension

collider is first bound to the Collider() struct and then immediately re-used as the loop variable:

collider.meshes = wp.array([collider.id for collider in colliders], dtype=wp.uint64)

Using the same identifier makes the code harder to read and can easily lead to bugs when the outer binding is referenced later. Rename the comprehension variable, e.g.:

- collider.meshes = wp.array([collider.id for collider in colliders], dtype=wp.uint64)
+ collider.meshes = wp.array([mesh.id for mesh in colliders], dtype=wp.uint64)
newton/_src/sim/articulation.py (1)

205-249: Use elif and avoid shadowing built-ins

Inside the loop type shadows Python’s built-in type() and every branch is written as an independent if, so each subsequent comparison is evaluated even after a match.

type = joint_type[i]
...
if type == JointType.PRISMATIC:
    ...
if type == JointType.REVOLUTE:
    ...

Rename the variable (e.g., jt) and chain with elif to improve clarity and save cycles:

jt = joint_type[i]
if jt == JointType.PRISMATIC:
    ...
elif jt == JointType.REVOLUTE:
    ...
newton/_src/solvers/euler/particles.py (1)

82-84: Remove unused 'count' to reduce register pressure

count is incremented but never used.

-    count = int(0)
+    # count removed (unused)
@@
-            count += 1
+            # increment removed (unused)

Also applies to: 91-92

newton/examples/example_ik_benchmark.py (2)

225-231: Avoid private API in example: replace _fk_two_pass with a public helper

This example calls solver._fk_two_pass, which is private and may change without notice. Consider exposing a public batched FK in newton (e.g., eval_fk_batched) or adding a public method on IKSolver that wraps this call.

I can draft a minimal public wrapper (e.g., ik.fk_two_pass) to maintain performance and stability. Want a patch?


215-216: Reduce side-effects in _fk_targets

Instead of mutating model.joint_q each loop, pass a temporary q array directly to eval_fk.

-            wp.copy(self.model.joint_q, wp.array(q, dtype=wp.float32))
-            newton.eval_fk(self.model, self.model.joint_q, self.model.joint_qd, state)
+            q_d = wp.array(q, dtype=wp.float32)
+            newton.eval_fk(self.model, q_d, self.model.joint_qd, state)

If allocation overhead matters, preallocate q_d once outside the loop and reuse.

newton/_src/solvers/solver.py (1)

263-263: Docstring type: Contact → Contacts

Parameter type name is inconsistent with the import and usage.

-            contacts (Contact): The contact information.
+            contacts (Contacts): The contact information.
newton/examples/example_selection_cartpole.py (1)

109-111: Type-check can be simplified

self.solver is always an instance of SolverMuJoCo (created on Line 82). The isinstance guard will therefore be false for all other solver types but true here, making the FK bypass unconditional. Consider flipping the logic or comparing against an abstract base (SolverBase) to future-proof this path.

newton/tests/test_model.py (1)

169-170: Make joint-type assertion explicit

builder.joint_type stores raw ints. Relying on IntEnum == int coercion is implicit and brittle. Cast for clarity:

assert list(map(newton.JointType, builder.joint_type)) == [
    newton.JointType.REVOLUTE,
    newton.JointType.FREE,
]
newton/ik.py (1)

16-24: Consider lazy re-export to keep import cost minimal

Importing the heavy _src.sim.ik module unconditionally adds overhead for users who never touch IK. A __getattr__-based lazy loader (PEP 562) would defer that cost until first use.

Not critical for correctness but worth considering.

newton/tests/test_ik_fk_kernels.py (2)

24-26: Prefer public newton.ik import over private path

import newton.ik as ik

keeps the test aligned with the new public surface while still allowing access to private helpers when necessary.


185-200: Consistent enum reference style

Within the same function both JointType.* and newton.JointType.* are used. Pick one style (ideally the imported alias) for readability.

elif jt == JointType.D6:
    ...
elif jt == JointType.FREE:
    ...
newton/_src/utils/selection.py (2)

23-23: Avoid name shadowing with the class method eval_fk.

Importing eval_fk into the module namespace and then defining an ArticulationView.eval_fk() method with the same identifier is legal but confusing.
Consider importing the function under an alias (e.g., from ..sim import eval_fk as _eval_fk) or renaming the method to make the call site intention crystal-clear.
This prevents readers from having to double-check Python’s LEGB lookup rules.


355-360: Prefer explicit casting to JointType for clarity.

root_joint_type is stored as a plain int, so the comparison against JointType.FIXED / FREE / DISTANCE relies on implicit IntEnumint equality.
Wrapping once:

root_joint_type = JointType(root_joint_type)

makes the intent obvious and future-proofs the checks if additional enum members appear.

newton/tests/test_rigid_contact.py (1)

30-36: Minor: reuse the public collide() behaviour check instead of explicit type test.

The special-casing of SolverMuJoCo by isinstance() ties the test to one concrete class.
If additional MuJoCo-derived solvers appear the branch will silently mis-classify them.

Consider feature detection instead, e.g.:

if getattr(solver, "uses_mujoco_backend", False):
    contacts = None
else:
    contacts = model.collide(state_0, rigid_contact_margin=100.0)

Makes the test resilient to future solver variants.

newton/examples/example_cartpole.py (1)

70-73: Remove stale, commented-out solver lines.

Leaving three alternative solver instantiations in the example clutters the tutorial and confuses new users.
Pick one (or expose a CLI flag) and delete the others.

newton/examples/example_cloth_self_contact.py (1)

183-187: Explicitly cast ParticleFlags when masking NumPy arrays.

-        flags[fixed_vertex_id] = flags[fixed_vertex_id] & ~ParticleFlags.ACTIVE
+        flags[fixed_vertex_id] &= ~int(ParticleFlags.ACTIVE)

Makes the intent crystal-clear and avoids relying on IntFlag’s subclass-of-int behaviour.

newton/examples/example_anymal_c_walk.py (1)

34-38: Unnecessary import – can drop newton.utils.

After the refactor this file only uses newton.utils.download_asset, which can be imported directly:

-import newton.utils
+from newton.utils import download_asset

Keeps the namespace clean.

newton/examples/example_rigid_force.py (1)

55-59: Consider importing newton.viewer explicitly for clarity

RendererOpenGL/RendererUsd are accessed through newton.viewer.*, but the sub-module is never imported.
While attribute access works after the earlier from newton.viewer import … in other files, being explicit here avoids relying on side-effects.

-import newton
+import newton
+import newton.viewer
newton/_src/geometry/flags.py (2)

23-26: Consider adding a NONE = 0 member to each IntFlag

Having an explicit zero-value member improves clarity and avoids magic 0s when clearing/initializing flags.

 class ParticleFlags(IntFlag):
+    NONE = 0
     ACTIVE = 1 << 0
@@
 class ShapeFlags(IntFlag):
+    NONE = 0
     VISIBLE = 1 << 0
     COLLIDE_SHAPES = 1 << 1
     COLLIDE_PARTICLES = 1 << 2

Also applies to: 39-47


18-20: Remove deprecated commented constants to reduce noise

These legacy wp.constant lines add clutter and can confuse readers. Consider deleting them; the git history retains the context.

-# ParticleFlags.ACTIVE = wp.constant(wp.uint32(1 << 0))
-# """Indicates that the particle is active."""
@@
-# ShapeFlags.VISIBLE = wp.constant(wp.uint32(1 << 0))
-# """Indicates that the shape is visible."""
-
-# ShapeFlags.COLLIDE_SHAPES = wp.constant(wp.uint32(1 << 1))
-# """Indicates that the shape collides with other shapes."""
-
-# ShapeFlags.COLLIDE_PARTICLES = wp.constant(wp.uint32(1 << 2))
-# """Indicates that the shape collides with particles."""

Also applies to: 28-36

newton/examples/example_cloth_style3d.py (1)

116-116: Bitmask with IntFlag: cast to int to avoid dtype surprises

~ParticleFlags.ACTIVE evaluates to a negative IntFlag (two’s complement). While numpy int32 handles it fine, being explicit avoids static/type check noise.

-            flags[fixed_vertex_id] = flags[fixed_vertex_id] & ~ParticleFlags.ACTIVE
+            flags[fixed_vertex_id] = flags[fixed_vertex_id] & ~int(ParticleFlags.ACTIVE)
newton/_src/utils/import_usd.py (1)

1114-1115: Bitmask with IntFlag: cast to int for clarity and dtype consistency

This avoids potential mypy/linters complaints and keeps operations purely on ints.

-                    builder.shape_flags[shape_id] &= ~newton.ShapeFlags.COLLIDE_SHAPES
+                    builder.shape_flags[shape_id] &= ~int(newton.ShapeFlags.COLLIDE_SHAPES)
newton/examples/example_quadruped.py (2)

35-37: Avoid module-level side-effects during import

RecorderImGuiManager pulls in an OpenGL context at import-time. Importing it unconditionally makes the script crash on headless machines even when stage_path is None. Delay the import until you actually create a renderer (or guard it with the same if stage_path: check).


82-86: Provide run-time selection instead of commented options

Switching solvers by commenting / uncommenting code is error-prone. Accept a CLI flag (e.g. --solver xpbd|featherstone|mujoco) and instantiate accordingly.

newton/_src/solvers/xpbd/kernels.py (3)

20-23: Relative import reaches across package layers

from ...core import velocity_at_point jumps three levels up from newton._src.solvers.xpbd. That path will break if solvers is ever moved. Prefer absolute import from newton._src.core import velocity_at_point to decouple the solver package layout.


70-71: Cast enum to kernel integer once

Warp captures the whole IntFlag object each time you write

if (particle_flags[i] & ParticleFlags.ACTIVE) == 0:

Introduce a local constant:

ACTIVE = wp.int32(int(ParticleFlags.ACTIVE))

and use ACTIVE in the bit-tests. This avoids repeated Python-side enum resolution during kernel compilation and makes the intent clearer.

Also applies to: 157-158, 252-253, 267-268


999-1004: Minor: built-in name shadowing

Functions such as update_joint_dof_mode use the parameter name mode, but elsewhere the variable mode is overloaded for bit-flags. Consider renaming to axis_mode to reduce cognitive load.

newton/_src/solvers/flags.py (1)

16-18: Misleading module docstring

The docstring says “Implementation of the Newton model class.” but the file only defines SolverNotifyFlags. Update the description to match the content.

newton/examples/example_contact_sensor.py (2)

129-130: Explicit import of sub-package

newton.solvers is accessed without an explicit import. Relying on newton/__init__.py to import sub-packages increases start-up time and may break if lazy import machinery is introduced later. Add

import newton.solvers

near the top.


231-236: Avoid negative isinstance checks

Instead of not isinstance(self.solver, newton.solvers.SolverMuJoCo) create a boolean flag once:

self.uses_explicit_collision = not isinstance(self.solver, SolverMuJoCo)

This prevents the tight inner loop from performing Python isinstance every sub-step.

newton/_src/solvers/__init__.py (1)

16-24: Consider lazy loading to cut import time

Eagerly importing every solver (XPBD, MuJoCo, VBD, …) adds ~200 ms to import newton. A lightweight pattern:

import importlib, types, sys

def __getattr__(name):
    if name in _LAZY:
        mod, attr = _LAZY[name]
        value = getattr(importlib.import_module(mod), attr)
        globals()[name] = value
        return value
    raise AttributeError(name)

_LAZY = {
    "SolverXPBD": ("newton._src.solvers.xpbd", "SolverXPBD"),
    ...
}

delays heavy dependencies until first use.

newton/_src/sim/model.py (2)

282-283: Sphinx link: add module qualifier for SolverXPBD

Use fully-qualified path for consistent cross-referencing.

-        """Restitution coefficient of soft contacts (used by :class:`SolverXPBD`). Default is 0.0."""
+        """Restitution coefficient of soft contacts (used by :class:`~newton.solvers.SolverXPBD`). Default is 0.0."""

287-290: Sphinx link: add module qualifier for SolverXPBD (friction coefficients)

Same nit: ensure consistent dotted path for Sphinx.

-        """Torsional friction coefficient for rigid body contacts (used by :class:`SolverXPBD`)."""
+        """Torsional friction coefficient for rigid body contacts (used by :class:`~newton.solvers.SolverXPBD`)."""
-        """Rolling friction coefficient for rigid body contacts (used by :class:`SolverXPBD`)."""
+        """Rolling friction coefficient for rigid body contacts (used by :class:`~newton.solvers.SolverXPBD`)."""
newton/tests/test_joint_drive.py (1)

75-80: Nit: simplify joint_type selection

Minor readability improvement.

-        joint_type = newton.JointType.PRISMATIC
-        if is_prismatic:
-            joint_type = newton.JointType.PRISMATIC
-        else:
-            joint_type = newton.JointType.REVOLUTE
+        joint_type = newton.JointType.PRISMATIC if is_prismatic else newton.JointType.REVOLUTE
newton/examples/example_selection_materials.py (1)

158-161: Type checks are fine; consider capability checks (optional)

isinstance(..., SolverMuJoCo) works, but a capability-based check (e.g., solver.requires_explicit_collisions) would decouple the example from class names. Optional.

Also applies to: 226-228

newton/tests/test_joint_controllers.py (1)

84-86: eval_ik gating by solver type (optional improvement)

The check excludes MuJoCo and Featherstone before calling eval_ik. Consider a capability flag (e.g., solver.owns_kinematics or similar) to avoid class-name checks. Optional.

newton/_src/sim/joints.py (3)

18-26: Remove stale commented constant definitions

These commented wp.constant lines are legacy and now superseded by enums. Removing them will reduce confusion and noise.

-# # Types of joints linking rigid bodies
-# JointType.PRISMATIC = wp.constant(0)
-# JointType.REVOLUTE = wp.constant(1)
-# JointType.BALL = wp.constant(2)
-# JointType.FIXED = wp.constant(3)
-# JointType.FREE = wp.constant(4)
-# JointType.DISTANCE = wp.constant(5)
-# JointType.D6 = wp.constant(6)

29-36: Enums introduced correctly; consider docstrings

JointType values preserve the prior mapping. Consider adding a brief docstring for clarity; otherwise LGTM.


39-52: Tighten type annotations and comparisons

Since JointType is an IntEnum, update the signature to reflect that and keep comparisons to enum members. Behavior unchanged.

-def get_joint_dof_count(joint_type: int, num_axes: int) -> tuple[int, int]:
+def get_joint_dof_count(joint_type: JointType, num_axes: int) -> tuple[int, int]:
newton/tests/test_import_usd.py (1)

56-57: Consistently treat ShapeFlags as an IntFlag.

Line 56 casts the enum to int, while Line 84 relies on the raw IntFlag. Pick one style (prefer the enum directly) to avoid needless casts and keep the code uniform.

Also applies to: 84-85

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

532-537: Why exclude prismatic joints from visual arrows?

JointType.PRISMATIC was previously rendered; it’s now commented out.
If that’s intentional, leave a comment explaining the rationale; otherwise restore it:

-                newton.JointType.REVOLUTE,
-                # newton.JointType.PRISMATIC,
+                newton.JointType.REVOLUTE,
+                newton.JointType.PRISMATIC,
docs/migration.rst (1)

158-163: Update table entry for SimRenderer.

newton.utils.SimRenderer no longer exists; the public alias lives in newton.viewer.
Replace the cell with ``:class:`newton.viewer.SimRenderer``` for accuracy.

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

1470-1483: Consider using a dictionary for joint type string conversion.

While the current implementation is correct, consider using a dictionary mapping for cleaner code:

JOINT_TYPE_NAMES = {
    JointType.FREE: "free",
    JointType.BALL: "ball",
    JointType.PRISMATIC: "prismatic",
    JointType.REVOLUTE: "revolute",
    JointType.D6: "D6",
    JointType.FIXED: "fixed",
    JointType.DISTANCE: "distance",
}

def joint_type_str(type):
    return JOINT_TYPE_NAMES.get(type, "unknown")
newton/__init__.py (2)

48-54: Remove stray “TODO: HMMM” and tidy import section

Left-over TODOs in the public package entry-point look unprofessional and may trigger import-time warnings in downstream projects. Either address the task or delete the comment before merge.


84-90: __all__ list is missing new top-level symbols added later

Sub-modules like solvers.flags.SolverNotifyFlags (now public) are not exported via __all__. Exporting them avoids from newton import * surprises.

Comment thread newton/_src/sim/style3d/builder_style3d.py
Comment thread newton/_src/solvers/solver.py
Comment thread newton/_src/solvers/vbd/solver_vbd.py
Comment thread newton/_src/solvers/xpbd/kernels.py Outdated
Comment thread newton/_src/utils/contact_sensor.py Outdated
Comment thread newton/examples/example_robot_manipulating_cloth.py
Comment thread newton/examples/example_selection_articulations.py
Comment thread newton/geometry.py
Comment thread newton/tests/test_import_usd.py Outdated
Comment thread newton/tests/test_inertia_validation.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
newton/_src/utils/import_mjcf.py (1)

226-229: Fix runtime crash: wrong argument type passed to wp.vec3.

The existing review comment remains valid - wp.vec3 expects three scalars, but euler is a NumPy array. This will raise a type error at runtime.

Apply this diff to fix the issue:

-            return quat_from_euler(wp.vec3(euler), *euler_seq)
+            return quat_from_euler(
+                wp.vec3(float(euler[0]), float(euler[1]), float(euler[2])),
+                *euler_seq,
+            )
newton/_src/utils/import_usd.py (1)

364-365: Fix runtime error: quat_between_axes expects Axis enum, not string.

The existing review comment remains valid - axis_str is a string but quat_between_axes expects an Axis enum value.

Apply this diff to fix the issue:

-                if axis_str != "Z":
-                    axis_q = quat_between_axes(Axis.Z, axis_str)
+                if axis_str != "Z":
+                    axis = Axis.from_string(axis_str)
+                    axis_q = quat_between_axes(Axis.Z, axis)
🧹 Nitpick comments (4)
newton/_src/solvers/featherstone/kernels.py (1)

174-179: Nit: prefer wp.vec3() over wp.vec3(0.0) for clarity

Both construct a zero vector, but wp.vec3() is clearer and consistent with usage elsewhere in this file.

Apply:

-        pos = wp.vec3(0.0)
+        pos = wp.vec3()
newton/_src/solvers/xpbd/kernels.py (3)

1418-1428: Clean up commented code or document its purpose.

This large block of commented code appears to be related to joint mode handling. If this code is no longer needed, it should be removed. If it's kept for reference, consider adding a comment explaining why it's preserved.

-    # if (type == JointType.PRISMATIC):
-    #     if mode == JointMode.TARGET_POSITION:
-    #         target = wp.clamp(target, limit_lower, limit_upper)
-    #         if target_ke > 0.0:
-    #             err = dx - target * axis
-    #             compliance = 1.0 / target_ke
-    #         damping = axis_damping[dim]
-    #     elif mode == JointMode.TARGET_VELOCITY:
-    #         if target_ke > 0.0:
-    #             err = (derr - target) * dt
-    #             compliance = 1.0 / target_ke
-    #         damping = axis_damping[dim]

Or add a documentation comment if this code serves as a reference:

+    # TODO: The following commented code shows the original joint mode handling pattern
+    # for PRISMATIC joints. This may be re-enabled in future versions.
     # if (type == JointType.PRISMATIC):
     #     if mode == JointMode.TARGET_POSITION:

1486-1495: Clean up commented joint type checks.

Similar to the previous comment, these commented conditional checks should either be removed if no longer needed or documented if they serve as reference for future development.

-    # if type == JointType.FIXED:
-    #     return
-    # if type == JointType.REVOLUTE:
-    #     return
-    # if type == JointType.PRISMATIC:
-    #     return
-    # if type == JointType.BALL:
-    #     return

1873-1880: Remove or uncomment debugging printf statements.

The commented debug printf statements should either be removed if they're no longer needed or uncommented if they're still useful for debugging. Large blocks of commented debug code can clutter the codebase.

-        # if type == JointType.D6:
-        #     wp.printf("axis_target: %f %f %f\t axis_stiffness: %f %f %f\t axis_damping: %f %f %f\t axis_limits_lower: %f %f %f \t axis_limits_upper: %f %f %f\n",
-        #               axis_target[0], axis_target[1], axis_target[2],
-        #               axis_stiffness[0], axis_stiffness[1], axis_stiffness[2],
-        #               axis_damping[0], axis_damping[1], axis_damping[2],
-        #               axis_limits_lower[0], axis_limits_lower[1], axis_limits_lower[2],
-        #               axis_limits_upper[0], axis_limits_upper[1], axis_limits_upper[2])
-        #     # wp.printf("wp.sqrt(1.0-qtwist[0]*qtwist[0]) = %f\n", wp.sqrt(1.0-qtwist[0]*qtwist[0]))
📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 891772e and e2d6d26.

📒 Files selected for processing (12)
  • newton/_src/sim/ik.py (4 hunks)
  • newton/_src/solvers/euler/kernels.py (12 hunks)
  • newton/_src/solvers/featherstone/kernels.py (13 hunks)
  • newton/_src/solvers/mujoco/solver_mujoco.py (36 hunks)
  • newton/_src/solvers/solver.py (6 hunks)
  • newton/_src/solvers/xpbd/kernels.py (29 hunks)
  • newton/_src/utils/contact_sensor.py (1 hunks)
  • newton/_src/utils/import_mjcf.py (8 hunks)
  • newton/_src/utils/import_usd.py (10 hunks)
  • newton/_src/utils/render.py (21 hunks)
  • newton/examples/example_anymal_c_walk_on_sand.py (3 hunks)
  • newton/examples/example_selection_articulations.py (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • newton/_src/sim/ik.py
  • newton/_src/utils/contact_sensor.py
  • newton/_src/solvers/mujoco/solver_mujoco.py
  • newton/_src/solvers/solver.py
  • newton/examples/example_selection_articulations.py
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: shi-eric
PR: newton-physics/newton#521
File: newton/examples/example_cloth_hanging.py:36-36
Timestamp: 2025-08-12T05:17:34.404Z
Learning: The Newton migration guide (docs/migration.rst) is specifically for documenting how to migrate existing warp.sim functionality to Newton equivalents. New Newton-only features that didn't exist in warp.sim do not need migration documentation.
📚 Learning: 2025-07-15T19:00:39.991Z
Learnt from: dylanturpin
PR: newton-physics/newton#394
File: newton/examples/example_ik_benchmark.py:132-146
Timestamp: 2025-07-15T19:00:39.991Z
Learning: In the Newton library, `newton.utils.download_asset()` already handles internal caching and can work with both local and remote assets, so conditional logic to check for local paths is unnecessary. The standard practice is to use `download_asset` directly rather than implementing custom path handling logic.

Applied to files:

  • newton/examples/example_anymal_c_walk_on_sand.py
🧬 Code Graph Analysis (5)
newton/_src/solvers/featherstone/kernels.py (1)
newton/_src/sim/joints.py (1)
  • JointType (29-36)
newton/_src/solvers/euler/kernels.py (4)
newton/_src/core/spatial.py (2)
  • quat_decompose (58-92)
  • quat_twist (38-48)
newton/_src/geometry/flags.py (1)
  • ParticleFlags (23-25)
newton/_src/geometry/kernels.py (1)
  • triangle_closest_point_barycentric (62-106)
newton/_src/sim/joints.py (2)
  • JointMode (61-64)
  • JointType (29-36)
newton/_src/solvers/xpbd/kernels.py (3)
newton/_src/core/spatial.py (1)
  • velocity_at_point (22-34)
newton/_src/geometry/flags.py (1)
  • ParticleFlags (23-25)
newton/_src/sim/joints.py (2)
  • JointMode (61-64)
  • JointType (29-36)
newton/examples/example_anymal_c_walk_on_sand.py (4)
newton/_src/solvers/implicit_mpm/solver_implicit_mpm.py (3)
  • SolverImplicitMPM (1040-1914)
  • setup_collider (1135-1196)
  • enrich_state (1120-1133)
newton/_src/solvers/featherstone/solver_featherstone.py (1)
  • SolverFeatherstone (48-666)
newton/_src/utils/render.py (1)
  • RendererOpenGL (1298-1348)
newton/_src/sim/model.py (1)
  • state (420-453)
newton/_src/utils/render.py (7)
newton/_src/core/types.py (2)
  • Axis (64-121)
  • from_any (83-90)
newton/_src/geometry/types.py (2)
  • GeoType (25-36)
  • SDF (44-71)
newton/_src/geometry/flags.py (1)
  • ShapeFlags (39-47)
newton/_src/sim/contacts.py (1)
  • Contacts (23-84)
newton/_src/sim/joints.py (1)
  • JointType (29-36)
newton/_src/sim/state.py (1)
  • State (21-109)
newton/_src/sim/model.py (1)
  • state (420-453)
🔇 Additional comments (44)
newton/_src/solvers/featherstone/kernels.py (6)

136-221: Enum migration in jcalc_transform looks correct

All joint-type checks were updated to use JointType.* with no control-flow changes. Given JointType is IntEnum, comparisons against the int-typed argument are safe.


161-173: Confirm intent: treating DISTANCE joints identically to FREE (6-DoF) in transform/motion/integration

These branches handle JointType.DISTANCE the same as JointType.FREE, i.e., 7 coords / 6 dofs. If Distance is meant to be a scalar constraint (typical for a distance joint), this may over-parameterize the state and rely on constraints elsewhere. If that’s intentional (and unchanged), all good; otherwise consider a dedicated path for DISTANCE with the intended dof/coord layout.

Would you like a follow-up patch that separates DISTANCE handling if it should not be 6-DoF?

Also applies to: 301-321, 467-519


238-321: Enum migration in jcalc_motion looks correct

The switch to JointType.* for PRISMATIC/REVOLUTE/D6/BALL/FREE/DISTANCE is consistent and preserves behavior. Indexing into joint_axis/joint_S_s remains unchanged.


353-402: Enum migration in jcalc_tau looks correct

Use of JointType for BALL, FREE/DISTANCE, and PRISMATIC/REVOLUTE/D6 matches the prior logic; drive and limit forces are applied where expected.


419-537: Enum migration in jcalc_integrate looks correct

The per-joint integration paths (FIXED early return, PRISMATIC/REVOLUTE scalar integration, BALL quaternion update, FREE/DISTANCE 6-DoF twist integration, D6 axis-wise integration) are preserved with the new enums.


18-22: JointType import correctly re-exported; no changes needed

JointType is explicitly re-exported in newton/_src/sim/__init__.py (line 26) and originally defined in newton/_src/sim/joints.py. The only use in newton/_src/solvers/featherstone/kernels.py is safe with the current import.

newton/_src/solvers/euler/kernels.py (6)

20-33: Import path updates are correct and consistent with the public API refactor.

The migration from absolute imports to relative imports is appropriate for the new private package structure. The changes align with moving implementation code to newton/_src while maintaining the same functionality.


643-644: Enum flag usage is correctly implemented.

The migration from PARTICLE_FLAG_ACTIVE constant to ParticleFlags.ACTIVE enum with bitwise AND operation is implemented correctly. The flag checking logic maintains the same behavior while benefiting from type safety.

Also applies to: 719-720


983-987: Joint mode enum comparisons are correctly implemented.

The conversion from JOINT_MODE_* constants to JointMode.* enum values is properly implemented. All conditional checks maintain their original logic while providing better type safety.

Also applies to: 992-994, 997-999


1040-1041: Joint type enum comparisons are correctly implemented.

The migration from JOINT_* constants to JointType.* enum values is consistent and maintains the original conditional logic. The enum usage provides better type safety and code clarity.

Also applies to: 1102-1102, 1110-1110, 1146-1146, 1181-1181, 1190-1190


1378-1378: Consistent enum usage in nested function call.

The use of JointMode.NONE in the eval_joint_force function call is consistent with the enum refactor and maintains the original behavior.


633-633: Approve migration of particle_flags dtype

Searches across the Python codebase returned no remaining wp.uint32 usages for particle_flags. Switching to wp.int32 correctly aligns with the ParticleFlags IntFlag refactor—no further updates needed.

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

151-151: LGTM!

The usage of geometry.MESH_MAXHULLVERT for default maxhullvert value is correct and consistent with the refactored API.


346-346: LGTM!

The usage of geometry.MESH_MAXHULLVERT as the fallback value for maxhullvert when creating meshes is correct.


466-595: LGTM!

The refactoring to use JointType enum values instead of string constants improves type safety and code clarity. All joint type assignments and comparisons have been correctly updated to use the new enum values.

newton/_src/utils/import_usd.py (6)

434-434: LGTM!

The Mesh constructor is correctly called with the imported geometry module.


553-556: LGTM!

The usage of JointMode enum values (TARGET_VELOCITY, TARGET_POSITION) instead of string constants improves type safety and consistency with the refactored API.

Also applies to: 619-622


610-610: LGTM!

Initializing mode with JointMode.NONE is correct and consistent with the enum-based API.


1065-1065: LGTM!

The Mesh constructor correctly uses geometry.MESH_MAXHULLVERT as the default value for maxhullvert.


1089-1089: LGTM!

The usage of quat_between_axes with proper Axis enum values is correct.


1111-1111: LGTM!

Correctly uses ShapeFlags.COLLIDE_SHAPES enum flag instead of the old constant for clearing collision flags.

newton/examples/example_anymal_c_walk_on_sand.py (4)

183-183: LGTM!

The solver class has been correctly renamed to SolverFeatherstone in accordance with the API refactor.


185-196: LGTM!

The SolverImplicitMPM class and its Options are correctly used according to the refactored API.


199-199: LGTM!

The renderer has been correctly updated to use newton.viewer.RendererOpenGL according to the refactored public API structure.


207-207: LGTM!

The forward kinematics function call has been correctly updated to use newton.eval_fk instead of newton.sim.eval_fk.

newton/_src/utils/render.py (13)

25-27: LGTM!

The imports have been correctly updated to use relative paths from the new internal module structure, properly importing the required types and functions.


170-174: LGTM!

The type hints have been correctly updated to use locally imported types (Model, AxisType) instead of the previously qualified names.


198-200: LGTM!

The Axis enum is correctly used with its from_any method to handle type conversion, and the default value is properly set to Axis.Z.


413-451: LGTM!

All geometry type checks have been correctly updated to use the GeoType enum values instead of the old constants. This improves type safety and consistency.


474-474: LGTM!

Shape visibility check correctly uses ShapeFlags.VISIBLE enum flag instead of the old integer constant.


534-536: LGTM!

Joint type checks correctly use JointType enum values (REVOLUTE, D6) instead of the old constants.


586-606: LGTM!

Method signatures have been correctly updated to use locally imported State type instead of the qualified name.


761-761: LGTM!

The isinstance check correctly uses the renamed RendererOpenGL class.


957-996: LGTM!

The RendererUsd class has been properly renamed from SimRendererUsd and its documentation correctly reflects the new public API path (newton.viewer.RendererUsd).


999-1004: LGTM!

The type hints in the __init__ method have been correctly updated to use locally imported types (Model, AxisType).


1231-1231: LGTM!

The static method call correctly uses the renamed class RendererUsd._xform_to_tqs.


1298-1348: LGTM!

The RendererOpenGL class has been properly renamed from SimRendererOpenGL and its documentation correctly reflects the new public API path (newton.viewer.RendererOpenGL).


1351-1351: LGTM!

The backward compatibility alias SimRenderer correctly points to the renamed RendererUsd class.

newton/_src/solvers/xpbd/kernels.py (6)

18-20: LGTM! Imports correctly updated to relative paths.

The imports have been properly updated to use relative imports from the private _src package structure, which aligns with the API refactor objectives. The enum imports (JointMode, JointType, ParticleFlags) follow the new pattern of importing directly from their respective modules.


906-907: Joint type enum usage correctly implemented.

The kernel now properly uses JointType enum values instead of raw constants. The conditional checks are clean and maintain the same logic while being more type-safe and readable.

Also applies to: 945-955


1168-1172: Joint type checks properly updated to use enum values.

All joint type comparisons have been correctly updated to use the new JointType enum values, maintaining consistency with the API refactor goals.

Also applies to: 1237-1237, 1373-1373, 1407-1407


1633-1633: Joint mode enum comparisons correctly implemented.

All comparisons with JointMode enum values have been properly updated, using the new enum constants instead of raw integers. The logic remains functionally equivalent while being more type-safe.

Also applies to: 1647-1647, 1661-1661, 1712-1712, 1718-1718, 1829-1829, 1843-1843, 1857-1857, 1908-1908, 1914-1914


38-38: particle_flags dtype consistency confirmed

All particle_flags arrays across the codebase are now declared with dtype=wp.int32. No instances of wp.uint32 remain for these arrays, and the builder converts flag values into int32. No further action is required.


68-68: Bitwise operations with ParticleFlags IntFlag are correct
All uses of & ParticleFlags.ACTIVE, == 0, != 0, and clearing via & ~ParticleFlags.ACTIVE behave as expected with Python’s IntFlag. No changes required.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🔭 Outside diff range comments (3)
newton/_src/solvers/euler/kernels.py (3)

659-659: Use Warp intrinsics inside kernels: replace Python min() with wp.min().

Python’s min() may not be supported in Warp kernels and can cause codegen errors. Use wp.min for device-compatibility.

-    jd = min(vn, 0.0) * kd
+    jd = wp.min(vn, 0.0) * kd

786-786: Use Warp abs() in kernels: replace abs() with wp.abs().

Python’s abs() may not be supported in Warp kernels and can break codegen. Use wp.abs for correctness.

-    ft = wp.normalize(vt) * wp.min(kf * wp.length(vt), abs(mu * c * ke))
+    ft = wp.normalize(vt) * wp.min(kf * wp.length(vt), wp.abs(mu * c * ke))

633-644: Cast ParticleFlags.ACTIVE to int in kernel bit tests

All particle_flags arrays on host and device use wp.int32, so the dtype is consistent. To avoid any JIT surprises when using an IntFlag in bitwise operations, explicitly cast the enum to int in the kernel.

Files and locations to update:

  • newton/_src/solvers/euler/kernels.py:643

Apply this diff:

--- a/newton/_src/solvers/euler/kernels.py
+++ b/newton/_src/solvers/euler/kernels.py
@@ -640,7 +640,7 @@ def eval_particle_ground_contacts(
     tid = wp.tid()
-    if (particle_flags[tid] & ParticleFlags.ACTIVE) == 0:
+    if (particle_flags[tid] & int(ParticleFlags.ACTIVE)) == 0:
         return

     x = particle_x[tid]
♻️ Duplicate comments (5)
newton/_src/utils/import_mjcf.py (1)

229-229: Fix runtime crash: wrong argument type passed to wp.vec3 in Euler path

wp.vec3 expects three scalars; passing the NumPy array raises a type error when an MJCF has euler=.

-            return quat_from_euler(wp.vec3(euler), *euler_seq)
+            return quat_from_euler(
+                wp.vec3(float(euler[0]), float(euler[1]), float(euler[2])),
+                *euler_seq,
+            )
newton/_src/solvers/euler/kernels.py (1)

685-721: Duplicate: IntFlag mask in kernel — cast to int to be explicit.

Same concern as in eval_particle_ground_contacts: cast the enum to int to be robust with Warp’s JIT.

-    if (particle_flags[particle_index] & ParticleFlags.ACTIVE) == 0:
+    if (particle_flags[particle_index] & int(ParticleFlags.ACTIVE)) == 0:
newton/_src/solvers/xpbd/kernels.py (1)

906-907: Addressed previous feedback: Local enum imports used correctly.

The joint type comparisons now use the locally imported JointType enum constants (e.g., JointType.FIXED, JointType.FREE) instead of referencing the public namespace, which addresses the previous concern about compile-time coupling between private kernels and the public API.

Also applies to: 945-955

newton/examples/example_selection_articulations.py (1)

284-286: Forward kinematics evaluation complete for both articulations

The FK evaluation correctly updates both ants and humanoids when not using MuJoCo solver, ensuring proper transforms for rendering.

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

364-365: Bug: quat_between_axes expects Axis enum, not a string

The axis_str variable is a string ("X"/"Y"/"Z"), but quat_between_axes expects an Axis enum value. This will cause a runtime error when the axis is not "Z".

Apply this fix to convert the string to an Axis enum:

-                    axis_q = quat_between_axes(Axis.Z, axis_str)
+                    axis = Axis.from_string(axis_str)
+                    axis_q = quat_between_axes(Axis.Z, axis)
🧹 Nitpick comments (3)
newton/_src/utils/import_mjcf.py (1)

151-152: Nit: avoid unnecessary str() cast for default; parse more defensively

No need to cast the default to str before int(). Also guard against malformed values.

-                maxhullvert = int(mesh.attrib.get("maxhullvert", str(geometry.MESH_MAXHULLVERT)))
+                maxhullvert_attr = mesh.attrib.get("maxhullvert")
+                maxhullvert = (
+                    int(maxhullvert_attr)
+                    if maxhullvert_attr is not None
+                    else int(geometry.MESH_MAXHULLVERT)
+                )
newton/_src/solvers/euler/kernels.py (1)

1040-1052: Minor readability: avoid shadowing builtins; prefer elif for mutually exclusive joint types.

  • Variable name type shadows Python’s builtin type. Rename to jt or joint_type_id for clarity.
  • Since joint types are mutually exclusive, consider elif for the subsequent branches for clarity (functional behavior is the same due to exclusivity and the early return on FREE/DISTANCE).

Example:

-    type = joint_type[tid]
+    jt = joint_type[tid]
...
-    if type == JointType.FREE or type == JointType.DISTANCE:
+    if jt == JointType.FREE or jt == JointType.DISTANCE:
       ...
-    if type == JointType.FIXED:
+    elif jt == JointType.FIXED:
       ...
-    if type == JointType.PRISMATIC:
+    elif jt == JointType.PRISMATIC:
       ...
newton/_src/solvers/mujoco/solver_mujoco.py (1)

1891-1891: Consider caching topological sort results

The topological_sort function is called during model conversion. For models with many joints, consider caching the sorted order if the joint topology doesn't change frequently.

📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 891772e and e2d6d26.

📒 Files selected for processing (12)
  • newton/_src/sim/ik.py (4 hunks)
  • newton/_src/solvers/euler/kernels.py (12 hunks)
  • newton/_src/solvers/featherstone/kernels.py (13 hunks)
  • newton/_src/solvers/mujoco/solver_mujoco.py (36 hunks)
  • newton/_src/solvers/solver.py (6 hunks)
  • newton/_src/solvers/xpbd/kernels.py (29 hunks)
  • newton/_src/utils/contact_sensor.py (1 hunks)
  • newton/_src/utils/import_mjcf.py (8 hunks)
  • newton/_src/utils/import_usd.py (10 hunks)
  • newton/_src/utils/render.py (21 hunks)
  • newton/examples/example_anymal_c_walk_on_sand.py (3 hunks)
  • newton/examples/example_selection_articulations.py (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • newton/_src/utils/contact_sensor.py
  • newton/_src/solvers/featherstone/kernels.py
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: shi-eric
PR: newton-physics/newton#521
File: newton/examples/example_cloth_hanging.py:36-36
Timestamp: 2025-08-12T05:17:34.404Z
Learning: The Newton migration guide (docs/migration.rst) is specifically for documenting how to migrate existing warp.sim functionality to Newton equivalents. New Newton-only features that didn't exist in warp.sim do not need migration documentation.
📚 Learning: 2025-07-21T19:11:04.077Z
Learnt from: dylanturpin
PR: newton-physics/newton#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/_src/sim/ik.py
📚 Learning: 2025-08-12T05:17:34.404Z
Learnt from: shi-eric
PR: newton-physics/newton#521
File: newton/examples/example_cloth_hanging.py:36-36
Timestamp: 2025-08-12T05:17:34.404Z
Learning: The Newton migration guide (docs/migration.rst) is specifically for documenting how to migrate existing warp.sim functionality to Newton equivalents. New Newton-only features that didn't exist in warp.sim do not need migration documentation.

Applied to files:

  • newton/_src/solvers/xpbd/kernels.py
📚 Learning: 2025-07-15T19:00:39.991Z
Learnt from: dylanturpin
PR: newton-physics/newton#394
File: newton/examples/example_ik_benchmark.py:132-146
Timestamp: 2025-07-15T19:00:39.991Z
Learning: In the Newton library, `newton.utils.download_asset()` already handles internal caching and can work with both local and remote assets, so conditional logic to check for local paths is unnecessary. The standard practice is to use `download_asset` directly rather than implementing custom path handling logic.

Applied to files:

  • newton/examples/example_anymal_c_walk_on_sand.py
🧬 Code Graph Analysis (9)
newton/_src/utils/import_mjcf.py (4)
newton/_src/core/spatial.py (2)
  • quat_between_axes (306-329)
  • quat_from_euler (191-224)
newton/_src/core/types.py (1)
  • Axis (64-121)
newton/_src/geometry/types.py (1)
  • Mesh (74-235)
newton/_src/sim/joints.py (1)
  • JointType (29-36)
newton/_src/solvers/xpbd/kernels.py (3)
newton/_src/core/spatial.py (1)
  • velocity_at_point (22-34)
newton/_src/geometry/flags.py (1)
  • ParticleFlags (23-25)
newton/_src/sim/joints.py (2)
  • JointMode (61-64)
  • JointType (29-36)
newton/_src/utils/import_usd.py (7)
newton/_src/core/spatial.py (1)
  • quat_between_axes (306-329)
newton/_src/core/types.py (1)
  • Axis (64-121)
newton/_src/geometry/types.py (1)
  • Mesh (74-235)
newton/_src/geometry/flags.py (1)
  • ShapeFlags (39-47)
newton/_src/sim/joints.py (1)
  • JointMode (61-64)
newton/_src/sim/builder.py (1)
  • ModelBuilder (68-3911)
newton/_src/utils/topology.py (1)
  • topological_sort (21-79)
newton/_src/solvers/solver.py (2)
newton/_src/geometry/flags.py (1)
  • ParticleFlags (23-25)
newton/_src/sim/contacts.py (1)
  • Contacts (23-84)
newton/examples/example_anymal_c_walk_on_sand.py (4)
newton/_src/solvers/implicit_mpm/solver_implicit_mpm.py (3)
  • SolverImplicitMPM (1040-1914)
  • setup_collider (1135-1196)
  • enrich_state (1120-1133)
newton/_src/solvers/featherstone/solver_featherstone.py (1)
  • SolverFeatherstone (48-666)
newton/_src/utils/render.py (1)
  • RendererOpenGL (1298-1348)
newton/_src/sim/model.py (1)
  • state (420-453)
newton/_src/solvers/mujoco/solver_mujoco.py (6)
newton/_src/geometry/types.py (1)
  • GeoType (25-36)
newton/_src/geometry/flags.py (1)
  • ShapeFlags (39-47)
newton/_src/sim/contacts.py (1)
  • Contacts (23-84)
newton/_src/sim/joints.py (3)
  • EqType (74-77)
  • JointMode (61-64)
  • JointType (29-36)
newton/_src/utils/topology.py (1)
  • topological_sort (21-79)
newton/_src/solvers/flags.py (1)
  • SolverNotifyFlags (22-36)
newton/_src/solvers/euler/kernels.py (4)
newton/_src/core/spatial.py (2)
  • quat_decompose (58-92)
  • quat_twist (38-48)
newton/_src/geometry/flags.py (1)
  • ParticleFlags (23-25)
newton/_src/geometry/kernels.py (1)
  • triangle_closest_point_barycentric (62-106)
newton/_src/sim/joints.py (2)
  • JointMode (61-64)
  • JointType (29-36)
newton/examples/example_selection_articulations.py (4)
newton/_src/utils/selection.py (1)
  • ArticulationView (125-712)
newton/_src/solvers/mujoco/solver_mujoco.py (1)
  • SolverMuJoCo (1060-2568)
newton/_src/utils/render.py (1)
  • RendererOpenGL (1298-1348)
newton/_src/sim/joints.py (1)
  • JointType (29-36)
newton/_src/utils/render.py (7)
newton/_src/core/types.py (2)
  • Axis (64-121)
  • from_any (83-90)
newton/_src/geometry/types.py (2)
  • GeoType (25-36)
  • SDF (44-71)
newton/_src/geometry/flags.py (1)
  • ShapeFlags (39-47)
newton/_src/sim/contacts.py (1)
  • Contacts (23-84)
newton/_src/sim/joints.py (1)
  • JointType (29-36)
newton/_src/sim/state.py (1)
  • State (21-109)
newton/_src/sim/model.py (1)
  • state (420-453)
🔇 Additional comments (72)
newton/_src/utils/import_mjcf.py (3)

346-347: LGTM: sensible default for mesh convex hull vertex cap

Using the per-asset value with a fallback to geometry.MESH_MAXHULLVERT is correct and consistent with the asset parsing above.


467-469: Enum migration looks good; joint-type mapping is clearer and safer

Replacing legacy integer constants with JointType improves readability and type-safety. The mapping for free/fixed/hinge/slide and the D6 fallback is reasonable.

Note: If MJCF type="ball" appears, it currently won't map explicitly to JointType.BALL in this path. If desired, consider adding a specific case for completeness.

Also applies to: 491-496, 519-527, 588-596


26-30: Re-exports confirmed; no import changes required

All of the names you’re importing are already re-exported in their package’s __init__.py, so the current imports in import_mjcf.py are safe:

• newton/_src/core/init.py re-exports quat_between_axes and quat_from_euler
• newton/_src/core/types.py imports Sequence from collections.abc and re-exports it
• newton/_src/geometry/init.py re-exports Mesh

If you prefer, you can import Sequence directly from typing (or collections.abc) for clarity, but it isn’t required for correctness.

newton/_src/solvers/euler/kernels.py (4)

20-31: Relative imports and enum usage look correct.

The migration to relative imports and IntEnum/IntFlag re-exports aligns with the public API refactor. The import of triangle_closest_point_barycentric from ...geometry.kernels also matches the current module layout.


975-1001: Enum-based joint mode handling is correct and reads clearly.

Comparisons against JointMode.TARGET_POSITION / TARGET_VELOCITY are consistent with sim/joints.py definitions. The limit handling that overrides the target force for velocity mode is a good safety.


1102-1190: JointType enum migration across branches looks consistent.

The FIXED/PRISMATIC/REVOLUTE/BALL/D6 branches consistently use JointType.* and hook into eval_joint_force via joint_dof_mode values. Attachment dynamics scaling (angular_damping_scale) is applied consistently across branches.


1373-1379: Passing JointMode.NONE to disable the last axis is appropriate.

This cleanly reuses eval_joint_force without target forces. Nice use of enums to avoid magic numbers.

newton/_src/sim/ik.py (4)

474-478: Import path update looks correct.

The import path change from newton.solvers.featherstone.kernels to ..solvers.featherstone.kernels correctly uses relative imports to reference the kernels module within the reorganized _src directory structure.


966-966: Class rename follows consistent naming convention.

The rename from PositionObjective to IKPositionObjective aligns with the PR's goal of improving API consistency by adding the IK prefix to all IK-related objective classes.


1200-1200: Class rename follows consistent naming convention.

The rename from JointLimitObjective to IKJointLimitObjective is consistent with the API refactoring approach of prefixing IK-related classes for better discoverability and organization.


1428-1428: Class rename follows consistent naming convention.

The rename from RotationObjective to IKRotationObjective completes the consistent naming pattern for all IK objective classes in this refactor.

newton/_src/solvers/xpbd/kernels.py (6)

18-27: LGTM! Clean transition to relative imports and enum usage.

The imports have been properly updated to use relative paths and the new enum-based flags, maintaining consistency with the broader API refactor.


38-38: LGTM! Consistent enum flag data type updates.

The particle_flags parameter data type has been correctly changed from wp.uint32 to wp.int32 across all kernel functions to align with the new ParticleFlags enum implementation.

Also applies to: 122-122, 234-234, 782-782


68-69: LGTM! Proper enum flag usage in conditional checks.

The flag checks have been correctly updated to use ParticleFlags.ACTIVE enum value instead of the raw constant, maintaining the same logical behavior while improving code readability and type safety.

Also applies to: 155-156, 250-251, 265-265, 790-791


1168-1174: LGTM! Comprehensive joint type enum usage.

All joint type comparisons throughout the solver kernels have been consistently updated to use the JointType enum constants, ensuring type safety and consistency across the codebase.

Also applies to: 1237-1243, 1258-1258, 1373-1373, 1407-1407, 1486-1496


1418-1429: LGTM! Joint mode enum integration.

The joint mode checks have been properly updated to use JointMode.NONE, JointMode.TARGET_POSITION, and JointMode.TARGET_VELOCITY enum constants, replacing the raw integer comparisons and improving code clarity.

Also applies to: 1633-1639, 1647-1653, 1661-1667, 1712-1722, 1829-1835, 1843-1849, 1857-1863, 1908-1919


1558-1558: LGTM! Distance and D6 joint type usage.

The remaining joint type references (JointType.DISTANCE and JointType.D6) are correctly using the enum constants, completing the transition away from raw integer constants.

Also applies to: 1751-1751, 1873-1873

newton/examples/example_anymal_c_walk_on_sand.py (5)

33-36: LGTM! API refactor properly applied

The import changes correctly reflect the new public API structure:

  • newton.viewer module added for rendering classes
  • Updated solver import location to newton.solvers

183-183: Solver name consistently updated

The solver instantiation correctly uses the new SolverFeatherstone name following the consistent Solver* naming pattern.


185-196: LGTM! Options creation properly updated

The solver options creation correctly uses the new SolverImplicitMPM.Options() syntax, maintaining consistency with the solver rename.


199-199: Renderer path correctly updated to use new public API

The renderer instantiation properly uses newton.viewer.RendererOpenGL instead of the old newton.utils.SimRendererOpenGL, aligning with the API restructuring.


207-207: Forward kinematics call properly updated

The FK evaluation correctly uses the new top-level newton.eval_fk entry point instead of the old newton.sim.eval_fk, consistent with the public API refactor.

newton/_src/utils/render.py (10)

25-27: Import paths correctly updated to relative imports

The imports have been properly converted to relative imports from the new internal _src structure, maintaining consistency with the private API organization.


169-200: Type annotations properly updated for local types

The __init__ method signature correctly uses local type imports (Model, AxisType) instead of the fully qualified newton.* types, and the axis initialization properly uses local Axis enum methods.


413-473: Geometry type enums consistently applied

The shape creation logic correctly uses GeoType enum values instead of the old constants, improving type safety and consistency across the codebase.


474-476: Shape visibility flag properly migrated to enum

The visibility check correctly uses ShapeFlags.VISIBLE instead of the old constant-based approach, aligning with the new flag-based API.


534-536: Joint type enum correctly applied

The joint visualization logic properly uses JointType.REVOLUTE and JointType.D6 enums instead of the old constants.


586-606: State type annotation properly updated

The apply_picking_force method correctly uses the local State type instead of newton.State, maintaining consistency with the import changes.


761-761: Class rename correctly applied in debug code

The debug check properly references RendererOpenGL instead of the old SimRendererOpenGL name.


865-923: Contacts type properly updated

The method signatures correctly use the local Contacts type (plural form) instead of the old newton.Contacts, maintaining consistency with the API refactor.


957-1231: Class renames correctly applied throughout

The RendererUsd class (formerly SimRendererUsd) is properly renamed, and all internal references including the static method call at line 1231 correctly use the new name.


1298-1351: RendererOpenGL class properly defined

The RendererOpenGL class is correctly created using the factory pattern, and the docstring examples properly reference newton.viewer.RendererOpenGL.

newton/examples/example_selection_articulations.py (5)

24-24: ArticulationView import correctly updated

The import properly references newton.selection.ArticulationView instead of the old newton.utils.selection path, aligning with the new public API structure.


113-113: Solver instantiation properly updated

The solver creation correctly uses newton.solvers.SolverMuJoCo following the new naming convention.


117-125: Renderer initialization properly enhanced

The RendererOpenGL initialization correctly includes additional parameters for better control over the rendering setup, and uses the new newton.viewer namespace.


144-147: JointType enum correctly used in ArticulationView

The exclude_joint_types parameter properly uses newton.JointType.FREE instead of the old JOINT_FREE constant, and the multi-line formatting improves readability.


216-216: Solver type check properly updated

The isinstance check correctly uses newton.solvers.SolverMuJoCo following the new class name.

newton/_src/solvers/solver.py (5)

18-19: Import paths correctly converted to relative imports

The imports properly use relative paths for internal modules, maintaining the private API structure.


28-28: Kernel parameter type correctly updated to int32

The particle_flags parameter type is properly changed from wp.uint32 to wp.int32 to align with the enum implementation.


38-39: ParticleFlags enum properly used for active state check

The active particle check correctly uses ParticleFlags.ACTIVE bitwise operation instead of the old constant.


263-263: Docstring properly updated to plural Contacts

The documentation correctly uses "Contacts" (plural) to match the actual type name.


272-287: Documentation properly updated to reference SolverNotifyFlags enum

The notify_model_changed documentation correctly references the new SolverNotifyFlags enum values instead of the old NOTIFY_FLAG_* constants, improving API clarity.

newton/_src/solvers/mujoco/solver_mujoco.py (21)

26-42: Import paths properly converted to relative imports

All imports correctly use relative paths from the internal _src structure, maintaining proper module organization.


381-411: JointType enum consistently applied in coordinate conversion

The kernel correctly uses JointType.FREE and JointType.BALL for joint type dispatch instead of the old constants.


568-571: JointMode enum properly used for actuator control

The control kernel correctly checks JointMode.NONE using the enum instead of the old constant.


615-636: Joint type checks properly use enum values

The force application kernel correctly uses JointType.FREE, JointType.DISTANCE, and JointType.BALL enums.


693-733: Forward kinematics properly handles all joint types with enums

The FK evaluation correctly uses JointType enum values for all joint type dispatch logic.


950-967: Joint mode properly checked for actuator configuration

The actuator property updates correctly use JointMode.TARGET_POSITION and JointMode.TARGET_VELOCITY enums.


1060-1060: Class properly renamed to SolverMuJoCo

The class name follows the new Solver* naming convention consistently.


1077-1092: Documentation examples properly updated

All documentation examples correctly reference newton.solvers.SolverMuJoCo with the new naming convention.


1289-1294: Notification flags properly use SolverNotifyFlags enum

The notify_model_changed method correctly uses SolverNotifyFlags enum values instead of the old constants.


1306-1306: Static method references properly updated

All static method calls correctly use SolverMuJoCo instead of the old class name.

Also applies to: 1379-1379, 1424-1424


1632-1632: Default parameter properly uses geometry constant

The maxhullvert parameter correctly defaults to geometry.MESH_MAXHULLVERT from the relative import.


1801-1806: Joint types properly filtered using enum

The supported joint types set correctly uses JointType enum values.


1808-1823: Geometry type mappings properly use GeoType enum

Both geom_type_mapping and geom_type_name dictionaries correctly use GeoType enum values as keys.


1880-1880: Shape flags properly checked using bitwise operations

The shape filtering correctly uses ShapeFlags.COLLIDE_SHAPES with bitwise AND operations.

Also applies to: 1904-1904


1913-1955: Shape type checks properly use GeoType enum

The geometry creation logic correctly uses GeoType enum values for all shape type checks.


2036-2172: Joint type dispatch properly uses JointType enum

The joint creation logic correctly uses JointType.FREE and JointType.FIXED for type checking.


2086-2098: Joint modes properly checked for actuator configuration

The actuator setup correctly uses JointMode.TARGET_POSITION and JointMode.TARGET_VELOCITY enums.

Also applies to: 2149-2161


2181-2207: Equality constraint types properly use EqType enum

The constraint creation correctly uses EqType.CONNECT, EqType.JOINT, and EqType.WELD enums.


2286-2286: Static method call properly uses new class name

The update_mjc_data call correctly references SolverMuJoCo.


2340-2343: Notification flags properly combined using bitwise OR

The flag combination correctly uses SolverNotifyFlags enum values with bitwise operations.


2353-2353: Function call properly uses renamed function

The count_rigid_contact_points call correctly uses the new function name from the refactored API.

newton/_src/utils/import_usd.py (9)

28-32: Import organization looks good

The migration to relative imports and direct function imports is clean and follows Python best practices for internal module organization.


434-434: LGTM - Mesh instantiation is correct

The Mesh constructor call correctly uses the imported class without needing the module prefix.


553-556: Enum usage is correct and consistent

The migration to JointMode enum values (TARGET_VELOCITY, TARGET_POSITION) is properly implemented and aligns with the new API structure.


610-610: JointMode enum usage is consistent

The JointMode.NONE, JointMode.TARGET_VELOCITY, and JointMode.TARGET_POSITION enums are correctly used in the D6 joint configuration logic.

Also applies to: 619-619, 622-622


1065-1065: LGTM - MESH_MAXHULLVERT constant properly accessed

The constant is correctly imported and accessed from the geometry module.


1089-1089: Same issue with quat_between_axes expecting Axis enum

Similar to line 364, this line passes an Axis enum directly to quat_between_axes, which is correct since usd_axis_to_axis[shape_spec.axis] returns an Axis enum value.


1111-1111: Shape collision flag clearing is correct

The use of bitwise AND with the complement of ShapeFlags.COLLIDE_SHAPES to clear the collision flag is properly implemented.


113-113: Relative import for topology utilities is appropriate

The circular import is properly handled with a deferred import inside the function, and the relative import path is correct.


732-732: Verified quat_between_axes import

The function quat_between_axes is defined in newton/_src/core/spatial.py with the expected signature and is imported in newton/_src/utils/import_usd.py (line 29) via:

from ..core import quat_between_axes

No issues found—this import is correct.

@preist-nvidia preist-nvidia moved this to In Progress in Newton Roadmap Aug 12, 2025
@preist-nvidia preist-nvidia added this to the Alpha MVP milestone Aug 12, 2025
@preist-nvidia preist-nvidia linked an issue Aug 12, 2025 that may be closed by this pull request

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🔭 Outside diff range comments (7)
newton/tests/test_ik_fk_kernels.py (1)

120-136: Clarify D6 Joint Keyword Args and Angular Axis Types

The add_joint_d6 method signature is:

def add_joint_d6(
    self,
    parent: int,
    child: int,
    linear_axes: Sequence[JointDofConfig] | None = None,
    angular_axes: Sequence[JointDofConfig] | None = None,
    …
) -> int

and JointDofConfig.axis accepts either an Axis enum or a 3-vector. To improve readability and consistency:

  • Name the parent/child arguments explicitly.
  • Use newton.Axis enums for both linear and angular axes.

Affected file:
newton/tests/test_ik_fk_kernels.py (around lines 120–136)

Proposed diff:

         builder.add_joint_d6(
-            -1,
-            child,
+            parent=-1,
+            child=child,
             linear_axes=[
                 cfg(axis=newton.Axis.X),
                 cfg(axis=newton.Axis.Y),
                 cfg(axis=newton.Axis.Z),
             ],
             angular_axes=[
-                cfg(axis=[1, 0, 0]),
-                cfg(axis=[0, 1, 0]),
-                cfg(axis=[0, 0, 1]),
+                cfg(axis=newton.Axis.X),
+                cfg(axis=newton.Axis.Y),
+                cfg(axis=newton.Axis.Z),
             ],
             parent_xform=parent_xf,
             child_xform=child_xf,
         )
newton/examples/example_contact_sensor.py (3)

323-338: Bug: torch.device is not a context manager — use torch.cuda.device or a nullcontext

torch.device(...) doesn’t implement enter/exit, so using it as a context manager will raise at runtime whenever USE_TORCH is True.

Robust fix that supports both CUDA and CPU:

 class ScopedDevice:
     def __init__(self, device):
         self.warp_scoped_device = wp.ScopedDevice(device)
         if USE_TORCH:
-            import torch  # noqa: PLC0415
-
-            self.torch_scoped_device = torch.device(wp.device_to_torch(device))
+            import torch  # noqa: PLC0415
+            from contextlib import nullcontext
+            torch_dev = wp.device_to_torch(device) if device is not None else None
+            # torch.cuda.device is a context manager, torch.device is not.
+            if torch_dev and str(torch_dev).startswith("cuda"):
+                self.torch_scoped_device = torch.cuda.device(torch_dev)
+            else:
+                self.torch_scoped_device = nullcontext()

     def __enter__(self):
         self.warp_scoped_device.__enter__()
         if USE_TORCH:
             self.torch_scoped_device.__enter__()

     def __exit__(self, exc_type, exc_val, exc_tb):
         self.warp_scoped_device.__exit__(exc_type, exc_val, exc_tb)
         if USE_TORCH:
             self.torch_scoped_device.__exit__(exc_type, exc_val, exc_tb)

112-113: Cannot locate populate_contacts in Contacts. The class only preallocates fixed-size arrays—it does not auto-resize. Thus, calling Contacts(0, 0) creates zero-length buffers and will break as soon as any contact is recorded.

Action Required: Allocate reasonable upper bounds instead of zeros.

• File newton/examples/example_contact_sensor.py, around line 112:
Replace
python self.contacts = Contacts(0, 0)
with something like
python # Preallocate for expected max contacts per environment max_rigid = 128 max_soft = 64 self.contacts = Contacts(max_rigid, max_soft)

Adjust max_rigid/max_soft based on your use case.


134-142: Fix up_axis parameter in RendererOpenGL call

The up_axis argument expects a newton.AxisType (e.g. an Axis enum or simple "X"|"Y"|"Z"), not the string returned by str(up_axis) (which produces "Axis.Z"). Update the call to pass the enum directly:

• File: newton/examples/example_contact_sensor.py
• Lines: 134–142

             self.renderer = newton.viewer.RendererOpenGL(
                 path=stage_path,
                 model=self.model,
                 scaling=2.0,
-                up_axis=str(up_axis),
+                up_axis=up_axis,
                 screen_width=1280,
                 screen_height=720,
                 camera_pos=(0, 4, 30),
             )
newton/examples/example_robot_manipulating_cloth.py (3)

50-67: Fix return type and docstring of vec_rotation (returns a quaternion, not a transform)

The function returns a wp.quat, and usage unpacks four components. The return annotation and docstring are misleading.

Apply:

-def vec_rotation(x: float, y: float, z: float) -> wp.transform:
-    """Convert plane coordinates given by the plane normal and its offset along the normal to a transform."""
+def vec_rotation(x: float, y: float, z: float) -> wp.quat:
+    """Return a quaternion that rotates +Z onto the normalized vector (x, y, z)."""

150-151: Use NumPy dtype when allocating with NumPy

np.empty(..., dtype=wp.float32) is invalid; NumPy expects a NumPy dtype.

Apply:

-    J = np.empty((out_dim, in_dim), dtype=wp.float32)
+    J = np.empty((out_dim, in_dim), dtype=np.float32)

472-475: Prevent out-of-bounds on searchsorted boundary conditions

When t_mod equals the last time point, np.searchsorted can return len(...), causing OOB on self.targets[current_interval].

Apply:

-        include_rotation = True
-        current_interval = np.searchsorted(self.robot_key_poses_time, t_mod)
-        self.target = self.targets[current_interval]
+        include_rotation = True
+        idx = np.searchsorted(self.robot_key_poses_time, t_mod, side="right")
+        current_interval = min(int(idx), self.targets.shape[0] - 1)
+        self.target = self.targets[current_interval]
♻️ Duplicate comments (1)
newton/examples/example_robot_manipulating_cloth.py (1)

37-39: Import transform_twist from public newton.core (not newton.utils)

transform_twist is part of the public spatial API exposed via newton.core (see newton/_src/core/spatial.py). Importing it from newton.utils is incorrect and may break at runtime.

Apply this diff:

-from newton.utils import transform_twist
+from newton.core import transform_twist
🧹 Nitpick comments (11)
newton/utils.py (1)

30-33: Add a safety note where exposing pickle-based recorder.

ModelAndStateRecorder uses pickle; it’s good that the class docstring warns, but since this module is the public façade, consider adding a brief module-level note about only loading recordings from trusted sources.

I can add a short module docstring summarizing utilities and warning about pickle upon request.

newton/tests/test_ik.py (3)

184-184: Centralize FK calls behind a local wrapper to ease future API swaps

Multiple direct invocations of the private _eval_fk_batched(...) make the test brittle. Wrap the call once (e.g., _run_fk(...)) and use that everywhere. This also makes switching to a future public helper trivial.

Apply these diffs at the call sites:

-        _eval_fk_batched(model, joint_q_2d, joint_qd_2d, body_q_2d, body_qd_2d)
+        _run_fk(model, joint_q_2d, joint_qd_2d, body_q_2d, body_qd_2d)

And add this helper near your existing FK utility (outside these ranges):

def _run_fk(model, joint_q_2d, joint_qd_2d, body_q_2d, body_qd_2d):
    # Funnel FK through one place to decouple tests from private/internal symbol changes.
    _eval_fk_batched(model, joint_q_2d, joint_qd_2d, body_q_2d, body_qd_2d)

Also applies to: 190-190, 238-238, 243-243, 284-284, 289-289


279-281: Prefer keyword arguments for clarity and to resist signature churn

The D6 objective creation mixes positional args, which is error-prone in refactors. Use keywords to match the other call sites and improve readability.

-        pos_obj = ik.IKPositionObjective(0, wp.vec3(0.0, 0.0, 0.0), pos_targets, n_problems, 6, 0)
-        rot_obj = ik.IKRotationObjective(0, wp.quat_identity(), rot_targets, n_problems, 6, 3)
+        pos_obj = ik.IKPositionObjective(
+            link_index=0,
+            link_offset=wp.vec3(0.0, 0.0, 0.0),
+            target_positions=pos_targets,
+            n_problems=n_problems,
+            total_residuals=6,
+            residual_offset=0,
+        )
+        rot_obj = ik.IKRotationObjective(
+            link_index=0,
+            link_offset_rotation=wp.quat_identity(),
+            target_rotations=rot_targets,
+            n_problems=n_problems,
+            total_residuals=6,
+            residual_offset=3,
+        )

420-421: Use keyword arguments for D6 Jacobian objective builder (consistency with other builders)

Minor style consistency to avoid positional arg pitfalls.

-    pos_obj = ik.IKPositionObjective(0, wp.vec3(0.0, 0.0, 0.0), pos_targets, n_problems, 6, 0)
-    rot_obj = ik.IKRotationObjective(0, wp.quat_identity(), rot_targets, n_problems, 6, 3)
+    pos_obj = ik.IKPositionObjective(
+        link_index=0,
+        link_offset=wp.vec3(0.0, 0.0, 0.0),
+        target_positions=pos_targets,
+        n_problems=n_problems,
+        total_residuals=6,
+        residual_offset=0,
+    )
+    rot_obj = ik.IKRotationObjective(
+        link_index=0,
+        link_offset_rotation=wp.quat_identity(),
+        target_rotations=rot_targets,
+        n_problems=n_problems,
+        total_residuals=6,
+        residual_offset=3,
+    )
newton/tests/test_ik_fk_kernels.py (2)

31-37: Use precise typing for JOINT_KINDS

The list is annotated as list[int] but populated with JointType (IntEnum). Typing it as list[JointType] improves clarity and keeps the tests aligned with the new public API.

Apply this diff:

-JOINT_KINDS: list[int] = [
+JOINT_KINDS: list[JointType] = [
     JointType.REVOLUTE,
     JointType.PRISMATIC,
     JointType.BALL,
     JointType.D6,
     JointType.FREE,
 ]

As a follow-up (outside this hunk), consider updating function signatures for precision:

# Outside this diff hunk
def _add_single_joint(builder: newton.ModelBuilder, jt: JointType) -> None: ...
def _build_model_for_joint(jt: JointType, device): ...

185-201: Unify enum usage style within this function

Elsewhere the file uses JointType.* via the import; here newton.JointType.* is used. For consistency, prefer the imported JointType.

Apply this diff:

-        elif jt == newton.JointType.D6:
+        elif jt == JointType.D6:
             # lin X,Y,Z
             q_np[q0 + 0 : q0 + 3] = rng.uniform(-0.1, 0.1, size=3)
             # rot XYZ
             q_np[q0 + 3 : q0 + 6] = rng.uniform(-np.pi / 8, np.pi / 8, size=3)

-        elif jt == newton.JointType.FREE:
+        elif jt == JointType.FREE:
             # translation
             q_np[q0 + 0 : q0 + 3] = rng.uniform(-0.3, 0.3, size=3)
             # quaternion
             axis = rng.normal(size=3)
             axis /= np.linalg.norm(axis) + 1e-8
             angle = rng.uniform(-np.pi / 6, np.pi / 6)
             qw = np.cos(angle / 2.0)
             qv = axis * np.sin(angle / 2.0)
             q_np[q0 + 3 : q0 + 7] = (*qv, qw)
newton/sensors.py (2)

17-20: Optional: make imports lazy to keep import-time light

If newton/_src/utils/contact_sensor pulls in heavy deps (e.g., Warp), consider lazy-loading the symbols to avoid unnecessary startup cost when users import newton but don’t use sensors.

Example refactor within this module:

-from ._src.utils.contact_sensor import (
-    ContactSensor,
-    populate_contacts,
-)
+def __getattr__(name: str):
+    if name in __all__:
+        from ._src.utils.contact_sensor import ContactSensor, populate_contacts
+        return {"ContactSensor": ContactSensor, "populate_contacts": populate_contacts}[name]
+    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

16-25: Consider a deprecation shim for old import paths

External code likely used newton.utils.contact_sensor.{ContactSensor,populate_contacts}. To smooth the migration, add a shim that re-exports and emits a DeprecationWarning.

Proposed new file: newton/utils/contact_sensor.py

# Deprecated shim: newton.utils.contact_sensor
import warnings
from newton.sensors import ContactSensor, populate_contacts  # public façade

warnings.warn(
    "newton.utils.contact_sensor is deprecated; import from newton.sensors instead.",
    DeprecationWarning,
    stacklevel=2,
)

__all__ = ["ContactSensor", "populate_contacts"]

I can open a follow-up PR to add the shim and wire it into the test suite if desired.

newton/_src/solvers/featherstone/kernels.py (1)

91-102: Minor: prefer elif for mutually exclusive joint-type branches

Since each branch returns immediately, behavior is correct; using elif clarifies mutual exclusivity and avoids redundant type comparisons.

Apply within this block:

- if type == JT_PRISMATIC:
+ if type == JT_PRISMATIC:
    ...
- if type == JT_REVOLUTE:
+ elif type == JT_REVOLUTE:
    ...
- if type == JT_BALL:
+ elif type == JT_BALL:
    ...

Repeat pattern for the subsequent branches in this function.

newton/examples/example_robot_manipulating_cloth.py (2)

330-333: Remove unused allocations to reduce clutter

self.delta_q, self.joint_q_des, and self.J_shape are not used. Removing them reduces mental overhead.

Apply:

-        # for robot control
-        self.delta_q = wp.empty(self.model.joint_count, dtype=float)
-        self.joint_q_des = wp.array(self.model.joint_q.numpy(), dtype=float)
+        # for robot control
+        # (removed unused: delta_q, joint_q_des)
@@
-        self.J_shape = wp.array((out_dim, in_dim), dtype=int)
+        # (removed unused: J_shape)

Also applies to: 345-347


86-89: Orientation error uses quaternion vector part; consider log-map/axis-angle for better scaling

Taking ang_diff[0:3] as angular error couples error magnitude to quaternion parameterization. Consider using a scaled-axis (log map) or axis-angle representation for more consistent IK behavior.

📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e2d6d26 and afd521e.

📒 Files selected for processing (10)
  • newton/__init__.py (1 hunks)
  • newton/_src/solvers/featherstone/kernels.py (14 hunks)
  • newton/examples/example_cloth_style3d.py (5 hunks)
  • newton/examples/example_contact_sensor.py (5 hunks)
  • newton/examples/example_robot_manipulating_cloth.py (3 hunks)
  • newton/sensors.py (1 hunks)
  • newton/tests/test_contact_sensor.py (2 hunks)
  • newton/tests/test_ik.py (10 hunks)
  • newton/tests/test_ik_fk_kernels.py (8 hunks)
  • newton/utils.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • newton/tests/test_contact_sensor.py
  • newton/init.py
  • newton/examples/example_cloth_style3d.py
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: shi-eric
PR: newton-physics/newton#521
File: newton/examples/example_cloth_hanging.py:36-36
Timestamp: 2025-08-12T05:17:34.404Z
Learning: The Newton migration guide (docs/migration.rst) is specifically for documenting how to migrate existing warp.sim functionality to Newton equivalents. New Newton-only features that didn't exist in warp.sim do not need migration documentation.
📚 Learning: 2025-07-21T19:11:04.077Z
Learnt from: dylanturpin
PR: newton-physics/newton#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_ik.py
🧬 Code Graph Analysis (5)
newton/sensors.py (1)
newton/_src/utils/contact_sensor.py (2)
  • ContactSensor (117-317)
  • populate_contacts (109-114)
newton/utils.py (5)
newton/_src/core/spatial.py (10)
  • quat_between_axes (306-329)
  • quat_decompose (58-92)
  • quat_from_euler (191-224)
  • quat_to_euler (136-187)
  • quat_to_rpy (96-132)
  • quat_twist (38-48)
  • quat_twist_angle (52-54)
  • transform_twist (228-264)
  • transform_wrench (268-300)
  • velocity_at_point (22-34)
newton/_src/utils/download_assets.py (1)
  • download_asset (157-174)
newton/_src/utils/import_mjcf.py (1)
  • parse_mjcf (33-920)
newton/_src/utils/import_usd.py (1)
  • parse_usd (35-1268)
newton/_src/utils/recorder.py (2)
  • BasicRecorder (26-117)
  • ModelAndStateRecorder (120-318)
newton/_src/solvers/featherstone/kernels.py (2)
newton/_src/core/spatial.py (1)
  • transform_twist (228-264)
newton/_src/sim/joints.py (1)
  • JointType (29-36)
newton/tests/test_ik_fk_kernels.py (3)
newton/_src/sim/joints.py (1)
  • JointType (29-36)
newton/tests/unittest_utils.py (2)
  • add_function_test (281-300)
  • get_test_devices (98-132)
newton/_src/sim/builder.py (1)
  • add_joint_ball (1064-1099)
newton/examples/example_robot_manipulating_cloth.py (5)
newton/_src/sim/state.py (1)
  • State (21-109)
newton/_src/solvers/featherstone/solver_featherstone.py (1)
  • SolverFeatherstone (48-666)
newton/_src/solvers/vbd/solver_vbd.py (1)
  • SolverVBD (2234-3011)
newton/_src/core/spatial.py (1)
  • transform_twist (228-264)
newton/_src/utils/render.py (1)
  • RendererOpenGL (1298-1348)
🔇 Additional comments (26)
newton/utils.py (3)

1-15: License header and SPDX metadata look correct.

Header complies with Apache-2.0 and includes copyright.


16-28: Good, focused public façade for spatial utilities.

Re-export set is coherent with the PR aim to curate the public API and avoid leaking private symbols.


35-57: All __all__ exports verified; no top‐level USD imports detected

  • Confirmed every name in __all__ exists in its leaf module:
    • Spatial functions in newton/_src/core/spatial.py
    • download_asset and parsers in newton/_src/utils/import_*.py
    • Recorder classes in newton/_src/utils/recorder.py
  • newton/_src/utils/__init__.py only imports parse_usd (no direct pxr import at import time); USD is imported lazily inside the parser functions

Maintaining this list as the single source of truth is appropriate.

newton/tests/test_ik.py (5)

172-179: LGTM: New IKPositionObjective usage with explicit keywords

The constructor call aligns with the renamed IK class and is clear/explicit about residual sizes and offsets.


227-234: LGTM: Consistent IKPositionObjective construction for FREE+REV test

Keyword arguments improve clarity and reduce risk of ordering mistakes.


346-353: LGTM: Position Jacobian objective builder is explicit and consistent

Clear use of keywords and correct residual sizing for a 3D position residual.


369-376: LGTM: Rotation Jacobian objective builder follows the updated API

Keyword args and quaternion target preparation look correct.


395-403: Validate applicability of IKJointLimitObjective to the model under test

Per prior team guidance, IKJointLimitObjective intentionally targets joints where DOF count equals coordinate count (e.g., REV/PRIS). This builder uses model.joint_coord_count, which should be fine for the planar REV-REV model used in _jacobian_compare.

Please confirm this builder never gets used with models containing joints where DOF != coord count (e.g., ball/free). If there’s any chance of reuse, add an assertion to guard against that scenario.

newton/tests/test_ik_fk_kernels.py (7)

24-25: Imports aligned with the new public API

Switching to newton.ik and importing JointType from the package root is consistent with the refactor goals and improves discoverability.


94-102: LGTM: Revolute joint branch updated to JointType

The branch correctly uses JointType.REVOLUTE and the builder API.


103-111: LGTM: Prismatic joint branch

Consistent with the new enum and public builder API. Axis is clearly specified.


112-119: LGTM: Ball joint branch

Uses the new enum and public add_joint_ball() API as expected.


138-145: LGTM: Free joint branch

Matches new enum and builder API usage.


170-184: LGTM: Randomization for REVOLUTE/PRISMATIC/BALL

The ranges and quaternion construction look sound and respect expected coordinate layouts (xyzw).


217-217: Top-level export of eval_fk is confirmed

  • In newton/__init__.py, eval_fk is explicitly imported (line 44) and included in __all__ (line 77).

No further changes required.

newton/sensors.py (1)

16-25: Public façade and explicit exports look correct

The module cleanly re-exports only the intended public symbols and declares them in all, which aligns with the PR’s API-curation goals.

newton/examples/example_contact_sensor.py (4)

25-28: Imports now target the public API — good cleanup

Switching to newton.Contacts, newton.selection.ArticulationView, and newton.sensors.{ContactSensor,populate_contacts} is consistent with the refactor goals and prevents private leaks.


130-130: Solver rename adoption looks correct

Instantiating newton.solvers.SolverMuJoCo matches the new naming convention and the type checks later in the example.


233-233: Correct conditional collision handling

The explicit collide() call only for non-MuJoCo solvers makes sense if SolverMuJoCo maintains its own contact pipeline internally.


161-164: exclude_joint_types usage is correct

ArticulationView.init explicitly types exclude_joint_types as list[int] | None, and JointType.FREE (an IntFlag) is a subclass of int. Passing [newton.JointType.FREE] correctly satisfies the expected list[int] and will be matched via the internal membership check.

newton/_src/solvers/featherstone/kernels.py (2)

116-128: Double-check semantics for JointType.DISTANCE treated as 6-DOF FREE

In jcalc_transform, jcalc_motion, and jcalc_integrate, DISTANCE is handled identically to FREE (7 coords, 6 dofs). If DISTANCE is meant to represent a constraint limiting the distance between two points (commonly 1-DOF), this handling could be incorrect.

  • If DISTANCE here is a placeholder for a 6-DOF “free” link used by downstream constraint solvers, ignore this.
  • Otherwise, this is likely a functional bug.

Can you confirm the intended DOF/coord layout for JointType.DISTANCE and whether matching FREE is by design?

Also applies to: 256-276, 422-475


91-129: Ensure Warp device code supports IntEnum comparisons

We’ve confirmed that JointType is defined as an IntEnum in newton/_src/sim/joints.py:29 and that in newton/_src/solvers/featherstone/kernels.py (lines 91–129) you’re comparing an int parameter type directly against JointType.* inside @wp.func. Warp may not lower IntEnum comparisons correctly on all backends.

Action items for verification and improvement:

  • Confirm that @wp.func builds and runs correctly when doing type == JointType.PRISMATIC (and other enum members) on CPU/GPU, debug/opt targets.
  • If not supported, introduce module‐level integer constants at the top of kernels.py:
    • JT_PRISMATIC = int(JointType.PRISMATIC)
    • JT_REVOLUTE = int(JointType.REVOLUTE)
    • JT_BALL = int(JointType.BALL)
    • JT_FIXED = int(JointType.FIXED)
    • JT_FREE = int(JointType.FREE)
    • JT_DISTANCE = int(JointType.DISTANCE)
    • JT_D6 = int(JointType.D6)
  • Update the branch tests to use these constants (e.g. if type == JT_PRISMATIC:) and convert the if chains to if/elif for clarity.
  • Double-check that treating JointType.DISTANCE exactly like a 6-DOF free joint is intentional, since distance joints are typically 1-DOF constraints.

Would you like me to draft a minimal diff introducing the module‐level constants, replacing the enum comparisons, and converting to elif?

newton/examples/example_robot_manipulating_cloth.py (4)

276-276: LGTM: Updated solver name and parameter use

Using SolverFeatherstone with update_mass_matrix_interval=self.num_substeps aligns with the public API rename and expected configuration.


279-279: LGTM: Correct type annotation for cloth solver

Annotating cloth_solver: SolverVBD | None matches the new naming and improves clarity.


282-295: Collision Detection Interval Semantics Verified
According to the SolverVBD docstring in solver_vbd.py (around lines 2300–2304):

  • < 0 → one collision‐detection pass only before initialization
  • 0 → two passes (once before and once immediately after initialization)
  • k ≥ 1 → one pass every k VBD iterations

Thus, passing -1 here will perform exactly one pre‐init detection and disable any further checks, matching the intended behavior. No changes are required.


298-307: Confirm OpenGL renderer keyword support

The Python RendererOpenGL class (a CreateSimRenderer subclass) defines an __init__ that accepts show_joints, show_particles and a **render_kwargs catch‐all. The example passes near_plane, far_plane, and enable_backface_culling into that catch‐all, which in turn must be supported by the underlying C++/Python OpenGLRenderer constructor.

Please manually verify in the native binding or documentation that OpenGLRenderer indeed accepts these keywords to avoid runtime errors.

• File: newton/examples/example_robot_manipulating_cloth.py
• Lines: 298–307

Comment thread newton/_src/solvers/featherstone/kernels.py
Comment thread newton/_src/solvers/featherstone/kernels.py
Comment thread newton/tests/test_ik.py
Comment thread newton/utils.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🔭 Outside diff range comments (5)
newton/_src/utils/import_usd.py (3)

132-139: Bug: mapping key won’t match due to lower() usage.

approximation.lower() is used, but the dict contains "meshSimplification" with capital S. That key will never match.

Fix by lowercasing the key:

-    approximation_to_remeshing_method = {
+    approximation_to_remeshing_method = {
         "convexdecomposition": "coacd",
         "convexhull": "convex_hull",
         "boundingsphere": "bounding_sphere",
         "boundingcube": "bounding_box",
-        "meshSimplification": "quadratic",
+        "meshsimplification": "quadratic",
     }

421-436: Indices should be int32, not float32.

Face indices are read as float32 and only later cast; use int32 at the source to avoid unnecessary conversions and potential precision pitfalls.

Apply:

-                indices = np.array(mesh.GetFaceVertexIndicesAttr().Get(), dtype=np.float32)
+                indices = np.array(mesh.GetFaceVertexIndicesAttr().Get(), dtype=np.int32)
...
-                m = Mesh(points, np.array(faces, dtype=np.int32).flatten())
+                m = Mesh(points, np.array(faces, dtype=np.int32).flatten(), maxhullvert=mesh_maxhullvert)

Note: The second line also applies the optional consistency tweak for maxhullvert.


1048-1067: Indices should be int32 here as well.

Same dtype issue for mesh collision shapes: read indices as int32 upstream.

Apply:

-                    indices = np.array(mesh.GetFaceVertexIndicesAttr().Get(), dtype=np.float32)
+                    indices = np.array(mesh.GetFaceVertexIndicesAttr().Get(), dtype=np.int32)
newton/tests/test_import_usd.py (1)

224-227: Missing USD guard will break environments without usd-core.

Other tests are guarded with @unittest.skipUnless(USD_AVAILABLE, ...). This one imports pxr inside the test but lacks the decorator.

Add the skip decorator:

-    def test_mesh_approximation(self):
+    @unittest.skipUnless(USD_AVAILABLE, "Requires usd-core")
+    def test_mesh_approximation(self):
newton/_src/utils/import_mjcf.py (1)

398-407: Bug: cylinders are added as capsules.

The cylinder branch calls add_shape_capsule instead of add_shape_cylinder, leading to incorrect geometry.

Fix the call:

-                if geom_type == "cylinder":
-                    s = builder.add_shape_capsule(
+                if geom_type == "cylinder":
+                    s = builder.add_shape_cylinder(
                         xform=tf,
                         radius=geom_radius,
                         half_height=geom_height,
                         axis=geom_up_axis,
                         **shape_kwargs,
                     )
♻️ Duplicate comments (1)
newton/_src/utils/import_usd.py (1)

365-366: Passing strings to quat_between_axes is correct.

This was previously flagged; however, quat_between_axes accepts AxisType and handles string inputs internally via Axis.from_any(). No change needed.

🧹 Nitpick comments (5)
newton/_src/utils/import_urdf.py (1)

69-91: New parameter mesh_maxhullvert is well-threaded and documented.

Signature and docstring addition look good and match usage below. Minor nit: consider documenting the default explicitly in the docstring for discoverability.

Apply this docstring tweak:

-        mesh_maxhullvert (int): Maximum vertices for convex hull approximation of meshes.
+        mesh_maxhullvert (int): Maximum vertices for convex hull approximation of meshes (default: MESH_MAXHULLVERT).
newton/_src/utils/import_usd.py (2)

53-54: Expose mesh_maxhullvert in parse_usd API — consider also applying to visual mesh shapes.

Adding mesh_maxhullvert is great. Currently it’s applied to Mesh for collision shapes; visual meshes created in load_visual_shapes don’t pass it (they rely on the default). Optional: pass mesh_maxhullvert there too for consistency across all Mesh instances.

Apply this change where visual meshes are created:

-                m = Mesh(points, np.array(faces, dtype=np.int32).flatten())
+                m = Mesh(points, np.array(faces, dtype=np.int32).flatten(), maxhullvert=mesh_maxhullvert)

Also applies to: 80-81


1112-1113: Type robustness when clearing COLLIDE_SHAPES bit.

If builder.shape_flags is an array of ints, mixing IntFlag in-place ops can yield IntFlag objects. Cast to int for predictability.

Apply:

-                    builder.shape_flags[shape_id] &= ~ShapeFlags.COLLIDE_SHAPES
+                    builder.shape_flags[shape_id] = int(builder.shape_flags[shape_id]) & ~int(ShapeFlags.COLLIDE_SHAPES)

If builder.shape_flags is already IntFlag-typed, this is a no-op semantically; the change ensures consistency across backends.

newton/tests/test_import_usd.py (1)

55-56: Nit: unify flag checks style for readability.

You use both int(ShapeFlags...) and direct IntFlag bit checks in this file. Consider consistently using the IntFlag style as below (matches usage later in this test).

Apply:

-        collision_shapes = [
-            i for i in range(builder.shape_count) if builder.shape_flags[i] & int(newton.ShapeFlags.COLLIDE_SHAPES)
-        ]
+        collision_shapes = [
+            i for i in range(builder.shape_count) if builder.shape_flags[i] & newton.ShapeFlags.COLLIDE_SHAPES
+        ]
newton/_src/utils/import_mjcf.py (1)

230-230: Prefer unpacked construction for wp.vec3 for consistency.

While Warp may accept an array, the codebase consistently uses wp.vec3(*array). Align for readability and consistency.

Apply:

-            return quat_from_euler(wp.vec3(euler), *euler_seq)
+            return quat_from_euler(wp.vec3(*euler), *euler_seq)
📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between afd521e and 5f888bd.

📒 Files selected for processing (6)
  • newton/_src/geometry/types.py (1 hunks)
  • newton/_src/solvers/mujoco/solver_mujoco.py (36 hunks)
  • newton/_src/utils/import_mjcf.py (10 hunks)
  • newton/_src/utils/import_urdf.py (5 hunks)
  • newton/_src/utils/import_usd.py (12 hunks)
  • newton/tests/test_import_usd.py (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • newton/_src/geometry/types.py
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
Learnt from: shi-eric
PR: newton-physics/newton#521
File: newton/examples/example_cloth_hanging.py:36-36
Timestamp: 2025-08-12T05:17:34.404Z
Learning: The Newton migration guide (docs/migration.rst) is specifically for documenting how to migrate existing warp.sim functionality to Newton equivalents. New Newton-only features that didn't exist in warp.sim do not need migration documentation.
📚 Learning: 2025-08-12T18:04:06.551Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/_src/solvers/featherstone/kernels.py:75-75
Timestamp: 2025-08-12T18:04:06.551Z
Learning: The Newton physics framework requires nightly Warp builds, which means compatibility concerns with older stable Warp versions (like missing functions such as wp.spatial_adjoint) are not relevant for this project.

Applied to files:

  • newton/_src/solvers/mujoco/solver_mujoco.py
  • newton/_src/utils/import_mjcf.py
📚 Learning: 2025-08-12T05:17:34.404Z
Learnt from: shi-eric
PR: newton-physics/newton#521
File: newton/examples/example_cloth_hanging.py:36-36
Timestamp: 2025-08-12T05:17:34.404Z
Learning: The Newton migration guide (docs/migration.rst) is specifically for documenting how to migrate existing warp.sim functionality to Newton equivalents. New Newton-only features that didn't exist in warp.sim do not need migration documentation.

Applied to files:

  • newton/_src/solvers/mujoco/solver_mujoco.py
  • newton/_src/utils/import_mjcf.py
📚 Learning: 2025-08-12T18:07:17.216Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.216Z
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/_src/utils/import_usd.py
📚 Learning: 2025-08-12T18:07:17.216Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/utils.py:30-33
Timestamp: 2025-08-12T18:07:17.216Z
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/_src/utils/import_usd.py
📚 Learning: 2025-08-12T17:51:37.434Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/geometry.py:16-22
Timestamp: 2025-08-12T17:51:37.434Z
Learning: In the Newton public API refactor, common geometry symbols like ParticleFlags and ShapeFlags are now exposed at the top level in newton/__init__.py rather than through newton.geometry. The newton/geometry.py module is intentionally focused only on broad-phase collision detection classes (BroadPhaseAllPairs, BroadPhaseExplicit, BroadPhaseSAP).

Applied to files:

  • newton/_src/utils/import_usd.py
  • newton/tests/test_import_usd.py
📚 Learning: 2025-08-12T18:04:06.254Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/_src/utils/import_usd.py:364-365
Timestamp: 2025-08-12T18:04:06.254Z
Learning: The quat_between_axes function in Newton accepts AxisType parameters, which includes strings like "X", "Y", "Z" as well as Axis enum values and integers. The function internally handles conversion using Axis.from_any(), so passing strings directly to quat_between_axes is valid and correct behavior.

Applied to files:

  • newton/_src/utils/import_usd.py
📚 Learning: 2025-08-12T18:04:06.254Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/_src/utils/import_usd.py:364-365
Timestamp: 2025-08-12T18:04:06.254Z
Learning: The quat_between_axes function in Newton accepts AxisType parameters, which includes both Axis enum values and strings. The function internally handles string-to-Axis conversion using Axis.from_any(), so passing strings directly to quat_between_axes is valid and correct.

Applied to files:

  • newton/_src/utils/import_usd.py
📚 Learning: 2025-08-12T17:58:16.858Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/tests/test_ik.py:25-26
Timestamp: 2025-08-12T17:58:16.858Z
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_import_usd.py
📚 Learning: 2025-08-12T17:52:14.970Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/_src/utils/import_mjcf.py:226-231
Timestamp: 2025-08-12T17:52:14.970Z
Learning: In the Newton codebase, when passing array-like objects (numpy arrays, lists, tuples) to wp.vec3(), the consistent pattern is to use the unpacking operator: wp.vec3(*array) rather than wp.vec3(array). This pattern is used throughout newton/_src/utils/import_mjcf.py and newton/_src/core/types.py.

Applied to files:

  • newton/_src/utils/import_mjcf.py
📚 Learning: 2025-08-12T05:17:34.404Z
Learnt from: shi-eric
PR: newton-physics/newton#521
File: newton/examples/example_cloth_hanging.py:36-36
Timestamp: 2025-08-12T05:17:34.404Z
Learning: The STYLE3D solver in Newton is a new solver that did not exist in warp.sim, so it does not require any migration documentation.

Applied to files:

  • newton/_src/utils/import_mjcf.py
🧬 Code Graph Analysis (4)
newton/_src/utils/import_urdf.py (3)
newton/_src/core/types.py (1)
  • Axis (64-121)
newton/_src/core/spatial.py (1)
  • quat_between_axes (306-329)
newton/_src/geometry/types.py (1)
  • Mesh (73-234)
newton/_src/solvers/mujoco/solver_mujoco.py (7)
newton/_src/core/types.py (1)
  • override (36-37)
newton/_src/geometry/types.py (3)
  • GeoType (25-36)
  • indices (172-173)
  • indices (176-178)
newton/_src/geometry/flags.py (1)
  • ShapeFlags (39-47)
newton/_src/sim/joints.py (3)
  • EqType (74-77)
  • JointMode (61-64)
  • JointType (29-36)
newton/_src/sim/collide.py (1)
  • count_rigid_contact_points (33-57)
newton/_src/utils/topology.py (1)
  • topological_sort (21-79)
newton/_src/solvers/flags.py (1)
  • SolverNotifyFlags (22-36)
newton/tests/test_import_usd.py (6)
newton/_src/geometry/utils.py (2)
  • create_box_mesh (498-560)
  • transform_points (563-566)
newton/_src/sim/joints.py (1)
  • JointType (29-36)
newton/_src/sim/builder.py (1)
  • shape_count (456-457)
newton/_src/geometry/flags.py (1)
  • ShapeFlags (39-47)
newton/_src/utils/import_usd.py (1)
  • parse_usd (34-1269)
newton/_src/geometry/types.py (1)
  • GeoType (25-36)
newton/_src/utils/import_mjcf.py (5)
newton/_src/core/spatial.py (2)
  • quat_between_axes (306-329)
  • quat_from_euler (191-224)
newton/_src/core/types.py (1)
  • Axis (64-121)
newton/_src/geometry/types.py (1)
  • Mesh (73-234)
newton/_src/sim/joints.py (1)
  • JointType (29-36)
newton/_src/sim/builder.py (1)
  • joint_type_str (1469-1484)
⏰ 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 (26)
newton/_src/utils/import_urdf.py (2)

27-30: Imports localized and aligned with new internal layout.

Relative imports for Axis/Transform/Mesh/ModelBuilder look correct and align with the refactor to _src. No issues.


221-221: Correctly pass mesh_maxhullvert into Mesh construction.

Both mesh creation paths now use the parameterized hull-vertex limit. Looks good.

Also applies to: 233-233

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

28-31: Imports updated to internal modules and enums.

Switch to relative imports for quat_between_axes, Axis/Transform, Mesh/ShapeFlags, JointMode, and ModelBuilder is consistent with the new public/private layout.


114-114: Deferred import to avoid circular dependency is appropriate.

Importing topological_sort here is a good placement and matches the lazy import pattern for heavy deps.

newton/tests/test_import_usd.py (2)

24-24: Using _src utilities in tests is acceptable in this project.

Per project conventions, tests may import internal _src helpers; this aligns with current practice.


50-50: Enums for joint types look correct.

Assertions use the new JointType enum and match expected articulation structure for the ant asset.

Also applies to: 78-78

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

26-30: Imports aligned with internal layout.

Switch to relative imports for core/geometry/sim is correct in the refactor context.


57-58: mesh_maxhullvert parameter is added and documented.

API and doc updates look good and are used in mesh paths below.

Also applies to: 87-88


151-153: Asset-level maxhullvert parsing is a good addition.

Respects per-mesh overrides while defaulting to the function parameter. Nice.


346-349: Thread maxhullvert through mesh geom creation.

maxhullvert from assets is correctly applied to Mesh instances in both scene and single-mesh branches. LGTM.

Also applies to: 354-366


467-468: JointType transitions are sensible and consistent with enum-based API.

Free/fixed detection, auto D6 fallback, and revolute/prismatic inference are clear and align with ModelBuilder expectations.

Also applies to: 491-496, 519-528, 590-607

newton/_src/solvers/mujoco/solver_mujoco.py (15)

26-41: LGTM! Clean import refactoring to use relative imports.

The conversion from absolute to relative imports aligns well with the PR's goal of establishing a clear private/public API boundary. The imports are properly structured and the new enum types are appropriately used.


59-59: LGTM! Class renamed for consistency.

The rename from MuJoCoSolver to SolverMuJoCo follows the new naming convention where all solvers start with Solver*.


380-380: LGTM! Comprehensive enum adoption for joint types.

The replacement of scalar constants with JointType enum values improves type safety and readability throughout the joint handling logic.

Also applies to: 411-411, 458-458, 491-491


567-567: LGTM! Consistent use of JointMode enum.

The adoption of JointMode enum for joint mode checks makes the code more maintainable and type-safe.

Also applies to: 949-949, 955-955, 2085-2085, 2090-2090, 2148-2148, 2153-2153


614-614: LGTM! Proper enum usage for special joint types.

The handling of DISTANCE joint type alongside FREE joint type is correctly maintained with the new enum system.

Also applies to: 631-631, 718-718, 732-732


692-692: LGTM! Clean joint type handling in FK evaluation.

The forward kinematics evaluation correctly uses the new JointType enum values.

Also applies to: 701-701, 710-710


1288-1292: LGTM! Proper use of SolverNotifyFlags.

The notification system correctly uses the new SolverNotifyFlags enum for flagging model changes.


1631-1631: Parameter rename aligns with Newton naming conventions.

The parameter rename from maxhullvert to mesh_maxhullvert is more descriptive and follows the Newton API conventions.


1800-1805: LGTM! Comprehensive enum mappings for geometry and joint types.

The mappings correctly translate between Newton's new enum types and MuJoCo's internal representations.

Also applies to: 1807-1822


1879-1879: LGTM! Proper use of ShapeFlags for collision detection.

The code correctly uses the new ShapeFlags enum to determine which shapes participate in collision detection.

Also applies to: 1903-1903, 1912-1912, 1916-1916, 1949-1949, 1954-1954


1890-1890: LGTM! Appropriate use of topological_sort from utils.

The function is correctly imported from the new utils path and used with the same parameters.


2035-2035: LGTM! Consistent use of enums throughout joint and equality constraint handling.

The conversion correctly handles all joint types and equality constraint types using the new enum system.

Also applies to: 2170-2170, 2180-2180, 2188-2188, 2196-2196


2285-2285: LGTM! Proper static method usage and flag combinations.

The static method calls are correctly updated to use the renamed class, and the flag system is properly utilized.

Also applies to: 2339-2342, 2352-2352


1076-1076: LGTM! Documentation and examples updated to reflect class rename.

All documentation, examples, and internal references have been consistently updated to use SolverMuJoCo instead of MuJoCoSolver.

Also applies to: 1087-1087, 1091-1091, 1095-1095, 1104-1104, 1305-1305, 1378-1378, 1423-1423


1926-1926: mesh-specific maxhullvert is already consistently handled

The Mesh class in newton/_src/geometry/types.py unconditionally sets self.maxhullvert (defaulting to MESH_MAXHULLVERT), and every importer (import_mjcf.py, import_urdf.py, import_usd.py) passes its mesh_maxhullvert into the Mesh constructor. The fallback in

maxhullvert = getattr(mesh_src, "maxhullvert", mesh_maxhullvert)

is therefore never triggered in normal use and is safe as a guard. No changes are required.

@nvlukasz nvlukasz force-pushed the public-api-refactor-v2 branch from ca6eff6 to 24f63b3 Compare August 12, 2025 23:39

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🔭 Outside diff range comments (15)
newton/examples/example_mpm_granular.py (1)

225-226: Update example to import load_mesh from its defining module
The public newton/utils.py does not re-export load_mesh, so calling newton.utils.load_mesh will fail. You should import it directly from the internal geometry utils where it’s defined.

• In newton/examples/example_mpm_granular.py (around line 225), replace:

    mesh_points, mesh_indices = newton.utils.load_mesh(collider)

with:

from newton._src.geometry.utils import load_mesh

    mesh_points, mesh_indices = load_mesh(collider)
newton/_src/solvers/style3d/kernels.py (1)

216-225: Avoid using not on bitwise expressions in Warp kernels; compare explicitly to zero

Using not on an int-typed bitwise result can be brittle in Warp’s type system and relies on implicit truthiness. Compare the mask against zero to produce a clear boolean.

Apply this diff:

-    if not particle_flags[tid] & ParticleFlags.ACTIVE:
+    # Explicit boolean check avoids relying on truthiness of an int in Warp kernels
+    if (particle_flags[tid] & ParticleFlags.ACTIVE) == 0:
         x_inertia[tid] = x_prev[tid]
         static_A_diags[tid] = 0.0
         dx[tid] = wp.vec3(0.0)
     else:
         v_prev = v_curr[tid]
         mass = particle_masses[tid]
         static_A_diags[tid] = pd_diags[tid] + mass / (dt * dt)
         x_inertia[tid] = x_last + v_prev * dt + (gravity + f_ext[tid] / mass) * (dt * dt)
         dx[tid] = v_prev * dt

Note: Keeping ParticleFlags as an IntEnum is fine; Warp handles IntEnum constants in kernels without explicit casts.

newton/_src/geometry/types.py (1)

64-67: Guard SDF.finalize/hash when volume is None

If SDF.volume is not provided (default None), both finalize() and __hash__() will raise AttributeError by accessing .id. Add a guard to fail fast in finalize() and a safe fallback in __hash__().

 def finalize(self) -> wp.uint64:
     """Returns the volume pointer of the SDF volume"""
-    return self.volume.id
+    if self.volume is None:
+        raise ValueError("SDF.volume is None; cannot finalize without a valid wp.Volume.")
+    return self.volume.id
@@
 @override
 def __hash__(self) -> int:
-    return hash(self.volume.id)
+    # Guard against uninitialized volume; fall back to object identity hash
+    if self.volume is None or not hasattr(self.volume, "id"):
+        return object.__hash__(self)
+    return hash(self.volume.id)

Also applies to: 68-71

newton/examples/example_anymal_c_walk_on_sand.py (1)

223-229: Recompute contacts each substep to avoid stale contact manifolds

Contacts are computed once before the substeps loop, which can produce stale contacts as the robot moves during the substeps. Recomputing per substep improves stability and collision accuracy. This also makes the CUDA graph capture reflect the intended contact update cadence.

-        self.contacts = self.model.collide(self.state_0, rigid_contact_margin=0.1)
-        for _ in range(self.sim_substeps):
+        for _ in range(self.sim_substeps):
+            self.contacts = self.model.collide(self.state_0, rigid_contact_margin=0.1)
             self.state_0.clear_forces()
             self.controller.assign_control(self.control, self.state_0)
             self.solver.step(self.state_0, self.state_1, self.control, self.contacts, self.sim_dt)
             self.state_0, self.state_1 = self.state_1, self.state_0
newton/_src/utils/recorder.py (3)

191-205: Warp array serialization drops empty arrays and loses dtype information.

Current logic:

  • Skips empty wp.array values (data loss).
  • Does not capture dtype (e.g., wp.vec3, wp.transform), so deserialization may reconstruct with incorrect types, breaking downstream kernels.

Serialize all warp arrays and include dtype; also handle basic containers to support nested warp arrays.

Apply this diff:

-        if isinstance(value, (int, float, bool, str, list, dict, set, tuple)):
-            return value
-        elif isinstance(value, np.ndarray):
-            return value
-        elif isinstance(value, wp.array):
-            if value.size > 0:
-                return {
-                    "__type__": "wp.array",
-                    "data": value.numpy(),
-                }
-        return None
+        # Primitives
+        if isinstance(value, (int, float, bool, str)):
+            return value
+        # Numpy arrays are pickleable
+        if isinstance(value, np.ndarray):
+            return value
+        # Warp arrays: always serialize (including empty) and include dtype hint
+        if isinstance(value, wp.array):
+            return {
+                "__type__": "wp.array",
+                "dtype": getattr(value.dtype, "__name__", str(value.dtype)),
+                "data": value.numpy(),
+            }
+        # Containers: recurse so nested warp arrays are serialized too
+        if isinstance(value, (list, tuple)):
+            return [self._serialize_value(v) for v in value]
+        if isinstance(value, dict):
+            return {k: self._serialize_value(v) for k, v in value.items()}
+        if isinstance(value, set):
+            return {"__type__": "set", "data": [self._serialize_value(v) for v in value]}
+        # Unsupported types
+        return None

220-228: Deserialization must restore warp dtype and handle nested containers.

Without restoring dtype, arrays like wp.transform may come back as plain numeric arrays. Also, nested containers serialized above must be walked recursively.

Apply this diff:

-        if isinstance(value, dict) and "__type__" in value:
-            type_name = value["__type__"]
-            obj_data = value["data"]
-
-            if type_name == "wp.array":
-                return wp.array(obj_data, device=device)
-        if isinstance(value, np.ndarray):
-            return value
-        return value
+        # Handle typed records
+        if isinstance(value, dict) and "__type__" in value:
+            type_name = value["__type__"]
+            if type_name == "wp.array":
+                obj_data = value["data"]
+                dtype_name = value.get("dtype")
+                wp_dtype = getattr(wp, dtype_name, None) if isinstance(dtype_name, str) else None
+                if wp_dtype is not None:
+                    return wp.array(obj_data, dtype=wp_dtype, device=device)
+                return wp.array(obj_data, device=device)
+            if type_name == "set":
+                return set(self._deserialize_and_restore_value(v, device) for v in value.get("data", []))
+        # Recurse containers
+        if isinstance(value, list):
+            return [self._deserialize_and_restore_value(v, device) for v in value]
+        if isinstance(value, dict):
+            return {k: self._deserialize_and_restore_value(v, device) for k, v in value.items()}
+        # Numpy arrays are already fine
+        if isinstance(value, np.ndarray):
+            return value
+        # Primitives and everything else
+        return value

230-242: Preserve warp array dtype in state recording and playback

The current record() (lines 230–242) only saves value.numpy(), and playback() (lines 253–271) always calls

wp.array(value_np, device=device)

without passing the original dtype. This drops structured types like wp.transform, wp.vec3, and wp.spatial_vector, which will break kernels expecting those dtypes.

Please update newton/_src/utils/recorder.py so that:

record() captures both the raw data and the array’s dtype.
playback() checks for this metadata and reconstructs via wp.array(data, dtype=dtype, device=device) when present.

Locations to change:

  • record(): around line 230
  • playback(): around line 253

Example diff:

--- a/newton/_src/utils/recorder.py
+++ b/newton/_src/utils/recorder.py
@@   def record(self, state: State):
-        for name, value in state.__dict__.items():
-            if isinstance(value, wp.array):
-                state_data[name] = value.numpy()
+        for name, value in state.__dict__.items():
+            if isinstance(value, wp.array):
+                state_data[name] = {
+                    "__type__": "wp.array",
+                    "data": value.numpy(),
+                    "dtype": value.dtype.name
+                }
@@   def playback(self, state: State, frame_id: int):
-        for name, value_np in state_data.items():
-            if hasattr(state, name):
-                value_wp = wp.array(value_np, device=device)
-                setattr(state, name, value_wp)
+        for name, entry in state_data.items():
+            if not hasattr(state, name):
+                continue
+            if isinstance(entry, dict) and entry.get("__type__") == "wp.array":
+                dtype = getattr(wp, entry["dtype"], None)
+                value_wp = wp.array(entry["data"], dtype=dtype, device=device)
+            else:
+                value_wp = wp.array(entry, device=device)
+            setattr(state, name, value_wp)
newton/_src/utils/import_usd.py (3)

420-444: Use integer dtype for mesh indices and carry mesh_maxhullvert for consistency

Face indices should be integers to avoid precision pitfalls; also consider passing maxhullvert for visual meshes for consistency (harmless even if unused).

Apply this diff:

-                indices = np.array(mesh.GetFaceVertexIndicesAttr().Get(), dtype=np.float32)
+                indices = np.array(mesh.GetFaceVertexIndicesAttr().Get(), dtype=np.int32)
@@
-                m = Mesh(points, np.array(faces, dtype=np.int32).flatten())
+                m = Mesh(points, np.array(faces, dtype=np.int32).flatten(), maxhullvert=mesh_maxhullvert)

131-139: Fix mapping key case for approximation flag

approximation.lower() is used later, but the dict contains "meshSimplification" mixed-case key; it won’t match. Use lowercase key.

Apply this diff:

-    approximation_to_remeshing_method = {
+    approximation_to_remeshing_method = {
         "convexdecomposition": "coacd",
         "convexhull": "convex_hull",
         "boundingsphere": "bounding_sphere",
         "boundingcube": "bounding_box",
-        "meshSimplification": "quadratic",
+        "meshsimplification": "quadratic",
     }

Also applies to: 1072-1085


1048-1067: Use integer dtype for mesh indices when building collision meshes

Same as for visual meshes: face indices should be int, not float.

Apply this diff:

-                    indices = np.array(mesh.GetFaceVertexIndicesAttr().Get(), dtype=np.float32)
+                    indices = np.array(mesh.GetFaceVertexIndicesAttr().Get(), dtype=np.int32)
newton/examples/example_selection_articulations.py (2)

205-211: Guard CUDA graph capture with mempool and non-MuJoCo solvers

The simulate() call in this example may allocate (e.g., through collision clones), so we should only enable CUDA graph capture when a mempool is available and we’re not using the pure MuJoCo backend. Update the assignment in newton/examples/example_selection_articulations.py (around lines 205–206):

• File: newton/examples/example_selection_articulations.py
Location: inside the method where self.use_cuda_graph is set

-        self.use_cuda_graph = wp.get_device().is_cuda and use_cuda_graph
+        dev = wp.get_device()
+        self.use_cuda_graph = (
+            dev.is_cuda
+            and wp.is_mempool_enabled(dev)
+            and not getattr(self.solver, "use_mujoco", False)
+            and use_cuda_graph
+        )

Please verify that the example still runs correctly on both CPU and CUDA targets, with and without MuJoCo enabled.


117-125: Prefer passing the Axis enum (or omit) for RendererOpenGL.up_axis
RendererOpenGL.init is declared as

def __init__(…, up_axis: AxisType | None = None, …)

and internally calls Axis.from_any(up_axis). Converting your enum to a string at the call site is redundant and undermines static type checks.

• Location to update:

  • newton/examples/example_selection_articulations.py: around lines 117–125

Apply one of the following diffs:

Option 1 – Pass the enum directly

-            self.renderer = newton.viewer.RendererOpenGL(
-                …,
-                up_axis=str(up_axis),
+            self.renderer = newton.viewer.RendererOpenGL(
+                …,
+                up_axis=up_axis,
                …,
            )

Option 2 – Omit the argument entirely (defaults to model’s up axis)

-                up_axis=str(up_axis),
+                # up_axis omitted; defaults to model’s up axis
docs/migration.rst (3)

119-134: Incorrect claim: add_joint_*(..., target=...) → action=...

In code, the add_joint_* APIs still use the argument name target (e.g., add_joint_revolute(..., target=...)). There is no action parameter in the public API.

Please update the table to keep target as the correct parameter name to avoid breaking user migrations.


56-62: Remove incorrect Model.joint_axis removal note

The migration guide claims that Model.joint_axis was removed, but the code still populates and uses this attribute throughout (e.g. in builder.finalize, render utilities, IK routines, articulations, and all solvers). Please update the docs or the implementation:

  • In docs/migration.rst (around line 61), remove or revise the sentence:
    “The Model.joint_axis attribute has been removed since it now equals :attr:newton.Model.joint_qd_start.”

If the intent is to keep joint_axis, document its continued presence or aliasing behavior. If you intend to drop it, ensure the code no longer assigns or uses model.joint_axis.


79-91: Update migration docs for eval_ik signature

The documentation currently shows an optional mask=None for eval_ik, but the implementation does not accept a mask. Please remove the mask from the Newton column in the migration table (or implement mask support in eval_ik).

• File: docs/migration.rst
• Section: “Forward and Inverse Kinematics” table

Suggested change:

--- a/docs/migration.rst
+++ b/docs/migration.rst
@@
-| ``eval_ik(model, state, joint_q, joint_qd)``           | ``eval_ik(model, state, joint_q, joint_qd, mask=None)``                |
+| ``eval_ik(model, state, joint_q, joint_qd)``           | ``eval_ik(model, state, joint_q, joint_qd)``                           |
♻️ Duplicate comments (5)
newton/examples/example_anymal_c_walk_on_sand.py (1)

34-34: Renderer public-API migration looks correct

Switching to newton.viewer.RendererOpenGL aligns with the refactor goals to avoid utils exposure for rendering.

Resolved the prior note about utils/renderer import paths. The explicit newton.utils import is present (Line 33), and rendering moved to newton.viewer as intended.

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

361-367: Correctness note: passing strings to quat_between_axes is valid

The call quat_between_axes(Axis.Z, axis_str) is correct; quat_between_axes accepts AxisType and internally converts strings via Axis.from_any().

newton/tests/test_ik.py (1)

25-27: Private _src import in tests acknowledged; acceptable per project conventions

Importing _eval_fk_batched from newton._src.sim.ik is fine for tests in this repo (as per maintainers’ guidance). The public renames (newton.ik module and IK*Objective classes) are correctly adopted.

newton/examples/example_selection_articulations.py (1)

284-286: Humanoid FK on reset added — thank you

Including self.hums.eval_fk(...) fixes stale transforms for humanoids when not using MuJoCo. Matches the prior review request.

newton/examples/example_robot_manipulating_cloth.py (1)

37-39: Import transform_twist from the public API (newton.core), not utils

transform_twist is re-exported publicly from newton.core; using newton.utils ties the example to an internal-ish path. This was discussed earlier and addressed; aligning the example keeps it purely on public APIs.

-from newton.utils import transform_twist
+from newton.core import transform_twist
🧹 Nitpick comments (38)
newton/examples/example_mpm_granular.py (1)

73-73: Minor: pass fps and up_axis explicitly to RendererOpenGL for clarity.

RendererOpenGL supports fps and up_axis; passing them avoids relying on defaults and keeps the example consistent with the model’s up axis and CLI arg.

Apply this diff:

-            self.renderer = newton.viewer.RendererOpenGL(self.model, "MPM Granular")
+            self.renderer = newton.viewer.RendererOpenGL(
+                self.model, "MPM Granular", fps=int(options.fps), up_axis=newton.Axis.Y
+            )
newton/examples/example_cloth_hanging.py (2)

132-141: Optional: simplify solver selection via a small dispatch table.

Removes branching and centralizes per-solver constructor differences.

-        if self.solver_type == SolverType.EULER:
-            self.solver = newton.solvers.SolverSemiImplicit(model=self.model)
-        elif self.solver_type == SolverType.XPBD:
-            self.solver = newton.solvers.SolverXPBD(
-                model=self.model,
-                iterations=self.iterations,
-            )
-        else:  # self.solver_type == SolverType.VBD
-            self.solver = newton.solvers.SolverVBD(model=self.model, iterations=self.iterations)
+        solver_factories = {
+            SolverType.EULER: lambda: newton.solvers.SolverSemiImplicit(model=self.model),
+            SolverType.XPBD: lambda: newton.solvers.SolverXPBD(model=self.model, iterations=self.iterations),
+            SolverType.VBD: lambda: newton.solvers.SolverVBD(model=self.model, iterations=self.iterations),
+        }
+        self.solver = solver_factories[self.solver_type]()

145-151: Optional: guard renderer creation for headless environments.

In CI or servers without an OpenGL context, consider a try/except around RendererOpenGL and fall back to no viewer with a warning to avoid example crashes.

newton/_src/solvers/style3d/kernels.py (1)

19-19: Prefer explicit import path for private symbol to reduce coupling on re-exports

ParticleFlags is defined in geometry/flags.py. Importing from the package root assumes init.py re-exports it, which can break silently if that re-export changes. Import directly from the defining module.

Apply this diff:

-from ...geometry import ParticleFlags
+from ...geometry.flags import ParticleFlags
newton/_src/geometry/types.py (2)

127-128: Normalize vertex dtype to float32 in init for consistency with Warp and hashing

vertices are set without an explicit dtype here but are coerced to float32 in the setter, and Warp typically expects float32. Normalizing upfront avoids subtle dtype-dependent behavior and ensures stable hashes.

-        self._vertices = np.array(vertices).reshape(-1, 3)
+        self._vertices = np.array(vertices, dtype=np.float32).reshape(-1, 3)

224-235: Reduce hashing overhead and ensure dtype-stable hashing for Mesh

Converting large arrays to Python tuples is slow and memory-heavy. Hashing bytes of normalized dtypes is faster and produces stable results across runs and platforms (given consistent endianness and dtype).

-        if self._cached_hash is None:
-            self._cached_hash = hash(
-                (tuple(np.array(self.vertices).flatten()), tuple(np.array(self.indices).flatten()), self.is_solid)
-            )
+        if self._cached_hash is None:
+            verts = np.asarray(self.vertices, dtype=np.float32)
+            inds = np.asarray(self.indices, dtype=np.int32)
+            self._cached_hash = hash((verts.tobytes(), inds.tobytes(), bool(self.is_solid)))

If is_solid can change after construction, consider clearing _cached_hash when it changes (optional):

# Add to class Mesh (optional)
@property
def is_solid(self) -> bool:
    return getattr(self, "_is_solid", False)

@is_solid.setter
def is_solid(self, value: bool):
    self._is_solid = bool(value)
    self._cached_hash = None
newton/examples/example_anymal_c_walk_on_sand.py (3)

183-183: Be consistent with solver imports to avoid relying on package attribute side-effects

You import SolverImplicitMPM directly but access SolverFeatherstone via newton.solvers.SolverFeatherstone. Prefer importing both explicitly for clarity and to help static analysis.

Apply:

-from newton.solvers import SolverImplicitMPM
+from newton.solvers import SolverImplicitMPM, SolverFeatherstone
...
-        self.solver = newton.solvers.SolverFeatherstone(self.model)
+        self.solver = SolverFeatherstone(self.model)

196-198: Consider setting collider friction to match the sand friction

MPM’s setup_collider supports per-collider friction. Using the sand_friction value for the shank colliders can improve robot-sand interaction fidelity.

-        self.mpm_solver = SolverImplicitMPM(self.model, options)
-        self.mpm_solver.setup_collider(self.model, [self.collider_mesh])
+        self.mpm_solver = SolverImplicitMPM(self.model, options)
+        self.mpm_solver.setup_collider(self.model, [self.collider_mesh], collider_friction=[sand_friction])

199-199: Optional: pass fps to the renderer to reflect simulation rate

RendererOpenGL accepts fps; setting it to match your 60 Hz sim can make playback pacing more predictable.

-        self.renderer = None if headless else newton.viewer.RendererOpenGL(self.model, stage_path)
+        self.renderer = None if headless else newton.viewer.RendererOpenGL(self.model, stage_path, fps=fps)
newton/_src/utils/recorder.py (5)

16-24: Guard type-only imports to avoid circular-imports at runtime.

Model and State are only used in annotations. You can avoid importing ..sim at runtime (and eliminate potential import cycles) by gating the import behind TYPE_CHECKING.

Apply this diff:

 from __future__ import annotations

 import pickle
+import warnings

 import numpy as np
 import warp as wp

-from ..sim import Model, State
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from ..sim import Model, State

62-68: Use warnings.warn instead of print and keep return types predictable.

Using warnings.warn is preferable for library code. Also, consider consistently returning an empty list instead of None for point clouds to simplify call-sites.

Apply this diff:

-        if not (0 <= frame_id < len(self.transforms_history)):
-            print(f"Warning: frame_id {frame_id} is out of bounds. Playback skipped.")
-            return None, None
+        if not (0 <= frame_id < len(self.transforms_history)):
+            warnings.warn(f"frame_id {frame_id} is out of bounds. Playback skipped.", RuntimeWarning)
+            return None, None

Optional (behavioral change; consider for DX consistency):

-        point_clouds = self.point_clouds_history[frame_id] if frame_id < len(self.point_clouds_history) else None
+        point_clouds = self.point_clouds_history[frame_id] if frame_id < len(self.point_clouds_history) else []

77-83: Add a simple version tag to NPZ for forward compatibility.

Embedding a small metadata version helps with future format changes.

Apply this diff:

-        np.savez_compressed(file_path, **history_np)
+        history_np["__format_version__"] = 1
+        np.savez_compressed(file_path, **history_np)

265-271: Device detection never raises ValueError; handle None explicitly and warn.

_get_device_from_state returns None when no wp.array is found; the try/except ValueError is dead code. Make the behavior explicit and warn via warnings.

Apply this diff:

-        try:
-            device = self._get_device_from_state(state)
-        except ValueError:
-            print("Warning: Unable to determine device from state. Playback skipped.")
-            return
+        device = self._get_device_from_state(state)
+        if device is None:
+            warnings.warn("Unable to determine device from state. Playback skipped.", RuntimeWarning)
+            return

292-301: Use highest pickle protocol and document security in code.

You already warn in the docstring. Also set protocol=pickle.HIGHEST_PROTOCOL for efficiency and to be explicit.

Apply this diff:

-        with open(file_path, "wb") as f:
-            data_to_save = {"model": self.model_data, "states": self.history}
-            pickle.dump(data_to_save, f)
+        with open(file_path, "wb") as f:
+            data_to_save = {"model": self.model_data, "states": self.history}
+            pickle.dump(data_to_save, f, protocol=pickle.HIGHEST_PROTOCOL)
newton/_src/sim/joints.py (2)

39-52: Improve type hints and comparisons in get_joint_dof_count

Use the enum type in the signature and simplify comparisons with membership.

Apply this diff:

-def get_joint_dof_count(joint_type: int, num_axes: int) -> tuple[int, int]:
+def get_joint_dof_count(joint_type: JointType | int, num_axes: int) -> tuple[int, int]:
@@
-    if joint_type == JointType.BALL:
+    if joint_type == JointType.BALL:
@@
-    elif joint_type == JointType.FREE or joint_type == JointType.DISTANCE:
+    elif joint_type in (JointType.FREE, JointType.DISTANCE):

18-26: Remove legacy constant comments to reduce noise

The commented legacy JOINT_* and JOINT_MODE_/EQ_ constants add clutter and can confuse readers. They’re superseded by the enums.

Apply this diff:

-# # Types of joints linking rigid bodies
-# JointType.PRISMATIC = wp.constant(0)
-# JointType.REVOLUTE = wp.constant(1)
-# JointType.BALL = wp.constant(2)
-# JointType.FIXED = wp.constant(3)
-# JointType.FREE = wp.constant(4)
-# JointType.DISTANCE = wp.constant(5)
-# JointType.D6 = wp.constant(6)
@@
-# # Joint axis control mode types
-# JointMode.NONE = wp.constant(0)
-# JointMode.TARGET_POSITION = wp.constant(1)
-# JointMode.TARGET_VELOCITY = wp.constant(2)
@@
-# # (temporary) equality constraint types
-# EqType.CONNECT = wp.constant(0)
-# EqType.WELD = wp.constant(1)
-# EqType.JOINT = wp.constant(2)

Also applies to: 55-59, 67-71

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

34-54: Docstring drift: update parameters and remove stale entry

The docstring still mentions default_density and lacks the new mesh_maxhullvert parameter detail.

  • Remove default_density from Args.
  • Add a brief explanation for mesh_maxhullvert in Args (you already added a short one-line at Line 80; keep it and remove default_density).

Also applies to: 80-81

newton/_src/solvers/mujoco/solver_mujoco.py (3)

1800-1805: Unsupported JointType.DISTANCE in MuJoCo conversion

Distance joints are not included in supported_joint_types and will trigger NotImplementedError below (Line 2170) if present. Given parse_usd can add DistanceJoint, this conversion path will fail at runtime for such models.

  • If intended, document this limitation in the SolverMuJoCo docstring.
  • Otherwise, consider mapping Distance to an equivalent constraint in MuJoCo (e.g., equality constraint) during conversion.

Would you like a follow-up patch mapping Distance joints to weld/connect equality constraints for parity?


1878-1883: Boolean mask: make the intention explicit

The current expression relies on non-zero ints being truthy. Make it explicit for readability and to avoid dtype gotchas.

Apply this diff:

-            selected_shapes = np.where(shape_flags & ShapeFlags.COLLIDE_SHAPES)[0]
+            selected_shapes = np.where((shape_flags & ShapeFlags.COLLIDE_SHAPES) != 0)[0]

1902-1916: Consistent collision-flag checks

Same readability point: make the bit check explicit in both places.

Apply this diff:

-                if skip_visual_only_geoms and not (shape_flags[shape] & ShapeFlags.COLLIDE_SHAPES):
+                if skip_visual_only_geoms and (shape_flags[shape] & ShapeFlags.COLLIDE_SHAPES) == 0:
                     continue
docs/api/newton_solvers.rst (1)

12-18: Docs list updated to new names — minor ordering nit

The renamed solver classes are correctly listed. For readability, consider listing SolverBase first, then the concrete solvers:

-   SolverFeatherstone
-   SolverMuJoCo
-   SolverSemiImplicit
-   SolverBase
-   SolverStyle3D
-   SolverVBD
-   SolverXPBD
+   SolverBase
+   SolverFeatherstone
+   SolverMuJoCo
+   SolverSemiImplicit
+   SolverStyle3D
+   SolverVBD
+   SolverXPBD
newton/examples/example_cloth_style3d.py (1)

116-117: Set device explicitly when reassigning particle_flags

Without specifying device, wp.array(flags) may go to the default device, which can differ from the model’s device in multi-device contexts. Use the model’s device for safety.

Apply this diff:

-        self.model.particle_flags = wp.array(flags)
+        self.model.particle_flags = wp.array(flags, device=self.model.device)
newton/examples/example_cloth_self_contact.py (2)

183-188: Set device explicitly for particle_flags reassignment; also consider dtype safety

To avoid device mismatches, pass device=self.model.device. Optionally cast the mask to the array’s dtype when clearing bits to avoid Python’s negative ~int behavior on different widths.

Apply this diff for device:

-            self.model.particle_flags = wp.array(flags)
+            self.model.particle_flags = wp.array(flags, device=self.model.device)

Outside this hunk (no diff): consider

import numpy as np
flags[fixed_vertex_id] = flags[fixed_vertex_id] & ~np.int32(ParticleFlags.ACTIVE)

183-188: Avoid hard-coded cloth_size when selecting edge vertices

The indices for left_side/right_side assume cloth_size = 50. If the asset changes, this will break. Compute cloth_size from mesh topology instead (e.g., infer grid size from vertex count) before clearing flags.

Would you like a helper to infer grid dimensions from the USD mesh to robustly build rot_point_indices?

newton/examples/example_anymal_c_walk_physx_policy.py (1)

145-147: Solver/renderer renames applied correctly; verify optional deps and consider a small perf tweak.

  • SolverMuJoCo and viewer.RendererOpenGL usages match the new API.
  • Request: ensure the example’s README/usage mentions the extras needed for MuJoCo/OpenGL on the target environment.
  • Optional perf: avoid host round-trips in compute_obs by converting Warp arrays to Torch via wp.to_torch once and slicing tensors, instead of wrapping slices with torch.tensor(...). This keeps data on device and reduces allocations.

Example pattern (outside this hunk, for compute_obs):

# cache once somewhere (e.g., in __init__)
self.joint_q_torch = wp.to_torch(self.state_0.joint_q)
self.joint_qd_torch = wp.to_torch(self.state_0.joint_qd)

# then in compute_obs, prefer slicing tensors you've already materialized on device
root_quat_w = self.joint_q_torch[3:7].unsqueeze(0)
root_lin_vel_w = self.joint_qd_torch[3:6].unsqueeze(0)
root_ang_vel_w = self.joint_qd_torch[:3].unsqueeze(0)
joint_pos_current = self.joint_q_torch[7:].unsqueeze(0)
joint_vel_current = self.joint_qd_torch[6:].unsqueeze(0)
newton/_src/sim/style3d/builder_style3d.py (1)

477-487: Clear ACTIVE flag without bitwise NOT on IntEnum to avoid negative intermediates.

Given ParticleFlags is an IntEnum (and only ACTIVE is used here), directly assigning 0 is simpler and avoids the -2 intermediate produced by ~ on IntEnum. It also clarifies intent.

-                particle_flag = ParticleFlags.ACTIVE
+                particle_flag = ParticleFlags.ACTIVE

                 if (
                     (x == 0 and fix_left)
                     or (x == dim_x and fix_right)
                     or (y == 0 and fix_bottom)
                     or (y == dim_y and fix_top)
                 ):
-                    particle_flag = particle_flag & ~ParticleFlags.ACTIVE
+                    particle_flag = 0  # deactivate
                     particle_mass = 0.0

Note: Storing ints in self.particle_flags is OK and consistent with the model’s int32 flag arrays.

newton/solvers.py (1)

28-38: Consider temporary deprecation aliases to smooth migration.

If you want to reduce breakage for downstream users, alias the old names to the new ones for one release cycle and optionally emit a DeprecationWarning. Example:

 __all__ = [
     "SolverBase",
     "SolverFeatherstone",
     "SolverImplicitMPM",
     "SolverMuJoCo",
     "SolverNotifyFlags",
     "SolverSemiImplicit",
     "SolverStyle3D",
     "SolverVBD",
     "SolverXPBD",
 ]
+
+# Optional backward-compat aliases (deprecated; remove in a future release)
+# from newton.solvers import MuJoCoSolver, SemiImplicitSolver, etc. will continue to work.
+MuJoCoSolver = SolverMuJoCo
+SemiImplicitSolver = SolverSemiImplicit
+VBDSolver = SolverVBD
+XPBDSolver = SolverXPBD
+FeatherstoneSolver = SolverFeatherstone
+Style3DSolver = SolverStyle3D
newton/tests/test_ik_fk_kernels.py (2)

31-37: Tighten type annotations and keep enum usage consistent.

Minor nit: JOINT_KINDS is a list of JointType values; annotate accordingly.

-JOINT_KINDS: list[int] = [
+JOINT_KINDS: list[JointType] = [
     JointType.REVOLUTE,
     JointType.PRISMATIC,
     JointType.BALL,
     JointType.D6,
     JointType.FREE,
 ]

170-201: Unify enum references (JointType vs newton.JointType).

Mixing styles can confuse readers. Prefer the imported JointType consistently.

-        elif jt == newton.JointType.D6:
+        elif jt == JointType.D6:
             # lin X,Y,Z
             q_np[q0 + 0 : q0 + 3] = rng.uniform(-0.1, 0.1, size=3)
             # rot XYZ
             q_np[q0 + 3 : q0 + 6] = rng.uniform(-np.pi / 8, np.pi / 8, size=3)

-        elif jt == newton.JointType.FREE:
+        elif jt == JointType.FREE:
             # translation
             q_np[q0 + 0 : q0 + 3] = rng.uniform(-0.3, 0.3, size=3)
             # quaternion
newton/_src/solvers/vbd/solver_vbd.py (1)

1215-1223: ParticleFlags IntEnum with int32 buffers and bit-tests — correct usage

  • particle_flags: wp.int32 + ParticleFlags.ACTIVE checks are consistent with the refactor.
  • Using IntEnum in Warp kernels is supported; the bitmask tests are fine. For readability, consider adding parentheses:

Apply this minor clarity tweak (optional):

-    if not particle_flags[particle] & ParticleFlags.ACTIVE:
+    if not (particle_flags[particle] & ParticleFlags.ACTIVE):

Repeat similarly for the other kernels’ ACTIVE checks.

Also applies to: 1237-1247, 1356-1361, 1491-1514, 1984-2009, 2104-2130

newton/tests/test_joint_controllers.py (3)

59-61: Remove no-op Model.ground assignment

The Model.ground attribute was removed in the API refactor. Setting it here does nothing and may mislead readers.

-    model.ground = False

84-86: IK fallback guard looks correct; keep an eye on future solver additions

Skipping eval_ik for reduced-coordinate solvers (SolverMuJoCo, SolverFeatherstone) is appropriate. If new reduced-coordinate solvers are added, extend this tuple accordingly.


98-103: Re-enable Featherstone and SemiImplicit test entries when ready

Once SolverFeatherstone/SolverSemiImplicit are fixed per TODOs, please re-enable their entries here to keep parity across solvers.

Would you like me to open a tracking issue and prepare parametrized tests for those solvers?

newton/examples/example_robot_manipulating_cloth.py (1)

276-276: Featherstone mass matrix update interval tied to substeps

Using update_mass_matrix_interval=self.num_substeps is fine, but it can under/over-update depending on contact/nonlinearities. Consider making this configurable via a constructor arg or CLI flag if users need tuning.

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

149-156: ShapeConfig.flags getter/setter: ensure consistent int typing

Bitwise ops with IntEnum yield ints. You already normalize at finalize time (wp.int32), so this is fine. For clarity, you could explicitly cast to int in getter/setter to avoid mixed IntEnum/int values in self.shape_flags.

-            shape_flags = ShapeFlags.VISIBLE if self.is_visible else 0
+            shape_flags = int(ShapeFlags.VISIBLE) if self.is_visible else 0
-            shape_flags |= ShapeFlags.COLLIDE_SHAPES if self.has_shape_collision else 0
+            shape_flags |= int(ShapeFlags.COLLIDE_SHAPES) if self.has_shape_collision else 0
-            shape_flags |= ShapeFlags.COLLIDE_PARTICLES if self.has_particle_collision else 0
+            shape_flags |= int(ShapeFlags.COLLIDE_PARTICLES) if self.has_particle_collision else 0
             return shape_flags

Also applies to: 162-165


879-883: Quaternion normalization for multi-DOF joints

Setting the last coord of q to 1.0 for FREE/DISTANCE/BALL establishes a valid quaternion. Consider normalizing the full quat in case prior assignments modify other components.

-            self.joint_q[-1] = 1.0
+            # ensure a valid unit quaternion
+            qi = self.joint_q_start[-1]
+            qx, qy, qz, qw = 0.0, 0.0, 0.0, 1.0
+            self.joint_q[qi + (coord_count - 4) : qi + coord_count] = [qx, qy, qz, qw]

Note: coord_count is available above (computed from get_joint_dof_count). If it’s not in scope here, compute indices accordingly.


3051-3064: Bit-twiddling ParticleFlags with IntEnum works but returns int

particle_flag & ~ParticleFlags.ACTIVE yields int. Since finalize() normalizes flags, this is OK. If you want to keep internal lists uniform, consider wrapping in int(...) explicitly.

docs/migration.rst (1)

98-99: Control: recommend clarifying JointMode.NONE as the way to disable targets

The guidance is correct. Consider adding a short code snippet to show setting JointMode.NONE via ModelBuilder.JointDofConfig for discoverability.

Comment thread newton/_src/solvers/mujoco/solver_mujoco.py
Comment thread newton/examples/example_mpm_granular.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🔭 Outside diff range comments (1)
newton/_src/solvers/solver.py (1)

257-265: Docstring claims a default for control that the signature does not provide

Signature requires a Control, but docs say it defaults to None. Align docs with the actual signature (least invasive), or update the signature to accept Optional[Control] with a default.

Apply this doc-only diff for consistency:

-            control (Control): The control input.
-                Defaults to `None` which means the control values from the
-                :class:`Model` are used.
+            control (Control): The control input.

If you prefer the Optional[Control] pattern, update the signature to control: Control | None = None and propagate to derived solvers.

♻️ Duplicate comments (1)
newton/_src/solvers/solver.py (1)

49-49: Fix gravity gating in integrate_particles (gravity currently disabled for movable particles)

wp.step(-inv_mass) returns 0 for positive inv_mass, unintentionally skipping gravity on all dynamic particles. Mirror the rigid-body path and use wp.nonzero(inv_mass) so gravity applies only when inv_mass != 0.

Apply this diff:

-    v1 = v0 + (f0 * inv_mass + gravity * wp.step(-inv_mass)) * dt
+    v1 = v0 + (f0 * inv_mass + gravity * wp.nonzero(inv_mass)) * dt

Additionally, consider adding a unit test to guard this behavior across both particle and rigid-body integrators.

🧹 Nitpick comments (2)
newton/_src/solvers/solver.py (2)

268-273: Doc now references SolverNotifyFlags (good); consider tightening the type hint

The doc correctly points to SolverNotifyFlags. Optionally, make the type hint SolverNotifyFlags (IntFlag) for clarity while still accepting ints at call sites if needed.

Example (optional):

  • Keep signature as-is to avoid ripple effects, but add a type comment or note in the docstring that flags is an IntFlag bitmask (SolverNotifyFlags).

279-287: Nit: table header label

The header “Constant” describes IntFlag entries. Consider “Flag” to better reflect semantics.

-        Constant                                        Description
+        Flag                                            Description
📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 24f63b3 and 71d5a5e.

⛔ Files ignored due to path filters (102)
  • docs/api/_generated/newton.Axis.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.AxisType.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.Contacts.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.Control.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.EqType.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.GeoType.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.JointMode.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.JointType.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.Mesh.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.Model.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.ModelBuilder.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.ParticleFlags.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.SDF.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.ShapeFlags.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.State.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.Style3DModel.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.Style3DModelBuilder.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.__version__.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.core.AxisType.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.core.quat_between_axes.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.core.quat_decompose.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.core.quat_from_euler.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.core.quat_to_euler.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.core.quat_to_rpy.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.core.quat_twist.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.core.quat_twist_angle.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.core.transform_twist.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.core.transform_wrench.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.core.velocity_at_point.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.eval_fk.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.eval_ik.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.geometry.build_ccd_generic.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.geometry.compute_shape_inertia.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.geometry.compute_shape_radius.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.geometry.create_box.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.geometry.create_capsule.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.geometry.create_cone.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.geometry.create_cylinder.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.geometry.create_none.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.geometry.create_plane.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.geometry.create_sphere.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.geometry.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.geometry.transform_inertia.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.ik.IKJointLimitObjective.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.ik.IKObjective.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.ik.IKPositionObjective.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.ik.IKRotationObjective.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.ik.IKSolver.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.ik.JacobianMode.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.ik.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.selection.ArticulationView.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.selection.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.sensors.ContactSensor.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.sensors.populate_contacts.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.sim.ModelBuilder.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.sim.Style3DModel.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.sim.Style3DModelBuilder.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.sim.color_graph.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.sim.count_rigid_contact_points.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.sim.eval_fk.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.sim.eval_ik.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.sim.get_joint_dof_count.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.sim.plot_graph.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.solvers.SolverFeatherstone.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.solvers.SolverImplicitMPM.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.solvers.SolverMuJoCo.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.solvers.SolverNotifyFlags.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.solvers.SolverSemiImplicit.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.solvers.SolverStyle3D.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.solvers.SolverVBD.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.solvers.SolverXPBD.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.solvers.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.BasicRecorder.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.ModelAndStateRecorder.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.SimRenderer.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.boltzmann.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.check_conditional_graph_support.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.clear_git_cache.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.leaky_max.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.leaky_min.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.quat_between_axes.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.quat_decompose.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.quat_from_euler.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.quat_to_euler.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.quat_to_rpy.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.quat_twist.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.quat_twist_angle.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.smooth_max.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.topological_sort.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.transform_twist.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.transform_wrench.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.vec_abs.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.vec_leaky_min.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.vec_max.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.vec_min.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.velocity_at_point.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.viewer.GizmoSystem.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.viewer.RecorderImGuiManager.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.viewer.RendererOpenGL.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.viewer.RendererUsd.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.viewer.rst is excluded by !**/_generated/**
📒 Files selected for processing (13)
  • docs/api/newton.rst (1 hunks)
  • docs/api/newton_core.rst (0 hunks)
  • docs/api/newton_geometry.rst (0 hunks)
  • docs/api/newton_ik.rst (1 hunks)
  • docs/api/newton_selection.rst (1 hunks)
  • docs/api/newton_sensors.rst (1 hunks)
  • docs/api/newton_sim.rst (0 hunks)
  • docs/api/newton_solvers.rst (1 hunks)
  • docs/api/newton_utils.rst (1 hunks)
  • docs/api/newton_viewer.rst (1 hunks)
  • docs/generate_api.py (3 hunks)
  • docs/index.rst (1 hunks)
  • newton/_src/solvers/solver.py (5 hunks)
💤 Files with no reviewable changes (3)
  • docs/api/newton_geometry.rst
  • docs/api/newton_core.rst
  • docs/api/newton_sim.rst
✅ Files skipped from review due to trivial changes (5)
  • docs/api/newton_ik.rst
  • docs/api/newton_viewer.rst
  • docs/api/newton_sensors.rst
  • docs/api/newton.rst
  • docs/api/newton_selection.rst
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: shi-eric
PR: newton-physics/newton#521
File: newton/examples/example_cloth_hanging.py:36-36
Timestamp: 2025-08-12T05:17:34.423Z
Learning: The Newton migration guide (docs/migration.rst) is specifically for documenting how to migrate existing warp.sim functionality to Newton equivalents. New Newton-only features that didn't exist in warp.sim do not need migration documentation.
📚 Learning: 2025-08-12T05:17:34.423Z
Learnt from: shi-eric
PR: newton-physics/newton#521
File: newton/examples/example_cloth_hanging.py:36-36
Timestamp: 2025-08-12T05:17:34.423Z
Learning: The Newton migration guide (docs/migration.rst) is specifically for documenting how to migrate existing warp.sim functionality to Newton equivalents. New Newton-only features that didn't exist in warp.sim do not need migration documentation.

Applied to files:

  • docs/index.rst
  • docs/api/newton_utils.rst
📚 Learning: 2025-08-12T17:51:37.474Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/geometry.py:16-22
Timestamp: 2025-08-12T17:51:37.474Z
Learning: In the Newton public API refactor, common geometry symbols like ParticleFlags and ShapeFlags are now exposed at the top level in newton/__init__.py rather than through newton.geometry. The newton/geometry.py module is intentionally focused only on broad-phase collision detection classes (BroadPhaseAllPairs, BroadPhaseExplicit, BroadPhaseSAP).

Applied to files:

  • docs/index.rst
  • newton/_src/solvers/solver.py
📚 Learning: 2025-08-12T05:17:34.423Z
Learnt from: shi-eric
PR: newton-physics/newton#521
File: newton/examples/example_cloth_hanging.py:36-36
Timestamp: 2025-08-12T05:17:34.423Z
Learning: The STYLE3D solver in Newton is a new solver that did not exist in warp.sim, so it does not require any migration documentation.

Applied to files:

  • docs/api/newton_solvers.rst
🧬 Code Graph Analysis (1)
newton/_src/solvers/solver.py (3)
newton/_src/geometry/flags.py (1)
  • ParticleFlags (20-22)
newton/_src/sim/contacts.py (1)
  • Contacts (23-84)
newton/_src/sim/state.py (1)
  • State (21-109)
⏰ 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 (10)
newton/_src/solvers/solver.py (3)

18-19: Imports refactor looks correct and layered properly

Using internal facades for ParticleFlags and sim types avoids leaking public facades into _src. Good separation.


28-28: Int32 dtype migration for particle_flags is correct

Kernel now expects wp.int32 which aligns with IntEnum/IntFlag usage and past dtype migration. LGTM.


38-41: Active-bit gating is correct and consistent

Switch to ParticleFlags.ACTIVE reads cleanly. The early-out behavior and zeroing v_new for inactive particles is consistent with prior semantics.

docs/index.rst (2)

32-32: API toctree now points to api/newton (public facade) — good alignment

Matches the refactor to expose the top-level public API.


34-37: New API pages are correctly wired (ik, selection, sensors, viewer)

Toctree entries align with the new public submodules. Looks consistent with generate_api.py MODULES.

Also applies to: 39-39

docs/api/newton_solvers.rst (1)

13-20: Renamed solver classes and SolverNotifyFlags are correctly listed

The autosummary reflects the new Solver* naming and adds SolverNotifyFlags. This aligns with the public API refactor.

docs/api/newton_utils.rst (2)

12-14: Recorder classes replacement is reflected correctly

BasicRecorder and ModelAndStateRecorder entries match the utils refactor.


21-30: Quaternion/transform utilities are properly surfaced

The new utility functions are listed and will be discoverable in the generated docs.

docs/generate_api.py (2)

23-23: shutil import addition is fine

Required for cleanup; no issues.


38-47: Expanded MODULES set matches the new public surface

Adding newton, geometry, ik, selection, sensors, solvers, utils, viewer is consistent with the refactor. Good.

Comment thread docs/generate_api.py
@kaloca

kaloca commented Aug 13, 2025

Copy link
Copy Markdown
Member
image

From your point in the meeting today about the Solver-X pattern, doesn't the ".solvers" submodule already list out all the options for solvers? Typing "Solver" again doesn't seem to clarify the autocomplete any further.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
docs/generate_api.py (1)

213-214: Resolved: guarded removal of OUTPUT_DIR avoids FileNotFoundError

This addresses the earlier review. The conditional rmtree prevents failures on a clean repo.

newton/utils.py (1)

36-54: OK to import parse_usd at module level

Confirmed in earlier discussion that pxr/USD is lazily imported within functions in import_usd.py. Re-exporting parse_usd here is safe and won’t break users without USD installed unless they call the function.

🧹 Nitpick comments (2)
docs/generate_api.py (1)

213-214: Nit: prefer pathlib API over os.path for consistency

Since OUTPUT_DIR is a Path, use its methods directly.

Apply this diff:

-    if os.path.isdir(OUTPUT_DIR):
+    if OUTPUT_DIR.is_dir():
         shutil.rmtree(OUTPUT_DIR)
newton/utils.py (1)

1-15: Add a short module docstring for API docs and readability

A concise docstring helps Sphinx and users quickly grasp the purpose of this façade.

Apply this diff:

 # limitations under the License.
 
+"""Public utilities façade.
+
+Re-exports spatial math helpers, graph utilities, asset importers, and recorders
+from internal modules under a stable public namespace.
+"""
+
 # TODO: move these to Warp?
📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 71d5a5e and c471fff.

⛔ Files ignored due to path filters (7)
  • docs/api/_generated/newton.CollisionPipeline.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.count_rigid_contact_points.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.get_joint_dof_count.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.color_graph.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.plot_graph.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.rst is excluded by !**/_generated/**
  • docs/api/_generated/newton.utils.transform_inertia.rst is excluded by !**/_generated/**
📒 Files selected for processing (5)
  • docs/api/newton.rst (1 hunks)
  • docs/api/newton_utils.rst (1 hunks)
  • docs/generate_api.py (3 hunks)
  • newton/__init__.py (1 hunks)
  • newton/utils.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/api/newton.rst
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/geometry.py:16-22
Timestamp: 2025-08-12T17:51:37.474Z
Learning: In the Newton public API refactor, common geometry symbols like ParticleFlags and ShapeFlags are now exposed at the top level in newton/__init__.py rather than through newton.geometry. The newton/geometry.py module is intentionally focused only on broad-phase collision detection classes (BroadPhaseAllPairs, BroadPhaseExplicit, BroadPhaseSAP).
📚 Learning: 2025-08-12T17:58:00.815Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/_src/solvers/featherstone/kernels.py:18-20
Timestamp: 2025-08-12T17:58:00.815Z
Learning: In newton/_src/core/__init__.py, the function transform_twist is properly re-exported from the spatial module via "from .spatial import (..., transform_twist, ...)" and included in __all__, making it available for import as "from ...core import transform_twist" from other modules.

Applied to files:

  • newton/utils.py
📚 Learning: 2025-08-12T17:58:00.815Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/_src/solvers/featherstone/kernels.py:18-20
Timestamp: 2025-08-12T17:58:00.815Z
Learning: In newton/_src/core/__init__.py, the function transform_twist is re-exported from the spatial module via "from .spatial import transform_twist", making it available for import as "from ...core import transform_twist" from other modules.

Applied to files:

  • newton/utils.py
📚 Learning: 2025-08-12T17:51:37.474Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/geometry.py:16-22
Timestamp: 2025-08-12T17:51:37.474Z
Learning: In the Newton public API refactor, common geometry symbols like ParticleFlags and ShapeFlags are now exposed at the top level in newton/__init__.py rather than through newton.geometry. The newton/geometry.py module is intentionally focused only on broad-phase collision detection classes (BroadPhaseAllPairs, BroadPhaseExplicit, BroadPhaseSAP).

Applied to files:

  • newton/utils.py
  • newton/__init__.py
📚 Learning: 2025-08-12T18:07:17.267Z
Learnt from: nvlukasz
PR: newton-physics/newton#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/utils.py
📚 Learning: 2025-08-12T18:07:17.267Z
Learnt from: nvlukasz
PR: newton-physics/newton#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/utils.py
📚 Learning: 2025-08-12T17:58:16.929Z
Learnt from: nvlukasz
PR: newton-physics/newton#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/utils.py
  • newton/__init__.py
📚 Learning: 2025-08-12T05:17:34.423Z
Learnt from: shi-eric
PR: newton-physics/newton#521
File: newton/examples/example_cloth_hanging.py:36-36
Timestamp: 2025-08-12T05:17:34.423Z
Learning: The Newton migration guide (docs/migration.rst) is specifically for documenting how to migrate existing warp.sim functionality to Newton equivalents. New Newton-only features that didn't exist in warp.sim do not need migration documentation.

Applied to files:

  • docs/api/newton_utils.rst
🔇 Additional comments (7)
docs/generate_api.py (1)

39-48: Confirm intent: include newton.sensors in generated API while it’s not exposed at package root

You’ve added newton.sensors to the docs generation list. However, newton/init.py doesn’t currently re-export a sensors submodule alongside geometry/ik/selection/solvers/utils/viewer. If the intent is that sensors is a first-class public submodule, mirror that in newton/init.py for consistency and discoverability.

Would you like me to add sensors to newton/init.py (all and import) in this PR?

docs/api/newton_utils.rst (2)

12-13: Docs: recorder classes update looks good

BasicRecorder and ModelAndStateRecorder replace the previous renderer entries cleanly and align with the new public utils surface.


21-47: Docs: functions list matches newton.utils exports

The autosummary list mirrors the re-exports in newton/utils.py (quaternion/spatial, transform_inertia, vec_*, graph utils, and importers). This ensures Sphinx stubs will generate for the full public surface.

newton/__init__.py (2)

57-100: Root exports are coherent and minimal

The curated set of core/geometry/sim types plus deliberate submodule exposure matches the refactor goals. The enums/flags (EqType, JointMode, JointType, ParticleFlags, ShapeFlags) are surfaced at the right level.


16-17: No circular-import issue found: geometry doesn't import the top-level package

I scanned the geometry modules for package-level imports and verified ParticleFlags/ShapeFlags are implemented and imported internally — no occurrences of "import newton" or "from newton import ..." inside newton/_src/geometry.

Files checked (highlights):

  • newton/geometry.py — line 16: "from ._src.geometry import BroadPhaseAllPairs, BroadPhaseExplicit, BroadPhaseSAP" (re-export).
  • newton/_src/geometry/init.py — imports ParticleFlags, ShapeFlags from .flags (local).
  • newton/_src/geometry/flags.py — defines ParticleFlags and ShapeFlags.
  • Search result: no "import newton" / "from newton import" in newton/_src/geometry/*.

Conclusion: importing the geometry submodule at package import-time does not create a circular import given the current code; no change required.

newton/utils.py (2)

16-29: Spatial math re-exports are clear and cohesive

Consolidating quaternion/spatial helpers here improves discoverability and keeps callers away from _src internals.


31-34: Graph utilities re-export looks good

color_graph and plot_graph routed via utils is consistent with the new API surface.

Comment thread newton/__init__.py Outdated
@nvlukasz

nvlukasz commented Aug 13, 2025

Copy link
Copy Markdown
Member Author

From your point in the meeting today about the Solver-X pattern, doesn't the ".solvers" submodule already list out all the options for solvers? Typing "Solver" again doesn't seem to clarify the autocomplete any further.

@kaloca On main, I see this:
image

All the solvers pop up, but they don't start with the name Solver*, so it's a bit harder to see "all the solvers".

In this PR it's clearer:
image

@kaloca

kaloca commented Aug 13, 2025

Copy link
Copy Markdown
Member

All the solvers pop up, but they don't start with the name Solver*, so it's a bit harder to see "all the solvers".

@nvlukasz . When I load the refactor branch, only the actual solvers appear after .solvers. If I revert the names to the previous pattern it looks like this:
image

Without the suffix it could also look like this:
image

Not sure which of all these options is more readable though.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
newton/__init__.py (1)

51-53: Track the Style3D helpers TODO

The TODO is fair; currently exposing Style3DModel/Style3DModelBuilder at root is convenient but may not scale. Consider moving them under a newton.style3d public submodule and re-exporting selectively for a gentler migration path.

Would you like me to open an issue proposing newton.style3d plus a deprecation path for root-level aliases?

📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c471fff and 0d3a513.

⛔ Files ignored due to path filters (1)
  • docs/api/_generated/newton.sensors.rst is excluded by !**/_generated/**
📒 Files selected for processing (2)
  • docs/api/newton.rst (1 hunks)
  • newton/__init__.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • docs/api/newton.rst
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/geometry.py:16-22
Timestamp: 2025-08-12T17:51:37.474Z
Learning: In the Newton public API refactor, common geometry symbols like ParticleFlags and ShapeFlags are now exposed at the top level in newton/__init__.py rather than through newton.geometry. The newton/geometry.py module is intentionally focused only on broad-phase collision detection classes (BroadPhaseAllPairs, BroadPhaseExplicit, BroadPhaseSAP).
📚 Learning: 2025-08-12T17:51:37.474Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/geometry.py:16-22
Timestamp: 2025-08-12T17:51:37.474Z
Learning: In the Newton public API refactor, common geometry symbols like ParticleFlags and ShapeFlags are now exposed at the top level in newton/__init__.py rather than through newton.geometry. The newton/geometry.py module is intentionally focused only on broad-phase collision detection classes (BroadPhaseAllPairs, BroadPhaseExplicit, BroadPhaseSAP).

Applied to files:

  • newton/__init__.py
📚 Learning: 2025-08-12T17:58:00.815Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/_src/solvers/featherstone/kernels.py:18-20
Timestamp: 2025-08-12T17:58:00.815Z
Learning: In newton/_src/core/__init__.py, the function transform_twist is properly re-exported from the spatial module via "from .spatial import (..., transform_twist, ...)" and included in __all__, making it available for import as "from ...core import transform_twist" from other modules.

Applied to files:

  • newton/__init__.py
📚 Learning: 2025-08-12T17:58:16.929Z
Learnt from: nvlukasz
PR: newton-physics/newton#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/__init__.py
🧬 Code Graph Analysis (1)
newton/__init__.py (10)
newton/_src/core/types.py (1)
  • Axis (64-121)
newton/_src/geometry/types.py (3)
  • SDF (43-70)
  • GeoType (25-36)
  • Mesh (73-234)
newton/_src/geometry/flags.py (2)
  • ParticleFlags (20-22)
  • ShapeFlags (26-34)
newton/_src/sim/collide.py (2)
  • CollisionPipeline (60-269)
  • count_rigid_contact_points (33-57)
newton/_src/sim/contacts.py (1)
  • Contacts (23-84)
newton/_src/sim/control.py (1)
  • Control (21-77)
newton/_src/sim/joints.py (4)
  • EqType (74-77)
  • JointMode (61-64)
  • JointType (29-36)
  • get_joint_dof_count (39-52)
newton/_src/sim/state.py (1)
  • State (21-109)
newton/_src/sim/style3d/model_style3d.py (1)
  • Style3DModel (24-55)
newton/_src/sim/style3d/builder_style3d.py (1)
  • Style3DModelBuilder (38-690)
⏰ 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 (5)
newton/__init__.py (5)

16-17: Top-level submodules exposed (incl. sensors) — LGTM

Exposing geometry, ik, selection, sensors, solvers, utils, and viewer at the package root improves discoverability and mirrors the docs surface.


20-23: Core re-exports are crisp and minimal — LGTM

Re-exporting Axis and AxisType at the root is consistent with the refactor goals.


26-32: Geometry flags moved to top-level as intended — LGTM

ParticleFlags and ShapeFlags at the root align with the public API plan (per retrieved learnings).


55-55: Version exposure — LGTM

Importing __version__ from ._version and surfacing it at the root is standard and matches the docs.


35-49: Confirmed — eval_fk / eval_ik are defined and re-exported from newton/_src/sim

Short check: eval_fk and eval_ik are implemented and are re-exported, so the import in newton/init.py should not error.

Files/locations (from the scan):

  • newton/_src/sim/articulation.py — def eval_fk (line ~393), def eval_ik (line ~689)
  • newton/_src/sim/init.py — from .articulation import eval_fk, eval_ik (line 17); all includes "eval_fk", "eval_ik" (lines ~46–49)

Optional: still consider lazy-loading heavy sim symbols (module-level getattr) if you want to reduce cost of a plain import newton.

Comment thread newton/__init__.py Outdated
@nvlukasz nvlukasz force-pushed the public-api-refactor-v2 branch from 0d3a513 to 57acda6 Compare August 13, 2025 21:04

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🔭 Outside diff range comments (3)
newton/examples/example_selection_cartpole.py (2)

147-150: Warp kernel launch dimension is wrong (likely 2D) — use the number of environments.

apply_forces_kernel uses a 1D tid and indexes joint_q[tid, 0]. Passing dim=joint_f.shape will launch a 2D grid and break indexing. Use the first dimension (num_envs).

Apply:

-            wp.launch(apply_forces_kernel, dim=joint_f.shape, inputs=[joint_q, joint_f])
+            wp.launch(apply_forces_kernel, dim=self.num_envs, inputs=[joint_q, joint_f])

171-189: torch.device is not a context manager — fix ScopedDevice Torch handling.

torch.device(...) does not support enter/exit. Use torch.cuda.device(...) for CUDA or a no-op context for CPU. Current code will raise at runtime if USE_TORCH is True.

Apply:

 class ScopedDevice:
     def __init__(self, device):
         self.warp_scoped_device = wp.ScopedDevice(device)
         if USE_TORCH:
             import torch  # noqa: PLC0415
-
-            self.torch_scoped_device = torch.device(wp.device_to_torch(device))
+            device_str = wp.device_to_torch(device)
+            # torch.device is not a context manager; use torch.cuda.device() when CUDA, otherwise a no-op context
+            if device_str.startswith("cuda"):
+                self.torch_ctx = torch.cuda.device(device_str)
+            else:
+                import contextlib  # local import to avoid global dependency when torch is unused
+                self.torch_ctx = contextlib.nullcontext()
 
     def __enter__(self):
         self.warp_scoped_device.__enter__()
         if USE_TORCH:
-            self.torch_scoped_device.__enter__()
+            self.torch_ctx.__enter__()
 
     def __exit__(self, exc_type, exc_val, exc_tb):
         self.warp_scoped_device.__exit__(exc_type, exc_val, exc_tb)
         if USE_TORCH:
-            self.torch_scoped_device.__exit__(exc_type, exc_val, exc_tb)
+            self.torch_ctx.__exit__(exc_type, exc_val, exc_tb)

If you prefer to avoid a local import of contextlib, add at the top-level:

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

612-614: Remove debug print statement

Debug print statement left in the code should be removed.

-        if body_name == "thigh_right":
-            print(geoms)
♻️ Duplicate comments (4)
newton/_src/solvers/featherstone/kernels.py (1)

75-75: LGTM! Using Warp's built-in spatial_adjoint function

Replacing the local spatial_adjoint implementation with wp.spatial_adjoint(R, S) is the right choice, especially since the project requires nightly Warp builds where this function is available.

newton/_src/solvers/mujoco/solver_mujoco.py (1)

1808-1823: Missing CONE handling in geometry mappings

The GeoType.CONE enum value exists but isn't mapped in geom_type_mapping or geom_type_name. This will cause a KeyError if a cone shape is encountered during conversion.

newton/_src/solvers/xpbd/kernels.py (1)

906-907: Kernel-to-public API coupling addressed appropriately

The imports have been successfully changed from the public newton package to local relative imports, resolving the previous coupling concern. This keeps the kernel independent from the public namespace and avoids JIT compilation overhead.

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

227-230: Fix inconsistent usage of wp.vec3 unpacking pattern

The code uses wp.vec3(euler) directly, but based on the learned pattern from the codebase, array-like objects should be unpacked when passed to wp.vec3().

-            return quat_from_euler(wp.vec3(euler), *euler_seq)
+            return quat_from_euler(wp.vec3(*euler), *euler_seq)
🧹 Nitpick comments (10)
newton/examples/example_selection_cartpole.py (1)

109-111: Avoid hard-coding solver class in behavior checks.

Using isinstance(self.solver, newton.solvers.SolverMuJoCo) bakes class knowledge into the example. Prefer a capability/feature check if the public API exposes one (e.g., a flag like solver.computes_fk or requires_fk_eval). This keeps the example resilient if solver names change or new solvers are added.

If such a flag exists, consider:

-        if not isinstance(self.solver, newton.solvers.SolverMuJoCo):
+        if getattr(self.solver, "requires_fk_eval", False):
             self.cartpoles.eval_fk(self.state_0)
docs/conventions.rst (1)

84-90: Align solver names with the new Solver convention and add Sphinx cross-refs*

The examples mix old and new naming (“XPBD, Euler, SolverMuJoCo”). For consistency and discoverability, use the Solver* names and link them with :class: so readers can click through.

Apply this diff:

-* **Standard Newton solvers** (XPBD, Euler, SolverMuJoCo etc.)  
+* **Standard Newton solvers** (:class:`~newton.solvers.SolverXPBD`, :class:`~newton.solvers.SolverSemiImplicit`, :class:`~newton.solvers.SolverMuJoCo`, etc.)  
@@
-  This matches the Isaac Lab convention exactly. Note that Newton will automatically
-  convert from this convention to MuJoCo's mixed-frame format when using the SolverMuJoCo.
+  This matches the Isaac Lab convention exactly. Note that Newton will automatically
+  convert from this convention to MuJoCo's mixed-frame format when using :class:`~newton.solvers.SolverMuJoCo`.
newton/geometry.py (1)

16-22: Add a short module docstring to clarify this facade’s scope

This module intentionally re-exports only the broad-phase classes. A brief docstring will guide users toward the top-level flags moved to newton.init and prevent confusion.

You can add this module docstring above the imports:

"""
Public geometry facade exposing broad-phase collision classes.

Note:
- Common flags like ParticleFlags and ShapeFlags are exposed at the top-level (newton).
- The full internal geometry surface is available under newton._src.geometry.
"""
newton/_src/geometry/flags.py (2)

20-22: Consider using IntFlag instead of IntEnum for bit flags.

ParticleFlags is being used for bitwise operations (e.g., flags[fixed_vertex_id] & ~ParticleFlags.ACTIVE) in the codebase. Using IntFlag instead of IntEnum would be more semantically correct for flags that are meant to be combined with bitwise operations.

Based on the retrieved learnings, I understand that IntEnum is currently used and Warp can handle it directly. However, for better semantic correctness and to prevent potential issues with future flag combinations, consider:

-from enum import IntEnum
+from enum import IntFlag


 # Particle flags
-class ParticleFlags(IntEnum):
+class ParticleFlags(IntFlag):
     ACTIVE = 1 << 0
     """Indicates that the particle is active."""

26-34: Consider using IntFlag for ShapeFlags as well.

Similar to ParticleFlags, ShapeFlags appears to support multiple simultaneous flags (VISIBLE, COLLIDE_SHAPES, COLLIDE_PARTICLES) that could be combined using bitwise operations.

 # Shape flags
-class ShapeFlags(IntEnum):
+class ShapeFlags(IntFlag):
     VISIBLE = 1 << 0
     """Indicates that the shape is visible."""

     COLLIDE_SHAPES = 1 << 1
     """Indicates that the shape collides with other shapes."""

     COLLIDE_PARTICLES = 1 << 2
     """Indicates that the shape collides with particles."""
newton/_src/sim/joints.py (3)

18-26: Consider removing commented-out legacy code

Lines 18-26 contain commented-out legacy constant definitions. Since these have been replaced with the IntEnum approach, consider removing these comments to keep the codebase clean. If needed for reference during migration, they could be documented in the migration guide instead.

-# # Types of joints linking rigid bodies
-# JointType.PRISMATIC = wp.constant(0)
-# JointType.REVOLUTE = wp.constant(1)
-# JointType.BALL = wp.constant(2)
-# JointType.FIXED = wp.constant(3)
-# JointType.FREE = wp.constant(4)
-# JointType.DISTANCE = wp.constant(5)
-# JointType.D6 = wp.constant(6)
-

55-59: Consider removing commented-out legacy code

Lines 55-59 contain commented-out legacy joint mode constants. As with the JointType comments above, these could be removed to maintain code cleanliness.

-# # Joint axis control mode types
-# JointMode.NONE = wp.constant(0)
-# JointMode.TARGET_POSITION = wp.constant(1)
-# JointMode.TARGET_VELOCITY = wp.constant(2)
-
-

67-71: Consider removing commented-out legacy code

Lines 67-71 contain commented-out legacy equality constraint constants. Following the same pattern, these could be removed.

-# # (temporary) equality constraint types
-# EqType.CONNECT = wp.constant(0)
-# EqType.WELD = wp.constant(1)
-# EqType.JOINT = wp.constant(2)
-
newton/__init__.py (1)

51-52: Consider timeline for Style3D helper consolidation

The TODO comment suggests these helpers might be rolled into Model and ModelBuilder. Consider creating a tracking issue for this refactoring to ensure it's not forgotten.

Would you like me to create a GitHub issue to track the consolidation of Style3DModel and Style3DModelBuilder into Model and ModelBuilder?

docs/migration.rst (1)

158-159: Note inconsistency in SimRenderer location

The table shows SimRenderer in newton.utils, but based on the code review, the actual renderers (RendererUsd, RendererOpenGL) are in newton.viewer. Consider clarifying that SimRenderer is just an alias.

Consider adding a note that SimRenderer is an alias for RendererUsd for backward compatibility:

 |``warp.sim.render.SimRenderer``                |:class:`newton.utils.SimRenderer`             |
+|                                               | (alias for :class:`newton.viewer.RendererUsd`)|

Comment thread newton/_src/solvers/mujoco/__init__.py Outdated
Comment thread newton/_src/utils/__init__.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🔭 Outside diff range comments (1)
docs/print_api.py (1)

72-73: Do not execute on import; add a main guard and CLI arg support

Unconditional execution will print during imports (e.g., Sphinx or other tooling), causing noisy logs and hard-to-diagnose failures. Guard it and allow choosing the root module via CLI.

Apply this diff:

-print_symbols(get_symbols("newton"))
+if __name__ == "__main__":
+    # Allow custom root module from CLI, default to 'newton'
+    root = sys.argv[1] if len(sys.argv) > 1 else "newton"
+    try:
+        print_symbols(get_symbols(root))
+    except ImportError as e:
+        print(f"Error: failed to import module '{root}': {e}", file=sys.stderr)
+        raise
♻️ Duplicate comments (1)
newton/tests/test_ik.py (1)

25-26: Test imports correctly use the refactored API.

The test properly imports newton.ik for the public API and uses the private _eval_fk_batched from newton._src.sim.ik, which is acceptable based on the project's testing conventions documented in the learnings.

🧹 Nitpick comments (1)
docs/print_api.py (1)

49-70: Minor readability and output polish

Rename the param for clarity and avoid emitting a stray blank line when there are no leaf symbols.

Apply this diff:

-def print_symbols(sym_dict, indent=0):
-    name, children = sym_dict[0], sym_dict[1]
+def print_symbols(sym_tree, indent=0):
+    """Pretty-print the nested symbols tree."""
+    name, children = sym_tree[0], sym_tree[1]
@@
-    print()
+    if symbols and submodules:
+        print()
📜 Review details

Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 57acda6039e75be7274cdc945d25dec8b41394b1 and 2862429d6bf9c84194e8f8e694f5f4ac193ecb35.

⛔ Files ignored due to path filters (1)
  • docs/api/_generated/newton.ik.IKJacobianMode.rst is excluded by !**/_generated/**
📒 Files selected for processing (9)
  • docs/api/newton_ik.rst (1 hunks)
  • docs/print_api.py (1 hunks)
  • newton/_src/sim/ik.py (19 hunks)
  • newton/examples/example_cloth_hanging.py (1 hunks)
  • newton/examples/example_ik_benchmark.py (5 hunks)
  • newton/examples/example_ik_interactive.py (5 hunks)
  • newton/ik.py (1 hunks)
  • newton/tests/test_ik.py (15 hunks)
  • newton/tests/test_ik_fk_kernels.py (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • docs/api/newton_ik.rst
  • newton/ik.py
  • newton/examples/example_cloth_hanging.py
  • newton/examples/example_ik_interactive.py
  • newton/tests/test_ik_fk_kernels.py
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/geometry.py:16-22
Timestamp: 2025-08-12T17:51:37.474Z
Learning: In the Newton public API refactor, common geometry symbols like ParticleFlags and ShapeFlags are now exposed at the top level in newton/__init__.py rather than through newton.geometry. The newton/geometry.py module is intentionally focused only on broad-phase collision detection classes (BroadPhaseAllPairs, BroadPhaseExplicit, BroadPhaseSAP).
📚 Learning: 2025-07-21T19:11:04.077Z
Learnt from: dylanturpin
PR: newton-physics/newton#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/_src/sim/ik.py
  • newton/tests/test_ik.py
  • newton/examples/example_ik_benchmark.py
📚 Learning: 2025-08-12T17:58:16.929Z
Learnt from: nvlukasz
PR: newton-physics/newton#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_ik.py
📚 Learning: 2025-07-15T21:00:03.709Z
Learnt from: dylanturpin
PR: newton-physics/newton#394
File: newton/tests/test_ik.py:119-119
Timestamp: 2025-07-15T21:00:03.709Z
Learning: In the Newton physics engine codebase, it's acceptable to test private API functions (those prefixed with underscore) in unit and component tests, as the maintainers consider this appropriate for testing internal functionality.

Applied to files:

  • newton/tests/test_ik.py
📚 Learning: 2025-08-12T18:04:06.577Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/_src/solvers/featherstone/kernels.py:75-75
Timestamp: 2025-08-12T18:04:06.577Z
Learning: The Newton physics framework requires nightly Warp builds, which means compatibility concerns with older stable Warp versions (like missing functions such as wp.spatial_adjoint) are not relevant for this project.

Applied to files:

  • newton/tests/test_ik.py
🧬 Code Graph Analysis (3)
newton/_src/sim/ik.py (1)
newton/tests/test_ik_fk_kernels.py (1)
  • supports_analytic (58-59)
newton/tests/test_ik.py (1)
newton/_src/sim/ik.py (5)
  • _eval_fk_batched (730-757)
  • IKJacobianMode (25-28)
  • IKPositionObjective (966-1120)
  • IKRotationObjective (1428-1594)
  • IKJointLimitObjective (1200-1317)
newton/examples/example_ik_benchmark.py (1)
newton/_src/sim/ik.py (4)
  • IKPositionObjective (966-1120)
  • IKRotationObjective (1428-1594)
  • IKJointLimitObjective (1200-1317)
  • IKJacobianMode (25-28)
⏰ 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 (16)
newton/examples/example_ik_benchmark.py (6)

31-31: LGTM! Import path properly updated.

The import path change from newton.sim.ik to newton.ik correctly aligns with the public API refactor.


170-170: LGTM! IK objective class names properly updated.

The renaming from PositionObjective to IKPositionObjective is consistent with the public API refactor.


174-174: LGTM! IK rotation objective properly renamed.

The renaming from RotationObjective to IKRotationObjective aligns with the public API changes.


186-186: LGTM! IK joint limit objective properly renamed.

The renaming from JointLimitObjective to IKJointLimitObjective is consistent with the public API refactor.


203-203: LGTM! Jacobian mode properly updated.

The change from JacobianMode.ANALYTIC to IKJacobianMode.ANALYTIC correctly reflects the enum renaming.


216-216: LGTM! Forward kinematics function path correctly updated.

The change from newton.sim.eval_fk to newton.eval_fk properly aligns with the public API refactor, moving the FK evaluation to a top-level namespace.

newton/_src/sim/ik.py (6)

25-25: LGTM! Enum renamed for consistency.

The renaming from JacobianMode to IKJacobianMode provides better namespacing and clarity in the public API.


474-474: LGTM! Import path updated to relative import.

The change from absolute import newton.solvers.featherstone.kernels to relative import ..solvers.featherstone.kernels correctly reflects the internal _src structure.


966-966: LGTM! Class renamed for clarity.

The renaming from PositionObjective to IKPositionObjective provides clearer context that this is an IK-specific objective.


1200-1200: LGTM! Joint limit objective properly renamed.

The renaming from JointLimitObjective to IKJointLimitObjective maintains consistency with other IK objective class names.


1428-1428: LGTM! Rotation objective consistently renamed.

The renaming from RotationObjective to IKRotationObjective aligns with the IK-prefixed naming convention.


57-58: LGTM! Jacobian mode references consistently updated throughout.

All references to JacobianMode have been properly updated to IKJacobianMode throughout the file, including in type hints, conditionals, and method signatures.

Also applies to: 105-105, 140-140, 183-187, 245-245, 262-262, 277-277, 290-290, 1004-1004, 1029-1029, 1243-1243, 1480-1480, 1505-1505

newton/tests/test_ik.py (1)

151-151: LGTM! Test code properly updated for API refactor.

All test functions and helper methods have been correctly updated to use:

  • IKJacobianMode instead of JacobianMode
  • IKPositionObjective, IKRotationObjective, IKJointLimitObjective instead of the unprefixed versions
  • Direct _eval_fk_batched calls instead of ik._eval_fk_batched

The changes are consistent throughout the test file.

Also applies to: 157-157, 172-172, 201-201, 204-206, 209-209, 212-212, 217-217, 227-227, 254-255, 257-258, 261-262, 265-265, 269-269, 279-280, 300-301, 303-305, 307-308, 327-328

docs/print_api.py (3)

1-15: License header: LGTM

Header is complete and compliant with SPDX and Apache-2.0 requirements.


20-21: No change required — project targets Python 3.9+

pyproject.toml declares requires-python = ">=3.9", so using PEP 585 generics (list[str]) is supported. Keep the annotation in docs/print_api.py as-is.


20-31: Avoid triggering lazy imports via getattr in public_symbols — repo scan OK but optional hardening suggested

Quick check: ripgrep found no top-level def __getattr__ / def __dir__ patterns in the repo, and many modules define __all__ (so the current fast path will usually be used). That means the immediate risk is low, but getattr(mod, name) can still trigger PEP 562 __getattr__ in third‑party or future modules — a small, defensive change avoids that.

Files to review

  • docs/print_api.py — function public_symbols (around lines 20–31)

Suggested (optional) patch — avoid importing attributes via getattr; use importlib to detect submodules instead:

+import importlib
@@
     def is_public(name: str) -> bool:
         if name.startswith("_"):
             return False
-        return not inspect.ismodule(getattr(mod, name))
+        # Avoid importing module attributes; classify submodules via importlib.
+        try:
+            return importlib.util.find_spec(f"{mod.__name__}.{name}") is None
+        except (ImportError, ValueError):
+            # Treat names that don't resolve as submodules as public symbols.
+            return True

If you prefer to keep the current code (given no repo-level __getattr__ was found) that's fine; apply the change if you want extra protection against lazy-import side effects from external or future modules.

Comment thread docs/print_api.py Outdated
Comment thread docs/print_api.py Outdated
Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

Use relative imports inside _src

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

More relative imports

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

Add missing import

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

Docstring update

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

Add missing eval_fk()

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

Removed duplicate functions from Featherstone solver

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

Exposed _src.core.spatial functions in newton.utils

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

Moved Style3D helpers

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

Formatting

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

Reduce private imports in IK tests

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

Added newton.sensors, moved ContactSensor there

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

Pass mesh_maxhullvert as an argument instead of modifying a global variable

Use IntEnum instead of IntFlag for intuitive behaviour with negation

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

Update API docs

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

Expose more things

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

Doc update

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

Add missing newton.sensors

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

Update docs

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

Renamed IKJacobianMode

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

Added simple docs/print_api.py script

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

Fixed solver name post-merge

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

Lazy import pyglet in gizmo.py
@nvlukasz nvlukasz force-pushed the public-api-refactor-v2 branch from 90a2b50 to 0d810de Compare August 13, 2025 22:59
Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🔭 Outside diff range comments (7)
newton/_src/utils/gizmo.py (3)

819-835: Avoid launching the kernel over MAX_CAPSULES; use num_active_capsules to cut GPU work.

You guard the launch with num_active_capsules > 0 but still set dim to MAX_CAPSULES. Launching with num_active_capsules saves a lot of cycles and memory traffic.

Apply this diff:

-            wp.launch(
-                test_all_capsule_hits,
-                dim=self.MAX_CAPSULES,
+            wp.launch(
+                test_all_capsule_hits,
+                dim=self.num_active_capsules,
                 inputs=[
                     wp.vec3(ray_origin),
                     wp.vec3(ray_direction),
                     self.body_transforms_gpu,
                     self.local_capsule_starts,
                     self.local_capsule_ends,
                     self.local_capsule_radii,
                     self.capsule_to_gizmo,
-                    self.num_active_capsules,
+                    self.num_active_capsules,
                 ],
                 outputs=[self.capsule_hit_distances],
                 device=self.device,
             )

166-177: Make dtypes explicit (wp.float32/wp.int32) to match the PR’s int32 policy and avoid precision surprises.

Warp defaults can vary; explicit types help ensure GPU-path and prevent silent float64/int64 promotion on CPU. This aligns with “switch flags to int32” mentioned in the PR.

Apply these diffs:

 @wp.kernel
 def test_all_capsule_hits(
     ray_origin_world: wp.vec3,
     ray_dir_world: wp.vec3,
     body_transforms: wp.array(dtype=wp.transform),
     local_capsule_starts: wp.array(dtype=wp.vec3),
     local_capsule_ends: wp.array(dtype=wp.vec3),
-    local_capsule_radii: wp.array(dtype=float),
-    capsule_to_gizmo: wp.array(dtype=int),
+    local_capsule_radii: wp.array(dtype=wp.float32),
+    capsule_to_gizmo: wp.array(dtype=wp.int32),
     num_active_capsules: int,
     # outputs
-    hit_distances: wp.array(dtype=float),
+    hit_distances: wp.array(dtype=wp.float32),
 ):
 @wp.kernel
 def find_closest_hit(
-    capsule_hit_distances: wp.array(dtype=float),
+    capsule_hit_distances: wp.array(dtype=wp.float32),
     num_capsules: int,
     # outputs
-    hit_type: wp.array(dtype=int),
-    hit_index: wp.array(dtype=int),
+    hit_type: wp.array(dtype=wp.int32),
+    hit_index: wp.array(dtype=wp.int32),
 ):
     def _allocate_gpu_arrays(self):
         """Allocate GPU arrays for collision detection."""
         self.body_transforms_gpu = wp.zeros((self.MAX_GIZMOS,), dtype=wp.transform, device=self.device)
         self.ray_origin_array = wp.zeros((1,), dtype=wp.vec3, device=self.device)
         self.ray_direction_array = wp.zeros((1,), dtype=wp.vec3, device=self.device)
-        self.capsule_hit_distances = wp.zeros((self.MAX_CAPSULES,), dtype=float, device=self.device)
-        self.hit_type = wp.zeros((1,), dtype=int, device=self.device)
-        self.hit_index = wp.zeros((1,), dtype=int, device=self.device)
+        self.capsule_hit_distances = wp.zeros((self.MAX_CAPSULES,), dtype=wp.float32, device=self.device)
+        self.hit_type = wp.zeros((1,), dtype=wp.int32, device=self.device)
+        self.hit_index = wp.zeros((1,), dtype=wp.int32, device=self.device)
         self.local_capsule_starts = wp.zeros((self.MAX_CAPSULES,), dtype=wp.vec3, device=self.device)
         self.local_capsule_ends = wp.zeros((self.MAX_CAPSULES,), dtype=wp.vec3, device=self.device)
-        self.local_capsule_radii = wp.zeros((self.MAX_CAPSULES,), dtype=float, device=self.device)
-        self.capsule_to_gizmo = wp.zeros((self.MAX_CAPSULES,), dtype=int, device=self.device)
-        self.capsule_to_component_type = wp.zeros((self.MAX_CAPSULES,), dtype=int, device=self.device)
-        self.capsule_to_component_idx = wp.zeros((self.MAX_CAPSULES,), dtype=int, device=self.device)
+        self.local_capsule_radii = wp.zeros((self.MAX_CAPSULES,), dtype=wp.float32, device=self.device)
+        self.capsule_to_gizmo = wp.zeros((self.MAX_CAPSULES,), dtype=wp.int32, device=self.device)
+        self.capsule_to_component_type = wp.zeros((self.MAX_CAPSULES,), dtype=wp.int32, device=self.device)
+        self.capsule_to_component_idx = wp.zeros((self.MAX_CAPSULES,), dtype=wp.int32, device=self.device)

Also applies to: 255-261, 656-668


731-742: Arc selection mapping is brittle; component_idx depends on capsules_per_arc. Store arc index directly.

Current logic derives arc_idx as component_idx // capsules_per_arc, which breaks if per-arc segment counts ever differ. Persist arc index during collision-data building.

Apply these diffs:

-            arc_segments = []
-            for arc in target.arcs.values():
-                segments = target._get_arc_collision_segments(arc, local_space_pos, local_space_rot)
-                arc_segments.extend(segments)
-
-            for i, (start_point, end_point) in enumerate(arc_segments):
-                collision_data["capsule_starts"].append(start_point)
-                collision_data["capsule_ends"].append(end_point)
-                collision_data["capsule_radii"].append(target.arc_capsule_radius)
-                collision_data["capsule_gizmo_ids"].append(target.body_id)
-                collision_data["capsule_comp_types"].append(ComponentType.ARC.value)
-                collision_data["capsule_comp_indices"].append(i)
+            for arc_idx, arc in enumerate(target.arcs.values()):
+                segments = target._get_arc_collision_segments(arc, local_space_pos, local_space_rot)
+                for start_point, end_point in segments:
+                    collision_data["capsule_starts"].append(start_point)
+                    collision_data["capsule_ends"].append(end_point)
+                    collision_data["capsule_radii"].append(target.arc_capsule_radius)
+                    collision_data["capsule_gizmo_ids"].append(target.body_id)
+                    collision_data["capsule_comp_types"].append(ComponentType.ARC.value)
+                    collision_data["capsule_comp_indices"].append(arc_idx)
-                elif component_type == ComponentType.ARC.value:
-                    arc_idx = component_idx // target.arcs["X"].capsules_per_arc
-                    component = list(target.arcs.values())[arc_idx]
+                elif component_type == ComponentType.ARC.value:
+                    component = list(target.arcs.values())[component_idx]

Also applies to: 865-868

newton/_src/solvers/mujoco/solver_mujoco.py (1)

1943-1947: Avoid mutating model.shape_scale when deriving MuJoCo geom size.

size = shape_size[shape] returns a writable view; updating it in-place (size[size == 0.0] = nonzero) mutates the model’s shape scales. That side effect is unintended since we only need a MuJoCo-local size.

Apply:

-                size = shape_size[shape]
+                size = shape_size[shape].copy()
                 if np.any(size > 0.0):
                     # duplicate nonzero entries at places where size is 0
                     nonzero = size[size > 0.0][0]
                     size[size == 0.0] = nonzero
                     geom_params["size"] = size
docs/migration.rst (1)

56-76: Replace removed ShapeGeometry.is_solid reference with Model.shape_is_solid

The docs reference the removed ShapeGeometry struct. The replacement attribute is :attr:newton.Model.shape_is_solid (dtype bool). Update the migration doc.

Files to change:

  • docs/migration.rst (line referencing :attr:newton.ShapeGeometry.is_solid)

Suggested minimal diff:

-:attr:`newton.ShapeGeometry.is_solid` now is of dtype ``bool`` instead of ``wp.uint8``.
+:attr:`newton.Model.shape_is_solid` now is of dtype ``bool`` instead of ``wp.uint8``.
newton/_src/solvers/style3d/kernels.py (1)

206-216: Make active-flag checks explicit (fix required — update multiple sites)

Avoid using not x & FLAG (precedes ambiguity and can break Warp DSL translation). Replace with an explicit equality check: (x & FLAG) == 0.

Locations found (from your search output):

  • newton/_src/solvers/style3d/kernels.py:216
  • newton/_src/solvers/vbd/solver_vbd.py:1220
  • newton/_src/solvers/vbd/solver_vbd.py:1245
  • newton/_src/solvers/vbd/solver_vbd.py:1377
  • newton/_src/solvers/vbd/solver_vbd.py:1511
  • newton/_src/solvers/vbd/solver_vbd.py:2006
  • newton/_src/solvers/vbd/solver_vbd.py:2126

Apply this change (example diffs):

For newton/_src/solvers/style3d/kernels.py

-    if not particle_flags[tid] & ParticleFlags.ACTIVE:
+    if (particle_flags[tid] & ParticleFlags.ACTIVE) == 0:

For newton/_src/solvers/vbd/solver_vbd.py (adjust variable name as used at each site)

-    if not particle_flags[particle] & ParticleFlags.ACTIVE:
+    if (particle_flags[particle] & ParticleFlags.ACTIVE) == 0:

or

-    if not particle_flags[particle_index] & ParticleFlags.ACTIVE:
+    if (particle_flags[particle_index] & ParticleFlags.ACTIVE) == 0:

Please update all matches returned by the search above to the explicit-check form to preserve behavior and avoid frontend/DSL translation issues.

newton/_src/geometry/kernels.py (1)

635-645: Flag arrays switched to wp.int32 — verified, one annotation needs updating

Search confirms model arrays and kernel/solver arguments use wp.int32 and the builder writes m.particle_flags / m.shape_flags as wp.int32. One remaining type annotation uses wp.uint32; update it to avoid confusion.

  • Fix:
    • newton/_src/sim/builder.py:2560
      • old: flags: list[wp.uint32] | None = None
      • new: flags: list[int] | None = None (or list[wp.int32] | None)
♻️ Duplicate comments (6)
newton/_src/sim/style3d/builder_style3d.py (1)

477-487: Avoid bitwise NOT on IntEnum; clear the flag explicitly.

With IntEnum, ~ParticleFlags.ACTIVE yields a negative int; the current x & ~ACTIVE works but is brittle and returns an int. Since particle_flag only ever holds ACTIVE here, just set it to 0 when fixing a boundary.

Apply this change:

-                particle_flag = ParticleFlags.ACTIVE
+                particle_flag = ParticleFlags.ACTIVE
@@
-                    particle_flag = particle_flag & ~ParticleFlags.ACTIVE
+                    # clear ACTIVE without relying on ~ semantics
+                    particle_flag = 0  # or ParticleFlags(0)
newton/examples/example_mpm_granular.py (1)

24-24: Import paths updated correctly

The solver import has been properly updated from ImplicitMPMSolver to SolverImplicitMPM following the new naming convention, and the renderer path changed to newton.viewer.RendererOpenGL, aligning with the API reorganization.

Also applies to: 64-64, 73-73

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

365-366: Passing strings to quat_between_axes is valid

quat_between_axes accepts AxisType and internally converts strings via Axis.from_any(), so quat_between_axes(Axis.Z, axis_str) is correct.

newton/examples/example_robot_manipulating_cloth.py (1)

37-39: LGTM! Imports updated to use public API correctly.

The imports correctly use the reorganized public API:

  • Top-level newton imports for core classes (Model, ModelBuilder, State, eval_fk)
  • newton.solvers for solver classes (SolverFeatherstone, SolverVBD)
  • newton.utils for the transform_twist utility function

This addresses the previous concern about using private imports and ensures the example relies only on public APIs.

newton/__init__.py (2)

35-49: Enum migration: verify no lingering usages of removed numeric constants, and old factory names in code/docs

Good move switching to IntEnum/IntFlag and promoting common sim utilities. To prevent breakage, scan for legacy constants (JOINT_, EQ_) and old geometry factories still referenced in code or docs.

Run this repo-safe scan:

#!/bin/bash
set -euo pipefail

echo "Searching for legacy joint/eq constants..."
rg -n -S $'\\bJOINT_(MODE_|PRISMATIC|REVOLUTE|BALL|FIXED|FREE|DISTANCE|D6)\\b|\\bEQ_(CONNECT|WELD|JOINT)\\b' || true

echo
echo "Searching for removed geometry factory helpers..."
rg -n -S $'\\bcreate_(box|capsule|cone|cylinder|none|plane|sphere)\\b' || true

echo
echo "Searching for old imports of flags from newton.geometry..."
rg -n -S $'from\\s+newton\\.geometry\\s+import\\s+.*(ParticleFlags|ShapeFlags)' || true

If you want, I can draft a quick PR comment with suggested replacements for any hits found.


57-100: all grouping is coherent and matches the intended public surface

The set of names looks consistent with the imports and the refactor goals. Previously suggested static check and temporary deprecation aliases are acknowledged as “not now”; leaving it as-is is fine.

🧹 Nitpick comments (19)
newton/_src/solvers/euler/__init__.py (2)

16-20: Consider a temporary alias to ease migration within _src (no change to public API).

If any internal code still references SemiImplicitSolver, a short-lived alias avoids churn while keeping the public API focused on Solver*. Keep it out of all so it isn’t re-exported publicly.

Apply this diff:

 from .solver_euler import SolverSemiImplicit

+# Deprecated compatibility alias for internal code. Remove after one release cycle.
+# Not included in __all__ on purpose to avoid public re-export.
+SemiImplicitSolver = SolverSemiImplicit
+
 __all__ = [
     "SolverSemiImplicit",
 ]

18-20: Nit: make all immutable to prevent accidental mutation.

A tuple is slightly safer and common in packages.

-__all__ = [
-    "SolverSemiImplicit",
-]
+__all__ = (
+    "SolverSemiImplicit",
+)
newton/tests/__main__.py (2)

18-19: Propagate the test runner’s exit status

Explicitly exit with the return code from main() so CI correctly fails/succeeds.

Apply this diff:

 if __name__ == "__main__":
-    main()
+    import sys
+    sys.exit(main())

Alternatively: raise SystemExit(main()) to avoid importing sys.


16-16: Prefer relative import within the tests package — apply the patch below

Short rationale: relative imports avoid hard-coding the top-level package name and make the tests package more robust to refactors.

Files to change:

  • newton/tests/main.py (import line)

Suggested diff:

-from newton.tests.thirdparty.unittest_parallel import main
+from .thirdparty.unittest_parallel import main

Verification run (script output summary):

  • newton/tests/thirdparty/unittest_parallel.py exists and defines main (found at line 51).
  • newton/tests/init.py and newton/tests/thirdparty/init.py are present.
  • The importlib.find_spec check could not complete because importing the top-level newton package executed package-level code that requires the optional dependency "warp" (ModuleNotFoundError: No module named 'warp') — so I couldn't confirm spec discovery in this environment.

Please verify the import works in your CI or a developer environment where optional dependencies (e.g., warp) are installed.

newton/_src/utils/gizmo.py (6)

801-804: Lazy import pyglet: add graceful fallback and avoid repeating the import.

Good call to import lazily. Make it robust when pyglet isn’t installed (headless environments) and stop importing the module twice per interaction.

Apply this diff:

-        import pyglet.window.mouse  # noqa: PLC0415
+        try:
+            import pyglet.window.mouse as mouse  # noqa: PLC0415
+        except Exception:
+            # pyglet is optional; without it we can't interpret button codes
+            return False
-
-        if button != pyglet.window.mouse.LEFT or self.num_gizmos == 0:
+        if button != mouse.LEFT or self.num_gizmos == 0:
             return False

Optionally, cache the import at module scope to avoid repeated imports:

# Place near imports at top of the module
try:
    import pyglet.window.mouse as _mouse
except Exception:
    _mouse = None

Then use _mouse if available and return False otherwise.


878-881: on_mouse_drag: same pyglet import concerns and bitmask check.

Mirror the lazy import-and-fallback pattern and use the alias. Also, confirm that the event source passes a bitmask (buttons) here; pyglet’s signature is on_mouse_drag(..., buttons, ...). If not a bitmask, the “& LEFT” test won’t behave as intended.

Apply this diff:

-        import pyglet.window.mouse  # noqa: PLC0415
+        try:
+            import pyglet.window.mouse as mouse  # noqa: PLC0415
+        except Exception:
+            return False
-
-        if not self.drag_state or not (button & pyglet.window.mouse.LEFT):
+        if not self.drag_state or not (button & mouse.LEFT):
             return False

If your caller does not pass a bitmask, change the condition to button == mouse.LEFT.


905-910: on_mouse_release: same pyglet import pattern.

Keep behavior consistent across mouse handlers.

Apply this diff:

-        import pyglet.window.mouse  # noqa: PLC0415
+        try:
+            import pyglet.window.mouse as mouse  # noqa: PLC0415
+        except Exception:
+            return False
-
-        if button == pyglet.window.mouse.LEFT and self.drag_state:
+        if button == mouse.LEFT and self.drag_state:
             self.drag_state = None
             self._hide_drag_axis()
             return True

860-868: Lookup by gizmo_id is O(n); maintain a body_id -> target map.

This runs on every click. A small map avoids linear scans.

Example change (outside this hunk):

  • In GizmoSystem.init: add self._targets_by_body_id = {}
  • In create_target: set self._targets_by_body_id[target.body_id] = target
  • Replace next(...) with a direct lookup:
-            target = next((t for t in self.targets.values() if t.body_id == gizmo_id), None)
+            target = self._targets_by_body_id.get(int(gizmo_id))

90-104: Guard against zero-length directions in closest_point_on_line_to_ray.

Defensive check prevents NaNs if ever called with degenerate inputs.

Apply this diff:

 def closest_point_on_line_to_ray(line_origin, line_dir, ray_origin, ray_dir):
     """Find parameter t such that line_origin + t*line_dir is closest to ray."""
-    line_dir_norm = line_dir / np.linalg.norm(line_dir)
-    ray_dir_norm = ray_dir / np.linalg.norm(ray_dir)
+    line_norm = np.linalg.norm(line_dir)
+    ray_norm = np.linalg.norm(ray_dir)
+    if line_norm < 1e-8 or ray_norm < 1e-8:
+        return 0.0
+    line_dir_norm = line_dir / line_norm
+    ray_dir_norm = ray_dir / ray_norm

271-276: Replace magic numbers in hit_type with an Enum for readability.

Use an IntEnum similar to ComponentType to encode “none/capsule” hit types.

Minimal change:

class HitType(Enum):
    NONE = 0
    CAPSULE = 1

Then set hit_type[0] = HitType.CAPSULE.value, else HitType.NONE.value.

newton/examples/example_ik_benchmark.py (1)

169-171: IK objective renames and signatures look consistent with the new API.

  • IKPositionObjective/IKRotationObjective offsets and counts are coherent with 3D residual packing.
  • IKJointLimitObjective arguments and weight are sensible defaults.
  • IKJacobianMode.ANALYTIC enum use matches the new naming.

Minor nit: consider passing the last scalar (1.0) as a named parameter for clarity if the API supports it.

Also applies to: 174-183, 186-194, 203-204

newton/_src/solvers/mujoco/solver_mujoco.py (1)

1879-1880: Make collision-shape selection explicit for readability.

Relying on truthiness of integers works, but being explicit avoids confusion with enum types.

Apply:

-            selected_shapes = np.where(shape_flags & ShapeFlags.COLLIDE_SHAPES)[0]
+            selected_shapes = np.where((shape_flags & ShapeFlags.COLLIDE_SHAPES) != 0)[0]
docs/migration.rst (1)

98-99: Optional: Cross-link JointMode for clarity

Consider linking the enum explicitly to aid navigation.

-To disable joint target control for a dof, use ``JointMode.NONE``.
+To disable joint target control for a dof, use :attr:`newton.JointMode.NONE`.

If JointMode is exposed at top-level newton (per public API refactor), this link will resolve; otherwise, point it to the correct module (e.g., :attr:newton.solvers.JointMode.NONE).

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

69-71: New mesh_maxhullvert parameter: sensible default and documented

Adding mesh_maxhullvert with the geometry default is a backward-compatible extension. The docstring includes the new parameter. No functional issues spotted.

Consider clarifying units/intent, e.g., “Maximum vertex count for convex hull approximation passed to Mesh(..., maxhullvert=...)”.

newton/tests/test_body_force.py (1)

180-185: Solver factory updates LGTM; minor consistency nit

Instantiations reflect the new Solver* class names. Optional: consider aligning the MuJoCo constructor options here with test_control_force (e.g., explicitly setting use_mujoco/update_data_interval) to keep test setups consistent across files.

-    "mujoco_c": lambda model: newton.solvers.SolverMuJoCo(model, disable_contacts=True),
+    "mujoco_c": lambda model: newton.solvers.SolverMuJoCo(
+        model, use_mujoco=True, update_data_interval=0, disable_contacts=True
+    ),
newton/_src/utils/import_usd.py (2)

53-54: New mesh_maxhullvert parameter: good addition; also pass it to visual meshes

The parameter makes convex hull approximation configurable. For consistency (and predictable behavior if someone later toggles visualization meshes into colliding ones), also pass mesh_maxhullvert when constructing Mesh for visual shapes.

-                m = Mesh(points, np.array(faces, dtype=np.int32).flatten())
+                m = Mesh(points, np.array(faces, dtype=np.int32).flatten(), maxhullvert=mesh_maxhullvert)

Also, minor doc nit: the Args section still documents default_density (Line 64), which is no longer a function parameter. Consider removing/updating that to avoid confusion.


435-443: Visual Mesh construction: ensure index dtype and propagate maxhullvert

Even though these are visual-only shapes (cfg with has_shape_collision=False), using int32 indices is preferable end-to-end and matching the collision mesh creation. Also propagate mesh_maxhullvert for consistency.

-                m = Mesh(points, np.array(faces, dtype=np.int32).flatten())
+                m = Mesh(points, np.array(faces, dtype=np.int32).flatten(), maxhullvert=mesh_maxhullvert)
newton/__init__.py (2)

16-18: Consider lazy-loading optional/heavy submodules (viewer, sensors) to keep import newton lightweight

Directly importing viewer and sensors at package import time can pull in optional GUI/camera deps, slow down cold start, and potentially fail in headless or minimal environments. Prefer lazy import for these two while keeping others eager.

Apply this diff in this file to avoid eager-importing viewer and sensors:

-# submodule APIs
-from . import geometry, ik, selection, sensors, solvers, utils, viewer
+# submodule APIs
+from . import geometry, ik, selection, solvers, utils

Then, add a simple lazy-loader elsewhere in this file (outside the selected range, e.g., right after the imports and before all):

# Lazy-load optional/heavy submodules to avoid import-time overhead/failures
import importlib as _importlib

_lazy_submodules = {"sensors", "viewer"}

def __getattr__(name):
    if name in _lazy_submodules:
        mod = _importlib.import_module(f"{__name__}.{name}")
        globals()[name] = mod  # cache
        return mod
    raise AttributeError(name)

This preserves __all__ and public surface while deferring optional deps until actually used. Please verify whether viewer/sensors have such optional deps in your environment; if not, you can ignore this.


51-53: Resolve or track the Style3D TODO to avoid long-lived TODOs in public API

Either move these helpers into a public newton.style3d submodule or proceed with the planned integration into Model/ModelBuilder. At minimum, open a tracking issue and reference it here.

I can open a follow-up issue with options/impact analysis and a deprecation timeline if you want.

Comment thread docs/migration.rst
Comment thread docs/migration.rst Outdated
Comment thread newton/_src/utils/import_usd.py
Comment thread newton/__init__.py

# ==================================================================================
# Style3D helpers
# TODO: eliminate these and roll the functionality into Model and ModelBuilder?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@nvlukasz Hi. Do you need me to merge Style3DModel and Style3DModelBuilder back into Model and ModelBuilder? In previous PR, we added Style3DModel because we tried to not modify the public class Model and ModelBuilder. Recently we also found it was not convenient to maintain Style3DModel.

@coderabbitai coderabbitai Bot mentioned this pull request Sep 9, 2025
4 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Dec 22, 2025
4 tasks
This was referenced Jan 9, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Jan 22, 2026
4 tasks
eric-heiden pushed a commit to eric-heiden/newton that referenced this pull request Jan 28, 2026
Restructuring to improve public API definitions

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>
Signed-off-by: Eric Shi <ershi@nvidia.com>
Co-authored-by: Eric Shi <ershi@nvidia.com>
This was referenced Feb 3, 2026
This was referenced Feb 16, 2026
vidurv-nvidia pushed a commit to vidurv-nvidia/newton that referenced this pull request Mar 6, 2026
…ewton-physics#519)

# Description

<!--
Thank you for your interest in sending a pull request. Please make sure
to check the contribution guidelines.

Link:
https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html
-->

Fixes callbacks being cleared when stage in memory is attached.
- uses this change Isaac Sim -
[link](https://gitlab-master.nvidia.com/omniverse/isaac/omni_isaac_sim/-/merge_requests/6598)
- removes some of the old callbacks fix, which is no longer needed with
the Isaac Sim change

Reorganizes environment unit tests
- split test_environments -> test_environments and
test_environments_with_stage_in_memory for separate test reporting
- add a utils file for shared functions in isaaclab_tasks/tests
- removes repose cube allegro direct unit test which can cause an OOM
during the CI test
- improves flakey env tests

<!-- As a practice, it is recommended to open an issue to have
discussions on the proposed pull request.
This makes it easier for the community to keep track of what is being
developed or added, and if a given feature
is demanded by more than one party. -->

## Type of change

<!-- As you go through the list, delete the ones that are not
applicable. -->

- Bug fix (non-breaking change which fixes an issue)
- New feature (non-breaking change which adds functionality)
- Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- This change requires a documentation update

## Screenshots

Please attach before and after screenshots of the change if applicable.

<!--
Example:

| Before | After |
| ------ | ----- |
| _gif/png before_ | _gif/png after_ |

To upload images to a PR -- simply drag and drop an image while in edit
mode and it should upload the image directly. You can then paste that
source into the above before/after sections.
-->

## Checklist

- [X] I have run the [`pre-commit` checks](https://pre-commit.com/) with
`./isaaclab.sh --format`
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [X] I have updated the changelog and the corresponding version in the
extension's `config/extension.toml` file
- [ ] I have added my name to the `CONTRIBUTORS.md` or my name already
exists there

<!--
As you go through the checklist above, you can mark something as done by
putting an x character in it

For example,
- [x] I have done this task
- [ ] I have not done this task
-->

---------

Signed-off-by: rwiltz <165190220+rwiltz@users.noreply.github.com>
Signed-off-by: Kelly Guo <kellyguo123@hotmail.com>
Signed-off-by: Ashwin Varghese Kuruttukulam <123109010+ashwinvkNV@users.noreply.github.com>
Signed-off-by: Kelly Guo <kellyg@nvidia.com>
Signed-off-by: Michael Gussert <michael@gussert.com>
Signed-off-by: samibouziri <79418773+samibouziri@users.noreply.github.com>
Signed-off-by: Mayank Mittal <12863862+Mayankm96@users.noreply.github.com>
Signed-off-by: Kyle Morgenstein <34984693+KyleM73@users.noreply.github.com>
Signed-off-by: Hongyu Li <lihongyu0807@icloud.com>
Signed-off-by: Toni-SM <toni.semu@gmail.com>
Signed-off-by: James Tigue <166445701+jtigue-bdai@users.noreply.github.com>
Signed-off-by: Pascal Roth <57946385+pascal-roth@users.noreply.github.com>
Signed-off-by: Victor Khaustov <3192677+vi3itor@users.noreply.github.com>
Signed-off-by: AlvinC <alvincny529@gmail.com>
Signed-off-by: Tyler Lum <tylergwlum@gmail.com>
Signed-off-by: Miguel Alonso Jr. <76960110+miguelalonsojr@users.noreply.github.com>
Signed-off-by: renaudponcelet <renaud.poncelet@gmail.com>
Signed-off-by: jiehanw <jiehanw@nvidia.com>
Signed-off-by: matthewtrepte <mtrepte@nvidia.com>
Co-authored-by: lotusl-code <lotusl@nvidia.com>
Co-authored-by: Kelly Guo <kellyg@nvidia.com>
Co-authored-by: jaczhangnv <jaczhang@nvidia.com>
Co-authored-by: rwiltz <165190220+rwiltz@users.noreply.github.com>
Co-authored-by: Yanzi Zhu <yanziz@nvidia.com>
Co-authored-by: nv-mhaselton <mhaselton@nvidia.com>
Co-authored-by: cosmith-nvidia <141183495+cosmith-nvidia@users.noreply.github.com>
Co-authored-by: Michael Gussert <michael@gussert.com>
Co-authored-by: CY Chen <cyc@nvidia.com>
Co-authored-by: oahmednv <oahmed@Nvidia.com>
Co-authored-by: Ashwin Varghese Kuruttukulam <123109010+ashwinvkNV@users.noreply.github.com>
Co-authored-by: Rafael Wiltz <rwiltz@nvidia.com>
Co-authored-by: Peter Du <peterd@nvidia.com>
Co-authored-by: chengronglai <chengrongl@nvidia.com>
Co-authored-by: pulkitg01 <pulkitg@nvidia.com>
Co-authored-by: Connor Smith <cosmith@nvidia.com>
Co-authored-by: Ashwin Varghese Kuruttukulam <ashwinvk@nvidia.com>
Co-authored-by: Kelly Guo <kellyguo123@hotmail.com>
Co-authored-by: shauryadNv <shauryad@nvidia.com>
Co-authored-by: Mayank Mittal <12863862+Mayankm96@users.noreply.github.com>
Co-authored-by: samibouziri <79418773+samibouziri@users.noreply.github.com>
Co-authored-by: James Smith <142246516+jsmith-bdai@users.noreply.github.com>
Co-authored-by: Shundo Kishi <syundo0730@gmail.com>
Co-authored-by: Sheikh Dawood <sabdulajees@nvidia.com>
Co-authored-by: Toni-SM <aserranomuno@nvidia.com>
Co-authored-by: Gonglitian <70052908+Gonglitian@users.noreply.github.com>
Co-authored-by: James Tigue <166445701+jtigue-bdai@users.noreply.github.com>
Co-authored-by: Mayank Mittal <mittalma@leggedrobotics.com>
Co-authored-by: Kyle Morgenstein <34984693+KyleM73@users.noreply.github.com>
Co-authored-by: Johnson Sun <20457146+j3soon@users.noreply.github.com>
Co-authored-by: Pascal Roth <57946385+pascal-roth@users.noreply.github.com>
Co-authored-by: Hongyu Li <lihongyu0807@icloud.com>
Co-authored-by: Jean-Francois-Lafleche <57650687+Jean-Francois-Lafleche@users.noreply.github.com>
Co-authored-by: Wei Jinqi <changshanshi@outlook.com>
Co-authored-by: Louis LE LAY <le.lay.louis@gmail.com>
Co-authored-by: Harsh Patel <hapatel@theaiinstitute.com>
Co-authored-by: Kousheek Chakraborty <kousheekc@gmail.com>
Co-authored-by: Victor Khaustov <3192677+vi3itor@users.noreply.github.com>
Co-authored-by: AlvinC <alvincny529@gmail.com>
Co-authored-by: Felipe Mohr <50018670+felipemohr@users.noreply.github.com>
Co-authored-by: AdAstra7 <87345760+likecanyon@users.noreply.github.com>
Co-authored-by: gao <ziqi.gao@iff-extern.fraunhofer.de>
Co-authored-by: Tyler Lum <tylergwlum@gmail.com>
Co-authored-by: -T.K.- <t_k_233@outlook.com>
Co-authored-by: Clemens Schwarke <96480707+ClemensSchwarke@users.noreply.github.com>
Co-authored-by: Miguel Alonso Jr. <76960110+miguelalonsojr@users.noreply.github.com>
Co-authored-by: Miguel Alonso Jr. <miguel.alonso@nfinite.app>
Co-authored-by: renaudponcelet <renaud.poncelet@gmail.com>
Co-authored-by: Ales Borovicka <aborovicka@nvidia.com>
Co-authored-by: nv-mm <mmagdics@nvidia.com>
Co-authored-by: Antoine RICHARD <antoiner@nvidia.com>
Co-authored-by: dvangelder-nvidia <dvangelder@nvidia.com>
Co-authored-by: Jiehan Wang <33852873+JerryJiehanWang@users.noreply.github.com>
Co-authored-by: Octi Zhang <zhengyuz@nvidia.com>
mmacklin pushed a commit to mmacklin/newton that referenced this pull request Apr 7, 2026
Restructuring to improve public API definitions

Signed-off-by: Lukasz Wawrzyniak <lwawrzyniak@nvidia.com>
Signed-off-by: Eric Shi <ershi@nvidia.com>
Co-authored-by: Eric Shi <ershi@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Public API Refactor

7 participants