diff --git a/examples/vehicle_teleop/.gitignore b/examples/vehicle_teleop/.gitignore index ba0bc20228..886f8da89a 100644 --- a/examples/vehicle_teleop/.gitignore +++ b/examples/vehicle_teleop/.gitignore @@ -1,3 +1,3 @@ logs/ thirdparty/ -python/.venv/ +.venv/ diff --git a/examples/vehicle_teleop/CMakeLists.txt b/examples/vehicle_teleop/CMakeLists.txt index 398f0ef253..988a183710 100644 --- a/examples/vehicle_teleop/CMakeLists.txt +++ b/examples/vehicle_teleop/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.20) include(${CMAKE_SOURCE_DIR}/cmake/InstallPythonExample.cmake) -install_python_example(DESTINATION examples/vehicle_teleop/python) +install_python_example(DESTINATION examples/vehicle_teleop) if(BUILD_TESTING AND BUILD_PYTHON_BINDINGS AND CMAKE_SYSTEM_NAME STREQUAL "Linux") add_subdirectory(tests) diff --git a/examples/vehicle_teleop/README.md b/examples/vehicle_teleop/README.md index a836efdcf6..ca440fdc9e 100644 --- a/examples/vehicle_teleop/README.md +++ b/examples/vehicle_teleop/README.md @@ -26,20 +26,24 @@ cmake --build build --parallel 4 cmake --install build ``` -Create the example virtual environment from this directory. The scripts use -`.venv/bin/python` directly, so keep the virtual environment at -`examples/vehicle_teleop/.venv`. +Create the example virtual environment in this directory, install the +`isaaccapture` build into it, then install the example: ```bash cd examples/vehicle_teleop -uv venv --python 3.11 .venv -uv pip install --python .venv/bin/python --find-links ../../build/wheels "isaaccapture[cloudxr]" +# Replace 3.11 with your ISAAC_TELEOP_PYTHON_VERSION and X.Y with the first two numbers of VERSION. +uv venv --python 3.11 --seed .venv source .venv/bin/activate -cd python -uv sync --active --inexact --no-install-project --no-install-package isaaccapture -cd .. +uv pip install "isaaccapture[cloudxr]==X.Y+local" --find-links=../../install/wheels/ --reinstall-package isaaccapture +uv pip install -e . ``` +This is the install from [step 4 of Build from source](https://nvidia.github.io/IsaacCapture/main/development/build_from_source/index.html#install-the-isaaccapture-pip-package), +which [Running an example](../README.md#running-an-example) also uses, except +that the venv lives in this directory because the scripts call +`.venv/bin/python`. Rerun the `isaaccapture` line after every rebuild to replace +the installed wheel. + Clone the vehicle-side dependencies into this example's `thirdparty` directory. These repositories are intentionally not added as git submodules. ```bash @@ -119,10 +123,29 @@ If the remote worker was started with `--log-mcap`, replay the recorded commands The replay command prints the retargeted vehicle command values. It does not write to PandaRunner. +## Running the Modules Directly + +The example has no single entry point: each script in `scripts/` runs one module +with `.venv/bin/python -m`. To run a module yourself, from this directory: + +```bash +.venv/bin/python -m isaaccapture_examples.vehicle_teleop.isaac_remote_steering_worker +.venv/bin/python -m isaaccapture_examples.vehicle_teleop.isaac_keyboard_control_worker +PYTHONPATH=thirdparty/opendbc:thirdparty/panda .venv/bin/python -m isaaccapture_examples.vehicle_teleop.panda_worker --connect "tcp://:5555" +.venv/bin/python -m isaaccapture_examples.vehicle_teleop.replay_command_mcap logs/vehicle_control.mcap +``` + +The Panda worker needs `thirdparty/opendbc` and `thirdparty/panda` on +`PYTHONPATH`; `run_panda_worker.sh` sets that for you. + ## Troubleshooting If the steering worker cannot find the native plugin, pass `--plugin-binary` explicitly. In a source build, the default location is usually `build/src/plugins/steering_wheel/steering_wheel_plugin`. In an install tree, it is usually `plugins/steering_wheel/steering_wheel_plugin`. +The default `--config` and `--plugin-binary` are found relative to this example directory, so they resolve in a source checkout set up as in [Setup](#setup) and in an install tree. If you installed the example as a regular, non-editable package, pass both explicitly, with `--config` pointing at this directory's `config/steering_wheel_config.yaml`. + If the worker fails while creating the OpenXR session, confirm that the CloudXR runtime is running, the CloudXR environment has been sourced in the worker terminal, and the OpenXR client is connected. If the Panda worker cannot import `opendbc` or `panda`, confirm the two dependency repositories were cloned under `examples/vehicle_teleop/thirdparty/`. + +If `python -m` reports `No module named 'isaaccapture_examples'`, the example is not installed in that virtual environment. Rerun `uv pip install -e .` from [Setup](#setup). diff --git a/examples/vehicle_teleop/python/pyproject.toml b/examples/vehicle_teleop/pyproject.toml similarity index 53% rename from examples/vehicle_teleop/python/pyproject.toml rename to examples/vehicle_teleop/pyproject.toml index 578d942313..f00cb0236f 100644 --- a/examples/vehicle_teleop/python/pyproject.toml +++ b/examples/vehicle_teleop/pyproject.toml @@ -1,8 +1,14 @@ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + [project] -name = "vehicle-teleop-example" +# The dist name mirrors the import path, so an installed example claims no bare +# top-level name in site-packages. +name = "isaaccapture-examples-vehicle-teleop" version = "0.0.0" description = "Isaac Teleop vehicle steering and Panda control example" requires-python = ">=3.11,<3.14" @@ -21,5 +27,11 @@ dependencies = [ "websockets>=17.3", ] +# `only-include` + `sources`, not `packages`, and no __init__.py above +# vehicle_teleop -- see the packaging rules in examples/README.md. +[tool.hatch.build.targets.wheel] +only-include = ["python/isaaccapture_examples/vehicle_teleop"] +sources = ["python"] + [[tool.uv.index]] url = "https://pypi.nvidia.com" diff --git a/examples/vehicle_teleop/python/vehicle_teleop/__init__.py b/examples/vehicle_teleop/python/isaaccapture_examples/vehicle_teleop/__init__.py similarity index 100% rename from examples/vehicle_teleop/python/vehicle_teleop/__init__.py rename to examples/vehicle_teleop/python/isaaccapture_examples/vehicle_teleop/__init__.py diff --git a/examples/vehicle_teleop/python/vehicle_teleop/command_log.py b/examples/vehicle_teleop/python/isaaccapture_examples/vehicle_teleop/command_log.py similarity index 98% rename from examples/vehicle_teleop/python/vehicle_teleop/command_log.py rename to examples/vehicle_teleop/python/isaaccapture_examples/vehicle_teleop/command_log.py index 1ae15747de..24e568285c 100644 --- a/examples/vehicle_teleop/python/vehicle_teleop/command_log.py +++ b/examples/vehicle_teleop/python/isaaccapture_examples/vehicle_teleop/command_log.py @@ -11,7 +11,7 @@ from mcap.reader import make_reader from mcap.writer import CompressionType, Writer -from vehicle_teleop.vehicle_command import VehicleControlCommand +from .vehicle_command import VehicleControlCommand @dataclass(frozen=True) diff --git a/examples/vehicle_teleop/python/vehicle_teleop/isaac_keyboard_control_worker.py b/examples/vehicle_teleop/python/isaaccapture_examples/vehicle_teleop/isaac_keyboard_control_worker.py similarity index 99% rename from examples/vehicle_teleop/python/vehicle_teleop/isaac_keyboard_control_worker.py rename to examples/vehicle_teleop/python/isaaccapture_examples/vehicle_teleop/isaac_keyboard_control_worker.py index 1d32091a5c..5269dbc850 100644 --- a/examples/vehicle_teleop/python/vehicle_teleop/isaac_keyboard_control_worker.py +++ b/examples/vehicle_teleop/python/isaaccapture_examples/vehicle_teleop/isaac_keyboard_control_worker.py @@ -19,7 +19,7 @@ ) from isaaccapture.schema import SteeringWheelOutput -from vehicle_teleop.vehicle_command import ( +from .vehicle_command import ( VehicleControlCommand, clamp, isaac_command_to_wire_command, diff --git a/examples/vehicle_teleop/python/vehicle_teleop/isaac_remote_steering_worker.py b/examples/vehicle_teleop/python/isaaccapture_examples/vehicle_teleop/isaac_remote_steering_worker.py similarity index 98% rename from examples/vehicle_teleop/python/vehicle_teleop/isaac_remote_steering_worker.py rename to examples/vehicle_teleop/python/isaaccapture_examples/vehicle_teleop/isaac_remote_steering_worker.py index daa24b48f4..98f56c5017 100644 --- a/examples/vehicle_teleop/python/vehicle_teleop/isaac_remote_steering_worker.py +++ b/examples/vehicle_teleop/python/isaaccapture_examples/vehicle_teleop/isaac_remote_steering_worker.py @@ -25,8 +25,8 @@ ) from isaaccapture.schema import SteeringWheelOutput -from vehicle_teleop.command_log import McapCommandLogger, SteeringWheelSample -from vehicle_teleop.vehicle_command import ( +from .command_log import McapCommandLogger, SteeringWheelSample +from .vehicle_command import ( VehicleControlCommand, isaac_command_to_wire_command, ) @@ -46,7 +46,9 @@ def example_root() -> Path: - return Path(__file__).resolve().parents[2] + # Four levels up is the example root, where config/ sits -- in the source + # tree and in the install tree alike. + return Path(__file__).resolve().parents[3] def default_config_path() -> Path: diff --git a/examples/vehicle_teleop/python/vehicle_teleop/panda_worker.py b/examples/vehicle_teleop/python/isaaccapture_examples/vehicle_teleop/panda_worker.py similarity index 98% rename from examples/vehicle_teleop/python/vehicle_teleop/panda_worker.py rename to examples/vehicle_teleop/python/isaaccapture_examples/vehicle_teleop/panda_worker.py index cbba828e0e..962338856d 100644 --- a/examples/vehicle_teleop/python/vehicle_teleop/panda_worker.py +++ b/examples/vehicle_teleop/python/isaaccapture_examples/vehicle_teleop/panda_worker.py @@ -11,7 +11,7 @@ import zmq -from vehicle_teleop.vehicle_command import VehicleControlCommand, clamp +from .vehicle_command import VehicleControlCommand, clamp DEFAULT_CONNECT = "tcp://127.0.0.1:5555" diff --git a/examples/vehicle_teleop/python/vehicle_teleop/replay_command_mcap.py b/examples/vehicle_teleop/python/isaaccapture_examples/vehicle_teleop/replay_command_mcap.py similarity index 96% rename from examples/vehicle_teleop/python/vehicle_teleop/replay_command_mcap.py rename to examples/vehicle_teleop/python/isaaccapture_examples/vehicle_teleop/replay_command_mcap.py index 5d401e63d3..cba1cd9a26 100644 --- a/examples/vehicle_teleop/python/vehicle_teleop/replay_command_mcap.py +++ b/examples/vehicle_teleop/python/isaaccapture_examples/vehicle_teleop/replay_command_mcap.py @@ -5,7 +5,7 @@ import argparse import time -from vehicle_teleop.command_log import McapCommandLogReader +from .command_log import McapCommandLogReader class CommandMcapReplay: diff --git a/examples/vehicle_teleop/python/vehicle_teleop/vehicle_command.py b/examples/vehicle_teleop/python/isaaccapture_examples/vehicle_teleop/vehicle_command.py similarity index 100% rename from examples/vehicle_teleop/python/vehicle_teleop/vehicle_command.py rename to examples/vehicle_teleop/python/isaaccapture_examples/vehicle_teleop/vehicle_command.py diff --git a/examples/vehicle_teleop/scripts/replay_command_mcap.sh b/examples/vehicle_teleop/scripts/replay_command_mcap.sh index 8706a85de4..84bb0c5437 100755 --- a/examples/vehicle_teleop/scripts/replay_command_mcap.sh +++ b/examples/vehicle_teleop/scripts/replay_command_mcap.sh @@ -8,11 +8,11 @@ PYTHON="${EXAMPLE_ROOT}/.venv/bin/python" if [[ ! -x "${PYTHON}" ]]; then echo "Missing example virtual environment: ${EXAMPLE_ROOT}/.venv" >&2 - echo "Create it with: uv venv --python 3.11 .venv" >&2 + echo "Set it up as in Setup in ${EXAMPLE_ROOT}/README.md" >&2 exit 1 fi export PYTHONPATH="${EXAMPLE_ROOT}/python${PYTHONPATH:+:${PYTHONPATH}}" -exec "${PYTHON}" -m vehicle_teleop.replay_command_mcap \ +exec "${PYTHON}" -m isaaccapture_examples.vehicle_teleop.replay_command_mcap \ "$@" diff --git a/examples/vehicle_teleop/scripts/run_isaac_keyboard_control_worker.sh b/examples/vehicle_teleop/scripts/run_isaac_keyboard_control_worker.sh index c7751d2d73..973d56e4d6 100755 --- a/examples/vehicle_teleop/scripts/run_isaac_keyboard_control_worker.sh +++ b/examples/vehicle_teleop/scripts/run_isaac_keyboard_control_worker.sh @@ -8,12 +8,12 @@ PYTHON="${EXAMPLE_ROOT}/.venv/bin/python" if [[ ! -x "${PYTHON}" ]]; then echo "Missing example virtual environment: ${EXAMPLE_ROOT}/.venv" >&2 - echo "Create it with: uv venv --python 3.11 .venv" >&2 + echo "Set it up as in Setup in ${EXAMPLE_ROOT}/README.md" >&2 exit 1 fi export PYTHONPATH="${EXAMPLE_ROOT}/python${PYTHONPATH:+:${PYTHONPATH}}" -exec "${PYTHON}" -m vehicle_teleop.isaac_keyboard_control_worker \ +exec "${PYTHON}" -m isaaccapture_examples.vehicle_teleop.isaac_keyboard_control_worker \ --rate-hz 50 \ "$@" diff --git a/examples/vehicle_teleop/scripts/run_isaac_remote_steering_worker.sh b/examples/vehicle_teleop/scripts/run_isaac_remote_steering_worker.sh index d4f2ea2742..b118096c16 100755 --- a/examples/vehicle_teleop/scripts/run_isaac_remote_steering_worker.sh +++ b/examples/vehicle_teleop/scripts/run_isaac_remote_steering_worker.sh @@ -8,12 +8,12 @@ PYTHON="${EXAMPLE_ROOT}/.venv/bin/python" if [[ ! -x "${PYTHON}" ]]; then echo "Missing example virtual environment: ${EXAMPLE_ROOT}/.venv" >&2 - echo "Create it with: uv venv --python 3.11 .venv" >&2 + echo "Set it up as in Setup in ${EXAMPLE_ROOT}/README.md" >&2 exit 1 fi export PYTHONPATH="${EXAMPLE_ROOT}/python${PYTHONPATH:+:${PYTHONPATH}}" -exec "${PYTHON}" -m vehicle_teleop.isaac_remote_steering_worker \ +exec "${PYTHON}" -m isaaccapture_examples.vehicle_teleop.isaac_remote_steering_worker \ --rate-hz 50 \ "$@" diff --git a/examples/vehicle_teleop/scripts/run_panda_worker.sh b/examples/vehicle_teleop/scripts/run_panda_worker.sh index d6792c4c99..02f42c7c44 100755 --- a/examples/vehicle_teleop/scripts/run_panda_worker.sh +++ b/examples/vehicle_teleop/scripts/run_panda_worker.sh @@ -8,11 +8,11 @@ PYTHON="${EXAMPLE_ROOT}/.venv/bin/python" if [[ ! -x "${PYTHON}" ]]; then echo "Missing example virtual environment: ${EXAMPLE_ROOT}/.venv" >&2 - echo "Create it with: uv venv --python 3.11 .venv" >&2 + echo "Set it up as in Setup in ${EXAMPLE_ROOT}/README.md" >&2 exit 1 fi export PYTHONPATH="${EXAMPLE_ROOT}/python:${EXAMPLE_ROOT}/thirdparty/opendbc:${EXAMPLE_ROOT}/thirdparty/panda${PYTHONPATH:+:${PYTHONPATH}}" -exec "${PYTHON}" -m vehicle_teleop.panda_worker \ +exec "${PYTHON}" -m isaaccapture_examples.vehicle_teleop.panda_worker \ "$@" diff --git a/examples/vehicle_teleop/tests/pyproject.toml b/examples/vehicle_teleop/tests/pyproject.toml index 9c8ee9000f..b87fd3f194 100644 --- a/examples/vehicle_teleop/tests/pyproject.toml +++ b/examples/vehicle_teleop/tests/pyproject.toml @@ -11,3 +11,7 @@ dev = ["pytest", "numpy", "mcap>=1.2.0", "pyyaml", "pyzmq"] [tool.pytest.ini_options] pythonpath = ["../python"] + +# uv reads settings from the nearest [tool.uv], so this empty table keeps the +# example's pypi.nvidia.com index one directory up out of the test run. +[tool.uv] diff --git a/examples/vehicle_teleop/tests/test_workers.py b/examples/vehicle_teleop/tests/test_workers.py index 0442474d12..7802cd0ee1 100644 --- a/examples/vehicle_teleop/tests/test_workers.py +++ b/examples/vehicle_teleop/tests/test_workers.py @@ -13,16 +13,16 @@ VehicleControlRetargeterConfig, ) -from vehicle_teleop.isaac_keyboard_control_worker import ( +from isaaccapture_examples.vehicle_teleop.isaac_keyboard_control_worker import ( IsaacKeyboardControlState, IsaacKeyboardControlWorker, TerminalKeyReader, ) -from vehicle_teleop.isaac_remote_steering_worker import ( +from isaaccapture_examples.vehicle_teleop.isaac_remote_steering_worker import ( IsaacRemoteSteeringWorker, build_parser, ) -from vehicle_teleop.vehicle_command import VehicleControlCommand +from isaaccapture_examples.vehicle_teleop.vehicle_command import VehicleControlCommand @pytest.fixture