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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ _build
# No benchmarks output
/benchmarks/
benchmark_*.json
/pyrightconfig.json


# Ruff cache
Expand Down
5 changes: 1 addition & 4 deletions .vscode/tools/settings.template.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,5 @@
},
"[restructuredtext]": {
"editor.tabSize": 2
},
// Python extra paths
// Note: this is filled up when "./isaaclab.sh -i" is run
"python.analysis.extraPaths": []
}
}
218 changes: 48 additions & 170 deletions .vscode/tools/setup_vscode.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,197 +3,75 @@
#
# SPDX-License-Identifier: BSD-3-Clause

"""This script sets up the vs-code settings for the Isaac Lab project.
"""Set up VS Code and Cursor for an Isaac Lab repository or external 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.
The script writes editor settings and a machine-local ``pyrightconfig.json``. The Pyright
configuration extends the project's checked-in ``pyproject.toml`` and adds import roots
discovered from the active Python environment. This supports source, editable, wheel, and
Isaac Sim binaries installations without storing machine-specific paths in version control.
"""

import argparse
import pathlib
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.
from isaaclab.utils.editor import build_extra_paths, resolve_isaacsim_dir, write_pyright_config

If the isaac-sim settings file does not exist, the extraPaths are not overwritten.
PROJECT_DIR = pathlib.Path(__file__).parents[2]
"""Path to the repository or generated project's root directory."""

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.
def overwrite_default_python_interpreter(settings: str, isaacsim_dir: pathlib.Path | None) -> str:
"""Set the editor's default Python interpreter.

Args:
isaaclab_settings: The settings string to use as template.
settings: VS Code settings template.
isaacsim_dir: Isaac Sim installation directory, or None.

Returns:
The settings string with overwritten default python interpreter.
Settings with the interpreter path updated.
"""
# 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,
python_exe = pathlib.Path(sys.executable)
if "kit/python/bin/python3" in python_exe.as_posix() and isaacsim_dir is not None:
wrapper = isaacsim_dir / "python.sh"
if wrapper.is_file():
python_exe = wrapper
return re.sub(
r'"python\.defaultInterpreterPath": ".*?"',
f'"python.defaultInterpreterPath": "{python_exe.as_posix()}"',
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"
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--isaac_path", help="Absolute path to the Isaac Sim installation.")
args = parser.parse_args()

try:
isaacsim_dir = resolve_isaacsim_dir(PROJECT_DIR, args.isaac_path)
except ValueError as error:
parser.error(str(error))
write_pyright_config(PROJECT_DIR, build_extra_paths(PROJECT_DIR, isaacsim_dir))

settings_template = PROJECT_DIR / ".vscode" / "tools" / "settings.template.json"
if not settings_template.is_file():
raise FileNotFoundError(f"Could not find the VS Code settings template: {settings_template}")
settings = overwrite_default_python_interpreter(settings_template.read_text(encoding="utf-8"), isaacsim_dir)
header = (
"// This file is automatically generated by setup_vscode.py.\n"
"// Do not edit this file directly.\n"
"// \n"
f"// Generated from: {isaaclab_vscode_template_filename}\n"
f"// Generated from: {settings_template}\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)
(PROJECT_DIR / ".vscode" / "settings.json").write_text(header + settings, encoding="utf-8")

launch_file = PROJECT_DIR / ".vscode" / "launch.json"
launch_template = PROJECT_DIR / ".vscode" / "tools" / "launch.template.json"
if not launch_file.exists():
launch_header = header.replace(str(settings_template), str(launch_template))
launch_file.write_text(launch_header + launch_template.read_text(encoding="utf-8"), encoding="utf-8")


if __name__ == "__main__":
Expand Down
18 changes: 17 additions & 1 deletion docs/source/developer-tools/template_generator.rst
Original file line number Diff line number Diff line change
Expand Up @@ -242,12 +242,28 @@ External projects should build their environment harness from public APIs and
maintain project-local fixtures. Copying ``env_test_utils.py`` into a project is
vendoring it, so the project must track upstream changes to that copy.

To configure VS Code, run the generated setup task or invoke it directly:
To configure VS Code or Cursor, run the generated setup task or invoke it directly:

.. code-block:: bash

uv run python .vscode/tools/setup_vscode.py

The command selects the active interpreter and creates a git-ignored
``pyrightconfig.json``. This child configuration inherits the checked-in
Pyright policy from ``pyproject.toml`` and adds the generated project's
``src`` import root, installed Isaac Lab packages, and any discovered Isaac
Sim extensions. When using the ``isaacsim`` extra, include it while generating
the configuration:

.. code-block:: bash

uv run --extra isaacsim python .vscode/tools/setup_vscode.py

In VS Code, use Pylance and select the interpreter that ran the setup command.
In Cursor, install the ``detachhead.basedpyright`` extension instead of Pylance,
select the same interpreter, and reload the window. Both language servers read
the generated ``pyrightconfig.json``.

Create an internal task
-----------------------

Expand Down
Loading
Loading