-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathgallery.json
More file actions
694 lines (694 loc) · 47.6 KB
/
Copy pathgallery.json
File metadata and controls
694 lines (694 loc) · 47.6 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
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
{
"_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 and Showcase",
"description": "Runnable, smoke-gated Blender Python examples — each executed headless on Blender 5.2 LTS and 4.5 LTS (5.1 on the weekly cron), so every render reflects code that actually runs. Showcase pieces share the grid and carry the 'showcase' tag; they are budget-conformance props, not examples.",
"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.",
"alt": "Six material spheres on graphite plinths in two tiers: mirror gold, brushed copper and red behind; blue, a glowing orange emissive globe and matte white in front.",
"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).",
"alt": "A smooth copper monkey head (Suzanne) turned three-quarters on a black turntable with a brushed metal platter.",
"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.",
"alt": "Two red tori side by side under pale edge cages: the source with even ring topology, and its SDF remesh covered in a dense irregular voxel grid.",
"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.",
"alt": "A game controller's sparse control cage in orange wire and vertex beads beside the smooth subdivided cobalt controller with sticks and colored buttons, on a dark studio floor.",
"witnessesFix": "Exports ship evaluated geometry: the controller shell subdivides to exactly its Catmull-Clark closed form, and the exported vertex count equals the subsurf-applied count, 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.",
"alt": "A blue tile whose top is a grid displaced into rolling sine-wave hills, standing on a dark studio floor.",
"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.",
"alt": "A row of orange bars seen from above, their heights rising and falling in a wave, low in the middle and tall at both ends, each casting a shadow on the floor.",
"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.",
"alt": "A brass fourteen-tooth gear leaning back on a dark display wedge, its lathe-turned face catching a soft highlight that fans from the centre.",
"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 five materials with different Tint values — one dipped-glaze group, five stoneware mugs, five colors.",
"alt": "Five speckled stoneware mugs in oxblood, amber, celadon, teal and cobalt on a walnut riser, each with the same bare clay foot and wavy dip line from one shared glaze group.",
"witnessesFix": "Grouping contract: interface sockets appear on every instance, all five materials share one group datablock (users == 5), 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 seven lantern parts into one object under bpy.context.temp_override — the supported replacement for the removed context.copy() dict-pass form.",
"alt": "A red enamel hurricane lantern with a glowing amber glass globe, black wire guard, side air tubes, brass wick knob and a wire bail with a wooden grip, all one joined object on a dark studio floor.",
"witnessesFix": "temp_override actually applies: join consumes all six sources, exactly one mesh remains, no geometry is lost, the five part materials merge into one slot each with every face still on its own material, and local Z spans foot to grip.",
"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 (a modeled keycap via Object Info) → Realize Instances → Set Shade Smooth → Set Material — attached as a NODES modifier with no Group Input geometry.",
"alt": "A cobalt-blue macropad with a brass knob and a glowing OLED meter, its three-by-three key field of cream keycaps instanced by Geometry Nodes, with one orange accent key at the front right.",
"witnessesFix": "Realized instances produce closed-form topology (9 keycaps × 121 = 1089 verts, 1089 faces), Set Material carries the keycap plastic, a position-field Set Material lands the accent on exactly one keycap, and the corner keycap 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": "gn-modifier-inputs",
"dir": "examples/gn-modifier-inputs",
"teaches": "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.",
"alt": "Three spiral staircases of one design, with oak treads, teal posts and brass rails, rising left to right from seven steps to seventeen to twenty-seven.",
"witnessesFix": "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 heights 1/2/3, which the shared tree turns into 7/17/27 treads.",
"hero": "docs/gallery/assets/gn-modifier-inputs-hero.webp",
"preview": "examples/gn-modifier-inputs/preview.webp",
"tags": [
"geometry-nodes",
"modifiers"
]
},
{
"name": "shape-key-blend",
"dir": "examples/shape-key-blend",
"teaches": "A relative Tall shape key that turns a squat ceramic jar into a trumpet vase — lifting and flaring the rim — authored via shape_key_add / key_blocks / .value and read back from the depsgraph-evaluated mesh.",
"alt": "One cobalt vase with an ochre band at shape-key values 0, 0.5 and 1: squat jar, amphora, tall trumpet vase, with pale rings of the jar's belly around the tallest.",
"witnessesFix": "Shape keys do not rewrite mesh.vertices: every evaluated vert matches basis + value × (key − basis), including the lifted, flared rim ring.",
"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.",
"alt": "A red horseshoe magnet made of one beveled Bezier curve on a dark floor, its filled end caps facing the camera as steel pole faces, iron filings arcing between them.",
"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.",
"alt": "Three glowing neon rings, violet, cyan and warm white, with fog-glow bloom and reflections on the floor.",
"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.",
"alt": "Gold spikes arranged around a glowing sphere, every tip aimed at the sphere.",
"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.",
"alt": "A board painted with a full hue wheel that fades to white at the centre, leaning on a dark plinth.",
"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.",
"alt": "A brass orrery: a glowing yellow sun on a stand with blue, green and orange planets on arms, orbit rings and a silver moon.",
"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.",
"alt": "A neon Grease Pencil rosette of nested cyan, violet, magenta and red petal loops on a dark blue backdrop.",
"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.",
"alt": "Three tapered tubes, coral to amber to teal, standing straight, half curled and fully curled by an armature.",
"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.",
"alt": "The Blender version number in large gold 3D characters standing on a dark plinth, the word BLENDER set into its face.",
"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.",
"alt": "A monitor showing a colour-bar test card with a black circle and a greyscale strip.",
"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).",
"alt": "Two monitors on a plinth labelled FLOAT to PNG and FLOAT to EXR, showing alpha and colour bar strips side by side.",
"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.",
"alt": "Two lightbox panels: the left one flat teal with no UV layer, the right one a full magenta, cyan and green checker.",
"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.",
"alt": "A monitor on a desk labelled PROGRAM, showing a sequencer frame of four colour blocks with a cut-list caption strip.",
"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.",
"alt": "One olive sci-fi supply crate with a cyan light strip, its right half traced by an amber wire cage of the re-imported glTF triangles that lands exactly on every bevel, rivet and vent slat.",
"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": "export-preset-axis",
"dir": "examples/export-preset-axis",
"teaches": "A radio mast exported under Unity and Godot glTF presets and re-imported, proving the two files have different vertex orientation",
"alt": "Two re-imports of a red-and-white radio mast with axis gizmos: the Unity copy stands with blue Z up, the Godot copy lies on the floor with green Y up and blue Z along the mast.",
"witnessesFix": "Unity export_yup=True round-trips standing (x,y,z); Godot export_yup=False reimports lying along -Y as (x,-z,y); --same-axis exits 9; exporter RNA is guarded so a future kwarg rename fails loudly",
"hero": "docs/gallery/assets/export-preset-axis-hero.webp",
"preview": "examples/export-preset-axis/preview.webp",
"tags": [
"export"
]
},
{
"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, with a wireframe of each evaluated mesh. The check proves the reduction is non-destructive, the triangle count hits ratio x base within bounds, and silhouette-critical dimensions survive.",
"alt": "Three identical cream-and-red retro rockets with brass trim and a porthole, each covered in a dark triangle wireframe that grows coarser left to right, labelled 4784, 2392 and 860 tris.",
"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); a fin extreme on an oblique plate corner is shaved at ratio 0.18 (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 industrial robot 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.",
"alt": "An orange industrial robot arm reaching down with a two-jaw gripper, each segment glowing in its bone colour and the cables on its back grading from colour to colour across each joint.",
"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.",
"alt": "A steel buckler on a walnut stand, turned three-quarter: pointed brass boss, domed face cut with two bands of concentric lathe grooves, brass rim ringed with steel rivets.",
"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.",
"alt": "Two orange-plated mech scorpions face off: the authored one coils its tail with claws tucked; the re-imported one, driven by its imported bones, rears its tail to strike with claws raised.",
"witnessesFix": "skins[0].joints names all 21 bones; JOINTS_0/WEIGHTS_0 per primitive with unit sums; every split copy's weights bit-exact on re-import; deformation matches within 6.0e-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.",
"alt": "A hooded grading monitor showing two orange-to-blue filmstrips: the gamma cross on top sinks to dark grey in the middle, while the linear cross beneath it passes through violet.",
"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.250; 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.",
"alt": "Two spheres on pedestals: a brightly lit orange sphere labelled LINKED and a dark grey sphere labelled UNLINKED.",
"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.",
"alt": "A red and yellow fire hydrant wrapped in a cyan wireframe convex collision hull.",
"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 (3.904e-05 over 8196 loops, not float-exact).",
"alt": "Three olive jerry cans with X-pressed panels, triple handles and red-sealed spouts: faceted flat, glossy-smeared smooth-everywhere, and crisp by-angle with its sharp edges traced in thin cyan lines.",
"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 (1304 of 4738 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).",
"alt": "A red-granite obelisk on a paved plaza lit only by the sky: navy dusk with an orange horizon glow at sun elevation 8 degrees, bright blue midday and a short shadow at 55 degrees.",
"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",
"rendering"
]
},
{
"name": "gp-lineart-contour",
"dir": "examples/gp-lineart-contour",
"teaches": "Grease Pencil LINEART modifier ink via the depsgraph on a cel-shaded lighthouse diorama. source_object is load-bearing (clear yields 0 strokes); every edge type off yields 0; the drawing is 255 strokes / 1393 points on 4.5.11, 5.1.2 and 5.2.1, gated above the count left when any one of contour, crease, material-border or intersection edges is dropped. Stroke width: thickness exists on 4.5, AttributeError on 5.1 — portable path is radius.",
"alt": "A red-and-white lighthouse on a rocky islet with a keeper's cottage and a rowboat, on a round blue sea with white wave crests, drawn like a comic panel in bold black Line Art ink.",
"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 every edge type 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 every part of a flanged street valve: no ngons, no loose verts, manifold edges, no zero-area faces, contiguous and outward winding, Euler V-E+F==2 on the body casting. A dirty copy carries a hole, a flipped patch, an ngon and loose verts, each marked from live audit incidence; the paint glows red wherever the renderer sees a back face.",
"alt": "Two blue street valves with brass handwheels: the left has a red-outlined hole, a red bonnet patch, an amber flange patch and three red beads beside it; the right is intact.",
"witnessesFix": "Pipelines that skip topology gates ship open boundaries, flipped patches, ngons, loose verts or stray shells into engine ingest. The still shows the first four on a dirty copy; zero-area faces and a buried second shell 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 elbow. After bake: scale (1,1,1), local min.z==0, world AABB unchanged. Bare parent throws the elbow off its mount; MPI keeps it seated.",
"alt": "Two green street utility pedestals in floor pivot rings: the left one keeps its orange conduit elbow bolted on, the right one's elbow hangs in the air beside a glowing cyan outline of its seat.",
"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": [
"transforms",
"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.",
"alt": "A round white soccer ball with black pentagon panels and dark stitched seams outlining every white hexagon, built as a smoothed Goldberg polyhedron.",
"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 stylized hatchback lofted as one half (52 stations, 13-point rings) and completed by the Mirror modifier, evaluated through the depsgraph. Wheels, lamps, grille, door mirrors and handles mirror about object origins parked on the symmetry plane; the grille is authored as a half and welded on it.",
"alt": "A red stylized hatchback in front three-quarter view with tinted glass, black pillars, spoked wheels, and matched headlamps and door mirrors either side of one centered grille.",
"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 fan: CORNER stays exact per face while a naive per-wedge POINT loop shears to the last write at the hub. A striped parasol keeps crisp stripes under CORNER and smears, with one panel turned the wrong color, under POINT; the measured shear equals the palette closed form.",
"alt": "Two striped patio parasols labelled CORNER and POINT: the left has crisp crimson and cream stripes, the right has stripes smeared pink along the seams and a front panel turned cream.",
"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. Two rugged cases whose shells are the check's meshes: flat end panel versus a rolled knife ridge, the collapsed seam traced hot from live mesh data.",
"alt": "Two blue hard-shell cases: the left with a flat end panel and clean chamfer bands, the right with its rim rolled into a ridge traced by a glowing red seam and orange collapse beads.",
"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.",
"alt": "A sci-fi corridor built from snapped modular segments, with glass wall panels, orange trim rails and a lit doorway at the far end.",
"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.",
"alt": "A wooden covered cart, its four posts carrying an arched canvas canopy, beside a board showing its packed lightmap UV atlas as coloured islands.",
"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",
"rendering"
]
},
{
"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.",
"alt": "A dark quadcopter drone with orange motor mounts, propellers and attached antenna and camera parts.",
"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.",
"alt": "A round stone well with a wooden windlass frame, its stones darkened near the base by baked vertex-colour ambient occlusion.",
"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",
"rendering"
]
},
{
"name": "cross-version-property-delete",
"dir": "examples/cross-version-property-delete",
"teaches": "Custom ID properties are removed with del, not property_unset. The IDs are built through bpy.data.objects.new so the check does not depend on active_object.",
"alt": "Two brass tags hanging from a stand on steel rods: the left shows a lit cyan inlay, the right a blank grey pocket.",
"witnessesFix": "Factory-empty has no active_object; property_unset raises TypeError and leaves the key; del removes it (clear_has=False). --skip-delete and --unset-instead exit 7.",
"hero": "docs/gallery/assets/cross-version-property-delete-hero.webp",
"preview": "examples/cross-version-property-delete/preview.webp",
"tags": [
"context",
"properties"
]
},
{
"name": "usd-export-evaluation-mode",
"dir": "examples/usd-export-evaluation-mode",
"teaches": "The USD exporter evaluation_mode chooses viewport versus render modifier quality. TESSELLATE makes the split observable; BEST_MATCH writes the cage and the mode is silent.",
"alt": "Two steel balls resting on the floor: a coarse, flat-shaded one on the left and a smooth, subdivided one on the right.",
"witnessesFix": "TESSELLATE+VIEWPORT writes 26/24; TESSELLATE+RENDER writes 98/96 subdivisionScheme=none; BEST_MATCH writes the 8-vert catmullClark cage. --evaluation-mode VIEWPORT and --subdivision BEST_MATCH exit 4.",
"hero": "docs/gallery/assets/usd-export-evaluation-mode-hero.webp",
"preview": "examples/usd-export-evaluation-mode/preview.webp",
"tags": [
"depsgraph",
"export"
]
},
{
"name": "gn-zone-iterate",
"dir": "examples/gn-zone-iterate",
"teaches": "Repeat Zone and For Each Element only iterate after pair_with_output. Evaluated cube counts follow 8 times (1+N) and 8 times P, not tree structure.",
"alt": "Four brass cubes evenly spaced in a row from a Repeat zone beside a vertical stack of six cyan cubes from a For Each zone.",
"witnessesFix": "Repeat 32/24 verts/faces with X-centers at k*1.2; For Each 48/36 with Z-centers at i*0.6+0.21. Unpaired evaluates empty; For Each main Geometry passthrough is 6 verts.",
"hero": "docs/gallery/assets/gn-zone-iterate-hero.webp",
"preview": "examples/gn-zone-iterate/preview.webp",
"tags": [
"geometry-nodes"
]
},
{
"name": "bake-normal-high-to-low",
"dir": "examples/bake-normal-high-to-low",
"teaches": "A collapse-decimated hatch plate receiving a Cycles cage-baked tangent normal map from a ribbed high-poly source",
"alt": "A baked tangent-space normal map shown as a lilac card beside a bronze plate wearing it, with a radial embossed pattern, both standing in low display plinths.",
"witnessesFix": "Statistical gates not byte-identity: detail frac 0.7211 / MAD 0.09356 vs flat 0.0000 / 0.00277; --flat-source exits 5; type=NORMAL not bake_type; RNA identical on 4.5.11, 5.1.2, 5.2.1",
"hero": "docs/gallery/assets/bake-normal-high-to-low-hero.webp",
"preview": "examples/bake-normal-high-to-low/preview.webp",
"tags": [
"mesh"
]
},
{
"name": "gn-socket-rename",
"dir": "examples/gn-socket-rename",
"teaches": "Compare and Random Value socket identifiers collapsed onto reused names in 5.2; enabled-name lookup wires on 4.5, 5.1, and 5.2",
"alt": "A copper measuring column with graduated tick marks standing on a grey base plate.",
"witnessesFix": "A_INT and Min_001 present on 4.5.11 and 5.1.2, absent on 5.2.1. --legacy-ids exits 5 only on 5.2. Evaluated 16 verts and gauge_h 1.80 on eight column verts; the still draws the column graduations from gauge_h, so an unwired Random Value renders a blank column",
"hero": "docs/gallery/assets/gn-socket-rename-hero.webp",
"preview": "examples/gn-socket-rename/preview.webp",
"tags": [
"geometry-nodes"
]
}
]
}