77
88from __future__ import annotations
99
10+ import logging
1011from typing import TYPE_CHECKING
1112
13+ from isaaclab .visualizers import Visualizer
14+
1215from .interface import Interface
13- from .ov_visualizer import OVVisualizer , RenderMode
16+ from isaaclab .visualizers .physx_ov_visualizer import PhysxOVVisualizer , RenderMode
17+ from isaaclab .visualizers .physx_ov_visualizer_cfg import PhysxOVVisualizerCfg
1418
1519if TYPE_CHECKING :
1620 from .simulation_context import SimulationContext
1721
18- # Re-export RenderMode for backwards compatibility
19- __all__ = ["RenderMode" , "VisualizerInterface" ]
22+ logger = logging .getLogger (__name__ )
2023
2124
2225class VisualizerInterface (Interface ):
2326 """Manages visualizer lifecycle and rendering for SimulationContext."""
2427
25- # Expose RenderMode as class attribute for backwards compatibility
26- RenderMode = RenderMode
27-
2828 def __init__ (self , sim_context : "SimulationContext" ):
2929 """Initialize visualizer interface.
3030
@@ -33,11 +33,18 @@ def __init__(self, sim_context: "SimulationContext"):
3333 """
3434 super ().__init__ (sim_context )
3535 self .dt = self ._sim .cfg .dt * self ._sim .cfg .render_interval
36- # Create OV visualizer helper
37- self ._ov_visualizer = OVVisualizer (sim_context )
38- # Track previous playing state to detect transitions
36+
37+ # Visualizer state
38+ visualizers = "omniverse"
39+ self ._visualizers_str = [v .strip () for v in visualizers .split ("," ) if v .strip ()]
40+ self ._visualizers : list [Visualizer ] = []
41+ self ._visualizer_step_counter = 0
42+ self ._scene_data_provider : SceneDataProvider | None = None
3943 self ._was_playing = False
4044
45+ # Initialize visualizers immediately
46+ self .initialize_visualizers ()
47+
4148 # -- Properties --
4249
4350 @property
@@ -53,34 +60,110 @@ def stage(self):
5360 return self ._sim .stage
5461
5562 @property
56- def offscreen_render (self ) -> bool :
57- """Whether offscreen rendering is enabled."""
58- return self ._ov_visualizer .offscreen_render
63+ def visualizers (self ) -> list [Visualizer ]:
64+ return self ._visualizers
5965
6066 @property
61- def render_viewport (self ) -> bool :
62- """Whether the default viewport should be rendered."""
63- return self ._ov_visualizer .render_viewport
67+ def scene_data_provider (self ) -> SceneDataProvider | None :
68+ return self ._scene_data_provider
6469
6570 @property
6671 def render_mode (self ) -> RenderMode :
67- """Current render mode."""
68- return self ._ov_visualizer .render_mode
69- # ------------------------------------------------------------------
70- # Timeline Control (delegate to OVVisualizer)
71- # ------------------------------------------------------------------
72+ """Current render mode from the first PhysxOVVisualizer."""
73+ for viz in self ._visualizers :
74+ if isinstance (viz , PhysxOVVisualizer ):
75+ return viz .render_mode
76+ return RenderMode .NO_GUI_OR_RENDERING
77+
78+ # -- Visualizer Initialization --
79+
80+ def _create_default_visualizer_configs (self , requested : list [str ]) -> list :
81+ """Create default configs for requested visualizer types."""
82+ configs = []
83+ type_map = {"omniverse" : PhysxOVVisualizerCfg }
84+
85+ for viz_type in requested :
86+ if viz_type in type_map :
87+ try :
88+ configs .append (type_map [viz_type ]())
89+ except Exception as e :
90+ logger .error (f"Failed to create default config for '{ viz_type } ': { e } " )
91+ else :
92+ logger .warning (f"Unknown visualizer type '{ viz_type } '. Valid: { list (type_map .keys ())} " )
93+
94+ return configs
95+
96+ def initialize_visualizers (self ) -> None :
97+ """Initialize visualizers based on --visualizer flag."""
98+ if not self ._visualizers_str :
99+ if bool (self .settings .get ("/isaaclab/visualizer" )) or bool (self .settings .get ("/isaaclab/render/offscreen" )):
100+ logger .info ("No visualizers specified via --visualizer flag." )
101+ return
102+
103+ # Get or create visualizer configs
104+ cfg_list = self ._sim .cfg .visualizer_cfgs
105+ if cfg_list is None :
106+ visualizer_cfgs = self ._create_default_visualizer_configs (self ._visualizers_str )
107+ else :
108+ visualizer_cfgs = cfg_list if isinstance (cfg_list , list ) else [cfg_list ]
109+ visualizer_cfgs = [c for c in visualizer_cfgs if c .visualizer_type in self ._visualizers_str ]
110+
111+ if not visualizer_cfgs :
112+ logger .info (f"Creating default configs for: { self ._visualizers_str } " )
113+ visualizer_cfgs = self ._create_default_visualizer_configs (self ._visualizers_str )
114+
115+ if not visualizer_cfgs :
116+ return
117+
118+ # Create scene data provider
119+ self ._scene_data_provider = None # SceneDataProvider(visualizer_cfgs)
120+
121+ # Initialize each visualizer
122+ for cfg in visualizer_cfgs :
123+ try :
124+ visualizer = cfg .create_visualizer ()
125+ scene_data = self ._build_scene_data (cfg )
126+ visualizer .initialize (scene_data )
127+ self ._visualizers .append (visualizer )
128+ logger .info (f"Initialized: { type (visualizer ).__name__ } ({ cfg .visualizer_type } )" )
129+ except Exception as e :
130+ logger .error (f"Failed to init '{ cfg .visualizer_type } ': { e } " )
131+
132+ def _build_scene_data (self , cfg ) -> dict :
133+ """Build scene data dict for visualizer initialization."""
134+ if cfg .visualizer_type in ("newton" , "rerun" ):
135+ return {"scene_data_provider" : self ._scene_data_provider }
136+ elif cfg .visualizer_type == "omniverse" :
137+ return {"usd_stage" : self ._sim .stage , "simulation_context" : self ._sim }
138+ return {}
139+
140+ # -- Unified Interface Methods --
72141
73142 def is_playing (self ) -> bool :
74143 """Check whether the simulation is playing."""
75- return self ._ov_visualizer .is_playing ()
144+ for viz in self .visualizers :
145+ if viz .is_playing ():
146+ return True
147+ return False
76148
77149 def is_stopped (self ) -> bool :
78150 """Check whether the simulation is stopped."""
79- return self ._ov_visualizer .is_stopped ()
151+ for viz in self .visualizers :
152+ if viz .is_stopped ():
153+ return True
154+ return False
155+
156+ def forward (self ) -> None :
157+ """Sync scene data and step all active visualizers.
158+
159+ Args:
160+ dt: Time step in seconds (0.0 for kinematics-only).
161+ """
162+ if self ._scene_data_provider :
163+ self ._scene_data_provider .update ()
80164
81- # ------------------------------------------------------------------
82- # Render Mode (delegate to OVVisualizer)
83- # ------------------------------------------------------------------
165+ if not self ._visualizers :
166+ return
84167
85168 def set_render_mode (self , mode : RenderMode ):
86169 """Change the current render mode of the simulation.
@@ -91,16 +174,9 @@ def set_render_mode(self, mode: RenderMode):
91174 mode: The rendering mode. If different than current rendering mode,
92175 the mode is changed to the new mode.
93176 """
94- self ._ov_visualizer .set_render_mode (mode )
95-
96-
97- def forward (self ) -> None :
98- """Sync scene data and step all active visualizers.
99-
100- Args:
101- dt: Time step in seconds (0.0 for kinematics-only).
102- """
103- pass
177+ for viz in self ._visualizers :
178+ if isinstance (viz , PhysxOVVisualizer ):
179+ viz .set_render_mode (mode )
104180
105181 def step (self , render : bool = True ) -> None :
106182 """Step visualizers and optionally render.
@@ -113,10 +189,10 @@ def step(self, render: bool = True) -> None:
113189 self .render ()
114190 self ._was_playing = False
115191
116- # # Detect transition: was not playing → now playing (resume from pause)
192+ # Detect transition: was not playing → now playing (resume from pause)
117193 is_playing = self .is_playing ()
118194 if not self ._was_playing and is_playing :
119- self .reset (soft = True ) # TODO: it is currently buggy
195+ self .reset (soft = True )
120196
121197 # Update state tracking
122198 self ._was_playing = is_playing
@@ -128,36 +204,77 @@ def step(self, render: bool = True) -> None:
128204
129205 def reset (self , soft : bool ) -> None :
130206 """Reset visualizers (warmup renders on hard reset)."""
131- self ._ov_visualizer .reset (soft )
207+ for viz in self ._visualizers :
208+ if isinstance (viz , PhysxOVVisualizer ):
209+ viz .reset (soft )
132210
133211 def close (self ) -> None :
134212 """Close all visualizers and clean up."""
135- self ._ov_visualizer .close ()
213+ for viz in self ._visualizers :
214+ try :
215+ viz .close ()
216+ except Exception as e :
217+ logger .error (f"Error closing { type (viz ).__name__ } : { e } " )
218+
219+ self ._visualizers .clear ()
220+ logger .info ("All visualizers closed" )
136221
137222 def play (self ) -> None :
138223 """Handle simulation start."""
139- self ._ov_visualizer .play ()
224+ for viz in self ._visualizers :
225+ viz .play ()
140226
141227 def stop (self ) -> None :
142228 """Handle simulation stop."""
143- self ._ov_visualizer .stop ()
229+ for viz in self ._visualizers :
230+ viz .stop ()
144231
145232 def pause (self ) -> None :
146233 """Pause the simulation."""
147- self ._ov_visualizer .pause ()
234+ for viz in self ._visualizers :
235+ viz .pause ()
148236
149- def render (self , mode : RenderMode | None = None ) :
150- """Render the scene (OV mode only) .
237+ def render (self ) -> None :
238+ """Render the scene.
151239
152240 Args:
153241 mode: Render mode to set, or None to keep current.
154-
155- Returns:
156- True if rendered, False if not in OV mode.
157242 """
158- self ._ov_visualizer .render (mode )
159-
243+ self ._visualizer_step_counter += 1
244+ to_remove = []
245+
246+ for viz in self ._visualizers :
247+ try :
248+ if not viz .is_running ():
249+ to_remove .append (viz )
250+ continue
251+
252+ # Block while training paused
253+ while viz .is_training_paused () and viz .is_running ():
254+ viz .step (0.0 , state = None )
255+
256+ viz .step (self .get_rendering_dt () or self .dt , state = None )
257+ except Exception as e :
258+ logger .error (f"Error stepping { type (viz ).__name__ } : { e } " )
259+ to_remove .append (viz )
260+
261+ for viz in to_remove :
262+ try :
263+ viz .close ()
264+ self ._visualizers .remove (viz )
265+ logger .info (f"Removed: { type (viz ).__name__ } " )
266+ except Exception as e :
267+ logger .error (f"Error closing visualizer: { e } " )
268+
269+ def get_rendering_dt (self ) -> float :
270+ """Get rendering dt from visualizers, or fall back to physics dt."""
271+ for viz in self ._visualizers :
272+ dt = viz .get_rendering_dt ()
273+ if dt is not None :
274+ return dt
275+ return self .dt
160276
161277 def set_camera_view (self , eye : tuple , target : tuple ) -> None :
162278 """Set camera view on all visualizers that support it."""
163- self ._ov_visualizer .set_camera_view (eye , target , "/OmniverseKit_Persp" )
279+ for viz in self ._visualizers :
280+ viz .set_camera_view (eye , target )
0 commit comments