Skip to content

Commit a4d0d45

Browse files
TMHSDigitalclaude
andcommitted
feat: add parent-inverse-orrery example (matrix_parent_inverse contract)
Generated Blender code constantly writes `child.parent = pivot` and is surprised the child teleports, then reads `matrix_world` before the depsgraph has evaluated it. No existing example covers object parenting or the transform-update contract, so this one witnesses both with a brass orrery whose hierarchy is the subject: arms, planets, and a two-level moon parented with the keep-world idiom (matrix_parent_inverse = parent.matrix_world.inverted()), spun to closed-form orbit positions the check verifies exactly. The trap itself is asserted too - a bare-parented probe must jump, and the fix must restore its world position to within 1e-5. Runs check-only headless on Blender 4.5 LTS and 5.1 (both verified locally on 4.5.11 and 5.1.2); --output renders the gallery still. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 4733e7f commit a4d0d45

12 files changed

Lines changed: 1032 additions & 7 deletions

File tree

.cursor-plugin/plugin.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
"examples/driver-wave",
6969
"examples/gn-instance-grid",
7070
"examples/gn-sdf-remesh",
71+
"examples/parent-inverse-orrery",
7172
"examples/shader-node-group",
7273
"examples/shape-key-blend",
7374
"examples/swatch-grid",

.github/workflows/blender-smoke.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,14 @@ jobs:
264264
# failure.
265265
xvfb-run -a "$BLENDER" --background \
266266
--python examples/color-attribute-wheel/color_attribute_wheel.py --
267+
268+
- name: Shipped example - parent inverse orrery (matrix_parent_inverse)
269+
run: |
270+
set -euo pipefail
271+
# Frame-independent check only (no render): a data-API orrery whose arms,
272+
# planets, and moon are parented with the keep-world idiom; asserts bare
273+
# `.parent =` teleports a probe, matrix_parent_inverse restores it exactly,
274+
# matrix_world is stale until view_layer.update(), and every orbit lands
275+
# on its closed form. Exits non-zero on failure.
276+
xvfb-run -a "$BLENDER" --background \
277+
--python examples/parent-inverse-orrery/parent_inverse_orrery.py --

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ The content base (counts are CI-enforced against README.md and the manifest):
2727
- 2 templates: `extension-addon-template` for Extensions Platform add-ons,
2828
and `headless-batch-script-template` for unattended batch jobs.
2929
- 17 snippets covering canonical patterns.
30-
- 15 examples under `examples/<name>/`: runnable scripts that assert a real
30+
- 16 examples under `examples/<name>/`: runnable scripts that assert a real
3131
API contract with deterministic checks, exit non-zero on failure, and
3232
optionally render a still via `--output`. Each is executed headless on
3333
Blender 4.5 LTS and 5.1 by `blender-smoke.yml`; its render ships in the
@@ -41,7 +41,7 @@ Blender-Developer-Tools/
4141
rules/<rule-name>.mdc # 6 rule files
4242
templates/<template-name>/ # 2 starter templates
4343
snippets/<snippet-name>.py # 17 standalone Python snippets
44-
examples/<name>/ # 15 runnable smoke-gated examples (+ gallery.json)
44+
examples/<name>/ # 16 runnable smoke-gated examples (+ gallery.json)
4545
scripts/build_gallery.py # generates docs/gallery/ (stdlib only)
4646
scripts/site/ # vendored landing-page build (build_site.py + template)
4747
docs/gallery/ # committed generated gallery pages + hero assets

CLAUDE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ skills/<skill-name>/SKILL.md - AI workflow definitions, 12 total
1919
rules/<rule-name>.mdc - Anti-pattern rules, 6 total
2020
templates/<template-name>/ - Starter projects, 2 total
2121
snippets/<snippet-name>.py - Standalone code patterns, 17 total
22-
examples/<name>/ - Runnable smoke-gated examples, 15 total (+ gallery.json)
22+
examples/<name>/ - Runnable smoke-gated examples, 16 total (+ gallery.json)
2323
scripts/build_gallery.py - Regenerates docs/gallery/ from gallery.json (stdlib only)
2424
scripts/site/ - Vendored landing-page build (Jinja2)
2525
docs/gallery/ - Committed generated gallery pages + hero renders
@@ -80,11 +80,11 @@ v0.1.0: canonical object creation and deletion, depsgraph evaluated mesh, bmesh
8080

8181
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'`.
8282

83-
## Examples (15)
83+
## Examples (16)
8484

8585
Runnable scripts at `examples/<name>/`, each asserting a real API contract with
8686
deterministic checks (exit non-zero on failure) and optionally rendering a still via
87-
`--output`. All fifteen run headless on Blender 4.5 LTS and 5.1 in `blender-smoke.yml`;
87+
`--output`. All sixteen run headless on Blender 4.5 LTS and 5.1 in `blender-smoke.yml`;
8888
their renders ship in the site gallery at `docs/gallery/`. `examples/gallery.json` is the
8989
gallery's source of truth. When authoring a new one, copy the anatomy of
9090
`examples/bmesh-gear/` (script structure, README shape, dark-studio render recipe) and

README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717
</p>
1818

1919
<p align="center">
20-
<strong>12 skills</strong> &nbsp;&bull;&nbsp; <strong>6 rules</strong> &nbsp;&bull;&nbsp; <strong>2 templates</strong> &nbsp;&bull;&nbsp; <strong>17 snippets</strong> &nbsp;&bull;&nbsp; <strong>15 examples</strong>
20+
<strong>12 skills</strong> &nbsp;&bull;&nbsp; <strong>6 rules</strong> &nbsp;&bull;&nbsp; <strong>2 templates</strong> &nbsp;&bull;&nbsp; <strong>17 snippets</strong> &nbsp;&bull;&nbsp; <strong>16 examples</strong>
2121
</p>
2222

2323
---
2424

2525
## Overview
2626

27-
This repository ships **12 skills, 6 rules, 2 templates, 17 snippets, and 15 runnable examples** for Blender Python development targeting Blender 5.1 (current stable) with Blender 4.5 LTS fallback support.
27+
This repository ships **12 skills, 6 rules, 2 templates, 17 snippets, and 16 runnable examples** for Blender Python development targeting Blender 5.1 (current stable) with Blender 4.5 LTS fallback support.
2828

2929
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.
3030

@@ -264,6 +264,22 @@ HSV across face corners with `foreach_get`/`foreach_set`. Asserts the attribute
264264
is sized to loop count (not vertex count), is `active_color`, and that the
265265
shader `Attribute` node is actually linked to Base Color.
266266

267+
</td>
268+
</tr>
269+
<tr>
270+
<td width="46%" valign="middle">
271+
<a href="examples/parent-inverse-orrery/"><img src="examples/parent-inverse-orrery/preview.webp" alt="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" /></a>
272+
</td>
273+
<td valign="middle">
274+
275+
### [parent-inverse-orrery](examples/parent-inverse-orrery/)
276+
277+
A brass orrery parented entirely through the data API — the keep-world idiom
278+
`child.parent = pivot; child.matrix_parent_inverse = pivot.matrix_world.inverted()`
279+
carries arms, planets, and a two-level moon through spinning pivots. Asserts bare
280+
`.parent =` really teleports, `matrix_world` is stale until `view_layer.update()`,
281+
and every orbit lands on its closed form.
282+
267283
</td>
268284
</tr>
269285
</table>
28.3 KB
Loading

docs/gallery/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,12 @@ <h1>Examples Gallery</h1>
190190
<button class="chip" data-tag="materials" type="button">materials</button>
191191
<button class="chip" data-tag="mesh" type="button">mesh</button>
192192
<button class="chip" data-tag="node-groups" type="button">node-groups</button>
193+
<button class="chip" data-tag="objects" type="button">objects</button>
193194
<button class="chip" data-tag="operators" type="button">operators</button>
194195
<button class="chip" data-tag="performance" type="button">performance</button>
195196
<button class="chip" data-tag="rendering" type="button">rendering</button>
196197
<button class="chip" data-tag="shape-keys" type="button">shape-keys</button>
198+
<button class="chip" data-tag="transforms" type="button">transforms</button>
197199
</div>
198200
<main id="main">
199201
<div class="grid">
@@ -362,6 +364,17 @@ <h2><a href="color-attribute-wheel/">color-attribute-wheel</a></h2>
362364
<a class="card-link" href="color-attribute-wheel/">View example <span aria-hidden="true">&rarr;</span></a>
363365
</div>
364366
</article>
367+
<article class="card" data-tags="objects transforms">
368+
<a class="card-media" href="parent-inverse-orrery/" aria-label="parent-inverse-orrery example detail page">
369+
<img src="assets/parent-inverse-orrery-hero.webp" alt="parent-inverse-orrery — Data-API parenting for a brass orrery — the keep-world idiom (child" loading="lazy" decoding="async" />
370+
</a>
371+
<div class="card-body">
372+
<h2><a href="parent-inverse-orrery/">parent-inverse-orrery</a></h2>
373+
<p class="teaches">Data-API parenting for a brass orrery — the keep-world idiom (child.parent = pivot; child.matrix_parent_inverse = pivot.matrix_world.inverted()) carrying arms, planets, and a two-level moon through spinning pivots.</p>
374+
<p class="witnesses"><span class="tag">witnesses</span> Bare `.parent =` really does teleport the child; the idiom restores world position exactly; matrix_world stays stale until view_layer.update(); every orbit lands on its closed form.</p>
375+
<a class="card-link" href="parent-inverse-orrery/">View example <span aria-hidden="true">&rarr;</span></a>
376+
</div>
377+
</article>
365378
</div>
366379
</main>
367380
<footer>

0 commit comments

Comments
 (0)