update
This commit is contained in:
parent
15a61fd986
commit
8362713dcc
|
|
@ -17,6 +17,13 @@
|
||||||
<i class="mdi mdi-fit-to-screen-outline icon-button"></i>
|
<i class="mdi mdi-fit-to-screen-outline icon-button"></i>
|
||||||
</button>
|
</button>
|
||||||
</MudTooltip>
|
</MudTooltip>
|
||||||
|
<div class="auto-follow-control">
|
||||||
|
<MudTooltip Text="Auto Follow Robot" Placement="Placement.Bottom" Color="Color.Info">
|
||||||
|
<MudSwitch T="bool" Value="AutoFollowRobot" ValueChanged="OnAutoFollowRobotChanged" Color="Color.Info" Size="Size.Small">
|
||||||
|
<span style="color: white; font-size: 14px; margin-left: 8px;">Follow Robot</span>
|
||||||
|
</MudSwitch>
|
||||||
|
</MudTooltip>
|
||||||
|
</div>
|
||||||
<MudSpacer />
|
<MudSpacer />
|
||||||
@if (MonitorData?.RobotPosition != null)
|
@if (MonitorData?.RobotPosition != null)
|
||||||
{
|
{
|
||||||
|
|
@ -58,6 +65,9 @@
|
||||||
@* Background Map Image *@
|
@* Background Map Image *@
|
||||||
@if (MapImageLoaded && MapImageWidth > 0 && MapImageHeight > 0)
|
@if (MapImageLoaded && MapImageWidth > 0 && MapImageHeight > 0)
|
||||||
{
|
{
|
||||||
|
@* Image origin is at bottom-left corner (MapImageOriginX, MapImageOriginY) in world coordinates
|
||||||
|
In SVG (after Y flip), image top-left corner is at (MapImageOriginX, -MapImageOriginY - MapImageHeight)
|
||||||
|
So we render image at: x = MapImageOriginX, y = -MapImageOriginY - MapImageHeight *@
|
||||||
<image href="@MapImageUrl"
|
<image href="@MapImageUrl"
|
||||||
x="@WorldToSvgX(MapImageOriginX)"
|
x="@WorldToSvgX(MapImageOriginX)"
|
||||||
y="@WorldToSvgY(MapImageOriginY + MapImageHeight)"
|
y="@WorldToSvgY(MapImageOriginY + MapImageHeight)"
|
||||||
|
|
@ -65,11 +75,11 @@
|
||||||
height="@MapImageHeight"
|
height="@MapImageHeight"
|
||||||
preserveAspectRatio="none"
|
preserveAspectRatio="none"
|
||||||
opacity="0.8"
|
opacity="0.8"
|
||||||
style="pointer-events: none;"
|
style="pointer-events: none; image-rendering: pixelated;"
|
||||||
id="map-background-image" />
|
id="map-background-image" />
|
||||||
}
|
}
|
||||||
|
|
||||||
@* Origin Marker (2 arrows: X+ and Y+) *@
|
@* Origin Marker (2 arrows: X+ and Y+) at (MapImageOriginX, MapImageOriginY) *@
|
||||||
<g transform="@GetOriginMarkerTransform()">
|
<g transform="@GetOriginMarkerTransform()">
|
||||||
@* X+ Arrow (pointing right) *@
|
@* X+ Arrow (pointing right) *@
|
||||||
<line x1="0" y1="0" x2="@GetOriginMarkerSize()" y2="0"
|
<line x1="0" y1="0" x2="@GetOriginMarkerSize()" y2="0"
|
||||||
|
|
@ -80,7 +90,7 @@
|
||||||
stroke="#00FF00" stroke-width="@GetOriginMarkerStrokeWidth()"
|
stroke="#00FF00" stroke-width="@GetOriginMarkerStrokeWidth()"
|
||||||
marker-end="url(#arrowhead-y)" />
|
marker-end="url(#arrowhead-y)" />
|
||||||
@* Origin point *@
|
@* Origin point *@
|
||||||
<circle cx="0" cy="0" r="@(GetOriginMarkerSize() * 0.3)"
|
<circle cx="0" cy="0" r="@(GetOriginMarkerSize() * 0.12)"
|
||||||
fill="#FFFF00" stroke="#000" stroke-width="@(GetOriginMarkerStrokeWidth() * 0.5)" />
|
fill="#FFFF00" stroke="#000" stroke-width="@(GetOriginMarkerStrokeWidth() * 0.5)" />
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
|
|
@ -144,7 +154,7 @@
|
||||||
private ElementReference SvgRef;
|
private ElementReference SvgRef;
|
||||||
private ElementReference SvgContainerRef;
|
private ElementReference SvgContainerRef;
|
||||||
|
|
||||||
private double ZoomScale = 2.0; // Zoom vào robot hơn khi mở
|
private double ZoomScale = 1.5; // Zoom vào robot hơn khi mở
|
||||||
private const double MIN_ZOOM = 0.1;
|
private const double MIN_ZOOM = 0.1;
|
||||||
private const double MAX_ZOOM = 5.0;
|
private const double MAX_ZOOM = 5.0;
|
||||||
private const double BASE_PIXELS_PER_METER = 50.0;
|
private const double BASE_PIXELS_PER_METER = 50.0;
|
||||||
|
|
@ -166,6 +176,19 @@
|
||||||
private double? MouseWorldX = null;
|
private double? MouseWorldX = null;
|
||||||
private double? MouseWorldY = null;
|
private double? MouseWorldY = null;
|
||||||
|
|
||||||
|
// Auto-follow robot
|
||||||
|
private bool AutoFollowRobot = false;
|
||||||
|
|
||||||
|
private void OnAutoFollowRobotChanged(bool value)
|
||||||
|
{
|
||||||
|
AutoFollowRobot = value;
|
||||||
|
if (AutoFollowRobot && MonitorData?.RobotPosition != null)
|
||||||
|
{
|
||||||
|
UpdateViewToFollowRobot();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Map image properties
|
// Map image properties
|
||||||
private const double MapImageOriginX = -20.0; // OriginX in world coordinates (meters)
|
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 MapImageOriginY = -20.0; // OriginY in world coordinates (meters)
|
||||||
|
|
@ -215,8 +238,6 @@
|
||||||
MapImageWidth = imageDimensions.Width * MapImageResolution;
|
MapImageWidth = imageDimensions.Width * MapImageResolution;
|
||||||
MapImageHeight = imageDimensions.Height * 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)
|
if (MapImageWidth > 0 && MapImageHeight > 0)
|
||||||
{
|
{
|
||||||
MapImageLoaded = true;
|
MapImageLoaded = true;
|
||||||
|
|
@ -232,6 +253,12 @@
|
||||||
|
|
||||||
private string GetTransform()
|
private string GetTransform()
|
||||||
{
|
{
|
||||||
|
// Transform applies: first translate (in screen pixels), then scale (pixels per meter)
|
||||||
|
// World coordinates are in meters
|
||||||
|
// After transform: screenX = TranslateX + worldX * (ZoomScale * BASE_PIXELS_PER_METER)
|
||||||
|
// screenY = TranslateY + worldY * (ZoomScale * BASE_PIXELS_PER_METER)
|
||||||
|
// But we need to flip Y: screenY = TranslateY - worldY * (ZoomScale * BASE_PIXELS_PER_METER)
|
||||||
|
// This is handled by WorldToSvgY which flips Y before applying transform
|
||||||
return $"translate({TranslateX}, {TranslateY}) scale({ZoomScale * BASE_PIXELS_PER_METER})";
|
return $"translate({TranslateX}, {TranslateY}) scale({ZoomScale * BASE_PIXELS_PER_METER})";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,7 +281,7 @@
|
||||||
|
|
||||||
// Điều chỉnh kích thước dựa trên ZoomScale
|
// Điều chỉnh kích thước dựa trên ZoomScale
|
||||||
// Tăng kích thước lên 1.5x để robot to hơn
|
// Tăng kích thước lên 1.5x để robot to hơn
|
||||||
double scaleFactor = 3 / ZoomScale; // Tăng kích thước hiển thị
|
double scaleFactor = 2 / ZoomScale; // Tăng kích thước hiển thị
|
||||||
|
|
||||||
double width = RobotWidthMeters * scaleFactor;
|
double width = RobotWidthMeters * scaleFactor;
|
||||||
double height = RobotLengthMeters * scaleFactor;
|
double height = RobotLengthMeters * scaleFactor;
|
||||||
|
|
@ -299,17 +326,20 @@
|
||||||
|
|
||||||
private string GetOriginMarkerTransform()
|
private string GetOriginMarkerTransform()
|
||||||
{
|
{
|
||||||
// Origin is at (MapImageOriginX, MapImageOriginY) in world coordinates
|
// Origin is at (MapImageOriginX, MapImageOriginY) in world coordinates (bottom-left corner of image)
|
||||||
// In SVG: (MapImageOriginX, -MapImageOriginY)
|
// In SVG coordinates (after Y flip): (MapImageOriginX, -MapImageOriginY)
|
||||||
var x = WorldToSvgX(MapImageOriginX);
|
// Note: Image is rendered at (MapImageOriginX, -MapImageOriginY - MapImageHeight) in SVG
|
||||||
var y = WorldToSvgY(MapImageOriginY);
|
// So origin marker should be at (MapImageOriginX, -MapImageOriginY) in SVG
|
||||||
|
var x = WorldToSvgX(0);
|
||||||
|
var y = WorldToSvgY(0);
|
||||||
|
|
||||||
return $"translate({x}, {y})";
|
return $"translate({x}, {y})";
|
||||||
}
|
}
|
||||||
|
|
||||||
private double GetOriginMarkerSize()
|
private double GetOriginMarkerSize()
|
||||||
{
|
{
|
||||||
// Marker size in world coordinates (meters)
|
// Marker size in world coordinates (meters)
|
||||||
const double BaseMarkerSize = 0.5; // 1 meter
|
const double BaseMarkerSize = 1; // 1 meter
|
||||||
double scaleFactor = 1.0 / ZoomScale; // Keep visual size constant
|
double scaleFactor = 1.0 / ZoomScale; // Keep visual size constant
|
||||||
return BaseMarkerSize * scaleFactor;
|
return BaseMarkerSize * scaleFactor;
|
||||||
}
|
}
|
||||||
|
|
@ -322,6 +352,25 @@
|
||||||
return BaseStrokeWidth * scaleFactor;
|
return BaseStrokeWidth * scaleFactor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnMonitorDataUpdated()
|
||||||
|
{
|
||||||
|
// Auto-follow robot when MonitorData changes
|
||||||
|
if (AutoFollowRobot && !IsPanning && MonitorData?.RobotPosition != null)
|
||||||
|
{
|
||||||
|
UpdateViewToFollowRobot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateViewToFollowRobot()
|
||||||
|
{
|
||||||
|
if (MonitorData?.RobotPosition == null) return;
|
||||||
|
|
||||||
|
// Center view on robot
|
||||||
|
TranslateX = SvgWidth / 2 - MonitorData.RobotPosition.X * BASE_PIXELS_PER_METER * ZoomScale;
|
||||||
|
TranslateY = SvgHeight / 2 + MonitorData.RobotPosition.Y * BASE_PIXELS_PER_METER * ZoomScale;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdatePath()
|
public void UpdatePath()
|
||||||
{
|
{
|
||||||
if (MonitorData is not null && MonitorData.EdgeStates.Length > 0)
|
if (MonitorData is not null && MonitorData.EdgeStates.Length > 0)
|
||||||
|
|
|
||||||
|
|
@ -66,18 +66,26 @@
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.auto-follow-control {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4px 8px;
|
||||||
|
background-color: #3d3d3d;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.svg-container {
|
.svg-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #fafafa;
|
background-color: #808080;
|
||||||
}
|
}
|
||||||
|
|
||||||
.svg-container svg {
|
.svg-container svg {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
background-color: dimgray;
|
background-color: #808080;
|
||||||
}
|
}
|
||||||
|
|
||||||
.svg-container svg:active {
|
.svg-container svg:active {
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
{
|
{
|
||||||
_monitorData = data;
|
_monitorData = data;
|
||||||
RobotMonitorViewRef?.UpdatePath();
|
RobotMonitorViewRef?.UpdatePath();
|
||||||
|
RobotMonitorViewRef?.OnMonitorDataUpdated();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user