Skip to content

Commit 6ac6ed4

Browse files
committed
refactor step 13: refactored visualizers to be newton consistent
1 parent 13920ef commit 6ac6ed4

File tree

8 files changed

+747
-146
lines changed

8 files changed

+747
-146
lines changed

source/isaaclab/isaaclab/sim/simulation_cfg.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from isaaclab.utils import configclass
1515

1616
from .spawners.materials import RigidBodyMaterialCfg
17-
17+
from isaaclab.visualizers import VisualizerCfg
1818

1919
@configclass
2020
class PhysxCfg:
@@ -436,3 +436,6 @@ class SimulationCfg:
436436
If :attr:`save_logs_to_file` is True, the logs will be saved to the directory specified by :attr:`log_dir`.
437437
If None, the logs will be saved to the temp directory.
438438
"""
439+
440+
visualizer_cfgs: list[VisualizerCfg] | VisualizerCfg | None = None
441+
"""The list of visualizer configurations. Default is None."""

source/isaaclab/isaaclab/sim/simulation_context.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ def set_camera_view(
275275
camera_prim_path: The path to the camera primitive in the stage. Defaults to
276276
"/OmniverseKit_Persp".
277277
"""
278-
self._visualizer.set_camera_view(eye, target, camera_prim_path)
278+
self._visualizer.set_camera_view(eye, target)
279279

280280
def set_render_mode(self, mode: RenderMode):
281281
"""Change the current render mode of the simulation.
@@ -429,7 +429,7 @@ def render(self, mode: RenderMode | None = None):
429429
mode: The rendering mode. Defaults to None, in which case the current rendering mode is used.
430430
"""
431431
self._physics_interface.forward()
432-
self._visualizer.render(mode)
432+
self._visualizer.render()
433433

434434
@classmethod
435435
def clear(cls):

source/isaaclab/isaaclab/sim/visualizer_interface.py

Lines changed: 167 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,24 @@
77

88
from __future__ import annotations
99

10+
import logging
1011
from typing import TYPE_CHECKING
1112

13+
from isaaclab.visualizers import Visualizer
14+
1215
from .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

1519
if 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

2225
class 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

Comments
 (0)