Skip to content

Commit 256f98d

Browse files
TMHSDigitalclaude
andauthored
feat: add image-pixels-testcard example (flat RGBA pixel buffer + save() lifecycle witness) (#60)
The gallery had no coverage of bpy.types.Image, and bulk pixels.foreach_set was an explicit ROADMAP candidate. A procedural broadcast test card written with one foreach_set witnesses the contracts AI-generated image code gets wrong: the buffer is always flat RGBA (channels == 4 even with alpha=False), byte storage quantizes at exactly <= 0.5/255 and strictly > 0, scale() reallocates so stale-size bulk reads raise, and Image.save() silently flips source to FILE and drops the in-memory buffer -- later pixels reads come from whatever sits on disk, proven by overwriting the file with an imposter. save_render() is the non-destructive path. All contracts probed identical on 4.5.11 LTS and 5.1.2. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 4531258 commit 256f98d

13 files changed

Lines changed: 1128 additions & 8 deletions

File tree

.cursor-plugin/plugin.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"examples/gn-instance-grid",
7171
"examples/gn-sdf-remesh",
7272
"examples/grease-pencil-rosette",
73+
"examples/image-pixels-testcard",
7374
"examples/parent-inverse-orrery",
7475
"examples/shader-node-group",
7576
"examples/shape-key-blend",

.github/workflows/blender-smoke.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,3 +311,18 @@ jobs:
311311
# at to_mesh_clear(). Exits non-zero on failure.
312312
xvfb-run -a "$BLENDER" --background \
313313
--python examples/text-version-stamp/text_version_stamp.py --
314+
315+
- name: Shipped example - image pixels testcard (flat RGBA buffer + save lifecycle)
316+
run: |
317+
set -euo pipefail
318+
# Check only (no render): a procedural broadcast test card written with
319+
# one pixels.foreach_set; asserts the buffer is always flat RGBA
320+
# (channels == 4 even with alpha=False, RGB-stride writes raise), the
321+
# byte round-trip error is <= 0.5/255 and strictly > 0 (storage really
322+
# is 8-bit) while float_buffer=True round-trips at float32 precision,
323+
# scale() reallocates so stale-size bulk reads raise, and save() flips
324+
# source to FILE and drops the buffer (pixels silently re-load from
325+
# disk — proven with an imposter file) while save_render() preserves
326+
# it. Exits non-zero on failure.
327+
xvfb-run -a "$BLENDER" --background \
328+
--python examples/image-pixels-testcard/image_pixels_testcard.py --

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ The content base (counts are CI-enforced against README.md and the manifest):
2727
- 2 templates: `extension-addon-template` for Extensions Platform add-ons,
2828
and `headless-batch-script-template` for unattended batch jobs.
2929
- 17 snippets covering canonical patterns.
30-
- 19 examples under `examples/<name>/`: runnable scripts that assert a real
30+
- 20 examples under `examples/<name>/`: runnable scripts that assert a real
3131
API contract with deterministic checks, exit non-zero on failure, and
3232
optionally render a still via `--output`. Each is executed headless on
3333
Blender 4.5 LTS and 5.1 by `blender-smoke.yml`; its render ships in the
@@ -41,7 +41,7 @@ Blender-Developer-Tools/
4141
rules/<rule-name>.mdc # 6 rule files
4242
templates/<template-name>/ # 2 starter templates
4343
snippets/<snippet-name>.py # 17 standalone Python snippets
44-
examples/<name>/ # 19 runnable smoke-gated examples (+ gallery.json)
44+
examples/<name>/ # 20 runnable smoke-gated examples (+ gallery.json)
4545
scripts/build_gallery.py # generates docs/gallery/ (stdlib only)
4646
scripts/site/ # vendored landing-page build (build_site.py + template)
4747
docs/gallery/ # committed generated gallery pages + hero assets

CLAUDE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ skills/<skill-name>/SKILL.md - AI workflow definitions, 12 total
1919
rules/<rule-name>.mdc - Anti-pattern rules, 6 total
2020
templates/<template-name>/ - Starter projects, 2 total
2121
snippets/<snippet-name>.py - Standalone code patterns, 17 total
22-
examples/<name>/ - Runnable smoke-gated examples, 19 total (+ gallery.json)
22+
examples/<name>/ - Runnable smoke-gated examples, 20 total (+ gallery.json)
2323
scripts/build_gallery.py - Regenerates docs/gallery/ from gallery.json (stdlib only)
2424
scripts/site/ - Vendored landing-page build (Jinja2)
2525
docs/gallery/ - Committed generated gallery pages + hero renders
@@ -80,11 +80,11 @@ v0.1.0: canonical object creation and deletion, depsgraph evaluated mesh, bmesh
8080

8181
v0.2.0: Principled BSDF material, driver-with-custom-function via `driver_namespace`, application handler registration, shader node group with cross-version `interface` API, `foreach_get` bulk vertex read, version-branch skeleton, and USD export with `evaluation_mode='RENDER'`.
8282

83-
## Examples (19)
83+
## Examples (20)
8484

8585
Runnable scripts at `examples/<name>/`, each asserting a real API contract with
8686
deterministic checks (exit non-zero on failure) and optionally rendering a still via
87-
`--output`. All nineteen run headless on Blender 4.5 LTS and 5.1 in `blender-smoke.yml`;
87+
`--output`. All twenty run headless on Blender 4.5 LTS and 5.1 in `blender-smoke.yml`;
8888
their renders ship in the site gallery at `docs/gallery/`. `examples/gallery.json` is the
8989
gallery's source of truth. When authoring a new one, copy the anatomy of
9090
`examples/bmesh-gear/` (script structure, README shape, dark-studio render recipe) and

README.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717
</p>
1818

1919
<p align="center">
20-
<strong>12 skills</strong> &nbsp;&bull;&nbsp; <strong>6 rules</strong> &nbsp;&bull;&nbsp; <strong>2 templates</strong> &nbsp;&bull;&nbsp; <strong>17 snippets</strong> &nbsp;&bull;&nbsp; <strong>19 examples</strong>
20+
<strong>12 skills</strong> &nbsp;&bull;&nbsp; <strong>6 rules</strong> &nbsp;&bull;&nbsp; <strong>2 templates</strong> &nbsp;&bull;&nbsp; <strong>17 snippets</strong> &nbsp;&bull;&nbsp; <strong>20 examples</strong>
2121
</p>
2222

2323
---
2424

2525
## Overview
2626

27-
This repository ships **12 skills, 6 rules, 2 templates, 17 snippets, and 19 runnable examples** for Blender Python development targeting Blender 5.1 (current stable) with Blender 4.5 LTS fallback support.
27+
This repository ships **12 skills, 6 rules, 2 templates, 17 snippets, and 20 runnable examples** for Blender Python development targeting Blender 5.1 (current stable) with Blender 4.5 LTS fallback support.
2828

2929
The content is consumed by AI coding agents (Cursor, Claude Code, any MCP-capable client) when working on Blender add-ons, geometry nodes scripts, batch pipelines, or animation tooling. There is no build step. Edit the markdown and Python files directly.
3030

@@ -332,6 +332,25 @@ but planar, that body edits regenerate geometry, that `version_string` is not ba
332332
semver on LTS builds (`"4.5.11 LTS"`), and that a Mesh reference dies at
333333
`to_mesh_clear()`.
334334

335+
</td>
336+
</tr>
337+
<tr>
338+
<td width="46%" valign="middle">
339+
<a href="examples/image-pixels-testcard/"><img src="examples/image-pixels-testcard/preview.webp" alt="Image pixels testcard: a studio monitor showing a procedural broadcast test card — seven neon color bars behind the classic dark circle, a luminance ramp, and a PLUGE row with a white bottom-left origin marker — over a teal underglow on a dark studio floor" /></a>
340+
</td>
341+
<td valign="middle">
342+
343+
### [image-pixels-testcard](examples/image-pixels-testcard/)
344+
345+
A procedural broadcast test card written into `bpy.data.images.new()` with one
346+
`pixels.foreach_set()` call. Asserts the buffer is always flat RGBA (`channels == 4`
347+
even with `alpha=False`), that byte storage quantizes at exactly ≤ 0.5/255 and
348+
strictly > 0 while `float_buffer=True` round-trips at float32 precision, that
349+
`scale()` reallocates (stale-size reads raise), and the `save()` trap: `source`
350+
silently flips to `FILE`, the buffer drops, and later `pixels` reads come from
351+
whatever sits on disk — proven with an imposter file. `save_render()` is the
352+
non-destructive path.
353+
335354
</td>
336355
</tr>
337356
</table>

ROADMAP.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,9 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo
9797
- Refresh the `slotted-actions-animation` skill against any 5.2 changes
9898
- Bump `blender_version_min` in the templates if 5.2 APIs are used
9999
- Additional snippets for asset library scripting, EXR baking, multi-file extensions
100-
- Gallery coverage follow-ups from the GPv3 review: light-linking, bulk `pixels.foreach_set`, VSE sequences-to-strips witness (`grease-pencil-rosette`, `armature-bend`, and `text-version-stamp` shipped first)
100+
- Gallery coverage follow-ups from the GPv3 review: light-linking, VSE sequences-to-strips witness (`grease-pencil-rosette`, `armature-bend`, `text-version-stamp`, and `image-pixels-testcard` shipped first)
101+
- UV-layer authoring witness: `bmesh.ops.create_grid(calc_uvs=True)` silently creates no UV layer unless one already exists, and an Image Texture without UVs samples texel (0,0) everywhere (found while authoring `image-pixels-testcard`)
102+
- Image save-format witness: float images saved to PNG with varying alpha suffer premultiply quantization (~0.98 worst-case round-trip error at 8-bit alpha extremes); EXR vs PNG storage contract
101103

102104
## Future (uncommitted)
103105

18.3 KB
Loading

0 commit comments

Comments
 (0)