From ccf03cf497d79b4f532dcaf9d67167072814f86a Mon Sep 17 00:00:00 2001 From: yiyi Date: Wed, 26 Nov 2025 22:38:26 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8B=86=E5=8C=85=E5=8D=8F=E8=AE=AE=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E8=A7=A3=E5=86=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App/GrabBag/Doc/视觉方案.docx | Bin 56045 -> 51957 bytes App/GrabBag/GrabBagApp/GrabBagApp.pro | 20 +- .../Presenter/Inc/GrabBagPresenter.h | 20 +- .../Presenter/Src/GrabBagPresenter.cpp | 741 ++++++++++-------- .../Presenter/Src/ParameterManager.cpp | 3 +- App/GrabBag/GrabBagApp/Version.h | 4 +- App/GrabBag/GrabBagApp/Version.md | 11 +- App/GrabBag/GrabBagApp/devstatus.cpp | 6 +- App/GrabBag/GrabBagApp/devstatus.ui | 6 +- App/GrabBag/GrabBagApp/dialogcameralevel.cpp | 61 +- App/GrabBag/GrabBagApp/dialogconfigtree.cpp | 41 +- App/GrabBag/GrabBagApp/dialogconfigtree.ui | 20 +- App/GrabBag/GrabBagApp/main.cpp | 27 +- App/GrabBag/GrabBagApp/mainwindow.ui | 14 +- App/GrabBag/GrabBagConfig/Inc/IVrConfig.h | 8 +- 15 files changed, 556 insertions(+), 426 deletions(-) diff --git a/App/GrabBag/Doc/视觉方案.docx b/App/GrabBag/Doc/视觉方案.docx index fcc445d15864d1409835bd8dcbdfebc5fcc0e39d..4a686f7bf9575af008938d76f0c4642e7e14348e 100644 GIT binary patch delta 12423 zcmbt)1ymecw`SuG!QGue0|~(`xVu|$cL|;%xVyUscM0z9mf#R9KyWAMkSp*0H}l@i ztTksXsIF7{t8G=gPw(>`)A?1VZ zD4GeUK1&&P!n0cpS@9zXefs!rrh z#5cFfn>(NB7`8||_z&FiZ@$Kmd|VG`ytjV}_!^q^aA4&!t(Pnm;CGY)2;X{r-TN4h zG+%?mB#3mV`bWiQk{`M3uS*_K|tTi?H|3Oc3l>i2s$~Qw!-&n|9~d5!lDb@2;0!d?y6=0>th#)vs6;<)oh zZPqP=Qer!=n}r{3{duPF2H9>Wh?ff9{W~X33z!4O&pGjb&I$T|=ETU((d1WB0%G6( zm6T=jGydwh*g!3dPypltgMlGR_CAr%hSHF>UUnwa7qiN2sNfqBrv*W%pdr^SuzG*W znPF|bUj8<%RmBJk7TP`Ajoz^yFRx``RzNhfS_VNa9GtUO_!}-~0)+gel~Xf?Rr%)~kJ~wodF#d20wx3cxs+T7xFpq!Hp)RbGTx z>4Zd+Z$B3XJ~9V{Fib!MOa6GJH9391Fv$QXm$GED{r<-WNe#?eNSmk8*{a5zivrpr z9vBEFt*EqJQhm^QW7h{^QvB_ZbNCGlQnwSRneemv5gcw(E&VVBM&AbsuUDxt{{56v zgGQm(6%`#2M}S7bc&ssA)CQ;2xLT=CQjNp4p;L-4zUN^i=fvA|yV|Jhq=~uDSWjxV z$u&+-X|FHxy26on5pvdd$#zJh-<-cGJO3_jqBt*ixZiO5)2`;XH{}iH;9UH3R*s&t zg88qkxHvi6*?@uP#MH-j*)6i*^&Q7u35c`@ancwdNEMNx!m|%@PpjCcPz?Ai^+s9? zr>Q-xS%p3g3UI6?^VdaR+h?XrLQ$8ZDUSbuh zCL0KJJS2mML*-&%B4~MFg44SQq{sH3Fa?;u!Lj+U;=o+Bo_))G}%FCHsYOr(rX;fK#cph-$o`)NA_=e|#@l;yqaS zY`l)4;=I2vomlN;A0ea^1o~`2Z4RN;*E0`hWIH+m zNx6}k5dU*!gn3S+tDU3qZ@He&UN!joTuU?5K_IlhMVy@7txcSMy?<>wLynt+=xsdR zPd=SP{N_3NiOtx+Sf;s+T2Mg)Q9SPsg#s2UO13^@?yT9ndzeW3n~gB}tEsFlygTOf zdJrSJ;K!8Rl(^pQ?U9mEulg#5;|@3>*aEQCc4NJUp^zA1Vxq011l2+DZl10 zz%8&|Mkj!SUMtYbb}VVE5TDppOBa6MhIV`%|MK$PNI{u{0$|kx3@Q&UrhqwEz$JC* zw{QarrITvyHxCI6)y3~RPNTtj(Ll%6o^Y2ioz;0CzI^UJ9dZ++%lT?gVGAz?7uLos za1D&Nx>Ncx^QI>VGL{cNhK8M-aeYf+#Ls>(8Y@0fA@m=PVbIc6v%|t^^Pn`TnGuja zHd=UVq#1w5$0$9XU*sNV6U;b~AbV(Tv!UM_(sN1^*`Mb2dbRh|4U=|Lww2g!L$?rt zC&V7G2ky|^H~ z+1+rRDR+P&=!@taZ#LTK3@u5|=!}s8yaom54mBgULa%K5mRFaP2CDXhS-e?ZR^$3AA(hJxXb6?CYY5e_&mTD- z&fIDzTMmA_Ah^QvfYrDDRC=(NGBsA>9wK*zANG=uS#+EDpl{u<>wLm z$I-kA$15+~v`Fw$tS#nnr3M+MHOyd+h_rTuc-|(e?NFRW zdwNXiR(zw9jCLw$G6Uj5ya~#DTZ?#j2g^bQmR524GEr#Tv7)Mu zZDyygZ%J6Auhhwp^$!cKeoD&;PH4oE7fOd-k{|*wNR^oeK!u&Z%Dg}oZ)~Zjini>= zO5Vq>sm(>-1x`R5>q`C#j5TQu^RRVR+t^qf!L7a<@jAj2Mz8|NwzUomSB?EjIu+GM zUj4H3GM=K0XQJ3~Le6@Al1vp{`7~jTQq#Ua>bo&A)aF_FF?;ge9L_C?H`cZkC4SJs zDm6<$0Q3)eK3n7XtEG0hErvv^HOCOB$IG8TTJkMCI9aV#Gw+^p0vf0pMgH1(%p1C&t@D764Y(=E{msz4NsnbfN_P zjTH+&HaiH*?bY(~$*qV)jZCL85qlE)UFqIQFSgIZ<(^kyH?f1oNpnwy2OOUggA@~H zb!U>A{W6^D_(bIs(UgkbNqTNrY2`pSo3#?vq0!QLa)?^rsXWOC9+u+6egQa50q(7i z8u`mL7q-!hsG7ArG2sVbW09%ZN4b)utyNB^eu>dSY^W!s)R!<6UDh7k!3gk|Xl#B; z@aq1m0tylyR9X-1(-I%3Y3#1XnXP85$hKzK*8qq2Ms6=^Sq)&=Ff|*6G^njri)fFW zXyle(x=~HN@8(VHEkKND#Sb6>Mz*H|1Et5P`z2RGq)7-U-k3*HE`B)Db|rSf_=c#g z-CE380jlpy8{P<&%W_k0qT7(&77F7?fM;4&p{R3H4w>ALrGB1>fu~(n!AN3YaZM1xMXyaYNR`L9|f5 zwO9x1^pj&wUz(Kv^X<{KT8l-@Pd{NiPe}M<+A$!AK1+aY%-Oy?Uyb<{J?+*I5x3JL zu9sI4ze0wn@JIZ1FG4sH!YH1@@*e^FGlD>!8)k_8a|m;ujUFjLiTL!aoZ4|nSlBr1 zGlj2q(*aV$7L&>kQy$JJmF^y9Wr5PHjRf)dZ_P~ZmB~#4(%z?W%?8pNTQeOe8MLr- zc?+MlQp6`_DFyb8vMxEUFS|G!p=A~|L~}HTluMg}IoeN!6*^dP?U#s>q1I54fg{ei z-TR%6FV-%%0Xt_>EMKh8UkjbqyZ5BSziiJOS^9dB^n;WTPiN3%b#rgLN9M}j`}5-L zQ}Z&{6XRNH4~(U9);3RV)Tb{cO_LtjKJN#8se2it(U>5bX^rh>>rLMe>O|R8Mjh?Z zwG+5{LY8W+9vc^vIQ(!`STix>nFl_whi>4c&#I#)oompd+&hhL-Xs=dF^IzQcdhvU zkVyc4JV~*T8O1Glbgrq)cinW7kZGK(B+V+#D;xGog4ktT!hcZWURVpOEU7IX@Kdut zv~u{4T+w%@8&^vsyuDfet!Y@nsq^u&?LD7C#odewg}>?wH#}fvb=I3Q-UrBFDLY6Z?5EA?-V+C)0us9A}Rq zz6h$ixLz8RmTq6tU!ME+=-SL?PJda@EK+J1;>PB2AAsU@hS$X(6mJmZEKwRv>YFRB zG!w)}5}e<^EgPHVvk~3i3AIgMpF)44H@ucYhjKamE~7`+#1&2aIrnEr$J5dY8>-oG(7S(|_{IaUE#(LU2KRXQ?%Bqd?m-5ihc| ztdK9Lq?Yk+Er(7^G7tJKuJ@}qYs;_Ga|FEZV`f$nkR&aCr~oqV`l zN+GvW^ZD_BYOpdGGD`BU-Z^2*NAx4*Kbu$x( z4qCoCULiX@)!s>DUNiz1XjgESyu{A;(6>gbV|a1Bd^_s_-b4BsX_>}gD?t&>ViE}J zwjoNMRG`B9jywQ-TT;?og6N)5(;q^sdhQD$SROqyGP8UB(cvQo;-RK{juT4T&q4Rq z(p<&u&*8yO0i>LM0d2EJCXE+?2vq^tFm5*Y~%Ms1CT*h)zkMaO7LT@ ze&~~{U3g9vdMu{0=PQUHpAS2$mc#Zm6weP9YdG3LLc{_^1 z4QyPGqPJa)9H|LgSG$hc23aBflMPpb?X8Loy@?wlQuh{}_uy3ah8~&p=+NvYybw;Wk%f#_GOeCu> zV>ivs>;509eeqb@kk@ivrufT|0;b^y*BU+$zF4c>^_0HqE}3ICPaW^!RE-x!u6fE9 zWEOi|@h}?hD*zs1_tK&6S;5-+V8Y0OnQTnWyS$-Bq*L0>J@b_S=$_Nb<5?V;{;vF# z4- z=z)2nSxz3N3=7bA*)vFXt9hrZ`2H#oqXI2$)T?>&m#vjwrkOB;)a-i5xr^~vfu`MQZNf15DiP$nV72TvJ1MD$4>&7Y|Je!dJ@@d*xanur_f=Wvi18NDS)47->To9K4(44^kp#y;Q_s@H0a}yhr-}(T9vp8Ch&piT0{J8QuLf}wS z+K!OpMLX^;pmETaV7!lx9|NTw)fkZh9TQ6-Zke=b(yI%W7>=}k>&SqJMHh|3#U?l< zjV3w;LYd6wyXcq=@MC*GCw45rrdPa#;)fPBCaqhxGvyz*?;uxKujG>{at&@GW3>61 z5H->sG~*LM6`}RG^7c6IWjigE6d)|38LIt=h4i#|6-25ds{N`T;i{7#Tf1m`K*?0Q zAQoO9FCc|PO`eQG5V^N)?0cPo>W4pRBSXy*tTgi-&=qX zA(Jh}`>qz|&DG5`pyC0Sn`7tFkhHqvz-N^ab0iaxRcj%ME4tv>w~b z=5y0h^@Ei{WM$aljNLXco zhAzb~C=-WWteD=S;T=OfulQrBF1OfvD6u*XKRnFrR04NBOSFoNs#tXaF%CSWh`qzn zh`S*^aWW0}@~@%g2fFu$O4SFV-jN7AUfvAFZ4L;etunRn#fyDfSprDgZ+CuD9v{DJ z_q@B<=e>BPmwcmoHe&CVh^)2N>h^HfH&n~Kc7HRw?j4kugFqS$jdV=Ie|A&_O;c2B z1lKA2fj{hRc(6rvoET(*gs8*K@$kIRS~^82QKPQ)&M65TlLJJhJc)+?9cz}v#)%uH z9OE|#Bj+RsGaTC@4KW}UE{@F+ZU+<76)s0u{eWGnoWYeAKbX#;$$DTu*F7y)B^SO> z!{Y}Dq0vF|inmZ%D@nA^>n^eB7Wr0Pk#u_e=$!gg95GL-kIgc=&^fDWwx~z$E#b|k zD_ueC7?~&{#9MI5S{AL5G#J$2B&c|W*Xwr@{ovRKdNdgwbqS!z*`NNrnH;Cv#3`)D zD?8MPSU)d?qY=f#((wBrLNc9MB>>xl_XV{Q_w-Udh6k{*OTwN;B5tNUl3 zx>rxv&8!0hLCd5gM>WFubB>Mouc_i;|)!euxQ8kiDxnE+t zbdL53lQrQn&xJa0vJ4XG+A*Rk1$d6JPs6QpL6~@wV2H7#^?vdbG2$IBFw*f*&2iUb#qV%UVvKz_m^Ew^YqVO$3!KWJQ%QR zb@12-)E~q8M6FQXk1Pr*E7P0*Ag;f^HAP93@u^7^@a^I8l6|GYBXnqn9`Q|7gUtQ0 z^PPz1E3h$K2&a4_PKFK&1_kRioupQK>IX&GR7;d8V3~oC4E~t0iDq!PMGQ6678gHC zj5FGFAp$*N?;Zn-e-3d+*XDymRgEzwovQ$9R4aML2@iatM!BLQ0&<9tI&T7R8u`bh z*nnuJuWL$1sY4Y%)f6goyojb4#cCrBL)}s5l=LR2)J0LlOBd3~bHt=fBkb{tVXbJs z{P-j~2MltR9X1yd7LVfRHtnV)mK0U&sjYKxGi(*X=S{YG zsy{bu{^%$_Z@9VGnAkf1Y8W4CEjcdrxSK^X9go&g2bv>@^lZ}9fK#l+?tfG#zIoT^p4XL;V=*Uia7DUR1;JWr0+(vBJaGLyGCHz z%LWan0nyn9(oY|rT3)MGWW(2DE3$i$k?-rMV#hwl8Wib}Wg}5nckdfvOHN|x(i0f@ zDk0GI0K{vF~Akmm(cmCD^4-A=0wpi%4Bz2 zC7FJi$*wpHWMfL8-b6Hq%hP=&H7!s&l+HA1Ax>7~UkhFH5pd84rx{V%jm_r^H`s)ir_WRp z&&2cM>8kaXU6dIbj^24s%}w$>G@;xSzcOoAUbv+=EPDRHD@VaAN}G}Cn>iQD2zq?=)0rkIVw#@dXGO;6$1UuINpIB(sv)sy=Cfwj3h7pXXl_h<8vJ6@%^w{V zdi&O`taIPT`ccjYN)Hc@m#(u?OYN>clg1VY*82dDl_XeXiG=dBLLoBpAfC@Gs@qge zj6=DVWPAO6`J@uES{8+Gw(IyQn3_detzY=#fN3Al*>O-=HK=tYZ-ry%FvH21V6_Yxs-h$Or;-C5<~3aTqn}#8#|8J@^QTmHb}Lltrd_kLi>@y(`}dbp zOAZ27H*#fzPOrTwl|$_%Kj*aXY#z0r+D2D*3aY)L*2_d6aKT1=;ZP*5azyudJW?-@ zjgj51d8kj*t_`7z5w0ogpEkL1GJ%7{M7!0NW)z*u>#FA)#5m2;r#Rj%Wys_SAx~B9 zwQ0N5wFfax{Eb~Vakf;HmpvbcMV_4v@UQ#wjHkKbl z6)aJTHtv>SizJ0<1(ZPx-M#n{EbIRoMJ7|Yjuiig{*-QoWiu-|12(CJAm<=R{e(z> zqhM*;xlph!6I2%hPMAYA$AEe8uFJ5dwi%Z+gv4>~rM^ya<76d?sD0r#?r1N14$lv0 zOM^(ylL|8y)%LUI7AJ7q4CFYm%rN)dGS3RcDj+QoK`pt^OEM6Sv5zcB!q*XGhiroU z3`{RKIX%hicq*2WV?4I&-S6D@r_}aa#>Vem&?JVo&T_1|4RU@WNQ?_Veh*1cW2?LHQ2{ZqnA!j zht2Hpy0$1!-53eEiXRwOkH235rKvx^o9>T{UG;P{Sglbi7*J+!`LI^)w;WuP(_|WH z(0D6(P!R>ytRj;}1mUsABPf0>o{QR9HO&*#Sbc18GAcYj(Apo^Uqy`9U?(0IIcxeL z?7kxtZb!Ni8R3{uCB8m{ROF)~J1c5~3e#fO*~DHmrOlGm#80}(zS43JY>mR#uA5tc z?cRUQs&Cg2Z)tMEgm_E^H$hjaeZLZ>Da1Y+V?Xnr}drh$qS7HaoeO^uM~r9yk;m+x)!`5(fTZmRv3-iU^)u-v?5JcA$vh+xq4$AtF3Xmu=_ zKJe?H5C^C=(Hasg^p*T`)3ypg>nMK8|~hP5$B>pje__uItP$FyBuNnrM+G0n@cCq)&MN4Nz) zbn57e8prsry4idMl6VNCCugWNQ^)!o8DGbv=?EXrL+tDK*?m6JNP(N0=ZYJtU0CnV9Zqn#3|MUUBuu#g^tdy)%t!}qGQzR&+WI;V?R~;p z8O(@L`O_4pTkS(8_lT)HF7HpwU}1CpQA34=2PAa8q=(4?3aL0#RBGVI!w{5E6@<}< z!ME|p2pE}ireKh{0LCWNCrZVY&UHWnKVkocCPJ<1dJFcE=Y<$Ri^GJt;%oe zjz+0dA9@A&sxA=ccI%hs9zJV+al56k^7uJ#Pr$;-b&)cpKy_K=iUO{kS^|rAcA$dm zHAU}@fXz<8?As^wn|bZGBmQ|IFGwVV7Ky~2!g<2ZmaWUutOUsK<#J{oFKzda*Bah! zRIEJMS8i?(H?Jv}fxRLrDniW^Xx@H+@Y}JhbFje#K3j6Re3x$xR1{WZC80TPW~pIQ zreH2~;N8yVDaUhjJN5>L^|e=0$1n%GVMU6Yw`r(g`8i`EB$j2QyfL5ip!p76Ad@+I zco)Y)Nba)MljY+kVXXy7wig-!wRPrW0C8>ghR%SU7AvZ!O zeOt;>b2i8xZ_Q;ZO(~j0-*Z5E>$LUBks_ZW52FgO@`cY}@;Og!CnNGz4_7{H%Z<3! za-=v2423VY0C`qOrce-dyM#yCqlup&tb>Kxi{s@(rt8{Qf^{3o(i6^2R2G`w1ARZd zn#12=_Q@E5#cL6GllRc&&|5Kx&|>kLB%*VnDNLCt$l11WCVNbYSHuo7m%mnP`!c@v zB!dK8a5Nj_*A&g6Vg%OsA0B@eKm~(*IzfU}ty&)?BA0x~YPFhg2vCi*qBS+`y7kjw z>eN~MC1X=|VUat(m9vOMV;#Wt@MP!3k2jR%)Al>2gAicDL>rs3)x?#ZHh$egj-;6h$ zVyJ>Ya_~(A`O)5RL4WksVi=}QWHAQX+feg8!k0F@ko2pgKP4K%^5AMIKp;pWC{`ih z2D`;+C5xaOXX?$HhL(byDkp#1t6*~a1&L}@G2{2vS^&t_xmjfYW_gWTaP?XT64ny0 z&$HP-rF$Q20BLID6p~g+YS;esqjR0UaHrGx)*gJ?A%KoFH*59;`k&b>fA_Kom~Bjq zEew8ZX32%G%;UwG5s*DMw0xd`ydVM}7zk8?)9u=zR15;(zo!nuKDWo-Iht7i&KrcF zpdfvCzPA4S(*08VrwsU)WO?k06cp==fMb|7@=nmHTL! zdT&oZps}i=?Zokw*|?X}pobIS?B|vfZ*zi}sEXp-irRlG*L8km;DAK&+QU!yB>$TT zKKs~q-)#u&)~k3=!df~o|I6K!&5U@>>n#>_@>u2!cB&wq;n|^Jk0|0HRo%70iy5fY zn8JAi7z?Y4w+C|7){jd(b>&~2NiVu$c0#Zn%jm|FxeZ||$?+}t;Yxi0WXDuf7yhy1 zBq{N5So{Li5FFNobd06g0}cfPcgBgIx~y<@yrkw{_bR;3#%F<}ycqqQ;?9>@BDc|8 zjqaIfW4N(I-lN}9{_p8w{_g_%LYNoz{oQjU8KFQRqUU3r9R?9l9y?^s_|r# zpL1$g>D=t}jx*mHPq)t)ofPSJBQM=vnVtvc(R_%9BdpcO&B8_&abPmBg>&Y{CC}YS zBb>C{brQq2^@&6;Hz&e2P>9HEQ67L|KkVchva70=xbOB->tUb5~Vb zvE-EKbBv_w)#?@GDq$3pY>qi-CWGg#QAuXRmB6o_E#7}HRhxFhtnHuqcVf?vFJ>Gt zhXd5dkI6x@Vg+2&wD;s|_h9MZ8R2Q%)T-dzfa=>`v&jL|+AmgDmp+nnlLhyVQO#VX zpI^!p_MA@7EhGS_bx98uH$I0D(U&M#Il`l{q>uSXDh`CDaQ1u z>!(Tx!WEoe1{6d8NF)ypMjCO2_lt-;RTf&66uwxE4ZH)F}gF)x*xY#Pnrhu6Nff5V~T zg1Tpq;`Gmq(9gGMZLZw&5DQwQ0fNCX-%byXt9mB6NyURp2_B)cPx%N)_tYR)dEv}H zPtMoHv^K$rXDLWSKw^RZVh@u(vyA_hl;?-`*MI<#J^w%Jjq{nq2e3f@b6xo*{3q*J zs{8K>@-Gd~3VyFMzYu>au>CL0A64g1;P1+SU-BT(F9P(R;}^&I*Z2=Q^nbYYtnhb2 z^e^E*71I7E>@Rxs@8|k59sfJY^dIfg&t>5s_@9*1=g!i9 z@AfYk2=x4-{N?{2)b1D0=YMe>f4Tg7sQ$$Ks^x!W+ea`V8H~9Pe^WTR5@eP^^0--+tVgIT4FU?Q1arOkb|C^`(Y?uGp z82ran_UG{ap~=a_+1bL@>?CgEs`{#b| z80Y@@7Ncvd*|Td_vAU{fb=A|iiC|}qU<7iXz`)S}?*LE$0Du_KdkG2W1_A(RyaNDG z0Z^a`y@RMgqC9X=${Yab+lc?h2l(F*`kT!EEC072qakt3qMs2-Fv z%Sc{~F|OGUkZ@KPVPgsLgryBvJf7UoWc1RVOv^0y>-or$VhPg5@#ujinG;_bacjUU zoFrG7O$qChZ?eddgOvL?VYyZXjS{YI7tES&kx&&@Q|ck|fSbId5K1H_z{!9@$t^+h zSRJ8}&e-fca33D5HTxr4{{b{hM0Nr`0pg^02?y}rHsT$0>fe`d7Ua3+lq;*duk*tJ z6fdz*7g0%|rIV^E&zq8(MUkJ|vs$J1-3bG=PY#2Fb1f^N{RHS%FAQww&%1s zIH<=l^Vm8D&Y=e-*-bCm06%a-GtJT|Q}8HJ=bmM~l8Jq(^m}+ki?Vg)C5q2M^j&O4 z)rY^nU#IL!b@fM^%ynKKvc{|6A?_*5!n&L6P5~niZ}t1IKds>^NOVSTmen7@<-&eI zQtF745z_$MPZ-^)tKG<52<8b@G9~*XR(ugN8}YcZhx?6(V*9|D@Yg1@(8b2|t=DOP zQ94d&VUDt(x~0XjVq_rCh%=~w!MW_1IR<*15J6Br=BL*W|LuYGa8z*`WqR6n zDMe0D2bWsx)pGh^L=1hdtJs_WexvrY|I;=$Jpl_{X+}c0hB|fx1EtB&&MW?xtu-y? z;t>||LMz|^AkT{8z{#Cfr-qgTIpV8eVvM=*<%MaO&Hh2FgX<5 zP)3rCS)D-|qmC%`l%&a8K*B+O?jD>uf{m!Jk|4c!aN&3}<7Jn?A$ayCw_hlTuU;_t zI<&|g`~X?3GXBT;k014vAyN3S6p}}n0*H|_#ZQ5H;zsXLIIt8#G9)uWmz)aWGTkRA zB{1-C=ECBQw6bQV05$aL$DcKnDj|Blq)za!CWQnJ$FDBeL%UXjnGJ88>>z#8zHaxKiWbhj18i`bL~-`YSP z2)s;@{v?^Rx!40Hk4l6YgXNV55QYSh(jX^@paxtsqBvMOlj5;$*I8`0=m>CHM5yr^ zz*%un0QB}Nv~n5H@}Mg9nsfXk*6=1%A|NC}#MR>yFpI|0h^5hcNz@3Kk&(h%JoA0#9Hqu&Wfw<|I?g&}1XCKRsZo?<|aN zzhYFgy6CK?e{$JNdww?Xeog=JTzofp(*GQCs4&*q`&jKd>4&ahp0&%e#T5%0R#-yI zwT3skQjlx@7(1f#Gq5O2X-uTC;)?^Y%v>8ZrDaa?sS>(7C^&{^U0l+*H3xfGltS77 z%a0USJ|tcde~v0`0tNgNiRBI1I3#+1iA^DN5{Z3J9HyQ>Yk(nM@S(8k-gUSY^m(I* zDI;sO8{BtN0mbPQP+L^1YY)o0_fc5kpFWDIQ~MS9Uj7(WfV;1X-N@#%755MTCW?Ul zm{}{_b=n*cq|jwgPwWST4Z+_V(<#P|&Pk5W>w^yCg`BSMAo5rMs9~At*bSXTBXvV& zmA|Gjp?wTcQMjM*Uz|4E`zoO<>L?2NQKY>d>C*(g6ER8UPFHDOS%gJxXi>xp6KUz^ zBD5ZmMlgjDjFEPiA}j3yd!!PsPPiiJdFJ>iO(SQP>c)5oE74AZmXRDrbKk z7B<2WZ-8m56UQ8rpB^VLE5W+nemEOUf%w4JqK6H&hrYrP%mF_X``bbGy9lVIt(PdU z8b4Bco9+hN59NF-<=&mEYHc`vuv$nXR%yk3T6%af#^4C#ofw!~*sn#cM82@eK}ZmQ zFo#4vEMGmkLq?nmzgZDjAbYxu6rmCAX6x0y%g#t8r4aPq8WPxZaZ{U0cNgCeE@j=y zRt22fj5eu~c9{{@)KP`hCX{!WlqBepbj4D|WQPr1@&XFZp`2^o%CC=poehu(CEY8f zUG4_euTCx9rv!NZpM+Q&xuAJ6u+H#=j{9RnG@u=dsorZb33?h@k{ z%9JyhU5encDW^VtEDZgo`FlVtrWfXtGvr#z;JpT>XOC`Q4t4n&-URcu_iA#2xX{ z3s(;du>>~*6>+b*_+R~ZPZ(5AmzFHE&lU+^%DcY9Lm{Pv0NW_SAM9j$pz*BxK~Lpa z1)u~m&a`C6c{&(AkQl@b#&HUmOxM@w?G^@E1yjt7o*E8$gTsSK$2C919Ssd8NHJNl_f9sb78P@lO;xPTS1Y?gS&X!TA zq%#aF)m-WXcV=GRp71WNcazDGh*hg63}<_U<<$fNv_PlSaz0N5UG;aRtLdu?ow9B% zH+on4I}`DQ{AahahydGrjpub1&m*Q&*Z|iRR~z>bnJiaFAIGOd=dUN0keXvP-FOxB zA21~KNX~WGM-MCe4%7x+jzWv%<8#b|i{yr(Y(kIF22dwirjq!wPDQI`q74cnkORzF zOY&DOZ-Hmpo)=l(CvDzOJQrjJXW^@RmMiPu>UC9O3!1A05lFZ4h=d#KAc=ALHXbPR zzAZis0SOn-cmy}&*!7m%X`ku+qJ~k14aXWW(*LBb41d;AOVkLW*IV`L zeG4*cOc>h0G)Y;UzX&Bo0aG{}KlpcA?AHBKWZ=xq{LkaG&IVK44;3n1&f1_J!MiG+ z2yxq*p`R&dl)IcsRnA^)tgRuTeRi1KV69WK`AffbM5v68YV1s4LdIa6;aVORpa$f} z$r#o74;QUc12uN-;>k7k!um-=jLno*oMH*i?Im1gAGPk(-L0*1Oe16cTy$0}Pogq2 zLxH!dX-H~*A0>Dn*!?DIz#~EwpK|JtR0W3=pC8O0l5t(N&Kgi>DP-bJ%=_H=za8iS z&x0iGXs6tC&S2T(z;pcd+++kL`g?CIqgN7>^V9mpv4Ey->>uG`-^nHuTpG@RxPynY z`Ix)e31#|>Npe#-)5aL#)L;i_gCLAmV*+^-O=hu}ka#t=No&`Dt8Bcj=h0A!*}buW z+cQ6kW7ot=C-!Ok@W|UtL^Ix((2C|j?)GknkB6Egz+$StPsHY7?|e?!R8@$@ct(3v zVJ4N1Lt_gajR_4DIQ^Jw9(BJq38xi*XV^Pr`^`V=sR6ZivtM3)+HnVynRv!gkqa0} zKw6bW-+GjyrsmSE1mM9v_yl1VfoQr}bN3iiZ0Y<;MPiZc-6!GZs7L61RHIS|2xviFwzxa59RXBC3T? zAa8eGA^)eaypGHBf%q+8?*s<`@BmOC3Dd)PztT(~Z)v8t?*Gc?|7e@_zo7AN@+&@1 z$h;+40x!fngm!uC6>^B?3orxcX#yebcBBoEtLZ!+M^+6RCDR4NI3HRJ&&G@6&K7=r zx*vh$tcJs&m1Ri+EQ(TF!QN@`X@9H#lJJqrBS5YOgjX{;8MEB<5F&_tgX$z<)4xj( zKhAq|Z3*9OOVJPr(l23{fq^Ev=>@!`z@*~H2346-z=UJY8JMgWvE z(+z?0spVS~=hmz@0b4{-NI!SysJj2hZ62Mjx@GQLB1GVAtB3M77k_N?Ol)nPY;2v3 z9e?ff8j`-*q%)!oUA#ZTN4hWdBO?=$E;BhQT9-b%160}jEB^!%uR@QkUZh;cC039q z?ez699UOGWcq4SR^Ixv5Dq5$Y!SZ)Nd&R(zC{i@RzTQ_~DaAL?-4?uOFBvc#I|@fc05c z@{?ecA4s=;?a&~w+HQ9`Cdop}w;oIeX{4s*+*~T#YQbE zqYTN44GyY|KkI~b)VI5>Ur_QX4W2Zrz2K2i!IbCf{9ST5%;B2&L~W32rwHCjb0!Xm zn*NxM`s68cih!0OBdRSJnxCrB=FW{pADn&atXIY&TI(gyRf8tg3(}+mXEFh-;my2>HK$e7jotSDv8#WKeuT^(UQWn)kk#Krzl$(bKPzkqfD+IfU<{qH8B*fcLqtn zd>{Cq*^9`;qKBL}v*vj->wjl2jBSj5v+7lwZAPTv3)L0ZPzTw}Ku~jVrDH$k3zgM# zu$E|Im7M@9q0KW_JO@48CK!RSAk@LssqP_`gz<}e-g_4%T#A7{L|f2}eE;Fkz_5wF z*XJgjfi6>8464b(9N5>7QNZq>FV;F_N(F)%7Q7#!641hFe~xP;5*dk$aTh!#hhYld%cK5}3^J|pZgG?g0MTiGC7Ct2*>%gVv z@SwR0%m9#18DZK>t_Dh|9Ll(JfU5#Tatp-L!EuvTHr$bD&Jy+d1@_5kh@{T5VXmlJ z)YN(obZkQyFURoPYCx+6s2&*2yGaKV#2&aJws$O7OdpZm*uX!?Fi?ymB~widnM=pP z0?S1d9%FET@A^~<3?VTfiTU}dTQN0iQHuEeIPgmTRBrlY<4@459k{VQ+ z!+(&&kuqU3x>p9c@K$HjC@t5|zbvd|MGsAbJ`Gn-Mjwdn>7w@M44HEa@63qxZCEZ! zavIsSU*=3_fODLF^|JUl4(hz@S{Nli&dJiD^S=YwL~I-=Q07Sbiz64QfREw!u*tb<;?Bt`t7r z7RZ>Q_icl#Yc8h0j2cgRN4+)2Md;0_4@w$(OGax^d~6VfVaC2RS?yG3-=k)Ho#fQu z8=N`40@@x_)G#zHuBY!2z8UquY#QdDFGym5Oysv^gfsx)S402TQf+N)WS+1(0+-K0 z>08fRf`SA5vn0h0F^;|UR*sq^`i;TK%-GtP;Sb96M;cFE(-Kz<&4=NJpZ2Nhu&F#E z46h&&jiVM7m4?b4uUfR-aN;~PH4Yycl-Qj*NRXJ$-7M#(5V4ST`lHa(^s7R0AnP-7 zW@=&s&n&Hs0WMIm*1DqA>tLMYAcM=X!Sivn6G}5AmRWc^NxMs_jt=X|D0Dl=c!S^u z7Jk(L?~9PH1f1p_9P`uYesQ+eJ6zsC{l3~Hs8|uL$hpXy zd7lEdWBo~UIr;`9OO`$WFP78YQUNZ#Yv%$Zs_J#FGPu~mB-7q~ z`{vQCu?W1xeqLbnT21W6(eaBfst>-n@0<_DmYa22y0j_&dUX5)ozi@9@$y!o{bZi4 z12hP0V290Vq*A$az$Q5sxmQ!vZO-|}?I>PWUU=%E9xmW?fHh8`v)bD(7c0y01^Cxwli*8{OXDqp+vy z+^j7{IhV}5pMKs(98_od-0l~@f9IC&|4i%UOEO%6dpX=DLQ*Tx4{Dd)mHmYXoRVfH z8tfg=fXEo`kN}bGCJMW!$M@vpw`kXrVGN4Rg(E*vH--YmXt-}BukSH zm?Au3(Ltg<^e~}eTYPp&iDA>Y_8c}O|}uNMhHki zE|$U~Qjjno0#i5e90~VyQH^kGMWe{*N!DpWPh3H`=pH1&lRHxi-c&PoSqWWO5tXn0 z6suZb9u=cLaM%q>)`oVp1{%ZXy}rAbUsEf|vamx*KVGwLV4V=ptrl-Hrfs7?8PX+^ zP$O2Rev!s7e;Qk8!v&?QFv?EdaTa9*zIKjbqcTGh5`k@Kijv*AHcbHZto>N^-T~BC zi=1eLS?`NUzH?I5mde`{5U$L_4*d5J>p@O|jFOD`=839IKnjSyIn7aS9lN|0kc7&CA21zl*O+{z{; zI1kbzNunx82W3~qXS5Vdq0E@Ck#ElEnKKK4OPFzRzfw|!0%S!;^jR{|m$7C=E*>B4 zv=@3Tz|I>kLV!>`eIR3wI3gPyQYDBCECdIQ|&@sYzTyu$xvGGv#nhdSb+hBanR%S)k37OvyBv zg+j|?L@j<1)K6+jD0@Ul1h=<1HtQ?&w3oR7$7@ZKyMKtHhsveug9z<;kjk4`oBl}l= zq__?Ag<~|wqLOf1IyAQk%yD+3@@5{5GP#=9$KQOG9C0UJz*f1H^oq4_Gc>B@ zOA`62H|Hheee%^y*m)8qS4*35`HG+QY%FW92M+o=!k6}c#fkrVrT+PbtexsCN`L?W z7?2WVkT8J_s;f4uoJihpC9U4eYwjc?iLM-pEz1NF^9bs1ALdZ3?SlBQ2sosD0Qc{(9oJO^Rp^ zdNO3|xR2~Nu9VarPHty^W~o^q7ro0A!Ak0rsW=mNF2~9ge?zWrm}^;wA- zBlm>%;eA!SB$Q6=DGF)qGn$Tll?7IOx-Ci&8Yg2MdC~k$`DX+0m-F|UelUluG6q)6 z9Rlde&1RXDI{vc=0&0CZE|jQdh-8G|BQJx3acDCI!c?IdWrPEb)N$k@A)n}n!AxOh zfFg$NfjS;`7||=oJS*AekSaVAaUZEN8rFRfG@)cNlg=|hVV*t>C@81C7@vKYBIBaI z>dR8p>DWOng}CiCHs$EOw}a#)g-P>}r~?%Md8cFa^R!w1^gV7UcR&viIi|Y@SKEni zA_D5csf(j=51~JxUa=7LE#qJy0Luf2UP>cFZ%P zWK%8pex6N~hgaw*vp~*T@wo=OCa)P3+3`$8uOa!KoEIn|!+uPoGF<^5SO=sot%B}P zPO)-y9A53`k==x!s*zcfj7`SuU@u{*2g+nF zMsbp1@8Yl{P#k|GHj^UX1(8+1LzWTnouQar<$Fj3OF7Eq7aD5i`|K8kEQ~xDx>29n zT8~b?2k+NJT&4Cd>Y&+LlpU8=4-B;O@SS8ZR2PSRQ|GDc87%f4{^#5+L?Yw4c5Kyf z%;XT}5VhCtbSIIedF=7vRC5TADN4jlSqeO%`69a}lh z*)yZK)yEt=Xpin|MFDSjRmcb;4=M?y#MXH&qE?+wWA-bpKG|}nH)c6;0dJmj@YNwA z23p|NgZf2yEkyG4*s_;c~Eb?yFMDkJUk^gYbzV#A5$JRC{NZ5Mf) z9#gypH8l6dhuzM{_qea0!-Z9Ox&(~P(+53HvRzAkioC3gHfaJj^!E)r;Vozx)%2LI z{S>^_rmHZcXzl96Jiy-Lver$H^oe3{sm~7^ypL$44KWq$5qE`r9s|-fdi7K7d1h#{ zKd{8X`(t~pb3-qz%y(?QZ^M}z-J=R1SUY*F1R7Y?y@WA%ph!a-nK@7{PH4HB;1>%E zHZ$mhD`|0_G6Xyl!;u{&5;O_XAnspZv0hp~U6ZnKQ(qJHbOHy`vEw<*nM`ri=Llb% zxO4&|ayWb~E`sLl*MRBL0qzXnMOlEm12cWPFPcl0Pldp%-*-cQF5O>zZgAq@003S$ z008k{yCFv>cdLJ7t;RDa9Cliehb~G-d1{;_5eObvSrkvc)$wJySse7Ye>a*$f>u{c z2ozMO;tOX? zbv_I37w31Hw&#Z))Jh5acyj-6yY@JEq@<=vdQY3k#gET->2UL{x%=k;$34T?G4Y;p z0l@u{IYL(Kx=f=3FJ{bbl11f10&G?}p=sT)T45JP^G+nX>lkoAhPED}{EJdSo(I;& z#W_I?ZPB4nTzzWjBMmkKOP1=OkJhCUva!zOK%SM^y|>wYoj68Nq`P`q=HzTzGE-bN zqh|=jMf4vup}AD>ZMOx?p?`l=R&RT_2tcaH)*OzfJM z@;lvE7Q#o&daaATL(Re<;lb*MNV>VQdEJPUKA*;o@$Mz&>e0g&avDQo8zry>73N%a zH|L~eg94weii%0e-ZesoGv?B3W$8 znkCxS>9exruZ;>x8|-6lUDx*8O&3l#&RvdIIt*!L(wWg}wJcn7!G`Vax4>dljB?vs zjTjH9QdM(zg<`Z@n(WTRD|}gs#2;Hu4don#7(QN(+ED5x4ffm~SAFyumr`0`WLvHE z1|Kk?Us-_%k3;eAPxkmb*k_wz4O+(+4gwMkcLo}y=)1po<)ViDU^8tK+OP~zs_sNP ziNd|!U}Id3sl0QXbYhRXBOsN!mJju|mgn|doz(iU;#!YkAp|MgnA=~xGCiYcefUty z?(|&=*W*PAmpSEiKUg@c8&ZfHI!*Q*_?c?EKX5J zUj6V*CFVPkT+~y@EVGM!8$!~)mer6z#`8O5qR4i>tq}X>gZsBDCxXFmS)KhYs|jpb znzD^~9FivPY-+|VROMPKyk!r`TdYYmNmNYcZXVM+5F;`uddK4?klVctDXAB$q+ddT ztX{m;PK6jF>a3{EWB$njxRcjYyos;`wPf##={mu_^Qqk(j(OT*i8H0*Z0h zl)5I0{>xf5n#>qesN3h3p=1*J=+jo}6OH2F0&=Fb^t+cu5~4|j>){S=y5{0eT~dlN zUp!<{4T9M^*cnr{3jKQFs^Q>F-1Ikr50PaNv{5C+Z>|5WZ5%%o9CLgk{b$2nx8=x_ zln!?_#rJ%U`KDcg5II9+9SoI2v5(HOoM z>k;PCH*3t7i(kdA0v}V;2+)bhBTEqANh0#!I610tc`vfB+3eufzgV$V=aG>po^Bck zUMXelC6*9eO@PrMXZ-b>-mGP6(f6$PPG2OHhq)KEMlNuZZ{mL%H>K1oq-dCf%>YYm z>EDO0*A8srx5=aq1I>TBv?egci8`@_mZ!Vg=Hs?b8yB5p;cT{6P^ZQ*oyGO&dT-C^ z&aqxfq4z$5&8%l3;}bB~zzQ%9&y%Gh#m%!S?)n?Sv2|QL_AAwqcgT2>7T;&9GknQ-~3lxnrYF@Nh>^DX)##k1GUzZ13hh!CUIY`NCM(!RWBRj(Lc1 zLTO2DZYx+8mEEqt7-}%x!fpEMQ%yZ?%N9?&U;fJ1h|h_Imsx`F`|@Wt{rT$~TLam9 zBT5+c#*#~Z@Uaz;xJ2rhqfV)NaU8#)~;_DwIHlPI`L^Dxjnk?}W6Zo&F z)aO_u>a@Ydgx>%|5>8gue4AKfzzNHxrqt9`#&;ZB+{R4VW2{?2rrAH6%5<`5)vOTb z0_MCs5j?xlo3E+U##P0hw8yW`RmO_+HW*jCdYeA*+nn>7vv>}n){dtI_oY)sBmTfm z4_#;cux8VdC1IsTaN7{L%DZmUnniA_mUZ)X;k7+cC?NTT(&LGOo)0$9q*(bVw&ffb*K5_JwZ30sE7}YsEm)Kt@b0b zHt16v5W~Z0h)lY^b729M=ptRhse1geUtw|Rj6;U=w!8EsDvfiGJVt{H@>y2P+6WuS ze<#D?BYe@ZBX%S6(MPx%D7DKbR!GKQ=xXzw)Z;xBKWJvglhO|hFSga>4^TtRS+5-<}OA8^T%9$xX1Sbq#^;hZ3tw>y2%STZHk1n-s12uYO0 zp^>Qz{?Y%71Ih1?VK|)4_i2#O=nB(loS%EF95Dplzq%@I+2Am#8-}3KS3=%c1Os8H{-N7HCH+!J@IQ4-O9%oB zggLVp47wv04El_e3lxO;w|4*F`E`jx&4`HdZ~cW;y;c-Ica5FiUyAW{s-E8SI~(KP z=6ljKM{pm;SZM5--gaYdX^pm*+z$4GDcarygql+qa0#DrRh~OckRz9%W)sr4U0m89 zff2SDT?8>6NP+yUsV3(va##kUf3G5^{EpC0@~siPezL#n^hCp7|FVYW2uGV8v#?|f z3d^++d&q6da6PdS-*I_Bn`?zBkZ&z96<=t%SNn09i8tKI!N@6w3@1r zU~D-E#o7WA`l&@Ij3b*=127Pbcy;Z>_jH>-3`2W5I4=qsjF2zn&NU5frn0kC6M^dyM2c@Pe7`&pKn0R;r zfO`HW$OXtI?A*G7C-T*fZL~j{%LZj1Yx|wi5K?jme!s89NG(D|5}u%5Be?g%1D+~f ztIRd(505F?!xK?{kUd$3cNc2>FlYMwZ_jVBD4kf?61+vNsTx%Nq@%i1Rw(l?Lf&0& zzo3x*57M7dpN+I@q18Bl^hSVx&|O!yDh&(AKgn$m$kzzyU%kgpZ{&MBjw)Sa>tBew zc2s#1_rJ-{Ut+EQO8%-vq3ftH5-+Gld~II%VHT$Az^MB`Fpxjv(opA8p(xuOW#;@? zb6zLOKwe1GI(MrlP(leC}hLkZ|vox!TVY|rkpj#CV_G*aE<4OxG&XIdorTHMY zOnRgBRCRl!4$wAV@nq3ML)~24snKp~E~+ul>&!!6@k>*1sfpBF+tWe_hkG92{?6FI z87zUJLbP;)!7Z)J@OH&vOP6i0zU;->c8Pa-bLL|1$5$_%r#>=!2EC52DL*V-8RunB znpH6$+B~`o%O{NKg*?A*RKXKhYB~Tk3^pd^T?rMJ5rG=p`^80biCnMkP$==naHL1$CIC3-MM;=&_C zpx%jQ{|sdAq(Ihos-{!x<8 znA5fFV?+X6-uHG1jkFhyqEV^n!wu;S>a9YC9xViux5h`5pU+*alsY~b+{eG(@6Z!p z?@fN)MFqvaX~lw9jPqSm!|EOxcv`h!M1>aFgEgKJqQ3)vG-z$-qpcq%IPnrosxayHJvp}8JSW9uf$0=RB-4keuM*As$ z3*>guFGjlqPETD}HnH@mgl)+TJ~$7d#4q&3{%*)|^Z}Knt=%_c1_XMHbJqX?+Zin+ zX4Yo+lz|a=1R2TTTN9K#8z&`w11sZSMYFYp zjbE6E!C#n&*b7`Z3Cak`GIr|%2}On?nUzXIIJJKEx=dh9O~_Z+Qk-@*T3OHQ%1|E;}Kc%mpHi=ny?_EHm6=s$!g*YX;S94ZO9wPI#i#hlSQIOOfho+ zMackK7%Sn^H)9 zN33<=^S*Re=Z=vlDSc5I5@xrOp98aD$Pn|N{E)0*URUM^uFL0(i0M|-F7$f76=HSZ zyabx&+AUav>kg8c0?cReNNm5>)6zH*dRCM^?(B$LK0O+=WjKFsOjfy`-4K1MgJ zQatpHjdmFhy>{@o`K9u|x$R%Q`j=BzNVfWWy*c&6n^hsdp|;-6QOG&i+Bq^9+B*DU zP=6q}fd4%R*V{t#hNJj{{`bf~# zPJ<>TpN`W>J~Qj{IGU8ETg{5^EvOdS=g%}LsrjxK(tfpu7eu9>fS|d8_NA2t9|~N8 z;EgTYKr@gXg2~-RON(33BPVh~UE(v~HN zHNnJYyzO(kGqHM#%;QaFbM;@*y4N=NTvVfiEIJK*%i`<%V0DF*w}r4s?jP?!#_xxO zPz+4`Jgm!)ZfLXjfbQ&_YgQ;l#*pU`m1maA>?;nKWTNUloSY^>S6fU^Ao&3wm*yXz zF55nV{Iu{iTGTDFuD>K-AyhCn4}o)Uy>8bS|D*k9D2)9=9(IskALq*uX%cQC7^A|{(X1cUt z`Z}xLOPV~Y+Kd(f#xrzV*Q@Tl!o}w^-hCK*kn)SpoNrA94}XXgp#Cb_wqrJd^_!Yn7R!4m3W}G9n1gD!0CP zG(7r|cd)DKatM9c&akaR!){2p^m!l|tm@Z2w{P;gkPZg5WUm21e!lO2;Oe;kV70WM zl{I}~Zv${V*YDO3u;yRHE{ElJhTQl4iDm==rF#pM{^w)Zf1S&O?m=we?}Pt(67=`M zwQtM#Z|6egKK)h7-w((A$vX2EGyKO4{--Z$c{)*!LlN$h#M@g6*Ci#n&e}`!P zIlSR5K>Sa@=5Nye5cYR0+@JhGnE&Me9U1qFostgl|BgHQlV1rZVR{7jFUtNMbM)un z#D;$g`#S>Z@B02(l>G;G@6Sh+w*T`Hj=u%Na=^b$&fhBofAU-Q{)_+Lb%MVO|9klT zCo|RLKbij)jQ@xHzlV>1KB9E$pN}xH{VkaMy9$2|7XIW00Pbf1fd2>` m_cameraConfigList; // 保存配置的相机列表 int m_expectedCameraCount = 0; // 期望的相机数量 - // 工作点、相机和包裹类型管理(三级结构:工作点->相机->包裹) - std::string m_currentWorkPositionId; // 当前工作点ID - std::string m_currentCameraId; // 当前相机ID - std::string m_currentPackageTypeId; // 当前包裹类型ID - int m_currentWorkPositionIndex; // 当前工作点索引(-1表示使用全局默认参数) - int m_currentPackageTypeIndex; // 当前包裹类型索引(-1表示使用全局默认参数) + // 当前执行参数(包含工作点、相机、包裹的完整配置信息) CurrentExecutionParams m_currentExecutionParams; // 当前执行参数 }; diff --git a/App/GrabBag/GrabBagApp/Presenter/Src/GrabBagPresenter.cpp b/App/GrabBag/GrabBagApp/Presenter/Src/GrabBagPresenter.cpp index d375e1a..1b0f862 100644 --- a/App/GrabBag/GrabBagApp/Presenter/Src/GrabBagPresenter.cpp +++ b/App/GrabBag/GrabBagApp/Presenter/Src/GrabBagPresenter.cpp @@ -36,8 +36,6 @@ GrabBagPresenter::GrabBagPresenter(QObject *parent) , m_bCameraConnected(false) , m_bRobotConnected(false) , m_currentWorkStatus(WorkStatus::Error) - , m_currentWorkPositionIndex(-1) - , m_currentPackageTypeIndex(-1) { m_detectionDataCache.clear(); @@ -291,7 +289,7 @@ int GrabBagPresenter::InitCamera(std::vector& cameraList) m_pStatus->OnStatusUpdate("启动重新连接相机..."); _StartCameraReconnectTimer(); } else if (allCamerasConnected) { - LOG_INFO("All cameras connected successfully\\n"); + LOG_INFO("All cameras connected successfully \n"); m_pStatus->OnStatusUpdate("所有相机连接成功"); // 确保定时器停止 _StopCameraReconnectTimer(); @@ -457,225 +455,241 @@ void GrabBagPresenter::onTcpDataReceivedFromCallback(const TCPClient* pClient, c LOG_INFO("TCP data received from client %p: %s\n", pClient, qData.toStdString().c_str()); - // 生成客户端ID用于标识 - QString clientId = QString("Client_%1").arg(reinterpret_cast(pClient)); - if (m_pStatus) { m_pStatus->OnStatusUpdate("收到TCP数据: " + qData.toStdString()); } - // 解析命令格式:@,视觉号,视觉模版号,启动信息,$ - // 示例:@,1,3,Trig,$ 表示触发1号视觉(相机)拍照,调用3号视觉模版,发出Trig拍照命令 - // 兼容旧格式:@3,Trig$ 或 Trig - int cameraIndex = -1; // 相机编号(视觉号) int templateIndex = -1; // 视觉模板编号 QString triggerCmd; // 启动信息(Trig等) - // 方法1:尝试解析新格式 @,视觉号,视觉模版号,启动信息,$ - // 正则表达式:@[,,]\s*(-?\d+)\s*[,,]\s*(-?\d+)\s*[,,]\s*(\w+) - // 支持负数(-1表示使用默认相机/模板) - QRegularExpression newFormatRegex("@[,,]\\s*(-?\\d+)\\s*[,,]\\s*(-?\\d+)\\s*[,,]\\s*(\\w+)"); - QRegularExpressionMatch newFormatMatch = newFormatRegex.match(qData); + // 尝试按优先级解析协议:新格式 -> 旧格式 -> 简单格式 + bool parsed = _ParseNewFormatProtocol(qData, cameraIndex, templateIndex, triggerCmd); + if (!parsed) { + parsed = _ParseOldFormatProtocol(qData, cameraIndex, templateIndex, triggerCmd); + } + if (!parsed) { + parsed = _ParseSimpleProtocol(qData, cameraIndex, templateIndex, triggerCmd); + } - if (newFormatMatch.hasMatch()) { - // 新格式解析成功 - cameraIndex = newFormatMatch.captured(1).toInt(); - templateIndex = newFormatMatch.captured(2).toInt(); - triggerCmd = newFormatMatch.captured(3); - - LOG_INFO("Parsed new format command: camera=%d, template=%d, trigger=%s\n", cameraIndex, templateIndex, triggerCmd.toStdString().c_str()); - - // 如果相机索引为-1,使用当前默认相机 - if (cameraIndex == -1) { - cameraIndex = m_currentCameraIndex; - LOG_INFO("Camera index is -1, using current default camera: %d\n", cameraIndex); - } - - // 如果模板索引为-1或0,表示使用当前模板(不切换) - if (templateIndex == -1 || templateIndex == 0) { - LOG_INFO("Template index is %d, will use current template (no switch)\n", templateIndex); - templateIndex = -1; // 标准化为-1表示不切换 - } - } else { - // 方法2:尝试解析旧格式 @模板编号,Trig$ 或 @模板编号Trig - QRegularExpression oldFormatRegex("@(\\d+).*Trig"); - QRegularExpressionMatch oldFormatMatch = oldFormatRegex.match(qData); - - if (oldFormatMatch.hasMatch()) { - // 旧格式解析成功(只有模板编号) - templateIndex = oldFormatMatch.captured(1).toInt(); - cameraIndex = m_currentCameraIndex; // 使用当前默认相机 - triggerCmd = "Trig"; - - LOG_INFO("Parsed old format command: template=%d, using current camera=%d\n", templateIndex, cameraIndex); - } else if (qData.contains("Trig", Qt::CaseInsensitive)) { - // 方法3:简单的Trig命令(不切换模板和相机) - cameraIndex = m_currentCameraIndex; - templateIndex = -1; // 不切换模板 - triggerCmd = "Trig"; - - LOG_INFO("Parsed simple Trig command, using current camera=%d and template\n", cameraIndex); - } else { - // 未识别的命令 - LOG_WARNING("Unknown TCP command received: %s\n", qData.toStdString().c_str()); - if (m_pStatus) { - m_pStatus->OnStatusUpdate("未知TCP命令: " + qData.toStdString()); - } - - // 发送错误响应 - std::string err = "Unknown_Command"; - m_pTcpServer->SendData(pClient, err.c_str(), err.length()); - return; + // 如果所有格式都无法解析,返回错误 + if (!parsed) { + LOG_WARNING("Unknown TCP command received: %s\n", qData.toStdString().c_str()); + if (m_pStatus) { + m_pStatus->OnStatusUpdate("未知TCP命令: " + qData.toStdString()); } + std::string err = "Unknown_Command"; + m_pTcpServer->SendData(pClient, err.c_str(), err.length()); + return; } // 处理Trig命令 if (triggerCmd.contains("Trig", Qt::CaseInsensitive)) { - // 1. 验证相机编号有效性 - if (cameraIndex < 1 || cameraIndex > static_cast(m_vrEyeDeviceList.size())) { - LOG_ERROR("Invalid camera index: %d, valid range: 1-%zu\n", cameraIndex, m_vrEyeDeviceList.size()); - - if (m_pStatus) { - m_pStatus->OnStatusUpdate(QString("无效的相机编号: %1").arg(cameraIndex).toStdString()); - } - - // 发送错误响应 - QString response = QString("Error_Invalid_Camera_%1").arg(cameraIndex); - m_pTcpServer->SendData(pClient, response.toStdString().c_str(), response.toStdString().length()); - return; + bool bRet = _HandleTrigCommand(pClient, cameraIndex, templateIndex); + if(!bRet){ + SetWorkStatus(WorkStatus::Error); } - - // 检查相机是否已连接 - if (m_vrEyeDeviceList[cameraIndex - 1].second == nullptr) { - LOG_ERROR("Camera %d is not connected\n", cameraIndex); - - if (m_pStatus) { - QString cameraName = QString::fromStdString(m_vrEyeDeviceList[cameraIndex - 1].first); - m_pStatus->OnStatusUpdate(QString("相机%1未连接").arg(cameraName).toStdString()); - } - - // 发送错误响应 - QString response = QString("Error_Camera_%1_Not_Connected").arg(cameraIndex); - m_pTcpServer->SendData(pClient, response.toStdString().c_str(), response.toStdString().length()); - return; - } - - // 2. 如果指定了模板编号,进行模板切换 - if (templateIndex > 0) { - // 在新的三级结构中,需要找到指定相机下的包裹 - // 首先获取当前工作点 - WorkPositionConfig* currentWorkPosition = nullptr; - for (auto& wp : m_configResult.workPositions) { - if (wp.id == m_currentWorkPositionId) { - currentWorkPosition = ℘ - break; - } - } - - if (!currentWorkPosition) { - LOG_ERROR("No current work position found (ID: %s), cannot switch template\n", - m_currentWorkPositionId.c_str()); - QString response = QString("Error_No_Work_Position"); - m_pTcpServer->SendData(pClient, response.toStdString().c_str(), response.toStdString().length()); - return; - } - - // 从工作点中找到对应cameraIndex的相机配置 - CameraConfig* targetCamera = currentWorkPosition->GetCameraByIndex(cameraIndex); - if (!targetCamera) { - LOG_ERROR("No camera config found for camera index %d in work position %s\n", - cameraIndex, m_currentWorkPositionId.c_str()); - QString response = QString("Error_No_Camera_Config_%1").arg(cameraIndex); - m_pTcpServer->SendData(pClient, response.toStdString().c_str(), response.toStdString().length()); - return; - } - - // 验证模板编号的有效性(1-based索引,对应相机下的包裹列表) - if (templateIndex < 1 || templateIndex > static_cast(targetCamera->packages.size())) { - LOG_ERROR("Invalid template index: %d for camera %d, valid range: 1-%zu\n", - templateIndex, cameraIndex, targetCamera->packages.size()); - - if (m_pStatus) { - m_pStatus->OnStatusUpdate(QString("相机%1的无效模板编号: %2").arg(cameraIndex).arg(templateIndex).toStdString()); - } - - // 发送错误��应 - QString response = QString("Error_Invalid_Template_%1_For_Camera_%2").arg(templateIndex).arg(cameraIndex); - m_pTcpServer->SendData(pClient, response.toStdString().c_str(), response.toStdString().length()); - return; - } - - // 切换到指定的包裹类型(视觉模板) - const PackageTypeConfig& selectedPackage = targetCamera->packages[templateIndex - 1]; - m_currentCameraId = targetCamera->id; - m_currentPackageTypeId = selectedPackage.id; - std::string templateName = selectedPackage.name; - - LOG_INFO("Switched to template %d for camera %d: %s (Camera ID: %s, Package ID: %s)\n", - templateIndex, cameraIndex, templateName.c_str(), - m_currentCameraId.c_str(), m_currentPackageTypeId.c_str()); - - if (m_pStatus) { - m_pStatus->OnStatusUpdate(QString("切换到相机%1的视觉模板%2: %3") - .arg(cameraIndex) - .arg(templateIndex) - .arg(QString::fromStdString(templateName)).toStdString()); - } - - // 更新执行参数(使用新的模板配置) - // 注意:这里需要先设置相机索引,以便_UpdateCurrentExecutionParams使用正确的相机 - int savedCameraIndex = m_currentCameraIndex; - m_currentCameraIndex = cameraIndex; - _UpdateCurrentExecutionParams(); - m_currentCameraIndex = savedCameraIndex; // 恢复原来的默认相机 - } - - // 3. 显示状态信息 - QString cameraName = (cameraIndex >= 1 && cameraIndex <= static_cast(m_vrEyeDeviceList.size())) ? - QString::fromStdString(m_vrEyeDeviceList[cameraIndex - 1].first) : - QString("相机%1").arg(cameraIndex); - - if (m_pStatus) { - if (templateIndex > 0) { - m_pStatus->OnStatusUpdate(QString("收到触发命令[相机%1,模板%2],启动%3检测") - .arg(cameraIndex).arg(templateIndex).arg(cameraName).toStdString()); - } else { - m_pStatus->OnStatusUpdate(QString("收到触发命令,启动%1检测") - .arg(cameraName).toStdString()); - } - } - - LOG_INFO("Starting detection with camera %d (template %d)\n", cameraIndex, templateIndex); - - // 4. 启动指定相机的检测(非自动模式) - int result = StartDetection(cameraIndex, false); - - // 5. 发送响应给客户端 - QString response; - if (result != SUCCESS) { - response = QString("Error_%1").arg(result); - LOG_ERROR("Failed to start camera %d detection, error: %d\n", cameraIndex, result); - } else { - response = QString("OK_Camera_%1_Template_%2").arg(cameraIndex).arg(templateIndex); - LOG_INFO("Detection started successfully: camera %d, template %d\n", cameraIndex, templateIndex); - } - - // 向客户端发送响应 - m_pTcpServer->SendData(pClient, response.toStdString().c_str(), response.toStdString().length()); } else { // 其他启动命令(未来扩展) LOG_WARNING("Unknown trigger command: %s\n", triggerCmd.toStdString().c_str()); if (m_pStatus) { m_pStatus->OnStatusUpdate("未知触发命令: " + triggerCmd.toStdString()); } - - // 发送错误响应 std::string err = "Unknown_Trigger_Command"; m_pTcpServer->SendData(pClient, err.c_str(), err.length()); } } +// 解析新格式协议:@,视觉号,视觉模版号,启动信息,$ +// 示例:@,1,3,Trig,$ 表示触发1号视觉(相机)拍照,调用3号视觉模版,发出Trig拍照命令 +bool GrabBagPresenter::_ParseNewFormatProtocol(const QString& qData, int& cameraIndex, int& templateIndex, QString& triggerCmd) +{ + // 正则表达式:@[,,]\s*(-?\d+)\s*[,,]\s*(-?\d+)\s*[,,]\s*(\w+) + // 支持负数(-1表示使用默认相机/模板) + QRegularExpression newFormatRegex("@[,,]\\s*(-?\\d+)\\s*[,,]\\s*(-?\\d+)\\s*[,,]\\s*(\\w+)"); + QRegularExpressionMatch newFormatMatch = newFormatRegex.match(qData); + + if (!newFormatMatch.hasMatch()) { + return false; + } + + // 新格式解析成功(相机编号、模板编号、触发命令) + cameraIndex = newFormatMatch.captured(1).toInt(); + templateIndex = newFormatMatch.captured(2).toInt(); + triggerCmd = newFormatMatch.captured(3); + + LOG_INFO("Parsed new format command: camera=%d, template=%d, trigger=%s\n", cameraIndex, templateIndex, triggerCmd.toStdString().c_str()); + + // 如果相机索引为-1,使用当前默认相机 + if (cameraIndex == -1) { + cameraIndex = m_currentCameraIndex; + LOG_INFO("Camera index is -1, using current default camera: %d\n", cameraIndex); + } + + // 如果模板索引为-1或0,表示使用当前模板(不切换) + if (templateIndex == -1 || templateIndex == 0) { + LOG_INFO("Template index is %d, will use current template (no switch)\n", templateIndex); + templateIndex = -1; // 标准化为-1表示不切换 + } + + return true; +} + +// 解析旧格式协议:@模板编号,Trig$ 或 @模板编号Trig +// 示例:@3,Trig$ 表示调用3号视觉模版 +bool GrabBagPresenter::_ParseOldFormatProtocol(const QString& qData, int& cameraIndex, int& templateIndex, QString& triggerCmd) +{ + QRegularExpression oldFormatRegex("@(\\d+).*Trig"); + QRegularExpressionMatch oldFormatMatch = oldFormatRegex.match(qData); + + if (!oldFormatMatch.hasMatch()) { + return false; + } + + // 旧格式解析成功(只有模板编号) + templateIndex = oldFormatMatch.captured(1).toInt(); + cameraIndex = m_currentCameraIndex; // 使用当前默认相机 + triggerCmd = "Trig"; + + LOG_INFO("Parsed old format command: template=%d, using current camera=%d\n", templateIndex, cameraIndex); + return true; +} + +// 解析简单协议:Trig +// 示例:Trig 表示使用当前相机和当前模板触发检测 +bool GrabBagPresenter::_ParseSimpleProtocol(const QString& qData, int& cameraIndex, int& templateIndex, QString& triggerCmd) +{ + if (!qData.contains("Trig", Qt::CaseInsensitive)) { + return false; + } + + // 简单的Trig命令(不切换模板和相机) + cameraIndex = m_currentCameraIndex; + templateIndex = -1; // 不切换模板 + triggerCmd = "Trig"; + + LOG_INFO("Parsed simple Trig command, using current camera=%d and template\n", cameraIndex); + return true; +} + +// 处理Trig命令 +bool GrabBagPresenter::_HandleTrigCommand(const TCPClient* pClient, int cameraIndex, int templateIndex) +{ + SetWorkStatus(WorkStatus::Working); + // 1. 验证相机编号有效性 + if (cameraIndex < 1 || cameraIndex > static_cast(m_vrEyeDeviceList.size())) { + LOG_ERROR("Invalid camera index: %d, valid range: 1-%zu\n", cameraIndex, m_vrEyeDeviceList.size()); + + if (m_pStatus) { + m_pStatus->OnStatusUpdate(QString("无效的相机编号: %1").arg(cameraIndex).toStdString()); + } + + // 发送错误响应 + QString response = QString("Error_Invalid_Camera_%1").arg(cameraIndex); + m_pTcpServer->SendData(pClient, response.toStdString().c_str(), response.toStdString().length()); + return false; + } + + // 检查相机是否已连接 + if (m_vrEyeDeviceList[cameraIndex - 1].second == nullptr) { + LOG_ERROR("Camera %d is not connected\n", cameraIndex); + + if (m_pStatus) { + QString cameraName = QString::fromStdString(m_vrEyeDeviceList[cameraIndex - 1].first); + m_pStatus->OnStatusUpdate(QString("相机%1未连接").arg(cameraName).toStdString()); + } + + // 发送错误响应 + QString response = QString("Error_Camera_%1_Not_Connected").arg(cameraIndex); + m_pTcpServer->SendData(pClient, response.toStdString().c_str(), response.toStdString().length()); + return false; + } + + // 2. 如果指定了模板编号,进行模板切换 + if (templateIndex > 0) { + // 通过包裹ID(全局唯一)在配置文件中查询包裹、相机和工位信息 + std::string packageId = "pkg_" + std::to_string(templateIndex); + + WorkPositionConfig* foundWorkPosition = nullptr; + CameraConfig* foundCamera = nullptr; + PackageTypeConfig* foundPackage = nullptr; + + // 遍历所有工位、相机、包裹,查找匹配的包裹ID + for (auto& wp : m_configResult.workPositions) { + for (auto& cam : wp.cameras) { + for (auto& pkg : cam.packages) { + if (pkg.id == packageId) { + foundWorkPosition = ℘ + foundCamera = &cam; + foundPackage = &pkg; + break; + } + } + if (foundPackage) break; + } + if (foundPackage) break; + } + + // 检查是否找到包裹 + if (!foundPackage || !foundCamera || !foundWorkPosition) { + LOG_ERROR("Package not found with ID: %s (template index: %d)\n", packageId.c_str(), templateIndex); + + if (m_pStatus) { + m_pStatus->OnStatusUpdate(QString("未找到包裹ID: %1 (模板编号: %2)") + .arg(QString::fromStdString(packageId)) + .arg(templateIndex).toStdString()); + } + + QString response = QString("Error_Package_Not_Found_%1").arg(templateIndex); + m_pTcpServer->SendData(pClient, response.toStdString().c_str(), response.toStdString().length()); + return false; + } + + // 验证找到的相机索引是否与请求的相机索引匹配 + if (foundCamera->cameraIndex != cameraIndex) { + LOG_WARNING("Package %s belongs to camera %d, but requested camera is %d. Using package's camera.\n", + packageId.c_str(), foundCamera->cameraIndex, cameraIndex); + // 更新相机索引为包裹所属的相机 + cameraIndex = foundCamera->cameraIndex; + } + + // 记录找到的配置信息 + std::string templateName = foundPackage->name; + + LOG_INFO("Switched to package by ID: %s (template index: %d)\n", packageId.c_str(), templateIndex); + LOG_INFO(" Work Position: %s (ID: %s)\n", foundWorkPosition->name.c_str(), foundWorkPosition->id.c_str()); + LOG_INFO(" Camera: %s (ID: %s, Index: %d)\n", foundCamera->cameraName.c_str(), foundCamera->id.c_str(), foundCamera->cameraIndex); + LOG_INFO(" Package: %s (ID: %s)\n", templateName.c_str(), foundPackage->id.c_str()); + + if (m_pStatus) { + m_pStatus->OnStatusUpdate(QString("切换到[工位:%1, 相机:%2, 包裹:%3]") + .arg(QString::fromStdString(foundWorkPosition->name)) + .arg(QString::fromStdString(foundCamera->cameraName)) + .arg(QString::fromStdString(foundPackage->name)).toStdString()); + } + + // 更新执行参数(使用新的模板配置) + _UpdateCurrentExecutionParams(foundWorkPosition, foundCamera, foundPackage, cameraIndex); + } + + // 3. 显示状态信息 + LOG_INFO("Starting detection with camera %d (template %d)\n", cameraIndex, templateIndex); + + // 4. 启动指定相机的检测(非自动模式) + // 如果已经切换了模板(templateIndex > 0),则不需要再更新参数 + // 如果没有切换模板,则需要在 StartDetection 中更新参数 + bool needUpdateParams = (templateIndex <= 0); + int result = StartDetection(cameraIndex, false, needUpdateParams); + + // 5. 发送响应给客户端 + if (result != SUCCESS) { + QString response = QString("Error_%1").arg(result); + LOG_ERROR("Failed to start camera %d detection, error: %d\n", cameraIndex, result); + m_pTcpServer->SendData(pClient, response.toStdString().c_str(), response.toStdString().length()); + } + return result == SUCCESS; +} + // TCP客户端连接状态回调处理方法 void GrabBagPresenter::onTcpClientEventFromCallback(const TCPClient* pClient, TCPServerEventType eventType) { @@ -812,11 +826,11 @@ void GrabBagPresenter::SetStatusCallback(IYGrabBagStatus* status) } // 模拟检测函数,用于演示 -int GrabBagPresenter::StartDetection(int cameraIdx, bool isAuto) -{ +int GrabBagPresenter::StartDetection(int cameraIdx, bool isAuto, bool updateParams) +{ LOG_INFO("--------------------------------\n"); - LOG_INFO("Start detection with camera index: %d\n", cameraIdx); - + LOG_INFO("Start detection with camera index: %d, updateParams: %d\n", cameraIdx, updateParams); + // 检查设备状态是否准备就绪 if (isAuto && m_currentWorkStatus != WorkStatus::Ready) { LOG_INFO("Device not ready, cannot start detection\n"); @@ -826,19 +840,17 @@ int GrabBagPresenter::StartDetection(int cameraIdx, bool isAuto) return ERR_CODE(DEV_BUSY); } - // 保存当前使用的相机ID(从1开始编号) - if(-1 != cameraIdx){ - m_currentCameraIndex = cameraIdx; - } - int cameraIndex = m_currentCameraIndex; + // 确定要使用的相机索引(不修改默认相机索引) + int cameraIndex = (cameraIdx != -1) ? cameraIdx : m_currentCameraIndex; - m_currentWorkStatus = WorkStatus::Working; + LOG_INFO("Using camera index: %d (default: %d)\n", cameraIndex, m_currentCameraIndex); + + m_currentWorkStatus = WorkStatus::Detecting; - // 通知UI工作状态变更为"正在工作" if (m_pStatus) { - m_pStatus->OnWorkStatusChanged(WorkStatus::Working); + m_pStatus->OnWorkStatusChanged(m_currentWorkStatus); } - + // 设置机械臂工作状态为忙碌 if (m_pRobotProtocol) { m_pRobotProtocol->SetWorkStatus(RobotProtocol::WORK_STATUS_BUSY); @@ -855,8 +867,14 @@ int GrabBagPresenter::StartDetection(int cameraIdx, bool isAuto) // 清空检测数据缓存(释放之前的内存) _ClearDetectionDataCache(); - // 更新当前执行参数(包括相机参数、调平参数、手眼标定参数) - _UpdateCurrentExecutionParams(); + // 更新当前执行参数(如果需要) + if (updateParams) { + // 临时保存默认相机索引 + int savedDefaultCameraIndex = m_currentCameraIndex; + m_currentCameraIndex = cameraIndex; + _UpdateCurrentExecutionParams(); + m_currentCameraIndex = savedDefaultCameraIndex; // 恢复默认相机索引 + } // 配置相机参数 int nRet = _ApplyCameraParameters(); @@ -1033,7 +1051,10 @@ int GrabBagPresenter::_OpenDevice(int cameraIndex, const char* cameraName, const // 先设置状态回调 nRet = pDevice->SetStatusCallback(&GrabBagPresenter::_StaticCameraNotify, this); - LOG_DEBUG("SetStatusCallback result: %d\n", nRet); + if(nRet != SUCCESS){ + delete pDevice; + LOG_ERROR("SetStatusCallback failed, error code: %d\n", nRet); + } ERR_CODE_RETURN(nRet); // 尝试打开相机1 @@ -1261,13 +1282,25 @@ void GrabBagPresenter::CheckAndUpdateWorkStatus() } } +void GrabBagPresenter::SetWorkStatus(WorkStatus status) +{ + m_currentWorkStatus = status; + if (m_pStatus) { + m_pStatus->OnWorkStatusChanged(m_currentWorkStatus); + } + + if(WorkStatus::Error == status){ + m_currentWorkStatus = WorkStatus::Ready; + } +} + void GrabBagPresenter::_AlgoDetectThread() { while(m_bAlgoDetectThreadRunning) { std::unique_lock lock(m_algoDetectMutex); m_algoDetectCondition.wait(lock, [this]() { - return m_currentWorkStatus == WorkStatus::Working; + return m_currentWorkStatus == WorkStatus::Working || m_currentWorkStatus == WorkStatus::Detecting; }); if(!m_bAlgoDetectThreadRunning){ @@ -1312,63 +1345,48 @@ int GrabBagPresenter::_DetectTask() return ERR_CODE(DEV_ARG_INVAILD); } - // 2. 准备算法参数 - 使用 ParameterManager 获取参数 + // 2. 准备算法参数 - 直接从 m_currentExecutionParams 和全局配置获取 SG_bagPositionParam detectionAlgoParam; SSG_planeCalibPara detectionCalibParam; CalibMatrix detectionHandEyeMatrix; - // 初始化调平参数为单位矩阵 - double identityCalibMatrix[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; - memcpy(detectionCalibParam.planeCalib, identityCalibMatrix, sizeof(identityCalibMatrix)); - memcpy(detectionCalibParam.invRMatrix, identityCalibMatrix, sizeof(identityCalibMatrix)); - detectionCalibParam.planeHeight = -1.0; + // 2.1 初始化算法参数结构 + memset(&detectionAlgoParam, 0, sizeof(SG_bagPositionParam)); - // 初始化手眼矩阵为单位矩阵 - double identityHandEyeMatrix[16] = { - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, 0.0, 1.0 - }; - memcpy(detectionHandEyeMatrix.clibMatrix, identityHandEyeMatrix, sizeof(identityHandEyeMatrix)); + // 从全局配置中获取算法参数 + const VrAlgorithmParams& xmlParams = m_configResult.algorithmParams; - LOG_INFO("[Algo Thread] work position index: %d, camera index: %d, package index: %d \n", m_currentWorkPositionIndex, m_currentCameraIndex, m_currentPackageTypeIndex); - // 2.1 获取包裹类型参数(优先级:指定索引 > 默认配置 > 全局默认) - bool packageParamsSuccess = false; - try { - packageParamsSuccess = m_pParameterManager->GetPackageTypeParams(m_currentPackageTypeIndex, detectionAlgoParam); - } catch (const std::exception& e) { - LOG_ERROR("[Algo Thread] Exception in GetPackageTypeParams: %s\n", e.what()); - if (m_pStatus) { - m_pStatus->OnStatusUpdate("获取包裹参数异常"); - } - return ERR_CODE(DEV_ARG_INVAILD); - } catch (...) { - LOG_ERROR("[Algo Thread] Unknown exception in GetPackageTypeParams\n"); - if (m_pStatus) { - m_pStatus->OnStatusUpdate("获取包裹参数异常"); - } - return ERR_CODE(DEV_ARG_INVAILD); - } + // 滤波参数 + detectionAlgoParam.filterParam.continuityTh = xmlParams.filterParam.continuityTh; + detectionAlgoParam.filterParam.outlierTh = xmlParams.filterParam.outlierTh; - // 2.2 获取工作点参数(优先级:指定索引 > 默认配置 > 当前执行参数) - try { - LOG_INFO("[Algo Thread] Getting work position params...\n"); - bool ret2 = m_pParameterManager->GetWorkPositionParams(m_currentWorkPositionIndex, m_currentCameraIndex, detectionCalibParam, detectionHandEyeMatrix); - LOG_INFO("[Algo Thread] GetWorkPositionParams returned: %d\n", ret2); - } catch (const std::exception& e) { - LOG_ERROR("[Algo Thread] Exception in GetWorkPositionParams: %s\n", e.what()); - if (m_pStatus) { - m_pStatus->OnStatusUpdate("获取工作点参数异常"); - } - return ERR_CODE(DEV_ARG_INVAILD); - } catch (...) { - LOG_ERROR("[Algo Thread] Unknown exception in GetWorkPositionParams\n"); - if (m_pStatus) { - m_pStatus->OnStatusUpdate("获取工作点参数异常"); - } - return ERR_CODE(DEV_ARG_INVAILD); - } + // 角点参数 + detectionAlgoParam.cornerParam.cornerTh = xmlParams.cornerParam.cornerTh; + detectionAlgoParam.cornerParam.scale = xmlParams.cornerParam.scale; + detectionAlgoParam.cornerParam.minEndingGap = xmlParams.cornerParam.minEndingGap; + detectionAlgoParam.cornerParam.minEndingGap_z = xmlParams.cornerParam.minEndingGap_z; + detectionAlgoParam.cornerParam.jumpCornerTh_1 = xmlParams.cornerParam.jumpCornerTh_1; + detectionAlgoParam.cornerParam.jumpCornerTh_2 = xmlParams.cornerParam.jumpCornerTh_2; + + // 生长参数 + detectionAlgoParam.growParam.maxLineSkipNum = xmlParams.growParam.maxLineSkipNum; + detectionAlgoParam.growParam.yDeviation_max = xmlParams.growParam.yDeviation_max; + detectionAlgoParam.growParam.maxSkipDistance = xmlParams.growParam.maxSkipDistance; + detectionAlgoParam.growParam.zDeviation_max = xmlParams.growParam.zDeviation_max; + detectionAlgoParam.growParam.minLTypeTreeLen = xmlParams.growParam.minLTypeTreeLen; + detectionAlgoParam.growParam.minVTypeTreeLen = xmlParams.growParam.minVTypeTreeLen; + + // 支持旋转 + detectionAlgoParam.supportRotate = xmlParams.supportRotate; + + // 使用 m_currentExecutionParams 中的包裹参数 + detectionAlgoParam.bagParam.bagL = m_currentExecutionParams.bagParam.bagL; + detectionAlgoParam.bagParam.bagW = m_currentExecutionParams.bagParam.bagW; + detectionAlgoParam.bagParam.bagH = m_currentExecutionParams.bagParam.bagH; + + // 2.2 直接使用 m_currentExecutionParams 中的调平参数和手眼标定参数 + detectionCalibParam = m_currentExecutionParams.planeCalibParam; + detectionHandEyeMatrix = m_currentExecutionParams.handEyeCalibMatrix; // 3. 准备检测 unsigned int lineNum = 0; @@ -1903,9 +1921,7 @@ void GrabBagPresenter::_LoadCurrentWorkPositionAndPackageType() WorkPositionConfig* defaultWorkPosition = nullptr; if (!m_configResult.workPositions.empty()) { defaultWorkPosition = &m_configResult.workPositions[0]; - m_currentWorkPositionId = defaultWorkPosition->id; - LOG_INFO("Using first work position: %s (name: %s)\n", - m_currentWorkPositionId.c_str(), defaultWorkPosition->name.c_str()); + LOG_INFO("Using first work position: %s\n", defaultWorkPosition->name.c_str()); } else { LOG_ERROR("No work positions configured!\n"); return; @@ -1914,40 +1930,34 @@ void GrabBagPresenter::_LoadCurrentWorkPositionAndPackageType() // 2. 从工作点中获取默认相机 CameraConfig* defaultCamera = defaultWorkPosition->GetDefaultCamera(); if (defaultCamera) { - m_currentCameraId = defaultCamera->id; - LOG_INFO("Using default camera: %s (name: %s, index: %d)\n", - m_currentCameraId.c_str(), defaultCamera->cameraName.c_str(), - defaultCamera->cameraIndex); + LOG_INFO("Using default camera: %s (index: %d)\n", + defaultCamera->cameraName.c_str(), defaultCamera->cameraIndex); } else if (!defaultWorkPosition->cameras.empty()) { // 如果没有默认配置,使用第一个相机 - m_currentCameraId = defaultWorkPosition->cameras[0].id; defaultCamera = &defaultWorkPosition->cameras[0]; LOG_WARNING("No default camera configured for work position %s, using first camera: %s\n", - m_currentWorkPositionId.c_str(), m_currentCameraId.c_str()); + defaultWorkPosition->name.c_str(), defaultCamera->cameraName.c_str()); } else { - LOG_ERROR("No cameras configured for work position %s!\n", m_currentWorkPositionId.c_str()); + LOG_ERROR("No cameras configured for work position %s!\n", defaultWorkPosition->name.c_str()); return; } // 3. 从相机中获取默认包裹 PackageTypeConfig* defaultPackage = defaultCamera->GetDefaultPackage(); if (defaultPackage) { - m_currentPackageTypeId = defaultPackage->id; - LOG_INFO("Using default package: %s (name: %s)\n", - m_currentPackageTypeId.c_str(), defaultPackage->name.c_str()); + LOG_INFO("Using default package: %s\n", defaultPackage->name.c_str()); } else if (!defaultCamera->packages.empty()) { // 如果没有默认配置,使用第一个包裹 - m_currentPackageTypeId = defaultCamera->packages[0].id; defaultPackage = &defaultCamera->packages[0]; LOG_WARNING("No default package configured for camera %s, using first package: %s\n", - m_currentCameraId.c_str(), m_currentPackageTypeId.c_str()); + defaultCamera->cameraName.c_str(), defaultPackage->name.c_str()); } else { - LOG_ERROR("No packages configured for camera %s!\n", m_currentCameraId.c_str()); + LOG_ERROR("No packages configured for camera %s!\n", defaultCamera->cameraName.c_str()); return; } - // 4. 更新当前执行参数 - _UpdateCurrentExecutionParams(); + // 4. 更新当前执行参数(使用找到的默认配置) + _UpdateCurrentExecutionParams(defaultWorkPosition, defaultCamera, defaultPackage, defaultCamera->cameraIndex); } int GrabBagPresenter::_ApplyCameraParameters() @@ -2033,7 +2043,7 @@ int GrabBagPresenter::_ApplyCameraParameters() void GrabBagPresenter::_UpdateCurrentExecutionParams() { - LOG_INFO("Updating current execution parameters\n"); + LOG_INFO("Updating current execution parameters using m_currentCameraIndex: %d\n", m_currentCameraIndex); // 1. 设置相机序号(使用当前默认相机) m_currentExecutionParams.cameraIndex = m_currentCameraIndex; @@ -2045,7 +2055,6 @@ void GrabBagPresenter::_UpdateCurrentExecutionParams() m_currentExecutionParams.planeCalibParam.invRMatrix[i] = identityMatrix9[i]; } m_currentExecutionParams.planeCalibParam.planeHeight = -1.0; - LOG_INFO("Initialized plane calibration parameters to identity matrix\n"); // 3. 初始化手眼标定矩阵为单位矩阵(默认值) double identityMatrix16[16] = { @@ -2057,52 +2066,33 @@ void GrabBagPresenter::_UpdateCurrentExecutionParams() for (int i = 0; i < 16; i++) { m_currentExecutionParams.handEyeCalibMatrix.clibMatrix[i] = identityMatrix16[i]; } - LOG_INFO("Initialized hand-eye calibration matrix to identity matrix\n"); - // 4. 按照三级结构导航:工作点->相机->包裹 - - // 4.1 获取当前工作点配置 + // 4. 通过 m_currentCameraIndex 查找配置 const WorkPositionConfig* currentWorkPosition = nullptr; - for (const auto& wp : m_configResult.workPositions) { - if (wp.id == m_currentWorkPositionId) { - currentWorkPosition = ℘ - break; - } - } - - if (!currentWorkPosition) { - LOG_WARNING("No current work position found (ID: %s), using default parameters\n", - m_currentWorkPositionId.c_str()); - return; - } - - // 4.2 获取当前相机配置 const CameraConfig* currentCamera = nullptr; - for (const auto& cam : currentWorkPosition->cameras) { - if (cam.id == m_currentCameraId) { - currentCamera = &cam; - break; - } - } - - if (!currentCamera) { - LOG_WARNING("No current camera found (ID: %s) in work position %s, using default parameters\n", - m_currentCameraId.c_str(), m_currentWorkPositionId.c_str()); - return; - } - - // 4.3 获取当前包裹配置 const PackageTypeConfig* currentPackage = nullptr; - for (const auto& pkg : currentCamera->packages) { - if (pkg.id == m_currentPackageTypeId) { - currentPackage = &pkg; - break; + + // 遍历所有工作点、相机,找到匹配 cameraIndex 的相机配置 + for (const auto& wp : m_configResult.workPositions) { + for (const auto& cam : wp.cameras) { + if (cam.cameraIndex == m_currentCameraIndex) { + currentWorkPosition = ℘ + currentCamera = &cam; + // 获取该相机的默认包裹或第一个包裹 + if (!cam.packages.empty()) { + currentPackage = cam.GetDefaultPackage(); + if (!currentPackage) { + currentPackage = &cam.packages[0]; + } + } + break; + } } + if (currentCamera) break; } - if (!currentPackage) { - LOG_WARNING("No current package found (ID: %s) in camera %s, using default parameters\n", - m_currentPackageTypeId.c_str(), m_currentCameraId.c_str()); + if (!currentWorkPosition || !currentCamera || !currentPackage) { + LOG_WARNING("Failed to find configuration for camera index %d, using default parameters\n", m_currentCameraIndex); return; } @@ -2127,6 +2117,11 @@ void GrabBagPresenter::_UpdateCurrentExecutionParams() m_currentExecutionParams.cameraParam.swingStopAngle = 180.0; } + // 5.1 设置包裹参数(从包裹配置中获取) + m_currentExecutionParams.bagParam = currentPackage->bagParam; + LOG_INFO("Using bag parameters: L=%.1f, W=%.1f, H=%.1f\n", + currentPackage->bagParam.bagL, currentPackage->bagParam.bagW, currentPackage->bagParam.bagH); + // 6. 设置调平参数(从相机配置中获取) if (currentCamera->planeCalibParam.isCalibrated && currentCamera->planeCalibParam.cameraIndex == m_currentCameraIndex) { @@ -2157,6 +2152,108 @@ void GrabBagPresenter::_UpdateCurrentExecutionParams() } LOG_INFO("Current execution parameters updated successfully\n"); + LOG_INFO(" Work Position: %s\n", currentWorkPosition->name.c_str()); + LOG_INFO(" Camera: %s (Index: %d)\n", currentCamera->cameraName.c_str(), m_currentCameraIndex); + LOG_INFO(" Package: %s\n", currentPackage->name.c_str()); + + // 更新 ParameterManager 的当前执行参数 + m_pParameterManager->SetCurrentExecutionParams( + m_currentExecutionParams.planeCalibParam, + m_currentExecutionParams.handEyeCalibMatrix + ); +} + +// 重载版本:直接使用配置指针更新执行参数 +void GrabBagPresenter::_UpdateCurrentExecutionParams(const WorkPositionConfig* workPosition, const CameraConfig* camera, const PackageTypeConfig* package, int cameraIndex) +{ + LOG_INFO("Updating current execution parameters with provided configuration\n"); + + if (!workPosition || !camera || !package) { + LOG_ERROR("Invalid configuration pointers provided\n"); + return; + } + + // 1. 设置相机序号 + m_currentExecutionParams.cameraIndex = cameraIndex; + + // 2. 初始化调平参数为单位矩阵(默认值) + double identityMatrix9[9] = {1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0}; + for (int i = 0; i < 9; i++) { + m_currentExecutionParams.planeCalibParam.planeCalib[i] = identityMatrix9[i]; + m_currentExecutionParams.planeCalibParam.invRMatrix[i] = identityMatrix9[i]; + } + m_currentExecutionParams.planeCalibParam.planeHeight = -1.0; + + // 3. 初始化手眼标定矩阵为单位矩阵(默认值) + double identityMatrix16[16] = { + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0 + }; + for (int i = 0; i < 16; i++) { + m_currentExecutionParams.handEyeCalibMatrix.clibMatrix[i] = identityMatrix16[i]; + } + + // 4. 设置相机参数(从包裹配置中获取) + const VrCameraParams* cameraParam = package->GetCameraParam(cameraIndex); + + if (cameraParam) { + m_currentExecutionParams.cameraParam = *cameraParam; + LOG_INFO("Using camera parameters from package %s for camera %d: exposure=%.2f, gain=%.2f, frameRate=%.2f\n", + package->name.c_str(), cameraIndex, + cameraParam->exposure, cameraParam->gain, cameraParam->frameRate); + } else { + LOG_WARNING("No camera parameters found for camera %d in package %s, using defaults\n", + cameraIndex, package->name.c_str()); + // 使用默认参数 + m_currentExecutionParams.cameraParam.cameraIndex = cameraIndex; + m_currentExecutionParams.cameraParam.exposure = 100.0; + m_currentExecutionParams.cameraParam.gain = 1.0; + m_currentExecutionParams.cameraParam.frameRate = 30.0; + m_currentExecutionParams.cameraParam.swingSpeed = 10.0; + m_currentExecutionParams.cameraParam.swingStartAngle = 0.0; + m_currentExecutionParams.cameraParam.swingStopAngle = 180.0; + } + + // 4.1 设置包裹参数(从包裹配置中获取) + m_currentExecutionParams.bagParam = package->bagParam; + LOG_INFO("Using bag parameters: L=%.1f, W=%.1f, H=%.1f\n", + package->bagParam.bagL, package->bagParam.bagW, package->bagParam.bagH); + + // 5. 设置调平参数(从相机配置中获取) + if (camera->planeCalibParam.isCalibrated && + camera->planeCalibParam.cameraIndex == cameraIndex) { + // 使用实际标定矩阵 + for (int i = 0; i < 9; i++) { + m_currentExecutionParams.planeCalibParam.planeCalib[i] = camera->planeCalibParam.planeCalib[i]; + m_currentExecutionParams.planeCalibParam.invRMatrix[i] = camera->planeCalibParam.invRMatrix[i]; + } + m_currentExecutionParams.planeCalibParam.planeHeight = camera->planeCalibParam.planeHeight; + LOG_INFO("Using calibrated plane parameters from camera %s (height=%.3f)\n", + camera->cameraName.c_str(), camera->planeCalibParam.planeHeight); + } else { + LOG_INFO("Camera %s is not calibrated, using identity matrix for plane calibration\n", + camera->cameraName.c_str()); + } + + // 6. 设置手眼标定参数(从相机配置中获取) + if (camera->handEyeCalibParam.isCalibrated && + camera->handEyeCalibParam.cameraIndex == cameraIndex) { + // 使用实际标定矩阵 + for (int i = 0; i < 16; i++) { + m_currentExecutionParams.handEyeCalibMatrix.clibMatrix[i] = camera->handEyeCalibParam.transformMatrix[i]; + } + LOG_INFO("Using calibrated hand-eye matrix from camera %s\n", camera->cameraName.c_str()); + } else { + LOG_INFO("Camera %s hand-eye is not calibrated, using identity matrix\n", + camera->cameraName.c_str()); + } + + LOG_INFO("Current execution parameters updated successfully\n"); + LOG_INFO(" Work Position: %s\n", workPosition->name.c_str()); + LOG_INFO(" Camera: %s (Index: %d)\n", camera->cameraName.c_str(), cameraIndex); + LOG_INFO(" Package: %s\n", package->name.c_str()); // 更新 ParameterManager 的当前执行参数 m_pParameterManager->SetCurrentExecutionParams( diff --git a/App/GrabBag/GrabBagApp/Presenter/Src/ParameterManager.cpp b/App/GrabBag/GrabBagApp/Presenter/Src/ParameterManager.cpp index b0dafef..6ce21b1 100644 --- a/App/GrabBag/GrabBagApp/Presenter/Src/ParameterManager.cpp +++ b/App/GrabBag/GrabBagApp/Presenter/Src/ParameterManager.cpp @@ -524,8 +524,7 @@ bool ParameterManager::GetWorkPositionParams(int workPositionIndex, int cameraIn } } - LOG_INFO("[ParameterManager] Found work position: %s (ID: %s)\n", - workPosition->name.c_str(), workPosition->id.c_str()); + LOG_INFO("[ParameterManager] Found work position: %s (ID: %s)\n", workPosition->name.c_str(), workPosition->id.c_str()); // 第2步:从工作点找到相机配置 if (cameraIndex >= 0) { diff --git a/App/GrabBag/GrabBagApp/Version.h b/App/GrabBag/GrabBagApp/Version.h index 9b7a438..338895f 100644 --- a/App/GrabBag/GrabBagApp/Version.h +++ b/App/GrabBag/GrabBagApp/Version.h @@ -3,8 +3,8 @@ #define GRABBAG_VERSION_STRING "1.3.2" -#define GRABBAG_BUILD_STRING "0" -#define GRABBAG_FULL_VERSION_STRING "V1.3.2_0" +#define GRABBAG_BUILD_STRING "2" +#define GRABBAG_FULL_VERSION_STRING "V1.3.2_2" // 获取版本信息的便捷函数 inline const char* GetGrabBagVersion() { diff --git a/App/GrabBag/GrabBagApp/Version.md b/App/GrabBag/GrabBagApp/Version.md index 807861d..93cd81d 100644 --- a/App/GrabBag/GrabBagApp/Version.md +++ b/App/GrabBag/GrabBagApp/Version.md @@ -1,5 +1,12 @@ -#1.3.2 2025-10-28 -## build_0 +#1.3.2 +## build_2 2025-11-23 +1. config 修改默认数据 + +## build_1 2025-11-23 +1. 修改协议触发错误 +2. 包裹ID修改为唯一标识 + +## build_0 2025-10-28 1. 摆动机构默认参数无效 2. 修改帧率范围 3. 相机IP配置:通过配置文件config.xml 配置相机IP,如果没有没有配置,搜索相机进行打开 diff --git a/App/GrabBag/GrabBagApp/devstatus.cpp b/App/GrabBag/GrabBagApp/devstatus.cpp index 2893958..899ae4d 100644 --- a/App/GrabBag/GrabBagApp/devstatus.cpp +++ b/App/GrabBag/GrabBagApp/devstatus.cpp @@ -52,7 +52,8 @@ void devstatus::setCameraStatusImage(QWidget* widget, bool isOnline) { QString imagePath = isOnline ? ":/common/resource/camera_online.png" : ":/common/resource/camera_offline.png"; - widget->setStyleSheet(QString("image: url(%1);").arg(imagePath)); + // 使用 border-image 而不是 image,这是 QWidget 支持的正确属性 + widget->setStyleSheet(QString("QWidget { border-image: url(%1); }").arg(imagePath)); // widget->setFixedSize(32, 32); // 调整大小以适应图片 } @@ -62,7 +63,8 @@ void devstatus::setRobotStatusImage(QWidget* widget, bool isOnline) { QString imagePath = isOnline ? ":/common/resource/robot_online.png" : ":/common/resource/robot_offline.png"; - widget->setStyleSheet(QString("image: url(%1); ").arg(imagePath)); + // 使用 border-image 而不是 image,这是 QWidget 支持的正确属性 + widget->setStyleSheet(QString("QWidget { border-image: url(%1); }").arg(imagePath)); // widget->setFixedSize(32, 32); // 调整大小以适应图片 } diff --git a/App/GrabBag/GrabBagApp/devstatus.ui b/App/GrabBag/GrabBagApp/devstatus.ui index 327457e..6145320 100644 --- a/App/GrabBag/GrabBagApp/devstatus.ui +++ b/App/GrabBag/GrabBagApp/devstatus.ui @@ -65,7 +65,7 @@ - image: url(:/common/resource/robot_offline.png); + QWidget { border-image: url(:/common/resource/robot_offline.png); } @@ -118,7 +118,7 @@ - image: url(:/common/resource/camera_offline.png); + QWidget { border-image: url(:/common/resource/camera_offline.png); } @@ -171,7 +171,7 @@ - image: url(:/common/resource/camera_offline.png); + QWidget { border-image: url(:/common/resource/camera_offline.png); } diff --git a/App/GrabBag/GrabBagApp/dialogcameralevel.cpp b/App/GrabBag/GrabBagApp/dialogcameralevel.cpp index 0a931e7..1609770 100644 --- a/App/GrabBag/GrabBagApp/dialogcameralevel.cpp +++ b/App/GrabBag/GrabBagApp/dialogcameralevel.cpp @@ -194,8 +194,7 @@ void DialogCameraLevel::updateCameraList() void DialogCameraLevel::on_btn_apply_clicked() { ui->label_level_result->setAlignment(Qt::AlignLeft); - -#ifndef LEVEL_DEBUG_MODE + // 检查是否有可用的相机 if (m_cameraList.empty()) { QMessageBox::warning(this, "错误", "无可用相机设备!"); @@ -208,7 +207,6 @@ void DialogCameraLevel::on_btn_apply_clicked() QMessageBox::warning(this, "错误", "请选择有效的相机!"); return; } -#endif // 清空之前的结果显示 ui->label_level_result->setText("调平计算中,请稍候..."); @@ -221,7 +219,7 @@ void DialogCameraLevel::on_btn_apply_clicked() // 执行相机调平 if (performCameraLeveling()) { // 调平成功,关闭对话框(这会触发析构函数中的回调恢复) - accept(); + // accept(); } else { // 显示失败信息到界面 ui->label_level_result->setText("调平失败!\n\n请检查:\n1. 相机连接是否正常\n2. 地面扫描数据是否充足\n3. 扫描区域是否有足够的地面"); @@ -249,24 +247,7 @@ bool DialogCameraLevel::performCameraLeveling() // 1. 设置调平状态回调 setLevelingStatusCallback(); - -#ifdef LEVEL_DEBUG_MODE - // Debug模式:使用文件对话框选择测试数据 - LOG_INFO("=== DEBUG MODE ENABLED ===\n"); - - // 选择debug数据文件 - QString debugDataFile = selectDebugDataFile(); - if (debugDataFile.isEmpty()) { - LOG_INFO("Debug data file selection cancelled\n"); - return false; - } - - // 使用选择的debug数据进行模拟扫描 - if (!loadDebugDataAndSimulateScan(debugDataFile)) { - LOG_ERROR("Failed to load debug data for camera leveling\n"); - return false; - } -#else + // 正常模式:使用真实相机 if (selectedIndex < 0 || selectedIndex >= m_cameraList.size()) { LOG_ERROR("Invalid camera index: %d\n", selectedIndex); @@ -305,7 +286,6 @@ bool DialogCameraLevel::performCameraLeveling() } else if (waitTime >= maxWaitTime) { LOG_WARNING("Timeout waiting for camera swing finish signal\n"); } -#endif // 5. 检查是否收集到足够的数据 // 6. 调用调平算法计算 @@ -329,12 +309,6 @@ bool DialogCameraLevel::performCameraLeveling() QString cameraName; QString workPosId; -#ifdef LEVEL_DEBUG_MODE - // Debug模式下使用默认名称 - cameraIndex = 1; - cameraName = QString("Camera_%1").arg(cameraIndex); - workPosId = "default_wp"; // Debug模式下的默认工位ID -#else // 正常模式下从列表获取名称 if (m_currentCameraIndex >= 0 && m_currentCameraIndex < static_cast(m_cameraList.size())) { cameraName = QString::fromStdString(m_cameraList[m_currentCameraIndex].first); @@ -350,7 +324,6 @@ bool DialogCameraLevel::performCameraLeveling() LOG_ERROR("Invalid work position index: %d\n", m_currentWorkPosIndex); return false; } -#endif if (!saveLevelingResults(planeCalib, planeHeight, invRMatrix, cameraIndex, cameraName, workPosId)) { LOG_ERROR("Failed to save leveling results\n"); @@ -556,35 +529,7 @@ bool DialogCameraLevel::calculatePlaneCalibration(double planeCalib[9], double& LOG_INFO("Plane calibration calculated: height=%.3f, rotX=%.2f°, rotY=%.2f°\n", planeHeight, rotAngleX, rotAngleY); - -#ifdef LEVEL_DEBUG_MODE - // 统计点云数据 - int totalPoints = 0; - double avgHeight = 0.0; - for (const auto& scanLine : m_scanDataCache) { - for (int i = 0; i < scanLine.nPositionCnt; i++) { - if (scanLine.p3DPosition[i].pt3D.z > -1000 && scanLine.p3DPosition[i].pt3D.z < 1000) { - avgHeight += scanLine.p3DPosition[i].pt3D.z; - totalPoints++; - } - } - } - - LOG_INFO("=== DEBUG MODE CALIBRATION RESULTS ===\n"); - LOG_INFO("Algorithm results:\n"); - LOG_INFO(" Calculated plane height: %.3f mm\n", calibResult.planeHeight); - LOG_INFO("Calibration matrix:\n"); - for (int i = 0; i < 3; i++) { - LOG_INFO(" [%.6f, %.6f, %.6f]\n", calibResult.planeCalib[i*3], calibResult.planeCalib[i*3+1], calibResult.planeCalib[i*3+2]); - } - LOG_INFO("Inverse rotation matrix:\n"); - for (int i = 0; i < 3; i++) { - LOG_INFO(" [%.6f, %.6f, %.6f]\n", calibResult.invRMatrix[i*3], calibResult.invRMatrix[i*3+1], calibResult.invRMatrix[i*3+2]); - } - LOG_INFO("=======================================\n"); -#endif - return true; } catch (const std::exception& e) { diff --git a/App/GrabBag/GrabBagApp/dialogconfigtree.cpp b/App/GrabBag/GrabBagApp/dialogconfigtree.cpp index eff3571..67852ab 100644 --- a/App/GrabBag/GrabBagApp/dialogconfigtree.cpp +++ b/App/GrabBag/GrabBagApp/dialogconfigtree.cpp @@ -107,7 +107,19 @@ void DialogConfigTree::initTree() // 包裹节点 for (auto& pkg : cam.packages) { QTreeWidgetItem* pkgItem = new QTreeWidgetItem(camItem); - QString pkgName = QString::fromStdString(pkg.name); + + // 从包裹ID中提取数字编号(格式:pkg_1 -> 1) + QString pkgIdStr = QString::fromStdString(pkg.id); + QString pkgNumber = pkgIdStr; + if (pkgIdStr.startsWith("pkg_")) { + pkgNumber = pkgIdStr.mid(4); // 提取 "pkg_" 后面的数字 + } + + // 显示格式:包裹名称 (ID编号) + QString pkgName = QString("%1 (%2)") + .arg(QString::fromStdString(pkg.name)) + .arg(pkgNumber); + if (pkg.id == defaultPkgId && cam.id == defaultCamId && wp.id == defaultWpId) { pkgName += " [默认]"; } @@ -673,12 +685,30 @@ void DialogConfigTree::addCamera(const QString& wpId, const QString& name, int c void DialogConfigTree::addPackage(const QString& wpId, const QString& camId, const QString& name) { + // 计算全局包裹ID(遍历所有工位、所有相机下的所有包裹) + int maxPkgId = 0; + for (const auto& wp : m_configResult->workPositions) { + for (const auto& cam : wp.cameras) { + for (const auto& pkg : cam.packages) { + // 从包裹ID中提取数字部分(格式:pkg_1, pkg_2, ...) + std::string pkgIdStr = pkg.id; + if (pkgIdStr.find("pkg_") == 0) { + int pkgNum = std::stoi(pkgIdStr.substr(4)); + if (pkgNum > maxPkgId) { + maxPkgId = pkgNum; + } + } + } + } + } + for (auto& wp : m_configResult->workPositions) { if (wp.id == wpId.toStdString()) { CameraConfig* cam = wp.GetCamera(camId.toStdString()); if (cam) { PackageTypeConfig pkg; - pkg.id = "pkg_" + std::to_string(cam->packages.size() + 1); + // 使用全局唯一的包裹ID + pkg.id = "pkg_" + std::to_string(maxPkgId + 1); pkg.name = name.toStdString(); // 使用ConfigResult中的默认值初始化包裹参数 @@ -928,11 +958,16 @@ void DialogConfigTree::onCameraLevelClicked() int x = parentGeometry.left() + (parentGeometry.width() - dlg.width()) / 2; int y = parentGeometry.top() + (parentGeometry.height() - dlg.height()) / 2; dlg.move(x, y); - +#if 0 if (dlg.exec() == QDialog::Accepted) { // 调平成功后,刷新相机配置显示 showCameraConfig(m_currentWorkPos, m_currentCamera); } +#else + dlg.exec(); + showCameraConfig(m_currentWorkPos, m_currentCamera); +#endif + } void DialogConfigTree::displayHandEyeCalibInline() diff --git a/App/GrabBag/GrabBagApp/dialogconfigtree.ui b/App/GrabBag/GrabBagApp/dialogconfigtree.ui index 5c89e99..f8addfb 100644 --- a/App/GrabBag/GrabBagApp/dialogconfigtree.ui +++ b/App/GrabBag/GrabBagApp/dialogconfigtree.ui @@ -7,7 +7,7 @@ 0 0 896 - 619 + 642 @@ -216,8 +216,8 @@ color: rgb(221, 225, 233); 0 0 - 243 - 436 + 536 + 569 @@ -251,12 +251,12 @@ color: rgb(221, 225, 233); 16 - - true - + + true + @@ -278,12 +278,12 @@ color: rgb(221, 225, 233); 16 - - true - + + true + @@ -619,7 +619,7 @@ color: rgb(221, 225, 233); 0 0 550 - 520 + 543 diff --git a/App/GrabBag/GrabBagApp/main.cpp b/App/GrabBag/GrabBagApp/main.cpp index d4542f2..e1770f9 100644 --- a/App/GrabBag/GrabBagApp/main.cpp +++ b/App/GrabBag/GrabBagApp/main.cpp @@ -5,10 +5,35 @@ #include #include #include +#include +#ifdef _WIN32 +#include +#include +#endif + +#ifdef _WIN32 +// Windows平台特定设置,解决中文乱码问题 +void setupWindowsConsoleEncoding() { + // 设置控制台代码页为UTF-8 + SetConsoleOutputCP(CP_UTF8); + SetConsoleCP(CP_UTF8); +} +#endif int main(int argc, char *argv[]) { QApplication a(argc, argv); + +#ifdef _WIN32 + // Windows平台特定设置,解决中文乱码问题 + setupWindowsConsoleEncoding(); +#endif + +#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) + // Qt4及更早版本需要显式设置编码 + QTextCodec *codec = QTextCodec::codecForName("UTF-8"); + QTextCodec::setCodecForLocale(codec); +#endif // 设置应用程序图标 a.setWindowIcon(QIcon(":/common/resource/logo.png")); @@ -30,4 +55,4 @@ int main(int argc, char *argv[]) w.show(); return a.exec(); -} \ No newline at end of file +} diff --git a/App/GrabBag/GrabBagApp/mainwindow.ui b/App/GrabBag/GrabBagApp/mainwindow.ui index 491750c..a14ccd7 100644 --- a/App/GrabBag/GrabBagApp/mainwindow.ui +++ b/App/GrabBag/GrabBagApp/mainwindow.ui @@ -96,7 +96,7 @@ background-color: rgba(255, 255, 255, 0); - 1175 + 1110 21 220 80 @@ -119,7 +119,7 @@ border: none; - 930 + 865 21 220 80 @@ -142,7 +142,7 @@ border: none; - 1175 + 1110 21 220 80 @@ -165,7 +165,7 @@ border: none; - 685 + 620 21 220 80 @@ -237,7 +237,7 @@ background-color: rgba(255, 255, 255, 0); - 1525 + 1470 21 80 80 @@ -259,7 +259,7 @@ background-color: rgba(255, 255, 255, 0); - 1645 + 1590 21 80 80 @@ -353,7 +353,7 @@ background-color: rgba(255, 255, 255, 0); 0 0 1920 - 19 + 21 diff --git a/App/GrabBag/GrabBagConfig/Inc/IVrConfig.h b/App/GrabBag/GrabBagConfig/Inc/IVrConfig.h index 12b09ba..22948bb 100644 --- a/App/GrabBag/GrabBagConfig/Inc/IVrConfig.h +++ b/App/GrabBag/GrabBagConfig/Inc/IVrConfig.h @@ -91,9 +91,9 @@ struct VrOutlierFilterParam struct VrCornerParam { double minEndingGap = 20.0; - double minEndingGap_z = 20.0; - double scale = 15.0; - double cornerTh = 30.0; + double minEndingGap_z = 40.0; + double scale = 50.0; + double cornerTh = 45.0; double jumpCornerTh_1 = 60.0; double jumpCornerTh_2 = 15.0; }; @@ -283,7 +283,7 @@ struct VrAlgorithmParams VrHsvCmpParam hsvCmpParam; VrRgbColorPattern rgbColorPattern; VrColorTemplateParam colorTemplateParam; - int supportRotate = 0; + int supportRotate = 1; }; /**