Skip to content

Add Newton course notebook, improve ViewerViser#2415

Draft
eric-heiden wants to merge 15 commits intonewton-physics:mainfrom
eric-heiden:course-notebook
Draft

Add Newton course notebook, improve ViewerViser#2415
eric-heiden wants to merge 15 commits intonewton-physics:mainfrom
eric-heiden:course-notebook

Conversation

@eric-heiden
Copy link
Copy Markdown
Member

@eric-heiden eric-heiden commented Apr 11, 2026

This PR contributes the course notebook that has been adapted from the GTC hands-on lab, see recording at https://www.nvidia.com/en-us/on-demand/session/gtc26-dlit81700/.

This is not ready for merging yet - we should evaluate the tutorial notebooks in a separate CI job, not the docs build job that runs on every PR on CPU. I will add a separate runner for it in this PR later.

ViewerViser improvements

  • Update Viser to 1.0.26
  • Use static index.html for playback of Viser recordings in iFrames (e.g. Jupyter notebook cells) that is bundled with the viser dependency instead of shipping our own static HTML/WASM/CSS files (removed them here)
  • Fix rendering of planes to use wireframe rendering because rendering them as solid meshes resulted in some flickering artifacts where seemingly the ground plane disappeared every 2nd frame from half of the image and occluded the other shapes:
image

Now we have:
image

  • Fix ViewerViser notebook playback behind Jupyter proxies

Summary by CodeRabbit

  • Documentation

    • Added a comprehensive Newton tutorial notebook covering simulation, CUDA graph capture, IK, and a Franka pick‑and‑place demo.
  • Changelog

    • Added “Unreleased” entries documenting the tutorial and fixes for notebook playback and plane rendering.
  • New Features

    • Notebook viewer now resolves browser URLs correctly behind Jupyter proxies and uses installed viewer client assets for playback.
  • Removals

    • Removed bundled viewer static assets; rely on the installed client.
  • Chores

    • Pinned viewer/client dependency and added Jupyter proxy support to optional deps.

## Motivation
Per project guidelines in `AGENTS.md`, user-facing additions must be recorded in the `[Unreleased]` section of `CHANGELOG.md`. This PR adds a substantial new tutorial notebook (`docs/tutorials/01_newton_course.ipynb`) but had no corresponding changelog entry.

## What changed
- Appended one line to `### Added` in the `[Unreleased]` section:
  `- Add introductory tutorial notebook covering ModelBuilder, solvers, CUDA graphs, IK, and pick-and-place`

## Why this fix is correct
- Uses imperative present tense ("Add …") per commit/changelog conventions.
- Placed at the end of `### Added`, keeping the existing order intact.
- Covers all major topics in the notebook (ModelBuilder, solvers, CUDA graphs, IK, pick-and-place) without leaking internal implementation details.

## Verification
- `grep -n "tutorial" CHANGELOG.md` confirms the new entry at line 28.
- Visual inspection of `git diff` confirms a single clean insertion with no unintended whitespace or formatting changes.
@review-notebook-app
Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 11, 2026

📝 Walkthrough

Walkthrough

Adds a large Newton tutorial notebook and changelog entries, refactors ViewerViser to construct Jupyter-proxied playback URLs and locate installed Viser client assets, renders plane instances as line-grid meshes, pins viser and adds jupyter-server-proxy to extras, and removes bundled Viser static assets from the repo.

Changes

Cohort / File(s) Summary
Changelog
CHANGELOG.md
Added Unreleased → Added entry for new Newton tutorial and Unreleased → Fixed entries for ViewerViser notebook playback and plane rendering fix.
Tutorial Notebook
docs/tutorials/01_newton_course.ipynb
Added comprehensive Newton tutorial notebook demonstrating ModelBuilder→Model→Solver pipeline, XPBD loop, CUDA graph capture, Franka robot examples, IK, pick-and-place, and Jupyter viewer utilities.
ViewerViser (URL, Jupyter proxy, plane rendering, helpers)
newton/_src/viewer/viewer_viser.py
Refactored URL/proxy handling (_normalize_jupyter_base_url, _get_jupyter_proxy_base_url, _build_browser_url), added _get_viser_client_dir, quaternion helper, plane-mesh tracking and finite-grid rendering (_plane_meshes, _log_plane_instances, _build_plane_grid_points, _remove_plane_handles), and updated url, log_geo, log_instances, and _serve_viser_recording to use new utilities. Review proxy URL construction, client-dir resolution, and plane rendering branch.
Sphinx static copy
docs/conf.py
Now prefers copying Viser client assets from installed viser package (via imported viser path) and updated warning when assets not found; removed repo-checkout candidate path.
Removed bundled Viser client assets
newton/_src/viewer/viser/static/index.html, newton/_src/viewer/viser/static/manifest.json, newton/_src/viewer/viser/static/robots.txt, newton/_src/viewer/viser/static/assets/*, newton/_src/viewer/viser/static/...__vite-browser-external-*.js
Deleted in-repo Viser client HTML, manifest, robots, JS assets and web worker scripts; client assets are expected from the installed viser package. Verify no remaining code assumes in-repo static files.
Optional dependencies / pyproject
pyproject.toml
Pinned viser in docs extra (viser>=1.0.16viser==1.0.26) and added jupyter-server-proxy>=4.5.0 and viser==1.0.26 to the notebook extra. Verify pinned versions and extras lists.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant JupyterProxy as "Jupyter Server Proxy"
    participant LocalServer as "Viewer Local Server (127.0.0.1:{port})"
    participant ViserClient as "Viser Client Assets (installed package)"

    Browser->>JupyterProxy: Request proxied URL /proxy/{port}/?playbackPath=recording.viser
    JupyterProxy->>LocalServer: Forward HTTP request to 127.0.0.1:{port}
    LocalServer->>ViserClient: Serve player HTML referencing client assets (from installed viser)
    ViserClient-->>LocalServer: Provide assets (index.html, JS, CSS)
    LocalServer-->>JupyterProxy: Return assembled recording page
    JupyterProxy-->>Browser: Return proxied response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • Guirec-Maloisel
  • chschuma-disney
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly summarizes the two main changes: adding a Newton course notebook and improving ViewerViser capabilities. It is concise, specific, and directly reflects the substantial changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@eric-heiden eric-heiden self-assigned this Apr 11, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/tutorials/01_newton_course.ipynb`:
- Around line 433-440: In simulate() the code currently creates a new contacts
buffer each substep via contacts = model.collide(state_0); change this to reuse
a preallocated contacts buffer by calling model.collide(state_0, contacts) so
collide populates the existing buffer in-place (matching the pattern used
elsewhere in the notebook); locate the contacts variable referenced in
simulate() and pass it as the second argument to model.collide rather than
assigning the return value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 25eba3c4-a12e-4f42-b026-0f95f7822232

📥 Commits

Reviewing files that changed from the base of the PR and between 8baee87 and 546e290.

⛔ Files ignored due to path filters (2)
  • docs/tutorials/images/break.jpg is excluded by !**/*.jpg
  • docs/tutorials/images/cuda-graph.png is excluded by !**/*.png
📒 Files selected for processing (2)
  • CHANGELOG.md
  • docs/tutorials/01_newton_course.ipynb

Comment on lines +433 to +440
"def simulate():\n",
" \"\"\"Run one frame of simulation (multiple substeps).\"\"\"\n",
" global state_0, state_1\n",
"\n",
" for _ in range(sim_substeps):\n",
" state_0.clear_forces()\n",
" contacts = model.collide(state_0)\n",
" solver.step(state_in=state_0, state_out=state_1, control=control, contacts=contacts, dt=sim_dt)\n",
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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify Model.collide signature/behavior around optional contacts argument.
fd -i "model.py" -t f | xargs -r rg -n -C4 'def collide\(|if contacts is None|self\.contacts\('

# Show collide call sites in this notebook for consistency checks.
rg -n -C2 'model\.collide\(' docs/tutorials/01_newton_course.ipynb

Repository: newton-physics/newton

Length of output: 2705


Fix the suboptimal contacts allocation pattern for consistency with the rest of the tutorial.

The code at line 439 allocates a fresh contacts buffer each substep (contacts = model.collide(state_0)), but the Model.collide() method accepts an optional contacts parameter for in-place population. Later in the notebook (lines 688, 778, 1499), the pattern correctly reuses a preallocated buffer (model.collide(state_0, contacts)). Update line 439 to match this efficient, consistent pattern.

♻️ Proposed change
 def simulate():
     """Run one frame of simulation (multiple substeps)."""
     global state_0, state_1

     for _ in range(sim_substeps):
         state_0.clear_forces()
-        contacts = model.collide(state_0)
+        model.collide(state_0, contacts)
         solver.step(state_in=state_0, state_out=state_1, control=control, contacts=contacts, dt=sim_dt)
         # Swap buffers so state_0 always holds the latest state
         state_0, state_1 = state_1, state_0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/tutorials/01_newton_course.ipynb` around lines 433 - 440, In simulate()
the code currently creates a new contacts buffer each substep via contacts =
model.collide(state_0); change this to reuse a preallocated contacts buffer by
calling model.collide(state_0, contacts) so collide populates the existing
buffer in-place (matching the pattern used elsewhere in the notebook); locate
the contacts variable referenced in simulate() and pass it as the second
argument to model.collide rather than assigning the return value.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 11, 2026

❌ 2 Tests Failed:

Tests completed Failed Passed Skipped
2120 2 2118 443
View the top 2 failed test(s) by shortest run time
TestViewerViserNotebookUrls::test_get_viser_client_dir_prefers_installed_package_build
Stack Traces | 0.006s run time
Traceback (most recent call last):
  File ".../newton/tests/test_viewer_viser_notebook.py", line 92, in test_get_viser_client_dir_prefers_installed_package_build
    self.assertEqual(ViewerViser._get_viser_client_dir(), package_build_dir)
AssertionError: PosixPath('.../folders/tb/y368xp_x10s3ty1b_m[42 chars]ild') != PosixPath('.../folders/tb/y368xp_x10s3ty1b_mtl5mxr00[34 chars]ild')
TestViewerViserNotebookUrls::test_show_notebook_uses_proxy_url_for_live_server
Stack Traces | 0.031s run time
Traceback (most recent call last):
  File ".../newton/tests/test_viewer_viser_notebook.py", line 68, in test_show_notebook_uses_proxy_url_for_live_server
    with patch("IPython.display.IFrame") as mock_iframe:
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "........./Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/unittest/mock.py", line 1451, in __enter__
    self.target = self.getter()
                  ^^^^^^^^^^^^^
  File "........./Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/pkgutil.py", line 513, in resolve_name
    mod = importlib.import_module(modname)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "........./Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/importlib/__init__.py", line 90, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1324, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'IPython'

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Load notebook playback assets directly from the installed `viser`
package instead of shipping a separate Newton copy so the browser
client stays compatible with the serializer version in the active
environment.

Also update the pinned `viser` dependency, adjust docs asset copying,
add regression coverage, and fix tutorial notebook lint issues so
pre-commit passes for the branch.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
docs/tutorials/01_newton_course.ipynb (1)

433-436: ⚠️ Potential issue | 🟡 Minor

Reuse the preallocated contacts buffer in this loop.

This section already created contacts = model.contacts(), but Line 435 replaces it with a fresh buffer on every substep. Using model.collide(state_0, contacts) keeps the tutorial consistent with the later sections and avoids needless allocations before the CUDA-captured variant.

♻️ Proposed fix
     for _ in range(sim_substeps):
         state_0.clear_forces()
-        contacts = model.collide(state_0)
+        model.collide(state_0, contacts)
         solver.step(state_in=state_0, state_out=state_1, control=control, contacts=contacts, dt=sim_dt)
         # Swap buffers so state_0 always holds the latest state
         state_0, state_1 = state_1, state_0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/tutorials/01_newton_course.ipynb` around lines 433 - 436, The loop
currently reassigns a fresh contacts buffer each substep; instead reuse the
preallocated buffer created earlier (contacts = model.contacts()) by calling
model.collide(state_0, contacts) inside the for loop so the existing contacts
object is filled rather than replaced; update the call in the loop where
solver.step(...) is invoked to pass the reused contacts buffer (use
model.collide(state_0, contacts) before solver.step) to avoid repeated
allocations and match later CUDA-captured usage.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/conf.py`:
- Around line 406-413: The docs conf currently only adds the installed viser
package's "client/build" path to src_candidates; update the block that imports
inspect and viser to also append the alternative "static" fallback used by
newton/_src/viewer/viewer_viser.py (i.e., add
Path(inspect.getfile(viser)).resolve().parent / "static") so Sphinx will copy
assets when the package exposes the fallback layout; keep both additions using
the same Path(inspect.getfile(viser)).resolve().parent base and append to
src_candidates like the existing "client/build" entry.

In `@newton/_src/viewer/viewer_viser.py`:
- Around line 192-194: The startup log in __init__ prints a hardcoded
"http://localhost:{self._port}" instead of the proxy-aware address; change the
verbose startup/message to use self.url (which already resolves via
is_jupyter_notebook and _build_browser_url using _port) so the log shows the
proxied "/proxy/<port>/..." path in notebook environments; update the log call
in __init__ to reference self.url rather than constructing localhost, and ensure
self.url is computed before the log is emitted.

---

Duplicate comments:
In `@docs/tutorials/01_newton_course.ipynb`:
- Around line 433-436: The loop currently reassigns a fresh contacts buffer each
substep; instead reuse the preallocated buffer created earlier (contacts =
model.contacts()) by calling model.collide(state_0, contacts) inside the for
loop so the existing contacts object is filled rather than replaced; update the
call in the loop where solver.step(...) is invoked to pass the reused contacts
buffer (use model.collide(state_0, contacts) before solver.step) to avoid
repeated allocations and match later CUDA-captured usage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 44209f5f-7081-4d90-b5eb-7ef352fdec04

📥 Commits

Reviewing files that changed from the base of the PR and between 2179054 and c067369.

⛔ Files ignored due to path filters (4)
  • newton/_src/viewer/viser/static/Inter-VariableFont_slnt,wght.ttf is excluded by !**/*.ttf
  • newton/_src/viewer/viser/static/assets/Sorter-Df0J3ZWJ.wasm is excluded by !**/*.wasm
  • newton/_src/viewer/viser/static/logo.svg is excluded by !**/*.svg
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • CHANGELOG.md
  • docs/conf.py
  • docs/tutorials/01_newton_course.ipynb
  • newton/_src/viewer/viewer_viser.py
  • newton/_src/viewer/viser/static/assets/SplatSortWorker-DiSpcAPr.js
  • newton/_src/viewer/viser/static/assets/WebsocketServerWorker-C6PJJ7Dx.js
  • newton/_src/viewer/viser/static/assets/__vite-browser-external-BIHI7g3E.js
  • newton/_src/viewer/viser/static/assets/index-BVvA0mmR.css
  • newton/_src/viewer/viser/static/assets/index-H4DT9vxj.js
  • newton/_src/viewer/viser/static/index.html
  • newton/_src/viewer/viser/static/manifest.json
  • newton/_src/viewer/viser/static/robots.txt
  • pyproject.toml
💤 Files with no reviewable changes (6)
  • newton/_src/viewer/viser/static/assets/__vite-browser-external-BIHI7g3E.js
  • newton/_src/viewer/viser/static/manifest.json
  • newton/_src/viewer/viser/static/index.html
  • newton/_src/viewer/viser/static/robots.txt
  • newton/_src/viewer/viser/static/assets/SplatSortWorker-DiSpcAPr.js
  • newton/_src/viewer/viser/static/assets/WebsocketServerWorker-C6PJJ7Dx.js
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • pyproject.toml

Comment thread docs/conf.py
Comment on lines +406 to +413
# Installed viser package build. Prefer this so the copied docs assets stay in
# sync with the serializer version used for notebook playback.
try:
import newton # noqa: PLC0415
import inspect # noqa: PLC0415

import viser # noqa: PLC0415

src_candidates.append(Path(newton.__file__).resolve().parent / "_src" / "viewer" / "viser" / "static")
src_candidates.append(Path(inspect.getfile(viser)).resolve().parent / "client" / "build")
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.

⚠️ Potential issue | 🟡 Minor

Add the same static fallback used by ViewerViser.

newton/_src/viewer/viewer_viser.py checks both client/build and static, but this docs-side copy path only registers client/build. If the installed viser package exposes the fallback layout, Sphinx will quietly skip copying the player assets and the embedded notebook replays will end up with a broken ../_static/viser/index.html.

♻️ Proposed fix
     try:
         import inspect  # noqa: PLC0415

         import viser  # noqa: PLC0415

-        src_candidates.append(Path(inspect.getfile(viser)).resolve().parent / "client" / "build")
+        viser_package_dir = Path(inspect.getfile(viser)).resolve().parent
+        src_candidates.extend(
+            [
+                viser_package_dir / "client" / "build",
+                viser_package_dir / "static",
+            ]
+        )
     except Exception:
         pass
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/conf.py` around lines 406 - 413, The docs conf currently only adds the
installed viser package's "client/build" path to src_candidates; update the
block that imports inspect and viser to also append the alternative "static"
fallback used by newton/_src/viewer/viewer_viser.py (i.e., add
Path(inspect.getfile(viser)).resolve().parent / "static") so Sphinx will copy
assets when the package exposes the fallback layout; keep both additions using
the same Path(inspect.getfile(viser)).resolve().parent base and append to
src_candidates like the existing "client/build" entry.

Comment on lines +192 to 194
if self.is_jupyter_notebook:
return self._build_browser_url(self._port)
return f"http://localhost:{self._port}"
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.

⚠️ Potential issue | 🟡 Minor

Make the startup log use the proxy-aware URL too.

url can now resolve to a proxied /proxy/<port>/... path in notebooks, but __init__ still prints http://localhost:{port} unconditionally. In remote Jupyter sessions that message is misleading and often unusable, so the verbose log should print self.url instead.

♻️ Suggested follow-up
-        if verbose:
-            print(f"Viser server running at: http://localhost:{port}")
+        if verbose:
+            print(f"Viser server running at: {self.url}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@newton/_src/viewer/viewer_viser.py` around lines 192 - 194, The startup log
in __init__ prints a hardcoded "http://localhost:{self._port}" instead of the
proxy-aware address; change the verbose startup/message to use self.url (which
already resolves via is_jupyter_notebook and _build_browser_url using _port) so
the log shows the proxied "/proxy/<port>/..." path in notebook environments;
update the log call in __init__ to reference self.url rather than constructing
localhost, and ensure self.url is computed before the log is emitted.

Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
Signed-off-by: Eric Heiden <eric-heiden@outlook.com>
@eric-heiden eric-heiden changed the title Add Newton course notebook Add Newton course notebook, improve ViewerViser Apr 16, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
newton/_src/viewer/viewer_viser.py (2)

1387-1389: ⚠️ Potential issue | 🟠 Major

Bind playback server to loopback only.

The playback URL is built for 127.0.0.1, but the HTTP server binds to all interfaces. This unnecessarily exposes notebook playback assets on the network.

♻️ Proposed fix
-        # Bind to all interfaces so IFrame can access it
-        server = HTTPServer(("", port), handler_class)
+        # Bind to loopback; notebook iframes/proxies can still access this.
+        server = HTTPServer(("127.0.0.1", port), handler_class)

Also applies to: 1397-1401

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@newton/_src/viewer/viewer_viser.py` around lines 1387 - 1389, The HTTPServer
is currently bound to all interfaces via HTTPServer(("", port), handler_class)
which exposes playback assets externally; change the bind address to loopback
only (e.g., use "127.0.0.1" or "localhost" instead of "" ) in both places where
HTTPServer is instantiated (the lines constructing server = HTTPServer(...)) so
the playback server only listens on the local interface; ensure any URL
generation still matches the chosen loopback host.

188-208: ⚠️ Potential issue | 🟠 Major

Clear plane-grid state in clear_model() to avoid stale overlays.

_plane_handles and _plane_meshes are not reset in clear_model(). Since plane grids are tracked outside _scene_handles, they can survive model resets.

♻️ Proposed fix
     def clear_model(self):
         """Reset model-dependent state, including scalar plot buffers."""
+        for handles in self._plane_handles.values():
+            for handle in handles:
+                try:
+                    handle.remove()
+                except Exception:
+                    pass
+        self._plane_handles.clear()
+        self._plane_meshes.clear()
+
         # Remove plot handles from the GUI.
         for handle in self._plot_handles.values():
             try:
                 handle.remove()
             except Exception:

Also applies to: 595-603, 664-665

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@newton/_src/viewer/viewer_viser.py` around lines 188 - 208, clear_model
currently resets plot-related state but leaves plane overlays alive; update
viewer_viser.clear_model to also remove and clear _plane_handles and
_plane_meshes: iterate over self._plane_handles and call handle.remove() in a
try/except then clear the dict/list, and if plane mesh objects exist call their
remove() or set them to None and clear self._plane_meshes. Apply the same
cleanup pattern used for _plot_handles/_plot_folder/_scalar_* to the plane state
so plane grids cannot persist across model resets (also replicate this change
wherever other model-reset helpers exist that manage plane state).
♻️ Duplicate comments (1)
newton/_src/viewer/viewer_viser.py (1)

167-174: ⚠️ Potential issue | 🟡 Minor

Use proxy-aware URL in startup log (still prints localhost).

The verbose message is still hardcoded to localhost, which is misleading in notebook-proxy environments. Please log self.url after _port and is_jupyter_notebook are initialized.

♻️ Proposed fix
-        if verbose:
-            print(f"Viser server running at: http://localhost:{port}")
-
-        # Store configuration
-        self._port = port
-
-        # Track if running in Jupyter
-        self.is_jupyter_notebook = is_jupyter_notebook()
+        # Store configuration
+        self._port = port
+
+        # Track if running in Jupyter
+        self.is_jupyter_notebook = is_jupyter_notebook()
+
+        if verbose:
+            print(f"Viser server running at: {self.url}")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@newton/_src/viewer/viewer_viser.py` around lines 167 - 174, The startup
verbose log currently prints a hardcoded localhost URL before instance state is
set; move the verbose print to after assigning self._port and computing
self.is_jupyter_notebook, and change it to log the proxy-aware URL by using
self.url (instead of hardcoded "http://localhost:{port}") so notebook-proxy
environments show the correct address; update the code around the assignments to
self._port and is_jupyter_notebook = is_jupyter_notebook() and print self.url
when verbose is true.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@newton/_src/viewer/viewer_viser.py`:
- Around line 1387-1389: The HTTPServer is currently bound to all interfaces via
HTTPServer(("", port), handler_class) which exposes playback assets externally;
change the bind address to loopback only (e.g., use "127.0.0.1" or "localhost"
instead of "" ) in both places where HTTPServer is instantiated (the lines
constructing server = HTTPServer(...)) so the playback server only listens on
the local interface; ensure any URL generation still matches the chosen loopback
host.
- Around line 188-208: clear_model currently resets plot-related state but
leaves plane overlays alive; update viewer_viser.clear_model to also remove and
clear _plane_handles and _plane_meshes: iterate over self._plane_handles and
call handle.remove() in a try/except then clear the dict/list, and if plane mesh
objects exist call their remove() or set them to None and clear
self._plane_meshes. Apply the same cleanup pattern used for
_plot_handles/_plot_folder/_scalar_* to the plane state so plane grids cannot
persist across model resets (also replicate this change wherever other
model-reset helpers exist that manage plane state).

---

Duplicate comments:
In `@newton/_src/viewer/viewer_viser.py`:
- Around line 167-174: The startup verbose log currently prints a hardcoded
localhost URL before instance state is set; move the verbose print to after
assigning self._port and computing self.is_jupyter_notebook, and change it to
log the proxy-aware URL by using self.url (instead of hardcoded
"http://localhost:{port}") so notebook-proxy environments show the correct
address; update the code around the assignments to self._port and
is_jupyter_notebook = is_jupyter_notebook() and print self.url when verbose is
true.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 2e8f2518-bdd6-4846-bb3a-f5f9895f9c5e

📥 Commits

Reviewing files that changed from the base of the PR and between c067369 and a5486a6.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • docs/tutorials/01_newton_course.ipynb
  • newton/_src/viewer/viewer_viser.py
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md

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.

1 participant