first commit

This commit is contained in:
zhenai 2026-08-09 05:43:03 +08:00
commit 4a8b860f87
26 changed files with 2556 additions and 0 deletions

31
.dockerignore Normal file
View File

@ -0,0 +1,31 @@
# ignore .git related folders
.git/
.github/
.gitignore
# ignore docs
docs/
# copy in licenses folder to the container
!docs/licenses/
# ignore logs
**/logs/
**/runs/
**/output/*
**/outputs/*
**/videos/*
**/wandb/*
*.tmp
# ignore docker
docker/cluster/exports/
docker/.container.cfg
# ignore recordings
recordings/
# ignore __pycache__
**/__pycache__/
**/*.egg-info/
# ignore isaac sim symlink
_isaac_sim
# Docker history
docker/.isaac-lab-docker-history
# ignore uv environment
env_isaaclab
tools/wheel_builder/build/

17
.gitattributes vendored Normal file
View File

@ -0,0 +1,17 @@
*.usd filter=lfs diff=lfs merge=lfs -text
*.usda filter=lfs diff=lfs merge=lfs -text
*.psd filter=lfs diff=lfs merge=lfs -text
*.hdr filter=lfs diff=lfs merge=lfs -text
*.dae filter=lfs diff=lfs merge=lfs -text
*.mtl filter=lfs diff=lfs merge=lfs -text
*.obj filter=lfs diff=lfs merge=lfs -text
*.gif filter=lfs diff=lfs merge=lfs -text
*.mp4 filter=lfs diff=lfs merge=lfs -text
*.pt filter=lfs diff=lfs merge=lfs -text
*.jit filter=lfs diff=lfs merge=lfs -text
*.hdf5 filter=lfs diff=lfs merge=lfs -text
source/isaaclab_tasks/test/golden_images/**/*.png filter=lfs diff=lfs merge=lfs -text
*.bat text eol=crlf
*.sh text eol=lf

90
.gitignore vendored Normal file
View File

@ -0,0 +1,90 @@
# C++
**/cmake-build*/
**/build*/
**/*.so
**/*.log*
# Omniverse
**/*.dmp
**/.thumbs
# No USD files allowed in the repo
**/*.usd
**/*.usda
**/*.usdc
**/*.usdz
# Python
.DS_Store
**/*.egg-info/
**/__pycache__/
**/.pytest_cache/
**/*.pyc
**/*.pb
# Docker/Singularity
**/*.sif
docker/cluster/exports/
docker/.container.cfg
# IDE
**/.idea/
**/.vscode/
# Don't ignore the top-level .vscode directory as it is
# used to configure VS Code settings
!.vscode
# Outputs
**/output/*
**/outputs/*
**/videos/*
**/wandb/*
**/.neptune/*
docker/artifacts/
*.tmp
# Doc Outputs
**/docs/_build/*
**/generated/*
# Isaac-Sim packman
_isaac_sim*
_repo
_build
.lastformat
# RL-Games
**/runs/*
**/logs/*
**/recordings/*
# Pre-Trained Checkpoints
/.pretrained_checkpoints/
# Teleop Recorded Dataset
/datasets/
# Tests
/tests/
# Docker history
.isaac-lab-docker-history
# TacSL sensor
**/tactile_record/*
**/gelsight_r15_data/*
# No benchmarks output
/benchmarks/
# Ruff cache
**/.ruff_cache/
# Dev-time files, generated stuff
**/__*
# Isaac Lab CI environments in native mode
**/_isaaclab_install_ci_*
# Superpowers (Claude Code plugin artifacts)
docs/superpowers/

71
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,71 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.10
hooks:
# Run the linter
- id: ruff
args: ["--fix"]
# Run the formatter
- id: ruff-format
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: check-symlinks
- id: destroyed-symlinks
- id: check-added-large-files
args: ["--maxkb=2000"] # restrict files more than 2 MB. Should use git-lfs instead.
- id: check-yaml
- id: check-merge-conflict
- id: check-case-conflict
- id: check-executables-have-shebangs
- id: check-toml
- id: end-of-file-fixer
- id: check-shebang-scripts-are-executable
- id: detect-private-key
- id: debug-statements
- repo: https://github.com/codespell-project/codespell
rev: v2.4.1
hooks:
- id: codespell
additional_dependencies:
- tomli
exclude: "CONTRIBUTORS.md|docs/source/setup/walkthrough/concepts_env_design.rst"
# FIXME: Figure out why this is getting stuck under VPN.
# - repo: https://github.com/RobertCraigie/pyright-python
# rev: v1.1.315
# hooks:
# - id: pyright
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.5.5
hooks:
- id: insert-license
files: \.(pyi?|ya?ml)$
args:
# - --remove-header # Remove existing license headers. Useful when updating license.
- --license-filepath
- .github/LICENSE_HEADER.txt
- --use-current-year
exclude: "source/isaaclab_mimic/|scripts/imitation_learning/isaaclab_mimic/"
# Apache 2.0 license for mimic files
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.5.5
hooks:
- id: insert-license
files: ^(source/isaaclab_mimic|scripts/imitation_learning/isaaclab_mimic)/.*\.py$
args:
# - --remove-header # Remove existing license headers. Useful when updating license.
- --license-filepath
- .github/LICENSE_HEADER_MIMIC.txt
- --use-current-year
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.10.0
hooks:
- id: rst-backticks
- id: rst-directive-colons
- id: rst-inline-touching-normal

10
.vscode/.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
# Note: These files are kept for development purposes only.
!tools/launch.template.json
!tools/settings.template.json
!tools/setup_vscode.py
!extensions.json
!tasks.json
# Ignore all other files
.python.env
*.json

200
.vscode/tools/setup_vscode.py vendored Normal file
View File

@ -0,0 +1,200 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""This script sets up the vs-code settings for the Isaac Lab project.
This script merges the python.analysis.extraPaths from the "{ISAACSIM_DIR}/.vscode/settings.json" file into
the ".vscode/settings.json" file.
This is necessary because Isaac Sim 2022.2.1 onwards does not add the necessary python packages to the python path
when the "setup_python_env.sh" is run as part of the vs-code launch configuration.
"""
import re
import subprocess
import sys
import os
import pathlib
ISAACLAB_DIR = pathlib.Path(__file__).parents[2]
"""Path to the Isaac Lab directory."""
# Try to find IsaacSim dir
_isaacsim_probe = subprocess.run(
[sys.executable, "-c", "import isaacsim; import os; print(os.environ.get('ISAAC_PATH', ''))"],
capture_output=True,
text=True,
check=False,
# avoid EULA prompt
stdin=subprocess.DEVNULL,
)
if _isaacsim_probe.returncode == 0 and _isaacsim_probe.stdout.strip():
isaacsim_dir = _isaacsim_probe.stdout.strip()
else:
isaacsim_dir = os.path.join(ISAACLAB_DIR, "_isaac_sim")
# check if the isaac-sim directory exists
if not os.path.exists(isaacsim_dir):
print(
f"[WARN] Could not find the isaac-sim directory: {isaacsim_dir}."
"\n\tIsaac Sim does not appear to be installed. VS Code settings will be generated"
"\n\twithout Isaac Sim extra paths."
)
isaacsim_dir = ""
ISAACSIM_DIR = isaacsim_dir
"""Path to the isaac-sim directory."""
def overwrite_python_analysis_extra_paths(isaaclab_settings: str) -> str:
"""Overwrite the python.analysis.extraPaths in the Isaac Lab settings file.
The extraPaths are replaced with the path names from the isaac-sim settings file that exists in the
"{ISAACSIM_DIR}/.vscode/settings.json" file.
If the isaac-sim settings file does not exist, the extraPaths are not overwritten.
Args:
isaaclab_settings: The settings string to use as template.
Returns:
The settings string with overwritten python analysis extra paths.
"""
# isaac-sim settings
isaacsim_vscode_filename = os.path.join(ISAACSIM_DIR, ".vscode", "settings.json")
# we use the isaac-sim settings file to get the python.analysis.extraPaths for kit extensions
# if this file does not exist, we will not add any extra paths
if ISAACSIM_DIR and os.path.exists(isaacsim_vscode_filename):
# read the path names from the isaac-sim settings file
with open(isaacsim_vscode_filename) as f:
vscode_settings = f.read()
# extract the path names
# search for the python.analysis.extraPaths section and extract the contents
settings = re.search(
r"\"python.analysis.extraPaths\": \[.*?\]", vscode_settings, flags=re.MULTILINE | re.DOTALL
)
settings = settings.group(0)
settings = settings.split('"python.analysis.extraPaths": [')[-1]
settings = settings.split("]")[0]
# read the path names from the isaac-sim settings file
path_names = settings.split(",")
path_names = [path_name.strip().strip('"') for path_name in path_names]
path_names = [path_name for path_name in path_names if len(path_name) > 0]
# change the path names to be relative to the Isaac Lab directory
rel_path = os.path.relpath(ISAACSIM_DIR, ISAACLAB_DIR)
path_names = ['"${workspaceFolder}/' + rel_path + "/" + path_name + '"' for path_name in path_names]
else:
path_names = []
# add the path names that are in the Isaac Lab extensions directory
isaaclab_extensions = os.listdir(os.path.join(ISAACLAB_DIR, "source"))
path_names.extend(['"${workspaceFolder}/source/' + ext + '"' for ext in isaaclab_extensions])
# combine them into a single string
path_names = ",\n\t\t".expandtabs(4).join(path_names)
# deal with the path separator being different on Windows and Unix
path_names = path_names.replace("\\", "/")
# replace the path names in the Isaac Lab settings file with the path names parsed
isaaclab_settings = re.sub(
r"\"python.analysis.extraPaths\": \[.*?\]",
'"python.analysis.extraPaths": [\n\t\t'.expandtabs(4) + path_names + "\n\t]".expandtabs(4),
isaaclab_settings,
flags=re.DOTALL,
)
# return the Isaac Lab settings string
return isaaclab_settings
def overwrite_default_python_interpreter(isaaclab_settings: str) -> str:
"""Overwrite the default python interpreter in the Isaac Lab settings file.
The default python interpreter is replaced with the path to the python interpreter used by the
isaac-sim project. This is necessary because the default python interpreter is the one shipped with
isaac-sim.
Args:
isaaclab_settings: The settings string to use as template.
Returns:
The settings string with overwritten default python interpreter.
"""
# read executable name
python_exe = sys.executable.replace("\\", "/")
# We make an exception for replacing the default interpreter if the
# path (/kit/python/bin/python3) indicates that we are using a local/container
# installation of IsaacSim. We will preserve the calling script as the default, python.sh.
# We want to use python.sh because it modifies LD_LIBRARY_PATH and PYTHONPATH
# (among other envars) that we need for all of our dependencies to be accessible.
if "kit/python/bin/python3" in python_exe:
return isaaclab_settings
# replace the default python interpreter in the Isaac Lab settings file with the path to the
# python interpreter in the Isaac Lab directory
isaaclab_settings = re.sub(
r"\"python.defaultInterpreterPath\": \".*?\"",
f'"python.defaultInterpreterPath": "{python_exe}"',
isaaclab_settings,
flags=re.DOTALL,
)
# return the Isaac Lab settings file
return isaaclab_settings
def main():
# Isaac Lab template settings
isaaclab_vscode_template_filename = os.path.join(ISAACLAB_DIR, ".vscode", "tools", "settings.template.json")
# make sure the Isaac Lab template settings file exists
if not os.path.exists(isaaclab_vscode_template_filename):
raise FileNotFoundError(
f"Could not find the Isaac Lab template settings file: {isaaclab_vscode_template_filename}"
)
# read the Isaac Lab template settings file
with open(isaaclab_vscode_template_filename) as f:
isaaclab_template_settings = f.read()
# overwrite the python.analysis.extraPaths in the Isaac Lab settings file with the path names
isaaclab_settings = overwrite_python_analysis_extra_paths(isaaclab_template_settings)
# overwrite the default python interpreter in the Isaac Lab settings file with the path to the
# python interpreter used to call this script
isaaclab_settings = overwrite_default_python_interpreter(isaaclab_settings)
# add template notice to the top of the file
header_message = (
"// This file is a template and is automatically generated by the setup_vscode.py script.\n"
"// Do not edit this file directly.\n"
"// \n"
f"// Generated from: {isaaclab_vscode_template_filename}\n"
)
isaaclab_settings = header_message + isaaclab_settings
# write the Isaac Lab settings file
isaaclab_vscode_filename = os.path.join(ISAACLAB_DIR, ".vscode", "settings.json")
with open(isaaclab_vscode_filename, "w") as f:
f.write(isaaclab_settings)
# copy the launch.json file if it doesn't exist
isaaclab_vscode_launch_filename = os.path.join(ISAACLAB_DIR, ".vscode", "launch.json")
isaaclab_vscode_template_launch_filename = os.path.join(ISAACLAB_DIR, ".vscode", "tools", "launch.template.json")
if not os.path.exists(isaaclab_vscode_launch_filename):
# read template launch settings
with open(isaaclab_vscode_template_launch_filename) as f:
isaaclab_template_launch_settings = f.read()
# add header
header_message = header_message.replace(
isaaclab_vscode_template_filename, isaaclab_vscode_template_launch_filename
)
isaaclab_launch_settings = header_message + isaaclab_template_launch_settings
# write the Isaac Lab launch settings file
with open(isaaclab_vscode_launch_filename, "w") as f:
f.write(isaaclab_launch_settings)
if __name__ == "__main__":
main()

135
README.md Normal file
View File

@ -0,0 +1,135 @@
# Template for Isaac Lab Projects
## Overview
This project/repository serves as a template for building projects or extensions based on Isaac Lab.
It allows you to develop in an isolated environment, outside of the core Isaac Lab repository.
**Key Features:**
- `Isolation` Work outside the core Isaac Lab repository, ensuring that your development efforts remain self-contained.
- `Flexibility` This template is set up to allow your code to be run as an extension in Omniverse.
**Keywords:** extension, template, isaaclab
## Installation
- Install Isaac Lab by following the [installation guide](https://isaac-sim.github.io/IsaacLab/main/source/setup/installation/index.html).
We recommend using the conda or uv installation as it simplifies calling Python scripts from the terminal.
- Clone or copy this project/repository separately from the Isaac Lab installation (i.e. outside the `IsaacLab` directory):
- Using a python interpreter that has Isaac Lab installed, install the library in editable mode using:
```bash
# use 'PATH_TO_isaaclab.sh|bat -p' instead of 'python' if Isaac Lab is not installed in Python venv or conda
python -m pip install -e source/go2Demo
- Verify that the extension is correctly installed by:
- Listing the available tasks:
Note: It the task name changes, it may be necessary to update the search pattern `"Template-"`
(in the `scripts/list_envs.py` file) so that it can be listed.
```bash
# use 'FULL_PATH_TO_isaaclab.sh|bat -p' instead of 'python' if Isaac Lab is not installed in Python venv or conda
python scripts/list_envs.py
```
- Running a task:
```bash
# use 'FULL_PATH_TO_isaaclab.sh|bat -p' instead of 'python' if Isaac Lab is not installed in Python venv or conda
python scripts/<RL_LIBRARY>/train.py --task=<TASK_NAME>
```
- Running a task with dummy agents:
These include dummy agents that output zero or random agents. They are useful to ensure that the environments are configured correctly.
- Zero-action agent
```bash
# use 'FULL_PATH_TO_isaaclab.sh|bat -p' instead of 'python' if Isaac Lab is not installed in Python venv or conda
python scripts/zero_agent.py --task=<TASK_NAME>
```
- Random-action agent
```bash
# use 'FULL_PATH_TO_isaaclab.sh|bat -p' instead of 'python' if Isaac Lab is not installed in Python venv or conda
python scripts/random_agent.py --task=<TASK_NAME>
```
### Set up IDE (Optional)
To setup the IDE, please follow these instructions:
- Run VSCode Tasks, by pressing `Ctrl+Shift+P`, selecting `Tasks: Run Task` and running the `setup_python_env` in the drop down menu.
When running this task, you will be prompted to add the absolute path to your Isaac Sim installation.
If everything executes correctly, it should create a file .python.env in the `.vscode` directory.
The file contains the python paths to all the extensions provided by Isaac Sim and Omniverse.
This helps in indexing all the python modules for intelligent suggestions while writing code.
### Setup as Omniverse Extension (Optional)
We provide an example UI extension that will load upon enabling your extension defined in `source/go2Demo/go2Demo/ui_extension_example.py`.
To enable your extension, follow these steps:
1. **Add the search path of this project/repository** to the extension manager:
- Navigate to the extension manager using `Window` -> `Extensions`.
- Click on the **Hamburger Icon**, then go to `Settings`.
- In the `Extension Search Paths`, enter the absolute path to the `source` directory of this project/repository.
- If not already present, in the `Extension Search Paths`, enter the path that leads to Isaac Lab's extension directory directory (`IsaacLab/source`)
- Click on the **Hamburger Icon**, then click `Refresh`.
2. **Search and enable your extension**:
- Find your extension under the `Third Party` category.
- Toggle it to enable your extension.
## Code formatting
We have a pre-commit template to automatically format your code.
To install pre-commit:
```bash
pip install pre-commit
```
Then you can run pre-commit with:
```bash
pre-commit run --all-files
```
## Troubleshooting
### Pylance Missing Indexing of Extensions
In some VsCode versions, the indexing of part of the extensions is missing.
In this case, add the path to your extension in `.vscode/settings.json` under the key `"python.analysis.extraPaths"`.
```json
{
"python.analysis.extraPaths": [
"<path-to-ext-repo>/source/go2Demo"
]
}
```
### Pylance Crash
If you encounter a crash in `pylance`, it is probable that too many files are indexed and you run out of memory.
A possible solution is to exclude some of omniverse packages that are not used in your project.
To do so, modify `.vscode/settings.json` and comment out packages under the key `"python.analysis.extraPaths"`
Some examples of packages that can likely be excluded are:
```json
"<path-to-isaac-sim>/extscache/omni.anim.*" // Animation packages
"<path-to-isaac-sim>/extscache/omni.kit.*" // Kit UI tools
"<path-to-isaac-sim>/extscache/omni.graph.*" // Graph UI tools
"<path-to-isaac-sim>/extscache/omni.services.*" // Services tools
...
```

79
git-force-push.bat Normal file
View File

@ -0,0 +1,79 @@
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
echo ============================================================
echo 脚本: git-force-push.bat
echo 功能: 将本地分支强制推送到远程,让远程与本地完全一致
echo ============================================================
REM ---------- 检查是否在 Git 仓库 ----------
git rev-parse --is-inside-work-tree >nul 2>&1
if errorlevel 1 (
echo [错误] 当前目录不是 Git 仓库
exit /b 1
)
REM ---------- 获取当前分支 ----------
for /f %%i in ('git branch --show-current') do set CURRENT_BRANCH=%%i
if "%CURRENT_BRANCH%"=="" (
echo [错误] 当前处于 HEAD 分离状态,请先切换到具体分支。
exit /b 1
)
echo [信息] 当前分支:%CURRENT_BRANCH%
REM ---------- 拉取远程最新信息 ----------
echo [信息] 正在获取远程最新引用...
git fetch origin
REM ---------- 检查本地是否有未提交更改 ----------
git diff --quiet >nul 2>&1
if errorlevel 1 (
echo [警告] 检测到本地有未提交的更改(已暂存或未暂存)。
echo [警告] 这些更改不会被推送,但会保留在工作区。
set /p CONTINUE="是否继续强制推送?(y/N): "
if /i not "!CONTINUE!"=="y" (
echo [信息] 操作已取消。
exit /b 0
)
)
REM ---------- 检查本地是否落后于远程 ----------
for /f %%i in ('git rev-list --count @..@{u} 2^>nul') do set AHEAD=%%i
if not defined AHEAD set AHEAD=0
if %AHEAD% gtr 0 (
echo [警告] 远程有 %AHEAD% 个新提交,本地落后于远程。
echo [警告] 强制推送会覆盖这些提交,请确认这不是他人刚提交的代码!
)
REM ---------- 选择推送模式 ----------
echo 请选择推送模式:
echo 1) --force-with-lease (推荐,安全覆盖)
echo 2) --force (强制覆盖,极度危险)
set /p MODE="请输入数字 (1 或 2默认 1): "
if "%MODE%"=="" set MODE=1
if "%MODE%"=="2" (
set FORCE_FLAG=--force
echo [警告] 您选择了 --force这将无条件覆盖远程分支
) else (
set FORCE_FLAG=--force-with-lease
echo [信息] 您选择了 --force-with-lease安全模式。
)
REM ---------- 最终确认 ----------
set /p CONFIRM="确认将本地分支 %CURRENT_BRANCH% 强制推送到远程?请输入 'yes' 继续: "
if /i not "%CONFIRM%"=="yes" (
echo [信息] 操作已取消。
exit /b 0
)
REM ---------- 执行推送 ----------
echo [信息] 正在执行: git push %FORCE_FLAG% origin %CURRENT_BRANCH%
git push %FORCE_FLAG% origin %CURRENT_BRANCH%
if errorlevel 1 (
echo [错误] 推送失败,请检查网络或权限。
exit /b %errorlevel%
)
echo [成功] 推送完成!远程 origin/%CURRENT_BRANCH% 已与本地保持一致。
endlocal

89
git-force-push.sh Executable file
View File

@ -0,0 +1,89 @@
#!/bin/bash
# ============================================================
# 脚本名称: git-force-push.sh
# 功能描述: 将本地分支强制推送到远程,让远程与本地完全一致
# 使用场景: 本地代码正确,远程有错误或需强制更新
# 安全机制: 默认使用 --force-with-lease并检查远程新提交
# ============================================================
set -e
# ---------- 颜色 ----------
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# ---------- 1. 检查是否在 Git 仓库 ----------
if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
echo -e "${RED}错误:当前目录不是 Git 仓库${NC}"
exit 1
fi
# ---------- 2. 获取当前分支 ----------
CURRENT_BRANCH=$(git branch --show-current)
if [ -z "$CURRENT_BRANCH" ]; then
echo -e "${RED}错误:当前处于 HEAD 分离状态,请先切换到具体分支。${NC}"
exit 1
fi
echo -e "${BLUE}当前分支:${CURRENT_BRANCH}${NC}"
# ---------- 3. 先拉取远程最新信息(但不会合并) ----------
echo -e "${BLUE}正在获取远程最新引用...${NC}"
git fetch origin
# ---------- 4. 检查本地是否有未提交的更改 ----------
if ! git diff --quiet || ! git diff --cached --quiet; then
echo -e "${YELLOW}⚠️ 检测到本地有未提交的更改(已暂存或未暂存)。${NC}"
echo -e "${YELLOW}这些更改不会被推送,但会保留在工作区。${NC}"
read -p "是否继续强制推送?(y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo -e "${GREEN}操作已取消。${NC}"
exit 0
fi
fi
# ---------- 5. 检查本地是否落后于远程(存在远程新提交) ----------
LOCAL_COMMIT=$(git rev-parse @)
REMOTE_COMMIT=$(git rev-parse @{u} 2>/dev/null || echo "")
if [ -z "$REMOTE_COMMIT" ]; then
echo -e "${YELLOW}⚠️ 当前分支未设置上游,将使用 --set-upstream 建立跟踪。${NC}"
fi
BEHIND=$(git rev-list --count @{u}..@ 2>/dev/null || echo "0")
AHEAD=$(git rev-list --count @..@{u} 2>/dev/null || echo "0")
if [ "$AHEAD" -gt 0 ]; then
echo -e "${YELLOW}⚠️ 远程有 $AHEAD 个新提交,本地落后于远程。${NC}"
echo -e "${YELLOW}强制推送会覆盖这些提交,请确认这不是他人刚提交的代码!${NC}"
fi
# ---------- 6. 选择强制推送模式 ----------
echo -e "${BLUE}请选择推送模式:${NC}"
echo " 1) --force-with-lease (推荐,安全覆盖,会检查远程是否有未知新提交)"
echo " 2) --force (强制覆盖,忽略远程所有内容,极度危险)"
read -p "请输入数字 (1 或 2默认 1): " MODE
MODE=${MODE:-1}
if [ "$MODE" = "2" ]; then
FORCE_FLAG="--force"
echo -e "${RED}您选择了 --force这将无条件覆盖远程分支${NC}"
else
FORCE_FLAG="--force-with-lease"
echo -e "${GREEN}您选择了 --force-with-lease安全模式。${NC}"
fi
# ---------- 7. 最终确认 ----------
read -p "确认将本地分支 '$CURRENT_BRANCH' 强制推送到远程?请输入 'yes' 继续: " CONFIRM
if [ "$CONFIRM" != "yes" ]; then
echo -e "${GREEN}操作已取消。${NC}"
exit 0
fi
# ---------- 8. 执行推送 ----------
echo -e "${BLUE}正在执行: git push $FORCE_FLAG origin $CURRENT_BRANCH${NC}"
git push $FORCE_FLAG origin $CURRENT_BRANCH
# ---------- 9. 结果反馈 ----------
echo -e "${GREEN}✅ 推送完成!远程 'origin/$CURRENT_BRANCH' 已与本地保持一致。${NC}"

266
pyproject.toml Normal file
View File

@ -0,0 +1,266 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
[project]
name = "isaaclab-dev"
version = "0.1.0"
description = "Isaac Lab source checkout development environment."
requires-python = ">=3.12,<3.13"
dependencies = [
"isaaclab",
"isaaclab-assets",
"isaaclab-contrib",
"isaaclab-experimental",
"isaaclab-newton[all]",
"isaaclab-ov",
"isaaclab-ovphysx",
"isaaclab-physx[newton]",
"isaaclab-ppisp",
"isaaclab-rl[rsl-rl]",
"isaaclab-tasks",
"isaaclab-tasks-experimental",
"isaaclab-visualizers",
"torch==2.10.0",
"torchaudio==2.10.0",
"torchvision==0.25.0",
]
[project.optional-dependencies]
contrib = [
"isaaclab-contrib",
]
mimic = [
"isaaclab-mimic",
]
newton = [
"isaaclab-newton[all]",
"isaaclab-physx[newton]",
"isaaclab-visualizers[newton]",
]
ov = [
"isaaclab-ovphysx[ovphysx]",
]
rl = [
"isaaclab-rl[rsl-rl]",
]
rl-all = [
"isaaclab-rl[all]",
]
rtx = [
"isaaclab-ov[ovrtx]",
]
all = [
"isaaclab-mimic",
"isaaclab-newton[all]",
"isaaclab-physx[newton]",
"isaaclab-rl[all]",
"isaaclab-visualizers[all]",
]
[tool.ruff]
line-length = 120
target-version = "py310"
# Exclude directories
extend-exclude = [
"logs",
"_isaac_sim",
".vscode",
"_*",
".git",
]
[tool.ruff.lint]
# Enable flake8 rules and other useful ones
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"I", # isort
"UP", # pyupgrade
"C90", # mccabe complexity
# "D", # pydocstyle
"SIM", # flake8-simplify
"RET", # flake8-return
]
# Ignore specific rules (matching your flake8 config)
ignore = [
"E402", # Module level import not at top of file
"D401", # First line should be in imperative mood
"RET504", # Unnecessary variable assignment before return statement
"RET505", # Unnecessary elif after return statement
"SIM102", # Use a single if-statement instead of nested if-statements
"SIM103", # Return the negated condition directly
"SIM108", # Use ternary operator instead of if-else statement
"SIM117", # Merge with statements for context managers
"SIM118", # Use {key} in {dict} instead of {key} in {dict}.keys()
"UP006", # Use 'dict' instead of 'Dict' type annotation
"UP018", # Unnecessary `float` call (rewrite as a literal)
]
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"] # Allow unused imports in __init__.py files
[tool.ruff.lint.mccabe]
max-complexity = 30
[tool.ruff.lint.pydocstyle]
convention = "google"
[tool.ruff.lint.isort]
# Custom import sections with separate sections for each Isaac Lab extension
section-order = [
"future",
"standard-library",
"third-party",
# Group omniverse extensions separately since they are run-time dependencies
# which are pulled in by Isaac Lab extensions
"omniverse-extensions",
# Group Isaac Lab extensions together since they are all part of the Isaac Lab project
"isaaclab",
"isaaclab-contrib",
"isaaclab-rl",
"isaaclab-mimic",
"isaaclab-tasks",
"isaaclab-assets",
# First-party is reserved for project templates
"first-party",
"local-folder",
]
[tool.ruff.lint.isort.sections]
# Define what belongs in each custom section
"omniverse-extensions" = [
"isaacsim",
"omni",
"pxr",
"carb",
"usdrt",
"Semantics",
"curobo",
]
"isaaclab" = ["isaaclab"]
"isaaclab-assets" = ["isaaclab_assets"]
"isaaclab-contrib" = ["isaaclab_contrib"]
"isaaclab-rl" = ["isaaclab_rl"]
"isaaclab-mimic" = ["isaaclab_mimic"]
"isaaclab-tasks" = ["isaaclab_tasks"]
[tool.ruff.format]
docstring-code-format = true
[tool.pyright]
include = ["source", "scripts"]
exclude = [
"**/__pycache__",
"**/_isaac_sim",
"**/docs",
"**/logs",
".git",
".vscode",
]
typeCheckingMode = "basic"
pythonVersion = "3.12"
pythonPlatform = "Linux"
enableTypeIgnoreComments = true
# This is required as the CI pre-commit does not download the module (i.e. numpy, torch, prettytable)
# Therefore, we have to ignore missing imports
reportMissingImports = "none"
# This is required to ignore for type checks of modules with stubs missing.
reportMissingModuleSource = "none" # -> most common: prettytable in mdp managers
reportGeneralTypeIssues = "none" # -> raises 218 errors (usage of literal MISSING in dataclasses)
reportOptionalMemberAccess = "warning" # -> raises 8 errors
reportPrivateUsage = "warning"
[tool.codespell]
skip = '*.usd,*.usda,*.usdz,*.svg,*.png,_isaac_sim*,*.bib,*.css,*/_build'
quiet-level = 0
# the world list should always have words in lower case
ignore-words-list = "haa,slq,collapsable,buss,reacher,thirdparty,segway"
[tool.pytest.ini_options]
markers = [
"isaacsim_ci: mark test to run in isaacsim ci",
]
# Add pypi.nvidia.com so that `uv pip install isaaclab[isaacsim]` works without --extra-index-url.
# Pip still needs "--extra-index-url https://pypi.nvidia.com".
[[tool.uv.index]]
url = "https://pypi.nvidia.com"
explicit = false
[[tool.uv.index]]
name = "pytorch-cu128"
url = "https://download.pytorch.org/whl/cu128"
explicit = true
[[tool.uv.index]]
name = "pytorch-cu130"
url = "https://download.pytorch.org/whl/cu130"
explicit = true
# Some NVIDIA-hosted dependencies have mismatched versions across pypi.nvidia.com
# and PyPI. unsafe-best-match lets uv resolve the correct version from any index,
# and prerelease=allow covers packages that only publish pre-release wheels.
[tool.uv]
index-strategy = "unsafe-best-match"
prerelease = "allow"
override-dependencies = ["numpy>=2"]
python-preference = "only-managed"
package = false
[tool.uv.sources]
isaaclab = { path = "source/isaaclab", editable = true }
"isaaclab-assets" = { path = "source/isaaclab_assets", editable = true }
"isaaclab-contrib" = { path = "source/isaaclab_contrib", editable = true }
"isaaclab-experimental" = { path = "source/isaaclab_experimental", editable = true }
"isaaclab-mimic" = { path = "source/isaaclab_mimic", editable = true }
"isaaclab-newton" = { path = "source/isaaclab_newton", editable = true }
"isaaclab-ov" = { path = "source/isaaclab_ov", editable = true }
"isaaclab-ovphysx" = { path = "source/isaaclab_ovphysx", editable = true }
"isaaclab-physx" = { path = "source/isaaclab_physx", editable = true }
"isaaclab-ppisp" = { path = "source/isaaclab_ppisp", editable = true }
"isaaclab-rl" = { path = "source/isaaclab_rl", editable = true }
"isaaclab-tasks" = { path = "source/isaaclab_tasks", editable = true }
"isaaclab-tasks-experimental" = { path = "source/isaaclab_tasks_experimental", editable = true }
"isaaclab-teleop" = { path = "source/isaaclab_teleop", editable = true }
"isaaclab-visualizers" = { path = "source/isaaclab_visualizers", editable = true }
torch = [
{ index = "pytorch-cu128", marker = "sys_platform == 'linux' and platform_machine == 'x86_64'" },
{ index = "pytorch-cu128", marker = "sys_platform == 'linux' and platform_machine == 'AMD64'" },
{ index = "pytorch-cu128", marker = "sys_platform == 'win32'" },
{ index = "pytorch-cu130", marker = "sys_platform == 'linux' and platform_machine == 'aarch64'" },
{ index = "pytorch-cu130", marker = "sys_platform == 'linux' and platform_machine == 'arm64'" },
]
torchaudio = [
{ index = "pytorch-cu128", marker = "sys_platform == 'linux' and platform_machine == 'x86_64'" },
{ index = "pytorch-cu128", marker = "sys_platform == 'linux' and platform_machine == 'AMD64'" },
{ index = "pytorch-cu128", marker = "sys_platform == 'win32'" },
{ index = "pytorch-cu130", marker = "sys_platform == 'linux' and platform_machine == 'aarch64'" },
{ index = "pytorch-cu130", marker = "sys_platform == 'linux' and platform_machine == 'arm64'" },
]
torchvision = [
{ index = "pytorch-cu128", marker = "sys_platform == 'linux' and platform_machine == 'x86_64'" },
{ index = "pytorch-cu128", marker = "sys_platform == 'linux' and platform_machine == 'AMD64'" },
{ index = "pytorch-cu128", marker = "sys_platform == 'win32'" },
{ index = "pytorch-cu130", marker = "sys_platform == 'linux' and platform_machine == 'aarch64'" },
{ index = "pytorch-cu130", marker = "sys_platform == 'linux' and platform_machine == 'arm64'" },
]
[tool.uv.pip]
index-strategy = "unsafe-best-match"
prerelease = "allow"

135
scripts/list_envs.py Normal file
View File

@ -0,0 +1,135 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""
Script to print all the available environments in Isaac Lab.
The script iterates over all registered environments and stores the details in a table.
It prints the name of the environment, the entry point and the config file.
All the environments are registered in the `go2Demo` extension. They start
with `Isaac` in their name.
"""
"""Launch Isaac Sim Simulator first."""
import argparse
import contextlib
from isaaclab.app import AppLauncher
# add argparse arguments
parser = argparse.ArgumentParser(description="List Isaac Lab environments.")
parser.add_argument("--keyword", type=str, default=None, help="Keyword to filter environments.")
parser.add_argument(
"--show_presets",
action="store_true",
default=False,
help=(
"Show available preset selectors for each environment. "
"Presets are grouped by selector type: physics (physics=NAME), "
"renderer (renderer=NAME), and domain (presets=NAME)."
),
)
# parse the arguments
args_cli = parser.parse_args()
# launch omniverse app
app_launcher = AppLauncher(headless=True)
simulation_app = app_launcher.app
"""Rest everything follows."""
import gymnasium as gym
from prettytable import PrettyTable
import go2Demo.tasks # noqa: F401
# PLACEHOLDER: Extension template (do not remove this comment)
with contextlib.suppress(ImportError):
import go2Demo.tasks_experimental # noqa: F401
def _format_presets(preset_map: dict | None) -> str:
"""Format a preset map returned by :func:`enumerate_task_presets` into a human-readable string.
Args:
preset_map: Mapping of :class:`~go2Demo.utils.preset_target.PresetTarget`
to sorted preset name lists, or ``None`` when the env cfg could not be loaded.
Returns:
A multi-line string with one line per non-empty selector category, or a
short placeholder when no presets are available or the cfg failed to load.
"""
if preset_map is None:
return "(unavailable)"
from go2Demo.utils.preset_target import PresetTarget
lines = []
labels = {
PresetTarget.PHYSICS: "physics",
PresetTarget.RENDERER: "renderer",
PresetTarget.DOMAIN: "domain",
}
for target, label in labels.items():
names = preset_map.get(target, [])
if names:
lines.append(f"{label}: {', '.join(names)}")
return "\n".join(lines) if lines else "(none)"
def main():
"""Print all environments registered in `go2Demo` extension."""
# Collect matching task specs first so we can enumerate presets in one pass.
task_specs = [
spec
for spec in gym.registry.values()
if "Template-" in spec.id and (args_cli.keyword is None or args_cli.keyword in spec.id)
]
if args_cli.show_presets:
from go2Demo.utils.preset_cli import enumerate_task_presets
table = PrettyTable(["S. No.", "Task Name", "Entry Point", "Config", "Presets"])
table.title = "Available Environments in Isaac Lab"
table.align["Task Name"] = "l"
table.align["Entry Point"] = "l"
table.align["Config"] = "l"
table.align["Presets"] = "l"
for index, spec in enumerate(task_specs):
preset_map = enumerate_task_presets(spec.id)
table.add_row(
[
index + 1,
spec.id,
spec.entry_point,
spec.kwargs["env_cfg_entry_point"],
_format_presets(preset_map),
]
)
else:
table = PrettyTable(["S. No.", "Task Name", "Entry Point", "Config"])
table.title = "Available Environments in Isaac Lab"
table.align["Task Name"] = "l"
table.align["Entry Point"] = "l"
table.align["Config"] = "l"
for index, spec in enumerate(task_specs):
table.add_row([index + 1, spec.id, spec.entry_point, spec.kwargs["env_cfg_entry_point"]])
print(table)
if __name__ == "__main__":
try:
# run the main function
main()
except Exception as e:
raise e
finally:
# close the app
simulation_app.close()

86
scripts/random_agent.py Normal file
View File

@ -0,0 +1,86 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Script to an environment with random action agent."""
import argparse
import contextlib
import sys
import gymnasium as gym
import torch
import isaaclab_tasks # noqa: F401
with contextlib.suppress(ImportError):
import isaaclab_tasks_experimental # noqa: F401
from isaaclab_tasks.utils import (
add_launcher_args,
launch_simulation,
resolve_task_config,
setup_preset_cli,
)
# add argparse arguments
parser = argparse.ArgumentParser(description="Random agent for Isaac Lab environments.")
parser.add_argument(
"--disable_fabric", action="store_true", default=False, help="Disable fabric and use USD I/O operations."
)
parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.")
parser.add_argument("--task", type=str, default=None, help="Name of the task.")
# append AppLauncher cli args
add_launcher_args(parser)
# simple agents should open Kit visualizer by default
parser.set_defaults(visualizer=["kit"])
args_cli, hydra_args = setup_preset_cli(parser)
sys.argv = [sys.argv[0]] + hydra_args
import go2Demo.tasks # noqa: F401
def main():
"""Random actions agent with Isaac Lab environment."""
torch.manual_seed(42)
# parse configuration via Hydra (supports preset selection, e.g. env.sim.physics=newton_mjwarp)
env_cfg, _ = resolve_task_config(args_cli.task, "")
with launch_simulation(env_cfg, args_cli):
# override with CLI arguments
env_cfg.scene.num_envs = args_cli.num_envs if args_cli.num_envs is not None else env_cfg.scene.num_envs
env_cfg.sim.device = args_cli.device if args_cli.device is not None else env_cfg.sim.device
if args_cli.disable_fabric:
env_cfg.sim.use_fabric = False
# create environment
env = gym.make(args_cli.task, cfg=env_cfg)
# print info (this is vectorized environment)
print(f"[INFO]: Gym observation space: {env.observation_space}")
print(f"[INFO]: Gym action space: {env.action_space}")
# reset environment
env.reset()
# simulate environment
sim = env.unwrapped.sim
while True:
if sim.visualizers:
# visualizer mode: run until the visualizer window is closed
if not any(v.is_running() and not v.is_closed for v in sim.visualizers):
break
# run everything in inference mode
with torch.inference_mode():
# sample actions from -1 to 1
actions = 2 * torch.rand(env.action_space.shape, device=env.unwrapped.device) - 1
# apply actions
env.step(actions)
# close the simulator
env.close()
if __name__ == "__main__":
# run the main function
main()

View File

@ -0,0 +1,93 @@
# Copyright (c) 2022-2026, 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 random
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from isaaclab_rl.rsl_rl import RslRlBaseRunnerCfg
def add_rsl_rl_args(parser: argparse.ArgumentParser):
"""Add RSL-RL arguments to the parser.
Args:
parser: The parser to add the arguments to.
"""
# create a new argument group
arg_group = parser.add_argument_group("rsl_rl", description="Arguments for RSL-RL agent.")
# -- experiment arguments
arg_group.add_argument(
"--experiment_name", type=str, default=None, help="Name of the experiment folder where logs will be stored."
)
arg_group.add_argument("--run_name", type=str, default=None, help="Run name suffix to the log directory.")
# -- load arguments
arg_group.add_argument("--resume", action="store_true", default=False, help="Whether to resume from a checkpoint.")
arg_group.add_argument("--load_run", type=str, default=None, help="Name of the run folder to resume from.")
arg_group.add_argument("--checkpoint", type=str, default=None, help="Checkpoint file to resume from.")
# -- logger arguments
arg_group.add_argument(
"--logger", type=str, default=None, choices={"wandb", "tensorboard", "neptune"}, help="Logger module to use."
)
arg_group.add_argument(
"--log_project_name", type=str, default=None, help="Name of the logging project when using wandb or neptune."
)
def parse_rsl_rl_cfg(task_name: str, args_cli: argparse.Namespace) -> RslRlBaseRunnerCfg:
"""Parse configuration for RSL-RL agent based on inputs.
Args:
task_name: The name of the environment.
args_cli: The command line arguments.
Returns:
The parsed configuration for RSL-RL agent based on inputs.
"""
from isaaclab_tasks.utils.parse_cfg import load_cfg_from_registry
# load the default configuration
rslrl_cfg: RslRlBaseRunnerCfg = load_cfg_from_registry(task_name, "rsl_rl_cfg_entry_point")
rslrl_cfg = update_rsl_rl_cfg(rslrl_cfg, args_cli)
return rslrl_cfg
def update_rsl_rl_cfg(agent_cfg: RslRlBaseRunnerCfg, args_cli: argparse.Namespace):
"""Update configuration for RSL-RL agent based on inputs.
Args:
agent_cfg: The configuration for RSL-RL agent.
args_cli: The command line arguments.
Returns:
The updated configuration for RSL-RL agent based on inputs.
"""
# override the default configuration with CLI arguments
if hasattr(args_cli, "seed") and args_cli.seed is not None:
# randomly sample a seed if seed = -1
if args_cli.seed == -1:
args_cli.seed = random.randint(0, 10000)
agent_cfg.seed = args_cli.seed
if args_cli.resume is not None:
agent_cfg.resume = args_cli.resume
if args_cli.load_run is not None:
agent_cfg.load_run = args_cli.load_run
if args_cli.checkpoint is not None:
agent_cfg.load_checkpoint = args_cli.checkpoint
if args_cli.experiment_name is not None:
agent_cfg.experiment_name = args_cli.experiment_name
if args_cli.run_name is not None:
agent_cfg.run_name = args_cli.run_name
if args_cli.logger is not None:
agent_cfg.logger = args_cli.logger
# set the project name for wandb and neptune
if agent_cfg.logger in {"wandb", "neptune"} and args_cli.log_project_name:
agent_cfg.wandb_project = args_cli.log_project_name
agent_cfg.neptune_project = args_cli.log_project_name
return agent_cfg

251
scripts/rsl_rl/play.py Normal file
View File

@ -0,0 +1,251 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Script to play a checkpoint if an RL agent from RSL-RL."""
import warnings
warnings.warn(
"scripts/reinforcement_learning/rsl_rl/play.py is deprecated. Use "
"`./isaaclab.sh play --rl_library rsl_rl --task <TASK>` instead. "
"Example: `./isaaclab.sh play --rl_library rsl_rl --task Isaac-Cartpole-v0`.",
DeprecationWarning,
stacklevel=1,
)
import argparse
import contextlib
import importlib.metadata as metadata
import os
import sys
import time
import gymnasium as gym
import torch
from packaging import version
from rsl_rl.runners import DistillationRunner, OnPolicyRunner
from isaaclab.envs import DirectMARLEnvCfg, DirectRLEnvCfg, ManagerBasedRLEnvCfg
from isaaclab.utils.assets import retrieve_file_path
from isaaclab.utils.dict import print_dict
from isaaclab.utils.seed import configure_seed
from isaaclab.utils.string import list_intersection, string_to_callable
from isaaclab_rl.rsl_rl import (
RslRlBaseRunnerCfg,
RslRlVecEnvWrapper,
export_policy_as_jit,
export_policy_as_onnx,
handle_deprecated_rsl_rl_cfg,
)
from isaaclab_rl.utils.pretrained_checkpoint import get_published_pretrained_checkpoint
import isaaclab_tasks # noqa: F401
from isaaclab_tasks.utils import (
add_launcher_args,
get_checkpoint_path,
launch_simulation,
setup_preset_cli,
)
from isaaclab_tasks.utils.hydra import hydra_task_config
# local imports
import cli_args # isort: skip
import go2Demo.tasks # noqa: F401
with contextlib.suppress(ImportError):
import isaaclab_tasks_experimental # noqa: F401
# -- argparse ----------------------------------------------------------------
parser = argparse.ArgumentParser(description="Train an RL agent with RSL-RL.")
parser.add_argument("--video", action="store_true", default=True, help="Record videos during training.")
parser.add_argument("--video_length", type=int, default=2000, help="Length of the recorded video (in steps).")
parser.add_argument(
"--disable_fabric", action="store_true", default=False, help="Disable fabric and use USD I/O operations."
)
parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.")
parser.add_argument("--task", type=str, default=None, help="Name of the task.")
parser.add_argument(
"--agent", type=str, default="rsl_rl_cfg_entry_point", help="Name of the RL agent configuration entry point."
)
parser.add_argument("--seed", type=int, default=None, help="Seed used for the environment")
parser.add_argument(
"--use_pretrained_checkpoint",
action="store_true",
help="Use the pre-trained checkpoint from Nucleus.",
)
parser.add_argument("--real-time", action="store_true", default=False, help="Run in real-time, if possible.")
parser.add_argument("--external_callback", default=None, help="Fully qualified path to an externally defined callback.")
cli_args.add_rsl_rl_args(parser)
add_launcher_args(parser)
args_cli, remaining_args = setup_preset_cli(parser)
if args_cli.video:
args_cli.enable_cameras = True
# Call an external callback if requested. This gives opportunity to external code to register the environments
# The function is expected to return a list of arguments that were not consumed by the callback.
remaining_args_env_registration = None
if args_cli.external_callback:
external_callback_function = string_to_callable(args_cli.external_callback, separator=".")
remaining_args_env_registration = external_callback_function()
# clear out sys.argv for Hydra
# The remaining arguments are the arguments that were not consumed by both this scripts
# argparser and (optionally) the external callback function. Both sides of this
# intersection are pre-fold (the callback reads the user's original sys.argv), so
# preset tokens like ``physics=NAME`` compare correctly here. Fold runs after.
remaining_args = list_intersection(remaining_args, remaining_args_env_registration)
sys.argv = [sys.argv[0]] + remaining_args
# Check for installed RSL-RL version
installed_version = metadata.version("rsl-rl-lib")
@hydra_task_config(args_cli.task, args_cli.agent)
def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agent_cfg: RslRlBaseRunnerCfg):
"""Play with RSL-RL agent."""
with launch_simulation(env_cfg, args_cli):
# grab task name for checkpoint path
task_name = args_cli.task.split(":")[-1]
train_task_name = task_name.replace("-Play", "")
# override configurations with non-hydra CLI arguments
agent_cfg = cli_args.update_rsl_rl_cfg(agent_cfg, args_cli)
env_cfg.scene.num_envs = args_cli.num_envs if args_cli.num_envs is not None else env_cfg.scene.num_envs
# handle deprecated configurations
agent_cfg = handle_deprecated_rsl_rl_cfg(agent_cfg, installed_version)
# set the environment seed
# note: certain randomizations occur in the environment initialization so we set the seed here
env_cfg.seed = agent_cfg.seed
env_cfg.sim.device = args_cli.device if args_cli.device is not None else env_cfg.sim.device
# specify directory for logging experiments
log_root_path = os.path.join("logs", "rsl_rl", agent_cfg.experiment_name)
log_root_path = os.path.abspath(log_root_path)
print(f"[INFO] Loading experiment from directory: {log_root_path}")
if args_cli.use_pretrained_checkpoint:
resume_path = get_published_pretrained_checkpoint("rsl_rl", train_task_name)
if not resume_path:
print("[INFO] Unfortunately a pre-trained checkpoint is currently unavailable for this task.")
return
elif args_cli.checkpoint:
resume_path = retrieve_file_path(args_cli.checkpoint)
else:
resume_path = get_checkpoint_path(log_root_path, agent_cfg.load_run, agent_cfg.load_checkpoint)
log_dir = os.path.dirname(resume_path)
# set the log directory for the environment
env_cfg.log_dir = log_dir
# create isaac environment
env = gym.make(args_cli.task, cfg=env_cfg, render_mode="rgb_array" if args_cli.video else None)
# convert to single-agent instance if required by the RL algorithm
if isinstance(env.unwrapped.cfg, DirectMARLEnvCfg):
from isaaclab.envs import multi_agent_to_single_agent
env = multi_agent_to_single_agent(env)
# wrap for video recording
if args_cli.video:
video_kwargs = {
"video_folder": os.path.join(log_dir, "videos", "play"),
"step_trigger": lambda step: step == 0,
"video_length": args_cli.video_length,
"disable_logger": True,
}
print("[INFO] Recording videos during training.")
print_dict(video_kwargs, nesting=4)
env = gym.wrappers.RecordVideo(env, **video_kwargs)
# wrap around environment for rsl-rl
env = RslRlVecEnvWrapper(env, clip_actions=agent_cfg.clip_actions)
print(f"[INFO]: Loading model checkpoint from: {resume_path}")
# load previously trained model
if agent_cfg.class_name == "OnPolicyRunner":
runner = OnPolicyRunner(env, agent_cfg.to_dict(), log_dir=None, device=agent_cfg.device)
elif agent_cfg.class_name == "DistillationRunner":
runner = DistillationRunner(env, agent_cfg.to_dict(), log_dir=None, device=agent_cfg.device)
else:
raise ValueError(f"Unsupported runner class: {agent_cfg.class_name}")
# configure_seed must be called after runner construction so that PyTorch deterministic settings
# do not interfere with the runner's internal initialization.
if args_cli.deterministic:
configure_seed(env_cfg.seed, True)
runner.load(resume_path)
# obtain the trained policy for inference
policy = runner.get_inference_policy(device=env.unwrapped.device)
# export the trained policy to JIT and ONNX formats
export_model_dir = os.path.join(os.path.dirname(resume_path), "exported")
if version.parse(installed_version) >= version.parse("4.0.0"):
# use the new export functions for rsl-rl >= 4.0.0
runner.export_policy_to_jit(path=export_model_dir, filename="policy.pt")
runner.export_policy_to_onnx(path=export_model_dir, filename="policy.onnx")
policy_nn = None # Not needed for rsl-rl >= 4.0.0
else:
# extract the neural network for rsl-rl < 4.0.0
if version.parse(installed_version) >= version.parse("2.3.0"):
policy_nn = runner.alg.policy
else:
policy_nn = runner.alg.actor_critic
# extract the normalizer
if hasattr(policy_nn, "actor_obs_normalizer"):
normalizer = policy_nn.actor_obs_normalizer
elif hasattr(policy_nn, "student_obs_normalizer"):
normalizer = policy_nn.student_obs_normalizer
else:
normalizer = None
# export to JIT and ONNX
export_policy_as_jit(policy_nn, normalizer=normalizer, path=export_model_dir, filename="policy.pt")
export_policy_as_onnx(policy_nn, normalizer=normalizer, path=export_model_dir, filename="policy.onnx")
dt = env.unwrapped.step_dt
# reset environment
obs = env.get_observations()
timestep = 0
# simulate environment
try:
while True:
start_time = time.time()
# run everything in inference mode
with torch.inference_mode():
# agent stepping
actions = policy(obs)
# env stepping
obs, _, dones, _ = env.step(actions)
# reset recurrent states for episodes that have terminated
if version.parse(installed_version) >= version.parse("4.0.0"):
policy.reset(dones)
else:
policy_nn.reset(dones)
if args_cli.video:
timestep += 1
if timestep == args_cli.video_length:
break
sleep_time = dt - (time.time() - start_time)
if args_cli.real_time and sleep_time > 0:
time.sleep(sleep_time)
# close the simulator
env.close()
except KeyboardInterrupt:
pass
if __name__ == "__main__":
main()

View File

@ -0,0 +1,234 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Script to play a checkpoint of an RL agent from RSL-RL."""
import argparse
import contextlib
import importlib.metadata as metadata
import os
import sys
import time
import gymnasium as gym
import torch
from packaging import version
from rsl_rl.runners import DistillationRunner, OnPolicyRunner
from isaaclab.envs import DirectMARLEnvCfg, DirectRLEnvCfg, ManagerBasedRLEnvCfg
from isaaclab.utils.assets import retrieve_file_path
from isaaclab.utils.dict import print_dict
from isaaclab.utils.string import list_intersection, string_to_callable
from isaaclab_rl.rsl_rl import (
RslRlBaseRunnerCfg,
RslRlVecEnvWrapper,
export_policy_as_jit,
export_policy_as_onnx,
handle_deprecated_rsl_rl_cfg,
)
from isaaclab_rl.utils.pretrained_checkpoint import get_published_pretrained_checkpoint
import isaaclab_tasks # noqa: F401
from isaaclab_tasks.utils import (
add_launcher_args,
get_checkpoint_path,
launch_simulation,
setup_preset_cli,
)
from isaaclab_tasks.utils.hydra import hydra_task_config
# local imports
import cli_args # isort: skip
import go2Demo.tasks # noqa: F401
with contextlib.suppress(ImportError):
import isaaclab_tasks_experimental # noqa: F401
# -- argparse ----------------------------------------------------------------
parser = argparse.ArgumentParser(description="Play a checkpoint of an RL agent from RSL-RL.")
parser.add_argument("--video", action="store_true", default=False, help="Record videos during play.")
parser.add_argument("--video_length", type=int, default=200, help="Length of the recorded video (in steps).")
parser.add_argument(
"--disable_fabric", action="store_true", default=False, help="Disable fabric and use USD I/O operations."
)
parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.")
parser.add_argument("--task", type=str, default=None, help="Name of the task.")
parser.add_argument(
"--agent", type=str, default="rsl_rl_cfg_entry_point", help="Name of the RL agent configuration entry point."
)
parser.add_argument("--seed", type=int, default=None, help="Seed used for the environment")
parser.add_argument(
"--use_pretrained_checkpoint",
action="store_true",
help="Use the pre-trained checkpoint from Nucleus.",
)
parser.add_argument("--real-time", action="store_true", default=False, help="Run in real-time, if possible.")
parser.add_argument("--external_callback", default=None, help="Fully qualified path to an externally defined callback.")
cli_args.add_rsl_rl_args(parser)
add_launcher_args(parser)
args_cli, remaining_args = setup_preset_cli(parser)
if args_cli.video:
args_cli.enable_cameras = True
# Call an external callback if requested. This gives opportunity to external code to register the environments
# The function is expected to return a list of arguments that were not consumed by the callback.
remaining_args_env_registration = None
if args_cli.external_callback:
external_callback_function = string_to_callable(args_cli.external_callback, separator=".")
remaining_args_env_registration = external_callback_function()
# clear out sys.argv for Hydra
# The remaining arguments are the arguments that were not consumed by both this scripts
# argparser and (optionally) the external callback function.
remaining_args = list_intersection(remaining_args, remaining_args_env_registration)
sys.argv = [sys.argv[0]] + remaining_args
# Check for installed RSL-RL version
installed_version = metadata.version("rsl-rl-lib")
@hydra_task_config(args_cli.task, args_cli.agent)
def main(env_cfg: ManagerBasedRLEnvCfg | DirectRLEnvCfg | DirectMARLEnvCfg, agent_cfg: RslRlBaseRunnerCfg):
"""Play with RSL-RL agent."""
with launch_simulation(env_cfg, args_cli):
# grab task name for checkpoint path
task_name = args_cli.task.split(":")[-1]
train_task_name = task_name.replace("-Play", "")
# override configurations with non-hydra CLI arguments
agent_cfg = cli_args.update_rsl_rl_cfg(agent_cfg, args_cli)
env_cfg.scene.num_envs = args_cli.num_envs if args_cli.num_envs is not None else env_cfg.scene.num_envs
# handle deprecated configurations
agent_cfg = handle_deprecated_rsl_rl_cfg(agent_cfg, installed_version)
# set the environment seed
# note: certain randomizations occur in the environment initialization so we set the seed here
env_cfg.seed = agent_cfg.seed
env_cfg.sim.device = args_cli.device if args_cli.device is not None else env_cfg.sim.device
# specify directory for logging experiments
log_root_path = os.path.join("logs", "rsl_rl", agent_cfg.experiment_name)
log_root_path = os.path.abspath(log_root_path)
print(f"[INFO] Loading experiment from directory: {log_root_path}")
if args_cli.use_pretrained_checkpoint:
resume_path = get_published_pretrained_checkpoint("rsl_rl", train_task_name)
if not resume_path:
print("[INFO] Unfortunately a pre-trained checkpoint is currently unavailable for this task.")
return
elif args_cli.checkpoint:
resume_path = retrieve_file_path(args_cli.checkpoint)
else:
resume_path = get_checkpoint_path(log_root_path, agent_cfg.load_run, agent_cfg.load_checkpoint)
log_dir = os.path.dirname(resume_path)
# set the log directory for the environment
env_cfg.log_dir = log_dir
# create isaac environment
env = gym.make(args_cli.task, cfg=env_cfg, render_mode="rgb_array" if args_cli.video else None)
# convert to single-agent instance if required by the RL algorithm
if isinstance(env.unwrapped.cfg, DirectMARLEnvCfg):
from isaaclab.envs import multi_agent_to_single_agent
env = multi_agent_to_single_agent(env)
# wrap for video recording
if args_cli.video:
video_kwargs = {
"video_folder": os.path.join(log_dir, "videos", "play"),
"step_trigger": lambda step: step == 0,
"video_length": args_cli.video_length,
"disable_logger": True,
}
print("[INFO] Recording videos during play.")
print_dict(video_kwargs, nesting=4)
env = gym.wrappers.RecordVideo(env, **video_kwargs)
# wrap around environment for rsl-rl
env = RslRlVecEnvWrapper(env, clip_actions=agent_cfg.clip_actions)
print(f"[INFO]: Loading model checkpoint from: {resume_path}")
# load previously trained model
if agent_cfg.class_name == "OnPolicyRunner":
runner = OnPolicyRunner(env, agent_cfg.to_dict(), log_dir=None, device=agent_cfg.device)
elif agent_cfg.class_name == "DistillationRunner":
runner = DistillationRunner(env, agent_cfg.to_dict(), log_dir=None, device=agent_cfg.device)
else:
raise ValueError(f"Unsupported runner class: {agent_cfg.class_name}")
runner.load(resume_path)
# obtain the trained policy for inference
policy = runner.get_inference_policy(device=env.unwrapped.device)
# export the trained policy to JIT and ONNX formats
export_model_dir = os.path.join(os.path.dirname(resume_path), "exported")
if version.parse(installed_version) >= version.parse("4.0.0"):
# use the new export functions for rsl-rl >= 4.0.0
runner.export_policy_to_jit(path=export_model_dir, filename="policy.pt")
runner.export_policy_to_onnx(path=export_model_dir, filename="policy.onnx")
policy_nn = None # Not needed for rsl-rl >= 4.0.0
else:
# extract the neural network for rsl-rl < 4.0.0
if version.parse(installed_version) >= version.parse("2.3.0"):
policy_nn = runner.alg.policy
else:
policy_nn = runner.alg.actor_critic
# extract the normalizer
if hasattr(policy_nn, "actor_obs_normalizer"):
normalizer = policy_nn.actor_obs_normalizer
elif hasattr(policy_nn, "student_obs_normalizer"):
normalizer = policy_nn.student_obs_normalizer
else:
normalizer = None
# export to JIT and ONNX
export_policy_as_jit(policy_nn, normalizer=normalizer, path=export_model_dir, filename="policy.pt")
export_policy_as_onnx(policy_nn, normalizer=normalizer, path=export_model_dir, filename="policy.onnx")
dt = env.unwrapped.step_dt
# reset environment
obs = env.get_observations()
timestep = 0
# simulate environment
try:
while True:
start_time = time.time()
# run everything in inference mode
with torch.inference_mode():
# agent stepping
actions = policy(obs)
# env stepping
obs, _, dones, _ = env.step(actions)
# reset recurrent states for episodes that have terminated
if version.parse(installed_version) >= version.parse("4.0.0"):
policy.reset(dones)
else:
policy_nn.reset(dones)
if args_cli.video:
timestep += 1
if timestep == args_cli.video_length:
break
sleep_time = dt - (time.time() - start_time)
if args_cli.real_time and sleep_time > 0:
time.sleep(sleep_time)
# close the simulator
env.close()
except KeyboardInterrupt:
pass
if __name__ == "__main__":
main()

View File

@ -0,0 +1,183 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""RSL-RL training logic for the unified reinforcement learning entrypoint."""
from __future__ import annotations
import argparse
import contextlib
import importlib.metadata as metadata
import logging
import os
import platform
import time
from datetime import datetime
from pathlib import Path
from common import (
add_common_train_args,
add_isaaclab_launcher_args,
apply_env_overrides,
configure_io_descriptors,
create_isaaclab_env,
dump_train_configs,
enable_cameras_for_video,
import_local_module,
set_hydra_args,
validate_distributed_device,
wrap_record_video,
)
from packaging import version
import isaaclab_tasks # noqa: F401
logger = logging.getLogger(__name__)
RSL_RL_VERSION = "5.0.1"
RL_ROOT = Path(__file__).resolve().parents[1]
CLI_ARGS = import_local_module("isaaclab_rsl_rl_cli_args", RL_ROOT / "rsl_rl" / "cli_args.py")
import go2Demo.tasks # noqa: F401
with contextlib.suppress(ImportError):
import isaaclab_tasks_experimental # noqa: F401
def _check_rsl_rl_version() -> str:
"""Check that the installed RSL-RL version is supported."""
installed_version = metadata.version("rsl-rl-lib")
if version.parse(installed_version) < version.parse(RSL_RL_VERSION):
if platform.system() == "Windows":
cmd = [r".\isaaclab.bat", "-p", "-m", "pip", "install", f"rsl-rl-lib=={RSL_RL_VERSION}"]
else:
cmd = ["./isaaclab.sh", "-p", "-m", "pip", "install", f"rsl-rl-lib=={RSL_RL_VERSION}"]
print(
f"Please install the correct version of RSL-RL.\nExisting version is: '{installed_version}'"
f" and required version is: '{RSL_RL_VERSION}'.\nTo install the correct version, run:"
f"\n\n\t{' '.join(cmd)}\n"
)
raise SystemExit(1)
return installed_version
def _parse_args(argv: list[str]) -> argparse.Namespace:
"""Parse RSL-RL training arguments."""
from isaaclab.utils.string import list_intersection, string_to_callable
from isaaclab_tasks.utils import setup_preset_cli
parser = argparse.ArgumentParser(description="Train an RL agent with RSL-RL.")
add_common_train_args(
parser,
agent_default="rsl_rl_cfg_entry_point",
agent_help="Name of the RL agent configuration entry point.",
)
parser.add_argument(
"--external_callback",
default=None,
help="Fully qualified path to an externally defined callback.",
)
CLI_ARGS.add_rsl_rl_args(parser)
add_isaaclab_launcher_args(parser)
# setup_preset_cli registers preset-selection help text + runs parse_known_args
args_cli, remaining_args = setup_preset_cli(parser, argv)
enable_cameras_for_video(args_cli)
remaining_args_env_registration = None
if args_cli.external_callback:
external_callback_function = string_to_callable(args_cli.external_callback, separator=".")
remaining_args_env_registration = external_callback_function()
# physics=/renderer=/presets= tokens pass through the remainder for hydra to parse later
set_hydra_args(list_intersection(remaining_args, remaining_args_env_registration))
return args_cli
def run(argv: list[str]) -> None:
"""Train an RSL-RL agent."""
import torch
from rsl_rl.runners import DistillationRunner, OnPolicyRunner
from isaaclab.envs import DirectMARLEnvCfg
from isaaclab_rl.rsl_rl import RslRlVecEnvWrapper, handle_deprecated_rsl_rl_cfg
from isaaclab_tasks.utils import get_checkpoint_path, launch_simulation, resolve_task_config
torch.backends.cuda.matmul.allow_tf32 = True
torch.backends.cudnn.allow_tf32 = True
torch.backends.cudnn.deterministic = False
torch.backends.cudnn.benchmark = False
args_cli = _parse_args(argv)
installed_version = _check_rsl_rl_version()
env_cfg, agent_cfg = resolve_task_config(args_cli.task, args_cli.agent)
with launch_simulation(env_cfg, args_cli):
agent_cfg = CLI_ARGS.update_rsl_rl_cfg(agent_cfg, args_cli)
apply_env_overrides(args_cli, env_cfg)
agent_cfg.max_iterations = (
args_cli.max_iterations if args_cli.max_iterations is not None else agent_cfg.max_iterations
)
agent_cfg = handle_deprecated_rsl_rl_cfg(agent_cfg, installed_version)
env_cfg.seed = agent_cfg.seed
validate_distributed_device(args_cli)
if args_cli.distributed:
global_rank = int(os.getenv("RANK", "0"))
agent_cfg.device = env_cfg.sim.device
seed = agent_cfg.seed + global_rank
env_cfg.seed = seed
agent_cfg.seed = seed
log_root_path = os.path.abspath(os.path.join("logs", "rsl_rl", agent_cfg.experiment_name))
print(f"[INFO] Logging experiment in directory: {log_root_path}")
log_dir = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
print(f"Exact experiment name requested from command line: {log_dir}")
if agent_cfg.run_name:
log_dir += f"_{agent_cfg.run_name}"
log_dir = os.path.join(log_root_path, log_dir)
configure_io_descriptors(env_cfg, args_cli, logger)
env_cfg.log_dir = log_dir
env = create_isaaclab_env(
args_cli.task,
env_cfg,
args_cli,
convert_marl_to_single_agent=isinstance(env_cfg, DirectMARLEnvCfg),
)
if agent_cfg.resume or agent_cfg.algorithm.class_name == "Distillation":
resume_path = get_checkpoint_path(log_root_path, agent_cfg.load_run, agent_cfg.load_checkpoint)
env = wrap_record_video(env, log_dir, args_cli)
start_time = time.time()
env = RslRlVecEnvWrapper(env, clip_actions=agent_cfg.clip_actions)
if agent_cfg.class_name == "OnPolicyRunner":
runner = OnPolicyRunner(env, agent_cfg.to_dict(), log_dir=log_dir, device=agent_cfg.device)
elif agent_cfg.class_name == "DistillationRunner":
runner = DistillationRunner(env, agent_cfg.to_dict(), log_dir=log_dir, device=agent_cfg.device)
else:
raise ValueError(f"Unsupported runner class: {agent_cfg.class_name}")
runner.add_git_repo_to_log(__file__)
if agent_cfg.resume or agent_cfg.algorithm.class_name == "Distillation":
print(f"[INFO]: Loading model checkpoint from: {resume_path}")
runner.load(resume_path)
dump_train_configs(log_dir, env_cfg, agent_cfg)
try:
runner.learn(num_learning_iterations=agent_cfg.max_iterations, init_at_random_ep_len=True)
print(f"Training time: {round(time.time() - start_time, 2)} seconds")
env.close()
except KeyboardInterrupt:
pass

86
scripts/zero_agent.py Normal file
View File

@ -0,0 +1,86 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Script to run an environment with zero action agent."""
import argparse
import contextlib
import sys
import gymnasium as gym
import torch
import isaaclab_tasks # noqa: F401
with contextlib.suppress(ImportError):
import isaaclab_tasks_experimental # noqa: F401
from isaaclab_tasks.utils import (
add_launcher_args,
launch_simulation,
resolve_task_config,
setup_preset_cli,
)
# add argparse arguments
parser = argparse.ArgumentParser(description="Zero agent for Isaac Lab environments.")
parser.add_argument(
"--disable_fabric", action="store_true", default=False, help="Disable fabric and use USD I/O operations."
)
parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.")
parser.add_argument("--task", type=str, default=None, help="Name of the task.")
# append AppLauncher cli args
add_launcher_args(parser)
# simple agents should open Kit visualizer by default
parser.set_defaults(visualizer=["kit"])
args_cli, hydra_args = setup_preset_cli(parser)
sys.argv = [sys.argv[0]] + hydra_args
import go2Demo.tasks # noqa: F401
MAX_STEPS = 100
def main():
"""Zero actions agent with Isaac Lab environment."""
torch.manual_seed(42)
# parse configuration via Hydra (supports preset selection, e.g. env.sim.physics=newton_mjwarp)
env_cfg, _ = resolve_task_config(args_cli.task, "")
with launch_simulation(env_cfg, args_cli):
# override with CLI arguments
env_cfg.scene.num_envs = args_cli.num_envs if args_cli.num_envs is not None else env_cfg.scene.num_envs
env_cfg.sim.device = args_cli.device if args_cli.device is not None else env_cfg.sim.device
if args_cli.disable_fabric:
env_cfg.sim.use_fabric = False
# create environment
env = gym.make(args_cli.task, cfg=env_cfg)
# print info (this is vectorized environment)
print(f"[INFO]: Gym observation space: {env.observation_space}")
print(f"[INFO]: Gym action space: {env.action_space}")
# reset environment
env.reset()
# simulate environment
# keep running while any visualizer is open, otherwise fall back to MAX_STEPS
sim = env.unwrapped.sim
actions = torch.zeros(env.action_space.shape, device=env.unwrapped.device)
while True:
if sim.visualizers:
# visualizer mode: run until the visualizer window is closed
if not any(v.is_running() and not v.is_closed for v in sim.visualizers):
break
# run everything in inference mode
with torch.inference_mode():
# apply actions
env.step(actions)
# close the simulator
env.close()
if __name__ == "__main__":
# run the main function
main()

View File

@ -0,0 +1,45 @@
[package]
# Semantic Versioning is used: https://semver.org/
version = "0.1.0"
# Description
category = "isaaclab"
readme = "README.md"
title = "Extension Template"
author = "Isaac Lab Project Developers"
maintainer = "Isaac Lab Project Developers"
description="Extension Template for Isaac Lab"
repository = "https://github.com/isaac-sim/IsaacLab.git"
keywords = ["extension", "template", "isaaclab"]
[dependencies]
"isaaclab" = {}
"isaaclab_assets" = {}
"isaaclab_mimic" = {}
"isaaclab_rl" = {}
"isaaclab_tasks" = {}
# NOTE: Add additional dependencies here
[[python.module]]
name = "go2Demo"
# UI extension module: Kit imports this submodule directly and scans it for ``omni.ext.IExt``
# subclasses. Kept separate from the package root so ``import go2Demo`` stays omni-free headless.
[[python.module]]
name = "go2Demo.ui_extension_example"
[isaac_lab_settings]
# TODO: Uncomment and list any apt dependencies here.
# If none, leave it commented out.
# apt_deps = ["example_package"]
# TODO: Uncomment and provide path to a ros_ws
# with rosdeps to be installed. If none,
# leave it commented out.
# ros_ws = "path/from/extension_root/to/ros_ws"
# TODO: Uncomment and list install_requires dependency names that should be upgraded
# after this extension is installed with ./isaaclab.sh --install.
# List package names only; version ranges, extras, and platform markers
# come from this extension's setup.py metadata.
# pip_upgrade_dependencies = ["example_package"]

View File

@ -0,0 +1,10 @@
Changelog
---------
0.1.0 (2026-08-08)
~~~~~~~~~~~~~~~~~~
Added
^^^^^
* Created an initial template for building an extension or project based on Isaac Lab

View File

@ -0,0 +1,49 @@
# 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 isaaclab.utils.configclass import configclass
from isaaclab_rl.rsl_rl import RslRlMLPModelCfg, RslRlOnPolicyRunnerCfg, RslRlPpoAlgorithmCfg
@configclass
class PPORunnerCfg(RslRlOnPolicyRunnerCfg):
"""
PPO算法运行配置类继承自RslRlOnPolicyRunnerCfg
定义了PPO算法训练过程中的各种参数设置
"""
num_steps_per_env = 16 # 每个环境执行的步数
max_iterations = 150 # 最大迭代次数
save_interval = 50 # 模型保存间隔
experiment_name = "cartpole_direct" # 实验名称,用于标识当前实验
# Actor网络配置
actor = RslRlMLPModelCfg(
hidden_dims=[32, 32], # 隐藏层维度
activation="elu", # 激活函数类型
obs_normalization=False, # 是否进行观测值归一化
distribution_cfg=RslRlMLPModelCfg.GaussianDistributionCfg(init_std=1.0), # 动作分布配置
)
# Critic网络配置
critic = RslRlMLPModelCfg(
hidden_dims=[32, 32], # 隐藏层维度
activation="elu", # 激活函数类型
obs_normalization=False, # 是否进行观测值归一化
)
# PPO算法配置
algorithm = RslRlPpoAlgorithmCfg(
value_loss_coef=1.0, # 价值损失系数
use_clipped_value_loss=True, # 是否使用裁剪的价值损失
clip_param=0.2, # 裁剪参数
entropy_coef=0.005, # 熵系数
num_learning_epochs=5, # 学习轮数
num_mini_batches=4, # 小批量数量
learning_rate=1.0e-3, # 学习率
schedule="adaptive", # 学习率调度策略
gamma=0.99, # 折扣因子
lam=0.95, # GAE(lambda)参数
desired_kl=0.01, # 期望的KL散度
max_grad_norm=1.0, # 最大梯度范数
)

View File

@ -0,0 +1,195 @@
# 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
import math
import isaaclab.sim as sim_utils
from isaaclab.assets import ArticulationCfg, AssetBaseCfg
from isaaclab.envs import ManagerBasedRLEnvCfg
from isaaclab.managers import EventTermCfg as EventTerm
from isaaclab.managers import ObservationGroupCfg as ObsGroup
from isaaclab.managers import ObservationTermCfg as ObsTerm
from isaaclab.managers import RewardTermCfg as RewTerm
from isaaclab.managers import SceneEntityCfg
from isaaclab.managers import TerminationTermCfg as DoneTerm
from isaaclab.scene import InteractiveSceneCfg
from isaaclab.utils.configclass import configclass
from . import mdp
##
# Pre-defined configs
##
from isaaclab_assets.robots.cartpole import CARTPOLE_CFG # isort:skip
##
# Scene definition
##
@configclass
class Go2demoSceneCfg(InteractiveSceneCfg):
"""Configuration for a cart-pole scene.""" # 多行注释:这是一个用于配置-cart-pole场景的类
# ground plane # 单行注释:地面平面配置
ground = AssetBaseCfg(
prim_path="/World/ground", # 单行注释:地面的基础路径
spawn=sim_utils.GroundPlaneCfg(size=(100.0, 100.0)), # 单行注释生成一个大小为100x100的地面平面
)
# robot # 单行注释:机器人配置
robot: ArticulationCfg = CARTPOLE_CFG.replace(prim_path="{ENV_REGEX_NS}/Robot") # 单行注释使用CARTPOLE_CFG配置并替换机器人路径
# lights # 单行注释:灯光配置
dome_light = AssetBaseCfg(
prim_path="/World/DomeLight", # 单行注释:穹顶灯光的基础路径
spawn=sim_utils.DomeLightCfg(color=(0.9, 0.9, 0.9), intensity=500.0), # 单行注释:生成一个颜色为(0.9, 0.9, 0.9)、强度为500.0的穹顶灯光
)
##
# MDP settings
##
@configclass
class ActionsCfg:
joint_effort = mdp.JointEffortActionCfg(asset_name="robot", joint_names=["slider_to_cart"], scale=100.0)
@configclass
class ObservationsCfg:
"""观察配置类,用于定义和管理观察相关的配置项"""
@configclass
class PolicyCfg(ObsGroup):
"""策略配置类继承自ObsGroup用于定义策略相关的观察项"""
# observation terms (order preserved)
joint_pos_rel = ObsTerm(func=mdp.joint_pos_rel) # 关节位置相对观察项
joint_vel_rel = ObsTerm(func=mdp.joint_vel_rel) # 关节速度相对观察项
def __post_init__(self) -> None:
"""
初始化后的设置方法
设置是否启用数据损坏和是否连接观察项
"""
self.enable_corruption = False # 禁用数据损坏
self.enable_corruption = False # 禁用数据损坏
self.concatenate_terms = True # 启用观察项连接
policy: PolicyCfg = PolicyCfg() # 实例化策略配置类作为观察组
@configclass
class EventCfg:
"""
事件配置类用于定义各种事件及其参数配置
包含两个事件重置小车位置和重置摆杆位置
"""
reset_cart_position = EventTerm(
func=mdp.reset_joints_by_offset, # 重置关节位置的函数
mode="reset", # 事件模式为重置
params={ # 事件参数配置
"asset_cfg": SceneEntityCfg("robot", joint_names=["slider_to_cart"]), # 场景实体配置,指定机器人及其关节名称
"position_range": (-1.0, 1.0), # 位置范围,限制小车的位置在-1.0到1.0之间
"velocity_range": (-0.5, 0.5), # 速度范围,限制小车的速度在-0.5到0.5之间
},
)
reset_pole_position = EventTerm(
func=mdp.reset_joints_by_offset, # 重置关节位置的函数
mode="reset", # 事件模式为重置
params={ # 事件参数配置
"asset_cfg": SceneEntityCfg("robot", joint_names=["cart_to_pole"]), # 场景实体配置,指定机器人及其关节名称
"position_range": (-0.25 * math.pi, 0.25 * math.pi), # 位置范围,限制摆杆的位置在-π/4到π/4之间
"velocity_range": (-0.25 * math.pi, 0.25 * math.pi), # 速度范围,限制摆杆的速度在-π/4到π/4之间
},
)
@configclass
class RewardsCfg:
"""
奖励配置类用于定义各种奖励项及其权重
"""
# 存活奖励项如果智能体存活则获得1.0的奖励
alive = RewTerm(func=mdp.is_alive, weight=1.0)
# 终止惩罚项:如果智能体终止,则获得-2.0的惩罚
terminating = RewTerm(func=mdp.is_terminated, weight=-2.0)
# 杆位置奖励项:鼓励杆保持在目标位置(0.0),偏离目标位置会受到惩罚
pole_pos = RewTerm(
func=mdp.joint_pos_target_l2,
weight=-1.0,
params={"asset_cfg": SceneEntityCfg("robot", joint_names=["cart_to_pole"]), "target": 0.0},
)
# 小车速度奖励项:限制小车的速度,速度过快会受到惩罚
cart_vel = RewTerm(
func=mdp.joint_vel_l1,
weight=-0.01,
params={"asset_cfg": SceneEntityCfg("robot", joint_names=["slider_to_cart"])},
)
# 杆速度奖励项:限制杆的速度,速度过快会受到惩罚
pole_vel = RewTerm(
func=mdp.joint_vel_l1,
weight=-0.005,
params={"asset_cfg": SceneEntityCfg("robot", joint_names=["cart_to_pole"])},
)
@configclass
class TerminationsCfg:
time_out = DoneTerm(func=mdp.time_out, time_out=True)
cart_out_of_bounds = DoneTerm(
func=mdp.joint_pos_out_of_manual_limit,
params={"asset_cfg": SceneEntityCfg("robot", joint_names=["slider_to_cart"]), "bounds": (-3.0, 3.0)},
)
@configclass
class Go2demoEnvCfg(ManagerBasedRLEnvCfg):
"""
Go2demo环境配置类继承自ManagerBasedRLEnvCfg
用于配置和管理Go2机器人在模拟环境中的各种参数
"""
scene: Go2demoSceneCfg = Go2demoSceneCfg(num_envs=4096, env_spacing=4.0) # 场景配置,设置环境数量和间距
observations: ObservationsCfg = ObservationsCfg() # 观察配置,定义智能体可以观察到的状态信息
actions: ActionsCfg = ActionsCfg() # 动作配置,定义智能体可以执行的动作
events: EventCfg = EventCfg() # 事件配置,定义环境中的各种事件
rewards: RewardsCfg = RewardsCfg() # 奖励配置,定义智能体获得奖励的规则
terminations: TerminationsCfg = TerminationsCfg() # 终止条件配置,定义回合结束的条件
def __post_init__(self) -> None:
"""
初始化方法在对象创建后自动调用
用于设置和配置模拟环境的各种参数
"""
self.decimation = 2 # 降采样率,控制模拟的频率
self.episode_length_s = 5 # 每个回合的持续时间(秒)
self.viewer.eye = (8.0, 0.0, 5.0) # 设置观察者的位置坐标x, y, z
self.sim.dt = 1 / 120 # 模拟时间步长(秒)
self.sim.render_interval = self.decimation # 渲染间隔,基于降采样率设置

View File

@ -0,0 +1,27 @@
# Copyright (c) 2022-2026, 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
from typing import TYPE_CHECKING
import torch
from isaaclab.managers import SceneEntityCfg
from isaaclab.utils.math import wrap_to_pi
if TYPE_CHECKING:
from isaaclab.assets import Articulation
from isaaclab.envs import ManagerBasedRLEnv
def joint_pos_target_l2(env: ManagerBasedRLEnv, target: float, asset_cfg: SceneEntityCfg) -> torch.Tensor:
"""Penalize joint position deviation from a target value."""
# extract the used quantities (to enable type-hinting)
asset: Articulation = env.scene[asset_cfg.name]
# wrap the joint positions to (-pi, pi)
joint_pos = wrap_to_pi(asset.data.joint_pos[:, asset_cfg.joint_ids])
# compute the reward
return torch.sum(torch.square(joint_pos - target), dim=1)

View File

@ -0,0 +1,47 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
import omni.ext
import omni.ui # used by ExampleExtension.on_startup
# Functions and vars are available to other extension as usual in python: `example.python_ext.some_public_function(x)`
def some_public_function(x: int):
print("[go2Demo] some_public_function was called with x: ", x)
return x**x
# Any class derived from `omni.ext.IExt` in top level module (defined in `python.modules` of `extension.toml`) will be
# instantiated when extension gets enabled and `on_startup(ext_id)` will be called. Later when extension gets disabled
# on_shutdown() is called.
class ExampleExtension(omni.ext.IExt):
# ext_id is current extension id. It can be used with extension manager to query additional information, like where
# this extension is located on filesystem.
def on_startup(self, ext_id):
print("[go2Demo] startup")
self._count = 0
self._window = omni.ui.Window("My Window", width=300, height=300)
with self._window.frame:
with omni.ui.VStack():
label = omni.ui.Label("")
def on_click():
self._count += 1
label.text = f"count: {self._count}"
def on_reset():
self._count = 0
label.text = "empty"
on_reset()
with omni.ui.HStack():
omni.ui.Button("Add", clicked_fn=on_click)
omni.ui.Button("Reset", clicked_fn=on_reset)
def on_shutdown(self):
print("[go2Demo] shutdown")

View File

@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools<82.0.0", "wheel", "toml"]
build-backend = "setuptools.build_meta"

44
source/go2Demo/setup.py Normal file
View File

@ -0,0 +1,44 @@
# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
"""Installation script for the 'go2Demo' python package."""
import os
import toml
from setuptools import setup
# Obtain the extension data from the extension.toml file
EXTENSION_PATH = os.path.dirname(os.path.realpath(__file__))
# Read the extension.toml file
EXTENSION_TOML_DATA = toml.load(os.path.join(EXTENSION_PATH, "config", "extension.toml"))
# Minimum dependencies required prior to installation
INSTALL_REQUIRES = [
# NOTE: Add dependencies
"psutil",
]
# Installation operation
setup(
name="go2Demo",
packages=["go2Demo"],
author=EXTENSION_TOML_DATA["package"]["author"],
maintainer=EXTENSION_TOML_DATA["package"]["maintainer"],
url=EXTENSION_TOML_DATA["package"]["repository"],
version=EXTENSION_TOML_DATA["package"]["version"],
description=EXTENSION_TOML_DATA["package"]["description"],
keywords=EXTENSION_TOML_DATA["package"]["keywords"],
install_requires=INSTALL_REQUIRES,
license="Apache-2.0",
include_package_data=True,
python_requires=">=3.12",
classifiers=[
"Natural Language :: English",
"Programming Language :: Python :: 3.12",
"Isaac Sim :: 6.0.0",
],
zip_safe=False,
)

80
sync.sh Executable file
View File

@ -0,0 +1,80 @@
#!/bin/bash
# ============================================================
# 脚本名称: git-force-sync.sh
# 功能描述: 强制让本地分支与远程仓库完全一致(远程覆盖本地)
# 警告提示: 会永久删除本地所有未提交的修改、未推送的提交和未跟踪的文件
# ============================================================
set -e # 遇到错误立即退出
# ---------- 颜色定义(提升可读性)----------
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# ---------- 1. 检查是否在 Git 仓库中 ----------
if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
echo -e "${RED}错误:当前目录不是 Git 仓库${NC}"
exit 1
fi
# ---------- 2. 获取当前分支名 ----------
CURRENT_BRANCH=$(git branch --show-current)
if [ -z "$CURRENT_BRANCH" ]; then
echo -e "${RED}错误:当前处于 HEAD 分离状态detached HEAD请先切换到具体分支。${NC}"
exit 1
fi
echo -e "${BLUE}当前分支:${CURRENT_BRANCH}${NC}"
# ---------- 3. 检查本地是否有未提交的更改 ----------
if ! git diff --quiet || ! git diff --cached --quiet; then
echo -e "${YELLOW}⚠️ 检测到本地存在未提交的更改(包括已暂存和未暂存)。${NC}"
echo -e "${RED}这些更改将在同步后永久丢失!${NC}"
read -p "确定要继续吗?请输入 'yes' 以确认: " CONFIRM
if [ "$CONFIRM" != "yes" ]; then
echo -e "${GREEN}操作已取消。${NC}"
exit 0
fi
fi
# ---------- 4. 检查是否有未跟踪的文件(可选清理提示) ----------
UNTRACKED=$(git ls-files --others --exclude-standard | head -n 1)
if [ -n "$UNTRACKED" ]; then
echo -e "${YELLOW}⚠️ 检测到本地存在未跟踪的文件(如编译产物、日志等)。${NC}"
read -p "是否同步删除这些未跟踪文件?(y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
DO_CLEAN=true
else
DO_CLEAN=false
fi
else
DO_CLEAN=false
fi
# ---------- 5. 拉取远程最新引用 ----------
echo -e "${BLUE}正在从远程仓库获取最新引用...${NC}"
git fetch origin
# ---------- 6. 验证远程分支是否存在 ----------
if ! git rev-parse "origin/$CURRENT_BRANCH" > /dev/null 2>&1; then
echo -e "${RED}错误:远程不存在分支 origin/$CURRENT_BRANCH,请检查分支名。${NC}"
exit 1
fi
# ---------- 7. 执行强制覆盖(硬重置) ----------
echo -e "${BLUE}正在将本地重置为 origin/$CURRENT_BRANCH ...${NC}"
git reset --hard "origin/$CURRENT_BRANCH"
# ---------- 8. 清理未跟踪文件 ----------
if [ "$DO_CLEAN" = true ]; then
echo -e "${BLUE}正在删除所有未跟踪的文件和目录...${NC}"
git clean -fd
else
echo -e "${YELLOW}已跳过删除未跟踪文件。${NC}"
fi
# ---------- 9. 完成 ----------
echo -e "${GREEN}✅ 同步完成!本地分支 '${CURRENT_BRANCH}' 已与远程 'origin/${CURRENT_BRANCH}' 完全一致。${NC}"