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 - + *@ { + 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 +