From efbd8d536d3a39d89984eeffc0c1ee6452eae410 Mon Sep 17 00:00:00 2001 From: klakhi Date: Fri, 26 Sep 2025 05:43:29 +0000 Subject: [PATCH] temp gen non int flow 2.3 release --- isaaclab.bat | 24 ++- tools/template/non_interactive.py | 235 ++++++++++++++++++++++++++++++ 2 files changed, 255 insertions(+), 4 deletions(-) create mode 100644 tools/template/non_interactive.py diff --git a/isaaclab.bat b/isaaclab.bat index 0a6cd9f7361..e31a462c48b 100644 --- a/isaaclab.bat +++ b/isaaclab.bat @@ -571,11 +571,15 @@ if "%arg%"=="-i" ( ) else if "%arg%"=="-n" ( rem run the template generator script call :extract_python_exe + rem detect non-interactive flag while reconstructing arguments + set "isNonInteractive=0" set "allArgs=" + set "skip=" for %%a in (%*) do ( REM Append each argument to the variable, skip the first one if defined skip ( - set "allArgs=!allArgs! %%a" + if /I "%%~a"=="--non-interactive" set "isNonInteractive=1" + set "allArgs=!allArgs! ^"%%~a^"" ) else ( set "skip=1" ) @@ -585,16 +589,24 @@ if "%arg%"=="-i" ( echo. echo [INFO] Running template generator... echo. - call !python_exe! tools\template\cli.py !allArgs! + if "!isNonInteractive!"=="1" ( + call !python_exe! tools\template\non_interactive.py !allArgs! + ) else ( + call !python_exe! tools\template\cli.py !allArgs! + ) goto :end ) else if "%arg%"=="--new" ( rem run the template generator script call :extract_python_exe + rem detect non-interactive flag while reconstructing arguments + set "isNonInteractive=0" set "allArgs=" + set "skip=" for %%a in (%*) do ( REM Append each argument to the variable, skip the first one if defined skip ( - set "allArgs=!allArgs! %%a" + if /I "%%~a"=="--non-interactive" set "isNonInteractive=1" + set "allArgs=!allArgs! ^"%%~a^"" ) else ( set "skip=1" ) @@ -604,7 +616,11 @@ if "%arg%"=="-i" ( echo. echo [INFO] Running template generator... echo. - call !python_exe! tools\template\cli.py !allArgs! + if "!isNonInteractive!"=="1" ( + call !python_exe! tools\template\non_interactive.py !allArgs! + ) else ( + call !python_exe! tools\template\cli.py !allArgs! + ) goto :end ) else if "%arg%"=="-t" ( rem run the python provided by Isaac Sim diff --git a/tools/template/non_interactive.py b/tools/template/non_interactive.py new file mode 100644 index 00000000000..841d76c7ae4 --- /dev/null +++ b/tools/template/non_interactive.py @@ -0,0 +1,235 @@ +# Copyright (c) 2022-2025, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +from __future__ import annotations + +import argparse +import os + +from common import ROOT_DIR +from generator import generate, get_algorithms_per_rl_library + + +def _parse_workflow_arg(item: str) -> dict[str, str]: + raw = item.strip().lower() + # Enforce strict underscore format: "_" + if "_" not in raw or any(sep in raw for sep in ("|", ":", " ")): + raise ValueError( + "Invalid workflow format. Use underscore format like 'direct_single_agent' or 'manager-based_single_agent'" + ) + name_token, type_token_raw = raw.split("_", 1) + type_token = type_token_raw.replace("_", "-") # normalize to single-agent / multi-agent + + if name_token not in {"direct", "manager-based"}: + raise ValueError(f"Invalid workflow name: {name_token}. Allowed: 'direct' or 'manager-based'") + if type_token not in {"single-agent", "multi-agent"}: + raise ValueError(f"Invalid workflow type: {type_token}. Allowed: 'single-agent' or 'multi-agent'") + + return {"name": name_token, "type": type_token} + + +def _validate_external_path(path: str) -> None: + if os.path.abspath(path).startswith(os.path.abspath(ROOT_DIR)): + raise ValueError("External project path cannot be within the Isaac Lab project") + + +def main(argv: list[str] | None = None) -> None: + """ + Non-interactive entrypoint for the template generator workflow. + + Parses command-line flags, builds the specification dict, and calls generate(). + This avoids any interactive prompts or dependencies on Inquirer-based flow. + """ + + parser = argparse.ArgumentParser( + description=( + "Non-interactive template generator for Isaac Lab. Use flags to choose workflows, RL libraries, " + "and algorithms." + ), + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + supported_workflows = [ + "direct_single_agent", + "direct_multi_agent", + "manager-based_single_agent", + ] + supported_rl_libraries = ["rl_games", "rsl_rl", "skrl", "sb3"] + # All known algorithms across libraries (lowercase for consistent CLI input) + _all_algos_map = get_algorithms_per_rl_library(True, True) + rl_algo_choices = sorted({algo.lower() for algos in _all_algos_map.values() for algo in algos}) + + parser.add_argument( + "--task-type", + "--task_type", + type=str, + required=True, + choices=["External", "Internal"], + help=( + "Where to create the project: 'External' (requires --project-path and must be outside this repo) " + "or 'Internal' (generated within the Isaac Lab repo)." + ), + ) + parser.add_argument( + "--project-path", + "--project_path", + type=str, + help=( + "Destination path for an external project. Required when --task-type External. " + "Must not be within the Isaac Lab project." + ), + ) + parser.add_argument( + "--project-name", + "--project_name", + type=str, + required=True, + help="Project identifier used in generated files (letters, digits, underscores)", + ) + parser.add_argument( + "--workflow", + action="append", + required=True, + type=str.lower, + choices=[*([w.lower() for w in supported_workflows]), "all"], + help=( + "Workflow(s) to generate. Repeat this flag to include multiple, or use 'all'. " + "Allowed values: direct_single_agent, direct_multi_agent, manager-based_single_agent. " + "Values are case-insensitive; underscores in the type are normalized (e.g., single_agent → single-agent)." + ), + ) + parser.add_argument( + "--rl-library", + "--rl_library", + type=str.lower, + required=True, + choices=[*supported_rl_libraries, "all"], + help=( + "RL library to target or 'all'. Choices are filtered by the selected workflows; libraries without " + "supported algorithms under those workflows are omitted." + ), + ) + parser.add_argument( + "--rl-algorithm", + "--rl_algorithm", + type=str.lower, + required=False, + default=None, + choices=[*rl_algo_choices, "all"], + help=( + "RL algorithm to use. If skipped, auto-selects when exactly one algorithm is valid for the chosen " + "workflows and library. Use 'all' to include every supported algorithm per selected library." + ), + ) + + args, _ = parser.parse_known_args(argv) + + is_external = args.task_type.lower() == "external" + if is_external: + if not args.project_path: + raise ValueError("--project-path is required for External task type") + _validate_external_path(args.project_path) + project_path = args.project_path + else: + project_path = None + + if not args.project_name.isidentifier(): + raise ValueError("--project-name must be a valid identifier (letters, numbers, underscores)") + + # Expand workflows: allow "all" to mean all supported workflows + if any(item == "all" for item in args.workflow): + workflows = [_parse_workflow_arg(item) for item in supported_workflows] + else: + workflows = [_parse_workflow_arg(item) for item in args.workflow] + single_agent = any(wf["type"] == "single-agent" for wf in workflows) + multi_agent = any(wf["type"] == "multi-agent" for wf in workflows) + + # Filter allowed algorithms per RL library under given workflow capabilities + algos_map = get_algorithms_per_rl_library(single_agent, multi_agent) + + # Expand RL libraries: allow "all" to mean all libraries that have at least one supported algorithm + rl_lib_input = args.rl_library.strip().lower() + if rl_lib_input == "all": + selected_libs = [lib for lib, algos in algos_map.items() if len(algos) > 0] + if not selected_libs: + raise ValueError( + "No RL libraries are supported under the selected workflows. Please choose different workflows." + ) + else: + selected_libs = [rl_lib_input] + if rl_lib_input not in algos_map: + raise ValueError(f"Unknown RL library: {rl_lib_input}") + # Pre-compute supported algorithms per selected library (lowercased) + supported_algos_per_lib = {lib: [a.lower() for a in algos_map.get(lib, [])] for lib in selected_libs} + + # Auto-select algorithm if not provided + rl_algo_input = args.rl_algorithm.strip().lower() if args.rl_algorithm is not None else None + + rl_libraries_spec = [] + if rl_algo_input is None: + # If a single library is selected, preserve previous behavior + if len(selected_libs) == 1: + lib = selected_libs[0] + supported_algos = supported_algos_per_lib.get(lib, []) + if len(supported_algos) == 0: + raise ValueError( + f"No algorithms are supported for {lib} under the selected workflows. " + "Please choose a different combination." + ) + if len(supported_algos) > 1: + allowed = ", ".join(supported_algos) + raise ValueError( + "Multiple algorithms are valid for the selected workflows and library. " + f"Please specify one using --rl-algorithm or use --rl-algorithm all. Allowed: {allowed}" + ) + rl_libraries_spec.append({"name": lib, "algorithms": [supported_algos[0]]}) + else: + # Multiple libraries selected. If each has exactly one algorithm, auto-select; otherwise require explicit choice. + libs_with_multi = [lib for lib, algos in supported_algos_per_lib.items() if len(algos) > 1] + if libs_with_multi: + details = "; ".join(f"{lib}: {', '.join(supported_algos_per_lib[lib])}" for lib in libs_with_multi) + raise ValueError( + "Multiple algorithms are valid for one or more libraries under the selected workflows. " + "Please specify --rl-algorithm or use --rl-algorithm all. Details: " + + details + ) + for lib, algos in supported_algos_per_lib.items(): + if not algos: + continue + rl_libraries_spec.append({"name": lib, "algorithms": [algos[0]]}) + elif rl_algo_input == "all": + # Include all supported algorithms per selected library + for lib, algos in supported_algos_per_lib.items(): + if not algos: + continue + rl_libraries_spec.append({"name": lib, "algorithms": algos}) + if not rl_libraries_spec: + raise ValueError("No algorithms are supported under the selected workflows.") + else: + # Specific algorithm requested: include only libraries that support it + matching_libs = [] + for lib, algos in supported_algos_per_lib.items(): + if rl_algo_input in algos: + matching_libs.append(lib) + rl_libraries_spec.append({"name": lib, "algorithms": [rl_algo_input]}) + if not matching_libs: + allowed_desc = {lib: algos for lib, algos in supported_algos_per_lib.items() if algos} + raise ValueError( + f"Algorithm '{args.rl_algorithm}' is not supported under the selected workflows for the chosen" + f" libraries. Supported per library: {allowed_desc}" + ) + + specification = { + "external": is_external, + "path": project_path, + "name": args.project_name, + "workflows": workflows, + "rl_libraries": rl_libraries_spec, + } + + generate(specification) + + +if __name__ == "__main__": + main() \ No newline at end of file