From 73e7650e8791a4d9790fec8f86f68121e13a15e4 Mon Sep 17 00:00:00 2001 From: seveibar Date: Fri, 20 Sep 2024 13:24:17 -0700 Subject: [PATCH 1/4] pcb from hole overview --- bun.lockb | Bin 257501 -> 258290 bytes docs/CIRCUIT_JSON_PCB_OVERVIEW.md | 293 ++++++++++++++++++ package.json | 3 +- src/lib/circuit-to-pcb-svg.ts | 13 +- ...create-svg-objects-from-pcb-plated-hole.ts | 2 +- tests/__snapshots__/hole.snap.svg | 13 + tests/hole.test.tsx | 36 +++ 7 files changed, 355 insertions(+), 5 deletions(-) create mode 100644 docs/CIRCUIT_JSON_PCB_OVERVIEW.md create mode 100644 tests/__snapshots__/hole.snap.svg create mode 100644 tests/hole.test.tsx diff --git a/bun.lockb b/bun.lockb index 2ed4923daf7c7eff405e1488eef53266efde0e0e..6391317cb540894ee4ba2400bc46bb5a98ea6312 100755 GIT binary patch delta 43063 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 delta 42800 zcmeEvd3;UR`|jC0Ipml_kO*QXhLDhfNRGMYc^(@PauA6OW|F9R){SK*RVCCs&(u&u z)l_q7QLQMgR4H2ReV)D6Nqqa&{(kp&zxUpMx(n%8>Q+WYJyKi$cH>t_Br ze!eODkN+I{P1y9(LwmOz7z*3v^TX z67l)LXCl5Ba573|`a~epRYa*w7X_Vm-GS6?_+SHmMm!qYH{}ut)^sn>3pfSUD+pW% zoh_VWl`ZOv8k66S)E>b2sEEi!bX+7#Ws430QAUavkQJ$iQdyC7BUAJ7YKhcEpdrSr z&LL#YYScD-_3Fv)Fl5?J)_7!RI$2Mhtztp&32BNboYTNYdk$O={~ zF6}KqPK#?MCRR8j)x>qW$)Hf2%_Bxd!#0_F$aRSEhz;?11a8&j=5{s(Kx%5MR(QLBMW zwMe0JI?smAv6*CCxB4U(sx23)1<0m93XzNH1`xfGazg2wftaQ#%Yd|-uHuKQ_y`r> zO6fsBPMK1Q&kJO_m%%ds?|{g^PRb=k900O_bwC=<2XacKs`x=FzKhZu8(VS|Ef`Tx z_H_q^ztl5sE=cCiYVHIg%}a${KuA0UTp zzrt9=b4aHFS;(|$kkQkAeln|BR1)kN)3*-Iygv4D+PiZOR z9Ydm{;vI|@Tvmv>T?3vBb1ZHlrY-%Hu7i+7E2XZDwMW$jV-q{26gdPB7?+l1FtCMV54Z;J&BUFU#uhUT`^jCNr zJlh`=l^7k{FTwH&ba&|Mf$Wc@A#sklD2rt>^a9W$V`B$7ppRDZ(LfGQFCZ&gyNfJm z1~8As=x;0N-WE}8TSR1JR9s?$Wv?;S=98Sy6*!XJL;iBvIlm#vV&&hlpI``Y^(Al73Ku+p>KsIE!>iMXIzDbV3{gN%t zW#e2<&gJ7=9nMwYTpiBk>RhJI<>*|7&gJG@WzN;)Tt&`hFPAm87Uw#3E<>k(aQXx1 zx>xHrr7@Nx$JPrgjQ!aS!@wa;h>nPlLr>N1FNdfMkS$J(h_BWkt!_F%+N}Yy2XjTE zhd3vG1HrCZ0%U?&M*ZB?lfQMy0fC>lD8?L0_Uqalzp;3<%H z@sUhrv5dj+k>{2i<48yZnEX)ioGC+M`$biY=<7J9s=W#b`!Xr>5MVuv0%_<6WX8_+ zv^3oF6iv>7$}u(}5eJ@_5td(L)qDWY#`LWfVHP(VI;%Sg$a&@LjmX%+v1pAY5FfNp z1+tV&K<0Z6y-Rd%@cW^&IfG;SMI=O923pb;(LbSD)QH6Rh|dybMgws^?e7>Ibp$#y zSfPd~KZcDJu?&+H`SZ!Mk>V||WAo$UV`I2n#u)B-izXW*+|a)q zD;-LqWI6oKgOGXbgvZSVo*6!aJ=b4kbj096$YfQD>|c1eYW)XC4EP@LETCW1(4+`V zi5t*a(NhY`jh77`1}uY%z zW6re{kS43)3FM-khAcKUKW5Upk@U?2i*4NqhRbKn#qf86xr^!nwHOqY`2V4~w!)TPxj8 z3ec;m96M#j(E+9xI=jCm>0;3{c=L)e@EfH5~X8(wsk7J z#J254$x0Y-XNyEfIxz~^zpx0+qE z84i3lH`4LB$jHFwNyFFAZY!C?VyT7XR%1&{h>`AR*ESd#es-&8PK%|1@z^ia7K2c0 zSm?%ENYGQJ~Bh-^2eZI|j=ofCi4mZ=&~9^w z%cub>E3Itxp|QJUnj|AV$gX{6WCYo5Za5DFBgSG3^$XVQh9lUnEj7}E?Y1uv!x6F= zTWSVtxeVVByR9zPdsT4g)Zk!ko{FJ*BJ9U0Gw{Ne>k)zPE9*)IKu3GZ>RB+-~bzEGwT=A#P9_!B{i4)D5=1g2rxeVN$J~k#4u! z3@kQsZifHF5H~nsDySt|u@qWeXgQ26Q$k#+7%{=2T1g|Lp52;)h1=PP2@ADdL5NLr zGZT4~l$t9$%+?1Q9#yc3sGe^FyX`wLsyx)Fw2v$zr{NE+m(p^W+9GK6kk)GW4-IjH z!iHFk%qqdQDy3v0Hjbywp)}O9O0abev^vJ)I-#~Z2yvW{2D2m4NN;46hT2xLSR7_(4?+=U zsF<%ab~r)B;LysqJBVYjV@ z#z}=P2uG8k)q^G%W~pk@OXV<|I~YXge$~O)`r-Z$T5eBvD&(?--JG*rS z7Ggc)ag|Wp4+zQGkXbL-Rtv7pZl-0&BtVnv(LW}{4GODaIxO1{&{%a;pjEK741xjX z>gdRj$+J%`Kx=Jg#|7qt?rC6Z?7awR>~-XaX_cll=^XArWAj|-0&PC)z!NB;#GXZ3ZSOVmPbYVq!s)vvy0LV6D879%0uajSPH#Wcc>wj_bgumyzDrZVhM# z|7C2b8EWf`5T}*t$+VS*Bhqerq&Rp@?0A)pj7YmS!0_#7w=IV=m6Hh*&3Xk|Bd!>& zn2~|l9xW^uEHrpq|GvSt8PGVH&8FEtgT`_&McV}1a>11{AMB~7u(!^XmDjL1!X>g##xPK*ABLYbe3_L2pHbM&{Vo` z9-tg?cKuanV_jT0D-mb6E$kunSw+Q=QYM zmt3ca!#tU1IELX61WtAeTT;=;7-rW78;;?2>#E*#modXbt(ge5G(%y1oS~^%q03pJ z((lq$F~uN+nwp_)S)pIELJcFFvD33cm$E|MeVwt9S)ug^HIa463I#_xxv^QHqX@x? zq6RtpIYTY7LbJ0%mz*J6(I`2RIJaZML_({J{<3ic({Dr>566bvTK1Q7DyKOuzJONG z93ajqj{$PH;TE8^hsNa4Fi&?vqa%d3uZrVXv~%d;7HmDUH2UI|(3qz=hxMoZWUJxo{ZwzCc2 z@pkJaaJ9@8m~)VG0n;pkRFGyKOJHre;ZOh&}{)h;|ROn#Q}8-K=e) zVvMVVS{EP`&XDa3gc_NZLGd=vSlJI~6vnC(Gnmy^WA;apsf|w*;w}Lu%~~t$|T(e5h?HLaohuV!~J- zLJKlt`h;qA499f4ZEB)i)wl&hd#^#0Q;q(}GfBpohdiyV;h15!&IVW4*ib7}Ka*rU zoDq&E0KPNrS}VhW&&fvmOuK$^nDKCCxGmRk**f@(VZqiW(6B2_4z*2EJSGoLm7hQh zK<#k2s}k(~vonoFUg1XR$`v_Q`l=Dez}eyYvk}I++2L9ZBV)GR7CX{n!93LSjoT12IV-XS8axwMMD1W}zA+Zd zJ4Vc~P+KR2LcyE2__jIFy2v8i23x;_*4@}pHPlvrtegy}3U-L$(3nP^dqT&_0mx_2 ztxtux3O z<2h#@F9@|RM5w zJ;8XmFx)l=WL=q*2ODV3py8GkNvlsZs}vS$9fMFkGm{etNqa5~eUkJRW`nGaptUz* z;<0EE!c-f_52wpi*TZ*)v8cn@eTjF_!;-4L*wwWW*s5}I1a!$RDq$erHw zr+Ug1<6)I>+d+_WR%X@-afN~dOx;kMeX6V%8~|p_XlN=MPGo2}71B9C%QH>Z*__t4 z*3hJ@VB7?ynV!)1?PDy_~H%G)@TV>!(2Dz@p2sR1ZUwd9q2*p}m7R zIpeG|op}xo4b~%O8tYbu+g5{Y029+`+nzzAhliWRa~xm8k!H8PI|~!lh)D~zZbS(C zFG8=fLQQ5nx%aa|w-Lhr3%g2l%smvLxUA6btdM1{Gq!0~Xiiq>T2`pk`%b%Pgy5T5 zm#omwS)n?HGd2Yw?7y%(mKAcF=j2*uh2}Uzwl5IET8G!*Gm9DX@hArxyg26T-TB7D zHQ~0h3*;JgH6O7IfyNQS^1?mT9%xOWNvExeH=M4`4vjMcBa3A@5*o(@NBxLk+i7T= z({P8F(>WH(vBY3tV+n^Aj5xV~Cqm;K#yBIF6VO^flOsE2k<`rPWxE6|d(?|9w(v~= z)+`XELu13>k*Wsk*A^QO*N5A3FOky=CUL>mcF=H}*ecYv9HAf>Y4%P>KXjI;g6$-L0(#dIX z2b!8pn5RuvJNt(XodylNIF0~^5n>wCzuSIOR+u`if^BU-lB0n;1KfFzht>#|a*WSG zt7AIbLTj?d4AZ+YGBhR<;aUzOV>h1pu9F)fjC@Mm0P$c!f|)FI@j%gRyR3xb-f0 zoPLmZwT)S=8xXAb*=P*hAI?Vs`|Y;Qo8Bz;gH1-|z;Nr=Am24&_J>-BZ^n(Lxd)y@ zsJj{R-D0uyW325ILL43RJKVO*R=Ix6hksgaBjcdmb{bqqv!8gfs<_R$7Pv?Ef!0vA zczUq@!8YUJp>X}_Hly<4aBH3I7E5pA@t#n9;dWykh}$5{FEecAcF1DnWbF- z^FnA`7c%a=vXo2DeW$d<$rx2>1I-Rgb9(Bxb{gwG3AcsplIs9t(>PgDCl{t%Qt#}ySlVS-E`*l7OuzdVE$G0TaTB3&8p&KP z{)^^w@Xfg4(CCqEJX+fJLE{c!-lW;ShnD4at%DAk=MfxzDjk+PgB*c0XkC#;KBs&M zt%20AXhM%j7i69etclP%n&*p?2sMEfmKz>2<@t>4j70d}wXV{`&?Y zPD12^J*Co7sTJfSe?8%-@$gKzbvH;n(!i7B9~go$cDh=@wsObbm^{`+(6IC45cVBH z9nC^RkIO-mt1|hxvF>cRo^jlGcsAVD?}ThXE@NobVCzTFLX8b)L#?YnVoSA~F@m*2DlH;{zeQh}1F5&C;+ZnHQ0zqwg)u zKV0cpRc1c*fxLhNodGkWMy6)=tEpoenHP~6VWT(s)(SDn&5Ov2z@L}}bOy2lSS}`F zqFF4);|syQwCW8ZMnKrSNCM*bXLi2@GaJ`ANUIZa7hVUZNekm1P zL>B)6gvDS-H0?hE@*7Qeu^^bw75{&XX8->uGelli$R$<5w;_k>x{6O` zfC+9x*hODMczq3F58P3BSK&P%uQ!o)-$01>A-ssp?>h+j2M}IF@(&^4^I5(}fR~BJ z#LGU$(#vkXHZadj{$G(`u- z3sqQ`3a&TN2YL_1zlqGCm*R;G_E!4akQMEV_`JYaW!H%ThT{}L= z2e=kUJ)IvClg;7Ypolk-722fuH<8WQuK2%0n(V*_?e{7?1SEf0;Sr6^H(p=&;k>aN z!v~8!q3|TIDDUhNX_!mtMC!Q}=21M6>GA_fxhwunBvnZ9S=bT;6BbbsL>5p~=|oo0Tj_s?nsMu< zkMZbc0rT`%O662qr6jVXazIiQ@WGrbQ&Gs0t0?_#$n<`Qr%4Tk0YIj!Nm8ObQPfhz z-yt&$R(3>Y6r%Juk$i3Nj0#nDb(I~F@nH(Xbqp%w>M4Uakv^*xcoxi`yfR@sAWb_c zy(5r4(^c`^mEIdjC(;kd$>LD)gMiF$h{B=C2r%P#Ag?zu7xXbIek_pFaw3o!Oi}iK zhcuan59U8z*%PVHP`dHrRvshIZ8visyst z2kkzf^7oi*CiqYp64|Y5l}==Z)&rU0$BHLX-=K6N(``|>L&fh@xJ%(~ATJ_^=K$vc zuLCOLZOHj>8u9cBSAeY0b!Go1(j7ig{C^uc4|x5z6a0tmliw_uGxs?PVAuYl@CA@% zzQPCN&F23JY0vF~b}sB5h19KzD~KJbO5o@@qaSQG@gVlv{b$fy$dV1XrpOjk$ z!D8DgY^Sij!VWI{J_Q#7S;$~#rN0ebjazq$x-;7z%8KY>yuMSk9lb|1G{!o}0tfTs zPssQfWj92{zln^G1@B_S+%1~SrVdxuBY^ChQA!^TR$aFq*w?!bt(AXu$*ATJ^d*Z?H8S@Hin$bz;a9dSF5`R`D;Q{gTb z=YdRrQQ;RrUPLzZI*^O)D8 zmi_=_!Y2xU0y5*LKqhHkwi7X0mn zDb6q}>;oZ|hOlB~Am&k*0Y;Rgj*IxugEVvkJ0%jr0T}>cuSG-td633V!1d3Aw5$hk z%8&l%p3I?p=_cOx0F7Nwp8oBh2Wk1aEOGq@P!8gMAfv1b(V_qIAnl(AX_y9YK49aa z^6ws?asDu#Q~RF>Y3z-^eIO^-?LQCFitxmOi^!={ROv(>V*c&{8q+cUp9g7X*ZuP# z?Vkr}Jb(T3Anl(AY5zP(!}EPU`@r=ka*O%rL7KT;{PQ5~|M!Em=Kr||Y1?YQ`pqF` z_S0Huf#P~Ut&6zUPs=O9qCfLN-&hcpVnNJ|1<^@d zCvlBLSR9BhVrm>dro@4GOro2pJrqR9P!P+8g6JVKNIW3XDjq~Hu_zwI!gvs`N%RrT z5KZmX%vW=qd+8y>m;s`2pbJzxR^Q`#FWt>9+Ma;YL5XCG6ux5F(5{Z3=$7Wv>FRy ztXMP_#KN&4UXw@`&BlRfIu68!aUfE~OA;?g^hyTto=8syu{Ie*-V_iMMYj|XT~k0D zATe3kQbD+-f`~~4F;(m#v71DR@gSxP$9NFY<3XGyF;jTG2g36`5M$p1Fags!} z2_WW*krP0Sm;mAyh*tdnjh&Zk&SLQ&F8bm2%^wS%x8K~b@>Z2<*UC=5KWpp8CO^`DpxO&PSJa#O?Pup^^l0r3Di{=!C{KzsnhP@2N+D2Jy4w#MntFXv0+GyXeh)Pmk&u)^gYF z$S=xNx^lSD)OiKgH~Z@Sg^}HwjhQ!Qm37*q!|Po(e|jc;;G*8Ee$)Fc?7gJG&Ayda zmCJwfgQ`u}H){9mu4GBudW)agxNu*&w!v3nWI&1`#+1#5R#S2SlYgAnudcA^hipxJF|BToAj& z9THRKf@u6ch&^KN`yfKz2l0%=K2hHQ@qol?1H=K5Nn)V^qSHJOheX;u5KZTSu+9f@ zM6{a^;suEvB#sJg0f@EpK}0P8aa?R6(RBfc!UDvnB0_+06CjS0I3?T{g4j(WX(5O+ z;s}Z8g&@i=0&!LhT?E2&5s1qq&WX~CL7XHpaWRMs;sS{gi$Me~0dYyBE&)+#35fe7 zE(`yqAg+;^zZAq(afif|r63wF194riwL(wCO|A)j_6J+ z5dD=%TLJ!S@se;yv|9n6id#T?^u+n7fv3UCXwT_*K+T2l0T!>U0pVMJ9=b=^#3-(^}|%XySu)T4&L8 z9T@9+m}sKidYHT*v4ez*&^`vSc0Guwk3m?)77|@Q22pqeh@2u~0|>VbAdZu;3HOa4 zc9Tfj2qL#QLLz!2i1M32>yEAXuCkH-3cOU7l`s=3yH3~ zKos5$qN0e{4Z>|Vh~p%Dh5H^5yGbPN0Z~O9ArZX?MESiSs)?a{L3r*3ahXJQQFpO(LIo{L<;RJh_#=Ah&l@*N^Bv~^(=_OpMw}6B0dM<_Bn{-Bpky19EjZ{ zlFoq`B#w}XJ_n-wc@QyT=y?#H=RsU15i3ex0CAGU#0wyXiVGx0TmTVx5k!JWy$GVx zMG*H%BnkgZAg+;^e+k5Jafif|OCTD50b-<>`vr)QFF-saF8t!hGR>8?Zo3C5T;jZflz~&3)J0ueI+rjNaJ0+AsP{chUL*?gQd} z*IdQ^dzuIR(LL=PZZOim(R$>4iFdE?-b;!LUNJAQ3;&HlunT@0?`m`Ym;Bv#wR)gU zcGX;VrMl{ybTgk_Q-0KX=CE42;VotU&j%e$c1^wKn#Q!^vqzd65=8u>71Fi3yOLgN zWnFEh66OC>=({WJwRTmfVP5C|zIy}D*K@J($!6hshdC0Xcr1K1121u~geo*I;ryRu zzndxli$gwqcK)xjX8qoA(a$XwxAW*1#h1@CH(5jGdry-cFejJZ)p`y;@33X%DvIaU z-K@4Xs6iHIbN+{*HFibh*7IxDy&vPXD@OM)J7=K!Z#m0}TY2M{EQvHyL&9y;syKjg(u3gI(#Am4W3MVk@Re$RA58QK-cuOyQcS5I;Lxx+}s)dw=j*R2|<_=XDE z2sVJ1GmdW$I=^~pjJRaBmapJAe+SS+CFaXW!xg7D%irc7G^GMogi zK8oYJNxfAsysJ3&p7Z6q2*nj*`}s~ITizGQLim%XGYIoyD_KZk$YzAu8>4`XE&`dr z04~0!%JP_gx#IXrBh&Kd9(*sBSBm2J<{XDHH~T+Tk;OpXMGAJ&cyRc~|LoK9Epx%e z!Wb+LDWy2R56d85;VFXvdvX#uR-`0^!^<98rtEwWR<9hI6*Yg2Q3~V?)m4;>sf2RN3)8R|fe?P)Eh_kL2K=r98x2m9bjcRREVD5afUNWxk}zR`P{hasiN8 z2(v+3fjS4MCX`o*W$m{s!&(UQ%}!?Ge2X^_VZNHoOm-@}AcPIYIp6LL2Dc2tYd7NX z&-{Vn3X{Q49aP%d&{uMT@Y<)yItZtk41Q0h5{E)Bh03r}2bEo2gcl&pN*z*M7{VVx zSb@We3rBbrg!vv(oE_m06<6>RMb=<$xay{fAB0{(6j-SDG=D0^wZf&rrNF7r0p$R4 zy!aMeO-Lds*q|BKS*^5Uncw-atCr3au4zi4lPyBIpOH4ygg5OAdh4f&@Z>AR&-Ckh+jCNI1j} zsRyCEZUW&u*X&*PCVP*)MYnwnLRU@C91lr=&?66rz&}+s9a23g^&t%)bgT5DO(FF6 zbis7FC&j5ndXEyF(QkCN4&Xq@Ajn`y3?x?6U#wS5?uQ`v?4FQzkoJ%c5Ux#5XL=j1 zCoT^zhf1ivFJvRae3#RTZgzv%Ah{rUA^9MD4O0X1wM@PPN=LwVMAso5-|y`L>B_fU zJ0j2t!ncc?Lil@ut-x&%2V@{*5QHx;)6w^VybFnd^o2x1`az-~10X#hJt4gyRUlO% z+zfI<@<8%J@C0J4hGwL`nW;t`wvkqynT8#2b=dyk4Sv^KWQa zY)~IrM46>}nG%UupM>p)9DwWrPrpew zNw>(|XbNO1WCbJw(ig&?Pdyavmg_~6HzBwU!XGeJg;az1LB2*megol)UeAGZA@4&B z$oG)zkQ#E^Ct7XWKW0} z5$)zLMs@&GQ1}$!D#(YB)sT-MeD{0_gg>*}2w4tU2}y&jf_w-`gv3Jln?OGE9R`Vl z^n~O_{uZRSLQ*vh{CJQPAQK^nAV(mFA=6;k9}*3z4k->P0VxUbffRxihGfF_C&G%V?1_kiZ5Yh-T1P0v@(G0?$bNmF3hhH987DJXoIzj3{?xLb)Air2dlOU5JDgXlC6xY};zA*D?10tn9rH3Jd@ z84MW&;jHch@q%1IMYwdhRAxh_L0aybxJv&nhlpFJcTEli(F&3qnN0<9sc^}>2jTKb zfwYE9@x*J@%xaukPjefkj0QikcAKdSpeY;2Pa`JAi%sC<&e#XQW?m2B2US@QDq$M zYCP-C=Ps#efrJ3Nc+O>9ONs6&j61@Rzr?K_CY>`TtFN-t|Oj{ zHv{Rq*F)Ap(jgx~=+L?&ehsiU@M9p|{3ggW=vz4zwnM1`g~T?5cSCkUc0hJP_CWSR zjzU<-Cy*nM!w}9_?w5xk2OtL_&VtyG6A(6l6+Q(#3HemvY4V&3Y%L=`gE);oNBFGb zCn5296?Yl<1>_RsBBVQ38$C)v2;H+Ege$Npgvnnh)$W7oiNFu@$vEnUa z?nd1w*B>{)u@kQ2I|98+@-3)eLVkffhdhJ)4B?X-X34=G2>Bgh+H{4yhH!bj0=|U2 zfcy&S2<|r^ZMs4JfN&<>g?ONvg&=Ig7traR)Db78jt}9^O*1sqydDRNH~OhRc%NJBpAZs4}fs)af9JTQw!n(o%u3d7=&}X zuF~rR>#>iSnO%`IWCW2btFh9#k&+8RQ@OD^?P$ZW1N zscGk|Xb*%rF?zCAy&&A9=@Yn|2O!)Z!h!AwiG3+D6TLUqjfdexkgwul=$^dM1r zr(R5}AR6z~YX(+FQ*$BVit;H{t4xgQ>!$S%^sD8E+r-+4DTEl;TaBV!KXrd##?FTx+EtQ84U z{ag9C>RNAqKYzafJR3xe4Ka)Ew!Aw(=(9i-gCFk-;31y4$Sg4uY%5*sZtncK$W)_PL72gtu;Rj#aGbHv&~$A=>THz4Xi+ zV&xv)vv}^D=JVN<3xkuq1FxUxjeKcY12;L+;w@P@ExL=)y~yfiPVob>(%gi7pI%Iq z2B*oWY={Nv9ffA%0Fe^*X=LM@@)Yqc6-IleK2tT9dYmJBO8Tm?DnA zAgev9QZnBx_Frm+>GJPMYT^x-uUHl5Um*9Y(QffYeQRBeY%NSSe}9XY_^PN&VNvOT zUc^>0pB(>4@zoO~a{hVp2WzM0zj7rO)Ks)~|`!t7|-@e1BZZWJePgL%+O5S%K zH=nuAL+c&vR}-070>qEV0Y#^2E`@SpO@g~5!j52oABrwV^qTt9LSn`dy`{JFuc$w4 z7dg7^^h)DU3)I06&kW9p9-qMeiipVV;*rCw*gaA46D-@Rh2?rMEo$QH*x4`)bp9Pg zAN_9cc0c@p&g)$p?Llui|1kQ*0zC&0o}ev74Avk1Va?t`Qg7#_Urz{h9XwyOb3h;LRb@eXozG&6! zr&D*b^QtAPAJe_s)W>sU4(ZyFzsJ^G>0RH|Y_UH|bN+qx*Mqgqab+6aK+-_zC!VM@ zFJ6s~{-o=?)w*^aOi|=w5$#T(R3AKu##Bk^*X`&h%k!_a>RK)^=%NxWL z3j@rg-XawS_~L#E?VNv&wC~VzVfAw74?#u&a>k{Jb1(>W{@K#j)j#>b>-13%vm~<) zZ>b&T6ep|d0QpDiEh*i#r8k@WvVx&0`yy7~$fJ;%AT9$4iH3udhI-v%>oqfzWNtFUs%koDV*(>f|$ML>F$Q5K1EPJJ~ z2>le3rM-wHEElT)-s{VmUl^q9^C(iV*X%{(UA0<%0sh=LPKeA;k@sDZ`y}e8mB+q~ z&d!l|vESg&pX%9mKEe)zK<8hb4|y`WO}+w?FJv1y|1SOX*{_%GD(n6*J0@AoWZup{ zWdCjb@@~%>HaeDVa9A9H0bb|2Njq2E9kLD`Zy!pV{BvrtY`Zcd|0&cV6b5+Yo>F2| z8}0s>%PH9gT@i!nl9K=K1^=qQo%t<0CQY#WErJW;_I<`J zqeAo6r#~}`H`mW<;d2%STVTMoxq89!N7hpB)>rkzoIEHxoJIW_i!|8bMUH(mcm6T% z_TlHN6ffbv##KXC)ns#0#g8xubpDm@<=!>+Udo8Gs!*FC+Rf2I3_^l<}!a4UZ?J9#N_2!?^qzaxGjtw4!m zl``678#w(BDhB6!F{EOu$pQreY`LL6box&n+ zG5bQ|mvee*pk7^`)s|1%*KqCYd>`S2pnPzLdeX%<%D;I{;>`2Y%(H{(oFc@A^O$D8 zz`z}q>NjI&XUCVzf5;wZbG~|r{1;I52$jv49KDu=%~(AIRycCe0ZA+DbeIl-qS*z^ zwCiH*1>L~AbnX}RP`pEx_A`1awxO)Zp`&YioZNcjHgbSR3Whrwi+7pOl<8v0MZK}@ z9_)Cyj1}!JA=wB#$Hk+bls2pCdVE>@BAjbwcu!u_L ztSNKg`S=Ui`QTwNkF(F4)mhu%Z0L;aj4F#2%xJ$dSo~wk(dF^`wjm=-PF^yH|2m^l zqBKf!+7*3AHaNX|yQ%K?*ALAuQ02EwO#K4A)m9u}{dzT(^{aaN@}mvs3pLg?oV9Dv z)kcdeuewO(wFrTICk1EV}%JZ^D<&q3jb@mmpFVGcNy!&&CAGim&ktwF`o$E zE6^^9JGTJ0#axn)#CnkW^QPkKE2firNgKS5eie~k!smuwQ;fQbyef+MS207bHj|F1 zI^MPFeJ0{(HH89dS?-8?FvzOLE>xog-Z2$zu4mQCSrfAgUd}?r`RjUXRhE__I-tnF zO>LyZ$y?+__a`&H9?Sm66vKCCzaoZH!ruF*VXqpl_*li@8#Px^|4S5wH|Gh(#E&-s zirgfo--Nbbgb|L5%sc3#xSME~iU|H6G?6CdVhi1wC zoa+Bth)gABeW@4o-h(teUw)8p;hv#x`itHi$1Jt9Wjyv`7h%xM|S`=n`XE54DZ@qP^EQb`$0Nu=Zhao& z$Gf@}FS}tk;;jJEo120PD?e}Yg|J%}M20~vKiir1{`OI(pEqOHiC*_mrMK>3rh&P^ zcxe`~o_5Z^?|w38ThZh}%qQ6yrHZd$pqblbDKq6uEFcdQdh@;hP5c+!IFfDKN>uv> znZ0#$Gn1P86+Ovl+P!t>Hthz9O)${RZCVyuO6`H`n>c#K+bb^3E;K^CW@c~QjLpot z3IF@ZOx`7GD@4?NEQNHj2%zte5})7KJ@s=@;=z4AB`|M)IYN1^EJ(am;}*|mYJ`d+ zhWkMD%P}dg_8spaMxENqh-KfR0)8;yO^N4&-~C$5PC5Yt%vNr7b;Si3;7#=JX*Umc zoOQ*2nRv0M?YB`XBdp(5!sk1^X7QaWql3BH^(s@P`fC`}z&#}lj`bHqzSH}|Z(W9= zCJV*_F8csY+1X!oeV_;6*JsnAYO}Pf_=ZOcemI2aDbW2?1G`<&O>ZGV?_1u0Zql&?~$sd*a9ftGFH0j_h%KW z-)PJ7O-RL_MPa|jE_j5UCWr8PgeN_d;-uYsS2|a?m(|2f`ilsSRs)#=x>p25IOO$@;x2w(d#vMPOVrFFa!V$!94uv#7{X@W#Vk2-f zjI#{4DvJpH0Ug+Am~7&zGYdzq72(m=OxD=`w z@vZOWcZWaVE`*`*!|KBWAmk>-{iGMM@{nnW{CtXI;OQgJfu*e9jOv`=HHd!P%-kX$ zCyK65^pa}vc|St#+>zVg`)JU-zMDGBKIRz8XF_7RWCuTguQkIVca|EFD8{@=bQ2O`d#EoZqfc|Wp$oCw#UCtNT z>*e~R?WhWu@IV(`W3IA7qW*JC0r?yTuk+7-j-uy^Rxh#WSEYz!&#~yuCw^X9IZ@>o zm>v>+eu0f7y@!p*_hG2sw{1tcwCc2o`#ow`%P-h6S8RZt_o@`>0vaEUj67TMX*>+z zce%i8h&#ydZN3C|%rr<7)nCAg6rLy_cWtiRIQ)}#@61FFwWQ-jGyA>J1B#c21fgi3?upo`G>N(nT6>vvpXaDCAxIZWU&-kImcBElX!qhp%oD;UZHDBi_*X9g+z^4dJaYDPo{|W zuk?VwnKm*lv)>CtmCk#|RCzG{B)RpS*su2C0lQgY>|16bUd|$yixN3qyu^@S_2}Xc zr^%^VePGj0sZ;Xtxl(}Kke*EwKmCdU#S!Z_bZoO3vNwMJWqkXIeWEg8s2)IdnjsQ@ z(*uGcVNnSEb@XbZN2%Y9E(Z%f7=iyDhM1y=d3pbfV!3ZL9H?s{a#SrdXNWI<(<_G^ zfJFgV#79L$!be-?e0S}(*PLVX3TS5$Qw=eHUhmm4a_)VKa$x%7eIAaCef{DB$#)<+(uU0#t41NB@11#|i1^vE7_a}Tew zTbuWpd8RUBx`?yPEmfWLJYT)?Z+|){D%*g^K|U#&UmWh?txr45e1^|&(}knqu5YI?Vs zFQ>~zzc0O7HSFY#KE=%p7iyS@LDju`z<_h&vx43IKgzv!5)81{bGDd|@vD@LhXFnG zpTBpbPs1}&e8nW6kK%d1e@#o|0+CC1NzulO={g+Y@&$4pSihSeG-Tn%Jm!#^D>Qw9 zIIO#P26aH*+$Fjl`oX&N_G9N=96J23AE_S}BA<&(OFe0!=;`9(sU8@JSuQTM@Fhir zC%Q+j7W^X1d^A!_xVwU^B&raCMU|2mw3ds+yRJxWw$rP4#3DHfPS2^mslxM}H$1X- zy2M3dBMsFA|Jv21pnhP6_}SH^l#}zbx)clQuvm6wcWwLrW=G!-MPqrRhOQil7&dZE z)Z@90uD|Mr`yF@#Uh|iTaaNc9-no{_j_Ta*OzVgO+doBGE?SIJVeyC6B~?Cz_R{46 zD?tA&QJl|#9ZI41U!SO?mwB60QpffVqFqjxDtf0CVq8v_I)P7D$PInryn4sJ*xl9B ztcm$B?Kc&(`-k!;jZ8Nj%1j@HI$FhJcrs_-FB7HRpyd;x0B_Hg($81hI6iOb&`*4j z%0JK#`wdTOFbG_ZXXIS=;l1_!U;OCCTMeF^0{ty~#hAzF4{Ba%y<+vtU?!EGtc*B- zq*?wvKx9&Fv{K};xp)@uh9bEtEZ#k8HcYC;M{>&1#jX_fV4+W3Dc&uK6}udk-mnar z)IYX@cAfVss2uyNumInymTrQ2f)K4?tu6m2hbjJM8 z-K?5;Zw2p2bXp#?O20ZZMbrJDOn$q-0w#9nH z6hzFVX5lSX{XB*DY-ZXTmIG_V=gj>QEVveC?d){t?&&M{V8P*r3% zc~Qz=Z130);_;#>C&sFQ!;(h+-lGKaz&^nPO3GTX9BK7wYsGHnwPKApoZtK+G1zi; zohX$L`Tq6s=fOHz`}!X19mO_v9&P4puEIC-`DVR%*WJYf_v68q%=L1r<$1qxNB0TW za5wBcIitP%QCjhwA4{)LVs7&xkt>bi*%rw22`pGgV)1W~7Z*bQhD~nf4|~in%FuW8 z0kU*Y9jC|J1z_1?gWRUSA2@oFtxnJi(=s3sH&C)}MM?%)dccPL+@MXTHmznF2c0%G zRBq0`DdJi~rOL`*^_r*mCG3ja9F`Q`^|08){kW&WSk>f~X?|Z;v$)tOkL8yB2ai3T zGxSRsaQ8r!RaJxjs;V_CA0usPWL|5`%!NOV*~$mn@Cvw#vz*;1s=A|@k6^)Je&_Ds z#+8rk;#ZKSMOKB2|8*lVRX-~zI~NmxPrY7k6erzX8fke%-hwWnf0I=8Hog-aThPTT zsOM(sBVxxHjjyk%d{)+ir{%w%NUHRITS^7k0yUu)6hif2*qXRv&6{e_%q?;vo+w`E z_hlLR>4a6^tUwIErTcz;)avCISL}4wCd6`Ji&%=RaI)D^$fZ^B$y?bSWlB$V0e$x%fD@Tj%YtbMuio272t1 zzCiCevFw2F|CpD3cj1c|PxR(>m;Ij@{{8Tz&w1ZbU!3*A66>Z6l3sN<6ji*|-fV-R zi19*F+pX{R=AUyB%mP+LL=m6}N1Q{rf~)Sa@H71;3r{!Ra-_UKT;rL^~-XCETeyp%&26mUpp z`A&yY`tgHbUqzPcyX`Q|V3XCx?$1YgbY z2tNPwn2pFvec`FjUe3-ua7dKIbk$ED5*u+O3;YTR`RwFY!idA0e8cg|iZkI;#BfKe zbUym$3m(g+B8JbTvDxV2SDfgJdmfgnwwzC~La(>w@`8anM8_Q#HGHu3kAVf9#(+?_ zO=Dsw)`o>@;8etLj&7?^wD622k^GKK#i+E!|2nO*&{b2s3d8pErm%QcT65L?41t{-x$|^ z2WqE!bJ8(!o)&-Ic4r4mY^k3-E(X&-m~Y5<}tBTkfYiN~E^?XoUDrK52e<*ohcry^|Jk&!=+hR)sNm*6=c`F`Y8 zbAlL2vn1H?dBx36|7+Dt!dELuHURmc7;F>|<@+yUa<6ClPM|W!7AHswoUHP{jmh)W zQqaAJ;*bpA1wFGKQ=9=;P#!;9V`#B~^g7*+)Ub^hDRDKZgSXm-z$v zTtH=yOPVqTQao=*PLEV!)@S-tHoZ%QS80px=myZiIvaEDdxAJOIS0Odh>L;qyLrTXIk z#pv=1pzTgT;QDrZkSehAuzjr>b7JiD4Kd83+cR63`y@eYcYn@hHs9_#k2#-ldf+T( zh3Ov_F>_D Created at 2024-09-20T18:37:19.158Z +> Latest Version: https://github.com/tscircuit/circuit-json/blob/main/docs/PCB_COMPONENT_OVERVIEW.md + +Any type below can be imported from `circuit-json`. Every type has a corresponding +snake_case version which is a zod type that can be used to parse unknown json, +for example `PcbComponent` has a `pcb_component.parse` function that you +can also import. + +```ts +export interface PcbFabricationNotePath { + type: "pcb_fabrication_note_path" + pcb_fabrication_note_path_id: string + pcb_component_id: string + layer: LayerRef + route: Point[] + stroke_width: Length + color?: string +} + +export interface PcbComponent { + type: "pcb_component" + pcb_component_id: string + source_component_id: string + center: Point + layer: LayerRef + rotation: Rotation + width: Length + height: Length +} + +export interface PcbPortNotMatchedError { + type: "pcb_port_not_matched_error" + pcb_error_id: string + message: string + pcb_component_ids: string[] +} + +export interface PcbSilkscreenText { + type: "pcb_silkscreen_text" + pcb_silkscreen_text_id: string + font: "tscircuit2024" + font_size: Length + pcb_component_id: string + text: string + layer: LayerRef + anchor_position: Point + anchor_alignment: + | "center" + | "top_left" + | "top_right" + | "bottom_left" + | "bottom_right" +} + +export interface PcbSilkscreenPill { + type: "pcb_silkscreen_pill" + pcb_silkscreen_pill_id: string + pcb_component_id: string + center: Point + width: Length + height: Length + layer: LayerRef +} + +export interface PcbPlatedHoleCircle { + type: "pcb_plated_hole" + shape: "circle" + outer_diameter: number + hole_diameter: number + x: Distance + y: Distance + layers: LayerRef[] + port_hints?: string[] + pcb_component_id?: string + pcb_port_id?: string + pcb_plated_hole_id: string +} + +export interface PcbPlatedHoleOval { + type: "pcb_plated_hole" + shape: "oval" | "pill" + outer_width: number + outer_height: number + hole_width: number + hole_height: number + x: Distance + y: Distance + layers: LayerRef[] + port_hints?: string[] + pcb_component_id?: string + pcb_port_id?: string + pcb_plated_hole_id: string +} + +export type PcbPlatedHole = PcbPlatedHoleCircle | PcbPlatedHoleOval + +export interface PcbFabricationNoteText { + type: "pcb_fabrication_note_text" + pcb_fabrication_note_text_id: string + font: "tscircuit2024" + font_size: Length + pcb_component_id: string + text: string + layer: VisibleLayer + anchor_position: Point + anchor_alignment: + | "center" + | "top_left" + | "top_right" + | "bottom_left" + | "bottom_right" + color?: string +} + +export interface PcbSilkscreenCircle { + type: "pcb_silkscreen_circle" + pcb_silkscreen_circle_id: string + pcb_component_id: string + center: Point + radius: Length + layer: VisibleLayer +} + +export interface PcbSilkscreenPath { + type: "pcb_silkscreen_path" + pcb_silkscreen_path_id: string + pcb_component_id: string + layer: VisibleLayerRef + route: Point[] + stroke_width: Length +} + +export interface PcbText { + type: "pcb_text" + pcb_text_id: string + text: string + center: Point + layer: LayerRef + width: Length + height: Length + lines: number + align: "bottom-left" +} + +export type PCBKeepout = z.infer + +export interface PcbVia { + type: "pcb_via" + pcb_via_id: string + x: Distance + y: Distance + outer_diameter: Distance + hole_diameter: Distance + layers: LayerRef[] +} + +export interface PcbSilkscreenOval { + type: "pcb_silkscreen_oval" + pcb_silkscreen_oval_id: string + pcb_component_id: string + center: Point + radius_x: Distance + radius_y: Distance + layer: VisibleLayer +} + +export interface PcbPlacementError { + type: "pcb_placement_error" + pcb_placement_error_id: string + message: string +} + +export interface PcbPort { + type: "pcb_port" + pcb_port_id: string + source_port_id: string + pcb_component_id: string + x: Distance + y: Distance + layers: LayerRef[] +} + +export interface PcbTraceHint { + type: "pcb_trace_hint" + pcb_trace_hint_id: string + pcb_port_id: string + pcb_component_id: string + route: RouteHintPoint[] +} + +export interface PcbSmtPadCircle { + type: "pcb_smtpad" + shape: "circle" + pcb_smtpad_id: string + x: Distance + y: Distance + radius: number + layer: LayerRef + port_hints?: string[] + pcb_component_id?: string + pcb_port_id?: string +} + +export interface PcbSmtPadRect { + type: "pcb_smtpad" + shape: "rect" + pcb_smtpad_id: string + x: Distance + y: Distance + width: number + height: number + layer: LayerRef + port_hints?: string[] + pcb_component_id?: string + pcb_port_id?: string +} + +export type PcbSmtPad = PcbSmtPadCircle | PcbSmtPadRect + +export interface PcbSilkscreenLine { + type: "pcb_silkscreen_line" + pcb_silkscreen_line_id: string + pcb_component_id: string + stroke_width: Distance + x1: Distance + y1: Distance + x2: Distance + y2: Distance + layer: VisibleLayer +} + +export interface PcbHoleCircleOrSquare { + type: "pcb_hole" + pcb_hole_id: string + hole_shape: "circle" | "square" + hole_diameter: number + x: Distance + y: Distance +} + +export interface PcbHoleOval { + type: "pcb_hole" + pcb_hole_id: string + hole_shape: "oval" + hole_width: number + hole_height: number + x: Distance + y: Distance +} + +export type PcbHole = PcbHoleCircleOrSquare | PcbHoleOval + +export interface PcbTraceRoutePointWire { + route_type: "wire" + x: Distance + y: Distance + width: Distance + start_pcb_port_id?: string + end_pcb_port_id?: string + layer: LayerRef +} + +export interface PcbTraceRoutePointVia { + route_type: "via" + x: Distance + y: Distance + from_layer: string + to_layer: string +} + +export type PcbTraceRoutePoint = PcbTraceRoutePointWire | PcbTraceRoutePointVia + +export interface PcbTrace { + type: "pcb_trace" + source_trace_id?: string + pcb_component_id?: string + pcb_trace_id: string + route_thickness_mode?: "constant" | "interpolated" + should_round_corners?: boolean + route: Array +} + +export interface PcbBoard { + type: "pcb_board" + pcb_board_id: string + width: Length + height: Length + center: Point + outline?: Point[] +} +``` diff --git a/package.json b/package.json index ecabd62..6aa1ec4 100644 --- a/package.json +++ b/package.json @@ -26,11 +26,12 @@ "@storybook/react": "^8.2.5", "@storybook/react-vite": "^8.2.5", "@storybook/test": "^8.2.5", - "@tscircuit/core": "^0.0.62", + "@tscircuit/core": "^0.0.71", "@tscircuit/plop": "^0.0.10", "@types/bun": "^1.1.9", "bun-match-svg": "^0.0.6", "esbuild": "^0.20.2", + "performance-now": "^2.1.0", "react": "^18.3.1", "storybook": "^8.2.5", "tsup": "^8.0.2", diff --git a/src/lib/circuit-to-pcb-svg.ts b/src/lib/circuit-to-pcb-svg.ts index fc11328..a23c05b 100644 --- a/src/lib/circuit-to-pcb-svg.ts +++ b/src/lib/circuit-to-pcb-svg.ts @@ -9,7 +9,7 @@ import { } from "transformation-matrix" import { createSvgObjectsFromPcbFabricationNotePath } from "./svg-object-fns/create-svg-objects-from-pcb-fabrication-note-path" import { createSvgObjectsFromPcbFabricationNoteText } from "./svg-object-fns/create-svg-objects-from-pcb-fabrication-note-text" -import { createSvgObjectsFromPcbHole } from "./svg-object-fns/create-svg-objects-from-pcb-plated-hole" +import { createSvgObjectsFromPcbPlatedHole } from "./svg-object-fns/create-svg-objects-from-pcb-plated-hole" import { createSvgObjectsFromPcbSilkscreenPath } from "./svg-object-fns/create-svg-objects-from-pcb-silkscreen-path" import { createSvgObjectsFromPcbSilkscreenText } from "./svg-object-fns/create-svg-objects-from-pcb-silkscreen-text" import { createSvgObjectsFromPcbTrace } from "./svg-object-fns/create-svg-objects-from-pcb-trace" @@ -40,7 +40,7 @@ interface Options { height?: number } -function circuitJsonToPcbSvg( +function convertCircuitJsonToPcbSvg( soup: AnyCircuitElement[], options?: Options, ): string { @@ -207,6 +207,8 @@ function createSvgObjects( case "pcb_trace": return createSvgObjectsFromPcbTrace(elm, transform) case "pcb_plated_hole": + return [createSvgObjectsFromPcbPlatedHole(elm, transform)].filter(Boolean) + case "pcb_hole": return [createSvgObjectsFromPcbHole(elm, transform)].filter(Boolean) case "pcb_smtpad": return createSvgObjectsFromSmtPad(elm, transform) @@ -293,4 +295,9 @@ function createSvgObjectFromPcbBoundary( } } -export { circuitJsonToPcbSvg } +/** + * @deprecated use `convertCircuitJsonToPcbSvg` instead + */ +export const circuitJsonToPcbSvg = convertCircuitJsonToPcbSvg + +export { convertCircuitJsonToPcbSvg } diff --git a/src/lib/svg-object-fns/create-svg-objects-from-pcb-plated-hole.ts b/src/lib/svg-object-fns/create-svg-objects-from-pcb-plated-hole.ts index 8a07f63..0f6e96e 100644 --- a/src/lib/svg-object-fns/create-svg-objects-from-pcb-plated-hole.ts +++ b/src/lib/svg-object-fns/create-svg-objects-from-pcb-plated-hole.ts @@ -1,7 +1,7 @@ import type { PCBPlatedHole } from "@tscircuit/soup" import { applyToPoint } from "transformation-matrix" -export function createSvgObjectsFromPcbHole( +export function createSvgObjectsFromPcbPlatedHole( hole: PCBPlatedHole, transform: any, ): any { diff --git a/tests/__snapshots__/hole.snap.svg b/tests/__snapshots__/hole.snap.svg new file mode 100644 index 0000000..96ebabd --- /dev/null +++ b/tests/__snapshots__/hole.snap.svg @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/tests/hole.test.tsx b/tests/hole.test.tsx new file mode 100644 index 0000000..b7d76cc --- /dev/null +++ b/tests/hole.test.tsx @@ -0,0 +1,36 @@ +import { test, expect } from "bun:test" +import { circuitJsonToPcbSvg } from "src" +import { Circuit } from "@tscircuit/core" + +test("should render a hole", () => { + const circuit = new Circuit() + + circuit.add( + + + + + + } + /> + , + ) + + const circuitJson = circuit.getCircuitJson() + + console.log(circuitJson) + + const svg = circuitJsonToPcbSvg(circuitJson as any) + + expect(svg).toMatchSvgSnapshot(import.meta.path) +}) From fab599e9276186acb30cacb71308f7884c5452bf Mon Sep 17 00:00:00 2001 From: seveibar Date: Fri, 20 Sep 2024 13:44:15 -0700 Subject: [PATCH 2/4] add create svg objects from pcb hole --- ....ts => convert-circuit-json-to-pcb-svg.ts} | 9 +- src/lib/index.ts | 3 +- .../create-svg-objects-from-pcb-hole.ts | 75 ++++++++++ ...create-svg-objects-from-pcb-plated-hole.ts | 132 ++++++++++-------- 4 files changed, 155 insertions(+), 64 deletions(-) rename src/lib/{circuit-to-pcb-svg.ts => convert-circuit-json-to-pcb-svg.ts} (97%) create mode 100644 src/lib/svg-object-fns/create-svg-objects-from-pcb-hole.ts diff --git a/src/lib/circuit-to-pcb-svg.ts b/src/lib/convert-circuit-json-to-pcb-svg.ts similarity index 97% rename from src/lib/circuit-to-pcb-svg.ts rename to src/lib/convert-circuit-json-to-pcb-svg.ts index a23c05b..9df1589 100644 --- a/src/lib/circuit-to-pcb-svg.ts +++ b/src/lib/convert-circuit-json-to-pcb-svg.ts @@ -16,6 +16,7 @@ import { createSvgObjectsFromPcbTrace } from "./svg-object-fns/create-svg-object import { createSvgObjectsFromSmtPad } from "./svg-object-fns/create-svg-objects-from-smt-pads" import { createSvgObjectsFromPcbBoard } from "./svg-object-fns/create-svg-objects-from-pcb-board" import { createSvgObjectsFromPcbVia } from "./svg-object-fns/create-svg-objects-from-pcb-via" +import { createSvgObjectsFromPcbHole } from "./svg-object-fns/create-svg-objects-from-pcb-hole" const OBJECT_ORDER: AnyCircuitElement["type"][] = [ "pcb_plated_hole", @@ -40,7 +41,7 @@ interface Options { height?: number } -function convertCircuitJsonToPcbSvg( +export function convertCircuitJsonToPcbSvg( soup: AnyCircuitElement[], options?: Options, ): string { @@ -207,9 +208,9 @@ function createSvgObjects( case "pcb_trace": return createSvgObjectsFromPcbTrace(elm, transform) case "pcb_plated_hole": - return [createSvgObjectsFromPcbPlatedHole(elm, transform)].filter(Boolean) + return createSvgObjectsFromPcbPlatedHole(elm, transform).filter(Boolean) case "pcb_hole": - return [createSvgObjectsFromPcbHole(elm, transform)].filter(Boolean) + return createSvgObjectsFromPcbHole(elm, transform) case "pcb_smtpad": return createSvgObjectsFromSmtPad(elm, transform) case "pcb_silkscreen_text": @@ -299,5 +300,3 @@ function createSvgObjectFromPcbBoundary( * @deprecated use `convertCircuitJsonToPcbSvg` instead */ export const circuitJsonToPcbSvg = convertCircuitJsonToPcbSvg - -export { convertCircuitJsonToPcbSvg } diff --git a/src/lib/index.ts b/src/lib/index.ts index fcef4a1..d00f34d 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1,3 +1,2 @@ -export * from "./circuit-to-pcb-svg" +export * from "./convert-circuit-json-to-pcb-svg" export * from "./circuit-to-schematic-svg" - diff --git a/src/lib/svg-object-fns/create-svg-objects-from-pcb-hole.ts b/src/lib/svg-object-fns/create-svg-objects-from-pcb-hole.ts new file mode 100644 index 0000000..c44108d --- /dev/null +++ b/src/lib/svg-object-fns/create-svg-objects-from-pcb-hole.ts @@ -0,0 +1,75 @@ +import type { PCBHole } from "@tscircuit/soup" +import { applyToPoint, type Matrix } from "transformation-matrix" +import type { SvgObject } from "../svg-object" + +export function createSvgObjectsFromPcbHole( + hole: PCBHole, + transform: Matrix, +): SvgObject[] { + const [x, y] = applyToPoint(transform, [hole.x, hole.y]) + + if (hole.hole_shape === "circle" || hole.hole_shape === "square") { + const scaledDiameter = hole.hole_diameter * Math.abs(transform.a) + const radius = scaledDiameter / 2 + + if (hole.hole_shape === "circle") { + return [ + { + name: "circle", + type: "element", + attributes: { + class: "pcb-hole", + cx: x.toString(), + cy: y.toString(), + r: radius.toString(), + fill: "black", + }, + children: [], + value: "", + }, + ] + } + // Square hole + return [ + { + name: "rect", + type: "element", + attributes: { + class: "pcb-hole", + x: (x - radius).toString(), + y: (y - radius).toString(), + width: scaledDiameter.toString(), + height: scaledDiameter.toString(), + fill: "black", + }, + children: [], + value: "", + }, + ] + } + if (hole.hole_shape === "oval") { + const scaledWidth = hole.hole_width * Math.abs(transform.a) + const scaledHeight = hole.hole_height * Math.abs(transform.a) + const rx = scaledWidth / 2 + const ry = scaledHeight / 2 + + return [ + { + name: "ellipse", + type: "element", + attributes: { + class: "pcb-hole", + cx: x.toString(), + cy: y.toString(), + rx: rx.toString(), + ry: ry.toString(), + fill: "black", + }, + children: [], + value: "", + }, + ] + } + + return [] +} diff --git a/src/lib/svg-object-fns/create-svg-objects-from-pcb-plated-hole.ts b/src/lib/svg-object-fns/create-svg-objects-from-pcb-plated-hole.ts index 0f6e96e..feb03fd 100644 --- a/src/lib/svg-object-fns/create-svg-objects-from-pcb-plated-hole.ts +++ b/src/lib/svg-object-fns/create-svg-objects-from-pcb-plated-hole.ts @@ -1,10 +1,11 @@ import type { PCBPlatedHole } from "@tscircuit/soup" import { applyToPoint } from "transformation-matrix" +import type { SvgObject } from "../svg-object" export function createSvgObjectsFromPcbPlatedHole( hole: PCBPlatedHole, transform: any, -): any { +): SvgObject[] { const [x, y] = applyToPoint(transform, [hole.x, hole.y]) if (hole.shape === "pill") { @@ -19,40 +20,48 @@ export function createSvgObjectsFromPcbPlatedHole( const innerRadiusY = scaledHoleHeight / 2 const straightLength = scaledOuterHeight - scaledOuterWidth - return { - name: "g", - type: "element", - children: [ - // Outer pill shape - { - name: "path", - type: "element", - attributes: { - class: "pcb-hole-outer", - d: - `M${x - outerRadiusX},${y - straightLength / 2} ` + - `v${straightLength} ` + - `a${outerRadiusX},${outerRadiusX} 0 0 0 ${scaledOuterWidth},0 ` + - `v-${straightLength} ` + - `a${outerRadiusX},${outerRadiusX} 0 0 0 -${scaledOuterWidth},0 z`, + return [ + { + name: "g", + type: "element", + children: [ + // Outer pill shape + { + name: "path", + type: "element", + attributes: { + class: "pcb-hole-outer", + d: + `M${x - outerRadiusX},${y - straightLength / 2} ` + + `v${straightLength} ` + + `a${outerRadiusX},${outerRadiusX} 0 0 0 ${scaledOuterWidth},0 ` + + `v-${straightLength} ` + + `a${outerRadiusX},${outerRadiusX} 0 0 0 -${scaledOuterWidth},0 z`, + }, + value: "", + children: [], }, - }, - // Inner pill shape - { - name: "path", - type: "element", - attributes: { - class: "pcb-hole-inner", - d: - `M${x - innerRadiusX},${y - (scaledHoleHeight - scaledHoleWidth) / 2} ` + - `v${scaledHoleHeight - scaledHoleWidth} ` + - `a${innerRadiusX},${innerRadiusX} 0 0 0 ${scaledHoleWidth},0 ` + - `v-${scaledHoleHeight - scaledHoleWidth} ` + - `a${innerRadiusX},${innerRadiusX} 0 0 0 -${scaledHoleWidth},0 z`, + // Inner pill shape + { + name: "path", + type: "element", + attributes: { + class: "pcb-hole-inner", + d: + `M${x - innerRadiusX},${y - (scaledHoleHeight - scaledHoleWidth) / 2} ` + + `v${scaledHoleHeight - scaledHoleWidth} ` + + `a${innerRadiusX},${innerRadiusX} 0 0 0 ${scaledHoleWidth},0 ` + + `v-${scaledHoleHeight - scaledHoleWidth} ` + + `a${innerRadiusX},${innerRadiusX} 0 0 0 -${scaledHoleWidth},0 z`, + }, + value: "", + children: [], }, - }, - ], - } + ], + value: "", + attributes: undefined, + }, + ] } // Fallback to circular hole if not pill-shaped if (hole.shape === "circle") { @@ -63,31 +72,40 @@ export function createSvgObjectsFromPcbPlatedHole( const outerRadius = Math.min(scaledOuterWidth, scaledOuterHeight) / 2 const innerRadius = Math.min(scaledHoleWidth, scaledHoleHeight) / 2 - return { - name: "g", - type: "element", - children: [ - { - name: "circle", - type: "element", - attributes: { - class: "pcb-hole-outer", - cx: x.toString(), - cy: y.toString(), - r: outerRadius.toString(), + return [ + { + name: "g", + type: "element", + children: [ + { + name: "circle", + type: "element", + attributes: { + class: "pcb-hole-outer", + cx: x.toString(), + cy: y.toString(), + r: outerRadius.toString(), + }, + value: "", + children: [], }, - }, - { - name: "circle", - type: "element", - attributes: { - class: "pcb-hole-inner", - cx: x.toString(), - cy: y.toString(), - r: innerRadius.toString(), + { + name: "circle", + type: "element", + attributes: { + class: "pcb-hole-inner", + cx: x.toString(), + cy: y.toString(), + r: innerRadius.toString(), + }, + value: "", + children: [], }, - }, - ], - } + ], + value: "", + attributes: {}, + }, + ] } + return [] } From 293402d7c8b66c6905563b6b62a69b8ddf47701e Mon Sep 17 00:00:00 2001 From: seveibar Date: Fri, 20 Sep 2024 13:51:39 -0700 Subject: [PATCH 3/4] update snapshot to show hole support, fix hole color --- src/lib/colors.ts | 1 + src/lib/svg-object-fns/create-svg-objects-from-pcb-hole.ts | 7 ++++--- tests/__snapshots__/colored-fabnotes.snap.svg | 2 +- tests/__snapshots__/fabnotes.snap.svg | 2 +- tests/__snapshots__/hole.snap.svg | 2 +- tests/hole.test.tsx | 6 ++---- 6 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 src/lib/colors.ts diff --git a/src/lib/colors.ts b/src/lib/colors.ts new file mode 100644 index 0000000..ee59853 --- /dev/null +++ b/src/lib/colors.ts @@ -0,0 +1 @@ +export const HOLE_COLOR = "#FF26E2" diff --git a/src/lib/svg-object-fns/create-svg-objects-from-pcb-hole.ts b/src/lib/svg-object-fns/create-svg-objects-from-pcb-hole.ts index c44108d..b971b57 100644 --- a/src/lib/svg-object-fns/create-svg-objects-from-pcb-hole.ts +++ b/src/lib/svg-object-fns/create-svg-objects-from-pcb-hole.ts @@ -1,6 +1,7 @@ import type { PCBHole } from "@tscircuit/soup" import { applyToPoint, type Matrix } from "transformation-matrix" import type { SvgObject } from "../svg-object" +import { HOLE_COLOR } from "../colors" export function createSvgObjectsFromPcbHole( hole: PCBHole, @@ -22,7 +23,7 @@ export function createSvgObjectsFromPcbHole( cx: x.toString(), cy: y.toString(), r: radius.toString(), - fill: "black", + fill: HOLE_COLOR, }, children: [], value: "", @@ -40,7 +41,7 @@ export function createSvgObjectsFromPcbHole( y: (y - radius).toString(), width: scaledDiameter.toString(), height: scaledDiameter.toString(), - fill: "black", + fill: HOLE_COLOR, }, children: [], value: "", @@ -63,7 +64,7 @@ export function createSvgObjectsFromPcbHole( cy: y.toString(), rx: rx.toString(), ry: ry.toString(), - fill: "black", + fill: HOLE_COLOR, }, children: [], value: "", diff --git a/tests/__snapshots__/colored-fabnotes.snap.svg b/tests/__snapshots__/colored-fabnotes.snap.svg index 79653f3..c7c31d6 100644 --- a/tests/__snapshots__/colored-fabnotes.snap.svg +++ b/tests/__snapshots__/colored-fabnotes.snap.svg @@ -10,4 +10,4 @@ .pcb-silkscreen-top { stroke: #f2eda1; } .pcb-silkscreen-bottom { stroke: #f2eda1; } .pcb-silkscreen-text { fill: #f2eda1; } - hello world! \ No newline at end of file + hello world! \ No newline at end of file diff --git a/tests/__snapshots__/fabnotes.snap.svg b/tests/__snapshots__/fabnotes.snap.svg index df76221..e6b3da4 100644 --- a/tests/__snapshots__/fabnotes.snap.svg +++ b/tests/__snapshots__/fabnotes.snap.svg @@ -10,4 +10,4 @@ .pcb-silkscreen-top { stroke: #f2eda1; } .pcb-silkscreen-bottom { stroke: #f2eda1; } .pcb-silkscreen-text { fill: #f2eda1; } - hello world! \ No newline at end of file + hello world! \ No newline at end of file diff --git a/tests/__snapshots__/hole.snap.svg b/tests/__snapshots__/hole.snap.svg index 96ebabd..3595e8f 100644 --- a/tests/__snapshots__/hole.snap.svg +++ b/tests/__snapshots__/hole.snap.svg @@ -10,4 +10,4 @@ .pcb-silkscreen-top { stroke: #f2eda1; } .pcb-silkscreen-bottom { stroke: #f2eda1; } .pcb-silkscreen-text { fill: #f2eda1; } - \ No newline at end of file + \ No newline at end of file diff --git a/tests/hole.test.tsx b/tests/hole.test.tsx index b7d76cc..69d3af9 100644 --- a/tests/hole.test.tsx +++ b/tests/hole.test.tsx @@ -1,5 +1,5 @@ import { test, expect } from "bun:test" -import { circuitJsonToPcbSvg } from "src" +import { circuitJsonToPcbSvg, convertCircuitJsonToPcbSvg } from "src" import { Circuit } from "@tscircuit/core" test("should render a hole", () => { @@ -28,9 +28,7 @@ test("should render a hole", () => { const circuitJson = circuit.getCircuitJson() - console.log(circuitJson) - - const svg = circuitJsonToPcbSvg(circuitJson as any) + const svg = convertCircuitJsonToPcbSvg(circuitJson as any) expect(svg).toMatchSvgSnapshot(import.meta.path) }) From f0bc434e303cce65480d016852ec2c65696e127e Mon Sep 17 00:00:00 2001 From: seveibar Date: Fri, 20 Sep 2024 13:53:14 -0700 Subject: [PATCH 4/4] minor type fix --- .../svg-object-fns/create-svg-objects-from-pcb-plated-hole.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/svg-object-fns/create-svg-objects-from-pcb-plated-hole.ts b/src/lib/svg-object-fns/create-svg-objects-from-pcb-plated-hole.ts index feb03fd..643b8ef 100644 --- a/src/lib/svg-object-fns/create-svg-objects-from-pcb-plated-hole.ts +++ b/src/lib/svg-object-fns/create-svg-objects-from-pcb-plated-hole.ts @@ -59,7 +59,7 @@ export function createSvgObjectsFromPcbPlatedHole( }, ], value: "", - attributes: undefined, + attributes: {}, }, ] }