Skip to content

Commit 4bfe83b

Browse files
authored
feat: add socket-attach-points example (spawn mount contract) (#118)
A spawn system bolting modules onto a prop at runtime rests on a chain of matrix identities that all fail quietly: a skipped matrix_parent_inverse, a basis built with the cross product the wrong way round, a transform frozen on the parent after the sockets were placed. Nothing in the gallery pinned that chain down — prop-origin-transform covers pivots and parent-inverse-orrery covers parent-inverse under animation, but neither asserts that a module dropped on a named socket with an identity local transform lands where the artist put it. The socket orientation check is deliberately non-circular: the mount pad mesh is swung onto its declared normal by a quaternion, while the socket basis is built by explicit Gram-Schmidt, and the check compares the socket's +Z against a Newell normal recomputed from raw vertex coordinates. Two independent derivations of the same declared value, so a basis-construction bug cannot hide behind the spec that produced it. Two hazards surfaced while authoring and are asserted rather than merely described, so they cannot change under us silently: applying a transform on an Empty root has no object data to bake into, so Blender pushes it down into every child (all 7 parent-inverses cleared, root scale handed to each child's local matrix) while preserving world matrices; and a child left selected during that apply gets the transform applied twice, drifting sockets 2.335 m. Signed-off-by: fOuttaMyPaint <tmhospitalitystrategies@gmail.com> Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
1 parent 7931829 commit 4bfe83b

13 files changed

Lines changed: 2728 additions & 3 deletions

File tree

.cursor-plugin/plugin.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
"examples/shape-key-blend",
9292
"examples/sky-texture-sun-elevation",
9393
"examples/soccer-ball-goldberg",
94+
"examples/socket-attach-points",
9495
"examples/swatch-grid",
9596
"examples/temp-override-join",
9697
"examples/text-version-stamp",

.github/workflows/blender-smoke.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,3 +593,19 @@ jobs:
593593
# non-zero on failure.
594594
xvfb-run -a "$BLENDER" --background \
595595
--python examples/lightmap-uv-channel/lightmap_uv_channel.py --
596+
597+
- name: Shipped example - socket attach points (spawn mount contract)
598+
run: |
599+
set -euo pipefail
600+
# Check only (no render): a 21-part survey drone with 7 SKT_ socket
601+
# empties and 7 seated modules; asserts every socket's evaluated
602+
# world matrix equals the authored transform within 1e-6 with an
603+
# orthonormal right-handed basis, socket +Z == the mount pad's
604+
# Newell normal and origin == that face's centroid (two independent
605+
# derivations), the documented up-axis fallback rule, modules seated
606+
# with a strictly identity local transform, rigid invariance under a
607+
# re-pose, and that freezing the root transform preserves world
608+
# matrices while clearing all 7 parent-inverses and pushing the
609+
# root scale down into the children. Exits non-zero on failure.
610+
xvfb-run -a "$BLENDER" --background \
611+
--python examples/socket-attach-points/socket_attach_points.py --

README.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
</p>
1919

2020
<p align="center">
21-
<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>42 examples</strong>
21+
<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>43 examples</strong>
2222
</p>
2323

2424
<p align="center">
@@ -36,7 +36,7 @@
3636

3737
## Overview
3838

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

4141
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.
4242

@@ -820,6 +820,29 @@ by an independent SAT scan, and a measured min island distance of
820820
[`uv-layer-grid`](examples/uv-layer-grid/) (UV authoring) and
821821
[`triangulate-tangents`](examples/triangulate-tangents/) (tangents from UVs).
822822

823+
</td>
824+
</tr>
825+
<tr>
826+
<td width="46%" valign="middle">
827+
<a href="examples/socket-attach-points/"><img src="examples/socket-attach-points/preview.webp" alt="Socket attach points: a dark carbon survey quadcopter hovering on a studio stage, orange mount pads ringing each canted rotor, a gimbal camera pod under the belly and a sensor mast on the deck — every module seated exactly on its named socket" /></a>
828+
</td>
829+
<td valign="middle">
830+
831+
### [socket-attach-points](examples/socket-attach-points/)
832+
833+
Named `SKT_` empties as the spawn contract, on a reusable survey drone. All
834+
**7** socket world matrices land within **1.788e-07** of the authored
835+
transform with orthonormal right-handed bases, each socket's **+Z** equals its
836+
mount pad's **Newell normal** (**1.794e-07**) by a derivation independent of
837+
the basis construction, and modules seat at offset **exactly 0.0** with
838+
identity local transforms. Freezing the root preserves world matrices but
839+
clears **7/7** parent-inverses and pushes the root scale into the children —
840+
both halves asserted. The no-parent-inverse probe jumps **0.690128 m**.
841+
Companion to [`prop-origin-transform`](examples/prop-origin-transform/)
842+
(pivot discipline) and
843+
[`parent-inverse-orrery`](examples/parent-inverse-orrery/) (parent-inverse
844+
under animation).
845+
823846
</td>
824847
</tr>
825848
</table>

ROADMAP.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo
111111
- ~~Degenerate-bevel weld hazard~~ **SHIPPED** as `examples/degenerate-bevel-weld/` — at `offset == min_dim/2` the bevel band collapses to exactly 12 zero-area faces (4 min-axis edges × 3 segments) with 16 coincident verts (4 × (segments+1)) and min_area down 3.2e5×; a stdlib GLB re-parse counts **32 degenerate triangles crossing the export boundary** (MEASURED regression constant) versus 0 for the safe mesh — the loops an engine merge-by-distance welds; byte-identical on 4.5.11 and 5.1.2
112112
- ~~Modular kit snap witness~~ **SHIPPED** as `examples/modular-kit-snap/` — tiling corridor segment: 16 boundary verts on `x ∈ {0, TILE}` within 1e-6 m, end rings coincident under the tile offset (nearest-key match), linked-duplicate joint deviation 0.0, shell bbox == declared tile (dev 0.0), manifold except the 16 open-end edges, 20 detail parts contained inside the tile; unsnapped probe (3 mm skew, 2 mm nudge) fails at exits 3/4 with measured errors; sorted-zip ring matching mispairs displaced verts — match by nearest key (authoring hazard, fixed); byte-identical on 4.5.11 and 5.1.2
113113
- ~~Lightmap UV channel witness~~ **SHIPPED** as `examples/lightmap-uv-channel/` — two-channel UV contract on a market cart: per-part `UVMap`/`UVLight` with `active`/`active_render` re-asserted after the ops (edit-mode UV ops clear both flags; `uv_layers.new()` never moves them), UV0 drift 0.0 (clobber trap measured 2.068), every UV1 loop in [0,1], 0 overlapping islands via independent binned strict-SAT (15 hits when falsified), min island distance 0.00401 vs nominal margin 0.002 (`MARGIN_DIV × 0.01` semantics measured live); held `MeshUVLoopLayer.data` after edit-mode UV ops segfaults 4.5.11 (5/5, EXCEPTION_ACCESS_VIOLATION), silent no-op pack without prior unwrap; byte-identical on 4.5.11 and 5.1.2 (3680 islands)
114+
- ~~Socket attach-point witness~~ **SHIPPED** as `examples/socket-attach-points/` — named `SKT_` empties as the spawn contract on a survey drone: 7 socket world matrices within 1.788e-07 of the authored transform with orthonormal right-handed bases (Gram 3.576e-07, det err 2.980e-07), socket +Z == the mount pad's Newell normal (1.794e-07) by a quaternion-swing construction independent of the Gram-Schmidt basis, origin on the mount-face centroid (6.687e-08), documented up-axis fallback for the +Z/−Z mounts, module seating offset exactly 0.0 with strictly identity local matrices, rigid invariance under a re-pose (2.384e-07); `transform_apply` on an **Empty** root preserves world matrices (2.235e-07) but clears **7/7** `matrix_parent_inverse` and pushes the root scale down into every child (0.35 for an applied 1.35) — both halves asserted; a child left selected during the apply drifts sockets **2.335 m**; `bm.normal_update()` does not fix inward winding (`recalc_face_normals` does — the lofted fuselage rendered as a flat white panel); no-parent-inverse probe jumps 0.690128 m; byte-identical on 4.5.11 and 5.1.2
114115
- UV atlas **utilization** witness — coverage/wasted-texel closed forms for a packed lightmap atlas (the non-overlap, unit-square, margin, and active/active_render contracts shipped in `lightmap-uv-channel`; utilization is the remaining unbuilt slice of the old "UV atlas pack" candidate)
115116
- ~~GAMMA_CROSS blend-curve witness~~ **SHIPPED** as `examples/vse-gamma-cross/` — the cross blends in a gamma-0.5 space: `((1-t)·√A + t·√B)²` with `t = (frame − start)/duration`, never 1 inside the effect; mid-cross dips 0.115 below the sRGB lerp from crimson/teal (closed form (0.341, 0.349, 0.463) confirmed per frame); AgX-default sampling poisons the fit (0.146 red-channel error, `view_transform='Standard'` mandatory); deleting a consumed input orphans-and-deletes the effect — follow-up to `vse-cut-list`
116117
- Falsy `bpy_prop_collection` trap snippet: an empty collection is falsy, so `editor.strips or editor.sequences` silently falls through to the legacy accessor on an empty timeline — always branch on `hasattr`; likely generalizes across the API (found authoring `vse-cut-list`)
30.4 KB
Loading
18.8 KB
Loading
45.9 KB
Loading

docs/gallery/index.html

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ <h1>Examples Gallery</h1>
272272
autocomplete="off" spellcheck="false" aria-label="Search examples" />
273273
<button class="q-clear" id="qClear" type="button" aria-label="Clear search" hidden>&times;</button>
274274
</div>
275-
<span class="count" id="count" role="status" aria-live="polite">42 examples</span>
275+
<span class="count" id="count" role="status" aria-live="polite">43 examples</span>
276276
<div class="density" role="group" aria-label="Card density">
277277
<button class="density-btn" data-density="compact" type="button" aria-pressed="false">Compact</button>
278278
<button class="density-btn" data-density="detailed" type="button" aria-pressed="false">Detailed</button>
@@ -784,6 +784,17 @@ <h2><a href="lightmap-uv-channel/">lightmap-uv-channel</a></h2>
784784
<a class="card-link" href="lightmap-uv-channel/">View example <span aria-hidden="true">&rarr;</span></a>
785785
</div>
786786
</article>
787+
<article class="card" data-tags="mesh transforms game-pipeline instancing">
788+
<a class="card-media" href="socket-attach-points/" aria-label="socket-attach-points example detail page">
789+
<img src="assets/socket-attach-points-hero.webp" alt="socket-attach-points — A survey drone whose named SKT_ empties are the spawn contract: modules parented with an identity local transform seat exactly on their mount pads." loading="lazy" decoding="async" />
790+
</a>
791+
<div class="card-body">
792+
<h2><a href="socket-attach-points/">socket-attach-points</a></h2>
793+
<p class="teaches">A survey drone whose named SKT_ empties are the spawn contract: modules parented with an identity local transform seat exactly on their mount pads.</p>
794+
<p class="witnesses"><span class="tag">witnesses</span> 7 socket world matrices within 1.788e-07 of the authored transform, socket +Z == the pad&#x27;s Newell normal (1.794e-07), seating offset 0.0; the no-parent-inverse probe jumps 0.690128 m.</p>
795+
<a class="card-link" href="socket-attach-points/">View example <span aria-hidden="true">&rarr;</span></a>
796+
</div>
797+
</article>
787798
</div>
788799
<p class="noresults" id="noResults" hidden>No examples match the current filters.
789800
<button class="chip" id="resetFilters" type="button">Clear search and tags</button></p>

0 commit comments

Comments
 (0)