Repository navigation
Expand file tree
/
Copy pathindex.html
More file actions
567 lines (554 loc) · 50.1 KB
/
Copy pathindex.html
File metadata and controls
567 lines (554 loc) · 50.1 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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Examples Gallery — Blender Developer Tools</title>
<meta name="description" content="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." />
<link rel="canonical" href="https://tmhsdigital.github.io/Blender-Developer-Tools/gallery/" />
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml" />
<meta name="theme-color" content="#1a1b1e" />
<meta property="og:type" content="website" />
<meta property="og:title" content="Examples Gallery — Blender Developer Tools" />
<meta property="og:description" content="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." />
<meta property="og:url" content="https://tmhsdigital.github.io/Blender-Developer-Tools/gallery/" />
<meta property="og:image" content="https://tmhsdigital.github.io/Blender-Developer-Tools/gallery/assets/swatch-grid-hero.webp" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Examples Gallery — Blender Developer Tools" />
<meta name="twitter:description" content="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." />
<meta name="twitter:image" content="https://tmhsdigital.github.io/Blender-Developer-Tools/gallery/assets/swatch-grid-hero.webp" />
<style>
/* fonts are deployed by the landing build (docs/fonts/) */
@font-face { font-family: 'Barlow Condensed'; font-weight: 600; font-display: swap;
src: url('../fonts/barlow-condensed-600.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-weight: 400; font-display: swap;
src: url('../fonts/inter-regular.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-weight: 500; font-display: swap;
src: url('../fonts/inter-medium.woff2') format('woff2'); }
@font-face { font-family: 'JetBrains Mono'; font-weight: 400; font-display: swap;
src: url('../fonts/jetbrains-mono-regular.woff2') format('woff2'); }
/* Blender-viewport system, shared with the landing page. Dark only. */
:root {
--bg: #1a1b1e; --surface: #222327; --surface-2: #2a2b30; --bg2: #131417;
--border: #3a3b40; --text: #e8e9eb; --text-dim: #9698a0;
--select: #ff8c19; --ok: #6dc96d;
--radius: 4px; --radius-lg: 6px; --maxw: 1080px;
--font-display: 'Barlow Condensed', 'Arial Narrow', sans-serif;
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
--font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
--code-k: #ff7b72; --code-s: #a5d6ff; --code-c: #9698a0; --code-n: #79c0ff;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body { font-family: var(--font-sans); background: var(--bg); color: var(--text);
line-height: 1.6; min-height: 100vh; -webkit-font-smoothing: antialiased; }
a { color: var(--select); text-decoration: none; }
a:hover { text-decoration: underline; }
:focus-visible { outline: 2px solid var(--select); outline-offset: 2px; border-radius: 3px; }
::selection { background: color-mix(in srgb, var(--select) 40%, transparent); }
.skip { position: absolute; left: -999px; top: 0; background: var(--select); color: #1a1b1e;
padding: 0.5rem 1rem; border-radius: var(--radius); z-index: 10; font-weight: 500; }
.skip:focus { left: 0.5rem; top: 0.5rem; }
.hud { font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.04em;
color: var(--text-dim); text-transform: uppercase; }
.topbar { position: sticky; top: 0; z-index: 5; display: flex; align-items: center;
justify-content: space-between; gap: 1rem; padding: 0 1.25rem; height: 46px;
background: color-mix(in srgb, var(--surface) 92%, transparent);
backdrop-filter: blur(10px); border-bottom: 1px solid var(--border); }
.topbar .back { color: var(--text-dim); font-size: 0.8125rem; font-weight: 500; }
.topbar .back:hover { color: var(--select); text-decoration: none; }
.topbar-right { display: flex; align-items: center; gap: 0.85rem; }
.topbar-right .ghlink { color: var(--text-dim); font-size: 0.8125rem; font-weight: 500; }
.topbar-right .ghlink:hover { color: var(--select); text-decoration: none; }
header.hero { max-width: var(--maxw); margin: 0 auto; padding: 2.75rem 1.25rem 1.25rem; }
header.hero h1 { font-family: var(--font-display); font-weight: 600; text-transform: uppercase;
font-size: clamp(2.2rem, 5vw, 3.4rem); letter-spacing: 0.005em; line-height: 0.98; }
header.hero p { color: var(--text-dim); max-width: 62ch; margin-top: 0.7rem; font-size: 1rem; }
/* ---- index: filter chips ---- */
.chips { max-width: var(--maxw); margin: 0 auto; padding: 0 1.25rem 0.25rem;
display: flex; flex-wrap: wrap; gap: 0.5rem; }
.chip { background: var(--surface-2); border: 1px solid var(--border); color: var(--text-dim);
border-radius: 3px; padding: 0.22rem 0.7rem; font-size: 0.72rem; font-weight: 400;
font-family: var(--font-mono); cursor: pointer; transition: color 0.15s, border-color 0.15s; }
.chip:hover { color: var(--select); border-color: var(--select); }
.chip.active { color: #1a1b1e; background: var(--select); border-color: var(--select); }
main { max-width: var(--maxw); margin: 0 auto; padding: 1rem 1.25rem 2rem; }
.grid { display: grid; grid-template-columns: 1fr; gap: 1.5rem; align-items: stretch; }
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
overflow: hidden; display: flex; flex-direction: column;
outline: 2px solid transparent; outline-offset: -1px;
transition: outline-color 0.12s ease, border-color 0.12s ease; }
.card:hover { border-color: var(--select); outline-color: var(--select); }
.card.hidden { display: none; }
.card-media { display: block; background: var(--bg2); line-height: 0; }
.card-media img { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
.card-body { padding: 1.15rem 1.4rem 1.45rem; display: flex; flex-direction: column; flex: 1 1 auto; }
.card-body h2 { font-family: var(--font-mono); font-size: 1rem; font-weight: 400; margin-bottom: 0.5rem; }
.card-body h2 a { color: var(--text); }
.card-body h2 a:hover { color: var(--select); text-decoration: none; }
.teaches { color: var(--text); margin-bottom: 0.7rem; font-size: 0.92rem; }
.witnesses { color: var(--text-dim); font-size: 0.85rem; margin-bottom: 1rem; }
.tag { display: inline-block; font-family: var(--font-mono); font-size: 0.62rem; text-transform: uppercase;
letter-spacing: 0.06em; color: var(--select); border: 1px solid color-mix(in srgb, var(--select) 55%, transparent);
border-radius: 3px; padding: 0.08rem 0.5rem; margin-right: 0.4rem; vertical-align: 1px; }
.card-link { font-weight: 600; font-size: 0.95rem; margin-top: auto; }
/* ---- detail page ---- */
.detail-hero { border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden;
background: var(--bg2); padding: 0; display: block; width: 100%; cursor: zoom-in; line-height: 0; }
.detail-hero img { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }
.zoom-hint { color: var(--text-dim); font-size: 0.78rem; margin-top: 0.4rem; }
.callout { border: 1px solid color-mix(in srgb, var(--select) 45%, var(--border));
border-left: 3px solid var(--select); border-radius: var(--radius);
background: color-mix(in srgb, var(--select) 7%, var(--surface));
padding: 0.85rem 1.1rem; margin: 1.5rem 0; font-size: 0.95rem; }
.runline { display: flex; align-items: stretch; gap: 0.5rem; margin: 1.5rem 0; }
.runline pre { flex: 1 1 auto; background: var(--surface-2); border: 1px solid var(--border);
border-radius: var(--radius); padding: 0.7rem 0.9rem; overflow-x: auto;
font-family: var(--font-mono); font-size: 0.83rem; line-height: 1.5; }
.copy-btn { flex: 0 0 auto; background: var(--surface-2); border: 1px solid var(--border);
color: var(--text-dim); border-radius: var(--radius); padding: 0 0.85rem; cursor: pointer;
font-family: var(--font-sans); font-size: 0.82rem; font-weight: 500; transition: color 0.15s, border-color 0.15s; }
.copy-btn:hover { color: var(--select); border-color: var(--select); }
.detail-section { margin-top: 2.25rem; }
.detail-section > h2 { font-family: var(--font-display); font-weight: 600; text-transform: uppercase;
font-size: 1.45rem; letter-spacing: 0.005em; padding-bottom: 0.5rem;
border-bottom: 1px solid var(--border); margin-bottom: 1rem; }
.md h1, .md h2, .md h3 { letter-spacing: -0.01em; margin: 1.4rem 0 0.6rem; line-height: 1.25; }
.md h1 { font-size: 1.35rem; } .md h2 { font-size: 1.15rem; } .md h3 { font-size: 1rem; }
.md p { margin: 0.7rem 0; }
.md ul { margin: 0.7rem 0 0.7rem 1.4rem; }
.md li { margin: 0.3rem 0; }
.md code { background: var(--surface-2); border: 1px solid var(--border); padding: 0.08rem 0.35rem;
border-radius: 4px; font-family: var(--font-mono); font-size: 0.84em; }
.md pre { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius);
padding: 0.85rem 1rem; overflow-x: auto; margin: 0.9rem 0; }
.md pre code { background: none; border: none; padding: 0; font-size: 0.83rem; line-height: 1.55; }
.src pre { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius);
padding: 1rem 1.1rem; overflow-x: auto; font-family: var(--font-mono);
font-size: 0.82rem; line-height: 1.55; }
.src .k { color: var(--code-k); } .src .s { color: var(--code-s); }
.src .c { color: var(--code-c); font-style: italic; } .src .n { color: var(--code-n); }
.src-meta { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem;
flex-wrap: wrap; margin-bottom: 0.6rem; color: var(--text-dim); font-size: 0.85rem; }
.src-meta code { font-family: var(--font-mono); font-size: 0.82rem; }
.lightbox { position: fixed; inset: 0; z-index: 50; display: none; align-items: center;
justify-content: center; background: rgba(0,0,0,0.85); padding: 2rem; cursor: zoom-out; }
.lightbox.open { display: flex; }
.lightbox img { max-width: 100%; max-height: 100%; border-radius: var(--radius); }
footer { border-top: 1px solid var(--border); background: var(--surface); margin-top: 2rem; }
footer .statusbar { max-width: var(--maxw); margin: 0 auto; padding: 0.55rem 1.25rem;
display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
font-family: var(--font-mono); font-size: 0.6875rem; letter-spacing: 0.04em;
text-transform: uppercase; color: var(--text-dim); }
footer .statusbar code { font-family: inherit; text-transform: none; }
@media (min-width: 720px) { .grid { grid-template-columns: 1fr 1fr; gap: 1.75rem; } }
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
.card { transition: none; }
.card:hover { transform: none; }
}
</style>
</head>
<body>
<a class="skip" href="#main">Skip to content</a>
<div class="topbar">
<a class="back" href="../"><span aria-hidden="true">←</span> Blender Developer Tools</a>
<div class="topbar-right">
<a class="ghlink" href="https://github.com/TMHSDigital/Blender-Developer-Tools">GitHub</a>
</div>
</div>
<header class="hero">
<h1>Examples Gallery</h1>
<p>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.</p>
</header>
<div class="chips" role="toolbar" aria-label="Filter examples by topic">
<button class="chip active" data-tag="" type="button">All</button>
<button class="chip" data-tag="alpha" type="button">alpha</button>
<button class="chip" data-tag="animation" type="button">animation</button>
<button class="chip" data-tag="armature" type="button">armature</button>
<button class="chip" data-tag="attributes" type="button">attributes</button>
<button class="chip" data-tag="bevel" type="button">bevel</button>
<button class="chip" data-tag="bmesh" type="button">bmesh</button>
<button class="chip" data-tag="compositor" type="button">compositor</button>
<button class="chip" data-tag="constraints" type="button">constraints</button>
<button class="chip" data-tag="context" type="button">context</button>
<button class="chip" data-tag="curves" type="button">curves</button>
<button class="chip" data-tag="depsgraph" type="button">depsgraph</button>
<button class="chip" data-tag="drivers" type="button">drivers</button>
<button class="chip" data-tag="export" type="button">export</button>
<button class="chip" data-tag="geometry-nodes" type="button">geometry-nodes</button>
<button class="chip" data-tag="grease-pencil" type="button">grease-pencil</button>
<button class="chip" data-tag="images" type="button">images</button>
<button class="chip" data-tag="instancing" type="button">instancing</button>
<button class="chip" data-tag="materials" type="button">materials</button>
<button class="chip" data-tag="mesh" type="button">mesh</button>
<button class="chip" data-tag="node-groups" type="button">node-groups</button>
<button class="chip" data-tag="objects" type="button">objects</button>
<button class="chip" data-tag="operators" type="button">operators</button>
<button class="chip" data-tag="performance" type="button">performance</button>
<button class="chip" data-tag="rendering" type="button">rendering</button>
<button class="chip" data-tag="sequencer" type="button">sequencer</button>
<button class="chip" data-tag="shape-keys" type="button">shape-keys</button>
<button class="chip" data-tag="transforms" type="button">transforms</button>
<button class="chip" data-tag="uv" type="button">uv</button>
</div>
<main id="main">
<div class="grid">
<article class="card" data-tags="materials rendering">
<a class="card-media" href="swatch-grid/" aria-label="swatch-grid example detail page">
<img src="assets/swatch-grid-hero.webp" alt="swatch-grid — Procedural Principled materials — metal and dielectric, the emission pattern, and the cross-version set_specular shim." loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="swatch-grid/">swatch-grid</a></h2>
<p class="teaches">Procedural Principled materials — metal and dielectric, the emission pattern, and the cross-version set_specular shim.</p>
<p class="witnesses"><span class="tag">witnesses</span> EEVEE engine-id mapping: BLENDER_EEVEE on 5.x, BLENDER_EEVEE_NEXT on 4.2–4.5.</p>
<a class="card-link" href="swatch-grid/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="animation">
<a class="card-media" href="turntable/" aria-label="turntable example detail page">
<img src="assets/turntable-hero.webp" alt="turntable — A slotted-actions Z-rotation turntable keyed through the cross-version channelbag path (get_channelbag_for_slot)." loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="turntable/">turntable</a></h2>
<p class="teaches">A slotted-actions Z-rotation turntable keyed through the cross-version channelbag path (get_channelbag_for_slot).</p>
<p class="witnesses"><span class="tag">witnesses</span> Slotted-actions boundary: ensure-helper channelbag on 5.x, strip.channelbag on 4.4/4.5.</p>
<a class="card-link" href="turntable/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="geometry-nodes materials">
<a class="card-media" href="gn-sdf-remesh/" aria-label="gn-sdf-remesh example detail page">
<img src="assets/gn-sdf-remesh-hero.webp" alt="gn-sdf-remesh — A Geometry Nodes SDF remesh (MeshToSDFGrid → GridToMesh at the SDF zero-level), with a Set Material node carrying the material through the remesh." loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="gn-sdf-remesh/">gn-sdf-remesh</a></h2>
<p class="teaches">A Geometry Nodes SDF remesh (MeshToSDFGrid → GridToMesh at the SDF zero-level), with a Set Material node carrying the material through the remesh.</p>
<p class="witnesses"><span class="tag">witnesses</span> An SDF grid is meshed with Grid to Mesh, not Volume to Mesh; GN geometry needs Set Material or it renders untextured.</p>
<a class="card-link" href="gn-sdf-remesh/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="depsgraph export">
<a class="card-media" href="depsgraph-export/" aria-label="depsgraph-export example detail page">
<img src="assets/depsgraph-export-hero.webp" alt="depsgraph-export — The depsgraph lifetime contract — evaluated_get().to_mesh() paired with to_mesh_clear() — measured against an OBJ export of the same object." loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="depsgraph-export/">depsgraph-export</a></h2>
<p class="teaches">The depsgraph lifetime contract — evaluated_get().to_mesh() paired with to_mesh_clear() — measured against an OBJ export of the same object.</p>
<p class="witnesses"><span class="tag">witnesses</span> Exports ship evaluated geometry: the exported vertex count equals the subsurf-applied count and is strictly greater than the base mesh.</p>
<a class="card-link" href="depsgraph-export/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="mesh performance">
<a class="card-media" href="wave-displace/" aria-label="wave-displace example detail page">
<img src="assets/wave-displace-hero.webp" alt="wave-displace — 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." loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="wave-displace/">wave-displace</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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.</p>
<a class="card-link" href="wave-displace/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="drivers animation">
<a class="card-media" href="driver-wave/" aria-label="driver-wave example detail page">
<img src="assets/driver-wave-hero.webp" alt="driver-wave — A driver_namespace function driving sixteen column heights through SCRIPTED drivers — the sine skyline is entirely driver-evaluated." loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="driver-wave/">driver-wave</a></h2>
<p class="teaches">A driver_namespace function driving sixteen column heights through SCRIPTED drivers — the sine skyline is entirely driver-evaluated.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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.</p>
<a class="card-link" href="driver-wave/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="mesh bmesh">
<a class="card-media" href="bmesh-gear/" aria-label="bmesh-gear example detail page">
<img src="assets/bmesh-gear-hero.webp" alt="bmesh-gear — 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." loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="bmesh-gear/">bmesh-gear</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> Parametric bmesh topology is exactly predictable: verts, edges, and faces match their closed forms, and every edge borders exactly two faces (watertight).</p>
<a class="card-link" href="bmesh-gear/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="materials node-groups">
<a class="card-media" href="shader-node-group/" aria-label="shader-node-group example detail page">
<img src="assets/shader-node-group-hero.webp" alt="shader-node-group — One reusable shader group declared via tree.interface.new_socket, instanced in two materials with different Tint values — two spheres, one group, two colors." loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="shader-node-group/">shader-node-group</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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.</p>
<a class="card-link" href="shader-node-group/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="operators context">
<a class="card-media" href="temp-override-join/" aria-label="temp-override-join example detail page">
<img src="assets/temp-override-join-hero.webp" alt="temp-override-join — Join three unit cubes into a staircase under bpy.context.temp_override — the supported replacement for the removed context.copy() dict-pass form." loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="temp-override-join/">temp-override-join</a></h2>
<p class="teaches">Join three unit cubes into a staircase under bpy.context.temp_override — the supported replacement for the removed context.copy() dict-pass form.</p>
<p class="witnesses"><span class="tag">witnesses</span> temp_override actually applies: join consumes the sources, exactly one mesh remains, topology is verts = 8 × steps, and local Z spans all three steps.</p>
<a class="card-link" href="temp-override-join/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="geometry-nodes instancing">
<a class="card-media" href="gn-instance-grid/" aria-label="gn-instance-grid example detail page">
<img src="assets/gn-instance-grid-hero.webp" alt="gn-instance-grid — A generative Geometry Nodes tree — Mesh Grid → Instance on Points → Realize Instances → Set Shade Smooth — attached as a NODES modifier with no Group Input…" loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="gn-instance-grid/">gn-instance-grid</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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.</p>
<a class="card-link" href="gn-instance-grid/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="mesh shape-keys">
<a class="card-media" href="shape-key-blend/" aria-label="shape-key-blend example detail page">
<img src="assets/shape-key-blend-hero.webp" alt="shape-key-blend — 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." loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="shape-key-blend/">shape-key-blend</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> Shape keys do not rewrite mesh.vertices: every evaluated vert matches basis + value × (key − basis), including the flared top half-extent.</p>
<a class="card-link" href="shape-key-blend/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="curves bevel">
<a class="card-media" href="curve-bevel-arc/" aria-label="curve-bevel-arc example detail page">
<img src="assets/curve-bevel-arc-hero.webp" alt="curve-bevel-arc — 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…" loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="curve-bevel-arc/">curve-bevel-arc</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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.</p>
<a class="card-link" href="curve-bevel-arc/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="compositor rendering">
<a class="card-media" href="compositor-glare/" aria-label="compositor-glare example detail page">
<img src="assets/compositor-glare-hero.webp" alt="compositor-glare — 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…" loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="compositor-glare/">compositor-glare</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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.</p>
<a class="card-link" href="compositor-glare/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="constraints animation">
<a class="card-media" href="damped-track-aim/" aria-label="damped-track-aim example detail page">
<img src="assets/damped-track-aim-hero.webp" alt="damped-track-aim — 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." loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="damped-track-aim/">damped-track-aim</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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.</p>
<a class="card-link" href="damped-track-aim/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="mesh materials attributes">
<a class="card-media" href="color-attribute-wheel/" aria-label="color-attribute-wheel example detail page">
<img src="assets/color-attribute-wheel-hero.webp" alt="color-attribute-wheel — The modern color-attributes API — mesh.color_attributes.new() on the CORNER domain, filled by expanding per-vertex HSV across face corners with…" loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="color-attribute-wheel/">color-attribute-wheel</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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.</p>
<a class="card-link" href="color-attribute-wheel/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="objects transforms">
<a class="card-media" href="parent-inverse-orrery/" aria-label="parent-inverse-orrery example detail page">
<img src="assets/parent-inverse-orrery-hero.webp" alt="parent-inverse-orrery — Data-API parenting for a brass orrery — the keep-world idiom (child.parent = pivot; child.matrix_parent_inverse = pivot.matrix_world.inverted()) carrying arms…" loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="parent-inverse-orrery/">parent-inverse-orrery</a></h2>
<p class="teaches">Data-API parenting for a brass orrery — the keep-world idiom (child.parent = pivot; child.matrix_parent_inverse = pivot.matrix_world.inverted()) carrying arms, planets, and a two-level moon through spinning pivots.</p>
<p class="witnesses"><span class="tag">witnesses</span> Bare `.parent =` really does teleport the child; the idiom restores world position exactly; matrix_world stays stale until view_layer.update(); every orbit lands on its closed form.</p>
<a class="card-link" href="parent-inverse-orrery/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="grease-pencil attributes">
<a class="card-media" href="grease-pencil-rosette/" aria-label="grease-pencil-rosette example detail page">
<img src="assets/grease-pencil-rosette-hero.webp" alt="grease-pencil-rosette — Grease Pencil v3's attribute-based API — layer → frames.new(1).drawing → add_strokes → per-point position/radius/opacity/vertex_color — drawing five nested…" loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="grease-pencil-rosette/">grease-pencil-rosette</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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.</p>
<a class="card-link" href="grease-pencil-rosette/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="armature depsgraph">
<a class="card-media" href="armature-bend/" aria-label="armature-bend example detail page">
<img src="assets/armature-bend-hero.webp" alt="armature-bend — Rigging end to end in the data API — edit_bones chain construction, name-bound vertex groups with smoothstep blend zones, posing, and depsgraph evaluation…" loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="armature-bend/">armature-bend</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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.</p>
<a class="card-link" href="armature-bend/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="curves depsgraph rendering">
<a class="card-media" href="text-version-stamp/" aria-label="text-version-stamp example detail page">
<img src="assets/text-version-stamp-hero.webp" alt="text-version-stamp — 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…" loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="text-version-stamp/">text-version-stamp</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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.</p>
<a class="card-link" href="text-version-stamp/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="images performance rendering">
<a class="card-media" href="image-pixels-testcard/" aria-label="image-pixels-testcard example detail page">
<img src="assets/image-pixels-testcard-hero.webp" alt="image-pixels-testcard — 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…" loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="image-pixels-testcard/">image-pixels-testcard</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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).</p>
<a class="card-link" href="image-pixels-testcard/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="images rendering alpha">
<a class="card-media" href="png-exr-alpha/" aria-label="png-exr-alpha example detail page">
<img src="assets/png-exr-alpha-hero.webp" alt="png-exr-alpha — Float-image PNG save trap — float_buffer=True Image.save() writes RGBA16 and unpremultiplies as if associated-alpha, clamping straight-authored dark values at…" loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="png-exr-alpha/">png-exr-alpha</a></h2>
<p class="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).</p>
<p class="witnesses"><span class="tag">witnesses</span> 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.</p>
<a class="card-link" href="png-exr-alpha/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="mesh bmesh uv materials">
<a class="card-media" href="uv-layer-grid/" aria-label="uv-layer-grid example detail page">
<img src="assets/uv-layer-grid-hero.webp" alt="uv-layer-grid — 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…" loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="uv-layer-grid/">uv-layer-grid</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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.</p>
<a class="card-link" href="uv-layer-grid/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="sequencer rendering">
<a class="card-media" href="vse-cut-list/" aria-label="vse-cut-list example detail page">
<img src="assets/vse-cut-list-hero.webp" alt="vse-cut-list — 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…" loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="vse-cut-list/">vse-cut-list</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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.</p>
<a class="card-link" href="vse-cut-list/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="export uv">
<a class="card-media" href="gltf-export-roundtrip/" aria-label="gltf-export-roundtrip example detail page">
<img src="assets/gltf-export-roundtrip-hero.webp" alt="gltf-export-roundtrip — A sci-fi supply crate exported to glTF and re-imported, verifying the round-trip against the depsgraph-evaluated mesh within float tolerances." loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="gltf-export-roundtrip/">gltf-export-roundtrip</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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.</p>
<a class="card-link" href="gltf-export-roundtrip/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="depsgraph mesh">
<a class="card-media" href="lod-decimate-chain/" aria-label="lod-decimate-chain example detail page">
<img src="assets/lod-decimate-chain-hero.webp" alt="lod-decimate-chain — A retro rocket at LOD0/1/2 via the Decimate modifier evaluated through the depsgraph." loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="lod-decimate-chain/">lod-decimate-chain</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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).</p>
<a class="card-link" href="lod-decimate-chain/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="armature mesh">
<a class="card-media" href="vertex-weight-limit/" aria-label="vertex-weight-limit example detail page">
<img src="assets/vertex-weight-limit-hero.webp" alt="vertex-weight-limit — A rigged mech arm pruned to the game-engine cap of four bone influences per vertex, through the data API." loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="vertex-weight-limit/">vertex-weight-limit</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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.</p>
<a class="card-link" href="vertex-weight-limit/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="mesh uv">
<a class="card-media" href="triangulate-tangents/" aria-label="triangulate-tangents example detail page">
<img src="assets/triangulate-tangents-hero.webp" alt="triangulate-tangents — A machined buckler verifying the tangent-space contract a game engine's normal mapping depends on." loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="triangulate-tangents/">triangulate-tangents</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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.</p>
<a class="card-link" href="triangulate-tangents/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="export armature">
<a class="card-media" href="gltf-skin-roundtrip/" aria-label="gltf-skin-roundtrip example detail page">
<img src="assets/gltf-skin-roundtrip-hero.webp" alt="gltf-skin-roundtrip — A rigged mech scorpion exported to glTF with skins and re-imported, verifying the skinning contract the geometry round-trip left uncovered." loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="gltf-skin-roundtrip/">gltf-skin-roundtrip</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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.</p>
<a class="card-link" href="gltf-skin-roundtrip/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="sequencer rendering">
<a class="card-media" href="vse-gamma-cross/" aria-label="vse-gamma-cross example detail page">
<img src="assets/vse-gamma-cross-hero.webp" alt="vse-gamma-cross — 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." loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="vse-gamma-cross/">vse-gamma-cross</a></h2>
<p class="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.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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).</p>
<a class="card-link" href="vse-gamma-cross/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
<article class="card" data-tags="rendering objects">
<a class="card-media" href="light-link-studio/" aria-label="light-link-studio example detail page">
<img src="assets/light-link-studio-hero.webp" alt="light-link-studio — One key, one hero: a light linked to a receiver collection lights only the hero, proven by two pixel renders in one pass." loading="lazy" decoding="async" />
</a>
<div class="card-body">
<h2><a href="light-link-studio/">light-link-studio</a></h2>
<p class="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 206% while the hero holds at 0.0% drift.</p>
<p class="witnesses"><span class="tag">witnesses</span> 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.7x on 4.5.11 EEVEE Next and 5.1.2 EEVEE), Cycles pinned for deterministic samples.</p>
<a class="card-link" href="light-link-studio/">View example <span aria-hidden="true">→</span></a>
</div>
</article>
</div>
</main>
<footer>
<div class="statusbar">
<span>generated from <code>examples/gallery.json</code></span>
<span>CC-BY-NC-ND-4.0</span>
<span style="color: var(--ok);">exit 0</span>
</div>
</footer>
<script>
(function () {
var chips = document.querySelectorAll('.chip');
var cards = document.querySelectorAll('.card');
chips.forEach(function (chip) {
chip.addEventListener('click', function () {
chips.forEach(function (c) { c.classList.remove('active'); });
chip.classList.add('active');
var tag = chip.getAttribute('data-tag');
cards.forEach(function (card) {
var tags = (card.getAttribute('data-tags') || '').split(' ');
card.classList.toggle('hidden', !!tag && tags.indexOf(tag) === -1);
});
});
});
})();
</script>
</body>
</html>