From da720d383dcf84542dcbddf5fa871eff8ba0181e Mon Sep 17 00:00:00 2001 From: fOuttaMyPaint Date: Sat, 20 Jun 2026 13:48:06 -0400 Subject: [PATCH] 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 Signed-off-by: fOuttaMyPaint --- .github/workflows/pages.yml | 70 ++++++++++ README.md | 3 +- docs/gallery/assets/gn-sdf-remesh-hero.webp | Bin 0 -> 6340 bytes docs/gallery/assets/swatch-grid-hero.webp | Bin 0 -> 7354 bytes docs/gallery/assets/turntable-hero.webp | Bin 0 -> 14260 bytes docs/gallery/index.html | 93 +++++++++++++ examples/gallery.json | 30 +++++ scripts/build_gallery.py | 138 ++++++++++++++++++++ site.json | 14 ++ 9 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/pages.yml create mode 100644 docs/gallery/assets/gn-sdf-remesh-hero.webp create mode 100644 docs/gallery/assets/swatch-grid-hero.webp create mode 100644 docs/gallery/assets/turntable-hero.webp create mode 100644 docs/gallery/index.html create mode 100644 examples/gallery.json create mode 100644 scripts/build_gallery.py create mode 100644 site.json diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..af61ec4 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,70 @@ +name: Deploy GitHub Pages + +# Builds the fleet landing page (docs/index.html, via the shared Developer-Tools-Directory +# template) AND the local examples gallery (docs/gallery/index.html, generated from +# examples/gallery.json by scripts/build_gallery.py). Both ship in the single docs/ artifact; +# the fleet build only writes docs/index.html + docs/fonts/ + docs/assets/, so it never +# clobbers docs/gallery/. When the fleet template gains examples support, the gallery step is +# retired and the data (examples/gallery.json) migrates onto the shared template. + +on: + push: + branches: [main] + paths: + - "skills/**" + - "rules/**" + - "mcp-tools.json" + - "site.json" + - ".cursor-plugin/plugin.json" + - "assets/**" + - "examples/gallery.json" + - "docs/gallery/**" + - "scripts/build_gallery.py" + workflow_dispatch: + +permissions: + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build-and-deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Checkout site template + uses: actions/checkout@v4 + with: + repository: TMHSDigital/Developer-Tools-Directory + sparse-checkout: site-template + path: _template + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - run: pip install Jinja2 + + - name: Build fleet landing page + run: python _template/site-template/build_site.py --repo-root . --out docs + + - name: Build local examples gallery (from examples/gallery.json) + # Stdlib-only; regenerates docs/gallery/index.html so the committed page can never + # drift from gallery.json. The committed docs/gallery/assets/*.webp are untouched. + run: python scripts/build_gallery.py + + - uses: actions/configure-pages@v5 + + - uses: actions/upload-pages-artifact@v4 + with: + path: docs + + - uses: actions/deploy-pages@v5 + id: deployment diff --git a/README.md b/README.md index e9835ec..f24e155 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,8 @@ The content is consumed by AI coding agents (Cursor, Claude Code, any MCP-capabl Runnable, smoke-gated demos live in [`examples/`](examples/) — each is executed headless on both Blender 4.5 LTS and 5.1 by the `blender-smoke` workflow, so the screenshots reflect code -that actually runs. +that actually runs. Browse them in the +**[examples gallery](https://tmhsdigital.github.io/Blender-Developer-Tools/gallery/)**. diff --git a/docs/gallery/assets/gn-sdf-remesh-hero.webp b/docs/gallery/assets/gn-sdf-remesh-hero.webp new file mode 100644 index 0000000000000000000000000000000000000000..2b23e2578876156bb91c26dea526bce54d118813 GIT binary patch literal 6340 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 Gk^lhSp%#q* literal 0 HcmV?d00001 diff --git a/docs/gallery/assets/swatch-grid-hero.webp b/docs/gallery/assets/swatch-grid-hero.webp new file mode 100644 index 0000000000000000000000000000000000000000..5e01cd0ed2d7d1528b3ebbf285c7a906080d1d20 GIT binary patch literal 7354 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 literal 0 HcmV?d00001 diff --git a/docs/gallery/assets/turntable-hero.webp b/docs/gallery/assets/turntable-hero.webp new file mode 100644 index 0000000000000000000000000000000000000000..23e5745c5c910fe8f842cf4e694ca684be7321c6 GIT binary patch literal 14260 zcmajFV~}P+(=Gb6?Vk3uZQHhO+qP|U+O}=mwrx(k@4W99_dDm;z4>GBjEbF=nJcrR zYV9aR2~p81HUL0PL{MH;o*h5$pDqs%oDDGlN zzwyQca3B^sksWbb(+89-$_U3M*SM@qf*x0{y^2~nesr%J*hjiI>`@Ic)tv;7L zi+t_Qqg3?H3zftXzHly0Zr{uV9B|`Dv#FgV)nJPK%{TJIXp!0B9C>1}&gycBIWt^m zbHBu%8*Z?@Ut!CSGTuI{vJ*n2A8~CS3BJP@04@>7*vqMda|trJ%VV#_4*CbseI)45Fv3h3`^AE>zI^X95Ih zn=#o$2YLLgMWos~SDKc$1=~Iew%m-Yc4N3R!_^v3sPWrT6(!KmzyGqD8-ps36*S{E zQlPIfS7CFdNpg#=Z@*_#ZsSJ9~*+U7VTRlK1GY4TdCWcpqMyhkD1hDfpiyse6OsRGJ*vLrFOw}`5QqUI+FkGWbMXaS3w@lyS<>4I12!m& zrD(PZQ9_R7w9Ze`{W#Skz)<`$PCUpGijo9&ebT7mNu7Fug4sE$zO`q)-0+|B>uWr} z`tR)%jjIIGqdMm@Mi>hSQ>KftX$-CrLG_o%6xts{kj~=OwDTPfgdoXsZntg5Im z>C+CAJ(rIQqbTRMTH$O2(y^&5)OTZAmcW9D|BCgcVXYfKe^6)MzL|JYNT7Pq0JT<@ zbrMtI0Q2>PH_ks}z(adXNBI}WopeEMzOIW+l7uo3)E_Hl`b1#YYje|EoFP02%-aSO zY>#OJP=>pL0#wg48Reh&1B-povZrd%*YhExe}$(eLoqL~gjJ~#M$G(HG5k(kFloq1 z6gz8Dhcg!Qz~_syD-&mb8gi)dye_0yNKWGSu5iDg!+b@JzXZZ+;!&3yF$U#PkhD04 zt*T;hCaC~{N~g#%DjWdfeCZC|apCrcniZ*>z%V$Cu1rsqxA{gMdhdh{;9D)@!< z(@j^;CU7{$hnr4$98--ui4_!EaJtv3B)yq4SsU(wul%b?$bFrV1ELUnAHV}vK3@6=&HUI962S~$}%p?ioFeV zX)7Ajp}-=u8Xv)&rIRBl4;p{=iVk?Y{)_Twt*d?*y)etSj%K}o5h(CCToRFZ3wc!9 zH;_1nL8+f%WVrs}Csa#>&DnXWDh`c9A!0xp@-XKxoM)l4d%M8MIOd00A5^F?FD{oYXf=F#v-ujion z%!!qcb3`AG()@! znv27o^}OiZLRs)lB1JiP@aSJlM;JZ(d|;=&DqnkY)`GPr1|+F;=!NF9<@fSF0?9aO z!#rIq$LeH}BPv6Wz$TNFQ=#WATY!#O2CT4UYbhXe|NQ;_Qz+JQVmlx=xP~j? zc{9Y8!9Z>ZSw?pomTCo9E)1wqH;k^d2wL?uERFc>wlNYOB&1E!G8}z;B1gv~6`>43 zcaW`kfv&+g%b;oul39+|VyX~SMrw%Q1ZuFg8I{ceY#qB6&7WOxl-#xIZBn?8;BEZ; zMc)!cE!OPWsaoV%FhI)f=tm=e_>qogq4L(Jlgys64%ilQ(QkrOy>$vvrv|~;z63E~ zVJcOmp12%;un=9?qG*C*QKGc1X)UrQf-)n-v8Gx$Em{Wg#(ANVrm{l&w~dM6gVE{X zeZ#WMfiHi9?-eO`Kw2OvudM_-P$#Wwc`Q!xZA-H>)9xkW-nMMWLMpy9qEKLc5nCWb zZN0cKygZWjR^T+M$`qSkw8fE)0eiKLSh8ohg*QLuez!~D$~q(PGPIxS4>@BaCWw;E zPz71Qkp1Drw36fLw$f*yf%lsW|9$8+|LRM()=`D(+8`C5(^2=L)14$yY2o3EV>>Vf z3;c!GNS3N)Kc^PAFdTEnxd1w#%9gKtv~DBh8(6yaw+E>lX1F`eeNZVSfYr*4b zKFj7e&*GM~SsebFbAYJgY7pwpqOuNxu1)YNUQUs$6INs56Z?7OG>t!0oFV05B3D2% z(YzSn_d>p#g&yg*!N!vy7hYM`jC1RhSuI@F@hq~lU`CzBB7EAU=2lUoiRKj*oif*& z&8c19JKZjsl7}FybQz4kjn2pu)t3LN*3)HsCm}5+3YS((x6|U2SD1=!C{2=LsJ*n1 z1JBsUy=6%-IvV{iAA9b*NmOd%9eUp$BXmO8p*beFayM)LksHgnCFo z(s>$^5el{uondDL_adeu<%A3(Lsnc-pJhCMzC`UL`<2@2>dTwRr`?+?vTu<{ z#eGU|>zQW1R%Kez1nA2=P=V48(i}l8dQc3=6r&`(jmZ8&2JCv!ALO=(j5nrM; z?3f3#*q{3s@#tC{A+G5)XifFIJ}D+rgi28&*k1nPWAQwjBYNK>q%aYhnZld;I+{qoh!8TR~`+xzMozEOFro!Ia#vpZ(oIV(d*j`3#E>M=Vx`|BJPbPNkOsA*P zWKkDYMMJ$>`6eF}szM4j-h%;UJyzG|4m;Ki@}!KvaHgkxDzRrL`0ri-vErNRwpoto zc}zf2#!F5p&URP=aK=M+2ywQ46{<6ym1okv7`|R?TQk~xaicDesCiou^9a|xu7&C5JRV# zZ8t=aJ+wwHNFgLv?-hrkagiT+tnDFkGdz@G+93nX9qAAhL*FywN((4EnPLtp`byUE zH)7h&L^e9)OFK@{vY(b_PuUau8vw@@ypwW~yO98ZI{i)wBfYAN#_;5(34Nr6N+);kuYp`_xcM(WZfel!4`r6TQ!Zlj`fD=3(ic2-rP z%_bdi+Pulc4zm?2n6Zo%h$UnmxO9DbxNf%0u2|F^H=1zVv_r*CLD+{A@78Q<#KM2h|9`3?>=H%)Au4v}gJ2?w24P*EWBzUl zS9lgl=o}R9A=4TJ!eD=x6ao+ZXJGFB)>6OJYn-zDGrN6y95|np#nH=O>K?tCJLud| z7|YyzoE6vc;0c3g$d><&{BcLhCq-(Z)9_GUI7?pSZ`j8ytc>6hC+Cr~u1kaMsFQFV zZ$luz*BdEsl$l|3%g%QvvKJ=N3XU?%HrfLH%?#b zPnvODv?r#rJ8BV4?V0JXy8GHUx!prkp~=8S%VpEZ)ye1(2K=9|6DGrseacg`M_~6$ zn16lk`8%rBvrbfZMzv;W_vtE)*ey1CHNME*RH_M|E9$w`)EVj_g1RpBUFDRm)&Z29%!$FKto09y$5jK{d((mBf7i>f=>){U6{dGQP zQYg!|Z~x2TxEM0(44Qy%=zJx_xiI_P?;}Da)H%K@aEkFS{U4V5e?0xiw{`f{fAK!?Y043n_L%%XIE>f5$A_N$OX?pp&b2jZ zKw>fw`_tC--|1Tf!P9-jxr3ew;HLg-W5N9dP`dsvhLgsGrUP;RH?RJ`Jj#R}26R#U z&piLa75LYa|AT<%ZV>?R{ia)tuLO~`|4XlqE_Im_zuG!Pyq@P0>2Xbiy~p$O)j<|d zCIS+M41HEF7YgF@Rh;j1Fwz0lNCxNH`rz#f&$A~BM=5N<>-;wJCFUp^)7fWO_T5|a z$~NRhah<)#3x*8s4)Fgi@C?iV40@2O*T#T2);z!ve>xzz>z@hGf$Ui~5(MIwYL9H- zVkhx;KgZ1Dc>|yGmED#|F2`<^r6cz_C%ZOwR5&!#*9}tH!&|Y0xP21Cx;Ln)ENj=4 zHL=>A{sAyTGSH6xq;!Lwga__QMI@}2d2Oy(V+Dl;P1fwvnd>M37lyo@j_jOi_`&DR@`t1YBJL+ML5N8GKB$A%asesL z#XJfuo%#!u07L=~u%Eu3HZt!J4D_6xnGj7kc>CqC!?m@_>yFDouAoS@`)|Sy$MwHP z>i-giw$;*A)@lu~i7C~0S$)?qt0{Lh>|VLY{CkJ`7;4ciLt~f0wg?1wn8!eq*^vc5 znl!k7)EV-&Li-e(Y*DSl`sW7GqW|`>TY?=?@P|K%xBQ{VMu)8Ts}zptNP|nasdbfA zK{;`JYOg9u)ILLwnDs-EC1BF@3FRj!I-S|cq>G%a)4Dc#CE8g9qHTTT-Lamcd?N5Q z9iW(x5gN6WkQ;{M_uDpJY&aJa9hx8!X0iA?;7$}d7Y@-F#jJi|g|D+XFR1$SQ_D~V-G6Zb~!Gv4S#62xgkL$F8VN7?UE7HKmQ z`_q8Yw+_yc!DEJJa9;*U6McQ1)?HLWZ&1>sj&GiJmWqv8!KT^Rqr2$E3jJ=P2<4s07R(-XlBPJ5>RGHpPy(D*X8odj@AxMoWB=H%b z-Z;zs#Lasv6;e{+!xmg?M|g}@Um7$GEgf*g0Wi2%{yGB^#A48ppH^=v1KMn$Itwo@ zI6baf>Q^Ga<^mrH2n*POOPUV%>htZ@4q*bsXKRM6T;5u-V}Hu83$4HWuS<6g+;a~) zsO~X>4TfdVQu(rC{hGa#?|-#^{*?5BX8W-pL-W0*XF%GUr0Z1N-`s&^;z7Hp+n@?K zCaG-w5|MCl(Ts$kO&uNbyfGKRCYe=V${!?Es{U57O;6|mYP<89bxSi!-4q-40163i zlCZ#1-{m*rLWRjMUcfhsxMrYKFhCxbnTr4 zFd_ZLIH1O`U)F8c4VO>Di5r3t}}f&l~(qaus$(`iinuO9z95xR}2vj%usN zQ5BkhPjOl(c1tsJbb4Ck~@qKiFlV zjKsov0DUX6jFXJRmE44I(&;aG392U2sYGIIn)Gii;yDDRs(w!-c&XF;ihy*`aZ&EW@slUV>Tk0ehT<$2z56S>T>FcK9uP}DEX9(yfp@pj zu}sH3-u8skYTo-!4~p(|^6)x({b~Vq7BU7Tr?x+(pmVS$o-oZ^MOlV}7C(F)3S5PH zWRz`LZrJe2L}A(E8WKX>a(}#>WY<#PDJD+xwH4KSN74-=Zfb!95BOE$`GR4ewr&J= zJ_ItnuFxYA&EZrEWPmDSqupGCn8nZ_O!H(gLOFezc6?H z@2z8tl+Sy%QsT=uLlZV5Pv8*j23?*Cr>qwpkh|+vYY|DlIh9VT&OviG!1WHUClsLU z7}T{oH*k#w>|&ofAe6auvo=NgLx*wM>0l6U`SxE)YnvFfz7sT3HKUTDH!NX0T!y1J zt`J2F*h53B`Mm+IHE150hBQc=130$hO&(!IV{I~>l`&Af(ac8%F$mWraS^XDeadk~g~(9O zxm$F-TnrYvNt4?E0ep6`?i(_H`PB(|qQcNuvR&CtO#^+ot!sv1SGFS- zP_O(Mgq6E}@!Wa+F^dD7sVB@?ZW_zR7h>vD7Wy`RPv{=e$S5<{RxeBGB$bZGGE9V* zAs~jpv9 zsTax?l>XnTXZTwCykO*LsWN~end{&49aUs#Z~R0#;b4JDWJBlH#$xp`w$phPtlpHy zG2tmzo=mHMoit+Q%Zay*jusYXTo_%xstupsq=_HH&T{8>GEfh%!5ziL^Pe(?^h<>! zitNsI-7Uzb#Dq(j8NL)m`0%8w9z>5jWgAmS)K5(FsqSUN1I_AX_eiL6TH!9}rE)76 zj<23ko2-JvQ_x+X&qsq?sizO)_>Z){h+gej_O%^tCm9B18Sg;3HSWR}l2*>F1N+JD z1ZJbB-A4R0G9hG4xV44y?L!RP4w=%KWjdPgbcAlH);dN0sA?O^ASgS)efU`4$Dh&7 z|Lvwe+e=B}jO+A*_7ugOhsB@8!|q{_26ZYBEYvogUHGun-Hy+A2!n=WKv7Z3uJrC< zu@Ki24KlA;>wJBgB22#)bydqFf1YTPoIVq`Z##AwKOl4KZQM}*W!I-w(hbEvEIe(u#%v#{T`0ES@BCA~Dk3 zdRHx4{={Y5>*LqZ0#O=&p=BP7Ci?ZDzF{4F+>hlWIFt@QD=-R(BkfCy6Oh@)X(D`rfNe`xGZbEi*!IuRYfiP0~3Dv57DH|)aGm#MtCoo4=c*J*(o2b z^u$^MjV0qt{X;Z3vi zmoxVi1dSEkO#1Yc%!?`hUnhr}MBiB|j{%%lfh?YtRc-}!mnF=r@WN7VJw-zuCPVk1 zaO9BcIQ-n4tAk3<5T}l-SJ`Y{F3(4;I(dM4w1v+mTa)0xpppN&Uxy+1*j(X?PF?D~owOa+v zD1!DyjVh)saRp@-Uq$^6Ws#w0pd$2hA~Njl zW`Hc{N$}>uI07@9sq%s?POMSGX&rie2N-kk6{AOcPqOS zkbi(%mG^X=@l*T(AqR-4v7DW2+m|P0rt+EZr7$jpcIY;umqGzXOn_pv6F z*Y}ffMnd})1V1eC8D!%LyR3k~Ehw6L0=_g_s<$$<0_KjuqgIZ^#GR4qZ^v!cd(iqPp4o$=H#Ia*EtXY?J6*z6zzVT? zY?QK^y;bG1ibC_WNY}EvhYW33?X+T}bo>ti&q>@< zwx7Lmh3c)qCE%Q*C8Q2)Y@wE35EE)caX(lnIsqR0 zi_x~s&&!&JLGLK#5nU&;p&@_Q#v|erZZ<4z91wccN2!=|#Enkz+y*o~cDh2;YrFt^ zim{JRig({U!!d^26YNjNujMXiED`y^o6r0rBl3G!DH7{!#cp}^QdiQtk^bLx-^K(K znx3E2QbRh26l~^uqFeb2NAH*>c5uR_tfV^K?1f_)f-kTIv9FAqIu)pOHL$&>*f}sg z@}^&if(CN0WwK=u`h%&yTSPrlZJ6LCM^=x&mE022J6zVp{b+jD5m0b{+vc0 z!bXIsiC)8YQQV93-)x?wu~Z5k9oChu2wTq4?r@jNAG z10i(DoA^bq!Cy`b0W`u?QS=%>DmZj&Q?pEE694&Xtn^rAZVTXGr431*^S{WtVhQxv zN!pzIU{3W(f9{+n3p@d-UDcni!?}k3lY(SW9f8_-ybG`)Q1A*yaC7_P;kx0R&SoRF zV;ZMboFfM0p|DXtrr#gchp){|-pp$6rrcePvzxt-hH*5S3}p(HHjuv;3r%En{n>r_ zWpr{bf#^98<1Svzl!VJB2^DV1aZ{C-ElhzwL>_;@#6R9IJ4*BUcCt|t{B}qM##!bb zkrqxm#(P_NdZvP{?7h!i|537E_l-tN_suu5sK9@Hb4Gg4to%OW>FudWEeh%3zK2rq z*nt;<0my*AfgojD7tJ(1^Cn0ncP@+H1zEG7dHE$^>{T?QCaBM^L^H3>R^^R}`aSCl^5^mg}_P^#f#M!;VOA~(EXIe5&s zg}s^VGdJ90>|_x^Z_Lyiij+tZ?8QU0*mG;>7|b1NUr6x&sAr#K_= znl@48H~#_rPl$qr5sxS@;RG{%yqu{8GurAHVy_WMjM-!ouVN_2TAV1xX`M2nD zVd0maCAE6DbRrbo$bpIGA<8-*6|+dS=>Fe*MLD4i`UDL@nWTG}VFslRCI(Uz^Z;~D za%~q(NLVzz-dZr`^N#1TF!5%izH179Lyaq#?EA^d;3!|V5|jwS-<<6JE+swTHK@*J z*Ph?<@&$v1x5O6{eiDR?&XUbbR4@{RK1`g^0g(2Q*|FEFje>L>7<{I-drv6Ce{+$% zXF}`{$wm*4TX(th_STI7`T_<>7%7Q9PzD_{Vv~uAF*Socv)*85#aK9;ccaxE&26Q( zR4=K@<=IyEBE{w35?Ld0o>uRq}_juVdDON%2Ejx+8E|W%GDk&{<6#Df2NJ` znIfC-4R?^ot-=Cse8Y-#wF*jXJ`+VVy@%X&29rk7R@R=q8fnX(s}+VdmLtLCzwT8epAQxD<&^^ZC+YW?XYC17S^zN>=Z-(orSW zaqjIcO=orlmp0!7@4|$aql1jl6V0EXRm0`THVS0Bv{w@4BqNy^WPar8Acd~vLXxpL zW>P9q%0DM6*&@iARVAgyIW=AYR#9C3P?{#deo%S?&M*k_x``XmyuO<4`L|cp8Pcr) zWS%EvtU1|Fhr`+@kvHc~bG!f!9okP!S5JU0U^mZLL9_3bWHC^h3#>ir+B5Ga7-Pqa z>qqsrS`v_A33Q7@cPdh%A+YB^iP#W}fr7jM)~{GNHXxK27e_@Lz`4ai$LjvIb^XCkiH})(5rt2F9}VbM}tcUi$pN^(q`ksRdJ?qwpCa0K^>!= z=+8dT=e-wuOTNxjUTrvDXHb^Dm7fXgepAE9$>m9-sG?jJ4YXdBB^z*k@|Gu*giOI; zM~)CHm=g>mH4+Z}5#GPL+dp6Q)8+L{gbv<)DpJ5mrnJujzkdZUIEb*~JbzC2?JKp7 zCco1nB)NH`o`N6()+H#90hZZsP?6=< z9hoqUb-~wN9$VN(cTY7E>eiRYsZ&}!*xld#6AtyxPXqVT5)1)MJVf@GQ&;lp+Af-ld=p&fZ+sH&F34-()nMO?7OTKT)!Kd#YNBH8Ufx$*4 z9M+{d^Z+}-P;%FB*va^%y`S4tK9jsinMvD**a-SlPswuX zJP)i$!;JDr_5BDH4aAm<*3D!L;S-sMRS)OL2MBBmU;Ijl1;l>;8{%b!HJ%3L;_Tza z(L(Ff8^4~Kt)XD%JBlYZ+!ZB|P^X8=YoUfJbjUZ+f}e|eG>by%ip> z6z}{PalJCjWYR}VxC%y*Wi#)#TI02C5ETDNxpztU>)CadAU=+Bb$z%EtJ`}Kju{TUUHaVWfj|cUw|PJ@>J>uZ{=I3qnf)xF#W-BTDaQb6SixV=IC>`5H{@-Ff+WwUjFY!I9q<*5PMC|yv zu*s?TIknf3Mef!zTN+x@LT~J4b~kV-**;3c7W6xF$1xf)z~Y>7_UIChl%M$^r&Rj! zuDXtHkc`oRU!XzA+K4Bk8RT#{{qVcZXM%WfbQe2OT zB-vD46<-oU&}W>OBFwcw^4w3sVba}i?R3YacpN*yGG`n}eZ4OUvt31agw|h7gRQBt z3nS^90n6w!Pe^^%FS&iNW)(io;C?Y$QRa(V6NArN^6Ps)hXUn7$hDE?$vS!wIRS_% zCT!T}Loe}Y&?Suw-NsFff7!2x7eRC(uray!$TDZBuHp{xO9anbZ0psejyy4n{2Gk| z9!m9^KR+@1uDk}w} zj5y~H8mDAeZ*Vl~O$1UkxRksla5(%E6zQs`ux1Ke*>5 za1sQX=q$697roTDrrxC^@e13Mmvw?IG-yOn*2xO-1;jYLA?WuC%E=WUBBL6-o}GFP zk}Laaz2446X%|LOSd}61D$sERuwdFzB!p--vFBBx_(d1jm6n>W!r zxl}X0kvZ2UwGi9k%5ua~UW{~F#n%PR&qV=IO8xZ*)X`L8QzBv?C)t<^ig2Rj3WZWC zW&1S%ATZ=i|KL{_^m?7O3fwh>6{)r)tNIs5F>^+ZL<-`37EcpTYaXO5i9Ue?hsR4j zlR)>*V6TJRq=P~THWzlY5Mcb$r1_=G*cE(lY^ZB9yY`Eh4qO#hg4zj;xYBv4-_oi>e)}RLx4G7Q;3^5{w@i4Y zZzqnzL+a&0ad`!9dp5@L+GX;B;C=QCAU3$m*1y^h_=Rch<)E!^oog@Q(KiEF9zKoW z_!{A`fwP+ZFV_j^(IR5fFOcnFrFb0vP|VT0B=$29Fihwkw~CJ^&r~z^se`SNGH7j$ z&kL4v+-R_x3)7X`OtpVRQtJ?mw^MqVZ7#;Bwx`@ zN3)bCSOJQGXdTImhn{tz+eHzxALLfo0=_AY$!l7qW5YevF~Xpf9sZ~o*b25~eO?g}FP#dDJG z+|P`4b`NlcO*v)0Qd7V|IGo&S2gVNc+A+zaY9Ea5tvo6{b>NDTAj4SyvXh%DJueMA zYPkt+PH<_Wj(oPt^1*fRD}j};nK(;AJSr!D`DiaTh(Ffvo&>Yw^hQa!MI!qS;wcNJ z)HtHL(k2IqZE50UUkiUDR+~mK~`E}N8I z1RWRxC(LG~11|g-eSMM|*rK-Tft@>h&W{_xHfrc-?7KzllF;9ByXZfzID0baSl$ff z6&?;3z}YbaQ*2Od>||?_=Cr|rLdf@ZMUjy5j%~*{0}y24hjB1A>w@$x$!G!nARE>s zB1V_seE^`OJ^=Mxu$c@=JrQtk)4(DpgxUmGdGLTMWiP5VO^M z&EmVGbYawKe)CKdu7+}_W@9G%#^WH46>_Kf;?$K!ADgX0#pDq7Z<(j{DG*8u9hOi8 zbgb!HMzegkffET3Xm39Evl}}B3yQN-^<|*D^A~5Xv(EvlwrKpS3+PhojrnKqG2i=~ z-5s<2r8U}CkIlgh2?IQ^GKBT;NmvT9fh#mJ1#d>s4{+Dj-<&7Nqho3ocSfahy8&}-? zDK#NB_u~cx@B( zX<(4q^!Po$km&b$u)nj7lQ@Wt4s&ka3lbuqUf=r@m-153r~(AD_Z6;sc9;H}klImpmNB-2sx<0RHMMeaRMh5$cRtWhL4(L#MD%!1NUf9ww;$bLo#LoFD&06##kdZ6HJ zWg_<&?Uw6gbReS}Ncu*03mCB^erQ8rqj~2LQnG zMxl~igzXR0%VOT$ekiNWb5v<1P&wxq{*ZlB%C_$E*=!AGRleCt8j#rJ0g0|I3NM)D zoo*&TB8SBKtUfvSJunk<5Nl#^)mBfDMyrXD@c@Z!J~@}Ul&&XC75j5o1%;a1Q-{eS)%w`201SH9-_gqHnyC*TLnp-Ck=i9iqQT%Z zeNW^Eq=V-Xt)6R4Y)O>G#;^Lm#+*5AMc>?arHAI2&S}HYm8q|Jb_($9aumcu_5c8b z_jJJ4M#W*XnU~fCQwV8&eY}@oO7{vb-QE=QwenH5IA7GDBKU6{-!1}kQKhs(zoOW_ zv%QzT3{I69{F%KY?wFh_xCiihf~GE!6HQJ};jgb@1DhWHDxgKi0>F zYF!dC*z_-AuGp{%VZsn36(g=30)}s%!Js}hpgXT9n29?|tYD70sMxt9HGx_9*hR)f zi$s}RasMI-VuE<%H$M$xHb1Y-n^|Rg;&%ff?LY;s1||B+cV167(0>twb(crGhu#AV z+ksq@`Oe#h?>~y`yZF}drw^is<8k`q*10!_B_=HamtrH5CLl5wFPlLeDv<`b5^9Ol z@j>HZ8}T=h*(0XfZ)67(004mhN&EjJaWelXH@sy2(|kQoJVrkW{88PFKyymHttL zs^8@2OHk=jtT4O9Qv+2zpU7iJ4As1f;4+Dvo2j0a6e@P*;BcRDld0NgAQ57M>?8fb zYs<^Kc_uO72#mR)hgA8jFl!nBE_a{`OeIj<@}Ks^FQ)JnaNR|}$?tH$6G&KLg7c`D{AwKU&DoZ1lTyB@T<35M)l`DEypP0umn?6|#}Km49a_2>r} z+9spY(C>a7N>sitW*4B&DDN)ZXH>9!BoHN$)wtf5*fA}-zCLe`L}>`SNZuX{tp6@n z3H~=M3S#j;XS@XRz2)#4djB2y_rTfvceBjJ9{`BiqmBmvpz!~V{jXsEul7$tqy`}U I=N{Vs0~m91{Qv*} literal 0 HcmV?d00001 diff --git a/docs/gallery/index.html b/docs/gallery/index.html new file mode 100644 index 0000000..4886f73 --- /dev/null +++ b/docs/gallery/index.html @@ -0,0 +1,93 @@ + + + + + + Examples Gallery — Blender Developer Tools + + + + +
+ ← Blender Developer Tools +

Examples Gallery

+

Runnable, smoke-gated demos. Each is executed headless on Blender 4.5 LTS and 5.1 by the + blender-smoke workflow, so every render reflects code that actually runs.

+
+
+
+ + swatch-grid — Procedural Principled materials — metal and dielectric, the emission pattern, and the cross-version set_specular shim + +
+

swatch-grid

+

Procedural Principled materials — metal and dielectric, the emission pattern, and the cross-version set_specular shim.

+

witnesses EEVEE engine-id mapping: BLENDER_EEVEE on 5.x, BLENDER_EEVEE_NEXT on 4.2–4.5.

+ View example → +
+
+
+ + turntable — A slotted-actions Z-rotation turntable keyed through the cross-version channelbag path (get_channelbag_for_slot) + +
+

turntable

+

A slotted-actions Z-rotation turntable keyed through the cross-version channelbag path (get_channelbag_for_slot).

+

witnesses Slotted-actions boundary: ensure-helper channelbag on 5.x, strip.channelbag on 4.4/4.5.

+ View example → +
+
+
+ + 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 + +
+

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.

+

witnesses An SDF grid is meshed with Grid to Mesh, not Volume to Mesh; GN geometry needs Set Material or it renders untextured.

+ View example → +
+
+
+
+ Generated from examples/gallery.json by scripts/build_gallery.py. +  •  CC-BY-NC-ND-4.0 +
+ + diff --git a/examples/gallery.json b/examples/gallery.json new file mode 100644 index 0000000..9f03ee1 --- /dev/null +++ b/examples/gallery.json @@ -0,0 +1,30 @@ +{ + "_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.", + "repoBaseUrl": "https://github.com/TMHSDigital/Blender-Developer-Tools/tree/main", + "examples": [ + { + "name": "swatch-grid", + "dir": "examples/swatch-grid", + "teaches": "Procedural Principled materials — metal and dielectric, the emission pattern, and the cross-version set_specular shim.", + "witnessesFix": "EEVEE engine-id mapping: BLENDER_EEVEE on 5.x, BLENDER_EEVEE_NEXT on 4.2–4.5.", + "hero": "docs/gallery/assets/swatch-grid-hero.webp", + "preview": "examples/swatch-grid/preview.webp" + }, + { + "name": "turntable", + "dir": "examples/turntable", + "teaches": "A slotted-actions Z-rotation turntable keyed through the cross-version channelbag path (get_channelbag_for_slot).", + "witnessesFix": "Slotted-actions boundary: ensure-helper channelbag on 5.x, strip.channelbag on 4.4/4.5.", + "hero": "docs/gallery/assets/turntable-hero.webp", + "preview": "examples/turntable/preview.webp" + }, + { + "name": "gn-sdf-remesh", + "dir": "examples/gn-sdf-remesh", + "teaches": "A Geometry Nodes SDF remesh (MeshToSDFGrid → GridToMesh at the SDF zero-level), with a Set Material node carrying the material through the remesh.", + "witnessesFix": "An SDF grid is meshed with Grid to Mesh, not Volume to Mesh; GN geometry needs Set Material or it renders untextured.", + "hero": "docs/gallery/assets/gn-sdf-remesh-hero.webp", + "preview": "examples/gn-sdf-remesh/preview.webp" + } + ] +} diff --git a/scripts/build_gallery.py b/scripts/build_gallery.py new file mode 100644 index 0000000..7fcee23 --- /dev/null +++ b/scripts/build_gallery.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +"""Generate the standalone examples gallery page from examples/gallery.json. + +This page is a LOCAL, this-repo gallery that rides alongside the fleet-generated +docs/index.html (which build_site.py owns and overwrites). It writes ONLY to +docs/gallery/ so it never collides with the fleet build's docs/index.html, +docs/fonts/, or docs/assets/. + +examples/gallery.json is the forward-compatible source of truth: when the fleet +template gains examples support, it consumes the same data and this script and +page are retired. Run after editing gallery.json: + + python scripts/build_gallery.py + +Stdlib only (no Jinja2), so the Pages workflow can regenerate it without extra deps. +""" +import html +import json +import sys +from pathlib import Path + +REPO = Path(__file__).resolve().parent.parent +DATA = REPO / "examples" / "gallery.json" +OUT = REPO / "docs" / "gallery" / "index.html" + +CARD = """ """ + +PAGE = """ + + + + + Examples Gallery — Blender Developer Tools + + + + +
+ ← Blender Developer Tools +

Examples Gallery

+

Runnable, smoke-gated demos. Each is executed headless on Blender 4.5 LTS and 5.1 by the + blender-smoke workflow, so every render reflects code that actually runs.

+
+
+{cards} +
+
+ Generated from examples/gallery.json by scripts/build_gallery.py. +  •  CC-BY-NC-ND-4.0 +
+ + +""" + + +def strip_to_page_relative(repo_rel: str) -> str: + """docs/gallery/assets/x.webp -> assets/x.webp (relative to the gallery page).""" + prefix = "docs/gallery/" + return repo_rel[len(prefix):] if repo_rel.startswith(prefix) else repo_rel + + +def main() -> int: + data = json.loads(DATA.read_text(encoding="utf-8")) + base = data["repoBaseUrl"].rstrip("/") + examples = data["examples"] + if not examples: + print("ERROR: no examples in gallery.json", file=sys.stderr) + return 2 + + cards = [] + for ex in examples: + hero_rel = strip_to_page_relative(ex["hero"]) + if not (REPO / ex["hero"]).is_file(): + print(f"ERROR: hero image missing: {ex['hero']}", file=sys.stderr) + return 3 + cards.append(CARD.format( + href=html.escape(f"{base}/{ex['dir']}"), + hero=html.escape(hero_rel), + name=html.escape(ex["name"]), + teaches=html.escape(ex["teaches"]), + teaches_plain=html.escape(ex["teaches"].split(".")[0]), + witnesses=html.escape(ex["witnessesFix"]), + )) + + OUT.parent.mkdir(parents=True, exist_ok=True) + OUT.write_text(PAGE.format(cards="\n".join(cards)), encoding="utf-8") + print(f"Wrote {OUT} ({len(examples)} examples)") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/site.json b/site.json new file mode 100644 index 0000000..c658b37 --- /dev/null +++ b/site.json @@ -0,0 +1,14 @@ +{ + "accent": "#7c3aed", + "accentLight": "#a78bfa", + "heroGradientFrom": "#0d1117", + "heroGradientTo": "#161b22", + "links": { + "github": "https://github.com/TMHSDigital/Blender-Developer-Tools" + }, + "installSteps": [ + "Open Cursor IDE and go to Settings > Extensions", + "Search for Blender Developer Tools", + "Click Install and reload" + ] +}