Skip to content
Merged
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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,18 @@ To compile and install the GTDynamics python library:
make && make python-install
```

To generate stubs explicitly (useful for IDEs/type checkers), run:

```sh
make python-stubs
```

On non-Windows platforms, `python-install` depends on `python-stubs`.

For VS Code / Pylance setup (including `python.analysis.extraPaths`), see `python/README.md`.

Important: use a `gtsam` Python package built from the same install/prefix as the GTSAM library linked into GTDynamics. Mixing a local GTDynamics build with an unrelated pip/conda `gtsam` wheel can cause runtime aborts.

4. To run the Python tests, you can simply run:

```sh
Expand Down
41 changes: 35 additions & 6 deletions python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,47 @@ set_target_properties(
RELWITHDEBINFO_POSTFIX "" # Otherwise you will have a wrong name
)

add_custom_target(
python-install
COMMAND ${PYTHON_EXECUTABLE} -m pip install .
DEPENDS ${PROJECT_NAME}_py
WORKING_DIRECTORY ${GTD_PYTHON_BINARY_DIR})

if(UNIX)
set(GTD_PATH_SEP ":")
else()
set(GTD_PATH_SEP ";")
endif()

set(GTD_PYTHON_INSTALL_EXTRA "")
set(GTD_PYTHON_STUB_MODULE "${PROJECT_NAME}.${PROJECT_NAME}")

# Determine library path for stub generation
# On Linux/Unix, we need LD_LIBRARY_PATH; on macOS, DYLD_LIBRARY_PATH
get_target_property(GTSAM_LIBRARY_LOCATION gtsam LOCATION)
get_filename_component(GTSAM_LIBRARY_DIR "${GTSAM_LIBRARY_LOCATION}" DIRECTORY)

if(APPLE)
set(GTD_STUB_LIB_PATH_VAR "DYLD_LIBRARY_PATH")
else()
set(GTD_STUB_LIB_PATH_VAR "LD_LIBRARY_PATH")
endif()

add_custom_target(
python-stubs
COMMAND
${CMAKE_COMMAND} -E env
"PYTHONPATH=${GTD_PYTHON_BINARY_DIR}${GTD_PATH_SEP}$ENV{PYTHONPATH}"
"${GTD_STUB_LIB_PATH_VAR}=${GTSAM_LIBRARY_DIR}${GTD_PATH_SEP}${CMAKE_LIBRARY_OUTPUT_DIRECTORY}${GTD_PATH_SEP}$ENV{${GTD_STUB_LIB_PATH_VAR}}"
${PYTHON_EXECUTABLE} -m pybind11_stubgen -o . --ignore-all-errors
${GTD_PYTHON_STUB_MODULE}
DEPENDS ${PROJECT_NAME}_py
WORKING_DIRECTORY ${GTD_PYTHON_BINARY_DIR})

if(NOT WIN32)
list(APPEND GTD_PYTHON_INSTALL_EXTRA python-stubs)
endif()

add_custom_target(
python-install
COMMAND ${PYTHON_EXECUTABLE} -m pip install .
DEPENDS ${PROJECT_NAME}_py ${GTD_PYTHON_INSTALL_EXTRA}
WORKING_DIRECTORY ${GTD_PYTHON_BINARY_DIR})

# Unit tests
set(python_unit_test_suites)
macro(PYTHON_UNIT_TEST_SUITE suiteName directory)
Expand Down
50 changes: 45 additions & 5 deletions python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This directory is where the Pybind11-generated GTDynamics package lives and wher
## Build prerequisites

1. **GTSAM** must be built with Python support (i.e., `-DGTSAM_BUILD_PYTHON=ON`) and installed to a prefix that GTDynamics can see via `GTSAM_DIR` or `CMAKE_PREFIX_PATH`.
2. **Python tooling**: the CI job installs `setuptools<70`, `wheel`, `numpy`, `pyparsing`, `pyyaml`, and `pybind11-stubgen` before configuring the project; matching this list locally avoids the same runtime issues.
2. **Python tooling**: the CI job installs `setuptools<70`, `wheel`, `numpy`, `pyparsing`, `pyyaml`, and `pybind11-stubgen` before configuring the project; matching this list locally avoids the same runtime issues. `pybind11-stubgen` is required for the `python-stubs` CMake target.
3. On macOS, the workflow creates and activates a virtual environment (`pythonX -m venv venv`) so that `pip install` and the tests run in the same interpreter that baked the bindings.

## Building and installing locally
Expand All @@ -30,7 +30,47 @@ This directory is where the Pybind11-generated GTDynamics package lives and wher
```sh
make python-install
```
This runs `${PYTHON_EXECUTABLE} -m pip install .` in `build/python`, which produces a wheel in `pip`'s cache before installing it.
This runs `${PYTHON_EXECUTABLE} -m pip install .` in `build/python`, which produces a wheel in `pip`'s cache before installing it. On non-Windows platforms, `python-install` depends on `python-stubs`, so `.pyi` files are generated first.

## GTSAM Python compatibility (important)

Use a `gtsam` Python package built from the same GTSAM install/prefix that GTDynamics links against.
Mixing a local GTDynamics build with an unrelated pip/conda `gtsam` wheel can cause hard runtime failures (for example, process aborts when adding factors to a graph).

If you built GTSAM from source in a sibling repo, prepend it before importing:

```sh
export PYTHONPATH=/path/to/gtsam/build/python:/path/to/GTDynamics/build/python:$PYTHONPATH
```

## Generating type stubs

- Run `make python-stubs` to generate stubs with `pybind11-stubgen`.
- Stubs are generated in `build/python/gtdynamics/*.pyi`.
- The generated `gtdynamics.pyi` file is what tools like Pylance use for attribute completion/type checking on wrapped symbols.

### VS Code / Pylance configuration

If your runtime works but Pylance still reports unknown attributes (for example, `CreateRobotFromFile`), make sure VS Code analyzes the build package path.

In workspace `.vscode/settings.json`:

```json
{
"python.defaultInterpreterPath": "/path/to/your/python",
"python.analysis.extraPaths": [
"${workspaceFolder}/build/python"
]
}
```

Then run:

```sh
make python-stubs
```

and reload the VS Code window.

## Running Python tests

Expand All @@ -40,9 +80,9 @@ This directory is where the Pybind11-generated GTDynamics package lives and wher

## Packaging tips

- `python/templates/setup.py.in` reads the CMake-generated `requirements.txt` and packages the shared library blobs (`.so` / `.pyd`) from `python/gtdynamics` so running `pip wheel .` in `build/python` yields a complete asset.
- Keep `python/requirements.txt` in sync with the requirements file copied to `build/python/requirements.txt` so that CI and a local `pip install` use the same dependency list.
- If you need to publish a wheel manually, the packaged wheel that `pip install .` writes to `~/.cache/pip` already encodes the GTDynamics version reported by `CMakeLists.txt`.
- `python/templates/pyproject.toml.in` drives packaging in `build/python`.
- `make python-install` runs `pip install .` from `build/python`, which installs the generated extension module and package files from `build/python/gtdynamics`.
- If you need to publish a wheel manually, the wheel produced by `pip` already encodes the GTDynamics version reported by CMake.

## Wheels
### CI wheel pipeline
Expand Down
Loading