From 15a61fd986a76625f295ae5ac246df6738b4c966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90=C4=83ng=20Nguy=E1=BB=85n?= Date: Tue, 30 Dec 2025 17:38:43 +0700 Subject: [PATCH] update --- .../Components/Monitor/RobotMonitorView.razor | 148 ++++++++++++++++-- .../Monitor/RobotMonitorView.razor.css | 12 ++ .../Pages/Order/JsonOutputPanel.razor | 4 +- RobotApp.Client/Pages/RobotMonitor.razor | 1 + .../wwwroot/images/gara20250309.png | Bin 0 -> 25552 bytes RobotApp.Client/wwwroot/js/robotMonitor.js | 18 +++ RobotApp/Components/App.razor | 1 + RobotApp/Hubs/RobotMonitorHub.cs | 1 + 8 files changed, 169 insertions(+), 16 deletions(-) create mode 100644 RobotApp.Client/wwwroot/images/gara20250309.png diff --git a/RobotApp.Client/Pages/Components/Monitor/RobotMonitorView.razor b/RobotApp.Client/Pages/Components/Monitor/RobotMonitorView.razor index ace1b8b..501eb37 100644 --- a/RobotApp.Client/Pages/Components/Monitor/RobotMonitorView.razor +++ b/RobotApp.Client/Pages/Components/Monitor/RobotMonitorView.razor @@ -22,7 +22,14 @@ {
- X: @MonitorData.RobotPosition.X.ToString("F2")m | Y: @MonitorData.RobotPosition.Y.ToString("F2")m | θ: @((MonitorData.RobotPosition.Theta * 180 / Math.PI).ToString("F1"))° + Robot: X: @MonitorData.RobotPosition.X.ToString("F2")m | Y: @MonitorData.RobotPosition.Y.ToString("F2")m | θ: @((MonitorData.RobotPosition.Theta * 180 / Math.PI).ToString("F1"))° +
+ } + @if (MouseWorldX.HasValue && MouseWorldY.HasValue) + { +
+ + Mouse: X: @MouseWorldX.Value.ToString("F2")m | Y: @MouseWorldY.Value.ToString("F2")m
} @* @@ -36,18 +43,49 @@ @onmousemove="HandleMouseMove" @onmouseup="HandleMouseUp" @onmouseleave="HandleMouseLeave"> + @* Arrow markers for origin *@ + + + + + + + + + @* Background Map Image *@ + @if (MapImageLoaded && MapImageWidth > 0 && MapImageHeight > 0) + { + + } + + @* Origin Marker (2 arrows: X+ and Y+) *@ + + @* X+ Arrow (pointing right) *@ + + @* Y+ Arrow (pointing up in world, down in SVG) *@ + + @* Origin point *@ + + + @if (MonitorData?.HasOrder == true) { - @* @for (int i = 0; i < MonitorData.EdgeStates.Length; i++) - { - var edge = MonitorData.EdgeStates[i]; - var (startX, startY, endX, endY) = GetEdgeEndpoints(i, edge); - - } *@ ("getElementSize", SvgContainerRef); + var containerSize = await JS.InvokeAsync("robotMonitor.getElementSize", SvgContainerRef); SvgWidth = containerSize.Width; SvgHeight = containerSize.Height; + // Load map image and get dimensions + await LoadMapImage(); + // Center view on robot if available with initial zoom if (MonitorData?.RobotPosition != null) { @@ -150,6 +205,31 @@ } } + private async Task LoadMapImage() + { + try + { + var imageDimensions = await JS.InvokeAsync("robotMonitor.loadImageAndGetDimensions", MapImageUrl); + + // Convert pixel dimensions to world coordinates (meters) + MapImageWidth = imageDimensions.Width * MapImageResolution; + MapImageHeight = imageDimensions.Height * MapImageResolution; + + Console.WriteLine($"Map image loaded: {imageDimensions.Width}x{imageDimensions.Height} pixels, {MapImageWidth}x{MapImageHeight} meters"); + + if (MapImageWidth > 0 && MapImageHeight > 0) + { + MapImageLoaded = true; + await InvokeAsync(StateHasChanged); // Force re-render after image is loaded + } + } + catch (Exception ex) + { + MapImageLoaded = false; + Console.WriteLine($"Failed to load map image: {ex.Message}"); + } + } + private string GetTransform() { return $"translate({TranslateX}, {TranslateY}) scale({ZoomScale * BASE_PIXELS_PER_METER})"; @@ -217,6 +297,31 @@ return -worldY; } + private string GetOriginMarkerTransform() + { + // Origin is at (MapImageOriginX, MapImageOriginY) in world coordinates + // In SVG: (MapImageOriginX, -MapImageOriginY) + var x = WorldToSvgX(MapImageOriginX); + var y = WorldToSvgY(MapImageOriginY); + return $"translate({x}, {y})"; + } + + private double GetOriginMarkerSize() + { + // Marker size in world coordinates (meters) + const double BaseMarkerSize = 0.5; // 1 meter + double scaleFactor = 1.0 / ZoomScale; // Keep visual size constant + return BaseMarkerSize * scaleFactor; + } + + private double GetOriginMarkerStrokeWidth() + { + // Stroke width in world coordinates + const double BaseStrokeWidth = 0.05; // 5cm + double scaleFactor = 1.0 / ZoomScale; // Keep visual size constant + return BaseStrokeWidth * scaleFactor; + } + public void UpdatePath() { if (MonitorData is not null && MonitorData.EdgeStates.Length > 0) @@ -291,14 +396,26 @@ PanStartY = e.ClientY - TranslateY; } - private void HandleMouseMove(MouseEventArgs e) + private async Task HandleMouseMove(MouseEventArgs e) { + // Calculate world coordinates of mouse + var svgRect = await JS.InvokeAsync("robotMonitor.getElementBoundingRect", SvgRef); + double mouseX = e.ClientX - svgRect.X; + double mouseY = e.ClientY - svgRect.Y; + + // Convert to world coordinates + // World X = (mouseX - TranslateX) / (ZoomScale * BASE_PIXELS_PER_METER) + MouseWorldX = (mouseX - TranslateX) / (ZoomScale * BASE_PIXELS_PER_METER); + // World Y = -(mouseY - TranslateY) / (ZoomScale * BASE_PIXELS_PER_METER) (flip Y axis) + MouseWorldY = -(mouseY - TranslateY) / (ZoomScale * BASE_PIXELS_PER_METER); + if (IsPanning) { TranslateX = e.ClientX - PanStartX; TranslateY = e.ClientY - PanStartY; - StateHasChanged(); } + + StateHasChanged(); } private void HandleMouseUp(MouseEventArgs e) @@ -309,6 +426,9 @@ private void HandleMouseLeave(MouseEventArgs e) { IsPanning = false; + MouseWorldX = null; + MouseWorldY = null; + StateHasChanged(); } private async Task HandleWheel(WheelEventArgs e) @@ -324,7 +444,7 @@ if (Math.Abs(ZoomScale - oldZoom) < 0.001) return; // Zoom at mouse position - var svgRect = await JS.InvokeAsync("getElementBoundingRect", SvgRef); + var svgRect = await JS.InvokeAsync("robotMonitor.getElementBoundingRect", SvgRef); double mouseX = e.ClientX - svgRect.X; double mouseY = e.ClientY - svgRect.Y; diff --git a/RobotApp.Client/Pages/Components/Monitor/RobotMonitorView.razor.css b/RobotApp.Client/Pages/Components/Monitor/RobotMonitorView.razor.css index 85f2893..21240f7 100644 --- a/RobotApp.Client/Pages/Components/Monitor/RobotMonitorView.razor.css +++ b/RobotApp.Client/Pages/Components/Monitor/RobotMonitorView.razor.css @@ -54,6 +54,18 @@ gap: 8px; } +.mouse-position-info { + color: #fff; + font-size: 14px; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + padding: 4px 12px; + background-color: #3d3d3d; + border-radius: 4px; + display: flex; + align-items: center; + gap: 8px; +} + .svg-container { flex: 1; overflow: hidden; diff --git a/RobotApp.Client/Pages/Order/JsonOutputPanel.razor b/RobotApp.Client/Pages/Order/JsonOutputPanel.razor index fbd2f25..11ba185 100644 --- a/RobotApp.Client/Pages/Order/JsonOutputPanel.razor +++ b/RobotApp.Client/Pages/Order/JsonOutputPanel.razor @@ -6,14 +6,14 @@
- Import JSON - + *@ >g*ND zCuqxdSGK_=L?;j;I1mXyfV*U4;Ama8oxu)`&a;auW>TTJ;Fl6aa{T0az!B ztYQF}n1j<%8pS<>jboV~Ct!bI=fExzFeVP9Nd#pTdB#e@gv78m36lWn`zI3>6+rcL$Q|AC_3Jh{yi$s6`Xtq~p*N%jmO&AM^*uVW{bB`Q}oH!w!%k>Q8 z!W|{^rGwk@sTbzEYxzQv5g$L2j*C^jJ5J1ncML_*HUcD03+l4k!n5lw0;hl_S}sY& z6Gc*=aWlu`5_b*v_so`2uM%ZR5t@^nA5kG<&WBl{yH%Q7OYIyD`X=k~bv&CW+iNs`!Xo~&g$)7CqdW+=p2`huBrd+E^wa`fc2W3hwXm)(RY)3q;-z2?6i|MX+q zx46paarL$*zkwh7$KQ=UmnGh<&s9?rqRl1As6+!)>kSNvwdA;Ajd%@)7%9xqHP%zU zZ;dg|7-NmGHZ@|cvCcVbopY}CmGRo70muo`2*;J`4om3aeL~I~`I-~QcD*Dw^XU^Q zYgX~Zb3`~)$d8)-!hKI={_cBvE-5-8aZ1kZs09QVedb*e_HA97BDQl~9?jsMUcCQ4bsiPmgKA#=WRM)0Z-twEtcKr4IJte6= zI(J&nBge>H3ae=>v}cr&Mq2dj$XP>*9|eA3o6&^o$j~ zv&&0s2s0Nd$sCwP5jmwAUd(x6SnF(~(Ln4BY>vA(&G+2Iip7zi8h{*2ZG3;%HCwZ( z$7~?u4LA5wny6>a-^7*PTSo2NvHdeo_e?vlr|7KoXZgYov0J-F3{915Lq%D|U05A! zNOMq0mKNsnc?x|}4B{dGR6`}R-KFXbHst|~VDGM@!vz4xW%Iz^yHP(KYaFqlXu%%edzV~eg^=Iz8~Ww2X>2Ag>OUp_21R_ zrMX&2;aSfiot>SO?g|$)S@wWXIH)J73OzB6dRD8%R>V30l)LMmrrwB{Br|miC!UR< z`sn#|F5GHrNm`z<-Ghx|T?3)hKMQL2<@k53opV{m8Co7TPut7nqq4^3oH8Yx*ujb{rld=Dvgs%bKJ>EN8p3Y(~ zjh=n|(1}Ce@>=iC-ZKUNE`Xa(MkBxW=H8XpYDs?fmrXd#`=rnpzFYyW^7+W+ySl## zLVxj@{(tH1tsmVvvhB4ChmT;(JFcH|HxJ_H0Tj$9ugy`u)bM%)#Lyh}MAaE{svc@m zB*s8nZ8K*bv4|4^frWxWKQ~c!zE-ReEA29y)#5bYmG?F?;`p_dlUrPECY;I$sgzG4 z_LFduZ~2L4C5D~9Df*4A^HnNcUm1&zRibKQP3r`QwWc{pW|Kx&$@i_c&O@kmSd~VW zs}qM^XC2vh%fkotEpm+JBLAH>0zwQcFH688# zmf-fG{0$5M{OwP9cT}ccncBGfkz&^kb1FX^bSI12vcK_XYBPs!dKm!JKgJc`p{Shl z$*~)Q=oPfnPrwIfBGl9v00OWt_KLhyX$=#u5CuT111jtK?^4I^uW&r!E z?`dqQooWP5BdgU>Wr9=~1M|{<&}KjWlGB}fZU2c%R7n%GRB%{xngd*trH!tFEwVF2 zeoqk2*1}VzvbLu+mufI@^5Ecb*R?PI$$!>kAN%4fZu-K%eA(a9^WdB+jPBWXyucYLFXZPK^=+#@t|3~4C$M1gd*-H7*Lawi$xERj(_q}AHRe9o%NBhZ~m_2&i zZg*;5;_3ttL5M&s-u>i^b(x%g{tJJ&=6?!AlHLs9yPmrzs1YYDfd!z#u#h;f2mrf2 z`oLCdj8{Fsc-=tl`uvXX30`<+ykUQ6zO4WGHfb!}m4}bSwOVXiE;zJFa~EHcbd4;D zi}k4F)$`&@Ydvs+1yKh*6q%> z;KGXcp7ru4kNv^tem5%pw#~K==-7kPRQH~G-~aUiICg*YB;5ocdUkB@ji(~Ez-e8P zP!D$XlC|Q z->craFlybsdr=j?{S{B#3IM1)Fc!B-aA;dQM;E(X$xIrnP}iL&>=lBUh-#(jkujbh z(iH(P>W3z?vEu>&zw;&l9}d~^j{H+czWRH=pKpgT;J^LP%7;I8<3wEAwZ{qo>)4G4 zbsrEx;{&eIT*aPy=v1RT^jEKHUTWgkx)!guWwS|j#!yynWY$E(J0Rhmz~N7%xgB?! zegOFXe@3?W?%5Q8E&}*&{6=l6@yxXTuit-xDsI{Y0RJ?_6KL%a?krlQIZ3_D=Y`88 z5@!QM;1>k3YD=pWYCv|6)!SjvhUI_=lzvGkjy3-{Ez%?W*itzx)D>sPEIJ5Yr*oFt1 zx1|2VK=rx8jyo=L_HX^u$kw6un&Dd5w<;E5Z8&oEeBRava^y^A?fj<_iwhU`hmsmw z%foY#M$weU#}Crs_%+SuA))vy(-YHZ%-wkAgr9zU^T*D7^7-NKZrct0&%agu*CLC{ zG9A{utR6OUT>$V`o1#BQ_;tIiC}TjKmn2iO(Omz0JBO(!Kl9alM=pHw z2i_6tJtWdrH-r|B=SVC_X%}pRi-UoN^9sgf#t4AM*hJM1?d)4T$Z)l!7iNZR=&oN|c>lFu`ukUn-<)Jxku~oc zS#|ln(b=Q_+SsY1wL6MCmUC|W2`Z>u)o-sOYpVrMk{RPusM*g4zI9!39Hp7)p~1eM zuYYVhIJ?nz^{iN(3cvGbLPIK{Benl`^woa{B&GlWHy!=Pzh89lmMMXnjRf61+{Qb4 z?O!eZ{yW~gJ={^5y<9uUfv5BXqaIPNC&&RnJU-h<9{f^%$8t{ou?8c{{5CJjw&Cj~ z&5-DP zOlaMfY_9u(yW4a>FU-xwB7ok@_0_$0cw*l^5O~w3ZC%5t9~zs@r24N;u!@!pq>o;cR7%W45Py$itZqsEEJ z9taZSKx87joKt-pP~Kb4kyn#8cI}cGn7QJhxJY#=ovZZ@2QT?_y2z2srsSh?qLeM` zHVqO07X%YN9^Kr&%W4U~a*PSSHA(7mzEo{Q+rDu*r_G5AuaslW>S?c&zBpD5gkCmR zor;wT@akO?X=Sc5er$YZt}^@h7oRSC>(TKoZf8YzRV7uq0<(w8nhlS-FcvWfl6sCaLfbv$l)YxD*fR`WqtSaUvCoEYF zlTP>a9*8bAvw!K^U-wo34GVxY5B8bQMHu^!@A2DvZC8^Ul)CQS_`yFs1@*^$eY-xT z|7uyMHGo1-yX)R=&5>taBaY&Dn_u;xxM>i3_C7SBW)9T7Fo&IcM{_~(&xe2UtO>#S z6Tj=;RRDmLEg&*r`^{VrHvRGjmK=WNGqd*t=-Zs`y#sjTKfP{wryZf76fW=3tH>IN z`yxk%`q7}dul)ufK2>?@qtQ3L;mi`-a+kC|v`+v5qx6Nz-GAQht6G!vMZK@y)9y;N zyPX37A!sgJzh=@}5#e^ckfCk0i;?ST_vNpd zyiCr*<@=xi!-D{Tix1!~RON5c03aef;QHEqK}%kvt2B_r3$d*x&9W9(K~QLa*Sc0T z2WIE8<3|rPUtQdvFkbrYyPD012>IT!W^pa1y#Jl=4-kZ`&0{A_YMk%2xWW6XS(X#Bs<-rSy7+j?0` zbG^-`zhD4K?9gXF{0slpTHRs+6g+OX-3w^@9KvIWusBC7&PbS^m>2;7ikDo~-LeG$ z%)_r*;@17;qbG`fY^~vEkHvSj_p+Aebx(d>*CTgLADs;L{@l+W`@H9tXJ5H;+}>Sj zTz)B+O?x#*wyJZP4pXOWVzb=fi6iqKbf+bEJn89OXpEnp`uMLDf%llde#0jD-v4p7 zfvaqezo%KK6SURe|3ZA=gMEFE&E{qvtbKQ5YVeb`)$`Y>zpG#^ELyg?os+r>sy#)W zdZsQJlBC)=XDl}Q+-w}=nsCZbGGh)LqMR%w3E&*P(Ir)}G!yLBW+cOCoPX>gT1M?mEF?Y0>NY)b=-g z`Cb1;yvegQhoSkXPVbgm0019&OM9>FeALaOPq2&oy0#tpom}Xrd^PU=)EjfZfBw6- z4*bqv?QJ~Ny1w-yar14~oGDM=yR+MAn{-Jn^h?9eGv^%QmzwDY7vMkrP3+w&-cS7G z>9;9Mt@xv%bje4rH9XUj?c72M5OWKUuY;QgB$T#fS-Jce1G#2eRUcYq|{%0Qkl}nsdKgS(s@<(p^t+rmpGC6hE z!1oOGzo5%*>fqDg@Xo6vIpC`w|JC!~{K}E<0+7>hZpCFtOI-5lFcvh^O+`wzo=GkKD)SzWmIIDO=jMeech;r3QJ5AA03g^0{fYaEi#&1SbM9 zOj?8;TYHww)YrD<;?F}k7Nh3q)G77K(|O$*t4JZUb}<#5-+gOw(r&)#RbNxO{PI@8 z*Z!|)u2Cvpfovs2WEbU=B34Q%6@XR{(xhO77q2|DL36}3k5azx`zpxi z!dxTv^IoaHds}7uX;<{xlOJpvPNU2i@O*BURAv9=)h(~}<->oty&F#!uklNF7j54Y zpTF_)i(D~ZdEV9Ik)?d-7U#`zcF{{`&;8*O5fR8Jw$0I-Qc8QC=LZF!L_|PJ0SyW@ z!#Xh#UYzf&<$Y_}Y~uxMXcRJ7C{wPX0#<&^sHyy!^lDwTZ)yJVsaWDrX@b&0u~0t# z$M4Uv)RUjM1C|9#LUmU|NW^-nJ^YUT>_PeXI@iNiM$3xv;FxQ$Li6}zLF_F~-- z0+o8fA`EE+I5QU@03=Kh@&)iftcZ|?J`ofg2u>+6#)k@*Rvk=b@P8uk5lH?a~`0eLzN~cT3zic!AsurnXx%nkZM)% z{u{4vOC9e0xTzi<+U!i`@(ffbg}eGGt=8iMr?TFimANpEj7x}%*A6K^6Ul6*mG<&M zo(KRFAjBjLup)pwEe?UNfy{|GA*3SK3RDhg=10~lT|8ZGx#oDm30MF?IRLOwuz+*g zO6bOqZ3TeE3%}crJH#qY0b7==%QTR5}&Lm8s)o*t2keote({ORk(aQJT74c-=_I%|?zifdLRRz)+%v0@%zs z2#K>sAR-VnCD(k;i|f$tdFz=!omG^wPU;i;3+GO)?$}qI|4W2WKiS4)xR-muyMV2i zU(D*+f6I~hnHMYols1pfCTViy_^vGnyS$s<^zr?+V7;9`u`8K5_!s*Vlc;cD&LpCA zQwm;YYL<{0!!?WE5c2@!3i5oM~Op&L{2s z(8DpC+RyK8O@2)Fv^5pE#2&$3eNF=xc(_8 zRicI@Y67X^zjtS|bdB6Q!6W1}uvhgZUxcGFS_m(~2>_ zLpfKbrbdqM)iT#}_A#$~$ejGt+_qjs(Tzt=Vq3fG{NiDD{Mba@Sb=lS!HEM1PI;_g zED10_h+F;;do5^A!GVOF)4i6w?!DQ`9m<{KUwy~P#tVn9fBn=%;PJCDer=Us)nTO^ zJ$`Z`G1krR%mV_6_^b&=AVki(6*O@G+U`FGEG$eqv66R6jTd&0ly3CS%UCt_z{Ah~ z`}B>pzJI)$O4DNmL=g6T z-{Z_VhDheDTg6K08t7XIhDbRCl7ecpX(6yA>DXC&`p`rzYj&p~5Wt>Nf)pTe)?(#N9Dp^UIRFrVIdhUG?z4?ebGeBR zUjL?ZRHq*K{8!)nUBHRzXka&zzsXv`j2)=O4R6z?EzND!#-2PmS2Inq5g^Q-B4i*I zXAMMFyD+~NG)D?>fHfWv>PO?Z&X}j4IQa5&c3a!QeqZ_iQ%ARb>bk$Q!|(i=b4yv1 zIZDsVtuF zLZ%KTo>7oYtO$hJ8A^r4S-Zk%>Up$EbMDf;;mm<+s)qW~?!LZj=cXU;I`{Lhd42$h zNu6j2{r$NgzV?#EcUY3oKe502O~*oHe!v?3WN`!l>;vO2bI$j5Y8*4`G-9F5rcq{> z^DHk)t2CDeN`Q;nMr?m!dYs1UBZ&Rt?6v(h-+2n3dd^0v9rCqV=;>&f!YT0-$U-Lm= zH214@RYW^-BK8_cXuDQ4`vQ8%}$w1-g+-0v; zJv~W1ecD})NU;;5rf;8OzyZ{4JhN+y-^}k4BdsP;p{}nxmF;MrH5Gl~Sf%3Nk0)y( zyU^tl+X5ElJZl&Cee|bs=ko-&>7LuV?*(S-Mc!@1?qd5{-K-~P&4_PMwbeif=zfkvI4uERWgf-M(o48@YBw=5Flv!9D zxTi(6zM3>ItF0SG1Hi7I28L=|ZoTi{yk%2h4-fSA^S}~^eeXVdXaG4|3;VoGU#!;W zjJ4(bHnDZR6pL|~zsaZF)0+mo(~itprgimmC%+8{RD+^gG?za8s%z#87=C=ZxjvFi zZsaZG+Q3}*H-GDv*AE7$j$Wt-+3j&78|})K+3?wlqxpwSdz!)e`|er-<|x2?hWQCH#2bgWn6KxHv_j^{E!<@OzZea`PS|8 zzZqJ1XscKqUQ;tb0Z{%jzW4MS!EZk{A7FiN>ErFL-IX9wYq6o*e8VLV`OOlB?yA$?q1ePKC+Bs-tw}s+Or=Sp4ZyC zuzf8TDQDT@C96Kb=>7lm1zMBY_g7532Da=ypG!5pS=1;${*}rg->t4hkmuTJ=^dyh zIB@oxI{o5_p7;N5yX$sESOdFlt(a_PY_jg2{_F%Pz9LIIYpR`dnetev=WaXu?Z>{F ze0|4;-xj1|4C!9S)vQh)U71k!HKjSy0zWwK%6c|^<6pMBYA;4=bG0IoS90a9O z{N?X%cQsy2OkH@z&>H~|&%upNsnKJH!eUb`43HpOwdU5)96}t3#4AR}FLcD|sMg>5 zV7n{3aj5=d`lBBzU- z9nSEfF(hVU%G+8RpPqKH@_cpH()F#~12^}hC+}N7lPfX)Utv3E-6F?Uxl~lmr3$N+ z&Rl{P5)dJ&B+a44q5toZ+-<8Zy2J9xB|Pb`ckFuy~p z@q{Fw-?!t`Nv>{tI@vOOR`$P27sPz(Z>IaRG%c_wvE*<$4c-bh$9&FajHk1w;A}yj z(cFb|eTvmeS-apgEJ}dvz#+X00CNBB>fPlIr`c8dsX0ZqQK%gaYTczqzMSUAU9c!1 zE&KiLZ+-lIPaUG7>q}Dy9m0`qq2|sTJ1%bFmL6D=mJ6Ju#MQe}IxS-%c3xlLU(68x z_P1ZwE$(nOuRdD%lbIZA+uhd{O_%&xFXZmNp2e#0ZkWjLe`;Sn-|ZT;SwGlhyx12* zs~=Te$$9J2bvUcvZT>nIrajt(4xvi3`9mE0diT@S#d|?MyFCZs z0h8Qw&5XxzQ})Psp|{wb@7*@o-+fl=@om8Oec>xEnc4pAfoN-wQ0gT07n+#0=_kCn zR_FB0y1CxtjK=`Uw21F&rCZM&@Ztad>?wTp4>$jE7e1GNY4g2*{o2lA%9Uc^P>@

pl{(MlCpEtVVe4U7r(6R86TtyBd)dI(0nV(qGR# z`;z9TlVgAI@pSNuy^UYl)P?d}2#0q7fbK}{it*W7ZhhMp8U=vlYj*xX{zB0wBo zIVQe#G)$G&&^SwImsFlC9{;s@P1u=0m&>!krcLGJabIy{UUFVc@-As&aQ zxlIlLk#iS~UGweciS6D8$0zitKEH2Z6HRyT-F43}78HkrUp!PTj(po2r|<83<^9ip z!@v3cE#3n!Og`|vm-BrcR>!w|V%Jz-kd~)rvfzeN;k2R_df3xZ%LD){?wn8~;ze_g zc54ofxR{q?vwQoyCi>s7sM~z#t79dv^q;@0{&V-d^6>M2*@wFyj<>q$!$70$2^U^; z{D=Pl0C?(|KY!LuRnATA)0`$Yw*#AR^Uhhskjw{oKF`S-u(v8EN*nJ!cGI@v-%r&F z$9@k0aH!HBbnPhJg2KBI*M5wEumAGLqdD2W26BA7SgGf(`Jtz4p?{7Elok!=1kHRP z=UNFo0WhCNqTTHrgIz;|?Re9# zwztABPNI5wrqYNSb7?}w^8)slSeS6;BB`6&sX1bhBBu(zEpE>IH@&%lBcJ=|(GdVZ zIxe%@%16KGcH^M#u9g6Feg4qSHdgmVh$KT^+M&p+U_`4a%ZL;xv9WxD4g zrM7KP5@L{ofMEq})6d=W+rXhsAwJx6CXpxZ`-&GGoHoDx133N}xqB}F{KjWL-KOfk z=&039OljOGCuu#ZJDgdM6EHylVt|;{Y2^u%2%vxi$F8|ZV#GGJb7Cn704L5kM>Z6^ zdGzFF>;y3HzeG1DGmrJqr2G62VDz4S%}dVBJk^d}>|b2CT9lg1+AMA)XOrF0Eoaj z07gjWXAn+=`!43x*?P@67E#R7+_r}mYfbSuH^#z=liT;6$(A1L-96)X|Be5x0D#*7 zVB)3ktUcE93crX*kcdsnjX0{tu_J&9rjheJ3s_okopW$xotuA2OC)(S7Yzw(z2+2* z%>iNnFta8>CM6&j9h@liTyxuKt(n;{1Rl+OD1G@$&Yb$R^z-d4ukeckYcsLNp^}+s zu9`(I6)RSNG|e?!Y#|rgX=$m=A}+3~c8uqgqzV9(W`;OpARbWcm6WPa9QevJk95x; z__~=}?Ryp`-Jc)tz3<;X(T3{2C`iiHT0?9aC2(S`7FQuj65ok5uJm`cZQklkD4r+A z&i{NQ36(%(=AY^Zd(JuboIP(kTm@!MOqC-T2ePD@5;$WUkuO#*YoDd1SZO;aS0pnJ zfUFUQlDN~?Lje=P$lxd{MUiu)Bt2+@ z%dSv!{!8-3E)$jG%m3!3rqg1}$r{R_^5TMvktw?r`L6!KI5#&v7?MKLu#QPP=WOQO zsYz@F≈y|~lR+7v6V%I1tckVo!Wy%yfU2D4-GGV1rJ{lBq`Po`uZdgp5 zR+G%QG<9ONv#{1#S%Ov`x~e>=#Od-KJhnFXM~NL|2akAl5k$q`BMs+_=4a zC7VfgxT5Hhk>2U)sZ&sM-HlvM>7Z{oi8+qq)MPe`#b(ZmaSoTGyOqYb!jagdJ{RN* zg)UR8u3C2E2k*L8#AJ%Z0t`W}l-0jfAN=pFy1^ZmVsK#Q)J!E&ZU)eP&>L!Sv4jmaVvev4gknED#vV3v&MH`NU7Reb2QH2uHdL<(V|HES{(=&Fb1y+J(g&m+~YO zMQNN?vqol9{Sv`^SUnYMKyp zY(R#sQ*cHD^%}E6K^F?c$kIlw+(;WXHAa@bAJgU8_ytyM)=*)PD|AOmqkj5ZCB0ze zbFC*od8qNaJC^w!Kf3v^FRP9nFk#b=4XiYm-^j?=#7r%7DT4$FDG;k52Mr0Fi`fV? z14k|r+A#4Sg7tM6Oq5fQOjoFvXK<$_QaoaU9HCAYAk$@_Zlo({k6hnKq~ z@>jDzbeUCraWj*{M?Y<5p8fdY(Qe@0f@mHe>-GqSGb z@^4(_N3{`n@(*77?Re#*3sI&gj_cC)9U0|MOcz;y;1kzuu8nM;-18~`kNivOz3d_- z(Qxm~@l&(Y+$#WJ1>!6-gfcf5vLEDhF_%|iT8FX?lg3VUl@m{rS{!Fiu%xN%iur5` zENm8eLC)_|ag@X6~Ilcd9Kl!QGEU!KEpwUFfm2gc(-Yp#LXMifCW&=qDO0Y-q!s&lPZzZFN?a6< zYE&LWFH?`PCP}lXVdXsCT~R+_v5>6hbl82b$C3zr{1$!xIQ*iwj>2Bq@}mHL=zB`{eDfC4%(3vnBm!>9eB#k(uMcLA?f}b!G`MJ>d5Y@9J z13(~lg6u>i7k6*E_KEMYjrv@r8awNQ2Y>I$uQYm(M1e?m-`D(CJ$Gi+=^I90?p?!2 zqwOPVEE?|81Nrju#@J&r%~0aj`v`)ntU`{^7)% zvZFIma}inhZW;`G)$zyP`P=7|qRJ1Pn64F6BRASxL~XpMG8flxdd~fDyC3K};LiX6 zn}Gj_qYu9IkN->} zg=?+&43nhDv7dQa!fEKDd{zl`Ez@LFn9FjAZ02+7%^dcly7Tj%<)~PX%T&r{XM%#* zMJe*H%0)ZCC9`wkO138Ai9OzzJlVa?==`Z!5dbvUmFBv(j;U`trv!Tb z!DF+D1YP+l)!i76t}_+rulSF=wc}p5BDQVA+unB0donha^aI*ey#XES?g4FJCM(aB zXEtxzab^~KA9{xd2OYB(fua=1$TQHHXq8&Z#IR1SQ4NJ8@x9njp+RGfTthjRY162d zy%NX%Nz>)>kTXf?u3Fre!^|VEDMrrg?aeB6!+l<<+uO{=+8^F9iXGF3$4^bB4(J6* z^)TK0inGs-?mskLPn}<{g!N*jtABG(#dpfb?ORt@n+~TLuUl6PG7C3L`OJjEskk5- z&*dvpi=OG(xv(e|sP4dM23bSe0xV9fkP{}#GLMa~8%|m0lW`J}F)3JOQ@NqK#~CqA z)n?r|JoHH45=z71jol9fjdqoeY-tF7B{wrpr*|{Je3I%1nfw9-UR^N=2@W zvsOX>49QrryQc>LUj5WJA1wEWBm!~(#KD*}^hm6bhJ|LOpa5qb!$@YexJ>6$s};a- z5_pOX;5kPyXRNYUQFFny!E>cT-w4dK>MohaT;#H`u_MDaxi$p+-Mi(l4-sn*^J#}L@pAH8lfZ!Z4J0^y6p1_F~!!}E8-hr4IXpZN> z2$!^eUfno;5ohCCImq>O_fghBe{Wi!X}G!a;Ugp4_bPx#MgT+;X5`E{Uw&q`;gpJ% zqrfRNa^*v}&iFmp6)K#U?7zeExemYdiP!EDcA}k{Ws}rxLovbg$*O3k=K8tVQ!_d8 zSjToE(L>eegKZ^!p2LV($3jBZu~-9TtQH7cKD$An3k`&>cJ2rfG~(*4@Aq}}B8#QJ zH>u4!H$DCIzR_Ff6XN7|oH_pRWD?O zb1_tGl(j-UpHcYIv?5=LSesJS3;Ozcx=fbu-_n?yNz|Nv{KnpM4}IvV$+-Dz2`Oo$ zX)G}5(UC34CO>7%uRqY(mUTvSE8_L(p_6aB6#(GA?uYA9wW^$U&RJF_wGgpHlxe0? z=CrVhsGcVTsU=C=&de|=u|nsmP6PyN5(O}cGo;C`A@#GH0wGRs7>o4TsZX&+VSQXo9HgZ4wg)!JO{@L z?)(lGBCeSVomQ5|rgfTA9$ZYUXtop_u8h9)Urv1cz&VH9KG zV}qP&&J2trSP(hkb5yq|ef0{e)FM+7hoNWUjLD@&W}mzNA0E>a@1Ornrl0-PejI%p zfIZ*vnwMVRry)d8I^XN%W~Ym0)7N!@0XvG0<}QbP-)M7SvTgrAW&Qodzz+(69|YR- z6glfatc-yOkx8xgJwNa~g_Oye%r9rb|-qeb^UW^xZZ4hVWKZ^#$ zddl-%W}LI)#1IIcb(GkE!D6lFLn}%d9sy1%GNO&W5_X`guQ~RC)G?EYh!+QriTZcc z9yobA&yITC-H(=zY~R+rAa&Cz=1WGjFVJYTR3MNd90iH4@t9wf{?lr{o+;D90vlIg1Ol! z*EnaG!Hw&mD_0u7+yDLZlhE$3#r1nX_Oah=zRJsOn~py_>)qJz^^A@`c-QOh8UpaF zCIE%i)#J)=f9;RoFwoW-TnWrc`jeH{S6{>aY)n7(@Ty)-+Y`3>_O>soWuGrLi4-19Y>#57Fm6j^dj+YYhE z?2%H6tj^`En>sPV%$$))$PqhnU|fj|bSu$ZvtKb1W;T?RFW$_G_D*zdsU%8BP7gc@ za_nBi-$aAuIuZO~4EqXEZ>wjB2@Dmh?n;N!^x}rOfJr*$+7?Q zJ?*T(MfuP_{`{gG^AFy7elX`QTkd_^&kg2B>*c5#7j1==O)`}kaez3ZC?+CiE*Fa0 zHPV!nR!A+mOfi#Vfavlq-Yd{tJlj=N&WY7fDCbn4h(u<-2@`kD=rYrdeM8?{o}P}= zBr$S&Bt`&8_cwTdj6(gw-}c?}|NFylIsIPvrLEs^C7;p?sXvhnj@|o_Yui|bi}B_| zi+jGuvPC<*{C#r!O~5Z66F)uh&`S#iRH}`tODZmg&TLjo>JCoCfmuY6OB(f@Glf_@ z5uHmVBTt%E1|hy|KZ_M;?(oCkac?DytRT#49^yzmzX%rR#KzhD9>fc6Z{pdbFO;qa z>gCzliP_Y|^BGOVo6Ev-x1{M*a{HMbNbvsX2bTWYneg_*01Ac8K)Et=Y^GvtX0p`Jg+|3)s;}q7TyFgF%3Cqr58&V{FYvWLedf>N{b(P% zCkDSe`6ApyKlB%HX?XbswwzgPzQcogTIacIS8tr#)PQP4p67~rYYl8cIt_L z`+bWKovuzF8IR-4i;eFqqO4LVm9nU-=POUtw*LO3-`MU)atQ{?vgMX9=rs2K^_~WW zHd|k^yK8Ihb9CFQ2_69AVjl*F3Z9uv>Y=s58oqO4ff!OrtznmDDO(CWFEry5$orvU z2X=y{<+U|=HtkZ@_2Fh{*{sqOqPO#trL?Qnf_DGZQVIKe`tUIjyo_@K6!ks zszR?b~K`+$<}tOz(^04_-@Gx_uZbEAoCG*T~mp4Lix z9&-R~qzg8RlC0WDOO68)=g2w@N6J~oWew|Apg9RHc}O_Ybofo?>g6Q18B!x+VNadZ z929n4Gk;ij_qD+E_dZvWCYn#NBod^LUyy{`yO4_;0Ta{7GsL7@HksmP$*0AJ&`)@sFH?6Ap9(&u7p+V)n@2`Kp-8ESq0gTOQ-!m%Jm-Y>D;!=Wm=*epUun4WoJmrKT zryrFg!fHC$tK7G49R3)8I&k9;w!0RqMuYrR5@GR|wUU)x=U)zdFl#AQ~N8Mq+gF+&s^N3IbY5D}U4|KG97$BvGz!fv;mojF8^K+80Toe_>EQQ}wFjlaaNq+Mr;TL69cOy9#s zoKZpmIbu?pJ>`2!dqLorTrg> zFM9Eh-*)Ej*|-1x9spBwrE9g|3Pf%#jQ}jv463-;ac6ah#&cejYYcR)=EpGK0$N}} zlmsv-VpdS>2Z0W9xqPnsYF+Y+p0o05>|FGB@9zTuZW(ypsBwa;s5%ECIJ)?-Zn5Sh zlb}#b;=#*qe+6hxeJ#v*$bVYkxApsFNy4bL8@m`u_BUFZ{yLf5m%q3u&2lM~4;(3UenE?f7O_F6!OO!Oy z%)IDVnOnVQ%p|BM%rK*7>(djG?MRQMKq3DxL6VU&c+iI~xt#hW^5&az+6oe?LdNQc@phl{ zz7dn+!B@DW2TmKKArxO82_MkLe)6vVB$96P!RL}H>rJ1W8)>M zp%n`$PMHo#kSFe=nlWmL7$@<7R(15|8$q1M*}~OG@-e&Sf`k#8{4O4^YW1`R1K1Z2 z2Yl%z*SoRPr@SpIq&d_$=48D$|;?Xgtr`MO@>HI1{DSZ``9c{Q5Nw| zq9W08?#VQd%QL-@MwwzQ&sp-8SFQaj zBc+Gw#oXj6X+Y1m3mCG9XHubHJYBp2op==@@`A5hTlh6S@GhY&Qb$XH9l@*uRU6BlHXrJn%1$(teO&Mahls@xE);8o;6>ZGe9qEF!=u3GFpuh(kl@+dgt$)G-BD2CuAgTG4|Zq5TNRdD z791hauqm7XqwT+XEapW2&Qrrq6t!wzOoSA*ZJmlh$A};EM2wX{ac1{+S zC?X*hi;~QGJOEPmAyFutF!bSddgJkT1}M|@^9iF(@jvRcL zc1XF&RLvQRZ4h*8pbSM;;}Fj|MS+3IFbxgxPhm1Z=$~*GeGoaAzmHUd6#88}{DQ(a zkKA(@9bBcePb?V^cG=uIt~54-9n7XegKh5?T#kq4mFUGZc2#qt8w5Lv)K6ExhCh}; z2Og>z0F*T>w#785e1bFMvsYhCqX%o*XosK)z5d6B7N6(Gx%7#o4xEvTQzhA`_W!)8275?Mt(L~F*{x3xl4?Bp!r5mGvJ*=#Rt!yjN*oI+Q) zC5gI=&;ROGut8=SD1?(~Z-yaTZ-AVp!<(&8Bejney&TMUXZy!3TAHhkiya|eef{}r z?Gnb7bje>)#Tapp?K!$FHU-Ut8eF_lExarla&EkBI<2|fUBQiwzjcUT>kc5b}`BtWthmOVf@V@ zkOLUeW}Edk>wWq&N}__FLuQHlC&B}vR#4eV(l*!S%lfqfoqexzr_Y^t6FKx%45LYz z4^bYzdmK<<=W+T}Pc}zk`kFd+7|L|8u#Xjx^<@GPp&|ovhzAU@SqZjBEH4N`>V}zEhPt6nj@TslwGKChKYwM9y-s{vr^ucHh=v!2f6ZFhHOY3vdi(>Yv=6Vh9EF! z{w%h`4A;i_4(iev;UQYrqeGGo(yq??>_;ex7|1y_JV^aRmh@Bjehb3X{r>(5;BQ~)(OvmwD%E(OZV}c` z=|8x6P<(gzY57z=vBv>89;Pa=hwj?%%YQAVx}`+DkqQ_c)6^`UW%9i% zy)V#g+OK5h)m#gj9BWDVTxdtWKQ2Y$T-)fG$^6QXRwvJK9g1nJnR7Vkyj$BTK)wGr zuiLP*yxBi6ZP#?yGr7{)xt|TybACJPZsL@(H)B+k*N>uay7@EPcdbTaVM|ROcQ_}^ z|5GgE^_w%THg{)4(vP0d;e(Jf2?GS>+2h~H$`H&TDn@!*p1o<`>k+UzFTechwtrFPiN zbGa_9MDeG4{FNw`>F3=8u{4i?b92AT4@Jg#k6z7puDRJy7y9~xD;79Wh-7`_bH1}m zxl4ib$m4Z97# z7g#tU6*Gn1)VBOMtS7xV6l-?eBpawgJ$U)KH1TrUEV!0L?DAL051YRh!RW?6lak}& zyV|u6$8)3=R6jr3*innfVumGk{PB-xq#S)ib-7ud9&$W`51J%&{O7rDpK`v7d!YD#zaK=7eVCChw$SY5{E&fW7PT zxAfKeWG*vUev+z;J&~M<3_NKb`Wt(ZBoi=vOps zN_(cQV}76S<;$B)^pSOgRBl5GwSIwY-phZVOvqo+|0DYva1`YATy{m&WoTP#>@42- ziR<4nk!+<_kCC~=?!+nVQ{9<%>CbL=8f>#;y`8PvH?Hkfn0yo3H$KG;HD=jom$^SF z%q)zuzX;^EbY+dcZnvgK8NJhJ6kOstt1R@IFfAuLN`sd_5;}L}Q5`eH&-5hAHqG^( zjW|#&Z=I0#$IyhNE)!jbupfVte7>aI`%N@2Is8BxGpLF;m1C$!(xGVP{8grW3`CB? z4?}Uw4%#SzxN*T&K6r3de~#?F`+?!;zl6Uoi%*>kTbzxuB(>VjPNE-(Q=iYid%9KX zcj?yYahKS>oB6gaV%KkAxNrS_!9vlxdt(b`n!|`<|8!@{{);T%u!sB>;%jF}!d{wGY%8rCd3bkcL!MLhO6Gmg z>hY_)s!OtEJEb>Y@;@kea_`72O;l6`{LUO_TWs>xefz7(^uv3={Y_-E(m3o9=7Hc^ zL_M_)u|;1Vq^v^|R<-yLf75JCe7-hlb~4eELP$Osaf1I6D5EG5zpqo7mX;h5%L;9_ zi7(m`;nbMSqAqd^fu+;58mL z-lwgs{L`t4Y_vIf_Xp1fyJ4bc(v@sK2G9G`n#M}$&(!9KdPkKszi8C%QeTyS;S+bE zI#fyDT=$6ht8}i?_N!-Pre13Sn_-KuzI+dPEq#j4I#Qf@mb!X?&-eFa`;(0_u;B>@ zw?}p52DJ}^|BEEa!)ftOf&=Br^!&qs-U*DI22ji~s!s%1v;HvW`FiYmEUD&N#ybx2 z5cw5hX<46VvRF-S0Up%=0zh( z>ATGYGGSV;pX@SEWD1b?xqAPLMj{jTfWurXWk}S5+{@!*F@y>g$QjXR6HmL6|Bhl?{<@jQf1O zNUml2I?vI$B1$N!$-$!XtNpRm z&d7nks(v$7u;(+u>}aZ{=|7{n*`}j4CY*7(bYw+4D?xQMauQA37vowN!6Q3n-}F3D zwOXHDf4#3|37Xv>I}GTL$z8@*Ir5zcL?($ZM@#y=>Nvs_m)_t?ZY#u&1wIS56){v+ zDR^e1zg5vf{lnz9pet*MiDTSx?5T^bB5rOsC*>xaQewcqj(k_V6rubEr~B7t>e%#I zSpm+;6;fqA@+i0K6YHq2MMVG*4jd6->^8<6C>(sH1nAP(ci8Ny+iIFQelbk>Y0x(~ zfz-?BObQuR*W%T!d#}^`IY$(<`>R%co3LO11(9{bu10&s?S_EMKj~S=gs3XNTW0aB z>ujZ{Dq=hoJsbpiCMqrM_o7I+*y2Uo&YLxD|8?V1t^GB-#&L{~=+`QqBPn=QwDqD# zc0SW{hQyPtzo*jDqp3_hNjxJX_Q!4dv7(d`EY0p68GVtFP`#rnra2cAR1-RA01c zYusUyIwm23lftqFyGE&jx?Zb)0aj+^FQx;tN`-8d4``*nRtK`-`lNLmsW^n}!PA$? z0hAnyWJvU_BiEn-LSoFs2KTTu8IBg}IQ1vxd?$Nl94fS&-O+${bxb=5r z3xVoxVOM7vauM7#^v^leU`Rt~CAC&DLaJ(ub7ZI9c#=z6g4ej{r*p%y7kB#B@cltdA;iJg*v4_js(^*{##VIn0>`~`~av&Nj%On!5 zm!~^{;}{-0&sRrvd8qA=I2O@5oD%FqB8MHuk)VoBBxO5 z!&RGbOBk-xNW4gmtqo3Bn3mfG%V4ej*2Z1IM4iKX*w8L>b4cInBhm+BpBUxF%%$)r z0K(n!pdR2D(AM=AJXPT@kIn6_`wUBEdvcO!PaxNKY1=6~^$bU@0wkq@ACqA|vEhP> zqN@kHT#vTvt;le5 zmmQzO+3=MS+;xSvZ6kh_Np#6?u9}sV$QLCq>uYKq+S2l|lG z2x&IFaG&9^&s%9d;#AuG@2j#I3>e26@ClH~J^>OEfrQ zo+ubGtR&*faG%;C0Hv+QV{U!Lk3dZiU+US$Up!1T;)G>K^9OZbmUrsDi=jm z9XB%Gf@k9!fkZ^DF#^B#~N}#}{FOF^=9C z#hooCBf)%UnzR_ zl1PUf){GK7laJbLm>xr!Oyv7P*ekcA!KK=j-MkHHIrcXeoIqx^c4S}0Bitls)3?DZ zLT#g2jQ6nM-TG0re-?Q*GEX~Q*vn!@f=Ss<#@8>!*pq@w_ev(EzwX0?_V zm>8)1@Yd8n#ixwRp>UrfBe9$jm9?Vu~nJ0O>W1C@VS6SR^BOCKO3DQIQ@ua`HUKEQsnQ;2HaNYN`rlRnv z1W2t2I;)_l*+&LI0nwfmd@7L^wsKV)%FIW}EQ)GdRE=;E%S%utUAT=zj^v=y z(979LlUopbk_~SvQO%%|2Lx-@v>L3(I`VYfkG0n0#MuX%Z{g%||4emP6p7ozURtcP z%!rKiN^!~s5ET(Nk2FL8E_@;$GIo3cju~mY zXC<#^$x-{ST_?tXicgFgkBZCN`x4^9_;md_hIteev;)Y&ujfA&B(rhK!+IWZ6b*K3 zuCmVhskCD#ndS6U0kL!vq$nEo(LFhS%jl8F>kS~^D+~SP-YCO@J#0eYT>{4>C)|cF z+7Y3-=Q3D^hjytslz>J$|7pmz2^h6#Nhv%90qgA7Y{rcl>FRZrymNi3q^ohANYVm+ z0K=?5{6BfJlwWhRj>sn#ZlzJ~8xR3c`{bTPaZxiyUI(F4$=dNn{A>{@1jpJ5mw{NpJNHwcb8OJn z3n*Z-{}fmqPS8dju|+kA-Ey>w{SH93n~R%68YQ(K7AV`}`Ww#t6XpZ&9=K?}_h|)vi6FLPXpjh9?;q~lLN4D2Q+i)?p+B>Y( zunQ#Dvhm#*2asz4MCz4$Y_i7^syY%Qp-!T}BP6?)W%tgc?m=%mA5;Nt)73El zudqG>nfbUw0J;i}MTk_aW&e!FpD2yGeaW?Ca<4Gt6;aXD@EMXV;3)Q&p$()J6x3uZ zyDsp5$rNJo*yOb5LCnN_#oOmX46eJ9VI{gJu#sr=0uOhpKr5lIVnb~nYWcbNL0*4|FJWS&p+qSMux}CT&`v#!XTSrkZXMZVwu5SG=yg1X3)dOx()O~p z>H47e?W<;hvQ%7^o)C$+Xusxwhu=N0ycR$fHdyufv!S7(h7jk6litF!o{s^DwxVLP zwVL@!lMxm$n?~DOu7E0)#3?rRZUMM-`^l2Qyz$|og;P$P2lXDwRI!YK!}73Py#Q2Q z6sd@S48GGF_3tBP)|5mN{5Hk1m`A~4>uc#BHC@Md`<>I1CZAaPSSNZ$ek#H!jASvL zYgk7_6fD3cHEYKv19o-2&pnU|7VR-U@zj1;vi1(MSW%upfj?USRQAt)OkjPqQlY5B zKzKh}~NGR!*?8E&&SFSZPAc^B$*HGxl23QGXV(d20Yt zz-OLA)c|X8{7wg0jH|7TQX8m2Q%|iV$FQO#7NV*@nsiLt?&^7Jbzkz|Clo>yIHjJ3LNV0HCotF(RRe2BLQxb-@cn zNmwGK>Ig+wht7N>u+a<|_tsX1@kC^(aB0^yNxXVNP}3yQT*ZdX8jAuRO9?cG*)9(s zJu7kaU#=Nh66jFg_6{2$nArUwV~Rh(Gk7Kr%mnY#b}FCvIy4`e>hT3~dz z_vZb7L6%hO#jI3H-F@|I3?+h$OVsrL-iWtnDTW(dJEodK9*byF?3s9BLwRV0OKfoK zpIWHPZH40MjjCa8kqy}K zfb3S3V~Lg~ z8=re0502YGsZ$xEIe`( z?7Y`ct?IALUIDsr+b(H+?z`zoc{7lv$Q z>MgHZE^937JZl)udtJTf^8yP|MFGH!3teb-^`>t>FXon@_nqkSpU~An9;QFu`>Z1? z*H)e#pIXYBzNHLN`^!t8mJj6nf61YK@475q+bobx%f|-T^P$1|vo~SZAPO!GuME3T zzENxVQva16{J3^f?{waAoEky;HwvyygotGg82|0vm#Sd%*ynzA6 zV0}8oA`>6aRXHgwgCeUCpkPqu+H=ifzMsg`GUWcOctG^1d0U27>EJI~mo z+do?Mg_6ndsLUoC2;5qUKbUK>n?`&2P+F?2on(7v4&D9~3lFPGwi-o3J85odzLM5z z8Mk0-k{X`s#-?J5Dm`%|i8xrB_m@F5)vDin@i4BpB)pSBHz$AN7S&2~o!BDy@c>uB z4wKomuatRSEMpbhNu^j-aidqU)au(tL1D;m!2LJ#7 literal 0 HcmV?d00001 diff --git a/RobotApp.Client/wwwroot/js/robotMonitor.js b/RobotApp.Client/wwwroot/js/robotMonitor.js index bb850b5..78bceb0 100644 --- a/RobotApp.Client/wwwroot/js/robotMonitor.js +++ b/RobotApp.Client/wwwroot/js/robotMonitor.js @@ -55,6 +55,23 @@ window.robotMonitor = { } return `M ${startX} ${startY} L ${endX} ${endY}`; + }, + + // Load image and get dimensions + loadImageAndGetDimensions: function (imageUrl) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => { + resolve({ + Width: img.naturalWidth || img.width, + Height: img.naturalHeight || img.height + }); + }; + img.onerror = () => { + reject(new Error(`Failed to load image: ${imageUrl}`)); + }; + img.src = imageUrl; + }); } }; @@ -65,3 +82,4 @@ window.robotMonitor = { + diff --git a/RobotApp/Components/App.razor b/RobotApp/Components/App.razor index 05a1e6a..68a6103 100644 --- a/RobotApp/Components/App.razor +++ b/RobotApp/Components/App.razor @@ -42,6 +42,7 @@ + diff --git a/RobotApp/Hubs/RobotMonitorHub.cs b/RobotApp/Hubs/RobotMonitorHub.cs index edb6900..6faa577 100644 --- a/RobotApp/Hubs/RobotMonitorHub.cs +++ b/RobotApp/Hubs/RobotMonitorHub.cs @@ -17,3 +17,4 @@ public class RobotMonitorHub : Hub +