Skip to content
Open
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
2 changes: 1 addition & 1 deletion source/isaaclab_tasks/config/extension.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]

# Note: Semantic Versioning is used: https://semver.org/
version = "1.5.11"
version = "1.5.12"

# Description
title = "Isaac Lab Environments"
Expand Down
15 changes: 15 additions & 0 deletions source/isaaclab_tasks/docs/CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
Changelog
---------

1.5.12 (2026-03-15)
~~~~~~~~~~~~~~~~~~~

Fixed
^^^^^

* Fixed :func:`~isaaclab_tasks.utils.hydra.collect_presets` not discovering
presets inside nested dicts (e.g. ``EventTerm.params.terms.*.params``).

Added
^^^^^

* Added unit tests for the Hydra preset system.


1.5.11 (2026-03-13)
~~~~~~~~~~~~~~~~~~~

Expand Down
33 changes: 27 additions & 6 deletions source/isaaclab_tasks/isaaclab_tasks/utils/hydra.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,21 @@ def collect_presets(cfg, path: str = "") -> dict:
else:
result.update(collect_presets(value, child_path))
elif isinstance(value, dict):
for dict_key, dict_val in value.items():
if hasattr(dict_val, "__dataclass_fields__"):
result.update(collect_presets(dict_val, f"{child_path}.{dict_key}"))
_collect_from_dict(value, child_path, result)

return result


def _collect_from_dict(d: dict, path: str, result: dict) -> None:
"""Recursively collect presets from dict values, including nested dicts."""
for dict_key, dict_val in d.items():
child_path = f"{path}.{dict_key}"
if hasattr(dict_val, "__dataclass_fields__"):
result.update(collect_presets(dict_val, child_path))
elif isinstance(dict_val, dict):
_collect_from_dict(dict_val, child_path, result)


def resolve_task_config(task_name: str, agent_cfg_entry_point: str):
"""Resolve env and agent configs with Hydra overrides, presets, and scalars fully applied.

Expand Down Expand Up @@ -274,12 +282,25 @@ def resolve_preset_defaults(cfg):
elif hasattr(value, "__dataclass_fields__"):
resolve_preset_defaults(value)
elif isinstance(value, dict):
for dict_val in value.values():
if hasattr(dict_val, "__dataclass_fields__"):
resolve_preset_defaults(dict_val)
_resolve_from_dict(value)
return cfg


def _resolve_from_dict(d: dict) -> None:
"""Recursively resolve preset defaults in dict values, including nested dicts."""
for dict_key, dict_val in d.items():
if isinstance(dict_val, PresetCfg) and hasattr(dict_val, "__dataclass_fields__"):
default = getattr(dict_val, "default", None)
if default is not None:
d[dict_key] = default
if hasattr(default, "__dataclass_fields__"):
resolve_preset_defaults(default)
elif hasattr(dict_val, "__dataclass_fields__"):
resolve_preset_defaults(dict_val)
elif isinstance(dict_val, dict):
_resolve_from_dict(dict_val)


def register_task(task_name: str, agent_entry: str) -> tuple:
"""Load configs, collect presets recursively, register base config to Hydra.

Expand Down
Loading
Loading