Skip to content

[PresetCLI] Add typed preset selection with task-aware help#5587

Merged
kellyguo11 merged 31 commits into
isaac-sim:developfrom
hujc7:jichuanh/preset-cli-basic
May 15, 2026
Merged

[PresetCLI] Add typed preset selection with task-aware help#5587
kellyguo11 merged 31 commits into
isaac-sim:developfrom
hujc7:jichuanh/preset-cli-basic

Conversation

@hujc7
Copy link
Copy Markdown
Collaborator

@hujc7 hujc7 commented May 12, 2026

Summary

Adds typed preset selection via Hydra-style tokens — physics=NAME / renderer=NAME / presets=NAME[,...] — that fold into the existing presets=<csv> Hydra-decorator flow. Makes --task=X --help list the actual PresetCfg variants present in that task's env_cfg, bucketed by typed target. Adopted in all 16 Hydra-using scripts (rl_games/sb3/skrl/rsl_rl train+play, environments/*, benchmarks/*, sim2sim_transfer, leapp/rsl_rl/export).

API shape

parser = argparse.ArgumentParser(...)
# ... script-specific args ...
add_launcher_args(parser)
args_cli, hydra_args = setup_preset_cli(parser)
sys.argv = [sys.argv[0]] + hydra_args

setup_preset_cli returns (args, hydra_argv) without mutating sys.argv. It registers no argparse flags for preset selection — the typed selectors are recognized as Hydra-style tokens in the parse_known_args remainder and folded into hydra_argv[0] as a single presets=<csv> token.

Grammar

python train.py --task=X  physics=newton_mjwarp  renderer=newton_renderer  presets=albedo,depth
  • physics=NAME — typed selector for PhysicsCfg variants
  • renderer=NAME — typed selector for RendererCfg variants
  • presets=NAME[,NAME,...] — broadcast: applied to every matching PresetCfg

All three fold into one presets=<csv> token: presets=newton_mjwarp,newton_renderer,albedo,depth.

The grammar matches Hydra's, so the same line can carry path-targeted overrides (env.sim.dt=0.001) that flow through untouched.

Namespace contract

No preset selector is registered with argparse, so the parsed args namespace gains no physics / renderer / presets attribute. AppLauncher's name-based forwarding (set(_SIM_APP_CFG_TYPES) & set(vars(args)), app_launcher.py:681) therefore cannot pick up a preset value and push it into SimulationApp.config — the historical --rendererconfig["renderer"]None.lower() crash class is structurally impossible. Two regression tests lock the contract.

Help text layout

--task=Isaac-Cartpole-v0 --help renders each selector with its available variants inline directly below it (bullets aligned with the description column):

preset selection:
  Select named PresetCfg alternatives via Hydra-style overrides (key=value, no leading dashes):
      physics=NAME              (typed) selects a PhysicsCfg variant. Available:
                                - newton_kamino
                                - newton_mjwarp
                                - physx
      renderer=NAME             (typed) selects a RendererCfg variant. Available:
                                (none)
      presets=NAME[,NAME,...]   broadcast: applied to every matching PresetCfg. Available:
                                (none)

  Hydra also accepts path-targeted overrides like env.sim.physics=NAME.

Typed variants appear only under their own typed selector. The presets= listing shows only DOMAIN-bucket variants (cfgs whose type doesn't subclass any typed target's base class).

Without --task, each row shows just the selector + description and the section adds a Pass --task=X hint on its own paragraph.

Test plan

  • pytest source/isaaclab_tasks/test/test_preset_cli.py — 24 tests pass. Coverage: enum wiring, token folding/dedupe/passthrough, _ArgvHelper semantics, type-based bucketing, all four help-text branches (parametrized), no-sys.argv-mutation contract, namespace-clean contract, AppLauncher intersection contract, hydra_args[0] preserves the presets= token for benchmark telemetry.
  • pytest source/isaaclab_tasks/test/test_hydra.py — 76 tests pass; legacy-alias FutureWarning behavior unchanged.
  • pre-commit clean.
  • Manual: --task=Isaac-Cartpole-v0 --help and --task=Isaac-Cartpole-RGB-Camera-Direct-v0 --help render correctly.
  • Manual: physics=newton_mjwarp renderer=newton_renderer presets=albedo folds into one presets=<csv> token at hydra_argv[0].
  • Manual: unknown name → grouped error from resolver; legacy alias newtonFutureWarning and resolves to newton_mjwarp.

hujc7 added 3 commits May 12, 2026 06:23
A thin argparse adapter on top of the existing Hydra preset flow. Adds
``--physics=NAME``, ``--renderer=NAME``, and ``--presets=NAME[,NAME,...]``
flags that fold their values into the same ``presets=<csv>`` token that
:func:`isaaclab_tasks.utils.hydra.resolve_presets` already consumes. The
resolver, name validation, alias rewriting, error formatting, and per-
``PresetCfg`` resolution are unchanged -- the new module is purely a
discoverability layer:

* ``--help`` now lists the canonical preset names registered with
  ``@register`` plus the :class:`PresetCfg` field names declared on the
  task selected by ``--task=<X>``. Users no longer need to type and
  fail to learn what's available.
* Unknown names emit a stderr hint and pass through; the existing
  ``hydra._format_unknown_presets_error`` produces the rich path-grouped
  error at resolve time as before.

New module ``isaaclab.utils.preset_registry`` exposes ``PresetTarget``
(closed enum of CLI categories) and ``PresetRegistry`` with the
``register`` decorator. Backend cfg classes declare themselves once,
e.g. ``@register(PresetTarget.PHYSICS, "physx")``. Legacy alias data
moves onto the enum members so per-target tables are the single source
hydra's resolver consults.

``hydra.py`` is not modified by this PR; resolution semantics are
unchanged.
setup_cli now peeks sys.argv for --task before argparse runs, loads
that task's env_cfg (safe pre-AppLauncher, enforced by
test_env_cfg_no_forbidden_imports), walks PresetCfg variants via the
existing collect_presets, and lists them in --help -- bucketed by
target via PresetRegistry so typed flags (--physics, --renderer) list
only their kind, while --presets lists every variant.

The registry is no longer a help-text source; it now serves as a
naming-convention hint that informs target bucketing. Without --task,
help tells the user to pass one. The env_cfg load is gated on --help
being in argv so normal training runs skip the work.
hydra.py had a literal _LEGACY_PRESET_ALIASES dict identical to what
PresetTarget.<target>.legacy_aliases now exposes via
PresetTarget.all_legacy_aliases(). Two sources of truth invite drift and
silence the registry's role as canonical home for these aliases.

Removes the local dict and its Newton-rationale comment (moved to the
PresetTarget.PHYSICS docstring where the data lives). Six call sites in
hydra now call PresetTarget.all_legacy_aliases() directly. Existing tests
across test_hydra.py, test_preset_cli.py, and test_newton_solver_preset_names.py
all pass; FutureWarning behavior for legacy 'newton' and 'kamino' names
is unchanged.
@github-actions github-actions Bot added documentation Improvements or additions to documentation asset New asset feature or request isaac-sim Related to Isaac Sim team isaac-mimic Related to Isaac Mimic team infrastructure labels May 12, 2026
@hujc7 hujc7 changed the base branch from main to develop May 12, 2026 08:00
Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Isaac Lab Review Bot

Summary

This PR introduces a well-designed typed CLI layer (--physics=, --renderer=, --presets=) for task variant selection, replacing loose Hydra overrides with discoverable, typed selectors. The implementation is clean, well-documented, and provides excellent user experience with task-aware help messages.


📝 Incremental Review Update (f531b98f02999d)

Merge from develop + conflict resolution

✅ Previous Issues Resolved

P1 Missing import sys after refactor — All affected files now have import sys restored. The merge from develop fixed this.

⚠️ Remaining Minor Issues

P2 PresetTarget.all_legacy_aliases() double-call — Still present at lines 581-582 of source/isaaclab_tasks/isaaclab_tasks/utils/hydra.py. The function builds a fresh dict on every call; calling it twice for lookup (membership test + value retrieval) is wasteful. Minor optimization opportunity, non-blocking.

📋 Changes in This Push

  1. CI fix (a5eb9add): Unsets HUB__ARGS__DETECT_ONLY in Docker test containers to fix OmniHub startup and prevent slow Nucleus asset retries.

  2. Preset CLI integration (f02999d0): Conflict resolution adapts preset CLI to the restructured export.py:

    • setup_preset_cli gains optional argv parameter for in-process test paths
    • parse_export_args uses the new signature
    • run_export_with_hydra folds typed selectors before Hydra

The changes look clean and the conflict resolution correctly integrates the preset CLI with the refactored export flow.


Automated review by Isaac Lab Review Bot

hujc7 added 3 commits May 12, 2026 19:51
setup_cli previously called AppLauncher.add_app_launcher_args internally.
That forced every adopting script to remove its existing add_launcher_args
call to avoid a duplicate-argparse-option error -- a silent footgun for
the 16 existing Hydra-using scripts that all call add_launcher_args
today.

Callers now register AppLauncher flags themselves (via the existing
add_launcher_args wrapper in isaaclab_tasks.utils.sim_launcher) before
calling setup_cli. setup_cli becomes single-purpose (preset CLI only),
and migration into setup_cli is a strict addition next to the existing
add_launcher_args call -- no removal needed.

Side effect: the stub_app_launcher pytest fixture that previously
monkey-patched isaaclab.app to dodge Kit init is no longer needed.
Removed it and the 'import types' that only fed it; 11 test signatures
lose the dummy parameter. 18 tests still pass.
The function is specifically about preset CLI setup (typed --physics /
--renderer / --presets flags + parse + sys.argv fold); the bare
'setup_cli' name suggested broader CLI orchestration. The new name
makes the scope explicit and parallels add_launcher_args's narrow,
descriptive style.

Also exposes setup_preset_cli at the package level: importable as
'from isaaclab_tasks.utils import setup_preset_cli' alongside the
existing add_launcher_args / hydra_task_config / resolve_task_config.
Replaces the parse + sys.argv-clearing pattern in all 16 scripts that
drive the Hydra-decorator preset flow with a single setup_preset_cli(parser)
call alongside the existing add_launcher_args(parser).

Now python <script>.py --physics newton_mjwarp --renderer newton_renderer
--presets albedo64 works end-to-end through the Hydra resolver.

Pattern breakdown:
- 10 scripts: pure 3->1 collapse (random_agent, zero_agent, rl_games
  train/play, sb3 train/play, skrl train/play, leapp/export, sim2sim
  rsl_rl_transfer). Each had add_launcher_args + parse_known_args +
  sys.argv mutation; replaced with add_launcher_args + setup_preset_cli.
- 4 benchmark scripts: capture sys.argv[1:] into hydra_args after
  setup_preset_cli so get_preset_string() telemetry keeps working.
- 2 scripts (rsl_rl train/play): preserve external_callback intersection
  by applying it after setup_preset_cli, with the "presets=..." token
  at sys.argv[1] held aside so typed flag selections always reach Hydra
  regardless of what the callback chose to drop. (Slight behavior
  improvement over the pre-migration state, where a callback that didn't
  echo back the presets token would silently nullify typed selections.)

Verified: 18 preset_cli tests + 79 hydra tests pass. Pre-commit clean.
@github-actions github-actions Bot added the isaac-lab Related to Isaac Lab team label May 12, 2026
hujc7 added 4 commits May 12, 2026 20:46
…argv

setup_preset_cli used to mutate sys.argv as a side effect. Moving the
mutation out and to the caller lets scripts run argv-aware logic
(e.g. the --external_callback hook in rsl_rl/{train,play}.py that
re-reads sys.argv) BEFORE the folded form replaces the user's typed
flags. Without this, the callback would see "presets=newton_mjwarp"
instead of "--physics newton_mjwarp" and fail to recognize the user's
intent.

setup_preset_cli now returns (args, hydra_argv). Caller assigns
sys.argv = [sys.argv[0]] + hydra_argv when ready.

Also drops a defensive try/except in _enumerate_variants -- a bad
--task value or a broken cfg now propagates its natural exception
instead of being buried as a string inside --help text. Users see
the real error and fix the real cause.

_peek_task returns the LAST --task value (matching argparse's
store-action semantics for repeated flags).

Tests: existing flag-folding tests rewritten to assert the tuple
return + sys.argv non-mutation. New regression tests lock in:
sys.argv stays unmutated, hydra_argv keeps the presets token for
telemetry consumers, _peek_task is last-wins.
setup_preset_cli no longer mutates sys.argv. Each script now unpacks
(args, hydra_args) from the call and assigns sys.argv itself. For the
14 simple scripts (incl. benchmarks) this is a mechanical change: one
extra "sys.argv = [sys.argv[0]] + hydra_args" line. The benchmarks
keep the same hydra_args local name they already used for the
get_preset_string telemetry call.

For rsl_rl/{train,play}.py the change has real effect: the
--external_callback hook now runs while sys.argv is still the user's
original command line, so callbacks that re-parse sys.argv see the
typed --physics / --renderer / --presets flags rather than the folded
form. The intersection filter then runs on hydra_args (the post-fold
list) before assigning sys.argv, preserving the "presets=..." token.
A canonical preset name registered under two different PresetTargets
would silently drop one binding: the help-time bucketing in preset_cli
flattens the registry to a single name -> target dict, so whichever
target's import landed last would win.

Extend the duplicate-binding check so it scans every target, not just
the one being assigned. Same-(target, name, class) re-imports remain
idempotent; any other reuse raises RuntimeError at decoration time so
the conflict surfaces at the point of definition, not at silently
wrong help output downstream.
New public module; per the repo's "keep documentation up-to-date" rule
in AGENTS.md, list it in the autosummary and add an automodule section
so the API docs page picks up PresetTarget, PresetRegistry, and the
register decorator.
@hujc7 hujc7 changed the title [PresetCLI] Make preset selection explicit and discoverable via typed CLI flags [PresetCLI] Add typed preset selection with task-aware help May 12, 2026
sys.argv is module-level state; these private helpers were only ever
called with sys.argv internally, so the parameter was boilerplate.
Reading sys.argv directly removes the noise at the two call sites in
setup_preset_cli and shortens the function signatures.

Tests that previously passed a synthetic argv list now use monkeypatch
on sys.argv -- consistent with the rest of the test file, which
already does this for the setup_preset_cli tests.
from isaaclab.utils.preset_registry import PresetTarget, register


@register(PresetTarget.PHYSICS, "ovphysx")
Copy link
Copy Markdown
Collaborator

@ooctipus ooctipus May 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if hydra/preset should ever be a dependency in non-isaaclab-task, when I was designing the hydra.py. I wanted to be only task-level dependency and not a cross package dependency.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main problem is that without this thin register, we would need to have a hard coded list in hydra for those predefined names or types, which could potentially drift. IMO, It's more natural to put things where it's first appearing, so it's easier to maintain or scale.

Copy link
Copy Markdown
Collaborator

@ooctipus ooctipus May 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my main concern is that preset is really just build around idea of user configuration customization, and the issue we are having is that its a bit too flexible that is confusing. Preset has 0 value beyond task-time variation, and becomes very concerning when it leaks to core package. If you want to add a registry like this, I would think it around Idea beyond just isaaclab-task useage. Maybe a general tagging system is what you want, and preset is just leveraging the tagging system?

Copy link
Copy Markdown
Collaborator Author

@hujc7 hujc7 May 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me check if there's a way to work without the register. To some extent yes I think, adding meta info to types so it's easier to identify them.

_bucket_variants_by_target used a flat name -> target lookup built
from PresetRegistry.names_for. That's a weak check: a task-local
preset accidentally reusing a backend's canonical name would route
to the wrong typed flag.

Replace with isinstance against PresetRegistry.classes_for(target).
Each (name, cfg) pair routes to whichever typed target's registered
class tree the cfg's type matches; cfgs that match no registered
target's classes (primitives, task-local non-backend types) fall
into DOMAIN. Subclassing works naturally via isinstance.

Return shape is unchanged -- still {PresetTarget: set[name]}. Names
remain the values, class type is purely the routing mechanism.

PresetRegistry gains a classes_for(target) classmethod that returns
a tuple suitable for isinstance() checks.

Test updates:
- test_bucket_variants now uses inline-registered mock classes and
  asserts cfg-instance type drives the bucket assignment.
- test_help_with_task_shows_actual_variants uses registered-class
  instances in its fake env_cfg so the typed-flag help renders
  PHYSICS/RENDERER variants correctly under type-based routing.
@hujc7 hujc7 marked this pull request as ready for review May 12, 2026 21:48
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 12, 2026

Greptile Summary

This PR introduces typed --physics, --renderer, and --presets CLI flags that fold into the existing presets=<csv> Hydra-decorator flow, along with a new PresetRegistry in isaaclab.utils that lets backend cfg classes self-register canonical names, and task-aware --help output. 16 Hydra-driving scripts are migrated to the new setup_preset_cli helper.

  • New modules: preset_registry.py (enum + @register decorator) and preset_cli.py (pure translator with no parse-time validation), covered by 18 unit tests.
  • Hydra refactor: _LEGACY_PRESET_ALIASES dict deleted; PresetTarget.all_legacy_aliases() is now the single source of truth consumed by 6 call sites in hydra.py.
  • Script adoption: 7 of the 16 migrated scripts had import sys removed but still call sys.argv at module level, which will crash every one of those scripts with a NameError before any training logic runs.

Confidence Score: 3/5

Not safe to merge — seven scripts will crash on every invocation with a NameError before any training logic runs.

The core library additions (preset_registry.py, preset_cli.py, the hydra refactor) are well-designed and well-tested. The problem is in the adoption layer: import sys was stripped from seven scripts while the module-level sys.argv assignment was left in place. Each of those scripts fails immediately on import — the random_agent, zero_agent, both rl_games scripts, both skrl scripts, and the sb3 play script are all broken.

scripts/environments/random_agent.py, scripts/environments/zero_agent.py, scripts/reinforcement_learning/rl_games/play.py, scripts/reinforcement_learning/rl_games/train.py, scripts/reinforcement_learning/sb3/play.py, scripts/reinforcement_learning/skrl/play.py, scripts/reinforcement_learning/skrl/train.py — all missing import sys.

Important Files Changed

Filename Overview
source/isaaclab/isaaclab/utils/preset_registry.py New module introducing PresetTarget enum and PresetRegistry with @register decorator; clean design with idempotent re-import guard and cross-target duplicate detection.
source/isaaclab_tasks/isaaclab_tasks/utils/preset_cli.py New typed CLI translator that folds --physics/--renderer/--presets into a single presets=<csv> Hydra token; correctly defers sys.argv mutation to callers and does no validation at parse time.
source/isaaclab_tasks/isaaclab_tasks/utils/hydra.py Replaces local _LEGACY_PRESET_ALIASES dict with PresetTarget.all_legacy_aliases() at six call sites; minor inefficiency at the apply_overrides site where the dict is built twice per lookup.
source/isaaclab_tasks/test/test_preset_cli.py 18 well-structured unit tests covering registry decoration, token folding, deduplication, passthrough behavior, and task-aware help enumeration.
scripts/environments/random_agent.py Removed import sys but line 26 still references sys.argv; script will crash with NameError on every invocation.
scripts/environments/zero_agent.py Same missing import sys as random_agent.py — sys.argv on line 26 will NameError at module load.
scripts/reinforcement_learning/rl_games/play.py Removed import sys but uses sys.argv on line 68; will raise NameError on every run.
scripts/reinforcement_learning/rl_games/train.py Removed import sys but uses sys.argv on line 73; will raise NameError on every run.
scripts/reinforcement_learning/sb3/play.py Removed import sys but uses sys.argv on line 70; will raise NameError on every run.
scripts/reinforcement_learning/skrl/play.py Removed import sys but uses sys.argv on line 84; will raise NameError on every run.
scripts/reinforcement_learning/skrl/train.py Removed import sys but uses sys.argv at module level; will raise NameError on every run.
scripts/reinforcement_learning/rsl_rl/train.py Correctly preserves external_callback intersection logic while protecting the new folded presets= token; sys import retained.
scripts/reinforcement_learning/rsl_rl/play.py Same correct external-callback pattern as train.py; sys import retained.

Sequence Diagram

sequenceDiagram
    participant Script
    participant setup_preset_cli
    participant argparse
    participant _peek_task
    participant _enumerate_variants
    participant hydra_resolver

    Script->>setup_preset_cli: setup_preset_cli(parser)
    setup_preset_cli->>_peek_task: peek sys.argv for --task
    _peek_task-->>setup_preset_cli: task_name (or None)
    alt --help in sys.argv AND task found
        setup_preset_cli->>_enumerate_variants: load env_cfg, collect_presets
        _enumerate_variants-->>setup_preset_cli: "{PresetTarget: set[name]}"
    end
    setup_preset_cli->>argparse: add --physics/--renderer/--presets flags
    setup_preset_cli->>argparse: parse_known_args()
    argparse-->>setup_preset_cli: (args, remaining)
    setup_preset_cli->>setup_preset_cli: "fold typed flags + presets= tokens into presets=csv"
    setup_preset_cli-->>Script: (args, hydra_argv)
    Script->>Script: "sys.argv = [sys.argv[0]] + hydra_argv"
    Script->>hydra_resolver: resolve_presets via Hydra decorator
    hydra_resolver-->>Script: validated cfg
Loading

Reviews (1): Last reviewed commit: "Bucket help-time preset variants by cfg ..." | Re-trigger Greptile

Comment thread scripts/environments/random_agent.py Outdated

# pass remaining args to Hydra
args_cli, hydra_args = setup_preset_cli(parser)
sys.argv = [sys.argv[0]] + hydra_args
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing import sys after refactor

import sys was removed in this PR, but sys.argv is still used on this line at module level. Running this script will raise NameError: name 'sys' is not defined immediately on import. The same issue affects scripts/environments/zero_agent.py, scripts/reinforcement_learning/rl_games/play.py, scripts/reinforcement_learning/rl_games/train.py, scripts/reinforcement_learning/sb3/play.py, scripts/reinforcement_learning/skrl/play.py, and scripts/reinforcement_learning/skrl/train.py — all seven had import sys stripped but still call sys.argv.

Comment on lines +580 to +581
if name in PresetTarget.all_legacy_aliases():
replacement = PresetTarget.all_legacy_aliases()[name]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 PresetTarget.all_legacy_aliases() builds a fresh dict on every call. In apply_overrides it is called twice in a row for a single name lookup — once for the membership test and again for the value retrieval. Assign the result once.

Suggested change
if name in PresetTarget.all_legacy_aliases():
replacement = PresetTarget.all_legacy_aliases()[name]
legacy_aliases = PresetTarget.all_legacy_aliases()
if name in legacy_aliases:
replacement = legacy_aliases[name]

In the earlier setup_preset_cli migration commit, sys.argv assignments
were removed from these scripts (setup_preset_cli used to mutate
sys.argv internally). Ruff's autofix then removed the now-unused
'import sys'. When the later API change required the scripts to assign
sys.argv themselves again, the import wasn't restored -- making 14
F821 'Undefined name sys' errors that only surfaced when pre-commit
was run on the full diff.

Also adds four section dividers in preset_cli.py grouping the public
entry point, help-text rendering, argv inspection, and help-time
variant enumeration so the module is easier to navigate.
@ooctipus
Copy link
Copy Markdown
Collaborator

ooctipus commented May 12, 2026

Thanks for taking this work from me@hujc7 !
Few questions:

If I am understanding correctly, preset_registry is just assign naming convention to each subclass? if that's the case can we build something that more along idea of generic tagging system, rather than blending with idea of preset? preset is basically just leveraging existing name and avoiding flexibile naming

we prototyped some naming idea in https://github.com/isaac-sim/IsaacLab/blob/develop/source/isaaclab_tasks/isaaclab_tasks/utils/presets.py

from isaaclab_tasks.utils import PresetCfg


@configclass
class MultiBackendRendererCfg(PresetCfg):
    default: IsaacRtxRendererCfg = IsaacRtxRendererCfg()
    newton_renderer: NewtonWarpRendererCfg = NewtonWarpRendererCfg()
    ovrtx_renderer: OVRTXRendererCfg = OVRTXRendererCfg()
    isaacsim_rtx_renderer = default

and if user import as their renderer, the environment is ensured to have consistent naming.

My second questions is:

how would adding this preset_registry help the isaaclab_tasks? as far as I am understanding we still need to list each option with its implementation like code snippet above, I was hoping for if we have this tagging system some of these burden can be lifted, but it doesn't seem to help in that direction? or correct me if I am wrong

@fatimaanes fatimaanes self-requested a review May 13, 2026 00:45
hujc7 added 4 commits May 13, 2026 01:42
Three review-driven cleanups, no behavioral change:

* PresetTarget.all_legacy_aliases() now @classmethod @functools.cache --
  the merged dict is built once on first call and reused, replacing the
  per-call rebuild that hydra hit six times for membership tests and
  indexed lookups.

* _peek_task() and _help_requested() collapse into a single _ArgvHelper
  class that scans sys.argv once and exposes task_name and help_requested
  as attributes. setup_preset_cli now uses one argv pass instead of two.

* PresetTarget gains a "Bucketing contract" paragraph explaining that
  isinstance(cfg, target.base_classes) is the typed-flag routing rule:
  a non-PhysicsCfg/RendererCfg variant still resolves correctly at
  runtime (hydra matches by name) but bucket to --presets DOMAIN in
  --help. Documented so the design decision is visible.

rsl_rl/{train,play}.py migration tightened to the minimum diff vs
pre-PR: only the import line gains setup_preset_cli and
parser.parse_known_args() becomes setup_preset_cli(parser); the
external_callback flow, list_intersection call, variable name, and
comment text are restored to their original form.
The "Pass `--task=X` along with `--help`" sentence was repeated three
times in --help output, once per typed flag. Hoist it to the preset
selection group's description so it prints once; per-flag help is now
just the label.

Add a parametrized test that locks the exact phrase produced by each
_help_text branch (zero-variants, typed-only, DOMAIN-only, three-bucket
mix) so wording changes are deliberate. Tighten the existing no-task
test to assert the hint appears exactly once.
setup_preset_cli previously registered --physics / --renderer with
default argparse dests (args.physics, args.renderer), which collide
with AppLauncher's SimulationApp config keys. Specifically, AppLauncher
materializes SimulationApp config via

    {k: launcher_args[k]
     for k in _SIM_APP_CFG_TYPES.keys() & launcher_args.keys()}

so any args.renderer value -- including the argparse default of None --
is forwarded to SimulationApp.config["renderer"], which then crashes
with ``AttributeError: 'NoneType' object has no attribute 'lower'``
inside ``_set_render_settings``. This breaks every Kit-launching script
that uses both add_app_launcher_args and setup_preset_cli (the LEAPP
export tests in test_rsl_rl_export_flow.py surfaced it on PR CI).

Use dest=f"{label}_preset" on the typed flag registrations so the
namespace key (args.renderer_preset) doesn't shadow AppLauncher's
field. User-facing flag names stay --physics / --renderer; only the
internal namespace dest changes. Update the fold loop and the existing
test that introspected args.physics. Add a focused regression test
that asserts both ``getattr(args, 'renderer_preset', None)`` carries
the value and ``hasattr(args, 'renderer')`` is False, so the contract
fails the moment the dest mapping regresses.
hujc7 and others added 9 commits May 14, 2026 04:00
setup_preset_cli now strips physics/renderer/presets off the parsed
Namespace via vars(args).pop(...) right after parse_known_args, instead
of registering them under <label>_preset dests.

Reverts the previous dest-rename workaround, which split the contract
into two surfaces: the user-facing flag (--renderer NAME) and a hidden
attribute name (args.renderer_preset). Callers and tests had to know
about that translation, and any future script that read args.renderer
by accident would silently get a preset name string.

Pop-after-parse keeps a single naming convention: --renderer at the CLI,
and nothing at all on the namespace afterward. The argparse registration
is unchanged, so --help still lists the typed flags; the values are
consumed during the fold into hydra_argv and the namespace AppLauncher
inspects on _SIM_APP_CFG_TYPES & vars(args) carries no preset keys.

Add test_setup_does_not_leak_into_app_launcher_sim_app_intersection,
which asserts exactly that intersection rather than checking specific
attribute names. Lifts the regression coverage from the symptom (three
named attrs) to the bug class (anything in PresetTarget leaking into
SimulationApp.config forwarding).
Help output for the preset section now formats each variant on its own
line under a bulleted ``Available:`` block, and the ``Pass --task=X``
discoverability hint sits on its own paragraph between the section
description and the flag list. Long variant lists were previously
collapsed into a single comma-separated wrap that buried individual
names; the new layout makes each variant visually distinct.

Add ``_PresetHelpFormatter`` -- an argparse formatter that wraps each
blank-line-separated paragraph in the section description while honoring
explicit newlines inside argument help strings. The default
``HelpFormatter`` strips newlines from both surfaces;
``RawDescriptionHelpFormatter`` preserves them in descriptions but drops
wrapping; ``RawTextHelpFormatter`` preserves them everywhere but drops
wrapping. None of the built-ins gives "preserve newlines AND wrap" on
both surfaces, which is what the new layout needs. Arguments without
embedded newlines (every AppLauncher flag) wrap exactly as before.

Update the parametrized help-text tests to match the new bullet format
and the more consistent broadcast-suffix phrasing for the empty DOMAIN
case.
setup_preset_cli no longer registers --physics / --renderer / --presets
as argparse flags. Instead it scans the parse_known_args remainder for
Hydra-style key=value tokens (physics=NAME, renderer=NAME,
presets=NAME[,NAME,...]) and folds them into the same presets=<csv>
token the existing Hydra-decorator resolver already consumes.

Eliminates the AppLauncher namespace collision at the source: with no
argparse registration, the parsed namespace gains no preset attributes,
so the set(_SIM_APP_CFG_TYPES) & set(vars(args)) intersection at
app_launcher.py:681 cannot pick up a preset value and forward it into
SimulationApp.config. The pop-after-parse workaround is therefore
removed.

Help-text discovery moves entirely onto the section description: when
--task=X is in argv alongside --help, the description lists the
PresetCfg variants present in the task's env_cfg, bucketed by typed
target. Variants render one per line as bulleted lists; the
``presets:`` bucket shows only DOMAIN-bucket variants (typed names
already appear under their own typed bucket).

Update tests for the hydra-only grammar. The new
test_namespace_carries_no_preset_attributes locks the contract that
makes the AppLauncher collision impossible.

Shorten the changelog fragment to a brief summary; the prose lives in
the docstring and PR description.
Replace ``--physics`` / ``--renderer`` / ``--presets`` references with the
Hydra-style ``physics=NAME`` / ``renderer=NAME`` / ``presets=NAME[,...]``
form. No behavior change.
Restore the prior design: each typed selector entry lists its available
variants directly below it (the same layout the -- flag version used),
not in a separate "Available in this task:" block.
Bullets now sit at column 33, the same column where each selector's
inline ``(typed)`` / ``broadcast:`` description starts. Reads more like a
standard argparse argument-description block.
…rings

Replace the three hardcoded header strings ("physics=NAME ... PhysicsCfg
variant", etc.) with a loop over ``PresetTarget``. Each row's syntax and
description are derived from the enum:

* selector key  -> ``target.value``
* typed vs DOMAIN distinction  -> ``target.base_classes``
* cfg class name in the description  -> ``target.base_classes[0].__name__``

Column widths are now named constants (``_SELECTOR_COL``, ``_DESC_GAP``,
``_ROW_PREFIX``); the bullet indent is computed from them so adjusting the
table width keeps the variant bullets aligned.

Also rename ``PresetTarget.DOMAIN.value`` from ``"domain"`` to ``"presets"``
so the enum value matches the CLI selector key, and dispatch in
``setup_preset_cli`` via ``PresetTarget.DOMAIN.value`` instead of a
hardcoded ``"presets"`` literal.
The selector-table constants and the helpers that drive each row are now
grouped under _DescriptionBuilder. Placement signals scope: they're
private to the description rendering, used nowhere else, and the column
widths only make sense alongside the row formatter that reads them.

Also drop a one-shot local variable that didn't earn a name.
@hujc7
Copy link
Copy Markdown
Collaborator Author

hujc7 commented May 14, 2026

/greptile review

setup_preset_cli no longer folds. It registers the help description and
calls parse_known_args, returning the raw remainder with preset tokens
in their original physics= / renderer= / presets= form.

A new fold_preset_tokens helper carries the fold logic. Scripts call it
explicitly at the assignment line:

    args_cli, remaining = setup_preset_cli(parser)
    sys.argv = [sys.argv[0]] + fold_preset_tokens(remaining)

Fixes the rsl_rl --external_callback bug: list_intersection used to compare
a post-fold remainder (presets=NAME) against a pre-fold callback return
(physics=NAME) and silently dropped the preset by string mismatch. With
parse and fold separated, the intersection runs in pre-fold space where
vocabularies match; the fold runs once at the end on the surviving tokens.

Benchmark scripts fold once into hydra_args and reuse that name for both
sys.argv and get_preset_string -- the telemetry helper continues to read
the canonical presets= form unchanged.

Tests reorganized into setup_preset_cli (parse-only) and fold_preset_tokens
sections, plus two bug-fix regressions that pin both the correct
intersection-then-fold order and the wrong fold-then-intersection shape.
@hujc7
Copy link
Copy Markdown
Collaborator Author

hujc7 commented May 14, 2026

/greptile review

future release.
"""

RENDERER = ("renderer", (RendererCfg,))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any options for "renderer"?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you elaborate the question? Not too sure what it means

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never mind. I was mistaken. Sorry for the inconvenience.

Conflict resolution touched two files:

* scripts/reinforcement_learning/leapp/rsl_rl/export.py
  Develop (isaac-sim#5633) restructured export.py into create_arg_parser /
  parse_export_args / run_export_with_hydra / main_cli functions so the
  test suite can drive the export flow in-process. Plug the preset CLI
  into the new shape:
  - parse_export_args uses setup_preset_cli(parser, argv) to attach the
    preset-selection help group and parse argv (now forwarded to
    parse_known_args for the in-process test path)
  - run_export_with_hydra folds typed selectors via fold_preset_tokens
    before assigning sys.argv for the Hydra decorator

* source/isaaclab_tasks/isaaclab_tasks/utils/preset_cli.py
  setup_preset_cli grows an optional argv: list[str] | None = None
  parameter forwarded to parse_known_args. Defaults to None so existing
  module-level callers keep working unchanged. Help-time variant
  introspection still reads sys.argv since --help only fires from the
  interactive command line.
@hujc7 hujc7 force-pushed the jichuanh/preset-cli-basic branch from f531b98 to f02999d Compare May 15, 2026 17:24
@kellyguo11 kellyguo11 moved this to In review in Isaac Lab May 15, 2026
@kellyguo11 kellyguo11 moved this from In review to Ready to merge in Isaac Lab May 15, 2026
@kellyguo11 kellyguo11 merged commit 112f3c0 into isaac-sim:develop May 15, 2026
33 of 34 checks passed
@github-project-automation github-project-automation Bot moved this from Ready to merge to Done in Isaac Lab May 15, 2026
hujc7 added a commit to hujc7/IsaacLab that referenced this pull request May 15, 2026
Resolved 21 preset-CLI conflicts by taking develop's version: the
preset-cli-basic work landed via isaac-sim#5587 (squash-merged into develop),
which superseded the work-in-progress state on this branch with the
final canonical signatures (``setup_preset_cli(parser, argv=None)``,
``fold_preset_tokens``, etc.). Cartpole task-config commits on top of
preset-cli-basic are unaffected.
hujc7 added a commit to hujc7/IsaacLab that referenced this pull request May 16, 2026
Collapse 35 per-variant Cartpole gym task IDs into 4 consolidated tasks
that select the variant via the typed preset CLI from isaac-sim#5587:

- Isaac-Cartpole-Camera-Direct-v0 (subsumes 7 Direct-backend perception IDs)
- Isaac-Cartpole-Camera-v0 (subsumes 4 manager-based perception IDs)
- Isaac-Cartpole-Showcase-Direct-v0 (subsumes 15 proprioceptive showcase IDs)
- Isaac-Cartpole-Camera-Showcase-Direct-v0 (subsumes 9 camera showcase IDs)

Variant selection moves from the gym ID to ``presets=<name>`` (plus
``physics=`` / ``renderer=`` on the Direct-backend camera task, and
``--agent=`` where the per-shape skrl / rl_games yaml differs from the
default). Each retired ID stays registered for one release with an
env_cfg_entry_point that emits a DeprecationWarning naming the new task
and the equivalent CLI tokens, then returns the corresponding cfg.

The shim machinery is factored into a new helper:

  isaaclab_tasks.utils.deprecated_task_alias(
      old_task_id, new_command, consolidated_cfg_path, cfg_factory=None
  )

which wraps the warning emission plus lazy cfg resolution behind the
``"module:Name"`` entry-point string convention gym uses elsewhere.

The consolidated PresetCfg classes live alongside their per-variant
siblings in the existing *_env_cfg.py files; no new wrapper modules.
PresetCfg's docstring now documents the underscore-prefix convention for
class-local helpers (the resolver already skips them).

Full per-ID migration table is in the PR description.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

asset New asset feature or request documentation Improvements or additions to documentation infrastructure isaac-lab Related to Isaac Lab team isaac-mimic Related to Isaac Mimic team isaac-sim Related to Isaac Sim team

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants