Skip to content

Commit fdd4530

Browse files
committed
resolve overlapping beahvior in viewrcfg and visualizercfg
1 parent 260ffcb commit fdd4530

12 files changed

Lines changed: 112 additions & 114 deletions

File tree

source/isaaclab/isaaclab/envs/common.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
class ViewerCfg:
2222
"""Configuration of the scene viewport camera."""
2323

24-
eye: tuple[float, float, float] = (7.5, 7.5, 117.5)
24+
eye: tuple[float, float, float] = (7.5, 7.5, 7.5)
2525
"""Initial camera position (in m). Default is (7.5, 7.5, 7.5)."""
2626

2727
lookat: tuple[float, float, float] = (0.0, 0.0, 0.0)
@@ -67,6 +67,7 @@ class ViewerCfg:
6767
This quantity is only effective if :attr:`origin` is set to "asset_body".
6868
"""
6969

70+
7071
##
7172
# Types.
7273
##

source/isaaclab/isaaclab/envs/manager_based_env.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def _init_sim(self):
167167
# FIXME: This needs to be fixed in the future when we unify the UI functionalities even for
168168
# non-rendering modes.
169169
# Initialize when GUI is available OR when visualizers are active (headless rendering)
170-
# Visualizers support camera updates via sim.set_camera_view() which forwards to all active visualizers
170+
# ViewerCfg camera updates are applied through renderer camera control.
171171
has_visualizers = bool(self.sim.get_setting("/isaaclab/visualizer/types")) or bool(
172172
self.sim.get_setting("/isaaclab/video/auto_start_kit")
173173
)

source/isaaclab/isaaclab/envs/mdp/commands/velocity_command.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ def _resolve_xy_velocity_to_arrow(self, xy_velocity: torch.Tensor) -> tuple[torc
223223

224224
return arrow_scale, arrow_quat
225225

226+
226227
class NormalVelocityCommand(UniformVelocityCommand):
227228
"""Command generator that generates a velocity command in SE(2) from a normal distribution.
228229

source/isaaclab/isaaclab/envs/ui/viewport_camera_controller.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,8 @@ def update_view_location(self, eye: Sequence[float] | None = None, lookat: Seque
218218
cam_eye = viewer_origin + self.default_cam_eye
219219
cam_target = viewer_origin + self.default_cam_lookat
220220

221-
# set the camera view
222-
self._env.sim.set_camera_view(eye=cam_eye, target=cam_target)
221+
# set the renderer viewport camera view (does not broadcast to visualizers)
222+
self._env.sim.set_renderer_camera_view(eye=cam_eye, target=cam_target, camera_prim_path=self.cfg.cam_prim_path)
223223

224224
"""
225225
Private Functions

source/isaaclab/isaaclab/sim/simulation_context.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,25 @@ def set_camera_view(self, eye: tuple, target: tuple) -> None:
659659
for viz in self._visualizers:
660660
viz.set_camera_view(eye, target)
661661

662+
def set_renderer_camera_view(
663+
self,
664+
eye: tuple[float, float, float] | list[float],
665+
target: tuple[float, float, float] | list[float],
666+
camera_prim_path: str = "/OmniverseKit_Persp",
667+
) -> None:
668+
"""Set camera view for renderer/viewport camera only.
669+
670+
This does not broadcast to visualizers.
671+
"""
672+
try:
673+
import isaacsim.core.utils.viewports as isaacsim_viewports
674+
675+
isaacsim_viewports.set_camera_view(
676+
eye=list(eye), target=list(target), camera_prim_path=str(camera_prim_path)
677+
)
678+
except Exception as exc:
679+
logger.debug("[SimulationContext] Renderer camera update skipped: %s", exc)
680+
662681
def forward(self) -> None:
663682
"""Update kinematics without stepping physics."""
664683
self.physics_manager.forward()

source/isaaclab/isaaclab/visualizers/base_visualizer.py

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -189,23 +189,11 @@ def set_camera_view(self, eye: tuple, target: tuple) -> None:
189189

190190
def _resolve_cfg_camera_pose(
191191
self, visualizer_name: str
192-
) -> tuple[tuple[float, float, float], tuple[float, float, float]] | None:
193-
"""Resolve camera pose from cfg fields, allowing opt-out via ``None``.
194-
195-
Returns ``None`` when both cfg camera fields are ``None`` (keep backend
196-
default camera) or when only one field is ``None`` (invalid partial pose).
197-
"""
198-
eye = getattr(self.cfg, "eye", None)
199-
lookat = getattr(self.cfg, "lookat", None)
200-
if eye is None and lookat is None:
201-
return None
202-
if eye is None or lookat is None:
203-
logger.warning(
204-
"[%s] eye/lookat must be both set or both None. "
205-
"Keeping current camera pose.",
206-
visualizer_name,
207-
)
208-
return None
192+
) -> tuple[tuple[float, float, float], tuple[float, float, float]]:
193+
"""Resolve camera pose from cfg eye/lookat fields."""
194+
del visualizer_name
195+
eye = tuple(float(v) for v in getattr(self.cfg, "eye"))
196+
lookat = tuple(float(v) for v in getattr(self.cfg, "lookat"))
209197
return eye, lookat
210198

211199
def _resolve_camera_pose_from_usd_path(

source/isaaclab/isaaclab/visualizers/visualizer_cfg.py

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,25 +34,17 @@ class VisualizerCfg:
3434
enable_live_plots: bool = True
3535
"""Enable live plotting of data."""
3636

37-
eye: tuple[float, float, float] | None = (8.0, 8.0, 3.0)
38-
"""Initial camera eye position (x, y, z) in world coordinates.
37+
eye: tuple[float, float, float] = (8.0, 8.0, 3.0)
38+
"""Initial camera eye position (x, y, z) in world coordinates."""
3939

40-
Set to ``None`` (with :attr:`lookat` also set to ``None``) to keep
41-
the visualizer's default camera pose.
42-
"""
43-
44-
lookat: tuple[float, float, float] | None = (0.0, 0.0, 0.0)
45-
"""Initial camera look-at point (x, y, z) in world coordinates.
46-
47-
Set to ``None`` (with :attr:`eye` also set to ``None``) to keep
48-
the visualizer's default camera pose.
49-
"""
40+
lookat: tuple[float, float, float] = (0.0, 0.0, 0.0)
41+
"""Initial camera look-at point (x, y, z) in world coordinates."""
5042

51-
camera_source: Literal["cfg", "usd_path"] = "cfg"
52-
"""Camera source mode: 'cfg' uses eye/lookat, 'usd_path' follows a USD camera prim."""
43+
cam_source: Literal["cfg", "prim_path"] = "cfg"
44+
"""Camera source mode: 'cfg' uses eye/lookat, 'prim_path' follows a camera prim."""
5345

54-
camera_usd_path: str = "/World/envs/env_0/Camera"
55-
"""Absolute USD path to a camera prim when camera_source='usd_path'."""
46+
cam_prim_path: str = "/World/envs/env_0/Camera"
47+
"""Absolute USD path to a camera prim when cam_source='prim_path'."""
5648

5749
env_filter_mode: Literal["none", "env_ids", "random_n"] = "none"
5850
"""Env filter mode: 'none', 'env_ids', or 'random_n'."""

source/isaaclab_visualizers/isaaclab_visualizers/kit/kit_visualizer.py

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ def initialize(self, scene_data_provider: BaseSceneDataProvider) -> None:
8383
rows=[
8484
("eye", self.cfg.eye),
8585
("lookat", self.cfg.lookat),
86-
("camera_source", self.cfg.camera_source),
86+
("cam_source", self.cfg.cam_source),
87+
("visualizer_camera_prim_path", self.cfg.visualizer_camera_prim_path),
88+
("enable_visualizer_cam", self.cfg.enable_visualizer_cam),
8789
("num_visualized_envs", num_visualized_envs),
8890
("create_viewport", self.cfg.create_viewport),
8991
("headless", self._runtime_headless),
@@ -181,6 +183,9 @@ def set_camera_view(
181183
if not self._is_initialized:
182184
logger.debug("[KitVisualizer] set_camera_view() ignored because visualizer is not initialized.")
183185
return
186+
if not self.cfg.enable_visualizer_cam:
187+
logger.debug("[KitVisualizer] set_camera_view() ignored because enable_visualizer_cam=False.")
188+
return
184189
self._set_viewport_camera(tuple(eye), tuple(target))
185190

186191
# ---- Viewport + camera ----------------------------------------------------------------
@@ -223,11 +228,17 @@ def _setup_viewport(self, usd_stage) -> None:
223228
# In headless mode we keep the visualizer active but skip viewport/window setup.
224229
self._viewport_window = None
225230
self._viewport_api = None
226-
self._controlled_camera_path = "/OmniverseKit_Persp"
231+
self._controlled_camera_path = "/OmniverseKit_Persp" if self.cfg.enable_visualizer_cam else None
232+
if not self.cfg.enable_visualizer_cam:
233+
return
227234
self._apply_cfg_camera_pose_if_configured()
228235
return
229236

230-
if self.cfg.create_viewport and self.cfg.viewport_name:
237+
if self.cfg.create_viewport:
238+
if not self.cfg.viewport_name.strip():
239+
raise RuntimeError(
240+
"[KitVisualizer] viewport_name must be a non-empty string when create_viewport=True."
241+
)
231242
dock_position_name = self.cfg.dock_position.upper()
232243
dock_position_map = {
233244
"LEFT": DockPosition.LEFT,
@@ -247,7 +258,6 @@ def _setup_viewport(self, usd_stage) -> None:
247258
)
248259

249260
asyncio.ensure_future(self._dock_viewport_async(self.cfg.viewport_name, dock_pos))
250-
self._create_and_assign_camera(usd_stage)
251261
else:
252262
self._viewport_window = vp_utils.get_active_viewport_window()
253263

@@ -256,16 +266,18 @@ def _setup_viewport(self, usd_stage) -> None:
256266
self._viewport_api = None
257267
return
258268
self._viewport_api = self._viewport_window.viewport_api
259-
# Pin the camera path we will write to, using the active camera at init time.
260-
# This must happen before any _set_viewport_camera() call so the path is known.
261-
self._controlled_camera_path = self._viewport_api.get_active_camera() or "/OmniverseKit_Persp"
262-
if self.cfg.camera_source == "usd_path":
263-
if not self._set_active_camera_path(self.cfg.camera_usd_path):
264-
logger.warning(
265-
"[KitVisualizer] camera_usd_path '%s' not found; using configured camera.",
266-
self.cfg.camera_usd_path,
269+
if not self.cfg.enable_visualizer_cam:
270+
self._controlled_camera_path = None
271+
return
272+
# Always create/use a dedicated visualizer-controlled camera so we never mutate
273+
# existing viewport cameras (e.g. /OmniverseKit_Persp or sensor cameras).
274+
self._create_and_assign_camera(usd_stage)
275+
if self.cfg.cam_source == "prim_path":
276+
if not self._set_active_camera_path(self.cfg.cam_prim_path):
277+
raise RuntimeError(
278+
"[KitVisualizer] cam_source='prim_path' requires a valid cam_prim_path. "
279+
f"Camera prim not found: '{self.cfg.cam_prim_path}'."
267280
)
268-
self._apply_cfg_camera_pose_if_configured()
269281
else:
270282
self._apply_cfg_camera_pose_if_configured()
271283

@@ -301,8 +313,10 @@ async def _dock_viewport_async(self, viewport_name: str, dock_position) -> None:
301313
viewport_window.focus()
302314

303315
def _create_and_assign_camera(self, usd_stage) -> None:
304-
"""Create viewport camera prim (if needed) and set it active."""
305-
camera_path = f"/World/Cameras/{self.cfg.viewport_name}_Camera".replace(" ", "_")
316+
"""Create dedicated visualizer camera prim (if needed) and set it active."""
317+
camera_path = self.cfg.visualizer_camera_prim_path
318+
if not camera_path:
319+
raise RuntimeError("[KitVisualizer] visualizer_camera_prim_path must be a non-empty prim path.")
306320

307321
camera_prim = usd_stage.GetPrimAtPath(camera_path)
308322
if not camera_prim.IsValid():
@@ -329,19 +343,7 @@ def _set_viewport_camera(self, position: tuple[float, float, float], target: tup
329343
isaacsim_viewports.set_camera_view(**kwargs)
330344

331345
def _apply_cfg_camera_pose_if_configured(self) -> None:
332-
"""Apply configured camera pose if both position and target are provided.
333-
334-
When either field is ``None``, keep the current/default perspective
335-
camera transform unchanged.
336-
"""
337-
if self.cfg.eye is None and self.cfg.lookat is None:
338-
return
339-
if self.cfg.eye is None or self.cfg.lookat is None:
340-
logger.warning(
341-
"[KitVisualizer] eye/lookat must be both set or both None. "
342-
"Keeping current camera pose."
343-
)
344-
return
346+
"""Apply configured camera pose from eye/lookat."""
345347
self._set_viewport_camera(self.cfg.eye, self.cfg.lookat)
346348

347349
def _set_active_camera_path(self, camera_path: str) -> bool:

source/isaaclab_visualizers/isaaclab_visualizers/kit/kit_visualizer_cfg.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,21 @@ class KitVisualizerCfg(VisualizerCfg):
1616
visualizer_type: str = "kit"
1717
"""Type identifier for Kit visualizer."""
1818

19-
eye: tuple[float, float, float] | None = (8.0, 8.0, 3.0)
20-
"""Initial camera eye override.
19+
viewport_name: str = "Visualizer Viewport"
20+
"""Viewport name to use when :attr:`create_viewport` is True."""
2121

22-
If set to ``None`` (with :attr:`lookat` also set to ``None``), the
23-
visualizer keeps the current/default perspective camera pose.
24-
"""
25-
26-
lookat: tuple[float, float, float] | None = (0.0, 0.0, 0.0)
27-
"""Initial camera look-at override.
22+
create_viewport: bool = False
23+
"""Create new viewport with specified name and camera pose."""
2824

29-
If set to ``None`` (with :attr:`eye` also set to ``None``), the
30-
visualizer keeps the current/default perspective camera pose.
31-
"""
25+
visualizer_camera_prim_path: str = "/World/Cameras/KitVisualizerCamera"
26+
"""Dedicated camera prim path controlled by the Kit visualizer."""
3227

33-
viewport_name: str | None = "Visualizer Viewport"
34-
"""Viewport name to use. If None, uses active viewport."""
28+
enable_visualizer_cam: bool = False
29+
"""Whether the Kit visualizer should control/bind a dedicated viewport camera.
3530
36-
create_viewport: bool = False
37-
"""Create new viewport with specified name and camera pose."""
31+
If False, Kit does not create/switch camera prims and ignores visualizer camera control
32+
updates (including eye/lookat and cam_source handling).
33+
"""
3834

3935
headless: bool = False
4036
"""Run without creating viewport windows when supported by the app."""

source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -315,8 +315,7 @@ def initialize(self, scene_data_provider: BaseSceneDataProvider) -> None:
315315
self._viewer.set_model(self._model, max_worlds=max_worlds)
316316
self._viewer.set_world_offsets((0.0, 0.0, 0.0))
317317
initial_pose = self._resolve_initial_camera_pose()
318-
if initial_pose is not None:
319-
self._apply_camera_pose(initial_pose)
318+
self._apply_camera_pose(initial_pose)
320319
self._viewer.up_axis = 2 # Z-up
321320

322321
self._viewer.scaling = 1.0
@@ -345,12 +344,10 @@ def initialize(self, scene_data_provider: BaseSceneDataProvider) -> None:
345344
rows=[
346345
(
347346
"eye",
348-
tuple(float(x) for x in self._viewer.camera.pos)
349-
if self._viewer is not None
350-
else self.cfg.eye,
347+
tuple(float(x) for x in self._viewer.camera.pos) if self._viewer is not None else self.cfg.eye,
351348
),
352349
("lookat", self._last_camera_pose[1] if self._last_camera_pose else self.cfg.lookat),
353-
("camera_source", self.cfg.camera_source),
350+
("cam_source", self.cfg.cam_source),
354351
("num_visualized_envs", num_visualized_envs),
355352
("headless", self.cfg.headless),
356353
],
@@ -374,7 +371,7 @@ def step(self, dt: float) -> None:
374371
self._state = self._scene_data_provider.get_newton_state(self._env_ids)
375372
return
376373

377-
if self.cfg.camera_source == "usd_path":
374+
if self.cfg.cam_source == "prim_path":
378375
self._update_camera_from_usd_path()
379376

380377
self._state = self._scene_data_provider.get_newton_state(self._env_ids)
@@ -433,19 +430,19 @@ def is_running(self) -> bool:
433430
return False
434431
return self._viewer.is_running()
435432

436-
def _resolve_initial_camera_pose(self) -> tuple[tuple[float, float, float], tuple[float, float, float]] | None:
433+
def _resolve_initial_camera_pose(self) -> tuple[tuple[float, float, float], tuple[float, float, float]]:
437434
"""Resolve initial camera pose from config or USD camera path.
438435
439436
Returns:
440-
Camera eye and target tuples, or ``None`` to keep viewer defaults.
437+
Camera eye and target tuples.
441438
"""
442-
if self.cfg.camera_source == "usd_path":
443-
pose = self._resolve_camera_pose_from_usd_path(self.cfg.camera_usd_path)
439+
if self.cfg.cam_source == "prim_path":
440+
pose = self._resolve_camera_pose_from_usd_path(self.cfg.cam_prim_path)
444441
if pose is not None:
445442
return pose
446-
logger.warning(
447-
"[NewtonVisualizer] camera_usd_path '%s' not found; using configured camera.",
448-
self.cfg.camera_usd_path,
443+
raise RuntimeError(
444+
"[NewtonVisualizer] cam_source='prim_path' requires a resolvable camera prim path, "
445+
f"but no camera pose was found for '{self.cfg.cam_prim_path}'."
449446
)
450447
return self._resolve_cfg_camera_pose("NewtonVisualizer")
451448

@@ -471,7 +468,7 @@ def _apply_camera_pose(self, pose: tuple[tuple[float, float, float], tuple[float
471468

472469
def _update_camera_from_usd_path(self) -> None:
473470
"""Refresh camera pose from configured USD camera path when it changes."""
474-
pose = self._resolve_camera_pose_from_usd_path(self.cfg.camera_usd_path)
471+
pose = self._resolve_camera_pose_from_usd_path(self.cfg.cam_prim_path)
475472
if pose is None:
476473
return
477474
if self._last_camera_pose == pose:

0 commit comments

Comments
 (0)