From 0b9122d781a2fc3a725f66943a0bbf5bb2499b84 Mon Sep 17 00:00:00 2001 From: klakhi Date: Wed, 25 Feb 2026 12:56:56 +0530 Subject: [PATCH 1/2] Collect frame names in a set (so each name is stored once) and log sorted(non_identity_offset_frames) so the message shows unique frame names only. --- .../frame_transformer/frame_transformer.py | 8 +- tools/template/non_interactive.py | 234 ++++++++++++++++++ 2 files changed, 238 insertions(+), 4 deletions(-) create mode 100644 tools/template/non_interactive.py diff --git a/source/isaaclab_physx/isaaclab_physx/sensors/frame_transformer/frame_transformer.py b/source/isaaclab_physx/isaaclab_physx/sensors/frame_transformer/frame_transformer.py index 6ac1757153a..b7215062ff3 100644 --- a/source/isaaclab_physx/isaaclab_physx/sensors/frame_transformer/frame_transformer.py +++ b/source/isaaclab_physx/isaaclab_physx/sensors/frame_transformer/frame_transformer.py @@ -163,8 +163,8 @@ def _initialize_impl(self): body_names_to_frames: dict[str, dict[str, set[str] | str]] = {} # The offsets associated with each target frame target_offsets: dict[str, dict[str, torch.Tensor]] = {} - # The frames whose offsets are not identity - non_identity_offset_frames: list[str] = [] + # The frames whose offsets are not identity (use set to avoid duplicates across envs) + non_identity_offset_frames: set[str] = set() # Only need to perform offsetting of target frame if any of the position offsets are non-zero or any of the # rotation offsets are not the identity quaternion for efficiency in _update_buffer_impl @@ -225,7 +225,7 @@ def _initialize_impl(self): offset_quat = torch.tensor(offset.rot, device=self.device) # Check if we need to apply offsets (optimized code path in _update_buffer_impl) if not is_identity_pose(offset_pos, offset_quat): - non_identity_offset_frames.append(frame_name) + non_identity_offset_frames.add(frame_name) self._apply_target_frame_offset = True target_offsets[frame_name] = {"pos": offset_pos, "quat": offset_quat} @@ -237,7 +237,7 @@ def _initialize_impl(self): else: logger.info( f"Offsets application needed from '{self.cfg.prim_path}' to the following target frames:" - f" {non_identity_offset_frames}" + f" {sorted(non_identity_offset_frames)}" ) # The names of bodies that RigidPrim will be tracking to later extract transforms from diff --git a/tools/template/non_interactive.py b/tools/template/non_interactive.py new file mode 100644 index 00000000000..3c320fe4bf0 --- /dev/null +++ b/tools/template/non_interactive.py @@ -0,0 +1,234 @@ +# 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 From 4f41d8478b65b9107666968d1b2696ea8864b09f Mon Sep 17 00:00:00 2001 From: klakhi Date: Wed, 25 Feb 2026 12:57:10 +0530 Subject: [PATCH 2/2] Collect frame names in a set (so each name is stored once) and log sorted(non_identity_offset_frames) so the message shows unique frame names only. --- tools/template/non_interactive.py | 234 ------------------------------ 1 file changed, 234 deletions(-) delete mode 100644 tools/template/non_interactive.py diff --git a/tools/template/non_interactive.py b/tools/template/non_interactive.py deleted file mode 100644 index 3c320fe4bf0..00000000000 --- a/tools/template/non_interactive.py +++ /dev/null @@ -1,234 +0,0 @@ -# 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