Skip to content

Commit da720d3

Browse files
TMHSDigitalclaude
andcommitted
docs: add local examples gallery on GitHub Pages
Stand up Pages for this repo (no site existed) following the fleet entry pattern: site.json + pages.yml that builds the shared landing page (docs/index.html via Developer-Tools-Directory's build_site.py). Add a this-repo-local examples gallery that rides alongside it: - examples/gallery.json: forward-compatible source of truth (per-entry name/dir/teaches/witnessesFix/hero/preview). When the fleet template gains examples support, it reads this same file and the local page is retired -- a lift-and-shift, not a rewrite. - scripts/build_gallery.py: stdlib-only generator -> docs/gallery/index.html. - docs/gallery/index.html + docs/gallery/assets/*-hero.webp: the standalone gallery and its web-optimized heroes (1280px WebP, 6-14 KB each), rendered on Blender 5.1 and asserted non-black. The fleet build only writes docs/index.html + docs/fonts/ + docs/assets/, so it never clobbers docs/gallery/. pages.yml regenerates the gallery before upload so the committed page can't drift from gallery.json. Cross-linking the generated landing page -> gallery needs a template edit (the Option-2 fleet change), so the README links to the live gallery instead and the gallery links back to the landing page. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: fOuttaMyPaint <TMhospitalitystrategies@gmail.com>
1 parent 1a5d62d commit da720d3

9 files changed

Lines changed: 347 additions & 1 deletion

File tree

.github/workflows/pages.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: Deploy GitHub Pages
2+
3+
# Builds the fleet landing page (docs/index.html, via the shared Developer-Tools-Directory
4+
# template) AND the local examples gallery (docs/gallery/index.html, generated from
5+
# examples/gallery.json by scripts/build_gallery.py). Both ship in the single docs/ artifact;
6+
# the fleet build only writes docs/index.html + docs/fonts/ + docs/assets/, so it never
7+
# clobbers docs/gallery/. When the fleet template gains examples support, the gallery step is
8+
# retired and the data (examples/gallery.json) migrates onto the shared template.
9+
10+
on:
11+
push:
12+
branches: [main]
13+
paths:
14+
- "skills/**"
15+
- "rules/**"
16+
- "mcp-tools.json"
17+
- "site.json"
18+
- ".cursor-plugin/plugin.json"
19+
- "assets/**"
20+
- "examples/gallery.json"
21+
- "docs/gallery/**"
22+
- "scripts/build_gallery.py"
23+
workflow_dispatch:
24+
25+
permissions:
26+
pages: write
27+
id-token: write
28+
29+
concurrency:
30+
group: pages
31+
cancel-in-progress: true
32+
33+
jobs:
34+
build-and-deploy:
35+
environment:
36+
name: github-pages
37+
url: ${{ steps.deployment.outputs.page_url }}
38+
runs-on: ubuntu-latest
39+
steps:
40+
- uses: actions/checkout@v4
41+
42+
- name: Checkout site template
43+
uses: actions/checkout@v4
44+
with:
45+
repository: TMHSDigital/Developer-Tools-Directory
46+
sparse-checkout: site-template
47+
path: _template
48+
49+
- uses: actions/setup-python@v5
50+
with:
51+
python-version: "3.12"
52+
53+
- run: pip install Jinja2
54+
55+
- name: Build fleet landing page
56+
run: python _template/site-template/build_site.py --repo-root . --out docs
57+
58+
- name: Build local examples gallery (from examples/gallery.json)
59+
# Stdlib-only; regenerates docs/gallery/index.html so the committed page can never
60+
# drift from gallery.json. The committed docs/gallery/assets/*.webp are untouched.
61+
run: python scripts/build_gallery.py
62+
63+
- uses: actions/configure-pages@v5
64+
65+
- uses: actions/upload-pages-artifact@v4
66+
with:
67+
path: docs
68+
69+
- uses: actions/deploy-pages@v5
70+
id: deployment

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ The content is consumed by AI coding agents (Cursor, Claude Code, any MCP-capabl
4747

4848
Runnable, smoke-gated demos live in [`examples/`](examples/) — each is executed headless on
4949
both Blender 4.5 LTS and 5.1 by the `blender-smoke` workflow, so the screenshots reflect code
50-
that actually runs.
50+
that actually runs. Browse them in the
51+
**[examples gallery](https://tmhsdigital.github.io/Blender-Developer-Tools/gallery/)**.
5152

5253
<table>
5354
<tr>
6.19 KB
Loading
7.18 KB
Loading
13.9 KB
Loading

docs/gallery/index.html

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>Examples Gallery — Blender Developer Tools</title>
7+
<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." />
8+
<style>
9+
:root {
10+
--accent: #7c3aed; --accent-light: #a78bfa;
11+
--bg: #0d1117; --bg2: #161b22; --card: #161b22;
12+
--text: #e6edf3; --text-dim: #9da7b3; --border: #30363d;
13+
}
14+
* { box-sizing: border-box; margin: 0; padding: 0; }
15+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
16+
background: linear-gradient(180deg, var(--bg), var(--bg2)); color: var(--text);
17+
line-height: 1.6; min-height: 100vh; }
18+
a { color: var(--accent-light); text-decoration: none; }
19+
a:hover { text-decoration: underline; }
20+
header { max-width: 1100px; margin: 0 auto; padding: 3rem 1.5rem 1.5rem; }
21+
.back { font-size: 0.875rem; color: var(--text-dim); }
22+
h1 { font-size: 2rem; margin: 0.75rem 0 0.5rem; }
23+
header p { color: var(--text-dim); max-width: 60ch; }
24+
main { max-width: 1100px; margin: 0 auto; padding: 1.5rem; display: grid;
25+
grid-template-columns: 1fr; gap: 1.5rem; }
26+
.card { background: var(--card); border: 1px solid var(--border); border-radius: 12px;
27+
overflow: hidden; display: flex; flex-direction: column; }
28+
.card-media { display: block; background: #0b0e13; }
29+
.card-media img { display: block; width: 100%; height: auto; }
30+
.card-body { padding: 1.25rem 1.5rem 1.5rem; }
31+
.card-body h2 { font-size: 1.25rem; margin-bottom: 0.5rem; }
32+
.card-body h2 a { color: var(--text); }
33+
.teaches { color: var(--text); margin-bottom: 0.6rem; }
34+
.witnesses { color: var(--text-dim); font-size: 0.9rem; margin-bottom: 0.9rem; }
35+
.tag { display: inline-block; font-size: 0.72rem; text-transform: uppercase;
36+
letter-spacing: 0.05em; color: var(--accent-light); border: 1px solid var(--accent);
37+
border-radius: 999px; padding: 0.05rem 0.5rem; margin-right: 0.35rem; }
38+
.card-link { font-weight: 500; }
39+
footer { max-width: 1100px; margin: 0 auto; padding: 2rem 1.5rem 3rem;
40+
color: var(--text-dim); font-size: 0.85rem; border-top: 1px solid var(--border); }
41+
@media (min-width: 720px) {
42+
main { grid-template-columns: 1fr 1fr; }
43+
}
44+
</style>
45+
</head>
46+
<body>
47+
<header>
48+
<a class="back" href="../">&larr; Blender Developer Tools</a>
49+
<h1>Examples Gallery</h1>
50+
<p>Runnable, smoke-gated demos. Each is executed headless on Blender 4.5 LTS and 5.1 by the
51+
<code>blender-smoke</code> workflow, so every render reflects code that actually runs.</p>
52+
</header>
53+
<main>
54+
<article class="card">
55+
<a class="card-media" href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/swatch-grid">
56+
<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" width="1280" />
57+
</a>
58+
<div class="card-body">
59+
<h2><a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/swatch-grid">swatch-grid</a></h2>
60+
<p class="teaches">Procedural Principled materials — metal and dielectric, the emission pattern, and the cross-version set_specular shim.</p>
61+
<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>
62+
<a class="card-link" href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/swatch-grid">View example &rarr;</a>
63+
</div>
64+
</article>
65+
<article class="card">
66+
<a class="card-media" href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/turntable">
67+
<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" width="1280" />
68+
</a>
69+
<div class="card-body">
70+
<h2><a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/turntable">turntable</a></h2>
71+
<p class="teaches">A slotted-actions Z-rotation turntable keyed through the cross-version channelbag path (get_channelbag_for_slot).</p>
72+
<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>
73+
<a class="card-link" href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/turntable">View example &rarr;</a>
74+
</div>
75+
</article>
76+
<article class="card">
77+
<a class="card-media" href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/gn-sdf-remesh">
78+
<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" width="1280" />
79+
</a>
80+
<div class="card-body">
81+
<h2><a href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/gn-sdf-remesh">gn-sdf-remesh</a></h2>
82+
<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>
83+
<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>
84+
<a class="card-link" href="https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main/examples/gn-sdf-remesh">View example &rarr;</a>
85+
</div>
86+
</article>
87+
</main>
88+
<footer>
89+
Generated from <code>examples/gallery.json</code> by <code>scripts/build_gallery.py</code>.
90+
&nbsp;&bull;&nbsp; CC-BY-NC-ND-4.0
91+
</footer>
92+
</body>
93+
</html>

examples/gallery.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"_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}; hero/preview/dir are repo-root-relative.",
3+
"repoBaseUrl": "https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main",
4+
"examples": [
5+
{
6+
"name": "swatch-grid",
7+
"dir": "examples/swatch-grid",
8+
"teaches": "Procedural Principled materials — metal and dielectric, the emission pattern, and the cross-version set_specular shim.",
9+
"witnessesFix": "EEVEE engine-id mapping: BLENDER_EEVEE on 5.x, BLENDER_EEVEE_NEXT on 4.2–4.5.",
10+
"hero": "docs/gallery/assets/swatch-grid-hero.webp",
11+
"preview": "examples/swatch-grid/preview.webp"
12+
},
13+
{
14+
"name": "turntable",
15+
"dir": "examples/turntable",
16+
"teaches": "A slotted-actions Z-rotation turntable keyed through the cross-version channelbag path (get_channelbag_for_slot).",
17+
"witnessesFix": "Slotted-actions boundary: ensure-helper channelbag on 5.x, strip.channelbag on 4.4/4.5.",
18+
"hero": "docs/gallery/assets/turntable-hero.webp",
19+
"preview": "examples/turntable/preview.webp"
20+
},
21+
{
22+
"name": "gn-sdf-remesh",
23+
"dir": "examples/gn-sdf-remesh",
24+
"teaches": "A Geometry Nodes SDF remesh (MeshToSDFGrid → GridToMesh at the SDF zero-level), with a Set Material node carrying the material through the remesh.",
25+
"witnessesFix": "An SDF grid is meshed with Grid to Mesh, not Volume to Mesh; GN geometry needs Set Material or it renders untextured.",
26+
"hero": "docs/gallery/assets/gn-sdf-remesh-hero.webp",
27+
"preview": "examples/gn-sdf-remesh/preview.webp"
28+
}
29+
]
30+
}

scripts/build_gallery.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
#!/usr/bin/env python3
2+
"""Generate the standalone examples gallery page from examples/gallery.json.
3+
4+
This page is a LOCAL, this-repo gallery that rides alongside the fleet-generated
5+
docs/index.html (which build_site.py owns and overwrites). It writes ONLY to
6+
docs/gallery/ so it never collides with the fleet build's docs/index.html,
7+
docs/fonts/, or docs/assets/.
8+
9+
examples/gallery.json is the forward-compatible source of truth: when the fleet
10+
template gains examples support, it consumes the same data and this script and
11+
page are retired. Run after editing gallery.json:
12+
13+
python scripts/build_gallery.py
14+
15+
Stdlib only (no Jinja2), so the Pages workflow can regenerate it without extra deps.
16+
"""
17+
import html
18+
import json
19+
import sys
20+
from pathlib import Path
21+
22+
REPO = Path(__file__).resolve().parent.parent
23+
DATA = REPO / "examples" / "gallery.json"
24+
OUT = REPO / "docs" / "gallery" / "index.html"
25+
26+
CARD = """ <article class="card">
27+
<a class="card-media" href="{href}">
28+
<img src="{hero}" alt="{name} — {teaches_plain}" loading="lazy" width="1280" />
29+
</a>
30+
<div class="card-body">
31+
<h2><a href="{href}">{name}</a></h2>
32+
<p class="teaches">{teaches}</p>
33+
<p class="witnesses"><span class="tag">witnesses</span> {witnesses}</p>
34+
<a class="card-link" href="{href}">View example &rarr;</a>
35+
</div>
36+
</article>"""
37+
38+
PAGE = """<!DOCTYPE html>
39+
<html lang="en">
40+
<head>
41+
<meta charset="utf-8" />
42+
<meta name="viewport" content="width=device-width, initial-scale=1" />
43+
<title>Examples Gallery — Blender Developer Tools</title>
44+
<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." />
45+
<style>
46+
:root {{
47+
--accent: #7c3aed; --accent-light: #a78bfa;
48+
--bg: #0d1117; --bg2: #161b22; --card: #161b22;
49+
--text: #e6edf3; --text-dim: #9da7b3; --border: #30363d;
50+
}}
51+
* {{ box-sizing: border-box; margin: 0; padding: 0; }}
52+
body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
53+
background: linear-gradient(180deg, var(--bg), var(--bg2)); color: var(--text);
54+
line-height: 1.6; min-height: 100vh; }}
55+
a {{ color: var(--accent-light); text-decoration: none; }}
56+
a:hover {{ text-decoration: underline; }}
57+
header {{ max-width: 1100px; margin: 0 auto; padding: 3rem 1.5rem 1.5rem; }}
58+
.back {{ font-size: 0.875rem; color: var(--text-dim); }}
59+
h1 {{ font-size: 2rem; margin: 0.75rem 0 0.5rem; }}
60+
header p {{ color: var(--text-dim); max-width: 60ch; }}
61+
main {{ max-width: 1100px; margin: 0 auto; padding: 1.5rem; display: grid;
62+
grid-template-columns: 1fr; gap: 1.5rem; }}
63+
.card {{ background: var(--card); border: 1px solid var(--border); border-radius: 12px;
64+
overflow: hidden; display: flex; flex-direction: column; }}
65+
.card-media {{ display: block; background: #0b0e13; }}
66+
.card-media img {{ display: block; width: 100%; height: auto; }}
67+
.card-body {{ padding: 1.25rem 1.5rem 1.5rem; }}
68+
.card-body h2 {{ font-size: 1.25rem; margin-bottom: 0.5rem; }}
69+
.card-body h2 a {{ color: var(--text); }}
70+
.teaches {{ color: var(--text); margin-bottom: 0.6rem; }}
71+
.witnesses {{ color: var(--text-dim); font-size: 0.9rem; margin-bottom: 0.9rem; }}
72+
.tag {{ display: inline-block; font-size: 0.72rem; text-transform: uppercase;
73+
letter-spacing: 0.05em; color: var(--accent-light); border: 1px solid var(--accent);
74+
border-radius: 999px; padding: 0.05rem 0.5rem; margin-right: 0.35rem; }}
75+
.card-link {{ font-weight: 500; }}
76+
footer {{ max-width: 1100px; margin: 0 auto; padding: 2rem 1.5rem 3rem;
77+
color: var(--text-dim); font-size: 0.85rem; border-top: 1px solid var(--border); }}
78+
@media (min-width: 720px) {{
79+
main {{ grid-template-columns: 1fr 1fr; }}
80+
}}
81+
</style>
82+
</head>
83+
<body>
84+
<header>
85+
<a class="back" href="../">&larr; Blender Developer Tools</a>
86+
<h1>Examples Gallery</h1>
87+
<p>Runnable, smoke-gated demos. Each is executed headless on Blender 4.5 LTS and 5.1 by the
88+
<code>blender-smoke</code> workflow, so every render reflects code that actually runs.</p>
89+
</header>
90+
<main>
91+
{cards}
92+
</main>
93+
<footer>
94+
Generated from <code>examples/gallery.json</code> by <code>scripts/build_gallery.py</code>.
95+
&nbsp;&bull;&nbsp; CC-BY-NC-ND-4.0
96+
</footer>
97+
</body>
98+
</html>
99+
"""
100+
101+
102+
def strip_to_page_relative(repo_rel: str) -> str:
103+
"""docs/gallery/assets/x.webp -> assets/x.webp (relative to the gallery page)."""
104+
prefix = "docs/gallery/"
105+
return repo_rel[len(prefix):] if repo_rel.startswith(prefix) else repo_rel
106+
107+
108+
def main() -> int:
109+
data = json.loads(DATA.read_text(encoding="utf-8"))
110+
base = data["repoBaseUrl"].rstrip("/")
111+
examples = data["examples"]
112+
if not examples:
113+
print("ERROR: no examples in gallery.json", file=sys.stderr)
114+
return 2
115+
116+
cards = []
117+
for ex in examples:
118+
hero_rel = strip_to_page_relative(ex["hero"])
119+
if not (REPO / ex["hero"]).is_file():
120+
print(f"ERROR: hero image missing: {ex['hero']}", file=sys.stderr)
121+
return 3
122+
cards.append(CARD.format(
123+
href=html.escape(f"{base}/{ex['dir']}"),
124+
hero=html.escape(hero_rel),
125+
name=html.escape(ex["name"]),
126+
teaches=html.escape(ex["teaches"]),
127+
teaches_plain=html.escape(ex["teaches"].split(".")[0]),
128+
witnesses=html.escape(ex["witnessesFix"]),
129+
))
130+
131+
OUT.parent.mkdir(parents=True, exist_ok=True)
132+
OUT.write_text(PAGE.format(cards="\n".join(cards)), encoding="utf-8")
133+
print(f"Wrote {OUT} ({len(examples)} examples)")
134+
return 0
135+
136+
137+
if __name__ == "__main__":
138+
sys.exit(main())

site.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"accent": "#7c3aed",
3+
"accentLight": "#a78bfa",
4+
"heroGradientFrom": "#0d1117",
5+
"heroGradientTo": "#161b22",
6+
"links": {
7+
"github": "https://github.com/TMHSDigital/Blender-Developer-Tools"
8+
},
9+
"installSteps": [
10+
"Open Cursor IDE and go to <code>Settings</code> &gt; <code>Extensions</code>",
11+
"Search for <code>Blender Developer Tools</code>",
12+
"Click <code>Install</code> and reload"
13+
]
14+
}

0 commit comments

Comments
 (0)