Skip to content

Example cleanup#580

Merged
mmacklin merged 42 commits into
newton-physics:mainfrom
mmacklin:example-refactor
Aug 21, 2025
Merged

Example cleanup#580
mmacklin merged 42 commits into
newton-physics:mainfrom
mmacklin:example-refactor

Conversation

@mmacklin

@mmacklin mmacklin commented Aug 19, 2025

Copy link
Copy Markdown
Member

Description

Newton Migration Guide

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

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

Before your PR is "Ready for review"

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

Summary by CodeRabbit

  • New Features
    • Added ViewerNull backend and wind effects in viewers.
    • Introduced builder.replicate() for environment creation.
    • Point rendering, vsync controls.
    • Exposed mesh-creation utilities on newton.utils and added new example CLI runner.

Migration Guide

Newton Examples Migration Guide

This guide explains how to migrate Newton examples from the old argument-parsing style to the new viewer-based pattern introduced in Newton 2025.

Overview

The new example format standardizes how Newton examples are structured, making them more consistent and easier to use. The key changes involve:

  1. Viewer Integration: Examples now receive a viewer object instead of managing their own renderers
  2. Standardized Arguments: Common arguments are handled by newton.examples.init()
  3. Consistent Structure: All examples follow the same pattern for simulation methods
  4. Short Name Support: Examples can be run with convenient short names

Newton Example Requirements

During migration please ensure your example follows these guidelines:

Functionality & Design

  • Single Concept: Examples should ideally introduce one new concept, piece of functionality, or behavior
  • Standalone: Examples should be standalone scripts that can be copy-pasted into a user environment and run outside Newton, and written as if they are a 'user' of the SDK
  • No Helper Classes: As much as possible examples should not hide API calls behind shared helper classes, base classes, or external utility functions
  • Minimal Configuration: Examples should minimize the number of configuration parameters and conditional paths they take so they are easy to follow
  • User-Friendly: Examples should be easy to navigate in the viewer with appropriate scaling

Technical Standards

  • Coordinate Frame: Examples should be in Z-up coordinate frame
  • Units: Examples should be in SI units / meter scale
  • Viewer Support: Examples should use the ViewerGL backend as a default but can force other viewers if required
  • Dependencies: Examples should not require additional dependencies except for specific cases (e.g.: PyTorch for policy inference)

Performance Requirements

  • Interactive Performance: Examples should run interactively at >= 25 FPS on 40xx series GPUs using default parameters
  • Startup Time: Examples should cold start in < 1 min and warm start in < 20s
  • Cross-Platform: Examples should be runnable on CPU and GPU
  • Solver Compatibility: Examples do not have to work with every solver or viewer combination, better to pick one good one than try to work with every solver

Documentation & Testing

  • Thumbnail: Examples should have a thumbnail image that is included in the examples gallery
  • Description: Examples should include a short blurb describing what functionality they illustrate
  • Test Coverage: Examples should include a corresponding entry in the test_examples.py that initializes and runs them
  • Clean Code: Examples should not include large sections of commented out code
  • No Warnings or Errors: Examples should not raise warnings or error messages during normal operation

Step-by-Step Migration

1. Update Imports

Before:

import warp as wp

wp.config.enable_backward = False

import newton

After:

import warp as wp

import newton
import newton.examples
  • Add import newton.examples
  • Remove wp.config.enable_backward = False

2. Update Constructor Signature

Before:

class Example:
    def __init__(self, stage_path="output.usd", num_frames=300):
        fps = 60
        self.frame_dt = 1.0 / fps
        self.num_substeps = 10
        self.dt = self.frame_dt / self.num_substeps
        self.num_frames = num_frames
        self.sim_time = 0.0
        self.use_cuda_graph = wp.get_device().is_cuda

After:

class Example:
    def __init__(self, viewer, arg0, arg1):
        # setup simulation parameters first
        self.fps = 60
        self.frame_dt = 1.0 / self.fps
        
        # group related attributes by prefix
        self.sim_time = 0.0
        self.sim_substeps = 10  # renamed from num_substeps
        self.sim_dt = self.frame_dt / self.sim_substeps  # renamed from dt

        # unpack any example specific args
        self.arg0 = arg0
        self.arg1 = arg1

        # save a reference to the viewer
        self.viewer = viewer

Key Changes:

  • Constructor now takes viewer instead of stage/output parameters
  • Remove num_frames, use_cuda_graph parameters
  • Rename num_substeps -> sim_substeps and dtsim_dt for consistency

3. Update Variable Names for Consistency

Before:

self.state0 = self.model.state()
self.state1 = self.model.state()

After:

self.state_0 = self.model.state()
self.state_1 = self.model.state()

Key Changes:

  • Use underscore naming: state0 -> state_0, state1state_1
  • This applies to all numbered variables

4. Replace Custom Renderer with Viewer

Before:

self.renderer = None
if stage_path:
    self.renderer = newton.viewer.RendererOpenGL(
        path=stage_path, model=self.model, scaling=1.0
    )

# Later in render method:
if self.renderer:
    self.renderer.begin_frame(self.sim_time)
    self.renderer.render(self.state_0)
    self.renderer.end_frame()

After:

# ensure this is called at the end of the Example constructor
self.viewer.set_model(self.model)

# Later in render method:
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()

5. Update Simulation Methods

Before:

def simulate_substeps(self):
    for _ in range(self.num_substeps):
        contacts = self.model.collide(self.state_0)
        self.state_0.clear_forces()
        self.solver.step(self.state_0, self.state_1, None, contacts, self.dt)
        (self.state_0, self.state_1) = (self.state_1, self.state_0)

# CUDA graph setup
self.cuda_graph = None
if self.use_cuda_graph:
    with wp.ScopedCapture() as capture:
        self.simulate_substeps()
    self.cuda_graph = capture.graph

After:

# move graph capture into it's own method
def capture(self):
    self.graph = None
    if wp.get_device().is_cuda:
        with wp.ScopedCapture() as capture:
            self.simulate()
        self.graph = capture.graph


# simulate() performs one frame's worth of updates
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.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

Key Changes:

  • Rename method: simulate_substeps()simulate()
  • Add separate capture() method with comment "put graph capture into it's own function"
  • Use self.graph instead of self.cuda_graph
  • Add self.viewer.apply_forces(self.state_0) call
  • Update collision handling to use self.contacts

6. Update Step Method

Before:

def step(self):
    with wp.ScopedTimer("step", print=False, dict=self.profiler):
        if self.use_cuda_graph:
            wp.capture_launch(self.cuda_graph)
        else:
            self.simulate_substeps()
        self.sim_time += self.frame_dt

After:

def step(self):
    if self.graph:
        wp.capture_launch(self.graph)
    else:
        self.simulate()

    self.sim_time += self.frame_dt

Key Changes:

  • Remove profiler/timer logic
  • Use self.graph instead of self.cuda_graph
  • Call self.simulate() instead of self.simulate_substeps()

7. Add Required Methods

Although not yet widely used, having a test() method is welcome and will be checked by the test suite in a follow up MR.

def test(self):
    pass

8. Initialize Model at End of Constructor

Before:

# Model setup code...
self.model = builder.finalize()

# More setup...

After:

# Model setup code...
self.model = builder.finalize()

self.state_0 = self.model.state()
self.state_1 = self.model.state()
self.control = self.model.control()
self.contacts = self.model.collide(self.state_0)

self.viewer.set_model(self.model)

# Ensure FK evaluation (for non-MuJoCo solvers):
newton.eval_fk(self.model, self.model.joint_q, self.model.joint_qd, self.state_0)

self.capture()

Key Changes:

  • Initialize all state objects
  • Call self.viewer.set_model(self.model)
  • Call self.capture() at the end

9. Update Main Function

Before:

if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument("--device", type=str, default=None, help="Override the default Warp device.")
    parser.add_argument("--stage-path", type=str, default="output.usd", help="Path to the output USD file.")
    parser.add_argument("--num-frames", type=int, default=300, help="Total number of frames.")

    args = parser.parse_known_args()[0]

    with wp.ScopedDevice(args.device):
        example = Example(stage_path=args.stage_path, num_frames=args.num_frames)
        example.run()

        if example.renderer:
            example.renderer.save()

After (Simple):

if __name__ == "__main__":
    # Parse arguments and initialize viewer
    viewer, args = newton.examples.init()

    # Create example and run
    example = Example(viewer)

    newton.examples.run(example)

After (With Custom Arguments):

if __name__ == "__main__":
    # Create parser that inherits common arguments and adds example-specific ones
    # keep example options short, don't overload user with options
    # device, viewer type, and other options are created by default
    parser = newton.examples.create_parser()
    parser.add_argument("--num-envs", type=int, default=100, help="Number of environments.")
    parser.add_argument("--solver", type=str, choices=["euler", "xpbd", "vbd"], 
                       default="vbd", help="Solver to use.")

    # Parse arguments and initialize viewer
    viewer, args = newton.examples.init(parser)

    # Create example and run
    example = Example(viewer, args.num_envs, args.solver)

    newton.examples.run(example)

Naming Conventions

Follow these naming conventions from the inline comments:

  • Individual objects: Use the name of the asset or just "robot" for individual articulations, not "articulation_builder"

    quadruped = newton.ModelBuilder()  # Good
    articulation_builder = newton.ModelBuilder()  # Avoid
  • Full scenes: Use "scene" for the entire set of environments

    scene = newton.ModelBuilder()  # Good for full scenes (all envs)

Updating Documentation

1. Add to newton.examples.init.py

Add your example to the example_map dictionary:

example_map = {
    "basic_pendulum": "newton.examples.basic.example_basic_pendulum",
    "basic_urdf": "newton.examples.basic.example_basic_urdf",
    "basic_viewer": "newton.examples.basic.example_basic_viewer",
    "cloth_bending": "newton.examples.cloth.example_cloth_bending",
    "cloth_hanging": "newton.examples.cloth.example_cloth_hanging",
    "cloth_style3d": "newton.examples.cloth.example_cloth_style3d",
    "your_example": "newton.examples.category.example_your_example",  # Add this line
}

2. Add Thumbnail to README.md

Add your example to the appropriate table in README.md:

<td align="center" width="33%">
  <a href="newton/examples/category/example_your_example.py">
    <img src="docs/images/examples/example_your_example.jpg" alt="Your Example">
  </a>
</td>

And the corresponding command:

<td align="center">
  <code>python -m newton.examples your_example</code>
</td>

3. Generate Thumbnail Image

  1. Run your example and capture a screenshot
  2. Save as docs/images/examples/example_your_example.png
  3. Run resize.bat / resize.sh to convert to a square jpg
  4. Commit to repo

Testing Your Migration

After migration, test your example:

  1. Direct execution:

    python newton/examples/category/example_your_example.py
  2. Short name execution:

    python -m newton.examples your_example
  3. Different viewers:

    python -m newton.examples your_example --viewer gl
    python -m newton.examples your_example --viewer usd --output-path test.usd
  4. Custom arguments (if any):

    python -m newton.examples your_example --your-custom-arg value

Add Unit Tests

Update test_examples.py with your example, pass use_viewer=True to the add_example_test() function:

class TestClothExamples(unittest.TestCase):
    pass


add_example_test(
    TestClothExamples,
    name="cloth.example_cloth_bending",     # Note that name should include the subdirectory
    devices=test_devices,
    test_options={"num_frames": 100},
    test_options_cpu={"num_frames": 100},
    use_viewer=True,                         # NEW
)

Common Issues

  1. Variable Naming: Use underscores consistently (state_0 not state0)
  2. Method Names: Use sim_substeps/sim_dt not num_substeps/dt
    3 Model Logging: Don't forget to call self.viewer.log_state(self.state_0) to update the viewer
  3. Viewer Forces: Don't forget self.viewer.apply_forces(self.state_0) in simulate if you want picking
  4. Contact Logging: Include self.viewer.log_contacts(self.contacts, self.state_0) in render if example has contacts

Example Runner

There is now a new example run script that will take care of standard argument parsing, viewer initialization, etc:

Example Options

The example run script supports the following common line arguments:

Argument Description Default
--viewer Viewer type: gl (OpenGL window), usd (USD file output), rerun (ReRun), or null (no viewer). gl
--device Compute device to use, e.g., cpu, cuda:0, etc. None (default Warp device)
--num-frames Number of frames to simulate (for USD output). 100
--output-path Output path for USD files (required if --viewer usd is used). None

Some examples may add additional arguments (see their respective source files for details).

Example Usage

# Basic usage
python -m newton.examples basic_pendulum

# With uv
uv run python -m newton.examples basic_pendulum

# With viewer options
python -m newton.examples basic_viewer --viewer usd --output-path my_output.usd

# With device selection
python -m newton.examples basic_urdf --device cuda:0

# Multiple arguments
python -m newton.examples basic_viewer --viewer gl --num-frames 500 --device cpu

Complete Example:

    # SPDX-FileCopyrightText: Copyright (c) 2025 The Newton Developers
    # SPDX-License-Identifier: Apache-2.0
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    # http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.

    ###########################################################################
    # Example Basic URDF
    #
    # Shows how to set up a simulation of a rigid-body quadruped articulation
    # from a URDF using the newton.ModelBuilder().
    # Note this example does not include a trained policy.
    #
    # Users can pick bodies by right-clicking and dragging with the mouse.
    #
    # Command: python -m newton.examples basic_urdf
    #
    ###########################################################################

    import warp as wp

    import newton
    import newton.examples


    class Example:
        def __init__(self, viewer, num_envs):
            # setup simulation parameters first
            self.fps = 100
            self.frame_dt = 1.0 / self.fps
            self.sim_time = 0.0
            self.sim_substeps = 10
            self.sim_dt = self.frame_dt / self.sim_substeps

            self.num_envs = num_envs

            self.viewer = viewer

            quadruped = newton.ModelBuilder()

            # set default parameters for the quadruped
            quadruped.default_body_armature = 0.01
            quadruped.default_joint_cfg.armature = 0.01
            quadruped.default_joint_cfg.mode = newton.JointMode.TARGET_POSITION
            quadruped.default_joint_cfg.target_ke = 2000.0
            quadruped.default_joint_cfg.target_kd = 1.0
            quadruped.default_shape_cfg.ke = 1.0e4
            quadruped.default_shape_cfg.kd = 1.0e2
            quadruped.default_shape_cfg.kf = 1.0e2
            quadruped.default_shape_cfg.mu = 1.0

            # parse the URDF file
            newton.utils.parse_urdf(
                newton.examples.get_asset("quadruped.urdf"),
                quadruped,
                xform=wp.transform([0.0, 0.0, 0.7], wp.quat_identity()),
                floating=True,
                enable_self_collisions=False,
            )

            # set initial joint positions
            quadruped.joint_q[-12:] = [0.2, 0.4, -0.6, -0.2, -0.4, 0.6, -0.2, 0.4, -0.6, 0.2, -0.4, 0.6]
            quadruped.joint_target[-12:] = quadruped.joint_q[-12:]

            # use "scene" for the entire set of environments
            scene = newton.ModelBuilder()

            # use the builder.replicate() function to create N copies of the environment
            scene.replicate(quadruped, self.num_envs)
            scene.add_ground_plane()

            # finalize model
            self.model = scene.finalize()

            self.solver = newton.solvers.SolverXPBD(self.model)

            self.state_0 = self.model.state()
            self.state_1 = self.model.state()
            self.control = self.model.control()
            self.contacts = self.model.collide(self.state_0)

            self.viewer.set_model(self.model)

            # not required for MuJoCo, but required for other solvers
            newton.eval_fk(self.model, self.model.joint_q, self.model.joint_qd, self.state_0)

            # put graph capture into it's own function
            self.capture()

        def capture(self):
            if wp.get_device().is_cuda:
                with wp.ScopedCapture() as capture:
                    self.simulate()
                self.graph = capture.graph
            else:
                self.graph = None

        def simulate(self):
            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.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

        def step(self):
            if self.graph:
                wp.capture_launch(self.graph)
            else:
                self.simulate()

            self.sim_time += self.frame_dt

        def test(self):
            pass

        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()


    if __name__ == "__main__":
        # Create parser that inherits common arguments and adds example-specific ones
        parser = newton.examples.create_parser()
        parser.add_argument("--num-envs", type=int, default=100, help="Total number of simulated environments.")

        # Parse arguments and initialize viewer
        viewer, args = newton.examples.init(parser)

        # Create viewer and run
        example = Example(viewer, args.num_envs)

        newton.examples.run(example)

Signed-off-by: Miles Macklin <mmacklin@nvidia.com>
…e template.

Signed-off-by: Miles Macklin <mmacklin@nvidia.com>
Signed-off-by: Miles Macklin <mmacklin@nvidia.com>
Signed-off-by: Miles Macklin <mmacklin@nvidia.com>
@coderabbitai

coderabbitai Bot commented Aug 19, 2025

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds ModelBuilder.replicate; refactors viewer architecture to decouple construction from model binding, adds ViewerNull, wind forces, point rendering, and vsync controls; updates GL instancing and utilities; reworks Rerun/USD viewers to new API; overhauls examples to viewer-driven flow with CUDA graph capture; expands tests; updates docs and assets handling.

Changes

Cohort / File(s) Summary
Simulation builder replication
newton/_src/sim/builder.py
Adds _compute_replicate_offsets and ModelBuilder.replicate for grid-based sub-builder duplication using translations only.
Viewer core refactor
newton/_src/viewer/viewer.py, newton/_src/viewer/picking.py, newton/_src/viewer/__init__.py, newton/viewer.py
ViewerBase now no-arg init; add set_model; device handling centralized; logging namespaces updated; log_points signature changed; early-exit guards when no model; export ViewerNull.
GL backend and OpenGL utilities
newton/_src/viewer/viewer_gl.py, newton/_src/viewer/gl/opengl.py, newton/_src/viewer/gl/shaders.py
ViewerGL constructor/API updated; adds vsync property; point rendering via instancing; CUDA interop enabled; new kernel update_vbo_transforms_from_points; hidden flags; minor shader comment cleanup.
Wind system (viewer)
newton/_src/viewer/wind.py
Adds WindParams struct, apply_wind_force_kernel, and Wind manager; integrates with viewer to apply per-frame particle forces.
Viewer backends (Rerun/USD/Null)
newton/_src/viewer/viewer_rerun.py, newton/_src/viewer/viewer_usd.py, newton/_src/viewer/viewer_null.py
Rerun/USD constructors drop model; add app_id (Rerun); initialize USD stage in ctor; update log_points signature; introduce no-op ViewerNull.
Examples framework
newton/examples/__init__.py, newton/examples/__main__.py
Adds CLI parser/init/run/main; viewer selection (gl/usd/rerun/null); deprecates compute_env_offsets in favor of builder.replicate; module entrypoint.
New/updated basic examples
newton/examples/basic/*
Adds pendulum, joints, shapes, URDF examples; adopt viewer-driven loops with CUDA graph capture; URDF example uses builder.replicate for multi-envs; reworks basic_viewer to viewer-only demo.
Cloth examples refactor
newton/examples/cloth/*
Switch to viewer-driven flow with capture/simulate/step/render; argument changes; solver selection by string; timing fields added.
MPM example refactor
newton/examples/mpm/example_mpm_granular.py
Viewer-integrated simulation; simplified collider mesh; updated stepping and rendering; signature changes.
Mesh utilities and re-exports
newton/_src/utils/mesh.py, newton/utils.py
Adds detailed docs; plane implemented explicitly; cylinder gets top_radius and validation; cone via cylinder; re-export mesh creators from utils.
Tests expansion
newton/tests/test_examples.py
Adds use_viewer mode; registers new/renamed examples; adjusts USD/viewer routing; prints stderr on failure.
Docs and assets
README.md, docs/images/examples/resize.*, .gitattributes
Adds Examples section and usage; image resize scripts (sh/bat); mark .jpg as binary in Git.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant App as Example
  participant Viewer as ViewerBase/GL/Rerun/USD
  participant Model as Model
  participant Solver as SolverXPBD/…

  User->>App: create viewer via newton.examples.init()
  App->>Viewer: set_model(Model)
  Note over Viewer: Binds device, populates shapes
  loop per frame
    App->>Viewer: begin_frame(time)
    App->>Solver: clear_forces()
    App->>Viewer: apply_forces(state)  # picking/wind guards if no model
    App->>Model: collide(state, contacts)
    App->>Solver: step(state, control, contacts, dt)
    App->>Viewer: log_state(state)
    App->>Viewer: log_contacts(state, contacts)
    App->>Viewer: end_frame()
  end
Loading
sequenceDiagram
  autonumber
  participant Builder as ModelBuilder
  participant Sub as Sub-Builder
  participant Grid as _compute_replicate_offsets

  Builder->>Grid: compute offsets(num_copies, spacing)
  Grid-->>Builder: list[vec3] offsets
  loop i in num_copies
    Builder->>Builder: add_builder(Sub, xform=transform(offsets[i], quat_identity))
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120–180 minutes

Possibly related PRs

Suggested reviewers

  • eric-heiden
  • nvlukasz

Tip

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

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

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

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

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

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

Status, Documentation and Community

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (3)
newton/_src/viewer/__init__.py (1)

22-38: Example still shows ViewerGL(model) — update to new model binding flow.

Viewers no longer require a model at construction. The snippet should reflect set_model.

Apply this doc update:

-    # Create viewer with OpenGL backend
-    viewer = ViewerGL(model)
+    # Create viewer with OpenGL backend
+    viewer = ViewerGL()
+    viewer.set_model(model)
newton/_src/viewer/viewer_rerun.py (2)

61-68: Potential UnboundLocalError: server_uri used when not defined; address argument unused

If server=False, server_uri is never set but is referenced when launching the viewer. Also, the address parameter is not used, preventing client-mode connections.

-        # Set up connection based on mode
-        if self.server:
-            server_uri = rr.serve_grpc()
-
-        # Optionally launch viewer client
-        if self.launch_viewer:
-            rr.serve_web_viewer(connect_to=server_uri)
+        # Set up connection based on mode
+        server_uri = None
+        if self.server:
+            # Start an in-process server; let rerun pick an available port
+            server_uri = rr.serve_grpc()
+        else:
+            # Connect to an existing server
+            try:
+                rr.connect(self.address)
+            except Exception as e:
+                raise RuntimeError(f"Failed to connect to rerun server at {self.address}: {e}") from e
+
+        # Optionally launch viewer client
+        if self.launch_viewer:
+            rr.serve_web_viewer(connect_to=server_uri)

151-157: vertex_colors may be referenced before assignment

When colors is None, vertex_colors is undefined but still passed to rr.Mesh3D, causing UnboundLocalError. Default to None.

-            # Log the base mesh with optional colors
+            # Log the base mesh with optional colors
+            vertex_colors = vertex_colors if 'vertex_colors' in locals() else None
             mesh_3d = rr.Mesh3D(
                 vertex_positions=mesh_data["points"],
                 triangle_indices=mesh_data["indices"],
                 vertex_normals=mesh_data["normals"],
                 vertex_colors=vertex_colors,
             )

Alternatively, set vertex_colors = None before the if block.

🧹 Nitpick comments (17)
newton/_src/viewer/viewer_usd.py (2)

20-26: Docstring default contradicts code (fps default 24 vs 60).

The Args section says fps default is 24, but the constructor uses 60. Align the docs or the code.

Apply this diff to fix the docstring:

-        fps: Frames per second for time sampling (default: 24)
+        fps: Frames per second for time sampling (default: 60)

207-214: PointInstancer ids/protoIndices authored only once — can desync when instance count changes across frames.

If num_instances varies over time, fixed ids/protoIndices authored “once” will mismatch attribute lengths and cause viewer issues. Re-author when counts differ.

Apply this diff:

-            # Initialize ids and protoIndices once for count stability
-            ids_attr = instancer.GetIdsAttr()
-            if ids_attr.GetNumTimeSamples() == 0 and not ids_attr.HasAuthoredValueOpinion():
-                instancer.CreateIdsAttr().Set(list(range(num_instances)))
-                instancer.CreateProtoIndicesAttr().Set([0] * num_instances)
+            # Ensure ids/protoIndices length matches current instance count
+            ids_attr = instancer.GetIdsAttr()
+            existing_ids = ids_attr.Get() if ids_attr.IsValid() else None
+            if existing_ids is None or len(existing_ids) != num_instances:
+                instancer.CreateIdsAttr().Set(list(range(num_instances)))
+                instancer.CreateProtoIndicesAttr().Set([0] * num_instances)
newton/_src/sim/builder.py (1)

575-582: Nit: Ensure transform arg types are explicit vec3 to avoid Warp coercion surprises.

Passing a NumPy row into wp.transform usually works, but being explicit improves readability and type clarity.

Apply this diff:

-        for i in range(num_copies):
-            self.add_builder(builder, xform=wp.transform(offsets[i], wp.quat_identity()))
+        for i in range(num_copies):
+            p = wp.vec3(float(offsets[i][0]), float(offsets[i][1]), float(offsets[i][2]))
+            self.add_builder(builder, xform=wp.transform(p, wp.quat_identity()))
newton/examples/__init__.py (1)

50-56: Deprecation warning: good placement and stacklevel

The explicit warning and stacklevel are appropriate. Consider adding a brief pointer to docs/migration.rst in the message to aid users migrating from compute_env_offsets to builder.replicate().

-    warnings.warn(
-        "compute_env_offsets is deprecated and will be removed in a future version. Use the builder.replicate() function instead.",
+    warnings.warn(
+        "compute_env_offsets is deprecated and will be removed in a future version. Use builder.replicate() instead. See docs/migration.rst for details.",
         stacklevel=2,
     )
newton/examples/basic/example_basic_pendulum.py (3)

16-26: Header comment is incorrect for this example

The header says “Example Basic URDF” and references a quadruped/URDF, but this file implements a pendulum example. Please correct to avoid confusion.

-###########################################################################
-# Example Basic URDF
+###########################################################################
+# Example Basic Pendulum
@@
-# Shows how to set up a simulation of a rigid-body quadruped articulation
-# from a URDF using the newton.ModelBuilder().
+# Shows how to set up and simulate a simple two-link pendulum using
+# newton.ModelBuilder().
@@
-# Users can pick bodies by right-clicking and dragging with the mouse.
+# Users can pick bodies by right-clicking and dragging with the mouse (in ViewerGL).

59-64: Consider specifying body mass to ensure dynamic behavior

By default, ModelBuilder.add_body() may create kinematic bodies if mass is 0. If that’s the case in this codebase, the pendulum won’t move. Consider setting a positive mass per link or using builder defaults to ensure dynamic simulation.

Would you like a patch that sets per-link masses and inertia approximations based on the box dimensions?


99-101: Minor grammar: “its” vs “it’s”

“It’s” is a contraction; possessive is “its.”

-        # put graph capture into it's own function
+        # put graph capture into its own function
newton/_src/viewer/viewer_rerun.py (2)

204-210: is_running may never terminate in run(viewer, example) flows

Without a spawned viewer process or an external stop signal, is_running returns self._running which is never toggled, causing infinite loops under newton.examples.run. Consider adding an optional num_frames limit (parity with ViewerNull) or respecting rr.is_recording/connection status.

Would you like a patch to add a num_frames parameter and increment an internal frame counter in begin_frame()/end_frame()?


216-225: _viewer_process lifecycle handling appears unused

You never assign to self._viewer_process; terminate/kill paths won’t execute. Either store the process handle from rr.serve_web_viewer (if available) or remove the dead code.

-        self._viewer_process = None
+        self._viewer_process = None  # Consider capturing a handle if rr exposes it; otherwise remove related code.

If rr.serve_web_viewer doesn’t return a Popen-like handle, remove the terminate/kill branches to avoid confusion.

newton/_src/viewer/viewer_null.py (1)

46-51: Update begin_frame to retain ViewerBase time

For consistency with other backends, call super().begin_frame(time) so self.time is tracked (useful for logs or future extensions).

-    def begin_frame(self, time):
-        pass
+    def begin_frame(self, time):
+        super().begin_frame(time)
newton/examples/basic/example_basic_viewer.py (4)

82-85: Fix viewer type check (always false currently).

viewer is now an object, not a string. The condition on Line 82 never triggers, so the usage hint is never printed for the GL backend.

Apply this diff to correctly detect the GL backend:

-        if viewer == "gl":
-            print("Viewer running. WASD/Arrow keys to move, drag to orbit, scroll to zoom. Close window to exit.")
+        # Only print usage hints for interactive GL viewer
+        try:
+            from newton.viewer import ViewerGL as _ViewerGL
+            if isinstance(viewer, _ViewerGL):
+                print("Viewer running. WASD/Arrow keys to move, drag to orbit, scroll to zoom. Close window to exit.")
+        except Exception:
+            pass

170-177: Align plane geo_scale with ViewerBase.log_geo contract.

log_shapes(..., GeoType.PLANE, (0.0, 0.0, 1.0, 0.0), ...) passes 4 values that are ignored by the plane path (only width/length are used). This is confusing and suggests plane-equation parameters, which aren’t supported here.

Use a 2-tuple (or a single float) to convey “infinite” plane intent:

-        self.viewer.log_shapes(
-            "/plane_instance",
-            newton.GeoType.PLANE,
-            (0.0, 0.0, 1.0, 0.0),
-            wp.array([wp.transform_identity()], dtype=wp.transform),
-            self.col_plane,
-            self.mat_plane,
-        )
+        self.viewer.log_shapes(
+            "/plane_instance",
+            newton.GeoType.PLANE,
+            (0.0, 0.0),  # non-positive -> treated as a large ground plane
+            wp.array([wp.transform_identity()], dtype=wp.transform),
+            self.col_plane,
+            self.mat_plane,
+        )

94-106: Comment/axis mismatch (Y used, comment says X).

The layout uses the Y coordinate (Line 106 uses base_left as Y), but comments say “arrange along X-axis” and “Sphere at x = -6”. Consider aligning comments with the actual axis used to avoid confusion.

Apply this diff to fix comments:

-        # Clean layout: arrange objects in a line along X-axis
+        # Clean layout: arrange objects in a line along Y-axis
...
-        # Sphere: gentle bounce at x = -6
+        # Sphere: gentle bounce at y ≈ -4

179-179: Clarify log_lines path name.

"coordinate_self.axes" looks like a typo and is inconsistent with other log paths that use a leading slash.

Rename for consistency:

-        self.viewer.log_lines("coordinate_self.axes", self.axes_begins, self.axes_ends, self.axes_colors)
+        self.viewer.log_lines("/axes", self.axes_begins, self.axes_ends, self.axes_colors)
newton/_src/viewer/viewer.py (1)

160-163: Fix comment color mismatch.

Comment says orange-red, but the color is green.

Minimal correction:

-        # Use orange-red color for contact normals
+        # Use green color for contact normals
         line_colors = (0.0, 1.0, 0.0)
newton/tests/test_examples_new.py (2)

100-107: Ensure viewer cleanup even on exceptions.

Wrap the frame loop in try/finally to guarantee viewer.close() runs. Reduces resource leaks and stray open windows/files on failure.

Apply this diff:

-        # run for N frames
-        for _ in range(num_frames):
-            example.step()
-            example.render()
-            example.test()
-
-        viewer.close()
+        try:
+            # run for N frames
+            for _ in range(num_frames):
+                example.step()
+                example.render()
+                example.test()
+        finally:
+            try:
+                viewer.close()
+            except Exception:
+                pass

85-88: Remove or use unused env_vars.

env_vars is computed but never used. Either set os.environ["WARP_CACHE_PATH"] inline or drop the block.

One-liner fix:

-        env_vars = os.environ.copy()
-        if warp_cache_path is not None:
-            env_vars["WARP_CACHE_PATH"] = warp_cache_path
+        if warp_cache_path is not None:
+            os.environ["WARP_CACHE_PATH"] = warp_cache_path
📜 Review details

Configuration used: .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.

📥 Commits

Reviewing files that changed from the base of the PR and between 59a7d5a and d6ea91c.

📒 Files selected for processing (15)
  • newton/_src/sim/builder.py (1 hunks)
  • newton/_src/viewer/__init__.py (1 hunks)
  • newton/_src/viewer/gl/shaders.py (0 hunks)
  • newton/_src/viewer/picking.py (2 hunks)
  • newton/_src/viewer/viewer.py (9 hunks)
  • newton/_src/viewer/viewer_gl.py (8 hunks)
  • newton/_src/viewer/viewer_null.py (1 hunks)
  • newton/_src/viewer/viewer_rerun.py (1 hunks)
  • newton/_src/viewer/viewer_usd.py (2 hunks)
  • newton/examples/__init__.py (3 hunks)
  • newton/examples/basic/example_basic_pendulum.py (1 hunks)
  • newton/examples/basic/example_basic_urdf.py (1 hunks)
  • newton/examples/basic/example_basic_viewer.py (6 hunks)
  • newton/tests/test_examples_new.py (1 hunks)
  • newton/viewer.py (1 hunks)
💤 Files with no reviewable changes (1)
  • newton/_src/viewer/gl/shaders.py
🧰 Additional context used
🧠 Learnings (1)
📓 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.
🧬 Code Graph Analysis (10)
newton/_src/viewer/__init__.py (1)
newton/_src/viewer/viewer_null.py (1)
  • ViewerNull (22-69)
newton/viewer.py (1)
newton/_src/viewer/viewer_null.py (1)
  • ViewerNull (22-69)
newton/examples/basic/example_basic_urdf.py (4)
newton/_src/sim/builder.py (3)
  • ModelBuilder (68-4112)
  • replicate (575-581)
  • add_ground_plane (2201-2221)
newton/examples/__init__.py (2)
  • get_asset (32-33)
  • run (36-44)
newton/_src/viewer/viewer.py (6)
  • set_model (66-74)
  • apply_forces (367-368)
  • begin_frame (76-77)
  • log_state (79-100)
  • log_contacts (102-163)
  • end_frame (371-372)
newton/_src/viewer/viewer_gl.py (6)
  • set_model (94-100)
  • apply_forces (262-264)
  • begin_frame (243-244)
  • log_state (201-208)
  • end_frame (246-260)
  • ViewerGL (33-1054)
newton/examples/__init__.py (6)
newton/tests/test_examples_new.py (1)
  • run (65-113)
newton/_src/viewer/viewer_usd.py (2)
  • is_running (258-262)
  • close (264-267)
newton/_src/viewer/viewer_null.py (2)
  • is_running (52-53)
  • close (55-56)
newton/_src/viewer/viewer_rerun.py (2)
  • is_running (204-209)
  • close (211-230)
newton/_src/viewer/viewer_gl.py (2)
  • is_running (297-299)
  • close (305-307)
newton/_src/viewer/viewer.py (1)
  • close (375-376)
newton/examples/basic/example_basic_pendulum.py (3)
newton/_src/sim/builder.py (5)
  • ModelBuilder (68-4112)
  • add_articulation (583-589)
  • add_shape_box (2256-2293)
  • add_joint_revolute (1062-1144)
  • add_ground_plane (2201-2221)
newton/_src/viewer/viewer.py (7)
  • finalize (401-409)
  • set_model (66-74)
  • apply_forces (367-368)
  • begin_frame (76-77)
  • log_state (79-100)
  • log_contacts (102-163)
  • end_frame (371-372)
newton/examples/__init__.py (1)
  • run (36-44)
newton/_src/viewer/viewer_null.py (3)
newton/_src/viewer/viewer.py (10)
  • ViewerBase (33-649)
  • log_mesh (334-344)
  • log_instances (347-348)
  • begin_frame (76-77)
  • end_frame (371-372)
  • close (375-376)
  • log_lines (351-352)
  • log_points (355-356)
  • log_array (359-360)
  • log_scalar (363-364)
newton/_src/viewer/viewer_usd.py (10)
  • log_mesh (60-115)
  • log_instances (117-241)
  • begin_frame (243-252)
  • end_frame (254-256)
  • is_running (258-262)
  • close (264-267)
  • log_lines (270-272)
  • log_points (274-276)
  • log_array (278-280)
  • log_scalar (282-284)
newton/_src/viewer/viewer_gl.py (10)
  • log_mesh (102-122)
  • log_instances (124-136)
  • begin_frame (243-244)
  • end_frame (246-260)
  • is_running (297-299)
  • close (305-307)
  • log_lines (138-190)
  • log_points (192-193)
  • log_array (195-196)
  • log_scalar (198-199)
newton/tests/test_examples_new.py (3)
newton/tests/unittest_utils.py (4)
  • add_function_test (281-300)
  • get_selected_cuda_test_devices (56-95)
  • get_test_devices (98-132)
  • sanitize_identifier (271-278)
newton/_src/viewer/viewer_usd.py (2)
  • ViewerUSD (14-317)
  • close (264-267)
newton/_src/viewer/viewer_null.py (1)
  • ViewerNull (22-69)
newton/_src/viewer/viewer_gl.py (4)
newton/_src/viewer/gl/opengl.py (1)
  • RendererGL (689-1488)
newton/_src/viewer/viewer.py (2)
  • set_model (66-74)
  • begin_frame (76-77)
newton/_src/viewer/picking.py (2)
  • Picking (25-163)
  • pick (101-163)
newton/_src/viewer/camera.py (1)
  • Camera (19-186)
newton/examples/basic/example_basic_viewer.py (8)
newton/examples/basic/example_basic_pendulum.py (4)
  • Example (36-138)
  • step (123-129)
  • render (134-138)
  • test (131-132)
newton/_src/viewer/viewer_usd.py (4)
  • begin_frame (243-252)
  • log_lines (270-272)
  • end_frame (254-256)
  • ViewerUSD (14-317)
newton/_src/viewer/viewer.py (4)
  • begin_frame (76-77)
  • log_shapes (165-246)
  • log_lines (351-352)
  • end_frame (371-372)
newton/_src/viewer/viewer_null.py (3)
  • begin_frame (46-47)
  • log_lines (59-60)
  • end_frame (49-50)
newton/_src/viewer/viewer_rerun.py (4)
  • begin_frame (193-197)
  • log_lines (233-234)
  • end_frame (199-202)
  • ViewerRerun (30-243)
newton/_src/viewer/viewer_gl.py (4)
  • begin_frame (243-244)
  • log_lines (138-190)
  • end_frame (246-260)
  • ViewerGL (33-1054)
newton/_src/geometry/types.py (1)
  • GeoType (25-36)
newton/examples/__init__.py (1)
  • run (36-44)
newton/_src/viewer/viewer.py (1)
newton/_src/viewer/viewer_gl.py (3)
  • set_model (94-100)
  • log_lines (138-190)
  • log_mesh (102-122)
⏰ 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). (4)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • 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/_src/viewer/viewer_usd.py (3)

217-241: Per-instance colors on PointInstancer likely need 'uniform' interpolation, not 'vertex'.

For PointInstancer primvars, per-instance data is typically authored with uniform interpolation. Using vertex here may be ignored by viewers.

Would you confirm with USD docs/viewer behavior? If needed, change interpolation:

-                        col_pv = pv_api.CreatePrimvar(
-                            "displayColor",
-                            Sdf.ValueTypeNames.Color3fArray,
-                            UsdGeom.Tokens.vertex,
-                        )
+                        col_pv = pv_api.CreatePrimvar(
+                            "displayColor",
+                            Sdf.ValueTypeNames.Color3fArray,
+                            UsdGeom.Tokens.uniform,
+                        )

264-268: LGTM: Explicitly releasing the stage reference on close.

Saving the root layer and nulling self.stage is a sensible cleanup.


28-33: All ViewerUSD instantiations updated to new constructor signature

No positional model argument was found in tests or examples—only keyword args (output_path, num_frames, etc.) are used.

• newton/tests/test_examples_new.py (Line 91)
• newton/examples/basic/example_basic_viewer.py (Line 198)

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

42-49: LGTM: ViewerNull added to public exports.

Import and all exposure look correct and consistent with the new backend.

newton/viewer.py (1)

21-32: LGTM: Public API now exports ViewerNull.

This lines up with the subpackage exports and enables a no-op backend for headless or dependency-light environments.

newton/_src/viewer/picking.py (2)

63-65: Good guard: avoid kernel launch when no model is bound.

Prevents invalid device access during early viewer lifecycle.


102-104: Good guard in pick(): skip work when model is None.

Consistent with decoupled model binding and avoids unnecessary raycast setup.

newton/examples/__init__.py (2)

36-45: Run loop looks good and aligns with viewer lifecycle

The step/render phasing with ScopedTimer and graceful viewer shutdown is clear and matches backend expectations.


97-101: Public example aliases/export are clear

The aliases and all exports match the new example structure and test harness references.

newton/examples/basic/example_basic_pendulum.py (1)

134-139: Viewer lifecycle usage is correct

begin_frame/log_state/log_contacts/end_frame sequence is consistent with ViewerBase API and backends.

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

75-82: Environment replication and scene composition look good

Using scene.replicate(quadruped, self.num_envs) followed by a single ground plane is clean and aligns with the deprecation of compute_env_offsets.


91-93: FK precomputation call is appropriate

eval_fk prior to stepping helps non-MuJoCo solvers; matches the codebase guidance.


97-104: Scoped CUDA graph capture usage is correct

The capture pattern mirrors the pendulum example and will accelerate steady-state stepping on CUDA devices.

newton/_src/viewer/viewer_rerun.py (1)

73-121: Type conversions and logging to rerun look consistent

Converting Warp arrays to numpy and reshaping triangle indices is correct for rr.Mesh3D.

newton/_src/viewer/viewer_null.py (2)

52-56: Frame-limited loop is appropriate for tests/headless runs

Incrementing frame_count in end_frame and using it for is_running matches the examples runner expectations and test harness behavior.


31-41: No-op logging methods are fine for a null backend

Signatures align with ViewerBase; keeping them as no-ops is appropriate.

newton/examples/basic/example_basic_viewer.py (1)

36-51: LGTM: Nice decoupling and per-instance style setup.

Constructor changes and per-instance color/material arrays are clean and align with the new viewer-driven flow.

newton/_src/viewer/viewer.py (3)

66-75: LGTM: Deferred model binding via set_model.

Single-assignment semantics and calling _populate_shapes() on bind are clear and make device ownership explicit.


111-115: LGTM: Contact logging properly guards the disabled case.

Early clearing with None buffers prevents stale visuals.


539-546: LGTM: Instances batching uses viewer.device consistently.

Batch device propagation looks correct and matches the set_model device selection.

newton/_src/viewer/viewer_gl.py (3)

243-246: LGTM: begin_frame now calls base for consistent timekeeping.

This aligns with ViewerBase semantics and keeps backends consistent.


381-387: LGTM: Right-click picking guarded on state availability.

Prevents pick attempts when no state has been logged yet.


691-702: LGTM: Stats overlay guards model access properly.

Avoids attribute access when no model is bound.

newton/tests/test_examples_new.py (1)

118-150: LGTM: Clean device-parameterized example registration.

Good coverage and device-aware argument injection for CPU/CUDA.

Comment thread newton/_src/sim/builder.py Outdated
Comment thread newton/_src/viewer/viewer_gl.py
Comment thread newton/examples/basic/example_basic_pendulum.py Outdated
Comment thread newton/tests/test_examples_new.py Outdated
Signed-off-by: Gabriel Noya <mega.kaloca@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

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

101-126: Also create per-frame transform arrays on the viewer’s device

Same device-mismatch concern for xforms generated every frame. These should be created on the viewer’s device.

Apply this diff:

-        x_sphere_anim = wp.array([wp.transform(sphere_pos, qy_slow)], dtype=wp.transform)
+        x_sphere_anim = wp.array([wp.transform(sphere_pos, qy_slow)], dtype=wp.transform, device=self.device)
@@
-        x_box_anim = wp.array([wp.transform([0.0, base_left, base_height], qx_slow)], dtype=wp.transform)
+        x_box_anim = wp.array([wp.transform([0.0, base_left, base_height], qx_slow)], dtype=wp.transform, device=self.device)
@@
-        x_cone_anim = wp.array([wp.transform([0.0, base_left, base_height], qy_slow)], dtype=wp.transform)
+        x_cone_anim = wp.array([wp.transform([0.0, base_left, base_height], qy_slow)], dtype=wp.transform, device=self.device)
@@
-        x_cyl_anim = wp.array([wp.transform([0.0, base_left, base_height], qz_slow)], dtype=wp.transform)
+        x_cyl_anim = wp.array([wp.transform([0.0, base_left, base_height], qz_slow)], dtype=wp.transform, device=self.device)
@@
-        x_cap_anim = wp.array([wp.transform(capsule_pos, qy_slow)], dtype=wp.transform)
+        x_cap_anim = wp.array([wp.transform(capsule_pos, qy_slow)], dtype=wp.transform, device=self.device)
@@
-            wp.array([wp.transform_identity()], dtype=wp.transform),
+            wp.array([wp.transform_identity()], dtype=wp.transform, device=self.device),

Also applies to: 170-175

🧹 Nitpick comments (12)
newton/examples/basic/example_basic_urdf.py (7)

71-77: Replication is fine; consider explicit spacing or per-env filtering if environments could interact

Using replicate(quadruped, self.num_envs) with default spacing likely avoids cross-env interactions. If you plan to reduce spacing or increase num_envs significantly, consider making spacing explicit or adding per-environment collision filtering to prevent inter-env collisions.

Example (optional):

-        scene.replicate(quadruped, self.num_envs)
+        scene.replicate(quadruped, self.num_envs, spacing=(5.0, 5.0, 0.0))  # explicit grid spacing

93-95: Fix minor grammar: "it's" → "its"

Nitpick on comment wording.

-        # put graph capture into it's own function
+        # put graph capture into its own function

96-103: Capture device should match model.device to avoid mismatches on multi-device setups

Using wp.get_device() reads the default device, which may differ from model.device. Tie capture to the model’s device for correctness.

-        if wp.get_device().is_cuda:
+        if wp.get_device(self.model.device).is_cuda:
             with wp.ScopedCapture() as capture:
                 self.simulate()
             self.graph = capture.graph
         else:
             self.graph = None

104-116: Contacts logged for rendering can lag one substep; refresh after the loop

You compute contacts before stepping, then swap states; render() logs contacts with the post-step state, which can be visually out of sync by one substep. Refresh contacts after the loop for accurate visualization.

     def simulate(self):
         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.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
+        # refresh contacts for the post-step state used for rendering
+        self.contacts = self.model.collide(self.state_0)

125-127: Provide a minimal smoke test implementation or remove the stub

Empty test() can hinder automated example tests. Implement a short smoke step or remove the method if not used by the harness.

-    def test(self):
-        pass
+    def test(self):
+        # Smoke test: run a couple of substeps to validate stepping without rendering
+        for _ in range(2):
+            self.simulate()

If the test harness doesn’t call test(), consider removing the method. I can wire this to newton/tests/test_examples*.py if you’d like.


35-37: Make num_envs optional in example_basic_urdf.py for consistency

Update the URDF example’s constructor so it matches the other basic examples (which take only viewer) and avoids breaking calls that omit num_envs.

• File: newton/examples/basic/example_basic_urdf.py
Change the __init__ signature as follows:

 class Example:
-    def __init__(self, viewer, num_envs):
+    def __init__(self, viewer, num_envs=1):
     # setup simulation parameters first
     self.fps = 100

This lets you call Example(viewer) just like in example_basic_pendulum.py and example_basic_viewer.py, while still allowing explicit overrides (e.g. num_envs=100).


136-139: Add headless fallback to ViewerNull in example_basic_urdf.py

Creating ViewerGL can fail on servers without an OpenGL context (e.g., CI or headless environments). Wrap its construction in a try/except and fall back to ViewerNull, which is already exported in newton.viewer.

• File: newton/examples/basic/example_basic_urdf.py (around lines 136–139)
• Change:

-if __name__ == "__main__":
-    viewer = newton.viewer.ViewerGL()
-    example = Example(viewer, num_envs=100)
-
-    newton.examples.run(example)
+if __name__ == "__main__":
+    try:
+        viewer = newton.viewer.ViewerGL()
+    except Exception:
+        # Headless fallback for CI / non-GL environments
+        viewer = newton.viewer.ViewerNull()
+    example = Example(viewer, num_envs=100)
+    newton.examples.run(example)
newton/examples/basic/example_basic_viewer.py (5)

94-99: Fix axis mentions in comments (objects are laid out along Y, not X)

Comments say X-axis and “x = …”, but base_left increments along Y in transforms.

Apply this diff:

-        # Clean layout: arrange objects in a line along X-axis
+        # Clean layout: arrange objects in a line along Y-axis
@@
-        # Sphere: gentle bounce at x = -6
+        # Sphere: gentle bounce at y = -4 (initial)
@@
-        # Box: rocking rotation at x = -3
+        # Box: rocking rotation at y = -2
@@
-        # Cone: spinning at origin (x = 0)
+        # Cone: spinning at origin (y = 0)
@@
-        # Cylinder: spinning on different axis at x = 3
+        # Cylinder: spinning on different axis at y = +2
@@
-        # Capsule: gentle sway at x = 6
+        # Capsule: gentle sway at y = +4

Also applies to: 105-126


179-179: Tweak log_lines path for readability

Name “coordinate_self.axes” reads odd. Consider a clearer path-like name.

-        self.viewer.log_lines("coordinate_self.axes", self.axes_begins, self.axes_ends, self.axes_colors)
+        self.viewer.log_lines("/debug/axes", self.axes_begins, self.axes_ends, self.axes_colors)

186-187: Optional: add a tiny smoke test to exercise rendering without a window

A short headless smoke in test() helps CI run examples (e.g., with ViewerUSD or ViewerNull). Not required, but useful.

Example implementation (adjust if your test harness provides a viewer):

def test(self):
    # Render a handful of frames deterministically
    for _ in range(5):
        self.render()

If you prefer a Null backend, I can open a follow-up PR to add a CLI --viewer null option and wire this example into the example tests.


192-193: Optional: Add “null” viewer backend for headless CI

ViewerNull is already implemented and exported in newton.viewer. Adding a "null" choice to the example’s --viewer flag enables faster, window-free CI runs.

In newton/examples/basic/example_basic_viewer.py:

• Update the argument parser to include "null" in the choices.
• Add an elif args.viewer == "null": branch to import and instantiate ViewerNull.

Proposed diff:

-    parser.add_argument("--viewer", choices=["gl", "usd", "rerun"], default="gl", help="Viewer backend to use.")
+    parser.add_argument("--viewer", choices=["gl", "usd", "rerun", "null"], default="gl", help="Viewer backend to use.")
     elif args.viewer == "rerun":
         from newton.viewer import ViewerRerun
         viewer = ViewerRerun(server=True, launch_viewer=True)
+    elif args.viewer == "null":
+        from newton.viewer import ViewerNull
+        viewer = ViewerNull(num_frames=120)

170-177: Plane geo_scale should be width/length or float, not a 4-tuple

The log_shapes doc and implementation expect for GeoType.PLANE a 2-tuple (width, length) or a single float (both dimensions), with non-positive values signalling an “infinite” plane. Passing a 4-tuple (nx, ny, nz, d) (a plane equation) is unsupported—only the first two entries are used (and extra values will pollute the mesh cache).

• File: newton/examples/basic/example_basic_viewer.py
Lines 172–174: replace the 4-tuple with either a 2-tuple or a float.

Example diffs:

Option A – infinite plane (default checkerboard):

-            newton.GeoType.PLANE,
-            (0.0, 0.0, 1.0, 0.0),
+            newton.GeoType.PLANE,
+            (0.0, 0.0),  # (width, length); zeros → infinite plane

Option B – finite plane size:

-            newton.GeoType.PLANE,
-            (0.0, 0.0, 1.0, 0.0),
+            newton.GeoType.PLANE,
+            12.0,  # width=length=12 units
📜 Review details

Configuration used: .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.

📥 Commits

Reviewing files that changed from the base of the PR and between d6ea91c and bd985a9.

📒 Files selected for processing (4)
  • newton/examples/__init__.py (3 hunks)
  • newton/examples/basic/example_basic_pendulum.py (1 hunks)
  • newton/examples/basic/example_basic_urdf.py (1 hunks)
  • newton/examples/basic/example_basic_viewer.py (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • newton/examples/init.py
  • newton/examples/basic/example_basic_pendulum.py
🧰 Additional context used
🧠 Learnings (1)
📓 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.
🧬 Code Graph Analysis (2)
newton/examples/basic/example_basic_urdf.py (5)
newton/examples/basic/example_basic_pendulum.py (5)
  • Example (35-133)
  • capture (97-103)
  • simulate (105-116)
  • step (118-124)
  • render (129-133)
newton/_src/sim/builder.py (4)
  • ModelBuilder (68-4112)
  • replicate (575-581)
  • add_ground_plane (2201-2221)
  • finalize (3742-4080)
newton/examples/__init__.py (2)
  • get_asset (32-33)
  • run (36-44)
newton/_src/viewer/viewer.py (7)
  • finalize (401-409)
  • set_model (66-74)
  • apply_forces (367-368)
  • begin_frame (76-77)
  • log_state (79-100)
  • log_contacts (102-163)
  • end_frame (371-372)
newton/_src/viewer/viewer_gl.py (6)
  • set_model (94-100)
  • apply_forces (262-264)
  • begin_frame (243-244)
  • log_state (201-208)
  • end_frame (246-260)
  • ViewerGL (33-1054)
newton/examples/basic/example_basic_viewer.py (6)
newton/_src/viewer/viewer_usd.py (4)
  • begin_frame (243-252)
  • log_lines (270-272)
  • end_frame (254-256)
  • ViewerUSD (14-317)
newton/_src/viewer/viewer.py (4)
  • begin_frame (76-77)
  • log_shapes (165-246)
  • log_lines (351-352)
  • end_frame (371-372)
newton/_src/viewer/viewer_null.py (3)
  • begin_frame (46-47)
  • log_lines (59-60)
  • end_frame (49-50)
newton/_src/viewer/viewer_gl.py (3)
  • begin_frame (243-244)
  • log_lines (138-190)
  • end_frame (246-260)
newton/_src/geometry/types.py (1)
  • GeoType (25-36)
newton/examples/__init__.py (1)
  • run (36-44)
⏰ 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). (4)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
🔇 Additional comments (6)
newton/examples/basic/example_basic_urdf.py (4)

117-124: LGTM: Graph-based stepping fallback is correct

Step() cleanly falls back to simulate() when CUDA graph is unavailable and advances sim_time consistently.


128-133: LGTM: Rendering pipeline aligns with viewer expectations

begin_frame → log_state → log_contacts → end_frame is consistent with ViewerBase/ViewerGL APIs.


16-26: Migration guide note: This example adds Newton-only functionality; migration doc likely doesn’t need updates

Per the team learning, docs/migration.rst is for warp.sim → Newton migration. This example demonstrates Newton features (URDF + replication) rather than deprecating/replacing a warp.sim API, so migration docs may be unaffected.

If you agree, check off the template item with a note “N/A – Newton-only example (no warp.sim equivalent)”.


61-70: Quadruped URDF asset and joint slicing validated

  • quadruped.urdf exists at newton/examples/assets/quadruped.urdf.
  • parse_urdf is defined in newton/_src/utils/import_urdf.py (and exercised in tests).
  • The trailing [-12:] slice is used identically in example_quadruped.py and example_mujoco.py, confirming those DOFs map to the four legs.

No changes required.

newton/examples/basic/example_basic_viewer.py (2)

92-92: LGTM: deterministic time stepping and begin_frame time plumbing

Using an internal time accumulator and passing it to begin_frame is consistent with USD frame mapping and deterministic playback.

Also applies to: 184-185


195-209: LGTM: backend selection + unified run loop

Viewer is constructed externally and injected into Example; delegating to newton.examples.run(example) matches the updated architecture.

Comment thread newton/examples/basic/example_basic_viewer.py
Comment thread newton/examples/basic/example_basic_viewer.py Outdated
…e test_examples.py to use new structure

Signed-off-by: Miles Macklin <mmacklin@nvidia.com>
Signed-off-by: Miles Macklin <mmacklin@nvidia.com>
…into example-refactor

Signed-off-by: Miles Macklin <mmacklin@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

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

48-62: Boolean CLI options builder produces duplicate/conflicting flags

The conditional chain uses two ifs with a single else, causing booleans to emit both “--key/--no-key” and “--key value” forms. This can break argparse. Switch the second if to elif and avoid appending a value for booleans.

Apply this diff:

-    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():
+        arg = f"--{key.replace('_', '-')}"
+        if isinstance(value, bool):
+            # Expect argparse.BooleanOptionalAction
+            additional_options.append(f"--{'no-' if not value else ''}{key.replace('_', '-')}")
+        elif isinstance(value, list):
+            additional_options.extend([arg] + [str(v) for v in value])
+        else:
+            additional_options.extend([arg, str(value)])
newton/examples/basic/example_basic_viewer.py (1)

104-124: Fix axis placement: base_left is used as Y instead of X; correct transforms and capsule sway

Comments and layout intent say “arrange objects along X”; current code varies Y. Also make capsule sway around its X position.

Apply this diff:

-        # Sphere: gentle bounce at x = -6
-        sphere_pos = wp.vec3(0.0, base_left, base_height + 0.3 * abs(math.sin(1.2 * self.time)))
+        # Sphere: gentle bounce along X
+        sphere_pos = wp.vec3(base_left, 0.0, base_height + 0.3 * abs(math.sin(1.2 * self.time)))
         x_sphere_anim = wp.array([wp.transform(sphere_pos, qy_slow)], dtype=wp.transform)
 
         base_left += spacing
 
         # Box: rocking rotation at x = -3
-        x_box_anim = wp.array([wp.transform([0.0, base_left, base_height], qx_slow)], dtype=wp.transform)
+        x_box_anim = wp.array([wp.transform(wp.vec3(base_left, 0.0, base_height), qx_slow)], dtype=wp.transform)
         base_left += spacing
 
         # Cone: spinning at origin (x = 0)
-        x_cone_anim = wp.array([wp.transform([0.0, base_left, base_height], qy_slow)], dtype=wp.transform)
+        x_cone_anim = wp.array([wp.transform(wp.vec3(base_left, 0.0, base_height), qy_slow)], dtype=wp.transform)
         base_left += spacing
 
         # Cylinder: spinning on different axis at x = 3
-        x_cyl_anim = wp.array([wp.transform([0.0, base_left, base_height], qz_slow)], dtype=wp.transform)
+        x_cyl_anim = wp.array([wp.transform(wp.vec3(base_left, 0.0, base_height), qz_slow)], dtype=wp.transform)
         base_left += spacing
 
         # Capsule: gentle sway at x = 6
-        capsule_pos = wp.vec3(0.3 * math.sin(0.8 * self.time), base_left, base_height)
+        capsule_pos = wp.vec3(base_left + 0.3 * math.sin(0.8 * self.time), 0.0, base_height)
         x_cap_anim = wp.array([wp.transform(capsule_pos, qy_slow)], dtype=wp.transform)

Note: You may also want to update the nearby comments that hardcode X positions (e.g., “x = -6”, “x = -3”) since the starting offset is -4.0. Consider removing absolute values or adjusting base_left/spacing to match.

♻️ Duplicate comments (4)
newton/examples/basic/example_basic_pendulum.py (1)

56-70: Correct Warp API usage for transforms — past issue addressed

Using wp.transform with wp.quat_identity() is the right API (vs. non-existent wp.transformf). Hinge frames for both joints look consistent.

newton/_src/viewer/viewer_gl.py (1)

92-101: Set a default Warp device when no model is provided

If set_model(None) is used (as here), self.device stays None and log_mesh/log_lines will fail when allocating arrays. Initialize a sensible default device for model-less flows.

Apply this diff:

     def set_model(self, model):
-        super().set_model(model)
+        super().set_model(model)
+
+        # Ensure a valid device for model-less workflows (e.g., viewer-only)
+        if model is None and self.device is None:
+            self.device = wp.get_device()
newton/examples/basic/example_basic_viewer.py (2)

36-46: Allocate all wp.arrays on the viewer device (GPU/CPU) to prevent device-mismatch bugs

Create a local device handle from the viewer and pass device=self.device to every wp.array (colors, materials, axes, and per-frame transform arrays). This avoids GL backend crashes when Warp’s default device differs from the viewer device.

Apply this diff:

 def __init__(self, viewer):
-        self.viewer = viewer
+        self.viewer = viewer
+        self.device = viewer.device

         # self.colors and materials per instance
-        self.col_sphere = wp.array([wp.vec3(0.9, 0.1, 0.1)], dtype=wp.vec3)
-        self.col_box = wp.array([wp.vec3(0.1, 0.9, 0.1)], dtype=wp.vec3)
-        self.col_cone = wp.array([wp.vec3(0.1, 0.4, 0.9)], dtype=wp.vec3)
-        self.col_capsule = wp.array([wp.vec3(0.9, 0.9, 0.1)], dtype=wp.vec3)
-        self.col_cylinder = wp.array([wp.vec3(0.8, 0.5, 0.2)], dtype=wp.vec3)
-        self.col_plane = wp.array([wp.vec3(0.125, 0.125, 0.15)], dtype=wp.vec3)
+        self.col_sphere = wp.array([wp.vec3(0.9, 0.1, 0.1)], dtype=wp.vec3, device=self.device)
+        self.col_box = wp.array([wp.vec3(0.1, 0.9, 0.1)], dtype=wp.vec3, device=self.device)
+        self.col_cone = wp.array([wp.vec3(0.1, 0.4, 0.9)], dtype=wp.vec3, device=self.device)
+        self.col_capsule = wp.array([wp.vec3(0.9, 0.9, 0.1)], dtype=wp.vec3, device=self.device)
+        self.col_cylinder = wp.array([wp.vec3(0.8, 0.5, 0.2)], dtype=wp.vec3, device=self.device)
+        self.col_plane = wp.array([wp.vec3(0.125, 0.125, 0.15)], dtype=wp.vec3, device=self.device)

         # material = (metallic, roughness, checker, unused)
-        self.mat_default = wp.array([wp.vec4(0.0, 0.7, 0.0, 0.0)], dtype=wp.vec4)
-        self.mat_plane = wp.array([wp.vec4(0.5, 0.5, 1.0, 0.0)], dtype=wp.vec4)
+        self.mat_default = wp.array([wp.vec4(0.0, 0.7, 0.0, 0.0)], dtype=wp.vec4, device=self.device)
+        self.mat_plane = wp.array([wp.vec4(0.5, 0.5, 1.0, 0.0)], dtype=wp.vec4, device=self.device)

         # Demonstrate log_lines() with animated debug/visualization lines
         axis_eps = 0.01
         axis_length = 2.0
-        self.axes_begins = wp.array(
+        self.axes_begins = wp.array(
             [
                 wp.vec3(0.0, 0.0, axis_eps),  # X axis start
                 wp.vec3(0.0, 0.0, axis_eps),  # Y axis start
                 wp.vec3(0.0, 0.0, axis_eps),  # Z axis start
             ],
-            dtype=wp.vec3,
+            dtype=wp.vec3,
+            device=self.device,
         )

-        self.axes_ends = wp.array(
+        self.axes_ends = wp.array(
             [
                 wp.vec3(axis_length, 0.0, axis_eps),  # X axis end
                 wp.vec3(0.0, axis_length, axis_eps),  # Y axis end
                 wp.vec3(0.0, 0.0, axis_length + axis_eps),  # Z axis end
             ],
-            dtype=wp.vec3,
+            dtype=wp.vec3,
+            device=self.device,
         )

-        self.axes_colors = wp.array(
+        self.axes_colors = wp.array(
             [
                 wp.vec3(1.0, 0.0, 0.0),  # Red X
                 wp.vec3(0.0, 1.0, 0.0),  # Green Y
                 wp.vec3(0.0, 0.0, 1.0),  # Blue Z
             ],
-            dtype=wp.vec3,
+            dtype=wp.vec3,
+            device=self.device,
         )

And in render (per-frame transform arrays):

-        x_sphere_anim = wp.array([wp.transform(sphere_pos, qy_slow)], dtype=wp.transform)
+        x_sphere_anim = wp.array([wp.transform(sphere_pos, qy_slow)], dtype=wp.transform, device=self.device)

-        x_box_anim = wp.array([wp.transform([0.0, base_left, base_height], qx_slow)], dtype=wp.transform)
+        x_box_anim = wp.array([wp.transform([0.0, base_left, base_height], qx_slow)], dtype=wp.transform, device=self.device)

-        x_cone_anim = wp.array([wp.transform([0.0, base_left, base_height], qy_slow)], dtype=wp.transform)
+        x_cone_anim = wp.array([wp.transform([0.0, base_left, base_height], qy_slow)], dtype=wp.transform, device=self.device)

-        x_cyl_anim = wp.array([wp.transform([0.0, base_left, base_height], qz_slow)], dtype=wp.transform)
+        x_cyl_anim = wp.array([wp.transform([0.0, base_left, base_height], qz_slow)], dtype=wp.transform, device=self.device)

-        x_cap_anim = wp.array([wp.transform(capsule_pos, qy_slow)], dtype=wp.transform)
+        x_cap_anim = wp.array([wp.transform(capsule_pos, qy_slow)], dtype=wp.transform, device=self.device)

Also applies to: 47-49, 54-61, 63-70, 72-79, 106-106, 111-111, 115-115, 119-119, 124-124


81-82: Remove stale string check for viewer type; print GL help in main instead

viewer is an object, so viewer == "gl" will never be True. Move the guidance print into main based on args.viewer.

Apply these diffs:

-        if viewer == "gl":
-            print("Viewer running. WASD/Arrow keys to move, drag to orbit, scroll to zoom. Close window to exit.")
 if __name__ == "__main__":
     # Parse arguments and initialize viewer
     viewer, args = newton.examples.init()

-    # Create viewer and run
+    # Print GL-only help when using the GL viewer backend
+    if getattr(args, "viewer", None) == "gl":
+        print("Viewer running. WASD/Arrow keys to move, drag to orbit, scroll to zoom. Close window to exit.")
+
+    # Create viewer and run
     example = Example(viewer)
     newton.examples.run(example)

Also applies to: 189-196

🧹 Nitpick comments (5)
newton/tests/test_examples.py (1)

179-182: Reduce noisy logs: print stderr only on failure (optional)

Currently prints stderr even on success, which can spam CI logs. Consider printing stderr/stdout only when returncode != 0.

Apply this diff:

-        # print any error messages (e.g.: module not found)
-        if result.stderr != "":
-            print(result.stderr)
+        # Print process output on failure to aid debugging
+        if result.returncode != 0:
+            if result.stdout:
+                print(result.stdout)
+            if result.stderr:
+                print(result.stderr)
newton/examples/__init__.py (1)

122-134: Docstring parameter mismatch (nit): clarify the ‘parser’ argument

The function expects an argparse.ArgumentParser (or None) and internally calls parse_known_args, but the docstring says “Parsed arguments.” Recommend updating the docstring for accuracy.

Apply this diff:

-    Args:
-        parser: Parsed arguments from argparse (should include arguments from
-              create_parser())
+    Args:
+        parser: An argparse.ArgumentParser (or None). If None, a base parser from
+                create_parser() is constructed and parse_known_args() is used.
newton/examples/basic/example_basic_viewer.py (3)

178-178: Typo in log_lines name

Use a clearer identifier without “self.” in the literal string.

-        self.viewer.log_lines("coordinate_self.axes", self.axes_begins, self.axes_ends, self.axes_colors)
+        self.viewer.log_lines("coordinate_axes", self.axes_begins, self.axes_ends, self.axes_colors)

89-124: Optional: Avoid per-frame allocations by reusing xform arrays

Allocate one-element wp.arrays for each x_*_anim in init and update their element each frame instead of constructing new arrays. This reduces Python/allocator overhead and GPU allocations.

Example outline (outside current ranges):

  • In init: create x_sphere_anim = wp.zeros(1, dtype=wp.transform, device=self.device) for each.
  • In render(): write x_sphere_anim[0] = wp.transform(...).

185-186: Implement a tiny smoke test for test harnesses that call Example.test()

If newton/tests/test_examples.py expects examples to expose a test() entrypoint, calling render once is usually sufficient and works with ViewerNull.

Apply this diff:

-    def test(self):
-        pass
+    def test(self):
+        # Single render step for CI smoke testing (works with ViewerNull)
+        self.render()

Please confirm how test_examples.py discovers and executes example tests; if it expects a different contract, I can adjust accordingly.

📜 Review details

Configuration used: .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.

📥 Commits

Reviewing files that changed from the base of the PR and between bd985a9 and dd3f18f.

📒 Files selected for processing (7)
  • newton/_src/viewer/gl/opengl.py (1 hunks)
  • newton/_src/viewer/viewer_gl.py (9 hunks)
  • newton/examples/__init__.py (3 hunks)
  • newton/examples/basic/example_basic_pendulum.py (1 hunks)
  • newton/examples/basic/example_basic_urdf.py (1 hunks)
  • newton/examples/basic/example_basic_viewer.py (5 hunks)
  • newton/tests/test_examples.py (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • newton/examples/basic/example_basic_urdf.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.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-18T15:56:26.540Z
Learnt from: adenzler-nvidia
PR: newton-physics/newton#552
File: newton/_src/solvers/mujoco/solver_mujoco.py:0-0
Timestamp: 2025-08-18T15:56:26.540Z
Learning: In Newton's MuJoCo solver, when transforming joint axes from Newton's internal frame to MuJoCo's expected frame, use wp.quat_rotate(joint_rot, axis) not wp.quat_rotate_inv(joint_rot, axis). The joint_rot represents rotation from joint-local to body frame, so forward rotation is correct.

Applied to files:

  • newton/examples/basic/example_basic_pendulum.py
🧬 Code Graph Analysis (6)
newton/_src/viewer/gl/opengl.py (1)
newton/_src/viewer/viewer_gl.py (2)
  • vsync (310-312)
  • vsync (315-317)
newton/tests/test_examples.py (1)
newton/tests/unittest_utils.py (1)
  • sanitize_identifier (271-278)
newton/examples/__init__.py (2)
newton/tests/test_examples.py (1)
  • run (92-195)
newton/tests/test_examples_new.py (1)
  • run (65-113)
newton/examples/basic/example_basic_pendulum.py (5)
newton/examples/basic/example_basic_urdf.py (5)
  • Example (35-132)
  • capture (96-102)
  • simulate (104-115)
  • step (117-123)
  • render (128-132)
newton/_src/sim/builder.py (2)
  • ModelBuilder (68-4112)
  • add_ground_plane (2201-2221)
newton/_src/viewer/viewer.py (7)
  • finalize (401-409)
  • set_model (66-74)
  • apply_forces (367-368)
  • begin_frame (76-77)
  • log_state (79-100)
  • log_contacts (102-163)
  • end_frame (371-372)
newton/_src/viewer/viewer_gl.py (5)
  • set_model (94-100)
  • apply_forces (262-264)
  • begin_frame (243-244)
  • log_state (201-208)
  • end_frame (246-260)
newton/examples/__init__.py (1)
  • init (122-161)
newton/_src/viewer/viewer_gl.py (4)
newton/_src/viewer/viewer.py (2)
  • set_model (66-74)
  • begin_frame (76-77)
newton/_src/viewer/viewer_usd.py (1)
  • begin_frame (243-252)
newton/_src/viewer/viewer_null.py (1)
  • begin_frame (46-47)
newton/_src/viewer/gl/opengl.py (2)
  • get_vsync (957-963)
  • set_vsync (949-955)
newton/examples/basic/example_basic_viewer.py (3)
newton/_src/viewer/viewer_gl.py (3)
  • begin_frame (243-244)
  • log_lines (138-190)
  • end_frame (246-260)
newton/_src/viewer/viewer.py (4)
  • begin_frame (76-77)
  • log_shapes (165-246)
  • log_lines (351-352)
  • end_frame (371-372)
newton/examples/__init__.py (2)
  • init (122-161)
  • run (36-44)
⏰ 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). (4)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)
🔇 Additional comments (20)
newton/_src/viewer/gl/opengl.py (1)

949-964: Runtime vsync control added — looks good

The new set_vsync/get_vsync API aligns with ViewerGL’s property and leverages pyglet’s window hooks directly. Keeps initial construction behavior intact while enabling runtime toggles.

newton/tests/test_examples.py (2)

81-82: New test toggle use_viewer is a good addition

This parameter cleanly separates legacy stage-path mode from the new viewer-driven path without breaking existing tests.


204-220: Viewer-mode smoke tests added — solid coverage

The basic example trio (pendulum, urdf, viewer) exercises the new viewer-oriented flow and USD/null switching. Good addition for preventing regressions in the example stack.

newton/examples/__init__.py (4)

36-45: run(example) loop matches new viewer lifecycle

Steps and renders under timers and ensures viewer closure. Works for GL (is_running()) and aligns with USD/Null flows assuming they implement the same predicate.


50-57: Good deprecation path for compute_env_offsets

Clear warning with replacement guidance to builder.replicate(), consistent with migration notes for warp.sim users.


97-120: Parser baseline is sensible and minimal

Common knobs (device, viewer, output-path, num-frames) are sufficient to bootstrap examples and tests. add_help=False makes it safe as a parent parser.


149-161: Viewer creation paths are sound; USD requires output_path

GL/USD/Null routing is correct and validates USD output path upfront. Returning (viewer, args) simplifies example entrypoints and test harness usage.

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

78-89: Solver wiring and initial FK

MuJoCo solver is instantiated correctly. Calling eval_fk ensures state_0 is coherent for non-MuJoCo solvers; harmless here and keeps parity with other examples.


92-112: CUDA graph capture and substep loop look correct

Capture-once and launch in step() match common patterns. The per-substep sequence (clear forces → apply viewer forces → collide → solver.step → swap) is in the right order.


124-139: Entry point integrates with new examples.init()/run() API

Main uses the shared init/run wiring. This keeps the example runnable both via CLI and the test harness.

newton/_src/viewer/viewer_gl.py (8)

34-41: Constructor refactor: defers model wiring and exposes vsync control

New signature and RendererGL wiring look good. Deferring model setup enables model-less UI/workflows.


244-246: begin_frame delegates to base — good

Ensures ViewerBase bookkeeping (e.g., time) runs consistently across backends.


551-599: UI: guard model-dependent panels

Conditionally showing “Model Information” and visualization toggles only when a model exists avoids None dereferences. Good defensive UI.


608-612: UI vsync toggle correctly routes to renderer

The checkbox uses the new ViewerGL.vsync property which calls RendererGL.set_vsync/get_vsync. Matches opengl.py additions.


707-717: Stats overlay: model metrics correctly gated

Prevents accessing model counters when no model is set.


734-736: Selection panel defaults to closed (sensible)

Keeps UI compact by default; users can expand when needed.


395-397: Picking guard prevents null-state use

Right-click picking only when a state was logged this frame avoids undefined ray/material data paths.


309-318: Public vsync property is simple and clear

Property accessors map 1:1 to renderer methods. No local state duplication — good.

newton/examples/basic/example_basic_viewer.py (2)

32-33: Good change: centralize viewer init/run via newton.examples

Importing newton.examples keeps example bootstrap consistent with the shared init/run helpers.


84-84: Time management and frame sequencing look correct

Internal time accumulator with begin_frame(self.time) and per-frame increment is clean and consistent.

Also applies to: 92-92, 100-103, 183-183

Comment thread newton/examples/basic/example_basic_viewer.py
Comment thread newton/tests/test_examples.py
Signed-off-by: Miles Macklin <mmacklin@nvidia.com>
Signed-off-by: Miles Macklin <mmacklin@nvidia.com>
Signed-off-by: Miles Macklin <mmacklin@nvidia.com>
Signed-off-by: Miles Macklin <mmacklin@nvidia.com>
Signed-off-by: Miles Macklin <mmacklin@nvidia.com>
Signed-off-by: Miles Macklin <mmacklin@nvidia.com>
Signed-off-by: Miles Macklin <mmacklin@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
newton/examples/basic/example_basic_pendulum.py (1)

56-70: Correct Warp API usage for transforms.

Using wp.transform with wp.quat_identity() for parent/child frames is correct and resolves the earlier ‘wp.transformf’ issue noted in prior reviews.

🧹 Nitpick comments (10)
docs/images/examples/.gitattributes (1)

1-1: Broaden LFS patterns to cover .jpeg and uppercase variants.

Some systems or tools may emit .jpeg or uppercase .JPG/.JPEG. Adding these prevents large binaries from slipping outside LFS.

Apply this diff to extend coverage:

 *.jpg filter=lfs diff=lfs merge=lfs -text
+*.jpeg filter=lfs diff=lfs merge=lfs -text
+*.JPG filter=lfs diff=lfs merge=lfs -text
+*.JPEG filter=lfs diff=lfs merge=lfs -text
docs/images/examples/resize.sh (1)

1-2: Add shebang and make robust when no PNGs are present (ShellCheck SC2148).

Without a shebang, shells vary. Also avoid iterating the literal pattern when there are no matches.

Apply this diff:

-# resize all pngs in a directory by 2x in dimension + convert to jpg
-for f in *.png; do ffmpeg -y -i "$f" -vf "scale=iw/2:ih/2" -q:v 3 "${f%.png}.jpg"; done
+#!/usr/bin/env bash
+# resize all pngs in a directory by 2x in dimension + convert to jpg
+set -euo pipefail
+shopt -s nullglob
+for f in *.png; do
+  ffmpeg -y -i "$f" -vf "scale=iw/2:ih/2" -q:v 3 "${f%.png}.jpg"
+done
newton/examples/__init__.py (3)

36-45: Guarantee viewer.close() runs even if step/render raises.

Wrap the loop in try/finally to ensure proper cleanup.

Apply this diff:

-def run(example):
-    while example.viewer.is_running():
-        with wp.ScopedTimer("step"):
-            example.step()
-
-        with wp.ScopedTimer("render"):
-            example.render()
-
-    example.viewer.close()
+def run(example):
+    try:
+        while example.viewer.is_running():
+            with wp.ScopedTimer("step"):
+                example.step()
+            with wp.ScopedTimer("render"):
+                example.render()
+    finally:
+        example.viewer.close()

50-57: Optional guard for invalid num_envs to avoid min/max on empty arrays.

If num_envs <= 0, np.min/np.max will raise. Consider a clearer error.

Apply this diff:

     warnings.warn(
         "compute_env_offsets is deprecated and will be removed in a future version. Use the builder.replicate() function instead.",
         stacklevel=2,
     )
 
+    if num_envs <= 0:
+        raise ValueError("num_envs must be > 0")

122-134: Fix docstring: ‘parser’ is an ArgumentParser (or None), not parsed args.

The current docstring is misleading.

Apply this diff:

-def init(parser=None):
-    """Initialize Newton example components from parsed arguments.
-
-    Args:
-        parser: Parsed arguments from argparse (should include arguments from
-              create_parser())
+def init(parser=None):
+    """Initialize Newton example components from CLI arguments.
+
+    Args:
+        parser: An argparse.ArgumentParser to parse arguments (if None, a default
+                parser from create_parser() is used)
newton/examples/basic/example_basic_pendulum.py (1)

100-112: Minor: consider a scoped timer for simulate() for consistent profiling.

Optional, but adding wp.ScopedTimer around the substep loop aligns with the timers used in newton.examples.run.

Apply this diff:

-    def simulate(self):
-        for _ in range(self.sim_substeps):
+    def simulate(self):
+        with wp.ScopedTimer("simulate"):
+            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.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
README.md (4)

63-63: Grammar: “command-line”

Use “command-line” (or “CLI”) instead of “common line arguments”.

Apply this change:

-The examples support the following common line arguments:
+The examples support the following common command-line arguments:

67-69: Headless environments: default gl may fail

Defaulting to gl is fine locally, but can fail in headless CI/servers. A short note will prevent confusion.

Append after the table:

-| `--output-path`         | Output path for USD files (required if `--viewer usd` is used).                            | `None`          |
+| `--output-path`         | Output path for USD files (required if `--viewer usd` is used).                            | `None`          |
+
+Tip: On headless systems, prefer `--viewer usd` (with `--output-path`) or `--viewer null` instead of the default `gl`.

79-81: Clarify “uv” and link to project

Nice inclusion. Consider linking to uv for discoverability.

-    # With uv
-    uv run python -m newton.examples basic_pendulum
+    # With uv (https://github.com/astral-sh/uv)
+    uv run python -m newton.examples basic_pendulum

65-71: Add note about ReRun SDK requirement

The rerun viewer backend is fully supported (ViewerRerun in newton/_src/viewer/viewer_rerun.py will raise an ImportError with “Install with: pip install rerun-sdk” if the SDK is missing). Please append the following under the argument table in README.md (around lines 65–71):

 Some examples may add additional arguments (see their respective source files for details).

+Note:
+- The `rerun` viewer requires the ReRun SDK to be installed (e.g., `pip install rerun-sdk`).
📜 Review details

Configuration used: .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.

📥 Commits

Reviewing files that changed from the base of the PR and between dd3f18f and ef5bb42.

⛔ Files ignored due to path filters (3)
  • docs/images/examples/example_basic_pendulum.jpg is excluded by !**/*.jpg
  • docs/images/examples/example_basic_urdf.jpg is excluded by !**/*.jpg
  • docs/images/examples/example_basic_viewer.jpg is excluded by !**/*.jpg
📒 Files selected for processing (6)
  • README.md (1 hunks)
  • docs/images/examples/.gitattributes (1 hunks)
  • docs/images/examples/resize.bat (1 hunks)
  • docs/images/examples/resize.sh (1 hunks)
  • newton/examples/__init__.py (3 hunks)
  • newton/examples/basic/example_basic_pendulum.py (1 hunks)
🧰 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.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-18T15:56:26.540Z
Learnt from: adenzler-nvidia
PR: newton-physics/newton#552
File: newton/_src/solvers/mujoco/solver_mujoco.py:0-0
Timestamp: 2025-08-18T15:56:26.540Z
Learning: In Newton's MuJoCo solver, when transforming joint axes from Newton's internal frame to MuJoCo's expected frame, use wp.quat_rotate(joint_rot, axis) not wp.quat_rotate_inv(joint_rot, axis). The joint_rot represents rotation from joint-local to body frame, so forward rotation is correct.

Applied to files:

  • newton/examples/basic/example_basic_pendulum.py
🪛 Shellcheck (0.10.0)
docs/images/examples/resize.sh

[error] 1-1: Tips depend on target shell and yours is unknown. Add a shebang or a 'shell' directive.

(SC2148)

🪛 LanguageTool
README.md

[grammar] ~67-~67: There might be a mistake here.
Context: ...(no viewer). | gl | | --device | Compute devi...

(QB_NEW_EN)


[grammar] ~68-~68: There might be a mistake here.
Context: ... | None (default Warp device) | | --num-frames | Number of fr...

(QB_NEW_EN)


[grammar] ~69-~69: There might be a mistake here.
Context: ... | 100 | | --output-path | Output path ...

(QB_NEW_EN)

⏰ 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). (4)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
🔇 Additional comments (7)
newton/examples/__init__.py (1)

97-120: CLI scaffolding looks good.

create_parser covers device/viewer/output-path/num-frames with sensible defaults and choices.

newton/examples/basic/example_basic_pendulum.py (1)

92-99: CUDA graph capture path is clean and safely falls back on CPU.

Gating on wp.get_device().is_cuda and storing capture.graph is appropriate; fallback to None is clear.

README.md (5)

29-31: Good addition: Examples navigation header

The new Examples section is a useful entry point and aligns with the new CLI runner.


29-60: Overall: Examples table and commands look coherent with the PR intent

The examples and commands are cohesive and provide a good quick-start path. Once the verification items above are confirmed, this section is in good shape.


33-47: All linked assets verified

The images and example scripts referenced in README.md (lines 33–47) are present in the PR; no broken links detected.


49-57: Example CLI entry names verified

The example_map in newton/examples/__init__.py includes all three commands used in the README:

  • basic_pendulum → newton.examples.basic.example_basic_pendulum
  • basic_urdf → newton.examples.basic.example_basic_urdf
  • basic_viewer → newton.examples.basic.example_basic_viewer

No changes needed.


83-89: No changes needed for basic_viewer example flags

The basic_viewer example (newton/examples/basic/example_basic_viewer.py) uses newton.examples.init(), which under the hood defines and parses the following flags in newton/examples/__init__.py:

  • --device
  • --viewer (choices: “gl”, “usd”, “null”)
  • --output-path (required when --viewer usd)
  • --num-frames

Verified:
• The README’s USD example passes both --viewer usd and --output-path, satisfying the requirement in init.
• The basic_viewer example accepts and parses --viewer, --device, and --num-frames as documented.
• No errors are raised and the flags propagate correctly into the viewer construction.

Conclusion: the commands in README.md are correct. No updates required.

Comment thread docs/images/examples/resize.bat Outdated
Comment thread newton/examples/__init__.py
Comment thread README.md Outdated
mmacklin and others added 3 commits August 20, 2025 14:52
Signed-off-by: Miles Macklin <mmacklin@nvidia.com>
Signed-off-by: Miles Macklin <mmacklin@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (4)
docs/images/examples/resize.bat (1)

7-7: Quality setting inconsistent with the Bash script

Windows uses -q:v 2 while the Bash script uses -q:v 3, leading to different file sizes/quality. Pick one value for consistency; the diff above sets both to 3.

README.md (1)

68-74: Clarify --num-frames applies to all viewers, not just USD

Align with parser behavior and prior discussion.

-| `--num-frames`          | Number of frames to simulate (for USD output).                                             | `100`          |
+| `--num-frames`          | Number of frames to simulate.                                                              | `100`          |
newton/examples/__init__.py (1)

164-199: CLI entrypoint is clear and robust; sys.argv rewriting is correct

The argv rewrite keeps a list and preserves argv[0] as the target module, fixing the prior tuple bug reported earlier.

newton/examples/basic/example_basic_pendulum.py (1)

59-72: Correct Warp API usage for transforms and quaternions

Good fix replacing wp.transformf(...) with wp.transform(...), and using wp.quat_identity(). This matches usage elsewhere (e.g., URDF example) and avoids runtime AttributeError.

🧹 Nitpick comments (11)
docs/images/examples/.gitattributes (1)

1-1: Consider tracking PNGs (and JPEG aliases) with LFS as well

If you plan to commit source PNGs alongside the generated JPGs, add PNG/JPEG aliases to avoid large blobs in Git history. Keeping this file scoped to docs/images/examples is fine.

Apply:

 *.jpg filter=lfs diff=lfs merge=lfs -text
+*.jpeg filter=lfs diff=lfs merge=lfs -text
+*.png filter=lfs diff=lfs merge=lfs -text

Follow-up: Confirm whether PNGs in this folder will be committed or only used locally as inputs. If they’re not committed, the current rule is sufficient.

docs/images/examples/resize.sh (1)

1-2: Add a shebang, fix the comment vs. behavior mismatch, and harden the loop

  • The comment says “2x” but the filter halves the dimensions.
  • No shebang; ShellCheck SC2148 flags target shell unknown.
  • If no PNGs exist, the glob can pass a literal “*.png” to ffmpeg. Enable nullglob.

Apply:

-# resize all pngs in a directory by 2x in dimension, crop to square, + convert to jpg
-for f in *.png; do ffmpeg -y -i "$f" -vf "crop=min(iw\,ih):min(iw\,ih),scale=iw/2:ih/2,format=yuv420p" -update 1 -q:v 3 "${f%.png}.jpg"; done
+#!/usr/bin/env bash
+# resize all PNGs in a directory to half size, crop to square, and convert to JPG
+set -euo pipefail
+shopt -s nullglob
+
+for f in *.png; do
+  ffmpeg -y -i "$f" -vf "crop=min(iw\,ih):min(iw\,ih),scale=iw/2:ih/2,format=yuv420p" -q:v 3 "${f%.png}.jpg"
+done

Notes:

  • Removed -update 1 since we’re writing unique outputs, not updating a single target.
  • Consider aligning quality with the Windows script (see next comment) so both produce comparable outputs.
docs/images/examples/resize.bat (1)

3-9: Avoid temp files: combine crop+scale in a single FFmpeg call; align quality and drop -update

The current two-pass flow is slower and temporarily writes PNGs. A single filter chain is simpler and faster. Also, -update is intended for single image sequences and is unnecessary here.

 for %%f in (*.png) do (
     echo Processing %%f...
-    REM First crop to square (keep as PNG), then resize to half and convert to JPG
-    ffmpeg -y -i "%%f" -vf "crop=min(iw\,ih):min(iw\,ih)" -update 1 "%%~nf_temp.png"
-    ffmpeg -y -i "%%~nf_temp.png" -vf "scale=iw/2:ih/2,format=yuv420p" -update 1 -q:v 2 "%%~nf.jpg"
-    del "%%~nf_temp.png"
+    REM Crop to square, resize to half, and convert to JPG in one step
+    ffmpeg -y -i "%%f" -vf "crop=min(iw,ih):min(iw,ih),scale=iw/2:ih/2,format=yuv420p" -q:v 3 "%%~nf.jpg"
 )

Notes:

  • The backslash before commas is not needed on Windows; keep the filter in double quotes.
  • Set -q:v 3 to match the Bash script for parity across platforms (see next comment).
newton/examples/__main__.py (1)

6-9: LGTM; minor aliasing nit to avoid any ambiguity

This is correct and concise. As a tiny readability tweak, alias the import to make it explicit you’re calling the function exported by the package, not a submodule.

-from . import main
+from . import main as _examples_main

 if __name__ == "__main__":
-    main()
+    _examples_main()
README.md (1)

66-66: Grammar: “command-line arguments”

Tweak phrasing.

-The examples support the following common line arguments:
+The examples support the following common command-line arguments:
newton/examples/__init__.py (6)

36-45: Close the viewer even if step/render raises: wrap loop in try/finally

Prevents resource leaks when exceptions occur inside the loop.

Apply this diff:

-def run(example):
-    while example.viewer.is_running():
-        with wp.ScopedTimer("step"):
-            example.step()
-
-        with wp.ScopedTimer("render"):
-            example.render()
-
-    example.viewer.close()
+def run(example):
+    try:
+        while example.viewer.is_running():
+            with wp.ScopedTimer("step"):
+                example.step()
+
+            with wp.ScopedTimer("render"):
+                example.render()
+    finally:
+        example.viewer.close()

50-56: Use DeprecationWarning for deprecations

Use a specific warning category so downstream can filter appropriately.

Apply this diff:

-    warnings.warn(
-        "compute_env_offsets is deprecated and will be removed in a future version. Use the builder.replicate() function instead.",
-        stacklevel=2,
-    )
+    warnings.warn(
+        "compute_env_offsets is deprecated and will be removed in a future version. Use the builder.replicate() function instead.",
+        category=DeprecationWarning,
+        stacklevel=2,
+    )

58-64: Guard against num_envs <= 0 to avoid reductions on empty arrays

np.min/max on an empty env_offsets array will raise. Either validate or early-return zero rows.

Apply this diff:

-    # compute positional offsets per environment
+    # compute positional offsets per environment
+    if num_envs <= 0:
+        return np.zeros((0, 3))

122-134: Docstring parameter description is misleading: this takes a parser, not parsed args

Clarify that the function accepts an argparse.ArgumentParser (or None), not a parsed Namespace.

Apply this diff:

-def init(parser=None):
-    """Initialize Newton example components from parsed arguments.
-
-    Args:
-        parser: Parsed arguments from argparse (should include arguments from
-              create_parser())
+def init(parser=None):
+    """Initialize Newton example components from parsed arguments.
+
+    Args:
+        parser (argparse.ArgumentParser | None): Parent parser that includes
+            the common arguments from create_parser(). If None, a default
+            parser is created via create_parser().

169-175: Optional: allow dotted module names in addition to short aliases

This lets users run python -m newton.examples newton.examples.basic.example_basic_pendulum without updating the map.

Apply this diff:

-    # Set up sys.argv for the target script
-    target_module = example_map[example_name]
+    # Set up sys.argv for the target script
+    if "." in example_name:
+        target_module = example_name
+    else:
+        target_module = example_map[example_name]

Also applies to: 192-198


205-205: Optional: export main in all

Useful if other tooling wants to import and forward the CLI routinely.

Apply this diff:

-__all__ = ["create_parser", "init", "run"]
+__all__ = ["create_parser", "init", "run", "main"]
📜 Review details

Configuration used: .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.

📥 Commits

Reviewing files that changed from the base of the PR and between dd3f18f and 6fc1267.

⛔ Files ignored due to path filters (3)
  • docs/images/examples/example_basic_pendulum.jpg is excluded by !**/*.jpg
  • docs/images/examples/example_basic_urdf.jpg is excluded by !**/*.jpg
  • docs/images/examples/example_basic_viewer.jpg is excluded by !**/*.jpg
📒 Files selected for processing (10)
  • README.md (1 hunks)
  • docs/images/examples/.gitattributes (1 hunks)
  • docs/images/examples/resize.bat (1 hunks)
  • docs/images/examples/resize.sh (1 hunks)
  • newton/_src/sim/builder.py (1 hunks)
  • newton/examples/__init__.py (3 hunks)
  • newton/examples/__main__.py (1 hunks)
  • newton/examples/basic/example_basic_pendulum.py (1 hunks)
  • newton/examples/basic/example_basic_urdf.py (1 hunks)
  • newton/examples/basic/example_basic_viewer.py (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • newton/examples/basic/example_basic_urdf.py
  • newton/_src/sim/builder.py
  • newton/examples/basic/example_basic_viewer.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.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-18T15:56:26.540Z
Learnt from: adenzler-nvidia
PR: newton-physics/newton#552
File: newton/_src/solvers/mujoco/solver_mujoco.py:0-0
Timestamp: 2025-08-18T15:56:26.540Z
Learning: In Newton's MuJoCo solver, when transforming joint axes from Newton's internal frame to MuJoCo's expected frame, use wp.quat_rotate(joint_rot, axis) not wp.quat_rotate_inv(joint_rot, axis). The joint_rot represents rotation from joint-local to body frame, so forward rotation is correct.

Applied to files:

  • newton/examples/basic/example_basic_pendulum.py
🧬 Code Graph Analysis (3)
newton/examples/__main__.py (1)
newton/examples/__init__.py (1)
  • main (164-198)
newton/examples/basic/example_basic_pendulum.py (6)
newton/examples/basic/example_basic_urdf.py (5)
  • Example (37-134)
  • capture (98-104)
  • simulate (106-117)
  • step (119-125)
  • render (130-134)
newton/_src/sim/builder.py (5)
  • ModelBuilder (67-4096)
  • add_articulation (582-588)
  • add_shape_box (2255-2292)
  • add_joint_revolute (1061-1143)
  • add_ground_plane (2200-2220)
newton/_src/viewer/viewer.py (7)
  • finalize (401-409)
  • set_model (66-74)
  • apply_forces (367-368)
  • begin_frame (76-77)
  • log_state (79-100)
  • log_contacts (102-163)
  • end_frame (371-372)
newton/_src/sim/model.py (3)
  • state (441-474)
  • control (476-508)
  • collide (510-564)
newton/_src/viewer/viewer_gl.py (5)
  • set_model (94-100)
  • apply_forces (262-264)
  • begin_frame (243-244)
  • log_state (201-208)
  • end_frame (246-260)
newton/examples/__init__.py (2)
  • init (122-161)
  • run (36-44)
newton/examples/__init__.py (5)
newton/tests/test_examples.py (1)
  • run (92-195)
newton/tests/test_examples_new.py (1)
  • run (65-113)
newton/_src/viewer/viewer_gl.py (2)
  • is_running (297-299)
  • close (305-307)
newton/_src/viewer/viewer_usd.py (3)
  • is_running (258-262)
  • close (264-267)
  • ViewerUSD (14-317)
newton/_src/viewer/viewer_null.py (3)
  • is_running (52-53)
  • close (55-56)
  • ViewerNull (22-69)
🪛 LanguageTool
README.md

[grammar] ~70-~70: There might be a mistake here.
Context: ...(no viewer). | gl | | --device | Compute devi...

(QB_NEW_EN)


[grammar] ~71-~71: There might be a mistake here.
Context: ... | None (default Warp device) | | --num-frames | Number of fr...

(QB_NEW_EN)


[grammar] ~72-~72: There might be a mistake here.
Context: ... | 100 | | --output-path | Output path ...

(QB_NEW_EN)

🪛 Shellcheck (0.10.0)
docs/images/examples/resize.sh

[error] 1-1: Tips depend on target shell and yours is unknown. Add a shebang or a 'shell' directive.

(SC2148)

⏰ 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). (4)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
🔇 Additional comments (5)
README.md (1)

33-63: LGTM: Clear, actionable examples with image links

Nice touch linking images and one-liner invocations; matches the -m entry-point behavior in newton/examples.

newton/examples/__init__.py (1)

97-120: Parser looks solid and self-contained

Good defaults, explicit choices, and add_help=False for parent usage. No issues.

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

95-101: CUDA graph capture implementation is sensible

Capturing simulate() with ScopedCapture and reusing the graph on step() is a clean pattern; falls back to non-capture on CPU.


126-131: Render path aligns with viewer API expectations

begin_frame/log_state/log_contacts/end_frame call order is consistent with the viewer backends and ensures Null/USD viewers advance frame counts for is_running().


134-141: Main flow integrates with newton.examples.init/run correctly

The example is directly runnable via python -m newton.examples.basic_pendulum and respects CLI options parsed by init().

Comment thread newton/examples/__init__.py
Comment thread newton/examples/__init__.py
Signed-off-by: Miles Macklin <mmacklin@nvidia.com>
Signed-off-by: Miles Macklin <mmacklin@nvidia.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (3)
README.md (1)

105-105: Clarify --num-frames applies to all viewers, not just USD output

This repeats a previously raised point; the flag controls simulation length across viewers.

Apply this diff:

-| `--num-frames`          | Number of frames to simulate (for USD output).                                             | `100`          |
+| `--num-frames`          | Number of frames to simulate.                                                               | `100`          |
newton/tests/test_examples.py (1)

141-166: Fix UnboundLocalError: stage_path undefined when use_viewer=True and USD is unavailable

In the null-viewer branch, stage_path is never assigned but is used later for cleanup (Line 191). Initialize it to None.

Apply this diff:

         else:
             # new-style example, setup viewer type and output path
             if USD_AVAILABLE:
                 stage_path = os.path.join(
                     os.path.dirname(__file__), f"outputs/{name}_{sanitize_identifier(device)}.usd"
                 )
                 command.extend(["--viewer", "usd", "--output-path", stage_path])
             else:
                 command.extend(["--viewer", "null"])
+                stage_path = None
newton/_src/viewer/viewer_gl.py (1)

95-103: Initialize a default Warp device when no model is set (prevents None device usage).

Model-less flows call set_model(None) in init, leaving self.device=None. That can leak into GL resource creation (e.g., LinesGL) and allocations with an invalid device. Set a sensible default when model is None.

Apply this diff:

 def set_model(self, model):
     super().set_model(model)
 
+    # Ensure we have a valid Warp device for model-less flows too
+    if model is None and self.device is None:
+        self.device = wp.get_device()
+
     self.picking = Picking(model, pick_stiffness=10000.0, pick_damping=1000.0)
     self.wind = Wind(model)
 
     fb_w, fb_h = self.renderer.window.get_framebuffer_size()
     self.camera = Camera(width=fb_w, height=fb_h)
🧹 Nitpick comments (10)
newton/examples/cloth/example_cloth_bending.py (1)

129-133: Consider rendering contacts for debugging consistency

Other cloth examples log contact normals; adding them here helps with consistency and debugging.

Apply this diff:

     def render(self):
         self.viewer.begin_frame(self.sim_time)
         self.viewer.log_state(self.state_0)
+        # Optional: visualize contact normals for consistency with other cloth examples
+        self.viewer.log_contacts(self.contacts, self.state_0)
         self.viewer.end_frame()
newton/examples/cloth/example_cloth_style3d.py (2)

171-172: Remove placeholder test() or wire it into CI

An empty test method is dead code here.

Apply this diff:

-    def test(self):
-        pass

46-50: Nit: fix comment typo “Grament” → “Garment”

Minor readability improvement.

Apply this diff:

-            # Grament
+            # Garment
README.md (1)

99-99: Use “command-line arguments” terminology

Minor wording improvement.

Apply this diff:

-The examples support the following common line arguments:
+The examples support the following common command-line arguments:
newton/_src/viewer/wind.py (3)

63-66: Guard against period <= 0 to avoid division by zero.

If period is set programmatically to 0 (UI constrains to [1,30] but not guaranteed elsewhere), time_phase = 2*pi*time/period will crash or produce NaNs.

Apply this diff:

-    time_phase = 2.0 * wp.pi * params.time / params.period
+    # Guard against zero/near-zero period
+    if params.period <= 1.0e-6:
+        return
+    time_phase = 2.0 * wp.pi * params.time / params.period

186-202: Always normalize direction; docstring promises normalization but wp.vec3 path skips it.

The tuple/list path normalizes, but the “vector-like” branch copies values without normalization. This violates the contract and can scale forces unintentionally.

Apply this diff:

     @direction.setter
     def direction(self, value):
         """Set wind direction (will be normalized)."""
-        if isinstance(value, (list, tuple)) and len(value) == 3:
-            # Normalize the direction vector
-            import math  # noqa: PLC0415
-
-            length = math.sqrt(value[0] ** 2 + value[1] ** 2 + value[2] ** 2)
-            if length > 0:
-                self._wind_params_host.direction = wp.vec3(value[0] / length, value[1] / length, value[2] / length)
-            else:
-                self._wind_params_host.direction = wp.vec3(1.0, 0.0, 0.0)
-        elif hasattr(value, "__len__") and len(value) == 3:
-            # Handle wp.vec3 or similar
-            self._wind_params_host.direction = wp.vec3(float(value[0]), float(value[1]), float(value[2]))
+        if hasattr(value, "__len__") and len(value) == 3:
+            import math  # noqa: PLC0415
+            x, y, z = float(value[0]), float(value[1]), float(value[2])
+            length = math.sqrt(x * x + y * y + z * z)
+            if length > 0.0:
+                self._wind_params_host.direction = wp.vec3(x / length, y / length, z / length)
+            else:
+                # Default to +X if zero vector is passed
+                self._wind_params_host.direction = wp.vec3(1.0, 0.0, 0.0)
         else:
             raise ValueError("Direction must be a 3-element vector")

55-58: Consider handling negative amplitudes explicitly or clamping at zero.

The UI slider allows negative amplitudes but the kernel early-returns for amplitude <= 0, turning half the slider range into a no-op. Either clamp on set or document that only positive values have effect.

newton/examples/cloth/example_cloth_hanging.py (1)

190-195: Contacts logging is fine; default visibility gated by UI.

Calling viewer.log_contacts even on cloth-only scenes is safe: the viewer-side option guards actual rendering and allocations.

newton/_src/viewer/viewer_gl.py (2)

229-236: Guard picking line rendering for models without bodies.

On cloth-only scenes, state.body_q may be None. Accessing numpy() will raise. Add a defensive check.

Apply this diff:

-        # Get the body's COM position
-        body_transforms = state.body_q.numpy()
+        # Get the body's COM position (guard cloth-only scenes)
+        if not hasattr(state, "body_q") or state.body_q is None:
+            self.log_lines("picking_line", None, None, None)
+            return
+        body_transforms = state.body_q.numpy()

169-177: Minor: verify wp.array.fill_ availability or use wp.fill().

Using line_colors.fill_(color_vec) assumes Warp arrays expose fill_. If unsupported on some versions, prefer wp.fill(line_colors, color_vec) to avoid API discrepancies.

📜 Review details

Configuration used: .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.

📥 Commits

Reviewing files that changed from the base of the PR and between 6fc1267 and ecb14d5.

⛔ Files ignored due to path filters (3)
  • docs/images/examples/example_cloth_bending.jpg is excluded by !**/*.jpg
  • docs/images/examples/example_cloth_hanging.jpg is excluded by !**/*.jpg
  • docs/images/examples/example_cloth_style3d.jpg is excluded by !**/*.jpg
📒 Files selected for processing (9)
  • README.md (1 hunks)
  • newton/_src/solvers/style3d/linear_solver.py (1 hunks)
  • newton/_src/viewer/viewer_gl.py (11 hunks)
  • newton/_src/viewer/wind.py (1 hunks)
  • newton/examples/__init__.py (3 hunks)
  • newton/examples/cloth/example_cloth_bending.py (2 hunks)
  • newton/examples/cloth/example_cloth_hanging.py (2 hunks)
  • newton/examples/cloth/example_cloth_style3d.py (8 hunks)
  • newton/tests/test_examples.py (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • newton/examples/init.py
🧰 Additional context used
🧠 Learnings (1)
📓 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.
🧬 Code Graph Analysis (6)
newton/_src/viewer/wind.py (2)
newton/_src/sim/model.py (1)
  • state (441-474)
newton/_src/sim/builder.py (1)
  • particle_count (506-507)
newton/_src/viewer/viewer_gl.py (4)
newton/_src/viewer/wind.py (13)
  • Wind (81-202)
  • time (142-144)
  • time (147-149)
  • _apply_wind_force (117-132)
  • update (104-115)
  • amplitude (162-164)
  • amplitude (167-169)
  • period (152-154)
  • period (157-159)
  • frequency (172-174)
  • frequency (177-179)
  • direction (182-184)
  • direction (187-202)
newton/_src/viewer/viewer.py (4)
  • ViewerBase (33-649)
  • set_model (66-74)
  • begin_frame (76-77)
  • update (411-420)
newton/_src/viewer/gl/opengl.py (7)
  • RendererGL (689-1503)
  • update (235-282)
  • update (381-426)
  • update (629-674)
  • update (830-839)
  • get_vsync (957-963)
  • set_vsync (949-955)
newton/_src/viewer/camera.py (1)
  • Camera (19-186)
newton/examples/cloth/example_cloth_style3d.py (6)
newton/examples/cloth/example_cloth_bending.py (5)
  • Example (36-132)
  • capture (101-107)
  • simulate (109-120)
  • step (122-127)
  • render (129-132)
newton/_src/sim/builder.py (1)
  • add_ground_plane (2200-2220)
newton/_src/sim/model.py (3)
  • state (441-474)
  • control (476-508)
  • collide (510-564)
newton/_src/viewer/viewer_gl.py (5)
  • set_model (95-102)
  • apply_forces (264-269)
  • begin_frame (245-246)
  • log_state (203-210)
  • end_frame (248-262)
newton/_src/viewer/viewer.py (1)
  • log_contacts (102-163)
newton/examples/__init__.py (2)
  • init (126-167)
  • run (36-44)
newton/tests/test_examples.py (1)
newton/tests/unittest_utils.py (1)
  • sanitize_identifier (271-278)
newton/examples/cloth/example_cloth_bending.py (4)
newton/_src/sim/model.py (2)
  • control (476-508)
  • collide (510-564)
newton/_src/viewer/viewer_gl.py (5)
  • set_model (95-102)
  • apply_forces (264-269)
  • begin_frame (245-246)
  • log_state (203-210)
  • end_frame (248-262)
newton/examples/cloth/example_cloth_hanging.py (4)
  • capture (158-164)
  • simulate (166-177)
  • step (179-185)
  • render (190-194)
newton/examples/__init__.py (2)
  • init (126-167)
  • run (36-44)
newton/examples/cloth/example_cloth_hanging.py (5)
newton/examples/cloth/example_cloth_bending.py (5)
  • Example (36-132)
  • capture (101-107)
  • simulate (109-120)
  • step (122-127)
  • render (129-132)
newton/_src/sim/model.py (3)
  • state (441-474)
  • control (476-508)
  • collide (510-564)
newton/_src/viewer/viewer_gl.py (5)
  • set_model (95-102)
  • apply_forces (264-269)
  • begin_frame (245-246)
  • log_state (203-210)
  • end_frame (248-262)
newton/_src/viewer/viewer.py (1)
  • log_contacts (102-163)
newton/examples/__init__.py (3)
  • create_parser (97-123)
  • init (126-167)
  • run (36-44)
🪛 LanguageTool
README.md

[grammar] ~103-~103: There might be a mistake here.
Context: ...(no viewer). | gl | | --device | Compute devi...

(QB_NEW_EN)


[grammar] ~104-~104: There might be a mistake here.
Context: ... | None (default Warp device) | | --num-frames | Number of fr...

(QB_NEW_EN)


[grammar] ~105-~105: There might be a mistake here.
Context: ... | 100 | | --output-path | Output path ...

(QB_NEW_EN)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
🔇 Additional comments (21)
newton/_src/solvers/style3d/linear_solver.py (1)

176-184: Add size equality check before low-level call & verify C function signature

To prevent out-of-bounds access if a and b differ in length, insert a precondition check just before the call into runtime.core, and then confirm that both backends’ C functions accept the same 7 arguments in this order.

File: newton/_src/solvers/style3d/linear_solver.py (around line 176)

Apply this diff:

-    func(
+    if len(a) != len(b):
+        raise ValueError(f"array_inner requires arrays of equal length, got {len(a)} and {len(b)}")
+
+    func(
         a.ptr,
         b.ptr,
         out_ptr,
         len(a),
         wp.types.type_size_in_bytes(a.dtype),
         wp.types.type_size_in_bytes(b.dtype),
         wp.types.type_length(a.dtype),
     )

• Please manually verify that wp_array_inner_float_host and wp_array_inner_float_device (via runtime.core) both expect these seven parameters in the same order by checking your C-extension headers or documentation.

newton/examples/cloth/example_cloth_bending.py (3)

37-45: LGTM: clean viewer-centric timing and substepping setup

The refactor to explicit fps/frame_dt/sim_substeps/sim_dt is clear and aligns with other examples.


97-101: Bind model to viewer before capture — good ordering

Model binding precedes graph capture; this ensures viewer-side resources (forces, picking, etc.) are initialized before simulate/capture runs.


135-143: Entry flow matches new shared runner — looks good

Using newton.examples.init()/run() keeps the example consistent with the unified CLI and viewer backends.

newton/examples/cloth/example_cloth_style3d.py (5)

35-38: Even substeps for CUDA graph capture — good defensive note

Documenting the even substeps requirement alongside the assignment improves clarity.


80-87: LGTM: rigid mesh as static collider with transform

Supplying xform and separate body for the avatar mesh is correct for collision; matches builder APIs.


126-127: Explicit gravity assignment — good for cross-example consistency

Setting gravity explicitly avoids relying on builder/model defaults.


145-151: Capture path is correct and isolated for CPU devices

Conditionally capturing only on CUDA while keeping CPU path functional is the right tradeoff.


175-178: Good: include contacts in render for debugging

Logging both state and contacts keeps the visualization informative.

README.md (1)

112-126: Docs additions look aligned with new example flow

The new Examples section and usage commands match the shared CLI and viewer-based runner.

newton/tests/test_examples.py (3)

179-182: Printing stderr aids debugging flaky example runs

Good addition; keeps CI logs actionable without failing the test prematurely.


204-220: Viewer-mode basic example registration — looks good

Tests exercise the viewer path consistently across devices; options split between CPU/CUDA are sensible.


226-258: Cloth examples wired to viewer mode — consistent with refactor

Style3D’s reduced num_frames on CPU is a reasonable concession.

newton/examples/cloth/example_cloth_hanging.py (4)

40-59: Initialization flow and substep timing look good.

The viewer-driven frame_dt and sim_substeps → sim_dt mapping is clear and consistent across solver types. Good separation between frame time and simulation substeps.


166-178: Force application ordering fits the solvers; dependency on viewer wind fix.

The clear_forces → viewer.apply_forces → collide → solver.step loop is correct. Once wind writes into particle_f (see wind.py fix), this flow will integrate the wind as intended.


197-224: CLI wiring and viewer bootstrap LGTM.

The CLI integrates cleanly with newton.examples.{create_parser,init,run}. Solver selection via --solver matches your branching.


62-74: Confirmed add_aniso_cloth_grid signature supports edge_kd

The add_aniso_cloth_grid method in newton/_src/sim/style3d/builder_style3d.py (around line 376) defines both edge_aniso_ke and edge_kd, so popping only edge_ke and passing edge_kd from common_params will not produce any unexpected keyword-argument errors. No changes needed here.

newton/_src/viewer/viewer_gl.py (4)

245-247: Good: begin_frame now respects the base viewer’s timing.

Delegating to super().begin_frame(time) ensures consistent timing across viewers.


268-270: Wind integration is well-placed; update cadence decouples from sim.

apply_forces() and _update() hooks are appropriate. Once wind writes to particle_f and drops dt scaling (see wind.py), the force will integrate correctly regardless of viewer frame rate.

Also applies to: 281-282


399-404: Nice: prevent picking when no cached state is available.

The added guard avoids None derefs during early interaction.


615-664: UI sliders and toggles are cohesive; direction slider feeds normalized setter.

Assuming wind.direction setter is fixed to always normalize (see wind.py), this UI produces predictable forces.

Comment thread newton/_src/solvers/style3d/linear_solver.py Outdated
Comment thread newton/_src/viewer/wind.py
Comment thread newton/examples/cloth/example_cloth_style3d.py
Signed-off-by: Gabriel Noya <mega.kaloca@gmail.com>
@dylanturpin dylanturpin mentioned this pull request Aug 25, 2025
5 tasks
This was referenced Sep 26, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Jan 29, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants