Public API refactor#519
Conversation
📝 WalkthroughWalkthroughReorganizes 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
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)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
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 unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
Actionable comments posted: 13
🔭 Outside diff range comments (13)
newton/tests/test_recorder.py (1)
107-121: Add playback round-trip assertions intest_model_and_state_recorderAfter you compare the serialized state blobs, instantiate a fresh state and call
new_recorder.playbackfor 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: insidetest_model_and_state_recorder, immediately after the existing history comparisonsSuggested 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 TestEqualityConstraintsThe new test instantiates
newton.solvers.SolverMuJoCoand callsnewton.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 andJointTypeenum: missing COMPOUND/UNIVERSAL membersThe docs still list legacy
JOINT_COMPOUNDandJOINT_UNIVERSAL, but theJointTypeenum innewton/_src/sim/joints.pyonly defines:
- PRISMATIC
- REVOLUTE
- BALL
- FIXED
- FREE
- DISTANCE
- D6
You must choose one of the following fixes:
- Add
COMPOUNDandUNIVERSALto theJointTypeenum (with the correct integer values) so thatJointType.COMPOUNDandJointType.UNIVERSALare valid, then update the docs to use those enum members.- Remove or update the two entries in
docs/concepts/articulations.rstfor 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 testif not particle_flags[tid] & ParticleFlags.ACTIVE:
notconverts the bitwise result to Pythonbool; Warp’s Python-to-CUDA translation has historically been brittle with Python boolean operators inside kernels.- The array was switched to
wp.int32, but many call-sites still passwp.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_flagsas 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 coincideIf 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 / dnewton/_src/solvers/featherstone/solver_featherstone.py (1)
500-503: Hard-coded DOF=18 assertions break general modelsThese 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 particlesUsing 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)) * dtOptionally: 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
RendererOpenGLexpects an Axis enum (or None), notstr(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 onvec10f
actuator_bias[worldid, actuator_idx][1] = -kp(and similar writes) mutates a single element of a vector stored inside a Warp array.
Warp only supportsarr[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] = biasApply 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 theif-chain / shadowing pattern in IK kernelThe same comments about
typeshadowing and the missingelifs apply here. Consider the same refactor for consistency.
🧹 Nitpick comments (69)
newton/tests/test_recorder.py (1)
24-24: Using internal_srcimport in tests is acceptable here; consider planning a public re-export.Per team practice, testing private APIs is fine. Longer term, expose
BasicRecorder/ModelAndStateRecordervia 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
_srcpath 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 mergeThe new comment highlights uncertainty about exposing
MESH_MAXHULLVERT. Since the constructor already acceptsmaxhullvert, keeping a module-level mutable constant can be confusing. Consider:
- Removing the constant entirely and hard-coding the default in the
Meshsignature, or- 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
GeoTypeis listed innewton/__init__.py(lines 28 & 64), so importing it fromnewtonis correct.- All five
ray_intersect_*functions live innewton/_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 undernewton.geometry.raycastornewton.raycast) to avoid private_srcimports 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 aliasThe 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-importsnewton/_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 toSolverVBDlooks 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-hintcontrolas optional
controlmay beNoneat 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 valuesUsing
ParticleFlags.ACTIVEandShapeFlags.*is clearer. To avoid numeric drift, consider documenting the enum classes directly (e.g., with.. autoclass:: ParticleFlagsand.. autoclass:: ShapeFlagsplus:members:) instead of hardcoding numeric values in a list-table.newton/selection.py (1)
16-20: Add a brief module docstring for Sphinx discoverabilityConsider adding a top-level docstring describing this as the public interface for selection utilities and briefly summarizing
ArticulationViewusage.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 geometryfrom ..geometry import MeshTo reduce ambiguity and ease refactors, prefer importing the module only and referring to
geometry.Meshat 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 ModelBuilderAnd 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.ikand instantiates the renamed classes.I can draft an additional test to import
newton.ikand constructIKPositionObjective,IKRotationObjective, andIKJointLimitObjectiveto 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 consistentThe 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
SimRendereris now just an alias ofRendererUsd, but exporting all three names (RendererOpenGL,RendererUsd, andSimRenderer) may mislead newcomers into thinking they are three distinct back-ends.
Consider deprecatingSimRenderer(e.g., withwarnings.warn("SimRenderer is deprecated …")) or dropping it from__all__in the next major release.
114-116:__all__order nitPurely stylistic: keep list alphabetically sorted to avoid merge conflicts (
RendererUsdbeforeRendererOpenGL).newton/examples/example_mujoco.py (3)
247-256: Variable shadowing can obscure intentParameter
solveris reused as a local variable passed intoSolverMuJoCo(solver=solver, …).
Because it masks the earlier imported module namespace (newton.solvers), future edits may accidentally shadow more attributes.
Renaming the arg tosolver_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 fornewton.viewerUnlike
newton.utils, theviewersub-module is not imported at the file top.
Ifnewton/__init__.pyever 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 renameThe 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 overriderelies on the package remaining exactly three levels deep.
Ifxpbdgets nested further (xpbd.v2etc.) 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 thetypeparameter and typing it asJointType
typeshadows the Python builtin and is annotated as plainint, yet throughout the file it is compared againstnewton.JointType.*(anIntEnum).
Changing the signature to something likedef 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
IntEnumvalues transparently (IntEnuminheritsint), 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_srctree.
If the implementation is later relocated, the test will break while public behaviour is unchanged. Patching via the public symbol exposed throughnewton.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 thecollidervariable inside the list-comprehension
collideris first bound to theCollider()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: Useelifand avoid shadowing built-insInside the loop
typeshadows Python’s built-intype()and every branch is written as an independentif, 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 withelifto 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 pressurecount 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 helperThis 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_targetsInstead 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 → ContactsParameter 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.solveris always an instance ofSolverMuJoCo(created on Line 82). Theisinstanceguard 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_typestores rawints. Relying onIntEnum == intcoercion 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 minimalImporting the heavy
_src.sim.ikmodule 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 publicnewton.ikimport over private pathimport newton.ik as ikkeeps the test aligned with the new public surface while still allowing access to private helpers when necessary.
185-200: Consistent enum reference styleWithin the same function both
JointType.*andnewton.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 methodeval_fk.Importing
eval_fkinto the module namespace and then defining anArticulationView.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 toJointTypefor clarity.
root_joint_typeis stored as a plainint, so the comparison againstJointType.FIXED / FREE / DISTANCErelies on implicitIntEnum–intequality.
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 publiccollide()behaviour check instead of explicit type test.The special-casing of
SolverMuJoCobyisinstance()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 castParticleFlagswhen 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 dropnewton.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_assetKeeps the namespace clean.
newton/examples/example_rigid_force.py (1)
55-59: Consider importingnewton.viewerexplicitly for clarity
RendererOpenGL/RendererUsdare accessed throughnewton.viewer.*, but the sub-module is never imported.
While attribute access works after the earlierfrom newton.viewer import …in other files, being explicit here avoids relying on side-effects.-import newton +import newton +import newton.viewernewton/_src/geometry/flags.py (2)
23-26: Consider adding a NONE = 0 member to each IntFlagHaving 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 << 2Also applies to: 39-47
18-20: Remove deprecated commented constants to reduce noiseThese 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 consistencyThis 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
RecorderImGuiManagerpulls in an OpenGL context at import-time. Importing it unconditionally makes the script crash on headless machines even whenstage_pathisNone. Delay the import until you actually create a renderer (or guard it with the sameif stage_path:check).
82-86: Provide run-time selection instead of commented optionsSwitching 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_pointjumps three levels up fromnewton._src.solvers.xpbd. That path will break ifsolversis ever moved. Prefer absolute importfrom newton._src.core import velocity_at_pointto decouple the solver package layout.
70-71: Cast enum to kernel integer onceWarp captures the whole
IntFlagobject each time you writeif (particle_flags[i] & ParticleFlags.ACTIVE) == 0:Introduce a local constant:
ACTIVE = wp.int32(int(ParticleFlags.ACTIVE))and use
ACTIVEin 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 shadowingFunctions such as
update_joint_dof_modeuse the parameter namemode, but elsewhere the variablemodeis overloaded for bit-flags. Consider renaming toaxis_modeto reduce cognitive load.newton/_src/solvers/flags.py (1)
16-18: Misleading module docstringThe 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.solversis accessed without an explicit import. Relying onnewton/__init__.pyto import sub-packages increases start-up time and may break iflazy importmachinery is introduced later. Addimport newton.solversnear the top.
231-236: Avoid negativeisinstancechecksInstead 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
isinstanceevery sub-step.newton/_src/solvers/__init__.py (1)
16-24: Consider lazy loading to cut import timeEagerly 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 SolverXPBDUse 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 selectionMinor 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.REVOLUTEnewton/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_kinematicsor similar) to avoid class-name checks. Optional.newton/_src/sim/joints.py (3)
18-26: Remove stale commented constant definitionsThese commented
wp.constantlines 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
JointTypevalues preserve the prior mapping. Consider adding a brief docstring for clarity; otherwise LGTM.
39-52: Tighten type annotations and comparisonsSince
JointTypeis anIntEnum, 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 treatShapeFlagsas anIntFlag.Line 56 casts the enum to
int, while Line 84 relies on the rawIntFlag. 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.PRISMATICwas 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 forSimRenderer.
newton.utils.SimRendererno longer exists; the public alias lives innewton.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 sectionLeft-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 laterSub-modules like
solvers.flags.SolverNotifyFlags(now public) are not exported via__all__. Exporting them avoidsfrom newton import *surprises.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
newton/_src/utils/import_mjcf.py (1)
226-229: Fix runtime crash: wrong argument type passed towp.vec3.The existing review comment remains valid -
wp.vec3expects three scalars, buteuleris 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_axesexpectsAxisenum, not string.The existing review comment remains valid -
axis_stris a string butquat_between_axesexpects anAxisenum 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 clarityBoth 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
📒 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 correctAll 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/integrationThese 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 correctThe 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 correctUse 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 correctThe 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 neededJointType is explicitly re-exported in
newton/_src/sim/__init__.py(line 26) and originally defined innewton/_src/sim/joints.py. The only use innewton/_src/solvers/featherstone/kernels.pyis 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/_srcwhile maintaining the same functionality.
643-644: Enum flag usage is correctly implemented.The migration from
PARTICLE_FLAG_ACTIVEconstant toParticleFlags.ACTIVEenum 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 toJointMode.*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 toJointType.*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.NONEin theeval_joint_forcefunction call is consistent with the enum refactor and maintains the original behavior.
633-633: Approve migration ofparticle_flagsdtypeSearches across the Python codebase returned no remaining
wp.uint32usages forparticle_flags. Switching towp.int32correctly aligns with theParticleFlagsIntFlag refactor—no further updates needed.newton/_src/utils/import_mjcf.py (3)
151-151: LGTM!The usage of
geometry.MESH_MAXHULLVERTfor defaultmaxhullvertvalue is correct and consistent with the refactored API.
346-346: LGTM!The usage of
geometry.MESH_MAXHULLVERTas the fallback value formaxhullvertwhen creating meshes is correct.
466-595: LGTM!The refactoring to use
JointTypeenum 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
Meshconstructor is correctly called with the imported geometry module.
553-556: LGTM!The usage of
JointModeenum 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
modewithJointMode.NONEis correct and consistent with the enum-based API.
1065-1065: LGTM!The
Meshconstructor correctly usesgeometry.MESH_MAXHULLVERTas the default value formaxhullvert.
1089-1089: LGTM!The usage of
quat_between_axeswith properAxisenum values is correct.
1111-1111: LGTM!Correctly uses
ShapeFlags.COLLIDE_SHAPESenum 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
SolverFeatherstonein accordance with the API refactor.
185-196: LGTM!The
SolverImplicitMPMclass and itsOptionsare correctly used according to the refactored API.
199-199: LGTM!The renderer has been correctly updated to use
newton.viewer.RendererOpenGLaccording to the refactored public API structure.
207-207: LGTM!The forward kinematics function call has been correctly updated to use
newton.eval_fkinstead ofnewton.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
Axisenum is correctly used with itsfrom_anymethod to handle type conversion, and the default value is properly set toAxis.Z.
413-451: LGTM!All geometry type checks have been correctly updated to use the
GeoTypeenum values instead of the old constants. This improves type safety and consistency.
474-474: LGTM!Shape visibility check correctly uses
ShapeFlags.VISIBLEenum flag instead of the old integer constant.
534-536: LGTM!Joint type checks correctly use
JointTypeenum values (REVOLUTE,D6) instead of the old constants.
586-606: LGTM!Method signatures have been correctly updated to use locally imported
Statetype instead of the qualified name.
761-761: LGTM!The
isinstancecheck correctly uses the renamedRendererOpenGLclass.
957-996: LGTM!The
RendererUsdclass has been properly renamed fromSimRendererUsdand 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
RendererOpenGLclass has been properly renamed fromSimRendererOpenGLand its documentation correctly reflects the new public API path (newton.viewer.RendererOpenGL).
1351-1351: LGTM!The backward compatibility alias
SimRenderercorrectly points to the renamedRendererUsdclass.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
_srcpackage 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
JointTypeenum 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
JointTypeenum 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
JointModeenum 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 confirmedAll
particle_flagsarrays across the codebase are now declared withdtype=wp.int32. No instances ofwp.uint32remain for these arrays, and the builder converts flag values intoint32. 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.ACTIVEbehave as expected with Python’s IntFlag. No changes required.
There was a problem hiding this comment.
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 testsAll
particle_flagsarrays on host and device usewp.int32, so the dtype is consistent. To avoid any JIT surprises when using anIntFlagin bitwise operations, explicitly cast the enum tointin 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 pathwp.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
JointTypeenum 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 articulationsThe 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 stringThe
axis_strvariable is a string ("X"/"Y"/"Z"), butquat_between_axesexpects anAxisenum 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 defensivelyNo 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 resultsThe
topological_sortfunction 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
📒 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 capUsing 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 saferReplacing 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 requiredAll of the names you’re importing are already re-exported in their package’s
__init__.py, so the current imports inimport_mjcf.pyare safe:• newton/_src/core/init.py re-exports
quat_between_axesandquat_from_euler
• newton/_src/core/types.py importsSequencefromcollections.abcand re-exports it
• newton/_src/geometry/init.py re-exportsMeshIf you prefer, you can import
Sequencedirectly fromtyping(orcollections.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.kernelsto..solvers.featherstone.kernelscorrectly uses relative imports to reference the kernels module within the reorganized_srcdirectory structure.
966-966: Class rename follows consistent naming convention.The rename from
PositionObjectivetoIKPositionObjectivealigns with the PR's goal of improving API consistency by adding theIKprefix to all IK-related objective classes.
1200-1200: Class rename follows consistent naming convention.The rename from
JointLimitObjectivetoIKJointLimitObjectiveis 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
RotationObjectivetoIKRotationObjectivecompletes 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.uint32towp.int32across all kernel functions to align with the newParticleFlagsenum 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.ACTIVEenum 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
JointTypeenum 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, andJointMode.TARGET_VELOCITYenum 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.DISTANCEandJointType.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 appliedThe import changes correctly reflect the new public API structure:
newton.viewermodule added for rendering classes- Updated solver import location to
newton.solvers
183-183: Solver name consistently updatedThe solver instantiation correctly uses the new
SolverFeatherstonename following the consistentSolver*naming pattern.
185-196: LGTM! Options creation properly updatedThe 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 APIThe renderer instantiation properly uses
newton.viewer.RendererOpenGLinstead of the oldnewton.utils.SimRendererOpenGL, aligning with the API restructuring.
207-207: Forward kinematics call properly updatedThe FK evaluation correctly uses the new top-level
newton.eval_fkentry point instead of the oldnewton.sim.eval_fk, consistent with the public API refactor.newton/_src/utils/render.py (10)
25-27: Import paths correctly updated to relative importsThe imports have been properly converted to relative imports from the new internal
_srcstructure, maintaining consistency with the private API organization.
169-200: Type annotations properly updated for local typesThe
__init__method signature correctly uses local type imports (Model,AxisType) instead of the fully qualifiednewton.*types, and the axis initialization properly uses localAxisenum methods.
413-473: Geometry type enums consistently appliedThe shape creation logic correctly uses
GeoTypeenum values instead of the old constants, improving type safety and consistency across the codebase.
474-476: Shape visibility flag properly migrated to enumThe visibility check correctly uses
ShapeFlags.VISIBLEinstead of the old constant-based approach, aligning with the new flag-based API.
534-536: Joint type enum correctly appliedThe joint visualization logic properly uses
JointType.REVOLUTEandJointType.D6enums instead of the old constants.
586-606: State type annotation properly updatedThe
apply_picking_forcemethod correctly uses the localStatetype instead ofnewton.State, maintaining consistency with the import changes.
761-761: Class rename correctly applied in debug codeThe debug check properly references
RendererOpenGLinstead of the oldSimRendererOpenGLname.
865-923: Contacts type properly updatedThe method signatures correctly use the local
Contactstype (plural form) instead of the oldnewton.Contacts, maintaining consistency with the API refactor.
957-1231: Class renames correctly applied throughoutThe
RendererUsdclass (formerlySimRendererUsd) 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 definedThe
RendererOpenGLclass is correctly created using the factory pattern, and the docstring examples properly referencenewton.viewer.RendererOpenGL.newton/examples/example_selection_articulations.py (5)
24-24: ArticulationView import correctly updatedThe import properly references
newton.selection.ArticulationViewinstead of the oldnewton.utils.selectionpath, aligning with the new public API structure.
113-113: Solver instantiation properly updatedThe solver creation correctly uses
newton.solvers.SolverMuJoCofollowing the new naming convention.
117-125: Renderer initialization properly enhancedThe
RendererOpenGLinitialization correctly includes additional parameters for better control over the rendering setup, and uses the newnewton.viewernamespace.
144-147: JointType enum correctly used in ArticulationViewThe
exclude_joint_typesparameter properly usesnewton.JointType.FREEinstead of the oldJOINT_FREEconstant, and the multi-line formatting improves readability.
216-216: Solver type check properly updatedThe
isinstancecheck correctly usesnewton.solvers.SolverMuJoCofollowing the new class name.newton/_src/solvers/solver.py (5)
18-19: Import paths correctly converted to relative importsThe imports properly use relative paths for internal modules, maintaining the private API structure.
28-28: Kernel parameter type correctly updated to int32The
particle_flagsparameter type is properly changed fromwp.uint32towp.int32to align with the enum implementation.
38-39: ParticleFlags enum properly used for active state checkThe active particle check correctly uses
ParticleFlags.ACTIVEbitwise operation instead of the old constant.
263-263: Docstring properly updated to plural ContactsThe documentation correctly uses "Contacts" (plural) to match the actual type name.
272-287: Documentation properly updated to reference SolverNotifyFlags enumThe
notify_model_changeddocumentation correctly references the newSolverNotifyFlagsenum values instead of the oldNOTIFY_FLAG_*constants, improving API clarity.newton/_src/solvers/mujoco/solver_mujoco.py (21)
26-42: Import paths properly converted to relative importsAll imports correctly use relative paths from the internal
_srcstructure, maintaining proper module organization.
381-411: JointType enum consistently applied in coordinate conversionThe kernel correctly uses
JointType.FREEandJointType.BALLfor joint type dispatch instead of the old constants.
568-571: JointMode enum properly used for actuator controlThe control kernel correctly checks
JointMode.NONEusing the enum instead of the old constant.
615-636: Joint type checks properly use enum valuesThe force application kernel correctly uses
JointType.FREE,JointType.DISTANCE, andJointType.BALLenums.
693-733: Forward kinematics properly handles all joint types with enumsThe FK evaluation correctly uses
JointTypeenum values for all joint type dispatch logic.
950-967: Joint mode properly checked for actuator configurationThe actuator property updates correctly use
JointMode.TARGET_POSITIONandJointMode.TARGET_VELOCITYenums.
1060-1060: Class properly renamed to SolverMuJoCoThe class name follows the new
Solver*naming convention consistently.
1077-1092: Documentation examples properly updatedAll documentation examples correctly reference
newton.solvers.SolverMuJoCowith the new naming convention.
1289-1294: Notification flags properly use SolverNotifyFlags enumThe
notify_model_changedmethod correctly usesSolverNotifyFlagsenum values instead of the old constants.
1306-1306: Static method references properly updatedAll static method calls correctly use
SolverMuJoCoinstead of the old class name.Also applies to: 1379-1379, 1424-1424
1632-1632: Default parameter properly uses geometry constantThe
maxhullvertparameter correctly defaults togeometry.MESH_MAXHULLVERTfrom the relative import.
1801-1806: Joint types properly filtered using enumThe supported joint types set correctly uses
JointTypeenum values.
1808-1823: Geometry type mappings properly use GeoType enumBoth
geom_type_mappingandgeom_type_namedictionaries correctly useGeoTypeenum values as keys.
1880-1880: Shape flags properly checked using bitwise operationsThe shape filtering correctly uses
ShapeFlags.COLLIDE_SHAPESwith bitwise AND operations.Also applies to: 1904-1904
1913-1955: Shape type checks properly use GeoType enumThe geometry creation logic correctly uses
GeoTypeenum values for all shape type checks.
2036-2172: Joint type dispatch properly uses JointType enumThe joint creation logic correctly uses
JointType.FREEandJointType.FIXEDfor type checking.
2086-2098: Joint modes properly checked for actuator configurationThe actuator setup correctly uses
JointMode.TARGET_POSITIONandJointMode.TARGET_VELOCITYenums.Also applies to: 2149-2161
2181-2207: Equality constraint types properly use EqType enumThe constraint creation correctly uses
EqType.CONNECT,EqType.JOINT, andEqType.WELDenums.
2286-2286: Static method call properly uses new class nameThe
update_mjc_datacall correctly referencesSolverMuJoCo.
2340-2343: Notification flags properly combined using bitwise ORThe flag combination correctly uses
SolverNotifyFlagsenum values with bitwise operations.
2353-2353: Function call properly uses renamed functionThe
count_rigid_contact_pointscall correctly uses the new function name from the refactored API.newton/_src/utils/import_usd.py (9)
28-32: Import organization looks goodThe 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 correctThe
Meshconstructor call correctly uses the imported class without needing the module prefix.
553-556: Enum usage is correct and consistentThe migration to
JointModeenum values (TARGET_VELOCITY,TARGET_POSITION) is properly implemented and aligns with the new API structure.
610-610: JointMode enum usage is consistentThe
JointMode.NONE,JointMode.TARGET_VELOCITY, andJointMode.TARGET_POSITIONenums are correctly used in the D6 joint configuration logic.Also applies to: 619-619, 622-622
1065-1065: LGTM - MESH_MAXHULLVERT constant properly accessedThe constant is correctly imported and accessed from the geometry module.
1089-1089: Same issue with quat_between_axes expecting Axis enumSimilar to line 364, this line passes an
Axisenum directly toquat_between_axes, which is correct sinceusd_axis_to_axis[shape_spec.axis]returns anAxisenum value.
1111-1111: Shape collision flag clearing is correctThe use of bitwise AND with the complement of
ShapeFlags.COLLIDE_SHAPESto clear the collision flag is properly implemented.
113-113: Relative import for topology utilities is appropriateThe 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 importThe 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_axesNo issues found—this import is correct.
There was a problem hiding this comment.
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 TypesThe
add_joint_d6method signature is:def add_joint_d6( self, parent: int, child: int, linear_axes: Sequence[JointDofConfig] | None = None, angular_axes: Sequence[JointDofConfig] | None = None, … ) -> intand
JointDofConfig.axisaccepts either anAxisenum or a 3-vector. To improve readability and consistency:
- Name the
parent/childarguments explicitly.- Use
newton.Axisenums 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 nullcontexttorch.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 locatepopulate_contactsinContacts. The class only preallocates fixed-size arrays—it does not auto-resize. Thus, callingContacts(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_softbased on your use case.
134-142: Fix up_axis parameter in RendererOpenGL callThe
up_axisargument expects anewton.AxisType(e.g. anAxisenum or simple"X"|"Y"|"Z"), not the string returned bystr(up_axis)(which produces"Axis.Z"). Update the call to pass the enum directly:• File:
newton/examples/example_contact_sensor.py
• Lines: 134–142self.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 ofvec_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 onsearchsortedboundary conditionsWhen
t_modequals the last time point,np.searchsortedcan returnlen(...), causing OOB onself.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: Importtransform_twistfrom publicnewton.core(notnewton.utils)
transform_twistis part of the public spatial API exposed vianewton.core(see newton/_src/core/spatial.py). Importing it fromnewton.utilsis 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.
ModelAndStateRecorderuses 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 swapsMultiple 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 churnThe 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_KINDSThe list is annotated as
list[int]but populated withJointType(IntEnum). Typing it aslist[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 functionElsewhere the file uses
JointType.*via the import; herenewton.JointType.*is used. For consistency, prefer the importedJointType.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 lightIf 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 pathsExternal 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: prefereliffor mutually exclusive joint-type branchesSince each branch returns immediately, behavior is correct; using
elifclarifies 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, andself.J_shapeare 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 scalingTaking
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
📒 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.pydownload_assetand parsers innewton/_src/utils/import_*.py- Recorder classes in
newton/_src/utils/recorder.pynewton/_src/utils/__init__.pyonly importsparse_usd(no directpxrimport at import time); USD is imported lazily inside the parser functionsMaintaining this list as the single source of truth is appropriate.
newton/tests/test_ik.py (5)
172-179: LGTM: New IKPositionObjective usage with explicit keywordsThe 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 testKeyword arguments improve clarity and reduce risk of ordering mistakes.
346-353: LGTM: Position Jacobian objective builder is explicit and consistentClear use of keywords and correct residual sizing for a 3D position residual.
369-376: LGTM: Rotation Jacobian objective builder follows the updated APIKeyword args and quaternion target preparation look correct.
395-403: Validate applicability of IKJointLimitObjective to the model under testPer 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 APISwitching to
newton.ikand importingJointTypefrom the package root is consistent with the refactor goals and improves discoverability.
94-102: LGTM: Revolute joint branch updated to JointTypeThe branch correctly uses
JointType.REVOLUTEand the builder API.
103-111: LGTM: Prismatic joint branchConsistent with the new enum and public builder API. Axis is clearly specified.
112-119: LGTM: Ball joint branchUses the new enum and public
add_joint_ball()API as expected.
138-145: LGTM: Free joint branchMatches new enum and builder API usage.
170-184: LGTM: Randomization for REVOLUTE/PRISMATIC/BALLThe ranges and quaternion construction look sound and respect expected coordinate layouts (xyzw).
217-217: Top-level export ofeval_fkis confirmed
- In
newton/__init__.py,eval_fkis 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 correctThe 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 cleanupSwitching 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 correctInstantiating newton.solvers.SolverMuJoCo matches the new naming convention and the type checks later in the example.
233-233: Correct conditional collision handlingThe 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 correctArticulationView.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 forJointType.DISTANCEtreated as 6-DOF FREEIn
jcalc_transform,jcalc_motion, andjcalc_integrate,DISTANCEis handled identically toFREE(7 coords, 6 dofs). IfDISTANCEis meant to represent a constraint limiting the distance between two points (commonly 1-DOF), this handling could be incorrect.
- If
DISTANCEhere 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.DISTANCEand whether matching FREE is by design?Also applies to: 256-276, 422-475
91-129: Ensure Warp device code supports IntEnum comparisonsWe’ve confirmed that
JointTypeis defined as anIntEnuminnewton/_src/sim/joints.py:29and that innewton/_src/solvers/featherstone/kernels.py(lines 91–129) you’re comparing anintparametertypedirectly againstJointType.*inside@wp.func. Warp may not lowerIntEnumcomparisons correctly on all backends.Action items for verification and improvement:
- Confirm that
@wp.funcbuilds and runs correctly when doingtype == 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 theifchains toif/eliffor clarity.- Double-check that treating
JointType.DISTANCEexactly 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 useUsing
SolverFeatherstonewithupdate_mass_matrix_interval=self.num_substepsaligns with the public API rename and expected configuration.
279-279: LGTM: Correct type annotation for cloth solverAnnotating
cloth_solver: SolverVBD | Nonematches the new naming and improves clarity.
282-295: Collision Detection Interval Semantics Verified
According to theSolverVBDdocstring insolver_vbd.py(around lines 2300–2304):
< 0→ one collision‐detection pass only before initialization0→ two passes (once before and once immediately after initialization)k ≥ 1→ one pass everykVBD iterationsThus, passing
-1here 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 supportThe Python
RendererOpenGLclass (aCreateSimRenderersubclass) defines an__init__that acceptsshow_joints,show_particlesand a**render_kwargscatch‐all. The example passesnear_plane,far_plane, andenable_backface_cullinginto that catch‐all, which in turn must be supported by the underlying C++/PythonOpenGLRendererconstructor.Please manually verify in the native binding or documentation that
OpenGLRendererindeed accepts these keywords to avoid runtime errors.• File: newton/examples/example_robot_manipulating_cloth.py
• Lines: 298–307
There was a problem hiding this comment.
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
📒 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.pynewton/_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.pynewton/_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.pynewton/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
_srchelpers; 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
MuJoCoSolvertoSolverMuJoCofollows the new naming convention where all solvers start withSolver*.
380-380: LGTM! Comprehensive enum adoption for joint types.The replacement of scalar constants with
JointTypeenum 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
JointModeenum 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
DISTANCEjoint type alongsideFREEjoint 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
JointTypeenum values.Also applies to: 701-701, 710-710
1288-1292: LGTM! Proper use of SolverNotifyFlags.The notification system correctly uses the new
SolverNotifyFlagsenum for flagging model changes.
1631-1631: Parameter rename aligns with Newton naming conventions.The parameter rename from
maxhullverttomesh_maxhullvertis 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
ShapeFlagsenum 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
SolverMuJoCoinstead ofMuJoCoSolver.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 handledThe
Meshclass innewton/_src/geometry/types.pyunconditionally setsself.maxhullvert(defaulting toMESH_MAXHULLVERT), and every importer (import_mjcf.py,import_urdf.py,import_usd.py) passes itsmesh_maxhullvertinto theMeshconstructor. The fallback inmaxhullvert = getattr(mesh_src, "maxhullvert", mesh_maxhullvert)is therefore never triggered in normal use and is safe as a guard. No changes are required.
ca6eff6 to
24f63b3
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🔭 Outside diff range comments (15)
newton/examples/example_mpm_granular.py (1)
225-226: Update example to importload_meshfrom its defining module
The publicnewton/utils.pydoes not re-exportload_mesh, so callingnewton.utils.load_meshwill 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 usingnoton bitwise expressions in Warp kernels; compare explicitly to zeroUsing
noton 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 * dtNote: 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 NoneIf
SDF.volumeis not provided (default None), bothfinalize()and__hash__()will raise AttributeError by accessing.id. Add a guard to fail fast infinalize()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 manifoldsContacts 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_0newton/_src/utils/recorder.py (3)
191-205: Warp array serialization drops empty arrays and loses dtype information.Current logic:
- Skips empty
wp.arrayvalues (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.transformmay 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 playbackThe current
record()(lines 230–242) only savesvalue.numpy(), andplayback()(lines 253–271) always callswp.array(value_np, device=device)without passing the original
dtype. This drops structured types likewp.transform,wp.vec3, andwp.spatial_vector, which will break kernels expecting those dtypes.Please update
newton/_src/utils/recorder.pyso that:•
record()captures both the raw data and the array’sdtype.
•playback()checks for this metadata and reconstructs viawp.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 consistencyFace 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 flagapproximation.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 meshesSame 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 solversThe
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 innewton/examples/example_selection_articulations.py(around lines 205–206):• File: newton/examples/example_selection_articulations.py
Location: inside the method whereself.use_cuda_graphis 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 asdef __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 axisdocs/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 incorrectModel.joint_axisremoval noteThe migration guide claims that
Model.joint_axiswas removed, but the code still populates and uses this attribute throughout (e.g. inbuilder.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:
“TheModel.joint_axisattribute 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 usesmodel.joint_axis.
79-91: Update migration docs for eval_ik signatureThe documentation currently shows an optional
mask=Noneforeval_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 ineval_ik).• File: docs/migration.rst
• Section: “Forward and Inverse Kinematics” tableSuggested 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 correctSwitching 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 validThe 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 conventionsImporting _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 youIncluding 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 utilstransform_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-exportsParticleFlags 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 ParticleFlagsnewton/_src/geometry/types.py (2)
127-128: Normalize vertex dtype to float32 in init for consistency with Warp and hashing
verticesare 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 MeshConverting 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_solidcan change after construction, consider clearing_cached_hashwhen 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 = Nonenewton/examples/example_anymal_c_walk_on_sand.py (3)
183-183: Be consistent with solver imports to avoid relying on package attribute side-effectsYou 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 frictionMPM’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 rateRendererOpenGL 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.
ModelandStateare only used in annotations. You can avoid importing..simat 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.warnis preferable for library code. Also, consider consistently returning an empty list instead ofNonefor 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, NoneOptional (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_statereturns None when nowp.arrayis found; thetry/except ValueErroris dead code. Make the behavior explicit and warn viawarnings.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_PROTOCOLfor 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_countUse 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 noiseThe 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 entryThe 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 conversionDistance 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 explicitThe 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 checksSame 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: continuedocs/api/newton_solvers.rst (1)
12-18: Docs list updated to new names — minor ordering nitThe 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 + SolverXPBDnewton/examples/example_cloth_style3d.py (1)
116-117: Set device explicitly when reassigning particle_flagsWithout 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 safetyTo 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 verticesThe 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.0Note: 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 = SolverStyle3Dnewton/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) # quaternionnewton/_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 assignmentThe 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 additionsSkipping 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 readyOnce 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 substepsUsing 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 typingBitwise 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_flagsAlso applies to: 162-165
879-883: Quaternion normalization for multi-DOF jointsSetting 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 intparticle_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 targetsThe guidance is correct. Consider adding a short code snippet to show setting JointMode.NONE via ModelBuilder.JointDofConfig for discoverability.
There was a problem hiding this comment.
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 provideSignature 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 = Noneand 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 positiveinv_mass, unintentionally skipping gravity on all dynamic particles. Mirror the rigid-body path and usewp.nonzero(inv_mass)so gravity applies only wheninv_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)) * dtAdditionally, 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 hintThe 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
flagsis an IntFlag bitmask (SolverNotifyFlags).
279-287: Nit: table header labelThe 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
⛔ Files ignored due to path filters (102)
docs/api/_generated/newton.Axis.rstis excluded by!**/_generated/**docs/api/_generated/newton.AxisType.rstis excluded by!**/_generated/**docs/api/_generated/newton.Contacts.rstis excluded by!**/_generated/**docs/api/_generated/newton.Control.rstis excluded by!**/_generated/**docs/api/_generated/newton.EqType.rstis excluded by!**/_generated/**docs/api/_generated/newton.GeoType.rstis excluded by!**/_generated/**docs/api/_generated/newton.JointMode.rstis excluded by!**/_generated/**docs/api/_generated/newton.JointType.rstis excluded by!**/_generated/**docs/api/_generated/newton.Mesh.rstis excluded by!**/_generated/**docs/api/_generated/newton.Model.rstis excluded by!**/_generated/**docs/api/_generated/newton.ModelBuilder.rstis excluded by!**/_generated/**docs/api/_generated/newton.ParticleFlags.rstis excluded by!**/_generated/**docs/api/_generated/newton.SDF.rstis excluded by!**/_generated/**docs/api/_generated/newton.ShapeFlags.rstis excluded by!**/_generated/**docs/api/_generated/newton.State.rstis excluded by!**/_generated/**docs/api/_generated/newton.Style3DModel.rstis excluded by!**/_generated/**docs/api/_generated/newton.Style3DModelBuilder.rstis excluded by!**/_generated/**docs/api/_generated/newton.__version__.rstis excluded by!**/_generated/**docs/api/_generated/newton.core.AxisType.rstis excluded by!**/_generated/**docs/api/_generated/newton.core.quat_between_axes.rstis excluded by!**/_generated/**docs/api/_generated/newton.core.quat_decompose.rstis excluded by!**/_generated/**docs/api/_generated/newton.core.quat_from_euler.rstis excluded by!**/_generated/**docs/api/_generated/newton.core.quat_to_euler.rstis excluded by!**/_generated/**docs/api/_generated/newton.core.quat_to_rpy.rstis excluded by!**/_generated/**docs/api/_generated/newton.core.quat_twist.rstis excluded by!**/_generated/**docs/api/_generated/newton.core.quat_twist_angle.rstis excluded by!**/_generated/**docs/api/_generated/newton.core.transform_twist.rstis excluded by!**/_generated/**docs/api/_generated/newton.core.transform_wrench.rstis excluded by!**/_generated/**docs/api/_generated/newton.core.velocity_at_point.rstis excluded by!**/_generated/**docs/api/_generated/newton.eval_fk.rstis excluded by!**/_generated/**docs/api/_generated/newton.eval_ik.rstis excluded by!**/_generated/**docs/api/_generated/newton.geometry.build_ccd_generic.rstis excluded by!**/_generated/**docs/api/_generated/newton.geometry.compute_shape_inertia.rstis excluded by!**/_generated/**docs/api/_generated/newton.geometry.compute_shape_radius.rstis excluded by!**/_generated/**docs/api/_generated/newton.geometry.create_box.rstis excluded by!**/_generated/**docs/api/_generated/newton.geometry.create_capsule.rstis excluded by!**/_generated/**docs/api/_generated/newton.geometry.create_cone.rstis excluded by!**/_generated/**docs/api/_generated/newton.geometry.create_cylinder.rstis excluded by!**/_generated/**docs/api/_generated/newton.geometry.create_none.rstis excluded by!**/_generated/**docs/api/_generated/newton.geometry.create_plane.rstis excluded by!**/_generated/**docs/api/_generated/newton.geometry.create_sphere.rstis excluded by!**/_generated/**docs/api/_generated/newton.geometry.rstis excluded by!**/_generated/**docs/api/_generated/newton.geometry.transform_inertia.rstis excluded by!**/_generated/**docs/api/_generated/newton.ik.IKJointLimitObjective.rstis excluded by!**/_generated/**docs/api/_generated/newton.ik.IKObjective.rstis excluded by!**/_generated/**docs/api/_generated/newton.ik.IKPositionObjective.rstis excluded by!**/_generated/**docs/api/_generated/newton.ik.IKRotationObjective.rstis excluded by!**/_generated/**docs/api/_generated/newton.ik.IKSolver.rstis excluded by!**/_generated/**docs/api/_generated/newton.ik.JacobianMode.rstis excluded by!**/_generated/**docs/api/_generated/newton.ik.rstis excluded by!**/_generated/**docs/api/_generated/newton.selection.ArticulationView.rstis excluded by!**/_generated/**docs/api/_generated/newton.selection.rstis excluded by!**/_generated/**docs/api/_generated/newton.sensors.ContactSensor.rstis excluded by!**/_generated/**docs/api/_generated/newton.sensors.populate_contacts.rstis excluded by!**/_generated/**docs/api/_generated/newton.sim.ModelBuilder.rstis excluded by!**/_generated/**docs/api/_generated/newton.sim.Style3DModel.rstis excluded by!**/_generated/**docs/api/_generated/newton.sim.Style3DModelBuilder.rstis excluded by!**/_generated/**docs/api/_generated/newton.sim.color_graph.rstis excluded by!**/_generated/**docs/api/_generated/newton.sim.count_rigid_contact_points.rstis excluded by!**/_generated/**docs/api/_generated/newton.sim.eval_fk.rstis excluded by!**/_generated/**docs/api/_generated/newton.sim.eval_ik.rstis excluded by!**/_generated/**docs/api/_generated/newton.sim.get_joint_dof_count.rstis excluded by!**/_generated/**docs/api/_generated/newton.sim.plot_graph.rstis excluded by!**/_generated/**docs/api/_generated/newton.solvers.SolverFeatherstone.rstis excluded by!**/_generated/**docs/api/_generated/newton.solvers.SolverImplicitMPM.rstis excluded by!**/_generated/**docs/api/_generated/newton.solvers.SolverMuJoCo.rstis excluded by!**/_generated/**docs/api/_generated/newton.solvers.SolverNotifyFlags.rstis excluded by!**/_generated/**docs/api/_generated/newton.solvers.SolverSemiImplicit.rstis excluded by!**/_generated/**docs/api/_generated/newton.solvers.SolverStyle3D.rstis excluded by!**/_generated/**docs/api/_generated/newton.solvers.SolverVBD.rstis excluded by!**/_generated/**docs/api/_generated/newton.solvers.SolverXPBD.rstis excluded by!**/_generated/**docs/api/_generated/newton.solvers.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.BasicRecorder.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.ModelAndStateRecorder.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.SimRenderer.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.boltzmann.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.check_conditional_graph_support.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.clear_git_cache.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.leaky_max.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.leaky_min.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.quat_between_axes.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.quat_decompose.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.quat_from_euler.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.quat_to_euler.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.quat_to_rpy.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.quat_twist.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.quat_twist_angle.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.smooth_max.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.topological_sort.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.transform_twist.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.transform_wrench.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.vec_abs.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.vec_leaky_min.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.vec_max.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.vec_min.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.velocity_at_point.rstis excluded by!**/_generated/**docs/api/_generated/newton.viewer.GizmoSystem.rstis excluded by!**/_generated/**docs/api/_generated/newton.viewer.RecorderImGuiManager.rstis excluded by!**/_generated/**docs/api/_generated/newton.viewer.RendererOpenGL.rstis excluded by!**/_generated/**docs/api/_generated/newton.viewer.RendererUsd.rstis excluded by!**/_generated/**docs/api/_generated/newton.viewer.rstis 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.rstdocs/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.rstnewton/_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 properlyUsing internal facades for ParticleFlags and sim types avoids leaking public facades into _src. Good separation.
28-28: Int32 dtype migration for particle_flags is correctKernel now expects wp.int32 which aligns with IntEnum/IntFlag usage and past dtype migration. LGTM.
38-41: Active-bit gating is correct and consistentSwitch 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 alignmentMatches 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 listedThe 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 correctlyBasicRecorder and ModelAndStateRecorder entries match the utils refactor.
21-30: Quaternion/transform utilities are properly surfacedThe new utility functions are listed and will be discoverable in the generated docs.
docs/generate_api.py (2)
23-23: shutil import addition is fineRequired for cleanup; no issues.
38-47: Expanded MODULES set matches the new public surfaceAdding newton, geometry, ik, selection, sensors, solvers, utils, viewer is consistent with the refactor. Good.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
docs/generate_api.py (1)
213-214: Resolved: guarded removal of OUTPUT_DIR avoids FileNotFoundErrorThis 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 levelConfirmed 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 consistencySince 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 readabilityA 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
⛔ Files ignored due to path filters (7)
docs/api/_generated/newton.CollisionPipeline.rstis excluded by!**/_generated/**docs/api/_generated/newton.count_rigid_contact_points.rstis excluded by!**/_generated/**docs/api/_generated/newton.get_joint_dof_count.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.color_graph.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.plot_graph.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.transform_inertia.rstis 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.pynewton/__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.pynewton/__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 rootYou’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 goodBasicRecorder and ModelAndStateRecorder replace the previous renderer entries cleanly and align with the new public utils surface.
21-47: Docs: functions list matches newton.utils exportsThe 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 minimalThe 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 packageI 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 cohesiveConsolidating quaternion/spatial helpers here improves discoverability and keeps callers away from _src internals.
31-34: Graph utilities re-export looks goodcolor_graph and plot_graph routed via utils is consistent with the new API surface.
@kaloca On main, I see this: All the solvers pop up, but they don't start with the name |
@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: Without the suffix it could also look like this: Not sure which of all these options is more readable though. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
newton/__init__.py (1)
51-53: Track the Style3D helpers TODOThe TODO is fair; currently exposing Style3DModel/Style3DModelBuilder at root is convenient but may not scale. Consider moving them under a
newton.style3dpublic submodule and re-exporting selectively for a gentler migration path.Would you like me to open an issue proposing
newton.style3dplus a deprecation path for root-level aliases?
📜 Review details
Configuration used: .coderabbit.yml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
docs/api/_generated/newton.sensors.rstis 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) — LGTMExposing 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 — LGTMRe-exporting Axis and AxisType at the root is consistent with the refactor goals.
26-32: Geometry flags moved to top-level as intended — LGTMParticleFlags and ShapeFlags at the root align with the public API plan (per retrieved learnings).
55-55: Version exposure — LGTMImporting
__version__from._versionand 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/simShort 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.
0d3a513 to
57acda6
Compare
There was a problem hiding this comment.
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 contextlibnewton/_src/utils/import_mjcf.py (1)
612-614: Remove debug print statementDebug 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 functionReplacing the local
spatial_adjointimplementation withwp.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 mappingsThe
GeoType.CONEenum value exists but isn't mapped ingeom_type_mappingorgeom_type_name. This will cause aKeyErrorif a cone shape is encountered during conversion.newton/_src/solvers/xpbd/kernels.py (1)
906-907: Kernel-to-public API coupling addressed appropriatelyThe imports have been successfully changed from the public
newtonpackage 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 patternThe code uses
wp.vec3(euler)directly, but based on the learned pattern from the codebase, array-like objects should be unpacked when passed towp.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 scopeThis 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.
ParticleFlagsis being used for bitwise operations (e.g.,flags[fixed_vertex_id] & ~ParticleFlags.ACTIVE) in the codebase. UsingIntFlaginstead ofIntEnumwould be more semantically correct for flags that are meant to be combined with bitwise operations.Based on the retrieved learnings, I understand that
IntEnumis 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,ShapeFlagsappears 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 codeLines 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 codeLines 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 codeLines 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 consolidationThe 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 locationThe table shows
SimRendererinnewton.utils, but based on the code review, the actual renderers (RendererUsd,RendererOpenGL) are innewton.viewer. Consider clarifying thatSimRendereris just an alias.Consider adding a note that
SimRendereris an alias forRendererUsdfor backward compatibility:|``warp.sim.render.SimRenderer`` |:class:`newton.utils.SimRenderer` | +| | (alias for :class:`newton.viewer.RendererUsd`)|
There was a problem hiding this comment.
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 supportUnconditional 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.ikfor the public API and uses the private_eval_fk_batchedfromnewton._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 polishRename 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.rstis 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.pynewton/tests/test_ik.pynewton/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.iktonewton.ikcorrectly aligns with the public API refactor.
170-170: LGTM! IK objective class names properly updated.The renaming from
PositionObjectivetoIKPositionObjectiveis consistent with the public API refactor.
174-174: LGTM! IK rotation objective properly renamed.The renaming from
RotationObjectivetoIKRotationObjectivealigns with the public API changes.
186-186: LGTM! IK joint limit objective properly renamed.The renaming from
JointLimitObjectivetoIKJointLimitObjectiveis consistent with the public API refactor.
203-203: LGTM! Jacobian mode properly updated.The change from
JacobianMode.ANALYTICtoIKJacobianMode.ANALYTICcorrectly reflects the enum renaming.
216-216: LGTM! Forward kinematics function path correctly updated.The change from
newton.sim.eval_fktonewton.eval_fkproperly 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
JacobianModetoIKJacobianModeprovides 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.kernelsto relative import..solvers.featherstone.kernelscorrectly reflects the internal_srcstructure.
966-966: LGTM! Class renamed for clarity.The renaming from
PositionObjectivetoIKPositionObjectiveprovides clearer context that this is an IK-specific objective.
1200-1200: LGTM! Joint limit objective properly renamed.The renaming from
JointLimitObjectivetoIKJointLimitObjectivemaintains consistency with other IK objective class names.
1428-1428: LGTM! Rotation objective consistently renamed.The renaming from
RotationObjectivetoIKRotationObjectivealigns with the IK-prefixed naming convention.
57-58: LGTM! Jacobian mode references consistently updated throughout.All references to
JacobianModehave been properly updated toIKJacobianModethroughout 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:
IKJacobianModeinstead ofJacobianModeIKPositionObjective,IKRotationObjective,IKJointLimitObjectiveinstead of the unprefixed versions- Direct
_eval_fk_batchedcalls instead ofik._eval_fk_batchedThe 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: LGTMHeader 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 suggestedQuick 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, butgetattr(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 TrueIf 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.
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
90a2b50 to
0d810de
Compare
There was a problem hiding this comment.
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"] = sizedocs/migration.rst (1)
56-76: Replace removed ShapeGeometry.is_solid reference with Model.shape_is_solidThe 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 updatingSearch 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 correctlyThe solver import has been properly updated from
ImplicitMPMSolvertoSolverImplicitMPMfollowing the new naming convention, and the renderer path changed tonewton.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 validquat_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
newtonimports for core classes (Model,ModelBuilder,State,eval_fk)newton.solversfor solver classes (SolverFeatherstone,SolverVBD)newton.utilsfor thetransform_twistutility functionThis 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/docsGood 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)' || trueIf 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 surfaceThe 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 statusExplicitly 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 belowShort 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 mainVerification 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 FalseOptionally, 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 = NoneThen use
_mouseif 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 FalseIf 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 = 1Then 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 clarityConsider 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 documentedAdding 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 nitInstantiations 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 meshesThe 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 maxhullvertEven 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 keepimport newtonlightweightDirectly importing
viewerandsensorsat 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
viewerandsensors:-# submodule APIs -from . import geometry, ik, selection, sensors, solvers, utils, viewer +# submodule APIs +from . import geometry, ik, selection, solvers, utilsThen, 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 whetherviewer/sensorshave 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 APIEither move these helpers into a public
newton.style3dsubmodule 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.
|
|
||
| # ================================================================================== | ||
| # Style3D helpers | ||
| # TODO: eliminate these and roll the functionality into Model and ModelBuilder? |
There was a problem hiding this comment.
@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.
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>
…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>
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>





Description
Code refactor to improve the public API clarity, discoverability, and consistency.
newton/_src, thus it is considered private. I left the structure mostly unchanged there, for now.newton/__init__.pydefines all the public entry points at the top level (newton.*).newton/ik.py,newton/solvers.py, etc. These define public entry points likenewton.ik.*ornewton.solvers.*. Here, we simply import the symbols from the_srctree that we wish to expose publicly. This way, there is no risk of "leaking" unwanted symbols from the private_srctree, and adding things to the public API becomes a deliberate act.Solver*.JOINT_TYPE_*were converted toIntEnumorIntFlag, as appropriate.Note that this is still a work in progress, but the basic structure is there. Still to do:
generate_api.py.Newton Migration Guide
Please ensure the migration guide for warp.sim users is up-to-date with the changes made in this MR.
docs/migration.rstis up-to dateBefore your PR is "Ready for review"
newton/tests/test_examples.py)pre-commit run -aSummary by CodeRabbit
New Features
Refactor
Documentation
Chores