Color Attribute Wheel
The modern color-attributes API — mesh.color_attributes.new() on the CORNER domain, filled by expanding per-vertex HSV across face corners with foreach_get/foreach_set, then wired into a shader Attribute node.
examples/grease-pencil-rosette/
Grease Pencil v3's attribute-based API — layer → frames.new(1).drawing → add_strokes → per-point position/radius/opacity/vertex_color — drawing five nested neon rose curves.
Rendered headless by the example itself. Select it to enlarge.
tags grease-pencil attributes
runs on 4.5 LTS · 5.1 · 5.2 LTS
blender --background --python examples/grease-pencil-rosette/grease_pencil_rosette.py --
falsify it Breaks the contract on purpose and must exit 5
blender --background --python examples/grease-pencil-rosette/grease_pencil_rosette.py -- --open-strokes
A runnable example that draws five nested neon rose curves as Grease Pencil v3 strokes — layer → frames.new(1).drawing → add_strokes([counts]) → per-point position, radius, opacity, vertex_color — the attribute-based API that replaced legacy GPencil across the 4.x-to-5.x window, the largest bpy API break the gallery witnesses.
What it witnesses: GPv3 lives at different addresses on the two supported versions, and the check asserts each side's actual contract:
bpy.data.grease_pencils_v3 (GreasePencilv3), while bpy.data.grease_pencils still holds legacy GPencil whose frames carry .strokes directly and have no .drawing. Same collection name, incompatible API — the trap is asserted, not just avoided.bpy.data.grease_pencils name, and grease_pencils_v3 / bpy.types.GPencilStroke no longer exist.On the shared GPv3 surface it then asserts that stroke points are views over attribute layers: writing pt.radius / pt.opacity / pt.vertex_color lazily materializes radius, opacity, vertex_color (POINT) and cyclic (CURVE) layers in drawing.attributes, and every closed-form rose position round-trips exactly through the raw position attribute buffer via foreach_get.
The strokes stay unlit (layer.use_lights = False) and act as the neon tubes of a sign: they hang a few centimetres proud of a satin black-lacquer backboard in a brass frame with brass standoff caps and a maker's plate, mounted on the studio wall. Grease Pencil does not light its surroundings, so the spill is staged: one small shadowless disk light per sample along each ring, placed on that ring's rose_point and tinted with its ring_color, washes the board behind each tube. The spill reads the same closed-form curve the check asserts. The render path gates framing through examples/gallery_framing.py (exit 10).
# Cheap correctness check (no render) — the CI check:
blender --background --python grease_pencil_rosette.py --
# Falsifier: open strokes. Must exit non-zero (cyclic).
blender --background --python grease_pencil_rosette.py -- --open-strokes
# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts):
blender --background --python grease_pencil_rosette.py -- --output rosette.png
blender --background --python grease_pencil_rosette.py -- --output rosette.png --engine cycles
Per-script sequential checks. 9 is a valid check code; there is no rule against it.
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Uncaught exception (FATAL wrapper) |
| 2 | argparse / usage |
| 3 | GPv3 address / legacy trap for this Blender |
| 4 | Object type, layer count, or frame number |
| 5 | Stroke topology or cyclic (--open-strokes lands here) |
| 6 | Lazy attribute layers missing or wrong domain/type |
| 7 | Position buffer length or closed-form round-trip |
| 8 | Grease Pencil material missing gpencil settings |
| 9 | --output produced no file |
| 10 | Framing gate violation on the --output path (gallery_framing) |
The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS (5.1 on the weekly cron, the needs-5.1 PR label, or manual dispatch). Smoke does not pass --output. Its catalog falsifier is --open-strokes (expects exit 5).