From 864fbf7a3cc85cf86fab9f9c3045c782bd86d05f Mon Sep 17 00:00:00 2001 From: fOuttaMyPaint Date: Wed, 9 Sep 2026 16:32:57 -0400 Subject: [PATCH 1/3] feat: target Blender 5.2 LTS and witness NodesModifier input writes CI was blind to the 5.2 removal of dict assignment on Geometry Nodes modifiers because no example asserted the write. Teach both sides of the split and fail the check if the value does not land. Signed-off-by: fOuttaMyPaint Co-authored-by: Cursor --- .cursor-plugin/plugin.json | 1 + .github/workflows/blender-smoke.yml | 24 +- AGENTS.md | 24 +- CLAUDE.md | 19 +- CONTRIBUTING.md | 2 +- README.md | 34 +- ROADMAP.md | 7 +- docs/gallery/armature-bend/index.html | 2 +- .../assets/gn-modifier-inputs-hero.webp | Bin 0 -> 9924 bytes .../gallery/attribute-domain-shear/index.html | 2 +- docs/gallery/bmesh-gear/index.html | 2 +- docs/gallery/car-mirror-symmetry/index.html | 2 +- docs/gallery/collision-hull-proxy/index.html | 2 +- docs/gallery/color-attribute-wheel/index.html | 2 +- docs/gallery/compositor-glare/index.html | 2 +- .../gn-modifier-inputs-contact-sheet.webp | Bin 0 -> 79300 bytes docs/gallery/curve-bevel-arc/index.html | 2 +- docs/gallery/custom-normals-shade/index.html | 2 +- docs/gallery/damped-track-aim/index.html | 2 +- docs/gallery/degenerate-bevel-weld/index.html | 2 +- docs/gallery/depsgraph-export/index.html | 2 +- docs/gallery/driver-wave/index.html | 2 +- docs/gallery/gltf-export-roundtrip/index.html | 2 +- docs/gallery/gltf-skin-roundtrip/index.html | 2 +- docs/gallery/gn-instance-grid/index.html | 2 +- docs/gallery/gn-modifier-inputs/index.html | 721 ++++++++++++++++++ docs/gallery/gn-sdf-remesh/index.html | 2 +- docs/gallery/gp-lineart-contour/index.html | 2 +- docs/gallery/grease-pencil-rosette/index.html | 2 +- docs/gallery/image-pixels-testcard/index.html | 2 +- docs/gallery/index.html | 21 +- docs/gallery/light-link-studio/index.html | 2 +- docs/gallery/lightmap-uv-channel/index.html | 2 +- docs/gallery/lod-decimate-chain/index.html | 2 +- docs/gallery/mesh-hygiene-audit/index.html | 2 +- docs/gallery/modular-kit-snap/index.html | 2 +- docs/gallery/parent-inverse-orrery/index.html | 2 +- docs/gallery/png-exr-alpha/index.html | 2 +- docs/gallery/prop-origin-transform/index.html | 2 +- docs/gallery/shader-node-group/index.html | 2 +- docs/gallery/shape-key-blend/index.html | 2 +- .../sky-texture-sun-elevation/index.html | 2 +- docs/gallery/soccer-ball-goldberg/index.html | 2 +- docs/gallery/socket-attach-points/index.html | 2 +- docs/gallery/swatch-grid/index.html | 2 +- docs/gallery/temp-override-join/index.html | 2 +- docs/gallery/text-version-stamp/index.html | 2 +- docs/gallery/triangulate-tangents/index.html | 2 +- docs/gallery/turntable/index.html | 2 +- docs/gallery/uv-layer-grid/index.html | 2 +- docs/gallery/vertex-color-ao/index.html | 2 +- docs/gallery/vertex-weight-limit/index.html | 2 +- docs/gallery/vse-cut-list/index.html | 2 +- docs/gallery/vse-gamma-cross/index.html | 2 +- docs/gallery/wave-displace/index.html | 2 +- docs/new-example-prompt.md | 15 +- docs/technical-audit.md | 11 +- examples/armature-bend/README.md | 2 +- examples/attribute-domain-shear/README.md | 2 +- examples/bmesh-gear/README.md | 2 +- examples/car-mirror-symmetry/README.md | 2 +- examples/collision-hull-proxy/README.md | 2 +- examples/color-attribute-wheel/README.md | 2 +- examples/compositor-glare/README.md | 2 +- examples/curve-bevel-arc/README.md | 2 +- examples/custom-normals-shade/README.md | 2 +- examples/damped-track-aim/README.md | 2 +- examples/degenerate-bevel-weld/README.md | 2 +- examples/depsgraph-export/README.md | 2 +- examples/driver-wave/README.md | 2 +- examples/gallery.json | 14 +- examples/gltf-export-roundtrip/README.md | 2 +- examples/gltf-skin-roundtrip/README.md | 2 +- examples/gn-instance-grid/README.md | 4 +- examples/gn-modifier-inputs/README.md | 45 ++ .../gn-modifier-inputs/gn_modifier_inputs.py | 396 ++++++++++ examples/gn-modifier-inputs/preview.webp | Bin 0 -> 9338 bytes examples/gn-sdf-remesh/README.md | 4 +- examples/gp-lineart-contour/README.md | 2 +- examples/grease-pencil-rosette/README.md | 2 +- examples/image-pixels-testcard/README.md | 2 +- examples/light-link-studio/README.md | 2 +- examples/lightmap-uv-channel/README.md | 2 +- examples/lod-decimate-chain/README.md | 2 +- examples/mesh-hygiene-audit/README.md | 2 +- examples/modular-kit-snap/README.md | 2 +- examples/parent-inverse-orrery/README.md | 2 +- examples/png-exr-alpha/README.md | 2 +- examples/prop-origin-transform/README.md | 2 +- examples/shader-node-group/README.md | 2 +- examples/shape-key-blend/README.md | 2 +- examples/sky-texture-sun-elevation/README.md | 2 +- examples/soccer-ball-goldberg/README.md | 2 +- examples/socket-attach-points/README.md | 2 +- examples/swatch-grid/README.md | 2 +- examples/temp-override-join/README.md | 4 +- examples/text-version-stamp/README.md | 2 +- examples/triangulate-tangents/README.md | 2 +- examples/turntable/README.md | 2 +- examples/uv-layer-grid/README.md | 2 +- examples/vertex-color-ao/README.md | 2 +- examples/vertex-weight-limit/README.md | 2 +- examples/vse-cut-list/README.md | 2 +- examples/vse-gamma-cross/README.md | 2 +- examples/wave-displace/README.md | 2 +- skills/addon-scaffolding/SKILL.md | 2 +- skills/bl-info-migration/SKILL.md | 2 +- skills/custom-properties/SKILL.md | 2 +- skills/depsgraph-and-evaluated-data/SKILL.md | 2 +- skills/geometry-nodes-python/SKILL.md | 24 +- skills/mesh-editing-and-bmesh/SKILL.md | 2 +- skills/operators/SKILL.md | 2 +- .../procedural-materials-and-shaders/SKILL.md | 2 +- skills/ui-panels/SKILL.md | 2 +- 114 files changed, 1390 insertions(+), 166 deletions(-) create mode 100644 docs/gallery/assets/gn-modifier-inputs-hero.webp create mode 100644 docs/gallery/contact-sheets/gn-modifier-inputs-contact-sheet.webp create mode 100644 docs/gallery/gn-modifier-inputs/index.html create mode 100644 examples/gn-modifier-inputs/README.md create mode 100644 examples/gn-modifier-inputs/gn_modifier_inputs.py create mode 100644 examples/gn-modifier-inputs/preview.webp diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 684e8f9..9441059 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -75,6 +75,7 @@ "examples/gltf-export-roundtrip", "examples/gltf-skin-roundtrip", "examples/gn-instance-grid", + "examples/gn-modifier-inputs", "examples/gn-sdf-remesh", "examples/gp-lineart-contour", "examples/grease-pencil-rosette", diff --git a/.github/workflows/blender-smoke.yml b/.github/workflows/blender-smoke.yml index f698d17..3754b6b 100644 --- a/.github/workflows/blender-smoke.yml +++ b/.github/workflows/blender-smoke.yml @@ -1,7 +1,8 @@ name: Blender Smoke Test # Executes the snippets' and skills' headline examples inside REAL Blender, headless, -# on the current stable (5.1.x) and the active LTS (4.5.x), and fails on any error or +# on the current stable (5.2.x LTS) and the fallback LTS (4.5.x) on every PR, +# plus 5.1.x on the weekly cron, and fails on any error or # empty-output assertion. py_compile (in validate.yml) cannot catch API-level regressions # like the EEVEE-id inversion, the slotted-actions boundary, the driver TypeError, or the # dead SDF link -- this gate runs the code so those surface in CI, not in users' files. @@ -24,13 +25,11 @@ jobs: smoke: name: Blender ${{ matrix.series }} smoke runs-on: ubuntu-latest - timeout-minutes: 30 + timeout-minutes: 45 strategy: fail-fast: false matrix: - include: - - series: "5.1" # current stable - - series: "4.5" # active LTS + series: ${{ github.event_name == 'schedule' && fromJSON('["5.2","5.1","4.5"]') || fromJSON('["5.2","4.5"]') }} steps: - uses: actions/checkout@v7 @@ -78,6 +77,12 @@ jobs: "$BLENDER" --version | head -1 | grep -q "Blender ${{ matrix.series }}\." \ || { echo "::error::version does not match series ${{ matrix.series }}"; exit 1; } + - name: Canary — 5.2 matrix must actually execute + if: matrix.series == '5.2' + run: | + echo "::error::intentional 5.2 canary; revert after the red run" + exit 1 + - name: Run in-Blender smoke driver run: | set -euo pipefail @@ -214,6 +219,15 @@ jobs: xvfb-run -a "$BLENDER" --background \ --python examples/gn-instance-grid/gn_instance_grid.py -- + - name: Shipped example - GN modifier inputs (5.1 dict vs 5.2 RNA) + run: | + set -euo pipefail + # Frame-independent check only (no render): one GN tree, three modifier + # copies; writes Scale 1/2/3 via version-appropriate path; asserts + # readback and evaluated Z-extent match. Exits non-zero on failure. + xvfb-run -a "$BLENDER" --background \ + --python examples/gn-modifier-inputs/gn_modifier_inputs.py -- + - name: Shipped example - shape-key blend (data API + evaluated mesh) run: | set -euo pipefail diff --git a/AGENTS.md b/AGENTS.md index 2afae39..4d98124 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,13 +14,13 @@ repeats the other. ## Repository overview Skills, rules, snippets, starter templates, and runnable smoke-gated examples -for Blender Python development. The repo targets **Blender 5.1** (current -stable) with a **Blender 4.5 LTS** fallback. There is no MCP server. It ships +for Blender Python development. The repo targets **Blender 5.2 LTS** (current +stable) with a **Blender 4.5 LTS** fallback. **Blender 5.1** is prior stable. There is no MCP server. It ships 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 44 +The content base is 12 skills, 6 rules, 2 templates, 17 snippets, and 45 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// # 40 runnable smoke-gated examples (+ gallery.json) + examples// # 45 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) @@ -76,7 +76,7 @@ Blender-Developer-Tools/ - **Smoke jobs do not re-run on the merge SHA.** `blender-smoke.yml` triggers on `pull_request` (plus a weekly schedule and manual dispatch) — there is no `push` trigger. The correct post-merge evidence for example changes is: - both Blender smoke jobs (4.5 LTS and 5.1) passed on the PR head SHA that + both Blender smoke jobs (5.2 LTS and 4.5 LTS) passed on the PR head SHA that became the sole squash-merged commit, with the actual binary versions confirmed in the job logs. - **Post-merge, verify green on `main`:** Release (`release.yml`), Validate @@ -97,12 +97,13 @@ Blender-Developer-Tools/ ## Blender version targeting -- Primary: **Blender 5.1.x** (current stable). All examples assume 5.1 +- Primary: **Blender 5.2 LTS** (current stable). All examples assume 5.2 unless otherwise stated. +- Prior stable: **Blender 5.1**. Skills document 5.1-only contracts where they + still matter; weekly smoke keeps a 5.1 leg. - Fallback: **Blender 4.5 LTS**. Skills and the extension template note 4.5 compatibility where it matters (slotted actions bridge, property delete, - manifest fields). -- Future: a 5.2 LTS sweep is planned for July 2026 (see `ROADMAP.md`). + manifest fields, NodesModifier dict inputs). When a 4.x and 5.x API genuinely diverge, skills must show both code paths, not just the 5.x one. The `slotted-actions-animation` skill is the load-bearing @@ -155,8 +156,8 @@ way, and a one-paragraph rationale. 30 to 80 lines is the right size. and the manifest `version` must equal `VERSION`. The release pipeline owns the manifest `version` line (see `release.yml` below) — never hand-edit it. - `blender-smoke.yml` executes every shipped example (check-only, no render) - plus snippet/template smoke tests inside REAL headless Blender, on both - 4.5 LTS and 5.1, on every PR and a weekly schedule. A new example is not + plus snippet/template smoke tests inside REAL headless Blender, on + 5.2 LTS and 4.5 LTS for every PR (5.1 on the weekly cron). A new example is not shipped until it has a step here. - `drift-check.yml` consumes `Developer-Tools-Directory/.github/actions/ drift-check@v1.15` to enforce ecosystem standards-version markers. @@ -176,7 +177,8 @@ way, and a one-paragraph rationale. 30 to 80 lines is the right size. ## Where to look for canonical references -- Blender 5.1 Python API: https://docs.blender.org/api/current/ +- Blender 5.2 LTS Python API: https://docs.blender.org/api/current/ +- Blender 5.1 Python API: https://docs.blender.org/api/5.1/ - Blender 4.5 LTS Python API: https://docs.blender.org/api/4.5/ - Extensions Platform reference: https://docs.blender.org/manual/en/latest/advanced/extensions/index.html - Release notes (`developer.blender.org`): https://developer.blender.org/ diff --git a/CLAUDE.md b/CLAUDE.md index 7647c72..a703c1f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -The **Blender Developer Tools** repository is at **v0.46.1**. It packages skills, rules, snippets, starter templates, and runnable smoke-gated examples for Blender Python development with Cursor and Claude Code. Coverage targets **Blender 5.1** (current stable) with **Blender 4.5 LTS** fallback. There is no MCP server; content is consumed directly by the AI when working in Blender add-on or scripting projects. +The **Blender Developer Tools** repository is at **v0.46.1**. It packages skills, rules, snippets, starter templates, and runnable smoke-gated examples for Blender Python development with Cursor and Claude Code. Coverage targets **Blender 5.2 LTS** (current stable) with **Blender 4.5 LTS** fallback. **Blender 5.1** is prior stable. There is no MCP server; content is consumed directly by the AI when working in Blender add-on or scripting projects. **Version:** 0.46.1 **License:** CC-BY-NC-ND-4.0 @@ -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, 44 total (+ gallery.json) +examples// - Runnable smoke-gated examples, 45 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 (44) +## Examples (45) 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 of them run headless on Blender 4.5 LTS and 5.1 in `blender-smoke.yml`; +`--output`. All of them run headless on Blender 5.2 LTS and 4.5 LTS in `blender-smoke.yml` (5.1 on the weekly cron); 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 @@ -100,8 +100,8 @@ cross-example import mechanism (see its docstring). ## Blender Runtime Discovery -- Local Blender binaries: check `.scratch/` at the repo root **first** — some machines have no system Blender install, and a prior agent run downloads official releases there (e.g. `.scratch/5.1/blender-5.1.x-.../blender[.exe]`). Then check system installs. Do not probe blindly; locate the binary, run it, and state the **exact binary path and the version the binary itself reports** in every report. -- **5.1 is the local check version. 4.5 LTS is exercised by CI when unavailable locally.** 4.4 is not a substitute for 4.5 and must never be reported as 4.5. +- Local Blender binaries: check `.scratch/` at the repo root **first** — some machines have no system Blender install, and a prior agent run downloads official releases there (e.g. `.scratch/5.2/blender-5.2.x-.../blender[.exe]`). Then check system installs. Do not probe blindly; locate the binary, run it, and state the **exact binary path and the version the binary itself reports** in every report. +- **5.2 LTS is the local check version. 4.5 LTS is exercised by CI when unavailable locally.** 4.4 is not a substitute for 4.5 and must never be reported as 4.5. - **CI floats within each series:** `blender-smoke.yml` resolves the highest published point release at run time (`sort -V | tail -1` on the download listing), so a local 4.5.x may lag CI — its 4.5 job ran 4.5.12 LTS on PR #107 while local `.scratch/` held 4.5.11. Either way, state the exact version the binary reports. - If `.scratch/` lacks a needed version, download an official release from download.blender.org into it. `.scratch` is gitignored. - In scripts, version-branch on the `bpy.app.version` tuple, never on `bpy.app.version_string` — it reads e.g. `"4.5.11 LTS"`, not bare semver. @@ -127,7 +127,7 @@ Stage with **explicit paths only** — never `git add -A` or `git add .`. Cursor - **Asset-sheet gate (asset-type examples — game props/kits):** composite the hero asset rendered alone (neutral three-quarter view, plain studio lighting, no staging tricks, no labels, no comparison props) beside the pinned asset-quality reference set — currently `collision-hull-proxy`, `custom-normals-shade`, `vertex-weight-limit`, `lod-decimate-chain` — rendered the same way; commit under `docs/gallery/asset-sheets/`, link it in the PR body, and report a verdict. The asset ships only if it is not identifiable as the least-designed object in that lineup — a strong scene can carry a weak model; this gate removes the scene. **This list is the canonical home of the reference set** — update it here when a new asset outclasses a member. The measurable floors behind the gate (naming, material variation, edge treatment) live in `examples/gallery_asset_quality.py` — render path only, same call pattern as `gallery_framing`, exit 11 on violation — with the calibration table and dropped-floor evidence in `docs/VISUAL-STYLE.md` § Asset quality. - **Falsification:** every check must be proven to fail once — break the contract, observe the non-zero exit, restore — with the probe and the measured error reported in the PR body. An assertion that cannot fail witnesses nothing. - **After gallery regeneration** (`python scripts/build_gallery.py`), read the **generated HTML** character by character — the `` text and witnesses callouts in `docs/gallery/index.html` and `docs/gallery//index.html` — not just `examples/gallery.json`. Precedent: the `teaches.split(".")[0]` bug truncated 14/21 card alts at dotted API paths like `bmesh.ops` while the source JSON looked fine (fixed in PR #68). -- **Playwright gallery captures:** gallery `` tags lazy-load, so force them first (`document.querySelectorAll('img').forEach(i => i.loading = 'eager')`, then wait). **Scroll the target card into view and take a viewport capture** — `scrollIntoView({block:'center', behavior:'instant'})`, short wait, `browser_take_screenshot` with `fullPage` omitted. A `fullPage` capture is NOT a workaround: on a tall gallery page it renders every card image blank even when the images are verified loaded (`complete === true`, `naturalWidth === 1280`, `opacity === 1`) — measured on the 44-card grid at 1425x4516. Verify load state via `browser_evaluate` rather than trusting the pixels. +- **Playwright gallery captures:** gallery `` tags lazy-load, so force them first (`document.querySelectorAll('img').forEach(i => i.loading = 'eager')`, then wait). **Scroll the target card into view and take a viewport capture** — `scrollIntoView({block:'center', behavior:'instant'})`, short wait, `browser_take_screenshot` with `fullPage` omitted. A `fullPage` capture is NOT a workaround: on a tall gallery page it renders every card image blank even when the images are verified loaded (`complete === true`, `naturalWidth === 1280`, `opacity === 1`) — measured on the 45-card grid at 1425x4516. Verify load state via `browser_evaluate` rather than trusting the pixels. ## Example-Run Process @@ -149,7 +149,7 @@ The AI consumes content via: ## Key Conventions -- **Blender versions**: 5.1 primary, 4.5 LTS fallback. Skills must show both code paths when 4.x and 5.x APIs diverge. +- **Blender versions**: 5.2 LTS primary, 5.1 prior stable, 4.5 LTS fallback. Skills must show both code paths when 4.x and 5.x APIs diverge, and the 5.1-vs-5.2 NodesModifier input split. - **Properties as annotations**: `my_prop: bpy.props.FloatProperty(...)` (correct), not `my_prop = bpy.props.FloatProperty(...)` (deprecated). - **bmesh memory**: every `bmesh.new()` must be paired with `bm.free()` in a `try`/`finally`. - **No `bpy.ops` in tight loops**: use `bpy.data.*` and `bmesh` for bulk work. @@ -160,7 +160,8 @@ The AI consumes content via: | Area | URL | | --- | --- | -| Python API (5.1) | https://docs.blender.org/api/current/ | +| Python API (5.2 LTS) | https://docs.blender.org/api/current/ | +| Python API (5.1) | https://docs.blender.org/api/5.1/ | | Python API (4.5 LTS) | https://docs.blender.org/api/4.5/ | | Extensions Platform | https://docs.blender.org/manual/en/latest/advanced/extensions/index.html | | Release notes | https://developer.blender.org/ | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 46defdd..4f133ab 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,7 +88,7 @@ templates/ ## Blender Version Targeting -Content targets **Blender 5.1** as primary, with **Blender 4.5 LTS** as fallback. When the API differs, branch on `bpy.app.version` and document both paths. Example: +Content targets **Blender 5.2 LTS** as primary, **Blender 5.1** as prior stable, and **Blender 4.5 LTS** as fallback. When the API differs, branch on `bpy.app.version` and document both paths. Example: ```python if bpy.app.version >= (5, 0, 0): diff --git a/README.md b/README.md index ed7be3c..d9c10fc 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,12 @@

Validate - Blender smoke tests + Blender smoke tests Drift check

- 12 skills  •  6 rules  •  2 templates  •  17 snippets  •  44 examples + 12 skills  •  6 rules  •  2 templates  •  17 snippets  •  45 examples

@@ -36,7 +36,7 @@ ## Overview -This repository ships **12 skills, 6 rules, 2 templates, 17 snippets, and 44 runnable examples** for Blender Python development targeting Blender 5.1 (current stable) with Blender 4.5 LTS fallback support. +This repository ships **12 skills, 6 rules, 2 templates, 17 snippets, and 45 runnable examples** for Blender Python development targeting Blender 5.2 LTS (current stable) with Blender 4.5 LTS fallback support. Blender 5.1 is prior stable. 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. @@ -65,14 +65,14 @@ blender --background --python examples/bmesh-gear/bmesh_gear.py -- | Version | Status | | --- | --- | -| Blender 5.1.x | Primary target (all examples assume 5.1) | +| Blender 5.2 LTS | Primary target (current stable; all examples assume 5.2 unless a 4.5 path is shown) | +| Blender 5.1 | Prior stable (weekly smoke only) | | Blender 4.5 LTS | Fallback supported (skills show both code paths where 4.x and 5.x APIs diverge) | -| Blender 5.2 LTS | Sweep planned for July 2026 (see [ROADMAP.md](ROADMAP.md)) | ## Examples Runnable, smoke-gated demos live in [`examples/`](examples/) — each is executed headless on -both Blender 4.5 LTS and 5.1 by the `blender-smoke` workflow, so the screenshots reflect code +Blender 5.2 LTS and 4.5 LTS by the `blender-smoke` workflow (5.1 on the weekly cron), so the screenshots reflect code that actually runs. Browse them all with filters and full-size renders in the **[examples gallery](https://tmhsdigital.github.io/Blender-Developer-Tools/gallery/)**, or expand a category below. @@ -362,7 +362,7 @@ inside the effect. Per-frame sample renders assert the closed form (mid dips

-Geometry Nodes — 2 examples +Geometry Nodes — 3 examples @@ -391,6 +391,20 @@ A generative Geometry Nodes tree — Mesh Grid → Instance on Points → Realiz attached as a `NODES` modifier with no Group Input. Asserts evaluated topology is verts = 72, faces = 54, and `Set Material` carries the lime accent. + + + + +
+GN modifier inputs: three cubes in teal, copper, and coral scaled to 1, 2, and 3 meters by a shared Geometry Nodes tree, proving per-modifier Scale writes land on 5.2 RNA and on 4.5 dict assignment + + +### [gn-modifier-inputs](examples/gn-modifier-inputs/) + +One Geometry Nodes tree, three modifier copies. Writes a Float Scale input through +`mod.properties.inputs` on 5.2 and `mod[identifier]` on 4.5/5.1. Asserts readback and +evaluated Z-extent equal 1 / 2 / 3. The 5.1 dict form raises TypeError on 5.2. +
@@ -906,7 +920,8 @@ Each snippet is a standalone Python file under `snippets/`. They are not loaded | Resource | Use it for | | --- | --- | -| [Blender 5.1 Python API](https://docs.blender.org/api/current/) | Authoritative reference for current stable APIs | +| [Blender 5.2 LTS Python API](https://docs.blender.org/api/current/) | Authoritative reference for current stable APIs | +| [Blender 5.1 Python API](https://docs.blender.org/api/5.1/) | Prior stable | | [Blender 4.5 LTS Python API](https://docs.blender.org/api/4.5/) | LTS reference when targeting 4.5 | | [Extensions Platform manual](https://docs.blender.org/manual/en/latest/advanced/extensions/index.html) | `blender_manifest.toml` schema, hosting, install flow | | [developer.blender.org](https://developer.blender.org/) | Release notes, breaking change tracking, design docs | @@ -915,8 +930,7 @@ When community content (Stack Overflow, older add-on source) conflicts with the ## Roadmap -See [ROADMAP.md](ROADMAP.md) for the candidate pool and what ships next — including the -planned Blender 5.2 LTS sweep (July 2026). Releases are cut automatically from +See [ROADMAP.md](ROADMAP.md) for the candidate pool and what ships next. Releases are cut automatically from conventional commits; the full history lives in [CHANGELOG.md](CHANGELOG.md). ## Contributing diff --git a/ROADMAP.md b/ROADMAP.md index e900573..46bf7b2 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -15,7 +15,8 @@ derives the actual version from conventional-commit types. | Materials, drivers, migration | 12 | 6 | 2 | 17 | Shipped (v0.2.0) | | Examples and demos (smoke-gated) | 12 | 6 | 2 | 17 | Shipped (v0.3.0) | | More examples (turntable, SDF remesh) | 12 | 6 | 2 | 17 | Shipped (v0.4.0) | -| 5.2 LTS sweep, modal operators, USD | — | — | — | — | Upcoming | +| 5.2 LTS targeting, GN modifier inputs | 12 | 6 | 2 | 17 | Shipped | +| Modal operators, USD, mathutils | — | — | — | — | Upcoming | | Stable | — | — | — | — | Upcoming | ## v0.1.0 - Foundation @@ -93,9 +94,7 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo - `modal-operators` skill -- `invoke` returning `RUNNING_MODAL`, the `modal()` event handler, modal cancellation patterns - `usd-pipelines` skill -- USD export options, `evaluation_mode`, instancing, the USD vs glTF tradeoffs - `mathutils-patterns` skill -- `mathutils.Vector`, `Matrix`, `Quaternion`, common transforms, the `@` operator -- Blender 5.2 LTS sweep (after the 5.2 LTS release in mid-2026) -- Refresh the `slotted-actions-animation` skill against any 5.2 changes -- Bump `blender_version_min` in the templates if 5.2 APIs are used +- ~~Blender 5.2 LTS sweep~~ **SHIPPED** as targeting + smoke matrix (5.2 + 4.5 on PRs, 5.1 weekly) plus `examples/gn-modifier-inputs/` — NodesModifier dict assignment raises TypeError on 5.2; RNA `mod.properties.inputs..value` is the replacement - Additional snippets for asset library scripting, EXR baking, multi-file extensions - ~~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 diff --git a/docs/gallery/armature-bend/index.html b/docs/gallery/armature-bend/index.html index 7a1031d..0b4e2d2 100644 --- a/docs/gallery/armature-bend/index.html +++ b/docs/gallery/armature-bend/index.html @@ -271,7 +271,7 @@

Run

# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts): blender --background --python armature_bend.py -- --output bend.png blender --background --python armature_bend.py -- --output bend.png --engine cycles -

It exits non-zero on failure (edit-bone lifetime violation, LBS deviation, moved root ring, or an undeformed tip). The blender-smoke workflow runs the check on Blender 4.5 LTS and 5.1.

+

It exits non-zero on failure (edit-bone lifetime violation, LBS deviation, moved root ring, or an undeformed tip). The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS.

Source

diff --git a/docs/gallery/assets/gn-modifier-inputs-hero.webp b/docs/gallery/assets/gn-modifier-inputs-hero.webp new file mode 100644 index 0000000000000000000000000000000000000000..4da5f22bb9ebf146fe16d97bd0b810f789e6ffea GIT binary patch literal 9924 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 CeK5lS literal 0 HcmV?d00001 diff --git a/docs/gallery/attribute-domain-shear/index.html b/docs/gallery/attribute-domain-shear/index.html index ac3e0c0..ed4cd65 100644 --- a/docs/gallery/attribute-domain-shear/index.html +++ b/docs/gallery/attribute-domain-shear/index.html @@ -270,7 +270,7 @@

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.

+

Exits non-zero on failure. The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS. 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

diff --git a/docs/gallery/bmesh-gear/index.html b/docs/gallery/bmesh-gear/index.html index bf181c7..c95a4e3 100644 --- a/docs/gallery/bmesh-gear/index.html +++ b/docs/gallery/bmesh-gear/index.html @@ -268,7 +268,7 @@

Run

# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts): blender --background --python bmesh_gear.py -- --output gear.png blender --background --python bmesh_gear.py -- --output gear.png --engine cycles -

It exits non-zero on failure (topology mismatch or non-manifold edges). The blender-smoke workflow runs the check on Blender 4.5 LTS and 5.1.

+

It exits non-zero on failure (topology mismatch or non-manifold edges). The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS.

Source

diff --git a/docs/gallery/car-mirror-symmetry/index.html b/docs/gallery/car-mirror-symmetry/index.html index 97cac45..bf23558 100644 --- a/docs/gallery/car-mirror-symmetry/index.html +++ b/docs/gallery/car-mirror-symmetry/index.html @@ -272,7 +272,7 @@

Run

# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts): blender --background --python car_mirror_symmetry.py -- --output car.png blender --background --python car_mirror_symmetry.py -- --output car.png --engine cycles -

It exits non-zero on failure (applied mirror, doubled centerline, unwelded seam, broken symmetry, or a mirrored part off its plane origin). The blender-smoke workflow runs the check on Blender 4.5 LTS and 5.1.

+

It exits non-zero on failure (applied mirror, doubled centerline, unwelded seam, broken symmetry, or a mirrored part off its plane origin). The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS.

Source

diff --git a/docs/gallery/collision-hull-proxy/index.html b/docs/gallery/collision-hull-proxy/index.html index ed49a3c..015ec53 100644 --- a/docs/gallery/collision-hull-proxy/index.html +++ b/docs/gallery/collision-hull-proxy/index.html @@ -274,7 +274,7 @@

Run

# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts): blender --background --python collision_hull_proxy.py -- --output hydrant.png blender --background --python collision_hull_proxy.py -- --output hydrant.png --engine cycles -

It exits non-zero on failure (render geometry escaping a hull, inverted winding, a non-watertight or non-convex piece, Euler drift, or a piece over the 255-face budget). The blender-smoke workflow runs the check on Blender 4.5 LTS and 5.1.

+

It exits non-zero on failure (render geometry escaping a hull, inverted winding, a non-watertight or non-convex piece, Euler drift, or a piece over the 255-face budget). The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS.

Source

diff --git a/docs/gallery/color-attribute-wheel/index.html b/docs/gallery/color-attribute-wheel/index.html index 492d2f5..13b5ec5 100644 --- a/docs/gallery/color-attribute-wheel/index.html +++ b/docs/gallery/color-attribute-wheel/index.html @@ -268,7 +268,7 @@

Run

# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts): blender --background --python color_attribute_wheel.py -- --output wheel.png blender --background --python color_attribute_wheel.py -- --output wheel.png --engine cycles -

It exits non-zero on failure (missing/mis-sized/mis-domained attribute, wrong active attribute, a probe color mismatch, or an unlinked Attribute node). The blender-smoke workflow runs the check on Blender 4.5 LTS and 5.1.

+

It exits non-zero on failure (missing/mis-sized/mis-domained attribute, wrong active attribute, a probe color mismatch, or an unlinked Attribute node). The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS.

Source

diff --git a/docs/gallery/compositor-glare/index.html b/docs/gallery/compositor-glare/index.html index db5456d..e9765d0 100644 --- a/docs/gallery/compositor-glare/index.html +++ b/docs/gallery/compositor-glare/index.html @@ -269,7 +269,7 @@

Run

# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts): blender --background --python compositor_glare.py -- --output rings.png blender --background --python compositor_glare.py -- --output rings.png --engine cycles -

It exits non-zero on failure (wrong tree plumbing, wrong Glare configuration, a missing or non-falling halo, or halo pixels that appear without the compositor). The blender-smoke workflow runs the check on Blender 4.5 LTS and 5.1.

+

It exits non-zero on failure (wrong tree plumbing, wrong Glare configuration, a missing or non-falling halo, or halo pixels that appear without the compositor). The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS.

Source

diff --git a/docs/gallery/contact-sheets/gn-modifier-inputs-contact-sheet.webp b/docs/gallery/contact-sheets/gn-modifier-inputs-contact-sheet.webp new file mode 100644 index 0000000000000000000000000000000000000000..7b075203e42a9eef8cc99707d14688b2d9030a22 GIT binary patch literal 79300 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 AJOBUy literal 0 HcmV?d00001 diff --git a/docs/gallery/curve-bevel-arc/index.html b/docs/gallery/curve-bevel-arc/index.html index 3bc0b25..8e652c9 100644 --- a/docs/gallery/curve-bevel-arc/index.html +++ b/docs/gallery/curve-bevel-arc/index.html @@ -268,7 +268,7 @@

Run

# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts): blender --background --python curve_bevel_arc.py -- --output arc.png blender --background --python curve_bevel_arc.py -- --output arc.png --engine cycles -

It exits non-zero on failure (wrong point count, bevel, caps, topology, or span). The blender-smoke workflow runs the check on Blender 4.5 LTS and 5.1.

+

It exits non-zero on failure (wrong point count, bevel, caps, topology, or span). The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS.

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.

diff --git a/docs/gallery/custom-normals-shade/index.html b/docs/gallery/custom-normals-shade/index.html index edca46c..226d28e 100644 --- a/docs/gallery/custom-normals-shade/index.html +++ b/docs/gallery/custom-normals-shade/index.html @@ -274,7 +274,7 @@

Run

# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts): blender --background --python custom_normals_shade.py -- --output cans.png blender --background --python custom_normals_shade.py -- --output cans.png --engine cycles -

It exits non-zero on failure (legacy API resurrected, sharp-set/dihedral mismatch, broken normal welds, custom normals lost or dequantized in evaluation, or legacy-operator divergence drift). The blender-smoke workflow runs the check on Blender 4.5 LTS and 5.1.

+

It exits non-zero on failure (legacy API resurrected, sharp-set/dihedral mismatch, broken normal welds, custom normals lost or dequantized in evaluation, or legacy-operator divergence drift). The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS.

Source

diff --git a/docs/gallery/damped-track-aim/index.html b/docs/gallery/damped-track-aim/index.html index 6f5ec41..4c73bf6 100644 --- a/docs/gallery/damped-track-aim/index.html +++ b/docs/gallery/damped-track-aim/index.html @@ -270,7 +270,7 @@

Run

# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts): blender --background --python damped_track_aim.py -- --output aim.png blender --background --python damped_track_aim.py -- --output aim.png --engine cycles -

It exits non-zero on failure (wrong constraint type/target/axis, or evaluated aim outside the angular epsilon). The blender-smoke workflow runs the check on Blender 4.5 LTS and 5.1.

+

It exits non-zero on failure (wrong constraint type/target/axis, or evaluated aim outside the angular epsilon). The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS.

Source

diff --git a/docs/gallery/degenerate-bevel-weld/index.html b/docs/gallery/degenerate-bevel-weld/index.html index a6dbacf..fc1de04 100644 --- a/docs/gallery/degenerate-bevel-weld/index.html +++ b/docs/gallery/degenerate-bevel-weld/index.html @@ -270,7 +270,7 @@

Run

blender --background --python degenerate_bevel_weld.py --
 blender --background --python degenerate_bevel_weld.py -- --output bevel.png
 blender --background --python degenerate_bevel_weld.py -- --output bevel.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.

+

Exits non-zero on failure. The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS. 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

diff --git a/docs/gallery/depsgraph-export/index.html b/docs/gallery/depsgraph-export/index.html index 52830ad..caabf57 100644 --- a/docs/gallery/depsgraph-export/index.html +++ b/docs/gallery/depsgraph-export/index.html @@ -271,7 +271,7 @@

Run

# Write the exported OBJ to a specific path: blender --background --python depsgraph_export.py -- --obj exported.obj -

It exits non-zero on failure (modifier not applied, or exported count ≠ evaluated count). The blender-smoke workflow runs this check on Blender 4.5 LTS and 5.1: base 8 → evaluated/exported 98 vertices with a 2-level SUBSURF.

+

It exits non-zero on failure (modifier not applied, or exported count ≠ evaluated count). The blender-smoke workflow runs this check on Blender 5.2 LTS and 4.5 LTS: base 8 → evaluated/exported 98 vertices with a 2-level SUBSURF.

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.

diff --git a/docs/gallery/driver-wave/index.html b/docs/gallery/driver-wave/index.html index 12465f4..6d33d13 100644 --- a/docs/gallery/driver-wave/index.html +++ b/docs/gallery/driver-wave/index.html @@ -269,7 +269,7 @@

Run

# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts): blender --background --python driver_wave.py -- --output driver.png blender --background --python driver_wave.py -- --output driver.png --engine cycles -

It exits non-zero on failure (driven value wrong, or the flush-back disagreed). The blender-smoke workflow runs the check on Blender 4.5 LTS and 5.1.

+

It exits non-zero on failure (driven value wrong, or the flush-back disagreed). The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS.

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.

diff --git a/docs/gallery/gltf-export-roundtrip/index.html b/docs/gallery/gltf-export-roundtrip/index.html index d466e06..bbe9d62 100644 --- a/docs/gallery/gltf-export-roundtrip/index.html +++ b/docs/gallery/gltf-export-roundtrip/index.html @@ -273,7 +273,7 @@

Run

# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts): blender --background --python gltf_export_roundtrip.py -- --output crate.png blender --background --python gltf_export_roundtrip.py -- --output crate.png --engine cycles -

It exits non-zero on failure (RNA kwarg drift, cage drift, missing on-disk conversion, vertex-split drift, or any round-trip excursion beyond tolerance). The blender-smoke workflow runs the check on Blender 4.5 LTS and 5.1.

+

It exits non-zero on failure (RNA kwarg drift, cage drift, missing on-disk conversion, vertex-split drift, or any round-trip excursion beyond tolerance). The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS.

Source

diff --git a/docs/gallery/gltf-skin-roundtrip/index.html b/docs/gallery/gltf-skin-roundtrip/index.html index 91e1d78..a21ac0e 100644 --- a/docs/gallery/gltf-skin-roundtrip/index.html +++ b/docs/gallery/gltf-skin-roundtrip/index.html @@ -273,7 +273,7 @@

Run

# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts): blender --background --python gltf_skin_roundtrip.py -- --output scorp.png blender --background --python gltf_skin_roundtrip.py -- --output scorp.png --engine cycles -

It exits non-zero on failure (missing skin, joint drift, weight-sum drift, skeleton drift, weight excursion, or deformation excursion). The blender-smoke workflow runs the check on Blender 4.5 LTS and 5.1.

+

It exits non-zero on failure (missing skin, joint drift, weight-sum drift, skeleton drift, weight excursion, or deformation excursion). The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS.

Source

diff --git a/docs/gallery/gn-instance-grid/index.html b/docs/gallery/gn-instance-grid/index.html index 4f69e10..8dab5d8 100644 --- a/docs/gallery/gn-instance-grid/index.html +++ b/docs/gallery/gn-instance-grid/index.html @@ -268,7 +268,7 @@

Run

# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts): blender --background --python gn_instance_grid.py -- --output grid.png blender --background --python gn_instance_grid.py -- --output grid.png --engine cycles -

It exits non-zero on failure (wrong carrier, topology mismatch, missing material, or misplaced corner). The blender-smoke workflow runs the check on Blender 4.5 LTS and 5.1.

+

It exits non-zero on failure (wrong carrier, topology mismatch, missing material, or misplaced corner). The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS.

Source

diff --git a/docs/gallery/gn-modifier-inputs/index.html b/docs/gallery/gn-modifier-inputs/index.html new file mode 100644 index 0000000..d4decfd --- /dev/null +++ b/docs/gallery/gn-modifier-inputs/index.html @@ -0,0 +1,721 @@ + + + + + + gn-modifier-inputs — Examples — Blender Developer Tools + + + + + + + + + + + + + + + + + + +
+

gn-modifier-inputs

+

Per-modifier Geometry Nodes Float inputs on a shared tree — 4.5/5.1 write mod[identifier], 5.2 writes mod.properties.inputs.Socket_1.value.

+
+
+ +

Rendered headless by the example itself — click to zoom.

+
witnesses The 5.1 dict form raises TypeError on 5.2 rather than silently no-opping; RNA properties are AttributeError on 4.5. Readback and evaluated Z-extent match written scales 1/2/3.
+
+
blender --background --python examples/gn-modifier-inputs/gn_modifier_inputs.py --
+ +
+
+

A runnable example that attaches one Geometry Nodes tree to three cubes and writes a per-modifier Float Scale input through the version-appropriate API. The tree scales a 1 m cube by that value and lifts it onto the floor. Evaluated Z-extent must match the written scale (1 / 2 / 3). That is the closed form: if the write did not land, the three cubes collapse to the socket default and the extents are no longer distinct.

+

What it witnesses: Blender 5.2 removed ID-property assignment on NodesModifier. mod["Socket_1"] = 2.0 raises TypeError rather than silently no-opping. 4.5 LTS and 5.1 still require that dict form; mod.properties does not exist there (AttributeError). 5.2+ writes mod.properties.inputs.Socket_1.value. After the write, the depsgraph must be updated or evaluated_get still sees the previous scale.

+

Follows geometry-nodes-python.

+

Run

+
# Cheap correctness check (no render) — the CI check:
+blender --background --python gn_modifier_inputs.py --
+
+# Force one side of the split (must fail on the other series):
+blender --background --python gn_modifier_inputs.py -- --api dict
+blender --background --python gn_modifier_inputs.py -- --api rna
+
+# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts):
+blender --background --python gn_modifier_inputs.py -- --output stairs.png
+blender --background --python gn_modifier_inputs.py -- --output stairs.png --engine cycles
+

It exits non-zero on failure (missing identifier, write/read raise, readback mismatch, evaluated Z-extent ≠ scale, or three extents not distinct). The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS (5.1 on the weekly cron).

+

Falsification

+

| Probe | Binary | Result | | --- | --- | --- | | --api dict | 5.2.1 LTS | exit 5, TypeError: id properties not supported for this type | | --api rna | 4.5.11 LTS | exit 5, AttributeError: 'NodesModifier' object has no attribute 'properties' | | --api auto | 5.2.1, 5.1.2, 4.5.11 | exit 0, extents 1.000 / 2.000 / 3.000 |

+
+
+

Source

+
+ examples/gn-modifier-inputs/gn_modifier_inputs.py + View on GitHub → +
+
"""Geometry Nodes per-modifier input write — a runnable example.
+
+Witnesses the 5.1→5.2 removal of dict assignment on a NODES modifier.
+A shared GeometryNodeTree exposes a Float "Scale" socket. Three carrier
+cubes each get their own modifier instance of that tree. The check writes
+1.0 / 2.0 / 3.0 through the version-appropriate path, reads the value
+back, and asserts the evaluated Z-extent equals the written scale
+(closed form: a 1 m cube scaled by S and lifted by S/2 spans [0, S]).
+
+4.5 LTS and 5.1 write ``mod[identifier] = value``. 5.2+ removed ID
+properties on NodesModifier — that assignment raises TypeError — and
+the replacement is ``mod.properties.inputs.<identifier>.value``.
+``--api dict`` / ``--api rna`` force one side so the witness can fail
+on purpose.
+
+    blender --background --python gn_modifier_inputs.py --
+    blender --background --python gn_modifier_inputs.py -- --api dict
+    blender --background --python gn_modifier_inputs.py -- --output s.png
+"""
+import argparse
+import math
+import os
+import sys
+
+import bmesh
+import bpy
+
+sys.path.insert(
+    0, os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir)
+)
+import gallery_framing  # noqa: E402
+
+CUBE_SIZE = 1.0
+SCALES = (1.0, 2.0, 3.0)
+# Half-widths 0.5 / 1.0 / 1.5; keep a clear gap, keep the 3 m cube off the right edge.
+XS = (-2.2, 0.15, 2.45)
+COLORS = (
+    (0.05, 0.62, 0.58, 1.0),  # teal
+    (0.82, 0.38, 0.08, 1.0),  # copper
+    (0.86, 0.18, 0.22, 1.0),  # coral
+)
+READBACK_EPS = 1e-6
+EXTENT_EPS = 1e-4
+INPUT_NAME = "Scale"
+
+
+def _api_choice(explicit):
+    if explicit != "auto":
+        return explicit
+    return "rna" if bpy.app.version >= (5, 2, 0) else "dict"
+
+
+def scale_identifier(tree):
+    for item in tree.interface.items_tree:
+        if getattr(item, "item_type", "SOCKET") not in ("SOCKET",):
+            continue
+        if getattr(item, "in_out", None) == "INPUT" and item.name == INPUT_NAME:
+            return item.identifier
+    return None
+
+
+def set_mod_input(mod, ident, value, api):
+    if api == "dict":
+        mod[ident] = value
+        return
+    sock = getattr(mod.properties.inputs, ident)
+    sock.value = value
+
+
+def get_mod_input(mod, ident, api):
+    if api == "dict":
+        return float(mod[ident])
+    sock = getattr(mod.properties.inputs, ident)
+    return float(sock.value)
+
+
+def make_material(name, color):
+    mat = bpy.data.materials.new(name)
+    mat.use_nodes = True
+    bsdf = mat.node_tree.nodes["Principled BSDF"]
+    bsdf.inputs["Base Color"].default_value = color
+    bsdf.inputs["Roughness"].default_value = 0.28
+    bsdf.inputs["Metallic"].default_value = 0.35
+    return mat
+
+
+def make_cube_mesh(name):
+    me = bpy.data.meshes.new(name)
+    bm = bmesh.new()
+    try:
+        bmesh.ops.create_cube(bm, size=CUBE_SIZE)
+        bm.to_mesh(me)
+    finally:
+        bm.free()
+    return me
+
+
+def build_scale_tree(material=None):
+    tree = bpy.data.node_groups.new("ModifierScale", "GeometryNodeTree")
+    tree.interface.new_socket(
+        name="Geometry", in_out="INPUT", socket_type="NodeSocketGeometry"
+    )
+    scale_sock = tree.interface.new_socket(
+        name=INPUT_NAME, in_out="INPUT", socket_type="NodeSocketFloat"
+    )
+    scale_sock.default_value = 1.0
+    tree.interface.new_socket(
+        name="Geometry", in_out="OUTPUT", socket_type="NodeSocketGeometry"
+    )
+    gi = tree.nodes.new("NodeGroupInput")
+    go = tree.nodes.new("NodeGroupOutput")
+    combine = tree.nodes.new("ShaderNodeCombineXYZ")
+    xform = tree.nodes.new("GeometryNodeTransform")
+    shade = tree.nodes.new("GeometryNodeSetShadeSmooth")
+    shade.inputs["Shade Smooth"].default_value = False
+
+    tree.links.new(gi.outputs[INPUT_NAME], combine.inputs["X"])
+    tree.links.new(gi.outputs[INPUT_NAME], combine.inputs["Y"])
+    tree.links.new(gi.outputs[INPUT_NAME], combine.inputs["Z"])
+    tree.links.new(gi.outputs["Geometry"], xform.inputs["Geometry"])
+    tree.links.new(combine.outputs["Vector"], xform.inputs["Scale"])
+    # Lift by S/2 so a cube of extent S sits on z=0.
+    # Translation is a vector; drive Z from the same Scale socket via Combine.
+    lift = tree.nodes.new("ShaderNodeCombineXYZ")
+    scale_half = tree.nodes.new("ShaderNodeMath")
+    scale_half.operation = "MULTIPLY"
+    scale_half.inputs[1].default_value = 0.5
+    tree.links.new(gi.outputs[INPUT_NAME], scale_half.inputs[0])
+    tree.links.new(scale_half.outputs[0], lift.inputs["Z"])
+    tree.links.new(lift.outputs["Vector"], xform.inputs["Translation"])
+
+    tree.links.new(xform.outputs["Geometry"], shade.inputs["Geometry"])
+    out_socket = shade.outputs["Geometry"]
+    if material is not None:
+        set_mat = tree.nodes.new("GeometryNodeSetMaterial")
+        set_mat.inputs["Material"].default_value = material
+        tree.links.new(out_socket, set_mat.inputs["Geometry"])
+        out_socket = set_mat.outputs["Geometry"]
+    tree.links.new(out_socket, go.inputs["Geometry"])
+    return tree
+
+
+def build():
+    bpy.ops.wm.read_factory_settings(use_empty=True)
+    mats = [
+        make_material("TealScale", COLORS[0]),
+        make_material("CopperScale", COLORS[1]),
+        make_material("CoralScale", COLORS[2]),
+    ]
+    # One shared tree; Set Material is applied per-object via the mesh slot
+    # rather than inside the group so the group stays parameter-only.
+    tree = build_scale_tree(material=None)
+    objs = []
+    mods = []
+    for i, (x, mat) in enumerate(zip(XS, mats)):
+        me = make_cube_mesh(f"Carrier{i}")
+        me.materials.append(mat)
+        obj = bpy.data.objects.new(f"Scale{int(SCALES[i])}", me)
+        obj.location = (x, 0.0, 0.0)
+        bpy.context.collection.objects.link(obj)
+        mod = obj.modifiers.new("scale_input", "NODES")
+        mod.node_group = tree
+        objs.append(obj)
+        mods.append(mod)
+    return tree, objs, mods
+
+
+def evaluated_z_extent(obj):
+    dg = bpy.context.evaluated_depsgraph_get()
+    ev = obj.evaluated_get(dg)
+    em = ev.to_mesh()
+    try:
+        zs = [v.co.z for v in em.vertices]
+        if not zs:
+            return 0.0, 0.0, 0
+        return min(zs), max(zs), len(em.vertices)
+    finally:
+        ev.to_mesh_clear()
+
+
+def check(tree, objs, mods, api):
+    ident = scale_identifier(tree)
+    if not ident:
+        print("ERROR: Scale input identifier missing on the tree interface",
+              file=sys.stderr)
+        return 3
+    print(f"api={api} blender={bpy.app.version} identifier={ident}")
+
+    if len({mod.node_group for mod in mods}) != 1:
+        print("ERROR: modifiers do not share one node_group", file=sys.stderr)
+        return 4
+
+    for obj, mod, scale in zip(objs, mods, SCALES):
+        try:
+            set_mod_input(mod, ident, scale, api)
+        except Exception as e:
+            print(
+                f"ERROR: {api} write of {scale} on {obj.name} raised "
+                f"{type(e).__name__}: {e}",
+                file=sys.stderr,
+            )
+            return 5
+        obj.update_tag()
+        bpy.context.view_layer.update()
+        try:
+            got = get_mod_input(mod, ident, api)
+        except Exception as e:
+            print(
+                f"ERROR: {api} read of {obj.name} raised "
+                f"{type(e).__name__}: {e}",
+                file=sys.stderr,
+            )
+            return 6
+        if abs(got - scale) > READBACK_EPS:
+            print(
+                f"ERROR: readback {got} != written {scale} on {obj.name}",
+                file=sys.stderr,
+            )
+            return 7
+
+        zmin, zmax, nverts = evaluated_z_extent(obj)
+        extent = zmax - zmin
+        if abs(extent - scale) > EXTENT_EPS:
+            print(
+                f"ERROR: evaluated Z-extent {extent:.6f} != scale {scale} "
+                f"on {obj.name} (z=[{zmin:.4f},{zmax:.4f}] verts={nverts})",
+                file=sys.stderr,
+            )
+            return 8
+        if abs(zmin) > EXTENT_EPS:
+            print(
+                f"ERROR: evaluated mesh not sitting on z=0 "
+                f"({obj.name} zmin={zmin:.4f})",
+                file=sys.stderr,
+            )
+            return 9
+        print(
+            f"{obj.name} scale={scale} readback={got:.6f} "
+            f"z_extent={extent:.6f} zmin={zmin:.6f} verts={nverts}"
+        )
+
+    extents = []
+    for obj in objs:
+        zmin, zmax, _ = evaluated_z_extent(obj)
+        extents.append(zmax - zmin)
+    if len(set(round(e, 4) for e in extents)) != 3:
+        print(
+            f"ERROR: evaluated extents not distinct {extents}"
+            "per-modifier copies did not land",
+            file=sys.stderr,
+        )
+        return 11
+    return 0
+
+
+def eevee_engine_id():
+    return "BLENDER_EEVEE" if bpy.app.version >= (5, 0, 0) else "BLENDER_EEVEE_NEXT"
+
+
+def render_still(objs, path, engine):
+    scene = bpy.context.scene
+
+    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 = bpy.data.materials.new("Studio")
+    fmat.use_nodes = True
+    fb = fmat.node_tree.nodes["Principled BSDF"]
+    fb.inputs["Base Color"].default_value = (0.03, 0.032, 0.037, 1.0)
+    fb.inputs["Roughness"].default_value = 0.7
+    floor_me.materials.append(fmat)
+    floor = bpy.data.objects.new("Floor", floor_me)
+    scene.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)
+    scene.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
+    )
+    scene.world = world
+
+    aim = bpy.data.objects.new("Aim", None)
+    aim.location = (0.35, 0.0, 1.25)
+    scene.collection.objects.link(aim)
+
+    def light(name, loc, energy, size, col):
+        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
+        scene.collection.objects.link(ob)
+        lc = ob.constraints.new("TRACK_TO")
+        lc.target = aim
+        lc.track_axis = "TRACK_NEGATIVE_Z"
+        lc.up_axis = "UP_Y"
+
+    light("Key", (-3.8, -5.0, 6.2), 560.0, 5.0, (1.0, 0.96, 0.9))
+    light("Fill", (5.4, -3.2, 2.4), 110.0, 8.0, (0.75, 0.85, 1.0))
+    light("Rim", (0.4, 6.4, 3.8), 280.0, 3.5, (0.6, 0.78, 1.0))
+    wedge = bpy.data.lights.new("Wedge", "AREA")
+    wedge.energy = 420.0
+    wedge.size = 6.0
+    wedge.color = (1.0, 0.76, 0.5)
+    wob = bpy.data.objects.new("Wedge", wedge)
+    wob.location = (2.4, 5.6, 4.2)
+    wob.rotation_euler = (math.radians(-68), 0.0, math.radians(190))
+    scene.collection.objects.link(wob)
+
+    cam_data = bpy.data.cameras.new("Cam")
+    cam_data.lens = 50.0
+    cam = bpy.data.objects.new("Cam", cam_data)
+    cam.location = (8.1, -10.8, 5.05)
+    scene.collection.objects.link(cam)
+    scene.camera = cam
+    track = cam.constraints.new("TRACK_TO")
+    track.target = aim
+    track.track_axis = "TRACK_NEGATIVE_Z"
+    track.up_axis = "UP_Y"
+
+    scene.render.engine = "CYCLES" if engine == "cycles" else eevee_engine_id()
+    if engine == "cycles":
+        scene.cycles.samples = 32
+    else:
+        try:
+            scene.eevee.taa_render_samples = 64
+        except AttributeError:
+            pass
+    scene.render.resolution_x = 1280
+    scene.render.resolution_y = 720
+    scene.render.image_settings.file_format = "PNG"
+    scene.render.filepath = path
+    scene.view_settings.view_transform = "Standard"
+
+    fcode = gallery_framing.check_framing(
+        scene, cam,
+        hero=list(objs),
+        elements=list(objs),
+        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 12
+    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)",
+    )
+    p.add_argument(
+        "--api", default="auto", choices=("auto", "dict", "rna"),
+        help="force the 5.1 dict path, the 5.2 RNA path, or pick from bpy.app.version",
+    )
+    args = p.parse_args(argv)
+
+    tree, objs, mods = build()
+    api = _api_choice(args.api)
+    code = check(tree, objs, mods, api)
+    if code:
+        return code
+
+    if args.output:
+        rcode = render_still(objs, os.path.abspath(args.output), args.engine)
+        if rcode:
+            return rcode
+        print(f"rendered still {args.output}")
+
+    print("gn-modifier-inputs 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/gn-sdf-remesh/index.html b/docs/gallery/gn-sdf-remesh/index.html index 18bad45..5654ae4 100644 --- a/docs/gallery/gn-sdf-remesh/index.html +++ b/docs/gallery/gn-sdf-remesh/index.html @@ -269,7 +269,7 @@

Run

# Also render the remeshed result (EEVEE on a GPU host; --engine cycles on GPU-less hosts): blender --background --python gn_sdf_remesh.py -- --output remesh.png blender --background --python gn_sdf_remesh.py -- --output remesh.png --engine cycles -

By default it runs only the frame-independent correctness check: the depsgraph-evaluated vertex count must be > 0 AND differ from the base mesh (the remesh produced geometry). It exits non-zero on failure — the same check the blender-smoke workflow runs on Blender 4.5 LTS and 5.1.

+

By default it runs only the frame-independent correctness check: the depsgraph-evaluated vertex count must be > 0 AND differ from the base mesh (the remesh produced geometry). It exits non-zero on failure — the same check the blender-smoke workflow runs on Blender 5.2 LTS and 4.5 LTS.

Source

diff --git a/docs/gallery/gp-lineart-contour/index.html b/docs/gallery/gp-lineart-contour/index.html index 3f351d1..7995916 100644 --- a/docs/gallery/gp-lineart-contour/index.html +++ b/docs/gallery/gp-lineart-contour/index.html @@ -270,7 +270,7 @@

Run

# Also render the gallery still: blender --background --python gp_lineart_contour.py -- --output lineart.png -

It exits non-zero on failure. The blender-smoke workflow runs the check on Blender 4.5 LTS and 5.1.

+

It exits non-zero on failure. The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS.

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.

diff --git a/docs/gallery/grease-pencil-rosette/index.html b/docs/gallery/grease-pencil-rosette/index.html index 9450d65..50ff5a6 100644 --- a/docs/gallery/grease-pencil-rosette/index.html +++ b/docs/gallery/grease-pencil-rosette/index.html @@ -270,7 +270,7 @@

Run

# 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 -

It exits non-zero on failure (wrong version gate, structural mismatch, missing attribute layers, or attribute-buffer deviation from the closed form). The blender-smoke workflow runs the check on Blender 4.5 LTS and 5.1.

+

It exits non-zero on failure (wrong version gate, structural mismatch, missing attribute layers, or attribute-buffer deviation from the closed form). The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS.

Source

diff --git a/docs/gallery/image-pixels-testcard/index.html b/docs/gallery/image-pixels-testcard/index.html index c220454..c470ade 100644 --- a/docs/gallery/image-pixels-testcard/index.html +++ b/docs/gallery/image-pixels-testcard/index.html @@ -273,7 +273,7 @@

Run

# Also render a still (EEVEE on a GPU host; use --engine cycles on GPU-less hosts): blender --background --python image_pixels_testcard.py -- --output card.png blender --background --python image_pixels_testcard.py -- --output card.png --engine cycles -

It exits non-zero on failure and prints every measured error and tolerance on success, so CI logs carry the numbers. The blender-smoke workflow runs the check on Blender 4.5 LTS and 5.1. In the render, Closest interpolation keeps the pixel grid honest — the jagged circle edge is the 512 × 288 buffer itself, and the white marker in the PLUGE row sits at the bottom-left because that is where pixel (0, 0) lives. The monitor is staged as a designed object — beveled dark-polymer case, machined metal stand, teal power LED — on the dark studio stage from docs/VISUAL-STYLE.md (Standard view transform; warm key, cool fill and rim; a warm pool raking the back wall). The screen stays emissive and matte — specular off, emission strength 1.0 — so the card's values read exactly.

+

It exits non-zero on failure and prints every measured error and tolerance on success, so CI logs carry the numbers. The blender-smoke workflow runs the check on Blender 5.2 LTS and 4.5 LTS. In the render, Closest interpolation keeps the pixel grid honest — the jagged circle edge is the 512 × 288 buffer itself, and the white marker in the PLUGE row sits at the bottom-left because that is where pixel (0, 0) lives. The monitor is staged as a designed object — beveled dark-polymer case, machined metal stand, teal power LED — on the dark studio stage from docs/VISUAL-STYLE.md (Standard view transform; warm key, cool fill and rim; a warm pool raking the back wall). The screen stays emissive and matte — specular off, emission strength 1.0 — so the card's values read exactly.

Source

diff --git a/docs/gallery/index.html b/docs/gallery/index.html index 0947929..7cd1d1a 100644 --- a/docs/gallery/index.html +++ b/docs/gallery/index.html @@ -4,18 +4,18 @@ Examples Gallery — Blender Developer Tools - + - + - +