Skip to content

Commit 430596b

Browse files
committed
merged Version1.0 to main ready for new academic year, ready to publish to pypy
1 parent 87fe8e8 commit 430596b

251 files changed

Lines changed: 26149 additions & 3108 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.claude/skills/wiki/SKILL.md‎

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
---
2+
name: wiki
3+
description: Maintain the PyNGL knowledge wiki (wiki/). Use for /wiki status (report stale pages), /wiki update (refresh stale pages), /wiki build [page] (regenerate pages from source). Also use after any change to the public API or module layout to keep the wiki in sync.
4+
---
5+
6+
# PyNGL Knowledge Wiki Maintenance
7+
8+
The `wiki/` directory is an agent-maintained knowledge base about how PyNGL
9+
works, written for both humans and coding agents. Every page's frontmatter
10+
records which source files it describes (`sources`) and the commit it was
11+
last verified against (`synced`). The staleness checker is the single
12+
source of truth for what needs updating:
13+
14+
uv run wiki/tools/check_sync.py # human report
15+
uv run wiki/tools/check_sync.py --json # machine-readable
16+
17+
Exit code 0 = all fresh; 1 = something stale or in error.
18+
19+
## Modes
20+
21+
Parse the argument after `/wiki`:
22+
23+
### `/wiki status`
24+
25+
Run the checker and relay the report: which pages are STALE (and which
26+
source files changed), which are in ERROR, which src/ files are UNTRACKED
27+
(covered by no page). Make no edits.
28+
29+
### `/wiki update`
30+
31+
1. Run the checker with `--json`.
32+
2. For each STALE page:
33+
- Read the page.
34+
- Run `git diff <synced>..HEAD -- <sources...>` (the page's own values)
35+
to see exactly what changed.
36+
- Read the current source files that changed.
37+
- Rewrite only the parts of the page the changes invalidate. Do not
38+
rewrite prose that is still accurate.
39+
- Set `synced` to the current full HEAD hash (`git rev-parse HEAD`).
40+
3. For each ERROR page: fix the frontmatter (usually a bad `synced` hash —
41+
re-verify the page against current sources, then set `synced` to HEAD).
42+
4. For UNTRACKED files: either add them to the most relevant existing
43+
page's `sources` (and document them) or note that a new page is needed.
44+
5. If more than ~4 pages are stale, fan out one subagent per page instead
45+
of updating inline; give each subagent the page path, its changed
46+
files, and the Page Format rules below.
47+
6. Re-run the checker; it must exit 0 before you finish.
48+
49+
### `/wiki build [page]`
50+
51+
Full regeneration of one page (by path) or every page listed in
52+
`wiki/index.md`. Dispatch one subagent per page. Each subagent must:
53+
read the page's source files in full, follow the Page Format below, and
54+
set `synced` to the current full HEAD hash. After all pages are built,
55+
run the checker (must exit 0) and update `wiki/index.md`'s page map if
56+
pages were added or removed.
57+
58+
## Page Format (all modes must preserve this)
59+
60+
Frontmatter, then four sections:
61+
62+
---
63+
sources:
64+
- src/ncca/ngl/vec*.py # repo-relative globs; * stays within
65+
- tests/test_api_consistency.py # a directory, ** crosses directories
66+
synced: <full 40-char commit hash>
67+
---
68+
69+
# <Page Title>
70+
71+
## Summary
72+
One paragraph: what this part of the system is for.
73+
74+
## How it works
75+
The narrative: data flow, class relationships, lifecycle.
76+
77+
## Key invariants
78+
Bulleted facts an agent must not violate when editing this code
79+
(e.g. "`_data` is always `np.float32`").
80+
81+
## Connections
82+
Relative links to related wiki pages.
83+
84+
## Style rules
85+
86+
- Point at code with `path` / `path:symbol` references; never paste code
87+
blocks longer than 3 lines — pasted code rots.
88+
- State invariants and contracts, not line-by-line narration.
89+
- Keep each page short enough to load whole as context (~150 lines max).
90+
- British "colour" spelling, per project convention.
91+
- When a module is added/renamed/removed, update the affected page's
92+
`sources` list and `wiki/index.md`'s page map in the same change.

‎.github/workflows/docs.yml‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
name: Deploy Docs
2+
3+
on:
4+
push:
5+
branches: [main, Version1.0]
6+
paths:
7+
- "docs/**"
8+
- "src/**"
9+
- ".github/workflows/docs.yml"
10+
workflow_dispatch:
11+
12+
permissions:
13+
contents: write
14+
15+
concurrency:
16+
group: docs-deploy
17+
cancel-in-progress: true
18+
19+
jobs:
20+
# Strict build to catch docs drift: broken references, bad ::: targets,
21+
# missing/orphaned nav entries, and docstring/signature mismatches.
22+
docs-strict:
23+
runs-on: ubuntu-latest
24+
steps:
25+
- uses: actions/checkout@v4
26+
27+
- name: Install uv
28+
uses: astral-sh/setup-uv@v6
29+
with:
30+
python-version: "3.13"
31+
32+
- name: Install the project (needed for mkdocstrings API reference)
33+
run: uv sync --locked --all-extras --dev
34+
35+
- name: Install docs dependencies
36+
run: uv pip install mkdocs "mkdocstrings[python]"
37+
38+
- name: Strict docs build
39+
run: uv run mkdocs build --strict -f docs/mkdocs.yml
40+
41+
deploy:
42+
runs-on: ubuntu-latest
43+
steps:
44+
- uses: actions/checkout@v4
45+
46+
- name: Install uv
47+
uses: astral-sh/setup-uv@v6
48+
with:
49+
python-version: "3.13"
50+
51+
- name: Install the project (needed for mkdocstrings API reference)
52+
run: uv sync --locked --all-extras --dev
53+
54+
- name: Install docs dependencies
55+
run: uv pip install mkdocs "mkdocstrings[python]"
56+
57+
- name: Deploy to GitHub Pages
58+
working-directory: docs
59+
run: uv run mkdocs gh-deploy --force --clean

‎.github/workflows/uv.yml‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,23 @@ on:
77
branches: [main]
88

99
jobs:
10+
# Enforces type hints (ANN) and docstrings (D) on src/ — see CLAUDE.md.
11+
lint-annotations-docstrings:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Install uv
17+
uses: astral-sh/setup-uv@v6
18+
with:
19+
python-version: "3.13"
20+
21+
- name: Install the project
22+
run: uv sync --locked --all-extras --dev
23+
24+
- name: Check type annotations and docstrings
25+
run: uv run ruff check src/
26+
1027
build:
1128
name: UV tests
1229
runs-on: ${{ matrix.os }}

‎.gitignore‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,15 @@ venv.bak/
7070

7171
# Cython debug symbols
7272
cython_debug/
73+
74+
NGLDebug.log
75+
*.backup
76+
.DS_Store
77+
.serena
78+
err*
79+
doc/site*
80+
docs/site/
81+
.superpowers/
82+
83+
# Agent worktrees
84+
.worktrees/

‎AGENTS.md‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,3 +206,10 @@ You understand modern Python development practices, architectural patterns, and
206206
You are a specialist in computer graphics programming and use appropriate technologies such as WebGPU and OpenGL.
207207

208208
You can use https://github.com/NCCA and https://nccastaff.bournemouth.ac.uk/jmacey/ as context for educational graphics programming patterns and best practices.
209+
210+
211+
## Documentation and English Guide
212+
213+
- Write using clear, concise English as would be used by an English-speaking professional. Do not use jargon or technical terms unless they are standard in the field. Do not use emojis or other non-standard formatting, including emdashes or other non-standard punctuation.
214+
215+
When making changes to the PyNGL codebase, always ensure that you check the documentation and tutorials and ensure any changes you make are reflected in the documentation.

‎CLAUDE.md‎

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,35 @@ uv run pre-commit run --all-files
3838

3939
A `Taskfile.yml` (go-task) wraps some of these: `task test-all`, `task lint`, `task coverage`, `task sync`.
4040

41+
## Documentation sync — do this whenever you touch the public API
42+
43+
The docs site (https://ncca.github.io/PyNGL/) is built with MkDocs + mkdocstrings from `docs/` and deployed by `.github/workflows/docs.yml`. mkdocstrings renders each class/function's docstring **live from `src/` at build time**, so editing a docstring updates the site automatically — but it does **not** discover new symbols. When you add, rename, or remove a public class or function you MUST also:
44+
45+
1. Update the matching `::: ncca.ngl...` directive in the relevant `docs/docs/*.md` page (Math, Geometry, VAO, Shaders, Camera, ImageAndTexture, Text, Misc). A new symbol that isn't listed there never appears in the docs.
46+
2. Add any new page to `nav:` in `docs/mkdocs.yml`.
47+
3. Ensure the symbol has a complete Google-style docstring — it *is* the rendered API reference — and update any tutorial examples in `docs/docs/tutorials/` that use it.
48+
4. Update the README feature list and this file's **Architecture** section if the module layout changed.
49+
50+
Verify before committing (this is the drift check `gh-deploy` skips):
51+
52+
```bash
53+
uv run --with mkdocs --with "mkdocstrings[python]" mkdocs build --strict -f docs/mkdocs.yml
54+
```
55+
56+
`--strict` fails on broken references, bad `:::` targets, missing/orphaned nav entries, and docstring/signature mismatches (griffe warnings). The strict build passes clean and runs as a **blocking** `docs-strict` job in `docs.yml` — keep it at zero warnings.
57+
58+
### Type hints and docstring linting
59+
60+
Ruff enforces type-hint presence (`ANN` rules) and docstring presence/format (`D` rules, Google convention) on `src/` — see `[tool.ruff.lint]` in `pyproject.toml` (`ANN401`/`Any` is deliberately allowed at math/shader-uniform bridge points). Check before committing with the normal lint command — it already picks up these rules from config:
61+
62+
```bash
63+
uv run ruff check src/
64+
```
65+
66+
**Do not** run `ruff check --select ANN,D src/` — an explicit CLI `--select` overrides the config's `ignore` list, which wrongly re-flags the allowed `ANN401` cases.
67+
68+
`src/` passes clean and the check runs as a **blocking** `lint-annotations-docstrings` CI job (see `uv.yml`). Any new or touched function/class must have complete type hints and a Google-style docstring.
69+
4170
**Always run the whole test suite after making changes, not just the tests for the touched file.**
4271

4372
## Test architecture — read this before writing/running tests
@@ -48,28 +77,54 @@ A `Taskfile.yml` (go-task) wraps some of these: `task test-all`, `task lint`, `t
4877

4978
### Core math/graphics library — `src/ncca/ngl/`
5079

51-
Flat module layout, one class family per file, all re-exported from `src/ncca/ngl/__init__.py`. Key groups:
80+
Flat module layout, one class family per file, all re-exported from `src/ncca/ngl/__init__.py`. This top-level package holds only API-agnostic modules (no direct `OpenGL.GL` imports); OpenGL-coupled modules live in the `src/ncca/ngl/opengl/` sub-package (see below) and are **not** re-exported from `ncca.ngl` — import them from `ncca.ngl.opengl` directly. Key groups:
5281

5382
- **Math primitives**: `vec2.py`/`vec3.py`/`vec4.py` (+ `vec*_array.py` list-like containers), `mat2.py`/`mat3.py`/`mat4.py`, `quaternion.py`, `plane.py`, `bbox.py`, `transform.py`, `util.py` (lookAt/perspective/ortho/frustum/clamp/lerp).
54-
- **Mesh/geometry**: `base_mesh.py` (`BaseMesh`, `Face`) is the base for loadable/generatable geometry; `obj.py` (`Obj`) parses Wavefront OBJ on top of it; `prim_data.py` generates raw vertex data for primitive shapes (`Prims` enum + `PrimData`); `primitives.py` wraps that data into drawable VAOs via `VAOFactory`.
55-
- **VAO abstraction**: `abstract_vao.py` defines the `AbstractVAO` interface + `VertexData`; `simple_vao.py`, `simple_index_vao.py`, `multi_buffer_vao.py` are concrete OpenGL implementations; `vao_factory.py` (`VAOFactory`) is a registry/factory so new VAO types can be added without touching call sites.
56-
- **Shaders (OpenGL)**: `shader.py` (single compiled shader) → `shader_program.py` (linked program, uniform setters) → `shader_lib.py` (`ShaderLib`, a singleton registry of named shader programs — this is the conventional entry point application code uses, not `ShaderProgram` directly).
57-
- **Cameras/input**: `first_person_camera.py`, `pyside_event_handling_mixin.py` (mouse/keyboard camera control mixin for PySide6 apps).
58-
- **Other**: `image.py` (Pillow-backed), `texture.py`, `text.py` (freetype-py glyph atlas + geometry-shader quads), `random.py`, `log.py` (colored logger, `setup_logger`).
83+
- **Mesh/geometry data**: `bezier_curve.py`, `prim_data.py` generates raw vertex data for primitive shapes (`Prims` enum + `PrimData`); `obj.py` (`Obj`) parses Wavefront OBJ files, importing `BaseMesh`/`Face` and `Texture` from `ncca.ngl.opengl`.
84+
- **Cameras**: `first_person_camera.py`.
85+
- **Other**: `image.py` (Pillow-backed), `random.py`, `log.py` (colored logger, `setup_logger`).
86+
87+
### `src/ncca/ngl/opengl/`
88+
89+
OpenGL-coupled modules (anything that directly `import OpenGL.GL`), mirroring the `webgpu/` sub-package structure. Re-exported from `src/ncca/ngl/opengl/__init__.py`; import as `from ncca.ngl.opengl import X`, not from `ncca.ngl`.
90+
91+
- **Mesh/geometry**: `base_mesh.py` (`BaseMesh`, `Face`) is the base for loadable/generatable geometry; `primitives.py` (`Primitives`) wraps `PrimData` (from top-level `ncca.ngl`) into drawable VAOs via `VAOFactory`.
92+
- **VAO abstraction**: `abstract_vao.py` defines the `AbstractVAO` interface + `VertexData`; `simple_vao.py`, `simple_index_vao.py`, `multi_buffer_vao.py` are concrete implementations; `vao_factory.py` (`VAOFactory`) is a registry/factory so new VAO types can be added without touching call sites.
93+
- **Shaders**: `shader.py` (single compiled shader) → `shader_program.py` (linked program, uniform setters) → `shader_lib.py` (`ShaderLib`, a singleton registry of named shader programs — this is the conventional entry point application code uses, not `ShaderProgram` directly).
94+
- **Input**: `pyside_event_handling_mixin.py` (mouse/keyboard camera control mixin for PySide6 apps).
95+
- **Other**: `texture.py`, `text.py` (freetype-py glyph atlas + geometry-shader quads), `shaders/` (glsl assets used by `shader_lib.py`).
5996

6097
### `src/ncca/ngl/webgpu/`
6198

62-
Parallel rendering stack targeting `wgpu` instead of OpenGL. `base_webgpu_pipeline.py` is the common base; `*_pipeline.py` files (triangle, line, point, point_list, instanced_geometry, custom_shader) are concrete render pipelines; `pipeline_factory.py` mirrors the OpenGL `VAOFactory` pattern for pipelines; `pipeline_shaders.py`/`webgpu_constants.py` hold shader source and enum constants. Has its own `__main__.py` for standalone demo/dev runs.
99+
Parallel rendering stack targeting `wgpu` instead of OpenGL. `webgpu_widget.py` (`WebGPUWidget`) is the PySide6 widget applications subclass — it renders offscreen and blits the result, so there is no OpenGL context or swapchain to manage. `base_webgpu_pipeline.py` is the common base; `*_pipeline.py` files (triangle, line, point, point_list, instanced_geometry, custom_shader) are concrete render pipelines; `pipeline_factory.py` mirrors the OpenGL `VAOFactory` pattern for pipelines; `pipeline_shaders.py`/`webgpu_constants.py` hold shader source and enum constants. Has its own `__main__.py` for standalone demo/dev runs.
63100

64101
### `src/ncca/ngl/widgets/`
65102

66-
PySide6 (Qt) widgets for editing/displaying NGL types in GUIs: `vec2widget.py`/`vec3widget.py`/`vec4widget.py`, `mat4widget.py`, `transformwidget.py`, `rgbcolourwidget.py`/`rgbacolourwidget.py`, `lookatwidget.py`. Has GLSL assets under `widgets/glsl/`.
103+
PySide6 (Qt) widgets for editing/displaying NGL types in GUIs: `vec2widget.py`/`vec3widget.py`/`vec4widget.py`, `mat2widget.py`/`mat3widget.py`/`mat4widget.py` (editable NxN grids sharing the private `_MatGridWidget` base in `mat_grid_widget.py`; Mat3Widget/Mat4Widget add a method combo box for `rotate_x`/`rotate_y`/`rotate_z`/`scale`/`translate`), `transformwidget.py`, `rgbcolourwidget.py`/`rgbacolourwidget.py`, `lookatwidget.py`, `perspectivewidget.py`. Has GLSL assets under `widgets/glsl/`.
104+
105+
### `src/ncca/ngl/qml/`
106+
107+
Qt Quick (QML) equivalents of the `widgets/` PySide6 widgets: `vec2_model.py`/`vec3_model.py`/`vec4_model.py`,
108+
`mat_grid_model.py` (shared base) + `mat2_model.py`/`mat3_model.py`/`mat4_model.py`, `transform_model.py`,
109+
`lookat_model.py`, `perspective_model.py`, `rgb_colour_model.py`/`rgba_colour_model.py` — each a `QObject` registered as a QML type via
110+
`@QmlElement`, paired with a same-named `.qml` view file (`Vec3Widget.qml`, `Mat4Widget.qml`, etc.) plus shared
111+
`DecimalSpinBox.qml`/`MatrixGridWidget.qml` components. It is a file-based QML module (`qmldir` declares
112+
`module ncca.ngl.qml`), so external `.qml` files only resolve `import ncca.ngl.qml 1.0` after calling the exported
113+
`add_import_path(engine)`. Has its own `__main__.py` + `main.qml` demo, run via `python -m ncca.ngl.qml`.
67114

68115
### API consistency conventions
69116

70-
- Vector/matrix classes implement `copy()`, `to_numpy()`, `to_list()`, `to_tuple()`, and `__hash__`.
117+
- All math classes (`Vec2/3/4`, `Mat2/3/4`, `Quaternion`) follow one contract:
118+
numpy `np.float32` storage in `_data`; immutable-style operations returning
119+
new objects (`normalized()`, `transposed()`, `inverse()`, `clamped()` — only
120+
`set()` and element assignment mutate); constructors take components with a
121+
sensible default; `from_list`/`from_numpy` classmethods;
122+
`copy()`/`to_numpy()`/`to_list()`/`to_tuple()`; `__eq__`/`__hash__`; eval-able
123+
`__repr__`. `@` is the linear-algebra product; `*` is scalar only, except
124+
`Quaternion * Vec3`, which rotates the vector (kept per spec).
125+
`tests/test_api_consistency.py` enforces this — run it when touching math code.
71126
- Prefer numpy arrays (`np.float32`) over Python lists for numeric data; use `__slots__` on data-heavy classes.
72-
- Module-specific errors are plain `Exception` subclasses named `<Module>Error` (e.g. `Mat3Error`, `ObjParseVertexError`), raised rather than returning sentinel values.
127+
- Module-specific errors are plain `Exception` subclasses named `<Module>Error` (e.g. `MatrixError`, `ObjParseVertexError`), raised rather than returning sentinel values.
73128
- **"Colour"** (not "color") is the correct spelling in this codebase's identifiers, docs, and variables.
74129
- Docstrings are Google-style with Args/Returns/Raises; type hints are required on all function signatures and class attributes.
75130
- Executable scripts use the shebang `#!/usr/bin/env -S uv run --script`.

‎LICENSE.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
44

55
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
66

7-
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
7+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

0 commit comments

Comments
 (0)