Update robot examples#626
Conversation
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
… shapes Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
📝 WalkthroughWalkthroughAdds ModelBuilder.add_urdf/add_usd/add_mjcf, moves importer functions to builder-first signatures, migrates examples/tests/benchmarks to a viewer-driven runner, adds new robot examples, removes older example scripts, gates geometry diagnostic prints behind verbosity, and normalizes numeric types in the implicit MPM solver. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Client as Client Code
participant MB as ModelBuilder
participant Parser as parse_usd/urdf/mjcf
participant Model as Model
User->>Client: request model load
Client->>MB: add_usd(source, options)
MB->>Parser: parse_usd(builder=self, source, ...)
Parser->>MB: mutate builder (bodies, joints, shapes)
Parser-->>MB: return results dict (optional)
MB-->>Client: return results (if provided)
Client->>Model: finalize()
sequenceDiagram
autonumber
actor User
participant Runner as newton.examples.run
participant Ex as Example (viewer-based)
participant Viewer as Viewer
participant Solver as SolverMuJoCo/...
participant Model as Model
User->>Runner: init & run example
Runner->>Ex: __init__(viewer)
Ex->>Viewer: set_model(model)
Ex->>Ex: capture() (optional CUDA graph)
loop per frame
Runner->>Ex: step()
alt Graph captured
Ex->>Ex: launch captured graph
else
Ex->>Viewer: apply_forces(state_0)
Ex->>Model: collide(state_0)
Ex->>Solver: step(model, state_0, state_1, control)
Ex->>Ex: swap states
end
Runner->>Ex: render()
Ex->>Viewer: begin_frame / log_state / log_contacts / end_frame
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Suggested reviewers
✨ 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: 5
🧹 Nitpick comments (15)
newton/_src/geometry/kernels.py (1)
1582-1583: Consider verbosity-gating instead of fully silencing this diagnosticThe generic unsupported-pair message can be useful when debugging new geometry types. Suggest gating it behind
wp.config.verbosewithwp.printfinstead of commenting it out.- # print("Unsupported geometry pair in collision handling") + # if wp.static(wp.config.verbose): + # wp.printf("handle_contact_pairs: unsupported geometry pair %i and %i\n", geo_type_a, geo_type_b)newton/examples/robot/example_quadruped.py (1)
68-75: Use math for host-side grid math (avoid Warp scalars on host)Minor:
wp.sqrt/wp.ceilare unnecessary here and can be slower on host. Usemathfor clarity and to avoid any device-type ambiguity.- spacing = 3.0 - sqn = int(wp.ceil(wp.sqrt(float(self.num_envs)))) + import math + spacing = 3.0 + sqn = int(math.ceil(math.sqrt(float(self.num_envs))))Additionally, remember to add
import mathat the top of the file if you prefer that style:@@ -import warp as wp +import math +import warp as wpnewton/examples/__init__.py (1)
36-43: Optional: Make timing scopes user-toggleable
ScopedTimer(..., active=False)disables timing by default. Consider wiring this to a CLI flag or env var (e.g.,NEWTON_PROFILE=1) so developers can enable profiling without code edits.newton/examples/robot/example_h1.py (3)
117-129: Recompute contacts each substep to avoid stale contact pairsCurrently, contacts are computed once per frame and reused across substeps, which can miss new contacts after integration within the same frame. Recompute inside the loop.
- self.contacts = self.model.collide(self.state_0) - for _ in range(self.sim_substeps): + for _ in range(self.sim_substeps): self.state_0.clear_forces() # apply forces to the model for picking, wind, etc self.viewer.apply_forces(self.state_0) + # refresh contacts per substep + self.contacts = self.model.collide(self.state_0) self.solver.step(self.state_0, self.state_1, self.control, self.contacts, self.sim_dt) # swap states self.state_0, self.state_1 = self.state_1, self.state_0
111-115: Use cached device handleYou already cache the device as self.device; use it here for consistency and micro-avoidance of API calls.
- if wp.get_device().is_cuda: + if self.device.is_cuda: with wp.ScopedCapture() as capture: self.simulate() self.graph = capture.graph
82-89: Use example helper to place environmentsnewton.examples.compute_env_offsets provides a consistent grid placement and keeps layout logic centralized.
- spacing = 3.0 - sqn = int(wp.ceil(wp.sqrt(float(self.num_envs)))) - - builder = newton.ModelBuilder(up_axis=newton.Axis.Z) - for i in range(self.num_envs): - pos = wp.vec3((i % sqn) * spacing, (i // sqn) * spacing, 0) - builder.add_builder(articulation_builder, xform=wp.transform(pos, wp.quat_identity())) + spacing = 3.0 + positions = newton.examples.compute_env_offsets(self.num_envs, env_offset=(spacing, spacing, 0.0)) + + builder = newton.ModelBuilder(up_axis=newton.Axis.Z) + for i in range(self.num_envs): + builder.add_builder(articulation_builder, xform=wp.transform(positions[i], wp.quat_identity()))newton/examples/robot/example_anymal_d.py (3)
118-129: Refresh contacts within substepsSame rationale as other examples: compute contacts after applying forces in each substep.
- self.contacts = self.model.collide(self.state_0) - for _ in range(self.sim_substeps): + for _ in range(self.sim_substeps): self.state_0.clear_forces() # apply forces to the model for picking, wind, etc self.viewer.apply_forces(self.state_0) + self.contacts = self.model.collide(self.state_0) self.solver.step(self.state_0, self.state_1, self.control, self.contacts, self.sim_dt) # swap states self.state_0, self.state_1 = self.state_1, self.state_0
111-116: Use cached device handleMinor consistency/clarity improvement.
- if wp.get_device().is_cuda: + if self.device.is_cuda: with wp.ScopedCapture() as capture: self.simulate() self.graph = capture.graph
91-95: Verify cone fallback mapping and module dependency
- The SolverMuJoCo constructor already accepts both the strings
"pyramidal"and"elliptic"(in addition to the integer constants) and maps them via_resolve_mj_opt, so passing"elliptic"will work as expected.- However,
solver_mujoco.pystill does a top-levelimport mujocoand directly referencesmujoco.mjtCone.mjCONE_PYRAMIDAL/mjCONE_ELLIPTICin its mapping dict, so the module cannot be imported without MuJoCo installed.- If the goal is truly to avoid a hard dependency on MuJoCo at import time (even when using string values), consider deferring the import or attribute lookup for
mujoco.mjtConeinside__init__(e.g. within a try/except) so that consumers who only refer to SolverMuJoCo by string names aren’t forced to install MuJoCo.newton/examples/robot/example_g1.py (2)
71-75: Be explicit about up_axis on the aggregate builderOther examples pass up_axis=Axis.Z explicitly; do the same here to avoid surprises if the default changes.
- builder = newton.ModelBuilder() + builder = newton.ModelBuilder(up_axis=newton.Axis.Z)
114-125: Recompute contacts in the substep loopKeeps contact pairs/normals consistent with the latest state.
- self.contacts = self.model.collide(self.state_0) - for _ in range(self.sim_substeps): + for _ in range(self.sim_substeps): self.state_0.clear_forces() # apply forces to the model for picking, wind, etc self.viewer.apply_forces(self.state_0) + self.contacts = self.model.collide(self.state_0) self.solver.step(self.state_0, self.state_1, self.control, self.contacts, self.sim_dt) # swap states self.state_0, self.state_1 = self.state_1, self.state_0newton/examples/robot/example_humanoid.py (2)
19-21: Comment mismatch: this example uses MJCF, not USDUpdate the header to avoid confusing readers.
-# Shows how to set up a simulation of a humanoid articulation -# from a USD stage using newton.ModelBuilder(). +# Shows how to set up a simulation of a humanoid articulation +# from an MJCF asset using newton.ModelBuilder().
112-124: Refresh contacts each substepEnsure contact computation reflects the latest integrated configuration.
- self.contacts = self.model.collide(self.state_0) - for _ in range(self.sim_substeps): + for _ in range(self.sim_substeps): self.state_0.clear_forces() # apply forces to the model for picking, wind, etc self.viewer.apply_forces(self.state_0) + self.contacts = self.model.collide(self.state_0) self.solver.step(self.state_0, self.state_1, self.control, self.contacts, self.sim_dt) # swap states self.state_0, self.state_1 = self.state_1, self.state_0newton/examples/robot/example_anymal_c_walk.py (2)
151-153: Strip trailing whitespace on blank lines (Ruff W293)Minor formatting cleanup to satisfy linters.
- - - + + +
198-207: Avoid redundant collide call; compute once per substep with consistent marginYou call collide before the loop and again inside. Drop the outer call and keep a single, consistent collide inside the loop (preserving the margin you used initially).
- self.contacts = self.model.collide(self.state_0, rigid_contact_margin=0.1) - for _ in range(self.sim_substeps): + # contacts are recomputed within the substep loop + for _ in range(self.sim_substeps): self.state_0.clear_forces() # apply forces to the model self.viewer.apply_forces(self.state_0) - self.contacts = self.model.collide(self.state_0) + self.contacts = self.model.collide(self.state_0, rigid_contact_margin=0.1) self.solver.step(self.state_0, self.state_1, self.control, self.contacts, self.sim_dt)
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (14)
newton/_src/geometry/kernels.py(4 hunks)newton/_src/utils/import_usd.py(4 hunks)newton/examples/__init__.py(2 hunks)newton/examples/example_anymal_c_walk.py(0 hunks)newton/examples/example_h1.py(0 hunks)newton/examples/example_humanoid.py(0 hunks)newton/examples/example_quadruped.py(0 hunks)newton/examples/robot/example_anymal_c_walk.py(7 hunks)newton/examples/robot/example_anymal_d.py(2 hunks)newton/examples/robot/example_cartpole.py(2 hunks)newton/examples/robot/example_g1.py(3 hunks)newton/examples/robot/example_h1.py(1 hunks)newton/examples/robot/example_humanoid.py(1 hunks)newton/examples/robot/example_quadruped.py(1 hunks)
💤 Files with no reviewable changes (4)
- newton/examples/example_anymal_c_walk.py
- newton/examples/example_quadruped.py
- newton/examples/example_humanoid.py
- newton/examples/example_h1.py
🧰 Additional context used
🧠 Learnings (2)
📚 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/_src/geometry/kernels.py
📚 Learning: 2025-08-19T12:27:30.629Z
Learnt from: preist-nvidia
PR: newton-physics/newton#579
File: newton/examples/example_mujoco.py:350-354
Timestamp: 2025-08-19T12:27:30.629Z
Learning: In Newton examples, there's a distinction between solver parameters and Example class attributes. The Example class can have its own use_mujoco attribute for controlling example-level behavior (like CUDA graphs, rendering logic), while the solver uses use_mujoco_cpu for backend selection. These serve different purposes and should not be conflated during API renames.
Applied to files:
newton/examples/robot/example_anymal_d.pynewton/examples/robot/example_g1.py
🧬 Code graph analysis (8)
newton/examples/__init__.py (1)
newton/examples/robot/example_anymal_c_walk.py (1)
step(212-236)
newton/examples/robot/example_humanoid.py (2)
newton/examples/__init__.py (4)
get_asset(32-33)create_parser(97-123)init(126-167)run(36-44)newton/_src/utils/import_mjcf.py (1)
parse_mjcf(32-931)
newton/examples/robot/example_h1.py (6)
newton/_src/sim/builder.py (5)
ModelBuilder(67-4215)JointDofConfig(195-264)collapse_fixed_joints(1801-2098)approximate_meshes(2554-2756)add_ground_plane(2267-2287)newton/_src/utils/import_usd.py (1)
parse_usd(34-1299)newton/_src/solvers/mujoco/solver_mujoco.py (1)
SolverMuJoCo(1096-2569)newton/_src/sim/model.py (2)
state(440-474)control(476-509)newton/_src/viewer/viewer.py (1)
log_contacts(101-162)newton/examples/__init__.py (3)
create_parser(97-123)init(126-167)run(36-44)
newton/examples/robot/example_quadruped.py (5)
newton/_src/sim/builder.py (2)
ModelBuilder(67-4215)add_ground_plane(2267-2287)newton/_src/sim/joints.py (1)
JointMode(81-95)newton/examples/__init__.py (4)
get_asset(32-33)create_parser(97-123)init(126-167)run(36-44)newton/_src/sim/model.py (2)
state(440-474)control(476-509)newton/_src/viewer/viewer.py (1)
log_contacts(101-162)
newton/examples/robot/example_cartpole.py (3)
newton/examples/robot/example_anymal_d.py (4)
capture(110-115)simulate(118-129)step(131-137)render(139-143)newton/_src/viewer/viewer.py (1)
log_contacts(101-162)newton/examples/__init__.py (3)
create_parser(97-123)init(126-167)run(36-44)
newton/examples/robot/example_anymal_d.py (6)
newton/examples/robot/example_anymal_c_walk.py (5)
Example(77-242)capture(188-196)simulate(198-210)step(212-236)render(238-242)newton/examples/robot/example_cartpole.py (6)
Example(31-127)capture(92-97)simulate(100-110)step(112-118)render(120-124)test(126-127)newton/_src/sim/state.py (1)
clear_forces(64-75)newton/_src/sim/model.py (1)
control(476-509)newton/_src/viewer/viewer.py (1)
log_contacts(101-162)newton/examples/__init__.py (3)
create_parser(97-123)init(126-167)run(36-44)
newton/examples/robot/example_g1.py (5)
newton/_src/sim/builder.py (2)
ModelBuilder(67-4215)approximate_meshes(2554-2756)newton/_src/sim/model.py (2)
state(440-474)control(476-509)newton/_src/sim/state.py (1)
clear_forces(64-75)newton/_src/viewer/viewer.py (1)
log_contacts(101-162)newton/examples/__init__.py (3)
create_parser(97-123)init(126-167)run(36-44)
newton/examples/robot/example_anymal_c_walk.py (2)
newton/examples/robot/example_anymal_d.py (5)
capture(110-115)step(131-137)simulate(118-129)render(139-143)Example(34-146)newton/examples/__init__.py (2)
init(126-167)run(36-44)
🪛 Ruff (0.12.2)
newton/examples/robot/example_anymal_c_walk.py
151-151: Blank line contains whitespace
Remove whitespace from blank line
(W293)
152-152: Blank line contains whitespace
Remove whitespace from blank line
(W293)
🔇 Additional comments (12)
newton/_src/geometry/kernels.py (1)
1110-1111: LGTM: Unsupported geometry pair message is now verbosity-gated and kernel-safeGood move switching to
wp.printfunderwp.static(wp.config.verbose). This avoids noisy logs while remaining device-compatible.newton/_src/utils/import_usd.py (2)
1026-1027: Plumbing is_visible into collision shape config is correctPassing
is_visible=not hide_collision_shapesprovides the expected toggle without impacting collision behavior.
53-55: No API break risk found:hide_collision_shapesinsertion is safe
Scan of allparse_usdcall sites (in tests and examples) shows that every invocation either uses only the two required positional arguments or specifies additional parameters (e.g.collapse_fixed_joints,mesh_maxhullvert,xform,ignore_paths,enable_self_collisions) by keyword. No calls passmesh_maxhullvertor any later parameter positionally, so insertinghide_collision_shapesbeforemesh_maxhullvertwill not affect existing callers.newton/examples/robot/example_quadruped.py (1)
96-101: LGTM: CUDA graph capture is correctly isolatedClean separation into
capture()and guarded byis_cuda. This aligns with the new example pattern.newton/examples/robot/example_cartpole.py (1)
32-47: LGTM: Viewer-centric API and timing fields are consistent with the new patternConstructor signature, time management, and storing the viewer reference look good and match the refactor across examples.
newton/examples/__init__.py (1)
186-193: LGTM: Example map extended with robot examplesNice consolidation. The new entries align with the viewer-driven workflow.
newton/examples/robot/example_h1.py (1)
148-161: Viewer-driven CLI wiring looks goodEntrypoint aligns with the unified runner (create_parser/init/run). Clean and consistent with other examples.
newton/examples/robot/example_anymal_d.py (1)
149-162: Viewer-centric main path LGTMThe CLI hooks and run loop match the new examples infrastructure.
newton/examples/robot/example_g1.py (2)
57-67: USD import and visibility flags look correctUsing hide_collision_shapes=True with approximate_meshes("bounding_box") aligns with the updated visibility flags and mesh simplification pipeline.
79-90: All SolverMuJoCo parameters validated – no changes required
- The
solver="newton"string is accepted and mapped via_resolve_mj_opttomujoco.mjtSolver.mjSOL_NEWTON(lines 1722–1724).- The
cone="elliptic"string is accepted and mapped via_resolve_mj_opttomujoco.mjtCone.mjCONE_ELLIPTIC(lines 1735–1737).- Both
nefc_per_env(default 100) andncon_per_env(default None) are parameters in the constructor (lines 1162–1163) and correctly drive the maximum constraint counts (njmaxandnconmax) in the MuJoCo-Warp data upload (lines 2351–2356).No further action needed.
newton/examples/robot/example_humanoid.py (1)
143-156: Unified CLI/viewer flow LGTMConstructor signature and main follow the standard pattern and integrate cleanly with newton.examples.
newton/examples/robot/example_anymal_c_walk.py (1)
246-253: Viewer-centered main path LGTMModernized entrypoint using newton.examples.init/run is consistent and clear.
Signed-off-by: Gilles Daviet <gdaviet@nvidia.com>
Signed-off-by: Gilles Daviet <gdaviet@nvidia.com>
Signed-off-by: Gilles Daviet <gdaviet@nvidia.com>
Signed-off-by: Gilles Daviet <gdaviet@nvidia.com>
Signed-off-by: Gilles Daviet <gdaviet@nvidia.com>
Signed-off-by: Gilles Daviet <gdaviet@nvidia.com>
Signed-off-by: Gilles Daviet <gdaviet@nvidia.com>
…amples Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
|
|
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
newton/examples/__init__.py (2)
186-192: Future-proof discovery: consider plugin-style example registration.Manual upkeep of example_map doesn’t scale. Consider discovering examples via entry points (setuptools) or a lightweight registry (e.g., modules exporting
EXAMPLE_NAMEor anregister_example()call in module import). This decouples updates in newton/examples/robot from this central map and simplifies community contributions.
38-42: Gate and de-conflict outer-loop timers across all examplesOuter timers in
newton/examples/__init__.pyare currently hard-disabled (active=False), but if ever enabled they will collide and double-count with the per-exampleScopedTimer("step")/("render")calls scattered throughout the examples. To avoid misleading timings:
- Introduce a single toggle (e.g. via
NEWTON_TIMINGorexample.enable_timing)- Rename the outer-loop timers to distinct names (e.g.
"frame.step"and"frame.render")- Ensure the viewer always closes by wrapping the loop in a
try…finallyPinpointed collisions (you’ll need to apply the same flag/renaming in each):
- newton/examples/robot/example_anymal_c_walk.py (lines 213–214)
- newton/examples/example_robot_manipulating_cloth.py (lines 572–573)
- newton/examples/example_rigid_force.py (lines 84 and 95–96)
- newton/examples/example_contact_sensor.py (lines 260–261 and 315–316)
- newton/examples/example_cloth_self_contact.py (line 266)
- newton/examples/example_anymal_c_walk_on_sand.py (lines 236–237 and 251–252)
Suggested patch for
newton/examples/__init__.py(apply analogous changes in the other files):def run(example): - while example.viewer.is_running(): - with wp.ScopedTimer("step", active=False): - example.step() - - with wp.ScopedTimer("render", active=False): - example.render() - - example.viewer.close() + enable_timing = ( + os.environ.get("NEWTON_TIMING", "0").lower() in ("1","true","yes") + or getattr(example, "enable_timing", False) + ) + try: + while example.viewer.is_running(): + with wp.ScopedTimer("frame.step", active=enable_timing): + example.step() + with wp.ScopedTimer("frame.render", active=enable_timing): + example.render() + finally: + example.viewer.close()
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
newton/examples/__init__.py(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
newton/examples/__init__.py (1)
newton/examples/robot/example_anymal_c_walk.py (1)
step(212-236)
⏰ 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). (3)
- GitHub Check: Run GPU Benchmarks (Pull Request)
- GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
- GitHub Check: run-newton-tests / newton-unittests (windows-latest)
🔇 Additional comments (1)
newton/examples/__init__.py (1)
186-192: All robot example modules are present and correctly mapped.I’ve verified that each
newton.examples.robot.example_*entry inexample_maphas a corresponding file undernewton/examples/robot/:
- example_anymal_c_walk.py
- example_anymal_d.py
- example_cartpole.py
- example_g1.py
- example_h1.py
- example_humanoid.py
- example_quadruped.py
No missing modules were found, so runtime imports via
python -m newton.examples <name>should succeed.(Optional) You may still consider:
- Alphabetizing the
example_mapkeys for cleaner diffs.- Adding a CI smoke test that runs each example with
--viewer null --num-frames 1to catch import/runtime issues early.
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (7)
newton/examples/example_ik_interactive.py (1)
498-513: Fix crash when running with --stage-path=None (renderer is optional in this example).run() unconditionally calls self.renderer.is_running(). If the user passes "--stage-path None" (the parser converts the string "None" to None), self.renderer will be None and this will raise an AttributeError at runtime.
Patch to make run() resilient to a missing renderer:
def run(self): - # initial solve so joints match targets before first frame - self._solve() - - while self.renderer.is_running(): + # initial solve so joints match targets before first frame + self._solve() + + # allow running headless (no renderer) when --stage-path=None + if self.renderer is None: + return + + while self.renderer.is_running(): self.sim_time += self.frame_dt # process any pending GUI events self._mouse_press_handler.flush() self._mouse_drag_handler.flush() self._mouse_release_handler.flush() self._render_frame() - self.renderer.close() + # guard close in case renderer was externally disposed + if self.renderer is not None: + self.renderer.close()newton/tests/test_examples.py (1)
52-61: Fix boolean CLI option duplication (causes commands like--headless --headless True)
_build_command_line_optionscurrently appends both a flag-style boolean and a--key valuepair for booleans due to the secondiffalling through to theelse. This creates duplicate/conflicting args and was observed in CI logs.Apply:
- for key, value in test_options.items(): - if isinstance(value, bool): - # Default behavior expecting argparse.BooleanOptionalAction support - additional_options.append(f"--{'no-' if not value else ''}{key.replace('_', '-')}") - if isinstance(value, list): - additional_options.extend([f"--{key.replace('_', '-')}"] + [str(v) for v in value]) - else: - # Just add --key value - additional_options.extend(["--" + key.replace("_", "-"), str(value)]) + for key, value in test_options.items(): + if isinstance(value, bool): + # BooleanOptionalAction: emit --key or --no-key only + additional_options.append(f"--{'no-' if not value else ''}{key.replace('_', '-')}") + elif isinstance(value, list): + additional_options.extend([f"--{key.replace('_', '-')}"] + [str(v) for v in value]) + else: + # Just add --key value + additional_options.extend(["--" + key.replace("_", "-"), str(value)])newton/_src/utils/import_mjcf.py (3)
413-429: Cylinders are added as capsules; wrong primitive typeIn the “cylinder” branch, we call add_shape_capsule. This will generate incorrect geometry and inertia.
Apply this diff to add cylinders correctly:
- 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, **shape_kwargs, ) shapes.append(s) else: s = builder.add_shape_capsule( xform=tf, radius=geom_radius, half_height=geom_height, **shape_kwargs, ) shapes.append(s)
582-589: Base-joint dict uses ‘child=root’ instead of ‘child=link’Passing the XML root as the child body is a logic bug. The ‘child’ should be the current body index ‘link’.
Apply this diff:
- base_joint["child"] = root + base_joint["child"] = link
210-221: Vector expansion for single-value attributes is hardcoded to 3 componentsWhen out has length 1, you construct a vector with len(default) components but pass exactly three arguments. This breaks when default is not length 3 (e.g., 2 or 6).
Apply this generalized construction:
- length = len(out) - if length == 1: - return wp.vec(len(default), wp.float32)(out[0], out[0], out[0]) - - return wp.vec(length, wp.float32)(out) + length = len(out) + if length == 1: + n = len(default) + return wp.vec(n, wp.float32)(*([out[0]] * n)) + # pass components explicitly for stability + return wp.vec(length, wp.float32)(*list(out))newton/_src/utils/import_usd.py (1)
134-141: Approximation mapping key won’t match due to lowercasingYou lowercase “physics:approximation” values before lookup, but the mapping has “meshSimplification” in mixed case, so it will never match.
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", }docs/migration.rst (1)
128-129: Inaccurate parameter rename: add_joint_ uses “target”, not “action”*The codebase still uses
target(andtarget_ke/target_kd/mode) for joint actuation onModelBuilder.add_joint_*. There is noactionparameter in the current API.Update the migration row to reflect the actual API to avoid breaking user code during migration:
-| ``ModelBuilder.add_joint_*(..., target=...)`` | ``ModelBuilder.add_joint_*(..., action=...)`` | +| ``ModelBuilder.add_joint_*(..., target=...)`` | ``ModelBuilder.add_joint_*(..., target=...)`` |If there’s a planned rename to
action, consider deferring the doc change until the API change lands, or ship a backward-compatible alias.
♻️ Duplicate comments (3)
newton/examples/robot/example_cartpole.py (1)
119-123: contacts=None but still passed to viewer.log_contacts; this will raise at runtime.Viewer.log_contacts dereferences contacts.rigid_contact_count. When contacts is None (as set in init), this will throw. Either compute contacts or guard the call.
Apply this diff to guard logging:
def render(self): - self.viewer.begin_frame(self.sim_time) - self.viewer.log_state(self.state_0) - self.viewer.log_contacts(self.contacts, self.state_0) - self.viewer.end_frame() + self.viewer.begin_frame(self.sim_time) + self.viewer.log_state(self.state_0) + if self.contacts is not None: + self.viewer.log_contacts(self.contacts, self.state_0) + self.viewer.end_frame()newton/_src/utils/import_usd.py (1)
314-319: Instance handling skips distinct instances; resolve to instance proxiesCurrent code iterates prototype children and reuses prototype child paths. Keys/path_name collide across instances, causing dropped visual shapes.
Apply this diff to remap prototype child paths via instance proxies:
- if prim.IsInstance(): - proto = prim.GetPrototype() - for child in proto.GetChildren(): - load_visual_shapes(parent_body_id, child, xform) - return + if prim.IsInstance(): + proto = prim.GetPrototype() + for child in proto.GetChildren(): + inst_path = child.GetPath().ReplacePrefix(proto.GetPath(), prim.GetPath()) + inst_child = stage.GetPrimAtPath(inst_path) + load_visual_shapes(parent_body_id, inst_child, xform) + returnnewton/examples/robot/example_humanoid.py (1)
48-56: Bug: re-initializing the builder drops configured defaults before add_mjcf()You configure articulation_builder (up_axis, joint/shape cfg), then overwrite it with a new ModelBuilder(), losing those settings.
Apply this diff to keep the configured builder:
- articulation_builder = newton.ModelBuilder() - - articulation_builder.add_mjcf( + articulation_builder.add_mjcf( mjcf_filename, ignore_names=["floor", "ground"], up_axis="Z", )If you intended a fresh builder, re-apply the defaults to the new instance before add_mjcf.
Also applies to: 59-66
🧹 Nitpick comments (34)
newton/examples/example_ik_interactive.py (5)
161-164: API migration looks correct; minor nit: pass str() to add_mjcf to avoid potential pathlib issues.ModelBuilder.add_mjcf docs show a str parameter. pathlib.Path typically works, but explicitly converting keeps the call-site robust across different backends.
- articulation_builder.add_mjcf( - newton.utils.download_asset("unitree_h1") / "mjcf/h1_with_hand.xml", + articulation_builder.add_mjcf( + str(newton.utils.download_asset("unitree_h1") / "mjcf/h1_with_hand.xml"), floating=floating, )
275-277: Guard quaternion normalization against divide-by-zero.tf[3:7] should be a valid quaternion, but numerical issues or malformed assets could yield near-zero norms, producing NaNs.
- quat = tf[3:7] / np.linalg.norm(tf[3:7]) + q = tf[3:7] + n = np.linalg.norm(q) + quat = (q / n) if n > 1.0e-12 else np.array([0.0, 0.0, 0.0, 1.0], dtype=np.float32)
399-421: Tied-position drag reinitializes every event; verify this is intended.Because _is_dragging_pos is set back to False at the end of the handler, each subsequent drag event re-captures _drag_start_positions. This yields incremental deltas frame-to-frame rather than a delta from the original mouse-down. If you intended a "one capture on press, accumulate until release" behavior, keep _is_dragging_pos True until mouse release (see _mouse_release_handler). Otherwise, ignore this note.
423-476: Same observation for rotation drag: state resets every event.As above, _is_dragging_rot is immediately reset to False, which re-captures start state on the next event. Confirm that per-frame incremental rotation is desired. If not, track the drag session across events and clear flags on mouse release only.
520-556: Optional: align entrypoint with centralized examples init/run.Per the repository’s examples pattern (newton.examples.create_parser/init/run), consider migrating this script’s main() to that framework for consistency, device handling, and viewer selection parity with other examples.
If desired, I can draft a minimal refactor that keeps gizmo behavior while using newton.examples.init() to create the viewer.
newton/examples/robot/example_anymal_c_walk.py (1)
178-189: Avoid hard-coding joint_target length (18); derive it from the model/control.Hard-coding 18 couples the example to a specific URDF. If the articulation changes, this breaks graph capture. Use control.joint_target length (or model.joint_coord_count) to size the tensor.
def capture(self): if self.device.is_cuda: - torch_tensor = torch.zeros(18, device=self.torch_device, dtype=torch.float32) - self.control.joint_target = wp.from_torch(torch_tensor, dtype=wp.float32, requires_grad=False) + n = int(self.control.joint_target.shape[0]) # robust to model changes + torch_tensor = torch.zeros(n, device=self.torch_device, dtype=torch.float32) + self.control.joint_target = wp.from_torch(torch_tensor, dtype=wp.float32, requires_grad=False) with wp.ScopedCapture() as capture: self.simulate() self.graph = capture.graph else: self.graph = Nonenewton/examples/robot/example_g1.py (1)
113-125: Update contacts each substep for accurate contact visualization.Currently, contacts are computed once per frame, before substeps. Viewer.log_contacts will then render normals from a pre-step state. If you want contact lines to reflect the latest state at frame end, recompute inside the substep loop.
- def simulate(self): - self.contacts = self.model.collide(self.state_0) - for _ in range(self.sim_substeps): + def simulate(self): + for _ in range(self.sim_substeps): self.state_0.clear_forces() # apply forces to the model for picking, wind, etc self.viewer.apply_forces(self.state_0) - self.solver.step(self.state_0, self.state_1, self.control, self.contacts, self.sim_dt) + # refresh contacts for the current substep state + self.contacts = self.model.collide(self.state_0) + self.solver.step(self.state_0, self.state_1, self.control, self.contacts, self.sim_dt) # swap states self.state_0, self.state_1 = self.state_1, self.state_0newton/tests/test_kinematics.py (1)
31-31: Prefer enum over string for up_axis for type-safety and consistencyMinor nit: pass the Axis enum instead of a raw string to align with the typed signature and other tests.
- builder.add_mjcf(newton.examples.get_asset("nv_ant.xml"), up_axis="Y") + builder.add_mjcf(newton.examples.get_asset("nv_ant.xml"), up_axis=newton.Axis.Y)newton/tests/test_import_urdf.py (1)
239-242: Migration to builder.add_urdf looks goodThe switch to the builder-centric API is correct. Defaults (e.g., up_axis=Axis.Z) look intentional for cartpole; if the asset is Y-up, consider specifying up_axis explicitly to avoid implicit assumptions.
newton/examples/basic/example_basic_urdf.py (1)
62-67: Nice API migration; consider making up_axis explicit (enum) to document intentNot required, but explicitly setting up_axis improves readability and prevents accidental regressions if default changes later. If the URDF is Z-up (common), this keeps behavior unchanged.
quadruped.add_urdf( newton.examples.get_asset("quadruped.urdf"), xform=wp.transform([0.0, 0.0, 0.7], wp.quat_identity()), floating=True, - enable_self_collisions=False, + enable_self_collisions=False, + up_axis=newton.Axis.Z, )newton/examples/selection/example_selection_cartpole.py (1)
70-75: Restrict USD import to the articulation root for faster loads; optionally pass verbose flagIf the cartpole prim lives under “/cartPole” (it’s later used by ArticulationView), importing only that subtree avoids scanning the full stage. Passing verbose=VERBOSE can help during example runs.
env.add_usd( newton.examples.get_asset("cartpole.usda"), xform=wp.transform((0.0, 0.0, 2.0), wp.quat_identity()), collapse_fixed_joints=COLLAPSE_FIXED_JOINTS, enable_self_collisions=False, + root_path="/cartPole", + verbose=VERBOSE, )If “/cartPole” is not the correct root in this asset, replace it with the actual root path or drop the argument.
newton/tests/test_import_mjcf.py (1)
35-39: Migration toModelBuilder.add_mjcfis solid; minor axis-style consistency nitAll call sites now target the builder method and preserve intent/options. One minor consistency tweak: prefer the enum over a string for
up_axisto align with the signature and avoid future type drift.Apply this small diff in the first test (optional but recommended):
- builder.add_mjcf( - mjcf_filename, - ignore_names=["floor", "ground"], - up_axis="Z", - ) + builder.add_mjcf( + mjcf_filename, + ignore_names=["floor", "ground"], + up_axis=newton.Axis.Z, + )If you agree, I can sweep-convert other tests that pass axis strings.
Also applies to: 107-108, 148-149, 162-163, 226-227, 263-264, 300-301, 349-350, 398-399, 455-456, 499-500
newton/tests/test_examples.py (1)
299-304: Likely module path + viewer mode update needed forexample_h1If
h1also moved undernewton/examples/robot/and uses the viewer API (as with other robot examples), update the import path and enableuse_viewer.-add_example_test( - TestRobotExamples, - name="example_h1", - devices=test_devices, - test_options={"usd_required": True, "num_frames": 500, "headless": True}, - test_options_cpu={"num_frames": 10}, -) +add_example_test( + TestRobotExamples, + name="robot.example_h1", + devices=test_devices, + test_options={"usd_required": True, "num_frames": 500, "headless": True}, + test_options_cpu={"num_frames": 10}, + use_viewer=True, +)If
example_g1similarly moved underrobot/, make the same path/viewer adjustments there as well.newton/examples/example_robot_manipulating_cloth.py (3)
360-373: URDF importer migration looks good; prefer enum forup_axisUsing
builder.add_urdf(...)directly is correct and options map 1:1. Forup_axis, pass the enum for type clarity and future safety.- builder.add_urdf( - str(asset_path / "urdf" / "fr3_franka_hand.urdf"), - up_axis=self.up_axis, + builder.add_urdf( + str(asset_path / "urdf" / "fr3_franka_hand.urdf"), + up_axis=newton.Axis.Y,
50-67: Type hint/docstring mismatch: function returns a quaternion, not a transform
vec_rotation(...)returnswp.quat(...)but is annotated/documented as returning awp.transform. Tighten the API to avoid confusion.-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 rotating +Z to the given normal direction."""
422-425: Duplicate assignment toself.targets
self.targets = self.robot_key_poses[:, 1:]appears twice back-to-back; the first is redundant.- self.targets = self.robot_key_poses[:, 1:] - self.targets = self.robot_key_poses[:, 1:] + self.targets = self.robot_key_poses[:, 1:]newton/examples/example_ik_benchmark.py (2)
61-61: Nit: Thepartial(..., scale=1.0)is unnecessary.
scale=1.0is already the default forModelBuilder.add_urdf; you can drop thepartialwithout changing behavior. This slightly simplifies the config and avoids confusion about whether the method is bound.Apply this diff if you prefer the simpler form:
- parser=partial(newton.ModelBuilder.add_urdf, scale=1.0), + parser=newton.ModelBuilder.add_urdf,
138-141: Pass a string path to the importer for consistency and robustness.Elsewhere (e.g., example_mujoco.py), paths are passed as
str(...). While many stdlib functions acceptPathLike, downstream XML loaders and helpers can be stricter in some environments. Converting tostrhere avoids edge cases and aligns examples.Apply this diff:
- asset_path = newton.utils.download_asset(self.cfg.asset) / self.cfg.file - self.cfg.parser(builder, asset_path, floating=False) + asset_path = newton.utils.download_asset(self.cfg.asset) / self.cfg.file + self.cfg.parser(builder, str(asset_path), floating=False)newton/examples/example_mujoco.py (1)
79-83: Optional: UseAxis.Zinstead of"Z"for clarity.The API accepts either, but using the enum improves readability and type clarity across examples.
Apply this diff if you like:
- articulation_builder.add_mjcf( - newton.examples.get_asset("nv_humanoid.xml"), - ignore_names=["floor", "ground"], - up_axis="Z", - ) + articulation_builder.add_mjcf( + newton.examples.get_asset("nv_humanoid.xml"), + ignore_names=["floor", "ground"], + up_axis=newton.Axis.Z, + )(Repeat similarly for other
"Z"usages in this file.)newton/tests/test_equality_constraints.py (2)
32-37: Minor readability: prefer pathlib for test asset paths.Pathlib avoids
os.path.joinverbosity and is cross-platform by construction.Apply this diff if desired:
- builder.add_mjcf( - os.path.join(os.path.dirname(__file__), "assets", "constraints.xml"), + from pathlib import Path + builder.add_mjcf( + str(Path(__file__).with_name("assets") / "constraints.xml"), ignore_names=["floor", "ground"], up_axis="Z", skip_equality_constraints=False, )
16-17: Optional: remove unused import(s) if flagged by linters.If your linting pipeline flags unused imports, consider dropping
numpy as nphere (only used later; keep if your test harness expects it). Otherwise, ignore this note.newton/_src/utils/import_urdf.py (1)
53-60: Consider broadening the type hint forurdf_filenameto acceptos.PathLike[str].Most stdlib consumers accept pathlikes; explicitly supporting them in the signature improves ergonomics. If you keep it
str, ensure call sites passstr(...)(as suggested in the IK benchmark).Proposed change:
-def parse_urdf( - builder: ModelBuilder, - urdf_filename: str, +def parse_urdf( + builder: ModelBuilder, + urdf_filename: str, # or: os.PathLike[str] | strIf you adopt
PathLike, also wrap uses withos.fspath(urdf_filename)where string paths are required.newton/tests/test_import_usd.py (1)
80-82: Nit: bitwise checks on flags — cast consistently or rely on IntFlag.You sometimes use
int(newton.ShapeFlags.COLLIDE_SHAPES)and sometimes not. Both work, but pick one style for consistency (prefer no cast ifShapeFlagsis an IntFlag).newton/tests/test_api.py (3)
30-44: Good parity harness; one brittle assertion can cause noisy failures across minor API editsThe structure is solid. The strict length equality is fine, but coupling to “excluding the first param” makes assumptions about the builder-first convention. If any parser/method gains a positional-only or keyword-only boundary change (e.g., adding a trailing “/” or “*”), this will fail even if it’s semantically compatible. Consider enhancing the error to print both full signatures for debuggability.
Apply this diff to include the full signatures in failure output:
- assert len(func_params) == len(method_params), ( - f"Parameter count mismatch (excluding first): " - f"{len(func_params)} ({func_name}) != {len(method_params)} ({method_name})" - ) + assert len(func_params) == len(method_params), ( + f"Parameter count mismatch (excluding first): " + f"{len(func_params)} ({func_name}) != {len(method_params)} ({method_name}); " + f"func={sig_func!s}, method={sig_method!s}" + )
63-67: Default-value equality check is too strict (e.g., tuple vs list, np arrays, NaNs)Parser defaults and builder defaults sometimes use equivalent-but-not-identical objects ((), []), or float("nan"). A strict “==” will falsely fail parity. Normalize before compare.
Apply this diff to compare defaults more robustly:
- assert pf.default == pm.default, ( + def _norm_default(v): + import math + if v is inspect._empty: + return v + if isinstance(v, (list, tuple)): + return tuple(v) + if isinstance(v, float) and math.isnan(v): + return ("nan",) + return v + assert _norm_default(pf.default) == _norm_default(pm.default), ( f"Param {pf.name!r} default mismatch: {pf.default!r} ({func_name}) != {pm.default!r} ({method_name})" )
85-89: Docstring equality is brittle; prefer normalized semantic checks or allow referencesRequiring exact docstring equality will churn on harmless edits (punctuation, Sphinx markup). Recommend either:
- Allowing docstrings to match if one contains a “See also” reference to the other, or
- Normalizing by lowercasing, removing multiple spaces, and stripping Sphinx roles before compare.
Apply this diff to make the doc check less brittle while preserving parity intent:
- doc_func = "\n".join(line.strip() for line in (func.__doc__ or "").splitlines()).strip() - doc_method = "\n".join(line.strip() for line in (method.__doc__ or "").splitlines()).strip() - assert doc_func == doc_method, f"Docstring mismatch between {func_name} and {method_name}" + import re + def _normalize_doc(s: str) -> str: + s = "\n".join(line.strip() for line in (s or "").splitlines()).strip().lower() + s = re.sub(r":\w+:`[^`]+`", "", s) # drop Sphinx roles + s = re.sub(r"\s+", " ", s) + return s + doc_func = _normalize_doc(func.__doc__ or "") + doc_method = _normalize_doc(method.__doc__ or "") + # allow parity if either references the other + if not (doc_func == doc_method or func_name in doc_method or method_name in doc_func): + raise AssertionError(f"Docstring mismatch between {func_name} and {method_name}")newton/_src/utils/import_mjcf.py (2)
331-384: Mesh path: assertion and normals access look inconsistent with commentComment says “ignore geom size attribute” but asserts len(geom_size) == 3. Also, trimesh.Scene meshes use per-geometry vertex_normals; for single mesh case, we access m.vertex_normals which may be empty until computed. Consider calling m.vertex_normals or m.vertex_normals if available; otherwise compute via m.vertex_normals = m.vertex_normals (trimesh computes lazily).
Would you like me to draft a guarded normals extraction that computes normals when missing? I can also convert the assertion into a warning and ignore geom_size as intended.
754-764: Nonzero inertia guard uses any() on a mat33; clarify intentUsing any(x for x in I_m) on a mat33 is unclear. It’s safer to check determinant or sum of absolute values.
Apply this diff:
- if any(x for x in I_m): + if float(abs(I_m[0,0]) + abs(I_m[1,1]) + abs(I_m[2,2])) > 0.0: builder.body_inv_inertia[link] = wp.inverse(I_m) else: builder.body_inv_inertia[link] = I_mnewton/_src/utils/import_usd.py (2)
430-447: Face index dtype should be integer; minor cleanupIndices are loaded as float32 then converted later. Load as int32 upfront to avoid shape/dtype confusion when building faces.
Apply this diff:
- indices = np.array(mesh.GetFaceVertexIndicesAttr().Get(), dtype=np.float32) + indices = np.array(mesh.GetFaceVertexIndicesAttr().Get(), dtype=np.int32)And likewise in the collision-shape mesh path:
- indices = np.array(mesh.GetFaceVertexIndicesAttr().Get(), dtype=np.float32) + indices = np.array(mesh.GetFaceVertexIndicesAttr().Get(), dtype=np.int32)
1185-1197: Inverse inertia computation mixes mat33 and constructor argsFor the “else” path, constructing wp.mat33 with star-expansion of a numpy array is error-prone. Prefer wp.mat33(numpy_array) consistently, then inverse on the same.
Apply this diff:
- if inertia.any(): - builder.body_inv_inertia[body_id] = wp.inverse(wp.mat33(*inertia)) - else: - builder.body_inv_inertia[body_id] = wp.mat33(*np.zeros((3, 3), dtype=np.float32)) + M = wp.mat33(inertia) + builder.body_inertia[body_id] = M + builder.body_inv_inertia[body_id] = wp.inverse(M) if inertia.any() else wp.mat33(np.zeros((3, 3), dtype=np.float32))newton/examples/robot/example_humanoid.py (1)
71-75: Controller gains hardcoded uniformly; consider centralizing per-DOF defaultsMinor, but if other robot examples follow, factor these target gains into a helper to ensure consistency across examples and simplify tuning.
I can draft a small utility (e.g., newton.examples.set_pd_targets(builder, mode, ke, kd)) if you want to use it here and in anymal/cartpole examples.
newton/examples/robot/example_anymal_d.py (2)
118-130: Contacts computed once per frame; may under-resolve high-frequency contactsIn the basic example, contacts are updated every substep. Here you compute before the loop. For stiff contact setups, consider moving collide() inside the substep loop like the basic example to match solver expectations.
Apply this diff if you want parity with the basic example:
- def simulate(self): - self.contacts = self.model.collide(self.state_0) - for _ in range(self.sim_substeps): + def simulate(self): + for _ in range(self.sim_substeps): + self.contacts = self.model.collide(self.state_0) self.state_0.clear_forces() # apply forces to the model for picking, wind, etc self.viewer.apply_forces(self.state_0) self.solver.step(self.state_0, self.state_1, self.control, self.contacts, self.sim_dt) # swap states self.state_0, self.state_1 = self.state_1, self.state_0If MuJoCo’s internal contact update cadence already matches your step, feel free to keep as-is.
91-95: Solver parameters are explicit; consider exposing via CLI flags for quick tuningOptional: add example-level args (iterations, ls_iterations) to experiment without code changes.
I can thread these through newton.examples.create_parser via an example-specific parent parser if desired.
docs/migration.rst (1)
27-37: Good mapping; verify public surface for resolve_usd_from_urlThe table accurately maps the importer entry points. One concern: pointing users to
newton._src.utils.import_usd.resolve_usd_from_urlexposes an internal, underscore-prefixed module path.
- If this utility is intended to be public, consider re-exporting it under a stable public namespace (e.g.,
newton.utils.resolve_usd_from_url) and update the table accordingly.- If it’s not intended to be public, consider removing it from the migration guide or labeling it as “advanced/internal”.
I can patch the docs once you confirm which direction you prefer.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (3)
docs/api/_generated/newton.utils.parse_mjcf.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.parse_urdf.rstis excluded by!**/_generated/**docs/api/_generated/newton.utils.parse_usd.rstis excluded by!**/_generated/**
📒 Files selected for processing (32)
docs/api/newton_utils.rst(0 hunks)docs/migration.rst(1 hunks)newton/_src/sim/builder.py(2 hunks)newton/_src/utils/__init__.py(0 hunks)newton/_src/utils/import_mjcf.py(2 hunks)newton/_src/utils/import_urdf.py(2 hunks)newton/_src/utils/import_usd.py(6 hunks)newton/examples/basic/example_basic_urdf.py(1 hunks)newton/examples/example_contact_sensor.py(1 hunks)newton/examples/example_ik_benchmark.py(3 hunks)newton/examples/example_ik_interactive.py(1 hunks)newton/examples/example_mujoco.py(6 hunks)newton/examples/example_robot_manipulating_cloth.py(1 hunks)newton/examples/robot/example_anymal_c_walk.py(7 hunks)newton/examples/robot/example_anymal_d.py(3 hunks)newton/examples/robot/example_cartpole.py(2 hunks)newton/examples/robot/example_g1.py(1 hunks)newton/examples/robot/example_h1.py(1 hunks)newton/examples/robot/example_humanoid.py(1 hunks)newton/examples/robot/example_quadruped.py(1 hunks)newton/examples/selection/example_selection_articulations.py(1 hunks)newton/examples/selection/example_selection_cartpole.py(1 hunks)newton/tests/test_anymal_reset.py(1 hunks)newton/tests/test_api.py(1 hunks)newton/tests/test_equality_constraints.py(1 hunks)newton/tests/test_examples.py(1 hunks)newton/tests/test_import_mjcf.py(11 hunks)newton/tests/test_import_urdf.py(2 hunks)newton/tests/test_import_usd.py(13 hunks)newton/tests/test_kinematics.py(1 hunks)newton/tests/test_pendulum_revolute_vs_d6.py(1 hunks)newton/utils.py(0 hunks)
💤 Files with no reviewable changes (3)
- newton/utils.py
- newton/_src/utils/init.py
- docs/api/newton_utils.rst
🚧 Files skipped from review as they are similar to previous changes (2)
- newton/examples/robot/example_quadruped.py
- newton/examples/robot/example_h1.py
🧰 Additional context used
🧠 Learnings (6)
📚 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/tests/test_pendulum_revolute_vs_d6.pynewton/_src/utils/import_usd.pynewton/tests/test_import_usd.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/tests/test_pendulum_revolute_vs_d6.pynewton/_src/utils/import_usd.pynewton/tests/test_import_usd.py
📚 Learning: 2025-08-19T12:27:30.629Z
Learnt from: preist-nvidia
PR: newton-physics/newton#579
File: newton/examples/example_mujoco.py:350-354
Timestamp: 2025-08-19T12:27:30.629Z
Learning: In Newton examples, there's a distinction between solver parameters and Example class attributes. The Example class can have its own use_mujoco attribute for controlling example-level behavior (like CUDA graphs, rendering logic), while the solver uses use_mujoco_cpu for backend selection. These serve different purposes and should not be conflated during API renames.
Applied to files:
newton/examples/robot/example_anymal_d.py
📚 Learning: 2025-08-25T21:41:45.769Z
Learnt from: dylanturpin
PR: newton-physics/newton#634
File: newton/tests/test_examples.py:329-333
Timestamp: 2025-08-25T21:41:45.769Z
Learning: Newton examples use a centralized CLI argument parsing system in newton/examples/__init__.py. The create_parser() function defines common arguments like --device, --viewer, --output-path, and --num-frames, while init(parser) creates viewers based on parsed arguments. Individual example scripts don't need to define these flags themselves - they inherit them from the centralized system via the example_map and runpy execution.
Applied to files:
newton/examples/robot/example_anymal_d.pynewton/examples/robot/example_cartpole.pynewton/examples/robot/example_anymal_c_walk.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/migration.rst
📚 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/migration.rst
🧬 Code graph analysis (23)
newton/tests/test_api.py (4)
newton/_src/sim/builder.py (4)
ModelBuilder(68-4468)add_urdf(654-712)add_mjcf(816-901)add_usd(714-814)newton/_src/utils/import_urdf.py (1)
parse_urdf(53-543)newton/_src/utils/import_mjcf.py (1)
parse_mjcf(32-931)newton/_src/utils/import_usd.py (1)
parse_usd(35-1300)
newton/examples/selection/example_selection_cartpole.py (1)
newton/_src/sim/builder.py (1)
add_usd(714-814)
newton/tests/test_equality_constraints.py (1)
newton/_src/sim/builder.py (1)
add_mjcf(816-901)
newton/examples/example_ik_benchmark.py (1)
newton/_src/sim/builder.py (3)
ModelBuilder(68-4468)add_mjcf(816-901)add_urdf(654-712)
newton/tests/test_import_urdf.py (1)
newton/_src/sim/builder.py (1)
add_urdf(654-712)
newton/examples/selection/example_selection_articulations.py (2)
newton/_src/sim/builder.py (2)
add_mjcf(816-901)collapse_fixed_joints(2054-2351)newton/examples/__init__.py (1)
get_asset(32-33)
newton/examples/example_robot_manipulating_cloth.py (1)
newton/_src/sim/builder.py (1)
add_urdf(654-712)
newton/tests/test_kinematics.py (2)
newton/_src/sim/builder.py (1)
add_mjcf(816-901)newton/examples/__init__.py (1)
get_asset(32-33)
newton/tests/test_pendulum_revolute_vs_d6.py (1)
newton/_src/sim/builder.py (1)
add_usd(714-814)
newton/examples/example_ik_interactive.py (1)
newton/_src/sim/builder.py (1)
add_mjcf(816-901)
newton/tests/test_anymal_reset.py (1)
newton/_src/sim/builder.py (1)
add_usd(714-814)
newton/tests/test_import_mjcf.py (1)
newton/_src/sim/builder.py (1)
add_mjcf(816-901)
newton/examples/example_mujoco.py (1)
newton/_src/sim/builder.py (3)
add_mjcf(816-901)add_urdf(654-712)add_usd(714-814)
newton/examples/basic/example_basic_urdf.py (1)
newton/_src/sim/builder.py (1)
add_urdf(654-712)
newton/_src/utils/import_usd.py (2)
newton/_src/sim/builder.py (1)
ModelBuilder(68-4468)newton/_src/sim/joints.py (1)
JointMode(81-95)
newton/examples/example_contact_sensor.py (2)
newton/_src/sim/builder.py (2)
add_mjcf(816-901)collapse_fixed_joints(2054-2351)newton/examples/__init__.py (1)
get_asset(32-33)
newton/examples/robot/example_g1.py (6)
newton/examples/basic/example_basic_urdf.py (5)
Example(35-134)capture(98-104)simulate(106-117)step(119-125)render(130-134)newton/examples/robot/example_anymal_d.py (5)
Example(34-146)capture(110-115)simulate(118-129)step(131-137)render(139-143)newton/_src/sim/builder.py (8)
ModelBuilder(68-4468)JointDofConfig(196-265)add_usd(714-814)collapse_fixed_joints(2054-2351)approximate_meshes(2807-3009)add_builder(905-1200)add_ground_plane(2520-2540)finalize(4077-4420)newton/_src/solvers/mujoco/solver_mujoco.py (1)
SolverMuJoCo(1096-2569)newton/_src/sim/model.py (2)
state(440-474)control(476-509)newton/examples/__init__.py (3)
create_parser(97-123)init(126-167)run(36-44)
newton/examples/robot/example_humanoid.py (1)
newton/examples/__init__.py (4)
get_asset(32-33)create_parser(97-123)init(126-167)run(36-44)
newton/_src/sim/builder.py (4)
newton/_src/core/types.py (1)
Axis(64-161)newton/_src/utils/import_urdf.py (1)
parse_urdf(53-543)newton/_src/utils/import_usd.py (1)
parse_usd(35-1300)newton/_src/utils/import_mjcf.py (1)
parse_mjcf(32-931)
newton/examples/robot/example_anymal_d.py (5)
newton/examples/basic/example_basic_urdf.py (5)
Example(35-134)capture(98-104)simulate(106-117)step(119-125)render(130-134)newton/examples/robot/example_cartpole.py (5)
Example(31-126)capture(91-96)simulate(99-109)step(111-117)render(119-123)newton/_src/sim/builder.py (2)
add_usd(714-814)collapse_fixed_joints(2054-2351)newton/_src/viewer/viewer.py (1)
log_contacts(101-162)newton/examples/__init__.py (3)
create_parser(97-123)init(126-167)run(36-44)
newton/tests/test_import_usd.py (2)
newton/_src/sim/builder.py (2)
add_usd(714-814)collapse_fixed_joints(2054-2351)newton/examples/__init__.py (1)
get_asset(32-33)
newton/examples/robot/example_cartpole.py (6)
newton/_src/sim/builder.py (2)
ModelBuilder(68-4468)add_usd(714-814)newton/examples/basic/example_basic_urdf.py (5)
capture(98-104)simulate(106-117)step(119-125)render(130-134)test(127-128)newton/examples/robot/example_anymal_d.py (5)
capture(110-115)simulate(118-129)step(131-137)render(139-143)test(145-146)newton/examples/selection/example_selection_cartpole.py (5)
capture(127-132)simulate(134-142)step(144-171)render(173-176)test(178-179)newton/_src/viewer/viewer.py (1)
log_contacts(101-162)newton/examples/__init__.py (3)
create_parser(97-123)init(126-167)run(36-44)
newton/examples/robot/example_anymal_c_walk.py (4)
newton/_src/sim/builder.py (1)
add_urdf(654-712)newton/examples/basic/example_basic_urdf.py (5)
capture(98-104)step(119-125)simulate(106-117)render(130-134)Example(35-134)newton/_src/viewer/viewer.py (1)
log_contacts(101-162)newton/examples/__init__.py (2)
init(126-167)run(36-44)
🪛 GitHub Actions: GPU Unit Tests on AWS EC2
newton/tests/test_examples.py
[error] 185-185: Test 'test_example_anymal_c_walk_cuda_0' failed. No module named 'newton.examples.example_anymal_c_walk'. Command: coverage run --data-file=/tmp/tmpfg711avl/tmp2lc7m862 -m newton.examples.example_anymal_c_walk --device cuda:0 --stage-path None --num-frames 200 --headless --headless True
⏰ 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). (3)
- GitHub Check: Run GPU Benchmarks (Pull Request)
- GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
- GitHub Check: run-newton-tests / newton-unittests (windows-latest)
🔇 Additional comments (24)
newton/examples/robot/example_anymal_c_walk.py (2)
230-235: Render path migration looks consistent with the viewer API.begin_frame/log_state/log_contacts/end_frame matches other viewerized examples. Good job unifying rendering.
94-103: Importer migration (add_urdf) is correct and uses appropriate flags.Using add_urdf with floating=True and collapse_fixed_joints=True aligns with the builder-first importer API introduced elsewhere.
newton/examples/robot/example_g1.py (2)
55-66: USD importer usage and mesh approximation are aligned with the new builder APIs.add_usd with hide_collision_shapes=True and a subsequent approximate_meshes("bounding_box") call are a nice combo for interactive performance.
145-157: Main entrypoint correctly uses centralized parser/init/run.This matches the examples infrastructure and keeps CLI consistent across examples.
newton/examples/robot/example_cartpole.py (2)
53-57: Importer migration to builder.add_usd looks good.Switching from parse_usd(...) to articulation_builder.add_usd(...) with collapse_fixed_joints=True and disable self-collisions matches the new API surface.
91-97: Graph-capture structure matches the examples pattern.Capture-once with fallback to simulate() is consistent with other viewerized examples.
newton/tests/test_anymal_reset.py (1)
50-54: Correct migration to builder.add_usd in the test setup.Passing stage_path into builder.add_usd with collapse_fixed_joints=False and enable_self_collisions=False preserves previous behavior while aligning with the builder-first API.
newton/tests/test_import_urdf.py (1)
154-155: Could you share the lines around theadd_urdfdefinition (from the last grep)? Once we confirm its signature and how it handlesup_axis, I’ll update the comment to reflect that using theAxisenum is stylistic (since strings are accepted) but still recommended, and I’ll also call out the two deprecatednewton.utils.parse_*calls in your examples.newton/tests/test_pendulum_revolute_vs_d6.py (1)
33-33: LGTM: builder.add_usd migration is correctSwitching from the free function to ModelBuilder.add_usd with only_load_enabled_rigid_bodies=False matches the updated API.
newton/examples/example_contact_sensor.py (1)
87-100: Builder-centric MJCF import migration looks correctSwitching to
ModelBuilder.add_mjcf(...)with the same arguments preserves behavior. The offsets, ignore lists, axis, and collapse flags are carried over intact. No functional concerns from this change.newton/examples/selection/example_selection_articulations.py (1)
98-109: Good API migration toenv.add_mjcf(...)The calls mirror the old
parse_mjcf(...)usage and keep transforms/ignore lists unchanged. Looks consistent with the new builder-first importer API.newton/examples/example_ik_benchmark.py (2)
51-57: Migration to ModelBuilder importers is correct and consistent with the new API.
- Using
newton.ModelBuilder.add_mjcffor h1 and a partial overModelBuilder.add_urdffor franka matches the builder-first design and keeps options aligned with the wrappers inbuilder.py. No behavioral regressions expected.Also applies to: 59-67
71-82: Good:_roberts_rootis numerically stable for intended dims.
- Uses Newton-Raphson with an early break and adequate tolerance. Memoization via
@lru_cacheis appropriate for repeated calls.newton/examples/example_mujoco.py (2)
79-83: Builder-based importer migration looks solid across all setups.
- All six call sites correctly switched to
add_mjcf/add_urdf/add_usdwith options preserved (e.g.,collapse_fixed_joints,enable_self_collisions,up_axis).- Paths are passed as strings, which is consistent and avoids pathlike pitfalls.
Also applies to: 95-101, 129-134, 145-150, 160-163, 179-184
239-248: Good: SolverMuJoCo initialization remains unchanged and parameterized.The importer changes do not affect solver configuration; keeping this stable limits blast radius in the example.
newton/tests/test_equality_constraints.py (1)
32-37: Switch toModelBuilder.add_mjcfis correct; options match the wrapper signature.
ignore_names,up_axis, andskip_equality_constraintsmap 1:1 to the builder wrapper. This aligns with the removal of publicparse_mjcffromnewton.utils.newton/_src/utils/import_urdf.py (1)
53-60: Signature swap toparse_urdf(builder, urdf_filename, ...)matches the builder wrapper and docs.
- Confirms compatibility with
ModelBuilder.add_urdf, which forwards(self, urdf_filename, ...)to this function.- Docstring updates correctly reflect the new parameter order.
Also applies to: 74-77
newton/tests/test_import_usd.py (1)
35-38: USD tests correctly migrated toModelBuilder.add_usd; path vs stage behavior handled properly.
- For file-path imports, capturing the returned
resultsdict is correct and assertions coverpath_body_map/path_shape_map.- For in-memory
Usd.Stageinputs, ignoring the return value matches the wrapper’s None-return behavior.- Options exercised (
collapse_fixed_joints,load_non_physics_prims,joint_ordering,cloned_env,invert_rotations,mesh_maxhullvert) align withadd_usdand strengthen coverage.Also applies to: 61-65, 88-91, 107-111, 151-155, 171-174, 193-201, 218-221, 245-249, 313-314, 345-346, 399-400, 492-493
newton/tests/test_api.py (1)
91-109: Sanity-check: importing internal parsers is safe (USD is lazily imported); keep it this wayTests import parse_usd/parse_mjcf/parse_urdf modules. Given our lazy pxr import design, this won’t explode on machines without USD. Keep the lazy import in utils; parity tests are safe to run anywhere.
newton/_src/utils/import_usd.py (1)
1013-1031: Visibility wiring looks correct; confirm flags flow through ShapeConfig.flagsUsing is_visible=not hide_collision_shapes is aligned with ShapeConfig.flags. Ensure no later code overwrites shape_flags for these shapes (only filtered pairs mutate flags in this function).
If you’ve seen unexpected visibility in the viewer, I can help generate a small repro to dump shape_flags and confirm the final value per shape.
newton/examples/robot/example_humanoid.py (1)
94-102: Graph capture pattern: good placement and guarded CUDA pathCapture after viewer.set_model is correct for GL/renderer resources. Nice.
newton/examples/robot/example_anymal_d.py (1)
62-71: USD importer flags: load_non_physics_prims + hide_collision_shapes combos are correctMatches add_usd signature changes; good usage to keep visuals and hide colliders.
newton/_src/sim/builder.py (2)
45-55: LGTM: exposing MESH_MAXHULLVERT at import-time is appropriate hereBringing
MESH_MAXHULLVERTinto this module avoids magic numbers in defaults for the new importer helpers and keeps behavior consistent with the geometry subsystem. No issues spotted.
816-902: Review: Update docstring and clarify defaults for add_mjcf
- Docstring lists a
builderparameter, but the method is an instance method and takes nobuilderarg (it'sself).- Types for
xformandfloatingin the docs don’t include| None.- There’s no “Returns” section documenting the parser result.
- Notably,
enable_self_collisionsdefaults differ (URDF=True, USD=True, MJCF=False); this should be highlighted if intentional.Suggested diff:
def add_mjcf( @@ - ) -> None: + ) -> Any: """ - Parses MuJoCo XML (MJCF) file and adds the bodies and joints to the given ModelBuilder. + Parses MuJoCo XML (MJCF) and adds bodies and joints to this ModelBuilder. Args: - builder (ModelBuilder): The :class:`ModelBuilder` to add the bodies and joints to. mjcf_filename_or_string (str): Filename or MJCF XML string to parse. - xform (Transform): The transform to apply to the imported mechanism. - floating (bool): If True, floating base; False, fixed base; None, auto-detect. + xform (Transform | None): Transform to apply to the imported mechanism. + floating (bool | None): If True, treat as floating base; False, fixed base; None to auto-detect. @@ mesh_maxhullvert (int): Maximum vertices for convex-hull approximation of meshes. - - Returns: - None + Returns: + Any: Return value from :func:`newton._src.utils.import_mjcf.parse_mjcf`. """Question: Should we harmonize or at least document the differing defaults of
enable_self_collisionsacross importers (URDF, USD, MJCF) in a migration guide or method docstrings?
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
…iet/newton into robot-examples Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com> Signed-off-by: Gilles Daviet <gdaviet@nvidia.com> Co-authored-by: Gilles Daviet <gdaviet@nvidia.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com> Signed-off-by: Gilles Daviet <gdaviet@nvidia.com> Co-authored-by: Gilles Daviet <gdaviet@nvidia.com>
Description
robotsexample moduleModelBuilder.add_urdf,add_usd,add_mjcfin user-facing API to replacenewton.utils.import_urdf, ...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