Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

5 changes: 0 additions & 5 deletions source/isaaclab/changelog.d/delay-buffer-invalid-state.rst

This file was deleted.

This file was deleted.

8 changes: 8 additions & 0 deletions source/isaaclab/changelog.d/visualizer-error-reason.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Fixed
^^^^^

* Fixed ``--visualizer newton`` (the deprecated alias for ``newton_gl``) always raising
``RuntimeError: Explicitly requested visualizer(s) [...] could not be configured`` even though it
resolved successfully. :meth:`SimulationContext._resolve_visualizer_cfgs` compared the raw,
possibly-aliased CLI string against the resolved config's canonical ``visualizer_type``, which
never matched for an aliased request.
18 changes: 18 additions & 0 deletions source/isaaclab/docs/CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
Changelog
---------

27.0.1 (2026-09-23)
~~~~~~~~~~~~~~~~~~~

Fixed
^^^^^

* Fixed ``DelayBuffer.set_time_lag`` leaving invalid per-batch delays in the buffer by validating requested
values before assignment, without cloning the live lag tensor.
* Fixed ``isaaclab --editor`` failing or writing invalid JSON when the interpreter path contained
backslashes. The interpreter path in ``.vscode/settings.json`` was JSON-encoded.
* Removed the deprecated Black, Flake8, and Jedi settings from the fallback VS Code settings
generated by ``isaaclab --editor`` in favor of Ruff.
* Fixed :class:`~isaaclab.envs.utils.video_recorder_cfg.VideoRecorderCfg` accepting clip schedules the
recorder cannot honor. Environment config validation now rejects a non-positive ``video_length`` or
``frame_stride`` and a negative ``video_interval`` or ``step_offset`` instead of silently writing
one-frame clips, dividing by zero, or recording nothing.


27.0.0 (2026-09-22)
~~~~~~~~~~~~~~~~~~~

Expand Down
16 changes: 12 additions & 4 deletions source/isaaclab/isaaclab/sim/simulation_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,10 @@ def _resolve_visualizer_cfgs(self) -> list[Any]:
cli_explicit = self._is_cli_visualizer_explicit()
cli_disable_all = self._is_cli_visualizer_disable_all()

# cli_requested holds raw, possibly-aliased strings (e.g. "newton"); resolved cfgs carry
# the canonical visualizer_type (e.g. "newton_gl"). Compare via this instead of directly.
canonical_requested = [_VISUALIZER_ALIASES.get(t, t) for t in cli_requested]

if cli_disable_all:
resolved = []
elif not cli_explicit:
Expand All @@ -582,15 +586,15 @@ def _resolve_visualizer_cfgs(self) -> list[Any]:
self._apply_visualizer_cli_overrides(resolved)
else:
# CLI selection is explicit: keep only requested cfg types, then add defaults for missing.
cli_requested_set = set(cli_requested)
cli_requested_set = set(canonical_requested)
resolved = [cfg for cfg in visualizer_cfgs if getattr(cfg, "visualizer_type", None) in cli_requested_set]
for cfg in resolved:
self._apply_default_visualizer_cfg(cfg)
existing_types = {getattr(cfg, "visualizer_type", None) for cfg in resolved}
for viz_type in cli_requested:
if viz_type not in existing_types and viz_type in _VISUALIZER_TYPES:
if _VISUALIZER_ALIASES.get(viz_type, viz_type) not in existing_types:
resolved.extend(self._create_default_visualizer_configs([viz_type]))
existing_types.add(viz_type)
existing_types.add(_VISUALIZER_ALIASES.get(viz_type, viz_type))
self._apply_visualizer_cli_overrides(resolved)

# When visualizers were explicitly requested via CLI, verify all
Expand All @@ -599,7 +603,11 @@ def _resolve_visualizer_cfgs(self) -> list[Any]:
# skips.
if cli_explicit and cli_requested:
resolved_types = {getattr(cfg, "visualizer_type", None) for cfg in resolved}
missing = [t for t in cli_requested if t not in resolved_types]
missing = [
t
for t, canonical in zip(cli_requested, canonical_requested, strict=True)
if canonical not in resolved_types
]
if missing:
install_hints = " ".join(
_get_visualizer_install_hint(visualizer_type)
Expand Down
2 changes: 1 addition & 1 deletion source/isaaclab/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "isaaclab"
version = "27.0.0"
version = "27.0.1"
description = "Extension providing main framework interfaces and abstractions for robot learning."
license = {text = "BSD-3-Clause"}
authors = [{name = "Isaac Lab Project Developers"}]
Expand Down
66 changes: 65 additions & 1 deletion source/isaaclab/test/sim/test_simulation_context_visualizers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1098,9 +1098,73 @@ def test_explicit_partial_valid_types_raises_for_invalid():
}
ctx = _make_context_with_settings(settings)

with pytest.raises(RuntimeError, match="bogus_viz"):
with pytest.raises(RuntimeError) as exc_info:
ctx._create_visualizers()

message = str(exc_info.value)
assert "bogus_viz" in message
# The successfully-resolved deprecated alias must not also be reported as missing.
assert "'newton'" not in message


def test_explicit_deprecated_alias_alone_does_not_raise():
"""Requesting only the deprecated 'newton' alias resolves successfully and does not raise,
even though the resolved cfg carries the canonical 'newton_gl' type."""
settings = {
"/isaaclab/visualizer/types": "newton",
"/isaaclab/visualizer/explicit": True,
"/isaaclab/visualizer/disable_all": False,
"/isaaclab/visualizer/max_visible_envs": None,
}
ctx = _make_context_with_settings(settings)

with pytest.warns(DeprecationWarning, match="newton.*deprecated.*newton_gl"):
ctx._create_visualizers()

assert len(ctx._pending_visualizers) == 1


def test_explicit_deprecated_alias_matches_existing_cfg_by_canonical_type():
"""Requesting 'newton' via CLI when cfg.visualizer_cfgs already has a customized 'newton_gl'
config selects and returns that exact instance, rather than discarding it and building a
fresh default -- exercising the branch that filters pre-existing cfgs by canonical type."""
existing_cfg = NewtonGLVisualizerCfg(background_color=(0.4, 0.5, 0.6))
settings = {
"/isaaclab/visualizer/types": "newton",
"/isaaclab/visualizer/explicit": True,
"/isaaclab/visualizer/disable_all": False,
"/isaaclab/visualizer/max_visible_envs": None,
}
ctx = _make_context_with_settings(settings, visualizer_cfgs=[existing_cfg])

# No alias-resolution warning: the pre-existing cfg already satisfies the canonical type.
cfgs = ctx._resolve_visualizer_cfgs()

assert len(cfgs) == 1
assert cfgs[0] is existing_cfg
assert cfgs[0].background_color == (0.4, 0.5, 0.6)


def test_explicit_existing_cfg_plus_failing_requested_type_raises_for_the_failure():
"""A pre-existing cfg satisfies one requested type; a second requested type that cannot be
resolved still raises, exercising the branch that extends pre-existing cfgs with freshly-created
defaults for the remaining requested types."""
existing_cfg = _FakeVisualizerCfg("kit")
settings = {
"/isaaclab/visualizer/types": "kit,bogus_viz",
"/isaaclab/visualizer/explicit": True,
"/isaaclab/visualizer/disable_all": False,
"/isaaclab/visualizer/max_visible_envs": None,
}
ctx = _make_context_with_settings(settings, visualizer_cfgs=[existing_cfg])

with pytest.raises(RuntimeError) as exc_info:
ctx._resolve_visualizer_cfgs()
message = str(exc_info.value)
# 'kit' was satisfied by the pre-existing cfg, so only the unresolved type is reported missing.
assert "['bogus_viz']" in message
assert "'kit':" not in message


def test_deprecated_newton_alias_warns_and_resolves_to_newton_gl():
"""Requesting 'newton' via CLI emits DeprecationWarning and resolves to a NewtonGLVisualizerCfg."""
Expand Down
5 changes: 5 additions & 0 deletions source/isaaclab_ov/changelog.d/ovrtx-cache-frame-keys.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Changed
^^^^^^^

* Cached OVRTX render-var frame keys during camera initialization, avoiding repeated version checks and
path construction during frame processing. Existing camera output behavior and APIs remained unchanged.

This file was deleted.

23 changes: 0 additions & 23 deletions source/isaaclab_ov/changelog.d/ovrtx_camera_frame_keys.rst

This file was deleted.

31 changes: 31 additions & 0 deletions source/isaaclab_ov/docs/CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,37 @@
Changelog
---------

3.3.1 (2026-09-23)
~~~~~~~~~~~~~~~~~~

Changed
^^^^^^^

* **Breaking:** Centralized camera identity and simplified USD helper inputs. Pass ``render_scope_name``
explicitly to ``OVRTXCameraRenderData`` and render-var configuration helpers. Pass ``spec`` and ``render_data``
to ``build_render_scope_usd`` and ``build_render_product_as_string`` instead of individual camera fields.
The product builder returns a complete USD layer string; pass it directly to USD loaders without adding
a header or default-prim metadata, and use ``OVRTXCameraRenderData.render_product_path`` instead of
unpacking a path from the builder's return value.

Removed
^^^^^^^

* **Breaking:** Removed ``build_render_var_frame_keys`` and ``RENDER_VAR_FRAME_KEYS``.
Use ``render_var_prim_names_by_source()`` for the static source-to-prim-name mapping. Direct frame readers
must use source names on OVRTX 0.4 and ``/<camera scope>/Vars/<prim name>`` paths on OVRTX 0.5 and later.

Fixed
^^^^^

* Fixed OVRTX 0.5 camera output and segmentation metadata lookups to use each camera's
authored RenderVar paths, preventing empty images and missing segmentation maps.
Kept OVRTX 0.4 support by resolving frame keys in the shared output lookup helper.
* Fixed OVRTX cloning of homogeneous scenes, where every spawner is single-variant and the clone plan replicates the
environment roots themselves. The exported stage retained those roots, so cloning targeted prims that already
existed. The env roots were trimmed for such plans and kept for plans whose rows target prims beneath them.


3.3.0 (2026-09-22)
~~~~~~~~~~~~~~~~~~

Expand Down
36 changes: 15 additions & 21 deletions source/isaaclab_ov/isaaclab_ov/renderers/ovrtx_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@

if TYPE_CHECKING:
from isaaclab_ppisp import PpispPipeline
from ovrtx import AttributeBinding, FrameOutput, RenderVarOutput
from ovrtx import AttributeBinding

from isaaclab.renderers.base_renderer import VisualMaterialBatch
from isaaclab.sensors.camera.camera_data import CameraData
Expand Down Expand Up @@ -290,6 +290,11 @@ def __init__(self, spec: CameraRenderSpec, device, render_scope_name: str):
self.render_scope_name = render_scope_name
self.render_product_name = "RenderProduct"
self.render_product_path = f"/{render_scope_name}/{self.render_product_name}"
self.render_var_keys: dict[str, str] = (
{source: f"/{render_scope_name}/Vars/{name}" for source, name in _RENDER_VAR_PRIM_NAMES.items()}
if uses_prim_path_render_vars(OVRTX_VERSION)
else {source: source for source in _RENDER_VAR_PRIM_NAMES}
)
self.camera_xform_binding = None
self.camera_xform_query = None
self.resources = contextlib.ExitStack()
Expand Down Expand Up @@ -1276,17 +1281,6 @@ def _generate_random_colors_from_ids(self, input_ids: wp.array, output_colors: w
)
return output_colors

@staticmethod
def _get_render_var_output(
render_data: OVRTXCameraRenderData, frame: FrameOutput, source_name: str
) -> RenderVarOutput | None:
"""Resolve a render-var source name to the installed OVRTX frame key and read its output."""
render_var_key = source_name
if uses_prim_path_render_vars(OVRTX_VERSION):
prim_name = _RENDER_VAR_PRIM_NAMES[source_name]
render_var_key = f"/{render_data.render_scope_name}/Vars/{prim_name}"
return frame.render_vars.get(render_var_key)

@contextlib.contextmanager
def _map_render_var_to_dlpack(self, render_var: Any) -> Iterator[wp.array]:
"""Map ``render_var`` for CUDA reads and yield it as a Warp array.
Expand Down Expand Up @@ -1344,7 +1338,7 @@ def _process_id_segmentation_render_var(
buffer_key: Data type key into ``output_buffers``.
colorize: If True, IDs are mapped to RGBA colors; otherwise raw uint32 IDs are copied.
"""
render_var = self._get_render_var_output(render_data, frame, render_var_key)
render_var = frame.render_vars.get(render_data.render_var_keys[render_var_key])
if render_var is None or buffer_key not in output_buffers:
return

Expand Down Expand Up @@ -1385,7 +1379,7 @@ def _process_semantic_id_map(self, render_data: OVRTXCameraRenderData, frame) ->
render_data: OVRTX render data for the current frame.
frame: OVRTX frame holding the mapped render vars.
"""
semantic_id_map = self._get_render_var_output(render_data, frame, _SEMANTIC_ID_MAP_VAR)
semantic_id_map = frame.render_vars.get(render_data.render_var_keys[_SEMANTIC_ID_MAP_VAR])
if semantic_id_map is None:
return

Expand Down Expand Up @@ -1423,7 +1417,7 @@ def _process_instance_segmentation_maps(self, render_data: OVRTXCameraRenderData
frame: OVRTX frame holding the mapped render vars.
"""
resolved = {
key: self._get_render_var_output(render_data, frame, key) for key in _INSTANCE_SEGMENTATION_MAP_VARS
key: frame.render_vars.get(render_data.render_var_keys[key]) for key in _INSTANCE_SEGMENTATION_MAP_VARS
}
missing = [key for key, render_var in resolved.items() if render_var is None]
if missing:
Expand Down Expand Up @@ -1561,7 +1555,7 @@ def _process_render_frame(self, render_data: OVRTXCameraRenderData, frame, outpu
# is available, so without this a missing SemanticIdMap on a later frame would leave a stale mapping.
render_data.renderer_info.clear()

ldr_color = self._get_render_var_output(render_data, frame, _LDR_COLOR_VAR)
ldr_color = frame.render_vars.get(render_data.render_var_keys[_LDR_COLOR_VAR])
if ldr_color is not None:
buffer_key = None

Expand All @@ -1580,7 +1574,7 @@ def _process_render_frame(self, render_data: OVRTXCameraRenderData, frame, outpu
self._extract_rgba_tiles(render_data, tiled_data, output_buffers, buffer_key)

for depth_var, buffer_keys in _DEPTH_VAR_BUFFER_KEYS.items():
depth_render_var = self._get_render_var_output(render_data, frame, depth_var)
depth_render_var = frame.render_vars.get(render_data.render_var_keys[depth_var])
if depth_render_var is None:
continue
if not any(buffer_key in output_buffers for buffer_key in buffer_keys):
Expand All @@ -1592,12 +1586,12 @@ def _process_render_frame(self, render_data: OVRTXCameraRenderData, frame, outpu
)
self._extract_depth_tiles(render_data, tiled_depth_data, output_buffers, buffer_keys)

albedo_var = self._get_render_var_output(render_data, frame, _ALBEDO_VAR)
albedo_var = frame.render_vars.get(render_data.render_var_keys[_ALBEDO_VAR])
if albedo_var is not None and "albedo" in output_buffers:
with self._map_render_var_to_dlpack(albedo_var) as tiled_albedo_data:
self._extract_rgba_tiles(render_data, tiled_albedo_data, output_buffers, "albedo", suffix="albedo")

hdr_color = self._get_render_var_output(render_data, frame, _HDR_COLOR_VAR)
hdr_color = frame.render_vars.get(render_data.render_var_keys[_HDR_COLOR_VAR])
if hdr_color is not None and "rgb_hdr" in output_buffers:
with self._map_render_var_to_dlpack(hdr_color) as tiled_hdr_data:
tiled_hdr_data = self._prepare_ppisp_hdr_source(render_data, tiled_hdr_data, output_buffers)
Expand Down Expand Up @@ -1628,15 +1622,15 @@ def _process_render_frame(self, render_data: OVRTXCameraRenderData, frame, outpu
if "instance_segmentation" in output_buffers:
self._process_instance_segmentation_maps(render_data, frame)

normals_var = self._get_render_var_output(render_data, frame, _NORMALS_VAR)
normals_var = frame.render_vars.get(render_data.render_var_keys[_NORMALS_VAR])
if normals_var is not None and "normals" in output_buffers:
with self._map_render_var_to_dlpack(normals_var) as tiled_normals_data:
self._launch_extract_all_tiles(render_data, tiled_normals_data, output_buffers["normals"])

# For motion vectors, extract only the first two (u, v) channels from the tiled buffer.
# Note: mirrors the Isaac RTX renderer's handling of the "TargetMotionSD" AOV
# (check: https://github.com/isaac-sim/IsaacLab/issues/2003).
motion_var = self._get_render_var_output(render_data, frame, _MOTION_VECTORS_VAR)
motion_var = frame.render_vars.get(render_data.render_var_keys[_MOTION_VECTORS_VAR])
if motion_var is not None and "motion_vectors" in output_buffers:
with self._map_render_var_to_dlpack(motion_var) as tiled_motion_vectors_data:
self._launch_extract_all_tiles(render_data, tiled_motion_vectors_data, output_buffers["motion_vectors"])
Expand Down
2 changes: 1 addition & 1 deletion source/isaaclab_ov/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "isaaclab_ov"
version = "3.3.0"
version = "3.3.1"
description = "Extension providing Omniverse rendering and OVPhysX simulation integrations."
license = {text = "BSD-3-Clause"}
authors = [{name = "Isaac Lab Project Developers"}]
Expand Down
Loading
Loading