Thank you for your interest in contributing to parapilot! This guide will help you get started.
- Python 3.10+
- VTK 9.4+ (installed automatically via pip)
- GPU with EGL support (optional, for headless rendering)
git clone https://github.com/kimimgo/parapilot.git
cd parapilot
pip install -e ".[dev]"Optional dependencies:
pip install -e ".[mesh]" # meshio + trimesh for format conversion
pip install -e ".[composite]" # Pillow + matplotlib for split_animate
pip install -e ".[all]" # everything# Full test suite (748 tests)
pytest --cov=parapilot --cov-report=term-missing -q
# Single test file
pytest tests/test_engine/test_filters.py -q
# Single test function
pytest tests/test_engine/test_filters.py::test_slice_plane -qTests use asyncio_mode = "auto" — async tests are detected automatically.
# Lint
ruff check src/ tests/
# Lint with auto-fix
ruff check src/ tests/ --fix
# Type check
mypy src/parapilot/ --ignore-missing-importsRuff config: target-version = "py310", line-length = 120.
src/parapilot/
├── server.py # FastMCP instance + tool registration
├── config.py # Environment-based configuration
├── tools/ # Tool implementations (render_impl, slice_impl, etc.)
├── pipeline/
│ └── models.py # Pydantic models (SourceDef, FilterStep, RenderDef, OutputDef)
├── core/
│ ├── compiler.py # PipelineDefinition → Python script string
│ ├── runner.py # VTKRunner: execute scripts via subprocess/Docker
│ ├── registry.py # PascalCase filter/format registries
│ └── output.py # RunResult → PipelineResult
└── engine/
├── filters.py # VTK filter functions (snake_case keys)
├── readers.py # File format reader factory
├── renderer.py # Off-screen rendering (EGL/OSMesa)
├── camera.py # Camera presets and positioning
└── colormaps.py # Built-in colormap definitions
Filters live in two places (dual registry pattern):
Add a function to src/parapilot/engine/filters.py:
def my_filter(dataset, param1: float = 1.0, param2: str = "default"):
"""Apply my custom filter to the dataset."""
import vtkmodules.vtkFiltersCore as vtk_filters
filt = vtk_filters.vtkMyFilter()
filt.SetInputData(dataset)
filt.SetParam1(param1)
filt.Update()
return filt.GetOutput()Add entry to _FILTER_REGISTRY dict in src/parapilot/engine/filters.py:
_FILTER_REGISTRY = {
# ... existing entries ...
"my_filter": my_filter,
}Add the filter schema in src/parapilot/core/registry.py:
"MyFilter": {
"params": {
"param1": {"type": "float", "default": 1.0},
"param2": {"type": "str", "default": "default"},
}
}The get_filter() function performs case-insensitive lookup to bridge PascalCase (pipeline DSL) with snake_case (engine implementation).
Add tests in tests/test_engine/test_filters.py:
def test_my_filter():
from tests.fixtures.create_data import create_wavelet
from parapilot.engine.filters import my_filter
dataset = create_wavelet()
result = my_filter(dataset, param1=2.0)
assert result is not None
assert result.GetNumberOfPoints() > 0Add to src/parapilot/engine/readers.py:
def _read_my_format(path: str):
"""Read .myext files."""
import vtkmodules.vtkIOMyModule as vtk_io
reader = vtk_io.vtkMyFormatReader()
reader.SetFileName(path)
reader.Update()
return reader.GetOutput()Add the file extension mapping in the reader factory within the same file.
Create a minimal test fixture if needed and add tests in tests/test_engine/test_readers.py.
- Fork the repository and create a feature branch from
main - Write tests for any new functionality
- Run the full check suite before submitting:
ruff check src/ tests/ mypy src/parapilot/ --ignore-missing-imports pytest --cov=parapilot --cov-report=term-missing -q
- Keep commits focused — one logical change per commit
- Open a PR against
mainwith a clear description of what and why
- Formatter/linter: ruff (line length 120, target Python 3.10)
- Type checker: mypy (strict mode)
- Test framework: pytest with pytest-asyncio
- Naming: snake_case for engine functions, PascalCase for registry/DSL keys
- Commits: Conventional Commits
Use GitHub Issues to report bugs or request features. Please include:
- Steps to reproduce
- Expected vs actual behavior
- VTK version and Python version
- File format being processed (if applicable)