From 2a87bfa91472c1b366e0225e8fc7c698fc55005c Mon Sep 17 00:00:00 2001 From: Matthew Trepte Date: Wed, 3 Jun 2026 05:52:46 +0000 Subject: [PATCH 01/17] init fixes --- .../clarify-visualizer-camera-sensor-mode.rst | 1 + .../isaaclab/isaaclab/cli/commands/install.py | 2 +- .../isaaclab/envs/utils/camera_view.py | 15 ++++- .../isaaclab/visualizers/visualizer_cfg.py | 2 +- .../test/visualizers/test_visualizer.py | 11 +++- .../fix-newton-hud-imgui-dependency.rst | 3 + .../newton/newton_visualizer.py | 16 +++++ .../isaaclab_visualizers/newton_adapter.py | 23 +++++++ .../rerun/rerun_visualizer.py | 22 ++++++- .../viser/viser_visualizer.py | 22 ++++++- source/isaaclab_visualizers/pyproject.toml | 4 ++ source/isaaclab_visualizers/setup.py | 65 ------------------- .../test/test_newton_adapter.py | 26 +++++++- .../test_visualizer_integration_newton.py | 5 +- .../test/test_visualizer_integration_physx.py | 5 +- ...est_visualizer_tiled_integration_newton.py | 5 +- ...test_visualizer_tiled_integration_physx.py | 5 +- .../test/visualizer_integration_utils.py | 26 ++++++++ tools/wheel_builder/res/python_packages.toml | 2 +- 19 files changed, 182 insertions(+), 78 deletions(-) create mode 100644 source/isaaclab/changelog.d/clarify-visualizer-camera-sensor-mode.rst create mode 100644 source/isaaclab_visualizers/changelog.d/fix-newton-hud-imgui-dependency.rst delete mode 100644 source/isaaclab_visualizers/setup.py diff --git a/source/isaaclab/changelog.d/clarify-visualizer-camera-sensor-mode.rst b/source/isaaclab/changelog.d/clarify-visualizer-camera-sensor-mode.rst new file mode 100644 index 000000000000..9dad61c329ec --- /dev/null +++ b/source/isaaclab/changelog.d/clarify-visualizer-camera-sensor-mode.rst @@ -0,0 +1 @@ +Improved visualizer tiled-camera errors when ``tiled_cam_prim_path`` is set but the scene has no Isaac Lab ``Camera`` sensors, and clarified the camera-mode documentation for Cartpole camera tasks. diff --git a/source/isaaclab/isaaclab/cli/commands/install.py b/source/isaaclab/isaaclab/cli/commands/install.py index 2fe6368b65d0..f4d141dc4a08 100644 --- a/source/isaaclab/isaaclab/cli/commands/install.py +++ b/source/isaaclab/isaaclab/cli/commands/install.py @@ -652,7 +652,7 @@ def _install_extra_feature(feature_name: str, selector: str = "") -> None: elif feature_name == "newton": if selector: print_warning(f"'newton' does not support selectors (got '{selector}'). Installing all newton extras.") - print_info("Installing newton extras (newton[sim], PyOpenGL-accelerate, imgui-bundle)...") + print_info("Installing newton extras (newton[sim], PyOpenGL-accelerate, imgui-bundle, typing-extensions)...") run_command(pip_cmd + ["install", "--editable", f"{source_dir}/isaaclab_newton[all]"]) run_command(pip_cmd + ["install", "--editable", f"{source_dir}/isaaclab_physx[newton]"]) run_command(pip_cmd + ["install", "--editable", f"{source_dir}/isaaclab_visualizers[newton]"]) diff --git a/source/isaaclab/isaaclab/envs/utils/camera_view.py b/source/isaaclab/isaaclab/envs/utils/camera_view.py index 2e2ea846a92f..2c4fbe80a5ec 100644 --- a/source/isaaclab/isaaclab/envs/utils/camera_view.py +++ b/source/isaaclab/isaaclab/envs/utils/camera_view.py @@ -92,7 +92,20 @@ def find_camera_by_prim_path(camera_sensors: dict[str, Camera], cam_prim_path: s f"cam_prim_path={cam_prim_path!r} matched USD camera prims, but no Isaac Lab Camera sensor owns them. " "Add the camera to scene.sensors or leave tiled_cam_prim_path unset to use generated tiled cameras." ) - raise RuntimeError(f"No Isaac Lab Camera sensor matched cam_prim_path={cam_prim_path!r}.") + if not camera_sensors: + raise RuntimeError( + f"No Isaac Lab Camera sensors are registered in the scene, so tiled_cam_prim_path={cam_prim_path!r} " + "cannot be used. Use an environment that defines Camera sensors, or leave tiled_cam_prim_path unset " + "to use generated tiled cameras." + ) + available_paths = { + getattr(camera.cfg, "prim_path", None) for camera in camera_sensors.values() if getattr(camera, "cfg", None) + } + raise RuntimeError( + f"No Isaac Lab Camera sensor matched cam_prim_path={cam_prim_path!r}. " + f"Available Camera sensor prim paths: {sorted(path for path in available_paths if path)}. " + "Leave tiled_cam_prim_path unset to use generated tiled cameras." + ) def ensure_camera_initialized(camera: Camera) -> None: diff --git a/source/isaaclab/isaaclab/visualizers/visualizer_cfg.py b/source/isaaclab/isaaclab/visualizers/visualizer_cfg.py index 78729f1779a8..03422e6d3ae4 100644 --- a/source/isaaclab/isaaclab/visualizers/visualizer_cfg.py +++ b/source/isaaclab/isaaclab/visualizers/visualizer_cfg.py @@ -36,7 +36,7 @@ class VisualizerCfg: """Camera focal length in millimeters for visualizer camera views.""" # Tiled camera settings - tiled_cam_view: bool = False + tiled_cam_view: bool = True """Enable a non-interactive tiled camera image view.""" tiled_cam_num: int = 16 diff --git a/source/isaaclab/test/visualizers/test_visualizer.py b/source/isaaclab/test/visualizers/test_visualizer.py index 3cc8deed56b2..d83b4ea6d794 100644 --- a/source/isaaclab/test/visualizers/test_visualizer.py +++ b/source/isaaclab/test/visualizers/test_visualizer.py @@ -13,7 +13,7 @@ import pytest import torch -from isaaclab.envs.utils.camera_view import apply_camera_view_from_origins +from isaaclab.envs.utils.camera_view import apply_camera_view_from_origins, find_camera_by_prim_path from isaaclab.visualizers.base_visualizer import BaseVisualizer from isaaclab.visualizers.visualizer import Visualizer from isaaclab.visualizers.visualizer_cfg import VisualizerCfg @@ -126,6 +126,15 @@ def test_apply_camera_view_from_origins_forwards_env_ids(): assert camera.update_poses_calls == [None] +def test_find_camera_by_prim_path_explains_missing_scene_cameras(): + with pytest.raises(RuntimeError) as exc_info: + find_camera_by_prim_path({}, "/World/envs/*/Camera", [0]) + + message = str(exc_info.value) + assert "No Isaac Lab Camera sensors are registered in the scene" in message + assert "leave tiled_cam_prim_path unset" in message + + def test_compute_visualized_env_ids_cap_only_returns_none(): """Cap-only path: :meth:`_compute_visualized_env_ids` is ``None``. diff --git a/source/isaaclab_visualizers/changelog.d/fix-newton-hud-imgui-dependency.rst b/source/isaaclab_visualizers/changelog.d/fix-newton-hud-imgui-dependency.rst new file mode 100644 index 000000000000..4cab0ab89428 --- /dev/null +++ b/source/isaaclab_visualizers/changelog.d/fix-newton-hud-imgui-dependency.rst @@ -0,0 +1,3 @@ +Fixed Newton visualizer HUD dependency checks by requiring ``typing-extensions>=4.15.0`` for the Newton visualizer extra and failing integration tests when Newton reports that ``imgui_bundle`` could not be imported. Removed the legacy ``setup.py`` for ``isaaclab_visualizers`` now that ``pyproject.toml`` carries the package metadata. + +Fixed Rerun and Viser visualizers rendering Newton infinite ground planes too small by expanding non-positive plane extents to the same large finite size used by Newton GL. diff --git a/source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py b/source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py index 7c4f82234d0f..57d0d604ea05 100644 --- a/source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py +++ b/source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py @@ -41,6 +41,21 @@ from isaaclab.scene_data import SceneDataProvider +def _log_newton_hud_dependency_issue() -> None: + """Log a clear warning if Newton's imgui HUD dependencies cannot be imported.""" + try: + from imgui_bundle import imgui as _imgui # noqa: F401 + from imgui_bundle import imguizmo as _imguizmo # noqa: F401 + from imgui_bundle.python_backends import pyglet_backend as _pyglet_backend # noqa: F401 + except ImportError as exc: + logger.warning( + "[NewtonVisualizer] Newton HUD disabled: failed to import imgui_bundle. " + "Install isaaclab-visualizers[newton] or fix its transitive dependencies " + "(for example typing-extensions>=4.15.0). ImportError: %s", + exc, + ) + + class NewtonViewerGL(ViewerGL): """Wrapper around Newton's ViewerGL with training/rendering pause controls.""" @@ -389,6 +404,7 @@ def initialize(self, scene_data_provider: SceneDataProvider) -> None: pyglet.options["headless"] = True + _log_newton_hud_dependency_issue() self._viewer = NewtonViewerGL( width=self.cfg.window_width, height=self.cfg.window_height, diff --git a/source/isaaclab_visualizers/isaaclab_visualizers/newton_adapter.py b/source/isaaclab_visualizers/isaaclab_visualizers/newton_adapter.py index 6bc3d5a2b4f1..0dffab16fbec 100644 --- a/source/isaaclab_visualizers/isaaclab_visualizers/newton_adapter.py +++ b/source/isaaclab_visualizers/isaaclab_visualizers/newton_adapter.py @@ -8,6 +8,29 @@ from __future__ import annotations +VISUALIZER_INFINITE_PLANE_SIZE = 1000.0 +"""Finite render size used for Newton planes encoded as infinite.""" + + +def expand_infinite_plane_scale( + geo_scale: tuple[float, ...], plane_size: float = VISUALIZER_INFINITE_PLANE_SIZE +) -> tuple[float, ...]: + """Return a finite visual scale for Newton planes encoded with non-positive extents. + + Newton uses non-positive X/Y plane scale values to represent an effectively + infinite plane. Newton GL renders those with a large finite mesh; web viewers + also need a finite size, otherwise their world-extents heuristic can shrink + the floor to just the actor bounds. + """ + scale = tuple(float(value) for value in geo_scale) + width = scale[0] if len(scale) > 0 else 0.0 + length = scale[1] if len(scale) > 1 else 0.0 + if width > 0.0 and length > 0.0: + return scale + tail = scale[2:] if len(scale) > 2 else () + return (float(plane_size), float(plane_size), *tail) + + def resolve_visible_env_indices( env_ids: list[int] | None, max_visible_envs: int | None, diff --git a/source/isaaclab_visualizers/isaaclab_visualizers/rerun/rerun_visualizer.py b/source/isaaclab_visualizers/isaaclab_visualizers/rerun/rerun_visualizer.py index 8b9eea880813..b13a89575a2d 100644 --- a/source/isaaclab_visualizers/isaaclab_visualizers/rerun/rerun_visualizer.py +++ b/source/isaaclab_visualizers/isaaclab_visualizers/rerun/rerun_visualizer.py @@ -18,12 +18,17 @@ import rerun as rr import rerun.blueprint as rrb +import newton from newton.viewer import ViewerRerun from isaaclab.visualizers.base_visualizer import BaseVisualizer from isaaclab_visualizers.newton.newton_visualization_markers import render_newton_visualization_markers -from isaaclab_visualizers.newton_adapter import apply_viewer_visible_worlds, resolve_visible_env_indices +from isaaclab_visualizers.newton_adapter import ( + apply_viewer_visible_worlds, + expand_infinite_plane_scale, + resolve_visible_env_indices, +) from .rerun_visualizer_cfg import RerunVisualizerCfg @@ -134,6 +139,21 @@ def _render_ui(self): if imgui.button("Pause Rendering" if not self._paused_rendering else "Resume Rendering"): self._paused_rendering = not self._paused_rendering + def log_geo( + self, + name: str, + geo_type: int, + geo_scale: tuple[float, ...], + geo_thickness: float, + geo_is_solid: bool, + geo_src=None, + hidden: bool = False, + ): + """Log geometry, preserving large render extents for infinite ground planes.""" + if geo_type == newton.GeoType.PLANE: + geo_scale = expand_infinite_plane_scale(geo_scale) + return super().log_geo(name, geo_type, geo_scale, geo_thickness, geo_is_solid, geo_src, hidden) + class RerunVisualizer(BaseVisualizer): """Rerun visualizer for Isaac Lab.""" diff --git a/source/isaaclab_visualizers/isaaclab_visualizers/viser/viser_visualizer.py b/source/isaaclab_visualizers/isaaclab_visualizers/viser/viser_visualizer.py index 5bd9a831adce..243812e856a0 100644 --- a/source/isaaclab_visualizers/isaaclab_visualizers/viser/viser_visualizer.py +++ b/source/isaaclab_visualizers/isaaclab_visualizers/viser/viser_visualizer.py @@ -16,12 +16,17 @@ from pathlib import Path from typing import TYPE_CHECKING, Any +import newton from newton.viewer import ViewerViser from isaaclab.visualizers.base_visualizer import BaseVisualizer from isaaclab_visualizers.newton.newton_visualization_markers import render_newton_visualization_markers -from isaaclab_visualizers.newton_adapter import apply_viewer_visible_worlds, resolve_visible_env_indices +from isaaclab_visualizers.newton_adapter import ( + apply_viewer_visible_worlds, + expand_infinite_plane_scale, + resolve_visible_env_indices, +) from .viser_visualizer_cfg import ViserVisualizerCfg @@ -114,6 +119,21 @@ def share_url(self) -> str | None: """Return the public share URL created by Viser, if any.""" return self._share_url + def log_geo( + self, + name: str, + geo_type: int, + geo_scale: tuple[float, ...], + geo_thickness: float, + geo_is_solid: bool, + geo_src=None, + hidden: bool = False, + ): + """Log geometry, preserving large render extents for infinite ground planes.""" + if geo_type == newton.GeoType.PLANE: + geo_scale = expand_infinite_plane_scale(geo_scale) + return super().log_geo(name, geo_type, geo_scale, geo_thickness, geo_is_solid, geo_src, hidden) + class ViserVisualizer(BaseVisualizer): """Viser web-based visualizer backed by Newton's ViewerViser.""" diff --git a/source/isaaclab_visualizers/pyproject.toml b/source/isaaclab_visualizers/pyproject.toml index c94ee57a3a21..9c439e7c327a 100644 --- a/source/isaaclab_visualizers/pyproject.toml +++ b/source/isaaclab_visualizers/pyproject.toml @@ -32,9 +32,11 @@ newton = [ "newton[sim] @ git+https://github.com/newton-physics/newton.git@v1.2.0", "PyOpenGL-accelerate", "imgui-bundle>=1.92.5", + "typing-extensions>=4.15.0", ] rerun = [ "newton[sim] @ git+https://github.com/newton-physics/newton.git@v1.2.0", + "pyarrow==22.0.0", "rerun-sdk>=0.29.0", ] viser = [ @@ -45,7 +47,9 @@ all = [ "imgui-bundle>=1.92.5", "newton[sim] @ git+https://github.com/newton-physics/newton.git@v1.2.0", "PyOpenGL-accelerate", + "pyarrow==22.0.0", "rerun-sdk>=0.29.0", + "typing-extensions>=4.15.0", "viser>=1.0.16", "warp-lang", ] diff --git a/source/isaaclab_visualizers/setup.py b/source/isaaclab_visualizers/setup.py deleted file mode 100644 index b02846e4b459..000000000000 --- a/source/isaaclab_visualizers/setup.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). -# All rights reserved. -# -# SPDX-License-Identifier: BSD-3-Clause - -"""Installation script for the 'isaaclab_visualizers' python package.""" - -from setuptools import setup - -# Base requirements shared across visualizer backends. -INSTALL_REQUIRES = [ - "isaaclab", - "numpy", -] - -# Every Newton declaration in the repo must use the SAME extra spec (`newton[sim]`). -# Pip resolves a git-URL requirement once per URL: if any package declares bare -# `newton @ git+...` while another declares `newton[sim] @ git+...`, the first -# resolution wins and silently drops the `[sim]` extra. That breaks `isaaclab_newton` -# at import time because `mujoco` / `mujoco-warp` go missing. So even the rerun/viser -# extras — which don't use the MuJoCo solver directly — must pin `newton[sim]` to -# stay consistent with `isaaclab_newton`. -EXTRAS_REQUIRE = { - "kit": [], - "newton": [ - "warp-lang", - "newton[sim] @ git+https://github.com/newton-physics/newton.git@v1.2.0", - "PyOpenGL-accelerate", - "imgui-bundle>=1.92.5", - ], - "rerun": [ - "newton[sim] @ git+https://github.com/newton-physics/newton.git@v1.2.0", - "rerun-sdk>=0.29.0", - "pyarrow==22.0.0", - ], - "viser": [ - "newton[sim] @ git+https://github.com/newton-physics/newton.git@v1.2.0", - "viser>=1.0.16", - ], -} - -EXTRAS_REQUIRE["all"] = sorted({dep for group in EXTRAS_REQUIRE.values() for dep in group}) - -setup( - name="isaaclab_visualizers", - author="Isaac Lab Project Developers", - maintainer="Isaac Lab Project Developers", - url="https://github.com/isaac-sim/IsaacLab", - version="0.1.0", - description="Visualizer backends for Isaac Lab (Kit, Newton, Rerun, Viser).", - keywords=["robotics", "simulation", "visualization"], - license="BSD-3-Clause", - include_package_data=True, - package_data={"": ["*.pyi"]}, - python_requires=">=3.12", - install_requires=INSTALL_REQUIRES, - extras_require=EXTRAS_REQUIRE, - packages=["isaaclab_visualizers"], - classifiers=[ - "Natural Language :: English", - "Programming Language :: Python :: 3.12", - "Isaac Sim :: 6.0.0", - ], - zip_safe=False, -) diff --git a/source/isaaclab_visualizers/test/test_newton_adapter.py b/source/isaaclab_visualizers/test/test_newton_adapter.py index 3c020a8d10ee..75944cfdfdc6 100644 --- a/source/isaaclab_visualizers/test/test_newton_adapter.py +++ b/source/isaaclab_visualizers/test/test_newton_adapter.py @@ -3,11 +3,33 @@ # # SPDX-License-Identifier: BSD-3-Clause -"""Unit tests for viewer env resolution helpers.""" +"""Unit tests for Newton viewer adapter helpers.""" from __future__ import annotations -from isaaclab_visualizers.newton_adapter import apply_viewer_visible_worlds, resolve_visible_env_indices +from isaaclab_visualizers.newton_adapter import ( + VISUALIZER_INFINITE_PLANE_SIZE, + apply_viewer_visible_worlds, + expand_infinite_plane_scale, + resolve_visible_env_indices, +) + + +def test_expand_infinite_plane_scale_expands_non_positive_extents(): + assert expand_infinite_plane_scale((0.0, 0.0, 1.0, 0.0)) == ( + VISUALIZER_INFINITE_PLANE_SIZE, + VISUALIZER_INFINITE_PLANE_SIZE, + 1.0, + 0.0, + ) + assert expand_infinite_plane_scale((-1.0, 25.0)) == ( + VISUALIZER_INFINITE_PLANE_SIZE, + VISUALIZER_INFINITE_PLANE_SIZE, + ) + + +def test_expand_infinite_plane_scale_preserves_finite_extents(): + assert expand_infinite_plane_scale((100.0, 50.0, 1.0)) == (100.0, 50.0, 1.0) def test_resolve_visible_env_indices_truncates_explicit_list(): diff --git a/source/isaaclab_visualizers/test/test_visualizer_integration_newton.py b/source/isaaclab_visualizers/test/test_visualizer_integration_newton.py index a5567735e5cf..30bfc316b646 100644 --- a/source/isaaclab_visualizers/test/test_visualizer_integration_newton.py +++ b/source/isaaclab_visualizers/test/test_visualizer_integration_newton.py @@ -28,9 +28,12 @@ pytestmark = [pytest.mark.isaacsim_ci, pytest.mark.flaky(max_runs=2, min_passes=1)] -def test_cartpole_env_visualizers_motion_with_play_pause_newton(caplog: pytest.LogCaptureFixture) -> None: +def test_cartpole_env_visualizers_motion_with_play_pause_newton( + caplog: pytest.LogCaptureFixture, capsys: pytest.CaptureFixture[str] +) -> None: """Cartpole env + all non-tiled visualizers on Newton MJWarp.""" run_cartpole_env_visualizers_motion_with_play_pause("newton", caplog) + _viz_utils.assert_no_newton_hud_dependency_warning(capsys, caplog) if __name__ == "__main__": diff --git a/source/isaaclab_visualizers/test/test_visualizer_integration_physx.py b/source/isaaclab_visualizers/test/test_visualizer_integration_physx.py index dac89b283b00..3cb997a64889 100644 --- a/source/isaaclab_visualizers/test/test_visualizer_integration_physx.py +++ b/source/isaaclab_visualizers/test/test_visualizer_integration_physx.py @@ -28,9 +28,12 @@ pytestmark = [pytest.mark.isaacsim_ci, pytest.mark.flaky(max_runs=2, min_passes=1)] -def test_cartpole_env_visualizers_motion_with_play_pause_physx(caplog: pytest.LogCaptureFixture) -> None: +def test_cartpole_env_visualizers_motion_with_play_pause_physx( + caplog: pytest.LogCaptureFixture, capsys: pytest.CaptureFixture[str] +) -> None: """Cartpole env + all non-tiled visualizers on PhysX.""" run_cartpole_env_visualizers_motion_with_play_pause("physx", caplog) + _viz_utils.assert_no_newton_hud_dependency_warning(capsys, caplog) if __name__ == "__main__": diff --git a/source/isaaclab_visualizers/test/test_visualizer_tiled_integration_newton.py b/source/isaaclab_visualizers/test/test_visualizer_tiled_integration_newton.py index e77ee9be05b5..fc19b969b88b 100644 --- a/source/isaaclab_visualizers/test/test_visualizer_tiled_integration_newton.py +++ b/source/isaaclab_visualizers/test/test_visualizer_tiled_integration_newton.py @@ -28,9 +28,12 @@ pytestmark = [pytest.mark.isaacsim_ci, pytest.mark.flaky(max_runs=2, min_passes=1)] -def test_visualizer_tiled_integration_newton(caplog: pytest.LogCaptureFixture) -> None: +def test_visualizer_tiled_integration_newton( + caplog: pytest.LogCaptureFixture, capsys: pytest.CaptureFixture[str] +) -> None: """Cartpole env + tiled Kit/Newton visualizers on Newton MJWarp.""" run_cartpole_env_visualizers_tiled_camera_motion("newton", caplog) + _viz_utils.assert_no_newton_hud_dependency_warning(capsys, caplog) if __name__ == "__main__": diff --git a/source/isaaclab_visualizers/test/test_visualizer_tiled_integration_physx.py b/source/isaaclab_visualizers/test/test_visualizer_tiled_integration_physx.py index 5693c1131ee0..b2407aaae482 100644 --- a/source/isaaclab_visualizers/test/test_visualizer_tiled_integration_physx.py +++ b/source/isaaclab_visualizers/test/test_visualizer_tiled_integration_physx.py @@ -28,9 +28,12 @@ pytestmark = [pytest.mark.isaacsim_ci, pytest.mark.flaky(max_runs=2, min_passes=1)] -def test_visualizer_tiled_integration_physx(caplog: pytest.LogCaptureFixture) -> None: +def test_visualizer_tiled_integration_physx( + caplog: pytest.LogCaptureFixture, capsys: pytest.CaptureFixture[str] +) -> None: """Cartpole env + tiled Kit/Newton visualizers on PhysX.""" run_cartpole_env_visualizers_tiled_camera_motion("physx", caplog) + _viz_utils.assert_no_newton_hud_dependency_warning(capsys, caplog) if __name__ == "__main__": diff --git a/source/isaaclab_visualizers/test/visualizer_integration_utils.py b/source/isaaclab_visualizers/test/visualizer_integration_utils.py index 8cc79b5ba08b..d05747d73f9b 100644 --- a/source/isaaclab_visualizers/test/visualizer_integration_utils.py +++ b/source/isaaclab_visualizers/test/visualizer_integration_utils.py @@ -61,6 +61,9 @@ # When True, tests also fail on WARNING-level records from visualizer-related loggers. ASSERT_VISUALIZER_WARNINGS = False +_NEWTON_IMGUI_BUNDLE_PRINT_WARNING = "Warning: imgui_bundle not found" +_NEWTON_HUD_IMPORT_LOG_WARNING = "Newton Visualizer HUD disabled: failed to import imgui_bundle. This can be caused by conflicting libraries." + _MAX_FRAME_CHECK_STEPS = 5 """Steps for Rerun / Viser smoke tests.""" @@ -221,6 +224,29 @@ def _assert_no_visualizer_log_issues(caplog: pytest.LogCaptureFixture, *, fail_o ) +def assert_no_newton_hud_dependency_warning( + capsys: pytest.CaptureFixture[str], caplog: pytest.LogCaptureFixture +) -> None: + """Fail when Newton disables the imgui HUD due to a broken dependency chain.""" + captured = capsys.readouterr() + captured_output = captured.out + captured.err + printed_warning = _NEWTON_IMGUI_BUNDLE_PRINT_WARNING in captured_output + logged_warnings = [ + record + for record in caplog.records + if _NEWTON_HUD_IMPORT_LOG_WARNING in record.getMessage() + or _NEWTON_IMGUI_BUNDLE_PRINT_WARNING in record.getMessage() + ] + assert not printed_warning and not logged_warnings, ( + "Newton visualizer HUD dependency failed. The Newton viewer printed/logged that imgui_bundle could not " + "be imported, which disables the HUD controls. Ensure isaaclab-visualizers[newton] installs " + "imgui-bundle and compatible transitive dependencies such as typing-extensions>=4.15.0. " + f"Captured output: {captured_output!r}. " + "Captured logs: " + + "; ".join(f"{record.name}: {record.getMessage()}" for record in logged_warnings) + ) + + def _configure_sim_for_visualizer_test(env: CartpoleCameraEnv) -> None: """Settings used by the previous smoke tests; keep RTX sensors enabled for camera paths.""" AppLauncher.apply_rtx_determinism_settings() diff --git a/tools/wheel_builder/res/python_packages.toml b/tools/wheel_builder/res/python_packages.toml index 830707e7ce82..c079bb9167d0 100644 --- a/tools/wheel_builder/res/python_packages.toml +++ b/tools/wheel_builder/res/python_packages.toml @@ -106,7 +106,7 @@ pyproject.optional-dependencies.all = [ { "rsl-rl" = ["rsl-rl-lib==5.0.1", "onnxscript>=0.5"] }, { "rsl_rl" = ["rsl-rl-lib==5.0.1", "onnxscript>=0.5"] }, # ================================================================================ - # https://github.com/isaac-sim/IsaacLab/blob/main/source/isaaclab_visualizers/setup.py + # https://github.com/isaac-sim/IsaacLab/blob/main/source/isaaclab_visualizers/pyproject.toml # ================================================================================ # Viser visualizer (opt-in: viser pulls websockets>=13.1 which collides with # isaacsim-kernel==6.0.0.0's websockets==12.0; do not include in [all]). From 21d51fbdf4289eb13d75d76ba93d227ef1734056 Mon Sep 17 00:00:00 2001 From: Matthew Trepte Date: Wed, 3 Jun 2026 07:01:50 +0000 Subject: [PATCH 02/17] more fixes --- docs/source/how-to/record_video.rst | 4 ++ .../overview/core-concepts/visualization.rst | 8 ++- .../clarify-single-video-stream.rst | 1 + ...fix-scene-data-articulation-transforms.rst | 1 + .../isaaclab/envs/utils/video_recorder.py | 8 +++ .../isaaclab/scene_data/scene_data_backend.py | 4 ++ .../isaaclab/sim/simulation_context.py | 3 +- .../isaaclab/test/envs/test_video_recorder.py | 11 ++++ .../reduce-ant-joint-wrench-warnings.rst | 1 + .../isaaclab_physx/physics/physx_manager.py | 66 +++++++++++++++++-- .../fix-newton-hud-imgui-dependency.rst | 2 + .../isaaclab_visualizers/newton_adapter.py | 1 - .../rerun/rerun_visualizer.py | 2 +- .../viser/viser_visualizer.py | 51 ++++++++++++++ .../test/visualizer_integration_utils.py | 7 +- 15 files changed, 155 insertions(+), 15 deletions(-) create mode 100644 source/isaaclab/changelog.d/clarify-single-video-stream.rst create mode 100644 source/isaaclab/changelog.d/fix-scene-data-articulation-transforms.rst create mode 100644 source/isaaclab_physx/changelog.d/reduce-ant-joint-wrench-warnings.rst diff --git a/docs/source/how-to/record_video.rst b/docs/source/how-to/record_video.rst index 3ef6bedf6c71..a556c253e057 100644 --- a/docs/source/how-to/record_video.rst +++ b/docs/source/how-to/record_video.rst @@ -80,6 +80,10 @@ precedence and only one ``--video`` stream is recorded. Rerun records ``.rrd`` r the Rerun visualizer rather than producing ``--video`` clips, and Viser does not currently provide a ``--video`` recording backend. +To record both Kit and Newton perspectives for the same task, run two jobs: one with ``--viz kit`` and +one with ``--viz newton``. The Gymnasium ``RecordVideo`` wrapper records one ``env.render()`` stream +per process; it does not emit one video per visualizer listed in ``--viz``. + Set ``VideoRecorderCfg.backend_source = "renderer"`` to ignore active visualizers and choose from the physics/renderer stack instead. In that mode, PhysX physics (``physics=physx``) or Isaac RTX (``renderer=isaacsim_rtx_renderer``) selects the Kit path. Newton physics (``physics=newton_mjwarp``) or diff --git a/docs/source/overview/core-concepts/visualization.rst b/docs/source/overview/core-concepts/visualization.rst index 71065522699c..51c08041f254 100644 --- a/docs/source/overview/core-concepts/visualization.rst +++ b/docs/source/overview/core-concepts/visualization.rst @@ -219,12 +219,16 @@ Note, Kit tiled camera views require launching with ``--enable_cameras``. - ``tiled_cam_view=False``, ``eye=(4, -4, 3)``, ``lookat=(0, 0, 0)`` - Interactive visualizer camera starts at ``eye`` and looks at the fixed ``lookat`` coordinate. * - Generated tiled camera - - ``tiled_cam_view=True``, ``tiled_cam_prim_path=None``, ``tiled_cam_target_prim_path="/World/envs/*/Robot/base"`` + - ``tiled_cam_view=True``, ``tiled_cam_prim_path=None``, ``tiled_cam_target_prim_path="/World/envs/*/Robot"`` - The visualizer creates per-env cameras. Each camera looks at the matched target prim, with ``tiled_cam_eye`` as an offset from that target. * - Existing tiled camera sensors - ``tiled_cam_view=True``, ``tiled_cam_prim_path="/World/envs/*/Camera"`` - The visualizer displays existing Isaac Lab ``Camera`` sensor output. Generated-camera fields such as ``tiled_cam_eye`` and ``tiled_cam_target_prim_path`` are ignored. + This mode requires an environment that registers Isaac Lab ``Camera`` sensors in ``scene.sensors``. + For Cartpole, use a camera task such as ``Isaac-Cartpole-Camera``. The plain ``Isaac-Cartpole`` task + has no ``/World/envs/*/Camera`` sensor, so leave ``tiled_cam_prim_path=None`` to use generated visualizer cameras. + **How to Access the Tiled Camera View in the UI** - **Kit Visualizer:** @@ -416,7 +420,7 @@ Newton Visualizer tiled_cam_prim_path=None, # Existing Camera sensor prim path, e.g. "/World/envs/*/Camera" tiled_cam_eye=(4.0, -4.0, 3.0), # Eye offset for generated tiled cameras tiled_cam_target_prim_path=( # Prim that generated cameras follow/look at - "/World/envs/*/Robot/base" + "/World/envs/*/Robot" ), # Performance tuning diff --git a/source/isaaclab/changelog.d/clarify-single-video-stream.rst b/source/isaaclab/changelog.d/clarify-single-video-stream.rst new file mode 100644 index 000000000000..c46aedb23384 --- /dev/null +++ b/source/isaaclab/changelog.d/clarify-single-video-stream.rst @@ -0,0 +1 @@ +Clarified ``--video`` behavior when multiple video-capable visualizers are active: Gymnasium video recording captures one ``env.render()`` stream, with Kit taking priority over Newton. diff --git a/source/isaaclab/changelog.d/fix-scene-data-articulation-transforms.rst b/source/isaaclab/changelog.d/fix-scene-data-articulation-transforms.rst new file mode 100644 index 000000000000..acade678be11 --- /dev/null +++ b/source/isaaclab/changelog.d/fix-scene-data-articulation-transforms.rst @@ -0,0 +1 @@ +Added a scene-data backend hook for active ``InteractiveScene`` access so backends can source scene-owned entity transforms without relying on global rigid-body views. diff --git a/source/isaaclab/isaaclab/envs/utils/video_recorder.py b/source/isaaclab/isaaclab/envs/utils/video_recorder.py index 3ff6a7e1a0a4..0925b4a0ab1c 100644 --- a/source/isaaclab/isaaclab/envs/utils/video_recorder.py +++ b/source/isaaclab/isaaclab/envs/utils/video_recorder.py @@ -71,6 +71,14 @@ def _resolve_video_backend( # Prefer the visualizer backend when --visualizer is active alongside --video. visualizer_types: list[str] = scene.sim.resolve_visualizer_types() if backend_source == "visualizer" else [] if visualizer_types: + supported_visualizers = [viz for viz in ("kit", "newton") if viz in visualizer_types] + if len(supported_visualizers) > 1: + logger.warning( + "[VideoRecorder] Multiple video-capable visualizers are active (%s), but --video records one " + "env.render() stream. Using Kit because it has priority. Run with only --viz newton to record " + "a Newton GL video.", + supported_visualizers, + ) # kit takes priority when multiple visualizers are active for preferred in ("kit", "newton"): if preferred in visualizer_types: diff --git a/source/isaaclab/isaaclab/scene_data/scene_data_backend.py b/source/isaaclab/isaaclab/scene_data/scene_data_backend.py index 183b08246848..377b61e6d726 100644 --- a/source/isaaclab/isaaclab/scene_data/scene_data_backend.py +++ b/source/isaaclab/isaaclab/scene_data/scene_data_backend.py @@ -71,6 +71,10 @@ class Matrix44: class SceneDataBackend: + def set_interactive_scene(self, scene) -> None: + """Attach the active interactive scene, if the backend needs scene-owned entities.""" + pass + @property def transforms( self, diff --git a/source/isaaclab/isaaclab/sim/simulation_context.py b/source/isaaclab/isaaclab/sim/simulation_context.py index c068469ec599..777be01a1932 100644 --- a/source/isaaclab/isaaclab/sim/simulation_context.py +++ b/source/isaaclab/isaaclab/sim/simulation_context.py @@ -662,10 +662,11 @@ def get_scene_data_provider(self) -> SceneDataProvider: return self._scene_data_provider def register_interactive_scene(self, scene) -> None: - """Register the active scene so scene data providers can expose scene-owned sensors.""" + """Register the active scene so scene-data consumers can access scene-owned entities.""" self._interactive_scene = scene if self._scene_data_provider is not None: self._scene_data_provider.set_interactive_scene(scene) + self._scene_data_provider.backend.set_interactive_scene(scene) def get_scene_data_requirements(self) -> SceneDataRequirement: """Return scene-data requirements resolved from visualizers/renderers.""" diff --git a/source/isaaclab/test/envs/test_video_recorder.py b/source/isaaclab/test/envs/test_video_recorder.py index 91af56632d6b..e22cbe656147 100644 --- a/source/isaaclab/test/envs/test_video_recorder.py +++ b/source/isaaclab/test/envs/test_video_recorder.py @@ -172,6 +172,17 @@ def test_resolve_backend_kit_wins_over_newton_visualizer(): assert matched == "kit" +def test_resolve_backend_warns_when_multiple_video_capable_visualizers(caplog: pytest.LogCaptureFixture): + """The Gymnasium video wrapper records one stream even if both Kit and Newton are active.""" + scene = _make_scene(["newton", "kit"]) + with caplog.at_level("WARNING"): + backend, matched = _resolve_video_backend(scene) + + assert backend == "kit" + assert matched == "kit" + assert any("Multiple video-capable visualizers are active" in record.getMessage() for record in caplog.records) + + def test_resolve_backend_unsupported_visualizer_falls_through(): """viser/rerun visualizers fall through to physics stack detection.""" scene = _make_scene(["viser"], physics_name="PhysxPhysicsManager") diff --git a/source/isaaclab_physx/changelog.d/reduce-ant-joint-wrench-warnings.rst b/source/isaaclab_physx/changelog.d/reduce-ant-joint-wrench-warnings.rst new file mode 100644 index 000000000000..37e9c511d23b --- /dev/null +++ b/source/isaaclab_physx/changelog.d/reduce-ant-joint-wrench-warnings.rst @@ -0,0 +1 @@ +Fixed excessive PhysX tensor warnings from Ant tasks with ``JointWrenchSensor`` by sourcing scene-data transforms for articulation links from Isaac Lab articulation views instead of a global PhysX rigid-body view. diff --git a/source/isaaclab_physx/isaaclab_physx/physics/physx_manager.py b/source/isaaclab_physx/isaaclab_physx/physics/physx_manager.py index df30c9e268a7..f8870db1acd7 100644 --- a/source/isaaclab_physx/isaaclab_physx/physics/physx_manager.py +++ b/source/isaaclab_physx/isaaclab_physx/physics/physx_manager.py @@ -160,6 +160,8 @@ def __init__(self): self._simulation_view: omni.physics.tensors.SimulationView | None = None self._rigid_body_view: omni.physics.tensors.RigidBodyView | None = None self._scene_data = SceneDataFormat.Transform() + self._interactive_scene: Any | None = None + self._articulation_body_paths: list[str] = [] @property def simulation_view(self) -> omni.physics.tensors.SimulationView | None: @@ -170,6 +172,42 @@ def simulation_view(self, simulation_view: omni.physics.tensors.SimulationView | self._simulation_view = simulation_view self._rigid_body_view = None + def set_interactive_scene(self, scene: Any) -> None: + """Attach the active scene so articulation link transforms can be read from asset views.""" + self._interactive_scene = scene + self._rigid_body_view = None + self._articulation_body_paths = [] + + def _get_articulation_body_paths(self) -> set[str]: + """Return body prim paths owned by scene articulations.""" + if self._interactive_scene is None: + self._articulation_body_paths = [] + return set() + + articulation_paths: list[str] = [] + for articulation in getattr(self._interactive_scene, "articulations", {}).values(): + root_paths = list(getattr(articulation.root_view, "prim_paths", [])) + body_names = list(getattr(articulation, "body_names", [])) + for root_path in root_paths: + asset_root_path = root_path.rsplit("/", 1)[0] + articulation_paths.extend(f"{asset_root_path}/{body_name}" for body_name in body_names) + + self._articulation_body_paths = articulation_paths + return set(articulation_paths) + + def _get_articulation_transforms(self) -> wp.array | None: + """Return flattened articulation link transforms from scene-owned articulation assets.""" + if self._interactive_scene is None: + return None + + transform_tensors = [] + for articulation in getattr(self._interactive_scene, "articulations", {}).values(): + transform_tensors.append(wp.to_torch(articulation.data.body_link_pose_w.warp).reshape(-1, 7)) + + if not transform_tensors: + return None + return wp.from_torch(torch.cat(transform_tensors, dim=0).contiguous(), dtype=wp.transformf) + def get_rigid_body_view(self) -> omni.physics.tensors.RigidBodyView | None: """Lazily create a rigid body view covering all rigid bodies in the scene. @@ -188,9 +226,11 @@ def get_rigid_body_view(self) -> omni.physics.tensors.RigidBodyView | None: return None patterns: set[str] = set() + articulation_body_paths = self._get_articulation_body_paths() for prim in stage.Traverse(): - if prim.HasAPI(UsdPhysics.RigidBodyAPI): - patterns.add(re.sub(r"/World/envs/env_\d+", "/World/envs/env_*", prim.GetPath().pathString)) + prim_path = prim.GetPath().pathString + if prim.HasAPI(UsdPhysics.RigidBodyAPI) and prim_path not in articulation_body_paths: + patterns.add(re.sub(r"/World/envs/env_\d+", "/World/envs/env_*", prim_path)) if not patterns: return None @@ -201,23 +241,35 @@ def get_rigid_body_view(self) -> omni.physics.tensors.RigidBodyView | None: @property def transforms(self) -> SceneDataFormat.Transform: """Return the current PhysX rigid body transforms as :class:`SceneDataFormat.Transform`.""" + transform_tensors = [] + articulation_transforms = self._get_articulation_transforms() + if articulation_transforms is not None: + transform_tensors.append(wp.to_torch(articulation_transforms).reshape(-1, 7)) if view := self.get_rigid_body_view(): - self._scene_data.transforms = view.get_transforms().view(wp.transformf) + transform_tensors.append(wp.to_torch(view.get_transforms().view(wp.transformf)).reshape(-1, 7)) + if transform_tensors: + self._scene_data.transforms = wp.from_torch( + torch.cat(transform_tensors, dim=0).contiguous(), dtype=wp.transformf + ) return self._scene_data @property def transform_count(self) -> int: """Return the number of rigid body transforms in the PhysX sim.""" + self._get_articulation_body_paths() + count = len(self._articulation_body_paths) if view := self.get_rigid_body_view(): - return view.count - return 0 + count += view.count + return count @property def transform_paths(self) -> list[str]: """Return the prim paths for each rigid body transform.""" + self._get_articulation_body_paths() + transform_paths = list(self._articulation_body_paths) if view := self.get_rigid_body_view(): - return list(view.prim_paths) - return [] + transform_paths.extend(list(view.prim_paths)) + return transform_paths class PhysxManager(PhysicsManager): diff --git a/source/isaaclab_visualizers/changelog.d/fix-newton-hud-imgui-dependency.rst b/source/isaaclab_visualizers/changelog.d/fix-newton-hud-imgui-dependency.rst index 4cab0ab89428..d3c5d8c4042c 100644 --- a/source/isaaclab_visualizers/changelog.d/fix-newton-hud-imgui-dependency.rst +++ b/source/isaaclab_visualizers/changelog.d/fix-newton-hud-imgui-dependency.rst @@ -1,3 +1,5 @@ Fixed Newton visualizer HUD dependency checks by requiring ``typing-extensions>=4.15.0`` for the Newton visualizer extra and failing integration tests when Newton reports that ``imgui_bundle`` could not be imported. Removed the legacy ``setup.py`` for ``isaaclab_visualizers`` now that ``pyproject.toml`` carries the package metadata. Fixed Rerun and Viser visualizers rendering Newton infinite ground planes too small by expanding non-positive plane extents to the same large finite size used by Newton GL. + +Fixed Viser visualizer ground-grid flickering by reusing unchanged plane grid line segments instead of removing and re-adding them every frame. diff --git a/source/isaaclab_visualizers/isaaclab_visualizers/newton_adapter.py b/source/isaaclab_visualizers/isaaclab_visualizers/newton_adapter.py index 0dffab16fbec..dc6b2265eb74 100644 --- a/source/isaaclab_visualizers/isaaclab_visualizers/newton_adapter.py +++ b/source/isaaclab_visualizers/isaaclab_visualizers/newton_adapter.py @@ -7,7 +7,6 @@ from __future__ import annotations - VISUALIZER_INFINITE_PLANE_SIZE = 1000.0 """Finite render size used for Newton planes encoded as infinite.""" diff --git a/source/isaaclab_visualizers/isaaclab_visualizers/rerun/rerun_visualizer.py b/source/isaaclab_visualizers/isaaclab_visualizers/rerun/rerun_visualizer.py index b13a89575a2d..55c9f7240103 100644 --- a/source/isaaclab_visualizers/isaaclab_visualizers/rerun/rerun_visualizer.py +++ b/source/isaaclab_visualizers/isaaclab_visualizers/rerun/rerun_visualizer.py @@ -16,9 +16,9 @@ from typing import TYPE_CHECKING from urllib.parse import quote +import newton import rerun as rr import rerun.blueprint as rrb -import newton from newton.viewer import ViewerRerun from isaaclab.visualizers.base_visualizer import BaseVisualizer diff --git a/source/isaaclab_visualizers/isaaclab_visualizers/viser/viser_visualizer.py b/source/isaaclab_visualizers/isaaclab_visualizers/viser/viser_visualizer.py index 243812e856a0..c12dcee3b17e 100644 --- a/source/isaaclab_visualizers/isaaclab_visualizers/viser/viser_visualizer.py +++ b/source/isaaclab_visualizers/isaaclab_visualizers/viser/viser_visualizer.py @@ -17,6 +17,7 @@ from typing import TYPE_CHECKING, Any import newton +import numpy as np from newton.viewer import ViewerViser from isaaclab.visualizers.base_visualizer import BaseVisualizer @@ -113,12 +114,62 @@ def _viser_server_with_bind_address(*args, **kwargs): record_to_viser=record_to_viser, ) self._metadata = metadata or {} + self._isaaclab_plane_grid_cache: dict[str, tuple] = {} @property def share_url(self) -> str | None: """Return the public share URL created by Viser, if any.""" return self._share_url + def clear_model(self) -> None: + """Clear cached static plane-grid signatures with the viewer model.""" + cache = getattr(self, "_isaaclab_plane_grid_cache", None) + if cache is not None: + cache.clear() + return super().clear_model() + + @staticmethod + def _array_signature(array) -> tuple[tuple[int, ...], bytes] | None: + """Return a stable signature for small transform/scale arrays.""" + if array is None: + return None + array_np = np.ascontiguousarray(np.asarray(array, dtype=np.float32)) + return tuple(int(dim) for dim in array_np.shape), array_np.tobytes() + + def _log_plane_instances( + self, + name: str, + plane_info: dict[str, float | bool], + xforms, + scales, + hidden: bool = False, + ) -> None: + """Avoid removing/re-adding unchanged Viser plane grids every frame.""" + cache = getattr(self, "_isaaclab_plane_grid_cache", None) + if hidden or xforms is None: + if cache is not None: + cache.pop(name, None) + return super()._log_plane_instances(name, plane_info, xforms, scales, hidden=hidden) + + xforms_np = self._to_numpy(xforms) + if xforms_np is None or len(xforms_np) == 0: + if cache is not None: + cache.pop(name, None) + return super()._log_plane_instances(name, plane_info, xforms, scales, hidden=hidden) + + scales_np = self._to_numpy(scales) if scales is not None else None + signature = ( + float(plane_info["width"]), + float(plane_info["length"]), + self._array_signature(xforms_np), + self._array_signature(scales_np), + ) + if cache is not None and cache.get(name) == signature and name in self._plane_handles: + return None + if cache is not None: + cache[name] = signature + return super()._log_plane_instances(name, plane_info, xforms, scales, hidden=hidden) + def log_geo( self, name: str, diff --git a/source/isaaclab_visualizers/test/visualizer_integration_utils.py b/source/isaaclab_visualizers/test/visualizer_integration_utils.py index d05747d73f9b..ea300a68e89a 100644 --- a/source/isaaclab_visualizers/test/visualizer_integration_utils.py +++ b/source/isaaclab_visualizers/test/visualizer_integration_utils.py @@ -62,7 +62,9 @@ ASSERT_VISUALIZER_WARNINGS = False _NEWTON_IMGUI_BUNDLE_PRINT_WARNING = "Warning: imgui_bundle not found" -_NEWTON_HUD_IMPORT_LOG_WARNING = "Newton Visualizer HUD disabled: failed to import imgui_bundle. This can be caused by conflicting libraries." +_NEWTON_HUD_IMPORT_LOG_WARNING = ( + "Newton Visualizer HUD disabled: failed to import imgui_bundle. This can be caused by conflicting libraries." +) _MAX_FRAME_CHECK_STEPS = 5 """Steps for Rerun / Viser smoke tests.""" @@ -242,8 +244,7 @@ def assert_no_newton_hud_dependency_warning( "be imported, which disables the HUD controls. Ensure isaaclab-visualizers[newton] installs " "imgui-bundle and compatible transitive dependencies such as typing-extensions>=4.15.0. " f"Captured output: {captured_output!r}. " - "Captured logs: " - + "; ".join(f"{record.name}: {record.getMessage()}" for record in logged_warnings) + "Captured logs: " + "; ".join(f"{record.name}: {record.getMessage()}" for record in logged_warnings) ) From 33a1774a43a571dd05d8a80754aa3a8141dbe55f Mon Sep 17 00:00:00 2001 From: Matthew Trepte Date: Wed, 3 Jun 2026 07:03:03 +0000 Subject: [PATCH 03/17] revert visualizer cfg --- source/isaaclab/isaaclab/visualizers/visualizer_cfg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/isaaclab/isaaclab/visualizers/visualizer_cfg.py b/source/isaaclab/isaaclab/visualizers/visualizer_cfg.py index 03422e6d3ae4..78729f1779a8 100644 --- a/source/isaaclab/isaaclab/visualizers/visualizer_cfg.py +++ b/source/isaaclab/isaaclab/visualizers/visualizer_cfg.py @@ -36,7 +36,7 @@ class VisualizerCfg: """Camera focal length in millimeters for visualizer camera views.""" # Tiled camera settings - tiled_cam_view: bool = True + tiled_cam_view: bool = False """Enable a non-interactive tiled camera image view.""" tiled_cam_num: int = 16 From 1012ea1dbcd31e69ae6aed835c6fff2350815a3f Mon Sep 17 00:00:00 2001 From: Matthew Trepte Date: Wed, 3 Jun 2026 07:06:45 +0000 Subject: [PATCH 04/17] address reviewbot; --- .../isaaclab_visualizers/newton/__init__.py | 7 ++++- .../newton/newton_visualizer.py | 7 +++-- .../isaaclab_visualizers/newton_adapter.py | 26 ++++++++++++++++++- .../rerun/rerun_visualizer.py | 16 +++++++++--- .../viser/viser_visualizer.py | 16 +++++++++--- .../test/test_newton_adapter.py | 26 +++++++++++++++++++ .../test/visualizer_integration_utils.py | 8 ++---- 7 files changed, 88 insertions(+), 18 deletions(-) diff --git a/source/isaaclab_visualizers/isaaclab_visualizers/newton/__init__.py b/source/isaaclab_visualizers/isaaclab_visualizers/newton/__init__.py index b83c42420d86..ead5cdf45859 100644 --- a/source/isaaclab_visualizers/isaaclab_visualizers/newton/__init__.py +++ b/source/isaaclab_visualizers/isaaclab_visualizers/newton/__init__.py @@ -16,9 +16,10 @@ from .newton_visualizer_cfg import NewtonVisualizerCfg if TYPE_CHECKING: + from .newton_visualizer import NEWTON_HUD_IMPORT_LOG_WARNING from .newton_visualizer import NewtonVisualizer -__all__ = ["NewtonVisualizer", "NewtonVisualizerCfg"] +__all__ = ["NEWTON_HUD_IMPORT_LOG_WARNING", "NewtonVisualizer", "NewtonVisualizerCfg"] def __getattr__(name: str): @@ -26,4 +27,8 @@ def __getattr__(name: str): from .newton_visualizer import NewtonVisualizer return NewtonVisualizer + if name == "NEWTON_HUD_IMPORT_LOG_WARNING": + from .newton_visualizer import NEWTON_HUD_IMPORT_LOG_WARNING + + return NEWTON_HUD_IMPORT_LOG_WARNING raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py b/source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py index 57d0d604ea05..28a4740f787d 100644 --- a/source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py +++ b/source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py @@ -37,6 +37,9 @@ logger = logging.getLogger(__name__) +NEWTON_HUD_IMPORT_LOG_WARNING = "[NewtonVisualizer] Newton HUD disabled: failed to import imgui_bundle." +"""Stable log prefix emitted when Newton's imgui HUD dependencies cannot be imported.""" + if TYPE_CHECKING: from isaaclab.scene_data import SceneDataProvider @@ -49,9 +52,9 @@ def _log_newton_hud_dependency_issue() -> None: from imgui_bundle.python_backends import pyglet_backend as _pyglet_backend # noqa: F401 except ImportError as exc: logger.warning( - "[NewtonVisualizer] Newton HUD disabled: failed to import imgui_bundle. " - "Install isaaclab-visualizers[newton] or fix its transitive dependencies " + "%s Install isaaclab-visualizers[newton] or fix its transitive dependencies " "(for example typing-extensions>=4.15.0). ImportError: %s", + NEWTON_HUD_IMPORT_LOG_WARNING, exc, ) diff --git a/source/isaaclab_visualizers/isaaclab_visualizers/newton_adapter.py b/source/isaaclab_visualizers/isaaclab_visualizers/newton_adapter.py index dc6b2265eb74..8a1554a253fd 100644 --- a/source/isaaclab_visualizers/isaaclab_visualizers/newton_adapter.py +++ b/source/isaaclab_visualizers/isaaclab_visualizers/newton_adapter.py @@ -7,6 +7,9 @@ from __future__ import annotations +from collections.abc import Callable +from typing import Any + VISUALIZER_INFINITE_PLANE_SIZE = 1000.0 """Finite render size used for Newton planes encoded as infinite.""" @@ -27,7 +30,28 @@ def expand_infinite_plane_scale( if width > 0.0 and length > 0.0: return scale tail = scale[2:] if len(scale) > 2 else () - return (float(plane_size), float(plane_size), *tail) + return ( + width if width > 0.0 else float(plane_size), + length if length > 0.0 else float(plane_size), + *tail, + ) + + +def log_geo_with_expanded_plane_scale( + super_log_geo: Callable[..., Any], + plane_geo_type: int, + name: str, + geo_type: int, + geo_scale: tuple[float, ...], + geo_thickness: float, + geo_is_solid: bool, + geo_src=None, + hidden: bool = False, +): + """Log geometry after expanding Newton infinite-plane extents for web viewers.""" + if geo_type == plane_geo_type: + geo_scale = expand_infinite_plane_scale(geo_scale) + return super_log_geo(name, geo_type, geo_scale, geo_thickness, geo_is_solid, geo_src, hidden) def resolve_visible_env_indices( diff --git a/source/isaaclab_visualizers/isaaclab_visualizers/rerun/rerun_visualizer.py b/source/isaaclab_visualizers/isaaclab_visualizers/rerun/rerun_visualizer.py index 55c9f7240103..631ae43c7168 100644 --- a/source/isaaclab_visualizers/isaaclab_visualizers/rerun/rerun_visualizer.py +++ b/source/isaaclab_visualizers/isaaclab_visualizers/rerun/rerun_visualizer.py @@ -26,7 +26,7 @@ from isaaclab_visualizers.newton.newton_visualization_markers import render_newton_visualization_markers from isaaclab_visualizers.newton_adapter import ( apply_viewer_visible_worlds, - expand_infinite_plane_scale, + log_geo_with_expanded_plane_scale, resolve_visible_env_indices, ) @@ -150,9 +150,17 @@ def log_geo( hidden: bool = False, ): """Log geometry, preserving large render extents for infinite ground planes.""" - if geo_type == newton.GeoType.PLANE: - geo_scale = expand_infinite_plane_scale(geo_scale) - return super().log_geo(name, geo_type, geo_scale, geo_thickness, geo_is_solid, geo_src, hidden) + return log_geo_with_expanded_plane_scale( + super().log_geo, + newton.GeoType.PLANE, + name, + geo_type, + geo_scale, + geo_thickness, + geo_is_solid, + geo_src, + hidden, + ) class RerunVisualizer(BaseVisualizer): diff --git a/source/isaaclab_visualizers/isaaclab_visualizers/viser/viser_visualizer.py b/source/isaaclab_visualizers/isaaclab_visualizers/viser/viser_visualizer.py index c12dcee3b17e..17611db34b6e 100644 --- a/source/isaaclab_visualizers/isaaclab_visualizers/viser/viser_visualizer.py +++ b/source/isaaclab_visualizers/isaaclab_visualizers/viser/viser_visualizer.py @@ -25,7 +25,7 @@ from isaaclab_visualizers.newton.newton_visualization_markers import render_newton_visualization_markers from isaaclab_visualizers.newton_adapter import ( apply_viewer_visible_worlds, - expand_infinite_plane_scale, + log_geo_with_expanded_plane_scale, resolve_visible_env_indices, ) @@ -181,9 +181,17 @@ def log_geo( hidden: bool = False, ): """Log geometry, preserving large render extents for infinite ground planes.""" - if geo_type == newton.GeoType.PLANE: - geo_scale = expand_infinite_plane_scale(geo_scale) - return super().log_geo(name, geo_type, geo_scale, geo_thickness, geo_is_solid, geo_src, hidden) + return log_geo_with_expanded_plane_scale( + super().log_geo, + newton.GeoType.PLANE, + name, + geo_type, + geo_scale, + geo_thickness, + geo_is_solid, + geo_src, + hidden, + ) class ViserVisualizer(BaseVisualizer): diff --git a/source/isaaclab_visualizers/test/test_newton_adapter.py b/source/isaaclab_visualizers/test/test_newton_adapter.py index 75944cfdfdc6..6e79bd76209d 100644 --- a/source/isaaclab_visualizers/test/test_newton_adapter.py +++ b/source/isaaclab_visualizers/test/test_newton_adapter.py @@ -11,6 +11,7 @@ VISUALIZER_INFINITE_PLANE_SIZE, apply_viewer_visible_worlds, expand_infinite_plane_scale, + log_geo_with_expanded_plane_scale, resolve_visible_env_indices, ) @@ -24,6 +25,10 @@ def test_expand_infinite_plane_scale_expands_non_positive_extents(): ) assert expand_infinite_plane_scale((-1.0, 25.0)) == ( VISUALIZER_INFINITE_PLANE_SIZE, + 25.0, + ) + assert expand_infinite_plane_scale((25.0, 0.0)) == ( + 25.0, VISUALIZER_INFINITE_PLANE_SIZE, ) @@ -32,6 +37,27 @@ def test_expand_infinite_plane_scale_preserves_finite_extents(): assert expand_infinite_plane_scale((100.0, 50.0, 1.0)) == (100.0, 50.0, 1.0) +def test_log_geo_with_expanded_plane_scale_delegates_with_adjusted_plane_scale(): + calls = [] + + def _log_geo(*args): + calls.append(args) + return "logged" + + assert log_geo_with_expanded_plane_scale(_log_geo, 1, "ground", 1, (0.0, 25.0), 0.0, True) == "logged" + assert calls == [("ground", 1, (VISUALIZER_INFINITE_PLANE_SIZE, 25.0), 0.0, True, None, False)] + + +def test_log_geo_with_expanded_plane_scale_preserves_non_plane_scale(): + calls = [] + + def _log_geo(*args): + calls.append(args) + + log_geo_with_expanded_plane_scale(_log_geo, 1, "box", 2, (0.0, 25.0), 0.0, True, hidden=True) + assert calls == [("box", 2, (0.0, 25.0), 0.0, True, None, True)] + + def test_resolve_visible_env_indices_truncates_explicit_list(): assert resolve_visible_env_indices([1, 3, 5], 2, 10) == [1, 3] assert resolve_visible_env_indices([1, 3], 1, 10) == [1] diff --git a/source/isaaclab_visualizers/test/visualizer_integration_utils.py b/source/isaaclab_visualizers/test/visualizer_integration_utils.py index ea300a68e89a..6eea6e711d72 100644 --- a/source/isaaclab_visualizers/test/visualizer_integration_utils.py +++ b/source/isaaclab_visualizers/test/visualizer_integration_utils.py @@ -36,7 +36,7 @@ import torch import warp as wp from isaaclab_visualizers.kit import KitVisualizer, KitVisualizerCfg -from isaaclab_visualizers.newton import NewtonVisualizer, NewtonVisualizerCfg +from isaaclab_visualizers.newton import NEWTON_HUD_IMPORT_LOG_WARNING, NewtonVisualizer, NewtonVisualizerCfg import isaaclab.sim as sim_utils from isaaclab.app import AppLauncher @@ -62,10 +62,6 @@ ASSERT_VISUALIZER_WARNINGS = False _NEWTON_IMGUI_BUNDLE_PRINT_WARNING = "Warning: imgui_bundle not found" -_NEWTON_HUD_IMPORT_LOG_WARNING = ( - "Newton Visualizer HUD disabled: failed to import imgui_bundle. This can be caused by conflicting libraries." -) - _MAX_FRAME_CHECK_STEPS = 5 """Steps for Rerun / Viser smoke tests.""" @@ -236,7 +232,7 @@ def assert_no_newton_hud_dependency_warning( logged_warnings = [ record for record in caplog.records - if _NEWTON_HUD_IMPORT_LOG_WARNING in record.getMessage() + if NEWTON_HUD_IMPORT_LOG_WARNING in record.getMessage() or _NEWTON_IMGUI_BUNDLE_PRINT_WARNING in record.getMessage() ] assert not printed_warning and not logged_warnings, ( From 749f0d0ea3715a15010e310ff101cb42556fd288 Mon Sep 17 00:00:00 2001 From: Matthew Trepte Date: Wed, 3 Jun 2026 21:06:32 +0000 Subject: [PATCH 05/17] lint --- .../overview/core-concepts/visualization.rst | 6 ++++-- .../changelog.d/clarify-single-video-stream.rst | 7 ++++++- .../clarify-visualizer-camera-sensor-mode.rst | 7 ++++++- .../fix-scene-data-articulation-transforms.rst | 7 ++++++- .../reduce-ant-joint-wrench-warnings.rst | 7 ++++++- .../fix-newton-hud-imgui-dependency.rst | 16 +++++++++++++--- 6 files changed, 41 insertions(+), 9 deletions(-) diff --git a/docs/source/overview/core-concepts/visualization.rst b/docs/source/overview/core-concepts/visualization.rst index 51c08041f254..d920ee406f34 100644 --- a/docs/source/overview/core-concepts/visualization.rst +++ b/docs/source/overview/core-concepts/visualization.rst @@ -221,9 +221,11 @@ Note, Kit tiled camera views require launching with ``--enable_cameras``. * - Generated tiled camera - ``tiled_cam_view=True``, ``tiled_cam_prim_path=None``, ``tiled_cam_target_prim_path="/World/envs/*/Robot"`` - The visualizer creates per-env cameras. Each camera looks at the matched target prim, with ``tiled_cam_eye`` as an offset from that target. + - Note that the ``tiled_cam_target_prim_path`` is the default value, but different environments may require a different paths. * - Existing tiled camera sensors - ``tiled_cam_view=True``, ``tiled_cam_prim_path="/World/envs/*/Camera"`` - The visualizer displays existing Isaac Lab ``Camera`` sensor output. Generated-camera fields such as ``tiled_cam_eye`` and ``tiled_cam_target_prim_path`` are ignored. + - Note that the ``tiled_cam_prim_path`` is the default value, but different environments may require a different paths. This mode requires an environment that registers Isaac Lab ``Camera`` sensors in ``scene.sensors``. For Cartpole, use a camera task such as ``Isaac-Cartpole-Camera``. The plain ``Isaac-Cartpole`` task @@ -420,8 +422,8 @@ Newton Visualizer tiled_cam_prim_path=None, # Existing Camera sensor prim path, e.g. "/World/envs/*/Camera" tiled_cam_eye=(4.0, -4.0, 3.0), # Eye offset for generated tiled cameras tiled_cam_target_prim_path=( # Prim that generated cameras follow/look at - "/World/envs/*/Robot" - ), + "/World/envs/*/Robot" # This is the default value, but different environments + ), # may require a different paths. # Performance tuning update_frequency=1, # Update every N frames (1=every frame) diff --git a/source/isaaclab/changelog.d/clarify-single-video-stream.rst b/source/isaaclab/changelog.d/clarify-single-video-stream.rst index c46aedb23384..39e5bacdc191 100644 --- a/source/isaaclab/changelog.d/clarify-single-video-stream.rst +++ b/source/isaaclab/changelog.d/clarify-single-video-stream.rst @@ -1 +1,6 @@ -Clarified ``--video`` behavior when multiple video-capable visualizers are active: Gymnasium video recording captures one ``env.render()`` stream, with Kit taking priority over Newton. +Changed +^^^^^^^ + +* Clarified ``--video`` behavior when multiple video-capable visualizers are active: + Gymnasium video recording captures one ``env.render()`` stream, with Kit taking + priority over Newton. diff --git a/source/isaaclab/changelog.d/clarify-visualizer-camera-sensor-mode.rst b/source/isaaclab/changelog.d/clarify-visualizer-camera-sensor-mode.rst index 9dad61c329ec..cb6f9c0081f7 100644 --- a/source/isaaclab/changelog.d/clarify-visualizer-camera-sensor-mode.rst +++ b/source/isaaclab/changelog.d/clarify-visualizer-camera-sensor-mode.rst @@ -1 +1,6 @@ -Improved visualizer tiled-camera errors when ``tiled_cam_prim_path`` is set but the scene has no Isaac Lab ``Camera`` sensors, and clarified the camera-mode documentation for Cartpole camera tasks. +Fixed +^^^^^ + +* Improved visualizer tiled-camera errors when ``tiled_cam_prim_path`` is set but + the scene has no Isaac Lab ``Camera`` sensors, and clarified the camera-mode + documentation for Cartpole camera tasks. diff --git a/source/isaaclab/changelog.d/fix-scene-data-articulation-transforms.rst b/source/isaaclab/changelog.d/fix-scene-data-articulation-transforms.rst index acade678be11..a997c0446000 100644 --- a/source/isaaclab/changelog.d/fix-scene-data-articulation-transforms.rst +++ b/source/isaaclab/changelog.d/fix-scene-data-articulation-transforms.rst @@ -1 +1,6 @@ -Added a scene-data backend hook for active ``InteractiveScene`` access so backends can source scene-owned entity transforms without relying on global rigid-body views. +Added +^^^^^ + +* Added a scene-data backend hook for active ``InteractiveScene`` access so + backends can source scene-owned entity transforms without relying on global + rigid-body views. diff --git a/source/isaaclab_physx/changelog.d/reduce-ant-joint-wrench-warnings.rst b/source/isaaclab_physx/changelog.d/reduce-ant-joint-wrench-warnings.rst index 37e9c511d23b..ceab9cc95ca3 100644 --- a/source/isaaclab_physx/changelog.d/reduce-ant-joint-wrench-warnings.rst +++ b/source/isaaclab_physx/changelog.d/reduce-ant-joint-wrench-warnings.rst @@ -1 +1,6 @@ -Fixed excessive PhysX tensor warnings from Ant tasks with ``JointWrenchSensor`` by sourcing scene-data transforms for articulation links from Isaac Lab articulation views instead of a global PhysX rigid-body view. +Fixed +^^^^^ + +* Fixed excessive PhysX tensor warnings from Ant tasks with ``JointWrenchSensor`` + by sourcing scene-data transforms for articulation links from Isaac Lab + articulation views instead of a global PhysX rigid-body view. diff --git a/source/isaaclab_visualizers/changelog.d/fix-newton-hud-imgui-dependency.rst b/source/isaaclab_visualizers/changelog.d/fix-newton-hud-imgui-dependency.rst index d3c5d8c4042c..bcbbbafcebcc 100644 --- a/source/isaaclab_visualizers/changelog.d/fix-newton-hud-imgui-dependency.rst +++ b/source/isaaclab_visualizers/changelog.d/fix-newton-hud-imgui-dependency.rst @@ -1,5 +1,15 @@ -Fixed Newton visualizer HUD dependency checks by requiring ``typing-extensions>=4.15.0`` for the Newton visualizer extra and failing integration tests when Newton reports that ``imgui_bundle`` could not be imported. Removed the legacy ``setup.py`` for ``isaaclab_visualizers`` now that ``pyproject.toml`` carries the package metadata. +Fixed +^^^^^ -Fixed Rerun and Viser visualizers rendering Newton infinite ground planes too small by expanding non-positive plane extents to the same large finite size used by Newton GL. +* Fixed Newton visualizer HUD dependency checks by requiring + ``typing-extensions>=4.15.0`` for the Newton visualizer extra and failing + integration tests when Newton reports that ``imgui_bundle`` could not be + imported. Removed the legacy ``setup.py`` for ``isaaclab_visualizers`` now that + ``pyproject.toml`` carries the package metadata. -Fixed Viser visualizer ground-grid flickering by reusing unchanged plane grid line segments instead of removing and re-adding them every frame. +* Fixed Rerun and Viser visualizers rendering Newton infinite ground planes too + small by expanding non-positive plane extents to the same large finite size + used by Newton GL. + +* Fixed Viser visualizer ground-grid flickering by reusing unchanged plane grid + line segments instead of removing and re-adding them every frame. From 5eb0cafd63673387bcb9899969ac1a3a11768689 Mon Sep 17 00:00:00 2001 From: Matthew Trepte Date: Wed, 3 Jun 2026 23:09:05 +0000 Subject: [PATCH 06/17] more fixes --- .github/workflows/check-links.yml | 6 + .../overview/core-concepts/visualization.rst | 13 +- ...fix-scene-data-articulation-transforms.rst | 2 +- .../scene_data/scene_data_provider.py | 14 ++ .../expose-newton-contact-buffer.rst | 5 + .../isaaclab_newton/physics/newton_manager.py | 5 + .../fix-newton-contact-visualization.rst | 6 + .../newton/newton_visualizer.py | 124 +++++++++++++++++ .../test/test_newton_adapter.py | 128 ++++++++++++++++++ 9 files changed, 298 insertions(+), 5 deletions(-) create mode 100644 source/isaaclab_newton/changelog.d/expose-newton-contact-buffer.rst create mode 100644 source/isaaclab_visualizers/changelog.d/fix-newton-contact-visualization.rst diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml index edec5899aa3e..b4a66e2d6c84 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/check-links.yml @@ -104,6 +104,12 @@ jobs: --exclude 'docs\.ray\.io' --exclude 'docs\.conda\.io' --exclude 'stackoverflow\.com' + --exclude 'docs\.isaacsim\.omniverse\.nvidia\.com' + --exclude 'download\.isaacsim\.omniverse\.nvidia\.com/isaaclab/images' + --exclude 'dx\.doi\.org/10\.1109/JRA\.1987\.1087068' + --exclude 'andrew\.cmu\.edu/course/10-703/textbook/BartoSutton\.pdf' + --exclude 'www\.nvidia\.com/en-us/security' + --exclude 'bostondynamics\.com/reinforcement-learning-researcher-kit' --max-retries 5 --retry-wait-time 10 --timeout 20 diff --git a/docs/source/overview/core-concepts/visualization.rst b/docs/source/overview/core-concepts/visualization.rst index d920ee406f34..db19de46cb2a 100644 --- a/docs/source/overview/core-concepts/visualization.rst +++ b/docs/source/overview/core-concepts/visualization.rst @@ -220,12 +220,10 @@ Note, Kit tiled camera views require launching with ``--enable_cameras``. - Interactive visualizer camera starts at ``eye`` and looks at the fixed ``lookat`` coordinate. * - Generated tiled camera - ``tiled_cam_view=True``, ``tiled_cam_prim_path=None``, ``tiled_cam_target_prim_path="/World/envs/*/Robot"`` - - The visualizer creates per-env cameras. Each camera looks at the matched target prim, with ``tiled_cam_eye`` as an offset from that target. - - Note that the ``tiled_cam_target_prim_path`` is the default value, but different environments may require a different paths. + - The visualizer creates per-env cameras. Each camera looks at the matched target prim, with ``tiled_cam_eye`` as an offset from that target. Note that the ``tiled_cam_target_prim_path`` is the default value, but different environments may require different paths. * - Existing tiled camera sensors - ``tiled_cam_view=True``, ``tiled_cam_prim_path="/World/envs/*/Camera"`` - - The visualizer displays existing Isaac Lab ``Camera`` sensor output. Generated-camera fields such as ``tiled_cam_eye`` and ``tiled_cam_target_prim_path`` are ignored. - - Note that the ``tiled_cam_prim_path`` is the default value, but different environments may require a different paths. + - The visualizer displays existing Isaac Lab ``Camera`` sensor output. Generated-camera fields such as ``tiled_cam_eye`` and ``tiled_cam_target_prim_path`` are ignored. Note that the ``tiled_cam_prim_path`` is the default value, but different environments may require different paths. This mode requires an environment that registers Isaac Lab ``Camera`` sensors in ``scene.sensors``. For Cartpole, use a camera task such as ``Isaac-Cartpole-Camera``. The plain ``Isaac-Cartpole`` task @@ -609,6 +607,13 @@ The FPS control in the Rerun visualizer UI may not affect the visualization fram Currently, live plots are only available in the Kit Visualizer. +**Newton Contact Visualization** + +Newton's native ``Show Contacts`` view can show all contacts from the Newton physics contact buffer. When running +with PhysX, the Newton visualizer can only show contacts reported by configured Isaac Lab contact sensors, so +currently the set of displayed contacts may differ across backends. + + **Viser Visualizer Renderer Requirement** The Viser visualizer requires a Newton model, which is provided automatically by diff --git a/source/isaaclab/changelog.d/fix-scene-data-articulation-transforms.rst b/source/isaaclab/changelog.d/fix-scene-data-articulation-transforms.rst index a997c0446000..14be85dbc997 100644 --- a/source/isaaclab/changelog.d/fix-scene-data-articulation-transforms.rst +++ b/source/isaaclab/changelog.d/fix-scene-data-articulation-transforms.rst @@ -3,4 +3,4 @@ Added * Added a scene-data backend hook for active ``InteractiveScene`` access so backends can source scene-owned entity transforms without relying on global - rigid-body views. + rigid-body views, and visualizers can discover scene-owned contact sensors. diff --git a/source/isaaclab/isaaclab/scene_data/scene_data_provider.py b/source/isaaclab/isaaclab/scene_data/scene_data_provider.py index 9d888347e38e..eb9e164d79b6 100644 --- a/source/isaaclab/isaaclab/scene_data/scene_data_provider.py +++ b/source/isaaclab/isaaclab/scene_data/scene_data_provider.py @@ -56,6 +56,20 @@ def get_camera_sensors(self) -> dict[str, Any]: if isinstance(sensor, Camera) } + def get_contact_sensors(self) -> dict[str, Any]: + """Return Isaac Lab contact sensors keyed by scene sensor name.""" + if self._interactive_scene is None: + return {} + try: + from isaaclab.sensors.contact_sensor import BaseContactSensor + except ImportError: + return {} + return { + name: sensor + for name, sensor in getattr(self._interactive_scene, "sensors", {}).items() + if isinstance(sensor, BaseContactSensor) + } + @property def transform_count(self) -> int: """Number of transforms available from the sim backend.""" diff --git a/source/isaaclab_newton/changelog.d/expose-newton-contact-buffer.rst b/source/isaaclab_newton/changelog.d/expose-newton-contact-buffer.rst new file mode 100644 index 000000000000..178e170378b7 --- /dev/null +++ b/source/isaaclab_newton/changelog.d/expose-newton-contact-buffer.rst @@ -0,0 +1,5 @@ +Added +^^^^^ + +* Added a ``NewtonManager.get_contacts()`` accessor so visualizers can render + Newton contact buffers without reaching into manager internals. diff --git a/source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py b/source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py index d5991ed395ab..41aea04368f4 100644 --- a/source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py +++ b/source/isaaclab_newton/isaaclab_newton/physics/newton_manager.py @@ -1584,6 +1584,11 @@ def get_state(cls, scene_data_provider: SceneDataProvider | None = None) -> Stat cls.update_visualization_state(scene_data_provider) return cls.get_state_0() + @classmethod + def get_contacts(cls) -> Contacts | None: + """Get the current Newton contact buffer, if the active solver exposes one.""" + return cls._contacts + @classmethod def get_num_envs(cls) -> int: return cls._num_envs diff --git a/source/isaaclab_visualizers/changelog.d/fix-newton-contact-visualization.rst b/source/isaaclab_visualizers/changelog.d/fix-newton-contact-visualization.rst new file mode 100644 index 000000000000..255a3a270ceb --- /dev/null +++ b/source/isaaclab_visualizers/changelog.d/fix-newton-contact-visualization.rst @@ -0,0 +1,6 @@ +Fixed +^^^^^ + +* Fixed Newton visualizer contact rendering by logging Newton contact buffers + when available and falling back to scene contact sensors for PhysX-backed + scenes. diff --git a/source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py b/source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py index 28a4740f787d..d37aafed9432 100644 --- a/source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py +++ b/source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py @@ -13,6 +13,7 @@ import sys from typing import TYPE_CHECKING +import torch import warp as wp from newton.viewer import ViewerGL from pyglet.math import Vec3 as PygletVec3 @@ -40,6 +41,15 @@ NEWTON_HUD_IMPORT_LOG_WARNING = "[NewtonVisualizer] Newton HUD disabled: failed to import imgui_bundle." """Stable log prefix emitted when Newton's imgui HUD dependencies cannot be imported.""" +CONTACT_ARROW_PATH = "/contacts" +"""Viewer path used for native and synthesized contact arrows.""" + +CONTACT_ARROW_COLOR = (0.0, 1.0, 0.0) +"""Color used by Newton's native contact visualization.""" + +CONTACT_ARROW_LENGTH = 0.1 +"""Length of synthesized contact arrows in meters.""" + if TYPE_CHECKING: from isaaclab.scene_data import SceneDataProvider @@ -507,6 +517,11 @@ def step(self, dt: float) -> None: if hasattr(body_q, "shape") and body_q.shape[0] == 0: return self._viewer.log_state(self._state) + contacts = NewtonManager.get_contacts() + if contacts is not None: + self._viewer.log_contacts(contacts, self._state) + else: + self._log_scene_contact_sensor_arrows(num_envs) if self.cfg.enable_markers: render_newton_visualization_markers( self._viewer, self._resolved_visible_env_ids, num_envs=num_envs @@ -530,6 +545,115 @@ def close(self) -> None: self._camera_sensor = None self._is_closed = True + def _log_scene_contact_sensor_arrows(self, num_envs: int) -> None: + """Render contact sensor data as Newton-style arrows when native contacts are unavailable.""" + if self._viewer is None: + return + if not self._viewer.show_contacts: + self._viewer.log_arrows(CONTACT_ARROW_PATH, None, None, None) + return + contact_sensors = ( + self._scene_data_provider.get_contact_sensors() if self._scene_data_provider is not None else {} + ) + if not contact_sensors: + self._viewer.log_arrows(CONTACT_ARROW_PATH, None, None, None) + return + + starts: list[torch.Tensor] = [] + ends: list[torch.Tensor] = [] + for sensor in contact_sensors.values(): + sensor_starts, sensor_ends = self._contact_sensor_arrow_tensors(sensor, num_envs) + if sensor_starts is not None and sensor_ends is not None: + starts.append(sensor_starts) + ends.append(sensor_ends) + + if not starts: + self._viewer.log_arrows(CONTACT_ARROW_PATH, None, None, None) + return + + starts_t = torch.cat(starts, dim=0).detach().to(dtype=torch.float32, device="cpu").contiguous() + ends_t = torch.cat(ends, dim=0).detach().to(dtype=torch.float32, device="cpu").contiguous() + self._viewer.log_arrows( + CONTACT_ARROW_PATH, + wp.array(starts_t.numpy(), dtype=wp.vec3, device=self._viewer.device), + wp.array(ends_t.numpy(), dtype=wp.vec3, device=self._viewer.device), + CONTACT_ARROW_COLOR, + ) + + def _contact_sensor_arrow_tensors(self, sensor, num_envs: int) -> tuple[torch.Tensor | None, torch.Tensor | None]: + """Build Newton-style arrow starts/ends from an Isaac Lab contact sensor.""" + try: + data = sensor.data + net_forces_proxy = data.net_forces_w + net_forces = net_forces_proxy.torch if net_forces_proxy is not None else None + except (AttributeError, NotImplementedError, RuntimeError): + return None, None + + if net_forces is None or net_forces.numel() == 0: + return None, None + net_forces = self._filter_visible_env_tensor(net_forces, num_envs) + + force_threshold = getattr(getattr(sensor, "cfg", None), "force_threshold", None) + if force_threshold is None: + force_threshold = 0.0 + + try: + contact_pos = getattr(data, "contact_pos_w", None) + force_matrix = getattr(data, "force_matrix_w", None) + except NotImplementedError: + contact_pos = None + force_matrix = None + if contact_pos is not None and force_matrix is not None: + contact_pos_t = self._filter_visible_env_tensor(contact_pos.torch, num_envs) + force_matrix_t = self._filter_visible_env_tensor(force_matrix.torch, num_envs) + if contact_pos_t.numel() != 0 and force_matrix_t.numel() != 0: + force_norm = torch.linalg.norm(force_matrix_t, dim=-1) + finite_pos = torch.isfinite(contact_pos_t).all(dim=-1) + active = (force_norm > force_threshold) & finite_pos + if torch.any(active): + starts = contact_pos_t[active] + directions = torch.nn.functional.normalize(force_matrix_t[active], dim=-1) + return starts, starts + directions * CONTACT_ARROW_LENGTH + + origins = self._contact_sensor_origin_positions(sensor, data, net_forces) + if origins is None: + return None, None + origins = self._filter_visible_env_tensor(origins, num_envs) + + force_norm = torch.linalg.norm(net_forces, dim=-1) + active = force_norm > force_threshold + if not torch.any(active): + return None, None + + starts = origins[active] + directions = torch.nn.functional.normalize(net_forces[active], dim=-1) + return starts, starts + directions * CONTACT_ARROW_LENGTH + + def _contact_sensor_origin_positions(self, sensor, data, net_forces: torch.Tensor) -> torch.Tensor | None: + """Return per-sensor origins for contact arrow starts.""" + try: + pos_w = getattr(data, "pos_w", None) + except NotImplementedError: + pos_w = None + if pos_w is not None: + return pos_w.torch + + body_physx_view = getattr(sensor, "body_physx_view", None) + if body_physx_view is None: + return None + try: + pose = body_physx_view.get_transforms() + except RuntimeError: + return None + return wp.to_torch(pose).view(*net_forces.shape[:-1], 7)[..., :3] + + def _filter_visible_env_tensor(self, tensor: torch.Tensor, num_envs: int) -> torch.Tensor: + """Apply Newton visualizer visible-world filtering to a sensor tensor.""" + if self._resolved_visible_env_ids is None or tensor.ndim == 0 or tensor.shape[0] != num_envs: + return tensor + ids = torch.as_tensor(self._resolved_visible_env_ids, dtype=torch.long, device=tensor.device) + return tensor.index_select(0, ids) + def is_running(self) -> bool: """Return whether the visualizer should continue stepping. diff --git a/source/isaaclab_visualizers/test/test_newton_adapter.py b/source/isaaclab_visualizers/test/test_newton_adapter.py index 6e79bd76209d..50d76902d3f0 100644 --- a/source/isaaclab_visualizers/test/test_newton_adapter.py +++ b/source/isaaclab_visualizers/test/test_newton_adapter.py @@ -7,6 +7,10 @@ from __future__ import annotations +from types import SimpleNamespace + +import torch +from isaaclab_visualizers.newton import NewtonVisualizer, NewtonVisualizerCfg from isaaclab_visualizers.newton_adapter import ( VISUALIZER_INFINITE_PLANE_SIZE, apply_viewer_visible_worlds, @@ -95,3 +99,127 @@ def set_visible_worlds(self, worlds): apply_viewer_visible_worlds(_V(), env_ids=None, max_visible_envs=None, num_envs=3) assert calls[-1] is None + + +class _BodyQ: + shape = (1,) + + +class _Viewer: + _update_frequency = 1 + + def __init__(self): + self.device = "cpu" + self.show_contacts = False + self.logged_state = None + self.logged_contacts = None + self.logged_arrows = None + + def is_paused(self): + return False + + def begin_frame(self, _time): + pass + + def log_state(self, state): + self.logged_state = state + + def log_contacts(self, contacts, state): + self.logged_contacts = (contacts, state) + + def log_arrows(self, name, starts, ends, colors): + self.logged_arrows = (name, starts, ends, colors) + + def end_frame(self): + pass + + +class _Proxy: + def __init__(self, tensor): + self.torch = tensor + + +class _ContactSensorData: + def __init__(self, net_forces_w, pos_w): + self.net_forces_w = _Proxy(net_forces_w) + self.pos_w = _Proxy(pos_w) + self.contact_pos_w = None + self.force_matrix_w = None + + +class _ContactSensor: + def __init__(self, net_forces_w, pos_w, force_threshold=1.0): + self.cfg = SimpleNamespace(force_threshold=force_threshold) + self.data = _ContactSensorData(net_forces_w, pos_w) + + +class _SceneDataProvider: + def __init__(self, contact_sensors=None): + self._contact_sensors = contact_sensors or {} + + def get_contact_sensors(self): + return self._contact_sensors + + +def _make_newton_visualizer(viewer, scene_data_provider=None): + visualizer = NewtonVisualizer.__new__(NewtonVisualizer) + visualizer.cfg = NewtonVisualizerCfg(enable_markers=False) + visualizer._is_initialized = True + visualizer._is_closed = False + visualizer._sim_time = 0.0 + visualizer._step_counter = 0 + visualizer._viewer = viewer + visualizer._state = None + visualizer._scene_data_provider = scene_data_provider + visualizer._resolved_visible_env_ids = None + visualizer._log_camera_sensor_image = lambda: None + return visualizer + + +def test_newton_visualizer_logs_native_contacts_when_available(monkeypatch): + from isaaclab_newton.physics import NewtonManager + + state = SimpleNamespace(body_q=_BodyQ()) + contacts = object() + viewer = _Viewer() + + monkeypatch.setattr(NewtonManager, "get_state", lambda _scene_data_provider=None: state) + monkeypatch.setattr(NewtonManager, "get_contacts", lambda: contacts) + monkeypatch.setattr(NewtonManager, "get_num_envs", lambda: 1) + + _make_newton_visualizer(viewer).step(0.1) + + assert viewer.logged_state is state + assert viewer.logged_contacts == (contacts, state) + + +def test_newton_visualizer_contact_sensor_fallback_obeys_show_contacts(monkeypatch): + from isaaclab_newton.physics import NewtonManager + + state = SimpleNamespace(body_q=_BodyQ()) + viewer = _Viewer() + sensor = _ContactSensor( + net_forces_w=torch.tensor([[[0.0, 0.0, 2.0], [0.0, 0.0, 0.5]]], dtype=torch.float32), + pos_w=torch.tensor([[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]], dtype=torch.float32), + force_threshold=1.0, + ) + scene_data_provider = _SceneDataProvider({"contact_forces": sensor}) + + monkeypatch.setattr(NewtonManager, "get_state", lambda _scene_data_provider=None: state) + monkeypatch.setattr(NewtonManager, "get_contacts", lambda: None) + monkeypatch.setattr(NewtonManager, "get_num_envs", lambda: 1) + + visualizer = _make_newton_visualizer(viewer, scene_data_provider) + visualizer.step(0.1) + assert viewer.logged_arrows == ("/contacts", None, None, None) + + viewer.show_contacts = True + visualizer.step(0.1) + + name, starts, ends, colors = viewer.logged_arrows + assert name == "/contacts" + assert len(starts) == 1 + assert len(ends) == 1 + assert colors == (0.0, 1.0, 0.0) + assert torch.allclose(torch.tensor(starts.numpy()[0]), torch.tensor([1.0, 2.0, 3.0])) + assert torch.allclose(torch.tensor(ends.numpy()[0]), torch.tensor([1.0, 2.0, 3.1])) From 7e86c65f7c396bd183b24fe9b5005f02c622a3a9 Mon Sep 17 00:00:00 2001 From: Matthew Trepte Date: Wed, 3 Jun 2026 23:21:45 +0000 Subject: [PATCH 07/17] clean up PR --- .../isaaclab_visualizers/newton/__init__.py | 7 +------ .../newton/newton_visualizer.py | 19 ------------------- .../test_visualizer_integration_newton.py | 2 +- .../test/test_visualizer_integration_physx.py | 2 +- ...est_visualizer_tiled_integration_newton.py | 2 +- ...test_visualizer_tiled_integration_physx.py | 2 +- .../test/visualizer_integration_utils.py | 19 +++++-------------- 7 files changed, 10 insertions(+), 43 deletions(-) diff --git a/source/isaaclab_visualizers/isaaclab_visualizers/newton/__init__.py b/source/isaaclab_visualizers/isaaclab_visualizers/newton/__init__.py index ead5cdf45859..b83c42420d86 100644 --- a/source/isaaclab_visualizers/isaaclab_visualizers/newton/__init__.py +++ b/source/isaaclab_visualizers/isaaclab_visualizers/newton/__init__.py @@ -16,10 +16,9 @@ from .newton_visualizer_cfg import NewtonVisualizerCfg if TYPE_CHECKING: - from .newton_visualizer import NEWTON_HUD_IMPORT_LOG_WARNING from .newton_visualizer import NewtonVisualizer -__all__ = ["NEWTON_HUD_IMPORT_LOG_WARNING", "NewtonVisualizer", "NewtonVisualizerCfg"] +__all__ = ["NewtonVisualizer", "NewtonVisualizerCfg"] def __getattr__(name: str): @@ -27,8 +26,4 @@ def __getattr__(name: str): from .newton_visualizer import NewtonVisualizer return NewtonVisualizer - if name == "NEWTON_HUD_IMPORT_LOG_WARNING": - from .newton_visualizer import NEWTON_HUD_IMPORT_LOG_WARNING - - return NEWTON_HUD_IMPORT_LOG_WARNING raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py b/source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py index d37aafed9432..3dbecdec794f 100644 --- a/source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py +++ b/source/isaaclab_visualizers/isaaclab_visualizers/newton/newton_visualizer.py @@ -38,9 +38,6 @@ logger = logging.getLogger(__name__) -NEWTON_HUD_IMPORT_LOG_WARNING = "[NewtonVisualizer] Newton HUD disabled: failed to import imgui_bundle." -"""Stable log prefix emitted when Newton's imgui HUD dependencies cannot be imported.""" - CONTACT_ARROW_PATH = "/contacts" """Viewer path used for native and synthesized contact arrows.""" @@ -54,21 +51,6 @@ from isaaclab.scene_data import SceneDataProvider -def _log_newton_hud_dependency_issue() -> None: - """Log a clear warning if Newton's imgui HUD dependencies cannot be imported.""" - try: - from imgui_bundle import imgui as _imgui # noqa: F401 - from imgui_bundle import imguizmo as _imguizmo # noqa: F401 - from imgui_bundle.python_backends import pyglet_backend as _pyglet_backend # noqa: F401 - except ImportError as exc: - logger.warning( - "%s Install isaaclab-visualizers[newton] or fix its transitive dependencies " - "(for example typing-extensions>=4.15.0). ImportError: %s", - NEWTON_HUD_IMPORT_LOG_WARNING, - exc, - ) - - class NewtonViewerGL(ViewerGL): """Wrapper around Newton's ViewerGL with training/rendering pause controls.""" @@ -417,7 +399,6 @@ def initialize(self, scene_data_provider: SceneDataProvider) -> None: pyglet.options["headless"] = True - _log_newton_hud_dependency_issue() self._viewer = NewtonViewerGL( width=self.cfg.window_width, height=self.cfg.window_height, diff --git a/source/isaaclab_visualizers/test/test_visualizer_integration_newton.py b/source/isaaclab_visualizers/test/test_visualizer_integration_newton.py index 30bfc316b646..ea8e35fab1ae 100644 --- a/source/isaaclab_visualizers/test/test_visualizer_integration_newton.py +++ b/source/isaaclab_visualizers/test/test_visualizer_integration_newton.py @@ -33,7 +33,7 @@ def test_cartpole_env_visualizers_motion_with_play_pause_newton( ) -> None: """Cartpole env + all non-tiled visualizers on Newton MJWarp.""" run_cartpole_env_visualizers_motion_with_play_pause("newton", caplog) - _viz_utils.assert_no_newton_hud_dependency_warning(capsys, caplog) + _viz_utils.assert_no_newton_imgui_bundle_warning(capsys, caplog) if __name__ == "__main__": diff --git a/source/isaaclab_visualizers/test/test_visualizer_integration_physx.py b/source/isaaclab_visualizers/test/test_visualizer_integration_physx.py index 3cb997a64889..c8b909ab532e 100644 --- a/source/isaaclab_visualizers/test/test_visualizer_integration_physx.py +++ b/source/isaaclab_visualizers/test/test_visualizer_integration_physx.py @@ -33,7 +33,7 @@ def test_cartpole_env_visualizers_motion_with_play_pause_physx( ) -> None: """Cartpole env + all non-tiled visualizers on PhysX.""" run_cartpole_env_visualizers_motion_with_play_pause("physx", caplog) - _viz_utils.assert_no_newton_hud_dependency_warning(capsys, caplog) + _viz_utils.assert_no_newton_imgui_bundle_warning(capsys, caplog) if __name__ == "__main__": diff --git a/source/isaaclab_visualizers/test/test_visualizer_tiled_integration_newton.py b/source/isaaclab_visualizers/test/test_visualizer_tiled_integration_newton.py index fc19b969b88b..0baaf7d8d5f7 100644 --- a/source/isaaclab_visualizers/test/test_visualizer_tiled_integration_newton.py +++ b/source/isaaclab_visualizers/test/test_visualizer_tiled_integration_newton.py @@ -33,7 +33,7 @@ def test_visualizer_tiled_integration_newton( ) -> None: """Cartpole env + tiled Kit/Newton visualizers on Newton MJWarp.""" run_cartpole_env_visualizers_tiled_camera_motion("newton", caplog) - _viz_utils.assert_no_newton_hud_dependency_warning(capsys, caplog) + _viz_utils.assert_no_newton_imgui_bundle_warning(capsys, caplog) if __name__ == "__main__": diff --git a/source/isaaclab_visualizers/test/test_visualizer_tiled_integration_physx.py b/source/isaaclab_visualizers/test/test_visualizer_tiled_integration_physx.py index b2407aaae482..bafffe831e02 100644 --- a/source/isaaclab_visualizers/test/test_visualizer_tiled_integration_physx.py +++ b/source/isaaclab_visualizers/test/test_visualizer_tiled_integration_physx.py @@ -33,7 +33,7 @@ def test_visualizer_tiled_integration_physx( ) -> None: """Cartpole env + tiled Kit/Newton visualizers on PhysX.""" run_cartpole_env_visualizers_tiled_camera_motion("physx", caplog) - _viz_utils.assert_no_newton_hud_dependency_warning(capsys, caplog) + _viz_utils.assert_no_newton_imgui_bundle_warning(capsys, caplog) if __name__ == "__main__": diff --git a/source/isaaclab_visualizers/test/visualizer_integration_utils.py b/source/isaaclab_visualizers/test/visualizer_integration_utils.py index 6eea6e711d72..3b4bc8f3449d 100644 --- a/source/isaaclab_visualizers/test/visualizer_integration_utils.py +++ b/source/isaaclab_visualizers/test/visualizer_integration_utils.py @@ -36,7 +36,7 @@ import torch import warp as wp from isaaclab_visualizers.kit import KitVisualizer, KitVisualizerCfg -from isaaclab_visualizers.newton import NEWTON_HUD_IMPORT_LOG_WARNING, NewtonVisualizer, NewtonVisualizerCfg +from isaaclab_visualizers.newton import NewtonVisualizer, NewtonVisualizerCfg import isaaclab.sim as sim_utils from isaaclab.app import AppLauncher @@ -222,23 +222,14 @@ def _assert_no_visualizer_log_issues(caplog: pytest.LogCaptureFixture, *, fail_o ) -def assert_no_newton_hud_dependency_warning( - capsys: pytest.CaptureFixture[str], caplog: pytest.LogCaptureFixture -) -> None: - """Fail when Newton disables the imgui HUD due to a broken dependency chain.""" +def assert_no_newton_imgui_bundle_warning(capsys: pytest.CaptureFixture[str], caplog: pytest.LogCaptureFixture) -> None: + """Fail when Newton reports that its imgui HUD dependency is missing.""" captured = capsys.readouterr() captured_output = captured.out + captured.err printed_warning = _NEWTON_IMGUI_BUNDLE_PRINT_WARNING in captured_output - logged_warnings = [ - record - for record in caplog.records - if NEWTON_HUD_IMPORT_LOG_WARNING in record.getMessage() - or _NEWTON_IMGUI_BUNDLE_PRINT_WARNING in record.getMessage() - ] + logged_warnings = [record for record in caplog.records if _NEWTON_IMGUI_BUNDLE_PRINT_WARNING in record.getMessage()] assert not printed_warning and not logged_warnings, ( - "Newton visualizer HUD dependency failed. The Newton viewer printed/logged that imgui_bundle could not " - "be imported, which disables the HUD controls. Ensure isaaclab-visualizers[newton] installs " - "imgui-bundle and compatible transitive dependencies such as typing-extensions>=4.15.0. " + "Newton viewer reported that imgui_bundle could not be imported, which disables HUD controls. " f"Captured output: {captured_output!r}. " "Captured logs: " + "; ".join(f"{record.name}: {record.getMessage()}" for record in logged_warnings) ) From 6738fb5d396628b48498494a5e536b112d4598c3 Mon Sep 17 00:00:00 2001 From: Matthew Trepte Date: Wed, 3 Jun 2026 23:23:06 +0000 Subject: [PATCH 08/17] tweak docs --- docs/source/how-to/record_video.rst | 4 ---- docs/source/overview/core-concepts/visualization.rst | 7 +++++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/source/how-to/record_video.rst b/docs/source/how-to/record_video.rst index a556c253e057..3ef6bedf6c71 100644 --- a/docs/source/how-to/record_video.rst +++ b/docs/source/how-to/record_video.rst @@ -80,10 +80,6 @@ precedence and only one ``--video`` stream is recorded. Rerun records ``.rrd`` r the Rerun visualizer rather than producing ``--video`` clips, and Viser does not currently provide a ``--video`` recording backend. -To record both Kit and Newton perspectives for the same task, run two jobs: one with ``--viz kit`` and -one with ``--viz newton``. The Gymnasium ``RecordVideo`` wrapper records one ``env.render()`` stream -per process; it does not emit one video per visualizer listed in ``--viz``. - Set ``VideoRecorderCfg.backend_source = "renderer"`` to ignore active visualizers and choose from the physics/renderer stack instead. In that mode, PhysX physics (``physics=physx``) or Isaac RTX (``renderer=isaacsim_rtx_renderer``) selects the Kit path. Newton physics (``physics=newton_mjwarp``) or diff --git a/docs/source/overview/core-concepts/visualization.rst b/docs/source/overview/core-concepts/visualization.rst index db19de46cb2a..13890a16cf92 100644 --- a/docs/source/overview/core-concepts/visualization.rst +++ b/docs/source/overview/core-concepts/visualization.rst @@ -220,10 +220,13 @@ Note, Kit tiled camera views require launching with ``--enable_cameras``. - Interactive visualizer camera starts at ``eye`` and looks at the fixed ``lookat`` coordinate. * - Generated tiled camera - ``tiled_cam_view=True``, ``tiled_cam_prim_path=None``, ``tiled_cam_target_prim_path="/World/envs/*/Robot"`` - - The visualizer creates per-env cameras. Each camera looks at the matched target prim, with ``tiled_cam_eye`` as an offset from that target. Note that the ``tiled_cam_target_prim_path`` is the default value, but different environments may require different paths. + - The visualizer creates per-env cameras. Each camera looks at the matched target prim, with ``tiled_cam_eye`` as an offset from that target. + Note that the ``tiled_cam_target_prim_path`` has a default value, but different environments may require different paths. * - Existing tiled camera sensors - ``tiled_cam_view=True``, ``tiled_cam_prim_path="/World/envs/*/Camera"`` - - The visualizer displays existing Isaac Lab ``Camera`` sensor output. Generated-camera fields such as ``tiled_cam_eye`` and ``tiled_cam_target_prim_path`` are ignored. Note that the ``tiled_cam_prim_path`` is the default value, but different environments may require different paths. + - The visualizer displays existing Isaac Lab ``Camera`` sensor output. Generated-camera fields such as ``tiled_cam_eye`` and + ``tiled_cam_target_prim_path`` are ignored. Note that the ``tiled_cam_prim_path`` has a default value, but different + environments may require different paths. This mode requires an environment that registers Isaac Lab ``Camera`` sensors in ``scene.sensors``. For Cartpole, use a camera task such as ``Isaac-Cartpole-Camera``. The plain ``Isaac-Cartpole`` task From 2b7366deca61850582f0bfa05f0e52e61d4d7ee3 Mon Sep 17 00:00:00 2001 From: Matthew Trepte Date: Wed, 3 Jun 2026 23:46:26 +0000 Subject: [PATCH 09/17] fix CI --- .github/workflows/license-exceptions.json | 2 +- docs/index.rst | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/license-exceptions.json b/.github/workflows/license-exceptions.json index e1e93a2ae7e0..95af798cd66e 100644 --- a/.github/workflows/license-exceptions.json +++ b/.github/workflows/license-exceptions.json @@ -470,7 +470,7 @@ }, { "package": "typing_extensions", - "license": "Python Software Foundation License", + "license": "PSF-2.0", "comment": "PSFL / OSRB" }, { diff --git a/docs/index.rst b/docs/index.rst index 448d897852e0..2a304b8f8d7b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -130,7 +130,6 @@ Table of Contents source/features/hydra source/features/multi_gpu source/features/population_based_training - Tiled Rendering source/features/ray source/features/reproducibility From fe15dd6fcbc6726ba659ab58bc2f813e65aec374 Mon Sep 17 00:00:00 2001 From: Matthew Trepte Date: Thu, 4 Jun 2026 00:28:15 +0000 Subject: [PATCH 10/17] fix table --- docs/source/overview/core-concepts/visualization.rst | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/source/overview/core-concepts/visualization.rst b/docs/source/overview/core-concepts/visualization.rst index 13890a16cf92..543cd20d3353 100644 --- a/docs/source/overview/core-concepts/visualization.rst +++ b/docs/source/overview/core-concepts/visualization.rst @@ -226,11 +226,9 @@ Note, Kit tiled camera views require launching with ``--enable_cameras``. - ``tiled_cam_view=True``, ``tiled_cam_prim_path="/World/envs/*/Camera"`` - The visualizer displays existing Isaac Lab ``Camera`` sensor output. Generated-camera fields such as ``tiled_cam_eye`` and ``tiled_cam_target_prim_path`` are ignored. Note that the ``tiled_cam_prim_path`` has a default value, but different - environments may require different paths. - - This mode requires an environment that registers Isaac Lab ``Camera`` sensors in ``scene.sensors``. - For Cartpole, use a camera task such as ``Isaac-Cartpole-Camera``. The plain ``Isaac-Cartpole`` task - has no ``/World/envs/*/Camera`` sensor, so leave ``tiled_cam_prim_path=None`` to use generated visualizer cameras. + environments may require different paths. This mode requires an environment that registers Isaac Lab ``Camera`` sensors + in ``scene.sensors``. For Cartpole, use a camera task such as ``Isaac-Cartpole-Camera``. The plain ``Isaac-Cartpole`` + task has no ``/World/envs/*/Camera`` sensor, so leave ``tiled_cam_prim_path=None`` to use generated visualizer cameras. **How to Access the Tiled Camera View in the UI** From 6b442afce59ff923db1a837b27b3a89bbc4a0cd6 Mon Sep 17 00:00:00 2001 From: Matthew Trepte Date: Thu, 4 Jun 2026 00:43:13 +0000 Subject: [PATCH 11/17] fix other doc issues --- docs/source/how-to/optimize_stage_creation.rst | 2 +- docs/source/refs/migration.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/how-to/optimize_stage_creation.rst b/docs/source/how-to/optimize_stage_creation.rst index cdbd59562992..e359669ebea2 100644 --- a/docs/source/how-to/optimize_stage_creation.rst +++ b/docs/source/how-to/optimize_stage_creation.rst @@ -49,7 +49,7 @@ Stage in memory can be toggled by setting the :attr:`isaaclab.sim.SimulationCfg. env = ManagerBasedRLEnv(cfg=cfg) When using stage in memory without an existing RL environment class, wrap the stage creation steps -in a :py:keyword:`with` statement to set the stage context. The stage is automatically attached +in a ``with`` statement to set the stage context. The stage is automatically attached to the USD context when ``SimulationContext`` is created with ``create_stage_in_memory=True``. **Using Stage in Memory with a manual scene setup** diff --git a/docs/source/refs/migration.rst b/docs/source/refs/migration.rst index 3ae599e91509..11db227bc142 100644 --- a/docs/source/refs/migration.rst +++ b/docs/source/refs/migration.rst @@ -157,7 +157,7 @@ Two key patterns support this: initialized. For full details, examples, and the ``{DIR}`` placeholder convention, see the -:ref:`contributing` guide — in particular the +:doc:`contributing` guide — in particular the `Lazy Loading & Module Exports `__, `Resolvable Strings `__, and `Config + Implementation File Split `__ @@ -197,7 +197,7 @@ With this in place, ``import my_package`` will not eagerly import any submodules are loaded on first access, giving ``SimulationApp`` time to initialize and auto-detect the correct backend. -For more details, refer to the :ref:`contributing` guide. +For more details, refer to the :doc:`contributing` guide. .. _simple script: https://gist.github.com/kellyguo11/3e8f73f739b1c013b1069ad372277a85 From 79c98a81ea3efffa732ae847d5f6c4ff801ec04f Mon Sep 17 00:00:00 2001 From: Matthew Trepte Date: Thu, 4 Jun 2026 21:21:59 +0000 Subject: [PATCH 12/17] PR feedback --- docs/index.rst | 1 + .../isaaclab/markers/visualization_markers.py | 8 +- .../isaaclab/scene_data/scene_data_backend.py | 4 - .../isaaclab/sim/simulation_context.py | 1 - .../markers/test_visualization_markers.py | 29 ++++++ .../check_scene_xr_visualization.py | 2 +- .../isaaclab_physx/physics/physx_manager.py | 89 ++++++------------- 7 files changed, 64 insertions(+), 70 deletions(-) rename source/isaaclab/test/{visualization => xr_visualization}/check_scene_xr_visualization.py (98%) diff --git a/docs/index.rst b/docs/index.rst index 2a304b8f8d7b..720c96676980 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -130,6 +130,7 @@ Table of Contents source/features/hydra source/features/multi_gpu source/features/population_based_training + Tiled Rendering source/features/ray source/features/reproducibility diff --git a/source/isaaclab/isaaclab/markers/visualization_markers.py b/source/isaaclab/isaaclab/markers/visualization_markers.py index 667f9b4ddf7d..ef9eb65e9927 100644 --- a/source/isaaclab/isaaclab/markers/visualization_markers.py +++ b/source/isaaclab/isaaclab/markers/visualization_markers.py @@ -111,6 +111,7 @@ def __init__(self, cfg: VisualizationMarkersCfg): self.prim_path = cfg.prim_path self._count = len(cfg.markers) self._is_visible = True + self._has_visualized = False self._backends: list[object] = [] self._ensure_backends_initialized() @@ -226,7 +227,11 @@ def visualize( if value is not None: num_markers = value.shape[0] - if norm_marker_indices is None and num_markers != 0 and num_markers != self._count: + if ( + norm_marker_indices is None + and num_markers != 0 + and (not self._has_visualized or num_markers != self._count) + ): norm_marker_indices = torch.zeros(num_markers, dtype=torch.int32, device=target_device) elif norm_marker_indices is None and num_markers == 0: if all(value is None for value in (norm_translations, norm_orientations, norm_scales)): @@ -238,6 +243,7 @@ def visualize( if num_markers != 0: self._count = num_markers + self._has_visualized = True def __del__(self): for backend in getattr(self, "_backends", []): diff --git a/source/isaaclab/isaaclab/scene_data/scene_data_backend.py b/source/isaaclab/isaaclab/scene_data/scene_data_backend.py index 377b61e6d726..183b08246848 100644 --- a/source/isaaclab/isaaclab/scene_data/scene_data_backend.py +++ b/source/isaaclab/isaaclab/scene_data/scene_data_backend.py @@ -71,10 +71,6 @@ class Matrix44: class SceneDataBackend: - def set_interactive_scene(self, scene) -> None: - """Attach the active interactive scene, if the backend needs scene-owned entities.""" - pass - @property def transforms( self, diff --git a/source/isaaclab/isaaclab/sim/simulation_context.py b/source/isaaclab/isaaclab/sim/simulation_context.py index 777be01a1932..61118919ffd6 100644 --- a/source/isaaclab/isaaclab/sim/simulation_context.py +++ b/source/isaaclab/isaaclab/sim/simulation_context.py @@ -666,7 +666,6 @@ def register_interactive_scene(self, scene) -> None: self._interactive_scene = scene if self._scene_data_provider is not None: self._scene_data_provider.set_interactive_scene(scene) - self._scene_data_provider.backend.set_interactive_scene(scene) def get_scene_data_requirements(self) -> SceneDataRequirement: """Return scene-data requirements resolved from visualizers/renderers.""" diff --git a/source/isaaclab/test/markers/test_visualization_markers.py b/source/isaaclab/test/markers/test_visualization_markers.py index b9ae8387cf0f..1dbd25ad7780 100644 --- a/source/isaaclab/test/markers/test_visualization_markers.py +++ b/source/isaaclab/test/markers/test_visualization_markers.py @@ -144,6 +144,27 @@ def test_rendering_context_authors_visible_usd_point_instancer(sim): assert list(instancer.GetProtoIndicesAttr().Get()) == [0, 1] +def test_first_visualize_defaults_to_first_prototype_when_count_matches_prototypes(sim): + """Omitted marker indices should not preserve initialization prototype placeholders.""" + from pxr import UsdGeom + + sim._has_offscreen_render = True + config = VisualizationMarkersCfg( + prim_path="/World/Visuals/default_marker_indices", + markers={ + "frame": sim_utils.SphereCfg(radius=0.1), + "line": sim_utils.CuboidCfg(size=(0.1, 0.1, 0.1)), + }, + ) + test_marker = VisualizationMarkers(config) + + # This is the regression case from #5262: first marker count equals prototype count. + test_marker.visualize(translations=torch.tensor([[0.0, 0.0, 0.0], [0.2, 0.0, 0.0]], device=sim.device)) + + instancer = UsdGeom.PointInstancer(sim_utils.get_current_stage().GetPrimAtPath(test_marker.prim_path)) + assert list(instancer.GetProtoIndicesAttr().Get()) == [0, 0] + + def test_usd_marker(sim): """Test with marker from a USD.""" # create a marker @@ -253,6 +274,7 @@ class _FakeViewer: def __init__(self): self.calls = [] + self.show_contacts = False def is_paused(self): return False @@ -263,6 +285,9 @@ def begin_frame(self, sim_time): def log_state(self, state): self.calls.append(("log_state", state)) + def log_arrows(self, name, starts, ends, colors): + pass + def end_frame(self): self.calls.append(("end_frame",)) @@ -276,6 +301,10 @@ def get_state(scene_data_provider=None): def get_num_envs() -> int: return 4 + @staticmethod + def get_contacts(): + return None + def _fake_render_markers(viewer, visible_env_ids, num_envs): marker_calls.append((viewer, visible_env_ids, num_envs)) diff --git a/source/isaaclab/test/visualization/check_scene_xr_visualization.py b/source/isaaclab/test/xr_visualization/check_scene_xr_visualization.py similarity index 98% rename from source/isaaclab/test/visualization/check_scene_xr_visualization.py rename to source/isaaclab/test/xr_visualization/check_scene_xr_visualization.py index 0ecded8048a8..c8d25c51f140 100644 --- a/source/isaaclab/test/visualization/check_scene_xr_visualization.py +++ b/source/isaaclab/test/xr_visualization/check_scene_xr_visualization.py @@ -9,7 +9,7 @@ .. code-block:: bash # Usage - ./isaaclab.sh -p source/isaaclab/test/visualization/check_scene_visualization.py + ./isaaclab.sh -p source/isaaclab/test/xr_visualization/check_scene_xr_visualization.py """ diff --git a/source/isaaclab_physx/isaaclab_physx/physics/physx_manager.py b/source/isaaclab_physx/isaaclab_physx/physics/physx_manager.py index f8870db1acd7..36701a248f5f 100644 --- a/source/isaaclab_physx/isaaclab_physx/physics/physx_manager.py +++ b/source/isaaclab_physx/isaaclab_physx/physics/physx_manager.py @@ -160,8 +160,6 @@ def __init__(self): self._simulation_view: omni.physics.tensors.SimulationView | None = None self._rigid_body_view: omni.physics.tensors.RigidBodyView | None = None self._scene_data = SceneDataFormat.Transform() - self._interactive_scene: Any | None = None - self._articulation_body_paths: list[str] = [] @property def simulation_view(self) -> omni.physics.tensors.SimulationView | None: @@ -172,48 +170,13 @@ def simulation_view(self, simulation_view: omni.physics.tensors.SimulationView | self._simulation_view = simulation_view self._rigid_body_view = None - def set_interactive_scene(self, scene: Any) -> None: - """Attach the active scene so articulation link transforms can be read from asset views.""" - self._interactive_scene = scene - self._rigid_body_view = None - self._articulation_body_paths = [] - - def _get_articulation_body_paths(self) -> set[str]: - """Return body prim paths owned by scene articulations.""" - if self._interactive_scene is None: - self._articulation_body_paths = [] - return set() - - articulation_paths: list[str] = [] - for articulation in getattr(self._interactive_scene, "articulations", {}).values(): - root_paths = list(getattr(articulation.root_view, "prim_paths", [])) - body_names = list(getattr(articulation, "body_names", [])) - for root_path in root_paths: - asset_root_path = root_path.rsplit("/", 1)[0] - articulation_paths.extend(f"{asset_root_path}/{body_name}" for body_name in body_names) - - self._articulation_body_paths = articulation_paths - return set(articulation_paths) - - def _get_articulation_transforms(self) -> wp.array | None: - """Return flattened articulation link transforms from scene-owned articulation assets.""" - if self._interactive_scene is None: - return None - - transform_tensors = [] - for articulation in getattr(self._interactive_scene, "articulations", {}).values(): - transform_tensors.append(wp.to_torch(articulation.data.body_link_pose_w.warp).reshape(-1, 7)) - - if not transform_tensors: - return None - return wp.from_torch(torch.cat(transform_tensors, dim=0).contiguous(), dtype=wp.transformf) - def get_rigid_body_view(self) -> omni.physics.tensors.RigidBodyView | None: """Lazily create a rigid body view covering all rigid bodies in the scene. - Discovers rigid body prims by traversing the USD stage and converts - per-environment paths (``/World/envs/env_N/...``) into wildcard - patterns so a single PhysX view covers every environment instance. + Discovers exact rigid body prims by traversing USD, then compacts cloned + environment paths into wildcard patterns. If a rigid body name is also + used by a non-rigid prim, the exact path is kept to avoid PhysX resolving + the wildcard to the non-rigid prim. """ if self._rigid_body_view is not None: return self._rigid_body_view @@ -225,51 +188,51 @@ def get_rigid_body_view(self) -> omni.physics.tensors.RigidBodyView | None: if stage is None: return None - patterns: set[str] = set() - articulation_body_paths = self._get_articulation_body_paths() + rigid_body_paths: list[str] = [] + non_rigid_body_names: set[str] = set() for prim in stage.Traverse(): prim_path = prim.GetPath().pathString - if prim.HasAPI(UsdPhysics.RigidBodyAPI) and prim_path not in articulation_body_paths: + if prim.HasAPI(UsdPhysics.RigidBodyAPI): + rigid_body_paths.append(prim_path) + elif re.search(r"/World/envs/env_\d+/", prim_path): + non_rigid_body_names.add(prim_path.rsplit("/", 1)[-1]) + + patterns: set[str] = set() + exact_paths: list[str] = [] + for prim_path in rigid_body_paths: + body_name = prim_path.rsplit("/", 1)[-1] + if body_name in non_rigid_body_names: + exact_paths.append(prim_path) + else: patterns.add(re.sub(r"/World/envs/env_\d+", "/World/envs/env_*", prim_path)) - if not patterns: + body_paths = [*sorted(patterns), *exact_paths] + if not body_paths: return None - self._rigid_body_view = self._simulation_view.create_rigid_body_view(list(patterns)) + self._rigid_body_view = self._simulation_view.create_rigid_body_view(body_paths) return self._rigid_body_view @property def transforms(self) -> SceneDataFormat.Transform: """Return the current PhysX rigid body transforms as :class:`SceneDataFormat.Transform`.""" - transform_tensors = [] - articulation_transforms = self._get_articulation_transforms() - if articulation_transforms is not None: - transform_tensors.append(wp.to_torch(articulation_transforms).reshape(-1, 7)) if view := self.get_rigid_body_view(): - transform_tensors.append(wp.to_torch(view.get_transforms().view(wp.transformf)).reshape(-1, 7)) - if transform_tensors: - self._scene_data.transforms = wp.from_torch( - torch.cat(transform_tensors, dim=0).contiguous(), dtype=wp.transformf - ) + self._scene_data.transforms = view.get_transforms().view(wp.transformf) return self._scene_data @property def transform_count(self) -> int: """Return the number of rigid body transforms in the PhysX sim.""" - self._get_articulation_body_paths() - count = len(self._articulation_body_paths) if view := self.get_rigid_body_view(): - count += view.count - return count + return view.count + return 0 @property def transform_paths(self) -> list[str]: """Return the prim paths for each rigid body transform.""" - self._get_articulation_body_paths() - transform_paths = list(self._articulation_body_paths) if view := self.get_rigid_body_view(): - transform_paths.extend(list(view.prim_paths)) - return transform_paths + return list(view.prim_paths) + return [] class PhysxManager(PhysicsManager): From 3246c9c46b146f6a12f9330433afa829fc6206c4 Mon Sep 17 00:00:00 2001 From: Matthew Trepte Date: Thu, 4 Jun 2026 21:38:31 +0000 Subject: [PATCH 13/17] update comment --- source/isaaclab/isaaclab/sim/simulation_context.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/isaaclab/isaaclab/sim/simulation_context.py b/source/isaaclab/isaaclab/sim/simulation_context.py index 61118919ffd6..d157a72d521f 100644 --- a/source/isaaclab/isaaclab/sim/simulation_context.py +++ b/source/isaaclab/isaaclab/sim/simulation_context.py @@ -662,7 +662,7 @@ def get_scene_data_provider(self) -> SceneDataProvider: return self._scene_data_provider def register_interactive_scene(self, scene) -> None: - """Register the active scene so scene-data consumers can access scene-owned entities.""" + """Register the active scene so scene-data providers can expose scene-owned sensors.""" self._interactive_scene = scene if self._scene_data_provider is not None: self._scene_data_provider.set_interactive_scene(scene) From b4707165eab9b7c87a4a98472c6b5a828318683a Mon Sep 17 00:00:00 2001 From: Matthew Trepte Date: Thu, 4 Jun 2026 21:46:33 +0000 Subject: [PATCH 14/17] fix license check --- .github/workflows/license-exceptions.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/license-exceptions.json b/.github/workflows/license-exceptions.json index 95af798cd66e..e1e93a2ae7e0 100644 --- a/.github/workflows/license-exceptions.json +++ b/.github/workflows/license-exceptions.json @@ -470,7 +470,7 @@ }, { "package": "typing_extensions", - "license": "PSF-2.0", + "license": "Python Software Foundation License", "comment": "PSFL / OSRB" }, { From 243f43abb552943d9b9a1ef411dbecf62024f006 Mon Sep 17 00:00:00 2001 From: Matthew Trepte Date: Thu, 4 Jun 2026 22:09:22 +0000 Subject: [PATCH 15/17] nits --- source/isaaclab/isaaclab/sim/simulation_context.py | 2 +- source/isaaclab/test/markers/test_visualization_markers.py | 1 - source/isaaclab_visualizers/pyproject.toml | 2 ++ 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/source/isaaclab/isaaclab/sim/simulation_context.py b/source/isaaclab/isaaclab/sim/simulation_context.py index d157a72d521f..c068469ec599 100644 --- a/source/isaaclab/isaaclab/sim/simulation_context.py +++ b/source/isaaclab/isaaclab/sim/simulation_context.py @@ -662,7 +662,7 @@ def get_scene_data_provider(self) -> SceneDataProvider: return self._scene_data_provider def register_interactive_scene(self, scene) -> None: - """Register the active scene so scene-data providers can expose scene-owned sensors.""" + """Register the active scene so scene data providers can expose scene-owned sensors.""" self._interactive_scene = scene if self._scene_data_provider is not None: self._scene_data_provider.set_interactive_scene(scene) diff --git a/source/isaaclab/test/markers/test_visualization_markers.py b/source/isaaclab/test/markers/test_visualization_markers.py index 1dbd25ad7780..79a66443eca1 100644 --- a/source/isaaclab/test/markers/test_visualization_markers.py +++ b/source/isaaclab/test/markers/test_visualization_markers.py @@ -158,7 +158,6 @@ def test_first_visualize_defaults_to_first_prototype_when_count_matches_prototyp ) test_marker = VisualizationMarkers(config) - # This is the regression case from #5262: first marker count equals prototype count. test_marker.visualize(translations=torch.tensor([[0.0, 0.0, 0.0], [0.2, 0.0, 0.0]], device=sim.device)) instancer = UsdGeom.PointInstancer(sim_utils.get_current_stage().GetPrimAtPath(test_marker.prim_path)) diff --git a/source/isaaclab_visualizers/pyproject.toml b/source/isaaclab_visualizers/pyproject.toml index 9c439e7c327a..2601cafa418d 100644 --- a/source/isaaclab_visualizers/pyproject.toml +++ b/source/isaaclab_visualizers/pyproject.toml @@ -36,6 +36,7 @@ newton = [ ] rerun = [ "newton[sim] @ git+https://github.com/newton-physics/newton.git@v1.2.0", + # Match rerun-sdk's supported Arrow stack and avoid resolver drift across environments. "pyarrow==22.0.0", "rerun-sdk>=0.29.0", ] @@ -47,6 +48,7 @@ all = [ "imgui-bundle>=1.92.5", "newton[sim] @ git+https://github.com/newton-physics/newton.git@v1.2.0", "PyOpenGL-accelerate", + # Match rerun-sdk's supported Arrow stack and avoid resolver drift across environments. "pyarrow==22.0.0", "rerun-sdk>=0.29.0", "typing-extensions>=4.15.0", From e88eb84ca553741d00457bbe5689e4c2bb831860 Mon Sep 17 00:00:00 2001 From: Matthew Trepte Date: Fri, 5 Jun 2026 22:56:03 +0000 Subject: [PATCH 16/17] fix test --- source/isaaclab/isaaclab/scene_data/scene_data_provider.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/isaaclab/isaaclab/scene_data/scene_data_provider.py b/source/isaaclab/isaaclab/scene_data/scene_data_provider.py index 69606010badb..ba4c051920e1 100644 --- a/source/isaaclab/isaaclab/scene_data/scene_data_provider.py +++ b/source/isaaclab/isaaclab/scene_data/scene_data_provider.py @@ -16,7 +16,6 @@ from pxr import UsdGeom import isaaclab.sim as sim_utils -from isaaclab.sensors.contact_sensor import BaseContactSensor from .scene_data_backend import SceneDataBackend, SceneDataFormat @@ -61,6 +60,8 @@ def get_contact_sensors(self) -> dict[str, Any]: """Return Isaac Lab contact sensors keyed by scene sensor name.""" if self._interactive_scene is None: return {} + from isaaclab.sensors.contact_sensor import BaseContactSensor + return { name: sensor for name, sensor in getattr(self._interactive_scene, "sensors", {}).items() From 2988b5bf4ccaeeffc6f4f46d8def36e91e5cae85 Mon Sep 17 00:00:00 2001 From: Matthew Trepte Date: Fri, 5 Jun 2026 23:45:23 +0000 Subject: [PATCH 17/17] remove duplicate link which causes CI failures --- docs/index.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 720c96676980..2a304b8f8d7b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -130,7 +130,6 @@ Table of Contents source/features/hydra source/features/multi_gpu source/features/population_based_training - Tiled Rendering source/features/ray source/features/reproducibility