-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathgallery.json
More file actions
563 lines (563 loc) · 34 KB
/
Copy pathgallery.json
File metadata and controls
563 lines (563 loc) · 34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
{
"_comment": "FORWARD-COMPATIBLE SOURCE OF TRUTH for the examples gallery. The local page at docs/gallery/index.html is GENERATED from this file by scripts/build_gallery.py -- do not hand-edit the HTML. When the fleet template (Developer-Tools-Directory: site-template/build_site.py + template.html.j2) gains examples support (see ROADMAP: 'Fleet Pages examples support'), it reads this same file and the local page is retired. That migration is a lift-and-shift, not a rewrite: keep this schema stable. Per-entry schema: {name, dir, teaches, witnessesFix, hero, preview, tags?}; hero/preview/dir are repo-root-relative; tags is an optional additive list driving the gallery filter chips. build_gallery.py also emits a detail page per example at docs/gallery/<name>/.",
"title": "Examples Gallery",
"description": "Runnable, smoke-gated Blender Python examples — each executed headless on Blender 4.5 LTS and 5.1, so every render reflects code that actually runs.",
"repoBaseUrl": "https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main",
"siteBaseUrl": "https://tmhsdigital.github.io/Blender-Developer-Tools",
"examples": [
{
"name": "swatch-grid",
"dir": "examples/swatch-grid",
"teaches": "Procedural Principled materials — metal and dielectric, the emission pattern, and the cross-version set_specular shim.",
"witnessesFix": "EEVEE engine-id mapping: BLENDER_EEVEE on 5.x, BLENDER_EEVEE_NEXT on 4.2–4.5.",
"hero": "docs/gallery/assets/swatch-grid-hero.webp",
"preview": "examples/swatch-grid/preview.webp",
"tags": [
"materials",
"rendering"
]
},
{
"name": "turntable",
"dir": "examples/turntable",
"teaches": "A slotted-actions Z-rotation turntable keyed through the cross-version channelbag path (get_channelbag_for_slot).",
"witnessesFix": "Slotted-actions boundary: ensure-helper channelbag on 5.x, strip.channelbag on 4.4/4.5.",
"hero": "docs/gallery/assets/turntable-hero.webp",
"preview": "examples/turntable/preview.webp",
"tags": [
"animation"
]
},
{
"name": "gn-sdf-remesh",
"dir": "examples/gn-sdf-remesh",
"teaches": "A Geometry Nodes SDF remesh (MeshToSDFGrid → GridToMesh at the SDF zero-level), with a Set Material node carrying the material through the remesh.",
"witnessesFix": "An SDF grid is meshed with Grid to Mesh, not Volume to Mesh; GN geometry needs Set Material or it renders untextured.",
"hero": "docs/gallery/assets/gn-sdf-remesh-hero.webp",
"preview": "examples/gn-sdf-remesh/preview.webp",
"tags": [
"geometry-nodes",
"materials"
]
},
{
"name": "depsgraph-export",
"dir": "examples/depsgraph-export",
"teaches": "The depsgraph lifetime contract — evaluated_get().to_mesh() paired with to_mesh_clear() — measured against an OBJ export of the same object.",
"witnessesFix": "Exports ship evaluated geometry: the exported vertex count equals the subsurf-applied count and is strictly greater than the base mesh.",
"hero": "docs/gallery/assets/depsgraph-export-hero.webp",
"preview": "examples/depsgraph-export/preview.webp",
"tags": [
"depsgraph",
"export"
]
},
{
"name": "wave-displace",
"dir": "examples/wave-displace",
"teaches": "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.",
"witnessesFix": "The bulk path is correct, not just fast: the Z span matches the wave amplitude and every vertex matches the closed-form wave, so a stride bug in the flat buffer cannot hide.",
"hero": "docs/gallery/assets/wave-displace-hero.webp",
"preview": "examples/wave-displace/preview.webp",
"tags": [
"mesh",
"performance"
]
},
{
"name": "driver-wave",
"dir": "examples/driver-wave",
"teaches": "A driver_namespace function driving sixteen column heights through SCRIPTED drivers — the sine skyline is entirely driver-evaluated.",
"witnessesFix": "Driven values appear after a view-layer update in two places that must agree: the evaluated copy and the original datablock the animation system flushes for display.",
"hero": "docs/gallery/assets/driver-wave-hero.webp",
"preview": "examples/driver-wave/preview.webp",
"tags": [
"drivers",
"animation"
]
},
{
"name": "bmesh-gear",
"dir": "examples/bmesh-gear",
"teaches": "A 14-tooth gear built entirely with bmesh — profile ring, face, extrude — with bm.free() in a try/finally, exactly as the ownership contract demands.",
"witnessesFix": "Parametric bmesh topology is exactly predictable: verts, edges, and faces match their closed forms, and every edge borders exactly two faces (watertight).",
"hero": "docs/gallery/assets/bmesh-gear-hero.webp",
"preview": "examples/bmesh-gear/preview.webp",
"tags": [
"mesh",
"bmesh"
],
"featured_rank": 7
},
{
"name": "shader-node-group",
"dir": "examples/shader-node-group",
"teaches": "One reusable shader group declared via tree.interface.new_socket, instanced in two materials with different Tint values — two spheres, one group, two colors.",
"witnessesFix": "Grouping contract: interface sockets appear on every instance, both materials share one group datablock (users == 2), and per-material parameters live on the group node, not inside the tree.",
"hero": "docs/gallery/assets/shader-node-group-hero.webp",
"preview": "examples/shader-node-group/preview.webp",
"tags": [
"materials",
"node-groups"
]
},
{
"name": "temp-override-join",
"dir": "examples/temp-override-join",
"teaches": "Join three unit cubes into a staircase under bpy.context.temp_override — the supported replacement for the removed context.copy() dict-pass form.",
"witnessesFix": "temp_override actually applies: join consumes the sources, exactly one mesh remains, topology is verts = 8 × steps, and local Z spans all three steps.",
"hero": "docs/gallery/assets/temp-override-join-hero.webp",
"preview": "examples/temp-override-join/preview.webp",
"tags": [
"operators",
"context"
]
},
{
"name": "gn-instance-grid",
"dir": "examples/gn-instance-grid",
"teaches": "A generative Geometry Nodes tree — Mesh Grid → Instance on Points → Realize Instances → Set Shade Smooth — attached as a NODES modifier with no Group Input geometry.",
"witnessesFix": "Realized instances produce closed-form topology (72 verts, 54 faces), Set Material carries Lime, and the corner instance center sits at its closed-form grid coordinate.",
"hero": "docs/gallery/assets/gn-instance-grid-hero.webp",
"preview": "examples/gn-instance-grid/preview.webp",
"tags": [
"geometry-nodes",
"instancing"
]
},
{
"name": "shape-key-blend",
"dir": "examples/shape-key-blend",
"teaches": "A relative Tall shape key that lifts and flares the top face — authored via shape_key_add / key_blocks / .value — read back from the depsgraph-evaluated mesh.",
"witnessesFix": "Shape keys do not rewrite mesh.vertices: every evaluated vert matches basis + value × (key − basis), including the flared top half-extent.",
"hero": "docs/gallery/assets/shape-key-blend-hero.webp",
"preview": "examples/shape-key-blend/preview.webp",
"tags": [
"mesh",
"shape-keys"
]
},
{
"name": "curve-bevel-arc",
"dir": "examples/curve-bevel-arc",
"teaches": "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.",
"witnessesFix": "Curve tubes are curve datablocks: eight Bezier points, bevel_depth == 0.15, filled caps, and the evaluated mesh has deterministic topology (1044 verts, 1028 faces) resting on the floor.",
"hero": "docs/gallery/assets/curve-bevel-arc-hero.webp",
"preview": "examples/curve-bevel-arc/preview.webp",
"tags": [
"curves",
"bevel"
]
},
{
"name": "compositor-glare",
"dir": "examples/compositor-glare",
"teaches": "Bloom where it actually lives — a compositor Glare (Fog Glow) node fed by Render Layers, wired via scene.compositing_node_group on 5.x and scene.node_tree on 4.x, with the Glare node's legacy properties vs 5.x menu sockets.",
"witnessesFix": "scene.node_tree is gone in 5.x and EEVEE has no use_bloom on either side: the halo beyond the ring silhouette falls off strictly with the compositor on and is exactly zero with it off.",
"hero": "docs/gallery/assets/compositor-glare-hero.webp",
"preview": "examples/compositor-glare/preview.webp",
"tags": [
"compositor",
"rendering"
]
},
{
"name": "damped-track-aim",
"dir": "examples/damped-track-aim",
"teaches": "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. Gallery still: brass spike cage around an ember core.",
"witnessesFix": "Every needle has one unmuted DAMPED_TRACK on the core; evaluated local +Z aligns toward the core (dot ≥ 0.998). TRACK_TO stand-ins and flipped axes fail.",
"hero": "docs/gallery/assets/damped-track-aim-hero.webp",
"preview": "examples/damped-track-aim/preview.webp",
"tags": [
"constraints",
"animation"
],
"featured_rank": 3
},
{
"name": "color-attribute-wheel",
"dir": "examples/color-attribute-wheel",
"teaches": "The modern color-attributes API — mesh.color_attributes.new() on the CORNER domain, filled by expanding per-vertex HSV across face corners with foreach_get/foreach_set, then wired into a shader Attribute node.",
"witnessesFix": "CORNER-domain attributes are sized to loop count, not vertex count; the check asserts the sizing, that active_color points at it, and that the Attribute node is actually linked to Base Color, not just present.",
"hero": "docs/gallery/assets/color-attribute-wheel-hero.webp",
"preview": "examples/color-attribute-wheel/preview.webp",
"tags": [
"mesh",
"materials",
"attributes"
]
},
{
"name": "parent-inverse-orrery",
"dir": "examples/parent-inverse-orrery",
"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.",
"witnessesFix": "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.",
"hero": "docs/gallery/assets/parent-inverse-orrery-hero.webp",
"preview": "examples/parent-inverse-orrery/preview.webp",
"tags": [
"objects",
"transforms"
]
},
{
"name": "grease-pencil-rosette",
"dir": "examples/grease-pencil-rosette",
"teaches": "Grease Pencil v3's attribute-based API — layer → frames.new(1).drawing → add_strokes → per-point position/radius/opacity/vertex_color — drawing five nested neon rose curves.",
"witnessesFix": "The GPv3 address break: 4.5 keeps GPv3 at grease_pencils_v3 while grease_pencils is still legacy GPencil (frame.strokes, no .drawing); 5.x deletes legacy and GPv3 takes over the grease_pencils name. Point writes lazily materialize attribute layers, and every position round-trips through the raw POINT buffer.",
"hero": "docs/gallery/assets/grease-pencil-rosette-hero.webp",
"preview": "examples/grease-pencil-rosette/preview.webp",
"tags": [
"grease-pencil",
"attributes"
]
},
{
"name": "armature-bend",
"dir": "examples/armature-bend",
"teaches": "Rigging end to end in the data API — edit_bones chain construction, name-bound vertex groups with smoothstep blend zones, posing, and depsgraph evaluation — bending a tapered tube through rest, half, and full curl.",
"witnessesFix": "edit_bones exists only in edit mode (empty in object mode), vertex groups bind to bones strictly by name, and the armature modifier is exactly linear blend skinning: every evaluated vertex must equal Σ wᵢ·(pose_bone.matrix @ bone.matrix_local.inverted()) @ rest, root ring pinned, tip deflected — a straight tube fails.",
"hero": "docs/gallery/assets/armature-bend-hero.webp",
"preview": "examples/armature-bend/preview.webp",
"tags": [
"armature",
"depsgraph"
],
"featured_rank": 6
},
{
"name": "text-version-stamp",
"dir": "examples/text-version-stamp",
"teaches": "The TextCurve data API — curves.new(type='FONT'), live body text from bpy.app.version_string, extrude and bevel_depth solids, and evaluated-mesh conversion — so every render self-documents which Blender produced it.",
"witnessesFix": "TextCurve solids are exactly predictable: evaluated z-extent = 2 × (extrude + bevel_depth), bevel widens the outline by 2 × bevel_depth, editing body regenerates geometry — and version_string is not bare semver on LTS (\"4.5.11 LTS\"), so parse bpy.app.version instead.",
"hero": "docs/gallery/assets/text-version-stamp-hero.webp",
"preview": "examples/text-version-stamp/preview.webp",
"tags": [
"curves",
"depsgraph",
"rendering"
]
},
{
"name": "image-pixels-testcard",
"dir": "examples/image-pixels-testcard",
"teaches": "The Image pixel-buffer contract — a procedural broadcast test card written into bpy.data.images.new() with one pixels.foreach_set (589,824 floats), byte vs float_buffer storage, scale() reallocation, and the save() vs save_render() lifecycle.",
"witnessesFix": "pixels is always flat RGBA (channels == 4 even with alpha=False), byte storage quantizes at exactly ≤ 0.5/255 and strictly > 0, stale-size bulk reads raise after scale() — and save() silently flips source to FILE and drops the buffer, so later pixels reads come from whatever sits on disk (proven with an imposter file).",
"hero": "docs/gallery/assets/image-pixels-testcard-hero.webp",
"preview": "examples/image-pixels-testcard/preview.webp",
"tags": [
"images",
"performance",
"rendering"
]
},
{
"name": "png-exr-alpha",
"dir": "examples/png-exr-alpha",
"teaches": "Float-image PNG save trap — float_buffer=True Image.save() writes RGBA16 and unpremultiplies as if associated-alpha, clamping straight-authored dark values at low alpha to white (closed-form err 0.98 at RGB 0.02 / a=1/255).",
"witnessesFix": "float→PNG IHDR is RGBA16 and matches q16(min(1, c/q16(a))); OpenEXR round-trips within 1e-5; byte→PNG is straight RGBA8; EXR color_mode=RGB drops alpha to opaque.",
"hero": "docs/gallery/assets/png-exr-alpha-hero.webp",
"preview": "examples/png-exr-alpha/preview.webp",
"tags": [
"images",
"rendering",
"alpha"
]
},
{
"name": "uv-layer-grid",
"dir": "examples/uv-layer-grid",
"teaches": "The UV-layer authoring hazard — bmesh.ops.create_grid(..., calc_uvs=True) is a silent no-op unless a UV layer already exists; without one an Image Texture samples texel (0,0) everywhere.",
"witnessesFix": "calc_uvs=True alone leaves 0 UV layers; pre-create + calc_uvs fills loops to the closed-form grid UVs ((x/size+1)/2, (y/size+1)/2) within 1e-6; explicit loop assignment is the calc_uvs-free fallback. The still is self-witnessing: the script re-reads its render and exits non-zero unless the hazard panel is one flat teal and the repaired panel a high-contrast checker.",
"hero": "docs/gallery/assets/uv-layer-grid-hero.webp",
"preview": "examples/uv-layer-grid/preview.webp",
"tags": [
"mesh",
"bmesh",
"uv",
"materials"
]
},
{
"name": "vse-cut-list",
"dir": "examples/vse-cut-list",
"teaches": "The sequencer API rename from 4.5 LTS to 5.x — strips (never .sequences), new_effect ending in length= vs frame_end=, and left_handle/right_handle/duration replacing the deprecated frame_final_*. A deterministic cut list — color programs, a clamped GAMMA_CROSS, a scene strip, a text strip — asserted before and after save/reload.",
"witnessesFix": "Each side hard-fails the other's spelling: TypeError on the wrong end kwarg, AttributeError on .sequences. The cross clamps to the source overlap, effect strips consume their inputs only when stacked above them, and a same-scene scene strip renders transparent.",
"hero": "docs/gallery/assets/vse-cut-list-hero.webp",
"preview": "examples/vse-cut-list/preview.webp",
"tags": [
"sequencer",
"rendering"
]
},
{
"name": "gltf-export-roundtrip",
"dir": "examples/gltf-export-roundtrip",
"teaches": "A sci-fi supply crate exported to glTF and re-imported, verifying the round-trip against the depsgraph-evaluated mesh within float tolerances. Positions, loop normals, box-mapped UVs, and per-triangle material bindings must all survive; the on-disk JSON proves the +Y-up conversion and the V-flipped UV layout.",
"witnessesFix": "export_yup bakes (x, y, z) -> (x, z, -y) into the vertex data with no node rotation (probed identical on 4.5.11 and 5.1.2); export_apply ships the evaluated mesh, not the base cage; exporter/importer RNA is guarded so a future kwarg rename fails loudly.",
"hero": "docs/gallery/assets/gltf-export-roundtrip-hero.webp",
"preview": "examples/gltf-export-roundtrip/preview.webp",
"tags": [
"export",
"uv"
]
},
{
"name": "lod-decimate-chain",
"dir": "examples/lod-decimate-chain",
"teaches": "A retro rocket at LOD0/1/2 via the Decimate modifier evaluated through the depsgraph. The check proves the reduction is non-destructive, the triangle count hits ratio x base within bounds, and silhouette-critical dimensions survive.",
"witnessesFix": "Evaluated-vs-original: the depsgraph carries the reduction while obj.data keeps the closed-form counts. A stacked Decimate halves the effective ratio (caught); an aggressive ratio collapses the nose tip (caught).",
"hero": "docs/gallery/assets/lod-decimate-chain-hero.webp",
"preview": "examples/lod-decimate-chain/preview.webp",
"tags": [
"depsgraph",
"mesh"
]
},
{
"name": "vertex-weight-limit",
"dir": "examples/vertex-weight-limit",
"teaches": "A rigged mech arm pruned to the game-engine cap of four bone influences per vertex, through the data API. The check proves no vertex exceeds the cap, weights still sum to one, the pose survives pruning, and the modifier is still exact linear blend skinning.",
"witnessesFix": "Limit-total and normalize without bpy.ops: keep the top four groups, VertexGroup.remove the rest, renormalize. LBS is verified against the weights read back from the mesh's own deform layer, not the authoring function.",
"hero": "docs/gallery/assets/vertex-weight-limit-hero.webp",
"preview": "examples/vertex-weight-limit/preview.webp",
"tags": [
"armature",
"mesh"
]
},
{
"name": "triangulate-tangents",
"dir": "examples/triangulate-tangents",
"teaches": "A machined buckler verifying the tangent-space contract a game engine's normal mapping depends on. Deterministic triangulation, unit orthogonal tangent frames, and the edge/UV-delta formula matching mikktspace within welding tolerance.",
"witnessesFix": "calc_tangents aborts on any ngon; planar UVs on a cylindrical wall collapse tangents onto normals; a MeshUVLoopLayer handle held across calc_tangents dangles on 4.5 (471 phantom flips, silent exit 0) while the mikktspace math is byte-identical on both versions.",
"hero": "docs/gallery/assets/triangulate-tangents-hero.webp",
"preview": "examples/triangulate-tangents/preview.webp",
"tags": [
"mesh",
"uv"
]
},
{
"name": "gltf-skin-roundtrip",
"dir": "examples/gltf-skin-roundtrip",
"teaches": "A rigged mech scorpion exported to glTF with skins and re-imported, verifying the skinning contract the geometry round-trip left uncovered. Skeleton, weights, and deformation must all survive the format.",
"witnessesFix": "skins[0].joints names every bone; JOINTS_0/WEIGHTS_0 per primitive with unit sums; weights bit-exact on re-import; deformation matches within 4.8e-07. The mesh must be parented to the armature or the exporter binds by name.",
"hero": "docs/gallery/assets/gltf-skin-roundtrip-hero.webp",
"preview": "examples/gltf-skin-roundtrip/preview.webp",
"tags": [
"export",
"armature"
]
},
{
"name": "vse-gamma-cross",
"dir": "examples/vse-gamma-cross",
"teaches": "The GAMMA_CROSS fade is not the naive linear mix: it blends in a gamma-0.5 space, so the mid-cross dips below the sRGB lerp. Tiny per-frame renders are asserted against the closed form per frame.",
"witnessesFix": "out = ((1-t)*sqrt(A) + t*sqrt(B))^2 with t = (frame - start) / duration, never 1 inside the effect. The mid lerp deviation is 0.115; AgX-default sampling poisons the fit (Standard is mandatory).",
"hero": "docs/gallery/assets/vse-gamma-cross-hero.webp",
"preview": "examples/vse-gamma-cross/preview.webp",
"tags": [
"sequencer",
"rendering"
]
},
{
"name": "light-link-studio",
"dir": "examples/light-link-studio",
"teaches": "One key, one hero: a light linked to a receiver collection lights only the hero, proven by two pixel renders in one pass. Linked: 3.6x luminance ratio; unlinked in the same check: the decoy rises 233% while the hero holds at 0.3% drift.",
"witnessesFix": "The API is obj.light_linking on the light OBJECT (ld.light_linking is an AttributeError). Luminance is sampled at each sphere's projected center; EEVEE honors linking too (probe-measured 3.8x on 4.5.11 EEVEE Next and 5.1.2 EEVEE), Cycles pinned for deterministic samples.",
"hero": "docs/gallery/assets/light-link-studio-hero.webp",
"preview": "examples/light-link-studio/preview.webp",
"tags": [
"rendering",
"objects"
],
"featured_rank": 4
},
{
"name": "collision-hull-proxy",
"dir": "examples/collision-hull-proxy",
"teaches": "A fire hydrant street prop inside its compound collision shell: four convex pieces hulled by bmesh.ops.convex_hull from a coarse inflated cage. The dense render mesh is never hulled - its hull would measure 380 faces, over the 255-face per-piece engine budget. Closed-form plane tests prove containment, convexity, watertightness, outward winding, and Euler characteristic 2 per piece.",
"witnessesFix": "A hull of the dense render mesh measures 380 faces, over the 255-face per-piece budget - the cage is a coarser lathe with rings inflated by sec(pi/n) so each cage ring circumscribes its render ring exactly (containment 4.4e-08). Proud details cost cage rows; concave grooves are free under the hull. Piece budgets: body 70, caps 60+60+60, compound 250.",
"hero": "docs/gallery/assets/collision-hull-proxy-hero.webp",
"preview": "examples/collision-hull-proxy/preview.webp",
"tags": [
"bmesh",
"mesh"
],
"featured_rank": 5
},
{
"name": "custom-normals-shade",
"dir": "examples/custom-normals-shade",
"teaches": "A jerry can prop shaded three ways to prove the post-4.1 shading contract: hard edges are mesh data, landing exactly where the dihedral crosses. Face smooth flags plus a sharp_edge attribute, verified against an independently recomputed dihedral test, and per-loop custom normals surviving depsgraph evaluation within their int16 storage quantization (1.407e-04, not float-exact).",
"witnessesFix": "use_auto_smooth, use_custom_normals and calc_normals are AttributeError on BOTH 4.5 LTS and 5.1 - AI code still emits them. The legacy shade_auto_smooth operator CANCELS headless on 4.5 (asset load never finishes; mesh untouched; no exception) while 5.1 FINISHES with the Smooth by Angle NODES modifier. By-angle sharp sets match the independent dihedral recompute exactly (188 of 388 edges over 3 meshes).",
"hero": "docs/gallery/assets/custom-normals-shade-hero.webp",
"preview": "examples/custom-normals-shade/preview.webp",
"tags": [
"mesh",
"attributes"
]
},
{
"name": "sky-texture-sun-elevation",
"dir": "examples/sky-texture-sun-elevation",
"teaches": "World ShaderNodeTexSky driving Background Color — the sky contract across 4.5 LTS and 5.1. sky_type is NISHITA on 4.5 and MULTIPLE_SCATTERING on 5.1 (NISHITA gone); dust_density exists only on 4.5 (aerosol_density on 5.1). Two tiny Cycles OPEN_EXR zenith probes prove sun_elevation 8 deg to 55 deg brightens zenith (rise 2.25x on 5.1.2, 1.50x on 4.5.11, gate >= 1.25).",
"witnessesFix": "AI code still assigns sky_type='NISHITA' and dust_density on 5.1 — both are wrong (NISHITA removed; dust_density is AttributeError; use MULTIPLE_SCATTERING + aerosol_density). A near-black Background Strength alone is not a sky: Sky must link into Background Color. Elevation that does not brighten zenith fails the rise gate.",
"hero": "docs/gallery/assets/sky-texture-sun-elevation-hero.webp",
"preview": "examples/sky-texture-sun-elevation/preview.webp",
"tags": [
"world",
"sky",
"render"
]
},
{
"name": "gp-lineart-contour",
"dir": "examples/gp-lineart-contour",
"teaches": "Grease Pencil LINEART modifier contours via the depsgraph on a faceted crystal. source_object is load-bearing (clear yields 0 strokes); contour+crease off yields 0; restore recovers 10 strokes / 34 points on both 4.5.11 and 5.1.2. Stroke width: thickness exists on 4.5, AttributeError on 5.1 — portable path is radius.",
"witnessesFix": "AI code still sets LINEART.thickness on 5.1 (AttributeError) or hand-draws strokes / enables Freestyle instead of modifiers.new(..., 'LINEART'). Clearing source_object or leaving contour+crease off silently yields an empty drawing — the check proves both failure modes and recovery.",
"hero": "docs/gallery/assets/gp-lineart-contour-hero.webp",
"preview": "examples/gp-lineart-contour/preview.webp",
"tags": [
"grease-pencil",
"line-art",
"modifiers"
]
},
{
"name": "mesh-hygiene-audit",
"dir": "examples/mesh-hygiene-audit",
"teaches": "Engine-ingest mesh hygiene on an octagonal street valve: no ngons, no loose verts, manifold edges, no zero-area faces, positive signed volume, Euler V-E+F==2. Dual-panel still shows a through-hole and loose-vert bead derived from live mesh incidence — same brass on both so color alone cannot carry the proof.",
"witnessesFix": "Pipelines that skip topology gates ship open boundaries, loose verts, or non-sphere Euler into engine ingest. The still makes the boundary hole and loose vert readable; ngon / zero-area / full winding invert remain check-only.",
"hero": "docs/gallery/assets/mesh-hygiene-audit-hero.webp",
"preview": "examples/mesh-hygiene-audit/preview.webp",
"tags": [
"bmesh",
"mesh",
"game-pipeline"
]
},
{
"name": "prop-origin-transform",
"dir": "examples/prop-origin-transform",
"teaches": "Street pedestal origin-to-base-center + data-API scale apply + matrix_parent_inverse for a flanged conduit accessory. After bake: scale (1,1,1), local min.z==0, world AABB unchanged. Bare parent jumps the accessory off an empty mount socket; MPI keeps it seated.",
"witnessesFix": "Bare child.parent = parent teleports accessories off their mounts; skipping view_layer.update() before matrix_world, or leaving non-uniform scale / centered origin, breaks engine ingest placement.",
"hero": "docs/gallery/assets/prop-origin-transform-hero.webp",
"preview": "examples/prop-origin-transform/preview.webp",
"tags": [
"transform",
"parenting",
"game-pipeline"
]
},
{
"name": "soccer-ball-goldberg",
"dir": "examples/soccer-ball-goldberg",
"teaches": "A soccer ball as a Goldberg polyhedron: a bmesh icosphere truncated at 1/3 per edge, faces ordered by link-topology walks, panels bound by face vertex count.",
"witnessesFix": "Goldberg invariants are closed forms — counts, degree 3, uniform edges, planar faces, one circumsphere — and panels bind by vertex count, never enumeration order.",
"hero": "docs/gallery/assets/soccer-ball-goldberg-hero.webp",
"preview": "examples/soccer-ball-goldberg/preview.webp",
"tags": [
"bmesh",
"mesh"
],
"featured_rank": 2
},
{
"name": "car-mirror-symmetry",
"dir": "examples/car-mirror-symmetry",
"teaches": "A generic hatchback lofted as one half (14 stations, 9-point rings) and completed by the Mirror modifier, evaluated through the depsgraph. Wheels and lamps mirror about object origins parked on the symmetry plane.",
"witnessesFix": "Mirror is a modifier, not applied data: the original keeps the authored half while the evaluated mesh is the welded, watertight, exactly symmetric whole.",
"hero": "docs/gallery/assets/car-mirror-symmetry-hero.webp",
"preview": "examples/car-mirror-symmetry/preview.webp",
"tags": [
"modifiers",
"depsgraph"
],
"featured_rank": 1
},
{
"name": "attribute-domain-shear",
"dir": "examples/attribute-domain-shear",
"teaches": "POINT vs CORNER color-attribute domains on a shared-vertex pinwheel: CORNER stays exact per face while a naive per-wedge POINT loop shears to the last write at the hub. Crisp petals versus a smeared swirl in one frame; the measured shear equals the palette closed form.",
"witnessesFix": "Authoring per-face colors into a POINT-domain attribute overwrites shared vertices once per neighbor; the last write wins and intended colors shear across every shared vertex.",
"hero": "docs/gallery/assets/attribute-domain-shear-hero.webp",
"preview": "examples/attribute-domain-shear/preview.webp",
"tags": [
"attributes",
"mesh",
"rendering"
]
},
{
"name": "degenerate-bevel-weld",
"dir": "examples/degenerate-bevel-weld",
"teaches": "Bevel offset >= half the min box dimension collapses the band into zero-area faces — and they ship: a stdlib GLB re-parse counts the degenerate triangles crossing the export boundary. Dual tray: rounded edge versus knife seam, markers from live mesh data.",
"witnessesFix": "At offset == min_dimension/2 the bevel band pinches to exactly 12 zero-area faces (4 edges x 3 segments); 32 degenerate triangles cross into the exported GLB, waiting for any merge-by-distance to weld them.",
"hero": "docs/gallery/assets/degenerate-bevel-weld-hero.webp",
"preview": "examples/degenerate-bevel-weld/preview.webp",
"tags": [
"bmesh",
"mesh",
"game-pipeline"
]
},
{
"name": "modular-kit-snap",
"dir": "examples/modular-kit-snap",
"teaches": "A tiling corridor kit whose open-end boundary verts snap to the tile grid, so instances at 4 m multiples join with zero gap or overlap.",
"witnessesFix": "16 boundary verts on x∈{0,4} within 1e-6 m, linked-duplicate joint deviation 0.0, bbox == declared tile; unsnapped probe fails at 3e-3 m.",
"hero": "docs/gallery/assets/modular-kit-snap-hero.webp",
"preview": "examples/modular-kit-snap/preview.webp",
"tags": [
"mesh",
"bmesh",
"game-pipeline",
"instancing"
],
"featured_rank": 8
},
{
"name": "lightmap-uv-channel",
"dir": "examples/lightmap-uv-channel",
"teaches": "A market cart carrying the two-channel UV contract for baked lighting: UV0 untouched, UVLight packed with no overlaps and a respected margin.",
"witnessesFix": "Per-part UVMap/UVLight with active vs active_render pinned (ops clear both flags), UV0 drift 0.0, 3680 islands with 0 SAT overlaps, min island distance 0.00401.",
"hero": "docs/gallery/assets/lightmap-uv-channel-hero.webp",
"preview": "examples/lightmap-uv-channel/preview.webp",
"tags": [
"uv",
"game-pipeline",
"render"
]
},
{
"name": "socket-attach-points",
"dir": "examples/socket-attach-points",
"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.",
"witnessesFix": "7 socket world matrices within 1.788e-07 of the authored transform, socket +Z == the pad's Newell normal (1.794e-07), seating offset 0.0; the no-parent-inverse probe jumps 0.690128 m.",
"hero": "docs/gallery/assets/socket-attach-points-hero.webp",
"preview": "examples/socket-attach-points/preview.webp",
"tags": [
"mesh",
"transforms",
"game-pipeline",
"instancing"
]
},
{
"name": "vertex-color-ao",
"dir": "examples/vertex-color-ao",
"teaches": "A stone well carrying baked ambient occlusion in a colour attribute, with the bake held to the closed-form hemisphere integral rather than to a captured value.",
"witnessesFix": "Integrator matches 1 - 0.5(1 - 1/sqrt(1+k^2)) to 6.760e-04, unoccluded plate exactly 1.0, FLOAT_COLOR exact vs BYTE_COLOR sRGB-quantised (model agreement 3.189e-07), evaluated deviation 0.0.",
"hero": "docs/gallery/assets/vertex-color-ao-hero.webp",
"preview": "examples/vertex-color-ao/preview.webp",
"tags": [
"mesh",
"attributes",
"game-pipeline",
"render"
]
}
]
}