From 47ced1b68333c1292535b946a9c7413bc032533b Mon Sep 17 00:00:00 2001 From: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Date: Thu, 17 Sep 2026 08:19:11 -0400 Subject: [PATCH 1/2] fix(showcase): rebuild market stall with wrap plinths and a seated awning Capped foot cubes and counter-leg pads z-fought at Z=0, and braces punched the counter. Wrap plates and a post-bay brace plane are durable; per-coordinate nudges are not. Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-authored-by: Cursor --- docs/gallery/assets/market-stall-hero.webp | Bin 33548 -> 31724 bytes docs/gallery/market-stall/index.html | 793 ++++++++++++++++----- showcase/market-stall/README.md | 94 ++- showcase/market-stall/market_stall.py | 763 ++++++++++++++++---- showcase/market-stall/preview.webp | Bin 20854 -> 31724 bytes 5 files changed, 1312 insertions(+), 338 deletions(-) diff --git a/docs/gallery/assets/market-stall-hero.webp b/docs/gallery/assets/market-stall-hero.webp index 4ab4f3d02323f79a603eadfac4049161ee7fe540..a223a5aa1b8132a002f2d697851f9a54a8df584d 100644 GIT binary patch literal 31724 zcmV)3K+C^UNk&HGdjJ4eMM6+kP&gpidjJ4X*8-gZDgXu00zNSqi$o$Jp(!I5_>mw6 z31@EHv8ZH{wR6gdJmbRD?P*_^k`mA~wdeMLo?%y>?&ryv@Ma(OyUyi5eSWL_XYH@} z|ABrt?tj-Gbo~MO9rqXhZy0^o{{Nm|pMMqp&-;(}k9$AzKWP2fewhE${uB4p=pXcF z`sekp?f|G0nmJ|VyIe(QVc|9$Ee{!6Ko{Kx*UWN$8i zW}oUm?!R$91wUGU`TtM%0RR8$0ssHh1ONZFKa&5I{>}Zz`#<{s&%b8B%>SSKne>13 z4|U&f{zLDt@qP_|KKy(85Aa{(z4Cu^;GgXu&Uy-dOZ;#9hxZTL5AVEj8+Vv@wZC`$ zbM_0K7wEcz|B?Tn$QXGK_a!;s`sB z7Bt7+03nr=ZRla+99V>a?`VI)MmJP(-6Y26l&k%BseK>G@4h$CScJElb63(6C{5bw z0aY14!9FB&uZK>D_$Ez^sAaNHRgvjC3m_{f z>}eAXoiE>Hn$x6~ACuh;Awvx#;Go#ks=Hh(C>Z~?EpaA7|NdvTc&bPXqXki7j40QW zU-7}M!G{gUIM5%T3O{(-eP($psB6vb{k(BW3%$Z&I#A2(@Y~IR5Z|VhQD6H*p#Mv4 zXJ(+^PK7_c_&+Q^QunF`5FLrhHh_k%2;A;GSknx4nAxt4BxCu;p8MTNS58vzvX^AS z@FBn9PB~lNP_g(99X@?O)W}jrWp4r-xIQ+jT->j^@eJf4;Nl+D8FOj%Hu%QL$7tg# z^)HpeuUwT6VBT1!FasG4WAqoowTKMk?txC7>4yrl#I{ahSgMQ3@d$V!`Ati+w`9A6 zNYG#rm(9{QF^`*xTVz<$J@l~V{RGJDg<~iCZyC9)`x6%@VirPjg?zk~upNyVI1@j3 zhi)Z(TZ%bT$SS-j{u&yl+3NlW#QUz#tFE_+w1d*A&h2nDAoGWVGAA{ocia4GzHjPk z&*X~g^zB|wwpXEnX}9&JrfmX$QzfyM9|y7~E{!ALhw^5~Wd8;b$FB+xD)vnei=a+Y z%GBK0EMf;ddkOGaN-HDihHw~tyUu;<&&A8GHntH_TkgVl;>=v5#ZQg`ws~jo;3g6P zy23YmBdAOnIjZsCR&0hQ9>k~bVoUw5J23c!1*=;cFXod1iT1u*q5@~k+kDKX=aPjR zz)hNMZ7(L~ocE5C(CB}HWQjGWy(j)UYLK@odP6V&+(26^{(r=96kaurL#IDZp}Ys^ z=`qjAE-tQA!+Kyv{8YIK&=c#S2leJpvZw0Pge{z-)Tk13S~+fh7&f&%09cr$?ull< zD7UfMuS}{P#DIlscmMrQ0MVbNdT9O411Zd28F6Ds_ue=Wb+E%uhxqswl7m$DmQfmy z_dgG^`*EtgDvInf_I%_`I|vPbwbmx-NgXTW>v8w41$=%89=U!fpuI6nxA8+wRpunU zMf5Ayc7O0wDvrcSTeoBGo%HUOb|(q~g2S`UOsk_Z(WxXZcPynKxdmr3(WwF18*L6Z zEf>kTr#cY{6xnAzHZu6=tcT`2W~7hII3GR7QPY?bK2~XSr)w!nUBcK*f;|A~>YLaA zAZGp)o+9ttyLJkIcbCiA(X;@ff0@|_ywXpF$0`Jn=s3Ht{cojWx8$&tG%v2K+sik_ z+ka}ItZF+{8AD>OPR}DZL!ah-C~r`aR4~99<#P+>BNb7v$Uh`KW0!V?<6<16HlZV| z{?gcsbuy%C)bgY^%?4C3upxq?7sod>)?uWCc@_3(tx~z`@J_WZZ?L}`Gl>7?-LoS= zK3cGg@v}10kLKv`RT0bo4L(oNmI!E8aziHDC)_Htf0-k9u)`R;wrV0HGe}qec z%&;US-du4cs}<6#+p^^Rxq`-t+v2b^E;cp@V0Ucp7?4i2M4!JF6+2eq991^->S}UM z?W&k|!ebW$tUqK_bgVCea|XBPT$2GPYEzdrE}r8>Y|f^#9+2w?S*b>R8A$^KkL1JG zzKo^4hCNHpyhPXYZ))V548pBv>R}Tlvn?4di!Xmz3L0?(`*E=M+T@&zi%4e96WVp% zAvydnuVkB2lde*?*GkW zFfM|eEz#zNufEX{xJyon|5DDDe?Z^t-kytg7fTK5sQn$)Phy2?o|)_@%da<3!^X55 z$d>+DJFC=v1|xPkD0Ek8J9sDsdGwcI^bwr4wsjUbNk;BBHnah-TYyQV5#Xr*i6!_5 z;ikJf$t)s%^x(8gBl{FLXzJJP17RHKPz@#=%mv7W`9#Phrr>kOE{!ZE>Zle=w1Y&R z$g36Z2s3ba87FglNY*^~8|m6Ed-o$kxh@*xSRf^)!<5HrzIr5>Z&b&`DE{Two%!LA z;S1e*BxrRki?PH9OmOKuJ$BB|NxvrtSvk+oIxV8Ki|_OoIcCXnV+^dKeL@1LX#hh3 zUX?&ux@s~V8v!D51FEfAwFH@(sMYbg#h&@#JpSCV(HIkFSzDQ~QwdCz2*D)0Y#Ts7 zRcJtWA5ZjzyFgM2ZjkxU&qsp}*ZeOc(qf+Q6vt2G;?nJ11%D*j14wRFC;B?nv0^`I zG>UFN5{SBc(UTIl396S%$TqC;gDDnG&nI-2Z+f(SG=eeakhl;6PM@db3TBC2>{xEy z$Q~G$`~1(FYzT~(zBzQmx^danoZ06iLvTiM7}4QcNWSZqH+SH(x+9)V(lio`DhlbE zXv|PJHICH)*`J$y`b%<+D`hk+0Hgmr#+NWUO5oUCQ-mVqMW`(T; zgHMJQ)rWu1(9hd7I^cW{pW;Z;vLC?Ue@2o#8T)u;M**TLZPVlvd7=`nAt7-3cipYg znVRH>B2sh=5L)oR={q*wOvaZ}2Qf74_g`&0@@(C5OOSd-m&#&h8R=d&^Q)g-vGFAq z1__NiN+M|}Wavmd<24=fHe`c%pr-#bC2J}n4L5b&PGf|7m|)#eX?SmL*W7H4D1rW* z%CYJaQF0pNk8YUUpG^l>E};~iSUq=TBiHhpx|1&{9XbO*pj7|5b=3wy>Cc;dWSz#$ zW+ynehl01i3NYz{`znJ_4&|)kV>5GbE{ES!Z#dsd>vWpEzX%_#RH~k3^55He-q&?> z=))>U+60pti#+yC%`5)|#*vLpHEz!!0;Pnccn9quAMl9zqIBDq)Wns6(W(W{l=S zha-XJ3lV=u1~Z9$9U+@RMjl6KG}n8VC_u^yxJR$z`3%Br%CSpoD?V;lI6K-X12iZ! zQ+F;wI=4eMcPqU$L;iXlna=bfQ25s-XkdSbxuv~`3#VOXHC2ulB|`6{&IAqW45?$L z{vNS72jTzYf6(9r69Kk-dgW?Y0HkYRqn-%^a;53yXH2T9vJA}3#-7SI=qxG;3u1?= z=*+MMg4Z>^ZY6%rG~~JlsWufX0wNtGRA1tX zb4sVYzoZwLnVDz*+754G^40@7XYD33uwje{A- zjTNz;&GHdRdHU3fhwT(!P>0>?e$c5ZDZjwO4i)G=wZQBT;yo(nbbCKKd>4>h^RfZG21^Vc^2b7B3oZ5G z&OCL2Kq?-hHpCKX^g>yy(pw~)3YEcUP!pOhC&5l_I6z)y1KF4N`=s$@o*oCLaBcRs zOt!=j{gXV_kFWc@3VD_Si4S}t*45EDYfK-QVyuBX7s2BgS(#i&zxiPV;SnE71%2f7 zu|vZWA)`Bx#`45v5hdAD#}y>FG>V))d5ygEmkL?xcg2P8vz< z@^?G1ZAE{SyNYq5SmB+a@x^7{PcR?mUQL#FhI?Ts_>|){zxOD?_(F5fS-&ie>u!pL zUT)|U-z_6UjK0=*SjFNiCi5^RAE&s-gTM8ev`T-g0RQe$_9$Zy;jgG6@}Jd-%s89d z?wTk-kqqnkx=Jhvkw7*X{c)IA72>S8fQlL-q=Gyr#51;ko>6~_2}9eyU(s-UWzp&rzq*%?_v7Z!d;Ac%{@3tBtxe`-y z(L1v-e&W0ETJR^w^4kot^STJL@`__u4qa8v)937JBdm**D)t!;VYPIXX_1Q4%zQ>A z&K*}uS!oog=1lNLKf*Qp4$LEy`|fKN6I0?Rue38b4RzaWmU72HbGF6lT^zQ`B}Rh@ zgAVc*=hanYhUZ$7X(&D?PT6q0@?UeYb?UE<#8S1#dO9ckJtm#Ue3#$fmb21tn;DNJ zxw8he`$vtB-&?q?y#k=3%Oh-d4Vw7(nO`WFv34OHvb3lmW`X~LBo)^l9KA*pMLUe@ zuZnx*JzoDHoXH0IFFWT+EDgZ$m!o#dAD1=bt{JH1C*FWvTSyg))9a(kxh_pix{=^d zNMC+8QgCYxps*<-F@Ej7;yj;*bmf!hp|k!Mu&ot~WuK}&;(RM!<7fFk&f&m_kopy} z9R&UP^!_XqSkNn+YVv6qaS0?526qN~TZt4^cY-RyC*X`@L836%44@)9Q0ws zeHX=@%^SWXAL@Q9GPLehs?AmgYes-+vySCt~`L<$eF(p9dGXW_8jL~BeN-5QbJ z*o8)6QAn$e&DgH(A&@ys)}QXkTPivhy?{-q)1brB$B4xQF0?S%1X_Z4?95BERVTfO#xfG zAg1j=vyD32W8ySS_#K97jCu%~={PU@l>f`yK5-(Gq!(rbpLWRHZr}$Qm1n%SXRz)* zo|h_6(3nVYNuao#Oe8fwNt3up0vrD&P@(Wha?QO!VW!e2;~k<}9gwU`l4Ue8!$Q^9 zs=Ves4cio)CWe3@UyuhYBBa;xOX~BaJnMeG>9oLTSOlH=4Vg=-h6;t<-*C8 z%Jb(lz|BImZK0tsyp6he)TybF$L=}8KuP)uzv-QeeW6)UrpX)0UUsmF8@Q5sCI9M4 zfZ95qe32M*RtP-&?9ddUdE$UQ(-xeV2Kd8tWeIDM1dS9@h+PhOX-x}|5hp(&Stm!F za!=A<sWet9Y#h!4i8DHbcEO}_YM49dzKQLxrQ6Q?;LPlhfX*vFLk+> z$a`zq(n~1t30pJa41_mnTOf*?RAtJ!xD&x z9c%yM&P!Q$b(wghKy=&03nD0bP!B6E+b@v?NDh+s4As-){NDa=#!^oBi-G&;RjnHyv; zR$^fE0M5GUeqaN`+3+A#*@dz~OYqz+65Zku&E{+VDk?Z@=XSUHY;ze>od>_GGOm}u zK3D{!SzBvR-!aVjnQ-~}0N@+|3wl^_$IR5D-Ta7W@uEFv#bs>zHv5jv-G?G~&K=a7 zwhhRg_Q91c-I8B-9Os%Tq)RPyc+AO+`5b1(yP~)ziUizLxZLPjNlmGz!*HKp$o1A z(b@zNKXT0%mk9eG_4f*F&;UvNO$E{Px1HI*2|s|p#0h1W=C?zdp5C?PR zy>{TV&rlKh;i8>DlTzJgz<2-$mJIj!DBxNEZk)>5)9=UJC%5eAeSpv<<5;-M`-!o8 zpNXdnyX!7qWqW)DOt2Y=drv~Pg21PQzBXtPE2G3&p&?KJGV}n08A3Qb)xY3gGSDiL z;vc}3{VrQE!40>viI9D(ZrKkDB(_xU9ginyUo>it7^q<%#Dqu;^G&FY1-A%00NeZK zR8%-WmG%WH-~tGA0by{GUbYArq4liZ(%{hn8FN$B98pRF_ngRj{TKQc4oiF3q8t_r z5SY8Rda5-6yufT^(TI|oa5}-j00Fv?D%AcXHDn!#8eKcWQ2$4Oxi&T}bNBr&vyB14 zyz1@t3-wohF@!#5#_%liC)z?Zz=E}UZ=e7G9a5YWrh9`yEuxSht=s=l1+AQUL$V;3;a&!QmRKF68T{8i9ESCt)^`Xr@yH4E>VGU_wdcS-Wjrb+ z%-fUhS*W)q$X}B;pg@DzOg7$5K&989mYhHX{{cdbUSnxvkg_eH0pBoPLKbnGpg%C2Ql+^=CI-1xGuFx<-1+XNYkK+IJ+8kTiPn|w+*EDlP^`qbh^ z`@`5NAGt%(Lpf3y<02%nt;l9Q=P1VYrK8Pt%Vlv%4&;AL9>w|xz3Naqn^MquszxAo zbSVnqwhWa~So?uxJ3g*S$s>lM*NwE{bTO=c(2n+|j8{pWH~TIEnV@SH3!Ger1qlJ@ zP?f7KVo1NH=DEFfkRyG>uk}Ftrw$6I?+z?mpEpZ1A@vyH-(D>jwJ^}ndH-so6E#T{ z*(qSJ0y3tm!VmozxE$JMslV4S&fy+i_};C;weF?-3lZwCG0M;yow|GbXGEv-de_kj zhkl0bI4Wx(rp$nt%DYH?=CJYCpCd)++G4m)xr& z>;Zw8U2kT)g`s26o98CKA(9{qU(dSpITQ zGowF~2AGlLt^h!jtNoWT(h}tot7$<_%7oR=a6tb479~ zfzNva56KJv!xPBPQwF8EAd+LLu?Dy%$B` zsLSqQhNZN!zz_KhnM!;B00ETWv52@pr)w6XjrC>+RomN!`YiJ5fi_G9+Isiz%+a>^ zQhFWHx1y*TYJ5xASPyQCvJ2CpyOBm8_X1V*GrV}W(HSCU<<;TR+`H#QKDrs;<+@0_jwh!xwOHc+zjP6Msaz=%?X@#>E{C=O3AM!xKG) z%uKu!4A;M$ zQ>2&1(MFZ;_-T2f4D-tIJ(4+0F|2k)z&h>8D#L-VxZR`8vpdTMea5Zn(Z~OJUq*0i zLC1IAagV7%qJk|CJi{5D~$xG3HXa~z=U$Ril4$TStt_X?C`98U#fuB{j(Hsuk zhTh{|;PAQn7k~Ow(g|v*(ygT4d>B$C{C};Fam@&O^S{(6)GbXEv75`8v`4vosX<1R z?Y7|JJ@vpP4JO-=op1fIChD{u$cNto!9~JRPcg(;>{WkGvPRHa$F2)X?z3yZ4l*Z4 zScuODQ`ey3Kz)1xgcfn1=uC7+gJDPWUIoARZ0y8qGf+|ksHQWy(998El>*gcFNLC- zB!;?=p3U3KZcf6>C}1uXU!wOYlKN$(@ez?K>IL?*ysVKc7Ut_-g{tJDoQ;oUWF9sb}KLTKl__^3pJEu~$ z+qLY|`od|Gw=!82(y76fNf9d|e1{_*BwtV@n`lWogIEEJFE5W9K>P%HLt7?bgxmb9 zJB%p5e*wQ(Ie&TJ568Jwc1xs3F=bZuNe3>*%T3X4R|>Y6Pz&PTMHwFous{DjMLdW3 z6*R%hK4ozTc{ojYN3dIarr!3c)XdWTlLw@M19G%EBVrm$O{Tnjgk+>~Cg!=ky%Mn4 z1yirFngJ3Gt*Fq&9jtc(+tDp`Sgi&0FRA<8i0D~IYa5OXVKPD}R~m&*SR4%w*@W=F z59MxCM9;f%LW8Ek{VEac=LC9xa}TE!aAdi-lcSgZI;N4A10J85H^k8{qpS>fe~Z3p znx9D2l$1-st=inVa!p)O6$^aHsFk$*=+}EXGUke1->qs8mc#?IX-tp;Atez{0~M`d zaTz>@>wAo`g3o>2@LjOSY*!@-8+lKlMQp7by zh8KXhu@+}1+Osh1h&Nr?dst6GSAZrljykL)(+^qBs;_;Z0X}rUt}7i^(sX0 ze|uq7Gax)|vf-0#&7iDAKp`j$2W&kzB{IbmFyh|GHRSijVQX1nk*wQOm0;$w*CHqg z2h)!xREDwTq4lSYV4&Ua4xrruP7|VWp@=j>jy;s#fmq1$lNajAX@3JfBfkQ@hu1ez zYd1X=jq;`WVJuWS`|P#j))!1#o}TiqjL%`0g^9eD9*e9-^7aq0iUZb_nxYTrx*k9K zmhfYzDlr2?+x)8M%1A3Igo~Uvcy!E_)mlvbE0E;CIcS@Kz;ssm} zo~;WkV8{BO33B9X8ydEo}%w;y-nio`<^#R-N28jprT(( ze+U* z9*3VNea<=yYX5q&bP|BqwS*d{=+~W2CK3h^IQK)0Ohf}F{J{M-5!7}d0w|*pzdbdY z>YbblAz<&j@EEexJLiXx000lsmhE(tFISBnjvCpdh&XjGS(1xnuQ%!i97_~sHW2ay zzeIroeXEeAVbT+-S$WjBp+4>{3G*LO;=zK?+#Xq~Y?4{(`cj0gp&ljPT%0(dpL`KD z=tT8!$}9(14ooxQY@;x27?Y>7R&(cW5&$x$@S_lFcBauKd_o;KTLBjD0Qf2LtoU}GJ8Z4}H_mjX7Pfk&BY8bn1M zeh|sbc;+Qur&jhaQ=a2yr66ftKLf{ydK6QZ-ywqpezWXrA zo)lOlgPq z=_Ec2WKEhSAzD57bC9q4_-3nl6CNf(QvB=CYV_TMITQ-uL~ z+2jC3M7jPE{4|$*dE<86jZ^yja3u5q`drg9kpr!<10TWJn&6C%1Khqzs-9=Jn2cfx zQspr3na@KcEz17_*7O*CmJTOAtk?2|b>tA5i1X;DrD8IEwVC%0aP-*PQg)&TQ-Xf4 z*pE{ysQ{-iVL2W#gT*z}f*lw^M*{>tl>3J`dTdN!F#+$uQY!wolD*8U&!K#H|1r+U znzO39JZzduhyW1wzcuB%jx3$&j08XjKDSYzsS(L_)w^sm?hqB6?hI~h>K>b>*T2?@z+v_b{`?pz8{^TMGC8I<1_)8FZC~ z=OdV)M-j#L`c&P6q}EeVsmiztWU$sYI0}2o4z#~BY~CqeG6PGN;VB}ubRtO$OJ)Bx zf_qBg>NwdCxMQJQ{xNAXLcB6d{{iSfBaJ84LljhaKW}2$&t8~+k!gr*k#!Z$T7ycw z2kwBs_#;K$K%f8tW*E+wR@n|MVy0+pU9Fg&n`33iPilcZj!ahbK~43^NpvB25@y%0 zpF$MW;|iuWgU8e^{&U&NhmAU7DZdN_DoB(LO+76+Qqf%7$SN0DP3yiVHaZ=#BCvU{Y6XA{-GWVr_Ir{=$UVm=id_nsX$ zEQ2M-pQu#ud(JhB(+sw@2r-SzLH>|H@&0Q$l9yhQ<}3P<3bYCntoo{vv-!x^3KptQ z+!ok+eU|LKqdVWbWJ9SR^{PjlUAHCP#j3#il8a;poxfc`TsvFem2Hi#2Co;Y(d(Sz zhTrW4VYHOuK!y3ofT0E?=|wl;fLZt(v{CttE#ZaUU>-KgI?KJLOq@YYOC2E6{Ye*(SXo4lfAnL*!5V8HY%Y$zTntdpCC6Ct1iG;b6KE#8zg z{jh=)l~8ZhURC#c2KQC$OyDuDE7)!ZTSzm*!@vA1Ywa+^+p^5HsTE~@-1zoGYaboA zS9rWn#&;}iBsVu@-F)-JvlxbsdAq(?%*%w`-x$}#t|3Y&nH`mjB0Pq?pJW;!qAJA3 z@x6yD5((-HDejp~6Mdb!^UuimqmK1#QM@m&o<9t}J%IPD?y@+()PF zNWRMf_M52~7U(<=PJ#BRt9Qf7_ZqzX5spp((BVS?2(i2H2s@Tx?x`VgHsXw{ye5Cd z%NpP0P5A1Q6gx@KvE6g|Lw40mY$UDhn+r~on=7+6{Lu~A2TNpl-q*GCWW5{WGQUn zFsKGPfzW4B1ddF+olff&&z=;EWK*sM6uCiJP)TzRc}z@%gDygR`vc>P6FOmfCE_(5 zkW+_QF70ICGya*}q(O>H7v=2=Px39!F^yl##gYU&LDxBl{WzUt}3EX+S;{BdScH$iN( z?X6SB<%o1Q1GcOaH@_@B^#ZKF3Jv+-NvFnB>LT;ln!4s?1c$JTxY9aXJs-}_2SyW+ zCr2B_e~u)~3uVtNBoPIP)fw!L^UCo_x2C+*q+ns(akIAl@R{QK!orCs9VlNty{XK6 zoX+)_>3`7# zR_VN_Fo@e>G?(ejaS)UvFHQOxITy30QA^dfL4{00-axzPr|snn{2PAtUSm*rfC%~^ zIML}^$h0k%eZ5S}Ki=)7QGKp$pimY=_5x@}d@L7rcyob`^V}`%d=I-9UG6{M$;iwHcxQ-`+&J@X1}WNES6{ANLlRi=GCc zTZik(g*bZYn_jJrQqmGgk1CY3DfzG7mRu2pgUREoIbRwKCru2)f{%UpB)SxLY9?Q{ z^B0@@%oe%s`rMIJZv zQR+R1!}D$L+001qEeh30_u-<0FvMq(0m*+VhBc>AMDC9*0lVXX8k=da&2%Vbr^^Xf@P?} zdBB`T0QPprEXJEa0k8lRkXIW|YJr}>|HjhDKTta5@J;+ij0Z0&ZmO2gCax;KCYNAw z*1ON(Rb}zr36JUaEj2&yl3E`FdQeHGa!LX_+Z_!99SxG6tcs3Imi`)1Na{)Y^_r*N_=ecNgfASn#v%ojrV^K<7!xwUx0j0YhB2`2Vn^&qr0I$nTO!Q$wI`IZN83DPcp_X zG=|0q*~V3i-wT z_EM}R4%7IQNt+02PUtMo4gJ^uNqk$b;ev+dWYh=Jl|1{87U}95=5lQ)=7??q56&pc z(sQSJOZUvJhtFTzOBgRZDl(KDKQ|nf$m~^2enwB1cmT z-lMTAb)56tVIFM&n1DkIz);_>P)sb*rCannxG$j6rej!J36V^Y&TZ>Vt_6+J_%@%L zD8M8T>J|yKk%^&awHF2<=<)OtQxA6K_TuG-7VeK^5J6jTLMCwS2ztTT@o4IYev}-n z2?Q&2)C?x;D+j{@;Gav1;aV(TbF1ph6pW1G?a$l{KsH!3i)bgn~&ihQRFBK z=QWUmJSRT0M-NDX>#Fm`|t599A z+)wY`AmazA}v)ZbbY+d zs)lK9u;Bg=pbz@2J5c)ZZ1dfivblU^;dQI3Nr`oZJuLwUL!l5CFzpIAP1{H_yeQqp zktv`8)j40?_5n63SbgYvBw^4!gnS&4G6FbJ6A=;hznp95~%!2!$$;0 ziz5G^4CHGkPSrD`Gp69$K&>8I)-c;g-pp*Av!+7SVk!{OaD{#896=Z# zyJggi`cI1bc2I}l^uK~PF*<9*(u~B$B+AD60goxhjpQ-nwlvzsmW(16%9uJpEYPy& z302Hf;BIDM$o%!Tc`!DO!cg#G|2;nwqy>&N`XwRDQgw4{l=@LBK4!G*7j^P|O=4%T zv7ufe^o9bnJef^IViydOZLB|FzfM8BfOlH7r{-VW(i&dLg%+9a##Ac~keyPNMP8?9 zz_sJrx-?l*B8UPi&d8ZysTggvxM(*rCnlR1XjhvED39FOB2JyBl%P2Yl!q%jVQ2Yj z>wGf04LqNB&N`$>!H7Dw-JZSU@eqi+bo!y+r0ELkASNXux3kz4vup?3x_>8YK?f!? z>5-!$QI8wVyiZOv+zwb5W!)_ZSbi<;1kKJaQ=!xK9GWgz5;c-H(pbif6I4EycaNu1#^DcLVs`g)y(UNO+livarqQ&<* zgD|R2QL~(|oF)~{_HouJ%(qttS2$Iw-|pjGLg}*^OzJZoQEXqRZu_dNCc3WQ1R;9iDc!6ds+o!bH#Cu%+!Yibqr}ajhiYg2N|IiOkpRFeDFZd? zR&v5}ui5XS#qtXLBV;y)^NgNR{~cfd8b}UK?!4q7nRKdENJtZ($DL(w11(!c&Th91 z3n~K%j}y*B^Y1(^Gl>Ef=4~S1cl9=d0L4%o;SR-&eV(pK?$+Z>%9D@hzff{E`={p)4Hhv_`a zVAe^~_iY^_QJuy?;cY*Ie6HJWh5+?vISfC$v>A~l(}tKY<)3fd-^3h@t!S1C``9FH*UACe0Lh0Ftln@uMB?JIq1iGc&malWMb1mX`xfHtky0+Lq~Jsbw1{e=2N)!k``7FY4|Vf+ zIL&^`9-iwz@E$Fyp2@=JLbT<6XoEv~c(2~|Q2SlR8uOSyz$)9`JT;m>*)o==p9d6g zsK_O@kcE?Yk0DHyc;odqDv2rxKXakH{VlPkC5BgRp*k80xt3A^kQ1K3D4EKGJky}! zM{Pw`S;SiCAhFWfuVRvO+*9x-XWIBIl*tNvBL*~?9Kz$PeI zMB&pjIwr1kcsH!0g^)#%kz3yi7AlzD(Ud;&{&BEGfteT^{t1O#p-K!Vaj1ImpMe|$ z$juGH?;*0p1P+gJ?x%aM-b&tBZDbY;CJIBunc^aDxzNc+2h3MT3nQGMw01Uu*$(KTFqU{UGICO8qh!CW<=Chp8r4DNA^kw!bb3ZIyHT5 zs|`X@+93Q~%2(>X5VpH+jVUUnJb!Y(KDbyIaYuq{kD>pXbZe90*Tfa?2wl&id4^US zg|TrZHEW!rzrSX_Enzs;jVTyM#boEYWW|gCQ9YC<=B^$hE{&i&4E~{*fxZo1&E3}i zZ1P<;&7+KuYEHkjw>3n|KiD_rh*_X@;X-HhA6rBuhvhv#d0$3wYgPbI!#&3**0bk0 zKH}0wH+2aqx_@TT%u;Fbl=_%JJjht|3?Aa~8aC1gbCeg%I^%P4nGu5p6D7!Qh!Wt` zVSZV5@`AU#fS!;qF((KyJ`qQX;_?QYcMD-xm_H$CgH^jJKO@d-p#csY3x~oIoB$nNqutU4e#pN^qU9b~(Q7U3UkyKuW{yQuHw zuxljg`?Xl;a)ofHrGdvi2SHpke%fBe**`jBE?3QV{{E$OKsegJPs8P+P%Nk!&+XY2 zIV(G2W(5Dsey!^@-K!am*3Nwo_r1Wag5xZD{U95`&d*j}f*APDaqq7@L>1Z^VY~yv zEGX0nw--iiUregV*{xz1#uMYCQ;ckDKWhcez0C|m-43sB{;;N{@^CQwzp<%fD#))8}oYo>c` zxw$}!=u|kMp+sN%7!ttYT-zWh_UD;)5k%IXV_qLX>|;bM&kSvFZrc#X%#4Q&VxGzSe5?jj7d_P}U~FAJM1v&Y_D-w-4!00OqI|P}U-+7@z#~E5!9zgyiWgR;N_8Jw z0ADkllVPGC;a12q{T+v;WmuLx9QP1H=qTuKWUm3jl^d+wrz82tG-U4JbQ@gbXvaWl zd*u~2Y74NTRT`Z}3y1A*b0Al>Zz5~z_)ShLjs%K!aGOTBc(&?oWahdt&1nW7R&^$x z-Y+9>dAR#7Ct=-N32>{Y(12jCmKbBxh_ghq_?0(>{^#kvs;m+w>B!J3o-xFTqu#``H*dWKUnY45I(oPh$qnohzArXJQ3XJYrGL@I?P5@&UFWVwlk*_;J0{n-bj(CR$ADPCd+UC%Gb(PE2ZYoj2Yy73{WF zN~%uic{IR+emEZ7mt7Pyh}&DfSmVKx%Nso?;e*q=k#%4s%A+z>>xY?cKm9%5bNG1u zyfzjS-eJ+b751zV@#wLxJ=(s$tf|vkp9$iI2M4S~3-R zGPvkCiu5ik$DXOu3*XOZ`IoBX*#!${@#2nj2EidEb*GidHz;}UO+QLcIHto*0)~E@ zFeoe$CrtUio0Ja1RkaCU6mlOTe`Sg+D^wEDI|)|QCu5w36)4Z_+tLFGo{A!ixqyy& z{NIiZ%_jYfj_)BksPTw&wGsScC52b_fnR|E?zgl<$3rv*{Fv+6yi@8iSqP!wQA-1k zdJrI{5SFptzi_obkJ>4J9Zm3=%sn^2R^YRU6b>i z6=~cL_*7|rfnM?-viC^Yq^oqtN~nlV;>Nlj_gPGj z2a8W|moFP4O~G5+3N$W0)Orh}H>V%>hE=oY9TWkm0s9plGZ#I*K05MllQcKL}6sn+&kQow>ow$$EB z^#9BN(iN`B>>(!4N(@9lTEg+IoM-{z4|h=hDQZ|bg%m;| zB}T9nhY{QjgcYK+1w)pcIm$i~PR*HW)5CX%9Vno2rxfo?)c(JMUV_(>5%JWf8_62s z*;W51p$*2Zp8(B{^3mI?g^~3Gst{T46AlgA%D7QS_lc+0!k#4SHA6uLN9?-q)5Ca@ z#~qn(lpyUGxOT$e+E~gS`xT7bja!OuSIeh3|b3AJ0`23pjpoL6wA>a(6-VJA>YW6|oBH$ae~;!%-B!(47gX`x?Lhff9Ww zIU{AkX=OXMQMcxZ-|iMujna@r^ae+L%NdQp@`9b$VgL_mORXcZQkC)JM;B0_RLwIn zD%HNbx&%Q|HR7pev?Jdtb0g%VY9UD^vSi+K{t~aSvQHlWdmm?6fZ;kOMQ8frRVb8Z zXn{IY_-ju-?;cs`LGDOJdt~4hE>$iRn|7a&om8-hc!GJysVqwysP%2x+k1WB2%yME zrsI|2_&n07IvZuV!sSf-bD*#7p7)3X5bgSTp3|jMheT*FBV^EHkBDJ<-rb@2}lgSna zwgx5A>1>gd627jbTtF9 z71}X@U`TgW+1&X~9<{veC(!_p{V~dR0@$sw><}X5GgXvj()c}ag4WyxZBOeL!Ogvd z+&MAVp5j}JQ?)tdvbiF1(PwdqXLrz+7&pnNs+|` z7fah6J%`1RUQsAO@;zK04R3isep!W>aD%XMj=^>l$7a$lk6uPJw($kCI@p+g=Zqor zhJnHEz@d5m`{b-rAb;Ch@)KRamt21DLVyY0Gta>H5#<}S4@9@03{-W_Jyd?gzy#J* zI>av%(r4)DC*8>zDtG)0W(!6qFs1X(efjgtsbp5-tJanTUgFtZEw&HulKkEV_m-Y@ zt`{H35h8UZsR9BbPd5znYQ`LAh3mIP%K3$+&C*kScPZ6i(g6;8{#VL>i5-X~Hl)*HSN$^?NdlCO;kfU~YSoDL`kV#D~h9EPA;(tSQ zTp3QwwZJrMK{Qmah?=~nw~tXXCngn9y_pC~d=ytGhVUFk^XTzDQxV??3EP|I6mu?- zU(G4_vLt;`iTm1BWH<_+8Qg4Z-W&R#wO3V>&@;@<2iTIzWpGSv+=&QwTAt-X+6rxA zCmj?^`pHS9KiRyFnAXsMphwvk!>3%b@^=Z?F;IS@6=2HTZDXCj+Uts7xA0PCwl`{@ zC%!$MEjBl5TG18Jn11Fec!;RP{x>}vHh(0XvOgka^L)iR!)JWP2E^9SeUd3<%acM5 zw_09d_g7h=fi*W_*(xpr(`ImPgK#9Y8o*=qKyj!CsA_*Rk{{}fQ#9 zm9@2Xz8%&+NM|WeC=Q6S4kwhdMERZ6oe2$3u9smA@IsX^H7q?jqm@;rL$kT^JO3Q;el{2D_QEbKnkA zpsRYCvFE12)Isay8j%@Vw_J7FtgJcgQR$%8x#|+pcGq@UqjS5lh#+jpnzB=`n zKAhN_1HtOX`GjyzQtmTkFF$DzG_4{nYC2`ew=8`cghj21Ok<#@rVgO(6b=8Gz--cd zKvq#McWI-|FvTSZCSq8n~Kb9SY2gtQ07#AEOC=GJ*gyS|qw5v6mi{zZTBH}E64&)b1Q4*XYTGB%l9Fn1uM3Uo5Bx%=N)P z|A$uAPqwnLbDBYDG9EQc>I(w`R7|BP^)2rlujyrX;sqpJ*Vh-dYD!&D+ptA^g@6+^%G$Nk^9OVZ@M#dbOWE#+;fdQQtA~bXe zfcoAiL@B2_!@^(TBJ|U*P|HM~Y0R){9bv@Px#`Hga4;i~g;c)v18Ad>%nMDHgZs1w zm_-uh(~3osjla@ENi>V> zHcX&;-^Vd_=ZU$t|1bGW4AH9?BiVVr2U}55<0e${h2)lpNkl=eK%ugIG3_EB4gCd1 zMO?P9E1W083H?G2WRlQ8HPQPzvzR<;V_9W;Zz~4Zl8)`%m`#KrW*GKj0(r&uHAa2`EURs z7piTbtWRO%e$PJE!c^DeehpbTJRw96?ucCfCPP)#z0xu4q=uWu>g@|+zfDZhH|I!H zlm;&v#6;R6T*6RRo0EA59Q>Bfb*L?3WOo88#k9&()=z>7yZ!(4O!rHq5&w1Q$M5r* zmX8HbhlZEU8kZ*c&y|*oOj(ql%o*(@Bu8&q-hKjwg?T6&foUl5z=n`XGyx6$r?bN> zQAP45xEoh@K8`bZ?xYj%BTk_KV?;B<4CEw$RTTh09Mmy_OVkBL24Bsxtp#CQ>5rIL z8CB~fZ0~{Z!-z${o~t($!@~YfG3M|49^cVrrH1qlimz;MkmMui!`U67TaQc6MqQ;aHSDx9BJX3LBF%UHM48vfbO5;=e*A#d@UqmT6Ud8NO-}VGDF9S&_2fp zX@4-20+^iSvJE{`c|b%GgqX4(q)x?V{XK8b=U!e+4r=B%gR$Z0Ss=EHTbkXwX9g-l z$j?|c0!!X_Hu+T^Nf*OtUlk1Ee}tE(=Wm@cNUXVbkS z%g(0+V&y=9%f{)?cqFGtSa1br9}%@mX`U`Nsjk*!_`Z?4Rgb-JrbU$$f_uDfo*#93 zyMNa7U%kDsA2T_zZ3qG*i3k#0Nd*>v-zW?{1?(Kc#LtfgWM<-6I7VyCwoC= zrgBA(iFBX~p@WIXt>vB+vgPdpH;P~IHkcxXhIKRPwNZ_l;7Q_-ttWYBzw3=T_H1#^ zfla@5-5GfH*@|-gW9IB(3q2+eXA)5~w$SP5U2f4Nt7|h<8H9ic1`UuLE2|{l zTx=t5vJ>40u2v&;UQY+4(4hbt+GYt(E~(ZU2~zE@PH!>PyeB_ZJXD)@qU`X({+@B{)um$TyCs|!uFJ;LE&HCd~4*qnsdd29<$0Y z=LMR_CMNX=kkk)5!$r&RPaRrnsL7PkoWmW7+B1o$fSC*T5P_VKWcbn>*NwNL1&czap;g_b=q}ETcR)IoS;Y$2sXIJ}6mt5D6IeS<>13&bm#uQ+ABk#O`$& zjjtTmffykp$8B3SI++MoseJWWtvY zl%av?^2}+6qgA5gVn8-UAwsvi^|@X~Sbz=T+>$kjAx^ZKhLdh)ak$w8f8R||<+ZO| zT;d43hASoMNfULFYMB1~Yk{ZY&(BxuWV3Ba*PBlLAUDbVhwCL>mlBBC@nhpPrHz{) zy0(D~LSDJjhdEI+#Od;-8$47|1uj8VOL6&CFAO%4vg8=Cnev1#M6!EAT`|QKFEwGw zkCh7iRs~8tm)cK`H8oQ)`xdj{l637tq1_VbKXHhS%H-kDZ~eNDT|kteMm2BfVaSJ? zTR9i|iV}iV;p_fSZXP)bUcQg%!?T&YpB|a2Ik`v6R@krj&)Xz36JebN4c7-UsnluH zz;CK+vJ!EhJe(ilAx)Ye9KQSN%H4H^%f&@3yC30F;*xf+JYU)qF4)1KA&91%bJ{vi zYL`cp?$j9jNoQp=KJaD_Pa7X)n5}rXf?S=_U}YR1-|TP>>NgEEqX}-jmN=)D?ksD^ z9F@jS#a?zVo^*z;H)!GB2+-KE@VPaAmxU68KNFS$gN`JeTf>h=ZxeXlbFG1 z@-L?5E3=TBsMe&B!r*L6m2oJzuvB5j3J`N3LgbLzw-3oZ8TGIzwB;@6?I#_*6sJ2d zumH{9-`SzazICHs!k#ab(;04gh4^TcOiHx2GO; zO~Z4#SW%(#TH4#PJ2ye%>uq=U)4v(OsLDgB6|xoVE2ZHj23C0XXerrd(ZhMv8x4+Y#4N?~#7-+OM~rB-<2mnn5{By!RilgxmGSg25Kao^8cB4QF)6n$`!c$^g4;^HE zk?I5vtBBba#cR@1HsGd(<`~VFL~kLnK^Cf1)I_ot+d3oS?T4~|E(UYh`CTU9>cNO< z;ZXN^XrGAwwB(od;Wd6flmctfh%9WrM42YT*lbW6QSG3r7gMl++omNcc= zN!~6ee5a8OJ>^`X8_h(&E&!xjUP!#VZdJ0AIC)+OTWmB3^^Qpc>U-#tQ8V{+b)USF zVgLs-wOjB2o2f#_Z+^vhs;sdoN85h5RQQl`VqOuT@Px7KY_S~{HfmD)&3zhsW!^~U zkSWC**xmq3JsqZhy%$GVK?$C3_u_5`*7ZSZDE5s}3G`k%BN(V^haHEZ-3 zF7HZX4g+dNg_2^oM&z`PIxV-Up##I;&uAni8#2xPKUwGZ0FEy_!RX2-i-bG!Y-V@= zhOREjv-U7cT5OAm`U7j4*u)oIW#DE3R}Auj{Q|}z=t4fI#5pCwO}__@ri#%a9@0f< z20|)N6Q5iv*7?HIAx_Ngb4lm#2g|T8OrC-O^+G(FK}p0nG!bK?qk)OXE$;rmC+>Sn z{k_FPy@)PV>Cr}2{7y2Y1a6RJhk}%+3i4?~< zMQ48$5{D$PMwp-5>;=umJY~5=Dx2O<7YyO&dO@?@bJFY055Es55Iws(XLx#A2wy}h z*FJO@l0K?+?Y)?m)x0WT;vIyMuY_g|M6oHwi5)zfl^N$);_M9y68o96chdL_mFoEZ>l0gQzhY1SDXwyf#E8n>u_ekxMe zEe__f&(+?%{XN65cgW1refi+0AdYIGKl-szNyKgI{Ll_cCo1El}DU z-%$N9@ECBMY$%9drtVG+uI{9(!H+-)k*c`L4DrBAW)I_8z!fOvUhRxU8{$eJl-rrW`rgg&e_e$!U9_-H~lLNDLJF zSRZ`PzVGeI(^QaE=ef9GK$UPuXLdchq+1rHQ~~jt!w9MvSXztzP;1+>r7DSeXiA3T z&;^+$N9>Y=-{vKoC?Uf*eIGLLja43u#Ui6LxhNdp0v zNc$Ze<;FH9hogKs*8hBIyuvM2NQ(W`#G?=!h{(feR)<(rGCM{avEaybpi=T0St{J+8XK7!S?)l%x(5j$4 z_W%EO@bf_Uxs^pAtRBtwK_5HsvPQcxG)1{z{_vC6T;-f0(c4G+B1vTx`h@<(yLfoX z=PnJIVV!1DAy@P~{+@%5D1Yy`J8Y;DcU1s!bnYgl85sXLjZe!ihQi)##FK&Fh0!*| zY7pjO^LCItFgiu?_EHLs0E+M$q)%mK(EKCju;fqLHx4B{??cw+qEF-%>P=tX9@yuk zYD4l}-nHm+A{XN2Pc_f*9f71q5@>aql9sk!YDYku=LCq^RS_$d47pFg(W=J7eQ7kj z~ADmol`B2d1#bO{Vi?DD|DdqZpn=uwCk8l%u6~n_|(Xeo`FLp-f!q?a`cq@Rr>%U2EfBk^t+Dc@xBngh#8I(R04|IJGYfHQn zH-oN~m7X1B4!?6#UP(%Kp-n$F0}AgmJ-u_6dfi_GRv^V}Xug~Z+b5QjLhU}8IyR~+ zm+>dS6bi2s@~VT!%nj9A&qJQAE7arF>#QcHLE7I1eGi{if@~*;9`CKybaD~}&C9nZ z-0v`BOx8%?V|eiq+bzK1|Mlbo)m`Q2dn4xaVRxozAvg8@SSfob%IkWgv7Rr}{EfWI zY{%*`!0^91g9z0O@$?70Kd%_6(B-biXZr77S+&wc6X#E0xG@Tb`vW z;{rU@u!>Cn+^Z^G%DoF zwe`B;f~Zm!4|@$+;|IgKty6eDs1od!I*T#>1^t{GJnH4F7!>3kU+jI8)-Zsy!mWjY zh1;NbLd`I1aZP*cb)qUtNC@RP620fZAB7;CBA*>Ip_Xm@Tsk^Lz_e~G%5?jc-KCpo zM%}RBL{z3{woo^Hrhvjrn={z)a#923WK9?VBcX$Rz$6X{Ln&`pm@ndx?v-w79{#Av4$qZUj|=avOoTDQ2MtOW9)p>V#H5d&Rp^A)tq~w12;biw zT-G&(EPX*1wF}E2a=fb#3B6A3?137|EmI-;6ekDXI82GwivxDM#3LeZ6%j%6S zS}6a-%X3m=U}>);PZRWvi&onHgM%AKqEL^%Z=Dy}E&I$z5#@+4VBKe&hHA0&d%I*p zKe<83tgoik#tk8myKdr8ANp@CBR4_WncPg!ZglKiD;zqD{syPnF;D~dtb9kcXuyr~ zV&HPTDFFEby$gw{chO=b;_jROhHhPa9`}3Xr7JRjPNntITwc1Jpd>rfK1R{|gGrrt z(JChKkMeIiJTbXMhh2dozMWHk|FFdI@U-CiTa8KA)<_lEg7i_!cMYGlg)`D}Ursw*{=|bjRWkb0 zit}=pv1+EV?s~-%#taPEc>-C7V%8wAW`O8QdhDkk6}U-wWXnoxH@LK^IQ~oCB^K`- zI#uqHU)@g!a+?U@?o}xxT(4=IpTYEsCd8Rxy3uolUoLOvq1{b3>bA;~BolRm>~?82e{4l@ zvIUj?{8-xa?bqq?!*vAJ)IyngRXBWDieTXYxzwgOmGxOic3g9M-`rO8oe-XNC)?@ka=3=Ba!>tvKAnb$bzJBe-H(v$P zdcV?TZhkpIX9ZoTFEmuac4aNfQIc7t_WzSY%XR;;7m3?W4b~JVH*f+NdTSF z5Fr4up;HZ9EQT_@7#*`w?lwZj`JB=VSPSP*@F8vz#_0N*1{!ky(7d+k8^q@$N?9J| zU~>UT(V>icf~q~}Rok0L@PFuO5-#2l?}qChZ&VIsIHF)HxWmEN47?`%>HQle1GKg= z!X(&`&3wt21iCJ97(Cbl#}?^fHCtwEN99G)S`ogN`DnP#1Q|c#lGve;_IXrGH zpZ@Vz)*A4V@=o3=aG`^_d4VGNafRhlY!&Qk!1k_1=cD-7+9!?27GHR zx|Rbt?-`(S$S6jmTmCO}Tfp=Y>?+P-HyNZ&z^x$t5!FVXL=avPq;S7#Qv7MGyQR8| z2y8kFZ>>0YA>A{$Lj-!<#b`4-yyFSycgxrD4DEeouAmnIIUpS3prv9uETixdZI|BI zh0o?`_juGE*k4hvv^n$dgakB@k8+^xt|Zq zOzONeW8sNqum_8H#XE>6XlpRXmlbLO=lu+uw%w418%RCEJtW~_--HJD;U0`zk65MfY zx)u^~cwy3z=GBFkZDdJqY=|Rw_z-O*X_v4JuO~BVe|B97dduY>sz7X6~6keu=>@(oU~)AalV=f@ZD<@89F-NYgilhpR#G0}!g1 zkn%x-{ugtJ6~>2XA`Bd<~Scv)KC>@%A51Og^{}z1U|`? zxY@tOjI`0aH7!D;>|Q6cdUYw!amu{@e83FQT=tgD)11M@0+wRqdI~@$CO>$S647cS zyp#j{43F5QjDEqSYL!5azN7agluom!9GTIDm_8v=K#F}uN8zZRctH7(wTZ-$suCRO z6!Y<*WYhV3lL8?!A5al)I({46{}OY2^7cctw&*Pb#53HMU1eYp?NQz<31jqJsrEG} z^iSuEqbB`T{lJhbaW+zGFaKFhy^9#H3Z8-(>@Jbv3o&mw=0huERd85w7-ongELu7d z6Qql{*zMC=?0=E=mu#rm9Z3Rku*HVtNkA5Z!f=q+W;$&DDxgMNRh8sBpxE)=#Wj*= zY_r$``J7Y4eX=U(%`E^LsKKfEcnE(Wi+hyja|cp9yROnug5%*BWVO5;CZlI~qeC zS`v8lH9feY8T!6>!SjjLA7VwN?~Wb6M`RU{)NK{c*&|Q#NzKSgMT^k!w6@9v85~j8 z9JW!}V+|-C%F@@DMR56NvYfv@4nS07p@}$sJ%QA-nCJ&y|MoesFvvf>qc0+ihxkWX&M{6m>Tvr z3-I(L*up*tzin^ZK#X`~G=ESc$__*6HX#^&Ay|GltN{6}Tw{#Gv$!>x^i$S$;t%b&c9Zo@^9Ae8IZ=y^=2UIjOk%$7S9dK%^y5?D6X@2L5` zR^o}&)_0Bd;iDvV_=Rqz;58M((Ocs)BxiH{=qvqj8CJU$xpSWYOQEVV zj=5h6qchqS>jlY}?3L!2xsw;Wwp~OnV0H=W*_~Vj3YB36<_INm?9C(2pi2eRG2@Ac zl8|2$n^+A4-(_)`9S>XzZ&hpG+ej4)uk(<^9`NC(5TMD})4SIV_2ghxeyCikh9tMH zx(!mMZ1fGDv&>zZ{3&D;;C6!{!y^#FxwYfuQiw5K&YM06AU^6O5&~mWasPBDOT|Ye z$HhZq2PSAx)T7-P#@P@-5FYIyM%C|!H&wY!#{@8~mZSdXx~7oM4n*%$vJ#s3c#*nm={&saIXe!!n?{Fo)}d;3 zd{5_Yi0~}SLqd+S_rVL%uI;tUBa80GFB9W?#W%BSly>M?hjSnn4%_7QYVu@P1DF~B z)RHe(?YQK;JRi=VY6+o#-ul>{4Tj0g(tmpuP7UZ!jiOs{#6QV{vj`%c{CJZO+2A84C@dL!bhP~~+gm!nl%ORraX zn>bduWQ5?)Jm>0GF97~caM^Dbd4d&TD`45HAGeLXt9GLF6SaV%!>oCR3-b{Is_l@a zFg&L*qu98N2J~W$UH}$mIUXv<~AslD$NPGJmLX|6*kg1ZIqxwj_^W1V>N#Oh-=J-}eyQT3wdX*}o^Q8I{?kavsK-Aes;i2`&cYHMTeu9K*h~ir9a> zn^MNz6;a(xu)*vh#iJx|9$NZ#Y{ivahw`dOWjeyrv1SB%GX()!i{{R{<6W?5NIu>4 zalxmr8W_M{qLmo6qY3Bu+}sGIWSPLU)O~Y_uDxOFI;c@RPRHO6LIEdKrQA{=UU+K$ z_JFdd*CEsVr9tV;26vHwkGviufIqSoW>p+0kN%gcO`JMorZG!ZR828)c=YO zTmUo}0=;HnX{+Ge#02#5+E*FA$Vwye*9G>sh=C~nZPKOlob^|l5!f>y1-5o4u))$4 z*NFB-!oow%85(j(C-|b>j8e3escfiQ)FRW;(R!7dr$AenG|H+>bD}kP=|1%a%+eR}c`2K!*uEdIeoPBH}r(>ugfF3|bkpql7M7s0D zn|O`gwC-9UNN>HkUQ}9CFP})pY!C@0o95O}Ool0NNX{+K{s}ow5d~|6(iLkdZh3}L z*$IheD5rj1bBTj|Ub1Ipx>Y=D(SV=tlIK2L0(p&+pPpW@#-b2CO+m-Hn5AMLmiQKb7Z{>51|4s`dpG2tLZC5PxZ20(*i`|7R1u49Ef2g#ZkZ*a$^i#`7^P;)Do zR&hgRl3I7dMv{|b$X;9aSRB<#%grcZf4{N3G5=+mFy8Xtc1K!)=T)@1eDM`s1=spe z&g#JrxfrT;Atgar6zjUf09+@^rF?@#4-iJwRs%e(e9nw!n*+UYLw&alPFm0X;~Y_r zbKcU6gwR`(LoQ97VLQ|7;<25RbC0*(oCr5Brn%q zq+#9AL5x5BBxe@>Qa1!WAIVRD;0oIP3!Nk?*~;&2`Oqi=)CYmqaYc*n&pu_T@{g|; z4>zTCBr}DOC$P}~$#89AyXQZ2S0XDEYB6$*9-eOki%7)`b}ngg{uQ+%3M^D-*=Ct5 z?XFrK@h_vX)Ip4l-(6Kn)0x0TC%QDtB_4kk=cdwuzt*HvY9eeMaVN(tdFA8jPkVmzA8XAkuT!o0n& zMoH(VMTnw4Tw<1Wy%`?UYEEl=JB}(-oaQ>CVYP3O*#dst`93j6FGlgYsq2$}fP$zF zDLTQv#d_DNIRB9_dlNBgXKR{T#oRJbtP_@abb18VM%u`7&%6FvU4K*+oN}{PhN#rq z+r#YbeX>gNZQ<3=c|*I^eaOoYfLvfO$ce? z*fud832xiBmPK%Zi*q|aCHuJ}&4D(zMZ)S4V*UiCt8m( z-7*tg&y7q__N?^aS}b*0QUZ=_Zo^!heK5v*LF#x^gyh73CvUW3a+~g zo^Qu)xTeIQ&WdV!!`rYqDf0xY|{ngBFZX{B$@I+-5*a^ud)4bhNQT{hseRrVHJ=A&REmW}4m*?~uj^qC({T(o7qThAY(@py7CIPc9(BvT%?uODIFQ4tYxWG;)Vx^h-n zhRv2H>~Ya}INYxGg=$hTk2*T$=pTv^pu=1*g}^T)qk!8$9+_fwEC+K^ z9|METkyOBM16i)kibDEV?Ocp5wP$$Hx{cchf3yG!)859b}Wd z1#!e(vl@Ih;s^aWD*G$%eO84GUeHA4t$aI&jW2jn=v6g;jCMB=<~|jF$Y?l3V#p{9 zbCE*P5dhgpt?wQOz2A+i^Oz&sAD{0i^;)iB!(V8bi-YUKiscEdCrqfoft^wX0(Y#i z{>yQN?33}NzR|}-Do=Ol1xHxoCbQ~J)NFfS7A!-+Zbj?lwzn@j5l>~XW=s}e#j?`* zjX-lY3wT?7?9UujG2NM#7sLaHQm#2eZOy03AB$bElJ0u7)leOqt14D+4)Ws27dh>r z;dJec%}h?O&{Bn701%}cu~2V6??7)d`hJS79T!wlYnqFQkM%WSxFy@f}UB2Euj<^>F6|1 zp7UjUid~4_@K;&&!m(4gO}MtHV3K-md3mKf&2jD>4?JxH>gz-Q)TYs)*^2?{H}Clx z>rE5%KjP23c%V!Q?lfbW4G<7}oMiA_gq`Dw6rq2@KTp@pCzHfp;p$`8&>67JH{=NJ za;+KtliBmcTFNIj&NY91@$Kwq)}OvJ^L#9iU0_kz%=+U1UiWFdBz$=^sOr5kaRl0;o&VLs!j*HN_WNv59;WalB0!99C(}A!DQ$_ zb&U489(Fj?LcYzH^763vLrO@UGWTZ1H~5hw-PAuay=vibj)TG&rL=FpNC3RwkGQPA@g$#*`LW1yWCS#!ArnjE0yai6rbfA6Z zemd<34uR~WQecuU_4M~u%5|6x=1jYi7*~_w!rM>U*wp+*~_gFHHnQL-3xh6zgOl}#hx8XUL^L@vP)OmERBpp z8Gb#kC;`Kv>lJ!?$b%ID+AS2Hzft>&gnbl4P?yjKjM+7No&YsAI?e&1fCqqNAfyVj zGan%>&OI})q{{uoavB9JyMpd6ap;sMC|`+zt69YITHCi??y_m^<$0F~K_DiF55U-pQt; zPD=-WNiF(0;q7xwks9){Gq+M^G=Q=E3_;)t!`j#Z{+6i5*j(TqbVhZR~mvmj;YAS`Yy+00}R)=m0K-WlAbX z?wSZXQ{1!y-GSGwV(T1l0%N4dNLlo`_U_IH`PD!#q+fmINY8MB}e9n;9t z`|p_KmJ>~?AdAk^r~~}GXs!A3E$k&IW%N=+Nx70APS3_m%tWCLBqdjY`ia*+9-nNs z0bF6|POW_zRZ^s&ZCQ9()^5bkC0W_vzoJW<6CuzOkh0pvT=1fSDb{x`_YCHW4!uSR zowY$zRTOZEQ6AL!JUPc;QcH6Bx|B@#pp}x#p|t@GOXq(x@i36D**&#kR5(qk8a>fl zL;YnH#i%LzFu*u242krM{NfnBAc@~>x%=hm#~b7vCb*{#OiNPirt_fnRKn{Y0nqc6 zIj2(9kT6?mkLD_>1vglNwM6S5i8aI8YVo7<`$*ml``Lm`I`sj05_xrr^rF|D>5_yU z^lSx1QJT)g@t3ux0*!;I5R?Uf=w!hewA!|oC&JMQ%CXBrXEJQz9*`2~D=0RA00>IZ zbbN$r|0IVs`c65K{mG5tN`y-C{0z6h%y-an{Kl_ZR~0YJmw{4H<7&7@krUo3 zcfxo^Doci^OcY?3W{$`5{gn1@Zm?Y>1(kUx(r&FwHpd(<$hD!dGd4KR^b?bZ0`{d+ zT#F2cVw)7?g+8F9=;a{{2=fAh+ugE!^=W^=xG&g{d6F{~EN?-MQBy>h=Wcb#hq+7@ zE>fZKeg1yk;X{Kq(2eG~#w1)FhnTd9gFTp8Wf#P@4Qn4Ju{R28W~heQBVsg7^VJA< z+rib^lXRNE7S^@s#>i%?atB>|5O~U9?uE+<4xjqDB(8rR;~(1gz_5LpVLW(3Z>)vD zQ4V@?$f7yCOrElwOJ$9d;&{v=zQZyhz47J+a{OtM-5C5#8UyyfVqc&<=4W&y2emR1 z3v&so`Tl1T1jyfP_|1lSFE3O6?hR&Q>U1-7{|To}|Pg*25;7#UgUVg}){T!OGg$XCRng3Y%p zl4R_Wu}KV~)pGdIE-rm_jJd_aLqsP3%@MMdu6G}T*q%bZHVjVr~k1u2uALjD5> z^@-QshW@yefB=u61eoLi5@0@@KJv@U$$pO#r3b8#k(&4`KzWCQB+cVnvB3pbBL2*J za)HaUsbA}pza@XCAF=ZBv-t5@V)Q%U9$Be*@%P^)js1UvDag%0B9_VdonEaA?wIoz@=x+h`H=Mbkjkl zxal5>n#LM539{x9FO9#TQvd+KVJn#tHp`mQ#)|{C@X{GoGI4;zHI?}Qx5L;IuPFw9 zd@t3;f7aHb8N#SIBqJbav7k^=50U%N8Q3~D-vUb{UqBEzvq{PAvJEV~iB5zvMF#H}u5Oit8Pl%GW#15zpdH57=-okcx_e9oi}aVj1F000G| z;7Pz6of2@i0N4`6KiKTj>YjxqsFvr5bK(u?yZN@54WL{sZ>I^B?HfjeEQGf8IYleoB45e|P@Jl-;ZU zPtzNp|I+`1{^{?h{a5~9XCKr*+<)Hp2K^WQ^ZpC*ck1W-FWawr-{JiR|2F>T|HuC4 zyeIxY+>d_W>it2#x_V}Mv;Ud@*X(QjPyNTI7qf5eXYSX)$Ll}-KjpvFfB*mg`T+m$ z>H+`%)C2$jwEu5Er~hgFv;6k^HS<5^|JZ&}|8Dh{^bc}Pz%aM*ehhy-{FD2?@*j_X zIlrs$FZM6z{RRIc{#E`D^3VJC7$(*KN62q%ly0?>2$v}MD!m5+gN!~-H&Ac}1$GnG34)>7h zav1h*ta&?JxkQjp>&op#1i{%wVR%5tC~l<|ttAA4fGJ&F8>TG*5WdCZ6%H zEFDjKa8bGr<#)P*H9;2EHn3JSBH`xrEqA|h+vqhL^^zEkH20H3XDv zxL6z0v08#R2477{nxXSklxn0s4h^#g3EOtbYw6g#$IC~x>)sLfc0J#ujGA#=c(%sY zIOP~S$;<6~Z`mt_*TWkw7^52eZ2kl8#@l)@?y70(z_SgRn0DeO*w}5UY6!N*&Su+o zqwewY(dPN*ev(O|)p4$OLHF(*xQWBfK^ua#1Y1~EesP<5-u`0*9=M)dCUx02<5Vyk zJL_>WbGaf3ngq_M34)oBF~##uM3B`1rac5wGq@nFagnQ;4wPar{I_h3S^^Ipc-$4J zF8rNzg{6&QbH%lV&lcL_BbglkXF_YZvWShgA(2J_89*uBvZy9J;ZLME9xGJG)g>OU zeD_twBaDh#WDFJ{eyuLZQ>=G19uO?XmoQ4O_2Uzx^NsxS@C3^YcXz#j+qD~unu|r`G-ficF4+Og$8*f}|)ll(z zCQX1iMBmeDz17lJ45qn}%S4zq?Y3vr%RG(x&Tk|+AoD3o&3N=uN#89%kCa( z2;Swba*7K=er%pp0_=9Y@jR#;95@8Wcu-I=<;iZOei>HAB5}i|<%$I*ddz|~0J16O zizsUg1fvgJxvmN)@{yWe0uKtew(>>CR5Q&`Z9gbFHr(YNII5uw{~GwW!vPf@p}v_W z*Nbdwg7ATbXd~7(z2A&UVUBDfA`lr#zj7O?M@abp%j<b;LK)n?hxsh_`x(t&99cek0-tXdp*&^g zk~EcRs2>*WoeN%t9nYq~6Krad2=$G&?UM4=^V9V)k9-d%?qt9u?rf);_7Gx!iIf8` zreA&zi-;%krAJ1DDJn4Emn6H-X{7L%1k=ON zg9>7NB+q1zQ;)5(5dlJKcUjRX7AHjL&m@Yr_?kk+na9#WL_2Xu zqP5DjQ1~aU|YmNdbmxhoa#Bb zNlMhYU^4rmhJSxb46Pjo%32`DSUy*w@Bc`K$UWVCwxPBOMlGk6-xkyu^O$m?VGkJ0 zThU4QkomSg6g9)QZ{F|74NOhc5w9laCxFAZIgHa#rl=``70?~6DxCpJeD zNHW4qcQXu?gif?V2FH~u%qj*g-CM2NCK5wejlm=Jbh>)))jdw{CYmzzV6Bm4N4`4L zNF)z6CpGKot1}_|3gsN?f^*sSMj?ZCZ>d*9nQDhx*n0~$017+{EB`K4G~Z&vvcG(I z7@1AdW}V|N?p^|+&882SedH9HOF!`U^pGLOwQUilV+a&sge1AD9uW|upahC)G-Z{^ zrm_^Z?_De`J+Rf)_c+mNb*uS;yZ9uu#odICNr&;B@4 zUzKg$Dc^Ey7u8{%Qm(C=Sh@jcP86B@`)H=B^?iu*^sYZTXAN2)zGRo?zDrmrsT%8q z?tiG-m~7A5wrdmH#6VZ;yn2*QzpcG9X>{nf3!1!>;{Y@si_W2+BWwP@g0TwatUt0N z^vrYH2WfTn=sx~~Y>M3Z4hCIbG|Ln~s&8*?l;15G?c?9Wj{|zd_+PL=$ar^z$F zQWFCIttM)^_bfQM%|3W-fjwFp6n2**u7tE0l3?|OzmZXS-^KjlG^cJ#caOIjK$&tB z8RMCPem74*CNJkc&Z597zJC1rP)-UmiQ*Z=qiYYpE1o2e^lY1n)UBVpfpv<=WeG?E z5B$LWR|hj75juRQ5^4?e=8f~cxcc)MTA~)Dne>%S12dFz)eZEn<#g1GV`nh7E{d6nTv^1P(&6g$!Y$8?uV;z#Q1=_YtMd#6KclS z!_l4X92fd#1J?v7q8h{AgN`!rwkB+V(I@d%R+I2$mP5({#`jlCG4xeW)9Pg-nOX5&SV&?x$2%YGuyJ(2Ds~B=VhP`JY&VxtD|wsy)EAZ zvJ_0Gj?Jtk&`y@H*+ORj40~(=7<(K}6vm@T=dE%W@v#9L}(@)>* zb~|A5YS4%Ld#rFckYoFet~2A6Zyv-(k8{+V?AJI?C(GATtWpTuBOgz3Bdyd1rvqQm zk$uY~X(Zo<`=w_*KiV_{z}SL->Fx1dF|4Kz-ZSb)1Gf_*)<6X{;fKT$oXT!#B!kCg zKA&C?Wd8gd2X4J1mz+ufV^O-6JSrw7!w4;UswDO!hq110Se@76d}yaUqd8CtQ1(xz zvndl~fx04cptu4FaD=`sk?*ltQm?N<9E|%wKtaPMhaM}}BAR2E>EG(V)9)e*9UQK{ zPVGWuLjDbLD5y#n$4bli(}4r8?HJW}(I~XQBiMIgF4l2%yD>GNLAoqRkGCTz?MbmA zS86?N0VbmRxY^BhE7(Jx9ospZW#dgWS!$*toR$Q3UIzpHYIpE7Kl4WIF|;{oM-)Gj z&(kr=vPqlKO3Bg1X*4{6KptZf2Y1PoNPfhIJ1%mI5)zGg$O8#^E|#!7cf4h zKl^#Hto!Y%QYeM1h;y@RdZyPVL#(fd`giwde2AO?=EcPK?cr?~YVDyww@%ur^Pc9u^j_A{w=;~y&%!;3eB+Fl{m#2Ga zA6v}d#dlY1iY-{ADcDy`dvy?u-l3R;pY(@B?n`%K^cEPX!NKLYJP^6uR%3ha7_HrC zeq!56P`O)yBTfx5Yl_&MI0g*W);Kn+y}K)!g7m3XWXc*g`Azdy8f>+Xp4!xgf_e|;II}= zy~QAYeR~ZYXIV+HpNq(*po=#ESJPv$n*OwBqbrk|n(PZD+S>MGw$bgpE~0$DEui7* z>tsm8y;Bm@(Y6Gm+f`?6+?{m@s0z?5^ESCz%jJP@{c9d!{qKL)#nmWa zwb?h7RX|gsW}=FB8Fx7h`vK_Dv|lXaCRVLWiB3ASB+yQ0uOb zASv^)x=IcV13zjUUu~VOsR8>XR6S3j5;W)g94i!VM&7k3Q?gM&Hnn14ki7Uu&GbZv z@03n6MLAZk<3;+uFZ{Kgj1g~R-CeQE!4)2;gSbv1OJ0+jEew8X=*+ zX=JtyQWR~E!BDvc>l7(SDq0+sMdWP5*SCy6I6Yy}0~fS>)*%TVo$7TDR^BF=HVq}C zS!dsnz>Z(1Sed^0>tj{U_a$XHJRIoi&`G~!yxnNwFoZ^9hwg=hB6IdKrTr# z5(5SVuS^o=&(g_@T^yH9|M;!9Ba~c9RXo^8UVHfj;S{kz6cxRES6mz*f5oq=+Usp#kPIo2onM`2JH;#d5 zF;y1VwB)$XhJx|rZ zS|LR8umLyV=bm$wAZhmmgC~x2#Tg7rYd69Sr#ox_&xTjj4DL5Ney-zz&)a|^b7MHJ zPmKfqh4&`$?h2Z$xQJ&BqK$<&N@7c1mO+M#WTTbtQVX=H^M+H z#173^jF1lp7vgE9LfQkT(H1|lcC{HFDucvKJ8I0+XhlNvjx-qQ?l3*(@CjAMLW}6p z_aN#dREP(EjGLi!_c!`{aKNXppsBhTq&7}GRn!r*`_itY9h3aR4ui>LeZ?jmrf$|` zVQOiR&)&rAit8+j+oz683kTsK^ImGppqv5}?n27L{ZrZ0i@N%_AfkWMzMfMA%0WG! zl}*@YKD|T66KH0g5w)6#+CC(c5ke!VH!FF6BppCkVm($^ z%QP|v+Rn3|kIT<0u-tu83BxgfR16&3h-zGXTDImQ@K0#YI$b9M@#w6_!SF9?r8TLm zbcA6Kbx=(s`LqcQR(gTuW;9|M-%8uLZod%D+tcwNo$u5s>Vd5L9YW%s9VgROB)Pxi zM&}nJ;0f^mh&8}{F|9KVI2?};{csrIZ@lM$s?aEVQ-h4aIE7OO4MWzZ=$oByJ~Zr4 za9B;lIqcpC6gk!^lkN>*`YMvaGPO0C>oK&54n8>(HQS0WnOV!3J{4rZskwMTi1Fju5d zlt}`T#uj5NHdps4#_hcq8AZye$nk|A>?W0-jZ*wrpiC>RO7FbXyg~EAJqL9(-4Hno z(xt)o zXC@+ViPQ(Lb1=JOmN`lUjCGO?5#OtGUQb0~{&BBKnYI}?fWA&eCye?$niuZsue#$r z3~dY1*ba_b)@#N`@vn=Ix6?I4&GXOdBcHIk-&T;O`eNXHmYeL(HZ4yE3Zf{Z1?;_; zHh8~)qae}r3ydOXmz|~YVP^4iVNC-af%6m=OT0?E1D(^HrIakTrGgrze;Q}%z>QMnzGRF{aMHb6#&<{IcB9oB~dQ6U_kk`7fn1Kyg;J27mZE9p*h~5 zw#m*v2#;0_3Fv~r{rLQ!XAK=aQh&rq)-}G)77jQusKFQN&kO)-005}VHb9#w15Z0D z;M~l>n~(qn4x<^S_zQ#pvrb0B09m)!f!BCR##FL=_7s2~r1Ea=<=@PGC7*Nl~ETsDzO)mdrGEbTIikI*Z zhqZAsE|)|Us2P9&00)<&tj!?upBjyTRBrjxGzHS4EJ+-rUi7Be3~7@sh1c~n)nvRj zPo}~kw#Zq|NWxcFti^3Mn@okXE;HR|O>6)FPiFt650O7Z)aOh9E7@mQhJRui-7!C-AE0d)&`9!U7T*Mdqwm)o@U2I9wkig3oNEb9&+4}wp~-|cZYW*D#H)F*aA9d<=8miOtqkpR8Oj8tph@rcm* zu8+#xWvUxq&iz?Ri5IZZ*a?Q72nGMGTK;{8-UlV-xv?3&xCRX+FV3b<3tb7BMo^!S z0(;8Z0O8h&4jigS0m6M(d%}La@qA|yE)YrCY$=M&MnPBI=F7h{C!d_GS*ocz!65ap zG2b#2{Cv!aL?|tSq#m+BpT&uTsd{4^Jc3bRYDg0mcX#ZCt1ux_`9p&(tui)^vAa)T zYpf={Ex-~NGI4$72Q4BZfHeaxP+CqB+7kk1b@7;0a-m%?KjES;*ac@Et-j^X=j%}z{P&`(_@-i!nH)mi=v;av@0h}?jVRm%;aWhmv3V@-=!NQe`TYaqIf+dyT zK7Q2cjFb3}fuha4d*r^y8y<3GXvJ-RBQSBxSL)2poV$NrWsJ+m-+U;QWX~s|lWEeh zNY&zFgVDu;bZ_3!13gC5l_APJ{(vCNbq9KyKL7v#02Ab;x=tb(+N9xtu&SD)*5jjefvEST5|f|FcG zI%;IaIbhg%;-uS{-v<({v}!><>NJJv|cGiQYg};02T}oCPV8NW320a0{vm(F_&wH1n(xP5kr6hT~V8 zzO^A9eTVGg;>89-Q~Ki&%cuzer&QCc0tK_|Wufa(Nvn&BM3U5WS*B@u?}e8nwEt6O zy(RBm1_UXi2v%x%)T)S*jzM$Ih~3_*q-VNyk|^6-q>L`=*5_2&@Oa%Y=e!HWwzTnf z*6wW~rpldzGul9(hyGtjTz_b_i<@(rRSRDMvdy&ttnocH%Hk!f9IpQe_HRHs4V-8I z7Z@VNC1R_egzx4^hF~O!4`UkM$}UnL^mZ&Lw@@OG&`>lVJsmuWn|BW}mUxF%vr7D- zYz(TIxB-1z-9<+7wdg<{JL>Na{qnd$z7o5r?IF3Oiby>&P7F%o}Ac%EU=Rv*75-A)H83_7OA5*iUet;(s!eV{%C zfXO(@g==840ncutM#R+niO=vp^DpoPze%>1YecrT#PKao7ebzekv>ueD1ph+CV_qb zExDR}(qO{^i9LTUMOYbmS}_IZ9tO{I4og6z;BAzSmcn2wOC}Q1e)uXIL{&v=Jxh?d zG<5$vVb2N<;g-|3B>yuXu+=NbrHw%7`l+XgsMA=_b&!%=2=`4V!i#Hk=B4`sDyn(u z2#CCkPA6V(gW=btjxYA~*FX7MYlVQ=Zt3gZi8iyAEn8pjeYY)dkcP+M5QiR}fS9o* z<|Fu=8mpEVy?xVRmXWvS=20TqFuR1kQ8eez8`*af{qxYr;ndg?5E*o!t1lARtQ-M4 zI!}!@*wLSU*R|yL2H5Hke5$ z@ME;ZPFiuw<1&9nX|lV#Kmoi!T_7KVV7O?NDR#lKT#Q;;vA^a%92OHB-D+mb4)#yk zaH0UU7|tIsW6yHqW%1D*Mq!AYU<|t4cj_xdN{>QFV>B~_h(M*W6xU8p|BJ+?U}@uP zWLh{z`B{6x*gIC@JwIjbs!hSf^C6NbC(cxOm_ns^BG;hKy_n!Z-2cN7j}uka8_Qfh z39d#{LW*=$rFa}9Mqi4Fvg%s0xmfN7t$)2d(ChtKo7NxGl0*OW&M0SGYEhXBJOL$@ zm@JFaR5)9zPgQZ8E(9%8f8f~TcM-^>xGA*ck%b*LL=4J`dHbcaj(|ot1mB^UppX-$ z`GRMA)h7j`PnjP%iPALaZJ+YcLvHeg*X^Hdwo;@eG`&OgSMc7_hiL)Id>du;`Sj0j z>}Z+s(kj$~RDKOk%Y2w_6k(n%R~XEk(7tGJ;A^=P*R>Qv@tOa~G{}2)^vdKk*FcukxYQdQ3SCQ<|lC0v9IJ>J$ zB!5vMuo*)=p2P+_uo^#c_E?tv9~-IO@{-~_fx#J4Kuq=nX;zjOq)a8@Za_li2tJGT zO4;q5+1T4$yUWshOnM#+(`?^)IjxcBPIg+mr|c`o>EhzITvF%`tJ{@=JfKU76sTMk zIm0<$93-Ez+g4Nb6)gUYK84;^FA@$co!+`YLp1eD+)Ev*1rPHaD^SVup%V4KoH^3G zD&ZE_kx?1dc~$kSTKmEE3<+kr&z-9|PfnL8@%y|&f==V~*rqn!;x~z_Qo0~Gt2FzU zmuzt<+2(h%cfvaszWM`6G_3%rxw;E|Fp@H7J?Qu?mfCF4%(X-&t)=I1VjIEVp9HV! zjNUh(B=T}M*^6^Iaj6K3nTGX3bo`%F(`$)-8`4Ue$ibfB1poeyTh#eoHYTZNv{~~* z9BH3uKkcq?zmWD-!=zg=nb?`PP>$HvjprT=#!J@0FTA4&yohi(T(YR7_MAI%&J=mx zj%9O2x*C~P=o`!tEzl><1b8ajm4%mt4@JIV$a8J@6Pk4L4Gx!n4c44g+0FTylFS*6 z))SIFv+9Gw`%BD__q=K>2avIoDaysDU5J&OU$6@&0k?w>a~-Y#gvKcXqMTII`=9=| zx8hbrE3W(_hZ}o!gFG2XWI=gkV$W3O2`BuU6cc#c6m`E(4nWNIr5UN%KjOb|g+p0003du*@OnqABUgTg|Z=jxNWUlX`5&kZKTg3f1?Z{%j%uw9PB_Fd{>2V*Jbv z&V%?rI(y+zY~O!sluuq@O4(UX+&vB!qTT&UK#Jn~9D>KO_ax7uN{dYm+7LwE2zF!< zFvgk#m5o(bjl{f=&@US=h7`FreF*mIf$USwYau2DK&9d?>tc%7eRT~AlCOB&w!a{+ zdM*#1b?z4W;I=Gu>QQ15Uyn%5S)0}$(~?8)OfLC(MX^|$!;{s1pOJ2O+!~rowJ*qx z>`Q6Gn!VM7zueZt1uTlu!7{HKA~JGV3M>%5#a?t#L~rPvcKiq!f~wt zG1htlOC(0Xftsy7B<`qZMxv-=E%cZ*>Z}fL$^ygA&XuYaF<*$=aoj7!3#MEwd;Ssh zh*Znn#})C0HA$uz|G&p%5Dy{zhArh7u5q~mRb=cylWqj7wT{0ACg+#vk!`YD2-;4o z&CzLC|6tl`j$VVJ9}&Duu5T9keG(lT{!GKJsE)IPwu{(-dOI#^)7Wq;g&LY*T%d6A8hhpV960 zb$G~n;95Qb{uW*s@)|4BLy^1dw z9<39fEg0iy-Wt=>%u&bk|B5M>Pvy? zpl2qaTg^mcsTZb<0qQJXCHtEl0bBc&=A6ab4i0x^AY6&c_Kss}C zR%Dg$9BQP#o`zwi0dyZN-&~GqmpT@gCo5;rMTj}?WA_$g2&81C>?A6ZhPKM(14 z`r;qP)(tTiO9$SzuA`hSE(wsj%m80tD>3s%?s$xH3$tl z06JC2kO6JOcqF{wAnv_At=W3J&keEw ztw3XP;`fC?vx$G&U?6#3MhA0MV-LW7J>uGD5 zAC6GM>*CH}9^+#wPe#d>1nXGKiI@w8oJ2YNNilgRu}@?}H!&48yJ60na<4)+P<_L4 z_R#LDjJs0&m3pfSr5H#88Rg5wq^I}KUA?rOQ#-^t4mjGAM&;i9brk^~?8w@L>t_aU zmlCEaKpnxZdVTX%a$*N^13I&J^N^ZtA*GFSS3!^-KslLa4P>Y+3814#cQKqnF#@2| z{NXxIEgVzg z+OnV7s8a}U5am6fya)fhTrA>Gb#YAcQwCCaQxXo<|CF40jf^Mz5sNnG*P9FSSG(0Y z+D);OCy0yGyr!WV)TQ~G6cOWA6bdAxA2~-l$usNhsEX2l1GxG9WV`mP$)b zd{@wbPMU8iaKC<;aIx?M4)$ycHAPU!KrnsKI~@y;oun|beG_B2L$)77?Cqsf6mC7B z=ci#JfG$Lq@HFgRDPE1Hwec_!JDd50{SIT7JeMo3|Lj7W*x!0V2s?^XKiG5W;1HR+ zaeHL!6Z=XNE$G0yWI`;T{Qfqb-D-8q3P0zW?E-5*7;)r|tM1;Fu@g088kbBnI2l>B zm%eMHWQ0f->tmg+N?j}HcrftVACQCxlfv#~_9`ozVS%a}ez;W9 zfAoEL9qek&>A-~^^H;_-b=GX!h$%Sz->amo)}jI1{tz*Kk>rjw?5*upuShcXw0~s` z%%LMn%f>gi2vA0X7tx406DYzTYnDG7D3z}DVbIn>UQvg4hO(>wpkNja0Q}k#__H#!Ql>9hn;aQH0QH6uwLZvq*Fz_e<#Pawrm- z-xly}oZX1u%|7^48#tHr9N_l)OXZr}wjH4?)#igH<+B`v#FYr7fV*&5)0Oqy54eZ#H9K@}^* zY~4B1Y8D*z&EK7nQs&$OsLE?IuhJW3m@?^;#Cl12{5~F>andk$={MI{!2vDCAVz?} zQu=H87<&pa$aLH6aOH_J_Yrq<*Uo1Q)YOUor}4V!y3H2=6bXfCyW)&gIf8>`EMl^zuQ_Z<|L zXxJfmVMp=rk>2hBGHGMv3>Hkh&PA$t9B>vOYxdQAej~&fQm(r4#7OT+JFHw&{u32r zk?Ev2-q3#yRt|(x;``Pt4FBsJOEL}Qf_##S>~aj$#nTR8iijYLGVK3hf?&p}4 zHKbhSWMx3C^6?>F_0ofw+;qocVcV(!olmxLiW$GNQG@g4 zLgV_}4&oso>J~sMhdS7+CLCWE}lSGDXI=B7QDiWw=Rt)F-VQd%S!!#PW@Q~3H$)k zSlZHy7Tm|QZwabMB{F+th{-=N!=95?Rnv&m{_=gDNnh+@s}neorS+IH;)O-nE=0{J zT_MfDAT1@(S!KM51Q!Ik6hmT}Z}|*(1$4rIRHx)B6ZO8Zp#Tl+-MVDCb0FnG0(FyYjp(($I#F5 z8n?Ot3>$(%hTC*q))rr{Z5a&4(BU~2KkL8g5sWDpxHSk3UQ<#m{35GLoS4E9?X$jb z4Mr|T0H&c9>xYyV(+@LBxYqe!E7mkjhSKY7cpq{1oQ0V|WO8IS$?w30T&H52;ZSVi zU-p+M_Rkn(YxO}kc0zFjvJ8EvvCxQgX;mlBOqiz9tHR@PFP4EG?_E!AW4Ff&uCH-&Y5DFCR}P|#4J?LM*;>j^{5 zr;?(-s3mn#1}o4^s@(J)T^%NpE*Dd7@ts2$X6CRmmS@D0lHjIoV&k16Gu6J&!{58c zMgtK`Mm~L;Qwoe{ZG(=%PtUYzu{ulf?##^GNkgcDftK(VS_Txw=(onmm*?r^I64Jt z`$A_Z^E%lVuKA{xF&lvRt{X<}8amSH#OskZ+v5n_0aoP^h(z?b((6+>z3tYossKR? z&YD+@O^GPX>w@RfiK4%X8O8I8!UlX*0V$)v2AgG5soZ@m6dRS|3#Y6DTMkn)Um zCD3KtIOU1YsG_zwR?^kRjAo9DzqRPCufJ~#rlICEdKoox)W12NH%{Gpq|`L*LRDYT z*Si23za01YRI^#QS$~jczVQH(5{LSC4N&lDb~)@$4UxP zCYtCOox!|g7m-7O@b2)qZZI46v>6bE2h|ac$z*y6{ud0z8=Jr}vCSxjKGj$jYs`{x z(%*2Vpolov_e%+2F2`O6uNU_HXkMnMBjc60XXI*m&#R}aVamddIz0ti5m-0dDT=AYjBg}vR4|kEG+5ru*In?J2;H1|diKa1FZkcCLqEWTsyLA1O z7t5;vO02e&*Yt0XVo-sva5shD3Z`?3f5L=XlN*!7LN4c&Gn|E-$Ss>`>^B~u1Yni% z;AK+@R9*|`=tRm%zr2ar()vH zuk)f-*$on%Y@I^1x=JTtmTn3^Xdn*U+R?ARr0M?W?vw=OY`;a$*@0BM3jVdurPp(v zpLmh*NqKZfVhX#RhQ<%-*it3T)|^gSOcRYprF1|A*R!{S0x;ip%aV`G&2LC`;-wt2 z?_n_`jB_dXQO^1nL2&VAv`UM+Pl8O%k=u)FH) z3O%U>hY9I1uIlwAw(rRisZam^NvI8nCa&xKbI5b*qhS*`g?;T`m2*e zoIDMe{5h7W!jjySOy6PYTc<#ke$8q8Kkl>qNU7nch@-D^g&WIpJtolX#X@|dAE6;n zpBFBU7|2K8`Y>oruW0#cttk=g1SS8QBR>xJN{?^Sz^K>8Qjg!%^6b{HyKQa0l-yP# z+1z4H-m_KxX|;XAT_hbImW|iExFw&_`|O}}0FuZM|I~F_7#jq;Huhv+3dd z$u~2mgV1K+!T=%L8^}~!S9ZZ8;M7o~-HR;_hvOs-``2A32s%Ajd+5E-?ka^i+_s$? zz#IuQeCBhAdaD;RbAfia#XAsT#Z9*uWw*b7zRn!K3H*WrBhh zuR$o}p4d7hYjPO|NGY6F(mL+JR2~&N@>@9uT!V_*Qj&x)qT#{9rV{{Y& z#L>&)WtlyHEl=AO2I}Jvbq5}~cmVd~z#Rj^9UEa`9A)8Byf(j9ryEfQ(`!QC>q&Dp zONj3njn9T<3KxvhFm;Z=QYxWDN14!#-q8P^-@Sa-k!QyVB$^})$)q;1cw0()Oc~;* z-B5D~?bi{RHSrtunYw06Dprrl$;yNd=Gj*_s?dP7*Z2!*0}=6Gkr(M?LvB{@e$nPa zj%!nP#g<*ebe+HcAPmX)iSIIArL4Nh`u_y@YQmraY^`lZA}%eTXXGrsQDP`+JWBS_ zpW!3}Y0C6BfGgbuJyf~^g$CVAirNqksYb{2Jgjg4svxw=cf$SXefIySO&i7(dvG!n#GZf+64LyN(cjQcRchMWHB4 z27iuDDc+DKM}woz&5)p{I^Qd8ds>|Ai~Wi&&_Z`=70M;7p9^N51jqOLN*XN8!4&>8 zccBUgq}m^m1P3O{p>WXoynVzDr_6HWxn23$vEYa8Dra$u-NHz&gdg|auj=*^|fGIIZnniV`tdL16O6iT5pEsQ&=f5!?{>nu%AdYw$E zV9?#vSQltaLlejPx&?+q9y*6=Gf#wVe4MQlA{sz)ozA4CqFZa@F0jVb{c>2JU;wOv)dX-xB6-)?6QylNQOw0HPv+09P~6p-FZODYylJN7hNs5= zSTL%e=Q1<4zrtl#&gWb|SBvn>p+fPX70TNpFQzsSzsLXp4r@au4)OCO7d42eA-ml{XN%aIxW0@@M|bdBvcZ*x+*Vx(xJ|!6 zAM`!2pzipcBIiJ13>9^;p-Uo#f)xeCA3eaANAM<4|#3=jAIxmEhCgr|(9Xge0Kc z9M#VE6SycvMOUKMF~D?cFVi^Z6@;hsR|{&wW~E^Gqd<`$p@GhcWR8hzo#K<-w;L1> z;q+{&_)aJ6YP);>nYgN==V5UQoZQyJb*%>yo4(n3TVH|e-CRj1+d)>$pnTeG zZ=fP+t1JqoEz7!gZ)=y4uORB&L(eWB0px5R+sF*+oFZL@cLvq>ZLODk;9dUXn0u&I zYS`Ta8b?3R$C@TIf3Oapf36%EXSKb1sk8twymbL&nv^D1_Ng!Jt^^< zmTlR1@^joTHW(VT-z`xyeTWevNjOhQh{y@1;;|;7o@2B)*PP@(yVkuZJMH0x9YX%{ zUbueK*OhyV#rit2ve2!TsKS!mr#=!*+U+P@DoP@xZxglrDv@Y|MCE^a-qGfEnmuC)* zQh0?PUF>-ezF%@_)aW&7FxhE@OE|-Fgc`oEakJ4c|3t51c9z4tua1ar8ULIK7vR%p za1HKAW!H?j`ZqN3cU#M=W>DmL$*N=Q@sEVi?!n*(f zd{-DwZ4QYXv862V@)3OBQJ=p0WT$H~>gx(34PS>}Lk zhTb0k-M&sRp4AraqQJXCHtg{0s^vZVOzzn(v2I^e`T?HpMEjz=Z}5m#xQlTc6ViJX zF>o#n)@IoBMGZRYxx&|w^|Lcd_yZ^kxz@#0c<{_dZ;ZwK$1Io@pR_!z8BQ?hnOTmh z;cfILLU((}&cZ(7>8Ejr!xcVWP9a~iQBWCc?JZQF0S(RibNLxA#p~p?g|b3*ys2a_ z`*XHHsp~+8n;OF3yeL}L9zbiitBFH@oDZ8HV|UUdf!E8kG%~iKfAWRrAtlW;y=VWAS46vM{hR=H#eDB#=JzUiWrLSs1L0MS=pXuJFM&b_dV&kbc&#&4fLwq7e}gy z&Z*m!#{8E08;K=_DB!^$S|1WEEoJ!=K%2w`Z};@~W)AjyR<^%?dHgO^g0*(_?&G=+P&Ni22!s)4D+Y*5KYszNa zuTvDkg&8N30Y8SNOJrv2R20AJ1;k&Cw z$>T=a345Ik*0`+zz067XGWBX4VTLa2;{fLH=BfA;Mj)vtWlqqIe?L@0Mu}$cdBGgT z6hbs2)t}P)9}9IY4}XmZ*qa>o0upm=#o}Y2Df*yr%z-JRw8Wkl`Zl~7&ySUQENkFg zDf)BMRFczxPm2)#*j*IA$?DZw!L+l5nrGBlVzfjIUG|#ozs*V?HhI% z$nu)ZN-3g;A@E0g9CWrwh za=u3=zu@SzA0H*haGT z?>2s0v|^wuAsQIE!GL!LS_yOUjU^#+MJ9U?Q8+b?;d|#z1}z+-`T-Pfq4ZA}S8koX zR^OE!E{gxve+^ck4!&%-AyU&D3>nw_VP5n0uw{UDutti%v;DqtupH$EB$>2M@JJij zX*C6G&CXZzt}Uy15-2v~g>`{R-^-oJOB$Z8qrG=lucbHzJW+fHV)?p?;*ZRh`F?rD zGfx_9?XZT6n@DHAF(^YqnzngQe-CUSaU%F(N84E|x=bU>e$SLO;&F)MMzr9qNN|Yc zj$I(^Oyb-Z-s@3^z^e}Oh&uKYBeIl^%{nkq*N?--pD6vK9~o4^Qfrf|TvW2!p8Y7g z`oj%IBOm=Ap#IiIQ;a$$39KliqR4Yczw)oEc-{m1h^)Uu>3Rh}Xe2 zBmx40J_9Y?Boo_5K=~KoNE;s445pyZvbv`WA6e`NbR!8{Rx=xi`>a4a#G^&*y^V}w zt-&aO?I0H3FttY`HPG#OkP$ZD<%=zjFW-cJwYR_3 zVYl)+*vDne8Ts)Xo`JA)&eR7W!KFF;N}3mebUUZY&`D&OVm5X)-jMPOH}CS?pZ|A( zUlQkUw?6l-RewNx*d>-#+H*PXL^x*FEo$>L#M=YOjra8Cb#M4{F-nWB8P{|l0+l}Q z3;4O|?nOcLN>6iJR$e|2LymavvAmf&y*xhpYTiZRSEcylqq^afcEbIRY0W=QZRv#d z|9kP9=GCSB*x1>f4O~Kg@iJ9vCr!!;pyz-OnvIrvbpAj6v=@q!8xs=QYnZ0t{Gq#G zQGeg60E5ZXgL6408zG?ecpuqqc;I-4&g>vKd{UMN>);J2#FkNaJH{N**)}WPH`zfh z_Xq4zr{gq>cc$NG3AZ(Jp2U}S7qU}z{8aPPjb!_wJ8X2qH>@iH@Y?)X682l6>yLl( zZU5}5Ur&_(gl>+}>tFm=(_(I5&}sDJ@89~s5X*y&^{DGmPd!-cgUJk;5XE^WvW#7j z*`rcrZ^1dnA-)7BOebS!!j!mv;fxAmmGoah@(b{+=~jyiQ)|$rNqgc&ozxr`k!o{u z`DA5TanRW+wQIXhGBN2UvIG*`1<-ll=ryIVc!`HE9E)eBqAfHcy%mJuHaLR5XLg?* z>x@#K_uFAU9t4*H#}^R&PL5K5aBpRc2uc?evT8a^%8)T9X4$cumyH1KD_ExzI7&c6 zmHxeEzg48r0f1FjMqGR+bEc-RHr&Y=62qq!!ejKen}=?ZMJMno27!O#E(&OlMqZDs z(sfI*3@|;WEztwB+@M13B1V5FJVX@&(A{F_y5}*c=eq(q5&&Imv`nhTl^!%gcDj?8 z2>7}@Ob7SW#_uN|hUD}hM;0OQNiL`k<1_OX6-1ln>@jsk$|&DmLgdt_Qo{MV?$oT3 z6|n$8O-sLZ-V)Rndpv-}9A@%@^)t_j9Mz(lt;Q{Cd)z59gT_{hH|v0(3RG(X8z0HV zSG0eNo}G={i*>@&HL$vp8aB+=RYBNI5K?{X#YGj8B51f~-?$q&N4sSaRyO1e*VZy@Bdha$vmMy-*;Zj8%C28{M2uI3yL zl7{uOOnlq`>1R00#>O>y&-maQUg5ZU>erpHN9JVWqV#-YR3eRBW|rX6y|Vu92}JTS z@ON8=gaa1eo9uiQ-DhliY-e%^~3-00BODGjf(gXef?nduK~{^r|WaKQnK)b0Q{dSZ~ zKQ-#8BQ&7tAnraX^*CxwU6eF4-Ew!Ow_#7tPw(pI+oz$kcnp-J#g~#sYp|(-ds7cb znuW*lbwT<0lwhsP`Fkpy|A=&QeOV4Y)-Xdga{UrN z(aVoweB;^S$cStaS=p@Cppc572xI_tG8vykM^m=NV3u9>cgnMM*k7dk^2$tvl7;>n z?16A;Wh^&aO>D)?<`dW!d@{Vw^!Bgwr7?wNvOppb5#4D#>9NY*H^3g-L*o?`rQ!a( z>abWB&+*P=<7oVvo09^uXZ4Y=`HdGL==9KLK+m57-)K_al7+E@_9m_mHRSb8^bdg6 z>|>YaWwOb3$N6!Z)M!<1U+lx_aX|7V!;d>w`~nr!cD1ZrPryt z4XEO+IC#uNN}CHY>KJ2bxC8#XXjothn5j)GZSSZ(>cOU6!Q%Kn^#&vqV=oUL2#CAk zt*rl1=-%vEO|4A&=b$!^tV%-$e}w(|HxyYPOWpWd=w5=nA7f1cD?B2kkQi`W zb&N)a;EZ;DR)>F{gz)?_a#3u@FN}pgPF}&OBaEjuS0ez0PCqyzc@x6do%36~cZKtMd4-GxjG)$-> zB094luZg+unKs=dqQVp!F5zrr2rx3lg1{UOwP`qvySWb?aV*0BFx91O1^57;Tu^_J z(rs25A<8NbR%?tj8aJoQnIKOFZ`Jh{?@A@5 zEHq#`adJb}SFRkgSXz2MN*$Mw%d5_-pPdEc``ge;%vd^FwRs;@Vo`%=L<@%rzDr3- zd|9!p^01NR0t5LvtTM1cJfuuZ(T$F(aQ}oBO2%*%QfJxkazM6FcxQaodnwucFPMH~ zd3?3H8XILQ2}?LRySg`KuT#26wjxZW$I2<2Giv%78%uEJnAkB)PSm7~us z^R5ru)`)B|9PK5nFU}|xwvoWxz1Y~-ep5fU_%S_x7;If1UE1+?)5tFlrcHPSVaGIX z_3LgH2u&ZGQeqKfHUQBf>RF+^BL-z82`*`3*648s**ziyzFvsSN=Xmf{6oB7~Ewa zbBcA(UyuGBP#PKacZoFVMZKQxH_}yqQW7-IWP#>WzZS(%Bhk9O*95ysrVmC`SL}r* z_iU>Je;kuT?gWvsE#Qwxy2dQKDd!KR^jABU-^57lLSP}e)7cBk2KPVNn*&W0G+E@y zK-88I1}O%^aA;xw-sc8M&3If`ufPr2`PDChxLVJ?fMI0|=tDo}VE=J1wcf(KePYj0BTx^ z7s<@7H3mwx0Vr`Ex|!?CvKpwO&rk0Z;Cq!#f0pg6HuNT`trB){Q8yt&HiT3ebzxyx zfGM)9&X?rLDeEFx;Z!!8R+Kl6UXGrTYSsY4+(TeJN;>_XHG5_{-4oj$kixk2s&mH$ z2+-ckxAibkZddT@UceKcyyJX_$u|899G`2Q~vMbRAd(D3BDgCX6h65Vws# z2@?M%OR-stpf42_WyMo~+>zmAQ_F`ejt-l@N&I5Q#o_reOi`Qhx-pp^F-V0u!I;?M zyxkcW4sud{Nw~8(Ye#}K!Oe%sN%}NVucEF<%=UF3TuamPY2c1hnR?2=>trvEn{0*_0RHLXSc=GZw$ zL#PUa6sEyHn|m8!4%l~>Bf{2;|ZRxFH5l?!UM=%gS~ z&e+Up468r30rrsW(?6VYRz{d3zls>Q5C=x6&SyYs0Ag3%s=A=h?D~nCTYmsa=VPCX z2wkdQ#ZUA4&eq=*5!(#AzL%3`1wVBk^738IL4})?h?(V1* z&h+-0PPvU<>>o4pgOYFXKE;I=7NCren^EuI01(P&Hvkkn!rYKGe!SfFPGhL>iU7VhQ5m(QcV5&FDY8T=qv4(}f4j%{`3 zYICTv^qtJ80a$wPhi>{9tLWQzzl2OB*f!oeNIeY}yF4abF{obB!nO_FD_Z(N`~sWp zlA;gQ0AH4hCAdg3!Vy*wZv@lz{~p3hZz@a;k~FZ9lb+=2$4{o2o4H+ zVGIUPSQ+#y+F3w=e?#6Ys<NoUicbADc)_h%4K%{4 zr@xUzkWBwG3N&Bph1^(EodE0Dew)uWV!QJ5$W@jmwh;Qt^0|Lh#RtC2VXs#L7dQ&0 z44OWDCmoNhFCeE6fZ;oSC5xA2VM@Rp#4G<>*N|UxbCEH~I{$@?kiIFTp*nU39&}m| z1-1EvJ)HEA8WR_*wC*lz3J$?hQyPoKB80|Km4c72xgolru&Ep$+aQNxsCtSdI3(@y zIH-%826TYv6svGfT|B98M8dX+9{j&Jtd<=8-WYY%c;P49u`obv*<2a1&Mo_%I)?)@ zjz!@a{;!-YuJ?h+GOJgtuw~AI@Q^WJO)?rV0HnKVB$pMY4XY-%! zAbxJAKH5hawo9L9u|q6Kig*@Wka=A^{{a4@xt7T8027vOi^mJF0kS z?%)DiO(RpPi=Y= zXkOWxy9&#g(8#W7L~3v_QbnmI@pT5E z+h<3LSV4`j)#p(VaO5h9^*~&};$Q*~RDjeI7p>Od&cI>?iAnVZlNTd_K%);Kz{D65 zTCqhbRDU@X(y}l|NsmuK_j<%PezZ!cKM!{-*bCxuPvNR=RJG(?kyg&wK6nS29w)Zr z3n^VfFu)LtB{y@7++E5`s18t2jtqD?GjPxMe?pTrc(UJ=Sp>Pq)DoB-Kkx%-dA26a zk10`bJdzJOZC*#yT0r~eno5I2m+;Nno`JRdNLZr2k|e$JE(T0{B0P5eU(^J&X+nahVi74v2>}UMna9rl z{Hkd`!fr0wQx!=ZRx$N3 z`a~xm&@BUs5=p9$0|PKp#!NyYwu8a({obhc;E;SI^oyl}`q~VAiXk?tGJ9SN*YDdv z=&!z+X-oySje=&Wf2XHkwiHx2d*9|{Ef||E{#ObbXJ^$kv|k8kdeDTjald3xNekQu z$ycR<8S$Yl{DE*X=oy5n$-@!P6c(RfLi{_9EM^KHdEO-sjdk2(Yj9*Ym_3P&q3t@x z)RYWn!ps8$%UJM=#rOG#Z$XGDw?%SPZh9kAdx(~8wG)mX3X#FAh*%NTH~#&B!@wtN z!D2Jv5tDxzDyy*f+xb@)7Rr=<;=~A=QJ~*{rAct#f~S47T@$~TdB~iBX_MDg9^;?@ z*Z`!MvM6uS$K3qF_CP}SJKn4pjwD9M2*c-e)BZ9_>Zp$ayB>F^o<*2U8LEyLr&*tU z7p|MamsFsI%6@gh?!FP270D|_sib67aEtm4rEnijEcR56ddX0@?e>JTY!{<%B{GSc z5_?MTIc$y220{&^d*;R=$`2!aZ*_1El;ewE82>(ZnRBb9pX}WSa#|DTDLu3NLni#t zsNVe}T(8u)z2_yr^8-v#TJ;lOS#9cqp*1IkOa+fWK>B<#OZk@8MoBm;YBbS)LX!d! zpF}B6lAF;yqBvMu#KqD$9;)M9qdqUG$o7j4^r8ftlk;RRm+lc|0;cn>q|DEa)|kj2 z_Sor4q8F#q;P5P#Dl$W$>G*mDKd_0o>l_BYr=WR+s7}#JCoG*IyZw)$w?bx2Kld%2 z-l_rbt_vKz7?~DFVeaQ1HT@KPZWnR0UykHp$V3=Y%0VI^*lR+FO2~?SY245bIuL$1 zH|5Avt1;NKm6AHeeRF6>V(z(J)SI`bJ}PUZsw?<#*1&EfEs=AJQ{<89^nc0L@aDboJ^W3SZTZoO!Zm;VF^7aY z7$milkvZgWOAVChNj2Bue`wn|tFa9+V(rOOts$l2Cr@Ui!*(jPJu27rw33b2%}i|L zu(N5!C(AsW<&5j)@5;#@5LQG5``3>=ONf-c&pPh7unYP{N!nJ4D z2`Leks}2&0I2V-ReiqJSQV7C_RD!$$wii(Nntza`cs>!KCG5>&oYdf@4}2=G2v^J3 zwFweIP?7II4g+s{$i}YJUn&N}ELv?uuKw(;&|pyPspcw@Fh-Z=L%j27nuX~isofwn zJ8y354YcK+UE%JAi*ama9)>(HqT5gZ$+^t5X*3fQaT~8Wlhz*Fc??&lNQC$`h{FK? zGz6C$eNrU3Hb7cvy}%-E2~i4IvB!p&G_6#gi;9v8ZHKvZ&dbf9TVnaYhR@VD*Z=A? zcA{t0Q3;1f6bb&8k~`K$9ttBnGOHle&2HHOv7-hu);#&x58`htQ>wmY`5=NNktlc> zWz)PZQA)bN>suoPdXKnuBLanE$_Mm2M03oL20JxMGY^~uxYrc9&$-F*ahOY+MywLu zka}|7cOUn>ckUcWt_c`wX?bMSf|Jn= z#xdfEWr&bnUO$eoAA_E}XV3kVFuH`0R9-Lp-a;h?@O6?se9u%}8$f`kXGg>u<1WN& z7{On=k23`aNNPI?3CUFi!|5k5E1K#M-uiCsJo&4mUy&d(Z|!#%_8VRpXI1b8NVtlv zo2jjL)T`qi3~BE~%`+6uiT&ZK5Qv$M@!9uiZ&9pOZzik7T8;`_w)+@7pC-Mb{Mg|C z1+Z4icdTkK2|*bkd;Su;-b+&OjCyWEmegap$U-zIkv$N)Z_^@!DIep{|1+0Dg1j$t zGdqQ9kmgo-RIam^d=>i*d!P?@E=Md+26}0aK?6u+tO7wAiT3&Kh#MDLJtBCjSi!Fu zk|Q+IuTWctzy^pr);n1&CR9{ig7B>CU8n=l1kU#W(vb6ra~{CMj+dy?c%N<*jKBCnV&UqQ_`9kn!P-XrBqVXb;op?VNC{2#&BXY{#-U+gIt zNf0}0)PdrA7Rq5iADl4b96MI|<4{s18l}IL`=m=;^n$Ua6IK=BvP;TCPYQR@C7tkg z@2>v-yTC7jYcuS5K+&9(3KR9>zV$x@uEQ)CO!x%pI6v04?RlvcEsnv!rNZpHta^XH zRtT2DXmP@)kj#&Ex>SvM$a7m-4*nREIt)c8N5?KM-nStVzlpLyx)&s|VfA@p!f-VI z;loSB^$cZ-e+0RB_>#eGlnTNt{_njp!Q#muhZ~GHrs0S`Y|?H~CHl+p)$%Q9emA;r zI|_t)_>6H7Zcm3AH|kv<#k9MpQ3)I8V2eei6OAZdLw#ck`h`G#n-=Bha3*T&h;X>s zST}U^i5jX{Y>lKbNO;~Q;ZbZ=AbmJtnm>P3Fz=Q7G$HD#|L$mI=^QRjKZ=L^1iHE0 z4c6A#rqKHQ(>huFD7!MOgCDnCJyMD6J0ce~fv4POv~mbY1prl-3q;8AhLgD2SMeG> zbleTqcBk>d19hXIM?G*}0^)y-E92&6!FG*?nRkVT9O;&BTe5Nxw>qq-sdoZ`w2*GX zL@_i1dwR!0vjVf!-oC#6*z=c%XiU2w)jqBiCyB3!AGEb{_u(cJwZV^((9^u!Py-wAVX_@({^_cThM`%DxJp3;-ga-&Y>Kc_?CB&9q zm-HXV2V|t6M8)@dj%aYIKJ^j>*EsX9X%OBkn{Yu{qT+u)wR*CV@fShIjeQqHuT84C zA~knJ4g*vN3_cEASnz>k!7gJqu0IbiQ=%RIBAv~) zO*&uzFw>OMWV@@?A@OHv#lS2p$wT9g?=q;VjV`cMP~AP$K#aT9$2h4G^xU{~&O7Ol zk%3xGi$E`8Pb!!^y(TSpoN>t%S0^!$LnK}Itu4t#3yhA=acRIsD{nyrpj@IXyTSel zhU`(%SCt3G7CwmJDj@osG%{0_)3Rp_6;ka(2JYbGk4I42s&ionzR=sfPJe&@iB-53 zG?Q>qSK}HFIX`gZyf~9H1P0Q5sQ|qJjc+AioM(aKE1%|siQaz+=Z{kxTRrYUCzVv) z9@H^UlG^%o^sLQ#?~CytEN#qhepN0ibtoJkce(6S#1_%b+RU=YT8T%4QW~NDNDhr zBj8M5bTvDW*T*CB=*!%xPC-yJv}Gs2SnxBLuL2e7cx%bOfGS;C1(@~I82)S-W8WMy zas*&hDx{Un-iQa1B+6g+D(#Oh&?QWvnBzOjKnCJ|7UPy<5MJM-Y1J;h#AaV#)J+=u zs-IbxOiinBvGc9zX;J=)ylJafK2>4-i9q8iaqJJry2!>}s)5$ijmBBK-OE0mL|3ef zadBO#1dFHy3%rrs*Kn(YD7X21&kct5Lxg@1pv1&>_YHg7dHHoLlw7o_tsS`7+2Z9e_rHY`bj9=M>>-mp1#B^OWpViN|)VKWXb zs-!b*G>78`=^HX;Z0u<^XgMIFNs7x!H}l^AaCSO%-fp_M-$KWF`;4J;o5Ch#w}2GH zjDiyr+^*s@UW32Ce>tJ#@Tzd`?uL)1G#T&2k=^xRh=A~YQv+>@IU~D^EIHejLR7C2 zr#P^Ii@4L3wz$YuovEvg8M^%9FqP_aIDI!TSt&Voyz zFjQ#MT{2O3XAD-hV9!1sa)N=PB z=rZcQwnnOlKgq-@mYEgnuNql@sza*5&V(#NWzzXHh3FC}MK}AK}pz{0^ zdMR;tPnlkr8rxTCe~@e?nu>U>@FqZ+e%ae9({0{hA^;a)V>C?LQpGDS)8V>2gIk`A znf*N0oFDU&YfhRc|07?&LJDrO(Q*!X?W>BJ!lt`caE_QyU2sY;BE)hC zG8k}umq(4<%({+aw$e9#Dne$*bjB)=lcX zG5sLksKFR5?Ielf88>w3shgsi_ZM)y4RX-0FVeorU~ALfDOza~jq}_Oizk`^I6DBg z7Fc9Dn^NV|QMj#FjDPiU*m)3&52dZc{3%yRi+E)6NSH;Psi1WUZV%l3?y z1Y)%~96@#nMF6NWoH>2hbr4n93E&&5gS2)v^X2VarryuaH0q+Vbh8>JxiXoeMh34K z#A?7$8$=G=b0L~4!U(`GdIyA$J1Lk3NtZf5Jc+0%b^olSjwM&AUx_P|j#M|R5pjj7 ztL~Z@(S+}jw^@r(JtoiIb@LdMKz`Fq&4I&+SlMamVVM+#Ds#qvhtclL7GS>T zxtz!sZ#>)(76!Ur-mOg^aB*a^ob+*r*2|~xaN$~^%TMH+gla#A?g;m1av`P;4=gaB zrgJB31yi+U(3i7{b}ixO*$S+=k-Exf>iav`I*>lFqR9dALHYiCY?o0tv=A6~d+FEH z#@^E0yb#X8$qwDsB`etDx31W<$mm{bP31+nBim4ay0*Oo*G);S_&N#ZI$jVoGCOHE z?AZU%cSs2SHX;=WF(RTIQm!1wh6!W>A#*_exdz=%*h?r38ZL5YgkHTt^K_aW`hN;H^ zKSMV`*`C;W3x;Fxy%n9Z4%N7msRA>E~Z&n69MCoVxbw%bmvl=I5^`wd7C~uiUoMZ?K7q_#NT21;aQ~ zH9_rMz{igac5V*|D`)uZdZ4Bfk%L*O#_+=ZsCzTyTgemVvV$Geg6fp85gN|u&T&(a%awO=T0IN3)M!VZMSny74( z`A{wGiA%!k3ngSN0ijf+VEQ20^1psm=zAgZH#9Z|2C25Dyis{+iy!uj_ejw4QK7Zq z%7Z#8bx@y2Nyl27kpCM4>*>H<$}w@5bi+e4sHZsAbu z#KRM`F6LCi2ys%!Z#+*>d^=Vv8%%u5;&*lv8iUK+boYE|PHmVI;<>&AS6#x)G;gHJ z8rxb^D~^P)H<9pdAY(B_Zt{RkgtG|N6x$MewrJ&o$i)hAuXm8~gxGhgK+>l)n6M%Z zJe*_?8g|jdP>Q1pfP2r&Z6w}UZa>h@ljhUdblcK#Go*#ryUAY)EbyFc+@=4z*TU0Q zm1`o#_pX>IFrKrnuDNmwY7Dw$l8EV3YX;z-%vhF3DmUbro7Q?vAe(Ya9#xD!fMfCK zK5Y9v(o1KQvIG3srYI^nzP0ozplE0gRwUE7=G{I)bz~1)SJ&wVf;dL(ay3qC8WHOn zr?@%NaqZdlKG$teQzNT!`xWcW<^sDq*U~;!4&qurVNpWoJTM}IBD2@Cur;M?$Cp+7 z2A`V4{ck)?rUJu3u5i{Z2<0J;gK;{x7}{2eMzFW@1j#7W}?H5?X*#;M~a!kGvb^?;prk7$aijzA_>~zvo5+2~&6&JbV%Mo?d|FHD?6Q8twg)iUi=0wDu^9=x zLP&gBHw{``&ehicikIZr)?u(AQhS!BlcK-k2o$K;m?jvL)j<)-S_}T<1uf~I3=t2N zzw5G7{t%lfmcEJl4AGjit*nI`x1j__q-i5g0jC!fJ0-k~YGgm+ww+Td_iw33DCN&N ziRnq>i1%wzaYTByu6g22ySC(=cre{CY#DY`xopcSmRK$7{zN0}lAtEi@?K?Ke;cUK z!pqTQDEq=*E-;(5%HwB_*up7&o%2Y+ci&6$XkGUatzE2l%pg>?kh9EskdFQxTHpqb z%Az5^$&(J9=IAHUlEoQ^qu}zSIDSGJ+~u#IWp^mKG+fR+3~LMD>!RM`3V~PyTg=6n zY%BscwQxe*U<6w~4bx|b85qU91Wd{ozC5a0Va1jsIy&L0?O6R~eWCX!v*d2M+k+rC z*?hhnV$kB8iEC8NNWG;RJYKr68~*}J#1E$EtPdNQ{J%CA$WrS6(?K4woIENl!x%gD zUX=qi{{s$@tOrH{aaf?lN`Dudj2$EuIU&M)X2{|7h5kD2Aqrq)&^1Pf80S}@$2SiQf-e$Z z6REtUQ&C*P5+=>0?AjEzE1F*i3Zg|N?T1R9qpLy~beO>`UnE+_Y_CFt4Q5qVqzff1 zb2PG&I%;Y_JLA6lcrrjX7s@BfJ=`x{kf4X`^~`U z>M}?Ic{nj&FpCn05Bm~AwGnxxk<`8EaRg

6$dx6pZkR7*ClwvF6#G4^np)*wyVYp4X=k$M3f30|gWcZXm3)DJl^AEP!9Sy^ajQ?yK6Y=XoRkM z?%yu;J5O@YI>;ToRO176sCyQq<~4=jn+;$*fCl@mSobTx$CnS$j@A|DIqewE$7qmPggl&}BOc&La@Y;-svS1Qf~dIhL#HySY700f$~wBb0{asnyr>mlc2 zlsOt3oz!Ij*Cc)w@1}NK{idw1%-FK3RVWrHqF1_;>Myt(Te-IGeoR|uZ9msLd0qk~ zv8ewsHQ&SaT>m*YZ4lXmzU5yd12jd}a3)0zJLCXV-7zFh2e2kH2viu$?1PkO7+CTp z)B=R{3NRl=(0MJR_}v37p;U-{sNeW(@|Uu#2X`Aw1)=Br2Y-Kg!N;%vI^u?!ily_p zwSrFO5SY}Ag7-e8L2nIyZ*9;j{0N0QQjoe%7AH0?H&EDM&BWNgSP32_!XV3)^77A= z9;VxaZUKa`*0%W8?9OwDr&(U;6c*$nrsOC*WM6EhMqe)#BtJh=Fk1D{MKc z4zthk4b~)^u&A1vN|vf#)X8XU4=)k`hSLS7g8bF+5^9A8Lw3aO4g1Wiw=iCG%; zPNa<>T0fZd|7m0sS)tJMOu17xxvW%STI8#w9EQbJc;Q>c-}#P-wtd* zV`#guH--D7V;99sDl7yGG{}u6(0*jnTB#Dc-opT0^r|1pOgEMQeVkKrNuKex9oeuv z`CUOS{`Xsxm$aJIPE6UhROM52eb&}a`A4ZA6GWHA@qD^~gWOaq00zHm; zes23JPIE?3Vnj6u?R=S;ug&6%aNZX4NgOhcI|@M@h=BLVY)}u6)G{6y>r_)Wr6DNN zMXXx^D4;O8u6G}RF-ISr;GRK30nK16^7v;Zs6Dw5*Bw=9B?^}rvj2a1X%$B6rNSts zy6Y7XhS!jvD@sYscqkvWKMx})W7>-XF>d5}2c%(e8(kuCof-!{f!yc#s9`jz0C#ow zxY!;&W^~}VlBVVd|rU z#QILSsK9eHd#9Ix0u{rEI1A|J;^ausA>#8{7O+x9N~Nsf&{hOUfiZ8(Ir0qZvB^0V znFLqqTK}Yw*FYCp)+B~)a&O>DCn8{Bh!#t(9ydiAGO(5PJo?$QPXXv8IYkAD@T^~L zx*K4K;X)E>kyp2fx-|gblhQ@nZ=d2eQet$hfA&wnTdxs~pDNupB6j#a0bOnBJmS2& zZyYJ52LqtRgBIpF1z{)`xzV!lehF1DH5#;|E(WwjZ7F|=(s2Uq{E++YBO!bQVS4f$ z3$5~fdWuax4}HSNQ@1s4t6vNead(W}%yDJY05rk5E3FqbnJ(t(c+LO>TLc7I=7MG5 z<@DdksD}h~KJ6Q<&61bMXwyP*h(D~Vfo$S^rHpD!ueUDh3Op1Gl{W-9L&}N7Y1j}C9z@CJW1|VkN92#0wPWQCf2Tr#0PkKhA%YLS_pM zyed=0Keqh`^aIn?X-lRhe^ca6ud`YZ=MPN*yfl6323hvLFP3=NhfsSBy96-HdPang zZ0C4r%hMptbMaxL@6ifR!=?`V@Q}B3kP=0h5YqNv{hmK{RjM}li(b75YZQ@LvR+AQZw!)nh`?1U6XoOeF{B<)Elm@2Ks-3=1_)0<-cxq6!{n< zw9q5%c}biX@M`p@Oz7?`GNHN(`yu0gP#X&s{WMS5#@)bjCJ!zYr#D>VHlnGKb-d$X z5SI!M;q6CN&~muH$V9FD5Ku;Y$&gV>SG)N+&hHAYyEprEFy#+P0X9ls`A^`=VgSCj zX0qNXutVMr5{H}~a`vcUv-v8BuO77DU(1Uf@f{DC&sf zuju$vGqrNb97Mj)dSyMPNpDXD4B)1-j(;oN2}fvg`QWSj=W~OKOuK%gnp`OJL?j^( zsSe?JH78695jC-QS{QhL>wEH3y2xk^@g-of(^aF({6_eL!V+5ACmvdi7{?WlG++Kk ztT+#ZxBT!c0LXdI4OQpc{ep;e&!L*N!sjpp>-Wz7;xM$6^ zIB<4F>H8Yk;JG{HR!0ZK&3BtVr-Gd)t*O&eJ>m?T-iM(vBA>oYGsM$wz$;eh*ID;S z{sP)^!yIp4meh#O+HS@Ds{seOc)Ih3lVVtv1_pgcJg1jYs=i<{d*ut}D=`S5qd_5% z0?HkR99guN!4MUugP{&@=q{koZdy#4AlxSlp28W4t1-4Vj? zj9y|`vIchuP6xSr65_7d!C>3A?FI$9c zTQIrHyPor#&{jwg2&YXIYr-)A+j!a$C|rdrp?YCN^kJ&MG7A+v|5wuVLdDePp>ytg8@ID9S?qIx0)L3 zFq$Q$Z<3OYGIER^W(_=~ne;sfpA|Xb+zz|TuG+1_CyHSD18qSlR8PCX1hm6HZl$5> zH57({KM6V2z?NTDh8&Prm~U(&eSUC+JJcAba23;ykoPlB^XX$Y4uUnRhuG;x`L%)? zg@7Gx@-6SKGVp&O&%PAXE_(9V zCg`JY^%_oZXfd{pmq_b-L?%%Ay9@5dhJyM>*Oat(Avw7HSIi0lMD~IEi)@Hx)*=QT z7~hm8i_@!exQK)K+4p=Gsv#9Hi*0nAGT~D+5L&l|c=_Tn98*2NOkMC{!mGYK9VYnT zC6#g(9l!`=d?hv$5vpQ~y%Btg&J1&HB?6mWp>R-?)c<1<7z)N`eOD968*SuYO_do* z#mW+^*>ard)gBmi3{d8eP06PbGHyEf(;(0McEuh31iXTY*V}w=?sp-uSc&owf5uQO zKyg#4YSp#d3=%wtQ%x0!h;nW~bhX*VO3TT4I(PcBIf7VRQx{EF**&sisBZxEonLW$ z1sTGu-FK7&pb#$|^UEjs%p86skN(NCqICNlWw+mD&cLps7oh|%**F8UbKeO2-J&1v z*(r!IAW48;Yoe6BCqrd$6R!4xCIrv9OhIT(Dt*~8j6?r0R>&HnCXX(L(9-yYZ&dP7 zwW>G4X=hU+=6@JK&>2%B5GOacjc#(-MbE91);iium6Hic zdbQJcpriQCs^e}>|0m~&v-ljk7@bhQemf;)&W~4{gKrC}?f*G%c1*+QSvc65mBmhb z>>es=E~lEJ1p9`*33!eBTR_L;$+teEOR9A1`q?gzgqi(uqta*soi;24u902cQ9Y znk4Y4ZFqKA52!Q5x#bTr*}P{{*2Ss%^DW5ERvc)BspOmXIJGxRS#A*zsL0d`LXArNrb;m zwVJixxU{S2Nd#Bn+Pqq8o(q<}8=*EVJ$Z_RN42caA!)X1)-a{~JU?x<&n^cCzM}kc zdr3{jxK4O!nvCf^#KRjV_?|8FV`S)*rw+>xr8m9A)LPtwQ70O{Fz3jDl80TH0=J@m zyihjR+k$kaPpE?cR;_yg;uTHCIWA&yYgAwShNPV!F7kuayZg3U|-321&d7ORxLe zj$o8Pk2T-wrlQ%p;sfFG3~5y<*fl%;^v)1?GfCW0YzF;z0{-8HDQK5>BpgwG%h#yE zzEb!qWgkQf695Z!Mv;NPKsn3%Sbb8%AS@Dw0Z_m+du%)I?4^bNnUc7`A`gzY4PZ<{ zP!vcueKeteGoc2G2VliFtk4e8!GD2Dd3)GVQ@sr~rdd6~*h(&#Nm-LjL&5K7Dxe7d0%^O+p(h|PG9|2n@$ho9OZw4aNYl^&e8_YGPDM!pl3OpX6mS4hV`RQ>cI47A zF;SRhxA7BE0WO%=icEFaT}vX)q7kFn3@WT

xCdbvTN>f?_KT7aL{zreR7kbO@c% zG3zu-2sICBItcF^pml6zw9BzFTsQ)Ylnr>riW~eZBE)C1flntMqzJuI$A_?Z?c1L~ z>QziAzr>wankwGF#Vd6w{oPNh4S9Gq;(XG4x!eJo0%iTgbI_uIXhC1P8S@Puy-~F- zxw@6^+vf#@c4mClDqQ|6_rj_xQtKevKqbHc4;}=i6aqUy3r;B}mN-Zg;K?9R5uMDD z&7Qz8ejRl&=#_)qVk{4(5Nb)e z=$BT0IAVHHNw(%<G z>4@p`%J|cr6<@uAO4B4L z;LxA{smFf_KApL)cxnD+iO#+aHK9ztX(ijdGU1IH>yOk}1BPmbuB4t6t3v*VLY?3M z0XG8cghhY@cL_)wX+!QzT)`E!+;rUj_-C6kp%~kL^%b8eFm8kl&` z9c}<6E#?)u4!wZwf9>0E??3`2YX_ diff --git a/docs/gallery/market-stall/index.html b/docs/gallery/market-stall/index.html index b7bcce6..e0189a9 100644 --- a/docs/gallery/market-stall/index.html +++ b/docs/gallery/market-stall/index.html @@ -253,28 +253,42 @@

market-stall

market-stall render

Rendered headless by the example itself — click to zoom.

-
witnesses Recomputed: 4404 tris, three materials with 48 faces per awning stripe slot, UVs in 0..1 with zero AABB overlap, outer AABB 1.392×0.966×1.743 m, LOD ratios in band, convex collider 46 tris, non-empty glTF. --skip-decimate exits 9 on the LOD1 ratio budget.
+
witnesses Recomputed: 4324 tris, three materials with 34 faces per awning stripe slot, UVs in 0..1 with zero AABB overlap, outer AABB 1.389×1.011×1.740 m, grounded zmin, 4 wrap feet, brace-counter overlap 0, awning seat 4 mm, post plumb 0, LOD ratios in band, convex collider 58 tris, non-empty glTF. Hygiene all zero. --skip-decimate exits 9; --stray-vert 15; --lift-z/--short-feet 16; --low-brace 17; --float-awning 18; --rake-posts 19.
blender --background --python showcase/market-stall/market_stall.py --
-

A showcase piece, not an example. Procedural timber stall (posts with feet, slanted rafters, slatted counter and shelf, back-wall planks, eave fascia, striped awning and valance) then the shipped pipeline: unique-cell UVs, Cycles high-to-low normal bake, LOD chain, convex collider, Unity glTF export.

+

A showcase piece, not an example. Procedural timber stall (corner posts with wrap plinths, slatted counter and shelf, back-wall planks, side braces that sit in the post bay, striped awning with a front roller and hanging valance) then the shipped pipeline: unique-cell UVs, Cycles high-to-low normal bake, LOD chain, convex collider, Unity glTF export.

+

The old piece used capped foot cubes coplanar with the post bottoms and counter-leg pads coplanar with the legs, which z-fought at Z=0. Wrap plates stand off the post; counter legs go to Z=0 without a second bottom face.

It asserts budget conformance of the generated result. It does not witness an API contract. "It rendered without error" is not a check.

Composes skills mesh-editing-and-bmesh, bake-high-to-low, depsgraph-and-evaluated-data, engine-export-presets, and snippets bake_normal_high_to_low.py, setup_bake_target_image.py, lod_chain.py / decimate_to_budget.py, convex_hull_collider.py, export_preset_unity.py (helpers copied, not imported as a package).

Budgets

Declared as named constants; every gate recomputes from the mesh, materials, UVs, evaluated LOD, collider, or export file.

-

| Axis | Declared | Measured (4.5.11 / 5.1.2 / 5.2.1) | | --- | --- | --- | | Base triangles | 4280–4550 | 4404 / 4404 / 4404 | | LOD1 ratio | 0.32–0.62 of base | 0.5000 / 0.5000 / 0.5000 | | LOD2 ratio | 0.10–0.35 of base | 0.2198 / 0.2198 / 0.2198 | | Materials | exactly 3 distinct, ≥16 faces per stripe slot | 3 slots, 48 / 48 stripe faces | | UVs | in 0..1, AABB overlap ≤ 1e-5 | in range, overlap 0 | | Outer AABB | (1.392, 0.966, 1.743) m ± 0.01 | (1.3915, 0.9659, 1.7430), zmin 0 | | Collider tris | ≤ 80 | 46 | | Export | written, size > 0 | 316748 / 316748 / 316724 bytes |

-

DECIMATE COLLAPSE triangle counts are not guaranteed identical across series — the gate is a ratio band, not an exact count. This mesh happened to match on 4.5.11 / 5.1.2 / 5.2.1. Bake pixels are stochastic; the gate is has_data plus operator FINISHED, not byte-identity. Construction uses no RNG. glTF byte size differs by a few bytes across series.

-

--skip-decimate skips the LOD DECIMATE stage so LOD1 ratio is 1.0 and exit 9 fires. That is the named budget the falsifier violates.

+

| Axis | Declared | Measured (4.5.11 / 5.1.2 / 5.2.1) | | --- | --- | --- | | Base triangles | 3900–5200 | 4324 / 4324 / 4324 | | LOD1 ratio | 0.32–0.62 of base | 0.5000 / 0.5000 / 0.5000 | | LOD2 ratio | 0.10–0.35 of base | 0.2197 / 0.2197 / 0.2197 | | Materials | exactly 3 distinct; ≥16 faces per stripe slot | 3 slots; 34 / 34 stripe faces | | UVs | in 0..1, AABB overlap ≤ 1e-5 | in range, overlap 0 | | Outer AABB | (1.389, 1.011, 1.740) m ± 0.01 | (1.3892, 1.0107, 1.7402), zmin 0 | | Collider tris | ≤ 80 | 58 | | Export | written, size > 0 | 312752 / 312752 / 312728 bytes |

+

Hygiene

+

Recomputed from the generated mesh, not asserted about the script.

+

| Axis | Declared | Measured (all three) | | --- | --- | --- | | Non-manifold edges | 0 | 0 | | Loose verts / edges | 0 / 0 | 0 / 0 | | Doubles merged at 1e-5 | 0 | 0 | | Zero-area faces | 0 | 0 | | N-gons | 0 | 0 | | Coplanar disjoint face pairs | 0 | 0 | | Grounded: zmin | within 1e-4 of 0 | 0.0000 | | Named supports: 4 wrap feet | each zmin ≤ 1e-3 | 4, foot_z 0.00000 | | Frame plan | 1.36 × 0.92 m ± 0.04 | 1.3600 × 0.9200 |

+

Joint fit and seat

+

| Axis | Declared | Measured (all three) | | --- | --- | --- | | Brace-vs-counter overlap | ≤ 1e-6 m³ | 0.000000 | | Header-post tenon engage | ≥ 0.4 × tenon | 0.0595 | | Awning-on-header seat | −0.002–0.010 m | 0.00378 | | Post plumb (XY drift) | ≤ 0.008 m | 0.00000 |

+

DECIMATE COLLAPSE triangle counts are not identical across series — the gate is a ratio band, not an exact count. Bake pixels are stochastic; the gate is has_data plus operator FINISHED, not byte-identity. Construction uses no RNG. Export byte counts differ by 24 B on 5.2.1 (glTF serializer), not a gated axis.

+

Falsifiers

+

Each violates one named budget. All eight were run on 4.5.11, 5.1.2 and 5.2.1 and returned the same code on each.

+

| Flag | Budget violated | Exit | | --- | --- | --- | | --skip-decimate | LOD1 ratio band | 9 | | --stray-vert | loose vertex count is 0 | 15 | | --lift-z | bounding box zmin is 0 | 16 | | --short-feet | named wrap-foot supports at Z=0 | 16 | | --low-brace | brace-vs-counter overlap | 17 | | --float-awning | awning-on-header seat | 18 | | --rake-posts | post plumb | 19 |

Run

blender --background --python market_stall.py --
 blender --background --python market_stall.py -- --skip-decimate
+blender --background --python market_stall.py -- --stray-vert
+blender --background --python market_stall.py -- --lift-z
+blender --background --python market_stall.py -- --short-feet
+blender --background --python market_stall.py -- --low-brace
+blender --background --python market_stall.py -- --float-awning
+blender --background --python market_stall.py -- --rake-posts
 blender --background --python market_stall.py -- --output stall.png
-

Smoke does not pass --output or --skip-decimate.

+

Smoke passes no flags.

Exit codes

-

File-local. 9 is a valid check code. 10 is reserved for gallery_framing.check_framing on the --output path.

-

| Code | Meaning | | --- | --- | | 0 | Success | | 1 | Uncaught exception (FATAL wrapper) | | 2 | argparse / usage | | 3 | Mesh did not build / no UV layer | | 4 | Base triangle count outside range | | 5 | Material count ≠ 3 distinct slots, or stripe faces missing | | 6 | UVs outside 0..1 | | 7 | UV AABB overlap above tolerance | | 8 | World AABB off declared outer size | | 9 | LOD ratio band (--skip-decimate lands here) | | 10 | Framing gate (render path only) | | 11 | Collider triangle count above ceiling | | 12 | Bake did not finish or image has no data | | 13 | Export file missing or empty | | 14 | --output produced no file |

+

File-local. 9 is a valid check code. 10 is reserved for gallery_framing.check_framing on the --output path. 1519 are the hygiene and joint-fit family.

+

| Code | Meaning | | --- | --- | | 0 | Success | | 1 | Uncaught exception (FATAL wrapper) | | 2 | argparse / usage | | 3 | Mesh did not build / no UV layer | | 4 | Base triangle count outside range | | 5 | Material count ≠ 3 distinct slots, or stripe faces missing | | 6 | UVs outside 0..1 | | 7 | UV AABB overlap above tolerance | | 8 | World AABB off declared outer size | | 9 | LOD ratio band (--skip-decimate lands here) | | 10 | Framing gate (render path only) | | 11 | Collider triangle count above ceiling | | 12 | Bake did not finish or image has no data | | 13 | Export file missing or empty | | 14 | --output produced no file | | 15 | Mesh hygiene: loose, non-manifold, zero-area, doubles, n-gons, z-fight | | 16 | Not grounded: bounding box zmin off 0, or a named wrap foot floats | | 17 | Joint fit: brace occupying the counter volume | | 18 | Seat: awning-on-header gap | | 19 | Post plumb or frame plan off the stated real-world size |

Source

@@ -289,12 +303,15 @@

Source

high-to-low normal bake, LOD chain, convex collider, Unity glTF export. Budgets are declared below and recomputed from the generated result. -They are not API-contract witnesses. ``--skip-decimate`` skips the LOD -DECIMATE stage so the LOD-ratio budget fails. +They are not API-contract witnesses. Each falsifier violates one named +budget: ``--skip-decimate`` the LOD-ratio band, ``--stray-vert`` mesh +hygiene, ``--lift-z`` grounded zmin, ``--short-feet`` named post-foot +supports, ``--low-brace`` brace-vs-counter joint fit, ``--float-awning`` +awning-on-header seat, ``--rake-posts`` post plumb. -No RNG. Construction is closed-form. DECIMATE COLLAPSE triangle counts -are not byte-identical across Blender versions — the LOD gate is a -ratio band, not an exact count. +No RNG. Slat jitter is closed-form ``sin(i)``. DECIMATE COLLAPSE +triangle counts are not byte-identical across Blender versions — the LOD +gate is a ratio band, not an exact count. blender --background --python market_stall.py -- blender --background --python market_stall.py -- --skip-decimate @@ -328,16 +345,20 @@

Source

BACK_H = 1.72 N_STRIPES = 8 AWNING_T = 0.018 +OVERHANG_F = 0.040 +OVERHANG_B = 0.030 COUNTER_Z = 0.82 COUNTER_D = 0.34 COUNTER_T = 0.070 BRACE = 0.036 +FOOT_H = 0.036 +TENON = POST * 0.35 BBOX_TOL = 0.01 # Fitted to the generated AABB after locking geometry. Recomputed from bound_box. -OUTER_SIZE = (1.392, 0.966, 1.743) +OUTER_SIZE = (1.389, 1.011, 1.740) -BASE_TRIS_MIN = 4280 -BASE_TRIS_MAX = 4550 +BASE_TRIS_MIN = 3900 +BASE_TRIS_MAX = 5200 LOD1_RATIO_MIN = 0.32 LOD1_RATIO_MAX = 0.62 LOD2_RATIO_MIN = 0.10 @@ -350,6 +371,24 @@

Source

COLLIDER_TRIS_MAX = 80 BAKE_RES = 256 CAGE_EXTRUSION = 0.06 +ZMIN_EPS = 1e-4 +DOUBLES_EPS = 1e-5 +AREA_EPS = 1e-10 +ZFIGHT_EPS = 1e-4 +ZFIGHT_COS = 0.998 +FOOT_ZMIN_MAX = 0.001 +FOOT_COUNT = 4 +AWNING_SEAT_MAX = 0.010 +AWNING_SEAT_MIN = -0.002 +BRACE_COUNTER_OVERLAP_MAX = 1e-6 +POST_PLUMB_MAX = 0.008 +FRAME_XY_TOL = 0.04 +LIFT_Z = 0.05 +# Small enough that the AABB still sits in BBOX_TOL; large enough that +# the front-eave seat drops below AWNING_SEAT_MIN. +FLOAT_AWNING = 0.008 +RAKE = math.radians(2.0) +SHORT_FOOT_Z = 0.048 WOOD_IDX = 0 STRIPE_A_IDX = 1 @@ -396,25 +435,75 @@

Source

return verts -def add_cone(bm, loc, radius1, radius2, depth, segments, mat_idx, euler=(0.0, 0.0, 0.0)): - geo = bmesh.ops.create_cone( +def add_oriented_box(bm, a, b, scale_xy, mat_idx): + a = Vector(a) + b = Vector(b) + delta = b - a + length = delta.length + if length < 1e-8: + return [] + quat = Vector((0.0, 0.0, 1.0)).rotation_difference(delta.normalized()) + eul = quat.to_euler("XYZ") + return add_box( bm, - cap_ends=True, - cap_tris=False, - segments=segments, - radius1=radius1, - radius2=radius2, - depth=depth, + ((a + b) * 0.5), + (scale_xy[0], scale_xy[1], length), + mat_idx, + euler=(eul.x, eul.y, eul.z), ) - verts = geo["verts"] + + +def add_wrap(bm, px, py, z, height, host, t, mat_idx): + """Four plates around a square post. Y-facing plates cover the corners.""" + half = host / 2.0 + off = half + t / 2.0 + 0.002 + add_box(bm, (px + off, py, z), (t, host, height), mat_idx) + add_box(bm, (px - off, py, z), (t, host, height), mat_idx) + add_box(bm, (px, py + off, z), (host + 2.0 * t, t, height), mat_idx) + add_box(bm, (px, py - off, z), (host + 2.0 * t, t, height), mat_idx) + return [] + + +def add_striped_slab(bm, loc, scale, euler, n_stripes, even_idx, odd_idx): + """One slab split into n stripes that share vertices — no daylight gaps.""" rot = Euler(euler).to_matrix() origin = Vector(loc) - for v in verts: - v.co = rot @ v.co + origin - faces = {f for v in verts for f in v.link_faces} - for f in faces: - f.material_index = mat_idx - return verts + sx, sy, sz = scale + stations = [] + for i in range(n_stripes + 1): + x = -0.5 * sx + sx * i / n_stripes + row = [] + for y, z in ( + (-0.5 * sy, -0.5 * sz), + (-0.5 * sy, 0.5 * sz), + (0.5 * sy, -0.5 * sz), + (0.5 * sy, 0.5 * sz), + ): + row.append(bm.verts.new(rot @ Vector((x, y, z)) + origin)) + stations.append(row) + kept = [] + for i in range(n_stripes): + a = stations[i] + b = stations[i + 1] + idx = even_idx if (i % 2 == 0) else odd_idx + quads = ( + (a[0], b[0], b[2], a[2]), + (a[1], a[3], b[3], b[1]), + (a[0], a[1], b[1], b[0]), + (a[2], b[2], b[3], a[3]), + ) + for q in quads: + face = bm.faces.new(q) + face.material_index = idx + kept.append(face) + first = stations[0] + last = stations[-1] + cap_a = bm.faces.new((first[0], first[2], first[3], first[1])) + cap_a.material_index = even_idx + cap_b = bm.faces.new((last[0], last[1], last[3], last[2])) + cap_b.material_index = even_idx if ((n_stripes - 1) % 2 == 0) else odd_idx + kept.extend((cap_a, cap_b)) + return kept def pack_uvs(bm, margin=0.08): @@ -460,205 +549,228 @@

Source

) -def build_stall_mesh(name, bevel_offset, bevel_segments): +def build_stall_mesh( + name, + bevel_offset, + bevel_segments, + low_brace=False, + float_awning=False, + rake_posts=False, + short_feet=False, +): bm = bmesh.new() wood_verts = [] - stripe_a = set() - stripe_b = set() + stripe_faces = [] try: hx = WIDTH / 2.0 hy = DEPTH / 2.0 - inset = POST / 2.0 + px = hx - POST / 2.0 + py_f = -(hy - POST / 2.0) + py_b = hy - POST / 2.0 + post_h_f = FRONT_H - POST + post_h_b = BACK_H - POST + rake = (RAKE, 0.0, 0.0) if rake_posts else (0.0, 0.0, 0.0) posts = ( - (-hx + inset, -hy + inset, FRONT_H / 2.0, FRONT_H), - (hx - inset, -hy + inset, FRONT_H / 2.0, FRONT_H), - (-hx + inset, hy - inset, BACK_H / 2.0, BACK_H), - (hx - inset, hy - inset, BACK_H / 2.0, BACK_H), + (-px, py_f, post_h_f), + (px, py_f, post_h_f), + (-px, py_b, post_h_b), + (px, py_b, post_h_b), ) - for x, y, z, h in posts: - wood_verts.extend(add_box(bm, (x, y, z), (POST, POST, h), WOOD_IDX)) + foot_z = SHORT_FOOT_Z if short_feet else FOOT_H / 2.0 + foot_h = 0.020 if short_feet else FOOT_H + wrap_t = POST * 0.18 + for x, y, h in posts: wood_verts.extend( - add_box(bm, (x, y, 0.018), (POST * 1.45, POST * 1.45, 0.036), WOOD_IDX) + add_box(bm, (x, y, h / 2.0), (POST, POST, h), WOOD_IDX, euler=rake) ) + add_wrap(bm, x, y, foot_z, foot_h, POST, wrap_t, WOOD_IDX) - # Front and back header beams (along X). + header_len = WIDTH - POST + 2.0 * TENON wood_verts.extend( add_box( bm, - (0.0, -hy + inset, FRONT_H - POST / 2.0), - (WIDTH - POST, POST, POST), + (0.0, py_f, FRONT_H - POST / 2.0), + (header_len, POST * 0.78, POST), WOOD_IDX, ) ) wood_verts.extend( add_box( bm, - (0.0, hy - inset, BACK_H - POST / 2.0), - (WIDTH - POST, POST, POST), + (0.0, py_b, BACK_H - POST / 2.0), + (header_len, POST * 0.78, POST), WOOD_IDX, ) ) - # Side plates, slanted to match the awning. - dz = BACK_H - FRONT_H - slant_len = math.hypot(DEPTH - POST, dz) - pitch = math.atan2(dz, DEPTH - POST) - for sx in (-hx + inset, hx - inset): + # Side plates sit on the header centres, spanning post to post. + for sx in (-px, px): wood_verts.extend( - add_box( + add_oriented_box( bm, - (sx, 0.0, (FRONT_H + BACK_H) / 2.0 - POST / 2.0), - (POST * 0.85, slant_len, POST * 0.85), + (sx, py_f, FRONT_H - POST / 2.0), + (sx, py_b, BACK_H - POST / 2.0), + (POST * 0.80, POST * 0.80), WOOD_IDX, - euler=(pitch, 0.0, 0.0), ) ) - # Inner rafters under the awning — the empty underside was a slab of air. for i in range(3): rx = -hx + POST * 2.0 + (i + 1) * (WIDTH - 4.0 * POST) / 4.0 wood_verts.extend( - add_box( + add_oriented_box( bm, - (rx, 0.0, (FRONT_H + BACK_H) / 2.0 - POST * 0.42), - (POST * 0.42, slant_len * 0.96, POST * 0.42), + (rx, py_f, FRONT_H - POST / 2.0), + (rx, py_b, BACK_H - POST / 2.0), + (POST * 0.42, POST * 0.42), WOOD_IDX, - euler=(pitch, 0.0, 0.0), ) ) - # Mid rail on the back wall and two diagonal braces at the sides. wood_verts.extend( add_box( bm, - (0.0, hy - inset, BACK_H * 0.55), - (WIDTH - 2.0 * POST, POST * 0.7, POST * 0.7), + (0.0, py_b, BACK_H * 0.48), + (WIDTH - POST, POST * 0.65, POST * 0.65), WOOD_IDX, ) ) - for sx in (-hx + inset, hx - inset): - p0 = Vector((sx, -hy + inset, 0.30)) - p1 = Vector((sx, hy - inset, BACK_H * 0.58)) - mid = (p0 + p1) * 0.5 - delta = p1 - p0 - brace_len = delta.length - brace_pitch = math.atan2(delta.z, delta.y) - wood_verts.extend( - add_box( - bm, - (mid.x, mid.y, mid.z), - (BRACE, brace_len, BRACE), - WOOD_IDX, - euler=(brace_pitch, 0.0, 0.0), - ) + wood_verts.extend( + add_box( + bm, + (0.0, py_f, 0.16), + (WIDTH - 2.0 * POST, POST * 0.70, POST * 0.55), + WOOD_IDX, ) + ) - # Slatted counter — crate-lid language, not one fat slab. - cy = -hy + COUNTER_D / 2.0 + 0.04 + for sign in (-1.0, 1.0): + sx = sign * px + if low_brace: + a = Vector((sx, py_f, 0.28)) + b = Vector((sx, py_b, BACK_H * 0.58)) + else: + # On the post centreline so the brace tenons the post instead + # of reading as a wing in the front elevation. + a = Vector((sx, 0.04, COUNTER_Z + 0.14)) + b = Vector((sx, py_b, BACK_H * 0.56)) + wood_verts.extend(add_oriented_box(bm, a, b, (BRACE, BRACE), WOOD_IDX)) + + inner_w = WIDTH - 2.0 * POST - 0.024 + y0 = py_f + POST / 2.0 + 0.016 + y1 = y0 + COUNTER_D n_slats = 6 - slat_gap = 0.010 - slat_d = (COUNTER_D - 0.02 - (n_slats - 1) * slat_gap) / n_slats - y0 = cy - COUNTER_D / 2.0 + 0.01 + slat_d / 2.0 + slat_gap = 0.008 + slat_d = (COUNTER_D - (n_slats - 1) * slat_gap) / n_slats for i in range(n_slats): + jw = 0.018 * math.sin(i * 2.31 + 0.5) + jt = 0.006 * math.sin(i * 1.87) + y = y0 + slat_d / 2.0 + i * (slat_d + slat_gap) wood_verts.extend( add_box( bm, - (0.0, y0 + i * (slat_d + slat_gap), COUNTER_Z), - (WIDTH - 0.06, slat_d, COUNTER_T * 0.72), + (0.0, y, COUNTER_Z), + (inner_w + jw, slat_d * 0.94, COUNTER_T * 0.62 + jt), WOOD_IDX, ) ) wood_verts.extend( add_box( bm, - (0.0, cy - COUNTER_D / 2.0 + 0.018, COUNTER_Z - 0.09), - (WIDTH - 0.08, 0.036, 0.16), + (0.0, y0 + 0.016, COUNTER_Z - 0.08), + (inner_w * 0.96, 0.032, 0.14), WOOD_IDX, ) ) - under_shelf_z = 0.38 + shelf_z = 0.38 n_shelf = 4 shelf_d = COUNTER_D * 0.78 - shelf_gap = 0.010 + shelf_gap = 0.008 shelf_slat = (shelf_d - (n_shelf - 1) * shelf_gap) / n_shelf - sy0 = cy - shelf_d / 2.0 + shelf_slat / 2.0 + sy0 = y0 + 0.04 + shelf_slat / 2.0 for i in range(n_shelf): + jw = 0.014 * math.sin(i * 1.63 + 0.9) wood_verts.extend( add_box( bm, - (0.0, sy0 + i * (shelf_slat + shelf_gap), under_shelf_z), - (WIDTH - 0.16, shelf_slat, 0.024), + (0.0, sy0 + i * (shelf_slat + shelf_gap), shelf_z), + (inner_w - 0.08 + jw, shelf_slat * 0.94, 0.024), WOOD_IDX, ) ) - for lx in (-hx + 0.16, hx - 0.16): - for ly in (cy - COUNTER_D * 0.32, cy + COUNTER_D * 0.32): + leg_h = COUNTER_Z - COUNTER_T * 0.32 + for lx in (-inner_w / 2.0 + 0.04, inner_w / 2.0 - 0.04): + for ly in (y0 + 0.05, y1 - 0.05): wood_verts.extend( - add_box( - bm, - (lx, ly, COUNTER_Z / 2.0), - (0.045, 0.045, COUNTER_Z), - WOOD_IDX, - ) + add_box(bm, (lx, ly, leg_h / 2.0), (0.042, 0.042, leg_h), WOOD_IDX) ) - # Back wall: horizontal planks with gaps, not a felt slab. + plank_y = py_b - POST / 2.0 - 0.014 + plank_len = WIDTH - POST for i in range(5): - z = 0.36 + i * 0.155 + z = 0.30 + i * 0.148 + jw = 0.004 * math.sin(i * 2.11) wood_verts.extend( add_box( bm, - (0.0, hy - inset - 0.014, z), - (WIDTH - 2.35 * POST, 0.028, 0.122), + (0.0, plank_y, z), + (plank_len + jw, 0.028, 0.118), WOOD_IDX, ) ) - # Front eave fascia under the valance. + fascia_y = -hy - OVERHANG_F wood_verts.extend( add_box( bm, - (0.0, -hy - 0.008, FRONT_H - 0.028), - (WIDTH - POST * 0.4, 0.032, 0.048), + (0.0, fascia_y, FRONT_H - 0.022), + (WIDTH - POST * 0.2, 0.032, 0.044), WOOD_IDX, ) ) - - stripe_w = WIDTH / N_STRIPES - awning_len = math.hypot(DEPTH, dz) - awning_pitch = math.atan2(dz, DEPTH) - cz = (FRONT_H + BACK_H) / 2.0 + AWNING_T * 0.35 - for i in range(N_STRIPES): - cx = -hx + (i + 0.5) * stripe_w - idx = STRIPE_A_IDX if (i % 2 == 0) else STRIPE_B_IDX - before = set(bm.faces) + wood_verts.extend( add_box( bm, - (cx, 0.0, cz), - (stripe_w * 0.98, awning_len + 0.04, AWNING_T), - idx, - euler=(awning_pitch, 0.0, 0.0), + (0.0, fascia_y - 0.006, FRONT_H - 0.004), + (WIDTH - POST * 0.05, 0.022, 0.022), + WOOD_IDX, ) - new_faces = set(bm.faces) - before - if idx == STRIPE_A_IDX: - stripe_a.update(new_faces) - else: - stripe_b.update(new_faces) + ) - # Front valance hanging from the eave. - for i in range(N_STRIPES): - cx = -hx + (i + 0.5) * stripe_w - idx = STRIPE_B_IDX if (i % 2 == 0) else STRIPE_A_IDX - before = set(bm.faces) - add_box( + # Centreline sits above the header; thickness then hangs along the + # roof normal so the front underside clears the fascia top. + seat = 0.012 + front = Vector((0.0, -hy - OVERHANG_F, FRONT_H + seat)) + back = Vector((0.0, hy + OVERHANG_B, BACK_H + seat)) + dy = back.y - front.y + dz = back.z - front.z + awning_len = math.hypot(dy, dz) + pitch = math.atan2(dz, dy) + nrm = Vector((0.0, -math.sin(pitch), math.cos(pitch))) + mid = (front + back) * 0.5 + if float_awning: + mid = Vector((mid.x, mid.y, mid.z + FLOAT_AWNING)) + stripe_faces.extend( + add_striped_slab( bm, - (cx, -hy - 0.01, FRONT_H - 0.08), - (stripe_w * 0.96, 0.016, 0.14), - idx, + mid, + (WIDTH, awning_len, AWNING_T), + (pitch, 0.0, 0.0), + N_STRIPES, + STRIPE_A_IDX, + STRIPE_B_IDX, ) - new_faces = set(bm.faces) - before - if idx == STRIPE_A_IDX: - stripe_a.update(new_faces) - else: - stripe_b.update(new_faces) + ) + val_z = FRONT_H - 0.09 + (FLOAT_AWNING if float_awning else 0.0) + stripe_faces.extend( + add_striped_slab( + bm, + (0.0, fascia_y - 0.002, val_z), + (WIDTH, 0.014, 0.15), + (0.0, 0.0, 0.0), + N_STRIPES, + STRIPE_B_IDX, + STRIPE_A_IDX, + ) + ) if bevel_offset > 0.0: edges = list({e for v in wood_verts for e in v.link_edges}) @@ -673,9 +785,10 @@

Source

) zmin = min(v.co.z for v in bm.verts) - if zmin != 0.0: - for v in bm.verts: - v.co.z -= zmin + for v in bm.verts: + v.co.z -= zmin + if v.co.z < 0.002: + v.co.z = 0.0 pack_uvs(bm) bmesh.ops.recalc_face_normals(bm, faces=list(bm.faces)) @@ -686,12 +799,9 @@

Source

if edge.is_manifold and len(edge.link_faces) == 2: if edge.calc_face_angle() < math.radians(25.0): edge.smooth = True - for f in stripe_a: - if f.is_valid: - f.material_index = STRIPE_A_IDX - for f in stripe_b: - if f.is_valid: - f.material_index = STRIPE_B_IDX + for face in stripe_faces: + if face.is_valid: + pass me = bpy.data.meshes.new(name) bm.to_mesh(me) me.update() @@ -757,6 +867,271 @@

Source

return min(us), min(vs), max(us), max(vs), overlap, len(aabbs) +def face_area(me, poly): + vs = [me.vertices[i].co for i in poly.vertices] + if len(vs) < 3: + return 0.0 + v0 = vs[0] + area = 0.0 + for i in range(1, len(vs) - 1): + area += (vs[i] - v0).cross(vs[i + 1] - v0).length * 0.5 + return area + + +def hygiene_audit(me): + # Combinatorics match examples/mesh-hygiene-audit.audit (copied, not imported). + nv, ne, nf = len(me.vertices), len(me.edges), len(me.polygons) + ngons = sum(1 for p in me.polygons if len(p.vertices) > 4) + zero_area = sum(1 for p in me.polygons if face_area(me, p) <= AREA_EPS) + bm = bmesh.new() + try: + bm.from_mesh(me) + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + loose_v = sum(1 for v in bm.verts if len(v.link_edges) == 0) + loose_e = sum(1 for e in bm.edges if len(e.link_faces) == 0) + nonman = sum(1 for e in bm.edges if not e.is_manifold) + ret = bmesh.ops.find_doubles(bm, verts=list(bm.verts), dist=DOUBLES_EPS) + doubles = len(ret.get("targetmap") or {}) + finally: + bm.free() + return { + "nv": nv, "ne": ne, "nf": nf, "ngons": ngons, + "loose_v": loose_v, "loose_e": loose_e, "nonman": nonman, + "zero_area": zero_area, "doubles": doubles, "euler": nv - ne + nf, + } + + +def zfight_pairs(me): + data = [ + (p.center.copy(), p.normal.copy(), frozenset(p.vertices)) + for p in me.polygons + ] + eps2 = ZFIGHT_EPS * ZFIGHT_EPS + count = 0 + for i in range(len(data)): + ci, ni, vi = data[i] + for j in range(i + 1, len(data)): + cj, nj, vj = data[j] + if (cj - ci).length_squared > eps2: + continue + if abs(ni.dot(nj)) <= ZFIGHT_COS: + continue + if vi & vj: + continue + count += 1 + return count + + +def shells(me): + neighbors = [[] for _ in range(len(me.vertices))] + for edge in me.edges: + a, b = edge.vertices + neighbors[a].append(b) + neighbors[b].append(a) + seen = [False] * len(me.vertices) + groups = [] + for start in range(len(me.vertices)): + if seen[start]: + continue + seen[start] = True + stack = [start] + group = [] + while stack: + current = stack.pop() + group.append(current) + for nxt in neighbors[current]: + if not seen[nxt]: + seen[nxt] = True + stack.append(nxt) + groups.append(group) + return groups + + +def shell_aabb(me, group): + pts = [me.vertices[i].co for i in group] + return ( + min(p.x for p in pts), min(p.y for p in pts), min(p.z for p in pts), + max(p.x for p in pts), max(p.y for p in pts), max(p.z for p in pts), + ) + + +def aabb_overlap(a, b): + x = min(a[3], b[3]) - max(a[0], b[0]) + y = min(a[4], b[4]) - max(a[1], b[1]) + z = min(a[5], b[5]) - max(a[2], b[2]) + if x <= 0.0 or y <= 0.0 or z <= 0.0: + return 0.0 + return x * y * z + + +def mat_of(me, group): + member = set(group) + for poly in me.polygons: + if all(i in member for i in poly.vertices): + return poly.material_index + return None + + +def support_audit(me): + """Post feet: wrap plates clustered at the four frame corners.""" + plates = [] + for group in shells(me): + if mat_of(me, group) != WOOD_IDX: + continue + a = shell_aabb(me, group) + dz = a[5] - a[2] + dx = a[3] - a[0] + dy = a[4] - a[1] + cz = 0.5 * (a[2] + a[5]) + if cz > 0.10 or dz > 0.10: + continue + if max(dx, dy) < POST * 0.8: + continue + plates.append(a) + corners = {} + for a in plates: + cx = 0.5 * (a[0] + a[3]) + cy = 0.5 * (a[1] + a[4]) + key = (1 if cx > 0.0 else -1, 1 if cy > 0.0 else -1) + corners.setdefault(key, []).append(a) + zmin = min((a[2] for a in plates), default=99.0) + return {"feet": len(corners), "foot_z": zmin} + + +def joint_audit(me): + """Braces must not occupy the counter volume. Headers bite the posts.""" + hx = WIDTH / 2.0 + groups = shells(me) + boxes = [(g, shell_aabb(me, g), mat_of(me, g)) for g in groups] + posts = [] + headers = [] + braces = [] + slats = [] + for _g, a, mat in boxes: + if mat != WOOD_IDX: + continue + dx, dy, dz = a[3] - a[0], a[4] - a[1], a[5] - a[2] + cx = 0.5 * (a[0] + a[3]) + cy = 0.5 * (a[1] + a[4]) + cz = 0.5 * (a[2] + a[5]) + if dz > FRONT_H * 0.55 and dx < POST * 2.4 and dy < POST * 2.4: + posts.append(a) + continue + if dx > WIDTH * 0.6 and dz < POST * 2.2 and dy < POST * 2.2 and cz > FRONT_H * 0.7: + headers.append(a) + continue + if abs(cx) > hx - POST * 1.8 and dz > 0.35 and dy > 0.25 and dx < 0.20: + braces.append(a) + continue + if abs(cz - COUNTER_Z) < 0.08 and dx > WIDTH * 0.4: + slats.append(a) + overlap = 0.0 + for brace in braces: + for slat in slats: + overlap = max(overlap, aabb_overlap(brace, slat)) + pairs = [ + min(h[3] - p[0], p[3] - h[0]) + for h in headers + for p in posts + if abs(0.5 * (h[1] + h[4]) - 0.5 * (p[1] + p[4])) < POST * 2.0 + ] + engage = min(pairs) if pairs else 1.0 + return { + "posts": len(posts), + "headers": len(headers), + "braces": len(braces), + "slats": len(slats), + "overlap": overlap, + "engage": engage, + } + + +def awning_seat(me): + """Gap from front-header top to awning underside, at the front eave.""" + hy = DEPTH / 2.0 + wood_z = [] + stripe_z = [] + for poly in me.polygons: + zs = [me.vertices[i].co.z for i in poly.vertices] + ys = [me.vertices[i].co.y for i in poly.vertices] + cy = sum(ys) / len(ys) + if cy > -hy + 0.15: + continue + zmax = max(zs) + zmin = min(zs) + if poly.material_index == WOOD_IDX and zmax > FRONT_H - 0.12: + if max(ys) - min(ys) > 0.028: + wood_z.append(zmax) + if poly.material_index in (STRIPE_A_IDX, STRIPE_B_IDX) and zmax > FRONT_H: + stripe_z.append(zmin) + if not wood_z or not stripe_z: + return 99.0 + return min(stripe_z) - max(wood_z) + + +def plumb_audit(me): + """XY centroid of each post's bottom slab vs top slab.""" + drifts = [] + for group in shells(me): + if mat_of(me, group) != WOOD_IDX: + continue + a = shell_aabb(me, group) + dz = a[5] - a[2] + dx = a[3] - a[0] + dy = a[4] - a[1] + if dz < FRONT_H * 0.55 or dx > POST * 2.4 or dy > POST * 2.4: + continue + pts = [me.vertices[i].co for i in group] + zcut_lo = a[2] + 0.08 * dz + zcut_hi = a[5] - 0.08 * dz + lo = [p for p in pts if p.z <= zcut_lo] + hi = [p for p in pts if p.z >= zcut_hi] + if len(lo) < 3 or len(hi) < 3: + continue + c_lo = Vector((sum(p.x for p in lo) / len(lo), sum(p.y for p in lo) / len(lo))) + c_hi = Vector((sum(p.x for p in hi) / len(hi), sum(p.y for p in hi) / len(hi))) + drifts.append((c_hi - c_lo).length) + return max(drifts) if drifts else 99.0 + + +def frame_size(me): + """Post-foot envelope vs declared stall plan, not the awning AABB.""" + hx = WIDTH / 2.0 + hy = DEPTH / 2.0 + xs, ys, zs = [], [], [] + for group in shells(me): + if mat_of(me, group) != WOOD_IDX: + continue + a = shell_aabb(me, group) + dz = a[5] - a[2] + dx = a[3] - a[0] + dy = a[4] - a[1] + cx = 0.5 * (a[0] + a[3]) + cy = 0.5 * (a[1] + a[4]) + if dz > FRONT_H * 0.55 and dx < POST * 2.4 and dy < POST * 2.4: + xs.extend((a[0], a[3])) + ys.extend((a[1], a[4])) + zs.append(a[5]) + continue + if dz < 0.08 and abs(cx) > hx - POST * 1.6 and abs(cy) > hy - POST * 1.6: + zs.append(a[2]) + if not xs: + return 0.0, 0.0, 0.0 + return max(xs) - min(xs), max(ys) - min(ys), max(zs) - min(zs) + + +def add_stray_vert(me): + bm = bmesh.new() + try: + bm.from_mesh(me) + bm.verts.new((0.0, 0.0, COUNTER_Z)) + bm.to_mesh(me) + me.update() + finally: + bm.free() + + def make_lod(obj, name, ratio, skip_decimate): mesh = obj.data.copy() lod = bpy.data.objects.new(name, mesh) @@ -849,16 +1224,47 @@

Source

) -def check(skip_decimate): +def check( + skip_decimate, + lift_z=False, + stray_vert=False, + short_feet=False, + low_brace=False, + float_awning=False, + rake_posts=False, +): bpy.ops.wm.read_factory_settings(use_empty=True) - low = build_stall_mesh("StallLow", bevel_offset=0.006, bevel_segments=2) - high = build_stall_mesh("StallHigh", bevel_offset=0.006, bevel_segments=4) + low = build_stall_mesh( + "StallLow", + bevel_offset=0.006, + bevel_segments=2, + low_brace=low_brace, + float_awning=float_awning, + rake_posts=rake_posts, + short_feet=short_feet, + ) + high = build_stall_mesh( + "StallHigh", + bevel_offset=0.006, + bevel_segments=4, + low_brace=low_brace, + float_awning=float_awning, + rake_posts=rake_posts, + short_feet=short_feet, + ) wood = principled("StallWood", (0.42, 0.24, 0.10, 1.0), 0.0, 0.55) stripe_a = principled("StallStripeA", (0.72, 0.12, 0.10, 1.0), 0.0, 0.62) stripe_b = principled("StallStripeB", (0.86, 0.80, 0.62, 1.0), 0.0, 0.58) assign_slots(low, wood, stripe_a, stripe_b) assign_slots(high, wood, stripe_a, stripe_b) + if stray_vert: + add_stray_vert(low.data) + if lift_z: + for v in low.data.vertices: + v.co.z += LIFT_Z + low.data.update() + if low.data is None or len(low.data.polygons) < 6: return fail("stall mesh did not build", 3), None, None, None, None, None @@ -875,6 +1281,13 @@

Source

size_x = bb[3] - bb[0] size_y = bb[4] - bb[1] size_z = bb[5] - bb[2] + hyg = hygiene_audit(low.data) + zf = zfight_pairs(low.data) + sup = support_audit(low.data) + jnt = joint_audit(low.data) + seat = awning_seat(low.data) + plumb = plumb_audit(low.data) + fx, fy, fz = frame_size(low.data) img, tex = setup_bake_image(low, wood) if img is None: @@ -903,9 +1316,7 @@

Source

export_unity(export_path, [low, collider]) export_size = os.path.getsize(export_path) if os.path.isfile(export_path) else 0 - print( - f"blender={tuple(bpy.app.version)} skip_decimate={skip_decimate}" - ) + print(f"blender={tuple(bpy.app.version)} skip_decimate={skip_decimate}") print( f"measured base_tris={base_tris} lod1_tris={lod1_tris} " f"lod2_tris={lod2_tris} r1={r1:.4f} r2={r2:.4f}" @@ -922,6 +1333,16 @@

Source

f"measured collider_tris={col_tris} bake={bake_result} " f"bake_has_data={img.has_data} export_bytes={export_size}" ) + print( + f"measured hygiene loose_v={hyg['loose_v']} loose_e={hyg['loose_e']} " + f"nonman={hyg['nonman']} zero_area={hyg['zero_area']} " + f"doubles={hyg['doubles']} ngons={hyg['ngons']} zfight={zf}" + ) + print( + f"measured feet={sup['feet']} foot_z={sup['foot_z']:.5f} " + f"brace_overlap={jnt['overlap']:.6f} engage={jnt['engage']:.4f} " + f"seat={seat:.5f} plumb={plumb:.5f} frame=({fx:.4f},{fy:.4f},{fz:.4f})" + ) if not (BASE_TRIS_MIN <= base_tris <= BASE_TRIS_MAX): return fail( @@ -986,6 +1407,40 @@

Source

), None, None, None, None, None if export_size <= 0: return fail("export file missing or empty", 13), None, None, None, None, None + if ( + hyg["loose_v"] or hyg["loose_e"] or hyg["nonman"] or hyg["zero_area"] + or hyg["doubles"] or hyg["ngons"] or zf + ): + return fail( + f"hygiene loose_v={hyg['loose_v']} loose_e={hyg['loose_e']} " + f"nonman={hyg['nonman']} zero_area={hyg['zero_area']} " + f"doubles={hyg['doubles']} ngons={hyg['ngons']} zfight={zf}", + 15, + ), None, None, None, None, None + if bb[2] > ZMIN_EPS or sup["feet"] != FOOT_COUNT or sup["foot_z"] > FOOT_ZMIN_MAX: + return fail( + f"grounded zmin={bb[2]:.5f} feet={sup['feet']} " + f"foot_z={sup['foot_z']:.5f}", + 16, + ), None, None, None, None, None + if jnt["overlap"] > BRACE_COUNTER_OVERLAP_MAX or jnt["engage"] < TENON * 0.4: + return fail( + f"joint overlap={jnt['overlap']:.6f} engage={jnt['engage']:.4f} " + f"posts={jnt['posts']} braces={jnt['braces']} slats={jnt['slats']}", + 17, + ), None, None, None, None, None + if seat < AWNING_SEAT_MIN or seat > AWNING_SEAT_MAX: + return fail(f"awning seat gap {seat:.5f} > {AWNING_SEAT_MAX}", 18), None, None, None, None, None + if ( + plumb > POST_PLUMB_MAX + or abs(fx - WIDTH) > FRAME_XY_TOL + or abs(fy - DEPTH) > FRAME_XY_TOL + ): + return fail( + f"plumb={plumb:.5f} frame=({fx:.4f},{fy:.4f},{fz:.4f}) " + f"off {WIDTH}x{DEPTH}", + 19, + ), None, None, None, None, None return 0, low, high, wood, tex, collider @@ -1050,13 +1505,15 @@

Source

light("Fill", (5.0, -3.6, 2.6), 48.0, 8.0, (0.72, 0.82, 1.0), (62, 0, 50)) light("Wedge", (2.4, 4.2, 4.1), 640.0, 5.5, (1.0, 0.70, 0.40), (-70, 0, 198)) + bb = world_bbox(low) + span = max(bb[3] - bb[0], bb[4] - bb[1], bb[5] - bb[2], 0.2) cam_data = bpy.data.cameras.new("Cam") cam_data.lens = 50.0 cam = bpy.data.objects.new("Cam", cam_data) - cam.location = (3.11, -5.10, 1.60) + cam.location = (span * 2.17, -span * 2.95, span * 1.12) scene.collection.objects.link(cam) aim = bpy.data.objects.new("Aim", None) - aim.location = (0.0, -0.10, 0.92) + aim.location = (0.0, -0.04 * span, 0.52 * (bb[2] + bb[5])) scene.collection.objects.link(aim) con = cam.constraints.new("TRACK_TO") con.target = aim @@ -1099,14 +1556,24 @@

Source

p = argparse.ArgumentParser() p.add_argument("--output", default=None) p.add_argument("--engine", default="eevee", choices=("eevee", "cycles")) - p.add_argument( - "--skip-decimate", - action="store_true", - help="falsification: skip the LOD DECIMATE stage", - ) + p.add_argument("--skip-decimate", action="store_true") + p.add_argument("--stray-vert", action="store_true") + p.add_argument("--lift-z", action="store_true") + p.add_argument("--short-feet", action="store_true") + p.add_argument("--low-brace", action="store_true") + p.add_argument("--float-awning", action="store_true") + p.add_argument("--rake-posts", action="store_true") args = p.parse_args(argv) - code, low, _high, wood, tex, _col = check(args.skip_decimate) + code, low, _high, wood, tex, _col = check( + args.skip_decimate, + lift_z=args.lift_z, + stray_vert=args.stray_vert, + short_feet=args.short_feet, + low_brace=args.low_brace, + float_awning=args.float_awning, + rake_posts=args.rake_posts, + ) if code: return code if args.output: diff --git a/showcase/market-stall/README.md b/showcase/market-stall/README.md index 7288aec..4e26e54 100644 --- a/showcase/market-stall/README.md +++ b/showcase/market-stall/README.md @@ -1,10 +1,15 @@ # Market stall -A showcase piece, not an example. Procedural timber stall (posts with feet, -slanted rafters, slatted counter and shelf, back-wall planks, eave fascia, -striped awning and valance) then the shipped pipeline: unique-cell UVs, -Cycles high-to-low normal bake, LOD chain, convex collider, Unity glTF -export. +A showcase piece, not an example. Procedural timber stall (corner posts +with wrap plinths, slatted counter and shelf, back-wall planks, side +braces that sit in the post bay, striped awning with a front roller and +hanging valance) then the shipped pipeline: unique-cell UVs, Cycles +high-to-low normal bake, LOD chain, convex collider, Unity glTF export. + +The old piece used capped foot cubes coplanar with the post bottoms and +counter-leg pads coplanar with the legs, which z-fought at Z=0. Wrap +plates stand off the post; counter legs go to Z=0 without a second +bottom face. It asserts **budget conformance** of the generated result. It does not witness an API contract. "It rendered without error" is not a check. @@ -22,38 +27,82 @@ materials, UVs, evaluated LOD, collider, or export file. | Axis | Declared | Measured (4.5.11 / 5.1.2 / 5.2.1) | | --- | --- | --- | -| Base triangles | 4280–4550 | 4404 / 4404 / 4404 | +| Base triangles | 3900–5200 | 4324 / 4324 / 4324 | | LOD1 ratio | 0.32–0.62 of base | 0.5000 / 0.5000 / 0.5000 | -| LOD2 ratio | 0.10–0.35 of base | 0.2198 / 0.2198 / 0.2198 | -| Materials | exactly 3 distinct, ≥16 faces per stripe slot | 3 slots, 48 / 48 stripe faces | +| LOD2 ratio | 0.10–0.35 of base | 0.2197 / 0.2197 / 0.2197 | +| Materials | exactly 3 distinct; ≥16 faces per stripe slot | 3 slots; 34 / 34 stripe faces | | UVs | in `0..1`, AABB overlap ≤ 1e-5 | in range, overlap 0 | -| Outer AABB | (1.392, 0.966, 1.743) m ± 0.01 | (1.3915, 0.9659, 1.7430), zmin 0 | -| Collider tris | ≤ 80 | 46 | -| Export | written, size > 0 | 316748 / 316748 / 316724 bytes | +| Outer AABB | (1.389, 1.011, 1.740) m ± 0.01 | (1.3892, 1.0107, 1.7402), zmin 0 | +| Collider tris | ≤ 80 | 58 | +| Export | written, size > 0 | 312752 / 312752 / 312728 bytes | + +### Hygiene + +Recomputed from the generated mesh, not asserted about the script. -DECIMATE COLLAPSE triangle counts are **not** guaranteed identical across -series — the gate is a ratio band, not an exact count. This mesh happened -to match on 4.5.11 / 5.1.2 / 5.2.1. Bake pixels are stochastic; the gate -is `has_data` plus operator `FINISHED`, not byte-identity. Construction -uses no RNG. glTF byte size differs by a few bytes across series. +| Axis | Declared | Measured (all three) | +| --- | --- | --- | +| Non-manifold edges | 0 | 0 | +| Loose verts / edges | 0 / 0 | 0 / 0 | +| Doubles merged at 1e-5 | 0 | 0 | +| Zero-area faces | 0 | 0 | +| N-gons | 0 | 0 | +| Coplanar disjoint face pairs | 0 | 0 | +| Grounded: `zmin` | within 1e-4 of 0 | 0.0000 | +| Named supports: 4 wrap feet | each `zmin` ≤ 1e-3 | 4, foot_z 0.00000 | +| Frame plan | 1.36 × 0.92 m ± 0.04 | 1.3600 × 0.9200 | + +### Joint fit and seat -`--skip-decimate` skips the LOD DECIMATE stage so LOD1 ratio is 1.0 and -exit 9 fires. That is the named budget the falsifier violates. +| Axis | Declared | Measured (all three) | +| --- | --- | --- | +| Brace-vs-counter overlap | ≤ 1e-6 m³ | 0.000000 | +| Header-post tenon engage | ≥ 0.4 × tenon | 0.0595 | +| Awning-on-header seat | −0.002–0.010 m | 0.00378 | +| Post plumb (XY drift) | ≤ 0.008 m | 0.00000 | + +DECIMATE COLLAPSE triangle counts are **not** identical across series — +the gate is a ratio band, not an exact count. Bake pixels are +stochastic; the gate is `has_data` plus operator `FINISHED`, not +byte-identity. Construction uses no RNG. Export byte counts differ by +24 B on 5.2.1 (glTF serializer), not a gated axis. + +### Falsifiers + +Each violates one named budget. All eight were run on 4.5.11, 5.1.2 and +5.2.1 and returned the same code on each. + +| Flag | Budget violated | Exit | +| --- | --- | --- | +| `--skip-decimate` | LOD1 ratio band | 9 | +| `--stray-vert` | loose vertex count is 0 | 15 | +| `--lift-z` | bounding box `zmin` is 0 | 16 | +| `--short-feet` | named wrap-foot supports at Z=0 | 16 | +| `--low-brace` | brace-vs-counter overlap | 17 | +| `--float-awning` | awning-on-header seat | 18 | +| `--rake-posts` | post plumb | 19 | ## Run ```bash blender --background --python market_stall.py -- blender --background --python market_stall.py -- --skip-decimate +blender --background --python market_stall.py -- --stray-vert +blender --background --python market_stall.py -- --lift-z +blender --background --python market_stall.py -- --short-feet +blender --background --python market_stall.py -- --low-brace +blender --background --python market_stall.py -- --float-awning +blender --background --python market_stall.py -- --rake-posts blender --background --python market_stall.py -- --output stall.png ``` -Smoke does not pass `--output` or `--skip-decimate`. +Smoke passes no flags. ## Exit codes File-local. `9` is a valid check code. `10` is reserved for -`gallery_framing.check_framing` on the `--output` path. +`gallery_framing.check_framing` on the `--output` path. `15`–`19` are the +hygiene and joint-fit family. | Code | Meaning | | --- | --- | @@ -72,3 +121,8 @@ File-local. `9` is a valid check code. `10` is reserved for | 12 | Bake did not finish or image has no data | | 13 | Export file missing or empty | | 14 | `--output` produced no file | +| 15 | Mesh hygiene: loose, non-manifold, zero-area, doubles, n-gons, z-fight | +| 16 | Not grounded: bounding box `zmin` off 0, or a named wrap foot floats | +| 17 | Joint fit: brace occupying the counter volume | +| 18 | Seat: awning-on-header gap | +| 19 | Post plumb or frame plan off the stated real-world size | diff --git a/showcase/market-stall/market_stall.py b/showcase/market-stall/market_stall.py index 01ed396..9263c8a 100644 --- a/showcase/market-stall/market_stall.py +++ b/showcase/market-stall/market_stall.py @@ -5,12 +5,15 @@ high-to-low normal bake, LOD chain, convex collider, Unity glTF export. Budgets are declared below and recomputed from the generated result. -They are not API-contract witnesses. ``--skip-decimate`` skips the LOD -DECIMATE stage so the LOD-ratio budget fails. +They are not API-contract witnesses. Each falsifier violates one named +budget: ``--skip-decimate`` the LOD-ratio band, ``--stray-vert`` mesh +hygiene, ``--lift-z`` grounded zmin, ``--short-feet`` named post-foot +supports, ``--low-brace`` brace-vs-counter joint fit, ``--float-awning`` +awning-on-header seat, ``--rake-posts`` post plumb. -No RNG. Construction is closed-form. DECIMATE COLLAPSE triangle counts -are not byte-identical across Blender versions — the LOD gate is a -ratio band, not an exact count. +No RNG. Slat jitter is closed-form ``sin(i)``. DECIMATE COLLAPSE +triangle counts are not byte-identical across Blender versions — the LOD +gate is a ratio band, not an exact count. blender --background --python market_stall.py -- blender --background --python market_stall.py -- --skip-decimate @@ -44,16 +47,20 @@ BACK_H = 1.72 N_STRIPES = 8 AWNING_T = 0.018 +OVERHANG_F = 0.040 +OVERHANG_B = 0.030 COUNTER_Z = 0.82 COUNTER_D = 0.34 COUNTER_T = 0.070 BRACE = 0.036 +FOOT_H = 0.036 +TENON = POST * 0.35 BBOX_TOL = 0.01 # Fitted to the generated AABB after locking geometry. Recomputed from bound_box. -OUTER_SIZE = (1.392, 0.966, 1.743) +OUTER_SIZE = (1.389, 1.011, 1.740) -BASE_TRIS_MIN = 4280 -BASE_TRIS_MAX = 4550 +BASE_TRIS_MIN = 3900 +BASE_TRIS_MAX = 5200 LOD1_RATIO_MIN = 0.32 LOD1_RATIO_MAX = 0.62 LOD2_RATIO_MIN = 0.10 @@ -66,6 +73,24 @@ COLLIDER_TRIS_MAX = 80 BAKE_RES = 256 CAGE_EXTRUSION = 0.06 +ZMIN_EPS = 1e-4 +DOUBLES_EPS = 1e-5 +AREA_EPS = 1e-10 +ZFIGHT_EPS = 1e-4 +ZFIGHT_COS = 0.998 +FOOT_ZMIN_MAX = 0.001 +FOOT_COUNT = 4 +AWNING_SEAT_MAX = 0.010 +AWNING_SEAT_MIN = -0.002 +BRACE_COUNTER_OVERLAP_MAX = 1e-6 +POST_PLUMB_MAX = 0.008 +FRAME_XY_TOL = 0.04 +LIFT_Z = 0.05 +# Small enough that the AABB still sits in BBOX_TOL; large enough that +# the front-eave seat drops below AWNING_SEAT_MIN. +FLOAT_AWNING = 0.008 +RAKE = math.radians(2.0) +SHORT_FOOT_Z = 0.048 WOOD_IDX = 0 STRIPE_A_IDX = 1 @@ -112,25 +137,75 @@ def add_box(bm, loc, scale, mat_idx, euler=(0.0, 0.0, 0.0)): return verts -def add_cone(bm, loc, radius1, radius2, depth, segments, mat_idx, euler=(0.0, 0.0, 0.0)): - geo = bmesh.ops.create_cone( +def add_oriented_box(bm, a, b, scale_xy, mat_idx): + a = Vector(a) + b = Vector(b) + delta = b - a + length = delta.length + if length < 1e-8: + return [] + quat = Vector((0.0, 0.0, 1.0)).rotation_difference(delta.normalized()) + eul = quat.to_euler("XYZ") + return add_box( bm, - cap_ends=True, - cap_tris=False, - segments=segments, - radius1=radius1, - radius2=radius2, - depth=depth, + ((a + b) * 0.5), + (scale_xy[0], scale_xy[1], length), + mat_idx, + euler=(eul.x, eul.y, eul.z), ) - verts = geo["verts"] + + +def add_wrap(bm, px, py, z, height, host, t, mat_idx): + """Four plates around a square post. Y-facing plates cover the corners.""" + half = host / 2.0 + off = half + t / 2.0 + 0.002 + add_box(bm, (px + off, py, z), (t, host, height), mat_idx) + add_box(bm, (px - off, py, z), (t, host, height), mat_idx) + add_box(bm, (px, py + off, z), (host + 2.0 * t, t, height), mat_idx) + add_box(bm, (px, py - off, z), (host + 2.0 * t, t, height), mat_idx) + return [] + + +def add_striped_slab(bm, loc, scale, euler, n_stripes, even_idx, odd_idx): + """One slab split into n stripes that share vertices — no daylight gaps.""" rot = Euler(euler).to_matrix() origin = Vector(loc) - for v in verts: - v.co = rot @ v.co + origin - faces = {f for v in verts for f in v.link_faces} - for f in faces: - f.material_index = mat_idx - return verts + sx, sy, sz = scale + stations = [] + for i in range(n_stripes + 1): + x = -0.5 * sx + sx * i / n_stripes + row = [] + for y, z in ( + (-0.5 * sy, -0.5 * sz), + (-0.5 * sy, 0.5 * sz), + (0.5 * sy, -0.5 * sz), + (0.5 * sy, 0.5 * sz), + ): + row.append(bm.verts.new(rot @ Vector((x, y, z)) + origin)) + stations.append(row) + kept = [] + for i in range(n_stripes): + a = stations[i] + b = stations[i + 1] + idx = even_idx if (i % 2 == 0) else odd_idx + quads = ( + (a[0], b[0], b[2], a[2]), + (a[1], a[3], b[3], b[1]), + (a[0], a[1], b[1], b[0]), + (a[2], b[2], b[3], a[3]), + ) + for q in quads: + face = bm.faces.new(q) + face.material_index = idx + kept.append(face) + first = stations[0] + last = stations[-1] + cap_a = bm.faces.new((first[0], first[2], first[3], first[1])) + cap_a.material_index = even_idx + cap_b = bm.faces.new((last[0], last[1], last[3], last[2])) + cap_b.material_index = even_idx if ((n_stripes - 1) % 2 == 0) else odd_idx + kept.extend((cap_a, cap_b)) + return kept def pack_uvs(bm, margin=0.08): @@ -176,205 +251,228 @@ def pack_uvs(bm, margin=0.08): ) -def build_stall_mesh(name, bevel_offset, bevel_segments): +def build_stall_mesh( + name, + bevel_offset, + bevel_segments, + low_brace=False, + float_awning=False, + rake_posts=False, + short_feet=False, +): bm = bmesh.new() wood_verts = [] - stripe_a = set() - stripe_b = set() + stripe_faces = [] try: hx = WIDTH / 2.0 hy = DEPTH / 2.0 - inset = POST / 2.0 + px = hx - POST / 2.0 + py_f = -(hy - POST / 2.0) + py_b = hy - POST / 2.0 + post_h_f = FRONT_H - POST + post_h_b = BACK_H - POST + rake = (RAKE, 0.0, 0.0) if rake_posts else (0.0, 0.0, 0.0) posts = ( - (-hx + inset, -hy + inset, FRONT_H / 2.0, FRONT_H), - (hx - inset, -hy + inset, FRONT_H / 2.0, FRONT_H), - (-hx + inset, hy - inset, BACK_H / 2.0, BACK_H), - (hx - inset, hy - inset, BACK_H / 2.0, BACK_H), + (-px, py_f, post_h_f), + (px, py_f, post_h_f), + (-px, py_b, post_h_b), + (px, py_b, post_h_b), ) - for x, y, z, h in posts: - wood_verts.extend(add_box(bm, (x, y, z), (POST, POST, h), WOOD_IDX)) + foot_z = SHORT_FOOT_Z if short_feet else FOOT_H / 2.0 + foot_h = 0.020 if short_feet else FOOT_H + wrap_t = POST * 0.18 + for x, y, h in posts: wood_verts.extend( - add_box(bm, (x, y, 0.018), (POST * 1.45, POST * 1.45, 0.036), WOOD_IDX) + add_box(bm, (x, y, h / 2.0), (POST, POST, h), WOOD_IDX, euler=rake) ) + add_wrap(bm, x, y, foot_z, foot_h, POST, wrap_t, WOOD_IDX) - # Front and back header beams (along X). + header_len = WIDTH - POST + 2.0 * TENON wood_verts.extend( add_box( bm, - (0.0, -hy + inset, FRONT_H - POST / 2.0), - (WIDTH - POST, POST, POST), + (0.0, py_f, FRONT_H - POST / 2.0), + (header_len, POST * 0.78, POST), WOOD_IDX, ) ) wood_verts.extend( add_box( bm, - (0.0, hy - inset, BACK_H - POST / 2.0), - (WIDTH - POST, POST, POST), + (0.0, py_b, BACK_H - POST / 2.0), + (header_len, POST * 0.78, POST), WOOD_IDX, ) ) - # Side plates, slanted to match the awning. - dz = BACK_H - FRONT_H - slant_len = math.hypot(DEPTH - POST, dz) - pitch = math.atan2(dz, DEPTH - POST) - for sx in (-hx + inset, hx - inset): + # Side plates sit on the header centres, spanning post to post. + for sx in (-px, px): wood_verts.extend( - add_box( + add_oriented_box( bm, - (sx, 0.0, (FRONT_H + BACK_H) / 2.0 - POST / 2.0), - (POST * 0.85, slant_len, POST * 0.85), + (sx, py_f, FRONT_H - POST / 2.0), + (sx, py_b, BACK_H - POST / 2.0), + (POST * 0.80, POST * 0.80), WOOD_IDX, - euler=(pitch, 0.0, 0.0), ) ) - # Inner rafters under the awning — the empty underside was a slab of air. for i in range(3): rx = -hx + POST * 2.0 + (i + 1) * (WIDTH - 4.0 * POST) / 4.0 wood_verts.extend( - add_box( + add_oriented_box( bm, - (rx, 0.0, (FRONT_H + BACK_H) / 2.0 - POST * 0.42), - (POST * 0.42, slant_len * 0.96, POST * 0.42), + (rx, py_f, FRONT_H - POST / 2.0), + (rx, py_b, BACK_H - POST / 2.0), + (POST * 0.42, POST * 0.42), WOOD_IDX, - euler=(pitch, 0.0, 0.0), ) ) - # Mid rail on the back wall and two diagonal braces at the sides. wood_verts.extend( add_box( bm, - (0.0, hy - inset, BACK_H * 0.55), - (WIDTH - 2.0 * POST, POST * 0.7, POST * 0.7), + (0.0, py_b, BACK_H * 0.48), + (WIDTH - POST, POST * 0.65, POST * 0.65), WOOD_IDX, ) ) - for sx in (-hx + inset, hx - inset): - p0 = Vector((sx, -hy + inset, 0.30)) - p1 = Vector((sx, hy - inset, BACK_H * 0.58)) - mid = (p0 + p1) * 0.5 - delta = p1 - p0 - brace_len = delta.length - brace_pitch = math.atan2(delta.z, delta.y) - wood_verts.extend( - add_box( - bm, - (mid.x, mid.y, mid.z), - (BRACE, brace_len, BRACE), - WOOD_IDX, - euler=(brace_pitch, 0.0, 0.0), - ) + wood_verts.extend( + add_box( + bm, + (0.0, py_f, 0.16), + (WIDTH - 2.0 * POST, POST * 0.70, POST * 0.55), + WOOD_IDX, ) + ) - # Slatted counter — crate-lid language, not one fat slab. - cy = -hy + COUNTER_D / 2.0 + 0.04 + for sign in (-1.0, 1.0): + sx = sign * px + if low_brace: + a = Vector((sx, py_f, 0.28)) + b = Vector((sx, py_b, BACK_H * 0.58)) + else: + # On the post centreline so the brace tenons the post instead + # of reading as a wing in the front elevation. + a = Vector((sx, 0.04, COUNTER_Z + 0.14)) + b = Vector((sx, py_b, BACK_H * 0.56)) + wood_verts.extend(add_oriented_box(bm, a, b, (BRACE, BRACE), WOOD_IDX)) + + inner_w = WIDTH - 2.0 * POST - 0.024 + y0 = py_f + POST / 2.0 + 0.016 + y1 = y0 + COUNTER_D n_slats = 6 - slat_gap = 0.010 - slat_d = (COUNTER_D - 0.02 - (n_slats - 1) * slat_gap) / n_slats - y0 = cy - COUNTER_D / 2.0 + 0.01 + slat_d / 2.0 + slat_gap = 0.008 + slat_d = (COUNTER_D - (n_slats - 1) * slat_gap) / n_slats for i in range(n_slats): + jw = 0.018 * math.sin(i * 2.31 + 0.5) + jt = 0.006 * math.sin(i * 1.87) + y = y0 + slat_d / 2.0 + i * (slat_d + slat_gap) wood_verts.extend( add_box( bm, - (0.0, y0 + i * (slat_d + slat_gap), COUNTER_Z), - (WIDTH - 0.06, slat_d, COUNTER_T * 0.72), + (0.0, y, COUNTER_Z), + (inner_w + jw, slat_d * 0.94, COUNTER_T * 0.62 + jt), WOOD_IDX, ) ) wood_verts.extend( add_box( bm, - (0.0, cy - COUNTER_D / 2.0 + 0.018, COUNTER_Z - 0.09), - (WIDTH - 0.08, 0.036, 0.16), + (0.0, y0 + 0.016, COUNTER_Z - 0.08), + (inner_w * 0.96, 0.032, 0.14), WOOD_IDX, ) ) - under_shelf_z = 0.38 + shelf_z = 0.38 n_shelf = 4 shelf_d = COUNTER_D * 0.78 - shelf_gap = 0.010 + shelf_gap = 0.008 shelf_slat = (shelf_d - (n_shelf - 1) * shelf_gap) / n_shelf - sy0 = cy - shelf_d / 2.0 + shelf_slat / 2.0 + sy0 = y0 + 0.04 + shelf_slat / 2.0 for i in range(n_shelf): + jw = 0.014 * math.sin(i * 1.63 + 0.9) wood_verts.extend( add_box( bm, - (0.0, sy0 + i * (shelf_slat + shelf_gap), under_shelf_z), - (WIDTH - 0.16, shelf_slat, 0.024), + (0.0, sy0 + i * (shelf_slat + shelf_gap), shelf_z), + (inner_w - 0.08 + jw, shelf_slat * 0.94, 0.024), WOOD_IDX, ) ) - for lx in (-hx + 0.16, hx - 0.16): - for ly in (cy - COUNTER_D * 0.32, cy + COUNTER_D * 0.32): + leg_h = COUNTER_Z - COUNTER_T * 0.32 + for lx in (-inner_w / 2.0 + 0.04, inner_w / 2.0 - 0.04): + for ly in (y0 + 0.05, y1 - 0.05): wood_verts.extend( - add_box( - bm, - (lx, ly, COUNTER_Z / 2.0), - (0.045, 0.045, COUNTER_Z), - WOOD_IDX, - ) + add_box(bm, (lx, ly, leg_h / 2.0), (0.042, 0.042, leg_h), WOOD_IDX) ) - # Back wall: horizontal planks with gaps, not a felt slab. + plank_y = py_b - POST / 2.0 - 0.014 + plank_len = WIDTH - POST for i in range(5): - z = 0.36 + i * 0.155 + z = 0.30 + i * 0.148 + jw = 0.004 * math.sin(i * 2.11) wood_verts.extend( add_box( bm, - (0.0, hy - inset - 0.014, z), - (WIDTH - 2.35 * POST, 0.028, 0.122), + (0.0, plank_y, z), + (plank_len + jw, 0.028, 0.118), WOOD_IDX, ) ) - # Front eave fascia under the valance. + fascia_y = -hy - OVERHANG_F wood_verts.extend( add_box( bm, - (0.0, -hy - 0.008, FRONT_H - 0.028), - (WIDTH - POST * 0.4, 0.032, 0.048), + (0.0, fascia_y, FRONT_H - 0.022), + (WIDTH - POST * 0.2, 0.032, 0.044), WOOD_IDX, ) ) - - stripe_w = WIDTH / N_STRIPES - awning_len = math.hypot(DEPTH, dz) - awning_pitch = math.atan2(dz, DEPTH) - cz = (FRONT_H + BACK_H) / 2.0 + AWNING_T * 0.35 - for i in range(N_STRIPES): - cx = -hx + (i + 0.5) * stripe_w - idx = STRIPE_A_IDX if (i % 2 == 0) else STRIPE_B_IDX - before = set(bm.faces) + wood_verts.extend( add_box( bm, - (cx, 0.0, cz), - (stripe_w * 0.98, awning_len + 0.04, AWNING_T), - idx, - euler=(awning_pitch, 0.0, 0.0), + (0.0, fascia_y - 0.006, FRONT_H - 0.004), + (WIDTH - POST * 0.05, 0.022, 0.022), + WOOD_IDX, ) - new_faces = set(bm.faces) - before - if idx == STRIPE_A_IDX: - stripe_a.update(new_faces) - else: - stripe_b.update(new_faces) + ) - # Front valance hanging from the eave. - for i in range(N_STRIPES): - cx = -hx + (i + 0.5) * stripe_w - idx = STRIPE_B_IDX if (i % 2 == 0) else STRIPE_A_IDX - before = set(bm.faces) - add_box( + # Centreline sits above the header; thickness then hangs along the + # roof normal so the front underside clears the fascia top. + seat = 0.012 + front = Vector((0.0, -hy - OVERHANG_F, FRONT_H + seat)) + back = Vector((0.0, hy + OVERHANG_B, BACK_H + seat)) + dy = back.y - front.y + dz = back.z - front.z + awning_len = math.hypot(dy, dz) + pitch = math.atan2(dz, dy) + nrm = Vector((0.0, -math.sin(pitch), math.cos(pitch))) + mid = (front + back) * 0.5 + if float_awning: + mid = Vector((mid.x, mid.y, mid.z + FLOAT_AWNING)) + stripe_faces.extend( + add_striped_slab( bm, - (cx, -hy - 0.01, FRONT_H - 0.08), - (stripe_w * 0.96, 0.016, 0.14), - idx, + mid, + (WIDTH, awning_len, AWNING_T), + (pitch, 0.0, 0.0), + N_STRIPES, + STRIPE_A_IDX, + STRIPE_B_IDX, ) - new_faces = set(bm.faces) - before - if idx == STRIPE_A_IDX: - stripe_a.update(new_faces) - else: - stripe_b.update(new_faces) + ) + val_z = FRONT_H - 0.09 + (FLOAT_AWNING if float_awning else 0.0) + stripe_faces.extend( + add_striped_slab( + bm, + (0.0, fascia_y - 0.002, val_z), + (WIDTH, 0.014, 0.15), + (0.0, 0.0, 0.0), + N_STRIPES, + STRIPE_B_IDX, + STRIPE_A_IDX, + ) + ) if bevel_offset > 0.0: edges = list({e for v in wood_verts for e in v.link_edges}) @@ -389,9 +487,10 @@ def build_stall_mesh(name, bevel_offset, bevel_segments): ) zmin = min(v.co.z for v in bm.verts) - if zmin != 0.0: - for v in bm.verts: - v.co.z -= zmin + for v in bm.verts: + v.co.z -= zmin + if v.co.z < 0.002: + v.co.z = 0.0 pack_uvs(bm) bmesh.ops.recalc_face_normals(bm, faces=list(bm.faces)) @@ -402,12 +501,9 @@ def build_stall_mesh(name, bevel_offset, bevel_segments): if edge.is_manifold and len(edge.link_faces) == 2: if edge.calc_face_angle() < math.radians(25.0): edge.smooth = True - for f in stripe_a: - if f.is_valid: - f.material_index = STRIPE_A_IDX - for f in stripe_b: - if f.is_valid: - f.material_index = STRIPE_B_IDX + for face in stripe_faces: + if face.is_valid: + pass me = bpy.data.meshes.new(name) bm.to_mesh(me) me.update() @@ -473,6 +569,271 @@ def uv_stats(mesh): return min(us), min(vs), max(us), max(vs), overlap, len(aabbs) +def face_area(me, poly): + vs = [me.vertices[i].co for i in poly.vertices] + if len(vs) < 3: + return 0.0 + v0 = vs[0] + area = 0.0 + for i in range(1, len(vs) - 1): + area += (vs[i] - v0).cross(vs[i + 1] - v0).length * 0.5 + return area + + +def hygiene_audit(me): + # Combinatorics match examples/mesh-hygiene-audit.audit (copied, not imported). + nv, ne, nf = len(me.vertices), len(me.edges), len(me.polygons) + ngons = sum(1 for p in me.polygons if len(p.vertices) > 4) + zero_area = sum(1 for p in me.polygons if face_area(me, p) <= AREA_EPS) + bm = bmesh.new() + try: + bm.from_mesh(me) + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + loose_v = sum(1 for v in bm.verts if len(v.link_edges) == 0) + loose_e = sum(1 for e in bm.edges if len(e.link_faces) == 0) + nonman = sum(1 for e in bm.edges if not e.is_manifold) + ret = bmesh.ops.find_doubles(bm, verts=list(bm.verts), dist=DOUBLES_EPS) + doubles = len(ret.get("targetmap") or {}) + finally: + bm.free() + return { + "nv": nv, "ne": ne, "nf": nf, "ngons": ngons, + "loose_v": loose_v, "loose_e": loose_e, "nonman": nonman, + "zero_area": zero_area, "doubles": doubles, "euler": nv - ne + nf, + } + + +def zfight_pairs(me): + data = [ + (p.center.copy(), p.normal.copy(), frozenset(p.vertices)) + for p in me.polygons + ] + eps2 = ZFIGHT_EPS * ZFIGHT_EPS + count = 0 + for i in range(len(data)): + ci, ni, vi = data[i] + for j in range(i + 1, len(data)): + cj, nj, vj = data[j] + if (cj - ci).length_squared > eps2: + continue + if abs(ni.dot(nj)) <= ZFIGHT_COS: + continue + if vi & vj: + continue + count += 1 + return count + + +def shells(me): + neighbors = [[] for _ in range(len(me.vertices))] + for edge in me.edges: + a, b = edge.vertices + neighbors[a].append(b) + neighbors[b].append(a) + seen = [False] * len(me.vertices) + groups = [] + for start in range(len(me.vertices)): + if seen[start]: + continue + seen[start] = True + stack = [start] + group = [] + while stack: + current = stack.pop() + group.append(current) + for nxt in neighbors[current]: + if not seen[nxt]: + seen[nxt] = True + stack.append(nxt) + groups.append(group) + return groups + + +def shell_aabb(me, group): + pts = [me.vertices[i].co for i in group] + return ( + min(p.x for p in pts), min(p.y for p in pts), min(p.z for p in pts), + max(p.x for p in pts), max(p.y for p in pts), max(p.z for p in pts), + ) + + +def aabb_overlap(a, b): + x = min(a[3], b[3]) - max(a[0], b[0]) + y = min(a[4], b[4]) - max(a[1], b[1]) + z = min(a[5], b[5]) - max(a[2], b[2]) + if x <= 0.0 or y <= 0.0 or z <= 0.0: + return 0.0 + return x * y * z + + +def mat_of(me, group): + member = set(group) + for poly in me.polygons: + if all(i in member for i in poly.vertices): + return poly.material_index + return None + + +def support_audit(me): + """Post feet: wrap plates clustered at the four frame corners.""" + plates = [] + for group in shells(me): + if mat_of(me, group) != WOOD_IDX: + continue + a = shell_aabb(me, group) + dz = a[5] - a[2] + dx = a[3] - a[0] + dy = a[4] - a[1] + cz = 0.5 * (a[2] + a[5]) + if cz > 0.10 or dz > 0.10: + continue + if max(dx, dy) < POST * 0.8: + continue + plates.append(a) + corners = {} + for a in plates: + cx = 0.5 * (a[0] + a[3]) + cy = 0.5 * (a[1] + a[4]) + key = (1 if cx > 0.0 else -1, 1 if cy > 0.0 else -1) + corners.setdefault(key, []).append(a) + zmin = min((a[2] for a in plates), default=99.0) + return {"feet": len(corners), "foot_z": zmin} + + +def joint_audit(me): + """Braces must not occupy the counter volume. Headers bite the posts.""" + hx = WIDTH / 2.0 + groups = shells(me) + boxes = [(g, shell_aabb(me, g), mat_of(me, g)) for g in groups] + posts = [] + headers = [] + braces = [] + slats = [] + for _g, a, mat in boxes: + if mat != WOOD_IDX: + continue + dx, dy, dz = a[3] - a[0], a[4] - a[1], a[5] - a[2] + cx = 0.5 * (a[0] + a[3]) + cy = 0.5 * (a[1] + a[4]) + cz = 0.5 * (a[2] + a[5]) + if dz > FRONT_H * 0.55 and dx < POST * 2.4 and dy < POST * 2.4: + posts.append(a) + continue + if dx > WIDTH * 0.6 and dz < POST * 2.2 and dy < POST * 2.2 and cz > FRONT_H * 0.7: + headers.append(a) + continue + if abs(cx) > hx - POST * 1.8 and dz > 0.35 and dy > 0.25 and dx < 0.20: + braces.append(a) + continue + if abs(cz - COUNTER_Z) < 0.08 and dx > WIDTH * 0.4: + slats.append(a) + overlap = 0.0 + for brace in braces: + for slat in slats: + overlap = max(overlap, aabb_overlap(brace, slat)) + pairs = [ + min(h[3] - p[0], p[3] - h[0]) + for h in headers + for p in posts + if abs(0.5 * (h[1] + h[4]) - 0.5 * (p[1] + p[4])) < POST * 2.0 + ] + engage = min(pairs) if pairs else 1.0 + return { + "posts": len(posts), + "headers": len(headers), + "braces": len(braces), + "slats": len(slats), + "overlap": overlap, + "engage": engage, + } + + +def awning_seat(me): + """Gap from front-header top to awning underside, at the front eave.""" + hy = DEPTH / 2.0 + wood_z = [] + stripe_z = [] + for poly in me.polygons: + zs = [me.vertices[i].co.z for i in poly.vertices] + ys = [me.vertices[i].co.y for i in poly.vertices] + cy = sum(ys) / len(ys) + if cy > -hy + 0.15: + continue + zmax = max(zs) + zmin = min(zs) + if poly.material_index == WOOD_IDX and zmax > FRONT_H - 0.12: + if max(ys) - min(ys) > 0.028: + wood_z.append(zmax) + if poly.material_index in (STRIPE_A_IDX, STRIPE_B_IDX) and zmax > FRONT_H: + stripe_z.append(zmin) + if not wood_z or not stripe_z: + return 99.0 + return min(stripe_z) - max(wood_z) + + +def plumb_audit(me): + """XY centroid of each post's bottom slab vs top slab.""" + drifts = [] + for group in shells(me): + if mat_of(me, group) != WOOD_IDX: + continue + a = shell_aabb(me, group) + dz = a[5] - a[2] + dx = a[3] - a[0] + dy = a[4] - a[1] + if dz < FRONT_H * 0.55 or dx > POST * 2.4 or dy > POST * 2.4: + continue + pts = [me.vertices[i].co for i in group] + zcut_lo = a[2] + 0.08 * dz + zcut_hi = a[5] - 0.08 * dz + lo = [p for p in pts if p.z <= zcut_lo] + hi = [p for p in pts if p.z >= zcut_hi] + if len(lo) < 3 or len(hi) < 3: + continue + c_lo = Vector((sum(p.x for p in lo) / len(lo), sum(p.y for p in lo) / len(lo))) + c_hi = Vector((sum(p.x for p in hi) / len(hi), sum(p.y for p in hi) / len(hi))) + drifts.append((c_hi - c_lo).length) + return max(drifts) if drifts else 99.0 + + +def frame_size(me): + """Post-foot envelope vs declared stall plan, not the awning AABB.""" + hx = WIDTH / 2.0 + hy = DEPTH / 2.0 + xs, ys, zs = [], [], [] + for group in shells(me): + if mat_of(me, group) != WOOD_IDX: + continue + a = shell_aabb(me, group) + dz = a[5] - a[2] + dx = a[3] - a[0] + dy = a[4] - a[1] + cx = 0.5 * (a[0] + a[3]) + cy = 0.5 * (a[1] + a[4]) + if dz > FRONT_H * 0.55 and dx < POST * 2.4 and dy < POST * 2.4: + xs.extend((a[0], a[3])) + ys.extend((a[1], a[4])) + zs.append(a[5]) + continue + if dz < 0.08 and abs(cx) > hx - POST * 1.6 and abs(cy) > hy - POST * 1.6: + zs.append(a[2]) + if not xs: + return 0.0, 0.0, 0.0 + return max(xs) - min(xs), max(ys) - min(ys), max(zs) - min(zs) + + +def add_stray_vert(me): + bm = bmesh.new() + try: + bm.from_mesh(me) + bm.verts.new((0.0, 0.0, COUNTER_Z)) + bm.to_mesh(me) + me.update() + finally: + bm.free() + + def make_lod(obj, name, ratio, skip_decimate): mesh = obj.data.copy() lod = bpy.data.objects.new(name, mesh) @@ -565,16 +926,47 @@ def export_unity(path, objects): ) -def check(skip_decimate): +def check( + skip_decimate, + lift_z=False, + stray_vert=False, + short_feet=False, + low_brace=False, + float_awning=False, + rake_posts=False, +): bpy.ops.wm.read_factory_settings(use_empty=True) - low = build_stall_mesh("StallLow", bevel_offset=0.006, bevel_segments=2) - high = build_stall_mesh("StallHigh", bevel_offset=0.006, bevel_segments=4) + low = build_stall_mesh( + "StallLow", + bevel_offset=0.006, + bevel_segments=2, + low_brace=low_brace, + float_awning=float_awning, + rake_posts=rake_posts, + short_feet=short_feet, + ) + high = build_stall_mesh( + "StallHigh", + bevel_offset=0.006, + bevel_segments=4, + low_brace=low_brace, + float_awning=float_awning, + rake_posts=rake_posts, + short_feet=short_feet, + ) wood = principled("StallWood", (0.42, 0.24, 0.10, 1.0), 0.0, 0.55) stripe_a = principled("StallStripeA", (0.72, 0.12, 0.10, 1.0), 0.0, 0.62) stripe_b = principled("StallStripeB", (0.86, 0.80, 0.62, 1.0), 0.0, 0.58) assign_slots(low, wood, stripe_a, stripe_b) assign_slots(high, wood, stripe_a, stripe_b) + if stray_vert: + add_stray_vert(low.data) + if lift_z: + for v in low.data.vertices: + v.co.z += LIFT_Z + low.data.update() + if low.data is None or len(low.data.polygons) < 6: return fail("stall mesh did not build", 3), None, None, None, None, None @@ -591,6 +983,13 @@ def check(skip_decimate): size_x = bb[3] - bb[0] size_y = bb[4] - bb[1] size_z = bb[5] - bb[2] + hyg = hygiene_audit(low.data) + zf = zfight_pairs(low.data) + sup = support_audit(low.data) + jnt = joint_audit(low.data) + seat = awning_seat(low.data) + plumb = plumb_audit(low.data) + fx, fy, fz = frame_size(low.data) img, tex = setup_bake_image(low, wood) if img is None: @@ -619,9 +1018,7 @@ def check(skip_decimate): export_unity(export_path, [low, collider]) export_size = os.path.getsize(export_path) if os.path.isfile(export_path) else 0 - print( - f"blender={tuple(bpy.app.version)} skip_decimate={skip_decimate}" - ) + print(f"blender={tuple(bpy.app.version)} skip_decimate={skip_decimate}") print( f"measured base_tris={base_tris} lod1_tris={lod1_tris} " f"lod2_tris={lod2_tris} r1={r1:.4f} r2={r2:.4f}" @@ -638,6 +1035,16 @@ def check(skip_decimate): f"measured collider_tris={col_tris} bake={bake_result} " f"bake_has_data={img.has_data} export_bytes={export_size}" ) + print( + f"measured hygiene loose_v={hyg['loose_v']} loose_e={hyg['loose_e']} " + f"nonman={hyg['nonman']} zero_area={hyg['zero_area']} " + f"doubles={hyg['doubles']} ngons={hyg['ngons']} zfight={zf}" + ) + print( + f"measured feet={sup['feet']} foot_z={sup['foot_z']:.5f} " + f"brace_overlap={jnt['overlap']:.6f} engage={jnt['engage']:.4f} " + f"seat={seat:.5f} plumb={plumb:.5f} frame=({fx:.4f},{fy:.4f},{fz:.4f})" + ) if not (BASE_TRIS_MIN <= base_tris <= BASE_TRIS_MAX): return fail( @@ -702,6 +1109,40 @@ def check(skip_decimate): ), None, None, None, None, None if export_size <= 0: return fail("export file missing or empty", 13), None, None, None, None, None + if ( + hyg["loose_v"] or hyg["loose_e"] or hyg["nonman"] or hyg["zero_area"] + or hyg["doubles"] or hyg["ngons"] or zf + ): + return fail( + f"hygiene loose_v={hyg['loose_v']} loose_e={hyg['loose_e']} " + f"nonman={hyg['nonman']} zero_area={hyg['zero_area']} " + f"doubles={hyg['doubles']} ngons={hyg['ngons']} zfight={zf}", + 15, + ), None, None, None, None, None + if bb[2] > ZMIN_EPS or sup["feet"] != FOOT_COUNT or sup["foot_z"] > FOOT_ZMIN_MAX: + return fail( + f"grounded zmin={bb[2]:.5f} feet={sup['feet']} " + f"foot_z={sup['foot_z']:.5f}", + 16, + ), None, None, None, None, None + if jnt["overlap"] > BRACE_COUNTER_OVERLAP_MAX or jnt["engage"] < TENON * 0.4: + return fail( + f"joint overlap={jnt['overlap']:.6f} engage={jnt['engage']:.4f} " + f"posts={jnt['posts']} braces={jnt['braces']} slats={jnt['slats']}", + 17, + ), None, None, None, None, None + if seat < AWNING_SEAT_MIN or seat > AWNING_SEAT_MAX: + return fail(f"awning seat gap {seat:.5f} > {AWNING_SEAT_MAX}", 18), None, None, None, None, None + if ( + plumb > POST_PLUMB_MAX + or abs(fx - WIDTH) > FRAME_XY_TOL + or abs(fy - DEPTH) > FRAME_XY_TOL + ): + return fail( + f"plumb={plumb:.5f} frame=({fx:.4f},{fy:.4f},{fz:.4f}) " + f"off {WIDTH}x{DEPTH}", + 19, + ), None, None, None, None, None return 0, low, high, wood, tex, collider @@ -766,13 +1207,15 @@ def light(name, loc, energy, size, col, rot): light("Fill", (5.0, -3.6, 2.6), 48.0, 8.0, (0.72, 0.82, 1.0), (62, 0, 50)) light("Wedge", (2.4, 4.2, 4.1), 640.0, 5.5, (1.0, 0.70, 0.40), (-70, 0, 198)) + bb = world_bbox(low) + span = max(bb[3] - bb[0], bb[4] - bb[1], bb[5] - bb[2], 0.2) cam_data = bpy.data.cameras.new("Cam") cam_data.lens = 50.0 cam = bpy.data.objects.new("Cam", cam_data) - cam.location = (3.11, -5.10, 1.60) + cam.location = (span * 2.17, -span * 2.95, span * 1.12) scene.collection.objects.link(cam) aim = bpy.data.objects.new("Aim", None) - aim.location = (0.0, -0.10, 0.92) + aim.location = (0.0, -0.04 * span, 0.52 * (bb[2] + bb[5])) scene.collection.objects.link(aim) con = cam.constraints.new("TRACK_TO") con.target = aim @@ -815,14 +1258,24 @@ def main(): p = argparse.ArgumentParser() p.add_argument("--output", default=None) p.add_argument("--engine", default="eevee", choices=("eevee", "cycles")) - p.add_argument( - "--skip-decimate", - action="store_true", - help="falsification: skip the LOD DECIMATE stage", - ) + p.add_argument("--skip-decimate", action="store_true") + p.add_argument("--stray-vert", action="store_true") + p.add_argument("--lift-z", action="store_true") + p.add_argument("--short-feet", action="store_true") + p.add_argument("--low-brace", action="store_true") + p.add_argument("--float-awning", action="store_true") + p.add_argument("--rake-posts", action="store_true") args = p.parse_args(argv) - code, low, _high, wood, tex, _col = check(args.skip_decimate) + code, low, _high, wood, tex, _col = check( + args.skip_decimate, + lift_z=args.lift_z, + stray_vert=args.stray_vert, + short_feet=args.short_feet, + low_brace=args.low_brace, + float_awning=args.float_awning, + rake_posts=args.rake_posts, + ) if code: return code if args.output: diff --git a/showcase/market-stall/preview.webp b/showcase/market-stall/preview.webp index c507a27ab1efe24745b278528df179408ffc3e67..a223a5aa1b8132a002f2d697851f9a54a8df584d 100644 GIT binary patch literal 31724 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

P3Ex^=|q+CrX)FUsUKfxJ}}z2xNUHnaa~bN8c<~ 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 Date: Thu, 17 Sep 2026 08:19:24 -0400 Subject: [PATCH 2/2] fix(showcase): rebuild watchtower with a closed shake roof and planted stiles Incomplete courses left a wood U through the eave, and shoes lifted after the zmin snap. A solid cone plus post-snap collars are durable; per-coordinate nudges are not. Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-authored-by: Cursor --- docs/gallery/assets/watchtower-hero.webp | Bin 36494 -> 26364 bytes docs/gallery/index.html | 4 +- docs/gallery/watchtower/index.html | 736 ++++++++++++++++++----- showcase/gallery.json | 4 +- showcase/watchtower/README.md | 90 ++- showcase/watchtower/preview.webp | Bin 18508 -> 26364 bytes showcase/watchtower/watchtower.py | 705 +++++++++++++++++----- 7 files changed, 1227 insertions(+), 312 deletions(-) diff --git a/docs/gallery/assets/watchtower-hero.webp b/docs/gallery/assets/watchtower-hero.webp index 326f4abd693d37df3925da0c14100726c77826b0..9f5cdc00ef693885daece685da47e7606dcbbab5 100644 GIT binary patch literal 26364 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 P-E4Ru(oyV15fA_X-au}d literal 36494 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

);v&w0d#{+$$QQGRc>$2)?SXpP2 z0qF8I;heC5T-9KQ$Da4c)e8(0gB0In)Jy+J9t!$x1vNr#44f6Mf;T{)@ zHX5zPVO_nrJ&@m0P$iwUk3cpIkpkEKx{4uPY+g+Z@#I*Ef%4T&P^@Ed0b!V7;Rnhf z8J{pSm@;%k7V*i}`G{3;t{#>FJ8S;Or_*ZwsOl|@4Qd2n{ZLbrIlIGzQby=%Jic8f zwHKxrEl$#Xdl^V~c7nE(kzVcItM^;qY-@hQ1!>c1bvZ8wxtqWtkqhpjQ9lgMAJQ81 zH^K#z-eH*mFfI#@+T9^CkG9uYkednF-Cy%kAv#ftbxPEuI-M8{$X)~c;t?$AI7FRQ z`4887i%m_BQu)4PtexleW|>v60pR@>9GS~uwcVIYRzO-)jB85vw|#b)@(6XSWDuQx zF;fA*j#S{2aiS@LCI)Ee0j0n4+xaH_y%e%))bvq_G9O?pLI2dsxj~hGVQpu2qWo^I z@U7xT#K=`EeBz0Vf>!6~oL5aPF4#dVJzHg|i57&SbMPMkn^C~d?d-(OkL@69;I}(m zw5~%Wngyo*mUQ!6H2A93GOxwcJdDblD$T zQ7x|qM&3RAFNlW|9^!x#q-M>aTG#OBmDBA^$?>9NTU8||L1b(~5JEdrn%F?Tg>IDN z>Q1}h{0V%Bga3X^+nn(Mr@PcSEG}5P+@6>GGR3}d9d6a*PqcG;02^;jnN^s4VDf0m zcw79Gw&0~L58|+N*mp_o&pxj|JZG?00{fS!Uk}QcR{|3P;Zd9>S2Imb#%9uG^|fq9 z#oA24iXqUOK@%*<$I;SxMFnZ`^PX7NRlJK>8jl`ZCY5|)XQ+@%;C1Nwjd*Y5e!Ycs zr4cHM;8*aSV7rTMW+mFdTgI4&99w=)Jz|U`fp?PPu~-c@tZ{0QU=5IrOS7<@MOQ5% z(cnfJFPPN2|t_aa;@us)U?-jqfDFbD!b)QRglv5VQ1p z4hQvcg^VffBrGxB>@P$!yp?b+VI`6BVQ+(~ED4B&Z1lrnxnZJsYRYpZFPggoy<)X- ze(~?@r#3BJJ0`23;n_l)FR$tn6vOiRcVjEyO2np>yTVq3dW>@*`y;4i(4JyMMiNQ$}~ zG3J=3X?)a|#c*IKT%O5#f3j19Z|Aokv4xeJ=C5@4sv&L?z}B_mBZeo-0)uUR#xl%$C*v(5 zc(HSe1IWK4e{xed%jyz(COL82nF)I&AZTE8UNr#u)1!|E+BE*v6dJ_~Vso7igt?X$ z4B9^=^4w5aI)f-hPfoKjY^m`5rfqv3*ekQS;hUmOI!qBKwOW*sC3spE%%S9#S|IM) z7Zjlu?v(BiSy2{{&2;aj&9)%+?@10^;}Fq z(J7T~@c)B>R2tmm+yNk7a`p|wj@Hm=@_+-R77C(f%8nXa6<=vNv|+4AOg0)ZOi?+n zHdQ1`PT%)*(X}PC$~Sv>WzSGL zoEnRug1p(W*`lLdbMaWLjBM#CGFvw6k64-yd%7TW1eD)HO{kCUzSC3({A#d`w;aVBAfclihW9Euzw>UZMAkX`elJC`MR%#HDUYEV_nO@l5FX75`xUD{t8lx z6pnSnjR!K0z90DW2+#6^ci|61r$MzXg&Qc}6Q^%)rcc7PD&13uIMM!zx-eCa??M|2i^!@MC?pwfz{sae%HSn8Kt0?0 z92??iBoZM2(iLA>dvS99uagQVu_fbP{Wajs&mzgjF%Ed|kMUyOSbG?%$ifN2;Ot?3 zkKHvb=q#H5E-|PGz+qoB!S+iS`<+~^K(nHa8&XE>ZGLWMcOKq1Dsx&jMEZOGpl1

msL0;e_gWz4vnzr<4YX$dq5t~B4J#yGzR z>CFktY~lfxeTW25Ihy_Q3+f%l;)VII@-!hBV)4*=W*vU)Wn26A7eOQ75r3&Gl)Wta zbj!;^Np-%%Bs4F!O@+Edp)ACEm}nna2s97U^PQXg!W?OxRyf;7AD$za?#vt(yLN$@ z1v@+8MT1fRf|io|3qTx}M>hlX0D_dbh2>Bjp`8}f6*RL<@7c-dBVWJ#qwLR=&f)`n z>+1!gv?;F>Kd5E-XE3g6FQUC@z{F@(U#z^a5Xhy{b0le&wo04m@gkz%_A~HQr~2Aa z1FvEf7Da<7J=DK)_EbZWZEi^1HIU%VvOYG?$clLitKNs~%W4RUXrW-_$=H^;mspSl zE&Gw~600SXZC8|B7H!|6IFBcy>SEHaL8HqY8lFNA2McbKqlmTN?R@fE4tk1 zAepzG-<~M2ErY&eFzq>j>+HjpX0IpoBY(4l?b)@ZjtSTPqo?kfij@T7JWgSF{=ASo zM$GBkQ;%DGYB#n*?*yDtXVN*Cx_F&NI+-&cl)B`5Vo||GdxUQDI#;(KzrxyqIq>;^ z1=C1U>-{?;^?j?N)Q2cN@0$4

5I)7@4%+Zd1bWJqilPxgF!ew^3E4w$3jDHJVW| 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 K0AK6iQ~&^LJBbcampfire

market-stall

A procedural timber market stall through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract.

-

witnesses Recomputed: 4404 tris, three materials with 48 faces per awning stripe slot, UVs in 0..1 with zero AABB overlap, outer AABB 1.392×0.966×1.743 m, LOD ratios in band, convex collider 46 tris, non-empty glTF. --skip-decimate exits 9 on the LOD1 ratio budget.

+

witnesses Recomputed: 4324 tris, three materials with 34 faces per awning stripe slot, UVs in 0..1 with zero AABB overlap, outer AABB 1.389×1.011×1.740 m, grounded zmin, 4 wrap feet, brace-counter overlap 0, awning seat 4 mm, post plumb 0, LOD ratios in band, convex collider 58 tris, non-empty glTF. Hygiene all zero. --skip-decimate exits 9; --stray-vert 15; --lift-z/--short-feet 16; --low-brace 17; --float-awning 18; --rake-posts 19.

View example
@@ -991,7 +991,7 @@

fence-kit

watchtower

A procedural timber watchtower through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract.

-

witnesses Recomputed: 5220 tris, three materials with 84 metal and 126 roof faces, UVs in 0..1 with zero AABB overlap, outer AABB 1.434×1.434×1.982 m, LOD ratios in band, convex collider 56 tris, non-empty glTF. --skip-decimate exits 9 on the LOD1 ratio budget.

+

witnesses Recomputed: 6408 tris, three materials with 300 metal and 126 roof faces, UVs in 0..1 with zero AABB overlap, outer AABB 1.594×1.594×2.973 m, grounded zmin, 4 iron shoes, rail engage 18 mm, collar standoff 2 mm, post plumb 0, LOD ratios in band (5.2 COLLAPSE leaner on LOD2), convex collider 150 tris, non-empty glTF. Hygiene all zero. --skip-decimate exits 9; --stray-vert 15; --lift-z/--short-shoes 16; --short-rails 17; --float-band 18; --rake-posts 19.

View example
diff --git a/docs/gallery/watchtower/index.html b/docs/gallery/watchtower/index.html index f036db6..67d3407 100644 --- a/docs/gallery/watchtower/index.html +++ b/docs/gallery/watchtower/index.html @@ -253,28 +253,43 @@

watchtower

watchtower render

Rendered headless by the example itself — click to zoom.

-
witnesses Recomputed: 5220 tris, three materials with 84 metal and 126 roof faces, UVs in 0..1 with zero AABB overlap, outer AABB 1.434×1.434×1.982 m, LOD ratios in band, convex collider 56 tris, non-empty glTF. --skip-decimate exits 9 on the LOD1 ratio budget.
+
witnesses Recomputed: 6408 tris, three materials with 300 metal and 126 roof faces, UVs in 0..1 with zero AABB overlap, outer AABB 1.594×1.594×2.973 m, grounded zmin, 4 iron shoes, rail engage 18 mm, collar standoff 2 mm, post plumb 0, LOD ratios in band (5.2 COLLAPSE leaner on LOD2), convex collider 150 tris, non-empty glTF. Hygiene all zero. --skip-decimate exits 9; --stray-vert 15; --lift-z/--short-shoes 16; --short-rails 17; --float-band 18; --rake-posts 19.
blender --background --python showcase/watchtower/watchtower.py --
-

A showcase piece, not an example. Procedural timber lookout (corner posts through a plank platform, lower-bay X-braces, hatch and ladder, coursed shake roof with eaves) then the shipped pipeline: unique-cell UVs, Cycles high-to-low normal bake, LOD chain, convex collider, Unity glTF export.

+

A showcase piece, not an example. Procedural timber lookout (corner posts through a plank platform, lower-bay X-braces, hatch and ladder whose stiles plant at Z=0, coursed shake roof over a solid cone, mitered iron collars and shoes) then the shipped pipeline: unique-cell UVs, Cycles high-to-low normal bake, LOD chain, convex collider, Unity glTF export.

+

The old roof was a wood U with incomplete shake coverage; rails showed through the eave. The cone plus non-overlapping courses close the pyramid. Shoes are added after the zmin snap so a bevel undershoot cannot lift them.

It asserts budget conformance of the generated result. It does not witness an API contract. "It rendered without error" is not a check.

Composes skills mesh-editing-and-bmesh, bake-high-to-low, depsgraph-and-evaluated-data, engine-export-presets, and snippets bake_normal_high_to_low.py, setup_bake_target_image.py, lod_chain.py / decimate_to_budget.py, convex_hull_collider.py, export_preset_unity.py (helpers copied, not imported as a package).

Budgets

Declared as named constants; every gate recomputes from the mesh, materials, UVs, evaluated LOD, collider, or export file.

-

| Axis | Declared | Measured (4.5.11 / 5.1.2 / 5.2.1) | | --- | --- | --- | | Base triangles | 5110–5330 | 5220 / 5220 / 5220 | | LOD1 ratio | 0.32–0.62 of base | 0.5000 / 0.5000 / 0.5000 | | LOD2 ratio | 0.10–0.35 of base | 0.2199 / 0.2199 / 0.2199 | | Materials | exactly 3 distinct, ≥24 metal, ≥40 roof | 3 slots, 84 metal, 126 roof | | UVs | in 0..1, AABB overlap ≤ 1e-5 | in range, overlap 0 | | Outer AABB | (1.434, 1.434, 1.982) m ± 0.01 | (1.4340, 1.4340, 1.9818), zmin 0 | | Collider tris | ≤ 80 | 56 | | Export | written, size > 0 | 374376 / 374376 / 374360 bytes |

-

DECIMATE COLLAPSE triangle counts are not required to match across series — the gate is a ratio band, not an exact count. Bake pixels are stochastic; the gate is has_data plus operator FINISHED, not byte-identity. Construction uses no RNG.

-

--skip-decimate skips the LOD DECIMATE stage so LOD1 ratio is 1.0 and exit 9 fires. That is the named budget the falsifier violates.

+

| Axis | Declared | Measured (4.5.11 / 5.1.2 / 5.2.1) | | --- | --- | --- | | Base triangles | 6200–8200 | 6408 / 6408 / 6408 | | LOD1 ratio | 0.32–0.62 of base | 0.5000 / 0.5000 / 0.5000 | | LOD2 ratio | 0.10–0.35 of base | 0.2197 / 0.2197 / 0.2185 | | Materials | exactly 3 distinct; ≥80 metal, ≥40 roof faces | 3 slots; 300 metal, 126 roof | | UVs | in 0..1, AABB overlap ≤ 1e-5 | in range, overlap 0 | | Outer AABB | (1.594, 1.594, 2.973) m ± 0.01 | (1.5940, 1.5940, 2.9731), zmin 0 | | Collider tris | ≤ 180 | 150 | | Export | written, size > 0 | 464572 / 464572 / 464560 bytes |

+

Base triangles rose from 5220 to 6408 in the quality pass: a taller eave-true roof and coursed shakes, not hidden interior faces.

+

Hygiene

+

Recomputed from the generated mesh, not asserted about the script.

+

| Axis | Declared | Measured (all three) | | --- | --- | --- | | Non-manifold edges | 0 | 0 | | Loose verts / edges | 0 / 0 | 0 / 0 | | Doubles merged at 1e-5 | 0 | 0 | | Zero-area faces | 0 | 0 | | N-gons | 0 | 0 | | Coplanar disjoint face pairs | 0 | 0 | | Grounded: zmin | within 1e-4 of 0 | 0.0000 | | Named supports: 4 iron shoes | each zmin ≤ 1e-3 | 4, shoe_z 0.00000 | | Plan | 1.56 m eave × 2.97 m height ± 0.08 / ± 0.28 | 1.5940 × 2.9731 |

+

Joint fit and seat

+

| Axis | Declared | Measured (all three) | | --- | --- | --- | | Rail-to-post engage | ≥ 0.012 m | 0.0180 | | Girt-collar standoff | ≤ 0.010 m | 0.00200 | | Post plumb (XY drift) | ≤ 0.010 m | 0.00000 |

+

DECIMATE COLLAPSE triangle counts are not identical across series — 5.2.1 is leaner on LOD2. The gate is a ratio band, not an exact count. Bake pixels are stochastic; the gate is has_data plus operator FINISHED, not byte-identity. Construction uses no RNG. Export byte counts differ by 12 B on 5.2.1 (glTF serializer), not a gated axis.

+

Falsifiers

+

Each violates one named budget. All eight were run on 4.5.11, 5.1.2 and 5.2.1 and returned the same code on each.

+

| Flag | Budget violated | Exit | | --- | --- | --- | | --skip-decimate | LOD1 ratio band | 9 | | --stray-vert | loose vertex count is 0 | 15 | | --lift-z | bounding box zmin is 0 | 16 | | --short-shoes | named shoe supports at Z=0 | 16 | | --short-rails | rail-to-post joint fit | 17 | | --float-band | iron-collar seat | 18 | | --rake-posts | post plumb | 19 |

Run

blender --background --python watchtower.py --
 blender --background --python watchtower.py -- --skip-decimate
+blender --background --python watchtower.py -- --stray-vert
+blender --background --python watchtower.py -- --lift-z
+blender --background --python watchtower.py -- --short-shoes
+blender --background --python watchtower.py -- --short-rails
+blender --background --python watchtower.py -- --float-band
+blender --background --python watchtower.py -- --rake-posts
 blender --background --python watchtower.py -- --output tower.png
-

Smoke does not pass --output or --skip-decimate.

+

Smoke passes no flags.

Exit codes

-

File-local. 9 is a valid check code. 10 is reserved for gallery_framing.check_framing on the --output path.

-

| Code | Meaning | | --- | --- | | 0 | Success | | 1 | Uncaught exception (FATAL wrapper) | | 2 | argparse / usage | | 3 | Mesh did not build / no UV layer | | 4 | Base triangle count outside range | | 5 | Material count ≠ 3 distinct slots, or metal/roof faces missing | | 6 | UVs outside 0..1 | | 7 | UV AABB overlap above tolerance | | 8 | World AABB off declared outer size | | 9 | LOD ratio band (--skip-decimate lands here) | | 10 | Framing gate (render path only) | | 11 | Collider triangle count above ceiling | | 12 | Bake did not finish or image has no data | | 13 | Export file missing or empty | | 14 | --output produced no file |

+

File-local. 9 is a valid check code. 10 is reserved for gallery_framing.check_framing on the --output path. 1519 are the hygiene and joint-fit family.

+

| Code | Meaning | | --- | --- | | 0 | Success | | 1 | Uncaught exception (FATAL wrapper) | | 2 | argparse / usage | | 3 | Mesh did not build / no UV layer | | 4 | Base triangle count outside range | | 5 | Material count ≠ 3 distinct slots, or metal/roof faces missing | | 6 | UVs outside 0..1 | | 7 | UV AABB overlap above tolerance | | 8 | World AABB off declared outer size | | 9 | LOD ratio band (--skip-decimate lands here) | | 10 | Framing gate (render path only) | | 11 | Collider triangle count above ceiling | | 12 | Bake did not finish or image has no data | | 13 | Export file missing or empty | | 14 | --output produced no file | | 15 | Mesh hygiene: loose, non-manifold, zero-area, doubles, n-gons, z-fight | | 16 | Not grounded: bounding box zmin off 0, or a named shoe floats | | 17 | Joint fit: rail-to-post engage | | 18 | Seat: girt-collar standoff | | 19 | Post plumb or plan off the stated real-world size |

Source

@@ -290,8 +305,11 @@

Source

bake, LOD chain, convex collider, Unity glTF export. Budgets are declared below and recomputed from the generated result. -They are not API-contract witnesses. ``--skip-decimate`` skips the LOD -DECIMATE stage so the LOD-ratio budget fails. +They are not API-contract witnesses. Each falsifier violates one named +budget: ``--skip-decimate`` the LOD-ratio band, ``--stray-vert`` mesh +hygiene, ``--lift-z`` grounded zmin, ``--short-shoes`` named shoe +supports, ``--short-rails`` rail-to-post joint fit, ``--float-band`` +iron-collar seat, ``--rake-posts`` post plumb. No RNG. Construction is closed-form. DECIMATE COLLAPSE triangle counts are not byte-identical across Blender versions — the LOD gate is a @@ -319,28 +337,37 @@

Source

sys.dont_write_bytecode = True import gallery_framing # noqa: E402 -HALF = 0.46 +HALF = 0.52 POST = 0.090 -POST_H = 1.58 -PLAT_Z = 1.04 -RAIL_H = 0.40 -GIRT_Z = 0.52 -KICK_H = 0.18 -BRACE_T = 0.042 +POST_H = 2.42 +PLAT_Z = 1.72 PLANK_T = 0.038 -IRON_T = 0.014 -SHOE_H = 0.034 -EAVE_HALF = 0.70 -ROOF_RISE = 0.40 +# Guardrails sit in the bay between the deck and the eave — a 0.90 m +# code rail would punch through the roof. +RAIL_CLEAR = 0.10 +RAIL_H = POST_H - PLAT_Z - PLANK_T - RAIL_CLEAR +GIRT_Z = 0.86 +GIRT_H = 0.075 +SILL_H = 0.085 +KICK_H = 0.20 +BRACE_T = 0.042 +IRON_T = 0.012 +IRON_H = 0.028 +IRON_STANDOFF = 0.002 +SHOE_H = 0.040 +EAVE_HALF = 0.78 +ROOF_RISE = 0.55 SHINGLE_T = 0.016 N_SHINGLE = 5 N_PLANK = 6 -HATCH_PLANKS = 1 +HATCH_PLANKS = 2 +LEAN = 0.18 +RAIL_TENON = POST * 0.40 BBOX_TOL = 0.01 -OUTER_SIZE = (1.434, 1.434, 1.982) -BASE_TRIS_MIN = 5110 -BASE_TRIS_MAX = 5330 +OUTER_SIZE = (1.594, 1.594, 2.973) +BASE_TRIS_MIN = 6200 +BASE_TRIS_MAX = 8200 LOD1_RATIO_MIN = 0.32 LOD1_RATIO_MAX = 0.62 LOD2_RATIO_MIN = 0.10 @@ -350,11 +377,28 @@

Source

MATERIAL_COUNT = 3 UV_EPS = 1e-4 UV_OVERLAP_MAX = 1e-5 -COLLIDER_TRIS_MAX = 80 +COLLIDER_TRIS_MAX = 180 BAKE_RES = 256 CAGE_EXTRUSION = 0.10 -METAL_FACES_MIN = 24 +METAL_FACES_MIN = 80 ROOF_FACES_MIN = 40 +ZMIN_EPS = 1e-4 +DOUBLES_EPS = 1e-5 +AREA_EPS = 1e-10 +ZFIGHT_EPS = 1e-4 +ZFIGHT_COS = 0.998 +SHOE_COUNT = 4 +SHOE_ZMIN_MAX = 0.001 +RAIL_ENGAGE_MIN = 0.012 +BAND_SEAT_MAX = 0.010 +POST_PLUMB_MAX = 0.010 +PLAN_XY = 2.0 * EAVE_HALF +PLAN_Z = POST_H + ROOF_RISE +PLAN_TOL = 0.08 +LIFT_Z = 0.05 +RAKE = math.radians(2.0) +SHORT_SHOE_Z = 0.055 +BAND_FLOAT = 0.040 WOOD_IDX = 0 ROOF_IDX = 1 @@ -393,6 +437,20 @@

Source

ob.select_set(False) +def add_box(bm, loc, scale, mat_idx, euler=(0.0, 0.0, 0.0)): + geo = bmesh.ops.create_cube(bm, size=1.0) + verts = geo["verts"] + rot = Euler(euler).to_matrix() + origin = Vector(loc) + for v in verts: + p = Vector((v.co.x * scale[0], v.co.y * scale[1], v.co.z * scale[2])) + v.co = rot @ p + origin + faces = {f for v in verts for f in v.link_faces} + for f in faces: + f.material_index = mat_idx + return verts + + def add_oriented_box(bm, a, b, scale_xy, mat_idx): a = Vector(a) b = Vector(b) @@ -411,20 +469,6 @@

Source

) -def add_box(bm, loc, scale, mat_idx, euler=(0.0, 0.0, 0.0)): - geo = bmesh.ops.create_cube(bm, size=1.0) - verts = geo["verts"] - rot = Euler(euler).to_matrix() - origin = Vector(loc) - for v in verts: - p = Vector((v.co.x * scale[0], v.co.y * scale[1], v.co.z * scale[2])) - v.co = rot @ p + origin - faces = {f for v in verts for f in v.link_faces} - for f in faces: - f.material_index = mat_idx - return verts - - def add_cone(bm, loc, radius1, radius2, depth, segments, mat_idx, euler=(0.0, 0.0, 0.0)): geo = bmesh.ops.create_cone( bm, @@ -446,6 +490,21 @@

Source

return verts +def add_collar(bm, px, py, z, height, mat_idx, standoff=0.0): + """Four plates on the post faces — a wrap, not a cube through the timber. + + X-facing plates cover the post face only. Y-facing plates cover the + corners so the wrap meets at a butt instead of a coplanar overlap. + """ + half = POST / 2.0 + t = IRON_T + off = half + t / 2.0 + standoff + add_box(bm, (px + off, py, z), (t, POST, height), mat_idx) + add_box(bm, (px - off, py, z), (t, POST, height), mat_idx) + add_box(bm, (px, py + off, z), (POST + 2.0 * t, t, height), mat_idx) + add_box(bm, (px, py - off, z), (POST + 2.0 * t, t, height), mat_idx) + + def pack_uvs(bm, margin=0.08): uv = bm.loops.layers.uv.new("UVMap") faces = list(bm.faces) @@ -490,7 +549,9 @@

Source

def add_shingle_roof(bm, eave_z, peak_z): - """Well-style coursed shakes on a square pyramid, plus a cone underside.""" + """Coursed shakes on a solid square pyramid. Each course is offset along + the face normal so overlapping shakes do not share vertex positions. + """ add_cone( bm, (0.0, 0.0, (eave_z + peak_z) / 2.0), @@ -503,7 +564,7 @@

Source

) nrm_local = Vector((0.0, ROOF_RISE, EAVE_HALF)).normalized() - def add_course(yaw, t0, t1): + def add_course(yaw, t0, t1, row): rot = Euler((0.0, 0.0, yaw)).to_matrix() nrm = rot @ nrm_local @@ -513,8 +574,8 @@

Source

z = peak_z - t * ROOF_RISE return rot @ Vector((s * w, y, z)) - inner = SHINGLE_T * 0.12 - outer = SHINGLE_T * 1.05 + inner = SHINGLE_T * (0.20 + 0.22 * row) + outer = inner + SHINGLE_T * 0.90 corners = ( pt(t0, -1.0), pt(t0, 1.0), @@ -538,12 +599,20 @@

Source

for side in range(4): yaw = side * (math.pi / 2.0) for row in range(N_SHINGLE): - t0 = (row + 0.16) / N_SHINGLE - t1 = min(1.0, (row + 1.08) / N_SHINGLE) - add_course(yaw, t0, t1) - - -def build_tower_mesh(name, bevel_offset, bevel_segments): + t0 = (row + 0.14) / N_SHINGLE + t1 = min(0.96, (row + 1.0) / N_SHINGLE) + add_course(yaw, t0, t1, row) + + +def build_tower_mesh( + name, + bevel_offset, + bevel_segments, + short_shoes=False, + short_rails=False, + float_band=False, + rake_posts=False, +): bm = bmesh.new() try: wood = [] @@ -553,28 +622,38 @@

Source

(-HALF, HALF), (HALF, HALF), ) + rake = (RAKE, 0.0, 0.0) if rake_posts else (0.0, 0.0, 0.0) for px, py in posts: wood.extend( - add_box(bm, (px, py, POST_H / 2.0), (POST, POST, POST_H), WOOD_IDX) + add_box( + bm, + (px, py, POST_H / 2.0), + (POST, POST, POST_H), + WOOD_IDX, + euler=rake, + ) ) bay = 2.0 * HALF - POST - sill_h = 0.085 - girt_h = 0.075 + # Tenon into the posts; thickness under POST so side faces are not + # coplanar with the post faces. + member = bay + POST * 0.40 + section = POST * 0.70 + sill_z = SHOE_H + SILL_H / 2.0 for sign in (-1.0, 1.0): wood.extend( add_box( bm, - (0.0, sign * HALF, sill_h / 2.0), - (bay, POST * 0.82, sill_h), + (0.0, sign * HALF, sill_z), + (member, section, SILL_H), WOOD_IDX, ) ) wood.extend( add_box( bm, - (sign * HALF, 0.0, sill_h / 2.0), - (POST * 0.82, bay, sill_h), + (sign * HALF, 0.0, sill_z), + (section, member, SILL_H), WOOD_IDX, ) ) @@ -582,7 +661,7 @@

Source

add_box( bm, (0.0, sign * HALF, GIRT_Z), - (bay, POST * 0.78, girt_h), + (member, section, GIRT_H), WOOD_IDX, ) ) @@ -590,73 +669,96 @@

Source

add_box( bm, (sign * HALF, 0.0, GIRT_Z), - (POST * 0.78, bay, girt_h), + (section, member, GIRT_H), WOOD_IDX, ) ) - for ysign in (-1.0, 1.0): - if ysign < 0.0: - continue + # Kick sits on the sill, not on the same plane as the sill bottom. + kick_z = SHOE_H + SILL_H + KICK_H / 2.0 + kick_span = bay * 0.88 + wood.extend( + add_box( + bm, + (0.0, HALF - 0.020, kick_z), + (kick_span, POST * 0.50, KICK_H), + WOOD_IDX, + ) + ) + for xsign in (-1.0, 1.0): wood.extend( add_box( bm, - (0.0, ysign * HALF, KICK_H / 2.0 + 0.01), - (bay * 0.92, POST * 0.55, KICK_H), + (xsign * (HALF - 0.020), 0.0, kick_z), + (POST * 0.50, kick_span, KICK_H), WOOD_IDX, ) ) - for xsign in (-1.0, 1.0): + hatch_gap = 0.32 + kick_half = (bay * 0.92 - hatch_gap) / 2.0 + for xoff in (-(hatch_gap / 2.0 + kick_half / 2.0), hatch_gap / 2.0 + kick_half / 2.0): wood.extend( add_box( bm, - (xsign * HALF, 0.0, KICK_H / 2.0 + 0.01), - (POST * 0.55, bay * 0.92, KICK_H), + (xoff, -HALF + 0.020, kick_z), + (kick_half, POST * 0.55, KICK_H), WOOD_IDX, ) ) - rise = GIRT_Z - sill_h + rise = GIRT_Z - (SHOE_H + SILL_H) run = bay blen = math.hypot(run, rise) bang = math.atan2(rise, run) - brace_z = (sill_h + GIRT_Z) / 2.0 - for ysign in (-1.0, 1.0): - if ysign < 0.0: - continue - for bang_sign in (-1.0, 1.0): + brace_z = (SHOE_H + SILL_H + GIRT_Z) / 2.0 + # Lap the two diagonals by a brace thickness so they do not occupy + # one plane (the black diamond in the old crossing). + for ysign in (1.0,): + for k, bang_sign in enumerate((-1.0, 1.0)): wood.extend( add_box( bm, - (0.0, ysign * HALF, brace_z), + (0.0, ysign * HALF + ysign * k * (BRACE_T + 0.008), brace_z), (blen, BRACE_T, BRACE_T), WOOD_IDX, euler=(0.0, bang_sign * bang, 0.0), ) ) for xsign in (-1.0, 1.0): - for bang_sign in (-1.0, 1.0): + for k, bang_sign in enumerate((-1.0, 1.0)): wood.extend( add_box( bm, - (xsign * HALF, 0.0, brace_z), + (xsign * HALF + xsign * k * (BRACE_T + 0.008), 0.0, brace_z), (BRACE_T, blen, BRACE_T), WOOD_IDX, euler=(bang_sign * bang, 0.0, 0.0), ) ) + for xsign in (-1.0, 1.0): + wood.extend( + add_oriented_box( + bm, + (xsign * (HALF - POST * 0.2), -HALF, SHOE_H + SILL_H), + (xsign * 0.22, -HALF, GIRT_Z), + (BRACE_T, BRACE_T), + WOOD_IDX, + ) + ) plank_span_x = 2.0 * HALF + POST * 0.15 usable_y = 2.0 * HALF - 0.06 plank_w = usable_y / N_PLANK y0 = -HALF + 0.03 + plank_w / 2.0 + hatch_y1 = y0 + (HATCH_PLANKS - 0.5) * plank_w for i in range(HATCH_PLANKS, N_PLANK): y = y0 + i * plank_w + jw = 0.006 * math.sin(i * 1.7) wood.extend( add_box( bm, (0.0, y, PLAT_Z + PLANK_T / 2.0), - (plank_span_x, plank_w * 0.86, PLANK_T), + (plank_span_x + jw, plank_w * 0.90, PLANK_T), WOOD_IDX, ) ) @@ -664,61 +766,85 @@

Source

wood.extend( add_box( bm, - (xj, 0.06, PLAT_Z - 0.022), - (POST * 0.62, 2.0 * HALF * 0.88, 0.044), + (xj, 0.10, PLAT_Z - 0.022), + (POST * 0.62, 2.0 * HALF * 0.82, 0.044), + WOOD_IDX, + ) + ) + # Hatch trim: three boards around the missing planks. + wood.extend( + add_box( + bm, + (0.0, hatch_y1, PLAT_Z + PLANK_T / 2.0), + (0.46, 0.040, PLANK_T), + WOOD_IDX, + ) + ) + for xsign in (-1.0, 1.0): + wood.extend( + add_box( + bm, + (xsign * 0.23, -HALF + 0.12, PLAT_Z + PLANK_T / 2.0), + (0.040, hatch_y1 + HALF - 0.02, PLANK_T), WOOD_IDX, ) ) - rail_z_lo = PLAT_Z + PLANK_T + 0.12 - rail_z_hi = PLAT_Z + PLANK_T + RAIL_H - 0.04 + rail_z_lo = PLAT_Z + PLANK_T + 0.14 + rail_z_hi = PLAT_Z + PLANK_T + RAIL_H - 0.05 rail_t = POST * 0.42 + rail_len = bay * 0.70 if short_rails else bay + RAIL_TENON wood.extend( - add_box(bm, (0.0, HALF, rail_z_lo), (bay, rail_t, 0.048), WOOD_IDX) + add_box(bm, (0.0, HALF, rail_z_lo), (rail_len, rail_t, 0.048), WOOD_IDX) ) wood.extend( - add_box(bm, (0.0, HALF, rail_z_hi), (bay, rail_t, 0.048), WOOD_IDX) + add_box(bm, (0.0, HALF, rail_z_hi), (rail_len, rail_t, 0.048), WOOD_IDX) ) + hatch_clear = 0.30 + side_y0 = -HALF + hatch_clear + side_span = HALF - side_y0 + side_cy = (side_y0 + HALF) / 2.0 + side_len = side_span + (0.0 if short_rails else RAIL_TENON * 0.5) for xsign in (-1.0, 1.0): wood.extend( add_box( bm, - (xsign * HALF, 0.08, rail_z_lo), - (rail_t, bay * 0.78, 0.048), + (xsign * HALF, side_cy, rail_z_lo), + (rail_t, side_len, 0.048), WOOD_IDX, ) ) wood.extend( add_box( bm, - (xsign * HALF, 0.08, rail_z_hi), - (rail_t, bay * 0.78, 0.048), + (xsign * HALF, side_cy, rail_z_hi), + (rail_t, side_len, 0.048), WOOD_IDX, ) ) - lean = 0.20 - bot_y = -HALF - lean - top_y = -HALF + 0.02 - top_z = PLAT_Z + 0.18 - for sx in (-0.12, 0.12): + bot_y = -HALF - LEAN + top_y = -HALF + 0.10 + top_z = PLAT_Z + # Stiles plant at z=0. Separate foot cubes on the same plane as the + # stile bottoms were coplanar z-fights; a chamfered stile end is the + # foot. + for sx in (-0.13, 0.13): wood.extend( add_oriented_box( bm, - (sx, bot_y, 0.03), + (sx, bot_y, 0.0), (sx, top_y, top_z), - (0.040, 0.044), + (0.044, 0.050), WOOD_IDX, ) ) - n_rung = 8 + n_rung = 9 for i in range(n_rung): - t = (i + 1) / (n_rung + 1) - z = 0.08 + t * (PLAT_Z - 0.14) - y = bot_y + (top_y - bot_y) * ((z - 0.03) / (top_z - 0.03)) - wood.extend( - add_box(bm, (0.0, y, z), (0.26, 0.034, 0.032), WOOD_IDX) - ) + t = (i + 0.6) / (n_rung + 0.2) + z = 0.06 + t * (PLAT_Z - 0.14) + y = bot_y + (top_y - bot_y) * (z / max(top_z, 1e-6)) + wood.extend(add_box(bm, (0.0, y, z), (0.28, 0.034, 0.032), WOOD_IDX)) if bevel_offset > 0.0: edges = list({e for v in wood for e in v.link_edges}) @@ -739,6 +865,7 @@

Source

add_shingle_roof(bm, eave_z, peak_z) fascia_h = 0.048 fascia_t = 0.034 + fascia_span = 2.0 * EAVE_HALF - 0.10 for side in range(4): yaw = side * (math.pi / 2.0) fx = EAVE_HALF * math.sin(yaw) @@ -746,58 +873,44 @@

Source

if side % 2 == 0: add_box( bm, - (0.0, fy, eave_z - fascia_h / 2.0), - (2.0 * EAVE_HALF + fascia_t, fascia_t, fascia_h), + (0.0, fy, eave_z - fascia_h / 2.0 - 0.008), + (fascia_span, fascia_t, fascia_h), WOOD_IDX, ) else: add_box( bm, - (fx, 0.0, eave_z - fascia_h / 2.0), - (fascia_t, 2.0 * EAVE_HALF + fascia_t, fascia_h), + (fx, 0.0, eave_z - fascia_h / 2.0 - 0.008), + (fascia_t, fascia_span, fascia_h), WOOD_IDX, ) + zmin = min(v.co.z for v in bm.verts) + for v in bm.verts: + v.co.z -= zmin + if v.co.z < 0.005: + v.co.z = 0.0 + + shoe_z = SHORT_SHOE_Z if short_shoes else SHOE_H / 2.0 + shoe_h = 0.018 if short_shoes else SHOE_H + band_off = BAND_FLOAT if float_band else IRON_STANDOFF + girt_band_z = GIRT_Z + GIRT_H / 2.0 + IRON_H / 2.0 + deck_band_z = PLAT_Z + PLANK_T + IRON_H / 2.0 + 0.006 for px, py in posts: - add_box( - bm, - (px, py, SHOE_H / 2.0), - (POST * 1.38, POST * 1.38, SHOE_H), - METAL_IDX, + add_collar(bm, px, py, shoe_z, shoe_h, METAL_IDX, standoff=IRON_STANDOFF) + add_collar(bm, px, py, girt_band_z, IRON_H, METAL_IDX, standoff=band_off) + add_collar( + bm, px, py, deck_band_z, IRON_H, METAL_IDX, + standoff=band_off, ) + for sx in (-0.13, 0.13): add_box( bm, - (px, py, GIRT_Z), - (POST * 1.12, POST * 1.12, IRON_T), - METAL_IDX, - ) - add_box( - bm, - (px, py, PLAT_Z + PLANK_T + IRON_T / 2.0), - (POST * 1.18, POST * 1.18, IRON_T), - METAL_IDX, - ) - for sx in (-0.12, 0.12): - add_box( - bm, - (sx, -HALF - 0.02, PLAT_Z + 0.02), - (0.055, 0.06, IRON_T), + (sx, -HALF + 0.02, PLAT_Z + 0.02), + (0.050, 0.055, IRON_T), METAL_IDX, ) - xs = [v.co.x for v in bm.verts] - ys = [v.co.y for v in bm.verts] - zs = [v.co.z for v in bm.verts] - cx = 0.5 * (min(xs) + max(xs)) - cy = 0.5 * (min(ys) + max(ys)) - zmin = min(zs) - for v in bm.verts: - v.co.x -= cx - v.co.y -= cy - v.co.z -= zmin - if v.co.z < 0.0: - v.co.z = 0.0 - pack_uvs(bm) bmesh.ops.recalc_face_normals(bm, faces=list(bm.faces)) for face in bm.faces: @@ -867,6 +980,232 @@

Source

return min(us), min(vs), max(us), max(vs), overlap, len(aabbs) +def face_area(me, poly): + vs = [me.vertices[i].co for i in poly.vertices] + if len(vs) < 3: + return 0.0 + v0 = vs[0] + area = 0.0 + for i in range(1, len(vs) - 1): + area += (vs[i] - v0).cross(vs[i + 1] - v0).length * 0.5 + return area + + +def hygiene_audit(me): + nv, ne, nf = len(me.vertices), len(me.edges), len(me.polygons) + ngons = sum(1 for p in me.polygons if len(p.vertices) > 4) + zero_area = sum(1 for p in me.polygons if face_area(me, p) <= AREA_EPS) + bm = bmesh.new() + try: + bm.from_mesh(me) + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + loose_v = sum(1 for v in bm.verts if len(v.link_edges) == 0) + loose_e = sum(1 for e in bm.edges if len(e.link_faces) == 0) + nonman = sum(1 for e in bm.edges if not e.is_manifold) + ret = bmesh.ops.find_doubles(bm, verts=list(bm.verts), dist=DOUBLES_EPS) + doubles = len(ret.get("targetmap") or {}) + finally: + bm.free() + return { + "nv": nv, "ne": ne, "nf": nf, "ngons": ngons, + "loose_v": loose_v, "loose_e": loose_e, "nonman": nonman, + "zero_area": zero_area, "doubles": doubles, "euler": nv - ne + nf, + } + + +def zfight_pairs(me): + data = [ + (p.center.copy(), p.normal.copy(), frozenset(p.vertices)) + for p in me.polygons + ] + eps2 = ZFIGHT_EPS * ZFIGHT_EPS + count = 0 + for i in range(len(data)): + ci, ni, vi = data[i] + for j in range(i + 1, len(data)): + cj, nj, vj = data[j] + if (cj - ci).length_squared > eps2: + continue + if abs(ni.dot(nj)) <= ZFIGHT_COS: + continue + if vi & vj: + continue + count += 1 + return count + + +def shells(me): + neighbors = [[] for _ in range(len(me.vertices))] + for edge in me.edges: + a, b = edge.vertices + neighbors[a].append(b) + neighbors[b].append(a) + seen = [False] * len(me.vertices) + groups = [] + for start in range(len(me.vertices)): + if seen[start]: + continue + seen[start] = True + stack = [start] + group = [] + while stack: + current = stack.pop() + group.append(current) + for nxt in neighbors[current]: + if not seen[nxt]: + seen[nxt] = True + stack.append(nxt) + groups.append(group) + return groups + + +def shell_aabb(me, group): + pts = [me.vertices[i].co for i in group] + return ( + min(p.x for p in pts), min(p.y for p in pts), min(p.z for p in pts), + max(p.x for p in pts), max(p.y for p in pts), max(p.z for p in pts), + ) + + +def mat_of(me, group): + member = set(group) + for poly in me.polygons: + if all(i in member for i in poly.vertices): + return poly.material_index + return None + + +def support_audit(me): + plates = [] + for group in shells(me): + if mat_of(me, group) != METAL_IDX: + continue + a = shell_aabb(me, group) + cz = 0.5 * (a[2] + a[5]) + if cz > 0.12: + continue + plates.append(a) + corners = {} + for a in plates: + cx = 0.5 * (a[0] + a[3]) + cy = 0.5 * (a[1] + a[4]) + key = (round(cx * 4.0) / 4.0, round(cy * 4.0) / 4.0) + corners.setdefault(key, []).append(a) + zmin = min((a[2] for a in plates), default=99.0) + return {"shoes": len(corners), "shoe_z": zmin} + + +def joint_audit(me): + """Guardrail bite into the +Y posts.""" + posts = [] + rails = [] + for group in shells(me): + if mat_of(me, group) != WOOD_IDX: + continue + a = shell_aabb(me, group) + dx, dy, dz = a[3] - a[0], a[4] - a[1], a[5] - a[2] + cy = 0.5 * (a[1] + a[4]) + cz = 0.5 * (a[2] + a[5]) + if dz > POST_H * 0.55 and dx < POST * 2.2 and dy < POST * 2.2: + posts.append(a) + continue + if ( + cz > PLAT_Z + and dx > HALF + and dy < POST + and abs(cy - HALF) < POST + ): + rails.append(a) + back_posts = [p for p in posts if 0.5 * (p[1] + p[4]) > 0.0] + pairs = [ + min(r[3] - p[0], p[3] - r[0]) + for r in rails + for p in back_posts + ] + engage = min(pairs) if pairs else 1.0 + return {"posts": len(posts), "rails": len(rails), "engage": engage} + + +def band_seat(me): + """Standoff from each girt collar's inner face to the post outer face.""" + posts = [] + for group in shells(me): + if mat_of(me, group) != WOOD_IDX: + continue + a = shell_aabb(me, group) + dz = a[5] - a[2] + dx = a[3] - a[0] + dy = a[4] - a[1] + if dz > POST_H * 0.55 and dx < POST * 2.2 and dy < POST * 2.2: + posts.append(a) + if not posts: + return 99.0 + gaps = [] + for group in shells(me): + if mat_of(me, group) != METAL_IDX: + continue + a = shell_aabb(me, group) + cz = 0.5 * (a[2] + a[5]) + girt_band_z = GIRT_Z + GIRT_H / 2.0 + IRON_H / 2.0 + if abs(cz - girt_band_z) > IRON_H: + continue + cx = 0.5 * (a[0] + a[3]) + cy = 0.5 * (a[1] + a[4]) + dx = a[3] - a[0] + dy = a[4] - a[1] + nearest = min( + posts, + key=lambda p: abs(cx - 0.5 * (p[0] + p[3])) + abs(cy - 0.5 * (p[1] + p[4])), + ) + px = 0.5 * (nearest[0] + nearest[3]) + py = 0.5 * (nearest[1] + nearest[4]) + if dx <= dy: + inner = a[0] if cx > px else a[3] + gaps.append(abs(inner - (px + (POST / 2.0 if cx > px else -POST / 2.0)))) + else: + inner = a[1] if cy > py else a[4] + gaps.append(abs(inner - (py + (POST / 2.0 if cy > py else -POST / 2.0)))) + if not gaps: + return 99.0 + return max(gaps) + + +def plumb_audit(me): + drifts = [] + for group in shells(me): + if mat_of(me, group) != WOOD_IDX: + continue + a = shell_aabb(me, group) + dz = a[5] - a[2] + dx = a[3] - a[0] + dy = a[4] - a[1] + if dz < POST_H * 0.55 or dx > POST * 2.2 or dy > POST * 2.2: + continue + pts = [me.vertices[i].co for i in group] + zcut_lo = a[2] + 0.08 * dz + zcut_hi = a[5] - 0.08 * dz + lo = [p for p in pts if p.z <= zcut_lo] + hi = [p for p in pts if p.z >= zcut_hi] + if len(lo) < 3 or len(hi) < 3: + continue + c_lo = Vector((sum(p.x for p in lo) / len(lo), sum(p.y for p in lo) / len(lo))) + c_hi = Vector((sum(p.x for p in hi) / len(hi), sum(p.y for p in hi) / len(hi))) + drifts.append((c_hi - c_lo).length) + return max(drifts) if drifts else 99.0 + + +def add_stray_vert(me): + bm = bmesh.new() + try: + bm.from_mesh(me) + bm.verts.new((0.0, 0.0, PLAT_Z)) + bm.to_mesh(me) + me.update() + finally: + bm.free() + + def make_lod(obj, name, ratio, skip_decimate): mesh = obj.data.copy() lod = bpy.data.objects.new(name, mesh) @@ -953,16 +1292,47 @@

Source

) -def check(skip_decimate): +def check( + skip_decimate, + lift_z=False, + stray_vert=False, + short_shoes=False, + short_rails=False, + float_band=False, + rake_posts=False, +): bpy.ops.wm.read_factory_settings(use_empty=True) - low = build_tower_mesh("TowerLow", bevel_offset=0.006, bevel_segments=2) - high = build_tower_mesh("TowerHigh", bevel_offset=0.006, bevel_segments=4) + low = build_tower_mesh( + "TowerLow", + bevel_offset=0.006, + bevel_segments=2, + short_shoes=short_shoes, + short_rails=short_rails, + float_band=float_band, + rake_posts=rake_posts, + ) + high = build_tower_mesh( + "TowerHigh", + bevel_offset=0.006, + bevel_segments=4, + short_shoes=short_shoes, + short_rails=short_rails, + float_band=float_band, + rake_posts=rake_posts, + ) wood = principled("TowerWood", (0.42, 0.22, 0.08, 1.0), 0.0, 0.56) roof = principled("TowerShake", (0.30, 0.28, 0.26, 1.0), 0.0, 0.74) metal = principled("TowerIron", (0.12, 0.125, 0.14, 1.0), 1.0, 0.30) assign_slots(low, wood, roof, metal) assign_slots(high, wood, roof, metal) + if stray_vert: + add_stray_vert(low.data) + if lift_z: + for v in low.data.vertices: + v.co.z += LIFT_Z + low.data.update() + if low.data is None or len(low.data.polygons) < 6: return fail("tower mesh did not build", 3), None, None, None, None, None @@ -979,6 +1349,12 @@

Source

size_x = bb[3] - bb[0] size_y = bb[4] - bb[1] size_z = bb[5] - bb[2] + hyg = hygiene_audit(low.data) + zf = zfight_pairs(low.data) + sup = support_audit(low.data) + jnt = joint_audit(low.data) + seat = band_seat(low.data) + plumb = plumb_audit(low.data) img, tex = setup_bake_image(low, wood) if img is None: @@ -1024,6 +1400,16 @@

Source

f"measured collider_tris={col_tris} bake={bake_result} " f"bake_has_data={img.has_data} export_bytes={export_size}" ) + print( + f"measured hygiene loose_v={hyg['loose_v']} loose_e={hyg['loose_e']} " + f"nonman={hyg['nonman']} zero_area={hyg['zero_area']} " + f"doubles={hyg['doubles']} ngons={hyg['ngons']} zfight={zf}" + ) + print( + f"measured shoes={sup['shoes']} shoe_z={sup['shoe_z']:.5f} " + f"rail_engage={jnt['engage']:.4f} band_seat={seat:.5f} " + f"plumb={plumb:.5f}" + ) if not (BASE_TRIS_MIN <= base_tris <= BASE_TRIS_MAX): return fail( @@ -1088,6 +1474,40 @@

Source

), None, None, None, None, None if export_size <= 0: return fail("export file missing or empty", 13), None, None, None, None, None + if ( + hyg["loose_v"] or hyg["loose_e"] or hyg["nonman"] or hyg["zero_area"] + or hyg["doubles"] or hyg["ngons"] or zf + ): + return fail( + f"hygiene loose_v={hyg['loose_v']} loose_e={hyg['loose_e']} " + f"nonman={hyg['nonman']} zero_area={hyg['zero_area']} " + f"doubles={hyg['doubles']} ngons={hyg['ngons']} zfight={zf}", + 15, + ), None, None, None, None, None + if bb[2] > ZMIN_EPS or sup["shoes"] != SHOE_COUNT or sup["shoe_z"] > SHOE_ZMIN_MAX: + return fail( + f"grounded zmin={bb[2]:.5f} shoes={sup['shoes']} " + f"shoe_z={sup['shoe_z']:.5f}", + 16, + ), None, None, None, None, None + if jnt["engage"] < RAIL_ENGAGE_MIN: + return fail( + f"rail engage {jnt['engage']:.4f} < {RAIL_ENGAGE_MIN} " + f"posts={jnt['posts']} rails={jnt['rails']}", + 17, + ), None, None, None, None, None + if seat > BAND_SEAT_MAX: + return fail(f"band seat {seat:.5f} > {BAND_SEAT_MAX}", 18), None, None, None, None, None + if ( + plumb > POST_PLUMB_MAX + or abs(size_z - PLAN_Z) > PLAN_TOL + or abs(size_x - PLAN_XY) > PLAN_TOL + 0.20 + ): + return fail( + f"plumb={plumb:.5f} size=({size_x:.4f},{size_y:.4f},{size_z:.4f}) " + f"plan={PLAN_XY:.3f}x{PLAN_Z:.3f}", + 19, + ), None, None, None, None, None return 0, low, high, wood, tex, collider @@ -1152,13 +1572,19 @@

Source

light("Fill", (5.0, -3.6, 2.6), 48.0, 8.0, (0.72, 0.82, 1.0), (62, 0, 50)) light("Wedge", (2.4, 4.2, 4.1), 640.0, 5.5, (1.0, 0.70, 0.40), (-70, 0, 198)) + bb = world_bbox(low) + span = max(bb[3] - bb[0], bb[4] - bb[1], bb[5] - bb[2], 0.2) cam_data = bpy.data.cameras.new("Cam") cam_data.lens = 50.0 cam = bpy.data.objects.new("Cam", cam_data) - cam.location = (4.25, -5.80, 2.55) + cam.location = (span * 1.98, -span * 2.75, span * 1.13) scene.collection.objects.link(cam) aim = bpy.data.objects.new("Aim", None) - aim.location = (0.0, 0.0, 0.98) + aim.location = ( + 0.5 * (bb[0] + bb[3]), + 0.5 * (bb[1] + bb[4]), + 0.42 * (bb[2] + bb[5]), + ) scene.collection.objects.link(aim) con = cam.constraints.new("TRACK_TO") con.target = aim @@ -1201,14 +1627,24 @@

Source

p = argparse.ArgumentParser() p.add_argument("--output", default=None) p.add_argument("--engine", default="eevee", choices=("eevee", "cycles")) - p.add_argument( - "--skip-decimate", - action="store_true", - help="falsification: skip the LOD DECIMATE stage", - ) + p.add_argument("--skip-decimate", action="store_true") + p.add_argument("--stray-vert", action="store_true") + p.add_argument("--lift-z", action="store_true") + p.add_argument("--short-shoes", action="store_true") + p.add_argument("--short-rails", action="store_true") + p.add_argument("--float-band", action="store_true") + p.add_argument("--rake-posts", action="store_true") args = p.parse_args(argv) - code, low, _high, wood, tex, _col = check(args.skip_decimate) + code, low, _high, wood, tex, _col = check( + args.skip_decimate, + lift_z=args.lift_z, + stray_vert=args.stray_vert, + short_shoes=args.short_shoes, + short_rails=args.short_rails, + float_band=args.float_band, + rake_posts=args.rake_posts, + ) if code: return code if args.output: diff --git a/showcase/gallery.json b/showcase/gallery.json index 4de7eb5..bdaac1f 100644 --- a/showcase/gallery.json +++ b/showcase/gallery.json @@ -57,7 +57,7 @@ "name": "market-stall", "dir": "showcase/market-stall", "teaches": "A procedural timber market stall through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract.", - "witnessesFix": "Recomputed: 4404 tris, three materials with 48 faces per awning stripe slot, UVs in 0..1 with zero AABB overlap, outer AABB 1.392×0.966×1.743 m, LOD ratios in band, convex collider 46 tris, non-empty glTF. --skip-decimate exits 9 on the LOD1 ratio budget.", + "witnessesFix": "Recomputed: 4324 tris, three materials with 34 faces per awning stripe slot, UVs in 0..1 with zero AABB overlap, outer AABB 1.389×1.011×1.740 m, grounded zmin, 4 wrap feet, brace-counter overlap 0, awning seat 4 mm, post plumb 0, LOD ratios in band, convex collider 58 tris, non-empty glTF. Hygiene all zero. --skip-decimate exits 9; --stray-vert 15; --lift-z/--short-feet 16; --low-brace 17; --float-awning 18; --rake-posts 19.", "hero": "docs/gallery/assets/market-stall-hero.webp", "preview": "showcase/market-stall/preview.webp", "tags": [ @@ -117,7 +117,7 @@ "name": "watchtower", "dir": "showcase/watchtower", "teaches": "A procedural timber watchtower through UVs, bake, LOD, collider, and Unity glTF, asserting recomputed budgets rather than an API contract.", - "witnessesFix": "Recomputed: 5220 tris, three materials with 84 metal and 126 roof faces, UVs in 0..1 with zero AABB overlap, outer AABB 1.434×1.434×1.982 m, LOD ratios in band, convex collider 56 tris, non-empty glTF. --skip-decimate exits 9 on the LOD1 ratio budget.", + "witnessesFix": "Recomputed: 6408 tris, three materials with 300 metal and 126 roof faces, UVs in 0..1 with zero AABB overlap, outer AABB 1.594×1.594×2.973 m, grounded zmin, 4 iron shoes, rail engage 18 mm, collar standoff 2 mm, post plumb 0, LOD ratios in band (5.2 COLLAPSE leaner on LOD2), convex collider 150 tris, non-empty glTF. Hygiene all zero. --skip-decimate exits 9; --stray-vert 15; --lift-z/--short-shoes 16; --short-rails 17; --float-band 18; --rake-posts 19.", "hero": "docs/gallery/assets/watchtower-hero.webp", "preview": "showcase/watchtower/preview.webp", "tags": [ diff --git a/showcase/watchtower/README.md b/showcase/watchtower/README.md index 1735941..f0c99b5 100644 --- a/showcase/watchtower/README.md +++ b/showcase/watchtower/README.md @@ -1,11 +1,17 @@ # Watchtower A showcase piece, not an example. Procedural timber lookout (corner -posts through a plank platform, lower-bay X-braces, hatch and ladder, -coursed shake roof with eaves) then the shipped pipeline: unique-cell +posts through a plank platform, lower-bay X-braces, hatch and ladder +whose stiles plant at Z=0, coursed shake roof over a solid cone, +mitered iron collars and shoes) then the shipped pipeline: unique-cell UVs, Cycles high-to-low normal bake, LOD chain, convex collider, Unity glTF export. +The old roof was a wood U with incomplete shake coverage; rails showed +through the eave. The cone plus non-overlapping courses close the +pyramid. Shoes are added after the zmin snap so a bevel undershoot +cannot lift them. + It asserts **budget conformance** of the generated result. It does not witness an API contract. "It rendered without error" is not a check. @@ -22,37 +28,84 @@ materials, UVs, evaluated LOD, collider, or export file. | Axis | Declared | Measured (4.5.11 / 5.1.2 / 5.2.1) | | --- | --- | --- | -| Base triangles | 5110–5330 | 5220 / 5220 / 5220 | +| Base triangles | 6200–8200 | 6408 / 6408 / 6408 | | LOD1 ratio | 0.32–0.62 of base | 0.5000 / 0.5000 / 0.5000 | -| LOD2 ratio | 0.10–0.35 of base | 0.2199 / 0.2199 / 0.2199 | -| Materials | exactly 3 distinct, ≥24 metal, ≥40 roof | 3 slots, 84 metal, 126 roof | +| LOD2 ratio | 0.10–0.35 of base | 0.2197 / 0.2197 / 0.2185 | +| Materials | exactly 3 distinct; ≥80 metal, ≥40 roof faces | 3 slots; 300 metal, 126 roof | | UVs | in `0..1`, AABB overlap ≤ 1e-5 | in range, overlap 0 | -| Outer AABB | (1.434, 1.434, 1.982) m ± 0.01 | (1.4340, 1.4340, 1.9818), zmin 0 | -| Collider tris | ≤ 80 | 56 | -| Export | written, size > 0 | 374376 / 374376 / 374360 bytes | +| Outer AABB | (1.594, 1.594, 2.973) m ± 0.01 | (1.5940, 1.5940, 2.9731), zmin 0 | +| Collider tris | ≤ 180 | 150 | +| Export | written, size > 0 | 464572 / 464572 / 464560 bytes | + +Base triangles rose from **5220 to 6408** in the quality pass: a taller +eave-true roof and coursed shakes, not hidden interior faces. + +### Hygiene + +Recomputed from the generated mesh, not asserted about the script. + +| Axis | Declared | Measured (all three) | +| --- | --- | --- | +| Non-manifold edges | 0 | 0 | +| Loose verts / edges | 0 / 0 | 0 / 0 | +| Doubles merged at 1e-5 | 0 | 0 | +| Zero-area faces | 0 | 0 | +| N-gons | 0 | 0 | +| Coplanar disjoint face pairs | 0 | 0 | +| Grounded: `zmin` | within 1e-4 of 0 | 0.0000 | +| Named supports: 4 iron shoes | each `zmin` ≤ 1e-3 | 4, shoe_z 0.00000 | +| Plan | 1.56 m eave × 2.97 m height ± 0.08 / ± 0.28 | 1.5940 × 2.9731 | -DECIMATE COLLAPSE triangle counts are **not** required to match across -series — the gate is a ratio band, not an exact count. Bake pixels are -stochastic; the gate is `has_data` plus operator `FINISHED`, not -byte-identity. Construction uses no RNG. +### Joint fit and seat -`--skip-decimate` skips the LOD DECIMATE stage so LOD1 ratio is 1.0 and -exit 9 fires. That is the named budget the falsifier violates. +| Axis | Declared | Measured (all three) | +| --- | --- | --- | +| Rail-to-post engage | ≥ 0.012 m | 0.0180 | +| Girt-collar standoff | ≤ 0.010 m | 0.00200 | +| Post plumb (XY drift) | ≤ 0.010 m | 0.00000 | + +DECIMATE COLLAPSE triangle counts are **not** identical across series — +5.2.1 is leaner on LOD2. The gate is a ratio band, not an exact count. +Bake pixels are stochastic; the gate is `has_data` plus operator +`FINISHED`, not byte-identity. Construction uses no RNG. Export byte +counts differ by 12 B on 5.2.1 (glTF serializer), not a gated axis. + +### Falsifiers + +Each violates one named budget. All eight were run on 4.5.11, 5.1.2 and +5.2.1 and returned the same code on each. + +| Flag | Budget violated | Exit | +| --- | --- | --- | +| `--skip-decimate` | LOD1 ratio band | 9 | +| `--stray-vert` | loose vertex count is 0 | 15 | +| `--lift-z` | bounding box `zmin` is 0 | 16 | +| `--short-shoes` | named shoe supports at Z=0 | 16 | +| `--short-rails` | rail-to-post joint fit | 17 | +| `--float-band` | iron-collar seat | 18 | +| `--rake-posts` | post plumb | 19 | ## Run ```bash blender --background --python watchtower.py -- blender --background --python watchtower.py -- --skip-decimate +blender --background --python watchtower.py -- --stray-vert +blender --background --python watchtower.py -- --lift-z +blender --background --python watchtower.py -- --short-shoes +blender --background --python watchtower.py -- --short-rails +blender --background --python watchtower.py -- --float-band +blender --background --python watchtower.py -- --rake-posts blender --background --python watchtower.py -- --output tower.png ``` -Smoke does not pass `--output` or `--skip-decimate`. +Smoke passes no flags. ## Exit codes File-local. `9` is a valid check code. `10` is reserved for -`gallery_framing.check_framing` on the `--output` path. +`gallery_framing.check_framing` on the `--output` path. `15`–`19` are the +hygiene and joint-fit family. | Code | Meaning | | --- | --- | @@ -71,3 +124,8 @@ File-local. `9` is a valid check code. `10` is reserved for | 12 | Bake did not finish or image has no data | | 13 | Export file missing or empty | | 14 | `--output` produced no file | +| 15 | Mesh hygiene: loose, non-manifold, zero-area, doubles, n-gons, z-fight | +| 16 | Not grounded: bounding box `zmin` off 0, or a named shoe floats | +| 17 | Joint fit: rail-to-post engage | +| 18 | Seat: girt-collar standoff | +| 19 | Post plumb or plan off the stated real-world size | diff --git a/showcase/watchtower/preview.webp b/showcase/watchtower/preview.webp index 7a8c86a6df893668fb761cd730d9f7867a0ff4a0..9f5cdc00ef693885daece685da47e7606dcbbab5 100644 GIT binary patch literal 26364 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 P-E4Ru(oyV15fA_X-au}d literal 18508 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 z∓OC?!(?17o0e;G~INVvl`LU4H5?(NA*oRJAAyrg7Hx1A+Av%1-hFXId&UzU0D` z>l)nu1Pdgsnt*)vd2GgN!I^X57elY0)+6Hyyf=r`0wFx84d=jH*YShm7G2_pNZ7irZNr;0h<}i ze23M(#)f#a23h2YzKb>6=Teiqegdcams~0hQ;kX-5=2}0`I1M@m%;U! zKI@i2)gAWLvFLR;<4r27mcj3dOxG+j zxK6Du^(QX^A;XfC=d!`r-RJ8u_eG_L6nyxnB(RQ z#0Z?)gFni;f|0liE$2xvC=2#^$-@R z_}$+=g7xWej)}%ldV(n~p_65K71{63rc;su4xx=_GM{`%``ML-aF9e=YdiTq5M;YeB>mRu2|;I9taz?AnsXL(D-8{up6kOFeMigC*jRpF7} z05^nO0EqwtcURSwDnhsTq`84^k`b(i8h{F|fPKIK000002>3Wv_8j>O*4 zKmY&{a=o83Q{V)O8TUvcL^t&QdtFxv=V(&J;6|V2%UuQK5&SM17AO~UzMowk7#$8` LHS@w;CGY?MaR9+D diff --git a/showcase/watchtower/watchtower.py b/showcase/watchtower/watchtower.py index 9373d47..68aea62 100644 --- a/showcase/watchtower/watchtower.py +++ b/showcase/watchtower/watchtower.py @@ -6,8 +6,11 @@ bake, LOD chain, convex collider, Unity glTF export. Budgets are declared below and recomputed from the generated result. -They are not API-contract witnesses. ``--skip-decimate`` skips the LOD -DECIMATE stage so the LOD-ratio budget fails. +They are not API-contract witnesses. Each falsifier violates one named +budget: ``--skip-decimate`` the LOD-ratio band, ``--stray-vert`` mesh +hygiene, ``--lift-z`` grounded zmin, ``--short-shoes`` named shoe +supports, ``--short-rails`` rail-to-post joint fit, ``--float-band`` +iron-collar seat, ``--rake-posts`` post plumb. No RNG. Construction is closed-form. DECIMATE COLLAPSE triangle counts are not byte-identical across Blender versions — the LOD gate is a @@ -35,28 +38,37 @@ sys.dont_write_bytecode = True import gallery_framing # noqa: E402 -HALF = 0.46 +HALF = 0.52 POST = 0.090 -POST_H = 1.58 -PLAT_Z = 1.04 -RAIL_H = 0.40 -GIRT_Z = 0.52 -KICK_H = 0.18 -BRACE_T = 0.042 +POST_H = 2.42 +PLAT_Z = 1.72 PLANK_T = 0.038 -IRON_T = 0.014 -SHOE_H = 0.034 -EAVE_HALF = 0.70 -ROOF_RISE = 0.40 +# Guardrails sit in the bay between the deck and the eave — a 0.90 m +# code rail would punch through the roof. +RAIL_CLEAR = 0.10 +RAIL_H = POST_H - PLAT_Z - PLANK_T - RAIL_CLEAR +GIRT_Z = 0.86 +GIRT_H = 0.075 +SILL_H = 0.085 +KICK_H = 0.20 +BRACE_T = 0.042 +IRON_T = 0.012 +IRON_H = 0.028 +IRON_STANDOFF = 0.002 +SHOE_H = 0.040 +EAVE_HALF = 0.78 +ROOF_RISE = 0.55 SHINGLE_T = 0.016 N_SHINGLE = 5 N_PLANK = 6 -HATCH_PLANKS = 1 +HATCH_PLANKS = 2 +LEAN = 0.18 +RAIL_TENON = POST * 0.40 BBOX_TOL = 0.01 -OUTER_SIZE = (1.434, 1.434, 1.982) -BASE_TRIS_MIN = 5110 -BASE_TRIS_MAX = 5330 +OUTER_SIZE = (1.594, 1.594, 2.973) +BASE_TRIS_MIN = 6200 +BASE_TRIS_MAX = 8200 LOD1_RATIO_MIN = 0.32 LOD1_RATIO_MAX = 0.62 LOD2_RATIO_MIN = 0.10 @@ -66,11 +78,28 @@ MATERIAL_COUNT = 3 UV_EPS = 1e-4 UV_OVERLAP_MAX = 1e-5 -COLLIDER_TRIS_MAX = 80 +COLLIDER_TRIS_MAX = 180 BAKE_RES = 256 CAGE_EXTRUSION = 0.10 -METAL_FACES_MIN = 24 +METAL_FACES_MIN = 80 ROOF_FACES_MIN = 40 +ZMIN_EPS = 1e-4 +DOUBLES_EPS = 1e-5 +AREA_EPS = 1e-10 +ZFIGHT_EPS = 1e-4 +ZFIGHT_COS = 0.998 +SHOE_COUNT = 4 +SHOE_ZMIN_MAX = 0.001 +RAIL_ENGAGE_MIN = 0.012 +BAND_SEAT_MAX = 0.010 +POST_PLUMB_MAX = 0.010 +PLAN_XY = 2.0 * EAVE_HALF +PLAN_Z = POST_H + ROOF_RISE +PLAN_TOL = 0.08 +LIFT_Z = 0.05 +RAKE = math.radians(2.0) +SHORT_SHOE_Z = 0.055 +BAND_FLOAT = 0.040 WOOD_IDX = 0 ROOF_IDX = 1 @@ -109,6 +138,20 @@ def deselect_all(): ob.select_set(False) +def add_box(bm, loc, scale, mat_idx, euler=(0.0, 0.0, 0.0)): + geo = bmesh.ops.create_cube(bm, size=1.0) + verts = geo["verts"] + rot = Euler(euler).to_matrix() + origin = Vector(loc) + for v in verts: + p = Vector((v.co.x * scale[0], v.co.y * scale[1], v.co.z * scale[2])) + v.co = rot @ p + origin + faces = {f for v in verts for f in v.link_faces} + for f in faces: + f.material_index = mat_idx + return verts + + def add_oriented_box(bm, a, b, scale_xy, mat_idx): a = Vector(a) b = Vector(b) @@ -127,20 +170,6 @@ def add_oriented_box(bm, a, b, scale_xy, mat_idx): ) -def add_box(bm, loc, scale, mat_idx, euler=(0.0, 0.0, 0.0)): - geo = bmesh.ops.create_cube(bm, size=1.0) - verts = geo["verts"] - rot = Euler(euler).to_matrix() - origin = Vector(loc) - for v in verts: - p = Vector((v.co.x * scale[0], v.co.y * scale[1], v.co.z * scale[2])) - v.co = rot @ p + origin - faces = {f for v in verts for f in v.link_faces} - for f in faces: - f.material_index = mat_idx - return verts - - def add_cone(bm, loc, radius1, radius2, depth, segments, mat_idx, euler=(0.0, 0.0, 0.0)): geo = bmesh.ops.create_cone( bm, @@ -162,6 +191,21 @@ def add_cone(bm, loc, radius1, radius2, depth, segments, mat_idx, euler=(0.0, 0. return verts +def add_collar(bm, px, py, z, height, mat_idx, standoff=0.0): + """Four plates on the post faces — a wrap, not a cube through the timber. + + X-facing plates cover the post face only. Y-facing plates cover the + corners so the wrap meets at a butt instead of a coplanar overlap. + """ + half = POST / 2.0 + t = IRON_T + off = half + t / 2.0 + standoff + add_box(bm, (px + off, py, z), (t, POST, height), mat_idx) + add_box(bm, (px - off, py, z), (t, POST, height), mat_idx) + add_box(bm, (px, py + off, z), (POST + 2.0 * t, t, height), mat_idx) + add_box(bm, (px, py - off, z), (POST + 2.0 * t, t, height), mat_idx) + + def pack_uvs(bm, margin=0.08): uv = bm.loops.layers.uv.new("UVMap") faces = list(bm.faces) @@ -206,7 +250,9 @@ def pack_uvs(bm, margin=0.08): def add_shingle_roof(bm, eave_z, peak_z): - """Well-style coursed shakes on a square pyramid, plus a cone underside.""" + """Coursed shakes on a solid square pyramid. Each course is offset along + the face normal so overlapping shakes do not share vertex positions. + """ add_cone( bm, (0.0, 0.0, (eave_z + peak_z) / 2.0), @@ -219,7 +265,7 @@ def add_shingle_roof(bm, eave_z, peak_z): ) nrm_local = Vector((0.0, ROOF_RISE, EAVE_HALF)).normalized() - def add_course(yaw, t0, t1): + def add_course(yaw, t0, t1, row): rot = Euler((0.0, 0.0, yaw)).to_matrix() nrm = rot @ nrm_local @@ -229,8 +275,8 @@ def pt(t, s): z = peak_z - t * ROOF_RISE return rot @ Vector((s * w, y, z)) - inner = SHINGLE_T * 0.12 - outer = SHINGLE_T * 1.05 + inner = SHINGLE_T * (0.20 + 0.22 * row) + outer = inner + SHINGLE_T * 0.90 corners = ( pt(t0, -1.0), pt(t0, 1.0), @@ -254,12 +300,20 @@ def pt(t, s): for side in range(4): yaw = side * (math.pi / 2.0) for row in range(N_SHINGLE): - t0 = (row + 0.16) / N_SHINGLE - t1 = min(1.0, (row + 1.08) / N_SHINGLE) - add_course(yaw, t0, t1) - - -def build_tower_mesh(name, bevel_offset, bevel_segments): + t0 = (row + 0.14) / N_SHINGLE + t1 = min(0.96, (row + 1.0) / N_SHINGLE) + add_course(yaw, t0, t1, row) + + +def build_tower_mesh( + name, + bevel_offset, + bevel_segments, + short_shoes=False, + short_rails=False, + float_band=False, + rake_posts=False, +): bm = bmesh.new() try: wood = [] @@ -269,28 +323,38 @@ def build_tower_mesh(name, bevel_offset, bevel_segments): (-HALF, HALF), (HALF, HALF), ) + rake = (RAKE, 0.0, 0.0) if rake_posts else (0.0, 0.0, 0.0) for px, py in posts: wood.extend( - add_box(bm, (px, py, POST_H / 2.0), (POST, POST, POST_H), WOOD_IDX) + add_box( + bm, + (px, py, POST_H / 2.0), + (POST, POST, POST_H), + WOOD_IDX, + euler=rake, + ) ) bay = 2.0 * HALF - POST - sill_h = 0.085 - girt_h = 0.075 + # Tenon into the posts; thickness under POST so side faces are not + # coplanar with the post faces. + member = bay + POST * 0.40 + section = POST * 0.70 + sill_z = SHOE_H + SILL_H / 2.0 for sign in (-1.0, 1.0): wood.extend( add_box( bm, - (0.0, sign * HALF, sill_h / 2.0), - (bay, POST * 0.82, sill_h), + (0.0, sign * HALF, sill_z), + (member, section, SILL_H), WOOD_IDX, ) ) wood.extend( add_box( bm, - (sign * HALF, 0.0, sill_h / 2.0), - (POST * 0.82, bay, sill_h), + (sign * HALF, 0.0, sill_z), + (section, member, SILL_H), WOOD_IDX, ) ) @@ -298,7 +362,7 @@ def build_tower_mesh(name, bevel_offset, bevel_segments): add_box( bm, (0.0, sign * HALF, GIRT_Z), - (bay, POST * 0.78, girt_h), + (member, section, GIRT_H), WOOD_IDX, ) ) @@ -306,73 +370,96 @@ def build_tower_mesh(name, bevel_offset, bevel_segments): add_box( bm, (sign * HALF, 0.0, GIRT_Z), - (POST * 0.78, bay, girt_h), + (section, member, GIRT_H), WOOD_IDX, ) ) - for ysign in (-1.0, 1.0): - if ysign < 0.0: - continue + # Kick sits on the sill, not on the same plane as the sill bottom. + kick_z = SHOE_H + SILL_H + KICK_H / 2.0 + kick_span = bay * 0.88 + wood.extend( + add_box( + bm, + (0.0, HALF - 0.020, kick_z), + (kick_span, POST * 0.50, KICK_H), + WOOD_IDX, + ) + ) + for xsign in (-1.0, 1.0): wood.extend( add_box( bm, - (0.0, ysign * HALF, KICK_H / 2.0 + 0.01), - (bay * 0.92, POST * 0.55, KICK_H), + (xsign * (HALF - 0.020), 0.0, kick_z), + (POST * 0.50, kick_span, KICK_H), WOOD_IDX, ) ) - for xsign in (-1.0, 1.0): + hatch_gap = 0.32 + kick_half = (bay * 0.92 - hatch_gap) / 2.0 + for xoff in (-(hatch_gap / 2.0 + kick_half / 2.0), hatch_gap / 2.0 + kick_half / 2.0): wood.extend( add_box( bm, - (xsign * HALF, 0.0, KICK_H / 2.0 + 0.01), - (POST * 0.55, bay * 0.92, KICK_H), + (xoff, -HALF + 0.020, kick_z), + (kick_half, POST * 0.55, KICK_H), WOOD_IDX, ) ) - rise = GIRT_Z - sill_h + rise = GIRT_Z - (SHOE_H + SILL_H) run = bay blen = math.hypot(run, rise) bang = math.atan2(rise, run) - brace_z = (sill_h + GIRT_Z) / 2.0 - for ysign in (-1.0, 1.0): - if ysign < 0.0: - continue - for bang_sign in (-1.0, 1.0): + brace_z = (SHOE_H + SILL_H + GIRT_Z) / 2.0 + # Lap the two diagonals by a brace thickness so they do not occupy + # one plane (the black diamond in the old crossing). + for ysign in (1.0,): + for k, bang_sign in enumerate((-1.0, 1.0)): wood.extend( add_box( bm, - (0.0, ysign * HALF, brace_z), + (0.0, ysign * HALF + ysign * k * (BRACE_T + 0.008), brace_z), (blen, BRACE_T, BRACE_T), WOOD_IDX, euler=(0.0, bang_sign * bang, 0.0), ) ) for xsign in (-1.0, 1.0): - for bang_sign in (-1.0, 1.0): + for k, bang_sign in enumerate((-1.0, 1.0)): wood.extend( add_box( bm, - (xsign * HALF, 0.0, brace_z), + (xsign * HALF + xsign * k * (BRACE_T + 0.008), 0.0, brace_z), (BRACE_T, blen, BRACE_T), WOOD_IDX, euler=(bang_sign * bang, 0.0, 0.0), ) ) + for xsign in (-1.0, 1.0): + wood.extend( + add_oriented_box( + bm, + (xsign * (HALF - POST * 0.2), -HALF, SHOE_H + SILL_H), + (xsign * 0.22, -HALF, GIRT_Z), + (BRACE_T, BRACE_T), + WOOD_IDX, + ) + ) plank_span_x = 2.0 * HALF + POST * 0.15 usable_y = 2.0 * HALF - 0.06 plank_w = usable_y / N_PLANK y0 = -HALF + 0.03 + plank_w / 2.0 + hatch_y1 = y0 + (HATCH_PLANKS - 0.5) * plank_w for i in range(HATCH_PLANKS, N_PLANK): y = y0 + i * plank_w + jw = 0.006 * math.sin(i * 1.7) wood.extend( add_box( bm, (0.0, y, PLAT_Z + PLANK_T / 2.0), - (plank_span_x, plank_w * 0.86, PLANK_T), + (plank_span_x + jw, plank_w * 0.90, PLANK_T), WOOD_IDX, ) ) @@ -380,61 +467,85 @@ def build_tower_mesh(name, bevel_offset, bevel_segments): wood.extend( add_box( bm, - (xj, 0.06, PLAT_Z - 0.022), - (POST * 0.62, 2.0 * HALF * 0.88, 0.044), + (xj, 0.10, PLAT_Z - 0.022), + (POST * 0.62, 2.0 * HALF * 0.82, 0.044), + WOOD_IDX, + ) + ) + # Hatch trim: three boards around the missing planks. + wood.extend( + add_box( + bm, + (0.0, hatch_y1, PLAT_Z + PLANK_T / 2.0), + (0.46, 0.040, PLANK_T), + WOOD_IDX, + ) + ) + for xsign in (-1.0, 1.0): + wood.extend( + add_box( + bm, + (xsign * 0.23, -HALF + 0.12, PLAT_Z + PLANK_T / 2.0), + (0.040, hatch_y1 + HALF - 0.02, PLANK_T), WOOD_IDX, ) ) - rail_z_lo = PLAT_Z + PLANK_T + 0.12 - rail_z_hi = PLAT_Z + PLANK_T + RAIL_H - 0.04 + rail_z_lo = PLAT_Z + PLANK_T + 0.14 + rail_z_hi = PLAT_Z + PLANK_T + RAIL_H - 0.05 rail_t = POST * 0.42 + rail_len = bay * 0.70 if short_rails else bay + RAIL_TENON wood.extend( - add_box(bm, (0.0, HALF, rail_z_lo), (bay, rail_t, 0.048), WOOD_IDX) + add_box(bm, (0.0, HALF, rail_z_lo), (rail_len, rail_t, 0.048), WOOD_IDX) ) wood.extend( - add_box(bm, (0.0, HALF, rail_z_hi), (bay, rail_t, 0.048), WOOD_IDX) + add_box(bm, (0.0, HALF, rail_z_hi), (rail_len, rail_t, 0.048), WOOD_IDX) ) + hatch_clear = 0.30 + side_y0 = -HALF + hatch_clear + side_span = HALF - side_y0 + side_cy = (side_y0 + HALF) / 2.0 + side_len = side_span + (0.0 if short_rails else RAIL_TENON * 0.5) for xsign in (-1.0, 1.0): wood.extend( add_box( bm, - (xsign * HALF, 0.08, rail_z_lo), - (rail_t, bay * 0.78, 0.048), + (xsign * HALF, side_cy, rail_z_lo), + (rail_t, side_len, 0.048), WOOD_IDX, ) ) wood.extend( add_box( bm, - (xsign * HALF, 0.08, rail_z_hi), - (rail_t, bay * 0.78, 0.048), + (xsign * HALF, side_cy, rail_z_hi), + (rail_t, side_len, 0.048), WOOD_IDX, ) ) - lean = 0.20 - bot_y = -HALF - lean - top_y = -HALF + 0.02 - top_z = PLAT_Z + 0.18 - for sx in (-0.12, 0.12): + bot_y = -HALF - LEAN + top_y = -HALF + 0.10 + top_z = PLAT_Z + # Stiles plant at z=0. Separate foot cubes on the same plane as the + # stile bottoms were coplanar z-fights; a chamfered stile end is the + # foot. + for sx in (-0.13, 0.13): wood.extend( add_oriented_box( bm, - (sx, bot_y, 0.03), + (sx, bot_y, 0.0), (sx, top_y, top_z), - (0.040, 0.044), + (0.044, 0.050), WOOD_IDX, ) ) - n_rung = 8 + n_rung = 9 for i in range(n_rung): - t = (i + 1) / (n_rung + 1) - z = 0.08 + t * (PLAT_Z - 0.14) - y = bot_y + (top_y - bot_y) * ((z - 0.03) / (top_z - 0.03)) - wood.extend( - add_box(bm, (0.0, y, z), (0.26, 0.034, 0.032), WOOD_IDX) - ) + t = (i + 0.6) / (n_rung + 0.2) + z = 0.06 + t * (PLAT_Z - 0.14) + y = bot_y + (top_y - bot_y) * (z / max(top_z, 1e-6)) + wood.extend(add_box(bm, (0.0, y, z), (0.28, 0.034, 0.032), WOOD_IDX)) if bevel_offset > 0.0: edges = list({e for v in wood for e in v.link_edges}) @@ -455,6 +566,7 @@ def build_tower_mesh(name, bevel_offset, bevel_segments): add_shingle_roof(bm, eave_z, peak_z) fascia_h = 0.048 fascia_t = 0.034 + fascia_span = 2.0 * EAVE_HALF - 0.10 for side in range(4): yaw = side * (math.pi / 2.0) fx = EAVE_HALF * math.sin(yaw) @@ -462,58 +574,44 @@ def build_tower_mesh(name, bevel_offset, bevel_segments): if side % 2 == 0: add_box( bm, - (0.0, fy, eave_z - fascia_h / 2.0), - (2.0 * EAVE_HALF + fascia_t, fascia_t, fascia_h), + (0.0, fy, eave_z - fascia_h / 2.0 - 0.008), + (fascia_span, fascia_t, fascia_h), WOOD_IDX, ) else: add_box( bm, - (fx, 0.0, eave_z - fascia_h / 2.0), - (fascia_t, 2.0 * EAVE_HALF + fascia_t, fascia_h), + (fx, 0.0, eave_z - fascia_h / 2.0 - 0.008), + (fascia_t, fascia_span, fascia_h), WOOD_IDX, ) + zmin = min(v.co.z for v in bm.verts) + for v in bm.verts: + v.co.z -= zmin + if v.co.z < 0.005: + v.co.z = 0.0 + + shoe_z = SHORT_SHOE_Z if short_shoes else SHOE_H / 2.0 + shoe_h = 0.018 if short_shoes else SHOE_H + band_off = BAND_FLOAT if float_band else IRON_STANDOFF + girt_band_z = GIRT_Z + GIRT_H / 2.0 + IRON_H / 2.0 + deck_band_z = PLAT_Z + PLANK_T + IRON_H / 2.0 + 0.006 for px, py in posts: - add_box( - bm, - (px, py, SHOE_H / 2.0), - (POST * 1.38, POST * 1.38, SHOE_H), - METAL_IDX, - ) - add_box( - bm, - (px, py, GIRT_Z), - (POST * 1.12, POST * 1.12, IRON_T), - METAL_IDX, + add_collar(bm, px, py, shoe_z, shoe_h, METAL_IDX, standoff=IRON_STANDOFF) + add_collar(bm, px, py, girt_band_z, IRON_H, METAL_IDX, standoff=band_off) + add_collar( + bm, px, py, deck_band_z, IRON_H, METAL_IDX, + standoff=band_off, ) + for sx in (-0.13, 0.13): add_box( bm, - (px, py, PLAT_Z + PLANK_T + IRON_T / 2.0), - (POST * 1.18, POST * 1.18, IRON_T), - METAL_IDX, - ) - for sx in (-0.12, 0.12): - add_box( - bm, - (sx, -HALF - 0.02, PLAT_Z + 0.02), - (0.055, 0.06, IRON_T), + (sx, -HALF + 0.02, PLAT_Z + 0.02), + (0.050, 0.055, IRON_T), METAL_IDX, ) - xs = [v.co.x for v in bm.verts] - ys = [v.co.y for v in bm.verts] - zs = [v.co.z for v in bm.verts] - cx = 0.5 * (min(xs) + max(xs)) - cy = 0.5 * (min(ys) + max(ys)) - zmin = min(zs) - for v in bm.verts: - v.co.x -= cx - v.co.y -= cy - v.co.z -= zmin - if v.co.z < 0.0: - v.co.z = 0.0 - pack_uvs(bm) bmesh.ops.recalc_face_normals(bm, faces=list(bm.faces)) for face in bm.faces: @@ -583,6 +681,232 @@ def uv_stats(mesh): return min(us), min(vs), max(us), max(vs), overlap, len(aabbs) +def face_area(me, poly): + vs = [me.vertices[i].co for i in poly.vertices] + if len(vs) < 3: + return 0.0 + v0 = vs[0] + area = 0.0 + for i in range(1, len(vs) - 1): + area += (vs[i] - v0).cross(vs[i + 1] - v0).length * 0.5 + return area + + +def hygiene_audit(me): + nv, ne, nf = len(me.vertices), len(me.edges), len(me.polygons) + ngons = sum(1 for p in me.polygons if len(p.vertices) > 4) + zero_area = sum(1 for p in me.polygons if face_area(me, p) <= AREA_EPS) + bm = bmesh.new() + try: + bm.from_mesh(me) + bm.verts.ensure_lookup_table() + bm.edges.ensure_lookup_table() + loose_v = sum(1 for v in bm.verts if len(v.link_edges) == 0) + loose_e = sum(1 for e in bm.edges if len(e.link_faces) == 0) + nonman = sum(1 for e in bm.edges if not e.is_manifold) + ret = bmesh.ops.find_doubles(bm, verts=list(bm.verts), dist=DOUBLES_EPS) + doubles = len(ret.get("targetmap") or {}) + finally: + bm.free() + return { + "nv": nv, "ne": ne, "nf": nf, "ngons": ngons, + "loose_v": loose_v, "loose_e": loose_e, "nonman": nonman, + "zero_area": zero_area, "doubles": doubles, "euler": nv - ne + nf, + } + + +def zfight_pairs(me): + data = [ + (p.center.copy(), p.normal.copy(), frozenset(p.vertices)) + for p in me.polygons + ] + eps2 = ZFIGHT_EPS * ZFIGHT_EPS + count = 0 + for i in range(len(data)): + ci, ni, vi = data[i] + for j in range(i + 1, len(data)): + cj, nj, vj = data[j] + if (cj - ci).length_squared > eps2: + continue + if abs(ni.dot(nj)) <= ZFIGHT_COS: + continue + if vi & vj: + continue + count += 1 + return count + + +def shells(me): + neighbors = [[] for _ in range(len(me.vertices))] + for edge in me.edges: + a, b = edge.vertices + neighbors[a].append(b) + neighbors[b].append(a) + seen = [False] * len(me.vertices) + groups = [] + for start in range(len(me.vertices)): + if seen[start]: + continue + seen[start] = True + stack = [start] + group = [] + while stack: + current = stack.pop() + group.append(current) + for nxt in neighbors[current]: + if not seen[nxt]: + seen[nxt] = True + stack.append(nxt) + groups.append(group) + return groups + + +def shell_aabb(me, group): + pts = [me.vertices[i].co for i in group] + return ( + min(p.x for p in pts), min(p.y for p in pts), min(p.z for p in pts), + max(p.x for p in pts), max(p.y for p in pts), max(p.z for p in pts), + ) + + +def mat_of(me, group): + member = set(group) + for poly in me.polygons: + if all(i in member for i in poly.vertices): + return poly.material_index + return None + + +def support_audit(me): + plates = [] + for group in shells(me): + if mat_of(me, group) != METAL_IDX: + continue + a = shell_aabb(me, group) + cz = 0.5 * (a[2] + a[5]) + if cz > 0.12: + continue + plates.append(a) + corners = {} + for a in plates: + cx = 0.5 * (a[0] + a[3]) + cy = 0.5 * (a[1] + a[4]) + key = (round(cx * 4.0) / 4.0, round(cy * 4.0) / 4.0) + corners.setdefault(key, []).append(a) + zmin = min((a[2] for a in plates), default=99.0) + return {"shoes": len(corners), "shoe_z": zmin} + + +def joint_audit(me): + """Guardrail bite into the +Y posts.""" + posts = [] + rails = [] + for group in shells(me): + if mat_of(me, group) != WOOD_IDX: + continue + a = shell_aabb(me, group) + dx, dy, dz = a[3] - a[0], a[4] - a[1], a[5] - a[2] + cy = 0.5 * (a[1] + a[4]) + cz = 0.5 * (a[2] + a[5]) + if dz > POST_H * 0.55 and dx < POST * 2.2 and dy < POST * 2.2: + posts.append(a) + continue + if ( + cz > PLAT_Z + and dx > HALF + and dy < POST + and abs(cy - HALF) < POST + ): + rails.append(a) + back_posts = [p for p in posts if 0.5 * (p[1] + p[4]) > 0.0] + pairs = [ + min(r[3] - p[0], p[3] - r[0]) + for r in rails + for p in back_posts + ] + engage = min(pairs) if pairs else 1.0 + return {"posts": len(posts), "rails": len(rails), "engage": engage} + + +def band_seat(me): + """Standoff from each girt collar's inner face to the post outer face.""" + posts = [] + for group in shells(me): + if mat_of(me, group) != WOOD_IDX: + continue + a = shell_aabb(me, group) + dz = a[5] - a[2] + dx = a[3] - a[0] + dy = a[4] - a[1] + if dz > POST_H * 0.55 and dx < POST * 2.2 and dy < POST * 2.2: + posts.append(a) + if not posts: + return 99.0 + gaps = [] + for group in shells(me): + if mat_of(me, group) != METAL_IDX: + continue + a = shell_aabb(me, group) + cz = 0.5 * (a[2] + a[5]) + girt_band_z = GIRT_Z + GIRT_H / 2.0 + IRON_H / 2.0 + if abs(cz - girt_band_z) > IRON_H: + continue + cx = 0.5 * (a[0] + a[3]) + cy = 0.5 * (a[1] + a[4]) + dx = a[3] - a[0] + dy = a[4] - a[1] + nearest = min( + posts, + key=lambda p: abs(cx - 0.5 * (p[0] + p[3])) + abs(cy - 0.5 * (p[1] + p[4])), + ) + px = 0.5 * (nearest[0] + nearest[3]) + py = 0.5 * (nearest[1] + nearest[4]) + if dx <= dy: + inner = a[0] if cx > px else a[3] + gaps.append(abs(inner - (px + (POST / 2.0 if cx > px else -POST / 2.0)))) + else: + inner = a[1] if cy > py else a[4] + gaps.append(abs(inner - (py + (POST / 2.0 if cy > py else -POST / 2.0)))) + if not gaps: + return 99.0 + return max(gaps) + + +def plumb_audit(me): + drifts = [] + for group in shells(me): + if mat_of(me, group) != WOOD_IDX: + continue + a = shell_aabb(me, group) + dz = a[5] - a[2] + dx = a[3] - a[0] + dy = a[4] - a[1] + if dz < POST_H * 0.55 or dx > POST * 2.2 or dy > POST * 2.2: + continue + pts = [me.vertices[i].co for i in group] + zcut_lo = a[2] + 0.08 * dz + zcut_hi = a[5] - 0.08 * dz + lo = [p for p in pts if p.z <= zcut_lo] + hi = [p for p in pts if p.z >= zcut_hi] + if len(lo) < 3 or len(hi) < 3: + continue + c_lo = Vector((sum(p.x for p in lo) / len(lo), sum(p.y for p in lo) / len(lo))) + c_hi = Vector((sum(p.x for p in hi) / len(hi), sum(p.y for p in hi) / len(hi))) + drifts.append((c_hi - c_lo).length) + return max(drifts) if drifts else 99.0 + + +def add_stray_vert(me): + bm = bmesh.new() + try: + bm.from_mesh(me) + bm.verts.new((0.0, 0.0, PLAT_Z)) + bm.to_mesh(me) + me.update() + finally: + bm.free() + + def make_lod(obj, name, ratio, skip_decimate): mesh = obj.data.copy() lod = bpy.data.objects.new(name, mesh) @@ -669,16 +993,47 @@ def export_unity(path, objects): ) -def check(skip_decimate): +def check( + skip_decimate, + lift_z=False, + stray_vert=False, + short_shoes=False, + short_rails=False, + float_band=False, + rake_posts=False, +): bpy.ops.wm.read_factory_settings(use_empty=True) - low = build_tower_mesh("TowerLow", bevel_offset=0.006, bevel_segments=2) - high = build_tower_mesh("TowerHigh", bevel_offset=0.006, bevel_segments=4) + low = build_tower_mesh( + "TowerLow", + bevel_offset=0.006, + bevel_segments=2, + short_shoes=short_shoes, + short_rails=short_rails, + float_band=float_band, + rake_posts=rake_posts, + ) + high = build_tower_mesh( + "TowerHigh", + bevel_offset=0.006, + bevel_segments=4, + short_shoes=short_shoes, + short_rails=short_rails, + float_band=float_band, + rake_posts=rake_posts, + ) wood = principled("TowerWood", (0.42, 0.22, 0.08, 1.0), 0.0, 0.56) roof = principled("TowerShake", (0.30, 0.28, 0.26, 1.0), 0.0, 0.74) metal = principled("TowerIron", (0.12, 0.125, 0.14, 1.0), 1.0, 0.30) assign_slots(low, wood, roof, metal) assign_slots(high, wood, roof, metal) + if stray_vert: + add_stray_vert(low.data) + if lift_z: + for v in low.data.vertices: + v.co.z += LIFT_Z + low.data.update() + if low.data is None or len(low.data.polygons) < 6: return fail("tower mesh did not build", 3), None, None, None, None, None @@ -695,6 +1050,12 @@ def check(skip_decimate): size_x = bb[3] - bb[0] size_y = bb[4] - bb[1] size_z = bb[5] - bb[2] + hyg = hygiene_audit(low.data) + zf = zfight_pairs(low.data) + sup = support_audit(low.data) + jnt = joint_audit(low.data) + seat = band_seat(low.data) + plumb = plumb_audit(low.data) img, tex = setup_bake_image(low, wood) if img is None: @@ -740,6 +1101,16 @@ def check(skip_decimate): f"measured collider_tris={col_tris} bake={bake_result} " f"bake_has_data={img.has_data} export_bytes={export_size}" ) + print( + f"measured hygiene loose_v={hyg['loose_v']} loose_e={hyg['loose_e']} " + f"nonman={hyg['nonman']} zero_area={hyg['zero_area']} " + f"doubles={hyg['doubles']} ngons={hyg['ngons']} zfight={zf}" + ) + print( + f"measured shoes={sup['shoes']} shoe_z={sup['shoe_z']:.5f} " + f"rail_engage={jnt['engage']:.4f} band_seat={seat:.5f} " + f"plumb={plumb:.5f}" + ) if not (BASE_TRIS_MIN <= base_tris <= BASE_TRIS_MAX): return fail( @@ -804,6 +1175,40 @@ def check(skip_decimate): ), None, None, None, None, None if export_size <= 0: return fail("export file missing or empty", 13), None, None, None, None, None + if ( + hyg["loose_v"] or hyg["loose_e"] or hyg["nonman"] or hyg["zero_area"] + or hyg["doubles"] or hyg["ngons"] or zf + ): + return fail( + f"hygiene loose_v={hyg['loose_v']} loose_e={hyg['loose_e']} " + f"nonman={hyg['nonman']} zero_area={hyg['zero_area']} " + f"doubles={hyg['doubles']} ngons={hyg['ngons']} zfight={zf}", + 15, + ), None, None, None, None, None + if bb[2] > ZMIN_EPS or sup["shoes"] != SHOE_COUNT or sup["shoe_z"] > SHOE_ZMIN_MAX: + return fail( + f"grounded zmin={bb[2]:.5f} shoes={sup['shoes']} " + f"shoe_z={sup['shoe_z']:.5f}", + 16, + ), None, None, None, None, None + if jnt["engage"] < RAIL_ENGAGE_MIN: + return fail( + f"rail engage {jnt['engage']:.4f} < {RAIL_ENGAGE_MIN} " + f"posts={jnt['posts']} rails={jnt['rails']}", + 17, + ), None, None, None, None, None + if seat > BAND_SEAT_MAX: + return fail(f"band seat {seat:.5f} > {BAND_SEAT_MAX}", 18), None, None, None, None, None + if ( + plumb > POST_PLUMB_MAX + or abs(size_z - PLAN_Z) > PLAN_TOL + or abs(size_x - PLAN_XY) > PLAN_TOL + 0.20 + ): + return fail( + f"plumb={plumb:.5f} size=({size_x:.4f},{size_y:.4f},{size_z:.4f}) " + f"plan={PLAN_XY:.3f}x{PLAN_Z:.3f}", + 19, + ), None, None, None, None, None return 0, low, high, wood, tex, collider @@ -868,13 +1273,19 @@ def light(name, loc, energy, size, col, rot): light("Fill", (5.0, -3.6, 2.6), 48.0, 8.0, (0.72, 0.82, 1.0), (62, 0, 50)) light("Wedge", (2.4, 4.2, 4.1), 640.0, 5.5, (1.0, 0.70, 0.40), (-70, 0, 198)) + bb = world_bbox(low) + span = max(bb[3] - bb[0], bb[4] - bb[1], bb[5] - bb[2], 0.2) cam_data = bpy.data.cameras.new("Cam") cam_data.lens = 50.0 cam = bpy.data.objects.new("Cam", cam_data) - cam.location = (4.25, -5.80, 2.55) + cam.location = (span * 1.98, -span * 2.75, span * 1.13) scene.collection.objects.link(cam) aim = bpy.data.objects.new("Aim", None) - aim.location = (0.0, 0.0, 0.98) + aim.location = ( + 0.5 * (bb[0] + bb[3]), + 0.5 * (bb[1] + bb[4]), + 0.42 * (bb[2] + bb[5]), + ) scene.collection.objects.link(aim) con = cam.constraints.new("TRACK_TO") con.target = aim @@ -917,14 +1328,24 @@ def main(): p = argparse.ArgumentParser() p.add_argument("--output", default=None) p.add_argument("--engine", default="eevee", choices=("eevee", "cycles")) - p.add_argument( - "--skip-decimate", - action="store_true", - help="falsification: skip the LOD DECIMATE stage", - ) + p.add_argument("--skip-decimate", action="store_true") + p.add_argument("--stray-vert", action="store_true") + p.add_argument("--lift-z", action="store_true") + p.add_argument("--short-shoes", action="store_true") + p.add_argument("--short-rails", action="store_true") + p.add_argument("--float-band", action="store_true") + p.add_argument("--rake-posts", action="store_true") args = p.parse_args(argv) - code, low, _high, wood, tex, _col = check(args.skip_decimate) + code, low, _high, wood, tex, _col = check( + args.skip_decimate, + lift_z=args.lift_z, + stray_vert=args.stray_vert, + short_shoes=args.short_shoes, + short_rails=args.short_rails, + float_band=args.float_band, + rake_posts=args.rake_posts, + ) if code: return code if args.output: