From b9779dd01fb55706bb4de5ff2580b97b75c42cfc Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Tue, 25 Apr 2023 22:31:19 +0300 Subject: [PATCH 01/27] =?UTF-8?q?=D0=9E=D0=BF=D0=B8=D1=81=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8F=20=D1=82?= =?UTF-8?q?=D0=B5=D1=85=D0=BD=D0=BE=D0=BB=D0=BE=D0=B3=D0=B8=D1=87=D0=B5?= =?UTF-8?q?=D1=81=D0=BA=D0=BE=D0=B9=20=D0=BF=D0=BE=D0=B4=D0=B3=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cg/freecad/Frames/MetaObj.py | 0 cg/freecad/Frames/img/qXX7sBMbsvA.jpg | Bin 0 -> 87958 bytes cg/freecad/Frames/newDatumCmd.py | 259 ++++++++++++++++++ .../Модуль технологической подготовки.md | 27 ++ 4 files changed, 286 insertions(+) create mode 100644 cg/freecad/Frames/MetaObj.py create mode 100644 cg/freecad/Frames/img/qXX7sBMbsvA.jpg create mode 100644 cg/freecad/Frames/newDatumCmd.py create mode 100644 cg/freecad/Frames/Модуль технологической подготовки.md diff --git a/cg/freecad/Frames/MetaObj.py b/cg/freecad/Frames/MetaObj.py new file mode 100644 index 0000000..e69de29 diff --git a/cg/freecad/Frames/img/qXX7sBMbsvA.jpg b/cg/freecad/Frames/img/qXX7sBMbsvA.jpg new file mode 100644 index 0000000000000000000000000000000000000000..17a6b4638ffea2916c7e0b0643d1b70b47e09cad GIT binary patch literal 87958 zcmex=af1{vGB88r8DUCL6>Jq?U}9uu zW@2Fm`GkRiv6hjEnSn)+RY=j$kxe)-kzJ`!#HexNLJno8jR!@8E`CrkPAY2R|V^ z&07y2J$~}^+4C1KUw!=a`ODXD-+%o4_5TRNRzU_PMkW>(W)^l<78V9Zrg8>GCT2kv zRz*WLA;&=W#6n>uqec!9r-=(U9^_Ou4*DRPRCJL`OvU7(>PL{v!442M=BG!@ts7>?A6TAGPhQ{cy~`U0%dS@KJ7k z++K4zBvPUA|+`ySyt zw|?ZvJbwGxeTB%@bJvc~3V%1>Yn5r#R*&7kxA}d)=#^c2b{R=5JK8Bab8+vw_dg##+W$kny~j@R-=Y5utoAALG8Ns2Hp{9%@@?mZPQOl2=+d0pRt%fhX2U@AL{GhxOyeYohIs;+3= zf%6aV-^}6{tvGz3xW=KVYNOYIq>8Bhu=r-Irpjl@%i}v(0>NeKaL;n zAM15L@-699_5RHnb!_P$Sw6hHmiqRv?Vs&+ist65(@c_9%)S$KB>L;+LsiG$ct4)c zUQzr=ziFS%5Bp>C{HAl`beY|+Z<*h19$rj-<&E2Rb^Tp5>*@Wc5rN&44@j=- zyX^LN%KELm{2rTsEKaXnomF-9KSR68>x_S@_a(RThMn-p*HLh+-sQczFyC?-E9>J= zOYW@R@;Fu7Z_d`DWecW$|8ALcf9@pTuR+YWY5Q@4vd#mu0u!Jec*v zf8u|J9Q`KyE#k-GCH`pb`;q=wlmD=;wg1((TmLg~AN^>4M2>0Fw|~i5Ia}r5#Rt4$NWR_8<)Rf{%xK0Pv-;M;>Z21b{Ze%ZGI$u zkOhc-z8U*^iU1hBR?b4dMH`)ol82wcD9-64&+iX+=c6n02qS z_))CfJO9q@r}@?{J}6S`zanbq-Xqs09<>qL^TH=%(qb{jM$6zSiY(Vo1+1*M@I1%6 zZ`GgMf2W?Vi&(N{?fZLkwoggtuJqC}@+>LK&Rh3wckVx}KY>3&e|y$c{#|bWU`;*q zKE9J5>@FXW7pSp*$j|y?cKAo#w@;+n+jQ5}*y|X_-MH_WJgJY8TduKlJOZE-jzUFR;DsO^Nl~@TkJP zgK^r`oQc~%xg1gbd)rKMivHW!w$|U;S^IP^*03(C@!NXErg&vV&5zB#7ZZO>t^0nf ztwUDXYL3{UFDF%}{Y>)TvTDY6tKOT5bJn*U{$};#(>#^g5Au)Y^Vg&|yY2qXwo$#+ zPAm4+>7_BBf6tzrTk|huzIIK2^TEemm-#bJp0XF6`&U0V{Cn)B{fG5U)`YB-yt#K; z_rH5j^X@+i?EQVTtZ4uL3xB`-nEsoursn$B{9DQo{xdY2);E>y-)w#~zH^rOk^O?% z)30CO_+$3rNe;K7@7=h0Lv?j@?9BUay^D5Fdd0T=xAVVO`MSl2YV>{-F0b+Y&(O5B zCh3};e8uy_bw+<@|6zYx<8W=uTPsJKDD$J;uh}mw-Ch|oiV0bADM%9{by+E zKg+lM!PEF2tJ1y8+nQtic)O23T=C^l{(pwz-y5I1M1QZ#fA^tq_mlOF|3v;X9JHy= zIjdyf{7b*vruxBou0NI^)~)~WIdJx&%{2)h-TT*mx@V4F8)+9~#E#KaK zqThde{N{~Xk&kci);rAqaDG0Ir-J+A4fSsdu37wNIJqkRNBa7Mcl}!Voc|2@U)R=yOtb%d{CNG3h-~>kQ{Dd0f!+Ckx{|pZk*VKOsWvKsu@!ws$oB!^=j(N7b{6B-RKHH!F44*~n{xf`e zUL1QJ&C(UBf9KYTomSm_GW&6P#{HVqJ@IRVdbg}|{}s;8bJ(t>?+fFPo8`YX{@sq} zt&{(Ep?=f%H$NZEzq$XPjr`g@JnU7SZJ|0=$Wi(}aUZ$z0{qM@7 zX4}Fxd1lmc|GTvRgO7Z#{1*2k>u* zgusqJ<^LI4+cy4ZI2p44!}-J*8ouIsgzKbPyJ&5ylObEzk& zEX?96$Et#*m$)u|x@36y$zFX;>o}i3Eh{sZ%Ws9x{f z$CELC1M43-yKAQYvNfj7q20#{v*d;T_^*C3o$dMIU2SvhH=68zsj_+7(W7s8fA6l2 z-O$POpW(dO-^2Es?;mzx{8WSDr+S`-gYBT~@ml`R)Cj+xRJV}0b2jVu?tQ=O&$*YG zPfE4c&%N^Q{oksR{NSH2{?4m^Ffm@FM)|}2Z#5V9S^V+-Ah!Rb+U0|G3Ln(_zpmwa z^jhqe%Rbpye$P!8ZElFp;*JP>mT%&^>(R4U0XwIx_*MT+{cqPkvx1Ka3AvmlL|MCqDn<@nioqckJtn-=wlko$-x5)qe_@Cj#`hO;j zkH3U(s6V8;;6KCp{(r9Z9~b`<%w_*qQ~yc(KSM7=P5j6Bf6n`ae%<`f@XB>z_~Y_F zG8MZ)8ML)o@;^iO+GX`G7l*IEBj5ikwr%G63hp-rKc&y^dq1KXNzhcV*nL-<9=#`g`~3 z@8hr6y!m-)+QGk<-46b}9A2}(%4UC+PxQP*T2`Vf3L5MRk!)~vfk$3%hvs@`%+o=rFet=|0}dL{wi&azeZc*uhZ7}3$!)< zBK3@C|NfuhKLe}Z56h3rfmiD@`uok|WFr5GWX5m&&v5iU*Zo)fa~f*rhwPCGJ-S8C zvEFj~(dGBdzB+Y3eKRYXS}mwR%i+6Ag6dTEM^{!=U}+Id5CYqs41y7IOD`o%x(%(y$?33o-W_~zt>+iTfk(+;nr+#$**8Jn(hjiad zTkm@D)*ZTc@=s>@imcPRyFCl7PVI`DoL;@mp>N}6&)l>{rqhy6ROxsBXZYYhzrVUp z{e>LchqQ^0&vU$#=eJ|Zta6?DZ+UiZ_D42+@EB_g~md~Rh( zU;j}5VEzw%-{r^j`@Yw+E&K5P$o%%q;@o=m_EKJ_{|r96%)<*DKD*|wicp&?eRC#H zwy;j|iBq4no-n1vo9#1d6Ti7~pZ7T}>02MCEazB#H%c$p)%NOQy_AVxZEO$h+*ax> z*?Hh%>0+nZE2GY7u85j7QKSFx<`&=b(2M%|Hq1+IX8!YA_~`VDH|y5v%7>P^c3sns%!^Iib~$z8)S!6n zbM{H{Kh)FTCjVCZlf3`O;z#}BHJ(3S%v1iDeuSU%-uXwp!EW<|Kcu(oZ;f2P<-Snn zr^8P*XT9>6_?4sax689vS9`L*v9|4BR94V9_xvf#^(XwB|1*4u^Z%jp{dj!WKH(qw zA1;5}a{2u}#n-!5XH=PPTm4X8AmiGtWs9T}wlsbFcZ@rH&PI-no#n=&p)4ENSBZqY zvT%;H*l{!e)Rm-5-myWMrmLP!*N%Iye=00D;QQT$lA5u%PI=E=cKhJimv7LNH{e$_3$dr+dUI3LO%K^@+b;0)vVTd}wpV90?(Xw{@tC31 z=K6%1vLCNMZ2p$|!~XI6{&?v>q5l~;+z<70zkItYd)M|GdHHrnvz9QcPt28>?Adhq zX|LqoRfQjVJ+79{-Mb`LebJ?>!qZdUhWYN!OwHE5AET49I(VvTIoqAE^KS{r@B@rib@OZJT%h;n%B| zD#~sjz9;XgSYDaX#%mooEA#CokKQxaFERiA(OmoYH*4Lw`64ySA6Nd<`4jU&{cZ7s z`64gp^Dp=yV*g>zD|&E#*%eK0R$LCO?o-A_)*eVL+(&f1Od>bGqF zBbxt>^Kbti?hopZ!ruyiJoos(Jf0sj(|)x#zFYWV+Rl~xcIQnvXl&7R>|AByk@B*) zozsjzKb8z*zV)9W<^Ap8Z;^j@*~#9i3H>|ipXm?x$Kg%7?|OTm9-I5@sNLKGwwl?i zBQIILU84P&WtxeKfP?a^FY21-)~x#US}s@j!*&t`qw)O&mHtf1XRMW0_@Y$^Ei za{0U67W<#YJ-aJYUoJK&EN0)^NuIUA4SufX%w4gEZNkt0XV{{CJiEWaPV~e6uH1dx ze~N!R`L(^nf+5Y^8e+3|1=hLF{Jr9FwHWa#Cf(ZsqX6h4-xNwsJp-M{@7Covg-Kpv(X3Y z#c#Ve$NxLF`Gb1L*X3eDyH$#3mfYs_iWqL`+1RGF^h6MY3eRJe(lvz=9l^ZJvKw%FK{ZvVK~9viz;%51+q1{oC-5%HLK0L^Adne|UcQ zy=27M`ehM6+U{|fN|*FKi!Ra=x|jJV>^}1ZF?R{&IZyako=&!0*EMCSU*4RnOU2W6 zo9>*dHE~YJqj$4*)%pCt0tsJG7$f8VS5d`(+JEc&&oJTr;rDCoe@)B~{gL~hfn8GX z{f)^FGj7lSv1?ZTzgebPe^35hy#I;yhu^RH|1F7Pt&^)icyxjMjp+{y?7xJ3x&PPX z)zvRH>i-#(rR*OW9;xB^r}Jm?-;3_k>UU{9JNVx1;QRQ?>}~b`ul=$3bpE@mT;#tO zt7o(Cm1f^tA2Z)77)^|U41xOp*J)_>1sa-tk%nepqM_NBX=wHpGR>}E-2TVP|C`@` zhPF-r8QPZpXJ|?PH~&AwLxE3|e@p*PuY14!XZ(-v6Z`eg{QUUG^jEv9=%1ci!9S<# z59Y41(};0g{$u_@vpR#FAD*|{M3+{tipl)AXWOpa4Oi zi8qUX&i&88GVcfDWBFU!kNA)OXSiMeIKAm9#m`$yYp%gauE?_K(%J@E3Rm)A^t zJXOjzAL~~;@!0o?n6{K&!2-Uz!{3U2gf7pVc8ho2+{gSs+?CtkoL=Vn=xmg?TC;?~ z-pzMr%u_O2W)`&6lSRN|{o3V6%T7L>VilO>_vdb%s_L@qepYs87av?Y`P{sW&0e9Y zk{Ua1W-eLuShaM$&SJG$H-E+azxwZ2-p)2BTYl!f^&N)AmhT0p3KYN%TC!RCzSy#9tT`ORU)~PMBOD=WQ&YCo7 zlHaSBORiq_ym{*-Yoz6+Z~705c7KkU+}vL>Yxdq*xtW{OKgRx6{G;%9>z4gLH1*%8 ze*DjH7}RjN-%-DD$>u+vAKm&t`rSS(&sUQuVvuR9vs&__c&DkX+q272O>t`8E7m@2 zGC!uGp2Y9?LH>_m)9uIne}v`#ac%#|{K)=I^T*(bzuWZoDc3mc{E?gc$ip<-`u+y) z6MDMyJuatJJS^Ff@noW^$L#a9JJN17)R)Yv{Wo{z&FEKFp?8-pUw)?X;kiQ;k$VydQ;`WXmxUO4MNgQ$({!aq{i z<{v4{&R?7wau0Ks9n+Mbo_RO%ltR(+CrWE8zJIg+&(JjWPvwum{|u}L|8B8Q zTYvNTWAQiTg&&W-KUi2{|FCa?u*b*pj*_)TA+d4qsx{Jrv&+i`cO>)A*yNL^x0vVl z)hGX695`n#Gs|b&AJGH0pJPS;GgNB&zWcfMX0b)FcFv=#rXusYbeFE3_2iKAO*etQ zi-G>TpH6u$UN^}vcc*T&=l!txA13~`t2?$|dR=|We}*5L%NwR2-oL&6&3}du`p4!8 zeb~Auj_ZT^o3HC%u=`v~zLMa%uEbY7^7^`bQ?^MWi7Sq>Y+Uj|Oj~&acU zwr|&spIYBFpE^A9nK(r^{{C&fw@W@{GQ8lp{p;F?Bd1J5bKkt&AD``tbC6u6+i#_Fn&^80Ft69k?l?q?-Rx6R;d`E7b_#czKKTKC-R=I7O@H$Rm+eO`LnI)y$ zm~5TC-SbWq6zq(XHwr!Uvxony;z#X&oSwfK|0MnRbAQYIqxv^LojyKa?8()fuP2vX z+2g$0cj=uhvkk{t_ukp1x$ED#NfnRVRD?`-<|sV5{-W!_gY^p6=eI0)`A}4M+0@Cg zwkI4-zIRgm=08Kz{fcOZeSfFeNH2UHXZUw|jbycchssB@C$paWU)4+9GR@Q> zEPNJoXIN@`fz0*Q{}~QO|7S?we~bIu#2+g^tUo?~MMR)S3R0h3sS@L(aeL{Z!{H^^*<8RG>bpF`;o5#P+`?$aN?fyi4 z?pJa$ySrVly=`0kQH{Go^|#U5T?_VHO`Lt!J}KYu;?2(f0+#y9gq~-MS+-R^`TA{L z+5F8@lD-#Bs^0Q!m0tGuzfa$-EoV8`6)L03p8D>X;I@-1yr!(pJAX6kwyLJ;(useR zGUGS-ADe%3<$s0@e+m6V?{9@4j^p|GpW%$SYF)V;7BQcItH zEa{H$_};&1v(Ch05gb=N*W3MPXv(QOe_yo5^^yG#_5Te0^{Ml3@_$?R!|>s2{hN!A zeA_SkCw={`yK|Wzt%`l&vr9%Q_29gStzyyB+9DpdhCX!bGwOUYHU8c2N6Qb}v({w( z$o|OukpIYB@qa@8?R6R#*Vovt&-~AjIqUx0TeVm8c|QESu*qBQ$CJ|g(%S2a)^M<` zs*(BO`r-8h{zLoQ=d;uaW!Qh{sxkO5Z({a>7gF)Jp6~j1enXR?jA7+>ew9nPu7SJU z(=1M^d=BsO+LNuBd0t#JuPZ*n?_Xf(rIxbqp>I=bK3re2VDZ|ycYke_;emyhe;T>aR6On!5D>mTVyr}<_7SYEM{s%SnEoFS(? zKX6yJ_u1q_Rk`10uk@c0TQjp>e^j@pmLhdWJVtM2>6Iz5PTE=|_GdgY>)^V2m>kv>iQDn<)g*5vA)~hzrMXR;zg^DJBo$}Ov?wmPEU$X4pE#qr?x>W2xL))ad&gduek6b$!_wC7+ zK-Z)a`Ty5ZoBFVB!2b&facJ}ZIlTGb{GWkcgsDEMzV$)<#h}aeUoNi>fBXHd?0*Ku z^X>N|>c1*o(Ecd?k54J~{zmshAA9~Y9K8flb>YMMKRp@0W<0WEtCyI~zg3!lYt+qu zCl{yxR@=X{^5Ub2)f;{XZulL#cK(}LX7!sgo;xQ0zxXHUM(v6lwX3e3|K=-gf7A4D z?z#SX8^aU7El&KlI@fj%>TSQZ7R=rJce1+`w`iGsmYICk*3Ew> zyPf~$EAv7p6vZr);sqQ3U!t`ImuYRm6(+9aU$g%0 z|EE}E^CA9dJx8`3QPB=7&B zzODDMto*}k>mTyB{R;2j%E5bW{=z#yW@T*Ue&MKal6Q3sr;5*N-cw6b{g>9YAJ`K; zJ$1_~ol$_X4f64#9@NY%^gK1Ik)~@|y^6}H1 z?5b@`7v8vk>*cS~CD;2+q>6O%j#(%tg(YW)t|ZU+UF8 z;Sbl=K9&{J-eFQ3_w1Nve#wk?PTVcqcBC3PZR%LWC(!W7JKf}@Yx2pefp8jo_CCC1u z{@DBe{|thEGCznP-sStg_sd#-uMbo1^P4PQktG}#{ZguJ;Uh_fkE~bXlXo;7ySwEk z&&K1tyCr7cI2?ENckQeCE$+wGH}C%t^Hx9L@3Mai71j^7AMR&6yS$?M;4i-B1(oF+ z{U1cfx_#1h`}E#_$F51@(_YT3nW;F{-23;XWv1QVnIDP2+4|e)PwYqQALk#PfAjW( z`J?(CzIi2+S66f&3X_`paHHbEZJ(ca-al!Vvw!!w`*U~v?5pLoZ@bUGxL#&gh48_P z+q<~7cgB|7aQw&_owAkf$J?{#bRTswKFd3jvGRKe*T*cc;GR2Qr|kUwUR`YK-6fN~ ze#g6gx81{=abdmRswnqquU$XfPTf@fYWroEE%uWC88Yun|GOH$S*`KGda*lom+iOQ z@7kZ5&sCxH`p7=BkL@j6{xh^sx6|4%C1%U~oNFbo|C({6UsT%W?jq&8>)vK}wVKOY zuD|^^wLW*%%WqLrryP5BuHsrms#f?JCnLT~jB-1a_J#LF`Pe9u3y6| z5*UB-_=Wkp|E}$qw$rIQzn^ofeb;<$JLdlkhaKnYmmb@=`1b3!%kD+rY&{(ly<+Q* z-)?t=PMB?onYf#U`J2~|n;(bzzSl z`mWu7^LvV@^UkgZb9XZQ^>k&Z$bYN&*th=S_BZc8-hL$CnxC`A#(!ajdDOKnue)a_ z7xIXQeG57*e3C=qMC0S20KpwhM#i)L`P66o_r?p@WIvD>dtoPXZIA8ade)b>rfi=t zP&zx-DmV2{WU+|YX4mR1P6-;U)G;I-)H~9=YEHs`Bm5NEp{pu z%CVE)y*k7_*ZoKse|~j(qUaMN-)CDlZMeeiGHLzI{|pxuiyW*S@9aMtXYwS*)8@U_ zIintt!Yc-oi>Gp)GwfMj@yYX8nE%wcb8ip6T6Df#cX{jDmi4XcLoZHsO;wqlnO(AW z>z94A*6MZb|KPk{yvG0I{5P9FT77@i-?69sk@k#z3TI~7e|vjoQ*FO@t+?kbi=4$X zXL;x}pS5=7mSdj8&Bk}F{=u^QTif5V{x+zo{@bblPrTxkuJnToavC4qkLF5lS?#_4 z?RnnJ^GlXqSyY*nx%Sq%+kt!cv$6N=+{STx%A@`M`}k{QKa?MoXN#;*KUi>wyHIiG zzP-B?-+iq<78<^5=L7~RH;(QGCM6}eu1+bXkMl+9)8yOYCH`sE=>1sx_(Z91(&t0F z^gC^A+4|Kd{fb+ky2d$I^hx;zPhpeAsZ+8R#)|VRBs^%aU+_U<&!N3q6>H8J^eO)S z+-sMgvOMjlxcQ!kXH#y6PuiUscf8q|*R7|v)zb5zUi9wb$QSohwX@c4-a2o({r}5< zPkrG3Bl7*R+sxl?b$8;0Y?5m$KWcwV+H%20-~Qlw=9kmT#6HL0y;rd~=))Qzy+z4F zyRxFD&6%vS>BL9Zd>g-24A%P&Uf=&<$$r5<>?|ymm+G@$+Y43%AO0oY zV$v@s{g?TObDVf@a>cGUvucD6=1RSkI=JJ0QcC|D{inz6XUhF&Nc=eek8u3A^p7?B zOty5pe`Igoqq(}q>cjoR-nRUQuM1{gi<7v##n$b4;H7sZTe}QZx8B`*_Qpi{YNfyP zc-+jk{W;)7KTiMF@T2$R{;2C)^o8mVUAX$bWa~$@$<<5prSfk~ zx^+m_Od~-!cKyU}#v+T}JwF#&^ITm*FRGwz?a?Lo=6wnEjsIIy^1kf8>Mc+0m`+Q} zka?Y_Bi2@i?41)jKhD+U+xPiL-q+S~e>nd~WcsoDn|qJHJ^bkXt#p?QHM);~@4qGP z{3G>YJWoacqomtEf*p2EJ}9O8SmYz?9gb;H&N|`08mC05^rYFpI9BPCesQ1dzx$Kw zuEopzXGnZmBm78~`|!nxn)r|HjWva-59;OgJN_6x;#|8&uy)0PYrE!da+F)1;(D~_ zMQWQ_Uy9a6m5Je3X4U@@Hh$D-)km)V z3f(qsdR6(<>uNf+cxQ*cbVt&p5spqI&0e<{!Y|Q{3w55ztkW1AKyRTeq7(T zPyR=D$VauwYko}pl99G|-gnRWK9jg-l)SaObSHX=dC)EuK_{2_#>Pz{*QCr^LcRyI zl-&B*YI3W3f0e0z?UhCEtCxzZs$JLX`n31^)pOThOr2wv`&mfAZDX{W|=gVWn7Od{_O4f>~{UyZ=ct|7UQu z_xsOqIc#o{*7_&O z?pKZrelY&=@P_S+8@8{$9e(R}_-*_p_KrYg0SA#X{wg#6s=eTjcL1ww^S$2Yd+T%T zuH@KVVf=snPZP50_-pFt@oI2S_`W#d`|8`^t~~>)P4fRQP}Q`HR5k4qRZY80Rnx9e z)ik6OsQvUj>%VLJIrk+0_Nqz#J7xct{|p=L41PG=jHzvUd$oSsn|C_8>;7HLsP8`g zJAB&9@6$8#cipLVkJqcv|K|0>_rd#H!H?zNM*p4pcl*coqt8y=fBQXS?cdJy{KVYM zZ`F=}?9y-69IxMVyXo)MC(N&`KKFND_f7pU|559!JJN3Lhj&d`l<=F!HFt^i_OfHf z^RB-5`eFN9%LnuO<>mK8C;zssD3X5PZqJ~<<^7f)r;mQPM(B+oyrxKgM3LnBsq%`2B*XQU5x&va^@evYYueVg{1 z8SiqRZuefXye~S;M1SzsHY%EL^c~dgh~_e>9)`v7Z;hUH|PvUYK}6XTi7I21+Wc3l$TV zt5$X0y|Pa!BhGI>-}F^2dp1lzxnowY)idwxOy*UatQWQ3{oJFGGpG5)*&@yd{8IZ{ z*X)zJd-q4d4SG!r; zt){J2{lgQxO($0;k9qD+-WKHscfX8F-(Id??_IaQJgM~O{ioOeF3gyJOtfg^Jck97LK42Mm;I=9FCts`hqTYt8ts*)_q%dRgLAg?-RjZGOPuF$ z?O}m(K+nIzoLuR$=d-@l>K~Z3cYcS-+o;Sf&$hM4?!PMOys=&@yR4T%x^R#9lXQs} z{$A&#)}`P6_&xr${_gihyUJq!Gw@gcnSL)bOJ2a`{lnM!>@P3;m>+m$+OvqD%3J3w zk{Vj4oH}M!lJI8bhGpe4G6~m8?1kQa&Ccg_eVEU?Zuz?Vx9`N7F1`9ef_Md;xv+KlvzA?Y5|2p>H+urE;o#x7JAL2Vr_VerB>t1yy>bBqKIF8hfmCGWw z{Y;cTlbU8J8Te^l_O&+cLtpKger!8`a3Al7`R$1xo~>7Hy}S17)w5r}Y}=QzF-GT& zppaxm8K05I?TLrOw?xfu+upc7xsCf!SE|PnBPBaauBRaz*ez6ee(_CR?*8iEYyE3a z+?Ox<{q?U-{Gu)m9b(3UK~)t9V{~!%{%2rk(R+X6^1}(o>K{GNy#FifYWBBw@WK(q z$nnYl4D5_G{~0#!KWsRs{^;4u`@dXW6@H;$|DPU>Uo*pgNdCY0XU?bnH%%(z-^{uI z?x4Q@wf>gzk#8C2QH5W8>#?0>{Ac|)U%Bw#YFljfgH{#WF6K|``Ol#Jce4A`e<#=S zgVz!FXB@t{*!~n~jQDB&rflca-xr_$zWA2Gw~YUn=xNzydRlgco|avur)AgZY1wt! zSoSyQ#iFi%A-0>5r&-oTXPkIa6@IpUoBk2~j(Cp$45{(Av>zQm)N--@VZx8}gSVD< zm^@sud?sUcbo$&)GZ|;6w2HC%@bq4<#4m$16+P%N$@$vm1eEn}S zzmXUE&yXcA`O-W}ymLud;j$n6uJa5O!NH2WPHb`;O!C^GQvw z>sHzd=bhcMKIOaBhJfkzTQ)sy?#?|H)Dt|Zt2og2)7>R+W|wc;ohiRSeq;QR^KyT3 z|4!VWw!de-i2cU@3=b_nrZ;Wr7rI~Dq^~7&FKX$bnwg66DJ%DeOy%F9sonJ}+pyuz z+v8Dxt3_=@ZC*^WePRsET3u6!D;wxr~Hmzy31B4KYXj~ckOHOBiW*R=Ops=|DD@VyQ|be zE?=`~MSAO9IV>Kn_gLVi7}m>l3~ z@;ajA$M&DwjceB?RVX{kw9aykVO9Bfwj%!U`X3tm-!y*I#kUnEJ^FV%@T$(*wfSoo_jF1{XQ~FYmz8~v@0rhc{b;=O{s(>6 z=6~?J&-+K_?}{4h-@&dAmN&eJ)Bj=p=stf&)zYJVi@*Ie*=gfkzwGc*t-D9_t{psk z?)Yy9hHZuMD`)k;)&0=_ZQT#!kI@g__t>Y*za8AX)`i{E{=GBIz0dy5%{-Bb6P0?`WQ$hpns;`2%AJ}oQS%q-^#K zoE7`OUH-B1w^-y4#dXr5Ki+@%RL}G3pUg+4yN6}B&0cmlF7sCIyOlZLj=g<4tzw{fVWE@7~;PnyS5Ax>))3{f7Bl?jMxDmGAk{Z1soyqt|a8`6c_~($}~8 zAs6$%_$D8|-ex*6;fl#xH|grzmQv>7p|eDE)<_odWnX;ovGmP<27#~k%<`-iyLj#H zdv3cbe#CD=ed=9LmCbHBj|(+t+&QKoWHqCSWtNg`XKMWO@^90Bcz#s=cH+u^hTQo6 zd7?jfKR!QX&ta38{~`S0x9Lk?)k$92VlP;6Bs0$RciGZ4+a{|Nz7i|@_U+b$&a`&y{w{)$M zy!5N^e@Yj>9M0M3lK;-+=KIDrGojsadX+N_r@Vjj{@rJ@xxb?LVlI_k()W93zIo5& z?v=S_^G)aIT#w6iw9a1LZ!-DC&*(5;)87{#&lkJ*!`Nff)irxWAN^;L^E6wz{N>v8 zMefXZqL-e%6ZW?3QD##7=G$wQJxY?;(WS|y(jzLpm51@m`lsxUKk`5PXEB6NA9WQ`{5c!wwWu`IGP5%3{nEP}Ypvt9EWLN3ZsK|2@S6wE zUC&brfi=0C#{EsaIn&ABudHP{oHQvfx(K=`Nqx8DB@6A(?Ri~ku| z|Hj_mc>b{CcKeUTCjS}!nr2P?t^eEYKf{UQhu^Qs|Ml3y^(W^)L(?CP{2Q+yZm_j~ znELWR!>{bC+V>ykfB$8-EOZ}Ry~4c6e=ioFw!bS{4DQ3Pf6;svwB2eaXube>HE!@c zNB`6IcSS1Ge=k~>Bljgo?h7Ls0{^f6p_$Rw$TZsf^nZqwpr7?`y0&kqn)NZiN2+h> zg8MZp`!AW=IvuP06P>!uZ{wLm+TEFlJB2(pZ~P=$aE3GZ)KryOzuvJtl$!Z1(_d?` zDd&~o&DC)RQzn&NRePLv^=0-hG z|NjE*jJ`-aqc4$Z^uJ{xfv)vBay!6-aL@_4cHJJi<$LR@DrP_W&+x|iw|-5@kF}4q z-}7hKvHiHd{%`+3p|wfpJ^wR!TshDxr*Gu?bf2eS-^I`7x;xvlzZftT6`rj(u;2bC zPG;ZVt#;N2A9>r_2YS5SW2fNVarn)DhR!UpTm0+7?ca5M+fm%EKI_rrmSvZwtuAQFbcR!?#{AojspF>S*OU)ivwWh3DvOW>pROE!-H_F7sh- zOs8Au)AOb38O!U-Pp_`!*KW0#H7{q$OZVwhm#z-GetwbL$C$4+RlyEVCe{DC`a}9Z z1IxJ|%D)9Go_~w_JM%w7c7Iou|Ka%(|8C8ia!owW_~XC5D|GDi`;Wi1yM0zF`rE|3 zM8kWP+p?FkMbvDGsF~EB`S736zgzNKO`o^cC-3jQb^on=_D8PwkEgXiwmuvG@Wh9I zlX>?aI=SVq()WPgFE16gM_&^&7YoQNPY#&&#OAw}`-DQtA8!t=I_$J&Ro2R7Padzj zXtpzM&Yf#zWuAKB>Y^&OVM0?@pLLUpU9x`h)vfRKroCEs_uZsi`Q7_P{)zrbek|T{ zzWGn&${P1eTh`m^)%<7R`f)t`!_g0==NR^1%FA8nBs1rj%8fgD>`WW#c>l0|us^Wc zx9svksr$!rvt->omhQ-xta1M+-ks7r!61-j*43}KqgUQ5*KuW*5o1|#{Bij~`~Ldv zvh$nkQ!lTqyEM!F=zoUJc!~X~{}}{p+_%~pTqx<)cKNWqM@QF0Qg>s`w{gk=Hu&KYme<$YnvvPv!l{)smO+t86h&AlOk^Nail6p z{aJaXPPZoKgZ?r3+y5CNf2{tUSrho-`kSvG)DP~HyP_;MPvFDXv-g&&?311K?Aw2a zVC`k^cjOx?@9?g6%-WYx`kx`>km!&5NB%QxJN`EGK|RBNhJ$YZ#42_lv6s;|z0dha z^pc&*kD&hykM6ZUurz&Zm%V$g+dbE5*AJ)Vnq{kr1=s}My}k6%d=n3qpdO>HUQa9k zSoy_a)1OOhPtO&vWiR*BT6HnDYS~kce?oJDvtF9*ik!c1KGXiR^Ue1+Pn-YfbbPCo zc!%!#(%)aNn%<3$`>3}3k!*6zu82t@lfH*u)ziz@GjkOwTreq&qf6AOPy6nSHud>a zO4lD;-xa^b{B8Qf^@9KI#Y>yqzhxe?^#lK*`rdimAKDrpNc=Hgv~BjPjO*9r#H)Yk zzq@yF{oEO^r}y(rxZ`xu!|k(CZl}%f2k|Zc8M4-M)|mWfXzH$*{H^rwLOaFR`-R@s zXV|9uZ29nQ#XOO1eYeD~n*7XHyR>x2WkLIGaz~$c-OOJ)>CEmUaeCfY4hz1OQC+{^ z*SEB+?$pV3*H&%IJEt3UYsN`8!ArN!PyG^}wPeY^n7;2@-&fy__}4Y%$%Kld=2g!n z4~HMA-%x%m_+$HnXS;s&_tjbcxPNS)%tyPW5A%iAAL-S1+Vqic;>z2bX6mf_&#*zM z-)qLPO~Cc&itKS~r>S;Y^`#Lk)ZSG~yW!o~BT=rj;xopSkro0V(w+vM8HSKG=`z681&2Q2E+U`F3v1ZvfQ@c(Ow8Izy(-}+HnIgkG{uzqIU zpM1XUK>bCp%l{d^TwfLbru?lu%9_L5|G4k$-k4ozWMGGuk`x+j`Nzi?;MShyBohygS#9(e_aE(UQ|U z^pv_Po=)84A)}Ia<5gkfB!~57pMDi&Gas#!|Ig4e|Ij_v57%$`_J8Y}Uzq&-XZ4@? zMrOXhi_DYD3MbAf75yG3_R*y%PW^Q8$}Q8R_DZdLx%Kz1iSt%jZC$o9>sMA$az=HO z@bW(=*YD-KRI068?tQs*t=@y!%<^d;??#{AI{i%N6{Bs}7M;T*ZUYKy7ZH~asWK`wD+bM7FZ_M^ ze};zoZ^yq{|7YMo`FFbgi`g}`{}~R}&Hc}Ca!WnB6YF(issfKMmH((`V|{P=Klc9& z4EB%Ge$3x%|A+ZM!#4Z>*Zxgq|0AyaE$a4v29?W?ztz8y2YIb#|3~p}|Nb*HCDw$0 zY+L#GzI@&3{|pje?@QcU<5^kzB-&Z^L@$%tN4Z4{CahY;DS1Hj2YdX|``I+Vh4JB8S8v6~>Gs}h{nptccveF%)o6vXz=^`fL-Tf=txx<|-dd^V=q9m? zYxR`*dQ&Uo&c0jpTmJAL{zu;h3;C2Pysl~Dj)OIZt-a4}THAZTlag>4&4P zO?@OQ|1IdpQNFdwg|c_o^veiwEOX=O-}+T)Nn3!MdtiKw`5$rpZ!zJ068|LXE(D)A zupxBAWhy8h_1zYE;o zs8{Tli`+h6x}UKk{daxY-JMp`Z|%E0r*Q6Hi?#mz{}~Q?|7UnG+fMl7{Qf%Sir{Y= zANkwr6o0JJRQ%|+cX@^S;c2(}JC%#4d;i|??#OnpfNbF?;rAxmAmx)kk_SCK9{l{`pwxrvH8RWx9>um-u;|)a_Z(uoa%47%gxq)n5XSq z{fNE&kNu-~A@#Pn=SSsuCwuJJJoAFp9oM|{iFvxGrYp8|-E`tw=T^)5;5*xjb(Nt# zQLlAPy@L8CmSvir*;eiu`(jUI#r0$FfMy`HY;d3 z-_3kkq%gO!moJv{XZVHiC#!zQAG%)8zcutJjZdEuxaz8Zty`cM}@DK4FxqIBxpH^Hx5%(D5T8N?O~*LOn%cJWmjLn(8@_uCr{q9Gx@DldE2l1al787 zH+zwJS<-d|2uK%I6{*UnQgR|5&Kb+70@1&g7 zhxu(=WsR=8UNQM=d?-$S>-PPZ-@e^ppOEy;MRzWK_UlE|f6-~Q#ioN63( zR;KI8ljfGxqanNhPO=G3|M06`)YrUMJhuNZ_oM5<4=dZcznQhfR62*>-*A^h!hZVG zoD-VQ@_%Nme8|pzXzMD!RlCCSqIT~*y8K;i(Wl+pm+eb*Keoo&EI=6A?%VEXa= z$Up7P&3+&K7O$zX`mp!y_0HU+ihCNSoJ)76@9LD|kPHeswM<2UX|epo`Qrb$ZvD~v z@SmZ5tKpg-=VN#5i;%Ov_TkaNh4)q;erCAg%F)ArmnSNE7`j!>DeU_B{Ji|V_3vcZ zFaB=b7;@E1XvL!~ao=-w*30gySyMmn#{DJv;t$i`yslQcJO9wUU(1*5OaHR?!{dKk zpMQf^y46km@cE(so6{ftAC@KFH!=TAFo8%B1S+ zW%dtt%5T>HE98Gm{XYXk$^3s2;h&y=>nZ;iGCxCZ>x=p$I}2Xl3)TN;`k&#%9Km0H zg;rs4=l?S>)IX~aybr3hX8-Gye=GQ(p&`ouKZE)e_kY~8f9oH+sCxX+{##=AD{n1b zW3q|!pvBMp^9l7@|6>1q{QK(M6;)on8Ais>=l>P^YV^yp;MKLS_8&ktOy^(nT*N%> z<$s0;{D02BYW^ob`L|yEMYA8j|1&iGwf@gAIpaS=`;&jC=YR3tXZN4s!G6F03{P(T zi`emHalcnQQq?s7$IN}|@BRNXF#KnD+yAPZG?VNf#Z;8<_5Z{FkD>m^*^l;B^*`AE zGvw6&zwmD=@m22nckh2pmy>-T{GWjtRMpOv|7cv}e{cRj#{Uer<*&T|W3T=#=0C%t z+>hV?GqC?>fsH6LEH-1^Pl17^T++I^_luDa)LiDKUixW zf62yl&EM5;uH9M^xb#Y$>y>S1pXmg7H0v`@Y_r#tJa|Ix`kXn>?WX>G{GWkkiSj=Q z@dsx0ugw25EMNWVFaL>ShCjkT=8L9I`4Blzo!$Mx@}R=-w~{9NS?a3_+S0?Nr#vj2 z`p;_0L-kzIry}26wcA*Wd8JW&h84KL5TA zpcBRxgg^ez@JGD2yzWFipPNzr!<5Yb48L-&yuSOe|EEOISL3xGjDISDC;5?9raQ`I zdV{CvSMP%kNQ*FH2>ieF=M}m}|9PGm>c|lIf0>a(k&(J?6{ZbcRIyK4JdK7H(;lC=Hw<~3&e zGZUYlJ-x3t|J9zRg2FpL4}UB7-;^OQu*Wo~X0N!*$G+7|Gbfb(HeI!A*YO>H&bCcG z_e^!?>jUc-)GT}*C;Z^(zf*q~)|qDBYU-{&8}E1VYpkW+_glJ&ook;=R#q%K<@7ek z@D-!X^$XSZHzn8K|2IMZ)<=`is;iC#9e$+0_h~%8-&+6bx2H@~E&QraElGPaW!?12 zQ(n4H{uv!#oLMKfeWLg2^Hpmr3p1BT#b!yh-rd^UBDm%9dE=Hoi7TmdZ_LqZ;8gj* zu+I50PfCsQk7Z>&^@l81*1y!5dcTJ0Z|8r8)W7Ku;}6EmzxvO>m9sToB=$$W`!&6# zJFl!yZF~0e?V_o-yvt@BI&sd^aEgYbR=7;~XLF&#;M30~v}*30`hELL`ev_wqrK;i z?ZxYa>n{fVDE`mTygUD&-k$gWq;@}Gu>WNLpW&(2_x}t%*I#bmg$p2UWRA1d^x^M2XI~85$A3T4{xc-Nx z&h((zy32=(*Ui;k*x{8Ymf2Iki_25bA%@9OyQ$Xtke%8koBW5p{6}Yr+^d^fpEF%B zb;_1XnPb1b8D16jESO`o;ca~Szl+cR89sh|{%_~3`~N1Kjh`;RaohIHHGg<6mi*4h zOP=k$ch80$MjBk7OqM+mIcOO>UBmL=i97>F`*k0dizXS)SQ)fFJ92C4wbx%PvM&XO zyI-zm_qkMA@jg;c;bVS_jruqLA6|a~AFk)+?)~Tft@TUTzq&hlH}|%^$%}cs?BVMe zzP%s+Gdx({uCsXG6+6`mzlu0VfoSfDwF*g`C;RSKxBm1$XQ#* zyH)%SYjaJWW;6Lhf#r|=S@likpWKrdR!k25sJGpiW6hV>vQi)4m2%#0X%aPLxMVG` zij!fIrRdA}jmM9z7yV=ZcaEKGg-hLO`K|Z+SeJim|Kn?8`=C86+w|-Y>JN3cUAwS!Pj2zU-_e~jL!FvcE-m&{eo*x8=!_?mJk>VU z@%~Bq_@AL~PyNT{hvi54LC1$Jw<&$_{>}RB5&szuIkK&NR=bT;z5n|LrDKLtZuhm# zf1QzC>al*&>d($=e*S)3-fm<4Q2u87-(J=F=(ej#nQzh}RYjk&PkVO>PpV7Wu+nyu zAA=14#rwCc|M?=No{9T6oLl|veXG^G$!06`-sNmvSr%>cd)B3U9!aT(gs1e*@pESo z`^+DE^}7sX$CG0_>P_MwrvFPf(|)+~|21kA(-?Kc|Le3f`T|y?f7SkH;92}T=s&~7 zt$%9&GaRxG_|Nb-r2eqRzbpH{i>@X1sM~5L!L8t zf2Mz1oaBdh?dETae+Rr4kE-2zb&+>&>dKo3wS0BfMI6w%&u8<9Iq%h~!U@{H{-rTD ze@@%aVxL=oYyGz=AI#tCf3$x1pW#rx)LePjOG%efB2uSlxukFE^U=6*^XD`-%YPq! zh0mQ)akc-vWkIPy!HUbfJkMG$`E7dX`T8jptj#Bu>ZJ0NEG$jClXP`*?Z2KFBzipfLcI~r9)=TlH1wSjNoL+EwdV%!ko<4{5ZF>J1QtfZe)1I9@ z|3_N>Ek2KyB1O#ydwn}pR?2X2{u7SBn16`>hvxhKt?{BSZ-pP@Z>^Ku_~FmXC!95= zr7GXuo=gz_Ud}6(qcZ2uP1()yo3>9c+f}>t?As?7Z{Kw4RoP^D=vYI_M%xC4S%3ev zcm4P$8?=v2Warnspm`U+PMfxG`RBOA2mAOtDq~}6R{geKnsG3C*4@R|j(^N5d4GFF zh4w@9#sz<8?lX^m`H%0)^{`9tra!#5CND3|Gb%#tEY)j`zeQ?XKfJ$D6Ex4@X8r?yLC! zF+E+Fny*|bczx9m^M71|zm3!B2HWOvlob-?fCdyVEx;IDbtT?s-K(h>3cHPH21#I<=8c+5C4^U z;keXaHuTe{V`fjY*CZ|a8FlTgUihloyn5~X{CoI+XZ|tWv~lx?>u>gdJX1UWNL788 z(zM49zpZ=!ZFj`;K9m0pLE@*iOVu9s-sE!DvwXLu_3x%VriBl-f7t&f_#^Y-ZB~2> zSG_7LoA}4(Bg;o$+rNvV{nE|ll24hrEp;|L_~2<>)W2R6{U4h1-^_o!p5e#*Zt4G(LCYbkil#cr63*j9Uez@eFlW<&-}>UDp4| z{>JP_ar+-hnB3UXT`%bSKwdy^vFH{L8k*!@T6kNmD@Kl;F1pYN5O_M}|BZy^`oZe4vdXUk2=N3BPi zvO83rpHBPq?cLRqGh7k6`-J=!=` z?t09I>`>{<)2WMExBLt1e{%as9Mj*`Tl<;sRWQ5#?Y7gb5O&v3jN#yXJ6~l*$%AwC^tYtt-X8d(tJ#t<@{PTxs{{eAebLDc*$Z z^)^>k$(Wzw-TxULERo~i@MH1sf_WsOn#%av^xno84O|op06Pi-+%q2!QX-N=5JPiD^`3ND){=>=a1rlMEJi&{3w2;&r`wvNdM@Hw|i2HlNaB46<%g1 zarN0H6Xz8h`c8iJb+WG1Eh*hKxg+dfMdT-jyVust|Dj?2X3l<*()G>#ALV2JGqhax z`Ix%Cu{6@XrF8MqOgn`geVn>mUhj>Gs%^UWsIWXoDN;S&a+!C&`v3TB%XxZY z!t!-^KbLPOtX=>G!qYwEeAradC?Hx3s^DL{wUD>~AV{Wyndqa=?0G{LST! z_1W(m>xK4dXVYqdrf*K<|+8V zd)mZUS*~|sfnPqoJM?t(kC6WiN6w_Jb&C&Qo}RW(H@tM+vDGh@PmNu_=E|;JRqpOr zZO!dl^og#*xBX}6oO{;(h1C3izW-9{k1TsS|BI;oF8e3-?E62QvfrxzS33Uq{XfcE z`Tv=^W2w}`zqh)|e>C2?{4aCxe}VG7^qbp5VoFGf5FuL&+I>k|EZem z{AXCO^6$m}49Y)j|1-1&{#{xB;?_UA{|pahSJZ!8WsmN>{d=@Dke!nFpFwE$wE7Dn z|6crOnE1i}kFfEfI?WI6Z`?nGAG&A!;o1I&{KB{XGvw4R_rI$1bnDJ-cURxDs+@3Y z)$5(I5-B{ZnJkp{y*F&`Gi&rb@II)p$??(tdBtnaRlG^f$#u;PowDT8%QLsPKdSdh zk@PW;kdolAOZ5_L$}d|iD*fk;r>jUfH=42DH}iYc+TtZ~_1St3UX`3k6q0Hv854 z&+|mx{TKA<`jty>0`EN9AunGO`y>0q;WsBA&zH!ol09?Vr|d@MI?3oV`4gQ{j&jL` zd)D3QNmt=IcG<@NVde91n)F=fW3P6}uA6g7#@(Rvw0F7G!&|QH z&jVJm`E%72kc#Kau=KC!9H{!p|ZrshBb-v5Sc>N#0kFk4B7OHK06r1(x-J`z~H#I#o zc*(M2m5^NS=f2uCYfRC-nt@5_~t>A~}J5{sg+kWLAvG3mXNPo88?1$_5 zV`J_*GAKyil@;JPY?9$uJX5@{;;+Mp^@s9X=P_SNyZVR!!{vvdJ7+Q#%`U}eTA6NL za>*$&;@Ej1Z4sT0vgeWk)m-miA6olJ%qYsqFuuBe{)_Cd5nt{9U;8s>>yO@#=0}3v z7yq#T7V~$ZUX8n1-ML-Q`gYk)&Xt~hcJIud!kSe#?%1W>T0Bj3?UTCeJO49GcDMf# zpIQ2AcJoWm{;Xf8=H$*edDrh=XWhyfs|4(2{}ev5@4wIVPw9``hvUa)?BjnWCEuOD zPDi1hd+nRa2bC&4mYzN78v0pp!8vBxMSezXb$y?%uQ*%N`=5bT@yFQ*z2QgZZ%*&3 zllbxYK%DIr6YaNN|6DF_wa8!GH{^RQ38>tiL3tU1sB3nEUXVlY{1ywTBI7$k;t%;BI}r zyMN_;!G!65_UvDJ|H<5cZgt1ptv{akzFb-x|E=g!X*RcQxX11vix))4M%_8G>2NXk zo>d=D^l)u5n#481k)=6)X`S2;?GM?9tj^Bws8Rng`Qh|7)8DoK8PfM%-Xa~H-C?fj zc2lRYEoQaR964vf3)i3S@B3r@JE-FLk^M|xPTy~;ll&36YWWdfv0C|nYc+vv3scP& zsiZbZKH3~?&@)Zv%wACmK9BX6rTuT7|HozeD7#~ezi^HJ-^q63FN&X^zg}a!>{ZpW zrO8|J;*~CaQQb59W|-N%!&%mwo=$cA)7@h@<@MuOFTYo(7st=?d42fkidWUDqR$Qf z9g^S5`)6askH!DE7}wpeNJ|sUGI$x!S8~_vTXvYm)ZF!A;n|s8Vw?tHh8uZA-X6{S z&v1C-9`*kW?xnxW|1&(Sm;cY8bp7<~`Twu~IR1}I_&M;5$;5~HH`^bs@7pp%%0+70LeV+Xg{RvU-20w!*-R{jxmbpW zvBAJ@a@*(QjVm>^t52`}&)_U|<>%D?-aXMD7eDlGwa?(^`EmK5aIbju#dUj(?Rr<7 zPI&grRyVBJCTMz#=QEQ&DSHiufW(uB8EXB`f1CWH@{yvBJzvTGj`?C=BQ{*VQIUUm zd$_pRN11;=4=>X2c^mWGGVyqjt^K!_54GnHM%Jh2cihr%{3lw&x@Gf^_WY$kyHx}Z z=JS5ty!6fYxS+M04t%(+v}cXYjU$#}foE&qvLEj6mYVkdZM@`<@Iy}xpZ;e^i!i-D zf8OptpLQ2E_U=6QcwW=~WqYC?M{WI4-o80vYvsdLf%A90d+sb8skYVAHDE4>ddi)J zW>^2dtk_v6cXid_v-bP6zwZ2L{E_*Qes4|u19{mW`7VFw?$6-0`u5p%U(21>RRWiD z)-L*OJu`LMGk2}s$$ST^cC$L3?)t6&ASZr%%zgHM{6EqU)o=XQoWEpy+m_Y5#z&>r zACa|xU_bu$E{)z9J!B5?f-T$z}{=stlFM@wt z>fgWbu>Wzlr2fO<_^t2%s_6V@c)0A+e}oO&U);0Rky@e?wc5!p;V?f&&6YHx*f0gw)E~jXFgSalKkfKH>*GBAF<<_ z_xb33E}QI+*1?rMi=Lf(^k(m>cO`hjOpfu)f9l*I#MawEG7>@CxzVEWNE#{uaNU@$|b--@cf=`P1W@ zHi{pDLq6PFYWuy>j6>Er{xigWU~A8~U-#;it*E{ZtR(Bsoud~%KTe}?}1%q73I&pOq2 z-{*bO#4#^D!gO}!lhgoDPLZ5*ZyOJ-(D7#wDvU{glln3JK%C*<<^LHnIrhk}weQTW zabKLV>*~@oW}BU5_mpXV^ISB;Z{v$kuEd*VOCuT;}7sR@CP<+qgonZ5t`{NF-f_y1L# zwV&rdLqcxRIewuU=MP6F-j)7+n%mb&J-}vbAA3o`amzctS1K3;8*VBy+wa`JrT)+C z^9Zf;_-`DK-E+y#`bWLr*1nq$uddNI`Q5CbvLst@2djabbnPadIlW7Foa6YuBX0lZ zGn(G#?$^yL01UmH(*!$ojT_JQu#sXU{fx>2$6BZFfb=*}b!z9$s<_T3)ox zaGv4Wy7x>BN9zLXF8^ov5X9^Lt?I|&N9S9&#q%8RZksl($Yi3)(VJ=8`Vx01P1v(r zh8|H<#UXJ8RYE{eD+eaLlvi^kdbg z+2xt{zGtr8cH{q*e=F@%?HliJuYa@mL4M2qO|`rKosP5lJKIKL+Ag+{i;_bg1yE=L{ar*98Q+`m-yZ^yF`>kp-ANJp@e$d~y^?t*| z8mS-Ri+^^jqFuU+?E%;8&`uDYb)|08SlV}HE=@awwIo{#nI$=h~#mQ>_FG>+Oh^|8R|oeXJ$ z)-%2hp#QO=51W@cUtxQjkneB)vK{o z#!Jd5ZTz;bCd6*DU*M{rQ$ybUXP8~Gv~F!&Shn}JyEiK;cbSTBj#;N0S@t@+B*-`O z&$g{o`_Inh{>J-b=aP#2Z<-hVJM3h2{xk5|r{@d(QNGSnUT&SYwfeO`zbntS`8%ZF zT>WCb=-nerh6<0~&V+fqALIXVZhw3?_3 zL#0m-|DC@7ys|#|{)y5*PY_LU-te*oksmZkNSl3x9)%2SAR?U(0=}ZiZ3?RSbfYtT=+1b>sI`q z@Q>~-Yxc;lHYx8cEv#IUF=30;7jvcRfV$gyH(XR*gjS~3J$`ikP5y7M>|OWYK7aV1 zf$8vv;P4-{A5TB5sgs%rGxbZ0z>m(e>$E_^g}vk_&kz>d$fs z7Fbmu*TLFeQ?}&ya{0Bzp^GIiO=aJ*zTSG-e(o>VU90DEZJ8Po8htolzy0%*sNB_C zXWI+dY5Zq6XltJ(o9*y-WgSl$Y zn`v~mykI5U#AR7_Qafg8w+Vn`4EX(Rsd7`ew=;S%Kxx`gFS1?^*zq3E9M{@Uq_k(I)#m8bH{-u-7- zw{PA1b0?#sG#0H{u&7Ie0g9QCw*!DmdJslmi1JhVKLhI(hJPpR`5ON-Eb2}C&#>&Q z-~O2W8K{FLlk7jl3h=*~{^5Q9&lS@^gC(nx)}Z{q{v#hUlCfL-_oCkJzZc^#&w24l zF9=e{y*v8v#p0y@3?+f*nx5){tIBn6;@@?d>3?^Xd4W}xy-wAf3(9eprrBBl8A`&r zAIppUX!qKEvBvm;J>#qWLUjsR*(Tehe7Bs;mY>LX*yg)grIS>wY2``_lG`jl=ogWo&2<7+9&1-uanbux6Wx? zRPpELNo%#fjKiY0{M~q=A3q%L#_J zMKpF#@CrXS`OM6j4KpXyI>r~z{kXU4?d8M2C;W*&zx?!j%aso=U7e@>Vfwe~AHg4j zq&xQrKiqM?bKTVaTS|X_H;-Dk;+?Y5p6~wCkMs+9TwHeJ`_m>()3>Fc|1>PmATiH2_}k?lEAE+Hy!|ipd(+aLm-d+L4gah4Si8FZOxbqLy^B7jg$M4H7X9${ zept!8z(@7BK7XtJ@a31^%w>O^FWV_qEDtW*e#s=BlX>m^tnHDZeb+SJmbcCdpQzlM zlEQbkp(4qOS1SMZ{6`^W<@MbE8Io-4566jKx&P)?-R{Tlg(F{V&g_Xkl|9$l^>mzy zp@-}#wUlWpyz;+}ivIfi;qZg+hvZxK-&+1K{Luck{|r2R3!m2R|0npD@4Dab^6C3e zf8NvLZ=60`?e5LndpGvX`tDsk<*0)FjQ-pH{-8ZU|IXO+*@!>3m;G`3Tl|N6lUMy% zcmD9kJ3qAU{rdF7*F;>1QKf2%n!I}M?n&YNJKPu#X8mS5{*Uwf$N5K!w)P*>zrFd< z^^O{&?q>)2n``WoA4Xr@#lo7YuVid8;Z3Gv%L<;O(gmLmSj&Hi<^O4>-(>$S+$8_6 z`K!?H{0H>~v*q-E7=GwJI!|f0RC|Z1KTl><@7LqgCjR^`bjw|(ej&H%I-g6-+O=S{A2d)+v;vU+%H`5^y%eN8J^|yrhRnQyzx*qc9Z^j zsdQznGW&;D;y3936$t##(CW7JKf}qXzm#1W>i=K-`THN28_k6GAH2zNdXPmi9 zbpGx2Nt?D^iM2W6yY1Wc2{Gq>Cb6Fu5!z=pcL&2H#-HCGen0lU+29BJLBW`sh#&ds z1-on~o#gp-cT14VA(2zNjN8K4o0c%~vxHqS6kfhYw&l^h$4NgDKU)12d-tE=QP|{# zapw28ZoGJ{Lg_ej&ZG@xCm81RaViwB#DRaGrWaoG5Z#;?7PL$}!I^iN zjohMrQ8CVQLoP{V$LCe8)Ss_4cmASD`=hh!{{CkW_}*af>#;>!s7T55s~OeeQzn0fIY+HucZ zvBxNJJL|2&pV@^U{{D$x^kebi`E36glGgW{#ToCp^e%VW=c60yQ=>CK?#dAt@U1pJ zcAev`u$JC+1xp^Lt#y;@&c<_=+~=y%{}H>Q;`oF6%{Htb%Y83hI`l7N;pUxtJ}(N} zZqD{e)Oub?yy=D`{gzX^1w0Rh8sD0=)l1&;%Jz9z-oIR){Z)R$hwa?Ux1KR9yZ)mh zN8kK=?pwn-({JC3Te|-2o4Y&Xs_Sm;-@NA3#f*Q(SN^!KF}R(%JmSUlv&Hp>4;4D2 zM1z&Li8>~5CQh=t^q=8y@N@Z(eeqkv{|U+e-L-G^l0Sytzpc%uZ8Z#xf4DS%|Ewkd z8Q2;N*SD9L9C~8IY&vyMn`&FU-k&s{N0mmcXSrb=0D?^WG8*Z`^Sw_n_~Bu%ho3P z8T~Z7UcK8;U#e)9)YCua4NG-*&cCj0w%g+RvCyUEi;H?KPJ6o^(>?m1;s2#SkN$4j zBmD98Bfr%j?jN$V{c-xkW0xNppZp6S*|Ys9dJ}d3RdiBudP&`dn%6T}79Mt4oV6#= zbL*+;+w0#be+;v~`Tkq^hxIM{GuGc?J{+fc=g5z^LlP=`ubA-a1~Nt6^UO)pV9Ytn z(_rIKv-gH0kKD(P*^k)&22OvrPp*Q``!rswhXrTcCpMffHVu6entiT9ORzVpaPqFuY|CR?E^j|- z5wKwCQ;v*XKP}e_Bo*uF9Zy>BC9?8VpX`d@c2$dgHBOUS!n!>U&kp;RA6_mazxDgU z*sPGtHr(}({xfvW)0%FddeKCl|K8c!>c*XamQ-ByJHoG=+9oRTxktX@Z}XP_45FFy zdFxaUKGJ9Zas7al&pex}>-N|$&aSGNroTM1Xo=8q8MAXtM+GX?ekT7kpJ_OGa-4hl ze+E{S{ny?;xojs@cT9fkyv>g@3%_aS3uj#WCx1ER;9d3kD^8lMW>XdtJ!STtV~+TV zL$kkK`@4B-#L~+q>u&@*ept4$Xc>2IaOjWOspYB5P94s=6Lb3@*I5(8NwSZ7+Ear> znjF{1eC@xQrTt0&pLqQrk>~#!?{Db;`|Bh7e+HRVQfHUHeY*11tz}D7e;2*HC%1XW z&Pf$(bpDCG%w_E|7EVoox=6HlYbZTx0~Krv-at4+g*WT@ebqW!~j6#cAE89YIUpO+8o1z)&0b*#2hoH?tpw zpcR|E<{#Hbebm_d*?yz_!<0YHV)NY2?yJAC?PTt5yVU(Nzklr5_pUC__}qVnCifbN zjaT;+|7YOLfAe%r^iihUXW9R##DAFL75dbHC#rmI$(x7Uo<8o_r1r_f_t~D~hvYXd zf3x#J|6BPt`Av1)dw+*de^#G;O#uyZfKvhlBfnhLfRJE;`6-)`)KXvHwwi|NX7($Mw7JbJjofuRoOhpMm54 z4U_$EU&lu0-&nI`+vM*blI_dPe@?8wmXLSLw>H|fa(YLd=^v>NeH$wuz8C!y{Uh~Z zZ{7X$TRNq(Ro8Ak;yoo>epID#y4I<~$`cNJF=6cf?LPm<@%jDs-&9}M|8h31H;`v9 zo&UrApLUJ$)%&6U4sUOd+4$S{;{1~lYj1@{mYACr8s2rU92EN)S^hIv zvZTpbJqX*PwW~NddU^d0{+AkV{KbwvRkO}~*ta+N(RrR9c9SpKHT{;@7lI~x8|Q%epq^KdhCyxAO16V$jM#r3AegBbvMTw)!c>8LhsyQc^iAX ze}n1Y({GYWfm3gt&67~D}4U^S>|5d z?S9KYWqz=KkpD+y_Cxi~{|ssGZ@mxwJM)kDgZrIw*4J$k#gD{>JAd?By!J<*L`}-a z^UHa|9j$D%GugU}(-$~ubMHL5MQl4uZ|hxK#r6K z8E*H*H_uQ1(OLRveyjM)6+dntxpvRzve-w_yW4N>aj#T;!fPWOedn(Hu7Kkcw*GaP z?~@WF#_;D(m3-8X9S5CkHiY#~>W_;!YVBq_^~vo`;or-iUVpW7_KH=WckjJlG-bB$ z$**}QHSX>Ut$T$N{gQQOt+Jkqp?rZ?(`JcWI9QsVGpjgrb#awqpJnfwN$(4V zz3NxoNoee8zBg-)r^4Tdw=Zp6$e;G$`i<#kd+V!S-HN?>+p{FoGb`DuJUH6&_R|a} z^Zv9eqPOl^y1w}GuVkNj-KjdU4_D{EUH_r%{=?<{b;5OLKGp<%2=B6yT~Lw!VA;~g z@tj%4^@r^9@)z#-Fm0xQRbQ{_70I1VSG2mXt7*KeuJD_XYi z+_kN9r4L8ht$m~~kaer9bwNb*$@1FjN~6Q9bN6ILKAje(7-Sl%bLI#GqsoJz7rV~& zKhjnA{>az3>gBI%t@Zh`Pp3Ye^V&pwg2%#15lfsxFHUV{3vy%;D9BprKh;ZoSDxr6c*39dD6&o_U-#h;K>fExecklJyyS3z2bxELWMg;t1Yox28K!Y+c4BIUP z<6{ig=>KP6JSd9?t*%D{finKel=;Yw1h9y)VPQ@q>=7{`=w6`Tv)GTu!bE z+_+ck-a+U+vOOU;?oG+b_u9Szw1VXVt8ed1w_{&cSA*}9WvEm?$}ja=PWaNAlJ%@F zK2&UeG;g#04IA@??-aLk_nGfz{GQm#?XA4gB{ar(*@LTJ_aCfpwo*U%Ryd%~*6I1t zIPdY#;x1X!uxUa7!d)=R?zf094o?WVO zFPkObDwQg9QmjAetngW$-cH4?XAeBy<^^(XQSAGB>gxCQ?OuQEyNqupYwtMv@11q! z&p)8^E^Mt#c81RgKP6}O@q0&|!H>hCKU6t~=Rq%g61#Iydq!`G0&_6+OW@ z%ZE>8_S7xG^U}0Bdw-|ZgkGdRCqk8_>L*K6d%Kgu9BsVEL*6rq%H52?LxBYxns?)Rg zYR7uH-TxVyobLVzb$z$?!*AywQ}d3W6tk24*z!%>(D3=WO#Q@9XA(4n7C%$H(#IUY z{>HzvK6C$#>qq~|UjL*1NdH#wxAH4C_NyZOm&Zr{XE;>6{P^?RrzQJb?oDosS#5A? zf?I{c^`n#iU6udfexKc2za_4|DZGEF;botd5489CrW(xGFW4tm)V-kW`FSCW$?xCi z-tIqt%lysi-}Zkj%_r8R|D9;3=(7TGBd~t@XVtk6tpoCUT~^JI)RLa0!TkTq z#~J${thV1|-l*))`A6f2=f~Yg?lEln&(P2Pp5up+bgo+Me}+Ai-+c5n-P7GE=bC#_ zFE*z4K$}%n{vVO^N9{#TdaaMdZ}xvv{;~Q|SVPVEt@kTthhP3X2@R{67+H!q z)}QbHp}zmmEcQe4zq|f3yu9?T!S>PbZ#)0)wn-IwIgjV>lz&2h#I7pm+xZ3`i!=Lo zYtxPnre*i6Yb}*$rm0Rm(&NS2dr&e}nd|+d{68YgkCuJPkXqjJpCRRZtC_jP{q;Z6 z-L7nv^*Wq+&!#*3yV<3DE#-WJg1$HV4WhnVeb(=im)s{_BYyGGbvyMRw_QKhWK8Zdi6}fF@^4*Qm8M$X0&oUJ5%A5MT^yrEk-mCxJE8l+0>r8Jc>-NLC ze4NW#4DL-lbz14z(<0_61)MCali4T!XJ||Q82?vp`rG^e8QP`%|Cz>o|6`;4&GA3O zqTT-)rtk*;XPCHp{eOnG_K*J={z`!7uG`Ml|Fd?GnR9=G`kTEg&+09GG@m8=p6N%^ z!;)WR(?dV1E!BOuw&b|cojbeEt-9>-bnXqUrZXPQORK`$>r?e_EHYh{we;JQ@cWxVL@!cYoK_JyPe}>L1LvQ<#5vdd7CE z>R0y#OKOdNzMYfTbLYk>pJ|&#wfC`GFV0!{)VDNDo&mHC?4RD2?jup<&7dQHTEfaD zZ$7*)@_N?1#j7%ZdpbYA^YY`?HM3963_oqJ<#$Tfq&ZnPVe9LZ{ml;NAFqw zc>HnkgZic#%f){ur}cmM>^bjSyg=o3!DDq7OINx7>wdDa_4=tke*=?Mw+q(BJ7m^> z@X7D~r?{h9zc+sS{iFXGgnmQ@oX%%&+GCmcQ2WVl7S>ArWOFtTCzAySH?XDkZs*rw z{v@H5{kvXR`}LcQ%(ee)-1ct$y07osrT+|^(}Uw*n;C_NSJarzx?A^IbltVh`=*s& zysNAK|Ekusr7JUUna!+d|qkq4YuYT}@H=nQFl2EmaelxY~#D1I1IXx@i znZ*B4nf^!kwBxmsxn;+{6&i`Xl{{~dkKYwuipQxDqpRzr(1OM);Pw7<;+<)oaKK+~C#kt0}Ph7j@x3G}g zXk$?FEDb43VdZu=t-~xr9Hzg6|1Rw zzD%pM#)rSTGuEup(OKZ3`)2K9hlz)IJeZbbtXj2d0RsaAg9h`@7aRVrV(-dTy1q}m z?rid zzKtIye`Ef!f5nd?b+gO+drSA)A2L7mgyD?x#y!a@6W&&Aw6Kos>9Xv({)N$%p-$wb zJ(vB1W%dI9E|}Ce>}Rk~-@ozgXZwd%AG=@M-}?Qzf75@46z$5mxWoI;9eZzPJHPbx zk_~?+*gsfmr@p(}`D6ak`8Sy##(e>cW|@kkIZE+r(eEhcw}X6 z)#jbsW-LsY+>$IUa4_E&tCjIr~nH>~DsDNA|IOcsBQ8uK2fzAF|cUqJGW`*>${q?bqyM_mwR_ z>+6R_cUBg$ea~Gb>Lwb(zdmCD|13MDx^s4X{~6k{e|-F){UE$?pX48n{|qgg4_n=v znVY*Tb7JPK`fsgIn{TZ>dMN7Xe}))m55f6KjLloP{xjGuIqb%6aZLNKw)USlc6LA5 z-G2mreEhil(0RT)ugdqye>mS`I-mcBZ`F(M!qK)6t+RZYbW>>`Z6 zIe+WEzJ5>r2lI#HLHoL{X)fNFYwM!WYIxi0+Y3KW>n^7y^IAI(Ff&wtpLXYCJm^Fh z`>(D0ez-oem8*@pdp9Eca%EX>;odcCm$a$HZk#-&cZoPl0b`<~r1k1|8=CL!U+_uC zd3Ezr`*-UPJD*y!R;}wca+~z4&#BqSe1`wmNo=6vZN1k2zd)jK^-GV61je6vzDXny zJiUTgLbL9jQTK&8kM>*EKUiA7EpO{@u^N>h`H$Z6>GQt4{A2pDU+JxC*SA(jzF%vj zo2fCWdajeN-JK=d_r-`^W@Pdf7E4v^yW9WEBDj!!;ri+J+5Z`Cnm_D+yYxeJ_ow~Zv3z&*l6Q00T=x0Z$8jWe%H|$ZpI3SjuBLBAN~f#e%CwdC4w;p?HEmXz zJ7& zblheB;a{oqn`*e(+f1}2RCBgI*l2wB^xq|+&l}fGD-CP%*FVJ~o`2bAUSomX)-QD` z&nnfF1>Gz~&7}&Of{LE*S@u-z{%)?qP5nBd3#D&vn=?EAl}-4WALXLG3;(SRo}%eA zsprnle5*NMRi6CRywo>wio&er(krx=eXVb`{}9bD`GftDTkmhHe+TNU=07_5LH($2 z(d)1uo&Om)>|4Ih7tGpL$9M6u_ezhg7vHYSyQqF8^`dUgMY#i;y|}qPp1<+=+le3F zf5*Oz-#Govo%!;63V-YV*v8NBW2H^~BkOwJ8%H1I3;bAqcvtJR>AF+Q<|%F5(RaS4 zTsdTx=33tKQ%m#MC$Y5cUvU42!ym!_44emkO#jcok>6(jFnO;2Z{gqdZv^Wz{xhUk z?km6X@9Oq5zwKxKKCfMN_CJG}@!3lKe*2X9H{%~|o4G#YKZEf8wD}G3LME}{QF5wR zbH4s(V9)&3BQbn&e-t_H*g7u1}kK@80V2tv)IL|I*)2SNGU|G(Kp* z_4prA=|}4({`ULhb4_mxf5SblYr@{O>W7Om?UW~E+9~&GzDv$ETxO{9!S>P`W2G&V zJtkFdXlb=Mef0je_P6f;8MxZNP57~O(Z5Tx@(-R~-}s+_b&v4lx%vkWAGR{SRTH>x z-^JcP%Y#0zo%P7tdv=Re?4GLZTSk79WG~5XADO@T`ayod zio?zq(>~nmTpj--=-8vr?w3?sA51%Rt-NE8*m{x6E*BI9H+j4YX;avKB!1I>hBsFq z-7SB3zH3kTZ<`wakKvtvq-6|#*au#z(M&8XedzJ%__SF$mt5j524}q$n8MxIleA%} zq}HU#)t_$rx1C#hcUzuU;I69bUH2{~P4)|z93JTR>ixa_n=ic%&5MlkzW04^@Slc{ z@87Ebj(cg<|MuX=XScsKd=zVZu)p=bWDWnrT=9qJ|0qXkN5sA~|5e_)^h$22^ztp) z&x>xoee9}YF0du2=vc~aq3Zj+bpn5MKG^>e%m0@AWA4LvwjcG*KRO@1Z~o69WdBh0 z>ZAU@`jNj)pSL7$i+rLoY2#uKK{k>6BDRRhzcN?+QTXA#^2U$LK3D6+{#al6@?P*& z*st>)Yu}kfXDVIa$`N)~ara&~Cq4DvoHbKbCl{~S*22V+*{||3{*B{r@rv({p6);X zUZ(EE{>|b&`>c;vrmr~qI5+%2ROr3^vUgYXM4j6;WrFE}X@V?D=KQ-1rE--sj@^hX z3R!GjBbfO$_f-F;s=ZNppEpvZ$A4n_@O?x#Y~^Eo9Q_ zw6bUgrvl@g2Ygd6U)41ay1aF+)!~Rsrrf&Wp1WK1a`Vz_AY1Kls1kuTa?|LxvK)BQK=4&0OeaBc2_=4bi83#RP&(gZfVG1-$wPZ#`?}>$h7ilC?4JwcuGp z!E>k2&wauX$}?xzvMahzH^eXbCzvIdx^(8<14kL^`YX&HYjd7oTC4x?<)(@ATx%vj z=$?Dgq4;Hdz>ln)B{jbS*#BSqt9?o3XZYUoGT#HD&u*FQQ8#@N=6gVL0soZx2aD<$ zF78wPcQ}44e|zcexqg>U-9GqzpMu+#l5lgWqFy&v)v4{%F5Nt`&Tvv|gO*3)r}RI< z_mAG%e^l1_(f6Zwy{=u*@7UtcdiT6-PwqGUxBPGVjb7ao)46lx^QtYWGbb0dJ^B3W z_UaeA%sWebqYv*>x>Rzu{_yv2mu|-2xOaQWx9{6NDS9f-QfG}b4@*r=aAaxXF#WOj zKLbm~)@vpEZ<-&8ll`c>ZTa$w#hxF}tzMc{CVt}M-OT9%M|0Y4Rm|rXnb@!@If1Kz zjg_Hpg_qUGTRTr*ef>JtFwgRb`)1QtIyd&Dk0s{3JMM#GK7ZbB$T#5-_P- z;kWp=2|sQ>;%AGmNPlR5SZ%YssEuoWVZpAJ6WaxTMEi9!dH;MMsdMJ|>Bbn2c>&I5 z0*n7S{VDlTx%`Lu!`$PoHpZ+6FTYWA^S6pU_i9t8%ETED#O$pn7#I~S;NfEU_ex`3 z`oTJb56_R-vsd^Z|Hpr&e(N9Qb(zz49h+MIA2yLs?tWX}#=d^KVTaYma~~zz<%>dA9JT8`R(d;L zRpalAA7}WOwp6r>c1?-jJe9{L_{CGH8Pio?O?h;G6_>`MeM^2D|G)6J0lFj*t9v=nEGDS47@uNab|5ovB)=6 zAqNZr`Tv)&)&}S&9kY@y!2bU-QAV@>@(*+s{d2Bb(N!dX(N!dX9adE~b|*Ic^j{zK zbN#p3e^=OW|JL~te8^7WW8KV0_QJ3CseV+O`;edeg_Td>?7ih-yQip>T|8}kJfHvc zGmp5+WZ#V&?(SH%{nBnf4rTjm`%U*hSUG>A+V0W>Y1(|<4|g71|2+9OOZ`Em{C|vfH}~=UU6N~K|1tXE{=@pb8QVWbX6!S+ z_9gml)%VzWoVq^8e7>eysi$|G|1*JFyzhzx{g>msW6p+x27pqaAy6 zADqp5@3;Mm(uXf*A5N}#6*XnsbdHanGGSrnf`49xcoy;(X`kCC{GZ{VN1fuIiVyN{ zmM^Md`XT!;zw~Qug-rzQ zl^lD=(e&72**PB!e^+;VSJXUuqT>}B>b={%7cA0&kf6?*$hik3FeydFXr8;eX?XtU(^{FRI6J=i=))6|%dt}lUBbW6X>QdYD z%)iYxyM1$O%43OTZzfI7D!O~qVtKXv^zhA3OTKq`Zz?aJGIwjP>S@+R{`YR~{^_>& zcR=0s3I8tqXW0I}$)@-Le|zt5+Y0T6;_WuRs_SNd)X8}+$(22%2rgLu~7C&n(MFoNB3`--?k_G@q4L? zum3FmR9uWdx{qP|kIKirW|v}R-`ZrS=jYb4ZhiVXCiByI-Z$~LrU!BAJl*bIJ1?b3 z>5X0DqyG#)RHq;87rPl>QFr;#_P5Lr)-z_$6S`<4`1n5q?~7S$TcYZZ+*S5%xPANR z8cCfe^UO0O*FK$gDB66M@YCH!TC1*_ssbER z@rFOnkJ&!n><`@In{?TAp7mRM52ptoqIa_>up6%An@@%Oe`Z`Q@+p{eUT=gpLop$2N1J??6%ziuR?hLuI zru!##3%cVyE9QR_{^9&+J!jpedE9%i+bP`0xGz$Zx%kKPII-zwE53=}D&04cMc~G> zJ8R0G+SsS=Oc65FHw6`x8X()L_PyRx?A{MRK1S)ZiWeD;z}ow#Jh(v%n5FS?%Gn|CGg%-k)T zXHC7;dnIPl)~h?0EPHX*>&{t|Ntdoa^YYvq?73`mS+4e*^A}HPEL!stbe9kegU*ru z2kN7sW7tKH82y#t{|v0#r1z)#w;u)_>)4$9pJCZuxBa2$>*=TN{}9c3_qR@sy~O!H zG2HTh&9hA36`cS5S3g+mKLfMJdHK$BCi`!$^`7Rnl8@){|7T!%^kZe=WBDeb8sm>5R%hq;{ShoFS^YcLaanf9 z+a;B9_v>dgpSrm%JCQHqn68^sTd4ekyV*a#2S2{wD!--w$lvrsatfDC>O0Ef6!+}i zK1F=exqrvL9({D<-ZGJs+$tB_m}fkR{kZK(nEj^z46OfbeuO{f>rE3f`_SKijeUC1 zhogT^>E~>-=zZE#_^sRN%|gA!a?$UYpB9|he{RMqyK_rc?(1E@+)(ce|SH< z-#ee*^gUxmlXXSvEMv2oD@$NH#aw&-s!Dq9ozWB`@pPaTR(Ik zn^>duV=r?>a#UrMJb%{pNr$H0Gip>7=zM$1&|{r!Pu{iEo({GCHunB2T95uzet3Q0 zzDSLA@k9TPII)X0u@8Ti%WuASNd9C|ul#J`zb}?@E4NPN+V##lLh3-e=L7Av7oE>l zh<;6PU4H8L**edgHHv>1{9`#W>-a(au3hVo&F5dHy6?mCBhnj9uRfGFX*(X~8rBvT zv8c^rX)u$?B#$Fs)(2m(&$E~LAscr1(epQ%zujKSDXO1s(r3SYyJFtiwf$V@%Xud1 z6z4@Uwlq1lBJT=$8>M|DPct5z4Ga6saO;!R`V*_N`#$Qw3H&Jc-u-G# z=5N>k3Vf9E%ubjzBl7>V!P+N#oVqUGHCl5@s#M-@04ZMJRw zo%NsLpu?ZaC;bfn&fI6%$5K1J{MLVlhn62xe*I_ow(d{PZ~bpcN1s$y%h%7|T|Zmy zPEO8`+V^jsudeaBnzP6KcVJDuO9(f5D-flZ6(=A}Q zLG_ZqSy`!Ca0CCGeHwo({?4_jU32e`<&Wq`@jdk)0`GTRiw^%CeC)p9d-V^`5ARsk zTUh$-x4)`h&GicITer7tvbuSO+2zC3`k=ov_b2tY-)G!v_M^2nJ#p2a;GM1CC8XRh z*Vv_`>$6SUEH*>IJ)-92+@~c@MNN?cf9oH<=c_0`WMw-ae1PFTi3_^l|IRz^x79nl z!rZXzrbvR;)0s0K6h+Kiw&OfWlde^{HTOR{Ki;-*@sC;gE&mxf{;1b}lz;U5pyZT} z+rOm`Eer4o&JOn~X4;Z%a+>Q|q)W6zuue`!MC89K_FKHxF6(_zTmCKjhhb68>c9C% zbf5g#_v^+Jk6oor?_%u&E}RyhTj2TWc~?cF_O`vR57o2plldd^VN!+s!}8Xg8r6qW z=0#6tG4fPCZ9IRLXX?~hp7D&~#Tyj^MG`|D7=C;^{;))japSeTue@o$kJZ^;T_!$H z@uTP~tvj#H6$|(-emcsM{mC_NzKRD^&!jW=j*SzrHQ>DLcM~-HfCEa1jw6Cu&`8{WI-RHcrRgJ%S9au#IQLy~~ z%b4B4^#}ZaSV$kRvRn8;{(=2b%e+$4k8Hh3!YBYd6R#=ni(>>WCFS1a;tTH0tAZ0{=WX{zQSF>MUhB}y zZ@+4n%neQTSk{{o@X(X7A#nnyaar2(o<>peejKEBaEeJ`@0%o`oD zZMcTrhJO>T1iIFn$sGW#DGNV1|HG&EH%|W(JN`$b|3~CMjsFa-hySkp+x?$`{ST|_5AFM~|Bs&hoBeO%e|YRW zWq0$N?afcy|IOa_?f!J@iUs_>mIv9_SN-AtyY)Xq*7;k(2lj8-e&jzx`+o+`Jy|8S z!k*op6BDNetQ4Gg%F<=pLiAduJ63UMgz_?~Il>FY#>O^cI5%8#gPHkDmjUrkCB_mAQW9 z=R4j#M*|LdE#uP*)mt-Ze{9JY`~TPfP5CGApW&eYpZZll*85)CXHcX4;cQ>CRV7nC zZ>*>7#hofFfA1c4o6V_iZ|0tJ{mqjWw>3AOOa8k4dE$r9-#GtnSW~C?Lpk^(e?y72 zFW>F3E%UxbmcCBo%-OVZN<-g~xwAJgyzctfd)NEe;hqOwzvBgp&TQs*D6?$Q+{=6C zo9*TAfAFU+v7_bbjKfoxtu4(D{w)2?>F?(KS@O5+AFgkz&*Yb{J8QrB`N97TH_Ib0 z<(qC^bMsH!m9P5+BW@j0<+zk_|3mL>hcjmd?yRmV(^RcF#5DC|{2$TgZ{hL`b$T_0 zKfG7}IQ?k%kMf6ezaO;~_nLgj>R!Z(Q>iZ?|76IW1E)gV?!A0e|3mHm(RlGcl7DB^3Elf~`r*GW8};A%6{jD> zcjzljdKCC~_3F%9tZTx}3TI3{x5e$^x|uy~>W7_MRA;9@zp?V{vBI`q=exBrbFyvo z|Gu~!be(OKOq1dc?(W4($ANY0t@Oz;jmxC_u$$zl6+TrTrcHh}vk?SWt4{Exm zVe&im$_$Uot~3AqNNN4%Y*v@_arqnj-##^U7j3lJkAD1M{IF~#`!wYS=aj7rj>u#du(o!kE6-#<7zVT*f-Lc%SlrhJ*J188ZBDO+WNc;=}#p ztNWYpb4>mxB7Q`_>DKNa)<>@Ydg=LfriozsifG$o^OsL^m}PqTGoJ#l zpP{Mi-w8R*57UpxOGniNUESi(|0CXW>-DekZ~rrJW=H8>*%f=HO*{IvwD)U|^eeh~ zs=*@0w^S~kRSfB!J45pq|AY6(`dMp=KU}@vy@xaLqq_g)-1l;as_R96=tq9o`X^XR z`E%mxTet4K(N+>vpT2SPVHue^zaP^7IDdYOtTFx=_W05EHy=MpKhi(KeV%oFbB^?p z{aeb9$cx|j(RcIx+Pw5G|F^Rvqa8bEU9wzkw&+vDvOXmhYtH;__KEt9{~6@|u~rB_ zQfvQs`RM6YdW#>0UD>1m@ao5FYage6@SFP0I%EIl=|`&e2xLihZTWCFrfr_;r`8jl zMH^RI9XnU#ohrRPE<1YP#w}}8)0QmFFQ2v8^3&0ik=xUD>$%*!m1({D?y|Mnuk&|n zEb8KG{!ip`TUaOW|0U32aqoZf)!6u-JF3)b$hvHvwOL-c3ve+Jg2tou{Qu>ZgON8|6pFKg`&%WvgdZ#(tuo9kB3+LymJ z_te&mSQ@OlK(K+4d(%1NCeJ$&&si8>&niFc&syWL<9!q{jY8LIqQ1h zWmVr}mI^z43=DVA6!iKQvqTEYkV25S#YBM^$p?-ktKKOE3@yH4E;C$3}i!3=TMDN9 z3Sa%B|DpMz``y1{s_c)vXZR6(>{~X=w-06uQzrSQP1cCy-E-r!LK)Y5o18UVnk=F$ z6>7^pT`T7H{}EpOaMt`yKlmS)zYYG-eaO3iQ$_XRII~Hz*8b=9pDg)!TH)T)r@80u z+w}JEFfPshEmC*=dc=Q*-ljM9-@HsV|7Y0xg8AC&lco9%@l2+3-TpI#U;W4RNA+rH z_Re|FUcGvLcv{ifV&jeJliV{-8*di!7V=b??9={$|BuM8Ql44M--xmAxu^J_;c@9g z`8W6fGqAc&e)nqs2J^@E9s7S=WhkqY`|!NIMtSkgAJvCu@pV7!Wx8K~^G(U?kZs$W zi^X#%x-qSboY}{&+W9IwYv);=AUq zpZU~vO;%Ok+O*q`Q|wabzCE(-(}yR$FK$O@^BAp^&%KdRW#0CsR{QWi*-Is7*$-b6 zyBHY}anoT!^v&pF3zfSxCa`*34Gj%-0Ig>WV1HEC`@#Dkm*mIFma2IZKlFdI{BYPf zI_7L;#XRkgEIQrKxMzDTxSep}gqK55x{!jf#rby!f6ac#{wDB){sXm@`aLDJ%F#cX zKgRk0`0;Ul_j|v!>&~`LGre8(Ku4l2nemg5?Z-vh@3m+Be)P8L*^byK`@Jv!tYALz zb;hSn`lb)-R9hbT@z%1hK0f!+9`7R){xht3{AB_E)IZs|yEOT1tHY1%7uMNxI(p&u zji%rCcfbAaY|d=@arqHx+lzNKWgQpr$Npz%(x_;6zG!D%cl{pY75%#NkJhGpfB3e0 zS=3Lz`6y>=3Se$JEAS$Sc@Y}`Iqkcr@{X?w?BIP;rl->%LjLjf9wAa ztjT?-n|W&D{BFHfMkL-Cg@P^aA_;tC-z|MW8zy{OU!Y?8A*u82Z`AgddsnQ}TeGyuZ05w+mc%7NM?*Up z9ta<;`YOWsFDt{@jg3>{Xub3NP4*q@Yr=n9)M#8al~|j&quW2%G>lczCv3B4uB5`G za*;-EnFkwH7!24Su^)fW@ZvuMmz_;b{*TCqx99qOKYUN}W83D3zd6^(%00@ij=q`D zyDTL%STW+t#?S!20)|KXLqNBV{d#z%j{C=ar}o-5h`m^IW|FiDD@;~R?Sl{;i|Mj2WA?I8`Pwe!Nb0oeHXO(Tf z*V}w=eU9Cg9J?!w|1VI}Fz7`yusRGxx64doi`p~&as4rQzCX-Af*Sz3qw(Q* zZu^A$-M9WTbUxeJX8CSQws*wCjuYR-OWM({x?6MQ;#{YuZ{1+B@x~leuPnXw zTZ_F6J*~XvYZZ3;ea>8$dCJo=(#kjA zJTnW#o_lSccZ+-Gwa4ibXFS=zC9J=GX6MvxZ%@kUe|Z1x!`~@(T7N8l@PABy`2OgM z8vh6Ko%fV?d|2N2e!<1BthvRZ4RKqpJz{H2ZBKd`{^pX;#A9MiM>m{`e(3*0C%ti> zty<%6yPC|uGwS4j+<(M>^sIfrNAbg*3qSmA*SUKr=C zcxXx4xhD-f*EiT{)*buLuvM)67X)9md`qBStcE#&BN%`ab9NXVY z+P^vdckUzM#}$9%=9!t-h14gC=~i4@WN%zQ^Z9$H+ckT47nDDLy8hYiNA_=S%lF$! z)m^cYf0O-N_1}eh#{UfIw($?!TTS={e}v^Ty}Bp4=Ocgj^ds9gNzab8%2>Q(=iY4_ zqxWyky0Ci9zZ4xk^-tdFd+Tn>Z~Wd@qx?JaN1gr6!w>kDrvH|Dv7cMPzvCXm2evMg zxhx}3YSd|sgBl_v(2Vv73NP9D)#$TW4Jo)-b&XU zZeCIMjG|+neA{y+-DK|L6PI57oNK*g%brP77Cm}d%>}=`}I{y=jt7!iw_;LNA^Nba2&ktMO>womS{$~3T!T2Bl8IGh~XAF@Z$5sI-}?nL-MCrfvVFRJZyeX(Enn)z zeq8(ft?kFu`iIfKeeI9k;<;vZ$X)pLCxxxo6L;*{apBpfElrWP&MGi0-OxGVX~(*k zmr5(6%O-pOG28y?e#E-4DYLVzSDRh^=KVf%x88e~#hZ%q^rzkW&)|C?UFJ!+>7#z( z7x&md9Pii@|0sIpn?Ke|Zk%5k?<>1!@)om?ZYdL|X-r)*p*5&%(Pp^@A&2$P<-g_q z?X%PP!}r7TKLg9-+28Vh7_O@k{PCY*?Z2~jsvrI{^q0OYkzA{%{82fh%4b&Ak7?=s z1@pL{%y8NsZ=#siWw1(8yZ9ex?}z%xAC@1L5APTJ&`=X+_t zS-g9XseSiW*KBq3s{I{$TVrAhnXHPBldcD@uX{#5Xr-u6b zlCtMlweK!HEtkGr-zI|u<~{CfeoQ|U_G|06)}mi+jVoSUH{1Sg(~NZr)6NGp-Fsx_9<;n9L2kjs zv(?{~=Ujin|3|3$kyLh6`rFutXWbvJe=Gjsamc? zTRGFIe9q(Q6{iCIclFHb{(0>D=DpW?g)h2_E?fZ`bb?|=*qQ`TjRV6!ouPfB_t3sk z@qY%^1+n)xeQ#(p{?8!#b^5>ATc!TG{aw8OiRFjiulfHiiDIpbtv{H$M7ZvZ9cQTL z<uIE-{JXN83j`hF4-d9d-KXCs4)sOYC3wU=w{eAK2 z@9SS0pNQ<)`1i8=H2Ymy@29_apZ?x~uz^NH~Zbywx2Hhi>y^Y&xi>7X5Zw{P2e zG3}b@+MA(0+bVYVdM3CX`gTGhS=F1t@t{0I4gXb^51F6ZPn3lHIa=p=+@!LwaDK%< z-~S8;tT(LtX#fA(&;A3`<6l*NS-;B0-2B(R`luh54@{5zRQYB7+8SfKFy+Y?0@yFb z$xqj4UjI05<5JJ>m4C92)>s-U8u?beo2st;+CgshvV9>{*F&@JT1!3Jvi$@@k^R&% zv9*_XEZ|#o<73zM$ZyM+@n!@tMuul!zqU22_10J04CepW|GMpt+j{iX%I`aVuHLmf zZtICxnNyRmHQ3A#>wjNWuk~@g&>{QEOM6d)xc?cfKIsb{o$#Ek!7k;akIHe2=L^?Q zu&K`ewsxE4KRzA*^-k3V_9ODVrF)AH*l(Nvrv1@+p1A)EsjvG# zN_$<1s@GIG+qY2vrrx7l52t^h;H#8(?CIT|UK?2IlfVA4`*&rR>(S#ae;mJM>^UhK zlw`yvm=d0Re$i_AL;JU$XZ>;b@O*(kvJdU$f7l29yZ)hX=d!5RQla-JtHft*-qD%b z<*(-&?t9|Z?MP>y2Mt9U%*z&mnyq*iLm@4aV*XM4pP^~j5B+}v=YMF39}mobX#d0I zi*NLA{pgLK-q@XHTlzfn+JAP*=><(t|qyDDe$e{;Qgcy%G?LH5&e2F_n6Tz}>NR{lSO;`#Rb5%ph{E@*$||Hmz$ zo_}-lA*JN%r~Mseb>Xu^4j#=^S=Jvc&q;Z#c%SwKKA98_D=Vj z{I05A>q9*6*?;TjU)#IgbFv~|<-Zcf*H$5OmR0>{uzH&ty*%slT`m9Jee&h<)Z#G ztb2TI-`l>p^lz!W{hwj8%4FZq+jC|O5e{?Fe(CLKSsd53jv_Sfcr9Q}`ci$niT{FC`G*6aIQDf!lY%Gp(X?d(&!zex*= zuD#h^z_{e^-?x|F6`lQkcIT~C4D4@Ap_7mD*d`sp#qkyRC<5~b{(pRIANcxL)Uf~H z|L7Gx*SE%TamAs0{#%FEHQn>B{;jD#b4k$do~~QIJDemKHqIWu)8GClS@42t_I7ip)k-*-1 z0i)iF+iZ=a0#`jWHNAL;|8@Ck`@15Q?!Omp{3H9w*ZSB$fe-6@zRXyk`(cnlY4%{P$w@Z1AxPhV%bl zB{$DuWI_x9`Ty6*HC+BhC8+G(`I=QE&_N!(@PZX$|4AQbhnM3&?a^*tS@@rUwUc#! zT0B$Ze+K8f)Bnx>%Jo%N2<-XKaI`e_Kf|)$zrKFr|9-0u zeAI<$HQK(9JN_1!`#vr|Dnnfk9Dk90Qt*x16*p>EJ)dU3D+9D`gdya{4O#7v&%Z6X zSn{8N>)+L1N6*eZ`)0eQ7z%llyCS;?qpo`G>9Vew)^A`5^qZ9oNn?g+0H*pKm{u{r1}grn`pmr4>@ol@#Y) z{qggKe8i8eed-G52IUL?^{YiuQ z|JAp9eEIJ`+L!xr?Y5)|kFV`p{?Ww7^Z3hntBQShOpFVR9pqX5cyGF&U1m6sbN!#9+blG5&edjEn=%C63lFO|75Xe*8Xc&@vA; zX!(!8pk;%7eSNaNaF(6H-x+oMS-+;31;n^6+c#}B)25YOmnwCYRSeY&y-UEiEifeT zFWyJ_)?muhC;NH%wN`zfp#J|w zqKvLzC@K;dfA0Aj(AHcKuK#}tM`MhL4QQ~k3`D~kbr@yS*8dEw_0s#(`k8wk|I?mr z|JVHf^_Ti+4YBfnTwiwYPutJJlm1V;+y1Y@{`K(+{~2nRze(HzJ zf9(+D^6vi(Q~#Y<43Z1~qQ1p_Vo`^G)sOUlT>KB;dI$cHUi72uShYsZ7k-hGxX1EnK+jvn>O4?+2)$qGgXH%$eG{XeCb<*t%`T;PptC0?)9&3| zzkfq}tY`cif32U(HdpUqyQ}?-^%wh@V(V8P{IdSg`HTA`AJFHmbI4g;mJbPD8M^jUCZ!z@ znEqyZVQ#_c-zU^2nkN^%yR$Tc$?o3Ar|WdU`hMkN7vcKJ!>LS#W|u zx6*F8TG5DqB8-0z2F9Fk`jhzZd`BJMhxz?~$o}CTb6K~vSL^-BN%~tO@3?F_ zsyvBiKyGylId5PN+x{xdWMS1k75@^Sew zeU{Ur53iQW8XXU9at$@qG23)fr0cmQU(_$51xy^_CnXsE-N<>qwvMO%pw9L3xBQP8 zd*=UediS5<%dS)VYyNCLeoFi6J%LsC*fhW7f8P1*`j0NV_`P!Rd*eabq%!_Z*RT4F zo^=7}xgzkbK+Hn4thV@;!p{Gfab=)(@SsunpY`8d<-&g}^=C9a<=4O0uYYg-&-gc8 zHXz}Ptf%BqEw^37jV0^a|G$F6mg2vRD;9PAtF|pbi6shqk)T2gg#TXvEnxW1fPCBC zocIsn0sL=nKWM0bQL*_y!zJ!te`V2{Q^o%o4n`le-`w8hp8v!5&3}e3P5<`RH2?iJ zf92Hr#`)*U!2LbwWp{sPeCDzSH(TStEu|Gq_^!UQ|9{c9`QD0Gv)rbyeYtB|@0uHt zS-))W2u0n!73;F;a4Gl7@IH?lq9v&+q6$r%Y}+AQw*G$kc>a0#1Jz?om*2Yaa@N_Y z|0b`wWw>R}mR+5TW?ieevG}-34zu!V&7dVr917Q8K5x|PjF7hZ7(ch_?7W>1^KYp? z-q!v|+WyU3mx|*-`#t_#slB#6c*D4sTz`}W zy(cYM@XM?}PVd$8JM%>6mlVkKP5q+YwoHEa%Xotyc8^O8i(kgO{K(7yTJvvf$NB0@ zOCH+VPpyiZs($C|ztShaTxN1MPyX}z(wCJM@m>sHw1X_(#>%cPIa_pX?S#Fr7jMm* zVZsQ?jf-aOS+P8THG1O2n7fz09*=sZF6s3=@A|q7=DsTdA~{^2bHOvmtFOGjuJKqV ze9o(jf8WPHcaSq*e{@PsU)|IPcB(CPpO@Auu01|u3G>nl{m#Vy43RzG7w}KmQoYNr$Nb6UB) zUg0c;|Ca}TbtOzN^39b2;kp zNWHEP-|RF^I&z@$2ZS*iO}GCuux7>H-#otwJO!|J{=W(F;V-76wVICq<2qqpcWxfH zziCg=p?k@an{CEDdyhHyP*4*x&UR?eD zu=RbTYZsnOK3cT@Ly)bu^TmC7|IYu@YPx*tqulDHuWqH@JwNODxoumg-SQAlv}U`$ zZ>Mzw%g-4qtpSQZ{Ok8!5}a4DKDi^~Q*xI3x&I7x`&Uk7uUPy={h+zZv1ZFh`zC(4 za{PO3_LKh%On08R7{~io*LcD*^fW3xBi$V zm-6__`UP7vepG$b7j|w|XKJvE__%h%rzIB8=Ux4%e`L4rx9!RK$??@c_T4Wr`d}O{ zRpYq%Ou3hPKnC;m{bcm-{&i>n4EfK{l>1}HAKU*7O??&SKL1YtyAm&5;T`p1+kXb@ zQ~%C<5-qj2mySMr_Zz=@`pJIdy2snI>qAQz3H0X><%{B9@|Ldrsi;4wz)=4q!;iag zLH*mmb(bZ6{aLnO-~Pe;dj2i>t@d*N8S?8}=gIE6^4^4>w_=|>;>@L28{94%X=zSa zlNGUQg~}WTi}hFK_3!OZcvzmpYCr$ql=J3-A29h3c2kD<5KK;+IN#oOhhMS`K;3YllZ&vo}{J-|*&;D<& za`C^F)Y&v`_HfmdSa@(fhdNepU3|*u$_?;8KPNX#`AK~9uC}C>yjP3%d+*O( z&-S0;Q08yVn%qmrR+Xe}xp;2x^GdtChs#e*{}*=mbieVNd2w(4?Eap(amzoi8o{e9 z7cxKPJ-q1uO#8E((b62R`K3SWJ>DPMU-JL@&&kkk8DwU$*82LfeJs0l2HuK@i zPa6elHJD%O^ZQPTEBswaZy^mkYP_=B<&%hhk!y!|P%O6(v zKaFi&UGh$YdHFV*q_x*-SH5hH5@87Q1+`|bt@*w2T7&)n3xC~q#qD?*6SivS-Q%Wr z@2-z}{MF^ur24-hj8A@n%9rcCKc>llov;5o?(u5(??2WneN>4Ivzf*4bN11IG|NWz zr8V`SdBi_e*KfrKNgOK7U-G{!HvL(+0@OC&pPv2mT7HW>&;Bg`Th@mana;1PP(Dz6 zHaWX^@~%s6d$KcGUbSg*aD?YveAua?Z@seqpwB_K8U}`{owt9yK&~?LRt6BKi>cLw)cpFsM*EanG=s1`JH+3$SqkSVo8v> z(ZqB2{v`ZoXj=T^(5-mBtUB|*eftD5ze{|!aeWxeAADu2uXQG$O?Ry8*)_9Ib<92^ zujTP(ztP&V%O^e--J1RL<nI7JpfPSbjtOK{xxCO3VK<@bb0(Gd)v( zSg+#zzyA6ExCH+*w425MGmUqa6Fau|asI5odP%Pj{QY`-Z$*BOedha{>mNSvG?i!n z@%vlr57`I#;*me=voCG!7rPM|7xlh3#ovfCHQe{ai^mG7ksc44dMJFJ=|`Yyzkl5l$k%1d(RaZEM{P^e)%Cy@lR0K@B1&@ zuPv(G`1t(~E%rBpAFfAkyRgUkw@-z1z}{&$^Dh3e=e@Q&Phj^7-s0u1HlAw9Imgb( zFs%t;3#xe>0GgrxcYOMVCH5Ta>I~~H-c#k1s^vc@$9Y9<-_~sh@0KgwbI+A%j5#W) zcsg&EjV8;~$A6R`$g|YgUrxWchyB5S2JRo~Zqv$kO}gxH`Qhd^&#qN7r7Wg~>; z=y@1YCVDj|?#T6AJI>qlmOuQ@!2U(~b)5U*`wXQiKN^1r)!nL>{NmrVU#O0&Lip%( zJE^^wb!>beo_|x_J-g%0bMKoyUQuQf-__ilJN3rKIo5N}Wxw?Y%_fGPZN75pO?=gF zp6zTKt|YtZy{udJ+~oP0cOjs{SfsN4P1knFBxJcE`~S;SERV4i+VcOekZpnd-K!#j z@lD_@Sd6f-LQsu`jES89hUr0`LxIz%S~PZisWIBc$e4*hNMx<2PZ33uj3s=>BsM?Y2W96z3uZI*JPGylifb=Gdr}DL$Ao;!0Rg|uj5zG-!%V^ z$bIh*{%_8I5I<*d2#*B-d(qz2KnnT zJU?}B-OrzV4_NE&S0o?$W!}8U^x=DsA4O*}_sL#blW;jl_s{XkZ@yhPw(;2PJJWKs zG)@co8F`w1spND1x>eKjmiA}E)#1~Vr|G8W3U^_p#I`HZI!1!SQ<_)VK z<^H+;Q|3QI2HBoWHey>y4jw zVFx##{CzKH_P?Q?jrL-2J=au|a^;VyH1`bU4{oi?f7tzqWeD||ey!`0 zC)@EU5*)YABo`G+tY7ub@XO-Fcdq5Z4|_y%-^E|u|9QjQ%yRqxS3d0g&yX@VJghC( z_~`wu@84d0JfG{%%I#Oa_{KkS`E=v8SdZE1>{6*?Yd(0jE-vL+vf6;9bY1a3+5NvI z{xe*Zw*L{3b^p)g_|l&#-$g3ZZ|zTS^Ka?9a{X@ona`E?ix1jAobjI_?fx(G^-c0W zJa5#0T=h-Y*z0nMuetBVExBf=Jukjh=(~35(lya8>4-q5C56WpXeq5=&|qL-5Z(Or zcbA#@*0MuA${&_=-dWxJBIDo})8yo;E;I8LYuD&umI9asMg9M)WR@tHW?>dO|F6-~ z@ax1GUY}k6VOx#*e+D~wf&UC2`mg?H_$k%@Bic^*Kf`?fj`|<&ul_Uq>5bF<&%nw4 zR{TGMgSPx1wYBy?7cBlK#D7cwUqhJv58tTz&yMj;^H#pKr;=d~a+m)zaHk)t|NQVH z|9=LKzw!SWCa(C;(7O4d{m&0S+W#}O)UE%|aN_Et`yVFMU9A7YwDCW~p{&dQ8D??W zKUDa;=s&{?*5v;TEmxQSXD|_A{D0x!>-vW|Km7mI*eU*JcqqU6Kf@#e)y66Kfz!7|1&5DVmfy9(fye2s6XTg zI;BKMK(zm#aM=7m7XmS2dI5hX zX2@TuKXmiM{69Zxoc}W%vJd~y@FeSF{tplPjQzhDBmOfyG|l|aaF$gbBX$`6U!;3f z97wM36Lc2|tWZB(c%(C5Nb85&5f%^W8ZGv3{~4IfR40@K6&Ykq{AY1mX2aPB^LbAS zA1@SMUEy}5-Sd9UDfe&x8I;@@=hO>ITADDvzmJ({u;dq=8pq$R^M5wt4T^8`2WJdj zqI(R{HnR-UJdEiq`~R1z7xad8ra#)QAFZ0-lK-XG{Al2fo2S!xk6gc{`m`rgWuL-D zo-CJD(zjr>QDSf=HtJXY^ZQPy4cMn zHrlsxp|AX#aIx!Rzs|M1{=D?>og%l7uevG}nznvA|3_4{yzcxzeu?vc!p!FXa;=VA z=L#AGpZQ1U4+mt5^F8duiMlfdQ}%#Y4eSOj-~^pt;UI!8Apif$*ZtsG%ymCur$>dq zPoEcrW*9^8r@ya%KKi>x`N|gm+shwI#ovg#5uIg_@vF6Gvdf9>8`jOuXZ99U=4DlxoulM?@iSZW$oc~^opK<>~PWq`>fr$>dJ>ZwoeT6c;d#hZb^!OJBv*7tlzs2Y_}9Fj-M)< zeoan#`rr7d@4iw#$7Y_bdHe8WW&gyu&kMb$__<{;|G)bCxmWSjuj?H*<+NI?Hv|uu z@0B|EsoAEB;WcO(=e?e%ZrQW9=FNS*py6o-Q&mc{M&_6Hg$wwq-d0^VuGq<=y=HeD zXl+5v1@_sX^#j^xN;?h;@#ml0n;+KyzTWDyZj_x|gZ=+&Z*9Cee*XBY{rG(EJ;Muo z7#EmrUHf<2qsVp28!x}nSzvtc)%Q&@u9=xke=hqg7p$;7sTew=`uSVNkKKpvv%lHW z|2Ee6sGZ(Nztz^aU+k0q@V0aR)vwE4?;CF08pN#<_{>vg!ioh<kpl8xM%#) zZTs??(7#LFnpgd3<33@!o~Od@ruWSXu7_bEKc9zx`l`LRpS$vtn~Shc+nrS#vc4Ma zS+!29X2b2(FZMr}sJV3K?BB+9=Wac3HoY7Dc>2-QZOxDMwlC||H92kh=X5-Grd`I& z9G33J;)2-Fr#mcTAE!hIdV78gpZ-4l&z}Db9Ls*DZ|eG+&Dy%$Y1UHTNaenu+`H(B zGIo(BWF-q^*<||fMGY7N_W!RFmrOCzEn$KB{}*Uz_C*?+eF>}C^=|Xne{B9;S|NPk zd;Avux89Y%_0}Jn9nTy8V{!b4{ALr|AlvH00*6yoFWo-4I_Cb#+|_% zwsPH=C;6YDY1JQ@Js--C*o*#g`{DW9p@whI1snB;{~4r?E~;p|?78oXjq}pi=K7gS z=h~LkI{j0h_w1GZX4Ng5T+L^^oWfNeF1e)c^nZq}q4y8_3;f~#G4pr+kK{-EhbnE< zSN;e(IsM_Bu&S?b!o`GcIh$=<@t+~%B=7CR&vcHz*|tqBQdxS2W~#kGjoyETCWnlB z>>u=x=ySx?#Qt!85V_T#ry||$^8SaxfAsqg-l~Z@*16JkWkf}H@P^5O`+U7ayk|!j zq^5P4IC-2n{4W0B)3uqpE35ZxyQ_b%ST}ml)Ri|+Ze8vBUZdnvaLCHfyRYuw|7U$v z)w+w@w_H?tH+lQb>34pe|0805NUFWbPUWT5{KkLg|IW{sGFdP1Bl_TUJNb`t6PLzi z&0>Atsh_mr;@7agTmQ19cQe~Oy>YnY4C~fIqC#q?KD}?*{~=_4yTOn0$KNJCt{1Gh z{CN72c%~o!jz9dl>%;QK#~0uHsa$*C`gm^g!*0z|>E+W*CUJMD?AW!#bvVd1oR925&cp9Fu&uU+25A> zoE0T|-6Jmlu`kSIecmMX+WkuYRtuBc8hXqXoyqlfk3QYb+4kPVYg3wge|GD$I^Ft% z0eh@Jc>QOH+VG?Ew`Sc{tB_ml4R8OcR6JZb`OtLk2ey3Cr*DT&`_FLtI_J&_vSyEh z#pdZtpVn-M`I)@*NBal)NBPIzH|)|s^uFgmgLDn&>L1_ur7Mz;UVeQ~_Cv|8*p`Ve zrT+G4^3nO$_z#n_?L;&5_18@g`|xh%-<6qvr>|gM!8_@v(VOau__@0?SKl)a z|9t9&$_z~aK<&pc_ZTMHb(#%)gy7%|#QrXAz^?sf5 zjJtKK;_anJW-}j2Z~Xq^%HrqW7k^%V?7n>cL0i-JVp;LxdlVPfUAmlUzj^(Uwehak z|EW}D2Yfhn|FzZCw^P^luDPFdZQoU)n@6kpCZ}!Z`nH|Nf7V4CHJ|<~_upD4UwUT$ z`$gEau4P+yzy7X$d%3>3x%Q8pxo0Nd=gQ0deRuln&wJOFuJV7FD-UW${b$&+_&>wE z#h(}d_Ok!^%HHbp`nTTsKY#T9`z2DJ6ww1Bx76Q$e9-^Hq0Z#T{9|_fAN3|bvTv8?{$k&G&tT)FcWb_Chi%(` z`Mti~>81Bn4(h8oF1xdQM$L-(hDJNXRV(k`l7E>0*7Bp*e}?EwcEWX6?fLf!)EHhf z$$!%u?sH|IjNa}Qw~ZxTwp-=?XZY5;rNHN<)(Ua4?}00O%nnZr{`jAvWB-G`{|uY< zckVI$Sbx~+U-O6k#|^XgbJW;=n10ZzzuQJUb?vKq{)Lym{C;h*{_VTxxu1OgGvqcU zZTvf*KWD%B8Lq;Up&{Bu_Vs>#uRZUd4nLXlG&^I8u3lZos(eB3*$=MI-+WE%^TpzO zr@p_|UKhX1PPgt}75l-n>WA-HeN;bSe@i~(hjQW4%w5Meeq{FBaw+BF9?jf}Q!B1t zv|BL!of6-AxcY+F6-)~=aEdIsM&1X70=#tq@gGp06H|*YTVW0W_hf3eX-(K#ISKB5(x_>BoM(KLC7yHCF zU-=XI;h4VQYdfyV{@Xw8`Om-?=UXq8IWb0HldsgFPvV)9>24Yq|4#kwUU&FDV||9c z$RCp*^YxqS1b@UoZg2aO{IFi+_~U6GdgtETZM$#s3dhoKtowiJDfgu`F1VwjDCSgV zA?;u5aWpJu-G}F!-z_}PUw!UEaM-5lduwBY>Qc=kAAbF(zj^<3`_0pLe_ru;?XB;* zi>6-Bf9U^1WBZ}?;(t6oJa5sdllzmg@kimhiuC~>)Z6P+?nXrFxBhW_B<)&zuBONuR1crX8O{`TRA?_+!Rf2Z!5U;3io5%w$m=zF1iKcWw3 z7G`H{j<|a}B5V34+Yga9lRJ(tid{A1%$mrDtPW`@Kgu60|068^@cDuI-m*HI54qFd zG_Uv}bp2@5^zNLLt9G0b=O3@;?%v(G&d0D~Yre?OK;NQA=GkU*lR|sW+_>xa!~Q=5 z%hJE|_i^8?J87r;p>OVoYR?~sKiob#YklX|`O=yD7&mRaR#PhbZ0_otTi0H_Q&jn4 z;nt32I-h(bZZ6O=XY-4%zv23y;qx?d}+vEG;d)&vc*{Qk9|2SM)@X2SoRYlXQ><<3vrTIpOYkAk47nyy{EVO7pN8LI5 z51#$3HM}3f57_fo%zqqu{Ah3ap{O84~NwtJV( zy;E3Vvip|hoYw00!WZ=$=l>C&eQ5s1{|r4f`YT?n{H^$EpWKIc-D>R%vv%npuiyQyH1FKy+^jXL z@BfSa{zqJE*QdHKvocNQ1YPy|&#>|St-${b=L3IU{JT{C&sX`mmDj&5-Tvo~`+tU7 zF8i&z`=37o6|v5?;?OeF9$JR3xB7Qw+kb}n9se2LFLX!fVfYgNR^vaz^A&$yK+DlR zuMvf&{H;HXYg2`eRof! z*4fA1D+QZZURONG{%?^S|9Zav3{CTz?lambvH#`$wjbP|_WbwV|H`cS-1cnSke1Jd zzfPYQi02$GwAFS0uYa#aS&i5D40Sag`xEoy=Wm|#zj-|3s-1p~&c$_l`&eQ;zQ?J4 z_&Z1S$%pL8JHK*EOiA7H$uo29m9+aamF42T^l$xP`glKg#dD7>*KL&7znrIZ>Bg#= zxl4JUTDsQjx&FR*LiECv1j!;J*1Cn2>9_Wo|JeOos-pZ@oYMS9=7;1Zv*vF;e>Agv z>%FB5Z#wGDU+3{@^Ojx9W*to4GlO&8wo{QYCbwtKPty2t=5d?llUEP7zy0U#Yjs!h zRh`W2j-M9mtD&p%>$30E1pHmHk1tkr`_cTH$w%vqE-dQe&VT#9ZTjl3(P9q?ne{kkjwO?Y;hR{aw93y}wsH>{kBa`Q3FQ*G*5RPrAixlqq%U-_yt4 zXNyJW9y)H)A*>S{uuk;nPicAKX#SwdPgjb~pLt=)yyJJHLgp9$yuKuJ{Z!fB%DAcO zcmIXHjEerS_hDvMy~y8ov~i@vSByklM0l`jVu@N#LaSNzfV+wl*}5C1i{E6N|u zKlY!Y#h!PMqv@u5hZ7(13*F4TE_8eIE}hdNpOhs|yDPOZvn=L$BE|5fd+MvOou`++ za`*Z%IrKk6)AYKtQO8!+KYZWx@lVj-WqZ8S*Bo7xCAN?0!;C;}=)w}+@U-c*cKSNXZpDec1E+3{JJbz>TVIhs}J9h8g6E9KGcQ3>) z{Z_7+!K`XWz2xnNXKaozm9wkalqGI@^>u$M+kXcA{Yf#C?)+Z<*!*aHTRrO@)#}In zUH8mByl=G87yJ6#Z*hV0Z05Gl{Vqe?NWk0njde_Q^!IzMfC>VF2gUp1TsDzij7%MU)=r8C=4 z`&DgAIorO?dJpxtcGj!@+W+bInbi1=pUV&Kzj^+UJy)r3{_$I_U;i^4k?)gw!ZlCr zIwd$iGYvh3Uq)`GeZxmL>d$k%=Bzw*UvG7E_1($uKfSpd)Nky)ck7FH>z9}9 zu0Oc_+5K;A3;6%k$Np!yf9*eml>HV{zWsT-|1<1g`_J^((+}St<{zB>JHO#_Rux;z zKh-@y(yMFLX5Ew9KJ9kU1V#Jo9dkC=Rmt!2GW^ew5h~xZpSMo(l^y#Jo5v5YAG$B_ zM|SUrw|CF?+XUAo{kw3fR8V?S(}Nw8nAQn~h7@tyeO()R;>rGL{~4NR{b#r>e|AYS z*Pc7;_y2T#d-eD2QqRqw@_#!A{AZZ|djIcV_aA$i@;d*kUw8iR*8Gp_Z`^OK=d6={ zJ%4lm;kRCo55)0ZQQC81k3(Lm_j&%?44;mhG~J3`8{E0JtM$+X>2g1_t|LFvKbRku zntw>w>iE%L@`q#VP6vL_`L%ZGyO|F&lBeBz8mtu^@%hh7y(v9=oOJgVH(1VkKY8AN zhUEVY+x{~=iGR3piD}`#&)fe^+W+nGC-dJ<{~5Nvz4@PE+uDC7^%?PBf0y3>`#1mN z`fKl-<9Y0OUtQb3Y3Z^358pDy3&#J5kDmMG+lQ}vK4klJ-QTx+?hJV&14gG4&uy>m zdVllsL;nM-!Ke6cmhZI5e#p=9YM#nfrE4X(t2Ik@sQkOO!|1f)j$L>5?-e+jb86}L zT%&acg;62x+LPWce{A|=N1fH1Gnv<(bxoi8pW#!)=@0%NF8oRUcj1ct^Iz}({oDTI z;@^cU>d$|LIPZFXvpvr~o>%hRf23F3sC*w6g!dJ{TQ!VSpS}x1omL#lL+&-!`pvuUE)!Fx)vi_(Qj$-IGb4?>txAe+VwU z`$7DWv*dqa-qKe1pFy;A{lAI# z!dFkK-+TG1+VVr^|6ly&ep>#nNoDfyW$S)|_fYy4;qkWuzFWYna zQT)ODR(VO2t(WXHYpg!Z?^|=}-lJQ0ze=l2s}`B*bSiiG@tJvV1nnwjMu-G)XmNa< zuiwA-SK#K0`@Mp@CT*E|;e6|s{|rCWm%n-VP@*)tza?zGNF7VXrgwLu>JK?S{h~x5{rff19|x;#hTJHtX>o zQ)_?G%nORs%!^8X?_d(wmJFRJD_brE5iLnLou=dGGv~ zT%-PXcWK0ZkB|0$MEs9Tmy_5PoV()ak{Z*CUv~f0jme#wo4;7~ZMsH;$_s9{+c8Wb zq4GbH>xDFa*5}Rtp;7hMPTliA!!hl5^7?<*Uz&dUKSTDHH47&sJl-Py=s$xQ-M zoHn$N{F&4{@9S?-oz49GP2BG+V#Dtoxm1#??{w+QU166?Z%T5-&fT+)wziH4)6h+D z<(hb0s$oUf0*wF$1_rJF4AbDpO2w{>IJ5L=|GQZ>8~M{~xpeKau*wn``R-Gq~y>-2a2u z{zv$mR?#1oFXjJV_}69s@aBF2_CHtlvpxT}clmz?F8hZ!_u0Sx&%g~+d|O`RpIyb_ z1OFMeK7TXy1H0Gd8q>lD@8w?nGrD5hJ-Z_{`*x&wqx4#>k!s`plS z5AOeAoBv1K>GeM@?KL&hf0yjfm*@Hu_rd(we%U{KKk6UN=Q*z5`9)XQH&_1Qw%+Q8 zZ;i_52Tpz7^zNB*X(O>vu+B*-$cK^Av-sUf{t9`q4_Z`P<-7P_9gghs#ZVg$s{N(99 zmoiJQPrLqh&12~|PgO;3XFYX4F?-tSKeK=CR(re4bNeE@6#EZ8_dDdj1c7$gHC+DB z&?^7o&3XB6Kc+vp_@5yUBv9M@Ti{RRkH(Mn2j!){_IFf$_{g?!$)DJd*V{~9+MVW+ zPrvl0^z`az4?`}6d_9T9zHhj<8J#&YsqUP7l%}Vbz=~~il2u=7EZbLB^|R~stbpi4 zLET?l=EUw8DIt#Q^5%`~h@9f9_41Y!Tv;AjyVDg{gVf-eMP@X^e{~6lTe*9Qr`o__qJUZ@>(Ew1+U`JK8{^F!#YlHYOfo*UIan7LnoAC!L{L-J4He+CxxVDx+~zkPkv z*8QFPY<`%2xcqScaebNC7x(xs{z+e5aolOsCBNQ2mJ6Akid;8?2e!JN?WycYEIVO+B;q?V70Fn|5u=U7oY^@{_06 zXPT{D*FEv&y;p^q+Ph7@W&Jb7Oa$(Ktj&-AXFy7*v9tei8L#{!|9A2Jyz{&@kssWT z&6oSb@qxedkHPv!bJ<-kN4x*XUv$5s+Gj`RSI)Q!y?06G=HiogZtwaOp?vy;Qupa~ z_nrUl*ec(?UvPg8FZ&}MIg1~ASKR!Qcln=8jqZo?)-R{Uu9g~`H=E?fzF9lVeOqhO zmRoBd-?``4DUwAM?8-m<*Zi}y)O?#bu7pZ0$JY4Yaso6Ei?Z{E$DB|njHAub{U+HD3Z!uQ#uc@R0f zuD{8p!TfPQBBT9hIFR|D;ZXmNvi$!43@?`cF#pfM2rB#kPFU1nFa4hZR0Yu8!2g$_ zg#+Df{w#gqpX81YVRIK%n7w<&eS58Pv{lMxHxAD@+4w_&(*i0Prl_P}+qD0|($%{k zgtr_s{?8zq3vOmFzW1Nu#N$W&zh(9JfBE|`>ht>_nhxv_(pwo{|7YM#1*<_c#SgCs zZTtuXEl+}O?RahT;`90cSAMNO4LU+A{r4i+Zo6_Tn@h{5)$amdrV|YwiGA_hAq0L< zRyFv<0ht$E@SE`r3$h`~O#IZo@U2+i;!cHe8^&4Hs!{!zJ=<;D5u_ z_0H~O_J0PuYyTO(A6Wcm+WyaP^Zzr{aQ$)l(tpnWR`<80ALl8#(w07l#BEo%>{P1wcVvnAtOxHc<1Bx)f1CB6 zp~?Bj<8M3TJ9lkAF5h>5^Y-qV`bYP9Z)9Bl(e(Mj!+HcYAG&tQ2Rn z+hx~Xm2r+b-GzcB0{nH>Y>Lu@U_3@~8Z7cO6s3xmPDmtz(ss?Nh(- z#rpJYv+~|N<)%*&cOU7CtWaTCnQ-|*}Ho9`djKm4-! zbgtO$huXc?5toc}&A`w}v4=0sevhlb+!E{NGC*xF`-`UHXJY$Z%S}15A z+ke=;?ZJPBleQn+|8cGT&+wy*{V;#q50(n%gVyzLg!BJ190^*k%IQ`RHYIfO>-=(m z(|5;CzF7Op=h0ICo8AxKUQS!L+uGV}Yi;D``EPE1;HW!qC-bBK;m1e&e|Z0Ac&PC4 zednIS=e6x`_9y?ldga-^-+K3reE)^tve|F0y+7}5=iJ3F)|4H~tbZ6)V|G~h!+(Z@ z7P9q^fwn{B?4t`RCky zciRGk|1%ug_#^i}gUf%0gYrK#xBq8&1iH&B`NQpho&On{>_31_4xQo{Q+~w%r+`iI ze};tW`+o(#-l@mXsNMae^TX!@Vs;E4&%T}CxJNd#oUgZ9$LQ!XhkoHTe9qdeClneS zWi^hNRra{C|j&tv6<$Lg(K2@C;3pO)&GhXR>v+zghhf~iio^Sua-k8>S z#mMSq*^ks$9Ez2q=RRC0oRYg_?)}+M3ihv8&i;5;Eo!~0hTpHFkyXWRVPAp*>!Q9) z-v9r~zx8$8b%)y@{D~J${?E|5@IM2m_#c)33_LZ`AD;hnn4kWe|C?&>*X_6KukWAv z{69lR{-?Tl{nN+3-<|p+{&DypL8a4=)em*liDdm}kgOA_Xu4X*{UV-Ix3pg3k9zl! zs`i#;mvf$N`*-cbW#1JlRX)?%QYWPdo>eq--&UfvX8)e$52qh4od09{KMwovoZJ61 zJkqoWC8CG>AIASsPW;cX!gv3F20^biKVO~NvQN|G_PfVt({D!4i!T2ZHb*Cv&kJfL^o4@?u)vEgTI_8?}$u8SJzHf~` zkn}tt*6g&$=F95M-dF4Hb!Y3|2`kds?44U6s^Z#banIQJ_K*Aj8CceQh(9*_e%C&Q zYkR^U@Ma&0T7Pu6eRrJnmXGlbZ~r(i%rpuqo2!r zXX2hy60NF?l8@I`|DAvMee12(t!uybH|}9BPc=!3NbC09Rx(v0H8MhvYl0(7f#Zs> z>HD_+Hvck3HT$R6oRXJm+luyFYFe_`&q`yp>XXN(x6g^4{d#eEru>v!-~R|#M_t*U zzE}N$+f@EP!kaxWKE1Yn>)LA1`kS-FJ}iCn>9|N?Ok~Ey>_FS4Z?pxE-oESic1KF0 z2>*rrhIlUa1M{2YIqkUnB`Ov_lKK&Rc$%F2zq{9-ea`<{zu948?V^XjPy9BQ+gmm3 z>wktHVe!1~poNpLEE@Em;n3zExfAEN|Km9Sw<#A?GF>v-ZWNzA?LWh}S6TK&x$K+c zUjK`}{qJ6ReYNtU<^LI4(Xx5C@v@A1rmgb@e#|~pW!L**a`?xv&ToZlUT(Ygt?2Gc zxth8Z`K(sQikc&=mn)>Fq@)x(F5Le{!?t$S-tUM1GqmnXoqBKKiXZ9+#H?xrO)j4| zo1HW9uvE|H-J*;k44&!@tQE@-{%7dgzmfedckv@>`G$JI{|u@5ed4>ES*|92sN1+C zTYs6M3S+^x^@<%&1lx}*E%l9`nz!|5Is2mBx0C;0{rAo~@Tg6V@sAuodr+$V&%kQ- zpW%;d(La@o`&cV%UN|3}Wq&B2r#|bI!HZw%-+K4X-Ttlj&+a{|BWw=X+`OOswzKH$ zmIpFYoxZxiUVg4p{hXBV_B;D~Uda|n_+=e``hmSE`rpHV`j4VVE9}lcXz$DWlezpy z^+7qAojWg>rd>2OEPhzFCF0!?fpadtHX7Rnf0i4}w-Wj&6zQDj9$NG2TlB5}4BAWU zy|?U*dZ(_kwL0#;ZS3Bh*+1=$XS(|TXW%;iaQk23{|qek50>5k&(N{>pJ@N#{9o$- zIQ}yn1ZTmJKvq%vKicwb_rLi65&X~4G`s$vQvZ)Id*1yo=KoNJd78nIIO81T|6lv} zPGjYNhKCV<%>FYhs(;Y`N96W@hL0kcWw-_tmsUN7Rq`L!)JfN0vK?bZMNN9W zNT5rjkVs`izpc4BoZ zZ$LNKTrGwTtM3oB;6giKbpikX>%aNI=NPS?7xC}q;?weXO@BK+7x{*I!m0xq0`mVa z(9YfN8DFJ!D_+%NaXLQ+Bg=iDt`Kc(G29zPyg zpLd)8hdR%FK|8G(Th@1!&zDpGD0yYwpWo}-&Wqn#yJh*_`#Tr^XJ~1A`Q`nl=~_8D zKCU&Jx9yu6|3fRknXT}{{3Ad9Gi+#yseion^Zdj8x1K)?Z?H4_lkj)JSAD*k)E|>S z>L0xjnc4O{?#h?@+J|SEE}1SA{n9F@Y}*Eow?f{xXL@@-JFjeEd#$y>-4Uv7TfOi*heR=ZiV06 zs?udv8D_z9PWI8d{|x^)TR(O`et#stPk!^;^GEn^89%K4lf3uB*76^BKl~2)@qO)& z?jvhgN+xo^pLA-Q+FC?SWSLA zXwtT>``|Ma)}PA%QPtmU|6J>C+R=KqYUP+X*=pW?|<^$r&xc3 z??1zT2G4u@&G$ciWykZMVUhQv`acp!|1%u9Cf@m<;lZ+h+#luJ{y0B;|0DFC=4SD4 z#y2bP=XL&BZ)E0syTG{j<}tUzD*@~i>kmekdfHk4@&7w*f5PwhhWjG*ndg~aecxU$ zv`@Rj{g7~`W~F_b$o|&U|YwgyJKc9bC{cXk8N&G+5 zK(~qBj(&Xp*nftb-cBF*rhhblxcuk}wU2Mlw~9Rvxftvet5#bdc4@tsb+PW$yK`^d zaW4^EwpUQ)+;-N~zb*e6+Lu=tH`EFJm@ofL5EL-$#Xj1<-Ta~YXuZrn!9Vp^>ZEJD zn~%luT-^GX+h<#I_*SuSv0tkrwjPdAY}xIx+|>}$$y#_|IhwT6a8Cq=e{5L&+x(D z_3xJd4B7Ji@~rjgZ?hkMKYDxp+tc%9U+v?K7rhbrW4h<|59b!Idu6sc>a^&QT`?QZ zO!oL_bLkF?YVT=R^_S{zf|*9N88RDU)`UZ-~8+TTha-jXdR(!wvUbuv1!O7tnu8n3C}=KrY5Z?;r^ zXx@49{eK2wj-CG*j%O4+Td;C}XYu{@N455BuZsP2&OGaux@h`ojpwKSE;skJ3wxUD zUah@s()Go;tB=M1zy9~oNByQc#Xm)Vx7e}#2>zDvcga4vAJY%jDg2$Nwr%q9`GS8W z^?TGl)^*ygOjucS?c4WVF>dclOXe<}C?%$I;9zs(`KmVPN_K}C<> zALvgL;C;Jc0P5~@|$9kd-Cq<)pH}= zvuot8c}CrOwdU5Ahu3xEvrS8;gzk!To0GD2@6Wxn_P$^Iv^;$O*(Y`N>wT%Ck9+-({F>kLCsn;8cj2curre#Xvu+2@JNfR` zV~?Iy4i_Evr?sEc_;>uH^|$69-OGQdf4iUmL;d)1|Bn6g^=W6deSV}}uQS;7@!sl3 z%ny5=UdQfiK5F>#pW-ZAuXk+C?%vkjt!>uL%dNL`GVOcu`gQ&-;cqv8*#GvdQU2(D zuwUkn`H$&KE7HR!o${XDs~)zs>nZQnC+40TpSHO$?A^sY#WLtX!h`;FNOtMn&$%an z|8I-Ze}+fdo|pH|diiqwo~NgE*Rw2Hv}k9p$ke-?3*|g^Y_Gl@x9Va`zW62Iqo;z( zcTd@Jb^i1y{@#BY71s~j@qO6dus^4lIqrw-BmVwz3z|el`qwvYxVbU z-LiYWc&}0rTRhq@ck|I$Nn=k z?cb8#T_gDL#6L0V_r3qra)SdMAHJSlS?V2az1FvHRj%rGoy#7Rx2Q@?^L=igy8e%- z`9bH#57Uo4sK4Q+|4(g$P3V6HR^t!PJ8DeVzlgGLu~Vv$4%@S7-nHA&m-Ta0_FhUY z(b{hIvXtf9Tb-2lK$DJzu5+>zu?w+*~JCs|*WQ zEqlAj>uj8P@RT{TUYA^nk1w^Y6aFlH{6B-}KHL8c2c7Iw=ijpb)_V1i`Nyc&5B=Nn z>SSu{KJGuPYpQp%v~cO`GOPY0qTRm!KmL?@rd>LAC4G{u(L0qpdR*II@W18%o%rv} ze1ZQAsq6dd6#p|cmDPCtNPhgj_n-6!E0MS)rQtRi+k4JeE#j~57XbKe>6V)XE+qk^2hkct2~p54w9VOMH{NNXB<_adYD{PX&cbTC^55EWW+};mdp6%Ipu-JJ0_;0;)2yqbBT0 z^*eR$y=!Wvt9ss^-g|+4AD_*7_I=${jZdeH!Y`c;FF!Tw>3+YukSR-7O%5!(9Dk-? zswQOD)h+k?>Nx)P{b$Jj&(I$)_@5zV<(|wB(%zTr6r*Z9uIzCxU;3jt_~W9;*SFGU zKk}Zrd|pYd&C6cfm9sqF-h27@Mw45I^Y6%t{KHl1y>^N<`ahO`tbZKa|K{$8?cb*U z$X~r~|GGcnA6dlZcrI?c$?ZKQv+1-=BmF>*!mwL_dL9 zo#YEMp;z~}^zAj-XMAD((~IBlX4Y{;=v!z& z{^74KblweS=E$%c;38~|F(M6hqF)W(0$wLZAq;)m~}g8Da#Ab)uNXW;ky5c!{BmhYp&{|tp3`+v+j zH`iosZEo$a?OH+m^M97BTwU|?$^E#TRR>R+%1U1}y?kdSuhq+QOSWE~sCx1Er|pm2 zkJtC@lelOnQDgow{^(tIzrR!e*ng~kw7sRq_0fIFKY7<{m^YgIzR}+P?S8HP=Be+={|wc$VottW zGj07nt_#n6b%l${-psjkDR$A~caf3S>WjYnUp+-cgDLB*KhX`Vu0PBEGqkVxVf?RC zzw-3BM1YtNI)AK(i9Oa18I3 zXG=cD9(@b}`Tv(`XY>`?8GV&@Mqi_y z(bs8b^aX52%P%ypJ9&@ma-B)VWzQ>H?~9lpIIDdq{g`3aMqjSGcW>-bIJR1`=lI^G zpT*x52|QrRKD4erJ+>~M`A75dc~T#@AJ&(Qd|6hQWi@T~(yaBi-)86i__;J#XyNT0 zY&VXxNWC@fzjZ$H>VJmxYW~LbL)$*Q%FNGScIQX?+nLke`?sY{(@y4}#u0jF_1q^3 za&~#v{}}}8?gYKw$N4e8S^VLaTdB*|KHUF?UyZl+Xxp6MW|iNr)%jgHud>e5rsnaS zf9jzoOBSx((m(&m<@%bWzj|w`LgwD7{Pq6nhvoTyW_dr^(--ghck&;{hwG(k^tXI$ z+xTJfk@f3NCflxWpC7k#`~1M|J6M|Th1lf;t}{5d*iv5ajgZAz;ctak{?tEOFZg1f z?8o_s?0K^nNAJ6wV`Evq*7w`aI8BMlf_EM7{LB`g3fO0xRU7_o%D<~W&VE?^=yiqu zsd%23w`v*G`ZUaz`$FI;)%il4XaYnPt= zaWixOiIX4t-`rgCQnst@LOPWx>`WKd?9X!OIBK{|u6U?5>rT?lPM$RW`F) zXL?u>gQtih1+*4qES`1?_p z#sVfZ%>Vxq4)M!4#IO9DR>ujNW&Dx<>MJMzGu(Ul%GbYj ze$Q|5D;GD``o@Bb**@wnEe0h@5@QQW|`@)T)mrrt2F=Cy5r~0+a1;IewA@!-?DY!aoa1!8#a2Q zD3W=j6Le$Wvfl1jS?>kjZ-Cy7N-+8)_UY^=3c(o9sK= zf6vyByl;|YyXW3`Rw~Lv7 zmoszyn11lY(Ir0bRsN}5ig@Yxt>?|Z+m}op;z#dG{js&uUQ&2`RYlop|I1O9Y=-)`zxGR=;IItNlU}*rY@6<}R~{ zzsvSKHsz}456@mbzw;l@jvv3*{&;>=@JILAjfVGQE6M{eB#$9mTLlA4CdR4 zCa5udeeE?lV%Ch3H4p1|S}yq&|Db%+)*L!m1=$5Gjk7o7UGn<_Eg9% zVBLjNjH(iRt3eZLeTJZ=5$u=$sha)y&+vHaPwvP0w`PA!{yU@o!MgkM>-T5c-_n1O z{^oD$>OZVM>Sl%9Gyj;{);4Rl>$-~7XQRt@Pczi?w^BM-yuIqVr07TYgYy0HTg3bS zGi1NDdLH+8**@M1@yEe`GOoN$`LJyJZ-;En@<;p(^%>_y=kA;S;YFsq&pgg8i_^>V zS36qonIt;%-JvbhUY~56yyC&6A3LZ1()OSHXZGuqH?ud@?$~|tTVJ&Mw7d6BRRjN- zH&qu~+&j5#|5C*dryp(;`Ok2O@j;vXH}HhL*3{>qY1Ut>q^VejI#om-(CckB`Y8k3YVDM8D&o{g2Nd)er1?+TK}HyfRD5 z^k2Wwk7Lo=s+ZMfu2uMLGfQvg(>v}`=fu>U72UMsjDPF?Dc<(c{#gAF^}5;1>aO44 z!hfW{f1dh>_FjGa41eaVduCVGzuDq1;<~W1KXXOKr4!eF$!xoxTP`D26L?VCU4E8w zvDE398FNCW$!(3Q7q!@)et+V*lX;gsi%mc5bj^8}^=NUJ$9y0h|vcDxng-{ zdZuuX;7=cweA9ph=N{~^*!^fd_aDuxb)ps1kIobRaQ*P_zh;jQf0t^Txia%x*d=$H z+`RK5AABVgr!=iwx12@5as9L157`gh7q4*srt`P?59dSwuK8QS`*iCJ|DBkpx$Pr= z`?^2bt70P0muEjbt5s|LTkT|;;L9TC=cjWP9ZfWLSNy!^x%KOB(%%@a*s;Ho|KKOj z`JdrX`;XP;N7wSnv#3a&y)w^!@5hu`Ve59eC_VPC?moSGrmmd)1ZjqgYgit*%01${ zF^fxL)|ce}40^Y=hp$tFR_dcdVGyKTcV#Y+lr`xl&;Y`so0lvAb1 zI9{{w}elb^;Yb5SLXbk@gje!f6M&g zz5GXe<)567)8AbF(Cm9X@7iIP9Uoo)O<(G|CT0VRbJlmArc91hMPZdEg=eeo-f!3^ zax>y?R{1=hAJv)54hqi8e0n)|lE$Kdh~1VktPDb)5BRcwmi*9OQ6c{=>B4`8G+Xa~ zT-ur056&}Z*>CKf-ehAd-t$PsY|-|heM=u+iJbE8>hevKoq8e}(&xYF>sptTV7Kn$ zzx9u9?tjqj{aYe?;*ZZH2zuAAN>V9vHqS)+8y=A|g-KKrt_FkiB$@F0N<*&Bg zPv=W3>;2~=;GY^cCF^xM+ok={UR&bttoVHDw&uF-SrGv%&ssRXmQ=YNb??<=KikVw z{amZ#0`KlLmA@h1HqZWV?>?gn`}iN-AKH)I-)w(0zkQd_X2(_8_oS|vto8T1yrk3o z)a1L-n~w^+?Kfsi2q`Mij(@1%X`k}G|DWjWWB(Z#x4ysm{Be5Iq*vZz*KAZvm%SF# zE}C(l|CX-L#7^;|r^XW}PCQ$0y?@KO%^!Y$n>za+m+HslZw9{VkG8+r{-1$s^24;= zN59uI-|n7kenfwH#k6M@B z@78|?nfn zdi13X_UNl3MDJYmxbgog3GEh({2yUxi!(4*OuWljz$Xo=nNXH`xT7!g*gZ>wxeaq= zP9WyGk#*0y7#{9NcRZFQ9iWz#{r_u2!9Ra??Ay9+)5rdfoHuJ9Y}wp?to);^h=-t( z>-9ZaOso1B7#J8{g6>@v;ViF__;BG^{iEgE|1(^QUiGT9|EJuZ(Ekj{#}@u)SpJ`Z zJKXel9nT-B4>!))KUxM+^kq~1_Zd}_gCC~~w*4Mqi|z(U)jv z^kv!^eT8;LU!{`K>|gh~iiAIYzM%g9wSOP%?tfJO*J{uFpW&f%;eUn`A+`G-`Tymr zWB<=^sO|B8h6$^8ENZZ*-~Pw*Kf^-*1OFL*1l+0rBr2c(WAc9nr}>Tl89prB@t@&| z);o;_eDZJqGYHkc2>xLIPsCjQPpkXwe=7eOE-Zgg|3@Rw{wLS&E)C{J@&62h$2>kP zZ({#@MqK_+YrM=4=5Jmf&$roGX6~2!Q~%Nak^b@f;+bMr^JYG@_80%L=wObGbat(Z zZ_&2E%v-Xvy_MJQF|7RFUu<=()uqAu-Cu?UJs-QyS$WURTmR$5e+FLty|dpPx%&9j zx62>?ZL`>q?)HR=Mb{tscl9`Xk5~iX!%ZG9A=GP(t?EK&UGj!U2(flF*pMgud|DV%hw2+NGD#Cd9FIrTs z_+5Ww{x6X~{QntRd5`~RPz*10WhnU1fDu>dksf*nbf@_Rvb{-uYG_Ayav-|T*FEZD zc=(T)RBHeK;sE&Iv-y!|B_1Y<0Cx21zYNcqeOULEsum0hcuCH zFVD3948OcfHv3*__Pup;3g-X6^j95S_(0`Td-xR2PVh}K z7g)&>VgG-bw#Hwft?^fBYy36Z8h@R(#$O=Uc=oT|kQziJ(3PS7|3w^kjZ<(3`JeAU zHveZ(O^^D|u;Je7~cNR@KX0|RsPZWKeer{|83XZ|7H8Z zt(Z>-h0v2W@x&F52*1*S0m zzxvk&JOI7w-qA0MlYWK2T0ScTSs;Mb7d#lf_D#H(nf_7+ypr|*uTj^y>(n*w0(FhM zNL}MDQP;T3gp6bV8tf_({?vU5C^>_0{r@XCk~EpOQ~&vak(@t&dnmn6xS}Fm{$rry ze}*O3O<&ZD)jwJDI{ur?w*L&5?z3H=_wV+9hEJZLTR)R|8hjRu5-dXY|1bU}N7p5?bX_J(*A=pK PT_sD`H56U;|8D{S-S?OA literal 0 HcmV?d00001 diff --git a/cg/freecad/Frames/newDatumCmd.py b/cg/freecad/Frames/newDatumCmd.py new file mode 100644 index 0000000..d1f2930 --- /dev/null +++ b/cg/freecad/Frames/newDatumCmd.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 +# coding: utf-8 +# +# LGPL +# Copyright HUBERT Zoltán +# +# newDatumCmd.py + + +import os + +from PySide import QtGui, QtCore +import FreeCADGui as Gui +import FreeCAD as App +from FreeCAD import Console as FCC + +import Asm4_libs as Asm4 + + + + +""" + +-----------------------------------------------+ + | a class to create all Datum objects | + +-----------------------------------------------+ +""" +class newDatum: + "My tool object" + def __init__(self, datumName): + self.datumName = datumName + # recognised containers (not the same as Asm4.containerTypes !) + self.containers = [ 'App::Part', 'PartDesign::Body', 'App::DocumentObjectGroup'] + if self.datumName == 'Point': + self.datumType = 'PartDesign::Point' + self.menutext = "New Point" + self.tooltip = "Create a new Datum Point in a Part" + self.icon = os.path.join( Asm4.iconPath , 'Asm4_Point.svg') + self.datumColor = (0.00,0.00,0.00) + self.datumAlpha = [] + elif self.datumName == 'Axis': + self.datumType = 'PartDesign::Line' + self.menutext = "New Axis" + self.tooltip = "Create a new Datum Axis in a Part" + self.icon = os.path.join( Asm4.iconPath , 'Asm4_Axis.svg') + self.datumColor = (0.00,0.00,0.50) + self.datumAlpha = [] + elif self.datumName == 'Plane': + self.datumType = 'PartDesign::Plane' + self.menutext = "New Plane" + self.tooltip = "Create a new Datum Plane in a Part" + self.icon = os.path.join( Asm4.iconPath , 'Asm4_Plane.svg') + self.datumColor = (0.50,0.50,0.50) + self.datumAlpha = 80 + elif self.datumName == 'LCS': + self.datumType = 'PartDesign::CoordinateSystem' + self.menutext = "New Coordinate System" + self.tooltip = "Create a new Coordinate System in a Part" + self.icon = os.path.join( Asm4.iconPath , 'Asm4_CoordinateSystem.svg') + self.datumColor = [] + self.datumAlpha = [] + elif self.datumName == 'Sketch': + self.datumType = 'Sketcher::SketchObject' + self.menutext = "New Sketch" + self.tooltip = "Create a new Sketch in a Part" + self.icon = os.path.join( Asm4.iconPath , 'Asm4_Sketch.svg') + self.datumColor = [] + self.datumAlpha = [] + + + def GetResources(self): + return {"MenuText": self.menutext, + "ToolTip": self.tooltip, + "Pixmap" : self.icon } + + + def IsActive(self): + if App.ActiveDocument: + # is something correct selected ? + if self.checkSelection(): + return(True) + return(False) + + + def checkSelection(self): + # if something is selected ... + if Gui.Selection.getSelection(): + selectedObj = Gui.Selection.getSelection()[0] + # ... and it's an App::Part or an datum object + selType = selectedObj.TypeId + if selType in self.containers or selType in Asm4.datumTypes or selType=='Sketcher::SketchObject': + return(selectedObj) + # or of nothing is selected ... + elif Asm4.getAssembly(): + # ... but there is as assembly: + return Asm4.getAssembly() + # if we're here it's because we didn't find a good reason to not be here + return None + + + + """ + +-----------------------------------------------+ + | the real stuff | + +-----------------------------------------------+ + """ + def Activated(self): + # check that we have somewhere to put our stuff + selectedObj = self.checkSelection() + # default name increments the datum type's end numeral + proposedName = Asm4.nextInstance( self.datumName, startAtOne=True ) + + parentContainer = None + # check whether we have selected a container + if selectedObj.TypeId in self.containers: + parentContainer = selectedObj + # if a datum object is selected + elif selectedObj.TypeId in Asm4.datumTypes or selectedObj.TypeId=='Sketcher::SketchObject': + # see whether it's in a container + parent = selectedObj.getParentGeoFeatureGroup() + if parent.TypeId in self.containers: + parentContainer = parent + # if there is an assembly + elif Asm4.getAssembly(): + parentContainer = Asm4.getAssembly() + # something went wrong + else: + Asm4.warningBox("I can't create a "+self.datumType+" with the current selections") + + # check whether there is already a similar datum, and increment the instance number + # instanceNum = 1 + #while App.ActiveDocument.getObject( self.datumName+'_'+str(instanceNum) ): + # instanceNum += 1 + #datumName = self.datumName+'_'+str(instanceNum) + if parentContainer: + # input dialog to ask the user the name of the Sketch: + #proposedName = Asm4.nextInstance( self.datumName + '_' + selectedObj.Label, startAtOne=True ) + text,ok = QtGui.QInputDialog.getText(None,'Create new '+self.datumName, + 'Enter '+self.datumName+' name :'+' '*40, text = proposedName) + if ok and text: + # App.activeDocument().getObject('Model').newObject( 'Sketcher::SketchObject', text ) + createdDatum = App.ActiveDocument.addObject( self.datumType, text ) + parentContainer.addObject( createdDatum ) + createdDatum.Label = text + # automatic resizing of datum Plane sucks, so we set it to manual + if self.datumType=='PartDesign::Plane': + createdDatum.ResizeMode = 'Manual' + createdDatum.Length = 100 + createdDatum.Width = 100 + elif self.datumType=='PartDesign::Line': + createdDatum.ResizeMode = 'Manual' + createdDatum.Length = 200 + # if color or transparency is specified for this datum type + if self.datumColor: + Gui.ActiveDocument.getObject(createdDatum.Name).ShapeColor = self.datumColor + if self.datumAlpha: + Gui.ActiveDocument.getObject(createdDatum.Name).Transparency = self.datumAlpha + # highlight the created datum object + Gui.Selection.clearSelection() + Gui.Selection.addSelection( App.ActiveDocument.Name, parentContainer.Name, createdDatum.Name+'.' ) + Gui.runCommand('Part_EditAttachment') + + + +""" + +-----------------------------------------------+ + | a class to create an LCS on a hole | + +-----------------------------------------------+ +""" +class newHole: + def GetResources(self): + return {"MenuText": "New Hole Axis", + "ToolTip": "Create a Datum Axis attached to a hole", + "Pixmap" : os.path.join( Asm4.iconPath , 'Asm4_Hole.svg') + } + + def IsActive(self): + selection = self.getSelectedEdges() + if selection is None: + return False + else: + return True + + + """ + +-----------------------------------------------+ + | the real stuff | + +-----------------------------------------------+ + """ + def getSelectedEdges(self): + # check that we have selected only circular edges + selection = None + parent = None + edges = [] + # 1 selection means a single parent + if App.ActiveDocument and len(Gui.Selection.getSelection()) == 1: + parent = Gui.Selection.getSelection()[0] + # parse all sub-elemets of the selection + for i in range(len(Gui.Selection.getSelectionEx()[0].SubObjects)): + edgeObj = Gui.Selection.getSelectionEx()[0].SubObjects[i] + edgeName = Gui.Selection.getSelectionEx()[0].SubElementNames[i] + # if the edge is circular + if Asm4.isCircle(edgeObj): + edges.append( [edgeObj,edgeName] ) + # if we found circular edges + if len(edges) > 0: + selection = ( parent, edges ) + return selection + + + def Activated(self): + ( selectedObj, edges ) = self.getSelectedEdges() + for i in range(len(edges)): + edgeObj = edges[i][0] + edgeName = edges[i][1] + parentPart = selectedObj.getParentGeoFeatureGroup() + # we can create a datum only in a container + if parentPart: + parentDoc = parentPart.Document + # if the solid having the edge is indeed in an App::Part + if parentPart and (parentPart.TypeId=='App::Part' or parentPart.TypeId=='PartDesign::Body'): + # check whether there is already a similar datum, and increment the instance number + instanceNum = 1 + while parentDoc.getObject( 'HoleAxis_'+str(instanceNum) ): + instanceNum += 1 + axis = parentPart.newObject('PartDesign::Line','HoleAxis_'+str(instanceNum)) + axis.Support = [( selectedObj, (edgeName,) )] + axis.MapMode = 'AxisOfCurvature' + axis.MapReversed = False + axis.ResizeMode = 'Manual' + axis.Length = edgeObj.BoundBox.DiagonalLength + axis.ViewObject.ShapeColor = (0.0,0.0,1.0) + axis.ViewObject.Transparency = 50 + axis.recompute() + parentPart.recompute() + # + else: + FCC.PrintMessage('Datum objects can only be created inside Part or Body containers') + + + +""" + +-----------------------------------------------+ + | add the commands to the workbench | + +-----------------------------------------------+ +""" +Gui.addCommand( 'Asm4_newPoint', newDatum('Point') ) +Gui.addCommand( 'Asm4_newAxis', newDatum('Axis') ) +Gui.addCommand( 'Asm4_newPlane', newDatum('Plane') ) +Gui.addCommand( 'Asm4_newLCS', newDatum('LCS') ) +Gui.addCommand( 'Asm4_newSketch',newDatum('Sketch')) +Gui.addCommand( 'Asm4_newHole', newHole() ) + +# defines the drop-down button for Datum objects +createDatumList = [ 'Asm4_newLCS', + 'Asm4_newPlane', + 'Asm4_newAxis', + 'Asm4_newPoint', + 'Asm4_newHole' ] +Gui.addCommand( 'Asm4_createDatum', Asm4.dropDownCmd( createDatumList, 'Create Datum Object')) diff --git a/cg/freecad/Frames/Модуль технологической подготовки.md b/cg/freecad/Frames/Модуль технологической подготовки.md new file mode 100644 index 0000000..4ae744f --- /dev/null +++ b/cg/freecad/Frames/Модуль технологической подготовки.md @@ -0,0 +1,27 @@ + + +# Модуль технологической подготовки + +На вход: - step-модель описываемого оборудования. + +1. Описание имеющихся сущностей: + - Мы можем создать описание действий со станком и его состояниями для связи с окружающим миром нужен некий обобщенный объект + его свойством будет возможность воздействовать на состояния, но сам он будет любым. Думаю, нужно описать сами рычаги, а не того, кто будет их нажимать. + + - Описать объекты можно созданием зон вокруг них и/или созданием призрака геометрии оборудования (может быть сложно и избыточно). + - Для этого возможно создать параллелепипед/цилиндр, сопоставимый по габаритам с рассматриваемой зоной. С помощью инструментов верстака Part создается объект, имеющий желаемое название (Станок, рабочая зона, etc). Эта зона с помощью отношений parent привязана к геометрии станка. + +2. Описание действий + - Создается папка actions, в которую сохраняются создаваемые действия + - Интерфейс создания действий аналогичен интерфейсу задания других сущностей. Через него мы задаем ссылки на существующие действия и элементы и указываем их тип: триггеры, рабочие органы, конечные состояния, начальные состояния. Указываем их статус (выбор "да/нет") + - Указываем ссылки на привязываемую к этим действиям геометрию. (катушки, статоры, расходники и тд) Для этого их геометрия должна быть импортирована в модель. Ссылка будет указывать на конкретный импортированный файл. Если существует идентификатор детали, можно ссылаться на него. + +3. Задание состояний и переменных + - Переменные задаются средствами параметризации FreeCAD, инструменты для этого можно взять из ASM4, используя функционал переменных (Variables) и панели конфигураций. + - Для состояний переменных аналогично создается (в ASM4 уже имеется при создании модели) отдельная директория. + +4. Результатом описания будет модель, имеющая дерево объектов, в свойствах которых мы имеем всю необходимую информацию. Геометрические характеристики мы сохраняем как json и отправляем в среды, работающие с геометрией и физикой. Действия и геометрия подставляются в шаблон pddl в соответствующие абзацы. + +Пример размеченной модели: + +![Разметка](img/qXX7sBMbsvA.jpg) \ No newline at end of file -- 2.49.0 From b5c5ec0bd6fb6eb5c5fd5ea400f3562f4d52862e Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Thu, 27 Apr 2023 00:21:45 +0300 Subject: [PATCH 02/27] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20=D0=BE=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BE=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Модуль технологической подготовки.md | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/cg/freecad/Frames/Модуль технологической подготовки.md b/cg/freecad/Frames/Модуль технологической подготовки.md index 4ae744f..191556c 100644 --- a/cg/freecad/Frames/Модуль технологической подготовки.md +++ b/cg/freecad/Frames/Модуль технологической подготовки.md @@ -22,6 +22,39 @@ 4. Результатом описания будет модель, имеющая дерево объектов, в свойствах которых мы имеем всю необходимую информацию. Геометрические характеристики мы сохраняем как json и отправляем в среды, работающие с геометрией и физикой. Действия и геометрия подставляются в шаблон pddl в соответствующие абзацы. +## Пример описания объекта + +Action - "Заправка 3д-принтера пластиком" + + - |- Объекты: + - - 3d-принтер [printer_id] /прямоугольная зона по габаритам принтера. Зона привязана к геометрии оборудования + - Workzone [printer_id] / прямоугольная зона. Указание на объект workzone, который содержит в себе габариты и позиционирование рабочей зоны относительно 3d-принтера. + - Wirenest [printer_id] /цилиндрическая зона. Указание на объект wirenest (цилиндр), хранящий информацию об ориентации и положении гнезда для катушки с пластиком + - Filament [filament_id] /катушка с пластиком требуемой модели, формы и габаритов. + - Observer [observer_id] / некая сущность(манипулятор, человек, камера), к которой обращается станок, чтобы с ним провели внешние манипуляции + - |- Длительность действия, с + + + - |- Стартовые состояния: + - Пластика достаточно (нет) + - Наблюдатель свободен (да) + - |- Во время действия: + - Наблюдатель[observer_id] свободен (нет) + - Катушка пластика установлена (нет) + - |- После окончания: + - Катушка пластика установлена (да) + - Наблюдатель [observer_id] свободен (да) + - Пластика достаточно (да) + + +--В раздел Variables мы можем (должны ли?) полуавтоматически/автоматически указать подобные состояния, привязанные к значениям да/нет.-- (Указывать стартовые значения по умолчанию?) + + +Указанные отдельно состояния пригодились бы, чтобы ссылаться на них при задавании действий, поскольку действия сообщаются между собой не напрямую, а через выполнение определенного набора состояний. + + + + Пример размеченной модели: ![Разметка](img/qXX7sBMbsvA.jpg) \ No newline at end of file -- 2.49.0 From 29cad491f5e89d52bfdddad13ae0a6f8267632c8 Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Tue, 23 May 2023 15:48:20 +0300 Subject: [PATCH 03/27] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD?= =?UTF-8?q?=D0=B0=D0=BB=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B0=20=D1=81=D0=BF=D0=B5=D1=86=D0=B8=D1=84=D0=B8=D0=BA?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cg/freecad/Frames/AuxObjCreation.py | 286 ++++++++++++++++++ cg/freecad/Frames/BoMList.py | 110 +++++++ cg/freecad/Frames/DatumCommand.py | 49 +++ cg/freecad/Frames/Frames.py | 47 +-- cg/freecad/Frames/InitGui.py | 5 +- cg/freecad/Frames/Sheet_addition_test.py | 60 ++++ cg/freecad/Frames/UI/icons/BoMList.svg | 72 +++++ cg/freecad/Frames/UI/icons/auxDatum.svg | 240 +++++++++++++++ cg/freecad/Frames/box.py | 0 cg/freecad/Frames/newLabel.py | 17 ++ cg/freecad/Frames/testSpread2.py | 77 +++++ .../Frames/usecases/asm4parser_usecase.py | 53 ++++ 12 files changed, 998 insertions(+), 18 deletions(-) create mode 100644 cg/freecad/Frames/AuxObjCreation.py create mode 100644 cg/freecad/Frames/BoMList.py create mode 100644 cg/freecad/Frames/DatumCommand.py create mode 100644 cg/freecad/Frames/Sheet_addition_test.py create mode 100644 cg/freecad/Frames/UI/icons/BoMList.svg create mode 100644 cg/freecad/Frames/UI/icons/auxDatum.svg create mode 100644 cg/freecad/Frames/box.py create mode 100644 cg/freecad/Frames/newLabel.py create mode 100644 cg/freecad/Frames/testSpread2.py create mode 100644 cg/freecad/Frames/usecases/asm4parser_usecase.py diff --git a/cg/freecad/Frames/AuxObjCreation.py b/cg/freecad/Frames/AuxObjCreation.py new file mode 100644 index 0000000..4db00e4 --- /dev/null +++ b/cg/freecad/Frames/AuxObjCreation.py @@ -0,0 +1,286 @@ + + + + " + #Создаем объект, к которому привязываем вспомогательную информацию: + 1. Выбираем элемент в модели: + 1.1. Поверхность - можем создать призматический объект. Соответствует точке захвата, рабочей зоне, опорной поверхности, поверхности базирования + 1.1.1 Выбираем тип объкта, который нам нужно построить. + 1.1.2 В зависимости от выбора, предлагается указать объект для ориентации осей. + 1.1.3 Указать размеры зоны построения. Для захвата соответствует габаритам пальца, для рабочей зоны - координатам. Полуавтоматическое (ручное???) создание эскиза? + 1.1.4 Для захвата выбираем вытягивание объекта на нужную длину до параллельной поверхности + 1.1.5 - Результат - построенный параллелограмм с размерами и привязкой к моделям. Его мы экспортируем в json через существующий функционал + + 1.2. Цилиндрическая поверхность - осесимметричный объект. Захват двухпальцевый, трехпальцевый, четырехпальцевый, цилиндрическая зона установки, отверстия сопряжения + 1.2.1 Выбираем тип объекта + 1.2.2. Выбираем ориентацию главной оси. //Потенциально, мы можем захватить цилиндрический объект в любой ориентации, нужно ли добавлять конкретное указание? + видимо, только если геометрия обязывает нас придерживаться ее (напр. не дает поставить 4 палец) + указание требуется, если есть фиксаторы, опорные площадки или что-то иное, что приводит к однозначному позиционированию + 1.2.3. Вытягиваем зону до привязки. Пальцы ставим как цилиндрический массив( нужно ли??) + 1.2.4 Результат - цилиндрическая зона с радиусом, ориентациями осей. + 2. К размеченным объектам необходимо привязать метаданные, содержащие связи с конкретными моделями. STEP-файл захвата, step-файл входной детали и/или выходной. + 2.1 Нужно ли привязывать захват? Да, но в некоем обобщенном виде. Позиция захвата - характеристика способа воздействия, а не объекта. + 2.2 Для станков входы и выходы необходимы. + 2.3 + " + + + #!/usr/bin/env python3 +# coding: utf-8 +# +# LGPL +# Copyright HUBERT Zoltán +# +# newDatumCmd.py + + +import os + +from PySide import QtGui, QtCore +import FreeCADGui as Gui +import FreeCAD as App +from FreeCAD import Console as FCC + +import Asm4_libs as Asm4 + + + + +""" + +-----------------------------------------------+ + | a class to create all Datum objects | + +-----------------------------------------------+ +""" +class newDatum: + "My tool object" + def __init__(self, datumName): + self.datumName = datumName + # recognised containers (not the same as Asm4.containerTypes !) + self.containers = [ 'App::Part', 'PartDesign::Body', 'App::DocumentObjectGroup'] + if self.datumName == 'Point': + self.datumType = 'PartDesign::Point' + self.menutext = "New Point" + self.tooltip = "Create a new Datum Point in a Part" + self.icon = os.path.join( Asm4.iconPath , 'Asm4_Point.svg') + self.datumColor = (0.00,0.00,0.00) + self.datumAlpha = [] + elif self.datumName == 'Axis': + self.datumType = 'PartDesign::Line' + self.menutext = "New Axis" + self.tooltip = "Create a new Datum Axis in a Part" + self.icon = os.path.join( Asm4.iconPath , 'Asm4_Axis.svg') + self.datumColor = (0.00,0.00,0.50) + self.datumAlpha = [] + elif self.datumName == 'Plane': + self.datumType = 'PartDesign::Plane' + self.menutext = "New Plane" + self.tooltip = "Create a new Datum Plane in a Part" + self.icon = os.path.join( Asm4.iconPath , 'Asm4_Plane.svg') + self.datumColor = (0.50,0.50,0.50) + self.datumAlpha = 80 + elif self.datumName == 'LCS': + self.datumType = 'PartDesign::CoordinateSystem' + self.menutext = "New Coordinate System" + self.tooltip = "Create a new Coordinate System in a Part" + self.icon = os.path.join( Asm4.iconPath , 'Asm4_CoordinateSystem.svg') + self.datumColor = [] + self.datumAlpha = [] + elif self.datumName == 'Sketch': + self.datumType = 'Sketcher::SketchObject' + self.menutext = "New Sketch" + self.tooltip = "Create a new Sketch in a Part" + self.icon = os.path.join( Asm4.iconPath , 'Asm4_Sketch.svg') + self.datumColor = [] + self.datumAlpha = [] + + + def GetResources(self): + return {"MenuText": self.menutext, + "ToolTip": self.tooltip, + "Pixmap" : self.icon } + + + def IsActive(self): + if App.ActiveDocument: + # is something correct selected ? + if self.checkSelection(): + return(True) + return(False) + + + def checkSelection(self): + # if something is selected ... + if Gui.Selection.getSelection(): + selectedObj = Gui.Selection.getSelection()[0] + # ... and it's an App::Part or an datum object + selType = selectedObj.TypeId + if selType in self.containers or selType in Asm4.datumTypes or selType=='Sketcher::SketchObject': + return(selectedObj) + # or of nothing is selected ... + elif Asm4.getAssembly(): + # ... but there is as assembly: + return Asm4.getAssembly() + # if we're here it's because we didn't find a good reason to not be here + return None + + + + """ + +-----------------------------------------------+ + | the real stuff | + +-----------------------------------------------+ + """ + def Activated(self): + # check that we have somewhere to put our stuff + selectedObj = self.checkSelection() + # default name increments the datum type's end numeral + proposedName = Asm4.nextInstance( self.datumName, startAtOne=True ) + + parentContainer = None + # check whether we have selected a container + if selectedObj.TypeId in self.containers: + parentContainer = selectedObj + # if a datum object is selected + elif selectedObj.TypeId in Asm4.datumTypes or selectedObj.TypeId=='Sketcher::SketchObject': + # see whether it's in a container + parent = selectedObj.getParentGeoFeatureGroup() + if parent.TypeId in self.containers: + parentContainer = parent + # if there is an assembly + elif Asm4.getAssembly(): + parentContainer = Asm4.getAssembly() + # something went wrong + else: + Asm4.warningBox("I can't create a "+self.datumType+" with the current selections") + + # check whether there is already a similar datum, and increment the instance number + # instanceNum = 1 + #while App.ActiveDocument.getObject( self.datumName+'_'+str(instanceNum) ): + # instanceNum += 1 + #datumName = self.datumName+'_'+str(instanceNum) + if parentContainer: + # input dialog to ask the user the name of the Sketch: + #proposedName = Asm4.nextInstance( self.datumName + '_' + selectedObj.Label, startAtOne=True ) + text,ok = QtGui.QInputDialog.getText(None,'Create new '+self.datumName, + 'Enter '+self.datumName+' name :'+' '*40, text = proposedName) + if ok and text: + # App.activeDocument().getObject('Model').newObject( 'Sketcher::SketchObject', text ) + createdDatum = App.ActiveDocument.addObject( self.datumType, text ) + parentContainer.addObject( createdDatum ) + createdDatum.Label = text + # automatic resizing of datum Plane sucks, so we set it to manual + if self.datumType=='PartDesign::Plane': + createdDatum.ResizeMode = 'Manual' + createdDatum.Length = 100 + createdDatum.Width = 100 + elif self.datumType=='PartDesign::Line': + createdDatum.ResizeMode = 'Manual' + createdDatum.Length = 200 + # if color or transparency is specified for this datum type + if self.datumColor: + Gui.ActiveDocument.getObject(createdDatum.Name).ShapeColor = self.datumColor + if self.datumAlpha: + Gui.ActiveDocument.getObject(createdDatum.Name).Transparency = self.datumAlpha + # highlight the created datum object + Gui.Selection.clearSelection() + Gui.Selection.addSelection( App.ActiveDocument.Name, parentContainer.Name, createdDatum.Name+'.' ) + Gui.runCommand('Part_EditAttachment') + + + +""" + +-----------------------------------------------+ + | a class to create an LCS on a hole | + +-----------------------------------------------+ +""" +class newHole: + def GetResources(self): + return {"MenuText": "New Hole Axis", + "ToolTip": "Create a Datum Axis attached to a hole", + "Pixmap" : os.path.join( Asm4.iconPath , 'Asm4_Hole.svg') + } + + def IsActive(self): + selection = self.getSelectedEdges() + if selection is None: + return False + else: + return True + + + """ + +-----------------------------------------------+ + | the real stuff | + +-----------------------------------------------+ + """ + def getSelectedEdges(self): + # check that we have selected only circular edges + selection = None + parent = None + edges = [] + # 1 selection means a single parent + if App.ActiveDocument and len(Gui.Selection.getSelection()) == 1: + parent = Gui.Selection.getSelection()[0] + # parse all sub-elemets of the selection + for i in range(len(Gui.Selection.getSelectionEx()[0].SubObjects)): + edgeObj = Gui.Selection.getSelectionEx()[0].SubObjects[i] + edgeName = Gui.Selection.getSelectionEx()[0].SubElementNames[i] + # if the edge is circular + if Asm4.isCircle(edgeObj): + edges.append( [edgeObj,edgeName] ) + # if we found circular edges + if len(edges) > 0: + selection = ( parent, edges ) + return selection + + + def Activated(self): + ( selectedObj, edges ) = self.getSelectedEdges() + for i in range(len(edges)): + edgeObj = edges[i][0] + edgeName = edges[i][1] + parentPart = selectedObj.getParentGeoFeatureGroup() + # we can create a datum only in a container + if parentPart: + parentDoc = parentPart.Document + # if the solid having the edge is indeed in an App::Part + if parentPart and (parentPart.TypeId=='App::Part' or parentPart.TypeId=='PartDesign::Body'): + # check whether there is already a similar datum, and increment the instance number + instanceNum = 1 + while parentDoc.getObject( 'HoleAxis_'+str(instanceNum) ): + instanceNum += 1 + axis = parentPart.newObject('PartDesign::Line','HoleAxis_'+str(instanceNum)) + axis.Support = [( selectedObj, (edgeName,) )] + axis.MapMode = 'AxisOfCurvature' + axis.MapReversed = False + axis.ResizeMode = 'Manual' + axis.Length = edgeObj.BoundBox.DiagonalLength + axis.ViewObject.ShapeColor = (0.0,0.0,1.0) + axis.ViewObject.Transparency = 50 + axis.recompute() + parentPart.recompute() + # + else: + FCC.PrintMessage('Datum objects can only be created inside Part or Body containers') + + + +""" + +-----------------------------------------------+ + | add the commands to the workbench | + +-----------------------------------------------+ +""" +Gui.addCommand( 'Asm4_newPoint', newDatum('Point') ) +Gui.addCommand( 'Asm4_newAxis', newDatum('Axis') ) +Gui.addCommand( 'Asm4_newPlane', newDatum('Plane') ) +Gui.addCommand( 'Asm4_newLCS', newDatum('LCS') ) +Gui.addCommand( 'Asm4_newSketch',newDatum('Sketch')) +Gui.addCommand( 'Asm4_newHole', newHole() ) + +# defines the drop-down button for Datum objects +createDatumList = [ 'Asm4_newLCS', + 'Asm4_newPlane', + 'Asm4_newAxis', + 'Asm4_newPoint', + 'Asm4_newHole' ] +Gui.addCommand( 'Asm4_createDatum', Asm4.dropDownCmd( createDatumList, 'Create Datum Object')) diff --git a/cg/freecad/Frames/BoMList.py b/cg/freecad/Frames/BoMList.py new file mode 100644 index 0000000..fef31eb --- /dev/null +++ b/cg/freecad/Frames/BoMList.py @@ -0,0 +1,110 @@ +from helper.is_solid import is_object_solid +import FreeCAD as App +import Spreadsheet + + +def createSpreadsheet(): + + if App.ActiveDocument.getObject("BoM_List") == None: + + sheet = App.activeDocument().addObject('Spreadsheet::Sheet', 'BoM_List') + sheet.set('A1', 'п.п.') + sheet.set('B1', 'Наименование детали') + sheet.set('C1', 'Количество') + else: + + sheet = App.ActiveDocument.getObject("BoM_List") + App.ActiveDocument.BoM_List.clear('A1:ZZ16384') + sheet.set('A1', 'п.п.') + sheet.set('B1', 'Наименование детали') + sheet.set('C1', 'Количество') + + return (sheet) + + +class SolidBodiesParcer: + _asmThere = [] + + def __init__(self) -> None: + if (self._asmThere.__len__() == 0): + + self.initParse() + pass + + def initParse(self): + for el in App.ActiveDocument.RootObjects: + if (is_object_solid(el) and hasattr(el, 'Group')): + self.getSubPartsLink(el.Group, el.Label) + + def getSubPartsLink(self, group, label): + groupLink = {label: []} + for el in group: + if (is_object_solid(el)): + groupLink[label].append( + {'label': el.Label, 'isGroup': hasattr(el, 'Group'), 'solid': el}) + + for el in groupLink[label]: + if ('isGroup' in el): + if (el['isGroup'] == False): + self._asmThere.append(el['solid'].Label) + if (el['isGroup']): + self.getSubPartsLink(el['solid'].Group, el['label']), + + return groupLink + + +def uniquePartsSort(labelParts): + + uniquePartsLabels = {} + + for el in labelParts: + for k in labelParts: + if (App.ActiveDocument.getObjectsByLabel(str(el))[0].Shape.isPartner(App.ActiveDocument.getObjectsByLabel(str(k))[0].Shape)): + + if uniquePartsLabels.get(el) == None: + uniquePartsLabels[el] = k + + sortedParts = {} + + for k, v in uniquePartsLabels.items(): + + if sortedParts.get(v) == None: + sortedParts[v] = [k] + else: + sortedParts[v].append(k) + + return sortedParts + + +def countForUniques(sortedParts): + countedParts = {} + for k in sortedParts: + countedParts[k] = len(sortedParts[k]) + return countedParts + + +def fillInBoMList(sheet, countedParts): + + a = 1 + + for label, count in countedParts.items(): + a += 1 + b = label + c = count + sheet.set('A' + str(a), str(a-1)) + sheet.set('B' + str(a), str(b)) + sheet.set('C' + str(a), str(c)) + + total_count = sum(countedParts.values()) + sheet.set('B'+str(a+1), 'Итого') + + sheet.set('C' + str(a+1), str(total_count)) + + +def run_BoM_list(): + createSpreadsheet() + sheet = App.ActiveDocument.getObject("BoM_List") + labelParts = SolidBodiesParcer()._asmThere + sortedParts = uniquePartsSort(labelParts) + countedParts = countForUniques(sortedParts) + fillInBoMList(sheet, countedParts) diff --git a/cg/freecad/Frames/DatumCommand.py b/cg/freecad/Frames/DatumCommand.py new file mode 100644 index 0000000..a7db4fd --- /dev/null +++ b/cg/freecad/Frames/DatumCommand.py @@ -0,0 +1,49 @@ +import FreeCAD +import FreeCADGui +from PySide import QtGui, QtCore + +class DatumTool: + """ + A tool for creating datums in existing models + """ + def __init__(self): + self.active = False + + def activate(self): + self.active = True + FreeCAD.Console.PrintMessage("Datum tool activatedn") + + def deactivate(self): + self.active = False + FreeCAD.Console.PrintMessage("Datum tool deactivatedn") + + def mousePressEvent(self, event): + if self.active: + # Create a datum at the position of the mouse click + pos = FreeCADGui.ActiveDocument.ActiveView.getCursorPos() + point = FreeCADGui.ActiveDocument.ActiveView.getPoint(pos) + datum = FreeCAD.ActiveDocument.addObject("Part::Datum", "Datum") + datum.Placement.Base = point + datum.ViewObject.ShapeColor = (0.0, 1.0, 0.0) # Set the color of the datum to green + FreeCAD.ActiveDocument.recompute() + +class DatumCommand: + """ + A command for activating and deactivating the datum tool + """ + def __init__(self): + self.tool = DatumTool() + + def Activated(self): + self.tool.activate() + FreeCADGui.ActiveDocument.ActiveView.addEventCallback("SoMouseButtonEvent", self.tool.mousePressEvent) + + def Deactivated(self): + self.tool.deactivate() + FreeCADGui.ActiveDocument.ActiveView.removeEventCallback("SoMouseButtonEvent", self.tool.mousePressEvent) + + def GetResources(self): + return {'Pixmap': 'path/to/icon.png', 'MenuText': 'Datum Tool', 'ToolTip': 'Creates datum elements in existing models'} + +# Add the command to the Draft Workbench +FreeCADGui.addCommand('DatumCommand', DatumCommand()) \ No newline at end of file diff --git a/cg/freecad/Frames/Frames.py b/cg/freecad/Frames/Frames.py index ea46662..1bc5544 100644 --- a/cg/freecad/Frames/Frames.py +++ b/cg/freecad/Frames/Frames.py @@ -11,9 +11,11 @@ # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . +from BoMList import run_BoM_list import FreeCAD import Tools -from scenarios.robossembler_freecad_export_scenario import RobossemblerFreeCadExportScenario +from usecases.asm4parser_usecase import Asm4StructureParseUseCase + if FreeCAD.GuiUp: import FreeCADGui @@ -297,6 +299,13 @@ def makeAllPartFrames(): def spawnFeatureFrameCreator(): ffpanel = FeatureFramePanel() FreeCADGui.Control.showDialog(ffpanel) + +def BoMGeneration(part): + + + obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython", + "FeatureFrame") + print(obj) ################################################################### @@ -306,22 +315,26 @@ uidir = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", __workbenchname__, "UI") icondir = os.path.join(uidir, "icons") -Tools.spawnClassCommand("FrameCommand", - makeFrame, - {"Pixmap": str(os.path.join(icondir, "frame.svg")), - "MenuText": "Make a free frame", - "ToolTip": "Make a freestanding reference frame."}) - -Tools.spawnClassCommand("ASM4StructureParsing", - RobossemblerFreeCadExportScenario().call, - {"Pixmap": str(os.path.join(icondir, "assembly4.svg")), - "MenuText": "Make a ASM4 parsing", - "ToolTip": "Make a ASM4 1"}) -Tools.spawnClassCommand("SelectedPartFrameCommand", - makeSelectedPartFrames, - {"Pixmap": str(os.path.join(icondir, "partframe.svg")), - "MenuText": "selected parts frames", - "ToolTip": "Make selected parts frames."}) +# Tools.spawnClassCommand("FrameCommand", +# makeFrame, +# {"Pixmap": str(os.path.join(icondir, "frame.svg")), +# "MenuText": "Make a free frame", +# "ToolTip": "Make a freestanding reference frame."}) +Tools.spawnClassCommand("BoMGeneration", + run_BoM_list, + {"Pixmap": str(os.path.join(icondir, "BoMList.svg")), + "MenuText": "Generate Bill of Materials", + "ToolTip": "Press the button to create big BoM"}) +# Tools.spawnClassCommand("ASM4StructureParsing", +# Asm4StructureParseUseCase().initParse, +# {"Pixmap": str(os.path.join(icondir, "assembly4.svg")), +# "MenuText": "Make a ASM4 parsing", +# "ToolTip": "Make a ASM4 1"}) +# Tools.spawnClassCommand("SelectedPartFrameCommand", +# makeSelectedPartFrames, +# {"Pixmap": str(os.path.join(icondir, "partframe.svg")), +# "MenuText": "selected parts frames", +# "ToolTip": "Make selected parts frames."}) Tools.spawnClassCommand("AllPartFramesCommand", makeAllPartFrames, diff --git a/cg/freecad/Frames/InitGui.py b/cg/freecad/Frames/InitGui.py index 536ec0a..4ed400b 100644 --- a/cg/freecad/Frames/InitGui.py +++ b/cg/freecad/Frames/InitGui.py @@ -36,10 +36,13 @@ class Frames(Workbench): """This function is executed when FreeCAD starts""" import Frames self.framecommands = [ + "BoMGeneration", "FrameCommand", + "SelectedPartFrameCommand", "AllPartFramesCommand", - "FeatureFrameCommand" + "FeatureFrameCommand", + ] self.toolcommands = [ "ExportPlacementAndPropertiesCommand", diff --git a/cg/freecad/Frames/Sheet_addition_test.py b/cg/freecad/Frames/Sheet_addition_test.py new file mode 100644 index 0000000..9b645b0 --- /dev/null +++ b/cg/freecad/Frames/Sheet_addition_test.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Sheet_addition_test.py +# +# Copyright 2015 Ulrich Brammer +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# + +theDoc = App.newDocument("SheetTest") +App.setActiveDocument("SheetTest") +#App.activeDocument("SheetTest") + + +p=App.ParamGet("User parameter:BaseApp/Preferences/General") +thePath = p.GetString('FileOpenSavePath') + + +mySheet = theDoc.addObject('Spreadsheet::Sheet','Spreadsheet') + +mySheet.set('A1', '1') +mySheet.set('A2', '2') +theDoc.recompute() +mySheet.set('A3', '=A1+A2') +mySheet.setPosition('A4') +#theDoc.saveAs("/home/ulrich/FreeCAD/Spreadsheet/Sheet_4.fcstd") +theDoc.saveAs(thePath + '/Sheet_5.fcstd') +mySheet.set('A4', '=A3') +theDoc.recompute() + +if mySheet.State == ['Invalid']: + print "Invalid Spreadsheet" +else: + print "No error found" + + + + +def main(): + + return 0 + +if __name__ == '__main__': + main() + diff --git a/cg/freecad/Frames/UI/icons/BoMList.svg b/cg/freecad/Frames/UI/icons/BoMList.svg new file mode 100644 index 0000000..caf5d4f --- /dev/null +++ b/cg/freecad/Frames/UI/icons/BoMList.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + ... + + + + + + Parts + + + + List + + + \ No newline at end of file diff --git a/cg/freecad/Frames/UI/icons/auxDatum.svg b/cg/freecad/Frames/UI/icons/auxDatum.svg new file mode 100644 index 0000000..c1ca8d8 --- /dev/null +++ b/cg/freecad/Frames/UI/icons/auxDatum.svg @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + [wmayer] + + + Sketcher_Sketch + 2011-10-10 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_Sketch.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cg/freecad/Frames/box.py b/cg/freecad/Frames/box.py new file mode 100644 index 0000000..e69de29 diff --git a/cg/freecad/Frames/newLabel.py b/cg/freecad/Frames/newLabel.py new file mode 100644 index 0000000..761ed81 --- /dev/null +++ b/cg/freecad/Frames/newLabel.py @@ -0,0 +1,17 @@ +import FreeCAD as App + + +def is_object_solid(obj): + """If obj is solid return True""" + if not isinstance(obj, FreeCAD.DocumentObject): + return False + + if not hasattr(obj, 'Shape'): + return False + + return obj.Shape.isClosed() + +def addProperty(): + for obj in App.ActiveDocument().Objects: + if is_object_solid(obj): + \ No newline at end of file diff --git a/cg/freecad/Frames/testSpread2.py b/cg/freecad/Frames/testSpread2.py new file mode 100644 index 0000000..a2953f0 --- /dev/null +++ b/cg/freecad/Frames/testSpread2.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# faculSpread.py +# +# Copyright 2015 ulrich1a +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# + + + +p=App.ParamGet("User parameter:BaseApp/Preferences/General") +thePath = p.GetString('FileOpenSavePath') + + +theDoc = App.newDocument("SheetTest2") +App.setActiveDocument("SheetTest2") + +mySheet = theDoc.addObject('Spreadsheet::Sheet','Spreadsheet') + + +col = 'A' +mySheet.setColumnWidth(col, 125) +theDoc.recompute() + +startRow = 3 + +for i in range(20): + print "i: ", i + if i == 0: + print "setting start" + mySheet.set(col+str(startRow+i), '1') + #App.activeDocument().recompute() + else: + mySheet.set(col + str(startRow+i), '='+col+str(startRow+i-1)+'*'+str(i)) + #App.activeDocument().recompute() + +#mySheet.show() +#App.activeDocument().recompute() + +mySheet.set('A1', 'This is the very long Titel, which needs more space!') +mySheet.set('A2', '=A1') + +# mySheet.setDisplayUnit('A10:A11', 'mm') + +theDoc.recompute() +mySheet.setAlias('A1', 'Title') +mySheet.set('D1', '=Title') + +theDoc.recompute() + +theDoc.saveAs(thePath + '/Sheet_6.fcstd') +mySheet.set('B4','=A4') +mySheet.set('E1', '=D1') +theDoc.recompute() +mySheet.setAlias('A2', 'huhu') +theDoc.recompute() + +if mySheet.State == ['Invalid']: + print "Invalid Spreadsheet" +else: + print "No error found" diff --git a/cg/freecad/Frames/usecases/asm4parser_usecase.py b/cg/freecad/Frames/usecases/asm4parser_usecase.py new file mode 100644 index 0000000..f732d1b --- /dev/null +++ b/cg/freecad/Frames/usecases/asm4parser_usecase.py @@ -0,0 +1,53 @@ +import FreeCAD as App + +class Asm4StructureParseUseCase: + _parts = [] + _label = [] + + def getSubPartsLabel(self, group): + groupLabel = [] + for el in group: + if str(el) == '': + groupLabel.append(el.Label) + return groupLabel + + def parseLabel(self, nextGroup, label, level=2, nextGroupParse=0): + if nextGroup.__len__() == nextGroupParse: + return + else: + groupParts = [] + + for el in nextGroup: + if str(el) == '': + groupParts.append(el) + + for el in groupParts: + if str(el) == '': + label.append({ + "level": level, + "attachedTo": el.AttachedTo.split('#'), + "label": el.Label, + "axis": self.getSubPartsLabel(el.Group) + }) + + def initParse(self): + + model = App.ActiveDocument.RootObjects[1] + self._label.append({ + "level": 1, + "attachedTo": "Parent Assembly", + "label": model.Label, + "axis": self.getSubPartsLabel(model.Group) + }) + for parent in model.Group: + if str(parent) == '': + self._label.append({ + "level": 1, + "attachedTo": parent.AttachedTo.split('#'), + "label": parent.Label, + "axis": self.getSubPartsLabel(parent.Group) + }) + print(self._label) + + + \ No newline at end of file -- 2.49.0 From 0c8767e3f5875e64d668f99061ab2ae57016b836 Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Wed, 31 May 2023 08:59:11 +0300 Subject: [PATCH 04/27] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=B0=D0=BB=D0=B3=D0=BE=D1=80=D0=B8=D1=82=D0=BC?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=BE=D0=B7=D0=B8=D1=86=D0=B8=D0=B9=20=D0=B7?= =?UTF-8?q?=D0=B0=D1=85=D0=B2=D0=B0=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cg/freecad/Frames/Tools.py | 4 + cg/freecad/Frames/normalEstimator | 0 cg/freecad/Frames/poseGenerator.py | 132 +++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 cg/freecad/Frames/normalEstimator create mode 100644 cg/freecad/Frames/poseGenerator.py diff --git a/cg/freecad/Frames/Tools.py b/cg/freecad/Frames/Tools.py index 484706d..d6ce47b 100644 --- a/cg/freecad/Frames/Tools.py +++ b/cg/freecad/Frames/Tools.py @@ -183,11 +183,15 @@ def getLocalPartProps(obj): "label": obj.Label, "parent_label": obj_parent_label, "placement": placement2pose(old_placement), + # "grip properties": { "grip open": obj.GripOpen, "grip depth": obj.GripDepth, "grip width": obj.GripWidth} # "boundingbox": boundingBox2list(obj.Shape.BoundBox), # "volume": obj.Shape.Volume*1e-9, # "centerofmass": vector2list(obj.Shape.CenterOfMass), # "principalproperties": principalProperties2dict(obj.Shape.PrincipalProperties) } + if obj.GripOpen != None: + partprops["grip properties"] = { "grip open": obj.GripOpen, "grip depth": obj.GripDepth, "grip width": obj.GripWidth} + # obj.Placement = old_placement return partprops diff --git a/cg/freecad/Frames/normalEstimator b/cg/freecad/Frames/normalEstimator new file mode 100644 index 0000000..e69de29 diff --git a/cg/freecad/Frames/poseGenerator.py b/cg/freecad/Frames/poseGenerator.py new file mode 100644 index 0000000..75eb2f2 --- /dev/null +++ b/cg/freecad/Frames/poseGenerator.py @@ -0,0 +1,132 @@ +import FreeCAD as App +import FreeCADGui as Gui + +# App.newDocument() +doc = App.ActiveDocument + + +print('задайте начальную точку захватной зоны. ') +print('Учтите, что ось X должна быть направлена вдоль направления раскрытия пальцев') +print('Ось Z должна быть направлена в противоположную сторону от направления кончика пальца захвата') +lcsname = input('Введите название начальной точки' + "\n") + +lcs = doc.getObject(lcsname) + +def poseGenerator(lcs): + + box = doc.addObject("Part::Box", "gripSpace") + box.Length = 62 #раскрытие + box.Width = 10 #ширина пальца + box.Height = 40 #глубина + + box.Placement = lcs.Placement + #box.Transparency = 80 #не работает, хз почему + + + + #есть смысл создавать привязку прямо здесь же. благодаря параметризации, при подстройке куба все точки сместятся как надо + gripPose = App.ActiveDocument.addObject('PartDesign::CoordinateSystem', 'GripPose') + gripPose.Support = box + gripPose.MapMode = 'ObjectXY' + gripPose.AttachmentOffset.Base = [box.Length/2, box.Width/2, 0] #здесь должна быть активная привязка, не просто значения координат + + gripPose.addProperty("App::PropertyFloat", "GripOpen") + gripPose.addProperty("App::PropertyFloat", "GripDepth") + gripPose.addProperty("App::PropertyFloat", "GripWidth") + gripPose.GripOpen = box.Length.Value + gripPose.GripWidth = box.Width.Value + gripPose.GripDepth = box.Height.Value + + + + + #нужно создавать эту хрень внутри Part, а не внутри главного документа. сбиваются привязки !!! + + + print('Установите захватную зону вручную, растянув обьект GripSpace') + doc.recompute() + + #вроде как работает + + +poseGenerator(lcs) + +# def collectAndExportProps(): + + + + +# This class logs any mouse button events. As the registered callback function fires twice for 'down' and +# 'up' events we need a boolean flag to handle this. + +# todo: +# добавить включатель-выключатель +# должно срабатывать на задание одной позиции +# научить считать нормаль поверхности в указанной точке +# добавить коррекцию позиции +# 1 выбираем первую грань +# 2 сохраняем позицию курсора +# 3 в данной позиции считаем нормаль поверхности, выравниваем вокруг нее ось x + +# 3 выбираем вторую грань +# расстояние до нее должно стать длиной параллелепипеда +# ширина задается вручную +# создаем ск, которая смещена на половину длины прямоугольника и на половину ширины +# полученная штука - центр расстояния между кончиками пальцев +# в эту штуку можно вставить параметризованный захват и проверить на коллизии + + +##### +#все хуйня +#давай по новой + +#создаем опорную точку в asm4 (взять оттуда генератор точек?) +#по ней строим от угла прямоугольник +#используя asm4, создаем ориентированный обьект и присваиваем ему свойства, равные размерам объекта + +# class ViewObserver: +# def __init__(self, view): +# self.view = view + + + + + +# def logPosition(self, info): +# # def turnOn(): +# # switch = 'Run' +# # while True: +# # switch = input('Tell me when to stop') +# # if switch != 'Run': +# # print('MacroStop') +# # break +# # return (switch) + + + +# down = (info["State"] == "DOWN") +# pos = info["Position"] +# if (down): +# App.Console.PrintMessage( +# "Clicked on position: ("+str(pos[0])+", "+str(pos[1])+")\n") +# pnt = self.view.getPoint(pos) +# App.Console.PrintMessage("World coordinates: " + str(pnt[0].x) + "\n") +# info = self.view.getObjectInfo(pos) +# print(info['x']) +# print(info['y']) +# print(info['z']) +# from random import randrange +# id = str('yo' + str(randrange(0, 10000))) +# # App.ActiveDocument.addObject("Part::Box",id) +# print(id) +# # App.ActiveDocument.getObject(id).Placement = App.Placement(App.Vector(info['x'],info['y'],info['z']),App.Rotation(App.Vector(0.00,0.00,1.00),0.00)) + +# # App.ActiveDocument.getObject(id).Width = '1.00 mm' +# # App.ActiveDocument.getObject(id).Length = '1.00 mm' +# # App.ActiveDocument.getObject(id).Height = '1.00 mm' +# # App.Console.PrintMessage("Object info: " + str(info) + "\n") + + + +# o = ViewObserver(v) +# c = v.addEventCallback("SoMouseButtonEvent", o.logPosition) -- 2.49.0 From c1e408774b6bfbe2c3686bc9d22380b7952a9c59 Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Wed, 31 May 2023 09:06:00 +0300 Subject: [PATCH 05/27] =?UTF-8?q?=D1=83=D0=B4=D0=B0=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BC=D1=83=D1=81=D0=BE=D1=80=20=D0=B8=D0=B7=20=D1=84=D0=B0?= =?UTF-8?q?=D0=B9=D0=BB=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cg/freecad/Frames/poseGenerator.py | 81 +----------------------------- 1 file changed, 1 insertion(+), 80 deletions(-) diff --git a/cg/freecad/Frames/poseGenerator.py b/cg/freecad/Frames/poseGenerator.py index 75eb2f2..ad51a2f 100644 --- a/cg/freecad/Frames/poseGenerator.py +++ b/cg/freecad/Frames/poseGenerator.py @@ -40,7 +40,7 @@ def poseGenerator(lcs): - #нужно создавать эту хрень внутри Part, а не внутри главного документа. сбиваются привязки !!! + #нужно создавать обьект внутри Part, а не внутри главного документа. сбиваются привязки !!! print('Установите захватную зону вручную, растянув обьект GripSpace') @@ -51,82 +51,3 @@ def poseGenerator(lcs): poseGenerator(lcs) -# def collectAndExportProps(): - - - - -# This class logs any mouse button events. As the registered callback function fires twice for 'down' and -# 'up' events we need a boolean flag to handle this. - -# todo: -# добавить включатель-выключатель -# должно срабатывать на задание одной позиции -# научить считать нормаль поверхности в указанной точке -# добавить коррекцию позиции -# 1 выбираем первую грань -# 2 сохраняем позицию курсора -# 3 в данной позиции считаем нормаль поверхности, выравниваем вокруг нее ось x - -# 3 выбираем вторую грань -# расстояние до нее должно стать длиной параллелепипеда -# ширина задается вручную -# создаем ск, которая смещена на половину длины прямоугольника и на половину ширины -# полученная штука - центр расстояния между кончиками пальцев -# в эту штуку можно вставить параметризованный захват и проверить на коллизии - - -##### -#все хуйня -#давай по новой - -#создаем опорную точку в asm4 (взять оттуда генератор точек?) -#по ней строим от угла прямоугольник -#используя asm4, создаем ориентированный обьект и присваиваем ему свойства, равные размерам объекта - -# class ViewObserver: -# def __init__(self, view): -# self.view = view - - - - - -# def logPosition(self, info): -# # def turnOn(): -# # switch = 'Run' -# # while True: -# # switch = input('Tell me when to stop') -# # if switch != 'Run': -# # print('MacroStop') -# # break -# # return (switch) - - - -# down = (info["State"] == "DOWN") -# pos = info["Position"] -# if (down): -# App.Console.PrintMessage( -# "Clicked on position: ("+str(pos[0])+", "+str(pos[1])+")\n") -# pnt = self.view.getPoint(pos) -# App.Console.PrintMessage("World coordinates: " + str(pnt[0].x) + "\n") -# info = self.view.getObjectInfo(pos) -# print(info['x']) -# print(info['y']) -# print(info['z']) -# from random import randrange -# id = str('yo' + str(randrange(0, 10000))) -# # App.ActiveDocument.addObject("Part::Box",id) -# print(id) -# # App.ActiveDocument.getObject(id).Placement = App.Placement(App.Vector(info['x'],info['y'],info['z']),App.Rotation(App.Vector(0.00,0.00,1.00),0.00)) - -# # App.ActiveDocument.getObject(id).Width = '1.00 mm' -# # App.ActiveDocument.getObject(id).Length = '1.00 mm' -# # App.ActiveDocument.getObject(id).Height = '1.00 mm' -# # App.Console.PrintMessage("Object info: " + str(info) + "\n") - - - -# o = ViewObserver(v) -# c = v.addEventCallback("SoMouseButtonEvent", o.logPosition) -- 2.49.0 From 4ffa7f7c08d87d6ee573c241ff09a39acd3c249b Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Fri, 2 Jun 2023 13:06:25 +0300 Subject: [PATCH 06/27] Changes to be committed: modified: cg/freecad/Frames/Tools.py new file: cg/freecad/Frames/markupEntities.py --- cg/freecad/Frames/Tools.py | 10 +- cg/freecad/Frames/markupEntities.py | 137 ++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 cg/freecad/Frames/markupEntities.py diff --git a/cg/freecad/Frames/Tools.py b/cg/freecad/Frames/Tools.py index d6ce47b..0a420e2 100644 --- a/cg/freecad/Frames/Tools.py +++ b/cg/freecad/Frames/Tools.py @@ -189,9 +189,17 @@ def getLocalPartProps(obj): # "centerofmass": vector2list(obj.Shape.CenterOfMass), # "principalproperties": principalProperties2dict(obj.Shape.PrincipalProperties) } - if obj.GripOpen != None: + + if obj.Type == 'grip': partprops["grip properties"] = { "grip open": obj.GripOpen, "grip depth": obj.GripDepth, "grip width": obj.GripWidth} + elif obj.Type == 'area': + partprops["area dim"] = { } #свойства, описывающие размер + + elif obj.Type == 'vol': + partprops["vol dim"] = { } #свойства, описывающие размер области + + # obj.Placement = old_placement return partprops diff --git a/cg/freecad/Frames/markupEntities.py b/cg/freecad/Frames/markupEntities.py new file mode 100644 index 0000000..3370d15 --- /dev/null +++ b/cg/freecad/Frames/markupEntities.py @@ -0,0 +1,137 @@ +import FreeCAD as App +import FreeCADGui as Gui + +# App.newDocument() +doc = App.ActiveDocument + + +### +#прога для общей генерации всяких разметок +#сюда будем добавлять функции для генерации точек хороших и разных, а так же - других обьектов +#список хреней: +#генерация захватов +#позиции размещения и базирования +#позиции соединения + +print('lcs - локальная система координат') +print('grip - позиция захвата') +print('area - плоскость') +print('vol - зона') +print('joint - соединение') +entityType = input('Введите тип получаемого обьекта') + +def create(entityType): + + if entityType == 'grip': + print('задайте начальную точку захватной зоны. ') + print('Учтите, что ось X должна быть направлена вдоль направления раскрытия пальцев') + print('Ось Z должна быть направлена в противоположную сторону от направления кончика пальца захвата') + objname = input('Введите название начальной точки' + "\n") + obj = doc.getObject(objname) + + + poseGenerator(obj) + + + + elif entityType == 'area': + print('задайте плоскость') + objname = input('Введите название плоскости'+ "\n") + obj = doc.getObject(objname) + + areaProps(obj) + + elif entityType == 'vol': + print('задайте обьем') + objname = input('Введите название обьема'+ "\n") + obj = doc.getObject(objname) + + volProps(obj) + + elif entityType == 'joint': + print('Задайте позицию соединения') + objname = input('Введите название соединения'+ "\n") + part1 = input('укажите название первой детали'+ "\n") + part2 = input('укажите название второй детали'+ "\n") + + jointGenerator(objname, part1, part2) + + obj.addProperty("App::PropertyString", "Type").Type = entityType #запишем тип хрени в свойства хрени + + + +## заглушки функций на случай, если что-то придумаю полезное для них + + +def areaProps(area): + #здесь нужно отметить свойства зоны + #в принципе, Placement и размеры тут есть, больше ничего особо не нужно + #добавить характеристику entityType + + print(area.Label) + +def volProps(vol): + #желательно указать координаты, габариты, позицию привязки + #но думаю, что это все уже есть + print(vol.Label) + + +def jointGenerator(jointName, part1, part2): + + #получаем относительные координаты для первой детали и для второй детали + #создаем две сущности - точка входа и точка выхода (??????) + print(jointName.Label) + print(part1.Label) + print(part2.Label) + + + + + + + +# Эта функция работает нормально + + +def poseGenerator(lcs): + + box = doc.addObject("Part::Box", "gripSpace") + box.Length = 62 #раскрытие + box.Width = 10 #ширина пальца + box.Height = 40 #глубина + + box.Placement = lcs.Placement + + + #есть смысл создавать привязку прямо здесь же. благодаря параметризации, при подстройке куба все точки сместятся как надо + gripPose = App.ActiveDocument.addObject('PartDesign::CoordinateSystem', 'GripPose') + gripPose.Support = box + gripPose.positionBySupport() + gripPose.MapMode = 'ObjectXY' + gripPose.AttachmentOffset.Base = [str(box.Label) + '.Length' + '/2', str(box.Label) + '.Length'+ '/2', 0] #здесь должна быть активная привязка, не просто значения координат + + gripPose.addProperty("App::PropertyFloat", "GripOpen") + gripPose.addProperty("App::PropertyFloat", "GripDepth") + gripPose.addProperty("App::PropertyFloat", "GripWidth") + gripPose.setExpression('GripDepth', str(box.Label) + '.Height') + gripPose.setExpression('GripOpen', str(box.Label) + '.Length') + gripPose.setExpression('GripWidth', str(box.Label) + '.Width') + + + + + + #нужно создавать эту хрень внутри Part, а не внутри главного документа. сбиваются привязки !!! + + + print('Установите захватную зону вручную, растянув обьект GripSpace') + doc.recompute() + + +#теперь нужно производить экспорт +#он делается через Tools.py + + +create(entityType) + + -- 2.49.0 From 5bc70326eda3ea9723b3262fb8cda0aaf6f3b6e1 Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Sat, 10 Jun 2023 22:56:53 +0300 Subject: [PATCH 07/27] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=81=D0=BA=D1=80=D0=B8=D0=BF=D1=82=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D1=80=D0=B0=D0=B7=D0=BE=D0=B2=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D1=8D=D0=BA=D1=81=D0=BF=D0=BE=D1=80=D1=82=D0=B0=20=D0=B2=D1=81?= =?UTF-8?q?=D0=B5=D1=85=20=D0=B2=D1=81=D0=BF=D0=BE=D0=BC=D0=BE=D0=B3=D0=B0?= =?UTF-8?q?=D1=82=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D1=85=20=D0=BE=D0=B1=D1=8C?= =?UTF-8?q?=D0=B5=D0=BA=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cg/freecad/Frames/ImportExportEntities.py | 105 ++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 cg/freecad/Frames/ImportExportEntities.py diff --git a/cg/freecad/Frames/ImportExportEntities.py b/cg/freecad/Frames/ImportExportEntities.py new file mode 100644 index 0000000..cc51f72 --- /dev/null +++ b/cg/freecad/Frames/ImportExportEntities.py @@ -0,0 +1,105 @@ +import os +import json +import FreeCAD +import Tools + +def export_coordinate_systems(): + # Получение активного документа FreeCAD + doc = FreeCAD.ActiveDocument + if not doc: + raise ValueError("Нет активного документа FreeCAD.") + + # Получение имени активного документа + doc_name = doc.Name + + + # Получение пути к папке и имя файла активного документа + folder_path, file_name = os.path.split(doc.FileName) + + # Создание папки для экспорта, если она не существует + output_folder = os.path.join(folder_path, 'entities') + if not os.path.exists(output_folder): + os.makedirs(output_folder) + + + + # Создание копии активного документа + # надо сохраняться, а не копироваться + + doc.save() + + + + # Получение списка объектов документа + objects = doc.Objects + + # Обход объектов для сохранения локальных систем координат + for obj in objects: + if obj.TypeId == 'PartDesign::CoordinateSystem': + + partprops = Tools.getLocalPartProps(obj) + + + output_file_path = os.path.join(output_folder, f"{obj.Label}.json") + with open(output_file_path, "w", encoding="utf8") as propfile: + json.dump(partprops, propfile, indent=1, separators=(',', ': ')) + + + print("Экспорт обьектов завершен.") + +export_coordinate_systems() + + +#работает + + +def import_coordinate_systems(): + # Получение активного документа FreeCAD + doc = FreeCAD.ActiveDocument + if not doc: + raise ValueError("Нет активного документа FreeCAD.") + + # Получение имени активного документа + doc_name = doc.Name + + # Получение пути к папке активного документа + folder_path, _ = os.path.split(doc.FileName) + + # Получение пути к папке с файлами JSON + json_folder_path = os.path.join(folder_path, doc_name) + + # Проверка существования папки с файлами JSON + if not os.path.exists(json_folder_path): + raise ValueError(f"Папка {json_folder_path} не существует.") + + # Получение списка файлов JSON в папке + json_files = [f for f in os.listdir(json_folder_path) if f.endswith(".json")] + + # Обход файлов JSON для создания локальных систем координат + for json_file in json_files: + json_file_path = os.path.join(json_folder_path, json_file) + with open(json_file_path, "r") as file: + json_data = json.load(file) + + # Извлечение информации о локальной системе координат из файла JSON + name = json_data.get("Name") + x = json_data.get("X") + y = json_data.get("Y") + z = json_data.get("Z") + + # Создание локальной системы координат + placement = FreeCAD.Placement() + placement.Base = FreeCAD.Vector(x, y, z) + part = doc.addObject("Part::Feature", name) + part.Placement = placement + + # Добавление дополнительных свойств из файла JSON + for key, value in json_data.items(): + if key not in ["Name", "X", "Y", "Z"]: + setattr(part, key, value) + + print("Импорт локальных систем координат завершен.") + +# Пример использования +#import_coordinate_systems() + -- 2.49.0 From 1086acfa16c914b99f83825806c2787ae585b872 Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Sat, 10 Jun 2023 22:58:44 +0300 Subject: [PATCH 08/27] =?UTF-8?q?=D0=97=D0=B0=D0=BA=D0=BE=D0=BC=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=B8=D0=BB=20=D0=B0=D0=BB=D0=B3=D0=BE=D1=80?= =?UTF-8?q?=D0=B8=D1=82=D0=BC=20=D0=B8=D0=BC=D0=BF=D0=BE=D1=80=D1=82=D0=B0?= =?UTF-8?q?=20=D0=B2=D1=81=D0=BF=D0=BE=D0=BC=D0=BE=D0=B3=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D1=8B=D1=85=20=D0=BE=D0=B1=D1=8A=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cg/freecad/Frames/ImportExportEntities.py | 76 +++++++++++------------ 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/cg/freecad/Frames/ImportExportEntities.py b/cg/freecad/Frames/ImportExportEntities.py index cc51f72..73f9aaa 100644 --- a/cg/freecad/Frames/ImportExportEntities.py +++ b/cg/freecad/Frames/ImportExportEntities.py @@ -53,53 +53,53 @@ export_coordinate_systems() #работает -def import_coordinate_systems(): - # Получение активного документа FreeCAD - doc = FreeCAD.ActiveDocument - if not doc: - raise ValueError("Нет активного документа FreeCAD.") +# def import_coordinate_systems(): +# # Получение активного документа FreeCAD +# doc = FreeCAD.ActiveDocument +# if not doc: +# raise ValueError("Нет активного документа FreeCAD.") - # Получение имени активного документа - doc_name = doc.Name +# # Получение имени активного документа +# doc_name = doc.Name - # Получение пути к папке активного документа - folder_path, _ = os.path.split(doc.FileName) +# # Получение пути к папке активного документа +# folder_path, _ = os.path.split(doc.FileName) - # Получение пути к папке с файлами JSON - json_folder_path = os.path.join(folder_path, doc_name) +# # Получение пути к папке с файлами JSON +# json_folder_path = os.path.join(folder_path, doc_name) - # Проверка существования папки с файлами JSON - if not os.path.exists(json_folder_path): - raise ValueError(f"Папка {json_folder_path} не существует.") +# # Проверка существования папки с файлами JSON +# if not os.path.exists(json_folder_path): +# raise ValueError(f"Папка {json_folder_path} не существует.") - # Получение списка файлов JSON в папке - json_files = [f for f in os.listdir(json_folder_path) if f.endswith(".json")] +# # Получение списка файлов JSON в папке +# json_files = [f for f in os.listdir(json_folder_path) if f.endswith(".json")] - # Обход файлов JSON для создания локальных систем координат - for json_file in json_files: - json_file_path = os.path.join(json_folder_path, json_file) - with open(json_file_path, "r") as file: - json_data = json.load(file) +# # Обход файлов JSON для создания локальных систем координат +# for json_file in json_files: +# json_file_path = os.path.join(json_folder_path, json_file) +# with open(json_file_path, "r") as file: +# json_data = json.load(file) - # Извлечение информации о локальной системе координат из файла JSON - name = json_data.get("Name") - x = json_data.get("X") - y = json_data.get("Y") - z = json_data.get("Z") +# # Извлечение информации о локальной системе координат из файла JSON +# name = json_data.get("Name") +# x = json_data.get("X") +# y = json_data.get("Y") +# z = json_data.get("Z") - # Создание локальной системы координат - placement = FreeCAD.Placement() - placement.Base = FreeCAD.Vector(x, y, z) - part = doc.addObject("Part::Feature", name) - part.Placement = placement +# # Создание локальной системы координат +# placement = FreeCAD.Placement() +# placement.Base = FreeCAD.Vector(x, y, z) +# part = doc.addObject("Part::Feature", name) +# part.Placement = placement - # Добавление дополнительных свойств из файла JSON - for key, value in json_data.items(): - if key not in ["Name", "X", "Y", "Z"]: - setattr(part, key, value) +# # Добавление дополнительных свойств из файла JSON +# for key, value in json_data.items(): +# if key not in ["Name", "X", "Y", "Z"]: +# setattr(part, key, value) - print("Импорт локальных систем координат завершен.") +# print("Импорт локальных систем координат завершен.") -# Пример использования -#import_coordinate_systems() + +# #import_coordinate_systems() -- 2.49.0 From 9d1d5cda458ef4dee98fdab8fadafe8faef759ed Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Sat, 10 Jun 2023 23:06:29 +0300 Subject: [PATCH 09/27] =?UTF-8?q?=D0=92=D1=8B=D0=BD=D0=B5=D1=81=20=D0=BC?= =?UTF-8?q?=D1=83=D1=81=D0=BE=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cg/freecad/Frames/markupEntities.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/cg/freecad/Frames/markupEntities.py b/cg/freecad/Frames/markupEntities.py index 3370d15..4a25d8a 100644 --- a/cg/freecad/Frames/markupEntities.py +++ b/cg/freecad/Frames/markupEntities.py @@ -1,17 +1,10 @@ import FreeCAD as App import FreeCADGui as Gui -# App.newDocument() doc = App.ActiveDocument -### -#прога для общей генерации всяких разметок -#сюда будем добавлять функции для генерации точек хороших и разных, а так же - других обьектов -#список хреней: -#генерация захватов -#позиции размещения и базирования -#позиции соединения + print('lcs - локальная система координат') print('grip - позиция захвата') @@ -56,7 +49,7 @@ def create(entityType): jointGenerator(objname, part1, part2) - obj.addProperty("App::PropertyString", "Type").Type = entityType #запишем тип хрени в свойства хрени + obj.addProperty("App::PropertyString", "Type").Type = entityType @@ -80,6 +73,7 @@ def jointGenerator(jointName, part1, part2): #получаем относительные координаты для первой детали и для второй детали #создаем две сущности - точка входа и точка выхода (??????) + #в работе сейчас print(jointName.Label) print(part1.Label) print(part2.Label) @@ -121,7 +115,7 @@ def poseGenerator(lcs): - #нужно создавать эту хрень внутри Part, а не внутри главного документа. сбиваются привязки !!! + #нужно создавать внутри Part, а не внутри главного документа. сбиваются привязки !!! print('Установите захватную зону вручную, растянув обьект GripSpace') @@ -129,7 +123,7 @@ def poseGenerator(lcs): #теперь нужно производить экспорт -#он делается через Tools.py +#он делается через Tools.py или через импорт-экспорт create(entityType) -- 2.49.0 From 988a5c65e0bbe2ba2f94d6c3b4e531b42baa8f41 Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Tue, 25 Apr 2023 22:31:19 +0300 Subject: [PATCH 10/27] =?UTF-8?q?=D0=9E=D0=BF=D0=B8=D1=81=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8F=20=D1=82?= =?UTF-8?q?=D0=B5=D1=85=D0=BD=D0=BE=D0=BB=D0=BE=D0=B3=D0=B8=D1=87=D0=B5?= =?UTF-8?q?=D1=81=D0=BA=D0=BE=D0=B9=20=D0=BF=D0=BE=D0=B4=D0=B3=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cg/freecad/Frames/MetaObj.py | 0 cg/freecad/Frames/img/qXX7sBMbsvA.jpg | Bin 0 -> 87958 bytes cg/freecad/Frames/newDatumCmd.py | 259 ++++++++++++++++++ .../Модуль технологической подготовки.md | 27 ++ 4 files changed, 286 insertions(+) create mode 100644 cg/freecad/Frames/MetaObj.py create mode 100644 cg/freecad/Frames/img/qXX7sBMbsvA.jpg create mode 100644 cg/freecad/Frames/newDatumCmd.py create mode 100644 cg/freecad/Frames/Модуль технологической подготовки.md diff --git a/cg/freecad/Frames/MetaObj.py b/cg/freecad/Frames/MetaObj.py new file mode 100644 index 0000000..e69de29 diff --git a/cg/freecad/Frames/img/qXX7sBMbsvA.jpg b/cg/freecad/Frames/img/qXX7sBMbsvA.jpg new file mode 100644 index 0000000000000000000000000000000000000000..17a6b4638ffea2916c7e0b0643d1b70b47e09cad GIT binary patch literal 87958 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 PT_sD`H56U;|8D{S-S?OA literal 0 HcmV?d00001 diff --git a/cg/freecad/Frames/newDatumCmd.py b/cg/freecad/Frames/newDatumCmd.py new file mode 100644 index 0000000..d1f2930 --- /dev/null +++ b/cg/freecad/Frames/newDatumCmd.py @@ -0,0 +1,259 @@ +#!/usr/bin/env python3 +# coding: utf-8 +# +# LGPL +# Copyright HUBERT Zoltán +# +# newDatumCmd.py + + +import os + +from PySide import QtGui, QtCore +import FreeCADGui as Gui +import FreeCAD as App +from FreeCAD import Console as FCC + +import Asm4_libs as Asm4 + + + + +""" + +-----------------------------------------------+ + | a class to create all Datum objects | + +-----------------------------------------------+ +""" +class newDatum: + "My tool object" + def __init__(self, datumName): + self.datumName = datumName + # recognised containers (not the same as Asm4.containerTypes !) + self.containers = [ 'App::Part', 'PartDesign::Body', 'App::DocumentObjectGroup'] + if self.datumName == 'Point': + self.datumType = 'PartDesign::Point' + self.menutext = "New Point" + self.tooltip = "Create a new Datum Point in a Part" + self.icon = os.path.join( Asm4.iconPath , 'Asm4_Point.svg') + self.datumColor = (0.00,0.00,0.00) + self.datumAlpha = [] + elif self.datumName == 'Axis': + self.datumType = 'PartDesign::Line' + self.menutext = "New Axis" + self.tooltip = "Create a new Datum Axis in a Part" + self.icon = os.path.join( Asm4.iconPath , 'Asm4_Axis.svg') + self.datumColor = (0.00,0.00,0.50) + self.datumAlpha = [] + elif self.datumName == 'Plane': + self.datumType = 'PartDesign::Plane' + self.menutext = "New Plane" + self.tooltip = "Create a new Datum Plane in a Part" + self.icon = os.path.join( Asm4.iconPath , 'Asm4_Plane.svg') + self.datumColor = (0.50,0.50,0.50) + self.datumAlpha = 80 + elif self.datumName == 'LCS': + self.datumType = 'PartDesign::CoordinateSystem' + self.menutext = "New Coordinate System" + self.tooltip = "Create a new Coordinate System in a Part" + self.icon = os.path.join( Asm4.iconPath , 'Asm4_CoordinateSystem.svg') + self.datumColor = [] + self.datumAlpha = [] + elif self.datumName == 'Sketch': + self.datumType = 'Sketcher::SketchObject' + self.menutext = "New Sketch" + self.tooltip = "Create a new Sketch in a Part" + self.icon = os.path.join( Asm4.iconPath , 'Asm4_Sketch.svg') + self.datumColor = [] + self.datumAlpha = [] + + + def GetResources(self): + return {"MenuText": self.menutext, + "ToolTip": self.tooltip, + "Pixmap" : self.icon } + + + def IsActive(self): + if App.ActiveDocument: + # is something correct selected ? + if self.checkSelection(): + return(True) + return(False) + + + def checkSelection(self): + # if something is selected ... + if Gui.Selection.getSelection(): + selectedObj = Gui.Selection.getSelection()[0] + # ... and it's an App::Part or an datum object + selType = selectedObj.TypeId + if selType in self.containers or selType in Asm4.datumTypes or selType=='Sketcher::SketchObject': + return(selectedObj) + # or of nothing is selected ... + elif Asm4.getAssembly(): + # ... but there is as assembly: + return Asm4.getAssembly() + # if we're here it's because we didn't find a good reason to not be here + return None + + + + """ + +-----------------------------------------------+ + | the real stuff | + +-----------------------------------------------+ + """ + def Activated(self): + # check that we have somewhere to put our stuff + selectedObj = self.checkSelection() + # default name increments the datum type's end numeral + proposedName = Asm4.nextInstance( self.datumName, startAtOne=True ) + + parentContainer = None + # check whether we have selected a container + if selectedObj.TypeId in self.containers: + parentContainer = selectedObj + # if a datum object is selected + elif selectedObj.TypeId in Asm4.datumTypes or selectedObj.TypeId=='Sketcher::SketchObject': + # see whether it's in a container + parent = selectedObj.getParentGeoFeatureGroup() + if parent.TypeId in self.containers: + parentContainer = parent + # if there is an assembly + elif Asm4.getAssembly(): + parentContainer = Asm4.getAssembly() + # something went wrong + else: + Asm4.warningBox("I can't create a "+self.datumType+" with the current selections") + + # check whether there is already a similar datum, and increment the instance number + # instanceNum = 1 + #while App.ActiveDocument.getObject( self.datumName+'_'+str(instanceNum) ): + # instanceNum += 1 + #datumName = self.datumName+'_'+str(instanceNum) + if parentContainer: + # input dialog to ask the user the name of the Sketch: + #proposedName = Asm4.nextInstance( self.datumName + '_' + selectedObj.Label, startAtOne=True ) + text,ok = QtGui.QInputDialog.getText(None,'Create new '+self.datumName, + 'Enter '+self.datumName+' name :'+' '*40, text = proposedName) + if ok and text: + # App.activeDocument().getObject('Model').newObject( 'Sketcher::SketchObject', text ) + createdDatum = App.ActiveDocument.addObject( self.datumType, text ) + parentContainer.addObject( createdDatum ) + createdDatum.Label = text + # automatic resizing of datum Plane sucks, so we set it to manual + if self.datumType=='PartDesign::Plane': + createdDatum.ResizeMode = 'Manual' + createdDatum.Length = 100 + createdDatum.Width = 100 + elif self.datumType=='PartDesign::Line': + createdDatum.ResizeMode = 'Manual' + createdDatum.Length = 200 + # if color or transparency is specified for this datum type + if self.datumColor: + Gui.ActiveDocument.getObject(createdDatum.Name).ShapeColor = self.datumColor + if self.datumAlpha: + Gui.ActiveDocument.getObject(createdDatum.Name).Transparency = self.datumAlpha + # highlight the created datum object + Gui.Selection.clearSelection() + Gui.Selection.addSelection( App.ActiveDocument.Name, parentContainer.Name, createdDatum.Name+'.' ) + Gui.runCommand('Part_EditAttachment') + + + +""" + +-----------------------------------------------+ + | a class to create an LCS on a hole | + +-----------------------------------------------+ +""" +class newHole: + def GetResources(self): + return {"MenuText": "New Hole Axis", + "ToolTip": "Create a Datum Axis attached to a hole", + "Pixmap" : os.path.join( Asm4.iconPath , 'Asm4_Hole.svg') + } + + def IsActive(self): + selection = self.getSelectedEdges() + if selection is None: + return False + else: + return True + + + """ + +-----------------------------------------------+ + | the real stuff | + +-----------------------------------------------+ + """ + def getSelectedEdges(self): + # check that we have selected only circular edges + selection = None + parent = None + edges = [] + # 1 selection means a single parent + if App.ActiveDocument and len(Gui.Selection.getSelection()) == 1: + parent = Gui.Selection.getSelection()[0] + # parse all sub-elemets of the selection + for i in range(len(Gui.Selection.getSelectionEx()[0].SubObjects)): + edgeObj = Gui.Selection.getSelectionEx()[0].SubObjects[i] + edgeName = Gui.Selection.getSelectionEx()[0].SubElementNames[i] + # if the edge is circular + if Asm4.isCircle(edgeObj): + edges.append( [edgeObj,edgeName] ) + # if we found circular edges + if len(edges) > 0: + selection = ( parent, edges ) + return selection + + + def Activated(self): + ( selectedObj, edges ) = self.getSelectedEdges() + for i in range(len(edges)): + edgeObj = edges[i][0] + edgeName = edges[i][1] + parentPart = selectedObj.getParentGeoFeatureGroup() + # we can create a datum only in a container + if parentPart: + parentDoc = parentPart.Document + # if the solid having the edge is indeed in an App::Part + if parentPart and (parentPart.TypeId=='App::Part' or parentPart.TypeId=='PartDesign::Body'): + # check whether there is already a similar datum, and increment the instance number + instanceNum = 1 + while parentDoc.getObject( 'HoleAxis_'+str(instanceNum) ): + instanceNum += 1 + axis = parentPart.newObject('PartDesign::Line','HoleAxis_'+str(instanceNum)) + axis.Support = [( selectedObj, (edgeName,) )] + axis.MapMode = 'AxisOfCurvature' + axis.MapReversed = False + axis.ResizeMode = 'Manual' + axis.Length = edgeObj.BoundBox.DiagonalLength + axis.ViewObject.ShapeColor = (0.0,0.0,1.0) + axis.ViewObject.Transparency = 50 + axis.recompute() + parentPart.recompute() + # + else: + FCC.PrintMessage('Datum objects can only be created inside Part or Body containers') + + + +""" + +-----------------------------------------------+ + | add the commands to the workbench | + +-----------------------------------------------+ +""" +Gui.addCommand( 'Asm4_newPoint', newDatum('Point') ) +Gui.addCommand( 'Asm4_newAxis', newDatum('Axis') ) +Gui.addCommand( 'Asm4_newPlane', newDatum('Plane') ) +Gui.addCommand( 'Asm4_newLCS', newDatum('LCS') ) +Gui.addCommand( 'Asm4_newSketch',newDatum('Sketch')) +Gui.addCommand( 'Asm4_newHole', newHole() ) + +# defines the drop-down button for Datum objects +createDatumList = [ 'Asm4_newLCS', + 'Asm4_newPlane', + 'Asm4_newAxis', + 'Asm4_newPoint', + 'Asm4_newHole' ] +Gui.addCommand( 'Asm4_createDatum', Asm4.dropDownCmd( createDatumList, 'Create Datum Object')) diff --git a/cg/freecad/Frames/Модуль технологической подготовки.md b/cg/freecad/Frames/Модуль технологической подготовки.md new file mode 100644 index 0000000..4ae744f --- /dev/null +++ b/cg/freecad/Frames/Модуль технологической подготовки.md @@ -0,0 +1,27 @@ + + +# Модуль технологической подготовки + +На вход: - step-модель описываемого оборудования. + +1. Описание имеющихся сущностей: + - Мы можем создать описание действий со станком и его состояниями для связи с окружающим миром нужен некий обобщенный объект + его свойством будет возможность воздействовать на состояния, но сам он будет любым. Думаю, нужно описать сами рычаги, а не того, кто будет их нажимать. + + - Описать объекты можно созданием зон вокруг них и/или созданием призрака геометрии оборудования (может быть сложно и избыточно). + - Для этого возможно создать параллелепипед/цилиндр, сопоставимый по габаритам с рассматриваемой зоной. С помощью инструментов верстака Part создается объект, имеющий желаемое название (Станок, рабочая зона, etc). Эта зона с помощью отношений parent привязана к геометрии станка. + +2. Описание действий + - Создается папка actions, в которую сохраняются создаваемые действия + - Интерфейс создания действий аналогичен интерфейсу задания других сущностей. Через него мы задаем ссылки на существующие действия и элементы и указываем их тип: триггеры, рабочие органы, конечные состояния, начальные состояния. Указываем их статус (выбор "да/нет") + - Указываем ссылки на привязываемую к этим действиям геометрию. (катушки, статоры, расходники и тд) Для этого их геометрия должна быть импортирована в модель. Ссылка будет указывать на конкретный импортированный файл. Если существует идентификатор детали, можно ссылаться на него. + +3. Задание состояний и переменных + - Переменные задаются средствами параметризации FreeCAD, инструменты для этого можно взять из ASM4, используя функционал переменных (Variables) и панели конфигураций. + - Для состояний переменных аналогично создается (в ASM4 уже имеется при создании модели) отдельная директория. + +4. Результатом описания будет модель, имеющая дерево объектов, в свойствах которых мы имеем всю необходимую информацию. Геометрические характеристики мы сохраняем как json и отправляем в среды, работающие с геометрией и физикой. Действия и геометрия подставляются в шаблон pddl в соответствующие абзацы. + +Пример размеченной модели: + +![Разметка](img/qXX7sBMbsvA.jpg) \ No newline at end of file -- 2.49.0 From dfb647a3ec3a047cf8ec493a64d36a9d3c3b6fc7 Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Thu, 27 Apr 2023 00:21:45 +0300 Subject: [PATCH 11/27] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20=D0=BE=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BE=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Модуль технологической подготовки.md | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/cg/freecad/Frames/Модуль технологической подготовки.md b/cg/freecad/Frames/Модуль технологической подготовки.md index 4ae744f..191556c 100644 --- a/cg/freecad/Frames/Модуль технологической подготовки.md +++ b/cg/freecad/Frames/Модуль технологической подготовки.md @@ -22,6 +22,39 @@ 4. Результатом описания будет модель, имеющая дерево объектов, в свойствах которых мы имеем всю необходимую информацию. Геометрические характеристики мы сохраняем как json и отправляем в среды, работающие с геометрией и физикой. Действия и геометрия подставляются в шаблон pddl в соответствующие абзацы. +## Пример описания объекта + +Action - "Заправка 3д-принтера пластиком" + + - |- Объекты: + - - 3d-принтер [printer_id] /прямоугольная зона по габаритам принтера. Зона привязана к геометрии оборудования + - Workzone [printer_id] / прямоугольная зона. Указание на объект workzone, который содержит в себе габариты и позиционирование рабочей зоны относительно 3d-принтера. + - Wirenest [printer_id] /цилиндрическая зона. Указание на объект wirenest (цилиндр), хранящий информацию об ориентации и положении гнезда для катушки с пластиком + - Filament [filament_id] /катушка с пластиком требуемой модели, формы и габаритов. + - Observer [observer_id] / некая сущность(манипулятор, человек, камера), к которой обращается станок, чтобы с ним провели внешние манипуляции + - |- Длительность действия, с + + + - |- Стартовые состояния: + - Пластика достаточно (нет) + - Наблюдатель свободен (да) + - |- Во время действия: + - Наблюдатель[observer_id] свободен (нет) + - Катушка пластика установлена (нет) + - |- После окончания: + - Катушка пластика установлена (да) + - Наблюдатель [observer_id] свободен (да) + - Пластика достаточно (да) + + +--В раздел Variables мы можем (должны ли?) полуавтоматически/автоматически указать подобные состояния, привязанные к значениям да/нет.-- (Указывать стартовые значения по умолчанию?) + + +Указанные отдельно состояния пригодились бы, чтобы ссылаться на них при задавании действий, поскольку действия сообщаются между собой не напрямую, а через выполнение определенного набора состояний. + + + + Пример размеченной модели: ![Разметка](img/qXX7sBMbsvA.jpg) \ No newline at end of file -- 2.49.0 From bd12df9d850895560004abe24ddbe246bb64467c Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Tue, 23 May 2023 15:48:20 +0300 Subject: [PATCH 12/27] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD?= =?UTF-8?q?=D0=B0=D0=BB=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B0=20=D1=81=D0=BF=D0=B5=D1=86=D0=B8=D1=84=D0=B8=D0=BA?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cg/freecad/Frames/AuxObjCreation.py | 286 ++++++++++++++++++ cg/freecad/Frames/BoMList.py | 110 +++++++ cg/freecad/Frames/DatumCommand.py | 49 +++ cg/freecad/Frames/Frames.py | 47 +-- cg/freecad/Frames/InitGui.py | 5 +- cg/freecad/Frames/Sheet_addition_test.py | 60 ++++ cg/freecad/Frames/UI/icons/BoMList.svg | 72 +++++ cg/freecad/Frames/UI/icons/auxDatum.svg | 240 +++++++++++++++ cg/freecad/Frames/box.py | 0 cg/freecad/Frames/newLabel.py | 17 ++ cg/freecad/Frames/testSpread2.py | 77 +++++ .../Frames/usecases/asm4parser_usecase.py | 53 ++++ 12 files changed, 998 insertions(+), 18 deletions(-) create mode 100644 cg/freecad/Frames/AuxObjCreation.py create mode 100644 cg/freecad/Frames/BoMList.py create mode 100644 cg/freecad/Frames/DatumCommand.py create mode 100644 cg/freecad/Frames/Sheet_addition_test.py create mode 100644 cg/freecad/Frames/UI/icons/BoMList.svg create mode 100644 cg/freecad/Frames/UI/icons/auxDatum.svg create mode 100644 cg/freecad/Frames/box.py create mode 100644 cg/freecad/Frames/newLabel.py create mode 100644 cg/freecad/Frames/testSpread2.py create mode 100644 cg/freecad/Frames/usecases/asm4parser_usecase.py diff --git a/cg/freecad/Frames/AuxObjCreation.py b/cg/freecad/Frames/AuxObjCreation.py new file mode 100644 index 0000000..4db00e4 --- /dev/null +++ b/cg/freecad/Frames/AuxObjCreation.py @@ -0,0 +1,286 @@ + + + + " + #Создаем объект, к которому привязываем вспомогательную информацию: + 1. Выбираем элемент в модели: + 1.1. Поверхность - можем создать призматический объект. Соответствует точке захвата, рабочей зоне, опорной поверхности, поверхности базирования + 1.1.1 Выбираем тип объкта, который нам нужно построить. + 1.1.2 В зависимости от выбора, предлагается указать объект для ориентации осей. + 1.1.3 Указать размеры зоны построения. Для захвата соответствует габаритам пальца, для рабочей зоны - координатам. Полуавтоматическое (ручное???) создание эскиза? + 1.1.4 Для захвата выбираем вытягивание объекта на нужную длину до параллельной поверхности + 1.1.5 - Результат - построенный параллелограмм с размерами и привязкой к моделям. Его мы экспортируем в json через существующий функционал + + 1.2. Цилиндрическая поверхность - осесимметричный объект. Захват двухпальцевый, трехпальцевый, четырехпальцевый, цилиндрическая зона установки, отверстия сопряжения + 1.2.1 Выбираем тип объекта + 1.2.2. Выбираем ориентацию главной оси. //Потенциально, мы можем захватить цилиндрический объект в любой ориентации, нужно ли добавлять конкретное указание? + видимо, только если геометрия обязывает нас придерживаться ее (напр. не дает поставить 4 палец) + указание требуется, если есть фиксаторы, опорные площадки или что-то иное, что приводит к однозначному позиционированию + 1.2.3. Вытягиваем зону до привязки. Пальцы ставим как цилиндрический массив( нужно ли??) + 1.2.4 Результат - цилиндрическая зона с радиусом, ориентациями осей. + 2. К размеченным объектам необходимо привязать метаданные, содержащие связи с конкретными моделями. STEP-файл захвата, step-файл входной детали и/или выходной. + 2.1 Нужно ли привязывать захват? Да, но в некоем обобщенном виде. Позиция захвата - характеристика способа воздействия, а не объекта. + 2.2 Для станков входы и выходы необходимы. + 2.3 + " + + + #!/usr/bin/env python3 +# coding: utf-8 +# +# LGPL +# Copyright HUBERT Zoltán +# +# newDatumCmd.py + + +import os + +from PySide import QtGui, QtCore +import FreeCADGui as Gui +import FreeCAD as App +from FreeCAD import Console as FCC + +import Asm4_libs as Asm4 + + + + +""" + +-----------------------------------------------+ + | a class to create all Datum objects | + +-----------------------------------------------+ +""" +class newDatum: + "My tool object" + def __init__(self, datumName): + self.datumName = datumName + # recognised containers (not the same as Asm4.containerTypes !) + self.containers = [ 'App::Part', 'PartDesign::Body', 'App::DocumentObjectGroup'] + if self.datumName == 'Point': + self.datumType = 'PartDesign::Point' + self.menutext = "New Point" + self.tooltip = "Create a new Datum Point in a Part" + self.icon = os.path.join( Asm4.iconPath , 'Asm4_Point.svg') + self.datumColor = (0.00,0.00,0.00) + self.datumAlpha = [] + elif self.datumName == 'Axis': + self.datumType = 'PartDesign::Line' + self.menutext = "New Axis" + self.tooltip = "Create a new Datum Axis in a Part" + self.icon = os.path.join( Asm4.iconPath , 'Asm4_Axis.svg') + self.datumColor = (0.00,0.00,0.50) + self.datumAlpha = [] + elif self.datumName == 'Plane': + self.datumType = 'PartDesign::Plane' + self.menutext = "New Plane" + self.tooltip = "Create a new Datum Plane in a Part" + self.icon = os.path.join( Asm4.iconPath , 'Asm4_Plane.svg') + self.datumColor = (0.50,0.50,0.50) + self.datumAlpha = 80 + elif self.datumName == 'LCS': + self.datumType = 'PartDesign::CoordinateSystem' + self.menutext = "New Coordinate System" + self.tooltip = "Create a new Coordinate System in a Part" + self.icon = os.path.join( Asm4.iconPath , 'Asm4_CoordinateSystem.svg') + self.datumColor = [] + self.datumAlpha = [] + elif self.datumName == 'Sketch': + self.datumType = 'Sketcher::SketchObject' + self.menutext = "New Sketch" + self.tooltip = "Create a new Sketch in a Part" + self.icon = os.path.join( Asm4.iconPath , 'Asm4_Sketch.svg') + self.datumColor = [] + self.datumAlpha = [] + + + def GetResources(self): + return {"MenuText": self.menutext, + "ToolTip": self.tooltip, + "Pixmap" : self.icon } + + + def IsActive(self): + if App.ActiveDocument: + # is something correct selected ? + if self.checkSelection(): + return(True) + return(False) + + + def checkSelection(self): + # if something is selected ... + if Gui.Selection.getSelection(): + selectedObj = Gui.Selection.getSelection()[0] + # ... and it's an App::Part or an datum object + selType = selectedObj.TypeId + if selType in self.containers or selType in Asm4.datumTypes or selType=='Sketcher::SketchObject': + return(selectedObj) + # or of nothing is selected ... + elif Asm4.getAssembly(): + # ... but there is as assembly: + return Asm4.getAssembly() + # if we're here it's because we didn't find a good reason to not be here + return None + + + + """ + +-----------------------------------------------+ + | the real stuff | + +-----------------------------------------------+ + """ + def Activated(self): + # check that we have somewhere to put our stuff + selectedObj = self.checkSelection() + # default name increments the datum type's end numeral + proposedName = Asm4.nextInstance( self.datumName, startAtOne=True ) + + parentContainer = None + # check whether we have selected a container + if selectedObj.TypeId in self.containers: + parentContainer = selectedObj + # if a datum object is selected + elif selectedObj.TypeId in Asm4.datumTypes or selectedObj.TypeId=='Sketcher::SketchObject': + # see whether it's in a container + parent = selectedObj.getParentGeoFeatureGroup() + if parent.TypeId in self.containers: + parentContainer = parent + # if there is an assembly + elif Asm4.getAssembly(): + parentContainer = Asm4.getAssembly() + # something went wrong + else: + Asm4.warningBox("I can't create a "+self.datumType+" with the current selections") + + # check whether there is already a similar datum, and increment the instance number + # instanceNum = 1 + #while App.ActiveDocument.getObject( self.datumName+'_'+str(instanceNum) ): + # instanceNum += 1 + #datumName = self.datumName+'_'+str(instanceNum) + if parentContainer: + # input dialog to ask the user the name of the Sketch: + #proposedName = Asm4.nextInstance( self.datumName + '_' + selectedObj.Label, startAtOne=True ) + text,ok = QtGui.QInputDialog.getText(None,'Create new '+self.datumName, + 'Enter '+self.datumName+' name :'+' '*40, text = proposedName) + if ok and text: + # App.activeDocument().getObject('Model').newObject( 'Sketcher::SketchObject', text ) + createdDatum = App.ActiveDocument.addObject( self.datumType, text ) + parentContainer.addObject( createdDatum ) + createdDatum.Label = text + # automatic resizing of datum Plane sucks, so we set it to manual + if self.datumType=='PartDesign::Plane': + createdDatum.ResizeMode = 'Manual' + createdDatum.Length = 100 + createdDatum.Width = 100 + elif self.datumType=='PartDesign::Line': + createdDatum.ResizeMode = 'Manual' + createdDatum.Length = 200 + # if color or transparency is specified for this datum type + if self.datumColor: + Gui.ActiveDocument.getObject(createdDatum.Name).ShapeColor = self.datumColor + if self.datumAlpha: + Gui.ActiveDocument.getObject(createdDatum.Name).Transparency = self.datumAlpha + # highlight the created datum object + Gui.Selection.clearSelection() + Gui.Selection.addSelection( App.ActiveDocument.Name, parentContainer.Name, createdDatum.Name+'.' ) + Gui.runCommand('Part_EditAttachment') + + + +""" + +-----------------------------------------------+ + | a class to create an LCS on a hole | + +-----------------------------------------------+ +""" +class newHole: + def GetResources(self): + return {"MenuText": "New Hole Axis", + "ToolTip": "Create a Datum Axis attached to a hole", + "Pixmap" : os.path.join( Asm4.iconPath , 'Asm4_Hole.svg') + } + + def IsActive(self): + selection = self.getSelectedEdges() + if selection is None: + return False + else: + return True + + + """ + +-----------------------------------------------+ + | the real stuff | + +-----------------------------------------------+ + """ + def getSelectedEdges(self): + # check that we have selected only circular edges + selection = None + parent = None + edges = [] + # 1 selection means a single parent + if App.ActiveDocument and len(Gui.Selection.getSelection()) == 1: + parent = Gui.Selection.getSelection()[0] + # parse all sub-elemets of the selection + for i in range(len(Gui.Selection.getSelectionEx()[0].SubObjects)): + edgeObj = Gui.Selection.getSelectionEx()[0].SubObjects[i] + edgeName = Gui.Selection.getSelectionEx()[0].SubElementNames[i] + # if the edge is circular + if Asm4.isCircle(edgeObj): + edges.append( [edgeObj,edgeName] ) + # if we found circular edges + if len(edges) > 0: + selection = ( parent, edges ) + return selection + + + def Activated(self): + ( selectedObj, edges ) = self.getSelectedEdges() + for i in range(len(edges)): + edgeObj = edges[i][0] + edgeName = edges[i][1] + parentPart = selectedObj.getParentGeoFeatureGroup() + # we can create a datum only in a container + if parentPart: + parentDoc = parentPart.Document + # if the solid having the edge is indeed in an App::Part + if parentPart and (parentPart.TypeId=='App::Part' or parentPart.TypeId=='PartDesign::Body'): + # check whether there is already a similar datum, and increment the instance number + instanceNum = 1 + while parentDoc.getObject( 'HoleAxis_'+str(instanceNum) ): + instanceNum += 1 + axis = parentPart.newObject('PartDesign::Line','HoleAxis_'+str(instanceNum)) + axis.Support = [( selectedObj, (edgeName,) )] + axis.MapMode = 'AxisOfCurvature' + axis.MapReversed = False + axis.ResizeMode = 'Manual' + axis.Length = edgeObj.BoundBox.DiagonalLength + axis.ViewObject.ShapeColor = (0.0,0.0,1.0) + axis.ViewObject.Transparency = 50 + axis.recompute() + parentPart.recompute() + # + else: + FCC.PrintMessage('Datum objects can only be created inside Part or Body containers') + + + +""" + +-----------------------------------------------+ + | add the commands to the workbench | + +-----------------------------------------------+ +""" +Gui.addCommand( 'Asm4_newPoint', newDatum('Point') ) +Gui.addCommand( 'Asm4_newAxis', newDatum('Axis') ) +Gui.addCommand( 'Asm4_newPlane', newDatum('Plane') ) +Gui.addCommand( 'Asm4_newLCS', newDatum('LCS') ) +Gui.addCommand( 'Asm4_newSketch',newDatum('Sketch')) +Gui.addCommand( 'Asm4_newHole', newHole() ) + +# defines the drop-down button for Datum objects +createDatumList = [ 'Asm4_newLCS', + 'Asm4_newPlane', + 'Asm4_newAxis', + 'Asm4_newPoint', + 'Asm4_newHole' ] +Gui.addCommand( 'Asm4_createDatum', Asm4.dropDownCmd( createDatumList, 'Create Datum Object')) diff --git a/cg/freecad/Frames/BoMList.py b/cg/freecad/Frames/BoMList.py new file mode 100644 index 0000000..fef31eb --- /dev/null +++ b/cg/freecad/Frames/BoMList.py @@ -0,0 +1,110 @@ +from helper.is_solid import is_object_solid +import FreeCAD as App +import Spreadsheet + + +def createSpreadsheet(): + + if App.ActiveDocument.getObject("BoM_List") == None: + + sheet = App.activeDocument().addObject('Spreadsheet::Sheet', 'BoM_List') + sheet.set('A1', 'п.п.') + sheet.set('B1', 'Наименование детали') + sheet.set('C1', 'Количество') + else: + + sheet = App.ActiveDocument.getObject("BoM_List") + App.ActiveDocument.BoM_List.clear('A1:ZZ16384') + sheet.set('A1', 'п.п.') + sheet.set('B1', 'Наименование детали') + sheet.set('C1', 'Количество') + + return (sheet) + + +class SolidBodiesParcer: + _asmThere = [] + + def __init__(self) -> None: + if (self._asmThere.__len__() == 0): + + self.initParse() + pass + + def initParse(self): + for el in App.ActiveDocument.RootObjects: + if (is_object_solid(el) and hasattr(el, 'Group')): + self.getSubPartsLink(el.Group, el.Label) + + def getSubPartsLink(self, group, label): + groupLink = {label: []} + for el in group: + if (is_object_solid(el)): + groupLink[label].append( + {'label': el.Label, 'isGroup': hasattr(el, 'Group'), 'solid': el}) + + for el in groupLink[label]: + if ('isGroup' in el): + if (el['isGroup'] == False): + self._asmThere.append(el['solid'].Label) + if (el['isGroup']): + self.getSubPartsLink(el['solid'].Group, el['label']), + + return groupLink + + +def uniquePartsSort(labelParts): + + uniquePartsLabels = {} + + for el in labelParts: + for k in labelParts: + if (App.ActiveDocument.getObjectsByLabel(str(el))[0].Shape.isPartner(App.ActiveDocument.getObjectsByLabel(str(k))[0].Shape)): + + if uniquePartsLabels.get(el) == None: + uniquePartsLabels[el] = k + + sortedParts = {} + + for k, v in uniquePartsLabels.items(): + + if sortedParts.get(v) == None: + sortedParts[v] = [k] + else: + sortedParts[v].append(k) + + return sortedParts + + +def countForUniques(sortedParts): + countedParts = {} + for k in sortedParts: + countedParts[k] = len(sortedParts[k]) + return countedParts + + +def fillInBoMList(sheet, countedParts): + + a = 1 + + for label, count in countedParts.items(): + a += 1 + b = label + c = count + sheet.set('A' + str(a), str(a-1)) + sheet.set('B' + str(a), str(b)) + sheet.set('C' + str(a), str(c)) + + total_count = sum(countedParts.values()) + sheet.set('B'+str(a+1), 'Итого') + + sheet.set('C' + str(a+1), str(total_count)) + + +def run_BoM_list(): + createSpreadsheet() + sheet = App.ActiveDocument.getObject("BoM_List") + labelParts = SolidBodiesParcer()._asmThere + sortedParts = uniquePartsSort(labelParts) + countedParts = countForUniques(sortedParts) + fillInBoMList(sheet, countedParts) diff --git a/cg/freecad/Frames/DatumCommand.py b/cg/freecad/Frames/DatumCommand.py new file mode 100644 index 0000000..a7db4fd --- /dev/null +++ b/cg/freecad/Frames/DatumCommand.py @@ -0,0 +1,49 @@ +import FreeCAD +import FreeCADGui +from PySide import QtGui, QtCore + +class DatumTool: + """ + A tool for creating datums in existing models + """ + def __init__(self): + self.active = False + + def activate(self): + self.active = True + FreeCAD.Console.PrintMessage("Datum tool activatedn") + + def deactivate(self): + self.active = False + FreeCAD.Console.PrintMessage("Datum tool deactivatedn") + + def mousePressEvent(self, event): + if self.active: + # Create a datum at the position of the mouse click + pos = FreeCADGui.ActiveDocument.ActiveView.getCursorPos() + point = FreeCADGui.ActiveDocument.ActiveView.getPoint(pos) + datum = FreeCAD.ActiveDocument.addObject("Part::Datum", "Datum") + datum.Placement.Base = point + datum.ViewObject.ShapeColor = (0.0, 1.0, 0.0) # Set the color of the datum to green + FreeCAD.ActiveDocument.recompute() + +class DatumCommand: + """ + A command for activating and deactivating the datum tool + """ + def __init__(self): + self.tool = DatumTool() + + def Activated(self): + self.tool.activate() + FreeCADGui.ActiveDocument.ActiveView.addEventCallback("SoMouseButtonEvent", self.tool.mousePressEvent) + + def Deactivated(self): + self.tool.deactivate() + FreeCADGui.ActiveDocument.ActiveView.removeEventCallback("SoMouseButtonEvent", self.tool.mousePressEvent) + + def GetResources(self): + return {'Pixmap': 'path/to/icon.png', 'MenuText': 'Datum Tool', 'ToolTip': 'Creates datum elements in existing models'} + +# Add the command to the Draft Workbench +FreeCADGui.addCommand('DatumCommand', DatumCommand()) \ No newline at end of file diff --git a/cg/freecad/Frames/Frames.py b/cg/freecad/Frames/Frames.py index ea46662..1bc5544 100644 --- a/cg/freecad/Frames/Frames.py +++ b/cg/freecad/Frames/Frames.py @@ -11,9 +11,11 @@ # Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see . +from BoMList import run_BoM_list import FreeCAD import Tools -from scenarios.robossembler_freecad_export_scenario import RobossemblerFreeCadExportScenario +from usecases.asm4parser_usecase import Asm4StructureParseUseCase + if FreeCAD.GuiUp: import FreeCADGui @@ -297,6 +299,13 @@ def makeAllPartFrames(): def spawnFeatureFrameCreator(): ffpanel = FeatureFramePanel() FreeCADGui.Control.showDialog(ffpanel) + +def BoMGeneration(part): + + + obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython", + "FeatureFrame") + print(obj) ################################################################### @@ -306,22 +315,26 @@ uidir = os.path.join(FreeCAD.getUserAppDataDir(), "Mod", __workbenchname__, "UI") icondir = os.path.join(uidir, "icons") -Tools.spawnClassCommand("FrameCommand", - makeFrame, - {"Pixmap": str(os.path.join(icondir, "frame.svg")), - "MenuText": "Make a free frame", - "ToolTip": "Make a freestanding reference frame."}) - -Tools.spawnClassCommand("ASM4StructureParsing", - RobossemblerFreeCadExportScenario().call, - {"Pixmap": str(os.path.join(icondir, "assembly4.svg")), - "MenuText": "Make a ASM4 parsing", - "ToolTip": "Make a ASM4 1"}) -Tools.spawnClassCommand("SelectedPartFrameCommand", - makeSelectedPartFrames, - {"Pixmap": str(os.path.join(icondir, "partframe.svg")), - "MenuText": "selected parts frames", - "ToolTip": "Make selected parts frames."}) +# Tools.spawnClassCommand("FrameCommand", +# makeFrame, +# {"Pixmap": str(os.path.join(icondir, "frame.svg")), +# "MenuText": "Make a free frame", +# "ToolTip": "Make a freestanding reference frame."}) +Tools.spawnClassCommand("BoMGeneration", + run_BoM_list, + {"Pixmap": str(os.path.join(icondir, "BoMList.svg")), + "MenuText": "Generate Bill of Materials", + "ToolTip": "Press the button to create big BoM"}) +# Tools.spawnClassCommand("ASM4StructureParsing", +# Asm4StructureParseUseCase().initParse, +# {"Pixmap": str(os.path.join(icondir, "assembly4.svg")), +# "MenuText": "Make a ASM4 parsing", +# "ToolTip": "Make a ASM4 1"}) +# Tools.spawnClassCommand("SelectedPartFrameCommand", +# makeSelectedPartFrames, +# {"Pixmap": str(os.path.join(icondir, "partframe.svg")), +# "MenuText": "selected parts frames", +# "ToolTip": "Make selected parts frames."}) Tools.spawnClassCommand("AllPartFramesCommand", makeAllPartFrames, diff --git a/cg/freecad/Frames/InitGui.py b/cg/freecad/Frames/InitGui.py index 536ec0a..4ed400b 100644 --- a/cg/freecad/Frames/InitGui.py +++ b/cg/freecad/Frames/InitGui.py @@ -36,10 +36,13 @@ class Frames(Workbench): """This function is executed when FreeCAD starts""" import Frames self.framecommands = [ + "BoMGeneration", "FrameCommand", + "SelectedPartFrameCommand", "AllPartFramesCommand", - "FeatureFrameCommand" + "FeatureFrameCommand", + ] self.toolcommands = [ "ExportPlacementAndPropertiesCommand", diff --git a/cg/freecad/Frames/Sheet_addition_test.py b/cg/freecad/Frames/Sheet_addition_test.py new file mode 100644 index 0000000..9b645b0 --- /dev/null +++ b/cg/freecad/Frames/Sheet_addition_test.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Sheet_addition_test.py +# +# Copyright 2015 Ulrich Brammer +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# + +theDoc = App.newDocument("SheetTest") +App.setActiveDocument("SheetTest") +#App.activeDocument("SheetTest") + + +p=App.ParamGet("User parameter:BaseApp/Preferences/General") +thePath = p.GetString('FileOpenSavePath') + + +mySheet = theDoc.addObject('Spreadsheet::Sheet','Spreadsheet') + +mySheet.set('A1', '1') +mySheet.set('A2', '2') +theDoc.recompute() +mySheet.set('A3', '=A1+A2') +mySheet.setPosition('A4') +#theDoc.saveAs("/home/ulrich/FreeCAD/Spreadsheet/Sheet_4.fcstd") +theDoc.saveAs(thePath + '/Sheet_5.fcstd') +mySheet.set('A4', '=A3') +theDoc.recompute() + +if mySheet.State == ['Invalid']: + print "Invalid Spreadsheet" +else: + print "No error found" + + + + +def main(): + + return 0 + +if __name__ == '__main__': + main() + diff --git a/cg/freecad/Frames/UI/icons/BoMList.svg b/cg/freecad/Frames/UI/icons/BoMList.svg new file mode 100644 index 0000000..caf5d4f --- /dev/null +++ b/cg/freecad/Frames/UI/icons/BoMList.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + ... + + + + + + Parts + + + + List + + + \ No newline at end of file diff --git a/cg/freecad/Frames/UI/icons/auxDatum.svg b/cg/freecad/Frames/UI/icons/auxDatum.svg new file mode 100644 index 0000000..c1ca8d8 --- /dev/null +++ b/cg/freecad/Frames/UI/icons/auxDatum.svg @@ -0,0 +1,240 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + [wmayer] + + + Sketcher_Sketch + 2011-10-10 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_Sketch.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cg/freecad/Frames/box.py b/cg/freecad/Frames/box.py new file mode 100644 index 0000000..e69de29 diff --git a/cg/freecad/Frames/newLabel.py b/cg/freecad/Frames/newLabel.py new file mode 100644 index 0000000..761ed81 --- /dev/null +++ b/cg/freecad/Frames/newLabel.py @@ -0,0 +1,17 @@ +import FreeCAD as App + + +def is_object_solid(obj): + """If obj is solid return True""" + if not isinstance(obj, FreeCAD.DocumentObject): + return False + + if not hasattr(obj, 'Shape'): + return False + + return obj.Shape.isClosed() + +def addProperty(): + for obj in App.ActiveDocument().Objects: + if is_object_solid(obj): + \ No newline at end of file diff --git a/cg/freecad/Frames/testSpread2.py b/cg/freecad/Frames/testSpread2.py new file mode 100644 index 0000000..a2953f0 --- /dev/null +++ b/cg/freecad/Frames/testSpread2.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# faculSpread.py +# +# Copyright 2015 ulrich1a +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# + + + +p=App.ParamGet("User parameter:BaseApp/Preferences/General") +thePath = p.GetString('FileOpenSavePath') + + +theDoc = App.newDocument("SheetTest2") +App.setActiveDocument("SheetTest2") + +mySheet = theDoc.addObject('Spreadsheet::Sheet','Spreadsheet') + + +col = 'A' +mySheet.setColumnWidth(col, 125) +theDoc.recompute() + +startRow = 3 + +for i in range(20): + print "i: ", i + if i == 0: + print "setting start" + mySheet.set(col+str(startRow+i), '1') + #App.activeDocument().recompute() + else: + mySheet.set(col + str(startRow+i), '='+col+str(startRow+i-1)+'*'+str(i)) + #App.activeDocument().recompute() + +#mySheet.show() +#App.activeDocument().recompute() + +mySheet.set('A1', 'This is the very long Titel, which needs more space!') +mySheet.set('A2', '=A1') + +# mySheet.setDisplayUnit('A10:A11', 'mm') + +theDoc.recompute() +mySheet.setAlias('A1', 'Title') +mySheet.set('D1', '=Title') + +theDoc.recompute() + +theDoc.saveAs(thePath + '/Sheet_6.fcstd') +mySheet.set('B4','=A4') +mySheet.set('E1', '=D1') +theDoc.recompute() +mySheet.setAlias('A2', 'huhu') +theDoc.recompute() + +if mySheet.State == ['Invalid']: + print "Invalid Spreadsheet" +else: + print "No error found" diff --git a/cg/freecad/Frames/usecases/asm4parser_usecase.py b/cg/freecad/Frames/usecases/asm4parser_usecase.py new file mode 100644 index 0000000..f732d1b --- /dev/null +++ b/cg/freecad/Frames/usecases/asm4parser_usecase.py @@ -0,0 +1,53 @@ +import FreeCAD as App + +class Asm4StructureParseUseCase: + _parts = [] + _label = [] + + def getSubPartsLabel(self, group): + groupLabel = [] + for el in group: + if str(el) == '': + groupLabel.append(el.Label) + return groupLabel + + def parseLabel(self, nextGroup, label, level=2, nextGroupParse=0): + if nextGroup.__len__() == nextGroupParse: + return + else: + groupParts = [] + + for el in nextGroup: + if str(el) == '': + groupParts.append(el) + + for el in groupParts: + if str(el) == '': + label.append({ + "level": level, + "attachedTo": el.AttachedTo.split('#'), + "label": el.Label, + "axis": self.getSubPartsLabel(el.Group) + }) + + def initParse(self): + + model = App.ActiveDocument.RootObjects[1] + self._label.append({ + "level": 1, + "attachedTo": "Parent Assembly", + "label": model.Label, + "axis": self.getSubPartsLabel(model.Group) + }) + for parent in model.Group: + if str(parent) == '': + self._label.append({ + "level": 1, + "attachedTo": parent.AttachedTo.split('#'), + "label": parent.Label, + "axis": self.getSubPartsLabel(parent.Group) + }) + print(self._label) + + + \ No newline at end of file -- 2.49.0 From 4dd0f948edaa6cd479a8e6b57d6a9135189e2e90 Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Wed, 31 May 2023 08:59:11 +0300 Subject: [PATCH 13/27] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=B0=D0=BB=D0=B3=D0=BE=D1=80=D0=B8=D1=82=D0=BC?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=BE=D0=B7=D0=B8=D1=86=D0=B8=D0=B9=20=D0=B7?= =?UTF-8?q?=D0=B0=D1=85=D0=B2=D0=B0=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cg/freecad/Frames/Tools.py | 4 + cg/freecad/Frames/normalEstimator | 0 cg/freecad/Frames/poseGenerator.py | 132 +++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 cg/freecad/Frames/normalEstimator create mode 100644 cg/freecad/Frames/poseGenerator.py diff --git a/cg/freecad/Frames/Tools.py b/cg/freecad/Frames/Tools.py index 484706d..d6ce47b 100644 --- a/cg/freecad/Frames/Tools.py +++ b/cg/freecad/Frames/Tools.py @@ -183,11 +183,15 @@ def getLocalPartProps(obj): "label": obj.Label, "parent_label": obj_parent_label, "placement": placement2pose(old_placement), + # "grip properties": { "grip open": obj.GripOpen, "grip depth": obj.GripDepth, "grip width": obj.GripWidth} # "boundingbox": boundingBox2list(obj.Shape.BoundBox), # "volume": obj.Shape.Volume*1e-9, # "centerofmass": vector2list(obj.Shape.CenterOfMass), # "principalproperties": principalProperties2dict(obj.Shape.PrincipalProperties) } + if obj.GripOpen != None: + partprops["grip properties"] = { "grip open": obj.GripOpen, "grip depth": obj.GripDepth, "grip width": obj.GripWidth} + # obj.Placement = old_placement return partprops diff --git a/cg/freecad/Frames/normalEstimator b/cg/freecad/Frames/normalEstimator new file mode 100644 index 0000000..e69de29 diff --git a/cg/freecad/Frames/poseGenerator.py b/cg/freecad/Frames/poseGenerator.py new file mode 100644 index 0000000..75eb2f2 --- /dev/null +++ b/cg/freecad/Frames/poseGenerator.py @@ -0,0 +1,132 @@ +import FreeCAD as App +import FreeCADGui as Gui + +# App.newDocument() +doc = App.ActiveDocument + + +print('задайте начальную точку захватной зоны. ') +print('Учтите, что ось X должна быть направлена вдоль направления раскрытия пальцев') +print('Ось Z должна быть направлена в противоположную сторону от направления кончика пальца захвата') +lcsname = input('Введите название начальной точки' + "\n") + +lcs = doc.getObject(lcsname) + +def poseGenerator(lcs): + + box = doc.addObject("Part::Box", "gripSpace") + box.Length = 62 #раскрытие + box.Width = 10 #ширина пальца + box.Height = 40 #глубина + + box.Placement = lcs.Placement + #box.Transparency = 80 #не работает, хз почему + + + + #есть смысл создавать привязку прямо здесь же. благодаря параметризации, при подстройке куба все точки сместятся как надо + gripPose = App.ActiveDocument.addObject('PartDesign::CoordinateSystem', 'GripPose') + gripPose.Support = box + gripPose.MapMode = 'ObjectXY' + gripPose.AttachmentOffset.Base = [box.Length/2, box.Width/2, 0] #здесь должна быть активная привязка, не просто значения координат + + gripPose.addProperty("App::PropertyFloat", "GripOpen") + gripPose.addProperty("App::PropertyFloat", "GripDepth") + gripPose.addProperty("App::PropertyFloat", "GripWidth") + gripPose.GripOpen = box.Length.Value + gripPose.GripWidth = box.Width.Value + gripPose.GripDepth = box.Height.Value + + + + + #нужно создавать эту хрень внутри Part, а не внутри главного документа. сбиваются привязки !!! + + + print('Установите захватную зону вручную, растянув обьект GripSpace') + doc.recompute() + + #вроде как работает + + +poseGenerator(lcs) + +# def collectAndExportProps(): + + + + +# This class logs any mouse button events. As the registered callback function fires twice for 'down' and +# 'up' events we need a boolean flag to handle this. + +# todo: +# добавить включатель-выключатель +# должно срабатывать на задание одной позиции +# научить считать нормаль поверхности в указанной точке +# добавить коррекцию позиции +# 1 выбираем первую грань +# 2 сохраняем позицию курсора +# 3 в данной позиции считаем нормаль поверхности, выравниваем вокруг нее ось x + +# 3 выбираем вторую грань +# расстояние до нее должно стать длиной параллелепипеда +# ширина задается вручную +# создаем ск, которая смещена на половину длины прямоугольника и на половину ширины +# полученная штука - центр расстояния между кончиками пальцев +# в эту штуку можно вставить параметризованный захват и проверить на коллизии + + +##### +#все хуйня +#давай по новой + +#создаем опорную точку в asm4 (взять оттуда генератор точек?) +#по ней строим от угла прямоугольник +#используя asm4, создаем ориентированный обьект и присваиваем ему свойства, равные размерам объекта + +# class ViewObserver: +# def __init__(self, view): +# self.view = view + + + + + +# def logPosition(self, info): +# # def turnOn(): +# # switch = 'Run' +# # while True: +# # switch = input('Tell me when to stop') +# # if switch != 'Run': +# # print('MacroStop') +# # break +# # return (switch) + + + +# down = (info["State"] == "DOWN") +# pos = info["Position"] +# if (down): +# App.Console.PrintMessage( +# "Clicked on position: ("+str(pos[0])+", "+str(pos[1])+")\n") +# pnt = self.view.getPoint(pos) +# App.Console.PrintMessage("World coordinates: " + str(pnt[0].x) + "\n") +# info = self.view.getObjectInfo(pos) +# print(info['x']) +# print(info['y']) +# print(info['z']) +# from random import randrange +# id = str('yo' + str(randrange(0, 10000))) +# # App.ActiveDocument.addObject("Part::Box",id) +# print(id) +# # App.ActiveDocument.getObject(id).Placement = App.Placement(App.Vector(info['x'],info['y'],info['z']),App.Rotation(App.Vector(0.00,0.00,1.00),0.00)) + +# # App.ActiveDocument.getObject(id).Width = '1.00 mm' +# # App.ActiveDocument.getObject(id).Length = '1.00 mm' +# # App.ActiveDocument.getObject(id).Height = '1.00 mm' +# # App.Console.PrintMessage("Object info: " + str(info) + "\n") + + + +# o = ViewObserver(v) +# c = v.addEventCallback("SoMouseButtonEvent", o.logPosition) -- 2.49.0 From 55ad261863de14ae6849a377054d17d3d69c5392 Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Wed, 31 May 2023 09:06:00 +0300 Subject: [PATCH 14/27] =?UTF-8?q?=D1=83=D0=B4=D0=B0=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BC=D1=83=D1=81=D0=BE=D1=80=20=D0=B8=D0=B7=20=D1=84=D0=B0?= =?UTF-8?q?=D0=B9=D0=BB=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cg/freecad/Frames/poseGenerator.py | 81 +----------------------------- 1 file changed, 1 insertion(+), 80 deletions(-) diff --git a/cg/freecad/Frames/poseGenerator.py b/cg/freecad/Frames/poseGenerator.py index 75eb2f2..ad51a2f 100644 --- a/cg/freecad/Frames/poseGenerator.py +++ b/cg/freecad/Frames/poseGenerator.py @@ -40,7 +40,7 @@ def poseGenerator(lcs): - #нужно создавать эту хрень внутри Part, а не внутри главного документа. сбиваются привязки !!! + #нужно создавать обьект внутри Part, а не внутри главного документа. сбиваются привязки !!! print('Установите захватную зону вручную, растянув обьект GripSpace') @@ -51,82 +51,3 @@ def poseGenerator(lcs): poseGenerator(lcs) -# def collectAndExportProps(): - - - - -# This class logs any mouse button events. As the registered callback function fires twice for 'down' and -# 'up' events we need a boolean flag to handle this. - -# todo: -# добавить включатель-выключатель -# должно срабатывать на задание одной позиции -# научить считать нормаль поверхности в указанной точке -# добавить коррекцию позиции -# 1 выбираем первую грань -# 2 сохраняем позицию курсора -# 3 в данной позиции считаем нормаль поверхности, выравниваем вокруг нее ось x - -# 3 выбираем вторую грань -# расстояние до нее должно стать длиной параллелепипеда -# ширина задается вручную -# создаем ск, которая смещена на половину длины прямоугольника и на половину ширины -# полученная штука - центр расстояния между кончиками пальцев -# в эту штуку можно вставить параметризованный захват и проверить на коллизии - - -##### -#все хуйня -#давай по новой - -#создаем опорную точку в asm4 (взять оттуда генератор точек?) -#по ней строим от угла прямоугольник -#используя asm4, создаем ориентированный обьект и присваиваем ему свойства, равные размерам объекта - -# class ViewObserver: -# def __init__(self, view): -# self.view = view - - - - - -# def logPosition(self, info): -# # def turnOn(): -# # switch = 'Run' -# # while True: -# # switch = input('Tell me when to stop') -# # if switch != 'Run': -# # print('MacroStop') -# # break -# # return (switch) - - - -# down = (info["State"] == "DOWN") -# pos = info["Position"] -# if (down): -# App.Console.PrintMessage( -# "Clicked on position: ("+str(pos[0])+", "+str(pos[1])+")\n") -# pnt = self.view.getPoint(pos) -# App.Console.PrintMessage("World coordinates: " + str(pnt[0].x) + "\n") -# info = self.view.getObjectInfo(pos) -# print(info['x']) -# print(info['y']) -# print(info['z']) -# from random import randrange -# id = str('yo' + str(randrange(0, 10000))) -# # App.ActiveDocument.addObject("Part::Box",id) -# print(id) -# # App.ActiveDocument.getObject(id).Placement = App.Placement(App.Vector(info['x'],info['y'],info['z']),App.Rotation(App.Vector(0.00,0.00,1.00),0.00)) - -# # App.ActiveDocument.getObject(id).Width = '1.00 mm' -# # App.ActiveDocument.getObject(id).Length = '1.00 mm' -# # App.ActiveDocument.getObject(id).Height = '1.00 mm' -# # App.Console.PrintMessage("Object info: " + str(info) + "\n") - - - -# o = ViewObserver(v) -# c = v.addEventCallback("SoMouseButtonEvent", o.logPosition) -- 2.49.0 From bec4f0b469f16210de7d7b8891574023e4faacd5 Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Fri, 2 Jun 2023 13:06:25 +0300 Subject: [PATCH 15/27] Changes to be committed: modified: cg/freecad/Frames/Tools.py new file: cg/freecad/Frames/markupEntities.py --- cg/freecad/Frames/Tools.py | 10 +- cg/freecad/Frames/markupEntities.py | 137 ++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 cg/freecad/Frames/markupEntities.py diff --git a/cg/freecad/Frames/Tools.py b/cg/freecad/Frames/Tools.py index d6ce47b..0a420e2 100644 --- a/cg/freecad/Frames/Tools.py +++ b/cg/freecad/Frames/Tools.py @@ -189,9 +189,17 @@ def getLocalPartProps(obj): # "centerofmass": vector2list(obj.Shape.CenterOfMass), # "principalproperties": principalProperties2dict(obj.Shape.PrincipalProperties) } - if obj.GripOpen != None: + + if obj.Type == 'grip': partprops["grip properties"] = { "grip open": obj.GripOpen, "grip depth": obj.GripDepth, "grip width": obj.GripWidth} + elif obj.Type == 'area': + partprops["area dim"] = { } #свойства, описывающие размер + + elif obj.Type == 'vol': + partprops["vol dim"] = { } #свойства, описывающие размер области + + # obj.Placement = old_placement return partprops diff --git a/cg/freecad/Frames/markupEntities.py b/cg/freecad/Frames/markupEntities.py new file mode 100644 index 0000000..3370d15 --- /dev/null +++ b/cg/freecad/Frames/markupEntities.py @@ -0,0 +1,137 @@ +import FreeCAD as App +import FreeCADGui as Gui + +# App.newDocument() +doc = App.ActiveDocument + + +### +#прога для общей генерации всяких разметок +#сюда будем добавлять функции для генерации точек хороших и разных, а так же - других обьектов +#список хреней: +#генерация захватов +#позиции размещения и базирования +#позиции соединения + +print('lcs - локальная система координат') +print('grip - позиция захвата') +print('area - плоскость') +print('vol - зона') +print('joint - соединение') +entityType = input('Введите тип получаемого обьекта') + +def create(entityType): + + if entityType == 'grip': + print('задайте начальную точку захватной зоны. ') + print('Учтите, что ось X должна быть направлена вдоль направления раскрытия пальцев') + print('Ось Z должна быть направлена в противоположную сторону от направления кончика пальца захвата') + objname = input('Введите название начальной точки' + "\n") + obj = doc.getObject(objname) + + + poseGenerator(obj) + + + + elif entityType == 'area': + print('задайте плоскость') + objname = input('Введите название плоскости'+ "\n") + obj = doc.getObject(objname) + + areaProps(obj) + + elif entityType == 'vol': + print('задайте обьем') + objname = input('Введите название обьема'+ "\n") + obj = doc.getObject(objname) + + volProps(obj) + + elif entityType == 'joint': + print('Задайте позицию соединения') + objname = input('Введите название соединения'+ "\n") + part1 = input('укажите название первой детали'+ "\n") + part2 = input('укажите название второй детали'+ "\n") + + jointGenerator(objname, part1, part2) + + obj.addProperty("App::PropertyString", "Type").Type = entityType #запишем тип хрени в свойства хрени + + + +## заглушки функций на случай, если что-то придумаю полезное для них + + +def areaProps(area): + #здесь нужно отметить свойства зоны + #в принципе, Placement и размеры тут есть, больше ничего особо не нужно + #добавить характеристику entityType + + print(area.Label) + +def volProps(vol): + #желательно указать координаты, габариты, позицию привязки + #но думаю, что это все уже есть + print(vol.Label) + + +def jointGenerator(jointName, part1, part2): + + #получаем относительные координаты для первой детали и для второй детали + #создаем две сущности - точка входа и точка выхода (??????) + print(jointName.Label) + print(part1.Label) + print(part2.Label) + + + + + + + +# Эта функция работает нормально + + +def poseGenerator(lcs): + + box = doc.addObject("Part::Box", "gripSpace") + box.Length = 62 #раскрытие + box.Width = 10 #ширина пальца + box.Height = 40 #глубина + + box.Placement = lcs.Placement + + + #есть смысл создавать привязку прямо здесь же. благодаря параметризации, при подстройке куба все точки сместятся как надо + gripPose = App.ActiveDocument.addObject('PartDesign::CoordinateSystem', 'GripPose') + gripPose.Support = box + gripPose.positionBySupport() + gripPose.MapMode = 'ObjectXY' + gripPose.AttachmentOffset.Base = [str(box.Label) + '.Length' + '/2', str(box.Label) + '.Length'+ '/2', 0] #здесь должна быть активная привязка, не просто значения координат + + gripPose.addProperty("App::PropertyFloat", "GripOpen") + gripPose.addProperty("App::PropertyFloat", "GripDepth") + gripPose.addProperty("App::PropertyFloat", "GripWidth") + gripPose.setExpression('GripDepth', str(box.Label) + '.Height') + gripPose.setExpression('GripOpen', str(box.Label) + '.Length') + gripPose.setExpression('GripWidth', str(box.Label) + '.Width') + + + + + + #нужно создавать эту хрень внутри Part, а не внутри главного документа. сбиваются привязки !!! + + + print('Установите захватную зону вручную, растянув обьект GripSpace') + doc.recompute() + + +#теперь нужно производить экспорт +#он делается через Tools.py + + +create(entityType) + + -- 2.49.0 From af9c849d07b13908552b8d8da04e7c96de06e2d6 Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Sat, 10 Jun 2023 22:56:53 +0300 Subject: [PATCH 16/27] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=81=D0=BA=D1=80=D0=B8=D0=BF=D1=82=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D1=80=D0=B0=D0=B7=D0=BE=D0=B2=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D1=8D=D0=BA=D1=81=D0=BF=D0=BE=D1=80=D1=82=D0=B0=20=D0=B2=D1=81?= =?UTF-8?q?=D0=B5=D1=85=20=D0=B2=D1=81=D0=BF=D0=BE=D0=BC=D0=BE=D0=B3=D0=B0?= =?UTF-8?q?=D1=82=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D1=85=20=D0=BE=D0=B1=D1=8C?= =?UTF-8?q?=D0=B5=D0=BA=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cg/freecad/Frames/ImportExportEntities.py | 105 ++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 cg/freecad/Frames/ImportExportEntities.py diff --git a/cg/freecad/Frames/ImportExportEntities.py b/cg/freecad/Frames/ImportExportEntities.py new file mode 100644 index 0000000..cc51f72 --- /dev/null +++ b/cg/freecad/Frames/ImportExportEntities.py @@ -0,0 +1,105 @@ +import os +import json +import FreeCAD +import Tools + +def export_coordinate_systems(): + # Получение активного документа FreeCAD + doc = FreeCAD.ActiveDocument + if not doc: + raise ValueError("Нет активного документа FreeCAD.") + + # Получение имени активного документа + doc_name = doc.Name + + + # Получение пути к папке и имя файла активного документа + folder_path, file_name = os.path.split(doc.FileName) + + # Создание папки для экспорта, если она не существует + output_folder = os.path.join(folder_path, 'entities') + if not os.path.exists(output_folder): + os.makedirs(output_folder) + + + + # Создание копии активного документа + # надо сохраняться, а не копироваться + + doc.save() + + + + # Получение списка объектов документа + objects = doc.Objects + + # Обход объектов для сохранения локальных систем координат + for obj in objects: + if obj.TypeId == 'PartDesign::CoordinateSystem': + + partprops = Tools.getLocalPartProps(obj) + + + output_file_path = os.path.join(output_folder, f"{obj.Label}.json") + with open(output_file_path, "w", encoding="utf8") as propfile: + json.dump(partprops, propfile, indent=1, separators=(',', ': ')) + + + print("Экспорт обьектов завершен.") + +export_coordinate_systems() + + +#работает + + +def import_coordinate_systems(): + # Получение активного документа FreeCAD + doc = FreeCAD.ActiveDocument + if not doc: + raise ValueError("Нет активного документа FreeCAD.") + + # Получение имени активного документа + doc_name = doc.Name + + # Получение пути к папке активного документа + folder_path, _ = os.path.split(doc.FileName) + + # Получение пути к папке с файлами JSON + json_folder_path = os.path.join(folder_path, doc_name) + + # Проверка существования папки с файлами JSON + if not os.path.exists(json_folder_path): + raise ValueError(f"Папка {json_folder_path} не существует.") + + # Получение списка файлов JSON в папке + json_files = [f for f in os.listdir(json_folder_path) if f.endswith(".json")] + + # Обход файлов JSON для создания локальных систем координат + for json_file in json_files: + json_file_path = os.path.join(json_folder_path, json_file) + with open(json_file_path, "r") as file: + json_data = json.load(file) + + # Извлечение информации о локальной системе координат из файла JSON + name = json_data.get("Name") + x = json_data.get("X") + y = json_data.get("Y") + z = json_data.get("Z") + + # Создание локальной системы координат + placement = FreeCAD.Placement() + placement.Base = FreeCAD.Vector(x, y, z) + part = doc.addObject("Part::Feature", name) + part.Placement = placement + + # Добавление дополнительных свойств из файла JSON + for key, value in json_data.items(): + if key not in ["Name", "X", "Y", "Z"]: + setattr(part, key, value) + + print("Импорт локальных систем координат завершен.") + +# Пример использования +#import_coordinate_systems() + -- 2.49.0 From a8573a2dea008aa1b71fe58e5b5d62d1c4f1aee0 Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Sat, 10 Jun 2023 22:58:44 +0300 Subject: [PATCH 17/27] =?UTF-8?q?=D0=97=D0=B0=D0=BA=D0=BE=D0=BC=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=B8=D0=BB=20=D0=B0=D0=BB=D0=B3=D0=BE=D1=80?= =?UTF-8?q?=D0=B8=D1=82=D0=BC=20=D0=B8=D0=BC=D0=BF=D0=BE=D1=80=D1=82=D0=B0?= =?UTF-8?q?=20=D0=B2=D1=81=D0=BF=D0=BE=D0=BC=D0=BE=D0=B3=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D1=8B=D1=85=20=D0=BE=D0=B1=D1=8A=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cg/freecad/Frames/ImportExportEntities.py | 76 +++++++++++------------ 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/cg/freecad/Frames/ImportExportEntities.py b/cg/freecad/Frames/ImportExportEntities.py index cc51f72..73f9aaa 100644 --- a/cg/freecad/Frames/ImportExportEntities.py +++ b/cg/freecad/Frames/ImportExportEntities.py @@ -53,53 +53,53 @@ export_coordinate_systems() #работает -def import_coordinate_systems(): - # Получение активного документа FreeCAD - doc = FreeCAD.ActiveDocument - if not doc: - raise ValueError("Нет активного документа FreeCAD.") +# def import_coordinate_systems(): +# # Получение активного документа FreeCAD +# doc = FreeCAD.ActiveDocument +# if not doc: +# raise ValueError("Нет активного документа FreeCAD.") - # Получение имени активного документа - doc_name = doc.Name +# # Получение имени активного документа +# doc_name = doc.Name - # Получение пути к папке активного документа - folder_path, _ = os.path.split(doc.FileName) +# # Получение пути к папке активного документа +# folder_path, _ = os.path.split(doc.FileName) - # Получение пути к папке с файлами JSON - json_folder_path = os.path.join(folder_path, doc_name) +# # Получение пути к папке с файлами JSON +# json_folder_path = os.path.join(folder_path, doc_name) - # Проверка существования папки с файлами JSON - if not os.path.exists(json_folder_path): - raise ValueError(f"Папка {json_folder_path} не существует.") +# # Проверка существования папки с файлами JSON +# if not os.path.exists(json_folder_path): +# raise ValueError(f"Папка {json_folder_path} не существует.") - # Получение списка файлов JSON в папке - json_files = [f for f in os.listdir(json_folder_path) if f.endswith(".json")] +# # Получение списка файлов JSON в папке +# json_files = [f for f in os.listdir(json_folder_path) if f.endswith(".json")] - # Обход файлов JSON для создания локальных систем координат - for json_file in json_files: - json_file_path = os.path.join(json_folder_path, json_file) - with open(json_file_path, "r") as file: - json_data = json.load(file) +# # Обход файлов JSON для создания локальных систем координат +# for json_file in json_files: +# json_file_path = os.path.join(json_folder_path, json_file) +# with open(json_file_path, "r") as file: +# json_data = json.load(file) - # Извлечение информации о локальной системе координат из файла JSON - name = json_data.get("Name") - x = json_data.get("X") - y = json_data.get("Y") - z = json_data.get("Z") +# # Извлечение информации о локальной системе координат из файла JSON +# name = json_data.get("Name") +# x = json_data.get("X") +# y = json_data.get("Y") +# z = json_data.get("Z") - # Создание локальной системы координат - placement = FreeCAD.Placement() - placement.Base = FreeCAD.Vector(x, y, z) - part = doc.addObject("Part::Feature", name) - part.Placement = placement +# # Создание локальной системы координат +# placement = FreeCAD.Placement() +# placement.Base = FreeCAD.Vector(x, y, z) +# part = doc.addObject("Part::Feature", name) +# part.Placement = placement - # Добавление дополнительных свойств из файла JSON - for key, value in json_data.items(): - if key not in ["Name", "X", "Y", "Z"]: - setattr(part, key, value) +# # Добавление дополнительных свойств из файла JSON +# for key, value in json_data.items(): +# if key not in ["Name", "X", "Y", "Z"]: +# setattr(part, key, value) - print("Импорт локальных систем координат завершен.") +# print("Импорт локальных систем координат завершен.") -# Пример использования -#import_coordinate_systems() + +# #import_coordinate_systems() -- 2.49.0 From e54b5e853b0310e306038bf4d6932fef94c9bf05 Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Sat, 10 Jun 2023 23:06:29 +0300 Subject: [PATCH 18/27] =?UTF-8?q?=D0=92=D1=8B=D0=BD=D0=B5=D1=81=20=D0=BC?= =?UTF-8?q?=D1=83=D1=81=D0=BE=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cg/freecad/Frames/markupEntities.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/cg/freecad/Frames/markupEntities.py b/cg/freecad/Frames/markupEntities.py index 3370d15..4a25d8a 100644 --- a/cg/freecad/Frames/markupEntities.py +++ b/cg/freecad/Frames/markupEntities.py @@ -1,17 +1,10 @@ import FreeCAD as App import FreeCADGui as Gui -# App.newDocument() doc = App.ActiveDocument -### -#прога для общей генерации всяких разметок -#сюда будем добавлять функции для генерации точек хороших и разных, а так же - других обьектов -#список хреней: -#генерация захватов -#позиции размещения и базирования -#позиции соединения + print('lcs - локальная система координат') print('grip - позиция захвата') @@ -56,7 +49,7 @@ def create(entityType): jointGenerator(objname, part1, part2) - obj.addProperty("App::PropertyString", "Type").Type = entityType #запишем тип хрени в свойства хрени + obj.addProperty("App::PropertyString", "Type").Type = entityType @@ -80,6 +73,7 @@ def jointGenerator(jointName, part1, part2): #получаем относительные координаты для первой детали и для второй детали #создаем две сущности - точка входа и точка выхода (??????) + #в работе сейчас print(jointName.Label) print(part1.Label) print(part2.Label) @@ -121,7 +115,7 @@ def poseGenerator(lcs): - #нужно создавать эту хрень внутри Part, а не внутри главного документа. сбиваются привязки !!! + #нужно создавать внутри Part, а не внутри главного документа. сбиваются привязки !!! print('Установите захватную зону вручную, растянув обьект GripSpace') @@ -129,7 +123,7 @@ def poseGenerator(lcs): #теперь нужно производить экспорт -#он делается через Tools.py +#он делается через Tools.py или через импорт-экспорт create(entityType) -- 2.49.0 From 6983c833f2c08364c195ba9b42f3dc5ab42d120d Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Tue, 27 Jun 2023 16:41:09 +0300 Subject: [PATCH 19/27] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20modelExport,=20=D0=B3=D0=BE=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=D1=8F=D1=89=D0=B8=D0=B9=20=D1=84=D1=80=D0=B8=D0=BA=D0=B0=D0=B4?= =?UTF-8?q?-=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D1=8C=20=D0=BA=20=D1=8D=D0=BA?= =?UTF-8?q?=D1=81=D0=BF=D0=BE=D1=80=D1=82=D1=83=20(=D0=BF=D0=BE=D0=BA?= =?UTF-8?q?=D0=B0=20=D1=82=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20=D0=BC=D0=B0?= =?UTF-8?q?=D1=82=D0=B5=D1=80=D0=B8=D0=B0=D0=BB=D1=8B)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cg/freecad/Frames/MetaObj.py | 0 cg/freecad/Frames/materialOperator.py | 84 ++++++++++ cg/freecad/Frames/material_requirements.JSON | 8 + cg/freecad/Frames/modelExport.py | 155 +++++++++++++++++++ cg/freecad/Frames/normalEstimator | 0 5 files changed, 247 insertions(+) delete mode 100644 cg/freecad/Frames/MetaObj.py create mode 100644 cg/freecad/Frames/materialOperator.py create mode 100644 cg/freecad/Frames/material_requirements.JSON create mode 100644 cg/freecad/Frames/modelExport.py delete mode 100644 cg/freecad/Frames/normalEstimator diff --git a/cg/freecad/Frames/MetaObj.py b/cg/freecad/Frames/MetaObj.py deleted file mode 100644 index e69de29..0000000 diff --git a/cg/freecad/Frames/materialOperator.py b/cg/freecad/Frames/materialOperator.py new file mode 100644 index 0000000..3f8044a --- /dev/null +++ b/cg/freecad/Frames/materialOperator.py @@ -0,0 +1,84 @@ +import FreeCAD as App +import FreeCADGui as Gui + + + +#создаем вкладку с материалами + +#концептуально нам нужен лишь обьект, в котором перечислены тела, связанные с материалами + +#создаем объект +#указываем референсный материал +#указываем референсное тело +#добавляем все остальные тела, которые имеют такой же цвет + + +#план-минимум +''' +План: + +Сделать приемлемым работу FEM: +1. Проверка на уникальность и повторяемость материалов +2. Проверка на детали, которые не имеют материала +3. Проверка материалов на соответствие требованиям +4. Назначение деталям цвета, соответствующего материалам +5. Черный материал - тем, кто без материала +6. BoM считает плотности деталей и пишет массы. Здесь должна быть функция, считающая массу детали и записывающая ее в свойства детали + + +''' + +doc = App.ActiveDocument +obj = doc.Objects + + +def partsCheckup(doc): + for obj in + +material = doc.addObject('App::Document') + +doc.addObject("App::DocumentObjectGroup", "Materials") #создаем папку с материалами + +group.addObject(material) + +#открываем и выбираем MaterialEditor + +import MaterialEditor +MaterialEditor.openEditor() + +doc.addObject('App::') +''' +Нам нужно указать материал в обьекте +''' + +# Создаем папку "Материалы" +material_group = doc.addObject("App::DocumentObjectGroup", "Materials") + +''' +нужно импортировать материалы +самое простое - взять целиком блок fem и работать с этим инструментом +в fem они вставляются тупо строкой +мы можем считать, что материал задан a.k.a json или что-то такое + + +''' + +# Создаем файл материалов fcmat +material_file = "путь_к_файлу.fcmat" # Укажите путь к файлу материалов + +# Создаем объект "Материал" +material_obj = doc.addObject("App::MaterialObject", "Материал") +material_obj.Material = material_file + +# Получаем список тел в сборке, состоящих из данного материала +part_names = ["Тело1", "Тело2", "Тело3"] # Укажите имена тел +for part_name in part_names: + part_obj = doc.getObject(part_name) + if part_obj: + material_obj.addObject(part_obj) + +# Добавляем объект "Материал" в папку "Материалы" +material_group.addObject(material_obj) + +# Обновляем вид дерева построения FreeCAD +FreeCADGui.updateGui() diff --git a/cg/freecad/Frames/material_requirements.JSON b/cg/freecad/Frames/material_requirements.JSON new file mode 100644 index 0000000..4c5c3ee --- /dev/null +++ b/cg/freecad/Frames/material_requirements.JSON @@ -0,0 +1,8 @@ + + +{ + "Density": "str" , + "DiffuseColor": "", + "EmissiveColor", + "amogus" +} \ No newline at end of file diff --git a/cg/freecad/Frames/modelExport.py b/cg/freecad/Frames/modelExport.py new file mode 100644 index 0000000..5ea03f2 --- /dev/null +++ b/cg/freecad/Frames/modelExport.py @@ -0,0 +1,155 @@ +''' +Эта штука создается с целью проводить все необходимые проверки и манипуляции при движении моделей +по пайплайну + +Планируемые фичи: + 1. Материалы: + 1.1 Проверка на уникальность + 1.2 Проверка на повторяемость + 1.3 Проверки на соответствие материала требованиям + 1.4 Изменение цветов деталей по факту назначения материалов + 1.5 Расчет массы по плотностям деталей и прописывание ее в свойствах детали + 1.6 Указание наименования материала в свойствах детали + + 2. Вспомогательные обьекты и тела: + 2.1. Вспомогательные обьекты имеют отметки о своем статусе + 2.2. Несолиды имеют пометку несолидовости + 2.3 Детали без материалов должны быть с материалами + + 3. [на будущее] Проверка существования разметки + + 4. Дополнение информации в бом-лист (это нужно засунуть в бом, а не сюда) + + + +''' + +import FreeCAD as App +import FreeCADGui as Gui +from helper.is_solid import is_object_solid + + +doc = App.ActiveDocument + +#0 Проверка существования директорий +#должны быть директории на материалы, на технологические операции, на вспомогательные обьекты +#если у нас есть директория "анализ" от Fem, то мы переименуем ее в свою директорию и сделаем ей пометку. +#если мы находим материалы в дереве, которые не относятся к этой директории, мы должны засунуть их в нашу директорию +#если материалов нет, мы должны указать, что их нет + + +def materialExistenceCheck(doc): + + # if "Materials" not in doc.Objects: + if doc.getObjectsByLabel('Materials') == 0: + material_group = doc.addObject("App::DocumentObjectGroup", "Materials") + App.Console.PrintMessage("Директория материалов не обнаружена и была создана вновь\n") + + + else: + material_group = doc.getObject("Materials") + + fem_materials = [obj for obj in doc.Objects if (hasattr(obj, 'Proxy') and hasattr(obj.Proxy, 'Type') and obj.Proxy.Type == 'Fem::MaterialCommon')] + + if len(fem_materials) > 0: + for material in fem_materials: + material_group.addObject(material) + print('Материалы успешно скомпонованы') + else: + + App.Console.PrintMessage("FEM-материалы не заданы. Укажите FEM-материалы и произведите проверку\n") + Gui.updateGui() + + return material_group, fem_materials #шоб не пропадало зазря + + +[material_group, fem_materials ] = materialExistenceCheck(doc) # укомпоновали материалы + + +#проверим, что материалы правильно описаны: +material_requirements = {'Density', 'DiffuseColor', 'EmissiveColor', 'amogus'} + + + +def material_isCorrect(fem_materials, material_requirements): + femmat_trobles = {} + for femmat in fem_materials: + for req in material_requirements: + if not ( req in femmat.Material and femmat.Material[req]): + if femmat in femmat_trobles: + femmat_trobles[femmat].append(req) + else: + femmat_trobles[femmat] = [req] + + print(femmat_trobles) + + +material_isCorrect(fem_materials, material_requirements) + + +#1 +#1.1 Проверка на уникальность + +#присвоим каждому телу имя материала и значение плотности + +def matProperties2bodies(doc, fem_materials): + #проходимся по всем материалам и присваиваем деталюхам параметры + #нужно кстати еще цвет им переназначить + for femmat in fem_materials: + + body = doc.getObject(femmat.References[0][0].Name).Label #вот эта вот абоба добывает нам тело из перечисления + #в это тело нам надо добавить два свойства + + #нужно проверить, есть ли материальные свойства у тела и, если нет, добавить их + #в следующей серии будем проверять все тела, есть ли у них материал + if 'MaterialName' in body.PropertiesList: + if body.getPropertyByName('MaterialName') != femmat.Material['CardName']: #если не совпадает + print('ВНИМАНИЕ! Тело ' + body.Label + ' имеет переназначение материалов. Проверьте и укажите один (1) правильный материал!') + body.setProperty('MaterialName', femmat.Material['CardName']) #пусть перепишется на новый + body.setProperty('Density', femmat.Material['Density']) #плотность + else: + body.addObjectProperty('MaterialName', femmat.Material['CardName']) #добавляем название материала + body.addObjectProperty('Density', femmat.Material['Density']) #добавляем плотность + + + #считаем массу деталюх + density, unit = parse_values(femmat.Material['Density']) + volume = body.Shape.Volume + body_mass = volume * density + body.addObjectProperty('Mass', body_mass) #масса деталюхи + +def bodyWithoutMaterialsCheck(doc): + objs = doc.Objects + for obj in objs: + #проверка на солидовость + if (is_object_solid(obj) and ('MaterialName' not in obj.PropertiesList)): + print('Деталь ' + obj.Label + 'не имеет назначения материала') + +matProperties2bodies(doc, fem_materials) +bodyWithoutMaterialsCheck(doc) + +#после выполнения предыдущей функции у нас появились деталюхи с обозначениями материалов +#все деталюхи без этой позиции нужно как-то выделить, а + +#гпт предлагает парсить значение единиц измерения +#я с ним соглашусь + +def parse_values(value_str): + value = "" + unit = "" + + # Проверяем каждый символ в строке плотности + for char in value_str: + if char.isdigit() or char == ".": + # Символ является цифрой или точкой - добавляем его к числу + value += char + else: + # Символ не является цифрой или точкой - считаем его частью единицы измерения + unit += char + + # Преобразуем значение плотности в число + density = float(value) + + return value, unit + + diff --git a/cg/freecad/Frames/normalEstimator b/cg/freecad/Frames/normalEstimator deleted file mode 100644 index e69de29..0000000 -- 2.49.0 From ed70c47b7c3b9131fc07d50db610ef2496dee916 Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Tue, 27 Jun 2023 16:51:02 +0300 Subject: [PATCH 20/27] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=B8=D0=BC=D0=BF=D0=BE=D1=80=D1=82=20=D1=82?= =?UTF-8?q?=D1=80=D0=B5=D0=B1=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B9=20=D1=87?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=B7=20json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cg/freecad/Frames/BoMList.py | 77 ++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/cg/freecad/Frames/BoMList.py b/cg/freecad/Frames/BoMList.py index fef31eb..f9063be 100644 --- a/cg/freecad/Frames/BoMList.py +++ b/cg/freecad/Frames/BoMList.py @@ -108,3 +108,80 @@ def run_BoM_list(): sortedParts = uniquePartsSort(labelParts) countedParts = countForUniques(sortedParts) fillInBoMList(sheet, countedParts) + + +def printETACounter(partLabel): + + + + + import os +import requests +import time + +import FreeCAD +from FreeCAD import Base + +def export_to_stl(doc, filename): + mesh = doc.getObjectsByType("Mesh")[0] + mesh.exportStl(filename) + +def upload_to_octoprint(file_path, api_key, octoprint_url): + headers = { + "X-Api-Key": api_key + } + files = { + "file": open(file_path, "rb") + } + response = requests.post(octoprint_url + "/api/files/local", headers=headers, files=files) + if response.status_code == 201: + print("Файл успешно загружен в OctoPrint.") + return response.json()["name"] + else: + print("Ошибка при загрузке файла в OctoPrint:", response.text) + return None + +def get_print_duration(file_name, api_key, octoprint_url): + headers = { + "X-Api-Key": api_key + } + response = requests.get(octoprint_url + "/api/files/local/" + file_name, headers=headers) + if response.status_code == 200: + return response.json()["gcodeAnalysis"]["estimatedPrintTime"] + else: + print("Ошибка при получении информации о файле из OctoPrint:", response.text) + return None + +# Путь к документу FreeCAD +doc_path = "/path/to/your/file.FCStd" + +# Путь для сохранения STL-файла +stl_path = "/path/to/your/output.stl" + +# Настройки OctoPrint +octoprint_api_key = "your_octoprint_api_key" +octoprint_url = "http://localhost:5000" + +# Открываем документ FreeCAD +doc = FreeCAD.open(doc_path) + +# Экспортируем модель в STL-файл +export_to_stl(doc, stl_path) +print("STL-файл успешно создан.") + +# Загружаем STL-файл в OctoPrint +uploaded_file_name = upload_to_octoprint(stl_path, octoprint_api_key, octoprint_url) +if uploaded_file_name is not None: + # Получаем информацию о длительности печати + print_duration = get_print_duration(uploaded_file_name, octoprint_api_key, octoprint_url) + if print_duration is not None: + print("Оценочная длительность печати: {} секунд.".format(print_duration)) + else: + print("Не удалось получить информацию о длительности печати.") +else: + print("Загрузка файла в OctoPrint не удалась.") + +# Закрываем документ FreeCAD +FreeCAD.closeDocument(doc) + + -- 2.49.0 From f08a38824fe2aa9a719297a6ac722b9db343424e Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Tue, 27 Jun 2023 16:51:25 +0300 Subject: [PATCH 21/27] =?UTF-8?q?=D0=A1=D0=BF=D0=B8=D1=81=D0=BE=D0=BA=20?= =?UTF-8?q?=D1=82=D1=80=D0=B5=D0=B1=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B9=20?= =?UTF-8?q?=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20json=20=D0=B2=20=D0=B2=D0=B8?= =?UTF-8?q?=D0=B4=D0=B5=20=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cg/freecad/Frames/material_requirements.JSON | 10 ++++------ cg/freecad/Frames/modelExport.py | 8 ++++++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/cg/freecad/Frames/material_requirements.JSON b/cg/freecad/Frames/material_requirements.JSON index 4c5c3ee..f4eb739 100644 --- a/cg/freecad/Frames/material_requirements.JSON +++ b/cg/freecad/Frames/material_requirements.JSON @@ -1,8 +1,6 @@ -{ - "Density": "str" , - "DiffuseColor": "", - "EmissiveColor", - "amogus" -} \ No newline at end of file +["Density", +"DiffuseColor", +"EmissiveColor" +] diff --git a/cg/freecad/Frames/modelExport.py b/cg/freecad/Frames/modelExport.py index 5ea03f2..2ff6f7b 100644 --- a/cg/freecad/Frames/modelExport.py +++ b/cg/freecad/Frames/modelExport.py @@ -27,7 +27,7 @@ import FreeCAD as App import FreeCADGui as Gui from helper.is_solid import is_object_solid - +import json doc = App.ActiveDocument @@ -67,7 +67,9 @@ def materialExistenceCheck(doc): #проверим, что материалы правильно описаны: -material_requirements = {'Density', 'DiffuseColor', 'EmissiveColor', 'amogus'} +# Открываем файл JSON с требованиями и загружаем его содержимое +with open('material_requirements.json', 'r') as f: + material_requirements = json.load(f) @@ -153,3 +155,5 @@ def parse_values(value_str): return value, unit +#todo: +# сделать проверку на солидовые тела \ No newline at end of file -- 2.49.0 From 07f7c5ab7062b4f5e6e1c9d0275c687991dc8be6 Mon Sep 17 00:00:00 2001 From: Mark Voltov Date: Thu, 29 Jun 2023 09:06:51 +0300 Subject: [PATCH 22/27] =?UTF-8?q?=D0=A1=D0=BF=D1=80=D1=8F=D1=82=D0=B0?= =?UTF-8?q?=D0=BB=20nonsolidcheck?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cg/freecad/Frames/modelExport.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cg/freecad/Frames/modelExport.py b/cg/freecad/Frames/modelExport.py index 2ff6f7b..3add19f 100644 --- a/cg/freecad/Frames/modelExport.py +++ b/cg/freecad/Frames/modelExport.py @@ -156,4 +156,6 @@ def parse_values(value_str): #todo: -# сделать проверку на солидовые тела \ No newline at end of file +# сделать проверку на солидовые тела +#укомпоновать все вспомогательные обьекты в отдельную штуку +#wip \ No newline at end of file -- 2.49.0 From 23edfea360428ffe3a5556a747e7704372a046c4 Mon Sep 17 00:00:00 2001 From: Igor Brylyov Date: Sat, 1 Jul 2023 14:52:09 +0300 Subject: [PATCH 23/27] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BE=D0=BF=D1=86=D0=B8=D0=BE=D0=BD=D0=B0=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=B0=D0=BB=D0=B3=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D1=82=D0=BC=20=D1=82=D0=B5=D1=81=D1=81=D0=B5=D0=BB=D1=8F=D1=86?= =?UTF-8?q?=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cg/blender/import_fcstd/import_cad_objects.py | 21 ++++++++++++------- cg/pipeline/freecad_to_asset.py | 6 ++++-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/cg/blender/import_fcstd/import_cad_objects.py b/cg/blender/import_fcstd/import_cad_objects.py index c997f8c..6088851 100644 --- a/cg/blender/import_fcstd/import_cad_objects.py +++ b/cg/blender/import_fcstd/import_cad_objects.py @@ -61,8 +61,10 @@ render = '_render' def obj_importer(filename, - linear_deflection, - angular_deflection, + tesselation_method="Standard", + linear_deflection=0.1, + angular_deflection=30.0, + max_edge_length=1, update=False, scene_placement=True, skiphidden=True, @@ -124,11 +126,16 @@ def obj_importer(filename, shape = obj.Shape.copy() shape.Placement = obj.Placement.inverse().multiply(shape.Placement) meshfromshape = doc.addObject('Mesh::Feature','Mesh') - meshfromshape.Mesh = MeshPart.meshFromShape( - Shape=shape, - LinearDeflection=linear_deflection, - AngularDeflection=math.radians(angular_deflection), - Relative=False) + if tesselation_method == "Mefisto": + meshfromshape.Mesh = MeshPart.meshFromShape( + Shape=shape, + LinearDeflection=linear_deflection, + AngularDeflection=math.radians(angular_deflection), + Relative=False) + else: + meshfromshape.Mesh = MeshPart.meshFromShape( + Shape=shape, + MaxLength=mefisto_max_length) t = meshfromshape.Mesh.Topology verts = [[v.x,v.y,v.z] for v in t[0]] faces = t[1] diff --git a/cg/pipeline/freecad_to_asset.py b/cg/pipeline/freecad_to_asset.py index d55e484..3734113 100644 --- a/cg/pipeline/freecad_to_asset.py +++ b/cg/pipeline/freecad_to_asset.py @@ -72,10 +72,12 @@ def freecad_asset_pipeline(fcstd_path, remove_collections() cleanup_orphan_data() - # import objects + # import objects, tesselation method can be "Standard" by default with linear/angular deflection parameters and Mefisto with max edge length parameter objs_for_render = obj_importer(fcstd_path, + tesselation_method, linear_deflection, - angular_deflection) + angular_deflection, + max_edge_length) # restructuring hierarchy by lcs points lcs_objects = restruct_hierarchy() -- 2.49.0 From c1e4b0e0f03aabf23c8f930757a032d36197ebda Mon Sep 17 00:00:00 2001 From: IDONTSUDO Date: Tue, 4 Jul 2023 07:19:55 +0000 Subject: [PATCH 24/27] =?UTF-8?q?=D0=92=D0=B5=D0=B1=20=D0=B8=D0=BD=D1=82?= =?UTF-8?q?=D0=B5=D1=80=D1=84=D0=B5=D0=B9=D1=81=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BA=D0=BE=D1=80=D1=80=D0=B5=D0=BA=D1=82=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D0=B8=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20ASP?= =?UTF-8?q?,=20=D0=B5=D0=B3=D0=BE=20=D0=B8=D0=BD=D1=82=D0=B5=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20=D1=81=20=D0=B0=D0=BB=D0=B3=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D1=82=D0=BC=D0=B0=D0=BC=D0=B8=20=D0=B3=D0=B5=D0=BD?= =?UTF-8?q?=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- asp-review-app/.gitignore | 2 + asp-review-app/server/package-lock.json | 500 ++++++++++++++++++ asp-review-app/server/package.json | 4 +- asp-review-app/server/src/app.ts | 6 +- .../server/src/core/di/register_di.ts | 10 +- .../server/src/core/helper/memorization.ts | 11 - .../core/middlewares/ValidationMiddleware.ts | 1 - .../src/core/repository/compute_repository.ts | 79 ++- .../src/core/repository/entity_repository.ts | 67 ++- .../src/core/repository/zip_repository.ts | 12 +- .../assembly_create_controller.ts | 113 +++- .../assembly_create/assembly_create_route.ts | 64 ++- .../assembly_create/model/zip_files_model.ts | 3 + .../assembly_previews_controller.ts | 15 +- .../assembly_previews_route.ts | 8 +- asp-review-app/ui/package-lock.json | 330 +++++++++--- asp-review-app/ui/package.json | 13 +- asp-review-app/ui/src/App.css | 34 +- .../ui/src/core/repository/http_repository.ts | 19 +- .../all_project/all_project_screen.tsx | 62 ++- .../create_project/create_project.tsx | 72 +++ .../stability_preview/stability_preview.tsx | 57 ++ .../topology_ajax_preview.tsx | 48 ++ asp-review-app/ui/src/index.tsx | 18 +- asp-review-app/ui/yarn.lock | 254 ++++++--- asp/.gitignore | 1 + asp/main.py | 12 +- asp/src/usecases/formatter_usecase.py | 5 +- asp/src/usecases/stability_check_usecase.py | 36 -- asp/src/usecases/urdf_sub_assembly_usecase.py | 6 +- cad_generation/env.json | 6 + cad_generation/helper/fs.py | 15 + cad_generation/helper/is_solid.py | 18 + cad_generation/main.py | 19 + cad_generation/model/files_generator.py | 13 + cad_generation/model/geometry_part.py | 86 +++ cad_generation/model/join_mesh_model.py | 33 ++ cad_generation/model/mesh_part_model.py | 32 ++ cad_generation/model/sdf_geometry_model.py | 107 ++++ .../model/simple_copy_part_model.py | 30 ++ .../robossembler_freecad_export_scenario.py | 54 ++ cad_generation/usecases/asm4parser_usecase.py | 53 ++ .../usecases/assembly_parse_usecase.py | 58 ++ .../export_assembly_them_all_usecase.py | 92 ++++ cad_generation/usecases/export_usecase.py | 36 ++ cad_generation/usecases/geometry_usecase.py | 58 ++ .../usecases/get_sdf_geometry_usecase.py | 68 +++ cad_stability_check/.gitignore | 95 ++++ cad_stability_check/main.py | 79 +++ cad_stability_input | 1 + .../robossembler_freecad_export_scenario.py | 2 +- geometric_feasibility_predicate/README.MD | 1 + geometric_feasibility_predicate/env.json | 4 + geometric_feasibility_predicate/main.py | 358 +++++++++++++ pddl/main.py | 2 +- stability_process_predicate/main.py | 16 + .../usecases/stability_check_usecase.py | 61 +++ 57 files changed, 2969 insertions(+), 290 deletions(-) create mode 100644 asp-review-app/ui/src/features/create_project/create_project.tsx create mode 100644 asp-review-app/ui/src/features/stability_preview/stability_preview.tsx create mode 100644 asp-review-app/ui/src/features/topology_ajax_preview/topology_ajax_preview.tsx create mode 100644 asp/.gitignore delete mode 100644 asp/src/usecases/stability_check_usecase.py create mode 100644 cad_generation/env.json create mode 100644 cad_generation/helper/fs.py create mode 100644 cad_generation/helper/is_solid.py create mode 100644 cad_generation/main.py create mode 100644 cad_generation/model/files_generator.py create mode 100644 cad_generation/model/geometry_part.py create mode 100644 cad_generation/model/join_mesh_model.py create mode 100644 cad_generation/model/mesh_part_model.py create mode 100644 cad_generation/model/sdf_geometry_model.py create mode 100644 cad_generation/model/simple_copy_part_model.py create mode 100644 cad_generation/scenarios/robossembler_freecad_export_scenario.py create mode 100644 cad_generation/usecases/asm4parser_usecase.py create mode 100644 cad_generation/usecases/assembly_parse_usecase.py create mode 100644 cad_generation/usecases/export_assembly_them_all_usecase.py create mode 100644 cad_generation/usecases/export_usecase.py create mode 100644 cad_generation/usecases/geometry_usecase.py create mode 100644 cad_generation/usecases/get_sdf_geometry_usecase.py create mode 100644 cad_stability_check/.gitignore create mode 100644 cad_stability_check/main.py create mode 160000 cad_stability_input create mode 100644 geometric_feasibility_predicate/README.MD create mode 100644 geometric_feasibility_predicate/env.json create mode 100644 geometric_feasibility_predicate/main.py create mode 100644 stability_process_predicate/main.py create mode 100644 stability_process_predicate/usecases/stability_check_usecase.py diff --git a/asp-review-app/.gitignore b/asp-review-app/.gitignore index 048c8dd..a2424f7 100644 --- a/asp-review-app/.gitignore +++ b/asp-review-app/.gitignore @@ -22,3 +22,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* **/node_modules +server/public/ +**/computed/ \ No newline at end of file diff --git a/asp-review-app/server/package-lock.json b/asp-review-app/server/package-lock.json index 5d626cf..686b9b1 100644 --- a/asp-review-app/server/package-lock.json +++ b/asp-review-app/server/package-lock.json @@ -15,6 +15,7 @@ "compression": "^1.7.4", "concurrently": "^8.0.1", "cors": "^2.8.5", + "decompress": "^4.2.1", "express": "^4.18.2", "express-cross": "^1.0.0", "express-fileupload": "^1.4.0", @@ -25,6 +26,7 @@ "multer": "^1.4.5-lts.1", "node-stream-zip": "^1.15.0", "nodemon": "^2.0.22", + "shelljs": "^0.8.5", "ts-node": "^10.9.1" }, "devDependencies": { @@ -350,6 +352,25 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -374,6 +395,15 @@ "node": ">=8" } }, + "node_modules/bl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.3.tgz", + "integrity": "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -426,6 +456,56 @@ "node": ">=14.20.1" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -577,6 +657,11 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -762,6 +847,95 @@ "ms": "2.0.0" } }, + "node_modules/decompress": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", + "integrity": "sha512-e48kc2IjU+2Zw8cTb6VZcJQ3lgVbS4uuB1TfCHbiZIP/haNXm+SVyhu+87jts5/3ROpd82GSVCoNs/z8l4ZOaQ==", + "dependencies": { + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tar": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "dependencies": { + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "dependencies": { + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-tarbz2/node_modules/file-type": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-targz": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "dependencies": { + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha512-1fqeluvxgnn86MOh66u8FjbtJpAFv5wgCT9Iw8rcBqQcCo5tO8eiJw7NNTrvt9n4CRBVq7CstiS922oPgyGLrw==", + "dependencies": { + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/decompress-unzip/node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -813,6 +987,14 @@ "node": ">= 0.8" } }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -928,6 +1110,22 @@ "node": ">= 0.8" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha512-Iq1nJ6D2+yIO4c8HHg4fyVb8mAJieo1Oloy1mLLaB2PvezNedhBVm+QU7g0qM42aiMbRXTxKKwGD17rjKNJYVQ==", + "engines": { + "node": ">=4" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -993,6 +1191,16 @@ "node": ">= 0.6" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -1032,6 +1240,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha512-AUGhbbemXxrZJRD5cDvKtQxLuYaIbNtDTK8YqupCI393Q2KSTreEsLUN3ZxAWFGiKTzL6nKuzfcIvieflUX9qA==", + "dependencies": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -1043,6 +1282,11 @@ "node": ">= 6" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -1099,16 +1343,52 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/ip": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", @@ -1134,6 +1414,17 @@ "node": ">=8" } }, + "node_modules/is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1161,6 +1452,11 @@ "node": ">=0.10.0" } }, + "node_modules/is-natural-number": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", + "integrity": "sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==" + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -1169,6 +1465,14 @@ "node": ">=0.12.0" } }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -1193,6 +1497,25 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/make-dir/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "engines": { + "node": ">=4" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -1566,6 +1889,14 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1574,11 +1905,29 @@ "node": ">= 0.8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -1590,6 +1939,33 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -1687,6 +2063,17 @@ "node": ">=8.10.0" } }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -1706,6 +2093,22 @@ "node": ">=0.10.0" } }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -1751,6 +2154,18 @@ "node": ">=6" } }, + "node_modules/seek-bzip": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.6.tgz", + "integrity": "sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==", + "dependencies": { + "commander": "^2.8.1" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, "node_modules/semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -1814,6 +2229,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -1944,6 +2375,14 @@ "node": ">=8" } }, + "node_modules/strip-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "dependencies": { + "is-natural-number": "^4.0.1" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -1955,6 +2394,44 @@ "node": ">=4" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", + "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", + "dependencies": { + "bl": "^1.0.0", + "buffer-alloc": "^1.2.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.1", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2081,6 +2558,15 @@ "node": ">=4.2.0" } }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -2166,6 +2652,11 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -2207,6 +2698,15 @@ "node": ">=12" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/asp-review-app/server/package.json b/asp-review-app/server/package.json index 48cfdf5..9f67de8 100644 --- a/asp-review-app/server/package.json +++ b/asp-review-app/server/package.json @@ -6,7 +6,7 @@ "type": "module", "scripts": { "build": "npx tsc", - "start": "node dist/index.js", + "start": "npx tsc && node --experimental-specifier-resolution=node dist/server.js", "dev": "nodemon --exec ts-node --esm --transpileOnly ./src/server.ts" }, "keywords": [], @@ -28,6 +28,7 @@ "compression": "^1.7.4", "concurrently": "^8.0.1", "cors": "^2.8.5", + "decompress": "^4.2.1", "express": "^4.18.2", "express-cross": "^1.0.0", "express-fileupload": "^1.4.0", @@ -38,6 +39,7 @@ "multer": "^1.4.5-lts.1", "node-stream-zip": "^1.15.0", "nodemon": "^2.0.22", + "shelljs": "^0.8.5", "ts-node": "^10.9.1" } } diff --git a/asp-review-app/server/src/app.ts b/asp-review-app/server/src/app.ts index 9d8dfda..cf8e430 100644 --- a/asp-review-app/server/src/app.ts +++ b/asp-review-app/server/src/app.ts @@ -17,8 +17,11 @@ const corsOptions = { }; export class App { public app: express.Application; + public port: string | number; + public env: string; + constructor(routes: Routes[], port) { this.app = express(); this.port = port; @@ -50,7 +53,6 @@ export class App { this.app.use(express.urlencoded({ extended: true })); this.app.use(bodyParser.json()); this.app.use(bodyParser.urlencoded({ extended: true })); - console.log(dirname + '/public/') this.app.use(express.static(dirname + '/public/')); this.app.use(fileUpload({ createParentPath: true @@ -62,8 +64,8 @@ export class App { this.app.use("/", route.router); }); } + loadAppDependencies() { - locator(new DevEnv()); } } \ No newline at end of file diff --git a/asp-review-app/server/src/core/di/register_di.ts b/asp-review-app/server/src/core/di/register_di.ts index ad79975..77f2abe 100644 --- a/asp-review-app/server/src/core/di/register_di.ts +++ b/asp-review-app/server/src/core/di/register_di.ts @@ -5,20 +5,24 @@ import { AssemblyController } from "../../features/assembly_create/assembly_crea import { AssemblyPreviewsController } from "../../features/assembly_previews/assembly_previews_controller"; import { EntityRepository } from "../repository/entity_repository"; import { ZipRepository } from "../repository/zip_repository"; +import { ComputeRepository } from "../repository/compute_repository"; export const locator = (env: Env) => { // override(Env, env) - registerRepository(env) registerController(env) + registerRepository(env) + }; const registerRepository = (env:Env) => { + override(ZipRepository, ZipRepository); - override(EntityRepository, EntityRepository) + override(EntityRepository, EntityRepository); + override(ComputeRepository,ComputeRepository); } const registerController = (env: Env) => { override(AssemblyController,AssemblyController) - override(AssemblyPreviewsController, AssemblyController) + override(AssemblyPreviewsController, AssemblyPreviewsController) } \ No newline at end of file diff --git a/asp-review-app/server/src/core/helper/memorization.ts b/asp-review-app/server/src/core/helper/memorization.ts index 0d5e87a..b936460 100644 --- a/asp-review-app/server/src/core/helper/memorization.ts +++ b/asp-review-app/server/src/core/helper/memorization.ts @@ -1,8 +1,4 @@ interface MemoOptions { - /** - * Serialize the function call arguments - * This is used to identify cache key - */ serialize?: (...args: Parameters) => S; } interface MemoAsyncOptions extends MemoOptions { @@ -22,16 +18,12 @@ type Fn = (...params: any[]) => any; type AsyncFn = (...params: any[]) => Promise; interface MemoFunc { - // Call the target function, if cache is valid, return cache (...args: Parameters): ReturnType; - // Same with this function get(...args: Parameters): ReturnType; - // Call the raw function and skip cache raw(...args: Parameters): ReturnType; - // Clear cache clear(...args: Parameters | []): void | Promise; } @@ -126,7 +118,6 @@ export function memoAsync( const root = makeNode(); const memoFunc = async function (...args: Parameters) { - // Serialize args const path = options.serialize ? options.serialize(...args) : args; const cur = walkAndCreate(root, path); @@ -155,7 +146,6 @@ export function memoAsync( await options.external.set(args, value); } - // Resolve other waiting callbacks for (const callback of cur.callbacks ?? []) { callback.res(value); } @@ -165,7 +155,6 @@ export function memoAsync( cur.state = State.Error; cur.error = error; - // Reject other waiting callbacks for (const callback of cur.callbacks ?? []) { callback.rej(error); } diff --git a/asp-review-app/server/src/core/middlewares/ValidationMiddleware.ts b/asp-review-app/server/src/core/middlewares/ValidationMiddleware.ts index d9ca9ca..d1ee527 100644 --- a/asp-review-app/server/src/core/middlewares/ValidationMiddleware.ts +++ b/asp-review-app/server/src/core/middlewares/ValidationMiddleware.ts @@ -11,7 +11,6 @@ const validationMiddleware = ( forbidNonWhitelisted = true, ): RequestHandler => { return (req, res, next) => { - console.log(req[value]) validate(plainToClass(type, req[value]), { skipMissingProperties, whitelist, forbidNonWhitelisted }).then((errors: ValidationError[]) => { if (errors.length > 0) { const message = errors.map((error: ValidationError) => Object.values(error.constraints)).join(', '); diff --git a/asp-review-app/server/src/core/repository/compute_repository.ts b/asp-review-app/server/src/core/repository/compute_repository.ts index eda546b..2b6ac30 100644 --- a/asp-review-app/server/src/core/repository/compute_repository.ts +++ b/asp-review-app/server/src/core/repository/compute_repository.ts @@ -1,3 +1,76 @@ -export class ComputeRepository{ - -} \ No newline at end of file +import { reflection } from 'first-di'; +import "reflect-metadata"; +import { promises as fs } from 'fs'; +import { async } from 'node-stream-zip'; +import * as cp from 'child_process'; + +import path from 'path'; + +async function exec(cmd: string, opts: (cp.ExecOptions & { trim?: boolean }) = {}): Promise { + return new Promise((c, e) => { + cp.exec(cmd, { env: process.env, ...opts }, (err, stdout) => err ? e(err) : c(opts.trim ? stdout.trim() : stdout)); + }); +} + +@reflection +export class ComputeRepository { + public computedAdjaxedMatrix = async (outPath: string, cadEntity: string, entityId: string) => { + const envPath = '/home/idontsudo/t/framework/asp-review-app/server/computed/geometric_feasibility_predicate/env.json' + const computedScript = '/home/idontsudo/t/framework/asp-review-app/server/computed/geometric_feasibility_predicate/main.py' + const computedComand = 'freecadcmd' + + const env = JSON.parse((await fs.readFile(envPath)).toString()) + env['cadFilePath'] = cadEntity + env['outPath'] = outPath + await fs.writeFile(envPath, JSON.stringify(env)) + // console.log(this._computedPath(computedScript)) + exec(computedComand + ' ' + computedScript, { cwd: this._computedPath(computedScript) }).then((data) => { + console.log(data) + }) + this.cadGeneration(cadEntity, entityId, outPath) + // if (stderr) { + // console.log(stderr) + // } + // console.log(stdout) + }; + public computedWriteStability = async (assemblyFolder: string, buildNumber: string, id: string) => { + const computedScript = '/home/idontsudo/t/framework/cad_stability_input/main.py' + const computedComand = 'freecad' + const envPath = '/home/idontsudo/t/framework/cad_stability_input/env.json' + const env = JSON.parse((await fs.readFile(envPath)).toString()) + env.assemblyFolder = assemblyFolder + env['projectId'] = id + env['buildNumber'] = buildNumber + env['assemblyFolder'] = assemblyFolder + env['resultURL'] = 'http://localhost:3002/assembly/stabilty/create/?id=' + id + '&' + 'buildNumber=' + buildNumber + + await fs.writeFile(envPath, JSON.stringify(env)) + await exec(computedComand + ' ' + computedScript, { cwd: this._computedPath(computedScript) }) + } + + private _computedPath(f: string) { + + const file = path.basename(f); + const absolutPath = path.resolve(f) + return absolutPath.replace(file, '') + } + + public cadGeneration = async (cadEntity, entity: string, outPath: string,) => { + const computedScript = '/home/idontsudo/t/framework/cad_generation/main.py' + const computedComand = 'freecad' + const envPath = '/home/idontsudo/t/framework/cad_generation/env.json' + + const env = JSON.parse((await fs.readFile(envPath)).toString()) + env.doc = cadEntity + env.projectId = entity + env.resultURL = "http://localhost:3002/assembly/save/out" + + await fs.writeFile(envPath, JSON.stringify(env)) + // /stabilty/create + + exec(computedComand + ' ' + computedScript, { cwd: this._computedPath(computedScript) }).then((data) => { + console.log(data) + }) + } + +} diff --git a/asp-review-app/server/src/core/repository/entity_repository.ts b/asp-review-app/server/src/core/repository/entity_repository.ts index 97277e6..cea7376 100644 --- a/asp-review-app/server/src/core/repository/entity_repository.ts +++ b/asp-review-app/server/src/core/repository/entity_repository.ts @@ -1,49 +1,86 @@ import { promises as fs } from 'fs'; import { dirname } from '../../app'; import fsSync from "fs"; -import { constants } from 'buffer'; +import { autowired, reflection } from 'first-di'; +import "reflect-metadata"; +import { ComputeRepository } from './compute_repository'; +import { ZipRepository } from './zip_repository'; +@reflection export class EntityRepository { - private path: String = dirname + '/public/' - private getFileName(file: String) { + @autowired() + private readonly computedRepository: ComputeRepository; + @autowired() + private readonly zipRepository: ZipRepository; + + private path: String = dirname + '/public/' + + + private getFileName(file: String) { return file.slice(0, file.indexOf('.')) } - public async getDir(path){ + + public async getDir(path) { return this._fullPath(await fs.readdir(path + ''), duplicatedDelete(this.path, path)) } - public isExistDirPath(path:String):boolean{ + + public isExistDirPath(path: String): boolean { return fsSync.existsSync(path + '') } + public async saveRootEntity(buffer: Buffer, name: string) { const filePath = this.path + this.getFileName(name) + '/' - + if (this.isExistDirPath(filePath)) { await fs.rm(filePath, { recursive: true }) } await fs.mkdir(filePath); await fs.writeFile(filePath + name, buffer); + this.computedRepository.computedAdjaxedMatrix(filePath, filePath + name, this.getFileName(name)) } + public async getAllRootEntity() { return await fs.readdir('' + this.path) } - public async getEntityStorage(entity: string):Promise | undefined { - return this._fullPath(await fs.readdir(this.path + entity), entity + '/' ) + + public async getEntityStorage(entity: string): Promise | undefined { + return this._fullPath(await fs.readdir(this.path + entity), entity + '/') } - private _fullPath(folderPath,helpElement = '') { - return folderPath.map((el) => this.path + helpElement + el ) + private _fullPath(folderPath, helpElement = '') { + return folderPath.map((el) => this.path + helpElement + el) } public async readJson(path) { - return JSON.parse((await fs.readFile(path)).toString()) } + public async saveGeration(data: Buffer, id: String) { + const rootFolderPath = '' + this.path + id + '/' + console.log(rootFolderPath) + this.zipRepository.archive(rootFolderPath, data) + } + public computedStability(id: string, buildNumber: string) { + const assemblyFolder = this.path + id + '/generation/' + this.computedRepository.computedWriteStability(assemblyFolder, buildNumber, id) + } + public async saveStability(zip: Buffer, id:string, buildNumber:string) { + const filePath = await this.zipRepository.archive(this.path as string, zip) + // const buildNumber = data['buildNumber'] + const assemblyFolder = this.path + id + '/generation/stability/' + + if (!this.isExistDirPath(assemblyFolder)) { + await fs.mkdir(assemblyFolder); + } + await this.zipRepository.archive(assemblyFolder as string, zip, buildNumber) + fs.rmdir(filePath + '/', { recursive: true}) + + } } -function duplicatedDelete(strChild:String,strMain:String){ +function duplicatedDelete(strChild: String, strMain: String) { let result = '' - for(let i = 0;i < strMain.length; i++){ - if(!(strMain[i] === strChild[i])){ - result+=strMain[i] + for (let i = 0; i < strMain.length; i++) { + if (!(strMain[i] === strChild[i])) { + result += strMain[i] } } return result diff --git a/asp-review-app/server/src/core/repository/zip_repository.ts b/asp-review-app/server/src/core/repository/zip_repository.ts index c27de1b..25efe1c 100644 --- a/asp-review-app/server/src/core/repository/zip_repository.ts +++ b/asp-review-app/server/src/core/repository/zip_repository.ts @@ -1,3 +1,13 @@ +import StreamZip from 'node-stream-zip'; +import { promises as fs } from 'fs'; +import decompress from 'decompress' + export class ZipRepository { - + public async archive(outhPath: string, zipFile: Buffer, name='generation') { + const entry = outhPath + 'archive.zip' + await fs.writeFile(entry, zipFile) + await decompress(entry, outhPath + name); + fs.rm(entry) + return outhPath + name + } } \ No newline at end of file diff --git a/asp-review-app/server/src/features/assembly_create/assembly_create_controller.ts b/asp-review-app/server/src/features/assembly_create/assembly_create_controller.ts index f229694..77e0a8b 100644 --- a/asp-review-app/server/src/features/assembly_create/assembly_create_controller.ts +++ b/asp-review-app/server/src/features/assembly_create/assembly_create_controller.ts @@ -1,36 +1,101 @@ -import { NextFunction, Request, Response } from 'express'; -import { autowired } from 'first-di'; - -import "reflect-metadata"; -import { dirname } from '../../app'; -import { EntityRepository } from '../../core/repository/entity_repository'; -import { IFile } from './model/zip_files_model'; - +import { NextFunction, Request, Response } from "express"; +import { autowired } from "first-di"; +import { async } from "node-stream-zip"; +import { EntityRepository } from "../../core/repository/entity_repository"; +import { IFile } from "./model/zip_files_model"; export class AssemblyController { - public getAllAssembly = (req: Request, res: Response, next: NextFunction): void => { - throw new Error('Method not implemented.'); - } - - @autowired() - private readonly fsRepository: EntityRepository; - - public createAssembly = (req: Request, res: Response, next: NextFunction): void => { + private readonly entityRepository: EntityRepository; + + public createRootEntity = ( + req: Request, + res: Response, + next: NextFunction + ) => { + const file = req.files; + const cadFile = file["freecad"] as IFile; + + this.entityRepository.saveRootEntity(cadFile.data, cadFile.name); + + res.status(200).json("ok"); + return; + }; + + public getAllAssembly = ( + req: Request, + res: Response, + next: NextFunction + ): void => { }; + + public createAssembly = ( + req: Request, + res: Response, + next: NextFunction + ): void => { try { const file = req.files.freecad as IFile; const buffer = file.data as Buffer; - console.log(file.data) - // console.log(files.freecad.data) - // const filePath = dirname + '/' + files.freecad.name as string; - this.fsRepository.saveRootEntity(file.data, file.name) - // console.log(filePath) - + this.entityRepository.saveRootEntity(file.data, file.name); res.sendStatus(200); } catch (error) { next(error); } }; -} - \ No newline at end of file + + public test = (req: Request, + res: Response, + next: NextFunction) => { + try { + const file = req.files; + + const generation = file["zip"] as IFile; + const id = 'cubes'; + + this.entityRepository.saveGeration(generation.data, id) + res.sendStatus(200); + } catch (error) { + next(error); + } + } + + public stabilityComputed = async ( + req: Request, + res: Response, + next: NextFunction + ) => { + try { + // const file = req.files; + console.log(req.body) + const id = req.body.id; + // console.log(req.query.id) + const buildNumber = req.body.buildNumber; + console.log(buildNumber) + console.log(id) + // const generation = file["zip"] as IFile; + // const id = 'cubes'; + + await this.entityRepository.computedStability(id, buildNumber) + res.sendStatus(200); + } catch (error) { + next(error); + } + } + + public stabilityCreate = ( + req: Request, + res: Response, + next: NextFunction + ) => { + try { + const files = req.files; + const zip = files['zip'] as IFile + const query = req.query as any + this.entityRepository.saveStability(zip.data, query.id, query.buildNumber) + res.sendStatus(200); + } catch (error) { + next(error); + } + } +} diff --git a/asp-review-app/server/src/features/assembly_create/assembly_create_route.ts b/asp-review-app/server/src/features/assembly_create/assembly_create_route.ts index 4449101..43fefe1 100644 --- a/asp-review-app/server/src/features/assembly_create/assembly_create_route.ts +++ b/asp-review-app/server/src/features/assembly_create/assembly_create_route.ts @@ -1,26 +1,46 @@ - -import express, { Router } from 'express'; -import { Routes } from '../../core/interfaces/router'; -import { autowired } from 'first-di'; -import { AssemblyController } from './assembly_create_controller'; -import path from 'path'; -import { dirname } from '../../app'; -import validationMiddleware from '../../core/middlewares/ValidationMiddleware'; -import { CadFilesModel } from './model/zip_files_model'; +import express, { Router } from "express"; +import { Routes } from "../../core/interfaces/router"; +import { autowired } from "first-di"; +import { AssemblyController } from "./assembly_create_controller"; +import validationMiddleware from "../../core/middlewares/ValidationMiddleware"; +import { CadFilesModel } from "./model/zip_files_model"; export class AssemblyRoute implements Routes { - public path = '/assembly'; - public router = Router(); - @autowired() - private readonly assemblyController: AssemblyController; - constructor() { - this.initializeRoutes(); - } + public path = "/assembly"; + public router = Router(); + + @autowired() + private readonly assemblyController: AssemblyController; + + constructor() { + this.initializeRoutes(); + } - private initializeRoutes() { - // this.router.use(`${this.path}`, express.static(path.join(dirname, '../../public'))); - this.router.post(`${this.path}`, validationMiddleware(CadFilesModel, 'files'), this.assemblyController.createAssembly) + private initializeRoutes() { + this.router.post( + `${this.path}`, + validationMiddleware(CadFilesModel, "files"), + this.assemblyController.createAssembly + ); + this.router.post( + `${this.path}/save/out`, + // validationMiddleware(CadFilesModel, "files"), + this.assemblyController.test + ); - this.router.get(`${this.path}`, this.assemblyController.getAllAssembly) - } -} \ No newline at end of file + this.router.get(`${this.path}`, this.assemblyController.getAllAssembly); + + this.router.post( + `${this.path}/create`, + this.assemblyController.createRootEntity + ); + this.router.post( + `${this.path}/stability/write/computed`, + this.assemblyController.stabilityComputed + ); + this.router.post( + `${this.path}/stabilty/create/`, + this.assemblyController.stabilityCreate + ); + } +} diff --git a/asp-review-app/server/src/features/assembly_create/model/zip_files_model.ts b/asp-review-app/server/src/features/assembly_create/model/zip_files_model.ts index 243f69c..99956e0 100644 --- a/asp-review-app/server/src/features/assembly_create/model/zip_files_model.ts +++ b/asp-review-app/server/src/features/assembly_create/model/zip_files_model.ts @@ -1,4 +1,5 @@ import { IsArray, IsObject } from "class-validator"; + export interface IFile { name: string, data: Buffer, @@ -9,9 +10,11 @@ export interface IFile { mimetype: string, md5: string, } + interface ICadFileModel { freecad: IFile; } + export class CadFilesModel implements ICadFileModel { @IsObject() public freecad: IFile; diff --git a/asp-review-app/server/src/features/assembly_previews/assembly_previews_controller.ts b/asp-review-app/server/src/features/assembly_previews/assembly_previews_controller.ts index b071b9f..68429e0 100644 --- a/asp-review-app/server/src/features/assembly_previews/assembly_previews_controller.ts +++ b/asp-review-app/server/src/features/assembly_previews/assembly_previews_controller.ts @@ -3,10 +3,7 @@ import { autowired } from "first-di"; import { EntityRepository } from "../../core/repository/entity_repository"; import { port } from "../../server"; import { memoAsync } from "../../core/helper/memorization"; - -import "reflect-metadata"; -import { async } from "node-stream-zip"; - + export class AssemblyPreviewsController { @autowired() private readonly entityRepository: EntityRepository; @@ -32,8 +29,9 @@ export class AssemblyPreviewsController { const entity = await this.entityRepository.getEntityStorage( req.params.id ); + const aspUsage = Number(req.query.count) - 1; - console.log(aspUsage); + if (entity === undefined) { res.status(404).json("entity not found"); return; @@ -59,10 +57,13 @@ export class AssemblyPreviewsController { next: NextFunction ) => { const entity = await this.entityRepository.getEntityStorage(req.params.id); + const aspUsage = Number(req.query.count); + const assemblyFolder = entity.find((el) => { return el.match("assembly"); }); + const asmCountFolder = "0000" + aspUsage; const assemblyDirPath = assemblyFolder + "/" + asmCountFolder; @@ -70,12 +71,15 @@ export class AssemblyPreviewsController { if (!this.entityRepository.isExistDirPath(assemblyDirPath)) { return res.status(400).json({ error: "bad request" }); } + const assemblyProcessDir = await this.entityRepository.getDir( assemblyDirPath + "/process/" ); + const firstObj = assemblyProcessDir.find((el) => { return el.match("1.obj"); }); + const zeroObj = await assemblyProcessDir.find((el) => { return el.match("0.obj"); }); @@ -148,4 +152,5 @@ export class AssemblyPreviewsController { }; } } + } diff --git a/asp-review-app/server/src/features/assembly_previews/assembly_previews_route.ts b/asp-review-app/server/src/features/assembly_previews/assembly_previews_route.ts index ef326cd..c12658c 100644 --- a/asp-review-app/server/src/features/assembly_previews/assembly_previews_route.ts +++ b/asp-review-app/server/src/features/assembly_previews/assembly_previews_route.ts @@ -12,16 +12,16 @@ export class AssemblyPreviewsRoute implements Routes { public path = '/assembly/preview/'; public router = Router(); @autowired() - private readonly assemblyController: AssemblyPreviewsController; + private readonly assemblyPreviewsController: AssemblyPreviewsController; constructor() { this.initializeRoutes(); } private initializeRoutes() { - this.router.get(`${this.path}`, this.assemblyController.getAllAssembly); + this.router.get(`${this.path}`, this.assemblyPreviewsController.getAllAssembly); // this.router.get(`${this.path}`) - this.router.get(`${this.path}subsequence/:id`, this.assemblyController.getAssemblySubsequenceById) - this.router.get(`${this.path}insertion_sequence/:id`, this.assemblyController.getAssemblyInsertionSequenceById) + this.router.get(`${this.path}subsequence/:id`, this.assemblyPreviewsController.getAssemblySubsequenceById) + this.router.get(`${this.path}insertion_sequence/:id`, this.assemblyPreviewsController.getAssemblyInsertionSequenceById) // this.router.post(`${this.path}`, validationMiddleware(CadFilesModel, 'files'), this.assemblyController.createAssembly) // this.router.get(`${this.path}`, this.assemblyController.getAllAssembly) diff --git a/asp-review-app/ui/package-lock.json b/asp-review-app/ui/package-lock.json index ab05d8c..519297f 100644 --- a/asp-review-app/ui/package-lock.json +++ b/asp-review-app/ui/package-lock.json @@ -17,8 +17,8 @@ "@testing-library/user-event": "^13.2.1", "@types/jest": "^27.0.1", "@types/node": "^16.7.13", - "@types/react": "^18.0.0", - "@types/react-dom": "^18.0.0", + "@types/react": "18.0.25", + "@types/react-dom": "18.0.9", "antd": "^5.5.2", "babel-jest": "^27.4.2", "babel-loader": "^8.2.3", @@ -55,16 +55,21 @@ "postcss-normalize": "^10.0.1", "postcss-preset-env": "^7.0.1", "prompts": "^2.4.2", - "react": "^16.12.0", + "react": "18.0.0", "react-app-polyfill": "^3.0.0", "react-dev-utils": "^12.0.1", - "react-dom": "^16.12.0", + "react-dom": "18.0.0", "react-i18next": "^12.2.0", "react-refresh": "^0.11.0", "react-router-dom": "^6.11.2", "react-three-fiber": "^6.0.13", "resolve": "^1.20.0", "resolve-url-loader": "^4.0.0", + "rete": "2.0.0-beta.9", + "rete-area-plugin": "2.0.0-beta.12", + "rete-connection-plugin": "2.0.0-beta.16", + "rete-react-render-plugin": "2.0.0-beta.22", + "rete-render-utils": "2.0.0-beta.12", "sass-loader": "^12.3.0", "semver": "^7.3.5", "sort-by": "^1.2.0", @@ -110,9 +115,9 @@ } }, "node_modules/@ant-design/cssinjs": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.9.1.tgz", - "integrity": "sha512-CZt1vCMs/sY7RoacYuIkZwQmb8Bhp99ReNNE9Y8lnUzik8fmCdKAQA7ecvVOFwmNFdcBHga7ye/XIRrsbkiqWw==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.10.1.tgz", + "integrity": "sha512-PSoJS8RMzn95ZRg007dJGr6AU0Zim/O+tTN0xmXmh9CkIl4y3wuOr2Zhehaj7s130wPSYDVvahf3DKT50w/Zhw==", "dependencies": { "@babel/runtime": "^7.11.1", "@emotion/hash": "^0.8.0", @@ -2370,6 +2375,27 @@ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "peer": true, + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "peer": true + }, + "node_modules/@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==", + "peer": true + }, "node_modules/@emotion/unitless": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", @@ -3048,9 +3074,9 @@ } }, "node_modules/@rc-component/mini-decimal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.0.1.tgz", - "integrity": "sha512-9N8nRk0oKj1qJzANKl+n9eNSMUGsZtjwNuDCiZ/KA+dt1fE3zq5x2XxclRcAbOIXnZcJ53ozP2Pa60gyELXagA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz", + "integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==", "dependencies": { "@babel/runtime": "^7.18.0" }, @@ -3112,9 +3138,9 @@ } }, "node_modules/@rc-component/trigger": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-1.13.3.tgz", - "integrity": "sha512-CA4s8QGj2kagp8dmYRVcSIW5IErw/YBxSeFEsQmt6SB0oaj9pj+akkB6O0S/Y6ww5JrIDu9Bukq89se1oW9F3w==", + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-1.13.6.tgz", + "integrity": "sha512-13aF9SrR5XAd+tyV/zja0A2pbrA/zdTCXRBNIsoLp8OmhVOnqiwjP7XZYPulLsH0ioEfvtXR1yI0anJD0/J7PQ==", "dependencies": { "@babel/runtime": "^7.18.3", "@rc-component/portal": "^1.1.0", @@ -3122,7 +3148,7 @@ "rc-align": "^4.0.0", "rc-motion": "^2.0.0", "rc-resize-observer": "^1.3.1", - "rc-util": "^5.31.1" + "rc-util": "^5.33.0" }, "engines": { "node": ">=8.x" @@ -4000,9 +4026,9 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, "node_modules/@types/react": { - "version": "18.0.32", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.32.tgz", - "integrity": "sha512-gYGXdtPQ9Cj0w2Fwqg5/ak6BcK3Z15YgjSqtyDizWUfx7mQ8drs0NBUzRRsAdoFVTO8kJ8L2TL8Skm7OFPnLUw==", + "version": "18.0.25", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.25.tgz", + "integrity": "sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -4010,9 +4036,9 @@ } }, "node_modules/@types/react-dom": { - "version": "18.0.11", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz", - "integrity": "sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==", + "version": "18.0.9", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.9.tgz", + "integrity": "sha512-qnVvHxASt/H7i+XG1U1xMiY5t+IHcPGUK7TDMDzom08xa7e86eCeKOiLZezwCKVxJn6NEiiy2ekgX8aQssjIKg==", "dependencies": { "@types/react": "*" } @@ -5178,6 +5204,28 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/babel-plugin-styled-components": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.3.tgz", + "integrity": "sha512-jBioLwBVHpOMU4NsueH/ADcHrjS0Y/WTpt2eGVmmuSFNEv2DF3XhcMncuZlbbjxQ4vzxg+yEr6E6TNjrIQbsJQ==", + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-module-imports": "^7.21.4", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.21", + "picomatch": "^2.3.1" + }, + "peerDependencies": { + "styled-components": ">= 2" + } + }, + "node_modules/babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==", + "peer": true + }, "node_modules/babel-plugin-transform-react-remove-prop-types": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", @@ -5499,6 +5547,15 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/camera-controls": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-2.3.4.tgz", @@ -6042,6 +6099,15 @@ "postcss": "^8.4" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "peer": true, + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.0.tgz", @@ -6223,6 +6289,17 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "peer": true, + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -8567,6 +8644,21 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "peer": true, + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "peer": true + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -13266,9 +13358,9 @@ } }, "node_modules/rc-field-form": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-1.32.0.tgz", - "integrity": "sha512-vr5pA0/gWiBZf0HKdevQJcWSsAac10Z8Nj1Brs3OOCnExk7l+u8GtsW+4cRSqJLug5fxV11dOGXpxf7+aHT/2A==", + "version": "1.32.2", + "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-1.32.2.tgz", + "integrity": "sha512-SzqG1YGyD2P42ztZJ7qoPQp6FV9bD51RUdKGG/5xwybU1wbFdgWTqiMXkS8UR9L4GwXVMKh5PaF2I4EBXd/Rng==", "dependencies": { "@babel/runtime": "^7.18.0", "async-validator": "^4.1.0", @@ -13396,9 +13488,9 @@ } }, "node_modules/rc-overflow": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.3.0.tgz", - "integrity": "sha512-p2Qt4SWPTHAYl4oAao1THy669Fm5q8pYBDBHRaFOekCvcdcrgIx0ByXQMEkyPm8wUDX4BK6aARWecvCRc/7CTA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.3.1.tgz", + "integrity": "sha512-RY0nVBlfP9CkxrpgaLlGzkSoh9JhjJLu6Icqs9E7CW6Ewh9s0peF9OHIex4OhfoPsR92LR0fN6BlCY9Z4VoUtA==", "dependencies": { "@babel/runtime": "^7.11.1", "classnames": "^2.2.1", @@ -13460,9 +13552,9 @@ } }, "node_modules/rc-progress": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-3.4.1.tgz", - "integrity": "sha512-eAFDHXlk8aWpoXl0llrenPMt9qKHQXphxcVsnKs0FHC6eCSk1ebJtyaVjJUzKe0233ogiLDeEFK1Uihz3s67hw==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-3.4.2.tgz", + "integrity": "sha512-iAGhwWU+tsayP+Jkl9T4+6rHeQTG9kDz8JAHZk4XtQOcYN5fj9H34NXNEdRdZx94VUDHMqCb1yOIvi8eJRh67w==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.6", @@ -13521,9 +13613,9 @@ } }, "node_modules/rc-select": { - "version": "14.5.1", - "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.5.1.tgz", - "integrity": "sha512-RQ3yiguq6yJ+kbtip7/6RTq2hOotS/s00nyZL2nxyz5194C6uOtSB8Kgsw3c6ZXII1EDjuJX3zLI1pkxkNWyww==", + "version": "14.5.2", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.5.2.tgz", + "integrity": "sha512-Np/lDHvxCnVhVsheQjSV1I/OMJTWJf1n10wq8q1AGy3ytyYLfjNpi6uaz/pmjsbbiSddSWzJnNZCli9LmgBZsA==", "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/trigger": "^1.5.0", @@ -13660,9 +13752,9 @@ } }, "node_modules/rc-tree": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.7.4.tgz", - "integrity": "sha512-7VfDq4jma+6fvlzfDXvUJ34SaO2EWkcXGBmPgeFmVKsLNNXcKGl4cRAhs6Ts1zqnX994vu/hb3f1dyTjn43RFg==", + "version": "5.7.5", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.7.5.tgz", + "integrity": "sha512-iyM60rUdJh+KinxSjtZ40eox/DdjIwCUM4oBUoOLyrSwXsaoVZtpcVgWwZExjgHp4MSsn3FhVSntO/5c3aMbSQ==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "2.x", @@ -13728,9 +13820,9 @@ } }, "node_modules/rc-util": { - "version": "5.32.4", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.32.4.tgz", - "integrity": "sha512-LRQpfPjtLbN9BrNgU/evbQrhTLfHepJPSKbhHF2Nm1cnNeZkSqXCKDTDPjMXa1VavcnJA1iP39O8PB0+Rc216Q==", + "version": "5.33.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.33.1.tgz", + "integrity": "sha512-oMs2OIV/2lUCF8nllevzLccneyxAzdSOaHSs5y91qOLdqaLbIMsuL49C6/DhF/WKMqiAKEKGdVk2F1sB5HQe9A==", "dependencies": { "@babel/runtime": "^7.18.3", "react-is": "^16.12.0" @@ -13764,13 +13856,11 @@ } }, "node_modules/react": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", - "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.0.0.tgz", + "integrity": "sha512-x+VL6wbT4JRVPm7EGxXhZ8w8LTROaxPXOqhlGyVSrv0sB1jkyFGgXxJ8LVoPRLvPR6/CIZGFmfzqUa2NYeMr2A==", "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" + "loose-envify": "^1.1.0" }, "engines": { "node": ">=0.10.0" @@ -13846,26 +13936,15 @@ } }, "node_modules/react-dom": { - "version": "16.14.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", - "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.0.0.tgz", + "integrity": "sha512-XqX7uzmFo0pUceWFCt7Gff6IyIMzFUn7QMZrbrQfGxtaxXZIcGQzoNpRLE3fQLnS4XzLLPMZX2T9TRcSrasicw==", "dependencies": { "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" + "scheduler": "^0.21.0" }, "peerDependencies": { - "react": "^16.14.0" - } - }, - "node_modules/react-dom/node_modules/scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "react": "^18.0.0" } }, "node_modules/react-error-overlay": { @@ -14272,6 +14351,67 @@ "node": ">=10" } }, + "node_modules/rete": { + "version": "2.0.0-beta.9", + "resolved": "https://registry.npmjs.org/rete/-/rete-2.0.0-beta.9.tgz", + "integrity": "sha512-tmQk0UIjG2qmCamT58Kg8kQQq30/Nal1zr2uqX4mH2L9rZKCUBAL2dWZ160FoRbfTzWr0oFP/7PYhWrYwhkWDA==", + "hasInstallScript": true, + "dependencies": { + "@babel/runtime": "^7.21.0" + } + }, + "node_modules/rete-area-plugin": { + "version": "2.0.0-beta.12", + "resolved": "https://registry.npmjs.org/rete-area-plugin/-/rete-area-plugin-2.0.0-beta.12.tgz", + "integrity": "sha512-F4JLAmCIKCrPr+HinR651cHfyocALZsF7ORRdOXTBMbsyvlRn0KsOz8HsR8t9+AQbcMb45YXFKO+7QG4WvZQ6w==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "peerDependencies": { + "rete": "^2.0.0-beta.9" + } + }, + "node_modules/rete-connection-plugin": { + "version": "2.0.0-beta.16", + "resolved": "https://registry.npmjs.org/rete-connection-plugin/-/rete-connection-plugin-2.0.0-beta.16.tgz", + "integrity": "sha512-jniCW/Mt4gPey46eK9or5aguHf8DSbSZa7fUzzRxpnnmO3MpMXoDj8AKjfoKSR24PXK3Xz1prjAg8OEP7YXRDQ==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "peerDependencies": { + "rete": "^2.0.0-beta.9", + "rete-area-plugin": "^2.0.0-beta.12" + } + }, + "node_modules/rete-react-render-plugin": { + "version": "2.0.0-beta.22", + "resolved": "https://registry.npmjs.org/rete-react-render-plugin/-/rete-react-render-plugin-2.0.0-beta.22.tgz", + "integrity": "sha512-Tcqk6sQMqD8p6+bssarNczZT+MnLv9M1WngmZrfsJ36C1jFckR2A1rmJhqSmXiIE00wcmvBeXblKTHqdFln/Kw==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "usehooks-ts": "^2.9.1" + }, + "peerDependencies": { + "react": "^16.8.6 || ^17 || ^18", + "react-dom": "^16.8.6 || ^17 || ^18", + "rete": "^2.0.0-beta.9", + "rete-area-plugin": "^2.0.0-beta.12", + "rete-render-utils": "^2.0.0-beta.12", + "styled-components": "^5.3.6" + } + }, + "node_modules/rete-render-utils": { + "version": "2.0.0-beta.12", + "resolved": "https://registry.npmjs.org/rete-render-utils/-/rete-render-utils-2.0.0-beta.12.tgz", + "integrity": "sha512-aRXyGQbF9Y9zIg/TuXFXrz+AVj74FLGGONdF/12okPqwm30aUXA3YakdHONfiaxRi/Ibvmrq7nLI+3wU64K4Xw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "peerDependencies": { + "rete": "^2.0.0-beta.9", + "rete-area-plugin": "^2.0.0-beta.12" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -14687,6 +14827,12 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "peer": true + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -15112,6 +15258,57 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz", + "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", + "peer": true, + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/styled-components/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "peer": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -15985,6 +16182,19 @@ "requires-port": "^1.0.0" } }, + "node_modules/usehooks-ts": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.9.1.tgz", + "integrity": "sha512-2FAuSIGHlY+apM9FVlj8/oNhd+1y+Uwv5QNkMQz1oSfdHk4PXo1qoCw9I5M7j0vpH8CSWFJwXbVPeYDjLCx9PA==", + "engines": { + "node": ">=16.15.0", + "npm": ">=8" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/asp-review-app/ui/package.json b/asp-review-app/ui/package.json index 20ac1dd..cff1c44 100644 --- a/asp-review-app/ui/package.json +++ b/asp-review-app/ui/package.json @@ -12,8 +12,8 @@ "@testing-library/user-event": "^13.2.1", "@types/jest": "^27.0.1", "@types/node": "^16.7.13", - "@types/react": "^18.0.0", - "@types/react-dom": "^18.0.0", + "@types/react": "18.0.25", + "@types/react-dom": "18.0.9", "antd": "^5.5.2", "babel-jest": "^27.4.2", "babel-loader": "^8.2.3", @@ -50,16 +50,21 @@ "postcss-normalize": "^10.0.1", "postcss-preset-env": "^7.0.1", "prompts": "^2.4.2", - "react": "^16.12.0", + "react": "18.0.0", "react-app-polyfill": "^3.0.0", "react-dev-utils": "^12.0.1", - "react-dom": "^16.12.0", + "react-dom": "18.0.0", "react-i18next": "^12.2.0", "react-refresh": "^0.11.0", "react-router-dom": "^6.11.2", "react-three-fiber": "^6.0.13", "resolve": "^1.20.0", "resolve-url-loader": "^4.0.0", + "rete": "2.0.0-beta.9", + "rete-area-plugin": "2.0.0-beta.12", + "rete-connection-plugin": "2.0.0-beta.16", + "rete-react-render-plugin": "2.0.0-beta.22", + "rete-render-utils": "2.0.0-beta.12", "sass-loader": "^12.3.0", "semver": "^7.3.5", "sort-by": "^1.2.0", diff --git a/asp-review-app/ui/src/App.css b/asp-review-app/ui/src/App.css index 8e01787..3991e48 100644 --- a/asp-review-app/ui/src/App.css +++ b/asp-review-app/ui/src/App.css @@ -6,7 +6,39 @@ .root{ + overflow-y: hidden; +} +.centeredDiv{ + width: 100vw; display: flex; + justify-content: center; +} +.projects-container{ + width: 100%; + background-color: aliceblue; + display: flex; + flex-direction: column; + justify-content: space-evenly; + align-items: center; + overflow-y:hidden; +} +.centeredContainer{ + display: flex; + flex-direction: column; + align-content: center; + align-items: center; +} +label { + background-color: indigo; + color: white; + padding: 0.5rem; + font-family: sans-serif; + border-radius: 0.3rem; + cursor: pointer; + margin-top: 1rem; } - \ No newline at end of file +#file-chosen{ + margin-left: 0.3rem; + font-family: sans-serif; +} \ No newline at end of file diff --git a/asp-review-app/ui/src/core/repository/http_repository.ts b/asp-review-app/ui/src/core/repository/http_repository.ts index 5908cd5..3f2eed3 100644 --- a/asp-review-app/ui/src/core/repository/http_repository.ts +++ b/asp-review-app/ui/src/core/repository/http_repository.ts @@ -2,21 +2,34 @@ export enum HttpMethod { GET = 'GET', POST = 'POST' } -export enum HttpRoute{ +export enum HttpRoute { insertionPath = '/assembly/preview/insertion_sequence/', assemblyPreviewPath = '/assembly/preview/subsequence/', - projects = '/assembly/preview' + projects = '/assembly/preview', + createProject = '/assembly/create', + ajaxMatrix = 'matrix.json' } export class HttpRepository { static server = 'http://localhost:3002' - static async jsonRequest(method: HttpMethod, url: string, data?: any):Promise { + static async jsonRequest(method: HttpMethod, url: string, data?: any): Promise { const reqInit = { 'body': data, 'method': method, + 'headers': { 'Content-Type': 'application/json' }, } if (data !== undefined) { reqInit['body'] = JSON.stringify(data) } return (await fetch(this.server + url, reqInit)).json() } + static async request(method: HttpMethod, url: string, data?: any): Promise { + const reqInit = { + 'body': data, + 'method': method, + } + if (data !== undefined) { + reqInit['body'] = data + } + return (await fetch(this.server + url, reqInit)).json() + } } \ No newline at end of file diff --git a/asp-review-app/ui/src/features/all_project/all_project_screen.tsx b/asp-review-app/ui/src/features/all_project/all_project_screen.tsx index 3ba25f6..0bf8e16 100644 --- a/asp-review-app/ui/src/features/all_project/all_project_screen.tsx +++ b/asp-review-app/ui/src/features/all_project/all_project_screen.tsx @@ -6,9 +6,33 @@ import { HttpRoute, } from "../../core/repository/http_repository"; import { Button } from "antd"; -export const ProjectsPath = '/' +import { Typography } from "antd"; +import { Card } from "antd"; +import { createProjectRoute } from "../create_project/create_project"; +import { useNavigate } from "react-router-dom"; +import { pathAjaxTopologyScreen } from "../topology_ajax_preview/topology_ajax_preview"; +import { pathStabilityScreen } from "../stability_preview/stability_preview"; + +const { Text, Link, Title } = Typography; +function LinkCreateProjectPage() { + const navigate = useNavigate(); + + return ( + { + navigate(createProjectRoute); + }} + > + <> add new project? + + ); +} + +export const ProjectsPath = "/"; export const ProjectScreen: React.FunctionComponent = () => { const [projects, setProjects] = useState>([]); + const navigate = useNavigate(); useEffect(() => { async function fetchData() { @@ -22,18 +46,44 @@ export const ProjectScreen: React.FunctionComponent = () => { fetchData(); }, []); return ( -
-
Projects
+ <> +
+ Projects +
+ {projects.length === 0 ? ( +
+ Not found projects + +
+ +
+
+ ) : ( +
+ )} +
+
{projects.map((el) => { return ( <> -
{el}
- {" "} + +
{el}
+ + + + + +
); })} +
{projects.length === 0 ? <> : }
-
+ ); }; diff --git a/asp-review-app/ui/src/features/create_project/create_project.tsx b/asp-review-app/ui/src/features/create_project/create_project.tsx new file mode 100644 index 0000000..044701f --- /dev/null +++ b/asp-review-app/ui/src/features/create_project/create_project.tsx @@ -0,0 +1,72 @@ +import { Spin, Typography } from "antd"; +import * as React from "react"; +import { useNavigate } from "react-router-dom"; +import { + HttpMethod, + HttpRepository, + HttpRoute, +} from "../../core/repository/http_repository"; +import { pathStabilityScreen } from "../stability_preview/stability_preview"; + +const { Title } = Typography; + +export const createProjectRoute = "/new_project"; + +const UploadButton = () => { + const navigate = useNavigate(); + const [isLoading, setLoading] = React.useState(false); + + const handleImageChange = function (e: React.ChangeEvent) { + const fileList = e.target.files; + + if (!fileList) return; + + let file = fileList[0] as File; + uploadFile(file); + }; + + const uploadFile = async (file: File) => { + if (file) { + const formData = new FormData(); + formData.append("freecad", file, file.name); + setLoading(true); + await HttpRepository.request( + HttpMethod.POST, + HttpRoute.createProject, + formData + ); + setLoading(false); + navigate(pathStabilityScreen) + } + }; + return isLoading ? ( + <> + + + ) : ( + + ); +}; +export default function CreateProject() { + return ( +
+
+ Create new project +
+
+ +
+
+ ); +} diff --git a/asp-review-app/ui/src/features/stability_preview/stability_preview.tsx b/asp-review-app/ui/src/features/stability_preview/stability_preview.tsx new file mode 100644 index 0000000..f85b80b --- /dev/null +++ b/asp-review-app/ui/src/features/stability_preview/stability_preview.tsx @@ -0,0 +1,57 @@ + +import { Button } from 'antd'; +import * as React from 'react'; +import { useParams } from 'react-router-dom'; +import { HttpRepository, HttpMethod, HttpRoute } from '../../core/repository/http_repository'; + + +export const pathStabilityScreen = '/stability/preview/usecase/' + +interface IStabilityCheckResponce { + status: "rejected" | "fulfilled"; + value: undefined | string; + index: number; +} +interface IStability { + status: boolean; + detail: string; +} + +export const StabilityPreviewScreen: React.FunctionComponent = () => { + const id = useParams().id + const [stabilityResult, setStability] = React.useState(null); + React.useEffect(() => { + const stabilityCheck = async () => { + const result = await HttpRepository.jsonRequest>(HttpMethod.GET, '/' + id + '/generation/step-structure.json') + const promises = [] + for (let i = 0; i !== result.length; i++) { + const stabilitySubId = i + 1 + promises.push(HttpRepository.jsonRequest>(HttpMethod.GET, '/' + id + '/generation/stability/' + stabilitySubId + '/geometry.json')) + } + const stabilityCheck = await (await Promise.allSettled(promises)).map((element, index) => { + return { + status: element.status === 'fulfilled' ? true : false, + detail: result[index], + } + }) + setStability(stabilityCheck) + }; + stabilityCheck() + }, []); + return (
+ {stabilityResult != null ? (<> + {stabilityResult.map((el, index) => { + return (
{el.detail}
{el.status ? (<>Sucses) : (<>)}
) + })} + + ) : (
loading
)} + +
); +}; + + diff --git a/asp-review-app/ui/src/features/topology_ajax_preview/topology_ajax_preview.tsx b/asp-review-app/ui/src/features/topology_ajax_preview/topology_ajax_preview.tsx new file mode 100644 index 0000000..a08435f --- /dev/null +++ b/asp-review-app/ui/src/features/topology_ajax_preview/topology_ajax_preview.tsx @@ -0,0 +1,48 @@ + +import * as React from 'react'; +import { useParams } from 'react-router-dom'; +import { HttpRepository, HttpMethod, HttpRoute } from '../../core/repository/http_repository'; + + +export const pathAjaxTopologyScreen = '/topology/adjax/usecase/' +export interface IAdjaxMatrix { + allPars: string[]; + firstDetail: string; + matrix: StringMap; + matrixError: StringMap | null; +} +interface StringMap { [key: string]: string; } + + +export const MatrixTopologyAdjaxScreen: React.FunctionComponent = () => { + const [matrix, setMatrix] = React.useState(null); + const param = useParams().id + React.useEffect(() => { + async function fetchData() { + setMatrix( + await HttpRepository.jsonRequest( + HttpMethod.GET, + '/' + param + '/' + HttpRoute.ajaxMatrix + ) + ); + } + fetchData(); + }, []); + return (
+ {matrix === null ? (<>loaded) : (<> + {matrix.matrixError != null ? (<> + {Object.keys(matrix.matrixError).map((keyName, i) => { + const m = matrix.matrixError as StringMap; + return ( +
+
{m[keyName]}
+
+ ) + })} + ) : (<>Success)} + )} + +
); +}; + + diff --git a/asp-review-app/ui/src/index.tsx b/asp-review-app/ui/src/index.tsx index ddfc042..b340726 100644 --- a/asp-review-app/ui/src/index.tsx +++ b/asp-review-app/ui/src/index.tsx @@ -14,7 +14,10 @@ import { AssemblyPreviewSubsequence, AssemblyPreviewSubsequencePath, } from "./features/assembly_preview_subsequence/assembly_preview_subsequence_screen"; - +import CreateProject, { createProjectRoute } from "./features/create_project/create_project"; +import { pathAjaxTopologyScreen, MatrixTopologyAdjaxScreen } from "./features/topology_ajax_preview/topology_ajax_preview"; +import { pathStabilityScreen, StabilityPreviewScreen } from "./features/stability_preview/stability_preview"; + const rootElement = document.getElementById("root"); const router = createBrowserRouter([ @@ -22,6 +25,10 @@ const router = createBrowserRouter([ path: ProjectsPath, element: , }, + { + path:createProjectRoute, + element: + }, { path: AssemblyPreviewSubsequencePath + ":id", element: , @@ -30,5 +37,14 @@ const router = createBrowserRouter([ path: AssemblyPreviewInsertVectorPath + ":id", element: , }, + { + path: pathAjaxTopologyScreen + ":id", + element: + }, + { + path: pathStabilityScreen + ':id', + element: + } ]); + render(, rootElement); diff --git a/asp-review-app/ui/yarn.lock b/asp-review-app/ui/yarn.lock index de64e8e..c46e755 100644 --- a/asp-review-app/ui/yarn.lock +++ b/asp-review-app/ui/yarn.lock @@ -23,9 +23,9 @@ "@ctrl/tinycolor" "^3.4.0" "@ant-design/cssinjs@^1.9.1": - version "1.9.1" - resolved "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.9.1.tgz" - integrity sha512-CZt1vCMs/sY7RoacYuIkZwQmb8Bhp99ReNNE9Y8lnUzik8fmCdKAQA7ecvVOFwmNFdcBHga7ye/XIRrsbkiqWw== + version "1.10.1" + resolved "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.10.1.tgz" + integrity sha512-PSoJS8RMzn95ZRg007dJGr6AU0Zim/O+tTN0xmXmh9CkIl4y3wuOr2Zhehaj7s130wPSYDVvahf3DKT50w/Zhw== dependencies: "@babel/runtime" "^7.11.1" "@emotion/hash" "^0.8.0" @@ -217,7 +217,7 @@ dependencies: "@babel/types" "^7.21.0" -"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.21.4": +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.21.4": version "7.21.4" resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz" integrity sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg== @@ -1101,7 +1101,7 @@ "@babel/parser" "^7.20.7" "@babel/types" "^7.20.7" -"@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.21.4", "@babel/traverse@^7.7.2": +"@babel/traverse@^7.20.5", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.21.4", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": version "7.21.4" resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz" integrity sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q== @@ -1279,7 +1279,24 @@ resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz" integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== -"@emotion/unitless@^0.7.5": +"@emotion/is-prop-valid@^1.1.0": + version "1.2.1" + resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz" + integrity sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw== + dependencies: + "@emotion/memoize" "^0.8.1" + +"@emotion/memoize@^0.8.1": + version "0.8.1" + resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz" + integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== + +"@emotion/stylis@^0.8.4": + version "0.8.5" + resolved "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz" + integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== + +"@emotion/unitless@^0.7.4", "@emotion/unitless@^0.7.5": version "0.7.5" resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz" integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== @@ -1685,9 +1702,9 @@ rc-util "^5.27.0" "@rc-component/mini-decimal@^1.0.1": - version "1.0.1" - resolved "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.0.1.tgz" - integrity sha512-9N8nRk0oKj1qJzANKl+n9eNSMUGsZtjwNuDCiZ/KA+dt1fE3zq5x2XxclRcAbOIXnZcJ53ozP2Pa60gyELXagA== + version "1.1.0" + resolved "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz" + integrity sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ== dependencies: "@babel/runtime" "^7.18.0" @@ -1721,9 +1738,9 @@ rc-util "^5.24.4" "@rc-component/trigger@^1.0.4", "@rc-component/trigger@^1.13.0", "@rc-component/trigger@^1.3.6", "@rc-component/trigger@^1.5.0", "@rc-component/trigger@^1.7.0": - version "1.13.3" - resolved "https://registry.npmjs.org/@rc-component/trigger/-/trigger-1.13.3.tgz" - integrity sha512-CA4s8QGj2kagp8dmYRVcSIW5IErw/YBxSeFEsQmt6SB0oaj9pj+akkB6O0S/Y6ww5JrIDu9Bukq89se1oW9F3w== + version "1.13.6" + resolved "https://registry.npmjs.org/@rc-component/trigger/-/trigger-1.13.6.tgz" + integrity sha512-13aF9SrR5XAd+tyV/zja0A2pbrA/zdTCXRBNIsoLp8OmhVOnqiwjP7XZYPulLsH0ioEfvtXR1yI0anJD0/J7PQ== dependencies: "@babel/runtime" "^7.18.3" "@rc-component/portal" "^1.1.0" @@ -1731,7 +1748,7 @@ rc-align "^4.0.0" rc-motion "^2.0.0" rc-resize-observer "^1.3.1" - rc-util "^5.31.1" + rc-util "^5.33.0" "@react-spring/animated@~9.6.1": version "9.6.1" @@ -2286,10 +2303,10 @@ resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== -"@types/react-dom@^18.0.0": - version "18.0.11" - resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.11.tgz" - integrity sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw== +"@types/react-dom@^18.0.0", "@types/react-dom@18.0.9": + version "18.0.9" + resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.9.tgz" + integrity sha512-qnVvHxASt/H7i+XG1U1xMiY5t+IHcPGUK7TDMDzom08xa7e86eCeKOiLZezwCKVxJn6NEiiy2ekgX8aQssjIKg== dependencies: "@types/react" "*" @@ -2307,10 +2324,10 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^18.0.0": - version "18.0.32" - resolved "https://registry.npmjs.org/@types/react/-/react-18.0.32.tgz" - integrity sha512-gYGXdtPQ9Cj0w2Fwqg5/ak6BcK3Z15YgjSqtyDizWUfx7mQ8drs0NBUzRRsAdoFVTO8kJ8L2TL8Skm7OFPnLUw== +"@types/react@*", "@types/react@18.0.25": + version "18.0.25" + resolved "https://registry.npmjs.org/@types/react/-/react-18.0.25.tgz" + integrity sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -3142,6 +3159,22 @@ babel-plugin-polyfill-regenerator@^0.4.1: dependencies: "@babel/helper-define-polyfill-provider" "^0.3.3" +"babel-plugin-styled-components@>= 1.12.0": + version "2.1.3" + resolved "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.3.tgz" + integrity sha512-jBioLwBVHpOMU4NsueH/ADcHrjS0Y/WTpt2eGVmmuSFNEv2DF3XhcMncuZlbbjxQ4vzxg+yEr6E6TNjrIQbsJQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-module-imports" "^7.21.4" + babel-plugin-syntax-jsx "^6.18.0" + lodash "^4.17.21" + picomatch "^2.3.1" + +babel-plugin-syntax-jsx@^6.18.0: + version "6.18.0" + resolved "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz" + integrity sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw== + babel-plugin-transform-react-remove-prop-types@^0.4.24: version "0.4.24" resolved "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz" @@ -3370,6 +3403,11 @@ camelcase@^6.2.0, camelcase@^6.2.1: resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== +camelize@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz" + integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== + camera-controls@^2.3.1: version "2.3.4" resolved "https://registry.npmjs.org/camera-controls/-/camera-controls-2.3.4.tgz" @@ -3741,6 +3779,11 @@ css-blank-pseudo@^3.0.3: dependencies: postcss-selector-parser "^6.0.9" +css-color-keywords@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz" + integrity sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg== + css-declaration-sorter@^6.3.1: version "6.4.0" resolved "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.0.tgz" @@ -3810,6 +3853,15 @@ css-select@^4.1.3: domutils "^2.8.0" nth-check "^2.0.1" +css-to-react-native@^3.0.0: + version "3.2.0" + resolved "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz" + integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ== + dependencies: + camelize "^1.0.0" + css-color-keywords "^1.0.0" + postcss-value-parser "^4.0.2" + css-tree@^1.1.2: version "1.1.3" resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz" @@ -5043,11 +5095,6 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.3.2, fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - function-bind@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" @@ -5297,6 +5344,13 @@ he@^1.2.0: resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +hoist-non-react-statics@^3.0.0: + version "3.3.2" + resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + hoopy@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz" @@ -7868,7 +7922,7 @@ postcss-unique-selectors@^5.1.1: dependencies: postcss-selector-parser "^6.0.5" -postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: +postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== @@ -7957,7 +8011,7 @@ prompts@^2.0.1, prompts@^2.4.2: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.8.1: +prop-types@^15.6.0, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -8120,9 +8174,9 @@ rc-dropdown@~4.1.0: rc-util "^5.17.0" rc-field-form@~1.32.0: - version "1.32.0" - resolved "https://registry.npmjs.org/rc-field-form/-/rc-field-form-1.32.0.tgz" - integrity sha512-vr5pA0/gWiBZf0HKdevQJcWSsAac10Z8Nj1Brs3OOCnExk7l+u8GtsW+4cRSqJLug5fxV11dOGXpxf7+aHT/2A== + version "1.32.2" + resolved "https://registry.npmjs.org/rc-field-form/-/rc-field-form-1.32.2.tgz" + integrity sha512-SzqG1YGyD2P42ztZJ7qoPQp6FV9bD51RUdKGG/5xwybU1wbFdgWTqiMXkS8UR9L4GwXVMKh5PaF2I4EBXd/Rng== dependencies: "@babel/runtime" "^7.18.0" async-validator "^4.1.0" @@ -8204,9 +8258,9 @@ rc-notification@~5.0.4: rc-util "^5.20.1" rc-overflow@^1.0.0, rc-overflow@^1.2.8: - version "1.3.0" - resolved "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.3.0.tgz" - integrity sha512-p2Qt4SWPTHAYl4oAao1THy669Fm5q8pYBDBHRaFOekCvcdcrgIx0ByXQMEkyPm8wUDX4BK6aARWecvCRc/7CTA== + version "1.3.1" + resolved "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.3.1.tgz" + integrity sha512-RY0nVBlfP9CkxrpgaLlGzkSoh9JhjJLu6Icqs9E7CW6Ewh9s0peF9OHIex4OhfoPsR92LR0fN6BlCY9Z4VoUtA== dependencies: "@babel/runtime" "^7.11.1" classnames "^2.2.1" @@ -8232,9 +8286,9 @@ rc-picker@~3.7.4: rc-util "^5.30.0" rc-progress@~3.4.1: - version "3.4.1" - resolved "https://registry.npmjs.org/rc-progress/-/rc-progress-3.4.1.tgz" - integrity sha512-eAFDHXlk8aWpoXl0llrenPMt9qKHQXphxcVsnKs0FHC6eCSk1ebJtyaVjJUzKe0233ogiLDeEFK1Uihz3s67hw== + version "3.4.2" + resolved "https://registry.npmjs.org/rc-progress/-/rc-progress-3.4.2.tgz" + integrity sha512-iAGhwWU+tsayP+Jkl9T4+6rHeQTG9kDz8JAHZk4XtQOcYN5fj9H34NXNEdRdZx94VUDHMqCb1yOIvi8eJRh67w== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.6" @@ -8270,9 +8324,9 @@ rc-segmented@~2.2.0: rc-util "^5.17.0" rc-select@~14.5.0: - version "14.5.1" - resolved "https://registry.npmjs.org/rc-select/-/rc-select-14.5.1.tgz" - integrity sha512-RQ3yiguq6yJ+kbtip7/6RTq2hOotS/s00nyZL2nxyz5194C6uOtSB8Kgsw3c6ZXII1EDjuJX3zLI1pkxkNWyww== + version "14.5.2" + resolved "https://registry.npmjs.org/rc-select/-/rc-select-14.5.2.tgz" + integrity sha512-Np/lDHvxCnVhVsheQjSV1I/OMJTWJf1n10wq8q1AGy3ytyYLfjNpi6uaz/pmjsbbiSddSWzJnNZCli9LmgBZsA== dependencies: "@babel/runtime" "^7.10.1" "@rc-component/trigger" "^1.5.0" @@ -8365,9 +8419,9 @@ rc-tree-select@~5.9.0: rc-util "^5.16.1" rc-tree@~5.7.0, rc-tree@~5.7.4: - version "5.7.4" - resolved "https://registry.npmjs.org/rc-tree/-/rc-tree-5.7.4.tgz" - integrity sha512-7VfDq4jma+6fvlzfDXvUJ34SaO2EWkcXGBmPgeFmVKsLNNXcKGl4cRAhs6Ts1zqnX994vu/hb3f1dyTjn43RFg== + version "5.7.5" + resolved "https://registry.npmjs.org/rc-tree/-/rc-tree-5.7.5.tgz" + integrity sha512-iyM60rUdJh+KinxSjtZ40eox/DdjIwCUM4oBUoOLyrSwXsaoVZtpcVgWwZExjgHp4MSsn3FhVSntO/5c3aMbSQ== dependencies: "@babel/runtime" "^7.10.1" classnames "2.x" @@ -8395,10 +8449,10 @@ rc-upload@~4.3.0: classnames "^2.2.5" rc-util "^5.2.0" -rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.15.0, rc-util@^5.16.0, rc-util@^5.16.1, rc-util@^5.17.0, rc-util@^5.18.1, rc-util@^5.19.2, rc-util@^5.2.0, rc-util@^5.20.1, rc-util@^5.21.0, rc-util@^5.21.2, rc-util@^5.22.5, rc-util@^5.24.4, rc-util@^5.25.2, rc-util@^5.26.0, rc-util@^5.27.0, rc-util@^5.27.1, rc-util@^5.28.0, rc-util@^5.30.0, rc-util@^5.31.1, rc-util@^5.32.0, rc-util@^5.32.2, rc-util@^5.6.1: - version "5.32.4" - resolved "https://registry.npmjs.org/rc-util/-/rc-util-5.32.4.tgz" - integrity sha512-LRQpfPjtLbN9BrNgU/evbQrhTLfHepJPSKbhHF2Nm1cnNeZkSqXCKDTDPjMXa1VavcnJA1iP39O8PB0+Rc216Q== +rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.15.0, rc-util@^5.16.0, rc-util@^5.16.1, rc-util@^5.17.0, rc-util@^5.18.1, rc-util@^5.19.2, rc-util@^5.2.0, rc-util@^5.20.1, rc-util@^5.21.0, rc-util@^5.21.2, rc-util@^5.22.5, rc-util@^5.24.4, rc-util@^5.25.2, rc-util@^5.26.0, rc-util@^5.27.0, rc-util@^5.27.1, rc-util@^5.28.0, rc-util@^5.30.0, rc-util@^5.31.1, rc-util@^5.32.0, rc-util@^5.32.2, rc-util@^5.33.0, rc-util@^5.6.1: + version "5.33.1" + resolved "https://registry.npmjs.org/rc-util/-/rc-util-5.33.1.tgz" + integrity sha512-oMs2OIV/2lUCF8nllevzLccneyxAzdSOaHSs5y91qOLdqaLbIMsuL49C6/DhF/WKMqiAKEKGdVk2F1sB5HQe9A== dependencies: "@babel/runtime" "^7.18.3" react-is "^16.12.0" @@ -8462,15 +8516,13 @@ react-dev-utils@^12.0.1: strip-ansi "^6.0.1" text-table "^0.2.0" -react-dom@*, react-dom@^16.12.0, react-dom@^18.0.0, react-dom@>=16.0.0, react-dom@>=16.11.0, react-dom@>=16.13, react-dom@>=16.8, react-dom@>=16.9.0, react-dom@>=17.0, react-dom@>=18.0: - version "16.14.0" - resolved "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz" - integrity sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw== +react-dom@*, "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.6 || ^17 || ^18", react-dom@^18.0.0, "react-dom@>= 16.8.0", react-dom@>=16.0.0, react-dom@>=16.11.0, react-dom@>=16.13, react-dom@>=16.8, react-dom@>=16.9.0, react-dom@>=17.0, react-dom@>=18.0, react-dom@18.0.0: + version "18.0.0" + resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.0.0.tgz" + integrity sha512-XqX7uzmFo0pUceWFCt7Gff6IyIMzFUn7QMZrbrQfGxtaxXZIcGQzoNpRLE3fQLnS4XzLLPMZX2T9TRcSrasicw== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.19.1" + scheduler "^0.21.0" react-error-overlay@^6.0.11: version "6.0.11" @@ -8495,7 +8547,12 @@ react-is@^16.13.1: resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^17.0.1: +react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-is@^17.0.1, "react-is@>= 16.8.0": version "17.0.2" resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== @@ -8552,14 +8609,12 @@ react-use-measure@^2.1.1: dependencies: debounce "^1.2.1" -react@*, "react@^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", react@^16.12.0, react@^16.14.0, "react@^16.8.0 || ^17 || ^18", "react@^16.8.0 || ^17.0.0 || ^18.0.0", react@^18.0.0, "react@>= 16.8.0", react@>=16.0.0, react@>=16.11.0, react@>=16.13, react@>=16.8, react@>=16.9.0, react@>=17.0, react@>=18.0: - version "16.14.0" - resolved "https://registry.npmjs.org/react/-/react-16.14.0.tgz" - integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== +react@*, "react@^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17 || ^18", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.6 || ^17 || ^18", react@^18.0.0, "react@>= 16.8.0", react@>=16.0.0, react@>=16.11.0, react@>=16.13, react@>=16.8, react@>=16.9.0, react@>=17.0, react@>=18.0, react@18.0.0: + version "18.0.0" + resolved "https://registry.npmjs.org/react/-/react-18.0.0.tgz" + integrity sha512-x+VL6wbT4JRVPm7EGxXhZ8w8LTROaxPXOqhlGyVSrv0sB1jkyFGgXxJ8LVoPRLvPR6/CIZGFmfzqUa2NYeMr2A== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" read-cache@^1.0.0: version "1.0.0" @@ -8766,6 +8821,42 @@ resolve@^2.0.0-next.4: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +rete-area-plugin@^2.0.0-beta.12, rete-area-plugin@2.0.0-beta.12: + version "2.0.0-beta.12" + resolved "https://registry.npmjs.org/rete-area-plugin/-/rete-area-plugin-2.0.0-beta.12.tgz" + integrity sha512-F4JLAmCIKCrPr+HinR651cHfyocALZsF7ORRdOXTBMbsyvlRn0KsOz8HsR8t9+AQbcMb45YXFKO+7QG4WvZQ6w== + dependencies: + "@babel/runtime" "^7.21.0" + +rete-connection-plugin@2.0.0-beta.16: + version "2.0.0-beta.16" + resolved "https://registry.npmjs.org/rete-connection-plugin/-/rete-connection-plugin-2.0.0-beta.16.tgz" + integrity sha512-jniCW/Mt4gPey46eK9or5aguHf8DSbSZa7fUzzRxpnnmO3MpMXoDj8AKjfoKSR24PXK3Xz1prjAg8OEP7YXRDQ== + dependencies: + "@babel/runtime" "^7.21.0" + +rete-react-render-plugin@2.0.0-beta.22: + version "2.0.0-beta.22" + resolved "https://registry.npmjs.org/rete-react-render-plugin/-/rete-react-render-plugin-2.0.0-beta.22.tgz" + integrity sha512-Tcqk6sQMqD8p6+bssarNczZT+MnLv9M1WngmZrfsJ36C1jFckR2A1rmJhqSmXiIE00wcmvBeXblKTHqdFln/Kw== + dependencies: + "@babel/runtime" "^7.21.0" + usehooks-ts "^2.9.1" + +rete-render-utils@^2.0.0-beta.12, rete-render-utils@2.0.0-beta.12: + version "2.0.0-beta.12" + resolved "https://registry.npmjs.org/rete-render-utils/-/rete-render-utils-2.0.0-beta.12.tgz" + integrity sha512-aRXyGQbF9Y9zIg/TuXFXrz+AVj74FLGGONdF/12okPqwm30aUXA3YakdHONfiaxRi/Ibvmrq7nLI+3wU64K4Xw== + dependencies: + "@babel/runtime" "^7.21.0" + +rete@^2.0.0-beta.9, rete@2.0.0-beta.9: + version "2.0.0-beta.9" + resolved "https://registry.npmjs.org/rete/-/rete-2.0.0-beta.9.tgz" + integrity sha512-tmQk0UIjG2qmCamT58Kg8kQQq30/Nal1zr2uqX4mH2L9rZKCUBAL2dWZ160FoRbfTzWr0oFP/7PYhWrYwhkWDA== + dependencies: + "@babel/runtime" "^7.21.0" + retry@^0.13.1: version "0.13.1" resolved "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz" @@ -8861,14 +8952,6 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" -scheduler@^0.19.1: - version "0.19.1" - resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz" - integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - scheduler@^0.21.0: version "0.21.0" resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz" @@ -9025,6 +9108,11 @@ setprototypeof@1.2.0: resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== +shallowequal@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz" + integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" @@ -9373,6 +9461,22 @@ style-loader@^3.3.1: resolved "https://registry.npmjs.org/style-loader/-/style-loader-3.3.2.tgz" integrity sha512-RHs/vcrKdQK8wZliteNK4NKzxvLBzpuHMqYmUVWeKa6MkaIQ97ZTOS0b+zapZhy6GcrgWnvWYCMHRirC3FsUmw== +styled-components@^5.3.6, "styled-components@>= 2": + version "5.3.11" + resolved "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz" + integrity sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/traverse" "^7.4.5" + "@emotion/is-prop-valid" "^1.1.0" + "@emotion/stylis" "^0.8.4" + "@emotion/unitless" "^0.7.4" + babel-plugin-styled-components ">= 1.12.0" + css-to-react-native "^3.0.0" + hoist-non-react-statics "^3.0.0" + shallowequal "^1.1.0" + supports-color "^5.5.0" + stylehacks@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz" @@ -9405,6 +9509,13 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" +supports-color@^5.5.0: + version "5.5.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + supports-color@^7.0.0, supports-color@^7.1.0: version "7.2.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" @@ -9897,6 +10008,11 @@ url-parse@^1.5.3: querystringify "^2.1.1" requires-port "^1.0.0" +usehooks-ts@^2.9.1: + version "2.9.1" + resolved "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.9.1.tgz" + integrity sha512-2FAuSIGHlY+apM9FVlj8/oNhd+1y+Uwv5QNkMQz1oSfdHk4PXo1qoCw9I5M7j0vpH8CSWFJwXbVPeYDjLCx9PA== + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" diff --git a/asp/.gitignore b/asp/.gitignore new file mode 100644 index 0000000..c585e19 --- /dev/null +++ b/asp/.gitignore @@ -0,0 +1 @@ +out \ No newline at end of file diff --git a/asp/main.py b/asp/main.py index 893c97d..48240cd 100644 --- a/asp/main.py +++ b/asp/main.py @@ -1,17 +1,14 @@ import argparse import shutil from helper.fs import FS -from src.usecases.stability_check_usecase import StabilityCheckUseCase from src.usecases.urdf_sub_assembly_usecase import UrdfSubAssemblyUseCase -from src.usecases.sdf_generate_world_usecase import SdfGenerateWorldUseCase +# from src.usecases.sdf_generate_world_usecase import SdfGenerateWorldUseCase from src.model.sdf_geometry import GeometryModel from src.usecases.sdf_sub_assembly_usecase import SdfSubAssemblyUseCase import os -# python3 main.py --generationFolder /Users/idontsudo/robo/Cube3/ --outPath /Users/idontsudo/robo/ --world true --format 'urdf' --stabilityCheck 'true' -# python3 main.py --generationFolder /Users/idontsudo/robo/Cube3/ --outPath /Users/idontsudo/robo/ --world true --format 'sdf' if __name__ == "__main__": parser = argparse.ArgumentParser() @@ -19,8 +16,6 @@ if __name__ == "__main__": parser.add_argument('--outPath', help='save SDF path') parser.add_argument('--world', help='adding sdf world') parser.add_argument('--format', help='urdf,sdf,mujoco') - parser.add_argument('--stabilityCheck', - help='do i need to check the stability?') args = parser.parse_args() if args.generationFolder == None or args.outPath == None: @@ -44,13 +39,10 @@ if __name__ == "__main__": generationFolder=args.generationFolder, outPath=args.outPath ) - if (args.format == 'urdf' and args.stabilityCheck != None): + if (args.format == 'urdf'): UrdfSubAssemblyUseCase().call( geometryModels=geometryModels, assembly=assemblyStructure, world=args.world, generationFolder=args.generationFolder, outPath=args.outPath ) - StabilityCheckUseCase().call( - args.outPath - ) diff --git a/asp/src/usecases/formatter_usecase.py b/asp/src/usecases/formatter_usecase.py index fa0de3f..9efa2e8 100644 --- a/asp/src/usecases/formatter_usecase.py +++ b/asp/src/usecases/formatter_usecase.py @@ -1,11 +1,10 @@ from src.model.enum import Enum -import xmlformatter from helper.fs import FS - +import xmlformatter class FormatterUseCase: def call(outPath: str, format: str): - formatter = xmlformatter.Formatter( + formatter = xmlformatter( indent="1", indent_char="\t", encoding_output="ISO-8859-1", preserve=["literal"]) files = FS.readFilesTypeFolder( diff --git a/asp/src/usecases/stability_check_usecase.py b/asp/src/usecases/stability_check_usecase.py deleted file mode 100644 index c55b092..0000000 --- a/asp/src/usecases/stability_check_usecase.py +++ /dev/null @@ -1,36 +0,0 @@ -import numpy as np -import pybullet as p -import time -import pybullet_data -from helper.fs import FS -from src.usecases.urdf_sub_assembly_usecase import URDF_GENERATOR_FILE -import json - -from src.model.enum import Enum - - -class StabilityCheckUseCase: - def call(self, outPath: str): - dirPath = outPath + Enum.folderPath - DURATION = 10000 - asm = json.loads(FS.readFile(dirPath + URDF_GENERATOR_FILE)) - inc = 0 - for el in asm['asm2']: - FS.writeFile(data=el, filePath=dirPath, - fileName=str(inc) + '.urdf') - inc += 1 - assemblyURDFS = list( - map(lambda el: dirPath+el, FS.readFilesTypeFolder(dirPath, '.urdf'))) - physicsClient = p.connect(p.GUI) - - p.setGravity(0, 0, -10) - - for el in assemblyURDFS: - p.loadURDF(el) - - - for i in range(DURATION): - p.stepSimulation() - time.sleep(1./240.) - - p.disconnect() diff --git a/asp/src/usecases/urdf_sub_assembly_usecase.py b/asp/src/usecases/urdf_sub_assembly_usecase.py index 0c82827..907662e 100644 --- a/asp/src/usecases/urdf_sub_assembly_usecase.py +++ b/asp/src/usecases/urdf_sub_assembly_usecase.py @@ -5,6 +5,7 @@ from src.model.asm import Assembly from src.model.sdf_geometry import GeometryModel from helper.fs import filterModels, listGetFirstValue import json +import re def toUrdf(el: GeometryModel): @@ -21,6 +22,7 @@ class UrdfSubAssemblyUseCase(Assembly): generateSubAssemblyModels = self.generateSubAssembly(assembly) inc = 0 for key, value in generateSubAssemblyModels.items(): + keyAsm = int(re.findall(r'\d', key)[0]) inc += 1 if value['assembly'].__len__() != 0: model: Optional[GeometryModel] = listGetFirstValue( @@ -32,11 +34,9 @@ class UrdfSubAssemblyUseCase(Assembly): geometryModels, value['assembly']))) urdfs.append(listGetFirstValue( geometryModels, None, lambda x: x.name == value['part']) .toUrdf()) - asm[key] = urdfs + asm[keyAsm] = urdfs self.copy(generationFolder=generationFolder, format='/sdf', outPath=outPath) - FS.writeFile(data=json.dumps(asm), fileName=URDF_GENERATOR_FILE, filePath=dirPath) - # for el in asm.keys(): diff --git a/cad_generation/env.json b/cad_generation/env.json new file mode 100644 index 0000000..3945e7a --- /dev/null +++ b/cad_generation/env.json @@ -0,0 +1,6 @@ +{ + "doc": "/home/idontsudo/t/framework/asp-review-app/server/public/cubes/cubes.FCStd", + "out": "/home/idontsudo/t/framework/cad_generation", + "resultURL": "http://localhost:3002/assembly/save/out", + "projectId": "cubes" +} \ No newline at end of file diff --git a/cad_generation/helper/fs.py b/cad_generation/helper/fs.py new file mode 100644 index 0000000..3026bb4 --- /dev/null +++ b/cad_generation/helper/fs.py @@ -0,0 +1,15 @@ +import os +import json + + +class FS: + def readJSON(path: str): + return json.loads((open(path)).read()) + + def writeFile(data, filePath, fileName): + file_to_open = filePath + fileName + + f = open(file_to_open, 'w', encoding='utf-8', + errors='ignore') + f.write(data) + f.close() diff --git a/cad_generation/helper/is_solid.py b/cad_generation/helper/is_solid.py new file mode 100644 index 0000000..431c814 --- /dev/null +++ b/cad_generation/helper/is_solid.py @@ -0,0 +1,18 @@ +import FreeCAD + + +def is_object_solid(obj): + """If obj is solid return True""" + if not isinstance(obj, FreeCAD.DocumentObject): + return False + + if not hasattr(obj, 'Shape'): + return False + + if not hasattr(obj.Shape, 'Solids'): + return False + + if len(obj.Shape.Solids) == 0: + return False + + return True diff --git a/cad_generation/main.py b/cad_generation/main.py new file mode 100644 index 0000000..83ddba7 --- /dev/null +++ b/cad_generation/main.py @@ -0,0 +1,19 @@ +import requests +import FreeCAD as App +from helper.fs import FS +from scenarios.robossembler_freecad_export_scenario import RobossemblerFreeCadExportScenario +import shutil +import os +import FreeCADGui as Gui + + +def main(): + env = FS.readJSON('./env.json') + App.openDocument(env.get('doc')) + RobossemblerFreeCadExportScenario().call(env.get('out')) + # requests.post(url=env.get('resultURL'), files={'zip': open(env.get('out') + '/' + 'generation.zip', "rb"), 'id':env.get('projectId')}) + # os.remove('./generation.zip') + App.closeDocument(App.ActiveDocument.Name) + freecadQTWindow = Gui.getMainWindow() + freecadQTWindow.close() +main() diff --git a/cad_generation/model/files_generator.py b/cad_generation/model/files_generator.py new file mode 100644 index 0000000..68b4a0b --- /dev/null +++ b/cad_generation/model/files_generator.py @@ -0,0 +1,13 @@ +from enum import Enum + + +class FilesGenerator(Enum): + DETAIL = 'detail.json' + ASSEMBLY = 'assembly.json' + + +class FolderGenerator(Enum): + MESHES = 'meshes' + ASSETS = 'assets' + SDF = 'sdf' + ASSEMBlY = 'assembly' diff --git a/cad_generation/model/geometry_part.py b/cad_generation/model/geometry_part.py new file mode 100644 index 0000000..f63f055 --- /dev/null +++ b/cad_generation/model/geometry_part.py @@ -0,0 +1,86 @@ +from typing import Any, TypeVar, Type, cast + + +T = TypeVar("T") + + +def from_float(x: Any) -> float: + assert isinstance(x, (float, int)) and not isinstance(x, bool) + return float(x) + + +def to_float(x: Any) -> float: + assert isinstance(x, float) + return x + + +def to_class(c: Type[T], x: Any) -> dict: + assert isinstance(x, c) + return cast(Any, x).to_dict() + + +class Axis: + x: float + y: float + z: float + + def __init__(self, x: float, y: float, z: float) -> None: + self.x = x + self.y = y + self.z = z + + @staticmethod + def from_dict(obj: Any) -> 'Axis': + assert isinstance(obj, dict) + x = from_float(obj.get("x")) + y = from_float(obj.get("y")) + z = from_float(obj.get("z")) + return Axis(x, y, z) + + def to_dict(self) -> dict: + result: dict = {} + result["x"] = to_float(self.x) + result["y"] = to_float(self.y) + result["z"] = to_float(self.z) + return result + + +class GeometryPart: + euler: Axis + position: Axis + rotation: Axis + center: Axis + + def __init__(self, euler: Axis, position: Axis, rotation: Axis, center: Axis) -> None: + self.euler = euler + self.position = position + self.rotation = rotation + self.center = center + + @staticmethod + def from_dict(obj: Any) -> 'GeometryPart': + assert isinstance(obj, dict) + euler = Axis.from_dict(obj.get("euler")) + position = Axis.from_dict(obj.get("position")) + rotation = Axis.from_dict(obj.get("rotation")) + center = Axis.from_dict(obj.get("center")) + return GeometryPart(euler, position, rotation, center) + + def to_dict(self) -> dict: + result: dict = {} + result["euler"] = to_class(Axis, self.euler) + result["position"] = to_class(Axis, self.position) + result["rotation"] = to_class(Axis, self.rotation) + result["center"] = to_class(Axis, self.center) + return result + + def toJson(self) -> str: + return str(self.to_dict()).replace('\'', '"') + + +def geometry_part_from_dict(s: Any) -> GeometryPart: + return GeometryPart.from_dict(s) + + +def geometry_part_to_dict(x: GeometryPart) -> Any: + return to_class(GeometryPart, x) diff --git a/cad_generation/model/join_mesh_model.py b/cad_generation/model/join_mesh_model.py new file mode 100644 index 0000000..52ca96b --- /dev/null +++ b/cad_generation/model/join_mesh_model.py @@ -0,0 +1,33 @@ +import FreeCAD +import Mesh +import FreeCAD as App +from model.mesh_part_model import MeshPartModel + + +class JoinMeshModel: + id = None + mesh = None + + def __init__(self, meshesPartModels: list['MeshPartModel']) -> None: + meshes = [] + import Mesh + from random import randrange + for el in meshesPartModels: + meshes.append(el.mesh.Mesh) + + self.id = 'MergedMesh' + str(randrange(1000000)) + document = App.ActiveDocument + merged_mesh = Mesh.Mesh() + for el in meshes: + merged_mesh.addMesh(el) + + new_obj = App.activeDocument().addObject("Mesh::Feature", self.id) + new_obj.Mesh = merged_mesh + new_obj.ViewObject.DisplayMode = "Flat Lines" # Set display mode to flat lines + self.mesh = new_obj + + def remove(self): + try: + App.ActiveDocument.removeObject(self.id) + except Exception as e: + print(e) diff --git a/cad_generation/model/mesh_part_model.py b/cad_generation/model/mesh_part_model.py new file mode 100644 index 0000000..dc77871 --- /dev/null +++ b/cad_generation/model/mesh_part_model.py @@ -0,0 +1,32 @@ +import FreeCAD as App +import uuid +import Mesh +import Part +# import PartGui +import MeshPart + + +class MeshPartModel: + id = None + mesh = None + + def __init__(self, part) -> None: + try: + from random import randrange + self.id = 'mesh' + str(randrange(1000000)) + document = App.ActiveDocument + mesh = document.addObject("Mesh::Feature", self.id) + shape = Part.getShape(part, "") + mesh.Mesh = MeshPart.meshFromShape( + Shape=shape, LinearDeflection=20, AngularDeflection=0.1, Relative=False) + mesh.Label = self.id + self.mesh = mesh + except Exception as e: + print(e) + pass + + def remove(self): + try: + App.ActiveDocument.removeObject(self.mesh.Label) + except Exception as e: + print(e) diff --git a/cad_generation/model/sdf_geometry_model.py b/cad_generation/model/sdf_geometry_model.py new file mode 100644 index 0000000..dca815d --- /dev/null +++ b/cad_generation/model/sdf_geometry_model.py @@ -0,0 +1,107 @@ +import json + + +def from_str(x): + assert isinstance(x, str) + return x + + +def from_none(x): + assert x is None + return x + + +def from_union(fs, x): + for f in fs: + try: + return f(x) + except: + pass + assert False + + +def to_class(c, x): + assert isinstance(x, c) + return x.to_dict() + + +class SdfGeometryModel: + def __init__(self, name, ixx, ixy, ixz, iyy, izz, massSDF, posX, posY, posZ, eulerX, eulerY, eulerZ, iyz, stl, friction): + self.name = name + self.ixx = ixx + self.ixy = ixy + self.ixz = ixz + self.iyy = iyy + self.izz = izz + self.massSDF = massSDF + self.posX = posX + self.posY = posY + self.posZ = posZ + self.eulerX = eulerX + self.eulerY = eulerY + self.eulerZ = eulerZ + self.iyz = iyz + self.stl = stl + self.friction = friction + + @staticmethod + def from_dict(obj): + assert isinstance(obj, dict) + name = from_union([from_str, from_none], obj.get("name")) + ixx = from_union([from_str, from_none], obj.get("ixx")) + ixy = from_union([from_str, from_none], obj.get("ixy")) + ixz = from_union([from_str, from_none], obj.get("ixz")) + iyy = from_union([from_str, from_none], obj.get("iyy")) + izz = from_union([from_str, from_none], obj.get("izz")) + massSDF = from_union([from_str, from_none], obj.get("massSDF")) + posX = from_union([from_str, from_none], obj.get("posX")) + posY = from_union([from_str, from_none], obj.get("posY")) + posZ = from_union([from_str, from_none], obj.get("posZ")) + eulerX = from_union([from_str, from_none], obj.get("eulerX")) + eulerY = from_union([from_str, from_none], obj.get("eulerY")) + eulerZ = from_union([from_str, from_none], obj.get("eulerZ")) + iyz = from_union([from_str, from_none], obj.get("iyz")) + stl = from_union([from_str, from_none], obj.get("stl") ) + friction = from_union([from_str, from_none], obj.get("friction")) + return SdfGeometryModel(name, ixx, ixy, ixz, iyy, izz, massSDF, posX, posY, posZ, eulerX, eulerY, eulerZ, iyz,stl,friction) + + def to_dict(self): + result = {} + if self.name is not None: + result["name"] = from_union([from_str, from_none], self.name) + if self.ixx is not None: + result["ixx"] = from_union([from_str, from_none], self.ixx) + if self.ixy is not None: + result["ixy"] = from_union([from_str, from_none], self.ixy) + if self.ixz is not None: + result["ixz"] = from_union([from_str, from_none], self.ixz) + if self.iyy is not None: + result["iyy"] = from_union([from_str, from_none], self.iyy) + if self.izz is not None: + result["izz"] = from_union([from_str, from_none], self.izz) + if self.massSDF is not None: + result["massSDF"] = from_union([from_str, from_none], self.massSDF) + if self.posX is not None: + result["posX"] = from_union([from_str, from_none], self.posX) + if self.posY is not None: + result["posY"] = from_union([from_str, from_none], self.posY) + if self.posZ is not None: + result["posZ"] = from_union([from_str, from_none], self.posZ) + if self.eulerX is not None: + result["eulerX"] = from_union([from_str, from_none], self.eulerX) + if self.eulerY is not None: + result["eulerY"] = from_union([from_str, from_none], self.eulerY) + if self.eulerZ is not None: + result["eulerZ"] = from_union([from_str, from_none], self.eulerZ) + if self.iyz is not None: + result["iyz"] = from_union([from_str, from_none], self.iyz) + if self.stl is not None: + result["stl"] = from_union([from_str, from_none], self.stl) + if self.friction is not None: + result["friction"] = from_union([from_str, from_none], self.eulerZ) + return result + + def toJSON(self) -> str: + return str(self.to_dict()).replace('\'', '"') + + \ No newline at end of file diff --git a/cad_generation/model/simple_copy_part_model.py b/cad_generation/model/simple_copy_part_model.py new file mode 100644 index 0000000..05c54f0 --- /dev/null +++ b/cad_generation/model/simple_copy_part_model.py @@ -0,0 +1,30 @@ +import FreeCAD as App +import Part + + +class SimpleCopyPartModel: + id = None + copyLink = None + label = None + part = None + + def getPart(self): + return self.part + + def __init__(self, part) -> None: + try: + from random import randrange + self.id = str(randrange(1000000)) + childObj = part + __shape = Part.getShape( + childObj, '', needSubElement=False, refine=False) + obj = App.ActiveDocument.addObject('Part::Feature', self.id) + obj.Shape = __shape + self.part = obj + self.label = obj.Label + App.ActiveDocument.recompute() + except Exception as e: + print(e) + + def remove(self): + App.ActiveDocument.removeObject(self.label) diff --git a/cad_generation/scenarios/robossembler_freecad_export_scenario.py b/cad_generation/scenarios/robossembler_freecad_export_scenario.py new file mode 100644 index 0000000..c834efb --- /dev/null +++ b/cad_generation/scenarios/robossembler_freecad_export_scenario.py @@ -0,0 +1,54 @@ + +from usecases.export_assembly_them_all_usecase import ExportAssemblyThemAllUseCase +import FreeCAD + +from usecases.export_usecase import EXPORT_TYPES, ExportUseCase +from usecases.get_sdf_geometry_usecase import SdfGeometryUseCase +from usecases.assembly_parse_usecase import AssemblyParseUseCase +from usecases.geometry_usecase import GeometryUseCase +from model.geometry_part import GeometryPart +from model.files_generator import FolderGenerator +from helper.fs import FS +import os +# import ImportGui +import shutil + + +class RobossemblerFreeCadExportScenario: + + def call(self, path): + + + directory = path + '/' + 'generation' + if os.path.exists(directory): + shutil.rmtree(directory) + if not os.path.exists(directory): + os.makedirs(directory) + + __objs__ = FreeCAD.ActiveDocument.RootObjects + directoryExport = directory + '/' + os.makedirs(directoryExport + FolderGenerator.ASSETS.value) + + os.makedirs(directoryExport + FolderGenerator.SDF.value) + os.makedirs(directoryExport + FolderGenerator.SDF.value + '/' + FolderGenerator.MESHES.value) + os.makedirs(directoryExport + FolderGenerator.ASSEMBlY.value) + f = open(directory + "/step-structure.json", "w") + f.write(AssemblyParseUseCase().toJson()) + f.close() + self.geometry(directory) + ExportAssemblyThemAllUseCase().call(directoryExport) + + shutil.make_archive(directory, 'zip', directory) + + shutil.rmtree(directory) + return True + + def geometry(self, outPutsPath: str): + exportUseCase = ExportUseCase.call(outPutsPath,EXPORT_TYPES.OBJ) + for el in SdfGeometryUseCase().call(exportUseCase): + FS.writeFile(el.toJSON(), outPutsPath + '/' + FolderGenerator.ASSETS.value + '/', el.name + '.json',) + + + + + \ No newline at end of file diff --git a/cad_generation/usecases/asm4parser_usecase.py b/cad_generation/usecases/asm4parser_usecase.py new file mode 100644 index 0000000..f732d1b --- /dev/null +++ b/cad_generation/usecases/asm4parser_usecase.py @@ -0,0 +1,53 @@ +import FreeCAD as App + +class Asm4StructureParseUseCase: + _parts = [] + _label = [] + + def getSubPartsLabel(self, group): + groupLabel = [] + for el in group: + if str(el) == '': + groupLabel.append(el.Label) + return groupLabel + + def parseLabel(self, nextGroup, label, level=2, nextGroupParse=0): + if nextGroup.__len__() == nextGroupParse: + return + else: + groupParts = [] + + for el in nextGroup: + if str(el) == '': + groupParts.append(el) + + for el in groupParts: + if str(el) == '': + label.append({ + "level": level, + "attachedTo": el.AttachedTo.split('#'), + "label": el.Label, + "axis": self.getSubPartsLabel(el.Group) + }) + + def initParse(self): + + model = App.ActiveDocument.RootObjects[1] + self._label.append({ + "level": 1, + "attachedTo": "Parent Assembly", + "label": model.Label, + "axis": self.getSubPartsLabel(model.Group) + }) + for parent in model.Group: + if str(parent) == '': + self._label.append({ + "level": 1, + "attachedTo": parent.AttachedTo.split('#'), + "label": parent.Label, + "axis": self.getSubPartsLabel(parent.Group) + }) + print(self._label) + + + \ No newline at end of file diff --git a/cad_generation/usecases/assembly_parse_usecase.py b/cad_generation/usecases/assembly_parse_usecase.py new file mode 100644 index 0000000..e3b4e0d --- /dev/null +++ b/cad_generation/usecases/assembly_parse_usecase.py @@ -0,0 +1,58 @@ +import FreeCAD as App +def is_object_solid(obj): + """If obj is solid return True""" + if not isinstance(obj, App.DocumentObject): + return False + if hasattr(obj, 'Group'): + return False + + if not hasattr(obj, 'Shape'): + return False + # if not hasattr(obj.Shape, 'Mass'): + # return False + if not hasattr(obj.Shape, 'Solids'): + return False + + if len(obj.Shape.Solids) == 0: + return False + + return True + + +class AssemblyParseUseCase: + _parts = [] + + _asm = [] + + def getAsm(self): + return self._asm + + def __init__(self) -> None: + if (self._asm.__len__() == 0): + self.initParse() + pass + + def initParse(self): + for el in App.ActiveDocument.Objects: + if (is_object_solid(el)): + self._asm.append(el.Label) + + def toJson(self): + return str(self._asm).replace('\'', "\"") + + def getSubPartsLink(self, group): + groupLink = {} + for el in group: + if (is_object_solid(el)): + if str(el.Shape).find('Solid') != -1: + if groupLink.get(el.Label) == None: + groupLink[el.Label] = [] + for i in el.Group: + if str(i).find('Pad') != -1: + groupLink[el.Label].append(i) + if groupLink.__len__() == 0: + return None + return groupLink + + def getLinkedProperty(self): + return self._asm diff --git a/cad_generation/usecases/export_assembly_them_all_usecase.py b/cad_generation/usecases/export_assembly_them_all_usecase.py new file mode 100644 index 0000000..b8f0a57 --- /dev/null +++ b/cad_generation/usecases/export_assembly_them_all_usecase.py @@ -0,0 +1,92 @@ + + +from typing import List +import FreeCAD as App +import Part +from model.join_mesh_model import JoinMeshModel +from model.mesh_part_model import MeshPartModel +from helper.fs import FS +from helper.is_solid import is_object_solid +from model.simple_copy_part_model import SimpleCopyPartModel +from model.files_generator import FolderGenerator +from usecases.assembly_parse_usecase import AssemblyParseUseCase +import os +import json + + +class ExportAssemblyThemAllUseCase: + + def call(self, path): + assembly = AssemblyParseUseCase().getAsm() + asmStructure = {} + inc = 0 + for el in assembly: + if (inc != 0): + asmStructure[inc] = { + "child": el, + "parents": assembly[0:inc] + } + inc += 1 + objectsFreeCad = App.ActiveDocument.Objects + asmSolids = {} + for k, v in asmStructure.items(): + assemblyParentList = v['parents'] + assemblyChild = v['child'] + for el in assemblyParentList: + for solid in objectsFreeCad: + if (el == solid.Label): + if (asmSolids.get(k) is None): + + asmSolids[k] = {'parents': [], 'child': list( + filter(lambda x: x.Label == assemblyChild, objectsFreeCad))[0]} + + asmSolids[k]['parents'].append(solid) + + inc = 0 + for k, v in asmSolids.items(): + geometry = {"0": [], "1": []} + if (k != 0): + App.activeDocument().addObject("Part::Compound", "Compound") + + copyLinks = list( + map(lambda el: SimpleCopyPartModel(el), v['parents'])) + + if copyLinks != None: + App.activeDocument().Compound.Links = list( + map(lambda el: el.getPart(), copyLinks)) + + object = App.activeDocument().getObject('Compound') + boundBox = object.Shape.BoundBox + geometry['0'].append(boundBox.XMax) + geometry['0'].append(boundBox.YMax) + geometry['0'].append(boundBox.ZMax) + + os.makedirs( + path + FolderGenerator.ASSEMBlY.value + '/' + '0000' + str(k)) + boundBoxChild = v['child'].Shape.BoundBox + geometry['1'].append(boundBoxChild.XMax) + geometry['1'].append(boundBoxChild.YMax) + geometry['1'].append(boundBoxChild.ZMax) + meshParents = [] + + for el in v['parents']: + meshParents.append(MeshPartModel(el)) + joinMesh = JoinMeshModel(meshParents) + for el in meshParents: + el.remove() + import importOBJ + importOBJ.export(joinMesh.mesh, path + FolderGenerator.ASSEMBlY.value + + '/' + '0000' + str(k) + '/' + str(1) + '.obj') + joinMesh.remove() + importOBJ.export(v['child'], path + FolderGenerator.ASSEMBlY.value + + '/' + '0000' + str(k) + '/' + str(0) + '.obj') + FS.writeFile(json.dumps(geometry), path + FolderGenerator.ASSEMBlY.value + + '/' + '0000' + str(k) + '/', 'translation.json') + + App.ActiveDocument.removeObject("Compound") + for el in copyLinks: + el.remove() + App.activeDocument().recompute() + inc += 1 + + diff --git a/cad_generation/usecases/export_usecase.py b/cad_generation/usecases/export_usecase.py new file mode 100644 index 0000000..fed67fa --- /dev/null +++ b/cad_generation/usecases/export_usecase.py @@ -0,0 +1,36 @@ +# import importDAE +import Mesh +import FreeCAD as App +from model.files_generator import FolderGenerator +from helper.is_solid import is_object_solid +from enum import Enum + +class EXPORT_TYPES(Enum): + STL = 'STL' + DAO = 'DAO' + OBJ = 'OBJ' + + +class ExportUseCase: + def call(path: str, type: EXPORT_TYPES): + meshes = {} + for el in App.ActiveDocument.Objects: + if (is_object_solid(el)): + match type.value: + case EXPORT_TYPES.STL.value: + Mesh.export([el], path + '/' + FolderGenerator.SDF.value + + '/' + FolderGenerator.MESHES.value + '/' + el.Label + '.stl') + meshes[el.Label] = '/' + FolderGenerator.MESHES.value + \ + '/' + el.Label + '.stl' + + # case EXPORT_TYPES.DAO.value: + # importDAE.export([el], path + '/' + FolderGenerator.SDF.value + + # '/' + FolderGenerator.MESHES.value + '/' + el.Label + '.dae') + case EXPORT_TYPES.OBJ.value: + import importOBJ + importOBJ.export([el], path + '/' + FolderGenerator.SDF.value + + '/' + FolderGenerator.MESHES.value + '/' + el.Label + '.obj') + meshes[el.Label] = '/' + FolderGenerator.MESHES.value + \ + '/' + el.Label + '.obj' + print(300) + return meshes diff --git a/cad_generation/usecases/geometry_usecase.py b/cad_generation/usecases/geometry_usecase.py new file mode 100644 index 0000000..13ebfd2 --- /dev/null +++ b/cad_generation/usecases/geometry_usecase.py @@ -0,0 +1,58 @@ + +import FreeCAD as App +from helper.is_solid import is_object_solid + + +class GeometryUseCase: + def call() -> dict: + labels = [] + Error = False + for el in App.ActiveDocument.Objects: + try: + + if is_object_solid(el): + labels.append(el.Label) + + geometry = { + "euler": { + "x": None, + "y": None, + "z": None + }, + "position": { + "x": None, + "y": None, + "z": None + }, + "rotation": { + "x": None, + "y": None, + "z": None + }, + "center": { + "x": None, + "y": None, + "z": None + }, + + } + + boundBox = el.Shape.BoundBox + geometry["center"]["x"] = boundBox.Center.x + geometry["center"]["y"] = boundBox.Center.y + geometry["center"]["z"] = boundBox.Center.z + geometry["position"]['x'] = boundBox.XMax + geometry["position"]['y'] = boundBox.YMax + geometry["position"]['z'] = boundBox.ZMax + rotation = el.Placement.Rotation + geometry["rotation"]['x'] = rotation.Axis.z + geometry["rotation"]['y'] = rotation.Axis.y + geometry["rotation"]['z'] = rotation.Axis.z + euler = el.Placement.Rotation.toEuler() + geometry["euler"]['x'] = euler[0] + geometry["euler"]['y'] = euler[1] + geometry["euler"]['z'] = euler[2] + except Exception as e: + print(e) + # App.Console.PrintMessage("Clicked on position: ("+str(pos[0])+", "+str(pos[1])+")\n") + return {"geometry": geometry, "labels": labels, "label": el.Label} diff --git a/cad_generation/usecases/get_sdf_geometry_usecase.py b/cad_generation/usecases/get_sdf_geometry_usecase.py new file mode 100644 index 0000000..45869c5 --- /dev/null +++ b/cad_generation/usecases/get_sdf_geometry_usecase.py @@ -0,0 +1,68 @@ +import FreeCAD as App +from model.sdf_geometry_model import SdfGeometryModel + +from helper.is_solid import is_object_solid + + +class SdfGeometryUseCase: + ShapePropertyCheck = ['Mass','MatrixOfInertia','Placement', ] + PartPropertyCheck = ['Shape'] + def call(self, stlPaths:dict) -> list[SdfGeometryModel]: + materialSolid = {} + for el in App.ActiveDocument.Objects: + if str(el) == '': + friction = el.Material.get('SlidingFriction') + for i in el.References: + materialSolid[i[0].Label] = friction + geometry = [] + try: + for el in App.ActiveDocument.Objects: + if is_object_solid(el): + mass = el.Shape.Mass + inertia = el.Shape.MatrixOfInertia + pos = el.Shape.Placement + inertia = el.Shape.MatrixOfInertia + name = el.Label + ixx = str(inertia.A11 / 1000000) + ixy = str(inertia.A12 / 1000000) + ixz = str(inertia.A13 / 1000000) + iyy = str(inertia.A22 / 1000000) + iyz = str(inertia.A23 / 1000000) + izz = str(inertia.A33 / 1000000) + massSDF = str(mass / 1000000) + posX = str(pos.Base[0] / 1000000) + posY = str(pos.Base[1] / 1000000) + posZ = str(pos.Base[2] / 1000000) + eulerX = str(pos.Rotation.toEuler()[0]) + eulerY = str(pos.Rotation.toEuler()[1]) + eulerZ = str(pos.Rotation.toEuler()[2]) + + geometry.append( + SdfGeometryModel( + stl=stlPaths.get(el.Label), + name=name, + ixx=ixx, + ixz=ixz, + ixy=ixy, + iyy=iyy, + iyz=iyz, + izz=izz, + massSDF=massSDF, + posX=posX, + posY=posY, + posZ=posZ, + eulerX=eulerX, + eulerY=eulerY, + eulerZ=eulerZ, + friction=materialSolid.get(el.Label) or '', + ) + ) + except Exception as e: + print(200) + + return geometry + + + + + diff --git a/cad_stability_check/.gitignore b/cad_stability_check/.gitignore new file mode 100644 index 0000000..2b2f40c --- /dev/null +++ b/cad_stability_check/.gitignore @@ -0,0 +1,95 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject +env.json \ No newline at end of file diff --git a/cad_stability_check/main.py b/cad_stability_check/main.py new file mode 100644 index 0000000..b4684e8 --- /dev/null +++ b/cad_stability_check/main.py @@ -0,0 +1,79 @@ +import FreeCAD as App +import json +import re +from pyquaternion import Quaternion + +def importObjAtPath(path: str): + import importOBJ + importOBJ.insert(u"" + path, App.ActiveDocument.Label) + + pass + + +def getFullPathObj(assemblyFolder: str, name: str): + return assemblyFolder + 'sdf/meshes/' + name + '.obj' + + +def computedStabiliti(refElement, childElement): + b = childElement.Shape.BoundBox + App.activeDocument().addObject("Part::MultiCommon", "Common") + App.activeDocument().Common.Shapes = [refElement, childElement, ] + App.ActiveDocument.getObject('Common').ViewObject.ShapeColor = getattr(App.getDocument('cubes').getObject( + refElement.Name).getLinkedObject(True).ViewObject, 'ShapeColor', App.getDocument('cubes').getObject('Common').ViewObject.ShapeColor) + App.ActiveDocument.getObject('Common').ViewObject.DisplayMode = getattr(App.getDocument('cubes').getObject( + childElement.Name).getLinkedObject(True).ViewObject, 'DisplayMode', App.getDocument('cubes').getObject('Common').ViewObject.DisplayMode) + App.ActiveDocument.recompute() + obj = App.ActiveDocument.getObjectsByLabel('Common')[0] + + shp = obj.Shape + bbox = shp.BoundBox + if bbox.XLength == b.XLength and bbox.YLength == b.YLength and b.ZLength == bbox.ZLength: + return True + return False + + +def main(): + App.newDocument() + env = json.loads((open('./env.json')).read()) + coordinatsFilePath = env.get('pathToTheSimulationCoordinatesFile') + assemblyFolder = env.get('generationFolder') + buildNumber = int(re.findall(r'\d', coordinatsFilePath)[0]) + + assemblyStructure = json.loads( + (open(assemblyFolder + 'step-structure.json')).read()) + assemblyNumber = int(buildNumber) + activeDetail = assemblyStructure[assemblyNumber] + + subassemblyNotParticipatingInMarkup = assemblyStructure[0:assemblyNumber - 1] + detailOfTheMarkingZoneOfWhich = assemblyStructure[assemblyNumber - 1] + importObjAtPath(getFullPathObj(assemblyFolder, activeDetail)) + importObjAtPath(getFullPathObj( + assemblyFolder, detailOfTheMarkingZoneOfWhich)) + meshMark = App.ActiveDocument.Objects[0] + meshDetailOfTheMarkZone = App.ActiveDocument.Objects[1] + meshMark.ViewObject.ShapeColor = (0.31, 0.77, 0.87) + meshDetailOfTheMarkZone.ViewObject.ShapeColor = (0.68, 0.66, 0.95) + for el in list(map(lambda el: getFullPathObj(assemblyFolder, el), subassemblyNotParticipatingInMarkup)): + importObjAtPath(el) + for el in App.ActiveDocument.Objects[2:App.ActiveDocument.Objects.__len__()]: + el.ViewObject.ShapeColor = (0.32, 0.05, 0.38) + + coordinats = json.loads((open(coordinatsFilePath)).read()) + inc = 1 + for el in App.ActiveDocument.Objects: + pos = coordinats[inc]['position'] + qua = coordinats[inc]['quaternion'] + new_quaternion = Quaternion(qua[0], qua[1], qua[2], qua[3]) + rotation_matrix = new_quaternion.rotation_matrix + current_position = el.Placement.Base + new_position = App.Vector(current_position.x, current_position.y, current_position.z) + new_placement = App.Placement(new_position, rotation_matrix) + el.Placement = new_placement + App.ActiveDocument.recompute() + el.Placement.move(App.Vector(pos[0], pos[1], pos[2])) + + print(computedStabiliti(meshMark, meshDetailOfTheMarkZone)) + pass + + +main() diff --git a/cad_stability_input b/cad_stability_input new file mode 160000 index 0000000..821cd28 --- /dev/null +++ b/cad_stability_input @@ -0,0 +1 @@ +Subproject commit 821cd287ef30fd6a0ba7cb420b6b7e6ff1b91577 diff --git a/cg/freecad/Frames/scenarios/robossembler_freecad_export_scenario.py b/cg/freecad/Frames/scenarios/robossembler_freecad_export_scenario.py index 71def3b..eb40dfe 100644 --- a/cg/freecad/Frames/scenarios/robossembler_freecad_export_scenario.py +++ b/cg/freecad/Frames/scenarios/robossembler_freecad_export_scenario.py @@ -18,7 +18,7 @@ import shutil class RobossemblerFreeCadExportScenario: def call(self): - + path = self.qtGuiFeature() if path == None: return diff --git a/geometric_feasibility_predicate/README.MD b/geometric_feasibility_predicate/README.MD new file mode 100644 index 0000000..7185164 --- /dev/null +++ b/geometric_feasibility_predicate/README.MD @@ -0,0 +1 @@ +freecadcmd main.py \ No newline at end of file diff --git a/geometric_feasibility_predicate/env.json b/geometric_feasibility_predicate/env.json new file mode 100644 index 0000000..2375211 --- /dev/null +++ b/geometric_feasibility_predicate/env.json @@ -0,0 +1,4 @@ +{ + "cadFilePath":"", + "outPath":"" +} \ No newline at end of file diff --git a/geometric_feasibility_predicate/main.py b/geometric_feasibility_predicate/main.py new file mode 100644 index 0000000..8623d7c --- /dev/null +++ b/geometric_feasibility_predicate/main.py @@ -0,0 +1,358 @@ +import FreeCAD as App +import uuid +import os +import json +from typing import List, Dict, Any, TypeVar, Callable, Type, cast + + +class FreeCadRepository: + _solids = [] + + def getAllSolids(self): + if (self._solids.__len__() == 0): + for part in App.ActiveDocument.Objects: + if (self.is_object_solid(part)): + self._solids.append(part) + + return self._solids + + def is_object_solid(self, obj): + if not isinstance(obj, App.DocumentObject): + return False + if hasattr(obj, 'Group'): + return False + + if not hasattr(obj, 'Shape'): + return False + if not hasattr(obj.Shape, 'Mass'): + return False + if not hasattr(obj.Shape, 'Solids'): + return False + + if len(obj.Shape.Solids) == 0: + return False + + return True + + +T = TypeVar("T") + + +def from_list(f: Callable[[Any], T], x: Any) -> List[T]: + assert isinstance(x, list) + return [f(y) for y in x] + + +def from_str(x: Any) -> str: + assert isinstance(x, str) + return x + + +def from_dict(f: Callable[[Any], T], x: Any) -> Dict[str, T]: + assert isinstance(x, dict) + return {k: f(v) for (k, v) in x.items()} + + +def to_class(c: Type[T], x: Any) -> dict: + assert isinstance(x, c) + return cast(Any, x).to_dict() + + +class AdjacencyMatrix: + matrixError: Dict[str,str] = {} + all_parts: List[str] + first_detail: str + matrix: Dict[str, List[str]] + + def __init__(self, all_parts: List[str], first_detail: str, matrix: Dict[str, List[str]]) -> None: + self.all_parts = all_parts + self.first_detail = first_detail + self.matrix = matrix + self.validateMatrix() + + def whatPlaceLeadingPartIndex(self): + i = 0 + for el in self.matrix: + if el == self.first_detail: + return i + i = +1 + def validateMatrix(self): + for el in self.all_parts: + if(self.matrix.get(el) == None): + self.matrixError[el] = 'Not found adjacency ' + el + @staticmethod + def from_dict(obj: Any) -> 'AdjacencyMatrix': + assert isinstance(obj, dict) + all_pars = from_list(from_str, obj.get("allPars")) + first_detail = from_str(obj.get("firstDetail")) + matrix = from_dict(lambda x: from_list(from_str, x), obj.get("matrix")) + + return AdjacencyMatrix(all_pars, first_detail, matrix) + + def to_dict(self) -> dict: + result: dict = {} + result["allPars"] = from_list(from_str, self.all_parts) + result["firstDetail"] = from_str(self.first_detail) + result["matrix"] = from_dict( + lambda x: from_list(from_str, x), self.matrix) + if(self.matrixError.values().__len__() == 0): + result['matrixError'] = None + else: + result['matrixError'] = self.matrixError + return result + + def getDictMatrix(self) -> dict: + result = {} + + for k, v in self.matrix.items(): + result[k] = {} + for el in v: + result[k][el] = el + + return result + + +def adjacency_matrix_from_dict(s: Any) -> AdjacencyMatrix: + return AdjacencyMatrix.from_dict(s) + + +def adjacency_matrix_to_dict(x: AdjacencyMatrix) -> Any: + return to_class(AdjacencyMatrix, x) + + +class FreeCadMetaModel(object): + + def __init__(self, label, vertex) -> None: + self.label = label + self.vertex = vertex + + +collision_squares_labels = [] + + +class MeshGeometryCoordinateModel(object): + + def __init__(self, x, y, z, label,): + self.x = x + self.y = y + self.z = z + self.label = label + self.cadLabel = '' + + def initializePrimitivesByCoordinate(self, detailSquares): + uuidDoc = str(uuid.uuid1()) + App.ActiveDocument.addObject("Part::Box", "Box") + App.ActiveDocument.ActiveObject.Label = uuidDoc + App.ActiveDocument.recompute() + part = App.ActiveDocument.getObjectsByLabel(uuidDoc)[0] + collision_squares_labels.append(uuidDoc) + part.Width = 2 + part.Height = 2 + part.Length = 2 + part.Placement = App.Placement( + App.Vector(self.x - 1, self.y - 1, self.z - 1), + App.Rotation(App.Vector(0.00, 0.00, 1.00), 0.00)) + if (detailSquares.get(self.label) is None): + detailSquares[self.label] = [] + detailSquares[self.label].append(self) + self.cadLabel = uuidDoc + App.ActiveDocument.recompute() + + +class FS: + def readJSON(path: str): + return json.loads((open(path)).read()) + + def writeFile(data, filePath, fileName): + + file_to_open = filePath + fileName + + f = open(file_to_open, 'w', encoding='utf8') + + f.write(data) + + def readFile(path: str): + return open(path).read() + + def readFilesTypeFolder(pathFolder: str, fileType='.json'): + filesJson = list( + filter(lambda x: x[-fileType.__len__():] == fileType, os.listdir(pathFolder))) + return filesJson + + +class GetAllPartsLabelsUseCase: + def call(self): + parts = [] + for part in FreeCadRepository().getAllSolids(): + parts.append(part.Label) + return parts + + +def isUnique(array, element): + for i in array: + if i == element: + return False + + return True + + +class GetCollisionAtPrimitiveUseCase(object): + + def call(self, freeCadMetaModels, detailSquares) -> Dict[str, List[str]]: + matrix: Dict[str, List[str]] = {} + for model in freeCadMetaModels: + activePart = App.ActiveDocument.getObjectsByLabel(model.label)[0] + for key in detailSquares: + if (model.label != key): + for renderPrimitive in detailSquares[key]: + primitivePart = App.ActiveDocument.getObjectsByLabel( + renderPrimitive.cadLabel)[0] + collisionResult: int = int( + activePart.Shape.distToShape(primitivePart.Shape)[0]) + if (collisionResult == 0): + if matrix.get(model.label) == None: + matrix[model.label] = [renderPrimitive.label] + else: + if isUnique(matrix[model.label], renderPrimitive.label): + matrix[model.label].append( + renderPrimitive.label + ) + return matrix + + +class GetFirstDetailUseCase: + def call(self): + return FreeCadRepository().getAllSolids()[0].Label + + +class GetPartPrimitiveCoordinatesUseCase(object): + + def call(self, freeCadMetaModels): + meshCoordinates: list[MeshGeometryCoordinateModel] = [] + for model in freeCadMetaModels: + vertexesDetail = model.vertex + labelDetail = model.label + for coords in vertexesDetail: + detailVertex = MeshGeometryCoordinateModel( + coords.X, + coords.Y, + coords.Z, + labelDetail, + ) + meshCoordinates.append(detailVertex) + + return meshCoordinates + + +class InitPartsParseUseCase(): + + def call(self): + product_details = [] + for part in FreeCadRepository().getAllSolids(): + if part is not None: + model = FreeCadMetaModel(part.Label, part.Shape.Vertexes) + if (model is not None): + product_details.append(model) + return product_details + + +class RenderPrimitiveUseCase(object): + + def call(self, meshModels: list[MeshGeometryCoordinateModel], detailSquares) -> None: + for mesh in meshModels: + mesh.initializePrimitivesByCoordinate(detailSquares) + + +class ClearWorkSpaceDocumentUseCase(object): + def call(self, detailSquares): + for key in detailSquares: + for renderPrimitive in detailSquares[key]: + primitivePart = App.ActiveDocument.getObjectsByLabel( + renderPrimitive.cadLabel)[0] + App.ActiveDocument.removeObject(primitivePart.Name) + + +class RenderPrimitivesScenario(object): + + def __init__( + self, + initPartsParseUseCase: InitPartsParseUseCase, + getPartPrimitiveCoordinatesUseCase: GetPartPrimitiveCoordinatesUseCase, + renderPrimitiveUseCase: RenderPrimitiveUseCase, + getCollisionAtPrimitives: GetCollisionAtPrimitiveUseCase, + clearWorkSpaceDocument: ClearWorkSpaceDocumentUseCase, + ) -> None: + self.initPartsParseUseCase = initPartsParseUseCase + self.getPartPrimitiveCoordinatesUseCase = getPartPrimitiveCoordinatesUseCase + self.renderPrimitiveUseCase = renderPrimitiveUseCase + self.getCollisionAtPrimitives = getCollisionAtPrimitives + self.clearWorkSpaceDocument = clearWorkSpaceDocument + + def call(self) -> None: + meshCoordinates = [] + detailSquares = {} + parts = self.initPartsParseUseCase.call() + meshCoordinates = self.getPartPrimitiveCoordinatesUseCase.call(parts) + self.renderPrimitiveUseCase.call(meshCoordinates, detailSquares) + matrix = self.getCollisionAtPrimitives.call(parts, detailSquares) + self.clearWorkSpaceDocument.call(detailSquares) + return matrix + + +class ClearWorkSpaceDocumentUseCase(object): + def call(self, detailSquares): + for key in detailSquares: + for renderPrimitive in detailSquares[key]: + primitivePart = App.ActiveDocument.getObjectsByLabel( + renderPrimitive.cadLabel)[0] + App.ActiveDocument.removeObject(primitivePart.Name) + + +class CadAdjacencyMatrix: + def primitiveMatrix(self): + matrix = RenderPrimitivesScenario( + InitPartsParseUseCase(), + GetPartPrimitiveCoordinatesUseCase(), + RenderPrimitiveUseCase(), + GetCollisionAtPrimitiveUseCase(), + ClearWorkSpaceDocumentUseCase(), + ).call() + + return AdjacencyMatrix( + all_parts=GetAllPartsLabelsUseCase().call(), + first_detail=GetFirstDetailUseCase().call(), + matrix=matrix, + ) + + def matrixBySurfaces(self,): + adjaxed = {} + for part in FreeCadRepository().getAllSolids(): + adjaxed[part.Label] = [] + for nextPart in FreeCadRepository().getAllSolids(): + if part.Label != nextPart.Label: + collisionResult: int = int( + part.Shape.distToShape(nextPart.Shape)[0]) + if (collisionResult == 0): + adjaxed[part.Label].append(nextPart.Label) + + return AdjacencyMatrix(all_parts=GetAllPartsLabelsUseCase( + ).call(), first_detail=GetFirstDetailUseCase().call(), + matrix=adjaxed + ) + + +def main(): + env = FS.readJSON('env.json') + cadFile = env['cadFilePath'] + outPath = env['outPath'] + if (cadFile == None): + return TypeError('CadFile not found env.json') + App.open(u'' + cadFile) + + + matrixOut = CadAdjacencyMatrix().primitiveMatrix().to_dict() + import json + FS.writeFile(json.dumps(matrixOut, ensure_ascii=False, indent=4), outPath,'out.json') + + +main() diff --git a/pddl/main.py b/pddl/main.py index 8541124..6766cce 100644 --- a/pddl/main.py +++ b/pddl/main.py @@ -5,7 +5,7 @@ from src.model.asm4_structure import Asm4Structure from src.usecases.assembly_to_pddl_use_case import AssemblyToPddlUseCase -# python3 main.py --stepStructurePath /Users/idontsudo/robo/Cube3/step-structure.json --outPath /Users/idontsudo/robo/Cube3/pddl/ +# python3 main.py --stepStructurePath /home/idontsudo/t/framework/asp-review-app/server/public/cubes/generation/step-structure.json --outPath /home/idontsudo/t/framework/pddl/ if __name__ == "__main__": parser = argparse.ArgumentParser() diff --git a/stability_process_predicate/main.py b/stability_process_predicate/main.py new file mode 100644 index 0000000..a4e0304 --- /dev/null +++ b/stability_process_predicate/main.py @@ -0,0 +1,16 @@ +import argparse + +from usecases.stability_check_usecase import StabilityCheckUseCase + +#python3 main.py --aspPath /home/idontsudo/t/framework/asp/out/sdf-generation --buildNumber 3 +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--aspPath', help='asp folder generation path') + parser.add_argument('--buildNumber', help='FreeCad generation buildNumber') + args = parser.parse_args() + # args.aspPath + # args.buildNumber + StabilityCheckUseCase().call( args.aspPath,args.buildNumber ) + + +main() \ No newline at end of file diff --git a/stability_process_predicate/usecases/stability_check_usecase.py b/stability_process_predicate/usecases/stability_check_usecase.py new file mode 100644 index 0000000..9d37b53 --- /dev/null +++ b/stability_process_predicate/usecases/stability_check_usecase.py @@ -0,0 +1,61 @@ +import numpy as np +import pybullet as p +import time +import pybullet_data +import os +import json + + +class StabilityCheckUseCase: + def call(self, outPath: str, buildNumber: int, duration=500): + DURATION = duration + try: + assemblyUrdf = json.loads( + (open(outPath + 'urdf-generation.json')).read()).get(buildNumber) + except: + return TypeError('not found urfd file or not found build number') + inc = 0 + urdfs = [] + + for el in assemblyUrdf: + inc += 1 + file_to_open = outPath + str(inc) + '.urdf' + + f = open(file_to_open, 'w', encoding='utf-8', + errors='ignore') + f.write(el) + urdfs.append(os.path.abspath(f.name)) + f.close() + + p.connect(p.DIRECT) + + p.setGravity(0, 0, -10) + p.setAdditionalSearchPath(pybullet_data.getDataPath()) + bulletIds = [] + for el in urdfs: + bulletIds.append(p.loadURDF(el)) + p.loadURDF("plane.urdf") + resultCoords = [] + for i in range(DURATION): + if (i + 200 == DURATION): + inc = 0 + for el in bulletIds: + inc += 1 + pos, rot = p.getBasePositionAndOrientation(el) + resultCoords.append({ + 'id': inc, + "quaternion": rot, + "position": pos + }) + p.stepSimulation() + time.sleep(1./240.) + + file_to_open = outPath + buildNumber + "_" + 'stability_coords.json' + + f = open(file_to_open, 'w', encoding='utf-8', + errors='ignore') + f.write(json.dumps(resultCoords)) + for el in urdfs: + os.remove(el) + f.close() + p.disconnect() -- 2.49.0 From 247c84d718b6c66347f44adb527c4f982391f1b9 Mon Sep 17 00:00:00 2001 From: brothermechanic Date: Tue, 4 Jul 2023 10:18:10 +0300 Subject: [PATCH 25/27] Tessellation: add optional FEM tessellation method --- cg/blender/import_fcstd/import_cad_objects.py | 41 +++++++++++-------- cg/pipeline/freecad_to_asset.py | 19 ++++++--- 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/cg/blender/import_fcstd/import_cad_objects.py b/cg/blender/import_fcstd/import_cad_objects.py index 6088851..49845ff 100644 --- a/cg/blender/import_fcstd/import_cad_objects.py +++ b/cg/blender/import_fcstd/import_cad_objects.py @@ -61,16 +61,16 @@ render = '_render' def obj_importer(filename, - tesselation_method="Standard", + tesselation_method='Standard', linear_deflection=0.1, angular_deflection=30.0, - max_edge_length=1, + fem_size=5.0, update=False, scene_placement=True, skiphidden=True, scale=0.001, select=True, - nonsolid_property = 'Robossembler_NonSolid'): + nonsolid_property='Robossembler_NonSolid'): ''' Reads a FreeCAD .FCStd file and creates Blender objects ''' @@ -126,16 +126,20 @@ def obj_importer(filename, shape = obj.Shape.copy() shape.Placement = obj.Placement.inverse().multiply(shape.Placement) meshfromshape = doc.addObject('Mesh::Feature','Mesh') - if tesselation_method == "Mefisto": + if tesselation_method == 'Standard': meshfromshape.Mesh = MeshPart.meshFromShape( Shape=shape, LinearDeflection=linear_deflection, AngularDeflection=math.radians(angular_deflection), Relative=False) - else: + elif tesselation_method == 'FEM': meshfromshape.Mesh = MeshPart.meshFromShape( - Shape=shape, - MaxLength=mefisto_max_length) + Shape=shape, + MaxLength=fem_size) + else: + raise TypeError('Wrong tesselation method! ' + 'Standard and FEM methods are supported only!') + break t = meshfromshape.Mesh.Topology verts = [[v.x,v.y,v.z] for v in t[0]] faces = t[1] @@ -192,17 +196,18 @@ def obj_importer(filename, # losted root lcs inlet workaround lcs_objects = lcs_collection.objects - root_lcs = [lcs for lcs in lcs_objects if lcs.name.endswith(root)][0] - root_inlet_name = ('{}{}'.format(root_lcs.name.split(root)[0], inlet)) - if not bpy.data.objects.get(root_inlet_name): - root_inlet = bpy.data.objects.new(root_inlet_name, None) - root_inlet.empty_display_type = 'ARROWS' - root_inlet.empty_display_size = 0.1 - root_inlet.show_in_front = True - root_inlet.location = root_lcs.location - root_inlet.rotation_euler = root_lcs.rotation_euler - root_inlet.parent = root_lcs.parent - lcs_collection.objects.link(root_inlet) + if lcs_objects: + root_lcs = [lcs for lcs in lcs_objects if lcs.name.endswith(root)][0] + root_inlet_name = ('{}{}'.format(root_lcs.name.split(root)[0], inlet)) + if not bpy.data.objects.get(root_inlet_name): + root_inlet = bpy.data.objects.new(root_inlet_name, None) + root_inlet.empty_display_type = 'ARROWS' + root_inlet.empty_display_size = 0.1 + root_inlet.show_in_front = True + root_inlet.location = root_lcs.location + root_inlet.rotation_euler = root_lcs.rotation_euler + root_inlet.parent = root_lcs.parent + lcs_collection.objects.link(root_inlet) FreeCAD.closeDocument(docname) diff --git a/cg/pipeline/freecad_to_asset.py b/cg/pipeline/freecad_to_asset.py index 3734113..96829be 100644 --- a/cg/pipeline/freecad_to_asset.py +++ b/cg/pipeline/freecad_to_asset.py @@ -59,9 +59,12 @@ hightpoly = '_hp' lowpoly = '_lp' render = '_render' + def freecad_asset_pipeline(fcstd_path, + tesselation_method, linear_deflection, angular_deflection, + fem_size, mesh_export_path=None, json_path=None, blend_path=None, @@ -72,12 +75,12 @@ def freecad_asset_pipeline(fcstd_path, remove_collections() cleanup_orphan_data() - # import objects, tesselation method can be "Standard" by default with linear/angular deflection parameters and Mefisto with max edge length parameter + # import objects objs_for_render = obj_importer(fcstd_path, - tesselation_method, - linear_deflection, - angular_deflection, - max_edge_length) + tesselation_method, + linear_deflection, + angular_deflection, + fem_size) # restructuring hierarchy by lcs points lcs_objects = restruct_hierarchy() @@ -132,10 +135,14 @@ if __name__ == '__main__': description='Convert and setup FreeCAD solid objects to 3d assets mesh files.') parser.add_argument( '--fcstd_path', type=str, help='Path to source FreeCAD scene', required=True) + parser.add_argument( + '--tesselation_method', type=str, help='Select tesselation method: Standard or FEM.', default='Standard', required=False) parser.add_argument( '--linear_deflection', type=float, help='Max linear distance error', default=0.1, required=False) parser.add_argument( '--angular_deflection', type=float, help='Max angular distance error', default=20.0, required=False) + parser.add_argument( + '--fem_size', type=float, help='For FEM method only! Finite element size in mm', default=1.0, required=False) parser.add_argument( '--mesh_export_path', type=str, help='Path for export meshes', required=False) parser.add_argument( @@ -147,8 +154,10 @@ if __name__ == '__main__': args = parser.parse_args() freecad_asset_pipeline(args.fcstd_path, + args.tesselation_method, args.linear_deflection, args.angular_deflection, + args.fem_size, args.mesh_export_path, args.json_path, args.blend_path, -- 2.49.0 From 5239e6d0919ef4af9a7179e35255d70a0f74fc68 Mon Sep 17 00:00:00 2001 From: IDONTSUDO Date: Tue, 4 Jul 2023 07:57:09 +0000 Subject: [PATCH 26/27] =?UTF-8?q?Resolve=20"=D0=9F=D1=80=D0=B5=D0=B4=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=D1=82=20=D1=81=D1=82=D0=B0=D0=B1=D0=B8=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=82=D0=B8"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cad_stability_input | 1 - cad_stability_input/.gitignore | 95 ++++ cad_stability_input/LICENSE | 505 ++++++++++++++++++ cad_stability_input/README.md | 0 cad_stability_input/gui/__init__.py | 4 + cad_stability_input/gui/init_gui.py | 89 +++ cad_stability_input/gui/my_numpy_function.py | 4 + .../gui/resources/server_sync.svg | 134 +++++ .../gui/resources/stability_zone.svg | 142 +++++ .../gui/resources/template_resource.svg | 166 ++++++ cad_stability_input/gui/version.py | 1 + cad_stability_input/main.py | 48 ++ 12 files changed, 1188 insertions(+), 1 deletion(-) delete mode 160000 cad_stability_input create mode 100644 cad_stability_input/.gitignore create mode 100644 cad_stability_input/LICENSE create mode 100644 cad_stability_input/README.md create mode 100644 cad_stability_input/gui/__init__.py create mode 100644 cad_stability_input/gui/init_gui.py create mode 100644 cad_stability_input/gui/my_numpy_function.py create mode 100644 cad_stability_input/gui/resources/server_sync.svg create mode 100644 cad_stability_input/gui/resources/stability_zone.svg create mode 100644 cad_stability_input/gui/resources/template_resource.svg create mode 100644 cad_stability_input/gui/version.py create mode 100644 cad_stability_input/main.py diff --git a/cad_stability_input b/cad_stability_input deleted file mode 160000 index 821cd28..0000000 --- a/cad_stability_input +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 821cd287ef30fd6a0ba7cb420b6b7e6ff1b91577 diff --git a/cad_stability_input/.gitignore b/cad_stability_input/.gitignore new file mode 100644 index 0000000..2b2f40c --- /dev/null +++ b/cad_stability_input/.gitignore @@ -0,0 +1,95 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject +env.json \ No newline at end of file diff --git a/cad_stability_input/LICENSE b/cad_stability_input/LICENSE new file mode 100644 index 0000000..5f2dd7f --- /dev/null +++ b/cad_stability_input/LICENSE @@ -0,0 +1,505 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +(This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.) + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + {description} + Copyright (C) {year} {fullname} + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random + Hacker. + + {signature of Ty Coon}, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + diff --git a/cad_stability_input/README.md b/cad_stability_input/README.md new file mode 100644 index 0000000..e69de29 diff --git a/cad_stability_input/gui/__init__.py b/cad_stability_input/gui/__init__.py new file mode 100644 index 0000000..f80dcdc --- /dev/null +++ b/cad_stability_input/gui/__init__.py @@ -0,0 +1,4 @@ +import os +from .version import __version__ + +ICONPATH = os.path.join(os.path.dirname(__file__), "resources") diff --git a/cad_stability_input/gui/init_gui.py b/cad_stability_input/gui/init_gui.py new file mode 100644 index 0000000..65c0bdb --- /dev/null +++ b/cad_stability_input/gui/init_gui.py @@ -0,0 +1,89 @@ +import os +import FreeCADGui as Gui +import FreeCAD as App +from PySide.QtCore import QT_TRANSLATE_NOOP +import shutil +ICONPATH = os.path.join(os.path.dirname(__file__), "resources") +zone = "StabilityZone" +import requests +import importOBJ + + +class AddStabilityZone: + def __init__(self): + pass + + def GetResources(self): + return {'Pixmap': os.path.join(ICONPATH, "stability_zone.svg"), + 'Accel': "Ctrl+A", + 'MenuText': QT_TRANSLATE_NOOP("My_Command", "My Command"), + 'ToolTip': QT_TRANSLATE_NOOP("My_Command", "Runs my command in the active document")} + + def Activated(self): + App.ActiveDocument.addObject("Part::Box", "Box") + App.ActiveDocument.ActiveObject.Label = zone + selected_object = App.ActiveDocument.getObjectsByLabel(zone)[0] + selected_object.ViewObject.Transparency = 50 + selected_object.ViewObject.ShapeColor = (0.15, 0.80, 0.50) + pass + + def IsActive(self): + return True + + +class SyncServer: + + httpURL = None + def __init__(self, httpURL): + self.httpURL = httpURL + + + def GetResources(self): + return {'Pixmap': os.path.join(ICONPATH, "server_sync.svg"), + 'Accel': "Ctrl+A", + 'MenuText': QT_TRANSLATE_NOOP("My_Command", "My Command"), + 'ToolTip': QT_TRANSLATE_NOOP("My_Command", "Runs my command in the active document")} + + def Activated(self): + StabilityZone = App.ActiveDocument.getObjectsByLabel(zone) + dir_path = os.path.dirname(os.path.realpath(__file__)) + dir_out = dir_path + '/out/' + if not os.path.exists(dir_out): + os.makedirs(dir_out) + importOBJ.export(StabilityZone, u"" + dir_out + 'StabilityZone.obj') + shutil.make_archive(dir_out + '/' + 'geometry', 'zip',) + zipArch = dir_out + 'geometry.zip' + requests.post(url=self.httpURL, files={'zip': open(zipArch, "rb")}) + shutil.rmtree(dir_out) + pass + + def IsActive(self): + return True + + +class StabilityWorkbench(Gui.Workbench): + + MenuText = "stability workbench" + ToolTip = "a simple template workbench" + Icon = os.path.join(ICONPATH, "template_resource.svg") + toolbox = ['AddStabilityZone', 'SyncServer'] + httpURL = None + def __init__(self,httpURL): + self.httpURL = httpURL + + + def GetClassName(self): + return "Gui::PythonWorkbench" + + def Initialize(self): + Gui.addCommand('AddStabilityZone', AddStabilityZone()) + Gui.addCommand('SyncServer', SyncServer( + self.httpURL)) + self.appendToolbar("Tools", self.toolbox) + self.appendMenu("Tools", self.toolbox) + + def Activated(self): + pass + + def Deactivated(self): + pass diff --git a/cad_stability_input/gui/my_numpy_function.py b/cad_stability_input/gui/my_numpy_function.py new file mode 100644 index 0000000..d74c650 --- /dev/null +++ b/cad_stability_input/gui/my_numpy_function.py @@ -0,0 +1,4 @@ +import numpy as np + +def my_foo(value): + return np.sqrt(value) diff --git a/cad_stability_input/gui/resources/server_sync.svg b/cad_stability_input/gui/resources/server_sync.svg new file mode 100644 index 0000000..da595a2 --- /dev/null +++ b/cad_stability_input/gui/resources/server_sync.svg @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/cad_stability_input/gui/resources/stability_zone.svg b/cad_stability_input/gui/resources/stability_zone.svg new file mode 100644 index 0000000..3f7686a --- /dev/null +++ b/cad_stability_input/gui/resources/stability_zone.svg @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/cad_stability_input/gui/resources/template_resource.svg b/cad_stability_input/gui/resources/template_resource.svg new file mode 100644 index 0000000..509be26 --- /dev/null +++ b/cad_stability_input/gui/resources/template_resource.svg @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + T + diff --git a/cad_stability_input/gui/version.py b/cad_stability_input/gui/version.py new file mode 100644 index 0000000..3486632 --- /dev/null +++ b/cad_stability_input/gui/version.py @@ -0,0 +1 @@ +__version__ = "0.8.1" \ No newline at end of file diff --git a/cad_stability_input/main.py b/cad_stability_input/main.py new file mode 100644 index 0000000..3b646fd --- /dev/null +++ b/cad_stability_input/main.py @@ -0,0 +1,48 @@ +import os +import FreeCADGui as Gui +import FreeCAD as App +from gui.init_gui import StabilityWorkbench +import json + + +def importObjAtPath(path: str): + import importOBJ + importOBJ.insert(u"" + path, App.ActiveDocument.Label) + + pass + + +def getFullPathObj(assemblyFolder: str, name: str): + return assemblyFolder + 'sdf/meshes/' + name + '.obj' + + +def main(): + App.newDocument() + env = json.loads((open('./env.json')).read()) + assemblyFolder = env.get('assemblyFolder') + + assemblyStructure = json.loads( + (open(assemblyFolder + 'step-structure.json')).read()) + assemblyNumber = int(env.get('buildNumber')) - 1 + activeDetail = assemblyStructure[assemblyNumber] + + subassemblyNotParticipatingInMarkup = assemblyStructure[0:assemblyNumber - 1] + detailOfTheMarkingZoneOfWhich = assemblyStructure[assemblyNumber - 1] + importObjAtPath(getFullPathObj(assemblyFolder, activeDetail)) + importObjAtPath(getFullPathObj( + assemblyFolder, detailOfTheMarkingZoneOfWhich)) + meshMark = App.ActiveDocument.Objects[0] + meshDetailOfTheMarkZone = App.ActiveDocument.Objects[1] + meshMark.ViewObject.ShapeColor = (0.31, 0.77, 0.87) + meshDetailOfTheMarkZone.ViewObject.ShapeColor = (0.68, 0.66, 0.95) + for el in list(map(lambda el: getFullPathObj(assemblyFolder, el), subassemblyNotParticipatingInMarkup)): + importObjAtPath(el) + for el in App.ActiveDocument.Objects[2:App.ActiveDocument.Objects.__len__()]: + el.ViewObject.ShapeColor = (0.32, 0.05, 0.38) + Gui.SendMsgToActiveView("ViewSelection") + + Gui.addWorkbench(StabilityWorkbench(env.get('resultURL'))) + Gui.activateWorkbench("StabilityWorkbench") + + +main() -- 2.49.0 From 8dfbe3887864bacb40ce8a22635eab531847753d Mon Sep 17 00:00:00 2001 From: IDONTSUDO Date: Tue, 4 Jul 2023 16:32:15 +0300 Subject: [PATCH 27/27] adding assembly_order case --- pddl/main.py | 2 +- pddl/src/usecases/assembly_to_pddl_use_case.py | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/pddl/main.py b/pddl/main.py index 6766cce..9c05f1c 100644 --- a/pddl/main.py +++ b/pddl/main.py @@ -5,7 +5,7 @@ from src.model.asm4_structure import Asm4Structure from src.usecases.assembly_to_pddl_use_case import AssemblyToPddlUseCase -# python3 main.py --stepStructurePath /home/idontsudo/t/framework/asp-review-app/server/public/cubes/generation/step-structure.json --outPath /home/idontsudo/t/framework/pddl/ +# python3 main.py --stepStructurePath /Users/idontsudo/robo/step-structure.json --outPath /Users/idontsudo/robo/pddl/out/ if __name__ == "__main__": parser = argparse.ArgumentParser() diff --git a/pddl/src/usecases/assembly_to_pddl_use_case.py b/pddl/src/usecases/assembly_to_pddl_use_case.py index 7583043..a6ba8ef 100644 --- a/pddl/src/usecases/assembly_to_pddl_use_case.py +++ b/pddl/src/usecases/assembly_to_pddl_use_case.py @@ -7,13 +7,11 @@ import os class AssemblyToPddlUseCase: def call(assembly: List[str], rootLabel: str): - print(assembly) partType = UserType("part") assemblyType = UserType('assembly') objectsPartPddl = [] objectsAsmToPddl = [] - i = 0 for el in assembly: objectsPartPddl.append(Object(el, partType)) @@ -30,15 +28,26 @@ class AssemblyToPddlUseCase: connected = Fluent('part-of', BoolType(), l_from=partType, l_to=assemblyType) + assemblyOrder = Fluent('assembly_order', BoolType(), + l_from=assemblyType, l_to=assemblyType) i = 0 for el in objectsPartPddl: problem.set_initial_value(connected(el, objectsAsmToPddl[i]), True) i = i+1 goal = Fluent(rootLabel) + problem.add_goal(connected(objectsPartPddl[objectsPartPddl.__len__( ) - 1], objectsAsmToPddl[objectsAsmToPddl.__len__() - 1]),) + + i = 0 + for el in objectsAsmToPddl: + if objectsAsmToPddl[i-1] != objectsAsmToPddl[objectsAsmToPddl.__len__() - 1]: + problem.set_initial_value(assemblyOrder(objectsAsmToPddl[i-1], el), True) + i = i+1 + + problem.add_goal(assemblyOrder(objectsAsmToPddl[objectsAsmToPddl.__len__( + ) - 1], objectsAsmToPddl[objectsAsmToPddl.__len__() - 1]),) return { "problem": unified_planning.io.PDDLWriter(problem).get_problem(), 'domain': FS.readFile(os.path.dirname(os.path.realpath(__file__)) + '/../../mocks' + '/domain.txt'), } - -- 2.49.0