Skip to content

Commit d3c574f

Browse files
TMHSDigitalclaude
andcommitted
feat(showcase): add gallery categories and backfill pieces as village
Every showcase/gallery.json piece now carries a category (village, sports, nature, household, vehicles). The gallery index gains a "Showcase category" segmented filter (one button per populated category, with counts), deep linked as #c=<key>; it hides in the Examples view. Detail pages link their category. build_gallery.py fails on a missing/unknown category, and the validate workflow imports the same CATEGORIES table. Verified: build + check_site_links (91 pages, 0 failures); renaming one category to 'vilage' exits 1 with a named error, restored. Playwright: #k=showcase&c=village shows 36 of 88, Examples view shows 52 and hides the row, category button with kind All shows 36. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent faad169 commit d3c574f

95 files changed

Lines changed: 974 additions & 49 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/validate.yml‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,16 @@ jobs:
256256
if path.replace('\\', '/') not in listed:
257257
errors.append(f'{key}: {path} on disk but not in manifest')
258258
259+
# Every showcase piece names a category the gallery build knows.
260+
sys.path.insert(0, 'scripts')
261+
from build_gallery import CATEGORIES
262+
for piece in json.load(open('showcase/gallery.json', encoding='utf-8'))['pieces']:
263+
if piece.get('category') not in CATEGORIES:
264+
errors.append(
265+
f"showcase/gallery.json: {piece['name']} category "
266+
f"{piece.get('category')!r} not in {sorted(CATEGORIES)}"
267+
)
268+
259269
if errors:
260270
for e in errors:
261271
print(f'::error::{e}', file=sys.stderr)

‎CLAUDE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ rules/<rule-name>.mdc - Anti-pattern rules, 9 total
2222
templates/<template-name>/ - Starter projects, 3 total
2323
snippets/<snippet-name>.py - Standalone code patterns, 27 total
2424
examples/<name>/ - Runnable smoke-gated examples, 60 total (+ gallery.json)
25-
showcase/<name>/ - Budget-conformance props, 36 pieces (sibling of examples/; see showcase/README.md)
25+
showcase/<name>/ - Budget-conformance props, 36 pieces, each with a gallery `category` (sibling of examples/; see showcase/README.md § Categories)
2626
scripts/build_gallery.py - Regenerates docs/gallery/ from examples/gallery.json + showcase/gallery.json
2727
scripts/site/ - Vendored landing-page build (Jinja2); tokens.css is the shared palette
2828
tests/check_site_links.py - Internal link/anchor/alt gate over the built site (docs/)

‎ROADMAP.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,11 @@ Not committed; target list for the next content version. (v0.3.0 shipped the smo
155155
- Tiered skep beehive on a stand as a game-prop showcase piece — coiled straw courses as stacked tori: each course seated on the one below, the dome closing on its axis
156156
- Rowboat with oars in oarlocks as a game-prop showcase piece — oar shafts passing through the oarlock horns with clearance inside a band, hull resting on its keel line on a flat deck/floor
157157
- Weathervane as a game-prop showcase piece — arrow and cardinal arms on a spindle: the arrow's pivot bore coaxial with the spindle and the arrow statically balanced (centroid on the pivot axis within a band)
158+
- Showcase pieces above this line are category `village`. Candidates for the other categories (`showcase/README.md` § Categories):
159+
- **Sports:** basketball hoop (pole, backboard, rim, hung net), soccer goal (tube frame, netting), tennis net on posts, weight rack with plates and dumbbells; alternates bowling pins and ball, skateboard
160+
- **Nature:** pine tree (trunk, tiered branch whorls), boulder cluster, fallen log with fungus, tree stump with mushrooms; alternates flowering shrub, cattail reeds
161+
- **Household:** articulated desk lamp, office chair (five-star base, casters), kitchen stool, mini fridge; alternates floor fan, laundry basket
162+
- **Vehicles:** traffic cone and barrier set, quad drone, hover bike, sci-fi cargo loader; alternates sci-fi supply crate
158163
- `persistent` app-handler witness — handlers registered without `@bpy.app.handlers.persistent` are dropped by `wm.read_homefile`/file load while persistent ones survive; assert the registered-handler set before and after a reload (silent loss AI code hits)
159164
- Link vs append witness — `bpy.data.libraries.load(link=True)` yields a linked, non-editable datablock (`library` set, `is_editable` False) while append yields a local copy; write a temp .blend with `bpy.data.libraries.write`, then assert both paths
160165
- Orphan purge witness — `bpy.data.orphans_purge(do_recursive=...)` removes exactly the zero-user datablocks computed independently beforehand, and `bpy.data.batch_remove` removes a given set in one call (counts closed-form)

‎docs/gallery/anvil/index.html‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,14 @@
116116
border-radius: var(--radius); padding: 0.3rem 0.5rem; cursor: pointer;
117117
font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.04em; text-transform: uppercase; }
118118
.sort select:hover, .sort select:focus { color: var(--select); border-color: var(--select); outline: none; }
119+
/* Category row: one segmented group that scrolls sideways on narrow
120+
screens rather than wrapping its bordered buttons. */
121+
.cats { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: thin;
122+
scrollbar-color: var(--border) transparent; }
123+
.cats[hidden] { display: none; }
124+
.cats .density { flex: 0 0 auto; }
125+
.cats-label { font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.04em;
126+
text-transform: uppercase; color: var(--text-dim); flex: 0 0 auto; }
119127
.tags-toggle { display: none; background: var(--surface-2); border: 1px solid var(--border);
120128
color: var(--text-dim); border-radius: 3px; padding: 0.3rem 0.7rem; cursor: pointer;
121129
font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.04em; text-transform: uppercase; }
@@ -356,6 +364,7 @@ <h1>anvil</h1>
356364
</button>
357365
<p class="zoom-hint">Rendered headless by the showcase piece itself — click to zoom.</p>
358366
<div class="callout"><span class="tag">witnesses</span> Recomputed: 3052 tris, two materials with 1040 wood and 854 metal faces, UVs in 0..1 with zero AABB overlap, outer AABB 0.621×0.414×0.489 m, LOD ratios in band, convex collider 134 tris, hygiene 0, 16 grounded staves, hoop bite 0.003 m, foot-on-head gap 0.003 m, waist corner reach 0.8572, non-empty glTF. --skip-decimate exits 9; --stray-vert 15; --lift-z/--short-staves 16; --float-anvil 17; --round-band 18; --round-waist 19.</div>
367+
<p class="taglist"><span class="tag">category</span> <a href="../#k=showcase&amp;c=village">Village</a></p>
359368
<p class="taglist"><span class="tag">tags</span> <a href="../#tag=mesh">mesh</a> <a href="../#tag=export">export</a> <a href="../#tag=showcase">showcase</a></p>
360369
<div class="runline">
361370
<pre id="runCmd">blender --background --python showcase/anvil/anvil.py --</pre>

‎docs/gallery/apothecary-shelf/index.html‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,14 @@
116116
border-radius: var(--radius); padding: 0.3rem 0.5rem; cursor: pointer;
117117
font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.04em; text-transform: uppercase; }
118118
.sort select:hover, .sort select:focus { color: var(--select); border-color: var(--select); outline: none; }
119+
/* Category row: one segmented group that scrolls sideways on narrow
120+
screens rather than wrapping its bordered buttons. */
121+
.cats { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: thin;
122+
scrollbar-color: var(--border) transparent; }
123+
.cats[hidden] { display: none; }
124+
.cats .density { flex: 0 0 auto; }
125+
.cats-label { font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.04em;
126+
text-transform: uppercase; color: var(--text-dim); flex: 0 0 auto; }
119127
.tags-toggle { display: none; background: var(--surface-2); border: 1px solid var(--border);
120128
color: var(--text-dim); border-radius: 3px; padding: 0.3rem 0.7rem; cursor: pointer;
121129
font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.04em; text-transform: uppercase; }
@@ -356,6 +364,7 @@ <h1>apothecary-shelf</h1>
356364
</button>
357365
<p class="zoom-hint">Rendered headless by the showcase piece itself — click to zoom.</p>
358366
<div class="callout"><span class="tag">witnesses</span> Recomputed: 10456 tris, six materials with a face floor each, UVs in 0..1 with zero AABB overlap, outer AABB 0.876×0.274×1.076 m, zmin 0, hygiene 0 including zero coplanar cross-shell pairs, both sides grounded, seven cross members housed 8 mm into each side and 14 mm clear of its far face, fifteen vessels seated 1.0–1.8 mm into their shelves, every cork and lid biting 5 mm, every label 0.3 mm into its body and 0.8 mm proud, no vessel touching another or the carcass, and at least 24 mm of headroom between every vessel and the member above it. --tall-flask exits 20 at −5.5 mm while every seat and the envelope still pass; --short-shelves exits 17 on the dado bite.</div>
367+
<p class="taglist"><span class="tag">category</span> <a href="../#k=showcase&amp;c=village">Village</a></p>
359368
<p class="taglist"><span class="tag">tags</span> <a href="../#tag=mesh">mesh</a> <a href="../#tag=export">export</a> <a href="../#tag=showcase">showcase</a></p>
360369
<div class="runline">
361370
<pre id="runCmd">blender --background --python showcase/apothecary-shelf/apothecary_shelf.py --</pre>

‎docs/gallery/armature-bend/index.html‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,14 @@
116116
border-radius: var(--radius); padding: 0.3rem 0.5rem; cursor: pointer;
117117
font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.04em; text-transform: uppercase; }
118118
.sort select:hover, .sort select:focus { color: var(--select); border-color: var(--select); outline: none; }
119+
/* Category row: one segmented group that scrolls sideways on narrow
120+
screens rather than wrapping its bordered buttons. */
121+
.cats { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: thin;
122+
scrollbar-color: var(--border) transparent; }
123+
.cats[hidden] { display: none; }
124+
.cats .density { flex: 0 0 auto; }
125+
.cats-label { font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.04em;
126+
text-transform: uppercase; color: var(--text-dim); flex: 0 0 auto; }
119127
.tags-toggle { display: none; background: var(--surface-2); border: 1px solid var(--border);
120128
color: var(--text-dim); border-radius: 3px; padding: 0.3rem 0.7rem; cursor: pointer;
121129
font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.04em; text-transform: uppercase; }

‎docs/gallery/attribute-domain-shear/index.html‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,14 @@
116116
border-radius: var(--radius); padding: 0.3rem 0.5rem; cursor: pointer;
117117
font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.04em; text-transform: uppercase; }
118118
.sort select:hover, .sort select:focus { color: var(--select); border-color: var(--select); outline: none; }
119+
/* Category row: one segmented group that scrolls sideways on narrow
120+
screens rather than wrapping its bordered buttons. */
121+
.cats { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: thin;
122+
scrollbar-color: var(--border) transparent; }
123+
.cats[hidden] { display: none; }
124+
.cats .density { flex: 0 0 auto; }
125+
.cats-label { font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.04em;
126+
text-transform: uppercase; color: var(--text-dim); flex: 0 0 auto; }
119127
.tags-toggle { display: none; background: var(--surface-2); border: 1px solid var(--border);
120128
color: var(--text-dim); border-radius: 3px; padding: 0.3rem 0.7rem; cursor: pointer;
121129
font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.04em; text-transform: uppercase; }

‎docs/gallery/bake-normal-high-to-low/index.html‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,14 @@
116116
border-radius: var(--radius); padding: 0.3rem 0.5rem; cursor: pointer;
117117
font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.04em; text-transform: uppercase; }
118118
.sort select:hover, .sort select:focus { color: var(--select); border-color: var(--select); outline: none; }
119+
/* Category row: one segmented group that scrolls sideways on narrow
120+
screens rather than wrapping its bordered buttons. */
121+
.cats { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: thin;
122+
scrollbar-color: var(--border) transparent; }
123+
.cats[hidden] { display: none; }
124+
.cats .density { flex: 0 0 auto; }
125+
.cats-label { font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.04em;
126+
text-transform: uppercase; color: var(--text-dim); flex: 0 0 auto; }
119127
.tags-toggle { display: none; background: var(--surface-2); border: 1px solid var(--border);
120128
color: var(--text-dim); border-radius: 3px; padding: 0.3rem 0.7rem; cursor: pointer;
121129
font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.04em; text-transform: uppercase; }

‎docs/gallery/bmesh-gear/index.html‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,14 @@
116116
border-radius: var(--radius); padding: 0.3rem 0.5rem; cursor: pointer;
117117
font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.04em; text-transform: uppercase; }
118118
.sort select:hover, .sort select:focus { color: var(--select); border-color: var(--select); outline: none; }
119+
/* Category row: one segmented group that scrolls sideways on narrow
120+
screens rather than wrapping its bordered buttons. */
121+
.cats { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: thin;
122+
scrollbar-color: var(--border) transparent; }
123+
.cats[hidden] { display: none; }
124+
.cats .density { flex: 0 0 auto; }
125+
.cats-label { font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.04em;
126+
text-transform: uppercase; color: var(--text-dim); flex: 0 0 auto; }
119127
.tags-toggle { display: none; background: var(--surface-2); border: 1px solid var(--border);
120128
color: var(--text-dim); border-radius: 3px; padding: 0.3rem 0.7rem; cursor: pointer;
121129
font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.04em; text-transform: uppercase; }

‎docs/gallery/book-trolley/index.html‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,14 @@
116116
border-radius: var(--radius); padding: 0.3rem 0.5rem; cursor: pointer;
117117
font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.04em; text-transform: uppercase; }
118118
.sort select:hover, .sort select:focus { color: var(--select); border-color: var(--select); outline: none; }
119+
/* Category row: one segmented group that scrolls sideways on narrow
120+
screens rather than wrapping its bordered buttons. */
121+
.cats { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: thin;
122+
scrollbar-color: var(--border) transparent; }
123+
.cats[hidden] { display: none; }
124+
.cats .density { flex: 0 0 auto; }
125+
.cats-label { font-family: var(--font-mono); font-size: 0.68rem; letter-spacing: 0.04em;
126+
text-transform: uppercase; color: var(--text-dim); flex: 0 0 auto; }
119127
.tags-toggle { display: none; background: var(--surface-2); border: 1px solid var(--border);
120128
color: var(--text-dim); border-radius: 3px; padding: 0.3rem 0.7rem; cursor: pointer;
121129
font-family: var(--font-mono); font-size: 0.7rem; letter-spacing: 0.04em; text-transform: uppercase; }
@@ -356,6 +364,7 @@ <h1>book-trolley</h1>
356364
</button>
357365
<p class="zoom-hint">Rendered headless by the showcase piece itself — click to zoom.</p>
358366
<div class="callout"><span class="tag">witnesses</span> Recomputed: 17704 tris, seven materials with 568 wood, 2788 cloth, 1476 leather, 312 paper, 2704 gilt, 932 iron and 656 rubber faces, UVs in 0..1 with zero AABB overlap, outer AABB 0.768×0.530×1.000 m, zmin 0, hygiene 0 including zero coplanar cross-shell pairs, four wheels on the floor, ten cross members 8 mm into both end panels, four caster plates 1.0 mm into their skids, carcass and casters mirror-exact in X and Y, 52 books each with one page block 0.6 mm into its boards and two gilt bands 0.3 mm into the spine and 0.8 mm proud, every book seated 1.0–3.2 mm, four troughs each with one backrest and all 47 trough books 0.8 mm into it, thickness and height spread 2.75 and 1.64, zero unplanned book overlaps, zero right-angle wood edges, convex collider 308 tris. --off-back and --deep-back exit 21 on the backrest contact while every seat, the envelope and the overlap budget still pass; --crowd-books exits 22; --skew-caster 19; --drop-plates 18.</div>
367+
<p class="taglist"><span class="tag">category</span> <a href="../#k=showcase&amp;c=village">Village</a></p>
359368
<p class="taglist"><span class="tag">tags</span> <a href="../#tag=mesh">mesh</a> <a href="../#tag=export">export</a> <a href="../#tag=showcase">showcase</a></p>
360369
<div class="runline">
361370
<pre id="runCmd">blender --background --python showcase/book-trolley/book_trolley.py --</pre>

0 commit comments

Comments
 (0)