From 50d90d8884b0cdbff81abf2c0b1646a9f9ef811b Mon Sep 17 00:00:00 2001 From: fOuttaMyPaint Date: Thu, 23 Jul 2026 18:40:43 -0400 Subject: [PATCH 1/3] docs: correct the stale example count in AGENTS.md and CLAUDE.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both files' prose said 23 examples while README, the plugin manifest, and CI all agree on 38 (derived from .cursor-plugin/plugin.json: 12 skills, 6 rules, 2 templates, 17 snippets, 38 examples — every other count in the two files verified correct as written). Example-shipping commits keep these lines current from here. Signed-off-by: fOuttaMyPaint --- AGENTS.md | 4 ++-- CLAUDE.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 046c12a..31d2853 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,7 @@ a `.cursor-plugin/plugin.json` manifest so the ecosystem drift checker classifies it as a `cursor-plugin`. This is content the AI loads when the user asks Blender questions or works on Blender add-ons in Cursor or Claude Code. -The content base is 12 skills, 6 rules, 2 templates, 17 snippets, and 23 +The content base is 12 skills, 6 rules, 2 templates, 17 snippets, and 38 examples (counts are CI-enforced against README.md and the manifest). The full inventory tables and per-item purposes live in `CLAUDE.md`. Example anatomy and authoring rules: copy `examples/bmesh-gear/`; the render look is specified @@ -35,7 +35,7 @@ Blender-Developer-Tools/ rules/.mdc # 6 rule files templates// # 2 starter templates snippets/.py # 17 standalone Python snippets - examples// # 23 runnable smoke-gated examples (+ gallery.json) + examples// # 38 runnable smoke-gated examples (+ gallery.json) examples/gallery_framing.py # shared Layer 1 framing measurement (render path only) scripts/build_gallery.py # generates docs/gallery/ (stdlib only) scripts/site/ # vendored landing-page build (build_site.py + template) diff --git a/CLAUDE.md b/CLAUDE.md index 4259c6c..4c8facc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,7 +21,7 @@ skills//SKILL.md - AI workflow definitions, 12 total rules/.mdc - Anti-pattern rules, 6 total templates// - Starter projects, 2 total snippets/.py - Standalone code patterns, 17 total -examples// - Runnable smoke-gated examples, 23 total (+ gallery.json) +examples// - Runnable smoke-gated examples, 38 total (+ gallery.json) scripts/build_gallery.py - Regenerates docs/gallery/ from gallery.json (stdlib only) scripts/site/ - Vendored landing-page build (Jinja2) docs/gallery/ - Committed generated gallery pages + hero renders @@ -82,11 +82,11 @@ v0.1.0: canonical object creation and deletion, depsgraph evaluated mesh, bmesh 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'`. -## Examples (23) +## Examples (38) Runnable scripts at `examples//`, each asserting a real API contract with deterministic checks (exit non-zero on failure) and optionally rendering a still via -`--output`. All twenty-three run headless on Blender 4.5 LTS and 5.1 in `blender-smoke.yml`; +`--output`. All thirty-eight run headless on Blender 4.5 LTS and 5.1 in `blender-smoke.yml`; their renders ship in the site gallery at `docs/gallery/`. `examples/gallery.json` is the gallery's source of truth. When authoring a new one, copy the anatomy of `examples/bmesh-gear/` (script structure, README shape, dark-studio render recipe) and From b236f76844806d36fd16f9719c83b83640be2a99 Mon Sep 17 00:00:00 2001 From: fOuttaMyPaint Date: Thu, 23 Jul 2026 18:41:26 -0400 Subject: [PATCH 2/3] docs: codify the numeric-contract exception in VISUAL-STYLE Layer 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two shipped examples have contracts with no visual signature: vertex-weight-limit's pose-preserving prune and triangulate-tangents' mikktspace field both falsify pixel-identical, and both declared it honestly. Layer 1 now makes that an explicit, narrow exception: the README declares the contract numeric, the render depicts subject and setup legibly rather than a break that cannot exist, and falsification is satisfied by naming why. Anchored to the two precedents by name so the exception stays narrow — where a visual witness is possible, it is still required. Signed-off-by: fOuttaMyPaint --- docs/VISUAL-STYLE.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/VISUAL-STYLE.md b/docs/VISUAL-STYLE.md index 01bacd7..cccf588 100644 --- a/docs/VISUAL-STYLE.md +++ b/docs/VISUAL-STYLE.md @@ -85,6 +85,18 @@ the default stage. - The image must witness the API: if the contract failed, the render should visibly break. If the render would look the same whether the code worked or not, the scene design is wrong, not merely unpolished. +- **Numeric-only contracts are a narrow, declared exception.** Some contracts + have no visual signature: the broken state renders pixel-identical because + the evidence is a count, a cap, or a buffer invariant, not a shape. Then + the example says so in its README, the render's job is to depict the + subject and its setup legibly — so the reader can see what the numbers + describe — and the falsification pass is satisfied by naming why no visual + difference exists. Precedents: `vertex-weight-limit` (the prune preserves + the pose by design, so the 4-influence cap is numeric) and + `triangulate-tangents` (the ring sweep is UV/bump-driven, so a + tangent-field break does not move pixels). This is for contracts that + genuinely cannot be seen, not for scenes that were not designed hard + enough: where a visual witness is possible, it is required. - Presentation may be staged; evidence may not. When the witnessed artifact is itself an image (a sequencer frame, a pixel buffer, a baked texture), the authentic pixels must appear unaltered — mount them in the scene From a323d805309ef1c6f8edba3e596c01518e9a1026 Mon Sep 17 00:00:00 2001 From: fOuttaMyPaint Date: Thu, 23 Jul 2026 19:08:29 -0400 Subject: [PATCH 3/3] feat: add attribute-domain-shear example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Witnesses what POINT vs CORNER means on Mesh.color_attributes once a mesh has shared vertices: an 8-wedge pinwheel around one raised hub. CORNER authoring holds each wedge's exact color at the hub (err <= 1e-6); the naive per-wedge POINT loop rewrites every shared vertex once per neighbor and the last write wins — hub reads palette[K-1], ring verts read palette[i] except the wrap-around vert 0, and the measured shear equals the palette closed form (0.751031) exactly. Storage sizes asserted per domain (3K loops vs K+1 verts). Byte-identical on 4.5.11 and 5.1.2. Falsified: corner corruption (exit 4), reversed write order (exit 5), ring corruption (exit 6), constant palette collapsing the shear (exit 7). Companion to color-attribute-wheel, not a repackage: that one covers domain sizing and active_color; this one covers what the domains mean on shared topology. Render path gated with check_framing (0.837x/0.478y, margins >= 0.081); dual pinwheel still carries the broken state in-frame — the POINT fan is the falsification variant. Count prose bumped to 39 across README/CLAUDE/AGENTS. Signed-off-by: fOuttaMyPaint --- .cursor-plugin/plugin.json | 3 +- .github/workflows/blender-smoke.yml | 12 + AGENTS.md | 4 +- CLAUDE.md | 6 +- README.md | 23 +- ROADMAP.md | 3 +- .../assets/attribute-domain-shear-hero.webp | Bin 0 -> 17030 bytes .../gallery/attribute-domain-shear/index.html | 656 ++++++++++++++++++ .../attribute-domain-shear-contact-sheet.webp | Bin 0 -> 39768 bytes docs/gallery/index.html | 11 + examples/attribute-domain-shear/README.md | 60 ++ .../attribute_domain_shear.py | 412 +++++++++++ examples/attribute-domain-shear/preview.webp | Bin 0 -> 14860 bytes examples/gallery.json | 13 + 14 files changed, 1194 insertions(+), 9 deletions(-) create mode 100644 docs/gallery/assets/attribute-domain-shear-hero.webp create mode 100644 docs/gallery/attribute-domain-shear/index.html create mode 100644 docs/gallery/contact-sheets/attribute-domain-shear-contact-sheet.webp create mode 100644 examples/attribute-domain-shear/README.md create mode 100644 examples/attribute-domain-shear/attribute_domain_shear.py create mode 100644 examples/attribute-domain-shear/preview.webp diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 8ef9afd..000b4f8 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -60,10 +60,11 @@ ], "examples": [ "examples/armature-bend", + "examples/attribute-domain-shear", "examples/bmesh-gear", "examples/car-mirror-symmetry", - "examples/color-attribute-wheel", "examples/collision-hull-proxy", + "examples/color-attribute-wheel", "examples/compositor-glare", "examples/curve-bevel-arc", "examples/custom-normals-shade", diff --git a/.github/workflows/blender-smoke.yml b/.github/workflows/blender-smoke.yml index d232c3a..521d82e 100644 --- a/.github/workflows/blender-smoke.yml +++ b/.github/workflows/blender-smoke.yml @@ -542,3 +542,15 @@ jobs: # non-zero on failure. xvfb-run -a "$BLENDER" --background \ --python examples/car-mirror-symmetry/car_mirror_symmetry.py -- + + - name: Shipped example - attribute domain shear (POINT vs CORNER semantics) + run: | + set -euo pipefail + # Check only (no render): an 8-wedge pinwheel around one shared hub + # vertex; asserts CORNER attr sizing (3K loops) and exact per-wedge + # hub colors, POINT sizing (K+1 verts), and that a naive per-wedge + # POINT loop shears to the last write at every shared vertex with + # the measured deviation equal to the palette closed form. Exits + # non-zero on failure. + xvfb-run -a "$BLENDER" --background \ + --python examples/attribute-domain-shear/attribute_domain_shear.py -- diff --git a/AGENTS.md b/AGENTS.md index 31d2853..5cb742e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,7 +20,7 @@ a `.cursor-plugin/plugin.json` manifest so the ecosystem drift checker classifies it as a `cursor-plugin`. This is content the AI loads when the user asks Blender questions or works on Blender add-ons in Cursor or Claude Code. -The content base is 12 skills, 6 rules, 2 templates, 17 snippets, and 38 +The content base is 12 skills, 6 rules, 2 templates, 17 snippets, and 39 examples (counts are CI-enforced against README.md and the manifest). The full inventory tables and per-item purposes live in `CLAUDE.md`. Example anatomy and authoring rules: copy `examples/bmesh-gear/`; the render look is specified @@ -35,7 +35,7 @@ Blender-Developer-Tools/ rules/.mdc # 6 rule files templates// # 2 starter templates snippets/.py # 17 standalone Python snippets - examples// # 38 runnable smoke-gated examples (+ gallery.json) + examples// # 39 runnable smoke-gated examples (+ gallery.json) examples/gallery_framing.py # shared Layer 1 framing measurement (render path only) scripts/build_gallery.py # generates docs/gallery/ (stdlib only) scripts/site/ # vendored landing-page build (build_site.py + template) diff --git a/CLAUDE.md b/CLAUDE.md index 4c8facc..ca11478 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -21,7 +21,7 @@ skills//SKILL.md - AI workflow definitions, 12 total rules/.mdc - Anti-pattern rules, 6 total templates// - Starter projects, 2 total snippets/.py - Standalone code patterns, 17 total -examples// - Runnable smoke-gated examples, 38 total (+ gallery.json) +examples// - Runnable smoke-gated examples, 39 total (+ gallery.json) scripts/build_gallery.py - Regenerates docs/gallery/ from gallery.json (stdlib only) scripts/site/ - Vendored landing-page build (Jinja2) docs/gallery/ - Committed generated gallery pages + hero renders @@ -82,11 +82,11 @@ v0.1.0: canonical object creation and deletion, depsgraph evaluated mesh, bmesh 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'`. -## Examples (38) +## Examples (39) Runnable scripts at `examples//`, each asserting a real API contract with deterministic checks (exit non-zero on failure) and optionally rendering a still via -`--output`. All thirty-eight run headless on Blender 4.5 LTS and 5.1 in `blender-smoke.yml`; +`--output`. All thirty-nine run headless on Blender 4.5 LTS and 5.1 in `blender-smoke.yml`; their renders ship in the site gallery at `docs/gallery/`. `examples/gallery.json` is the gallery's source of truth. When authoring a new one, copy the anatomy of `examples/bmesh-gear/` (script structure, README shape, dark-studio render recipe) and diff --git a/README.md b/README.md index 2a49cd9..b8a33bf 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@

- 12 skills  •  6 rules  •  2 templates  •  17 snippets  •  38 examples + 12 skills  •  6 rules  •  2 templates  •  17 snippets  •  39 examples

@@ -739,6 +739,27 @@ Companion to [`depsgraph-export`](examples/depsgraph-export/) (evaluated-vs- original) and [`shape-key-blend`](examples/shape-key-blend/) (non-destructive evaluation). Break the mirror and the render is literally half a car. + + + + +Attribute domain shear: two eight-petal pinwheels side by side — left CORNER-domain with crisp colored petals to the hub, right naive POINT-domain smeared into a swirl, proving last-write-wins shear at shared vertices + + + +### [attribute-domain-shear](examples/attribute-domain-shear/) + +What `POINT` versus `CORNER` **means** on `Mesh.color_attributes` once a mesh +has shared vertices. An eight-wedge pinwheel around one raised hub: CORNER +authoring holds each wedge's exact color (hub corners disagree by face, +err ≤ 1e-6), while the naive per-wedge POINT loop rewrites every shared +vertex once per neighbor and the **last write wins** — the hub reads +palette[K−1], and the measured shear matches the palette closed form +(0.751031) exactly. Companion to +[`color-attribute-wheel`](examples/color-attribute-wheel/) (domain sizing, +`active_color`, the shader Attribute node). The broken state is in-frame: +the right pinwheel is the falsification variant. + diff --git a/ROADMAP.md b/ROADMAP.md index 52abe8a..22fef76 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -100,7 +100,7 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo - Gallery coverage follow-ups from the GPv3 review: light-linking (`grease-pencil-rosette`, `armature-bend`, `text-version-stamp`, `image-pixels-testcard`, and `vse-cut-list` shipped first) - ~~UV-layer authoring witness~~ **SHIPPED** as `examples/uv-layer-grid/` — `create_grid(..., calc_uvs=True)` silent no-op without a pre-existing UV layer; closed-form UV fill + explicit assignment fallback; dual-panel render (flat texel (0,0) vs neon checker) - ~~Image save-format witness~~ **SHIPPED** as `examples/png-exr-alpha/` — float→PNG is RGBA16 and false-unpremultiplies as if associated-alpha (closed-form err 0.98 at RGB 0.02 / a=1/255); OpenEXR preserves float RGBA; byte→PNG is straight RGBA8; `EXR color_mode='RGB'` drops alpha -- Attribute domain witness: writing a POINT-domain color attribute and reading it as if it were CORNER (or vice versa) silently shears colors across shared verts — companion to `color-attribute-wheel` +- ~~Attribute domain witness~~ **SHIPPED** as `examples/attribute-domain-shear/` — POINT vs CORNER semantics on an 8-wedge pinwheel with one shared hub: CORNER authoring exact (hub corners disagree by face, err ≤ 1e-6), naive per-wedge POINT loop shears to the last write at hub + ring verts, measured deviation == palette closed form (0.751031); byte-identical on 4.5.11 and 5.1.2; crisp-vs-smeared dual pinwheel still — companion to `color-attribute-wheel` - ~~Light-linking collection witness~~ **SHIPPED** as `examples/light-link-studio/` — the API is `obj.light_linking` on the light OBJECT (`ld.light_linking` is an AttributeError on both versions); linked ratio 4.0x at projected-center luminance samples, unlink raises the decoy 244–251% with 0.0% hero drift in the same two-render check; EEVEE Next honors linking too (5.5x on 4.5.11, 5.9x on 5.1.2), Cycles pinned for deterministic samples - ~~VSE sequences-to-strips witness~~ **SHIPPED** as `examples/vse-cut-list/` — `.sequences` removed on 5.x (4.5 bridges to `.strips`), `new_effect` end kwarg `frame_end=` (4.5) vs `length=` (5.x), `frame_final_*` deprecated in favor of `left_handle`/`right_handle`/`duration`, TRANSFORM effect type removed, GAMMA_CROSS clamps to the source overlap, effect strips consume inputs only when stacked above them, same-scene scene strips render transparent; save/reload round-trip + tiny-render pixel witness - ~~glTF export round-trip witness~~ **SHIPPED** as `examples/gltf-export-roundtrip/` — `export_yup` bakes `(x,y,z)→(x,z,−y)` into vertex data with no node rotation (probed identical on 4.5.11 and 5.1.2), `export_apply` ships the evaluated mesh (one disk vertex per evaluated loop), TEXCOORD_0 is V-flipped on disk, per-triangle material bindings survive; exporter/importer RNA signatures byte-identical between 4.5.11 and 5.1.2 (guarded against future renames); `Mesh.calc_normals()` removal on 5.x surfaced during authoring @@ -127,7 +127,6 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo - Bicycle-wheel radial witness: spokes via radial Array with object offset — exact angular spacing, spoke count, hub/rim concentricity, every evaluated spoke endpoint on its computed rim coordinate - ~~mesh-hygiene-audit witness~~ **SHIPPED** as `examples/mesh-hygiene-audit/` — engine-ingest topology checklist on a stepped street electrical pedestal: no ngons, no loose verts, manifold edges (exactly 2 faces), no zero-area faces (`min_area` printed), positive signed volume, Euler `V−E+F==2` (24/44/22, volume 0.989248); byte-identical on 4.5.11 and 5.1.2; dual-panel dirty vs clean still - UV atlas pack witness — lightmap/unique UV island packing with non-overlap + utilization closed forms (deferred behind hygiene/origin; overlaps `uv-layer-grid` / `triangulate-tangents` / `gltf-export-roundtrip` UV arc) -- Attribute domain witness: writing a POINT-domain color attribute and reading it as if it were CORNER (or vice versa) silently shears colors across shared verts — companion to `color-attribute-wheel` - ~~Gallery framing recomposition worklist~~ **DRAINED**: all four recomposition cases restaged and gated with `check_framing` — depsgraph-export (0.984x/touch→0.812x; cube+dome recentered, aim-based camera), vertex-weight-limit (touch B→0.867y; more frontal camera, lower aim, base in frame), triangulate-tangents (0.334x→0.833y; 2x-class move-in plus Key/Glint trim that also cleared a pre-existing dome blowout), text-version-stamp (0.663x→0.719x; moved in with aim on the stamp center, dead third gone). The seven marginal cases were camera-nudged in the preceding sweep; deviations documented in #108 (damped-track-aim, wave-displace); sky-texture-sun-elevation is a measurement artifact (world subject — report-only if wired). Remaining: nothing on the framing worklist — future examples inherit the gate at authoring time. diff --git a/docs/gallery/assets/attribute-domain-shear-hero.webp b/docs/gallery/assets/attribute-domain-shear-hero.webp new file mode 100644 index 0000000000000000000000000000000000000000..6bf8f32fc8dd73ee60f83c001387f5d45b69dcb0 GIT binary patch literal 17030 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

yZCt?0AO0G4XXUAP&@? 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 + + + + + attribute-domain-shear — Examples — Blender Developer Tools + + + + + + + + + + + + + + + + + +

+
+

attribute-domain-shear

+

POINT vs CORNER color-attribute domains on a shared-vertex pinwheel: CORNER stays exact per face while a naive per-wedge POINT loop shears to the last write at the hub. Crisp petals versus a smeared swirl in one frame; the measured shear equals the palette closed form.

+
+
+ +

Rendered headless by the example itself — click to zoom.

+
witnesses Authoring per-face colors into a POINT-domain attribute overwrites shared vertices once per neighbor; the last write wins and intended colors shear across every shared vertex.
+
+
blender --background --python examples/attribute-domain-shear/attribute_domain_shear.py --
+ +
+
+

A runnable example that witnesses what POINT versus CORNER means on Mesh.color_attributes once the mesh has shared vertices — the domain is not a storage detail, it decides where colors can live. Companion to color-attribute-wheel (which covers color_attributes.new() versus the deprecated alias, CORNER sizing == len(loops), and active_color); this example covers the trap one step later, when AI code knows the API exists but authors per-face colors into a POINT-domain attribute.

+

Pipeline arc neighbors: attribute authoring in color-attribute-wheel, mesh topology gates in mesh-hygiene-audit, tangent-space UV contracts in triangulate-tangents.

+

What it witnesses: a pinwheel of K=8 triangles around one raised hub vertex shared by every wedge (plus a shared outer ring). The contract, all closed form:

+
  • Storage sizes. CORNER attr data == len(loops) == 3K; POINT attr == len(vertices) == K+1.
  • CORNER authoring is exact. The hub corner of wedge i reads palette[i] within 1e-6 — K faces at one vertex may disagree there.
  • POINT naive authoring shears by construction. A per-wedge authoring loop ("paint each wedge its color") rewrites every shared vertex once per neighbor, and the last write wins: the hub reads palette[K-1], ring vert i reads palette[i] — except ring vert 0, which the wrap-around last wedge rewrites to palette[K-1]. The measured mean deviation from intended equals the palette closed form (0.751031) exactly.
+

What each check catches on failure: wedge 3 miscolored in the CORNER pass (exit 4); naive writes reversed, so the hub reads palette[0] (exit 5); a ring vert corrupted, breaking the overwrite-ordering witness (exit 6); a constant palette, collapsing the shear so the probe cannot distinguish naive from correct (exit 7). Sizes wrong for the declared domain (exit 3).

+

Version witness: output is byte-identical on Blender 4.5.11 LTS and 5.1.2 — the color_attributes domain API is stable across both.

+

Render as proof: dual pinwheel from the same closed-form palette the check asserts. CORNER (left) holds eight crisp petals to the hub; naive POINT (right) smears — petal colors bleed across the shared hub and ring verts into a swirl. The broken state is in-frame by design: the right fan *is* the falsification variant. Fully matte petal materials (Specular IOR Level = 0) so the flat color data carries no specular line, per docs/VISUAL-STYLE.md.

+

Run

+
blender --background --python attribute_domain_shear.py --
+blender --background --python attribute_domain_shear.py -- --output shear.png
+blender --background --python attribute_domain_shear.py -- --output shear.png --engine cycles
+

Exits non-zero on failure. The blender-smoke workflow runs the check on Blender 4.5 LTS and 5.1. The --output render path additionally measures framing against the Layer 1 band via examples/gallery_framing.py (exit 10 on violation) before writing the still.

+
+
+

Source

+
+ examples/attribute-domain-shear/attribute_domain_shear.py + View on GitHub → +
+
"""Attribute domain shear — POINT vs CORNER color attributes on shared verts.
+
+Witnesses the domain-semantics contract of `Mesh.color_attributes` that
+AI-generated code trips on after learning `color_attributes.new()` exists:
+the DOMAIN chooses where colors live. A `CORNER`-domain attribute stores one
+color per loop (face-corner), so the K corners of one shared vertex can each
+carry their own face's color. A `POINT`-domain attribute stores one color per
+vertex, so a naive per-face authoring loop — "paint every wedge its own
+color" — overwrites the shared vertices once per neighbor and the LAST write
+wins: intended per-face colors shear across every shared vertex. Companion
+to `color-attribute-wheel` (which covers CORNER sizing == loops and
+`active_color`): this example covers what the domains *mean* on a fan whose
+entire point is one hub vertex shared by every wedge.
+
+Check (all closed form, nothing captured from a prior run):
+
+1. Storage sizes: CORNER attr data == len(loops) == 3*K; POINT == K+1 verts.
+2. CORNER authoring is exact: the hub corner of wedge i reads palette[i].
+3. POINT naive authoring shears by construction: the hub reads palette[K-1]
+   (last write wins), EVERY wedge's hub-side loop reads that same color, and
+   outer ring vert i reads palette[i] (overwritten by wedge i after wedge
+   i-1 wrote it) — the measured mean deviation from intended equals the
+   closed-form shear computed from the palette.
+
+By default it runs only the correctness check (no render) — the CI smoke
+check. Pass --output to also render a still:
+
+    blender --background --python attribute_domain_shear.py --                 # check only
+    blender --background --python attribute_domain_shear.py -- --output a.png  # + render
+"""
+import bpy, bmesh, sys, os, math, argparse, colorsys
+
+# Shared Layer 1 framing measurement (render path only) — see gallery_framing.py
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir))
+sys.dont_write_bytecode = True  # keep examples/__pycache__ out of the repo tree
+import gallery_framing
+
+K = 8                 # pinwheel wedges; hub vertex is shared by all K
+HUB_Z = 0.55          # raised hub: folded-paper pinwheel, not a flat disc
+RING_R = 1.15
+ATTR_C = "PinCorner"
+ATTR_P = "PinPoint"
+COLOR_EPS = 1e-6
+
+
+def eevee_engine_id():
+    return "BLENDER_EEVEE" if bpy.app.version >= (5, 0, 0) else "BLENDER_EEVEE_NEXT"
+
+
+def palette(k=K):
+    """Closed-form wedge hues: saturated HSV wheel in linear-ish floats."""
+    out = []
+    for i in range(k):
+        r, g, b = colorsys.hsv_to_rgb(i / k, 0.82, 0.95)
+        out.append((r, g, b, 1.0))
+    return out
+
+
+def closed_form_shear(pal):
+    """Mean per-wedge |palette[i] - palette[K-1]| over RGB — the exact shear a
+    last-write-wins hub produces. Derived from the palette, never measured."""
+    last = pal[-1]
+    return sum(
+        math.sqrt(sum((pal[i][c] - last[c]) ** 2 for c in range(3)))
+        for i in range(len(pal))
+    ) / len(pal)
+
+
+def build_fan():
+    """K triangles around one raised hub vertex; outer ring alternates fold
+    height so petals read as folded paper under the key light."""
+    me = bpy.data.meshes.new("Pinwheel")
+    bm = bmesh.new()
+    try:
+        hub = bm.verts.new((0.0, 0.0, HUB_Z))
+        ring = []
+        for i in range(K):
+            a = 2.0 * math.pi * i / K
+            fold = 0.14 if i % 2 else 0.0
+            ring.append(bm.verts.new((RING_R * math.cos(a), RING_R * math.sin(a), fold)))
+        for i in range(K):
+            bm.faces.new((hub, ring[i], ring[(i + 1) % K]))
+        bm.to_mesh(me)
+    finally:
+        bm.free()
+    return me
+
+
+def assign_corner(me, pal):
+    """Correct path: CORNER domain, one exact wedge color per loop."""
+    attr = me.color_attributes.new(ATTR_C, type='FLOAT_COLOR', domain='CORNER')
+    colors = [0.0] * (len(me.loops) * 4)
+    for poly in me.polygons:
+        for li in poly.loop_indices:
+            colors[li * 4: li * 4 + 4] = pal[poly.index]
+    attr.data.foreach_set("color", colors)
+    me.color_attributes.active_color = attr
+    return attr
+
+
+def assign_point_naive(me, pal):
+    """The AI mistake: author per-wedge colors into a POINT-domain attribute.
+    Every wedge rewrites the shared hub (and its leading ring vert), so the
+    last wedge wins — colors shear across every shared vertex."""
+    attr = me.color_attributes.new(ATTR_P, type='FLOAT_COLOR', domain='POINT')
+    hub_index = 0  # build_fan creates the hub first
+    for i in range(K):
+        # naive per-wedge pass: set the hub and both ring verts to palette[i]
+        attr.data[hub_index].color = pal[i]
+        attr.data[1 + i].color = pal[i]
+        attr.data[1 + (i + 1) % K].color = pal[i]
+    me.color_attributes.active_color = attr
+    return attr
+
+
+def check():
+    pal = palette()
+    expect_shear = closed_form_shear(pal)
+    print(f"palette K={K} closed_form_shear={expect_shear:.6f}")
+
+    # --- CORNER: exact authoring ---
+    me_c = build_fan()
+    attr_c = assign_corner(me_c, pal)
+    if len(attr_c.data) != len(me_c.loops) or len(me_c.loops) != 3 * K:
+        print(f"ERROR: CORNER attr size {len(attr_c.data)} != loops {len(me_c.loops)}",
+              file=sys.stderr)
+        return 3
+    hub_loop_err = 0.0
+    for poly in me_c.polygons:
+        got = attr_c.data[poly.loop_indices[0]].color  # loop 0 of each tri is the hub
+        hub_loop_err = max(hub_loop_err,
+                           max(abs(got[c] - pal[poly.index][c]) for c in range(4)))
+    print(f"corner_hub_max_err={hub_loop_err:.3e} (must be <= {COLOR_EPS})")
+    if hub_loop_err > COLOR_EPS:
+        print("ERROR: CORNER hub corners do not carry their wedge's exact color — "
+              "per-face color at a shared vertex failed", file=sys.stderr)
+        return 4
+
+    # --- POINT: the shear, measured against the closed form ---
+    me_p = build_fan()
+    attr_p = assign_point_naive(me_p, pal)
+    if len(attr_p.data) != len(me_p.vertices) or len(me_p.vertices) != K + 1:
+        print(f"ERROR: POINT attr size {len(attr_p.data)} != verts {len(me_p.vertices)}",
+              file=sys.stderr)
+        return 3
+    hub_got = attr_p.data[0].color
+    hub_err = max(abs(hub_got[c] - pal[K - 1][c]) for c in range(4))
+    if hub_err > COLOR_EPS:
+        print(f"ERROR: hub reads {tuple(round(c,4) for c in hub_got)} != last-write "
+              f"palette[{K-1}] — last-write-wins contract broken", file=sys.stderr)
+        return 5
+    # Every wedge's hub-side loop reads the same shared color: sample the POINT
+    # value at the hub through each face's hub loop — one value, K faces.
+    # Outer ring vert i reads pal[i] — written by wedge i after wedge i-1 —
+    # EXCEPT vert 0, which the wrap-around last wedge rewrites to pal[K-1].
+    ring_err = 0.0
+    for i in range(K):
+        want = pal[i] if i > 0 else pal[K - 1]
+        got = attr_p.data[1 + i].color
+        ring_err = max(ring_err, max(abs(got[c] - want[c]) for c in range(4)))
+    print(f"point_ring_max_err={ring_err:.3e} hub=last_write_ok")
+    if ring_err > COLOR_EPS:
+        print("ERROR: outer ring verts do not read their last write — the "
+              "overwrite-ordering witness failed", file=sys.stderr)
+        return 6
+    shear = sum(
+        math.sqrt(sum((pal[i][c] - hub_got[c]) ** 2 for c in range(3)))
+        for i in range(K)
+    ) / K
+    print(f"point_shear measured={shear:.6f} closed_form={expect_shear:.6f}")
+    if abs(shear - expect_shear) > 1e-6:
+        print("ERROR: measured shear does not match the palette closed form — "
+              "the domain mistake is not being witnessed", file=sys.stderr)
+        return 7
+    if shear < 0.05:
+        print("ERROR: shear is ~0 — the probe cannot distinguish naive POINT "
+              "authoring from correct authoring", file=sys.stderr)
+        return 7
+
+    print(f"attribute-domain-shear OK corner_exact point_shear={shear:.6f} "
+          f"(last of {K} writes wins at 1 shared hub + {K} shared ring verts)")
+    return 0
+
+
+def make_attr_material(name, attr_name, matte=True):
+    mat = bpy.data.materials.new(name)
+    mat.use_nodes = True
+    nt = mat.node_tree
+    bsdf = nt.nodes["Principled BSDF"]
+    if matte:
+        # flat color data: no specular line across the petals (VISUAL-STYLE)
+        spec = bsdf.inputs.get("Specular IOR Level")
+        if spec is not None:
+            spec.default_value = 0.0
+    bsdf.inputs["Roughness"].default_value = 0.6
+    node = nt.nodes.new("ShaderNodeAttribute")
+    node.attribute_type = "GEOMETRY"
+    node.attribute_name = attr_name
+    nt.links.new(node.outputs["Color"], bsdf.inputs["Base Color"])
+    return mat
+
+
+def make_material(name, rgb, rough=0.45, metallic=0.35, emit=None, estr=0.0):
+    mat = bpy.data.materials.new(name)
+    mat.use_nodes = True
+    b = mat.node_tree.nodes["Principled BSDF"]
+    b.inputs["Base Color"].default_value = (*rgb, 1.0)
+    b.inputs["Roughness"].default_value = rough
+    b.inputs["Metallic"].default_value = metallic
+    if emit is not None:
+        sock = b.inputs.get("Emission Color") or b.inputs["Emission"]
+        sock.default_value = (*emit, 1.0)
+        b.inputs["Emission Strength"].default_value = estr
+    return mat
+
+
+def _pinwheel_obj(sc, name, me, loc, rot_z):
+    ob = bpy.data.objects.new(name, me)
+    ob.location = loc
+    ob.rotation_euler = (math.radians(12), 0.0, rot_z)
+    sc.collection.objects.link(ob)
+    # stem + hub cap: a garden pinwheel on a stick, not a floating disc
+    stem_me = bpy.data.meshes.new(name + "Stem")
+    bm = bmesh.new()
+    try:
+        bmesh.ops.create_cone(bm, cap_ends=True, segments=10, radius1=0.05,
+                              radius2=0.06, depth=1.35)
+        bmesh.ops.translate(bm, vec=(0.0, 0.0, -0.72), verts=bm.verts)
+        bm.to_mesh(stem_me)
+    finally:
+        bm.free()
+    stem_me.materials.append(make_material("StemMetal", (0.16, 0.17, 0.18),
+                                           rough=0.4, metallic=0.8))
+    stem = bpy.data.objects.new(name + "Stem", stem_me)
+    stem.location = loc
+    stem.rotation_euler = (math.radians(12), 0.0, rot_z)
+    sc.collection.objects.link(stem)
+    cap_me = bpy.data.meshes.new(name + "Cap")
+    bm = bmesh.new()
+    try:
+        bmesh.ops.create_uvsphere(bm, u_segments=12, v_segments=8, radius=0.075)
+        bmesh.ops.translate(bm, vec=(0.0, 0.0, HUB_Z + 0.02), verts=bm.verts)
+        bm.to_mesh(cap_me)
+    finally:
+        bm.free()
+    cap_me.materials.append(make_material("CapMetal", (0.09, 0.09, 0.095),
+                                          rough=0.35, metallic=0.85))
+    cap = bpy.data.objects.new(name + "Cap", cap_me)
+    cap.location = loc
+    cap.rotation_euler = (math.radians(12), 0.0, rot_z)
+    sc.collection.objects.link(cap)
+    return ob
+
+
+def placard(sc, text, loc, size=0.18):
+    cu = bpy.data.curves.new(text, "FONT")
+    cu.body = text
+    cu.size = size
+    cu.align_x = "CENTER"
+    ob = bpy.data.objects.new(text, cu)
+    ob.location = loc
+    sc.collection.objects.link(ob)
+    ob.data.materials.append(make_material("Label", (0.9, 0.9, 0.92),
+                                           rough=0.6, metallic=0.0))
+    return ob
+
+
+def build_studio(sc):
+    floor_me = bpy.data.meshes.new("Floor")
+    bm = bmesh.new()
+    try:
+        bmesh.ops.create_grid(bm, x_segments=1, y_segments=1, size=30.0)
+        bm.to_mesh(floor_me)
+    finally:
+        bm.free()
+    fmat = make_material("Studio", (0.03, 0.032, 0.037), rough=0.7, metallic=0.0)
+    floor_me.materials.append(fmat)
+    floor = bpy.data.objects.new("Floor", floor_me)
+    sc.collection.objects.link(floor)
+    wall = bpy.data.objects.new("Wall", floor_me.copy())
+    wall.location = (0.0, 9.0, 0.0)
+    wall.rotation_euler = (math.radians(90), 0.0, 0.0)
+    sc.collection.objects.link(wall)
+
+    world = bpy.data.worlds.new("World")
+    world.use_nodes = True
+    world.node_tree.nodes["Background"].inputs["Color"].default_value = (
+        0.02, 0.021, 0.025, 1.0,
+    )
+    sc.world = world
+
+    def light(name, loc, energy, size, col, rot):
+        ld = bpy.data.lights.new(name, "AREA")
+        ld.energy = energy
+        ld.size = size
+        ld.color = col
+        ob = bpy.data.objects.new(name, ld)
+        ob.location = loc
+        ob.rotation_euler = tuple(math.radians(a) for a in rot)
+        sc.collection.objects.link(ob)
+
+    light("Key", (-3.5, -4.5, 5.5), 480.0, 4.5, (1.0, 0.96, 0.9), (48, 0, -35))
+    light("Fill", (5.0, -3.5, 2.5), 120.0, 9.0, (0.75, 0.85, 1.0), (65, 0, 50))
+    light("Rim", (1.5, 4.5, 3.5), 280.0, 3.0, (0.6, 0.78, 1.0), (-55, 0, 170))
+    light("Wedge", (2.5, 5.5, 4.0), 400.0, 6.0, (1.0, 0.72, 0.42), (-68, 0, 190))
+    return floor, wall
+
+
+def render_still(path, engine):
+    """Dual pinwheel: CORNER (crisp petals to the hub) vs naive POINT (last
+    write smears the shared hub + ring verts). Colors come from the same
+    closed-form palette the check asserts."""
+    bpy.ops.wm.read_factory_settings(use_empty=True)
+    sc = bpy.context.scene
+    pal = palette()
+
+    me_c = build_fan()
+    assign_corner(me_c, pal)
+    me_c.materials.append(make_attr_material("MatCorner", ATTR_C))
+    left = _pinwheel_obj(sc, "Corner", me_c, (-1.15, 0.0, 1.35), math.radians(-8))
+
+    me_p = build_fan()
+    assign_point_naive(me_p, pal)
+    me_p.materials.append(make_attr_material("MatPoint", ATTR_P))
+    right = _pinwheel_obj(sc, "Point", me_p, (1.15, 0.0, 1.35), math.radians(8))
+
+    p_corner = placard(sc, "CORNER", (-1.15, -1.05, 0.02), size=0.13)
+    p_point = placard(sc, "POINT — last write wins", (1.15, -1.05, 0.02), size=0.10)
+
+    floor, wall = build_studio(sc)
+
+    cam_data = bpy.data.cameras.new("Cam")
+    cam_data.lens = 48.0
+    cam = bpy.data.objects.new("Cam", cam_data)
+    cam.location = (0.0, -6.4, 4.6)
+    sc.collection.objects.link(cam)
+    aim = bpy.data.objects.new("Aim", None)
+    aim.location = (0.0, 0.0, 0.8)
+    sc.collection.objects.link(aim)
+    tr = cam.constraints.new("TRACK_TO")
+    tr.target = aim
+    tr.track_axis = "TRACK_NEGATIVE_Z"
+    tr.up_axis = "UP_Y"
+    sc.camera = cam
+
+    sc.render.engine = "CYCLES" if engine == "cycles" else eevee_engine_id()
+    if engine == "cycles":
+        sc.cycles.device = "CPU"
+        sc.cycles.samples = 64
+        sc.cycles.use_denoising = True
+    else:
+        try:
+            sc.eevee.taa_render_samples = 64
+        except AttributeError:
+            pass
+    sc.render.resolution_x = 1280
+    sc.render.resolution_y = 720
+    sc.render.image_settings.file_format = "PNG"
+    sc.render.filepath = path
+    # Standard, always: AgX would bend the closed-form palette the check asserts
+    sc.view_settings.view_transform = "Standard"
+    # Layer 1 framing gate (silhouette matte) — exit 10 on violation.
+    hero = [left, right]
+    elements = hero + [p_corner, p_point]
+    fcode = gallery_framing.check_framing(
+        sc, cam,
+        hero=hero,
+        elements=elements,
+        stage=[floor, wall],
+    )
+    if fcode:
+        return fcode
+    bpy.ops.render.render(write_still=True)
+    if not (os.path.exists(path) and os.path.getsize(path) > 0):
+        print("ERROR: render produced no file", file=sys.stderr)
+        return 9
+    return 0
+
+
+def main():
+    argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
+    p = argparse.ArgumentParser()
+    p.add_argument("--output", default=None, help="optional: render a still PNG here")
+    p.add_argument("--engine", default="eevee", choices=("eevee", "cycles"),
+                   help="render engine for --output (cycles for GPU-less hosts)")
+    args = p.parse_args(argv)
+
+    print(f"binary version: {bpy.app.version} ({bpy.app.version_string})")
+    bpy.ops.wm.read_factory_settings(use_empty=True)
+    code = check()
+    if code:
+        return code
+
+    if args.output:
+        rcode = render_still(os.path.abspath(args.output), args.engine)
+        if rcode:
+            return rcode
+        print(f"rendered still {args.output}")
+
+    print("attribute-domain-shear OK")
+    return 0
+
+
+if __name__ == "__main__":
+    try:
+        sys.exit(main())
+    except Exception as e:
+        import traceback
+
+        traceback.print_exc()
+        print(f"FATAL: {e}", file=sys.stderr)
+        sys.exit(1)
+
+
+
+ +
+
+ generated from examples/gallery.json + CC-BY-NC-ND-4.0 + exit 0 +
+
+ + + diff --git a/docs/gallery/contact-sheets/attribute-domain-shear-contact-sheet.webp b/docs/gallery/contact-sheets/attribute-domain-shear-contact-sheet.webp new file mode 100644 index 0000000000000000000000000000000000000000..3cec318726b94fbd0043393a390e15d74f411bb3 GIT binary patch literal 39768 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 View example +
+ + attribute-domain-shear — POINT vs CORNER color-attribute domains on a shared-vertex pinwheel: CORNER stays exact per face while a naive per-wedge POINT loop shears to the last write at… + +
+

attribute-domain-shear

+

POINT vs CORNER color-attribute domains on a shared-vertex pinwheel: CORNER stays exact per face while a naive per-wedge POINT loop shears to the last write at the hub. Crisp petals versus a smeared swirl in one frame; the measured shear equals the palette closed form.

+

witnesses Authoring per-face colors into a POINT-domain attribute overwrites shared vertices once per neighbor; the last write wins and intended colors shear across every shared vertex.

+ View example +
+