Skip to content

Conversation

@matthewtrepte
Copy link
Contributor

@matthewtrepte matthewtrepte commented Nov 8, 2025

Description

Initial design to support multiple visualizers with Isaac Lab 3.0

The visualizers

  • OV Visualizer
  • Newton OpenGL Visualizer
  • Newton Rerun Visualizer

Type of change

  • New feature (non-breaking change which adds functionality)
  • Breaking change (existing functionality will not work without user modification)
  • Documentation update

Screenshots

Please attach before and after screenshots of the change if applicable.

Checklist

  • I have read and understood the contribution guidelines
  • I have run the pre-commit checks with ./isaaclab.sh --format
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the changelog and the corresponding version in the extension's config/extension.toml file
  • I have added my name to the CONTRIBUTORS.md or my name already exists there

@github-actions github-actions bot added documentation Improvements or additions to documentation asset New asset feature or request isaac-sim Related to Isaac Sim team isaac-mimic Related to Isaac Mimic team infrastructure labels Nov 8, 2025
@matthewtrepte matthewtrepte changed the base branch from main to dev/newton November 8, 2025 03:13
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Nov 11, 2025

Greptile Overview

Greptile Summary

This PR introduces a visualizer abstraction layer to support multiple rendering backends (Newton OpenGL, Omniverse, Rerun) for Isaac Lab 3.0. The architecture includes a base Visualizer class with lifecycle methods, a SceneDataProvider abstraction to decouple visualizers from physics backends, and a registry pattern for instantiation.

Key Changes:

  • Added Visualizer base class with initialize(), step(), close(), and is_running() abstract methods
  • Implemented SceneDataProvider abstraction for physics backend independence
  • Fully implemented NewtonVisualizer with training/rendering pause controls
  • Updated training scripts to use --visualize flag with enable_visualizers() utility
  • Added visualizer lifecycle management in SimulationContext

Issues Found:

  • Stub implementations incomplete: OmniverseVisualizer and RerunVisualizer are stubs missing all abstract method implementations, will cause runtime errors if instantiated
  • Copy-paste errors: rerun_visualizer.py imports from wrong module and has copy-pasted docstrings from Omniverse
  • Encapsulation violation: NewtonSceneDataProvider directly accesses private NewtonManager._model and ._state_0 fields
  • Blocking pause loop: Training pause in simulation_context.py:631 blocks simulation thread indefinitely without timeout
  • Rendering pause skips visualizer step: When rendering is paused (line 636), continue skips visualizer step entirely, potentially breaking ImGui state that needs per-frame updates

Confidence Score: 2/5

  • Unsafe to merge - contains critical bugs in pause logic and incomplete stub implementations
  • The Newton visualizer implementation is solid, but the stub implementations will crash if used, and the simulation context pause handling has a blocking infinite loop bug and UI state management issue. The encapsulation violations should also be addressed before merging.
  • Pay close attention to simulation_context.py (pause loop bug), rerun_visualizer.py and ov_visualizer.py (incomplete stubs), and newton_scene_data_provider.py (encapsulation)

Important Files Changed

File Analysis

Filename Score Overview
source/isaaclab/isaaclab/sim/scene_data_providers/newton_scene_data_provider.py 3/5 Accesses private NewtonManager fields _model and _state_0, breaking encapsulation
source/isaaclab/isaaclab/sim/visualizers/newton_visualizer.py 4/5 Robust Newton OpenGL implementation with training controls, but suppresses exceptions silently
source/isaaclab/isaaclab/sim/visualizers/ov_visualizer.py 0/5 Stub implementation missing all required abstract methods (initialize, step, close, is_running)
source/isaaclab/isaaclab/sim/visualizers/rerun_visualizer.py 0/5 Stub with wrong import, copy-pasted docstrings, and missing abstract method implementations
source/isaaclab/isaaclab/sim/simulation_context.py 3/5 Training pause blocks in infinite loop, rendering pause skips visualizer step which may break UI state

Sequence Diagram

sequenceDiagram
    participant Script as Training Script
    participant SimCtx as SimulationContext
    participant Provider as NewtonSceneDataProvider
    participant NewtonMgr as NewtonManager
    participant Visualizer as NewtonVisualizer
    participant Viewer as NewtonViewerGL

    Script->>SimCtx: initialize_visualizers()
    SimCtx->>Provider: get_scene_data()
    Provider->>NewtonMgr: _model, _state_0 (private access)
    NewtonMgr-->>Provider: model, state
    Provider-->>SimCtx: scene_data dict
    SimCtx->>Visualizer: create_visualizer()
    SimCtx->>Visualizer: initialize(scene_data)
    Visualizer->>Viewer: NewtonViewerGL(cfg)
    Visualizer->>Viewer: set_model(model)
    Viewer-->>Visualizer: initialized
    Visualizer-->>SimCtx: visualizer ready

    loop Training Loop
        Script->>SimCtx: step_visualizers(dt)
        SimCtx->>Visualizer: is_running()?
        Visualizer->>Viewer: is_running()
        Viewer-->>Visualizer: true
        Visualizer-->>SimCtx: true
        
        alt Training Paused
            SimCtx->>Visualizer: is_training_paused()?
            Visualizer->>Viewer: is_training_paused()
            Viewer-->>Visualizer: true
            Visualizer-->>SimCtx: true
            loop While Paused
                SimCtx->>Visualizer: step(0.0, provider)
                Visualizer->>Viewer: begin_frame/end_frame
                Note over SimCtx,Viewer: BLOCKS simulation thread
            end
        end
        
        alt Rendering Paused
            SimCtx->>Visualizer: is_rendering_paused()?
            Visualizer->>Viewer: is_rendering_paused()
            Viewer-->>Visualizer: true
            Visualizer-->>SimCtx: true
            Note over SimCtx: continue (skip step)
        end
        
        SimCtx->>Visualizer: step(dt, provider)
        Visualizer->>Provider: get_state()
        Provider->>NewtonMgr: _state_0 (private access)
        NewtonMgr-->>Provider: current state
        Provider-->>Visualizer: state
        Visualizer->>Viewer: begin_frame(time)
        Visualizer->>Viewer: log_state(state)
        Visualizer->>Viewer: end_frame()
    end
    
    Script->>SimCtx: close_visualizers()
    SimCtx->>Visualizer: close()
    Visualizer->>Viewer: cleanup

Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

14 files reviewed, 5 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +368 to +369
# Frame lifecycle is now properly handled by try-finally
pass # Silently ignore to avoid log spam - the viewer will recover
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Silently suppressing exceptions can hide bugs

The pass statement after the try/finally block will hide any rendering errors. While the comment explains this is to avoid log spam, completely ignoring exceptions can make debugging difficult and hide serious issues.

Suggested change
# Frame lifecycle is now properly handled by try-finally
pass # Silently ignore to avoid log spam - the viewer will recover
except Exception as e:
# Log at debug level to avoid spam but still track issues
import omni.log
omni.log.debug(f"Rendering error in Newton visualizer: {e}")

Comment on lines 618 to 622
while visualizer.is_training_paused() and visualizer.is_running():
if isinstance(visualizer, NewtonVisualizer):
# Update state before rendering during pause
visualizer.update_state(NewtonManager._state_0)
visualizer.step(0.0) # Step with 0 dt during pause
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: Potential infinite loop if visualizer hangs

The while loop blocks indefinitely if the visualizer is paused. If is_running() never returns False and the user never unpauses, this will hang the entire simulation thread. The inner step(0.0) call could also raise exceptions that aren't caught here.

Consider adding a timeout or making this non-blocking to prevent simulation hangs.

Comment on lines +625 to +626
if visualizer.is_rendering_paused():
continue
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: Rendering continues when paused

When rendering is paused via is_rendering_paused(), this continue skips the visualization step but the simulation still runs. However, the visualizer's ImGui state still needs to be updated every frame (as noted in the comment on line 347 of newton_visualizer.py). This could cause the UI to become unresponsive.

The Newton visualizer's step() should probably still be called with a flag to update UI but skip rendering.

Comment on lines 569 to 575
else:
# Skip unsupported visualizer types for now
omni.log.warn(
f"Visualizer type '{type(viz_cfg).__name__}' is not yet implemented. Skipping."
)
continue

Copy link
Contributor

Choose a reason for hiding this comment

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

style: Accessing private class variable directly

The code directly accesses NewtonManager._model and NewtonManager._state_0 which are private class variables (indicated by the leading underscore). This creates tight coupling and could break if the NewtonManager implementation changes.

Consider adding public getter methods in NewtonManager like get_model() and get_state() for better encapsulation.

Comment on lines +697 to +700

# Initialize visualizers after simulation is set up (only on first reset)
if not soft and not self._visualizers:
self.initialize_visualizers()
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Check visualizer recreation behavior after cleanup

The condition if not soft and not self._visualizers means visualizers are only initialized if the list is empty. If visualizers were previously created and then closed (making the list empty again), they won't be recreated on the next reset.

Verify this matches the intended behavior for simulation resets.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

29 files reviewed, 5 comments

Edit Code Review Agent Settings | Greptile


from __future__ import annotations

from .ov_visualizer_cfg import RerunVisualizerCfg
Copy link
Contributor

Choose a reason for hiding this comment

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

syntax: incorrect import - importing RerunVisualizerCfg from ov_visualizer_cfg instead of rerun_visualizer_cfg

Suggested change
from .ov_visualizer_cfg import RerunVisualizerCfg
from .rerun_visualizer_cfg import RerunVisualizerCfg



class RerunVisualizer(Visualizer):
"""Omniverse Visualizer implementation."""
Copy link
Contributor

Choose a reason for hiding this comment

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

syntax: docstring copy-pasted from OmniverseVisualizer instead of describing Rerun visualizer

Suggested change
"""Omniverse Visualizer implementation."""
"""Rerun Visualizer implementation."""

#
# SPDX-License-Identifier: BSD-3-Clause

"""Omniverse Visualizer implementation."""
Copy link
Contributor

Choose a reason for hiding this comment

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

syntax: module docstring copy-pasted from Omniverse instead of describing Rerun

Suggested change
"""Omniverse Visualizer implementation."""
"""Rerun Visualizer implementation."""

Comment on lines +14 to +18
class OmniverseVisualizer(Visualizer):
"""Omniverse Visualizer implementation."""
def __init__(self, cfg: OVVisualizerCfg):
super().__init__(cfg)
# stub for now No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: stub visualizer missing abstract method implementations - base class requires initialize(), step(), close(), and is_running() to be implemented

Comment on lines +14 to +18
class RerunVisualizer(Visualizer):
"""Omniverse Visualizer implementation."""
def __init__(self, cfg: RerunVisualizerCfg):
super().__init__(cfg)
# stub for now No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: stub visualizer missing abstract method implementations - base class requires initialize(), step(), close(), and is_running() to be implemented

@matthewtrepte matthewtrepte changed the title [wip] Add initial cfgs for visualizers [wip] Add initial design for Visualizers Nov 12, 2025
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

30 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

30 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +631 to +633
while visualizer.is_training_paused() and visualizer.is_running():
# Step with 0 dt during pause, pass scene provider for state updates
visualizer.step(0.0, self._scene_provider)
Copy link
Contributor

Choose a reason for hiding this comment

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

logic: This while loop will block the simulation thread indefinitely if visualizer is paused and never unpauses. If is_running() becomes false during pause, the visualizer is removed but simulation remains blocked. The inner step(0.0) call could also raise exceptions that aren't handled.

Add a timeout or make this non-blocking to prevent simulation hangs.

Comment on lines +431 to +439
def update_state(self, state) -> None:
"""Update the simulation state for visualization.
This method should be called before step() to provide the latest simulation state.
Args:
state: The Newton State object to visualize.
"""
self._state = state
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Unused method - update_state() is never called. The visualizer pulls state from scene_provider in step() (line 365). Consider removing if not needed.

Comment on lines +42 to +44
self.cfg = cfg
self._is_initialized = False
self._is_closed = False
Copy link
Contributor

Choose a reason for hiding this comment

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

style: _is_initialized and _is_closed flags must be manually set by subclasses - error-prone. Consider setting them in base class methods or document this requirement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

asset New asset feature or request documentation Improvements or additions to documentation infrastructure isaac-mimic Related to Isaac Mimic team isaac-sim Related to Isaac Sim team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant