diff --git a/README.md b/README.md index 7d6c3b2..344ac76 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@

Validate + Blender smoke tests Drift check

@@ -20,6 +21,17 @@ 12 skills  •  6 rules  •  2 templates  •  17 snippets  •  20 examples

+

+ Examples Gallery +  •  Quick start +  •  Examples +  •  Skills +  •  Rules +  •  Templates +  •  Snippets +  •  Roadmap +

+ --- ## Overview @@ -35,6 +47,20 @@ The content is consumed by AI coding agents (Cursor, Claude Code, any MCP-capabl | **Templates** | A working Extensions Platform add-on starter and a headless batch script starter | | **Snippets** | 17 small standalone Python files demonstrating canonical patterns | +## Quick start + +```bash +git clone https://github.com/TMHSDigital/Blender-Developer-Tools.git +``` + +- **Cursor** — point Cursor at the checkout (or symlink `rules/` into your project). The `.mdc` rules apply automatically by glob scope; skills are referenced by name in chat. +- **Claude Code** — copy `skills/` and `rules/` into your project workspace, or keep this repo as a checkout that Claude Code references directly. +- **Run an example** — every example is a self-checking headless script (exit non-zero on failure, no GPU needed for the check): + +```bash +blender --background --python examples/bmesh-gear/bmesh_gear.py -- +``` + ## Supported Blender versions | Version | Status | @@ -47,8 +73,19 @@ The content is consumed by AI coding agents (Cursor, Claude Code, any MCP-capabl 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 -that actually runs. Browse them in the -**[examples gallery](https://tmhsdigital.github.io/Blender-Developer-Tools/gallery/)**. +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. + +

+ Grease pencil rosette: five nested neon rose curves glowing against a dark studio wall + Parent inverse orrery: a brass tabletop orrery with a glowing sun and three planets + Compositor glare: three neon rings with colored bloom halos + Image pixels testcard: a studio monitor showing a procedural broadcast test card +

+ +
+Materials, shading & compositing — 5 examples @@ -67,74 +104,76 @@ mapping (`BLENDER_EEVEE` on 5.x, `BLENDER_EEVEE_NEXT` on 4.2-4.5). - - -
-Turntable: a copper Suzanne mid-rotation on a dark studio floor, rendered with EEVEE +Shader node group: a teal sphere and a magenta sphere sharing one TintedGloss node group with different Tint parameters -### [turntable](examples/turntable/) +### [shader-node-group](examples/shader-node-group/) -A slotted-actions Z-rotation turntable keyed through the cross-version channelbag path -(`get_channelbag_for_slot`). Witnesses the slotted-actions fix: ensure-helper channelbag on -5.x, `strip.channelbag` on 4.4/4.5. +One reusable `TintedGloss` group declared via `tree.interface.new_socket`, instanced in two +materials with different Tint values. Witnesses the grouping contract: shared datablock +(`users == 2`), parameters on the group **node** — two spheres, one group, two colors.
-Geometry Nodes SDF remesh: a crimson ceramic torus with visible voxel-remesh facets, resting on a dark studio floor +Compositor glare: three neon rings - violet, cyan, and amber - with colored bloom halos and mirrored reflections on a dark studio floor -### [gn-sdf-remesh](examples/gn-sdf-remesh/) +### [compositor-glare](examples/compositor-glare/) -A Geometry Nodes SDF remesh (`MeshToSDFGrid` → `GridToMesh` at the SDF zero-level). -Witnesses the fix that an SDF grid is meshed with **Grid to Mesh**, not Volume to Mesh, -and that a `Set Material` node carries the material through the remesh. +Bloom through the compositor on both sides of the 5.0 rewrite — a `Glare` (Fog Glow) +node fed by `Render Layers`, wired via `scene.compositing_node_group` on 5.x and +`scene.node_tree` on 4.x. Witnesses with pixels that the halo falls off strictly with +the compositor on and is exactly zero with it off — and that EEVEE has no `use_bloom`.
-Depsgraph-evaluated export: a matte graphite base cube beside its glossy green subdivision-surface evaluated form on a dark studio floor +Color attribute wheel: an HSV color wheel disc with a white center fading into a vivid rainbow rim, leaning upright toward the camera on a dark studio floor with a warm light pool behind it -### [depsgraph-export](examples/depsgraph-export/) +### [color-attribute-wheel](examples/color-attribute-wheel/) -A depsgraph-evaluated export — builds a cube with `SUBSURF`, measures the evaluated mesh via -`evaluated_get().to_mesh()` / `to_mesh_clear()`, and asserts `wm.obj_export` ships the -modifier-applied geometry (exported vertex count == evaluated > base). +The modern color-attributes API — `mesh.color_attributes.new()` on the `CORNER` +domain, not the deprecated `vertex_colors` alias, filled by expanding per-vertex +HSV across face corners with `foreach_get`/`foreach_set`. Asserts the attribute +is sized to loop count (not vertex count), is `active_color`, and that the +shader `Attribute` node is actually linked to Base Color.
-Wave displace: a glossy sapphire-blue surface displaced into smooth standing-wave dunes against a black backdrop +Image pixels testcard: a studio monitor showing a procedural broadcast test card — seven neon color bars behind the classic dark circle, a luminance ramp, and a PLUGE row with a white bottom-left origin marker — over a teal underglow on a dark studio floor -### [wave-displace](examples/wave-displace/) +### [image-pixels-testcard](examples/image-pixels-testcard/) -Bulk vertex IO at real scale — 9,409 vertices displaced into a standing wave with **one -`foreach_get` and one `foreach_set`**, no per-vertex access. Asserts the count is unchanged, -the Z span matches the amplitude, and a probe vertex matches the closed-form wave. +A procedural broadcast test card written into `bpy.data.images.new()` with one +`pixels.foreach_set()` call. Asserts the buffer is always flat RGBA (`channels == 4` +even with `alpha=False`), that byte storage quantizes at exactly ≤ 0.5/255 and +strictly > 0 while `float_buffer=True` round-trips at float32 precision, that +`scale()` reallocates (stale-size reads raise), and the `save()` trap: `source` +silently flips to `FILE`, the buffer drops, and later `pixels` reads come from +whatever sits on disk — proven with an imposter file. `save_render()` is the +non-destructive path.
-Driver wave: sixteen orange columns whose heights form a sine skyline on a dark studio floor, each driven by a driver_namespace function - +
-### [driver-wave](examples/driver-wave/) +
-A `driver_namespace` function driving sixteen column heights through SCRIPTED drivers. -Witnesses the evaluation contract: driven values appear after a view-layer update on the -evaluated copy **and** the flushed-back original, and both must match the closed form. +
+Mesh, curves & text — 6 examples - - + +
Bmesh gear: a machined steel 14-tooth gear at a three-quarter angle on a dark studio floor @@ -151,151 +190,163 @@ result is watertight (every edge borders exactly two faces).
-Shader node group: a teal sphere and a magenta sphere sharing one TintedGloss node group with different Tint parameters +Wave displace: a glossy sapphire-blue surface displaced into smooth standing-wave dunes against a black backdrop -### [shader-node-group](examples/shader-node-group/) +### [wave-displace](examples/wave-displace/) -One reusable `TintedGloss` group declared via `tree.interface.new_socket`, instanced in two -materials with different Tint values. Witnesses the grouping contract: shared datablock -(`users == 2`), parameters on the group **node** — two spheres, one group, two colors. +Bulk vertex IO at real scale — 9,409 vertices displaced into a standing wave with **one +`foreach_get` and one `foreach_set`**, no per-vertex access. Asserts the count is unchanged, +the Z span matches the amplitude, and a probe vertex matches the closed-form wave.
-Temp-override join: an amber three-step staircase of joined unit cubes on a dark studio floor +Shape-key blend: three violet blocks on a dark studio floor showing a relative Tall shape key at values 0, 0.25, and 0.5 - plain cube to lifted, flared truncated pyramid, left to right -### [temp-override-join](examples/temp-override-join/) +### [shape-key-blend](examples/shape-key-blend/) -Three unit cubes joined into a staircase under `bpy.context.temp_override` — the supported -replacement for the removed `context.copy()` dict-pass form. Asserts one mesh remains, -sources are gone, and local Z spans all three steps. +A relative Tall shape key that lifts and flares the top face — authored through +`shape_key_add` / `key_blocks` / `.value`. Witnesses that shape keys do not rewrite +`mesh.vertices`: every evaluated vert matches `basis + value × (key − basis)`.
-GN instance grid: nine lime-green cubes in a 3x3 grid on a dark studio floor, instanced via Geometry Nodes Instance on Points +Curve bevel arc: a rose beveled Bezier semicircle tube resting on a dark studio floor -### [gn-instance-grid](examples/gn-instance-grid/) +### [curve-bevel-arc](examples/curve-bevel-arc/) -A generative Geometry Nodes tree — Mesh Grid → Instance on Points → Realize Instances — -attached as a `NODES` modifier with no Group Input. Asserts evaluated topology is -verts = 72, faces = 54, and `Set Material` carries the lime accent. +A beveled Bezier semicircle authored on `bpy.types.Curve` — `splines.new('BEZIER')`, +`bezier_points`, `bevel_depth`, `use_fill_caps` — so the curve renders as a solid tube +without a prior mesh conversion. Asserts eight points, `bevel_depth == 0.15`, and +evaluated topology 1044 verts / 1028 faces.
-Shape-key blend: three violet blocks on a dark studio floor showing a relative Tall shape key at values 0, 0.25, and 0.5 - plain cube to lifted, flared truncated pyramid, left to right +Text version stamp: beveled brass 5.1.2 numerals standing on a dark studio floor over a glowing teal underline, with a small steel BLENDER caption above — the body text is the live bpy.app.version_string -### [shape-key-blend](examples/shape-key-blend/) +### [text-version-stamp](examples/text-version-stamp/) -A relative Tall shape key that lifts and flares the top face — authored through -`shape_key_add` / `key_blocks` / `.value`. Witnesses that shape keys do not rewrite -`mesh.vertices`: every evaluated vert matches `basis + value × (key − basis)`. +A beveled 3D stamp of the running Blender version — a `TextCurve` whose `body` is the +live `bpy.app.version_string`, so every render self-documents which Blender made it. +Asserts the TextCurve solids closed form (evaluated z-extent = 2 × (extrude + +bevel_depth), bevel widening the outline by 2 × bevel_depth), that flat text is filled +but planar, that body edits regenerate geometry, that `version_string` is not bare +semver on LTS builds (`"4.5.11 LTS"`), and that a Mesh reference dies at +`to_mesh_clear()`.
-Curve bevel arc: a rose beveled Bezier semicircle tube resting on a dark studio floor +Depsgraph-evaluated export: a matte graphite base cube beside its glossy green subdivision-surface evaluated form on a dark studio floor -### [curve-bevel-arc](examples/curve-bevel-arc/) +### [depsgraph-export](examples/depsgraph-export/) -A beveled Bezier semicircle authored on `bpy.types.Curve` — `splines.new('BEZIER')`, -`bezier_points`, `bevel_depth`, `use_fill_caps` — so the curve renders as a solid tube -without a prior mesh conversion. Asserts eight points, `bevel_depth == 0.15`, and -evaluated topology 1044 verts / 1028 faces. +A depsgraph-evaluated export — builds a cube with `SUBSURF`, measures the evaluated mesh via +`evaluated_get().to_mesh()` / `to_mesh_clear()`, and asserts `wm.obj_export` ships the +modifier-applied geometry (exported vertex count == evaluated > base).
+ +
+ +
+Geometry Nodes — 2 examples + + +
-Compositor glare: three neon rings - violet, cyan, and amber - with colored bloom halos and mirrored reflections on a dark studio floor +Geometry Nodes SDF remesh: a crimson ceramic torus with visible voxel-remesh facets, resting on a dark studio floor -### [compositor-glare](examples/compositor-glare/) +### [gn-sdf-remesh](examples/gn-sdf-remesh/) -Bloom through the compositor on both sides of the 5.0 rewrite — a `Glare` (Fog Glow) -node fed by `Render Layers`, wired via `scene.compositing_node_group` on 5.x and -`scene.node_tree` on 4.x. Witnesses with pixels that the halo falls off strictly with -the compositor on and is exactly zero with it off — and that EEVEE has no `use_bloom`. +A Geometry Nodes SDF remesh (`MeshToSDFGrid` → `GridToMesh` at the SDF zero-level). +Witnesses the fix that an SDF grid is meshed with **Grid to Mesh**, not Volume to Mesh, +and that a `Set Material` node carries the material through the remesh.
-Damped Track aim: brass spikes in a tilted cage around an ember core on a dark studio plinth, each tip constrained toward the center +GN instance grid: nine lime-green cubes in a 3x3 grid on a dark studio floor, instanced via Geometry Nodes Instance on Points -### [damped-track-aim](examples/damped-track-aim/) +### [gn-instance-grid](examples/gn-instance-grid/) -Aim constraints via the data API — `Object.constraints.new('DAMPED_TRACK')` with -`target` and `TRACK_Z`, not `bpy.ops.object.constraint_add` in a headless loop. -Asserts twelve unmuted Damped Track constraints and evaluated local `+Z` alignment -toward the core (dot ≥ 0.998). +A generative Geometry Nodes tree — Mesh Grid → Instance on Points → Realize Instances — +attached as a `NODES` modifier with no Group Input. Asserts evaluated topology is +verts = 72, faces = 54, and `Set Material` carries the lime accent.
+ +
+ +
+Animation, rigging & constraints — 5 examples + + @@ -318,43 +369,63 @@ ring pinned and the tip deflected. A straight tube is a failure. +
-Color attribute wheel: an HSV color wheel disc with a white center fading into a vivid rainbow rim, leaning upright toward the camera on a dark studio floor with a warm light pool behind it +Turntable: a copper Suzanne mid-rotation on a dark studio floor, rendered with EEVEE -### [color-attribute-wheel](examples/color-attribute-wheel/) +### [turntable](examples/turntable/) -The modern color-attributes API — `mesh.color_attributes.new()` on the `CORNER` -domain, not the deprecated `vertex_colors` alias, filled by expanding per-vertex -HSV across face corners with `foreach_get`/`foreach_set`. Asserts the attribute -is sized to loop count (not vertex count), is `active_color`, and that the -shader `Attribute` node is actually linked to Base Color. +A slotted-actions Z-rotation turntable keyed through the cross-version channelbag path +(`get_channelbag_for_slot`). Witnesses the slotted-actions fix: ensure-helper channelbag on +5.x, `strip.channelbag` on 4.4/4.5.
-Parent inverse orrery: a brass tabletop orrery with a glowing sun, three planets on pivot arms inside brass orbit rings, and a silver moon, on a dark studio floor +Driver wave: sixteen orange columns whose heights form a sine skyline on a dark studio floor, each driven by a driver_namespace function -### [parent-inverse-orrery](examples/parent-inverse-orrery/) +### [driver-wave](examples/driver-wave/) -A brass orrery parented entirely through the data API — the keep-world idiom -`child.parent = pivot; child.matrix_parent_inverse = pivot.matrix_world.inverted()` -carries arms, planets, and a two-level moon through spinning pivots. Asserts bare -`.parent =` really teleports, `matrix_world` is stale until `view_layer.update()`, -and every orbit lands on its closed form. +A `driver_namespace` function driving sixteen column heights through SCRIPTED drivers. +Witnesses the evaluation contract: driven values appear after a view-layer update on the +evaluated copy **and** the flushed-back original, and both must match the closed form.
-Grease pencil rosette: five nested neon rose curves drawn as tapered Grease Pencil v3 strokes, cyan through magenta to red, glowing against a dark studio wall with a soft blue halo +Damped Track aim: brass spikes in a tilted cage around an ember core on a dark studio plinth, each tip constrained toward the center -### [grease-pencil-rosette](examples/grease-pencil-rosette/) +### [damped-track-aim](examples/damped-track-aim/) -Five nested rose curves drawn with the Grease Pencil v3 attribute API — layer → -`frames.new().drawing` → `add_strokes` → per-point position, radius, opacity, and -vertex color. Asserts the GPv3 address break: on 4.5 GPv3 is `grease_pencils_v3` -while `grease_pencils` is still legacy; on 5.x legacy is gone and GPv3 owns the -name. Point writes lazily materialize attribute layers, and every position -round-trips through the raw `POINT` buffer. +Aim constraints via the data API — `Object.constraints.new('DAMPED_TRACK')` with +`target` and `TRACK_Z`, not `bpy.ops.object.constraint_add` in a headless loop. +Asserts twelve unmuted Damped Track constraints and evaluated local `+Z` alignment +toward the core (dot ≥ 0.998).
-Text version stamp: beveled brass 5.1.2 numerals standing on a dark studio floor over a glowing teal underline, with a small steel BLENDER caption above — the body text is the live bpy.app.version_string +Parent inverse orrery: a brass tabletop orrery with a glowing sun, three planets on pivot arms inside brass orbit rings, and a silver moon, on a dark studio floor -### [text-version-stamp](examples/text-version-stamp/) +### [parent-inverse-orrery](examples/parent-inverse-orrery/) -A beveled 3D stamp of the running Blender version — a `TextCurve` whose `body` is the -live `bpy.app.version_string`, so every render self-documents which Blender made it. -Asserts the TextCurve solids closed form (evaluated z-extent = 2 × (extrude + -bevel_depth), bevel widening the outline by 2 × bevel_depth), that flat text is filled -but planar, that body edits regenerate geometry, that `version_string` is not bare -semver on LTS builds (`"4.5.11 LTS"`), and that a Mesh reference dies at -`to_mesh_clear()`. +A brass orrery parented entirely through the data API — the keep-world idiom +`child.parent = pivot; child.matrix_parent_inverse = pivot.matrix_world.inverted()` +carries arms, planets, and a two-level moon through spinning pivots. Asserts bare +`.parent =` really teleports, `matrix_world` is stale until `view_layer.update()`, +and every orbit lands on its closed form.
+ +
+ +
+Context & Grease Pencil — 2 examples + + + + + +
-Image pixels testcard: a studio monitor showing a procedural broadcast test card — seven neon color bars behind the classic dark circle, a luminance ramp, and a PLUGE row with a white bottom-left origin marker — over a teal underglow on a dark studio floor +Temp-override join: an amber three-step staircase of joined unit cubes on a dark studio floor -### [image-pixels-testcard](examples/image-pixels-testcard/) +### [temp-override-join](examples/temp-override-join/) -A procedural broadcast test card written into `bpy.data.images.new()` with one -`pixels.foreach_set()` call. Asserts the buffer is always flat RGBA (`channels == 4` -even with `alpha=False`), that byte storage quantizes at exactly ≤ 0.5/255 and -strictly > 0 while `float_buffer=True` round-trips at float32 precision, that -`scale()` reallocates (stale-size reads raise), and the `save()` trap: `source` -silently flips to `FILE`, the buffer drops, and later `pixels` reads come from -whatever sits on disk — proven with an imposter file. `save_render()` is the -non-destructive path. +Three unit cubes joined into a staircase under `bpy.context.temp_override` — the supported +replacement for the removed `context.copy()` dict-pass form. Asserts one mesh remains, +sources are gone, and local Z spans all three steps. + +
+Grease pencil rosette: five nested neon rose curves drawn as tapered Grease Pencil v3 strokes, cyan through magenta to red, glowing against a dark studio wall with a soft blue halo + + +### [grease-pencil-rosette](examples/grease-pencil-rosette/) + +Five nested rose curves drawn with the Grease Pencil v3 attribute API — layer → +`frames.new(1).drawing` → `add_strokes` → per-point position, radius, opacity, and +vertex color. Asserts the GPv3 address break: on 4.5 GPv3 is `grease_pencils_v3` +while `grease_pencils` is still legacy; on 5.x legacy is gone and GPv3 owns the +name. Point writes lazily materialize attribute layers, and every position +round-trips through the raw `POINT` buffer.
+
+ ## How content is organized ``` @@ -400,7 +471,17 @@ When community content (Stack Overflow, older add-on source) conflicts with the ## Roadmap -See [ROADMAP.md](ROADMAP.md). v0.2.0 shipped procedural materials, depsgraph queries, drivers and app handlers, `bl_info` to manifest migration, two new rules, and the headless batch script template. v0.3.0 candidates include modal operators, USD pipelines, and `mathutils` patterns. +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 +conventional commits; the full history lives in [CHANGELOG.md](CHANGELOG.md). + +## Contributing + +Issues and pull requests are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for the +workflow, [SECURITY.md](SECURITY.md) for reporting vulnerabilities, and +[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for community standards. New examples must +follow the anatomy of [`examples/bmesh-gear/`](examples/bmesh-gear/) and the render +look in [`docs/VISUAL-STYLE.md`](docs/VISUAL-STYLE.md). ## License