|
| 1 | +# DEVELOPMENT.md — handanim |
| 2 | + |
| 3 | +Developer reference for setting up, testing, documenting, and running handanim locally. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## Prerequisites |
| 8 | + |
| 9 | +- Python 3.11+ |
| 10 | +- [Poetry](https://python-poetry.org/) for dependency management |
| 11 | +- Cairo system library (`pycairo` requires it; see platform notes below) |
| 12 | + |
| 13 | +### Cairo installation |
| 14 | + |
| 15 | +**macOS** |
| 16 | +```bash |
| 17 | +brew install cairo pkg-config |
| 18 | +``` |
| 19 | + |
| 20 | +**Ubuntu / Debian** |
| 21 | +```bash |
| 22 | +sudo apt-get install libcairo2-dev pkg-config python3-dev |
| 23 | +``` |
| 24 | + |
| 25 | +**Windows** — install via the [GTK for Windows Runtime](https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer/releases). |
| 26 | + |
| 27 | +--- |
| 28 | + |
| 29 | +## Setup |
| 30 | + |
| 31 | +```bash |
| 32 | +# Clone and enter the repo |
| 33 | +git clone <repo-url> |
| 34 | +cd handanim |
| 35 | + |
| 36 | +# Install all dependencies (runtime + dev) |
| 37 | +poetry install --with dev |
| 38 | + |
| 39 | +# Activate the virtual environment (optional, for a plain shell) |
| 40 | +poetry shell |
| 41 | +``` |
| 42 | + |
| 43 | +--- |
| 44 | + |
| 45 | +## Running the Tests |
| 46 | + |
| 47 | +### Full test suite |
| 48 | + |
| 49 | +```bash |
| 50 | +poetry run python3 -m pytest |
| 51 | +``` |
| 52 | + |
| 53 | +### Run a specific test file |
| 54 | + |
| 55 | +```bash |
| 56 | +poetry run python3 -m pytest tests/test_opsset.py |
| 57 | +poetry run python3 -m pytest tests/test_sketch.py |
| 58 | +poetry run python3 -m pytest tests/test_visuals.py |
| 59 | +``` |
| 60 | + |
| 61 | +### Run a single test by name |
| 62 | + |
| 63 | +```bash |
| 64 | +poetry run python3 -m pytest -k "test_rotate_90_degrees" |
| 65 | +``` |
| 66 | + |
| 67 | +### Verbose output |
| 68 | + |
| 69 | +```bash |
| 70 | +poetry run python3 -m pytest -v |
| 71 | +``` |
| 72 | + |
| 73 | +--- |
| 74 | + |
| 75 | +## Test Coverage |
| 76 | + |
| 77 | +Coverage is measured with `pytest-cov`. The `--cov-report=term-missing` flag shows exact line numbers not covered — open the file alongside the report to see which branches are untested. |
| 78 | + |
| 79 | +### Terminal report (line numbers) |
| 80 | + |
| 81 | +```bash |
| 82 | +poetry run python3 -m pytest --cov=src/handanim --cov-report=term-missing |
| 83 | +``` |
| 84 | + |
| 85 | +### HTML report (browsable, colour-coded per file) |
| 86 | + |
| 87 | +```bash |
| 88 | +poetry run python3 -m pytest --cov=src/handanim --cov-report=html |
| 89 | +open htmlcov/index.html # macOS |
| 90 | +xdg-open htmlcov/index.html # Linux |
| 91 | +``` |
| 92 | + |
| 93 | +### Single module only |
| 94 | + |
| 95 | +```bash |
| 96 | +poetry run python3 -m pytest --cov=src/handanim/primitives/text --cov-report=term-missing |
| 97 | +``` |
| 98 | + |
| 99 | +### Enforce a minimum threshold (useful in CI) |
| 100 | + |
| 101 | +```bash |
| 102 | +poetry run python3 -m pytest --cov=src/handanim --cov-fail-under=50 |
| 103 | +``` |
| 104 | + |
| 105 | +--- |
| 106 | + |
| 107 | +## Visual Regression Tests |
| 108 | + |
| 109 | +Visual regression tests live in `tests/test_visuals.py`. They render small deterministic scenes to PNG and compare them against reference files stored in `tests/snapshots/` using [SSIM](https://scikit-image.org/docs/stable/api/skimage.metrics.html#skimage.metrics.structural_similarity). |
| 110 | + |
| 111 | +**How numpy seeding makes renders deterministic:** every test runs with `numpy.random.seed(42)` (set via the `seed_numpy` autouse fixture in `conftest.py`). This makes the random jitter in rough primitives (Line, Rectangle, etc.) identical across runs. |
| 112 | + |
| 113 | +### Regenerate reference snapshots |
| 114 | + |
| 115 | +Run this after intentionally changing rendering output — for example, after modifying a primitive's draw method or a style default: |
| 116 | + |
| 117 | +```bash |
| 118 | +poetry run python3 -m pytest tests/test_visuals.py --snapshot-update |
| 119 | +``` |
| 120 | + |
| 121 | +Review the diff in `tests/snapshots/` before committing to make sure the visual change is intentional. |
| 122 | + |
| 123 | +### Failure threshold |
| 124 | + |
| 125 | +A visual test fails when SSIM drops below **0.98**. This catches structural rendering changes while tolerating sub-pixel float differences across platforms. |
| 126 | + |
| 127 | +--- |
| 128 | + |
| 129 | +## Building the Documentation |
| 130 | + |
| 131 | +Docs are built with [Sphinx](https://www.sphinx-doc.org/) using the [Furo](https://pradyunsg.me/furo/) theme. Docstrings are pulled in automatically via `sphinx.ext.autodoc`. |
| 132 | + |
| 133 | +```bash |
| 134 | +# Build HTML docs |
| 135 | +cd docs |
| 136 | +poetry run make html |
| 137 | + |
| 138 | +# Output is written to docs/build/html/ |
| 139 | +# Open in browser |
| 140 | +open build/html/index.html # macOS |
| 141 | +xdg-open build/html/index.html # Linux |
| 142 | +``` |
| 143 | + |
| 144 | +### Regenerating the API stubs |
| 145 | + |
| 146 | +If you add a new module and want it to appear in the docs, regenerate the `.rst` stubs from the project root: |
| 147 | + |
| 148 | +```bash |
| 149 | +poetry run sphinx-apidoc -o docs/source src/handanim --force |
| 150 | +``` |
| 151 | + |
| 152 | +Then rebuild HTML as above. |
| 153 | + |
| 154 | +### Live reload during doc writing |
| 155 | + |
| 156 | +```bash |
| 157 | +poetry run sphinx-autobuild docs/source docs/build/html |
| 158 | +# Serves at http://127.0.0.1:8000 and reloads on file change |
| 159 | +``` |
| 160 | + |
| 161 | +> `sphinx-autobuild` is not in the dev dependencies by default. Install it once with `poetry add --group dev sphinx-autobuild`. |
| 162 | +
|
| 163 | +--- |
| 164 | + |
| 165 | +## Running Examples |
| 166 | + |
| 167 | +Each script in `examples/` is a self-contained scene that renders to an MP4 (written to `examples/output/`). |
| 168 | + |
| 169 | +```bash |
| 170 | +# Pythagoras theorem — text, polygons, eraser |
| 171 | +poetry run python3 examples/pythagoras.py |
| 172 | + |
| 173 | +# (a+b)² visual proof — algebra with hand-drawn shapes |
| 174 | +poetry run python3 examples/a_plus_b_square.py |
| 175 | + |
| 176 | +# Distributive property with an SVG character |
| 177 | +poetry run python3 examples/distributive_property.py |
| 178 | + |
| 179 | +# Solar system orbit animation |
| 180 | +poetry run python3 examples/solar_system.py |
| 181 | + |
| 182 | +# Custom font rendering |
| 183 | +poetry run python3 examples/custom_font.py |
| 184 | +``` |
| 185 | + |
| 186 | +Output files land in `examples/output/`. The examples are the canonical "does this actually work end-to-end" check — run one before and after touching rendering code. |
| 187 | + |
| 188 | +--- |
| 189 | + |
| 190 | +## Quick Smoke Test (no video output) |
| 191 | + |
| 192 | +To verify a primitive renders without errors, use `OpsSet.quick_view()`. It renders to a temporary SVG and opens it in your browser: |
| 193 | + |
| 194 | +```python |
| 195 | +from handanim.primitives import Rectangle |
| 196 | +from handanim.core.styles import StrokeStyle, SketchStyle |
| 197 | + |
| 198 | +rect = Rectangle( |
| 199 | + top_left=(100, 100), |
| 200 | + width=400, |
| 201 | + height=300, |
| 202 | + stroke_style=StrokeStyle(color=(0.1, 0.1, 0.8), width=2), |
| 203 | + sketch_style=SketchStyle(roughness=1), |
| 204 | +) |
| 205 | +rect.draw().quick_view() |
| 206 | +``` |
| 207 | + |
| 208 | +Run it with: |
| 209 | + |
| 210 | +```bash |
| 211 | +poetry run python3 -c " |
| 212 | +from handanim.primitives import Rectangle |
| 213 | +from handanim.core.styles import StrokeStyle |
| 214 | +rect = Rectangle((100,100), 400, 300, stroke_style=StrokeStyle(color=(0,0,0.8), width=2)) |
| 215 | +rect.draw().quick_view(block=False) |
| 216 | +" |
| 217 | +``` |
| 218 | + |
| 219 | +--- |
| 220 | + |
| 221 | +## Project Layout Cheat Sheet |
| 222 | + |
| 223 | +``` |
| 224 | +src/handanim/ |
| 225 | +├── core/ # OpsSet, Drawable, AnimationEvent, Scene, Viewport, styles |
| 226 | +├── primitives/ # Line, Rectangle, Ellipse, Arrow, Text, Math, VectorSVG, … |
| 227 | +├── animations/ # SketchAnimation, FadeIn/Out, Zoom, Translate |
| 228 | +└── stylings/ # color constants, fill patterns, stroke utilities |
| 229 | +
|
| 230 | +tests/ |
| 231 | +├── conftest.py # shared fixtures (seed_numpy, render_to_png_bytes) |
| 232 | +├── snapshots/ # reference PNGs for visual regression |
| 233 | +├── test_opsset.py # geometry unit tests (no Cairo) |
| 234 | +├── test_sketch.py # SketchAnimation logic tests (no Cairo) |
| 235 | +└── test_visuals.py # visual regression tests (Cairo + pytest-snapshot) |
| 236 | +
|
| 237 | +examples/ # runnable end-to-end scene scripts |
| 238 | +docs/ # Sphinx source and build output |
| 239 | +``` |
| 240 | + |
| 241 | +--- |
| 242 | + |
| 243 | +## Useful One-Liners |
| 244 | + |
| 245 | +```bash |
| 246 | +# Check that the package imports cleanly |
| 247 | +poetry run python3 -c "import handanim; print('ok')" |
| 248 | + |
| 249 | +# List all test node IDs without running them |
| 250 | +poetry run python3 -m pytest --collect-only -q |
| 251 | + |
| 252 | +# Run only tests that don't touch Cairo (fast pure-logic subset) |
| 253 | +poetry run python3 -m pytest tests/test_opsset.py tests/test_sketch.py |
| 254 | + |
| 255 | +# Show which snapshot files exist |
| 256 | +ls tests/snapshots/ |
| 257 | +``` |
0 commit comments