Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 68 additions & 42 deletions docs/source/overview/developer-guide/template.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,32 @@ The generator will guide you in setting up the project/task for your needs by as
* Isaac Lab workflows (see :ref:`feature-workflows`).
* Reinforcement learning libraries (see :ref:`rl-frameworks`), and algorithms (if the selected libraries support multiple algorithms).

Optional: Include Custom USD Assets (external project only)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

If your project involves custom robots or environments with their own USD files, the template generator supports this directly.

This is especially useful if you want to keep everything self-contained in a single, isolated directory — without having your tasks in the external project and your custom assets elsewhere (e.g., in the main Isaac Lab repository structure).

To add your own robot:

1. Place your USD file in::

FULL_PATH_TO_PROJECT/<given-project-name>/source/<given-project-name>/data/Robots/<your_robot_name>/<your_robot_name>.usd

2. Create a corresponding configuration file in::

FULL_PATH_TO_PROJECT/<given-project-name>/source/<given-project-name>/<given-project-name>/robots/<your_robot_name>.py

following the format described in the :ref:`how-to-write-articulation-config`.

3. Import your asset configuration in the following file::

FULL_PATH_TO_PROJECT/<given-project-name>/source/<given-project-name>/<given-project-name>/robots/__init__.py

4. Import your robot config in your environment configuration file (e.g., ``*_env_cfg.py``), just like in the Cartpole examples provided by the generated template.


External project usage (once generated)
---------------------------------------

Expand Down Expand Up @@ -134,6 +160,48 @@ Here are some general commands to get started with it:

python scripts\<specific-rl-library>\train.py --task=<Task-Name>

* Run 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

.. tab-set::
:sync-group: os

.. tab-item:: :icon:`fa-brands fa-linux` Linux
:sync: linux

.. code-block:: bash

python scripts/zero_agent.py --task=<Task-Name>

.. tab-item:: :icon:`fa-brands fa-windows` Windows
:sync: windows

.. code-block:: batch

python scripts\zero_agent.py --task=<Task-Name>

* Random-action agent

.. tab-set::
:sync-group: os

.. tab-item:: :icon:`fa-brands fa-linux` Linux
:sync: linux

.. code-block:: bash

python scripts/random_agent.py --task=<Task-Name>

.. tab-item:: :icon:`fa-brands fa-windows` Windows
:sync: windows

.. code-block:: batch

python scripts\random_agent.py --task=<Task-Name>

For more details, please follow the instructions in the generated project's ``README.md`` file.

Internal task usage (once generated)
Expand Down Expand Up @@ -185,45 +253,3 @@ Here are some general commands to get started with it:
.. code-block:: batch

python scripts\reinforcement_learning\<specific-rl-library>\train.py --task=<Task-Name>

* Run 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

.. tab-set::
:sync-group: os

.. tab-item:: :icon:`fa-brands fa-linux` Linux
:sync: linux

.. code-block:: bash

python scripts/zero_agent.py --task=<Task-Name>

.. tab-item:: :icon:`fa-brands fa-windows` Windows
:sync: windows

.. code-block:: batch

python scripts\zero_agent.py --task=<Task-Name>

* Random-action agent

.. tab-set::
:sync-group: os

.. tab-item:: :icon:`fa-brands fa-linux` Linux
:sync: linux

.. code-block:: bash

python scripts/random_agent.py --task=<Task-Name>

.. tab-item:: :icon:`fa-brands fa-windows` Windows
:sync: windows

.. code-block:: batch

python scripts\random_agent.py --task=<Task-Name>
13 changes: 13 additions & 0 deletions tools/template/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,18 @@ def main() -> None:
),
)

# custom robot support (if 'external')
use_custom_usd = None
if is_external_project:
use_custom_usd = (
cli_handler.input_select(
"Do you want to include a custom robot USD file?",
choices=["Yes", "No"],
default="No",
)
== "Yes"
)

# Isaac Lab workflow
# - show supported workflows and features
workflow_table = rich.table.Table(title="RL environment features support according to Isaac Lab workflows")
Expand Down Expand Up @@ -244,6 +256,7 @@ def main() -> None:
"external": is_external_project,
"path": project_path,
"name": project_name,
"custom_usd": use_custom_usd,
"workflows": workflow,
"rl_libraries": rl_library_algorithms,
}
Expand Down
141 changes: 141 additions & 0 deletions tools/template/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import shutil
import subprocess
import sys
import urllib.request
from datetime import datetime

import jinja2
Expand Down Expand Up @@ -245,6 +246,146 @@ def _external(specification: dict) -> None:
os.path.join(TEMPLATE_DIR, "extension", "__init__workflow"),
os.path.join(dir, workflow["name"].replace("-", "_"), "__init__.py"),
)

# create custom robot structure
if specification.get("custom_usd", False):
print(" |-- Creating custom robot folder structure...")

import argparse

from isaaclab.app import AppLauncher

# create argparser
parser = argparse.ArgumentParser(description="Downloading the assets.")
# append AppLauncher cli args
AppLauncher.add_app_launcher_args(parser)
# parse the arguments
args_cli = parser.parse_args()
args_cli.headless = True
# launch omniverse app
app_launcher = AppLauncher(args_cli)
simulation_app = app_launcher.app

from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR

# look at workflows once
has_single = any(wf["type"] == "single-agent" for wf in specification["workflows"])
has_multi = any(wf["type"] == "multi-agent" for wf in specification["workflows"])

# only include the assets needed
usd_assets = {}
if has_single:
usd_assets["cartpole"] = [
("Robots/Classic/Cartpole", "cartpole.usd"),
("Robots/Classic/Cartpole/Props", "instanceable_meshes.usd"),
]
if has_multi:
usd_assets["cart_double_pendulum"] = [
("Robots/Classic/CartDoublePendulum", "cart_double_pendulum.usd"),
("Robots/Classic/CartDoublePendulum/Props", "instanceable_meshes.usd"),
]

# download just the ones in usd_assets
for robot, files in usd_assets.items():
for subfolder, filename in files:
target_dir = os.path.join(project_dir, "source", name, "data", subfolder)
os.makedirs(target_dir, exist_ok=True)
usd_url = f"{ISAACLAB_NUCLEUS_DIR}/{subfolder}/{filename}"
usd_path = os.path.join(target_dir, filename)

try:
urllib.request.urlretrieve(usd_url, usd_path)
print(f" | |-- Downloaded USD to {usd_path}")
except Exception as e:
print(f" | |-- Failed to download {filename}: {e}")
with open(usd_path, "w") as f:
f.write("# Placeholder for your custom robot USD file.\n")

# now copy & patch only the robot modules needed
robot_py_dir = os.path.join(project_dir, "source", name, name, "robots")
os.makedirs(robot_py_dir, exist_ok=True)

def copy_and_patch_robot(robot_file, path):
src = os.path.join(ROOT_DIR, "source", "isaaclab_assets", "isaaclab_assets", "robots", robot_file)
dst = os.path.join(robot_py_dir, robot_file)
shutil.copyfile(src, dst)
_replace_in_file(
[
(
"from isaaclab.utils.assets import ISAACLAB_NUCLEUS_DIR",
(
"from pathlib import Path\n\nTEMPLATE_ASSETS_DATA_DIR ="
' Path(__file__).resolve().parent.parent.parent / "data"'
),
),
(
'usd_path=f"{ISAACLAB_NUCLEUS_DIR}/' + path + '"',
'usd_path=f"{TEMPLATE_ASSETS_DATA_DIR}/' + path + '"',
),
],
src=dst,
)

if has_single:
copy_and_patch_robot("cartpole.py", "Robots/Classic/Cartpole/cartpole.usd")
if has_multi:
copy_and_patch_robot(
"cart_double_pendulum.py", "Robots/Classic/CartDoublePendulum/cart_double_pendulum.usd"
)

# write __init__.py
with open(os.path.join(robot_py_dir, "__init__.py"), "w") as f:
f.write(
"""# Copyright (c) 2022-2025, The Isaac Lab Project Developers.\n# All rights reserved.\n#\n# SPDX-License-Identifier: BSD-3-Clause\n\nfrom .cartpole import *\nfrom .cart_double_pendulum import *\n"""
)

simulation_app.close()

# replace import in task config
task_dir = os.path.join(project_dir, "source", name, name, "tasks")
for subpath in [
os.path.join(task_dir, "direct", name, f"{name}_env_cfg.py"),
os.path.join(task_dir, "manager_based", name, f"{name}_env_cfg.py"),
os.path.join(task_dir, "direct", f"{name}_marl", f"{name}_marl_env_cfg.py"),
]:
if os.path.exists(subpath):
_replace_in_file(
[
(
"from isaaclab_assets.robots.cartpole import CARTPOLE_CFG",
f"from {name}.robots.cartpole import CARTPOLE_CFG",
),
(
"from isaaclab_assets.robots.cart_double_pendulum import CART_DOUBLE_PENDULUM_CFG",
f"from {name}.robots.cart_double_pendulum import CART_DOUBLE_PENDULUM_CFG",
),
],
src=subpath,
)
else:
print(subpath)

# remove USD files interdiction from .gitignore
gitignore_path = os.path.join(project_dir, ".gitignore")
with open(gitignore_path) as f:
lines = f.readlines()
cleaned = []
skip = False
for line in lines:
if "# No USD files allowed in the repo" in line:
skip = True
continue
if skip and (line.startswith("**/*.usd") or line.startswith("**/*.usd")):
continue
if skip and line.strip() == "":
skip = False
continue
if not skip:
cleaned.append(line)
with open(gitignore_path, "w") as f:
f.writelines(cleaned)
print(" |-- Updated .gitignore to allow USD files (custom_usd=True)")

# - other files
dir = os.path.join(project_dir, "source", name, name)
template = jinja_env.get_template("extension/ui_extension_example.py")
Expand Down
30 changes: 30 additions & 0 deletions tools/template/templates/external/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,36 @@ It allows you to develop in an isolated environment, outside of the core Isaac L
# 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>
```
### Include Custom USD Assets (Optional)


If your project uses custom robots or environments with their own USD files, the template generator supports setting this up directly.

This setup ensures everything — tasks, code, and assets — lives in a single, isolated directory instead of splitting files between your external project and the core Isaac Lab repository.

To add your own robot:

1. Add your USD asset as:

```bash
FULL_PATH_TO_PROJECT/<given-project-name>/source/<given-project-name>/data/Robots/<your_robot_name>/<your_robot_name>.usd
```

2. Create a config file describing your robot as:

```bash
FULL_PATH_TO_PROJECT/<given-project-name>/source/<given-project-name>/<given-project-name>/robots/<your_robot_name>.py
```

Follow the format described in the [How-To Guide](https://isaac-sim.github.io/IsaacLab/main/source/how-to/write_articulation_cfg.html).

3. Import your asset configuration in the following file:

```bash
FULL_PATH_TO_PROJECT/<given-project-name>/source/<given-project-name>/<given-project-name>/robots/__init__.py
```

4. Import your config in the task’s environment configuration file (e.g., `*_env_cfg.py`), just like in the Cartpole examples included in this template.

### Set up IDE (Optional)

Expand Down