From 65a78fb0210645d59f83a8fec902b2759ad8f01b Mon Sep 17 00:00:00 2001 From: Adnane Belmadiaf Date: Sun, 5 Oct 2025 00:39:24 +0200 Subject: [PATCH] feat(RibbonFilter): add vtkRibbonFilter --- .../content/docs/gallery/RibbonFilter.jpg | Bin 0 -> 40300 bytes Documentation/content/examples/index.md | 3 + .../Modeling/RibbonFilter/example/index.js | 73 +++ .../Filters/Modeling/RibbonFilter/index.d.ts | 196 +++++++ .../Filters/Modeling/RibbonFilter/index.js | 493 ++++++++++++++++++ Sources/Filters/Modeling/index.js | 5 + 6 files changed, 770 insertions(+) create mode 100644 Documentation/content/docs/gallery/RibbonFilter.jpg create mode 100644 Sources/Filters/Modeling/RibbonFilter/example/index.js create mode 100644 Sources/Filters/Modeling/RibbonFilter/index.d.ts create mode 100644 Sources/Filters/Modeling/RibbonFilter/index.js create mode 100644 Sources/Filters/Modeling/index.js diff --git a/Documentation/content/docs/gallery/RibbonFilter.jpg b/Documentation/content/docs/gallery/RibbonFilter.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c0fa80203130c8f500b2edffeb42381f70274742 GIT binary patch literal 40300 zcmd42byOTrw9drYIp7G?&@7zU*=v`0ZeIeDRBS>1_qFP-GG;Mm^mpC5j_PZd2y+4 zVy_JV05br*7Wgj!0J8aMuO#u6L|sFZ1Zfq3dENe_7#KPH_)qA6d3?pXp7~GQNrwL= z#{Y9Aim{1<(JRX7>rP?+O8j+9IIl2{*?+>+|G|4b)%xPK z5s;AKP~O1)(=Lt)z{0@6!o$HMA;H1H!6E@Luy6o80wNX?_B&E!W?>vMMFSKTJKvb> zQ(W?|kk0Wl3Z*qX5kpqC*!T97qGEnI)$6bQ;J;FU{lAa_;NW3k5fG7H8)aVy1Pcqm zA;2TSBOxHd|3l=p84H_~8R4C7Y+k|#z+gC!Xz`u~FysvHpe zCG%;p+HN{|@gY}7j|cm5-bAxEKYQB@tE>qKRAGM`&KEE)8Ur=l`69;9U1mkGEhm(n zM~_PU(QLvatfIaqQij2a2li7h1syK@VbwfbCSy%bqvTK-VbBzj57@{nmL};Q&Ft-o z?->&$OESTFfpI{R1|;yswAgq`P4@0VS0ug|wW`6Kw*{n;B0 zy?~ZgPU!xzDY$&v_k96kmW6&C6LM)Su582irFiEo^rKViimfhcsDJm|?6G<#&|xMD zWH6adyqU1w#h#ApL7(QiegRB4UjX(lkJPmOgQb}JKXMEkr?!j3FM#UyFFU%xMWNN} zlBRKjZxeTf#3khnASRnHP4Z@(%uu~PlbS{hEB+A-+#dZ}TY^z!c)}2}W@SA&Swkqj z79EApK^sUeWE*DFlY%}XIMDa|ti+6vrA66)-?YO%@ai4tLWcAx`y&-G>1 zNV&$f1`W;Qif@Jg;h_ewrRJQ=b^Pdf32BQBJ^cKn?vUY(Vb&clGA`M!Ir#A4Py!;n zPS8U7)^oQOY%&CfP(>Gp4Th2DJm@xU7Hy8*T3Crsu)Tks_+}wR!)-C~9h^NzTCvhF zsv>0)7)x!&^7gws`Ik=y+0NgL*Cu&>MmgMG+6nc{WBa_XrC)kPh^X@ccKVQ+bWn+_ z7Q<=7wnCea(r5kJW#|C9mS}E;SN1zfx^#nfvHr%xN$sjt6(wpJcImXvvg(FHW07(* zFVd_n+?P`BD2rW_UY28^h6`n&E<5Et+*3 zFC$_$8iDjg&WY=h!vaS4ay3RW5!8oC4b7E!_`Y73rIvW+7M@j%hG^aZBAzPC?wz4_ zMlbk^tqA3fCg*Yfnx~bP=j*nMS)aIdL5!ozcq%jJC=VmuQ!jwv zj%3C+fkcoDUe;N9ne}?gXC0b+Hi1-|uZ8*4T@o3oc(ih(u{)W`gefxC5|;N=fL|KJ*+XZu~iCY_Ou=bNIXUZGSyw|*LBv56KAoas6Z1J zOmNlyXV(aAA`OFMYfU9gmr*sC4@{tpVi}kh;9ZMgUcAAwTcDmh##R$U{b_djHZWYo zR>pf*ltxE~(O|7f^N@D;slqFM6&!Cu>^3F%Jo)%}MX{l`^bYa@Kq<%hh!41tT1tM_ zKl=u$W3-0DHLQA**LU0$V!QwtaW4Ss3sB5@g)hkC-S+$)?2W`Hqq027ntOtg!|<$x zDCjZgDEtBp+h*FmvThH!o01`UH&ySz!Y3D5Io#ZAku$+RqTrQV*$q%K1E|pM&XLFr zzv#=V?-|yn&DnWJcLLUe_gX2+WpMRM`H9}ir0toQ-36JY9TRZ?u>;cu`DOABdncIQ z;wt96m3R8D2;P%c(T}8tYenA$=l1?yN7LNZZx6&@Ew6O)eVqbblMm&^I7*ss$P;WQ zL@n#wW;P!3qLM!)FPob-B&=o7kfgE>Q+yX6myY0@-uO|L=kWnf4F(<-ndFk>kk}5J zyIoV0YLWl}Hgam-0h;;&B7GvE3-Z;sO>h{Pe)C(8;)d*M5?@?9)RCbbc`?6{X zBhn2n1j;`IpV;$F2sgt#9LuL}?j*GmC9CIoTe$sEKSv1xNQP)ZofTW)lTLcAFZ_YV&54tLusrd%4se$v)_ z*IXFhPOa5(Y;`}V3jjr%&glcZcJj{4*pEs@f}QO`wohmvvR|Ce*S8Ncr4M`S_0^Du zjPvt-XZ5xEy86(9#te%OfnaJwiC77cUrx=tsxlw-Kse)QfKZA`2*cIyCkj6jDzd@oT(Hsn*){^1ACxE&iQvTaxWLY1A^j(xh8`&|bMx*NKcy z#>Dn>W20woR2AiSr$ixSFekZXaZ*~Ozn*KHxsY>Lo3%Y3782`P&7gQc7Jl07_qm^8 z@+rGWtr>bFwSwepQ&?W>$; z{PI16)@s@Ai2(Xm=pj-u?^A{usNbNfJW8OO(bW*6Lqigq0R(@OQ1{-1s#4C`hc{#EpH!WF~03m}PJcH8$AHK&R~` z>#?Ge2ITN=I0{UhPYZ zT#Wv36ZOkqor4g&PW|475O{r-gc}7@uD8#Gf8wxERqJ;QHp1zwxYDZ`HLu!nYW7{` zDXnXChk#yg=cSpR80hBKvhX3NSwYxv&D_zC9=Jga_WcBX&lyhG4|nnBP5AHpjUMz2 zEY^U?PoC8hOFD~e5+eP-TXrRPIJCdnGY=l|;c}`g{hq##;R-{6T?7-2>Q7g}`1Epf z?1E{h>)}iKN$L13t&ii`JWj~@Gbun#PYdTdH7R@{%y}`+_>xk*#_Byj7FDn<^s+qP z@8rrEdiS%Ft_z6q0%dgEW#05vEFaNIKZt6$nR~X0kH8nHtd*7(Jo#VqjI*Q>tg1=~-X{}S75ENoVACUQrRetK49gPCi2W%|813K?tNX#o+~dQcY+8*y zLP1{5o0;Rll4&O}9QllRKPrLmb@M#e;d@fo;Cy*frmD93MAZiHI#R}$ata#Sk2!Do zC(aOANO+G?so`VlY#7Ra&D4`R0@a90`xsw`+et8jen-y-rl{bWEQH7DGK}oW$&4Fr zXz$(3ln#ZJ8_(8aE$FgTQsveLl?0YwF(&^uTZ@sdHi6)AKKRdB&wi2I_jFcO#Xm;V zC?zNJZ?>(^5uBeJyr%&kB8mng2NSfGVV=nh(9~9=d{ZGNNrYiT=EHZ~15J~zG-8zs zF%S=31!L<(_LGNFuEStYf3!fyv*#G61RnIg=-jR;0?nt(HPC$nPQ_e&{pB0tdA@cD zBi$oerCDMLTWn^pamVf_S>GAekdJHq#!Z(qPO^iWex$t122_qj2l94_eYy1`q!C0a z$3~v73{oaeHlBHZ__{9YdsaQhkMgT+x1q5>{q2YfzNqTuV|QX~(FsqhZco?lw3B)0 zhLjoGpGj7F!8&@=Am~9-3wy$OFr;1hfHHmL>;>4;^@}L;$lvI1Zc*#^^(DVaI-$AL ze_J##@N-}J9^Ah1_n{|X>O%b=jqhF4N-pp3#rt9AptE33yS4kH*Xq*-x64rRpG z5g2uqjfGn40)G~@y~yb84#Acf>jGYDYulbkf?^heV;7GN#51j;dqeqY>rRhR*AFd9 zR0wm|ormx7R2v$H2N<&k85aD4kV1{{w#Tzlc?XVrVkLNFC?p|t7mltyrkuKhHJ0Q1 zj*blxhd-hLUDz@LfdPGKADDwS8TFaB-g}NF#)dnFOHno`{Yn)K5{$MVWgKn8Znv=Z zEhlEJJ~}Q1GBR3yUe5Dq?O%FEkfI$`{QLsIU3*1c=E*fY#|vT(cIu(cgP4Na@$Oe5 z?6r5T=@o}c9W4q?e5A^2WboFY$W`YB@X`T%@9)7~j*DN{TRJ38+v|SSe_7&cuDj+y zOCyUvU%_M=D=H6;C`~d$&_sfI8@nEy5hn)U6vhqR6KwYW8_ISHoY1aSq=DBT8g~!= zxa{^O#hQWsWi4JqVM2@dElmje5f@da=HqvSmC(@nTAwTd*{mCt44 zjWOYHA!3NzJA>~}hy~|~+Py?F6R$7cM^$yr_Zmdh28~W&9gDp=_j3YTBb+TvJR4k% zO`QcbB0bI@!>d77m5?$>a}D9H3oSS-*CQT4@<}%q6MK^d@Cl||Q1xV(>cBs^Me}kf}@&F1FQrNKTc`C!y<11|n50S9_>bJE@2HT0)>-@&ko_DBXzx-ozky~({`OU$&% zfVL^LC*%n&ah?Jl?xGjoouwFVQ3tYA{IF293)g=H*X4wP?gxv(Ujo*5Maitt&bxVC z&wGt%H{i6W>(FfVyfmtHYcyy&LD%Sb`p!KI9l{=nzc~j$TU*!nuJ^V|($xhZwV)oK z`ETu<1iaoSmF4Bm0`4$;A?k-Kb0G|;^Oqfi=U|8uJ0fNAahk<)?j*#F(GUyB%`Fd6BC}IE2*N)w?Z*RjeuEWpf zo=Ltn6xJO_jZ}Z8QrTM!Pp_AupAp#CeHQ9R8y?^k(DLo*Dwz*WP*`f5$Bp7F=p|;! zlI;yyOc;-f*7x>JF&*5<5Rk&93=c+pi1; zo+!(|O85*>UZsBAP)9aV1!P^RlfbKH$dg{0Tdb=3{f|1P@HTSn0=~I~sHm}zsArF# z1em17Pcch%w>p#L6YCHPb3Ffn+@1sy2Cjj?elD)8my-V+`QJ*W?p%6;gOv`>8&}zA zftkhB*h9bHnDvY@{;^=rYNfyQB+l8trxKwIo6?U3_|?3f@BA$voG6Xc-OgG7r?2&Y zEA0CRkco)!31eBK_CxJ7~Nl{ zAFgKtBIk&iNSkU@+3H)wAJ{T-vkQ+_z;7s>b6)_6VqNu#-VOzbiJTRHtL?K`X2n zrq#dVo!Q9E#WgrKA$hLORuFy*Lqy^u(ep8{Rr8+PMXP)xld?N}0sL`iUSnp6B8e%l zVv{_XqH%s(>(LiV$-6HZ3`qAE+;u5uk zou6#J`1h2UvBZl|;`hOXqg0>Z+g8I><8y(Ai3Z}TZ%_C-IzbExlkZJJv20^};f%I3 z4Gn9DJEL0T5+Z)^6kA@+=@bTM2y~4vTbU+ZGw2B#Kdtguo(Tg(xpFSX`@i1wX#2;9tu&%UM%3FD8`d#gyHn-C&J^)OgQpd(e&TYuX6vX zIugjM`G{O}X?9^(Cy&;!)Fw^};?I?tq6&RFA>Et0-F3XgFif1`n3+*k5H>TjfajP= zOoT`v)Ya8B(<^8#bYkZzbiS305yO!T$1yJo!vXjdDPpDm`5nf(#!78V@eT5uNur^l z`$s1kXAXC)yZ{32x)L^5A(;+3W7vKA4ORuNtD!N*U{V|w=~Djg6l4U@Iun!JmkH@1 z2noX7tSUXQrnFh@2Ll%uvC=>w%}^-e_zRZ||o!{ZLUFus#i*pvj| zklKYm+uFeIsnF|v|LFpHZRB8;PtQ5|DA*;p^8$4JERU!>oEXNu`A(UVUNB;Uylvdn zaxE5fba>?V#98x2t>#QACfDHl*zUGWpR;ma0k$%0Mac~BK(-$5bT@ZULO%2RNouJM z3eLY_NepdqV@1M4{kC3aq@saV=Bx&)?~`fC(UAx>p@c(lG0iZDxy+93bdZr}HgEXJGPOd0({G<%*TtZloX6W}8u%-%>RT zSBhEqQ+SdgAQ6uSFV|oFs|Y*<4i_D#I!>kK5$WKhJn!8lZ^iQ71Ne3Q@g9v~Ptvvf z&gmH4)}wE;)n`=xIdMj&)X=ECE%v?nmfa5w{#Js0Q5hSfn7Dr%i_r9A*!Me>KG&M|8 z#S<+k64iKHV_T%iGsAgfN4$*VbmM_6TAva`9{_l@g+>$YeT#LCtJyZ|H6@E7(p3)S zo%B#xXN>;o$yv@$M1z$OSo=|?nEpfMG|NT>MQWFg;v5R-7a+M{C|vA>P9*+ts7X(i zgjFtbasC4?${(e9!O(NoV6Ub!X6s%y@9#2?OPo*a2S+Djq%v!@Xc;~`0z{K~5?0hS z+&CO5ou{$6y%uY?u&m+@W6zIWHD*M^b_>64+seEh2Wl##+)op~bM!u}X?q7e_A}j> zKAo2Kz5ocffs}Otcau364(D0D`xl5TA3x8zC#J-(CA;1&+6xmjI5jvxl(mEoi#)dV z44i@(=hUvk`o;+H?U9buvh}w{_B12}t@^YoX$v>yVa{#zPJTWKU#8_oVf`Vn7ayco z${C0yr;i!L2b(w=G`HI1u~f(AVTx~zMMx+}B$GrXlRW2W!22Qdkc(ov`cGj>>>3A+ z5s`t}s_9Haskj2k#2ZYgT*<#=e8;Ze@@;UD9uX}w)^*toQN~iSAj`t{pv}sLKS~Nt zLguCTfir0lg$b?ONS(*ivNb0+n+NHvc$>^HD)AKIYBF6v%*f%0e^l8-%wo4#5_ASB zS(#y_@MrUXN=)QdO3dz-X?KJa{i>5;~F=(i1*#lyOR9!@T4pgg(-WRt>v* zq96P9v`R@U_6+S^;k0bH{B-7ls)km}#-vU^nZHuY9KDEcI$ksM_&TY+t`J%~wN|S2 zm~6$@mOjc#X=D-8uiJG!Td#p?Zga?Zw53$Nu%x9;5v?skmZ3qKKuG90qm(sOG7|Ao zy_5z)LrPVN2U9$R{I`G8Hy`nkSApaLY3<7jMl0T}{n`m;g-crDt+!z+Sc4e=3X8|NNwz?%t8b|7eDxZ~jQX@xM#ZCh$(7!pr~j622Fx4_Wc zqg>K(JaZ8Fv(53>4`|8=znoQRaC1t2hUuH1Nzhd5Spz9c|bz zZQkp-me-XQ0yrAJdXW)6sQ?H7+Bdy88=*W+s~LWjNQH`23se=8WJH`<_3A!n=jsDE z9jhAtdfr%*@y{!>hbJp*bN4R*4^)_&vdwP>rJ|xR3<`zPP&GDYi28jX3H&5gkgD;~ z2uz);>Pq3HEUGlxDa;;?^6KTK5_+{k|Akrq_Va)b>PgpCaB#Q69^4Y*yo3D;KT8!=e#LY_ss$13CZRof^E{~b#k%{#B3S5w^DL{y|G zh`gwHeCk~CTSKB*ZqZwHOe zNGhrdW>{M0z4Ve^fbE8aow;g?qhZJc(siD!*18_b#fO7~#cRX~YcTDxhRtz#dGxR| zI91ic>;;hX8DvN?oS=ZveZYxNQ6b0fXg;)AD1ch^4mpuWi+1&>=R4 ztx1V*?Of|3YSwI$O|4pQl4>rxwGf;1KkM7*ARl}#d9WfOrN>wm_K_b|HTI;Xm7ZEuJKuNOV;BM!_xbbR~hY3 zf90nQ9S!9zDn%2~nDR>y$fd(>(W<}8vDE&cUcBtK)SEFN$_2@bh?VR2cyofWs<={F z85|uQT8f6`e7rwIiGy6ikIcEGmH)U{%PV~c*F4D>Hjgp5^$3IL+ zkyoii!ZA~U>W|snvD@C8=zf0F!b3kIq@p;T9QzuYL2tCaxm6b)Dc10*r1~M!T*ERa zo(VKlb!}Brt2AbmAO@F$z4dlB^5N?auJGZM zRl%{&(D%HnxIcEyA&_4g#+fFH%GLnad?=Ddcfsh{d)zlq1~rkJ9hmucbp0xPv?cDt zw#~DoDaqqHG#YXzBuo8bqg`}XAIR&f{qU{Ko0@+Jf9l0fa*7p4_JN&{JfVp2yos5g z53gY+qqM}XQx;oJ5CW`Y<$^;gV%=h;cqvjUR8{3bCvi!hP26~=H%WR%8b%i8Pv;rLidp6!@sQ9g*q_yy5}JdC ziKA*7bX_ZKj%^4@gD4c2Qu;On6x4m3Mcs9*$~Be-RKyTcLU#dz!DI^RSJh-&Y7_pV zW+QFy!_Zj)Uk6Gfr=7dsnhfv!P94$Yycw52j`bjAb>BCK^`G&Za1R11GLNmoa^JKT zq&|On$ml<0?^zohf;o4^krSidn(blFy+A9;Y^sJEH5He4t(6Tp<41ku3B26*aGY=y z)Q$cAZ^m87hOAaJHPjm9v0NbMG^XcuElMtkC%xl1-&hKTfj@u8P{aJj%)*c=Lp}aZ zFi$6O<^_{g=vIUN)n2uRV9xsQ8^F z-dv_^`){O2FT_^uJ?X&Om0S7w7Xa!0ljvPAF*`(ZLo9YOMUfmJ*XShdphj&pZBmUv zGNq$+l&6VNQ8a>y_TewN1_;rli`S+a>5&+}aWz?^p+;P8mbOJ$4!t=Zb=pgDOd*g6 zIa2$W*kIHHr~EXxemoz}4xNGWCg-LyK)G>v@Fc`(y%%Fk1|oR zpE{EIL9H)9LI2hnRE%Du{hfKbs>X$;*Qiy}O6oO2?9grf0_?1lE&Rk$he>*#uFaO* zv^Re31q9G!u~sIe18Jxe2@|kX%6H-14`hUuZ4YpGV{UpGRrWjJ>)lFB%;u+I71bo? zbT7#ym$Sr(zsd()cwMH73rnnt3%55XI;rjPTTEATmsH2h|Uk zE0-_8M?!CerX;~V_GJN;OHMBQG^3l4YG-}Wf@ksz;BIwUQFC*eE`PXKexEvdD>;hl zqK?rf`D|+6z3Q&^O&q!)k04Oc{rNRE1(V~Uso_pJ^QIJE?~&o*>Hg~C)oI6k_5ShT zoJGoD6`%Wu8uT_Mtp0lgbNytJa|GuteUlZ*3QP<2ZHy-8L~{o^Ven0b$}?{5K&LG} za6=94@&An$Jx&5xmG?>i$ssYfhD>YxsVBLiZeqqBH{%&0x6f==3@K=3L zmfdgCDUCMG<1=mlNBoj^!&7)aM5bxgqm8#ggS^L56)Fu&?sht$#D;Y6s6A4A$U#8;NgwlEfHD4oiAdp3@5M2&;&UF`6-ac~*k9qDnR+>=Mmu4U>+O zNTbW;&G=`vs!)xJ@EyPQB8CIVN$wiQg&H%W93pA4P_aasiZ}L^Y}Mk1vCHpwcJ`?F zCu%*pO^^>yKf^$}gLHRoB}M22>i&eC#(!@+1=GQNqKBbGJZ`Mz;8oNAqu=Vv&aXNd zK^&5h&!AO0*u=%SEhA#s5T}pD? z@FxlE$XqbjxXOEDT?CbNGg|h(9h?4QdLcODi@z`uVgLZ|`rgySvX7?S{n>ldqu;yVQgr47bD8N@ zaIFUKq|AYeq13Elf9(<-X02ta348U@v^k=AVa+b0JLTNf0yMrMg%F^DnlB^8!$p{s z07bi&>Q>-!Kb$M~_{~GPFsMxAwoc0(0%~s#u60^|!yUKp5nOyVGca{gx9;92i0dJ5 z`o&-amarXb{v9;nl#^EwoM* zQ3i0_yMG|AK6VE`THF5&sc< zc9RvQSw#mM7Gp-K?z;3dqQ9_-ZX&d2{6;O#y7+J0N&9@?TUmP!@sY?9R8eThqGT`c zYltsaU#-VdV`6f+Bo4s#Bp3sv2vxY()6tA+kpkmsosH88z+T;P(*;yJt14%k>Zure#7T34)+$VT800(Wzw%Ps`FnL!3cS;^MdZEx%EMMHr?rYnn5L%)l!0aaG+%d! zgxbel(H9ys_ds7VhTDUk2O1{tr?-JrQQ$hM>IXP4Vk2MSpFlvxoirxaID28uRo^!y zDd2Vu59y&q&6PbvISXpwlH>m728g^wIU#PkyuuJNd3J0ImvW#9N%3(WrZ606;F84c zs3U8Un7pv&qbstiQ)O> zv8?&a*cIQjh^Y!y_Qvw)?EQ~)SM~#P{u#H0XlAE#kJR19%s>iRZIhXAe$cY1zP1Fx z5GeI4hAH$%C4q4ir_cjz7sufTdisb#tyq*d6};8Zv&r3(Ob+1o^l~18T^6=GFx0Q2 z9|@W}X2p(P96B>fs#s9=`gCZ#PO3S59k#M&HAoWBH*;N+As%A_mYJJ&CK%6_Dhst) zKtZ8JSt;m-5M9N%$U1hAbtY6O@G1XjqwOZxU7Q z%S59)sgk^BD{qM0{j6MdbxD$jZFla^FK?p)$#im(Udt z6qxyZTYE}gxTVqVG(!y93udGr9CMR*jw$1x=E#kVrmQ2Qn5!gOvDPRj%y*d}4Ra3< zEpvV1eaC%LXU@(a7KX)I{A8RPt2*jjj*n~&G~U)XL`&h+PnO8yjfPEEH=xp4$fF`o z0z*HY3%(D=Uy_`Y$E3zoNeCEegQI?f_3y_h+gVz<4=T~MjsED@*@(~-LwFLmZxqa* z9OF~1hRZcVQa3M#cG1D8RG)r~{*nn!G)TqxTb10%`NEQ#*#aQ>9v}uv*eS z2;Y#z6ent?F_IWdgw~g|lsB2FJi3W{_y#X7Qqy!k{yTQ%e+6w(v`8P6tK=Me=E|EK z(~l>!UCYwZ!2q zbHI;Zii@5;u$5@)_sCfgge^nuRdUaEsv2O4JuSzh*y zI!Ey81XN2W({AeCR^pLL;xYvr)tj;Z1*n$(FS$PiCvG z@sQ)eduQwR{_fCOw5`_yr^qOldNcC(j)d4xyM-EZL{%z-ZTtcB=~CJ%+M%-YX?0kP zRzm-?*lEWp^`!5PwF`<%PSZweCT5vzKWPVCW9mpQ1Wa|a`Za2Qbj>9G!GZsfYfi@8 zBfR(_x{?-Ce6N90d}9uN#x95mQxMhRvD zXOEjy7~8kWjf!_YOheOBRPYRElj3yQ`<;#Quk-QZ%F}4naGN%{t26wEq!`FV>4!q{ zTOcX`zAEV>x>+HHiDc6pK)v7DM5Gbc3>h<|4YvbLd9_!C9LFhlj$mN9!! zDh}KaWud{{=J`WeQ`2IuD-j|-bKdfv7lZhKlNSCh?lf`aUruo^OtpY?#%_*K{FzOQ zIF+d1c_&)%k#O5c{3qIXQQUJgoZ{>BuIgR0_E8TFvQscEwzaf)0Ov7x%)IxayxZn5 z_JbYvJW~VTo|S=_tpmgP2i#n9qfp~_7vk`cx{@xZH@X}_QVDC$m{MqdRdjG%w+E`EB=Uf54e==db0^#vmw~LAX@SBJY@VDM50iA_(p$XtQyP zzVX+g(7FR=hRdJUaXsYJ3t}UBexl(qudyK+;m-vVy8gK{@;W~^M2<)gs?W)qMY_L9iHyMlE&8(lqznz1KNHa(#JQguLxZR;^PONvRZC}UAS2xVZhJ|?%> zV{hwG5+gb0jz5%~%Phz(EouH@?D)2fJ4h8D-VM{4D-TnWFNBADAk?xv=YJaK{>6>{ zzr=ZGN{>WTL=5b9#!MxcnD7Rv!sF`yW7*>GMgGVBwJE$S5fd6$d-I7aw}8hgd?qFC zt+Bo4rrF1E6%_z^;%$j8h2kN{W{7yZF(Ac0)=}NUOKv(M?GmM0?p`2}UqUT)#T#3G zT`7aFD|&{m8`&{o>cJG@MXruKNfR_NAFtE2$fV@uBFFb0wHYBVpQB2l6yEm zyl3`VU|)z)ZYL2hd&ZQf?Us z1zM|gjOc@U?%;k%5*_b={@@FQajiI}77l1g>7_A5z5w*mW*EI{gAMn%#C^uvW|7CZ z1IoIqSLMNWE%@(zTC#nOgZ8w~Tp+r70mw|Y z1@S$Q!(X#W9O;~%3Ar6Fj!q;y-rcDR90!b174dBHatTHeA9c3M%n{CRG_Om3hn4_2(iI)#H{Hxd^XhOPHvjb@e~jMxMk z2EHK0Fa09opyI#2*%&l7R$OWN5-SL`{CUri==Che^Slcg+Yb@c$R3r4H##Bl{^hgv z96M4;{yA+;PGI^Wpz+Usc=#3h0A6iXkCEJtzU2G1)&>Dbn~t0i4Cpp52JIfQch8LA zV~xl!Z!~@Pxy)7gp61{^v@@6_)hNuLPO;xd;2S{#Hw`3T7Ftt-9@xLp?!o|1QWz`(^ zLD(BPe{_uOl!7m?*CDbd;CMIc*!d=5QYU!-slu8ny8*WKzY**Ot8X`SL8>N-jLo=h%T2IUttw=SKwuhbt8F23$DA4^ZEDThXtf)< zjQZ%s+gztGN>qDuM`_s4HeQ%R~<>e;0bX`W16gWR)o0i<6;v3Xy3 zNc=F0GNv2f^%5`G!JRR`6!YFke|#Dx7pnA6iro+c-lv8n`51HPyy^Zs2{^Qn6@Vtyif2 zW5F=;eMzEi5X?36k+iy{u?g?1q(AqTqAf%7*l{QnDzT-Cju`e+`u*Dt=wAR6{HGIA zGk`#gpM<6K6TPR#9z;7(Ni+mGoQ9c?47HKtXCue~tNfH|Zb_{)pJEbQr&U!$Zr5|= zeeQU(teOGxN)|*P-vxi(JLcazV8%lYGyegtV^~-A8`fSe%hqzA-BcFkNrS_d8N%I6 zBK05E)s~*nEvj_5v|?KT-`Y6cE>9b$$H~OKW)V#~REjw&0Pj*#^iac*BsRTWFt+)5 z{C9XPD&=YZd-Krb#XG(>;86Veu@bgO+TLiEB)V^H3hB%n)k(rC1AT@JYHDWlN`MTK zCcCaxbb{+Fk-Uz`LF-=e8-X3f6c`6qmYE`F$|5uZCW)h1dwR`3tMn}QHbpQT(1?A= zy)Jkg_M*N&3AVXvxc!jD8D_5y`AflfL^z(JRW`7%y2P;gwxq%)LZ~ckZ9GK*|X zmceYbHyQqKInn-KQwDjh?>{*r^s)AeyCZ_dDhk#F^8L)4LNM&YM)q3MYmVAS#(UAA zkq^FfpaWg!Jr=~nOYcwTR=k(S5cz%4h5$!9B6Ep-jPs0KCqcN|Pp7P<)YVvQi4OJ} z!rpAcjO8H4B||@}1#ax){#56nRjq2>*Ki5#5K1cxMJiFIPcw;VxfXa`Y`YOcO1mPn zN0^HgJjG~TE|PB%dul?~Sm@RVTd-SD6}`mXSl72&2UxN+T54Hv(rwIJY6#ci?a!B( zzS&k)J+HluWlQ7?Q&s~jn{SHazlOf4i6cQ&m4uN-uxTc!f^-GU1J)|>Z(FiR@iljk zDXjSU4PDjEg$Cba0^ErsyJZy;+|&yiZ)#Kt|4Dm_I}qy-EdmiI~>_i-6`C@ zaz8)pQ2!j9OVE9>u%nQf=b6mI|K~WrBcq@j`(ibBxl5%XK$qpHmFp30@KU{ICQN?* z2o<=yA+JN45E$mSuJgR?cwQQLdbOfc3N90mDNVYsX@O_VC(tk`*QpnOd0ELXL5OjK z_zHDGFI7Ssp4nNPPD})RhTh2Ulx*F_=^@|ko7>qE&u9A6JR=%?J3(!XDp1)R+ttws z3u0%y;dCL0cM1#Nex70Y0}+NS_2jz~J(XN(G&)Dq4&`*Y(Lw{;7J1iK3i3cY;Wsf! zQ^jyQ<&u;dssG|myo&ck%;(E4zrlO&fA*YqYw3Mkx>ioKCSvetu`wCwLwtx5SU8k` z+oOT2k$jw;Lh4wsT`kb;er{PTe+<6JC_UJjET^ed`80ZD9ONEgr4{JzZpOSg8bpPL z)*4QaKoyT2!`+me14cF_V5rmm)-7QmH~Y;E-6%8UfU1w4#cpU`g`TOX zyrIa=(!emrWXP(PUGnD)P08;8WcEfrs&ucCYr13bh_k`MAb7`~FLd$dO29GZuOmUL z+oPv3!%fr|*9@a*zI)&7fm?fmR`~m}5qSbVDGm;Lwa8_DX!oa{*qxsugh%}j@bgWc! zdXqb8RxAKGP({O!1kwvBNbv*stOl1!!3(9?xHIL0?(k*_V5Uz-sV9cQH2ep_^?||D zCVsr-os=d@ymuaBc~=FKZz?V?2(K12pAOU~tFBBd_2Q3pDv{IiwSuqJQf@Q+S25bH zol@3j0wz5<`YjgSwLkahuJo{QX8W$BQ?(tiF?3|sgaj8-0}QSs;OV2cIqr*Tu$hMk z{L^mdw7>X7ZW<#RAD>Y2Jy;4~tYmUI?CW@3q-T@?3GJ*7Cx4|leQVuw#%QzKakYP6EuFxU>p!V4YUwmsf| zWT=OUvR7|V9rvrIQ&GQ}nkIQ!?Lk!kZXs@f;+V`6@3{Is2f#p)YARX4 zbMVc$jua>){QLAy0bqK@97`2HW1_eFboE(*W$R2HMAf5f;sjAGrB*$MpSGk)s?y2 z2y+p?Z*DfX=V)|ncp|(d9OK&cQ(5McXX=7kG|X+()@%6h%@SmF87y z(a^vMxC|VjGVZIYsw6^31C-LhWB#M}EjORH-u*|aNPA}X`(W0=S#* zzHaSWufrSXS!_#wF&z8Ml4}JGi#S@wtt85iAiqk^n0pl$l<)`)mN9p<%Qsw@G5eZ- zy!LMOI(3}{nt`l`Ysp&+FNeQTtQdL+j|Y9D5xE23O3hgKduB4mC#%8xP=Wm3w%*5L z6A(SEL8CC4wzlXnMb)4!7NJ%kCPjdS0n7(H<~V<)Z!N0hZBE zHO8ugvKoYAMBm5Ve|!h2C)BH4&AAV$=YE$7l0K9vBQ}%bsD{&-y7O{uEf;j1-4@hi z*n8KHG$EYOFmems*xIViF<+nJdaK2C{%-YBF1brb?7Gu2Dix2&AeZFOU2Wn4OWH~I z17AIO1*yKD_{;B64ymPUVg&(dq`h)k(uy*_e^&(${W9LnN?BGdFl!v2k$)>=DGH)OvR7uXE-!$iTh?R0`)WN; zOx4_8Bk*|`af5jri++atwOCG9@Fi7z5X8(;0^ZNrz7`@nKkGjm<5-&Y91_hYjqB2!P!}UwfSz{I!KFa3l#U_?p}&Rao6G=oZ#;6 zZpB@K1h?YBTU?8~7c0_}-#+gddw)A)zyCloMxHzCxz}9tnk#~=L>p|c&65hXa{Dl1 zJ6WoKwu>;Y3rdOt6z#^!guD6oop)B&DXB<-0S^^X%+j<^pZc}^UJ zRs-azo!x~2Co|6Y6mCK^d9R)89Q`hM|L&QhLS>t>q-8XaMpRh+&(^G#H7U zf=FdkB?K1cyNsoe1Z$CZI#Iy7!vq_3yCN<;>4vJSG(a!vm1C%_g&ByCbhZ0iz7Z^xhGz=Ls5*&H}L; zL?=er&Ti5MI)x?h7a%|DINi>Sxs~980*I6rp8=wOt5rLb(}=@ z(*}J9{ECq}nDM6~7lYYtA|v{xPL~0GvVuLDrmlu((Ix7tOa<$dxG2S&>=!G{sl%^< zX|?V|>zDN%G4)MW;ZHq{%Wco{$XfMevF7fo_arUhCU!#K>lWFCoh5@N2frRuSqM2G zI)}Isen=kpW~M>%qJHs)81*eLHDd*0$4gPtxVPq$Vo3K(0rFS782wrHlTq%EWQw45 zpX_0tzkpew^qc4uV2R0lcU0^b8=xIZeLGTM*TRsRly+R&Z1`4E2$SP6@|wqLtLvtd z?I&>nOyfcpLRyn7IlS$pjhyeu;}lGF7jc93VY~m5?9Vm1jOwrBA9D4m;J!A#)fjzY zu_)6baj^c=|yn~v^P zf`9v%=8)XdcDp(e$G2}Jv-8WJ+P}zcYn!0&0>2Hv)$2*$wcgVYOHIA10YAk@iE-WM z#@3#K%V7I+J%`UBZSNps1?MhB+EB-n=+s%4!3(9xu=vcmy-7LUOUgCY^kriGZi#E| ziE~Z0)dk%?`GhO=Em@O7braK+tV#C1b?w=9ve!X6zj5$;8`^9e0~%E+BKT`L>c%3f z;E6%TVA=OH069T~EV^%GmUNAq2nHyQnEAxbA45jQ~)|a8$ z?l$b158!OK4&25%N(;nWrhSpxtlPDdI`xjZ=t>xgUvG?ac&az8B%-t+;x{kfL$~6% zA-H!*M2^y0Bz{Hx==kShkynvnx81T6(i(m}PZN{9e?6OK-kv|UosS%A{3Xtz=yLFN zX7)>bgPg;A?mXGwbPiQ@-x6$Q;+iPYi;ZR&`5%<=BZXzkKI`NfX1fdw zQeKqDEdwd~ER(k;ruU4ujX7%DL+U{%0|_cJ2l~*T+dh#j-?I0;@ab8$lWj^|q2M80 zLK+Yq5LU9@?E?O>vSOO&!R>!>%O~JptrPo`F)YgHjEwVhVeRt*QU1C{GI+(Z>HhxC zsnFp$aYY%Oq1Rth@6Rxc;V`d=*lA@|Z!}e<7%m?_>zdNpV^+dM3P+eS&wn&)|KHvj z)``nM8wnn;B{dD}){9Ku4uY;QpWx5Hpw%&IBO4mA@6jVaih6d3%T`YX<&*NdMq&1Q z`w{u#!9-z8Xz3E;4YQjlmq+sk>hzawptBY#{O7{*%nDpXg9cK&Eafb(4t^lL4lli5v68Nr{90(LnLARx8vpjITC=FrucbEw zn%U#)uNL)K^2ab%AuLTYji9~*yX-P_hb%7}e%rHFwvffL%P0C7kku?FN~ytxGp%<( z1=+0tPIJx5@QQAYXH-&nU!?ZwPU8^O9j%Cn*;#}@)*x^^VCJ=kXV4!JyG|n8Gayi`}tj$Mv6X!AB=WpH;CHv4_>?-BzrT z9uDJWy@5!*sPEXH%^fm0$bsD(Uz$Ctp`fKeq*`$+a}coC2Q#RbXt6`=GFmb0o=t&# z+p~9nZF)s)MEhNXBMHn(S0^Qf~(oB2$O}PI7<-l`pDK&x`jhYEf zQgnq*6GV|2dl==u47$3#4d7sY@8Bu$RmSzI-!|BSV)=!BO^VF7_IGxo&G9uq{Hbsm2-!Jqbuwy!3|5*(x){-AqKo! z(u~KWP*@4CM%Wbxgd9pX!W_E6Sf2$#u2Pun-Rv*8%?e#<4BxqaFmge1V~r&ei2WiL z`K9ii2TFqn2AWoB*5VR4BO=bd8{_hnwAv}z(=%vcs-=llJIi)aOQd6Tu**5a0$FFTZ9s^l9Ozbu94Trz(9e1e@`#s zzW^r#PITwgJ>y+$E(3n8?h8pg)~W1WuPfGs%_S<}t$CNT3D}ODVqV)S|L$}H9v_*>zg%O<@q6V;k17Hx~`h4;}a;fkF&91sS?p1Dozoy6xV+?srFP#B^WG% zEY>&GEUe*2ykZwQv+F*B@C(*YgNE%^U+9PIo6{@PLKN7*RFnyd`&NE`lu^rYkJa$c z(Y=xlqW?II{}Hzo!yqiJD^ITN23Geu*F{Ix+sVL*eimL`w;VV zpR0m#bVb%X%+WJ|TjI0B|&S zg|K9t8Imy(zzr8;%>AH|vt z0mn^(k%=STU;rj;?j(OBCeOwYr3g6b?Tm`Z*5Ir6Ag1=2 zQ^=Gh8aCzY>*_{x+3fu^nX~H{`K)|bk0zL$ex|^zLI5!Q(vWDF+JTsClSr2O?M4DU zC@uJ{k_9exn(gfGPwEfTScdl2U{Wki_3;dro43jd4b#?0r%b#Tq3l}BEn0)lg+!1+ z$_V_N&{T`1G_zc95Ly2DriCL#Y8Kk<@X}XvIyKXV=Ju{cHd8Z5*uE2%bGrtG@3Ee( z-Fa2o)520(efJW*7`$a&zh>rdO}bq^Zb!bXp6T85#v{g^B?d%4|kk!kr2TAFJK z)WoEU-^9r}P2^Q(W|~n_Zfb>UEeP`o+aOWqksNZaaf#1P>np>oBjyE(mk2Won*V;i ze;NDkAnL3T+)iKL%v#pb(ufa#Lwy^kq`G(kR-ui?2Rrf$X@1#j7 z59&}Me}WTrI0?^LpZ&+e_G_lH>&CN5SIXrKEPu-0`%sW#GR`=sXa0{<=2+~71tn?r zWmcpWEwN_}GJreSuK*vVflfZRq*Z=npa5Z}U|FpNgov5q|VNoO~G3?1}E3FE1(w#gV6{(tx3MJ#(pci`%ZQ z57%AXg3+3SY0yA=73(&dX}JDzEzszC$8+>;-L^>_SURTDp25j}-2|LCU;J&5{SJ&v{|X6e8k&z?opDVW$Wdf2({}BAY}Nizum5 zRXl^m)_l?PbS@`!u4rj$kQ`9(57cXR@%y$!i7Djl)c#`&xO>o0Z)0^&ZC|NaOw}Y_ zh}zURdEjs-n1 z33@-aE*nW4>E)S-ZN0s{$3IUve*awh{BCM?AN#qjcgkKCHvevW33*%H05 zBcVrj3wMfthY&9eeL8+mBsw#GFNJp0hZI9nQ!#&Cs@7N$HI~)bE);32cjx7eBa=LH z8HEK$7Kz?czeTPl$f}Hb8@EP}4#b5S6fuNv$JEuPnC+gZpv=+}$ z?S16zcPYagrkn(x;}3dh;|v=pJ0_5ch$eh|(`CU!Jd3M`V zmBWESa(H26;Y>2v&4YvgOQ}vZ2DY2bpBLf5!pe{Twqz8@UJ#cgb;`g#;U67tE?iq$ zjZjwyzUE)(k$SyO-u%OdtKat?Iz|}Ii!3yY+Ul#@zqN5(aGR5rg(n)8jd|P$Zn!3y z%Y=L#zNNK#n72-d@v(k-IA1xXK-l~B4FJDrR!;%gx36#mV5Hah%=i<|3`36$%V)nn zynU59I1qw_4k81>skd$e*u*-2j!)ZOzbj-*|M><$5AZk@^17#M9cka3I9DE2Glwxc zUU1W+Ze&|qY}nI#n=nIzSkNkDIOW}0<2x>9olUcjbm9{q`{;3xAH3|sHm*XPQWyg* z2aV(C=?gA3@T#te_1r3Cg>iXN@O5X!FfuZ3V9X2jdu6LSZ z)&Ubyr_)6*T=me!TZ=KXogY;TtDjlf7VIEhu~n19#ZvR7S_WP42NoE$snTftlr2$Z z?d_ghab_~@-EHaT*IVC%CY?PzgzvSvc(0Y6nVX<6`e?*lvIb8|Lv3o89zXz&=_GZx z0$G8CmovdM$>sut0)!wmm-<$!p>lFkqXRoa=PUYp0{l>BWeonMx%q?ywc`%!st!BN zT0E&)%T34Gr-b?&7?}O(H8FqyLPqSV>Jj|YOLpXgXNZYM>@37d7Gu9)MBz1qq1NGh zGSF;z@LD{?#!K&Ba3&Mi#~!3w`(zliTrNf<67zx|O!UKB$Xzd@0B6?uSI$NZ>xudL z`p2NVO=B!dstll9oH&P=0*;|YIzvsJEgrMoAxDke{>Hw1 zd^X09elMe94o|3$gQv>njk-n29}iHN9f>n{Jq?&Dr5Tu6NRrwRQKZy2HIOE_EztVi8n71o+P2D`w*G#6 zR$jq!)Z5TXWbyjWY}6U%fRO6|LXOrqFOC)b5|TOM=!d;`(uX1pqrWdx&bef|6qinW zmU^3_6g}D|R=uNU)x5hEA9Uis1=rRYlUr-|k~$DS)d}(y6l39>%=xD%yIY~Rl*K0f z1=yR^VPvQ8LYIm+%xI*9T%R=L{R#p&NVoMBTV1h9Pu8|U`S`XWQscw!9rlzgh!0Nh z&U|J%9yxNy85)br)axgBHJ;s{-TY`!>qq}5Z&H@~JY}A! zuC|jkH9kq2%EiebGTx@4g2wd*G4iydN~3#WQ7zu0#$Eiz^yB9yQpR-APq_Y{9UW0b z<5@XjCN;;FvnjrCenM2?q)lP3T2*dljf=Mgqyo(?rn&tYOlgfBVUH3zqi8Odjjzcj zsx2cJX~s({XN*V-!^FZcM-)4CF^X-Mkbce)Qa$ej-1xpYnR=@~yr;yk47dqGQoiyj z4=oC{m1)f!-#QQhDuVy`$_6ny)w{{w(`q4~EWA(;K5feP2@H8FkL|1a%;(2qOY_Y& zaj&8lHDW!odksZK)npy*L4%uF?FE5{q6q%xlf#gL@$=i)zkpvCH)rE18;zr|PnXWlWD3?SMi3~8ty=+(km$JWnIMp_2fK+V z*S^cxrk4`0!v05fs@=J2T!t~{&y1k~&Dvg(@+KOlnE*+3Fhm38_Seb z%#ud3^Rv$URWLv!x+-T&)954f8U^M@hZ^_!gec*OQsDiD9p5@|xGFh14#JbS&V!+? z=_`4u6=H(pR^9ZC*J4GWtMj$De#xJlaYc$}aqur7+7M~WC!*E6NqtQsY0d+z3@rL> zK)^tzu3-s(M#T7Zt!r|+{=;|JNOpx-$k;5*!Q~wcp>Y!uYN|UU9&!(E3C3u}IbG@)BJb8asQ!o5V1iN@xOwFg0YfH6c@JM_C{!# zsbeK)X{(xUNjVe;E-OgF;a-cp?B}!Nv8gyn;I$#8n}U6xT7?)I>IB}`T{)OaM=;t8 zPg;#(ap~@j+sunQ)Umv5UvfN2k)#DcRrxJ03B8qhL*C#ngwgDVjugTQpzyYK+}CnD&@L}^qF`_ZGKHEDTH1GI(5DYRTx@WZhdqVp$q z#ykwhSDR)XdWGJRzi$bvbssLvEj3VaY&_Lmq1DCTl;+WNCY0(8&TZ`#I;cK4kTb5Gowe>CjG>?&|*N}GucQ{Fd8(%H?IM$I>PLLm6>up; z)132dbkFz|e*qp_l>Bb_VI%Dk%FzY4?e~tqgd$Qn)93%pFsvmKznO%*K+K}3&xA|J zMFJ}WE|jcZ$%$Xt{sPp@j(J`^Uc`=4a@hhMFeRwu0x}od6(je9?R&^p)<>4j%FoVa zrtQ9g9=wPD0_2{=!_&JoZt<36{{rwz1J<)XXkykJYy(~h0&1o^WWMu7bo0+DVqrRG z*+q_;h!(ex+5!#&p3<{~Dz7HN7p>s4UC=3Su_N#C-n<(rV zP_RPV``P`EV(m{)1J?ZJ1oEIk)5~<6H##+7Ui{2e**?;CmYVHwJOGHKg7?f|v2pxE zOZ*+j@zlhr-(HkhO(vjN9zga9Ao^MPboWoMhokuA=IN@d-j^|+njVQ$*kSFpT5bPb zq7c`kXr+)g6583;O2t`sEpBT`@f7Dxk;NMu%qo({S{l;POnawJ;7y{#06!uzP^8PW zE&6uj?N_U-OC<9{ZSS`a{oJBVpcqjyQTZItG^So=733$W?|ieyya-*6HOnzdpYD=h zPYDQ~+-+!EI`ZYTbf_p+`<>~Vjz({hR zzTyzkBv>VUO6)QYx(}{)#mNn;tI4%xs;ugnr>1)Lp0a{f9Ym{B;VnIqW!7BcU;rwcgNukP z1(tfRY1GcaFup`ghA5S1PVA1B!wz4~@BNa|Ukhd=khV-G&^@Pe5RoG>O=BIGvXSXI zOQX)Xq=zfUg%PZn9o0F|{zl=P?cd063kq+WTK7 z_a$9k&Ri1hBh@VE^-}-W-PpebpX!F*uAvS6U59DDk+XEY-~k9RNW4xh9B(NMa0SbZjmzgKZ-L-Fi?av$f#IeIQ7Qq_^Cmb{N!kR zs8z{yinB&zNYnlMzuK2AfnJ#HAdS6f|jAiHABHpanfqi;us@89#aUdiH^zcT+TYEOiR$=LN#!q(#k3P{2+ka(-D7a;jK|0M{kk^0DOdt9kZ6V z#SOC|@u1q1p5@!B&1%ZZeDb-&SXB7&j=0J3gJ!w)a+Lc}2G5fOW`9sbEn;I={wiP0s>{S@dpDNVVNFy7qpOLk^tf`Usi6*GmM6WwCu&INpVwJ%78@w9| zEDcx~;%mP2(5|bct;EXn$h$JcSWTA0+Bl*@nA}{en-`ooq=5^E7$=8UCOs%+N6{WF z*}=$?kAG6zZ`$ehg-WUHH8&@EW>@`tEVGtA&w8q`$(trGG%FGmI|h+5Mo z^72iZatsRHZ&b&c3p(B)y&P@1z~M7j`Ax6VO0C5ePECuL2HfTxwq{FPb>%l8lLm5vRf@^i?9p#8_xMIijHg<&;C6Zk*f=>X{E;~AeeLN_N9l~ zSvKR@mruK~20u~3naPVrUMJ{vpSqD^^8}3AQ*J9cmRZY%QF3ROm8Lkqsj;#&GGPh| zLTs#Qdb7(4PTt2JC1Fal(KKK!o#U(&wA$Vx5`K`L7O}<_I82n?Xf6Ku#mBztX_mig zVaY=%$mQAh+AvD}MVsHF?NZnD4Ht?`@=vh|AG;r_8Bv-BJMfQ+YYtmxxd^hkTu zi<5B6%u*kaJQ;y7Yv|d1ZpFvL4c)nd$Le;k&lD>;LhaTl{$Kl}t&GU8?scre7;#Sl za}TnQIQEU|ZCx!pTMob-72FS7#MxU1cbH@*yjr5QUdy~%#X-|K z^9>~_6OC!6USJ!`nrfz9zZNoduLM_D>QRISVZ?Pqv=K5=I~hoG2y2^fcfaHpB`22H zA72hE!dH&6Y8*-|?@i;0Q-nh+bR-g)#w20Gx2VvH9Q?qijU{Bf{)@Xil zEfGKng6kjh+UGlwDDH&Y*WSerS(aZiMlzaiCQ#@xp$^$9XWjqO zWHcBK(F@y)uuV_Cl&M2Q|BJ9^xA%GsEx8MKc~5f#wV*kWVW#ydk;XrxodA#D){ zT1}&2G<;tVObh(Ix>H%zxHV~QD4hmnBZ|+)s12$IScFYZ>ah$Pbd?Tww_S`F#7Gf{ z-j4vqpE`++$>`KTeBEQbqAMGlx&4UjS((CR^vPV**J%fdMU@^)z=_qoPDiP_=56}O zu=u5_sw=V_AY4t&I1RWhUg-YH!)l*Hgy3zgm9J3`}+2dtdhVN}VQ_GUe z^C%1&yU>STOW6Cwy%PHi1q6IgqT!w1+uJ;NhOI|1(N3feF-a_3P@j%L>_Um93bI7-kdKk{X6`Aj-6%_K`#pwW;HuH^>DtE29KkD_gG=F6CkE=J|13jb@L2iVdv6sDJ9P=Hdw#d z)Ph&OvQ)P4Y_IH<88l6rC49&g<&`UJc!gmMK+z;{-uef~b{2;NGGU-AL+1^a!3w zT&TDS5U<9t6^9;u*NZ!ubT_T{Iw`BieV=yu)?1QefUZT{y6AeAOd(B64)Lb6aix$` zk~;rfx45;{NhhjAN@tg4bVbMQ{Z$p<5XBgoGLsE?naGNUv0&oU>_%y<`HtPYwpPk` zQ3*!1A-yI|MOUMKwePoPrH<~<&+$j3G2e_Rbw_)#CjJ7vH?;HYUzmwdSg@ek z5vYIgs%VaHYBbILDML8XU*-aQ_xSCLQE^wksfRjVCp;gjjO*dqaNW;ZzS3@%%C2@w zus=`-VGi1E*CF>@s(-un8Zhj|R$rv_<-bUo{7=zMG>J?`XGhwLG?J+{euGWR+h05p^9!J;mOPGbZOaPXzR^zUX$~4GH+*GEpU>jG91-FXt)+IEX>Lb zC^0(kT&nlwNnp|HP)rcPhb2|zA(nN#ZU_4Q0YBsiKrNX4_4ZdC@&<~6ZWZ3f`a;Dx zv05E_d-ikx?FmU<^KbtAK8f_$es|0YWANmux2{Yg=PJOkf)crnWU9Agw8w}ImUqN!$i)I znGd(`w^JD{CaajrwKD=SO~@OSkWH-?)i&9NeB4z%p!>|_wxq@Owc!;x!2I{Gbw9&v zQKEA1=Hv63zTwum@Bt4(LLS3)tf2spHOk!u21GfLxV@5ds_5;HnH6Rz4!U#X+Ch(& zu`j@Zq9iaPsAyH649}gd_VQgh!d>Fg++(=%D=yNWn2Hm{0{Y8x2xYbS&CAAW zfx>boXSJtyJAd@7cucfi1^galYC{W$qSr zzN*gmkx7$|m2Cx;WZ#{~S=q3J1sw@!d*v~QQu!?nbBl@DGE?OSBAPIrHq7wQK6L01IAqCIn&gDeS3)Jiisd8|(a4ZAx zjEJ=`{zSy*GExL8jdpAz|40VVrAm^tuNntMonok+u>>qX!uF(mH}`$YSUjPr{E-;Bj%B&PCm@8qI@)*dp-*lxNw|3Q;z1GcBzZ!9Rb{|0cP`?=sKn!ptDR zqvE8VoXt=U{M2L{BQ@Me!al;tLQ#ythRsyuKGFcn{j71#HgF@h0ZE*+yvmDgo$yq+ zBDWHBEioY6##ALR-8^Q`c7d%*vi@|j?f|}Q5Di5*((Yu52F2r$DrquZdLRc#b21Gd zwcdYt4ibZUX4h9L(fqIK8XZq}*S+fi5coc2%|JGB0A1cl&LSzeh$?QJivs|6Y^UuG z%UBC8Vz9InMEz#}&vFkP81}v|H$!GV*|*h-VeeL>vd5V;h2P6;-^Lfo?ruJ7VopWTGunOJ07tGuZarg+UW~{ zKf>a~GZL#6_lWb}CQNA)`3oR@?q&*NQIc)soR~;A`COSqcI@Vgs_;PtpWmy<=bKKN zrJQM~OV1(})rVNAZbhE}wVrth$+j@D6(#JBbn zf0mXEymk&`6+$g&ZF^gaTde~N!MH|fgHO0!D{jeb%OZ0OVkM!JHPw<7900s8iK)Aq zwc4QV5_)!Tf=)ur-TwgtKBn$HwN-YYS6<4vu1KMGy+_?81Pn|pmeJhX0c7vVA0^SK*udei_rE;G+IXl3o(EcD|$DSRh0P;L(agxZ{ zYc3m+rq=L%(bOv`ZU@v31tR(QxZ9+(e%R&>hZ1bi(p{|~ZlC6?p)5spg|YZCOb5h@ zyh>3`Ns6&GjPqN%*E+EP&DzbI3X&EVFF>JCuWESI{7|{Hu$W;9{b?Gy($U1R*P=OW zWG(BgbVm>8^8r`X#44<$TA`w@6ObVrvh>{vQht=0#Py~m7!Kf|`)6AZes6o;|NMfv zr~Ys`Oc{%aWwbutvrTTN_Z?+wXT!eN7v?=rA)}EWl5vI?8Wl~Oez|;H&5-AEkB=Jc zRCq9b@05h8i{ZugGVoi=xA+r3%)2$4;)yHxAlawS9yPJtW3zotJ&^G{rqer2*Wepl zXj+A8%3ujqG>N%L)#fY?D%WH!a3@_c5m{Tx4oVoKcu7O%U5M#53WKU^Dsq?a|{Nd>;ZVo|}?ZjQDVzx6yMRG9DJ) zBddec!QFY`o6XJ)r=`ogT|KY*&Kc2vUQoBX$I>&KB6iew?Z zgyACz2kwr#TB7Zee&T>Gdgt8_XG>-eNz~KK*z+0L^Kca~ynD>|oPgkeyp)>%+)6%j!exi7~ zZnN{$2TRJ0uF02pZkBWxDPT@lHdo3|FO6Nsx_CG8!o8)6FZ+uF0F#wc>0!ovB~U}w%e+-8ulkU zRwLF!&cw7N@3Z_2g*9yvS9N!_}GH6NoOGCdw0 zy;*2_re|4-gHRNsnwr(p?pAh2%M_PE&LNVzI)c*WmyDDZW}k1krGh>MzNp@p6(sAw zVXLnMobeFfVJwzX7x0R_cYk=q(yjTE5bu4%e=TE?_!m%Kqwkcy6!fEwX&1en0K@Z# zy)sA8S&`Xh5%Syad^%&0Kl_`LKa(NV>ysW&JXlQ#v`xdu~@3l@4$fJ?UIYcc$j z>zUt~MK{!mqfpDyuNz+{Hudz!E6+=TU9HI;9zwp8Al!}<5}qQh;-aFepi33RIyu;F z&UbA2F-t~&LhdbrmTjH@<8=A$M-LRn#s*&{*zk*N4eCL%F)l0~xH=Yu2%9|3P!sAujgdN^K;jIQn{|JZ0*(c@FY(b+^%PA!_*B=S9EUGIxR*^O-+ai zjcT)fA7Noqq^KHOY$0Z~+ZRPS3Ft6vr@fZGf~VhqIlwoh)GHnP-T(diu*%dcZ1PV?SDw zvKU0JZSM5z5L+|4!B`y8=TFGIjy*d?2<7OAuj|)$S|>m`caWy(Qfxe5kw$Vl<%+&~ z7^E6b0Y^47BjJ^Z-(y=sdc2IMHbR2dhUD5NPT%CcLf%h86cwPe6<1uHy`@{MxicTq zGTWBa=JL5|OIPdRFHqP^ffFR9MG${(@KUGqP$mTH`%ZQN4|7>s3vG2732eKeU3P)8 zGRMr*Z|NRCB2kpvga{*Q=NtoPA~ZS2Z_%3(D@bhSTW4Gypkp5q@$5ORBmx_tX}6uh zo9F?-m>m6TgYFo7oou;|m)4betDWt6>F>!7?fO+Za8fl7xV&fJ(cn;T@i+;MJFZ(Y zXaPJHr_7tk$`3n+^5#480w0%tbGvlRXFsBmGTNLiyEChF>Yn+V) ziaRZRqghxiYM8ee)aK^YNbvQUFEmv+(cGO7$*IKZxAdV+_0y$j`Vz&$1kOsV;OHlv z$u8dU!Bkx_Az{1eyas3$<`>KH9F{V8} z46OY%Mp2q~e-MPj7H1D&s|No2b;hnMtKIAKEhYNytaX&F2?4BLyE-WW8CFAu5Tw`Vv6gK)`M|&nM+dmhp+fym4GP znv}0}9Sc1t4z8_`5pdKEh^5|XBk&N366&G(nXzK`JwO!U#pgg|etZBinId)+AglHH zgm{`7`In6Kf{dnD9!{3(C`Ow)WF7&xN_OG46v>;}i(U}%Fd2!${5M78oYwZQWjoY2 ztSuC5cJv@k=9X%9bSMhZ(%Np27P4t5A&C@`G)>w>^4Msh*Ip8YQ})SKj@oBx7ksX0 zAp_fbdkEJEQtFbsqN^08%UxLHUA72Q#fYEo*9)!Xj}qqjwj0^GB}tO@Np)1g--8Dq z4I^oMvXeHfCWYDjY(5o`_!%qtw%mFf>1Tm=OH}-_7xxasETpgGRrC1lKLT6)n=~1A8tB-Mv#to&v7M{EE>oMs+gw1*!Q39lj##B79Tc^jmbe+%g}U{kaUB zV2zbyPT#J$Qj!1p4XM`$b=Mf^vcSpCH7IOO(ax!=pG+t+HoZxly9bF5|5-sVHk}sY@0lrs5lU$;A?DJ8GJRL_3Ja)-%?BC6_FKt&D&j zdpXvTA-ks}R}?RM%=wlwf=*=(3HVLNIvfzsBOIkZZp{9Zv7udBLIa|_52!youY^SW!@`jXUG(^5x?`ZcA`I0B)G?CPt$c{Bz zP*zPDF9lE_sR(siRr>_~d>@VZpnwr)53g~v1q8UsP}Lw%6CUZEYl6WpL>E;Xhwpmy z?0MRZYfcQuPIGK_ENgW2fz^%GWfgO#{|W6?k9EP4uBZel=dK zPM@6ykhAUE;O37FHY)pHZs<*O*zpFRZ)FL7I2k`YtD}U(Z`;Yf*`Bz!^~D-l zuO0(FNkr+TuhXmzTIk0Km8w~geHrE99_CvP=4tLKHIJ)adchywOGzK7$H2?;vK@z! zhUmV?YX=8nYc-xeR<{U`Qz3SXQ@;P3K03vEP>#nkS7tSJ-ySP~Wnqgj;Pn&mrpmBH z{?6ZqRGQ^4;4gp};g1}{qK^@5Nc|UpQ@`Ly&O|Qp+?(gy=s`>RU=hsKrhJ?EOD^x% z=`xzd-a(XMPJzEt6ueNpqgB>Uw6^UbOoOTIqu{DDa_lLOa^u_ ze|T*c-O^vygjRidIV-jn3A|CfVaip#DP#6VmZ%SGR=eGrRwlZgmwRJvuqoxiQJZ@H z-u?@_Ig5GfBjy-U7FGa_sK4gYDS^hBf!c_H;W2ywm%6d)6%GBZ7~xm?cM>N9q0a~y zlf3mMO2Yr~8|83qc?@neUQX7ZwUFWK$EK%@O#&Tjn|&0=tqO*U6D@7eGUt{au0O2K-IFj_#&7>P$5qhVTr!m5NqZ^jQ~-K8 za^u=xCHn*Y@OKO?2pANYOl!nc#ILjvIg>^!5#94rE!@$|Z3m410@yr-VgF!yU1pW! zV}c6k`<{(^3Yst^M+!N*p8+Cq5{2C-1&R+RP{*b^(tOg32rK=C*1DV-1V48+=jki4 zfX7#v=~nMAO2o?;i>wTVqduaaH_jZ`PRsFQEJ#dgckNQ!!tc;xdcX=+VCghO*YSA> z$M8Or3|S?U6-L$8X>G8FG2CdIg}c@EkL-TxFP<5navmhju0X--VVfp~v2$B{KbGkj zPI>u=cDnJmSC#nOhZ5TzTEO0RcpJ>_1amnDt5@XCzh5<(3+cI5Vz5dZbn_G|II&Hg z1by%lC-+@EzGy9yM-Q!x`z3bg1)5SqT7q>;C>2}^_s~H|=F;FF&1zGy!Y$au0r%;a zCWu{DMNSZ4597bDw70e61lX{B9k;RY*#<9HVG8g8f{9Dtc=V5@%eEJuSf{$HnuSas zJE<-XK6!=xjywSF;@qXPHX3fO`z+759qKEcjrq89W~J6?y>}62Ii$I-)T3T-qgbi^ z?hgOeX5IgH9bn~;*<4;WaFB2jiGKZYn6!4?FYMBQu;{MFd%ICITHjN2-0oMar9E8^ z3G;g1hpsW#KI-s|o`aB3OJ+xy&4pctlcoF4329VD2OQa<;^fp{X)PFsbZk=7C~gr+ zvHgZUf|iyJy`t`Uva$)OFleMDEpR2*1yqujixa~cp&-h3S)>Q$G~0nF*=1rxpi<;5 zQhq{RNg99E$|-$1qPuB28xcQJb_dWiHD1yg>ob1&eVCE z49#B2&S)k3v+!&Xe{=`U;=j}M^Ial3mDaMLj8h!`i1wr(!&H%OXV*pxX^ZNNqRb;= zz`k2^rDorp@SEoWiD=Qbb-5iwcjT3E%G)$jEUB43Hgs0Y?0gSDll{ylS@Fd+WL_uliSqWEVQ-%ma-OF1fo zd|UVApGw9isdsIYmPWjgCL<#oYKRf!^O@?x@YJysgC^$BsBVwnGX@vr8#F@_k--+b+Ub+P`Z99z902UwA1l>(L@A64 zOU2pP1QLdi)tR-RH-1UXDoq=V!>b{A(T}1I-c}`XV5vQKKzEWOjfW!FN~ODO)wC%F z_bYG+Q4;u=R-JwOTntHF9UQ4zeZOb$km=*>ptiG|teITitgb0PU)#>-6n$7XUQw}h zyttY0sqxRb7W;tB+1qzaJo9s{uFXFug@ms|)uBIyGK=E*cfyH1A1T8>{NUeFVWR(M zvCwKe2sX0<&+HAt=sCuU9~A%q;vzNUTHP=G^`N*oEfywC9<^#s14(Dpq&Mk8-RZ0D zxmv5h`14a`rVhoMr-0o6RTzFWYtL)Uc{x^KeV_>ooEuO|A^!h!m${%^-7&z#qpc4gG$w!Va}b)wmQf^3GPp+WD8 zR4&%nF=ZnMnTW`*3F=3M+g1h9cdmK=K0W{O%K86mGK#amhRYNs%G8O4b9S2_qM;Ua zo9TnFS{!uTZzc`#e+q9!A9U9a$bX^e=Wj3DBPwTZiK&aRUi2>C7}OEw{zL9I1)p_rDdPf57HJ? zeWrzWL8~HDqzls>N>~*om*mbz*K@zX&*AChymvZ1J=8(729Us&Z6B({nbOy@$IXxn)Pf* zf?$3fSWJCfumDVe#ERq$VWQ>~OtYZXg?V-?1R47dVW1amN^d>gm7X$aE9|zEyB;cl zh8EN`z?nB6NJ$$l$;8rib?)u@{fOC|Oq)3GU}Urtf4(DzsG6XdEpk`BK}buzu*F#8 zA%xpa|9y3!_|4@lQiW)d?JM}QHYcpKYo_&T4>)G|!M7wy&zuS|8LYoarohx2co$@< zfe`~;O*v(MP5JTbqq}7~Lf-#r?L4EJY_~KVntXIY5D`Kz(h(`rMF-qEke)hZ8{p@}3 z>mH~4l`r2n`1P!H{gZKM=0laSz}Qtd)lc<^lg(vCg4VlVF&P2mE5qMSPuPMrSLedH z+hz9V$`yED_(--jccB=?3*s94A&>PAxkc{LX{FA zmc*@j|7BSC&(Ni7aVht_-e~nMY>BRl$zrE{2h+UT&J0I-_o42N+w0HKEgp8ZXdihs z7~ZR)J0(Nzoc#iOZMF6r)futCIP+Lbmmkqkqh*KWhdIvN{C)e-T+~gf{f3{>1imrh zL(L0YRk4m2UvCK=b2j?$4>@fgQKvd9oc%3e*>|&MVBJSEb!IV@`JI7!bUj(2u9a18 zv+M>G&IH+1(dnVl-Di^3nqp)~Q)J%}H-L%+LQ-@jHnO%XVKkEU^tzEe^@YLJ3am3{G5dBXXZPW)EiPWSK6a&QpKJGr2@CqZ^TGMF&1phZ$CjY6cQomn7kcIgAEG*UVsD@;- zxdYB_+F)R^E-l=jnZr(by6Lg7S+v8Ok`jNQ)yXc#Ws*}N6ppR-h@PBnWyN(Br{!{q zm@66sLsT(xK~a~DygoRfC(i^2JFTQBBrCU3j$5~-f+UkMsgK?(1~j73VOSI&174ND%_^X7nK$@7*`S&w*KLc|tq&fg8HqC8O#Tv)SZr*k1)N z1f02r6K>qJ2vVaKiyVy^S{ju89MEA|GBhS9+0$ZRTuUhHv2cbuiZs%A&nb?BN*@Tl(M1zN0HP$LtosL`8+y*?QPGmNW2(MAxVOSoF73V z%!=IU^SZ8TQ?q{D7jg)gOXb_l$FyJ2S>8n!)V$9TFRX1dXqZr8(m=IHh4gv6z^64J zv(7g+?=`0=Y}<0gA-`lv-&26&N`{bFTAq_O?1fp1t|ys>=% z(;%;krE;m-4ol@P+|ZD_QC6D15zlLktc=I)*(a(NmX}N9K?!k+(Oj05&yW*cP0;h7 zQ)5sIzDt6K&q^<&jpuSOHCTLeZZke#$$_v2&IzZzNBfcDToiM{+6lPA&ggd-gWnSQ5qvolW6gQVu>t?I$&V90L+8Q6e z595zstT3z}mivn*jdd8+U&_zB=PU3 zMEc^FpHLwvclW{7R+2tbLTU0nsY+Rab|`;Vamo>$bWt6d!)j2WoFSu^X@`G^(=IcV z+GQJ*en?qbv1MbhL7WiRu~J)PutCnQ{ ztg4b|sk^5}k{%fM&!G{cF;8{ZtsI%SyPSBtKe?dbmRhtf1tj2L3UFm*cO|4x@DXMB zTNeP?%g;oypY}4KZf@WPerjunU<32E%G}g(B3p|@Hn_;UKds1>&?US=suZu_vamU9 zEoQ_7VX!Mha7fzy6({5kOBhxAJ044$t<<{kGOQpl zsj6&<=Uw5tw>*gT98zi#YT6li==HF&@|BDZ?6^6P%f(Cc$=nXG(dzsZJh*=TEcge`t8HbxG{sIv(%q zHdQtJa?J)oiKIF$blF!|H{9l}un;MnR@IaU!W{4x9%lK^k)0+glR2kguYwo226*pP>Aas@@vDdU2X>qc%keea+X$$U>@_ zH9al-2HS@lSM(SYo2gtjdOK6ax!I0k=xk9P4}^SAl5fh~3PW#DlskAO!%TK13676# zU-4QiUoSH~HZBSXxcexY(h8gU`tx*-1)e!J;74q@hwD?7sbfjTwxOQjm;GGVp^F(? z=ZW8UTe{2Rb^MLkb9eC1pvUQQfIH@DjbelnBc_@`_={Wi9XmF`Wu}GpCPwcXvS(1H z;Xq@z(mACqtQ-v%@-oSd#a4>B8)I8pj5rgK4C=MlpPMazvQP25PYR0z`f&5zslDZ- zSkJ^|sbJk84wz|F@pTq2=yCvobn&+oI8@9@JHIHL8C@P(pu30)jW+$I-GYSx3{%%4F)0VV)Bo5Es zoljrk40x&{JwHDy+w`$j`*~m>IfDGOh;X@Z1)wm~Fx*sq9DC1rA=HU!XM!d%5Lx)` zE{Ybej~m9iF8BUAx;xZ_-p{GOE`R)cJ8U@Y2P8G7a{Myo!PcZcpR*XlyCa)TG2WVa zzv<92nOe7_jZzy!pXwuop%J1Hcv=#bLP(=%`1rRJSl9>MpmOHeqi7bUMN`n1Dyw2Od&we&Lm)%c z1-{~MX90`8ae$FASWSUoh{2!gV9mmXYB&j1@mmQ7BO(+>vE)1C!|H1@CU zE>zc<^QT3M=8jj<`F;8=7@)YD|iQFJ_oOksmi|_*j`eN8&Ch$j~ebS0SSd-_pw8 zyJim6uD35F<|3m3IBVjoI<+^EApbRKwf z{{kGGe*aC!-*urdLye2E@n59nfIGO6HeV$Ye{dy9uZ}giuNajskOfLf@(NlB#TAEc zByWr(bq}t}?d}1TESF!+1oW&P1w0cKLD?%&eD{-v9#vSeK=M5tWkf}o;SZ9^dI(iO zQ?&+x^2(-UN&iKE2+f$%u~+MFm-D+$xXG!ryc>IELTzLeLXwQ~ePT<49GHSysWH}1 zVm)(<3OLXS5RXF^UgId1N?wdZszdnxpubv${3IYv9pqnDk_yT+!gI_M$1UG|= z8a_sX_5({$DF#y^kch*5vel^D2?rU8$>OZ&#{_1PK_$Yh#H+sAWJ*bn?OSzo znCC15&$zg(x&9G%>-iEk#!BsHZ2F$nc!Ov=4 z8wLNQ&6&{Zp}qIkFn#UgmufHOH#(^-w!9C+?3}WiZi*#@C`?4McW%7#wW!&Ti0|*c zZ6&u7F>2%1d9^XLcNZ$s&$sA(y`{$l3R}81x7?_GQS8`KWd}_S5jyV3mlGLl!)k7# z^E>AHPI=oY^*8Sn7wO=(=N*(Tk;F?o8w*v#$>noR{VLf*;gnJ7`m>fl0G1QAki@a9 zKY(YO{ot*yO_$HKVoB|XMPN|&X%Ld&brP?eX3Z3di69-YqeScA^}9#p1wwMW`iDV^ zp#^~X(ynOxD4%%Ajzas*qnPPDOCMyD;=cKCAt~=@-kjJyY758$2;Pb55;@XS4jmKt z*bpNhG9!D3Vd^3$H@#3#G2wk-^5I1WB0`T<|IV0X#P1`&?nK z(IpX__XMxpQFnQO9M#G?F|PQS)x#_56)WfS#$ZAv*EFz7$XYwp`!Q#N6#}u;^?bYQ zZp%|CR;prj5zy@2nmo8U-xQ5P$Gy36Sge!Lk9kqIwBtV%YMJZHQ!IwtQX_{Y1+jR3 z*Z4$-cI_Jd?;87`KJ9?(jiTq5@(KROj3CM`q2{-i-(=q@6Ui(bP~$F|}7^RncG`-eZG_R4xf?n~GK#Y+vQ zxV)#tA|2Ed2{g!Xcs`!v;@Yrr<@F zHIt-mUSiMmW3#>Y2or8n^(H*;+b%auu0O1*%4x`bf-LyBz5|_UZ;Sf_=xk15T;Bs} zF6r;hq;?-BI28at8aV{BXh6Nz>j?u!h>+&(Jw?8YF_Uv2a!$9iRu}R| z+9VRp+^{Od(FvDynLTCJk^H+45X^^w|IKFUzt5Ntf6xx{ik_88(Xk?wuXt67g^7_~E6G)cqb;3_?R5!D qWg*)r|Kk|1q|T(6c!LiVz4iYYa{Yg*kAGkF{x1pq|M;QxXZjx%#$-zX literal 0 HcmV?d00001 diff --git a/Documentation/content/examples/index.md b/Documentation/content/examples/index.md index b92e8a2ad50..7efb9835333 100644 --- a/Documentation/content/examples/index.md +++ b/Documentation/content/examples/index.md @@ -110,6 +110,8 @@ This will allow you to see the some live code running in your browser. Just pick [![ThresholdPoints Example][ThresholdPoints]](./ThresholdPoints.html "Cut/Treshold points with point data criteria") [![ShrinkPolyData Example][ShrinkPolyData]](./ShrinkPolyData.html "ShrinkPolyData") [![CleanPolyData Example][CleanPolyData]](./CleanPolyData.html "CleanPolyData") +[![RibbonFilter Example][RibbonFilter]](./RibbonFilter.html "RibbonFilter") + @@ -131,6 +133,7 @@ This will allow you to see the some live code running in your browser. Just pick [ThresholdPoints]: ../docs/gallery/ThresholdPoints.jpg [ShrinkPolyData]: ../docs/gallery/ShrinkPolyData.jpg [CleanPolyData]: ../docs/gallery/CleanPolyData.jpg +[RibbonFilter]: ../docs/gallery/RibbonFilter.jpg # Sources diff --git a/Sources/Filters/Modeling/RibbonFilter/example/index.js b/Sources/Filters/Modeling/RibbonFilter/example/index.js new file mode 100644 index 00000000000..52ffe6029a8 --- /dev/null +++ b/Sources/Filters/Modeling/RibbonFilter/example/index.js @@ -0,0 +1,73 @@ +import '@kitware/vtk.js/favicon'; + +// Load the rendering pieces we want to use (for both WebGL and WebGPU) +import '@kitware/vtk.js/Rendering/Profiles/Geometry'; +import '@kitware/vtk.js/IO/Core/DataAccessHelper/HttpDataAccessHelper'; + +import vtkFullScreenRenderWindow from '@kitware/vtk.js/Rendering/Misc/FullScreenRenderWindow'; +import vtkActor from '@kitware/vtk.js/Rendering/Core/Actor'; +import vtkRibbonFilter from '@kitware/vtk.js/Filters/Modeling/RibbonFilter'; +import vtkMapper from '@kitware/vtk.js/Rendering/Core/Mapper'; + +import vtkPoints from '@kitware/vtk.js/Common/Core/Points'; +import vtkCellArray from '@kitware/vtk.js/Common/Core/CellArray'; +import vtkPolyData from '@kitware/vtk.js/Common/DataModel/PolyData'; + +// ---------------------------------------------------------------------------- +// Standard rendering code setup +// ---------------------------------------------------------------------------- + +const fullScreenRenderer = vtkFullScreenRenderWindow.newInstance(); +const renderer = fullScreenRenderer.getRenderer(); +const renderWindow = fullScreenRenderer.getRenderWindow(); + +// ---------------------------------------------------------------------------- +// Example code +// ---------------------------------------------------------------------------- + +// Spiral parameters. +const nV = 256; // Number of vertices +const rS = 2; // Spiral radius +const nCyc = 3; // Number of helical cycles +const h = 10; // Height + +const points = vtkPoints.newInstance(); +let p = [0, 0, 0]; +for (let i = 0; i < nV; i++) { + const angle = (2 * Math.PI * nCyc * i) / (nV - 1); + const vX = rS * Math.cos(angle); + const vY = rS * Math.sin(angle); + const vZ = (h * i) / nV; + p = [vX, vY, vZ]; + points.insertPoint(i, p); +} + +const v = [nV, ...Array.from({ length: nV }, (_, i) => i)]; +const lines = vtkCellArray.newInstance({ + values: v, +}); + +const polyData = vtkPolyData.newInstance(); +polyData.setPoints(points); +polyData.setLines(lines); + +const lineActor = vtkActor.newInstance(); +const lineMapper = vtkMapper.newInstance(); +lineMapper.setInputData(polyData); +lineActor.setMapper(lineMapper); +lineActor.getProperty().setColor(1, 0, 0); +lineActor.getProperty().setLineWidth(3); + +const ribbonFilter = vtkRibbonFilter.newInstance(); +ribbonFilter.setInputData(polyData); + +const ribbonActor = vtkActor.newInstance(); +const ribbonMapper = vtkMapper.newInstance(); +ribbonActor.setMapper(ribbonMapper); + +ribbonMapper.setInputConnection(ribbonFilter.getOutputPort()); + +renderer.addActor(ribbonActor); +renderer.addActor(lineActor); +renderer.resetCamera(); +renderWindow.render(); diff --git a/Sources/Filters/Modeling/RibbonFilter/index.d.ts b/Sources/Filters/Modeling/RibbonFilter/index.d.ts new file mode 100644 index 00000000000..c3f51095402 --- /dev/null +++ b/Sources/Filters/Modeling/RibbonFilter/index.d.ts @@ -0,0 +1,196 @@ +import { vtkAlgorithm, vtkObject } from '../../../interfaces'; +import { Vector3 } from '../../../types'; + +export type IGenerateTCoords = + | 'TCOORDS_OFF' + | 'TCOORDS_FROM_SCALARS' + | 'TCOORDS_FROM_LENGTH' + | 'TCOORDS_FROM_NORMALIZED_LENGTH'; + +/** + * + */ +export interface IRibbonFilterInitialValues { + useDefaultNormal?: boolean; + width?: number; + varyWidth?: boolean; + angle?: number; + generateTCoords?: IGenerateTCoords; + widthFactor?: number; + textureLength?: number; + defaultNormal?: Vector3; +} + +type vtkRibbonFilterBase = vtkObject & vtkAlgorithm; + +export interface vtkRibbonFilter extends vtkRibbonFilterBase { + /** + * Get the angle (in degrees) of rotation about the line tangent used to + * orient the ribbon. + */ + getAngle(): number; + + /** + * Get the default normal used to orient the ribbon when no normals are + * provided in the input. + */ + getDefaultNormal(): Vector3; + + /** + * Get the default normal used to orient the ribbon when no normals are + * provided in the input. + */ + getDefaultNormalByReference(): Vector3; + + /** + * Get the method used to generate texture coordinates. + */ + getGenerateTCoords(): IGenerateTCoords; + + /** + * Get the method used to generate texture coordinates as a string. + */ + getGenerateTCoordsAsString(): string; + + /** + * Get the texture length, used when generating texture coordinates from + * length. + */ + getTextureLength(): number; + + /** + * Get whether to use the default normal to orient the ribbon when no + * normals are provided in the input. + */ + getUseDefaultNormal(): boolean; + + /** + * Get whether to vary the width of the ribbon using scalar data. + */ + getVaryWidth(): boolean; + + /** + * Get the width of the ribbon. + */ + getWidth(): number; + + /** + * Get the width factor, used to scale the width when varying the width. + */ + getWidthFactor(): number; + + /** + * + * @param inData + * @param outData + */ + requestData(inData: any, outData: any): void; + + /** + * Set the angle (in degrees) of rotation about the line tangent used to orient the ribbon. + * Default is 0.0. + * @param angle The angle in degrees. + * @returns true if the angle is set successfully. + */ + setAngle(angle: number): boolean; + + /** + * Set the default normal used to orient the ribbon when no normals are provided in the input. + * The default normal is a vector defined by three components (x,y,z). The + * default is (0,0,1). + * @param defaultNormal The default normal as an array of three numbers or a Vector3. + * @returns true if the default normal is set successfully. + */ + setDefaultNormal(defaultNormal: Vector3): boolean; + + /** + * Set the default normal used to orient the ribbon when no normals are provided in the input. + * The default normal is a vector defined by three components (x,y,z). The + * default is (0,0,1). + * @returns true if the default normal is set successfully. + */ + setDefaultNormalFrom(defaultNormal: Vector3): boolean; + + /** + * Set the method used to generate texture coordinates. By default, texture + * coordinates are not generated. + * @param generateTCoords The method to generate texture coordinates. + * @returns true if the method is set successfully. + */ + setGenerateTCoords(generateTCoords: IGenerateTCoords): boolean; + + /** + * Set the texture length, used when generating texture coordinates from length. + * The default is 1.0. + * @param textureLength The texture length. + * @returns true if the texture length is set successfully. + */ + setTextureLength(textureLength: number): boolean; + + /** + * Set whether to use the default normal to orient the ribbon when no normals are provided in the input. + * The default is false. + * @param useDefaultNormal Whether to use the default normal. + * @returns true if the flag is set successfully. + */ + setUseDefaultNormal(useDefaultNormal: boolean): boolean; + + /** + * Set whether to vary the width of the ribbon using scalar data. By default, + * the width of the ribbon is uniform. + * @param varyWidth Whether to vary the width of the ribbon. + * @returns true if the flag is set successfully. + */ + setVaryWidth(varyWidth: boolean): boolean; + + /** + * Set the width of the ribbon. The width is the total width of the ribbon; + * the ribbon extends width/2 on either side of the line. The default is 0.5. + * @param width The width of the ribbon. + * @returns true if the width is set successfully. + */ + setWidth(width: number): boolean; + + /** + * Set the width factor, used to scale the width when varying the width. + * The default is 1.0. + * @param widthFactor The width factor. + * @returns true if the width factor is set successfully. + */ + setWidthFactor(widthFactor: number): boolean; +} + +/** + * Method used to decorate a given object (publicAPI+model) with vtkRibbonFilter characteristics. + * + * @param publicAPI object on which methods will be bounds (public) + * @param model object on which data structure will be bounds (protected) + * @param {IRibbonFilterInitialValues} [initialValues] (default: {}) + */ +export function extend( + publicAPI: object, + model: object, + initialValues?: IRibbonFilterInitialValues +): void; + +/** + * Method used to create a new instance of vtkRibbonFilter + * @param {IRibbonFilterInitialValues} [initialValues] for pre-setting some of its content + */ +export function newInstance( + initialValues?: IRibbonFilterInitialValues +): vtkRibbonFilter; + +/** + * vtkRibbonFilter is a filter to create oriented ribbons from lines defined in + * polygonal dataset. The orientation of the ribbon is along the line segments + * and perpendicular to "projected" line normals. Projected line normals are the + * original line normals projected to be perpendicular to the local line + * segment. An offset angle can be specified to rotate the ribbon with respect + * to the normal. + */ +export declare const vtkRibbonFilter: { + newInstance: typeof newInstance; + extend: typeof extend; +}; +export default vtkRibbonFilter; diff --git a/Sources/Filters/Modeling/RibbonFilter/index.js b/Sources/Filters/Modeling/RibbonFilter/index.js new file mode 100644 index 00000000000..2445e1e6894 --- /dev/null +++ b/Sources/Filters/Modeling/RibbonFilter/index.js @@ -0,0 +1,493 @@ +/* eslint-disable no-continue */ +import macro from 'vtk.js/Sources/macros'; +import vtkPolyData from 'vtk.js/Sources/Common/DataModel/PolyData'; +import vtkPoints from 'vtk.js/Sources/Common/Core/Points'; +import vtkCellArray from 'vtk.js/Sources/Common/Core/CellArray'; +import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray'; +import vtkMath from 'vtk.js/Sources/Common/Core/Math/index'; + +const { vtkWarningMacro } = macro; + +// Texture coordinate generation modes +const GenerateTCoords = { + TCOORDS_OFF: 0, + TCOORDS_FROM_SCALARS: 1, + TCOORDS_FROM_LENGTH: 2, + TCOORDS_FROM_NORMALIZED_LENGTH: 3, +}; + +// ---------------------------------------------------------------------------- +// vtkRibbonFilter methods +// ---------------------------------------------------------------------------- + +function vtkRibbonFilter(publicAPI, model) { + // Set our className + model.classHierarchy.push('vtkRibbonFilter'); + + // Private methods + function generateSlidingNormals(points, lines, normals) { + // Simplified normal generation for polylines + // This is a basic implementation - you might want to use the actual vtk.js implementation + const lineArray = lines.getData(); + let offset = 0; + + for (let cellId = 0; cellId < lines.getNumberOfCells(); cellId++) { + const npts = lineArray[offset++]; + const pts = lineArray.slice(offset, offset + npts); + offset += npts; + + if (npts < 2) continue; + + for (let i = 0; i < npts; i++) { + const v1 = [0, 0, 0]; + const v2 = [0, 0, 0]; + + if (i === 0) { + points.getPoint(pts[1], v2); + points.getPoint(pts[0], v1); + } else if (i === npts - 1) { + points.getPoint(pts[i], v2); + points.getPoint(pts[i - 1], v1); + } else { + points.getPoint(pts[i + 1], v2); + points.getPoint(pts[i - 1], v1); + } + + const tangent = [v2[0] - v1[0], v2[1] - v1[1], v2[2] - v1[2]]; + vtkMath.normalize(tangent); + + // Generate a normal perpendicular to the tangent + let normal = [0, 0, 1]; + if (Math.abs(vtkMath.dot(tangent, normal)) > 0.9) { + normal = [1, 0, 0]; + } + + const binormal = [0, 0, 0]; + vtkMath.cross(tangent, normal, binormal); + vtkMath.normalize(binormal); + vtkMath.cross(binormal, tangent, normal); + vtkMath.normalize(normal); + + normals.setTuple(pts[i], normal); + } + } + return true; + } + + function generatePoints( + offset, + npts, + pts, + inPts, + newPts, + inScalars, + range, + inNormals, + outPD, + pd, + newNormals + ) { + const theta = (model.angle * Math.PI) / 180.0; + let ptId = offset; + const p = [0, 0, 0]; + const pNext = [0, 0, 0]; + const sNext = [0, 0, 0]; + const sPrev = [0, 0, 0]; + + for (let j = 0; j < npts; j++) { + if (j === 0) { + inPts.getPoint(pts[0], p); + inPts.getPoint(pts[1], pNext); + for (let i = 0; i < 3; i++) { + sNext[i] = pNext[i] - p[i]; + sPrev[i] = sNext[i]; + } + } else if (j === npts - 1) { + for (let i = 0; i < 3; i++) { + sPrev[i] = sNext[i]; + p[i] = pNext[i]; + } + } else { + for (let i = 0; i < 3; i++) { + p[i] = pNext[i]; + } + inPts.getPoint(pts[j + 1], pNext); + for (let i = 0; i < 3; i++) { + sPrev[i] = sNext[i]; + sNext[i] = pNext[i] - p[i]; + } + } + + const n = [0, 0, 0]; + inNormals.getTuple(pts[j], n); + + if (vtkMath.normalize(sNext) === 0.0) { + vtkWarningMacro('Coincident points!'); + return false; + } + + const s = [ + (sPrev[0] + sNext[0]) / 2.0, + (sPrev[1] + sNext[1]) / 2.0, + (sPrev[2] + sNext[2]) / 2.0, + ]; + + if (vtkMath.normalize(s) === 0.0) { + vtkMath.cross(sPrev, n, s); + if (vtkMath.normalize(s) === 0.0) { + vtkWarningMacro('Using alternate bevel vector'); + } + } + + const w = vtkMath.cross(s, n, []); + if (vtkMath.normalize(w) === 0.0) { + vtkWarningMacro(`Bad normal s = ${s} n = ${n}`); + return false; + } + + const nP = vtkMath.cross(w, s, []); + vtkMath.normalize(nP); + + let sFactor = 1.0; + if (inScalars && model.varyWidth) { + sFactor = + 1.0 + + ((model.widthFactor - 1.0) * + (inScalars.getValue(pts[j]) - range[0])) / + (range[1] - range[0]); + } + + const v = [ + w[0] * Math.cos(theta) + nP[0] * Math.sin(theta), + w[1] * Math.cos(theta) + nP[1] * Math.sin(theta), + w[2] * Math.cos(theta) + nP[2] * Math.sin(theta), + ]; + + const sp = [ + p[0] + model.width * sFactor * v[0], + p[1] + model.width * sFactor * v[1], + p[2] + model.width * sFactor * v[2], + ]; + + const sm = [ + p[0] - model.width * sFactor * v[0], + p[1] - model.width * sFactor * v[1], + p[2] - model.width * sFactor * v[2], + ]; + + newPts.setPoint(ptId, ...sm); + newNormals.setTuple(ptId, nP); + outPD.passData(pd, pts[j], ptId); + ptId++; + + newPts.setPoint(ptId, ...sp); + newNormals.setTuple(ptId, nP); + outPD.passData(pd, pts[j], ptId); + ptId++; + } + + return true; + } + + function generateStrip(offset, npts, inCellId, outCD, cd, newStrips) { + const stripData = []; + for (let i = 0; i < npts; i++) { + const idx = 2 * i; + stripData.push(offset + idx); + stripData.push(offset + idx + 1); + } + + newStrips.insertNextCell(stripData); + const outCellId = newStrips.getNumberOfCells() - 1; + outCD.passData(cd, inCellId, outCellId); + } + + function generateTextureCoords( + offset, + npts, + pts, + inPts, + inScalars, + newTCoords + ) { + // First texture coordinate is always 0 + for (let k = 0; k < 2; k++) { + newTCoords.setTuple(offset + k, [0.0, 0.0]); + } + + if ( + model.generateTCoords === GenerateTCoords.TCOORDS_FROM_SCALARS && + inScalars + ) { + const s0 = inScalars.getValue(pts[0]); + for (let i = 1; i < npts; i++) { + const s = inScalars.getValue(pts[i]); + const tc = (s - s0) / model.textureLength; + for (let k = 0; k < 2; k++) { + newTCoords.setTuple(offset + i * 2 + k, [tc, 0.0]); + } + } + } else if (model.generateTCoords === GenerateTCoords.TCOORDS_FROM_LENGTH) { + const xPrev = [0, 0, 0]; + const x = [0, 0, 0]; + let len = 0.0; + inPts.getPoint(pts[0], xPrev); + + for (let i = 1; i < npts; i++) { + inPts.getPoint(pts[i], x); + len += Math.sqrt(vtkMath.distance2BetweenPoints(x, xPrev)); + const tc = len / model.textureLength; + for (let k = 0; k < 2; k++) { + newTCoords.setTuple(offset + i * 2 + k, [tc, 0.0]); + } + xPrev[0] = x[0]; + xPrev[1] = x[1]; + xPrev[2] = x[2]; + } + } else if ( + model.generateTCoords === GenerateTCoords.TCOORDS_FROM_NORMALIZED_LENGTH + ) { + const xPrev = [0, 0, 0]; + const x = [0, 0, 0]; + let length = 0.0; + let len = 0.0; + + // Calculate total length + inPts.getPoint(pts[0], xPrev); + for (let i = 1; i < npts; i++) { + inPts.getPoint(pts[i], x); + length += Math.sqrt(vtkMath.distance2BetweenPoints(x, xPrev)); + xPrev[0] = x[0]; + xPrev[1] = x[1]; + xPrev[2] = x[2]; + } + + // Generate normalized coordinates + inPts.getPoint(pts[0], xPrev); + for (let i = 1; i < npts; i++) { + inPts.getPoint(pts[i], x); + len += Math.sqrt(vtkMath.distance2BetweenPoints(x, xPrev)); + const tc = len / length; + for (let k = 0; k < 2; k++) { + newTCoords.setTuple(offset + i * 2 + k, [tc, 0.0]); + } + xPrev[0] = x[0]; + xPrev[1] = x[1]; + xPrev[2] = x[2]; + } + } + } + + publicAPI.requestData = (inData, outData) => { + const input = inData[0]; + const output = outData[0]?.initialize() || vtkPolyData.newInstance(); + outData[0] = output; + + if (!input || !input.getPoints() || !input.getLines()) { + return; + } + + const inPts = input.getPoints(); + const inLines = input.getLines(); + const pd = input.getPointData(); + const cd = input.getCellData(); + + const numPts = inPts.getNumberOfPoints(); + const numLines = inLines.getNumberOfCells(); + + if (numPts < 1 || numLines < 1) { + return; + } + + // Get scalar data if available + let inScalars = null; + const scalarsArray = pd.getScalars(); + if (scalarsArray) { + inScalars = scalarsArray; + } + + let inNormals = pd.getNormals(); + let generateNormals = false; + + if (!inNormals || model.useDefaultNormal) { + inNormals = vtkDataArray.newInstance({ + numberOfComponents: 3, + size: numPts * 3, + dataType: 'Float32Array', + }); + + if (model.useDefaultNormal) { + for (let i = 0; i < numPts; i++) { + inNormals.setTuple(i, model.defaultNormal); + } + } else { + generateNormals = true; + } + } + + // Calculate scalar range if varying width + let range = [0, 1]; + if (model.varyWidth && inScalars) { + range = inScalars.getRange(); + if (range[1] - range[0] === 0.0) { + vtkWarningMacro('Scalar range is zero!'); + range[1] = range[0] + 1.0; + } + } + + const numNewPts = 2 * numPts; + const newPts = vtkPoints.newInstance(); + newPts.setNumberOfPoints(numNewPts); + + const newNormals = vtkDataArray.newInstance({ + numberOfComponents: 3, + size: numNewPts * 3, + }); + + const newStrips = vtkCellArray.newInstance(); + + const outPD = output.getPointData(); + outPD.copyStructure(pd); + + const outCD = output.getCellData(); + outCD.copyStructure(cd); + + let newTCoords = null; + if (model.generateTCoords !== GenerateTCoords.TCOORDS_OFF) { + newTCoords = vtkDataArray.newInstance({ + numberOfComponents: 2, + size: numNewPts * 2, + }); + } + + // Process each polyline + const lineArray = inLines.getData(); + let offset = 0; + let arrayOffset = 0; + + for (let inCellId = 0; inCellId < numLines; inCellId++) { + const npts = lineArray[arrayOffset++]; + const pts = lineArray.slice(arrayOffset, arrayOffset + npts); + arrayOffset += npts; + + if (npts < 2) { + vtkWarningMacro('Less than two points in line!'); + continue; + } + + // Generate normals if needed + if (generateNormals) { + const singlePolyline = vtkCellArray.newInstance(); + singlePolyline.insertNextCell(pts); + if (!generateSlidingNormals(inPts, singlePolyline, inNormals)) { + vtkWarningMacro('No normals for line!'); + continue; + } + } + + // Generate points for this polyline + if ( + !generatePoints( + offset, + npts, + pts, + inPts, + newPts, + inScalars, + range, + inNormals, + outPD, + pd, + newNormals + ) + ) { + vtkWarningMacro('Could not generate points!'); + continue; + } + + // Generate strip for this polyline + generateStrip(offset, npts, inCellId, outCD, cd, newStrips); + + // Generate texture coordinates if needed + if (newTCoords) { + generateTextureCoords(offset, npts, pts, inPts, inScalars, newTCoords); + } + + // Update offset for next polyline + offset += 2 * npts; + } + + // Set output data + output.setPoints(newPts); + output.setStrips(newStrips); + outPD.setNormals(newNormals); + + if (newTCoords) { + outPD.setTCoords(newTCoords); + } + }; + + publicAPI.getGenerateTCoordsAsString = () => { + switch (model.generateTCoords) { + case GenerateTCoords.TCOORDS_OFF: + return 'GenerateTCoordsOff'; + case GenerateTCoords.TCOORDS_FROM_SCALARS: + return 'GenerateTCoordsFromScalar'; + case GenerateTCoords.TCOORDS_FROM_LENGTH: + return 'GenerateTCoordsFromLength'; + case GenerateTCoords.TCOORDS_FROM_NORMALIZED_LENGTH: + return 'GenerateTCoordsFromNormalizedLength'; + default: + return 'GenerateTCoordsOff'; + } + }; +} + +// ---------------------------------------------------------------------------- +// Object factory +// ---------------------------------------------------------------------------- + +const DEFAULT_VALUES = { + width: 0.5, + angle: 0.0, + varyWidth: false, + widthFactor: 2.0, + defaultNormal: [0.0, 0.0, 1.0], + useDefaultNormal: false, + generateTCoords: GenerateTCoords.TCOORDS_OFF, + textureLength: 1.0, +}; + +// ---------------------------------------------------------------------------- + +export function extend(publicAPI, model, initialValues = {}) { + Object.assign(model, DEFAULT_VALUES, initialValues); + + // Build VTK API + macro.obj(publicAPI, model); + macro.algo(publicAPI, model, 1, 1); + + // Set/Get methods + macro.setGet(publicAPI, model, [ + 'width', + 'angle', + 'varyWidth', + 'widthFactor', + 'useDefaultNormal', + 'generateTCoords', + 'textureLength', + ]); + + macro.setGetArray(publicAPI, model, ['defaultNormal'], 3); + + // Object specific methods + vtkRibbonFilter(publicAPI, model); +} + +// ---------------------------------------------------------------------------- + +export const newInstance = macro.newInstance(extend, 'vtkRibbonFilter'); + +// ---------------------------------------------------------------------------- + +export default { newInstance, extend, GenerateTCoords }; diff --git a/Sources/Filters/Modeling/index.js b/Sources/Filters/Modeling/index.js new file mode 100644 index 00000000000..0bdddcc752b --- /dev/null +++ b/Sources/Filters/Modeling/index.js @@ -0,0 +1,5 @@ +import vtkRibbonFilter from './RibbonFilter'; + +export default { + vtkRibbonFilter, +};