Grease Pencil Rosette

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.

witnesses The GPv3 address break: 4.5 keeps GPv3 at grease_pencils_v3 while grease_pencils is still legacy GPencil (frame.strokes, no .drawing); 5.x deletes legacy and GPv3 takes over the grease_pencils name. Point writes lazily materialize attribute layers, and every position round-trips through the raw POINT buffer.

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:

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.

Render#

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).

Run#

# 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

Exit codes#

Per-script sequential checks. 9 is a valid check code; there is no rule against it.

CodeMeaning
0Success
1Uncaught exception (FATAL wrapper)
2argparse / usage
3GPv3 address / legacy trap for this Blender
4Object type, layer count, or frame number
5Stroke topology or cyclic (--open-strokes lands here)
6Lazy attribute layers missing or wrong domain/type
7Position buffer length or closed-form round-trip
8Grease Pencil material missing gpencil settings
9--output produced no file
10Framing 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).

Source

examples/grease-pencil-rosette/grease_pencil_rosette.py View the script on GitHub →
A neon rosette sign: five nested Grease Pencil rose loops, cyan through magenta to red, glowing a few centimetres proud of a black lacquer board in a brass frame on a dark wall.