Reuse Newton visualizer frames in videos#6282
Conversation
Greptile SummaryThis PR extends the Newton GL video capture pipeline to mirror the active Newton visualizer's world-visibility selection, visual world spacing, and marker overlays into recorded frames. The synchronization runs per frame in
Confidence Score: 4/5Safe to merge; all changes are additive and well-tested, with no modifications to existing public APIs. The synchronization logic in _sync_newton_camera is correct for all common cases and is backed by thorough unit tests. The try/finally in render_rgb_array is a genuine correctness improvement. The three findings are cosmetic: the num_envs=0 edge case is documented behavior that only affects a degenerate scene configuration, the missing deduplication in set_frame_overlay_callback is a per-frame attribute store with no downstream side effects, and the BOM in the RST file did not break the documented build. No files require special attention beyond the minor notes on video_recorder.py (num_envs=0 edge case), newton_gl_perspective_video.py (callback deduplication), and the RST BOM. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant VR as VideoRecorder
participant NV as NewtonVisualizer (live)
participant CAP as NewtonGlPerspectiveVideo
participant GL as ViewerGL (headless)
VR->>VR: render_rgb_array()
VR->>VR: _sync_newton_camera()
VR->>NV: get_visualized_env_ids()
NV-->>VR: [0,1,2,3] or None
VR->>VR: apply max_visible_envs cap
VR->>CAP: set_visible_worlds([0,1,2,3])
CAP->>CAP: dedup check
CAP->>GL: set_visible_worlds([0,1,2,3])
VR->>CAP: set_world_offsets((2.0,2.0,0.0))
CAP->>CAP: dedup check
CAP->>GL: set_world_offsets((2.0,2.0,0.0))
VR->>CAP: set_frame_overlay_callback(render_markers)
CAP->>CAP: store callback
VR->>CAP: update_camera(pos, target)
CAP->>GL: set_camera(pos, pitch, yaw)
VR->>CAP: render_rgb_array()
CAP->>GL: begin_frame(dt)
CAP->>GL: log_state(state)
CAP->>GL: render_markers(viewer) [overlay callback]
CAP->>GL: end_frame() [always, via finally]
CAP->>GL: get_frame()
GL-->>VR: np.ndarray (RGB frame)
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant VR as VideoRecorder
participant NV as NewtonVisualizer (live)
participant CAP as NewtonGlPerspectiveVideo
participant GL as ViewerGL (headless)
VR->>VR: render_rgb_array()
VR->>VR: _sync_newton_camera()
VR->>NV: get_visualized_env_ids()
NV-->>VR: [0,1,2,3] or None
VR->>VR: apply max_visible_envs cap
VR->>CAP: set_visible_worlds([0,1,2,3])
CAP->>CAP: dedup check
CAP->>GL: set_visible_worlds([0,1,2,3])
VR->>CAP: set_world_offsets((2.0,2.0,0.0))
CAP->>CAP: dedup check
CAP->>GL: set_world_offsets((2.0,2.0,0.0))
VR->>CAP: set_frame_overlay_callback(render_markers)
CAP->>CAP: store callback
VR->>CAP: update_camera(pos, target)
CAP->>GL: set_camera(pos, pitch, yaw)
VR->>CAP: render_rgb_array()
CAP->>GL: begin_frame(dt)
CAP->>GL: log_state(state)
CAP->>GL: render_markers(viewer) [overlay callback]
CAP->>GL: end_frame() [always, via finally]
CAP->>GL: get_frame()
GL-->>VR: np.ndarray (RGB frame)
Reviews (1): Last reviewed commit: "Record Newton visualizer overlays in vid..." | Re-trigger Greptile |
| elif max_visible_envs is not None and self._scene.num_envs > 0: | ||
| num_envs = max(0, int(self._scene.num_envs)) | ||
| visible_env_ids = list(range(min(max(0, int(max_visible_envs)), num_envs))) |
There was a problem hiding this comment.
num_envs == 0 with a cap set forwards None (render all) instead of [] (render none)
When visible_env_ids is None (no explicit selection) and max_visible_envs is set but self._scene.num_envs == 0, neither branch is taken, so visible_env_ids remains None and set_visible_worlds(None) is called — meaning "render all worlds." A cap of, say, 4 with 0 known environments arguably should forward [] rather than None, since there is nothing to show. The parametrized test (0, 4, None) documents this as expected, but the semantics could surprise callers who interpret None as "unconstrained."
| def set_frame_overlay_callback(self, callback: Callable[[ViewerGL], None] | None) -> None: | ||
| """Set a callback that renders viewer-side overlays into each recorded frame. | ||
|
|
||
| Args: | ||
| callback: Function invoked with the capture viewer after Newton state logging and | ||
| before the frame ends, or ``None`` to render no overlays. | ||
| """ | ||
| self._frame_overlay_callback = callback |
There was a problem hiding this comment.
set_frame_overlay_callback stores unconditionally on every call
set_visible_worlds and set_world_offsets both deduplicate before touching the underlying viewer, explicitly to avoid unnecessary Newton GL cache rebuilds. set_frame_overlay_callback skips this guard entirely — it always overwrites self._frame_overlay_callback regardless of whether the value changed. Because _sync_newton_camera calls this every frame, the attribute is re-assigned on each render even when enable_markers and the bound method are stable. A simple identity check (if callback is self._frame_overlay_callback: return) would make the three setters consistent and avoid the per-frame churn.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| isaaclab\_newton.video\_recording | ||
| ================================= |
There was a problem hiding this comment.
The file begins with a UTF-8 BOM (U+FEFF). While Sphinx strips it before parsing in most configurations (which is why the documented build passed), docutils in stricter or older environments may count the BOM as a title character, making the title one character longer than the underline and triggering a "Title underline too short" warning or error. The BOM is not meaningful in UTF-8 files and is best removed to keep the RST consistent with the other files in the same directory.
Capture the active Newton visualizer framebuffer directly so videos inherit its camera, selected worlds, and markers without creating or synchronizing a second viewer. Keep Kit and renderer fallback captures eager for pre-reset setup.
Summary
--videoWith
VideoRecorderCfg.backend_source = "renderer", recording remains independent of active visualizers and uses the existing standalone capture path.Testing
38 passed:test_video_recorder.pyandtest_newton_adapter.py./isaaclab.sh -dpassed; rendered HTML contains both stable:pyobject:excerpts