update
This commit is contained in:
parent
b3f765d261
commit
15a61fd986
|
|
@ -22,7 +22,14 @@
|
|||
{
|
||||
<div class="robot-position-info">
|
||||
<i class="mdi mdi-map-marker"></i>
|
||||
<span>X: @MonitorData.RobotPosition.X.ToString("F2")m | Y: @MonitorData.RobotPosition.Y.ToString("F2")m | θ: @((MonitorData.RobotPosition.Theta * 180 / Math.PI).ToString("F1"))°</span>
|
||||
<span>Robot: X: @MonitorData.RobotPosition.X.ToString("F2")m | Y: @MonitorData.RobotPosition.Y.ToString("F2")m | θ: @((MonitorData.RobotPosition.Theta * 180 / Math.PI).ToString("F1"))°</span>
|
||||
</div>
|
||||
}
|
||||
@if (MouseWorldX.HasValue && MouseWorldY.HasValue)
|
||||
{
|
||||
<div class="mouse-position-info">
|
||||
<i class="mdi mdi-cursor-pointer"></i>
|
||||
<span>Mouse: X: @MouseWorldX.Value.ToString("F2")m | Y: @MouseWorldY.Value.ToString("F2")m</span>
|
||||
</div>
|
||||
}
|
||||
@* <MudChip T="string" Color="@(IsConnected? Color.Success: Color.Error)" Size="Size.Small">
|
||||
|
|
@ -36,18 +43,49 @@
|
|||
@onmousemove="HandleMouseMove"
|
||||
@onmouseup="HandleMouseUp"
|
||||
@onmouseleave="HandleMouseLeave">
|
||||
@* Arrow markers for origin *@
|
||||
<defs>
|
||||
<marker id="arrowhead-x" markerWidth="10" markerHeight="10"
|
||||
refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,6 L9,3 z" fill="#FF0000" />
|
||||
</marker>
|
||||
<marker id="arrowhead-y" markerWidth="10" markerHeight="10"
|
||||
refX="9" refY="3" orient="auto" markerUnits="strokeWidth">
|
||||
<path d="M0,0 L0,6 L9,3 z" fill="#00FF00" />
|
||||
</marker>
|
||||
</defs>
|
||||
<g transform="@GetTransform()">
|
||||
@* Background Map Image *@
|
||||
@if (MapImageLoaded && MapImageWidth > 0 && MapImageHeight > 0)
|
||||
{
|
||||
<image href="@MapImageUrl"
|
||||
x="@WorldToSvgX(MapImageOriginX)"
|
||||
y="@WorldToSvgY(MapImageOriginY + MapImageHeight)"
|
||||
width="@MapImageWidth"
|
||||
height="@MapImageHeight"
|
||||
preserveAspectRatio="none"
|
||||
opacity="0.8"
|
||||
style="pointer-events: none;"
|
||||
id="map-background-image" />
|
||||
}
|
||||
|
||||
@* Origin Marker (2 arrows: X+ and Y+) *@
|
||||
<g transform="@GetOriginMarkerTransform()">
|
||||
@* X+ Arrow (pointing right) *@
|
||||
<line x1="0" y1="0" x2="@GetOriginMarkerSize()" y2="0"
|
||||
stroke="#FF0000" stroke-width="@GetOriginMarkerStrokeWidth()"
|
||||
marker-end="url(#arrowhead-x)" />
|
||||
@* Y+ Arrow (pointing up in world, down in SVG) *@
|
||||
<line x1="0" y1="0" x2="0" y2="@(-GetOriginMarkerSize())"
|
||||
stroke="#00FF00" stroke-width="@GetOriginMarkerStrokeWidth()"
|
||||
marker-end="url(#arrowhead-y)" />
|
||||
@* Origin point *@
|
||||
<circle cx="0" cy="0" r="@(GetOriginMarkerSize() * 0.3)"
|
||||
fill="#FFFF00" stroke="#000" stroke-width="@(GetOriginMarkerStrokeWidth() * 0.5)" />
|
||||
</g>
|
||||
|
||||
@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);
|
||||
<path d="@GetPathFromTrajectory(edge.Trajectory, startX, startY, endX, endY)"
|
||||
fill="none"
|
||||
stroke="#42A5F5"
|
||||
stroke-width="0.08" />
|
||||
} *@
|
||||
<path d="@PathView"
|
||||
fill="none"
|
||||
stroke="#42A5F5"
|
||||
|
|
@ -124,14 +162,31 @@
|
|||
private string PathView = "";
|
||||
private string PathIsNot = "hidden";
|
||||
|
||||
// Mouse world coordinates
|
||||
private double? MouseWorldX = null;
|
||||
private double? MouseWorldY = null;
|
||||
|
||||
// Map image properties
|
||||
private const double MapImageOriginX = -20.0; // OriginX in world coordinates (meters)
|
||||
private const double MapImageOriginY = -20.0; // OriginY in world coordinates (meters)
|
||||
private const double MapImageResolution = 0.1; // Resolution: meters per pixel
|
||||
private const string MapImageUrl = "images/gara20250309.png";
|
||||
|
||||
private bool MapImageLoaded = false;
|
||||
private double MapImageWidth = 0; // Width in world coordinates (meters)
|
||||
private double MapImageHeight = 0; // Height in world coordinates (meters)
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
var containerSize = await JS.InvokeAsync<ElementSize>("getElementSize", SvgContainerRef);
|
||||
var containerSize = await JS.InvokeAsync<ElementSize>("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<ElementSize>("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<ElementBoundingRect>("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<ElementBoundingRect>("getElementBoundingRect", SvgRef);
|
||||
var svgRect = await JS.InvokeAsync<ElementBoundingRect>("robotMonitor.getElementBoundingRect", SvgRef);
|
||||
double mouseX = e.ClientX - svgRect.X;
|
||||
double mouseY = e.ClientY - svgRect.Y;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@
|
|||
<div class="d-flex gap-2">
|
||||
|
||||
<!-- IMPORT -->
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
@* <MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Secondary"
|
||||
Size="Size.Small"
|
||||
StartIcon="@Icons.Material.Filled.UploadFile"
|
||||
OnClick="OnImport">
|
||||
Import JSON
|
||||
</MudButton>
|
||||
|
||||
*@
|
||||
<!-- CANCEL -->
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="@CancelButtonColor"
|
||||
|
|
|
|||
|
|
@ -47,3 +47,4 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
BIN
RobotApp.Client/wwwroot/images/gara20250309.png
Normal file
BIN
RobotApp.Client/wwwroot/images/gara20250309.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
|
|
@ -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 = {
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
<script src="@Assets["_content/MudBlazor/MudBlazor.min.js"]"></script>
|
||||
<script src="@Assets["js/canvas.js"]"></script>
|
||||
<script src="@Assets["js/app.js"]"></script>
|
||||
<script src="@Assets["js/robotMonitor.js"]"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -17,3 +17,4 @@ public class RobotMonitorHub : Hub
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user