save
This commit is contained in:
parent
f6f8a3bf65
commit
7e0c6af9d5
|
|
@ -1,4 +1,5 @@
|
||||||
@page "/"
|
@page "/"
|
||||||
|
@attribute [Authorize]
|
||||||
@using RobotApp.Client.Services
|
@using RobotApp.Client.Services
|
||||||
@using RobotApp.VDA5050.State
|
@using RobotApp.VDA5050.State
|
||||||
@using MudBlazor
|
@using MudBlazor
|
||||||
|
|
|
||||||
|
|
@ -117,14 +117,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
<!-- Apply Curve -->
|
<!-- Apply Curve -->
|
||||||
@if (edge.Radius > 0 && !edge.Expanded)
|
@if (!edge.HasTrajectory && edge.Radius > 0 && !edge.Expanded)
|
||||||
{
|
{
|
||||||
<MudItem xs="12">
|
<MudItem xs="12">
|
||||||
<MudButton Color="Color.Primary"
|
<MudButton Color="Color.Primary"
|
||||||
Variant="Variant.Outlined"
|
Variant="Variant.Outlined"
|
||||||
StartIcon="@Icons.Material.Filled.Merge"
|
StartIcon="@Icons.Material.Filled.Merge"
|
||||||
OnClick="@(() => ApplyCurveAsync(edge))">
|
OnClick="@(() => ApplyCurveAsync(edge))">
|
||||||
Apply Curve
|
Apply Curve (generate node)
|
||||||
</MudButton>
|
</MudButton>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
104
RobotApp.Client/Pages/Order/ImportOrderDialog.razor
Normal file
104
RobotApp.Client/Pages/Order/ImportOrderDialog.razor
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
@using System.Text.Json
|
||||||
|
@using MudBlazor
|
||||||
|
|
||||||
|
<MudDialog>
|
||||||
|
<TitleContent>
|
||||||
|
<MudText Typo="Typo.h6">Import Order JSON</MudText>
|
||||||
|
</TitleContent>
|
||||||
|
|
||||||
|
<DialogContent>
|
||||||
|
|
||||||
|
@if (ShowWarning)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Warning"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Class="mb-4">
|
||||||
|
Only valid <b>VDA 5050 Order JSON</b> is accepted.<br />
|
||||||
|
Invalid structure will be rejected.
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
|
||||||
|
<MudTextField @bind-Value="JsonText"
|
||||||
|
Label="Paste Order JSON"
|
||||||
|
Lines="20"
|
||||||
|
Variant="Variant.Filled"
|
||||||
|
Immediate
|
||||||
|
Style="font-family: monospace" />
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(ErrorMessage))
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Error" Class="mt-3">
|
||||||
|
@ErrorMessage
|
||||||
|
</MudAlert>
|
||||||
|
}
|
||||||
|
|
||||||
|
</DialogContent>
|
||||||
|
|
||||||
|
<DialogActions>
|
||||||
|
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||||
|
<MudButton Variant="Variant.Filled"
|
||||||
|
Color="Color.Primary"
|
||||||
|
OnClick="ValidateAndImport">
|
||||||
|
Import
|
||||||
|
</MudButton>
|
||||||
|
</DialogActions>
|
||||||
|
</MudDialog>
|
||||||
|
@code {
|
||||||
|
[CascadingParameter] public IMudDialogInstance MudDialog { get; set; } = default!;
|
||||||
|
|
||||||
|
public string JsonText { get; set; } = "";
|
||||||
|
public string? ErrorMessage;
|
||||||
|
public bool ShowWarning { get; set; } = false;
|
||||||
|
|
||||||
|
private void Cancel() => MudDialog.Cancel();
|
||||||
|
|
||||||
|
private void ValidateAndImport()
|
||||||
|
{
|
||||||
|
ErrorMessage = null;
|
||||||
|
ShowWarning = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var doc = JsonDocument.Parse(JsonText);
|
||||||
|
var root = doc.RootElement;
|
||||||
|
|
||||||
|
// ===== BASIC STRUCTURE CHECK =====
|
||||||
|
if (!root.TryGetProperty("nodes", out _) ||
|
||||||
|
!root.TryGetProperty("edges", out _))
|
||||||
|
throw new Exception("Missing 'nodes' or 'edges' field.");
|
||||||
|
|
||||||
|
var order = OrderMessage.FromSchemaObject(root);
|
||||||
|
|
||||||
|
ValidateOrder(order);
|
||||||
|
|
||||||
|
MudDialog.Close(DialogResult.Ok(order));
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
ShowWarning = true;
|
||||||
|
ErrorMessage = "Invalid JSON format.";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ShowWarning = true;
|
||||||
|
ErrorMessage = ex.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidateOrder(OrderMessage order)
|
||||||
|
{
|
||||||
|
if (order.Nodes.Count == 0)
|
||||||
|
throw new Exception("Order must contain at least one node.");
|
||||||
|
|
||||||
|
var nodeIds = order.Nodes.Select(n => n.NodeId).ToHashSet();
|
||||||
|
|
||||||
|
foreach (var e in order.Edges)
|
||||||
|
{
|
||||||
|
if (!nodeIds.Contains(e.StartNodeId) ||
|
||||||
|
!nodeIds.Contains(e.EndNodeId))
|
||||||
|
throw new Exception(
|
||||||
|
$"Edge '{e.EdgeId}' references unknown node."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,7 +5,16 @@
|
||||||
|
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
|
|
||||||
<!-- ================= SEND BUTTON ================= -->
|
<!-- IMPORT -->
|
||||||
|
<MudButton Variant="Variant.Outlined"
|
||||||
|
Color="Color.Secondary"
|
||||||
|
Size="Size.Small"
|
||||||
|
StartIcon="@Icons.Material.Filled.UploadFile"
|
||||||
|
OnClick="OnImport">
|
||||||
|
Import JSON
|
||||||
|
</MudButton>
|
||||||
|
|
||||||
|
<!-- SEND -->
|
||||||
<MudButton Variant="Variant.Filled"
|
<MudButton Variant="Variant.Filled"
|
||||||
Color="@SendButtonColor"
|
Color="@SendButtonColor"
|
||||||
StartIcon="@SendButtonIcon"
|
StartIcon="@SendButtonIcon"
|
||||||
|
|
@ -14,7 +23,7 @@
|
||||||
@SendButtonText
|
@SendButtonText
|
||||||
</MudButton>
|
</MudButton>
|
||||||
|
|
||||||
<!-- ================= COPY ================= -->
|
<!-- COPY -->
|
||||||
<MudTooltip Text="@(Copied ? "Copied!" : "Copy to clipboard")">
|
<MudTooltip Text="@(Copied ? "Copied!" : "Copy to clipboard")">
|
||||||
<MudButton Variant="Variant.Filled"
|
<MudButton Variant="Variant.Filled"
|
||||||
Color="@(Copied ? Color.Success : Color.Primary)"
|
Color="@(Copied ? Color.Success : Color.Primary)"
|
||||||
|
|
@ -27,15 +36,14 @@
|
||||||
</div>
|
</div>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
<!-- ================= JSON ================= -->
|
|
||||||
<div class="flex-grow-1" style="overflow:auto;">
|
<div class="flex-grow-1" style="overflow:auto;">
|
||||||
<MudTextField Value="@OrderJson"
|
<MudTextField Value="@OrderJson"
|
||||||
ReadOnly="true"
|
ReadOnly
|
||||||
Variant="Variant.Filled"
|
Variant="Variant.Filled"
|
||||||
Lines="70"
|
Lines="70"
|
||||||
Class="h-100"
|
Class="h-100"
|
||||||
Style="font-family: 'Roboto Mono', Consolas, 'Courier New', monospace;
|
Style="font-family: 'Roboto Mono', Consolas, monospace;
|
||||||
font-size: 0.875rem;
|
font-size: 0.85rem;
|
||||||
background:#1e1e1e;
|
background:#1e1e1e;
|
||||||
color:#d4d4d4;" />
|
color:#d4d4d4;" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -45,10 +53,11 @@
|
||||||
[Parameter] public string OrderJson { get; set; } = "";
|
[Parameter] public string OrderJson { get; set; } = "";
|
||||||
[Parameter] public bool Copied { get; set; }
|
[Parameter] public bool Copied { get; set; }
|
||||||
[Parameter] public bool? SendSuccess { get; set; }
|
[Parameter] public bool? SendSuccess { get; set; }
|
||||||
|
|
||||||
[Parameter] public EventCallback OnCopy { get; set; }
|
[Parameter] public EventCallback OnCopy { get; set; }
|
||||||
[Parameter] public EventCallback OnSend { get; set; }
|
[Parameter] public EventCallback OnSend { get; set; }
|
||||||
|
[Parameter] public EventCallback OnImport { get; set; }
|
||||||
|
|
||||||
// ================= SEND BUTTON STATE =================
|
|
||||||
private string SendButtonText =>
|
private string SendButtonText =>
|
||||||
SendSuccess switch
|
SendSuccess switch
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,8 @@
|
||||||
Copied="@copied"
|
Copied="@copied"
|
||||||
SendSuccess="@sendSuccess"
|
SendSuccess="@sendSuccess"
|
||||||
OnCopy="CopyJsonToClipboard"
|
OnCopy="CopyJsonToClipboard"
|
||||||
OnSend="SendOrderToServer" />
|
OnSend="SendOrderToServer"
|
||||||
|
OnImport="OpenImportDialog" />
|
||||||
|
|
||||||
</MudItem>
|
</MudItem>
|
||||||
|
|
||||||
|
|
@ -85,6 +86,25 @@
|
||||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
private async Task OpenImportDialog()
|
||||||
|
{
|
||||||
|
var dialog = await DialogService.ShowAsync<ImportOrderDialog>(
|
||||||
|
"Import Order JSON",
|
||||||
|
new DialogOptions
|
||||||
|
{
|
||||||
|
FullWidth = true,
|
||||||
|
MaxWidth = MaxWidth.Large
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = await dialog.Result;
|
||||||
|
|
||||||
|
if (!result.Canceled && result.Data is OrderMessage imported)
|
||||||
|
{
|
||||||
|
Order = imported;
|
||||||
|
RebuildOrderJson();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnOrderChanged()
|
private void OnOrderChanged()
|
||||||
{
|
{
|
||||||
|
|
@ -151,7 +171,7 @@
|
||||||
|
|
||||||
Order.Nodes.Add(newNode);
|
Order.Nodes.Add(newNode);
|
||||||
edge.EndNodeId = newNode.NodeId;
|
edge.EndNodeId = newNode.NodeId;
|
||||||
edge.Expanded = true;
|
edge.MarkExpanded(); // ✅
|
||||||
|
|
||||||
ResequenceNodes();
|
ResequenceNodes();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using RobotApp.VDA5050.InstantAction;
|
using RobotApp.VDA5050.InstantAction;
|
||||||
using RobotApp.VDA5050.Order;
|
using RobotApp.VDA5050.Order;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace RobotApp.Client.Services;
|
namespace RobotApp.Client.Services;
|
||||||
|
|
@ -7,13 +8,37 @@ namespace RobotApp.Client.Services;
|
||||||
// ======================================================
|
// ======================================================
|
||||||
// EDGE UI
|
// EDGE UI
|
||||||
// ======================================================
|
// ======================================================
|
||||||
public class UiEdge : VDA5050.Order.Edge
|
public class UiEdge
|
||||||
{
|
{
|
||||||
public double Radius { get; set; } = 0.0;
|
public string EdgeId { get; set; } = "";
|
||||||
public Quadrant Quadrant { get; set; } = Quadrant.I;
|
public int SequenceId { get; set; }
|
||||||
|
public bool Released { get; set; } = true;
|
||||||
|
|
||||||
// Đánh dấu đã expand chưa
|
public string StartNodeId { get; set; } = "";
|
||||||
public bool Expanded { get; set; } = false;
|
public string EndNodeId { get; set; } = "";
|
||||||
|
|
||||||
|
// ===== CURVE (EDITOR GENERATED) =====
|
||||||
|
public double Radius { get; set; } = 0;
|
||||||
|
public Quadrant Quadrant { get; set; }
|
||||||
|
|
||||||
|
// ===== IMPORTED TRAJECTORY =====
|
||||||
|
public bool HasTrajectory { get; set; } = false;
|
||||||
|
public UiTrajectory? Trajectory { get; set; }
|
||||||
|
|
||||||
|
// ===== UI STATE =====
|
||||||
|
public bool Expanded { get; private set; } = false;
|
||||||
|
|
||||||
|
public void MarkExpanded()
|
||||||
|
{
|
||||||
|
Expanded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class UiTrajectory
|
||||||
|
{
|
||||||
|
public int Degree { get; set; }
|
||||||
|
public double[] KnotVector { get; set; } = Array.Empty<double>();
|
||||||
|
public List<Point> ControlPoints { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Quadrant
|
public enum Quadrant
|
||||||
|
|
@ -138,7 +163,111 @@ public class OrderMessage
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
public static OrderMessage FromSchemaObject(JsonElement root)
|
||||||
|
{
|
||||||
|
var order = new OrderMessage
|
||||||
|
{
|
||||||
|
HeaderId = root.GetProperty("headerId").GetInt32(),
|
||||||
|
Timestamp = root.GetProperty("timestamp").GetString(),
|
||||||
|
Version = root.GetProperty("version").GetString(),
|
||||||
|
Manufacturer = root.GetProperty("manufacturer").GetString(),
|
||||||
|
SerialNumber = root.GetProperty("serialNumber").GetString(),
|
||||||
|
OrderId = root.GetProperty("orderId").GetString(),
|
||||||
|
OrderUpdateId = root.GetProperty("orderUpdateId").GetInt32()
|
||||||
|
};
|
||||||
|
|
||||||
|
// ================= NODES =================
|
||||||
|
foreach (var n in root.GetProperty("nodes").EnumerateArray())
|
||||||
|
{
|
||||||
|
var node = new Node
|
||||||
|
{
|
||||||
|
NodeId = n.GetProperty("nodeId").GetString()!,
|
||||||
|
SequenceId = n.GetProperty("sequenceId").GetInt32(),
|
||||||
|
Released = n.GetProperty("released").GetBoolean(),
|
||||||
|
|
||||||
|
NodePosition = new NodePosition
|
||||||
|
{
|
||||||
|
X = n.GetProperty("nodePosition").GetProperty("x").GetDouble(),
|
||||||
|
Y = n.GetProperty("nodePosition").GetProperty("y").GetDouble(),
|
||||||
|
Theta = n.GetProperty("nodePosition").GetProperty("theta").GetDouble(),
|
||||||
|
AllowedDeviationXY = n.GetProperty("nodePosition").GetProperty("allowedDeviationXY").GetDouble(),
|
||||||
|
AllowedDeviationTheta = n.GetProperty("nodePosition").GetProperty("allowedDeviationTheta").GetDouble(),
|
||||||
|
MapId = n.GetProperty("nodePosition").GetProperty("mapId").GetString()
|
||||||
|
},
|
||||||
|
|
||||||
|
Actions = ParseActions(n)
|
||||||
|
};
|
||||||
|
|
||||||
|
order.Nodes.Add(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var e in root.GetProperty("edges").EnumerateArray())
|
||||||
|
{
|
||||||
|
var edge = new UiEdge
|
||||||
|
{
|
||||||
|
EdgeId = e.GetProperty("edgeId").GetString()!,
|
||||||
|
SequenceId = e.GetProperty("sequenceId").GetInt32(),
|
||||||
|
Released = e.GetProperty("released").GetBoolean(),
|
||||||
|
StartNodeId = e.GetProperty("startNodeId").GetString()!,
|
||||||
|
EndNodeId = e.GetProperty("endNodeId").GetString()!,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===== IMPORT TRAJECTORY =====
|
||||||
|
if (e.TryGetProperty("trajectory", out var traj))
|
||||||
|
{
|
||||||
|
edge.HasTrajectory = true;
|
||||||
|
edge.Trajectory = new UiTrajectory
|
||||||
|
{
|
||||||
|
Degree = traj.GetProperty("degree").GetInt32(),
|
||||||
|
KnotVector = traj.GetProperty("knotVector")
|
||||||
|
.EnumerateArray()
|
||||||
|
.Select(x => x.GetDouble())
|
||||||
|
.ToArray(),
|
||||||
|
|
||||||
|
ControlPoints = traj.GetProperty("controlPoints")
|
||||||
|
.EnumerateArray()
|
||||||
|
.Select(p => new Point(
|
||||||
|
p.GetProperty("x").GetDouble(),
|
||||||
|
p.GetProperty("y").GetDouble()
|
||||||
|
))
|
||||||
|
.ToList()
|
||||||
|
};
|
||||||
|
|
||||||
|
// 🔥 IMPORTED CURVE → LOCK APPLY
|
||||||
|
edge.MarkExpanded();
|
||||||
|
}
|
||||||
|
|
||||||
|
order.Edges.Add(edge);
|
||||||
|
}
|
||||||
|
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================= ACTION PARSER =================
|
||||||
|
private static VDA5050.InstantAction.Action[] ParseActions(JsonElement parent)
|
||||||
|
{
|
||||||
|
if (!parent.TryGetProperty("actions", out var acts))
|
||||||
|
return Array.Empty<VDA5050.InstantAction.Action>();
|
||||||
|
|
||||||
|
return acts.EnumerateArray().Select(a =>
|
||||||
|
new VDA5050.InstantAction.Action
|
||||||
|
{
|
||||||
|
ActionId = a.GetProperty("actionId").GetString(),
|
||||||
|
ActionType = a.GetProperty("actionType").GetString(),
|
||||||
|
BlockingType = a.GetProperty("blockingType").GetString(),
|
||||||
|
|
||||||
|
ActionParameters = a.TryGetProperty("actionParameters", out var ps)
|
||||||
|
? ps.EnumerateArray()
|
||||||
|
.Select(p => new ActionParameter
|
||||||
|
{
|
||||||
|
Key = p.GetProperty("key").GetString(),
|
||||||
|
Value = p.GetProperty("value").GetString()
|
||||||
|
})
|
||||||
|
.ToArray()
|
||||||
|
: Array.Empty<ActionParameter>()
|
||||||
|
}
|
||||||
|
).ToArray();
|
||||||
|
}
|
||||||
public object ToSchemaObject()
|
public object ToSchemaObject()
|
||||||
{
|
{
|
||||||
int seq = 0;
|
int seq = 0;
|
||||||
|
|
@ -199,7 +328,36 @@ public class OrderMessage
|
||||||
// ================= EDGES =================
|
// ================= EDGES =================
|
||||||
edges = Edges.Select<UiEdge, object>(e =>
|
edges = Edges.Select<UiEdge, object>(e =>
|
||||||
{
|
{
|
||||||
// ---------- ĐƯỜNG THẲNG ----------
|
// =================================================
|
||||||
|
// 1️⃣ IMPORTED TRAJECTORY (ƯU TIÊN CAO NHẤT)
|
||||||
|
// =================================================
|
||||||
|
if (e.HasTrajectory && e.Trajectory != null)
|
||||||
|
{
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
edgeId = e.EdgeId,
|
||||||
|
sequenceId = seq++,
|
||||||
|
released = true,
|
||||||
|
|
||||||
|
startNodeId = e.StartNodeId,
|
||||||
|
endNodeId = e.EndNodeId,
|
||||||
|
|
||||||
|
trajectory = new
|
||||||
|
{
|
||||||
|
degree = e.Trajectory.Degree,
|
||||||
|
knotVector = e.Trajectory.KnotVector,
|
||||||
|
controlPoints = e.Trajectory.ControlPoints
|
||||||
|
.Select(p => new { x = p.X, y = p.Y })
|
||||||
|
.ToArray()
|
||||||
|
},
|
||||||
|
|
||||||
|
actions = Array.Empty<object>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// =================================================
|
||||||
|
// 2️⃣ STRAIGHT EDGE (KHÔNG CÓ CURVE)
|
||||||
|
// =================================================
|
||||||
if (e.Radius <= 0)
|
if (e.Radius <= 0)
|
||||||
{
|
{
|
||||||
return new
|
return new
|
||||||
|
|
@ -207,14 +365,19 @@ public class OrderMessage
|
||||||
edgeId = e.EdgeId,
|
edgeId = e.EdgeId,
|
||||||
sequenceId = seq++,
|
sequenceId = seq++,
|
||||||
released = true,
|
released = true,
|
||||||
|
|
||||||
startNodeId = e.StartNodeId,
|
startNodeId = e.StartNodeId,
|
||||||
endNodeId = e.EndNodeId,
|
endNodeId = e.EndNodeId,
|
||||||
|
|
||||||
actions = Array.Empty<object>()
|
actions = Array.Empty<object>()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- ĐƯỜNG CONG 1/4 ----------
|
// =================================================
|
||||||
|
// 3️⃣ EDITOR GENERATED CURVE (RADIUS + QUADRANT)
|
||||||
|
// =================================================
|
||||||
var startNode = Nodes.First(n => n.NodeId == e.StartNodeId);
|
var startNode = Nodes.First(n => n.NodeId == e.StartNodeId);
|
||||||
|
|
||||||
var A = new Point(
|
var A = new Point(
|
||||||
startNode.NodePosition.X,
|
startNode.NodePosition.X,
|
||||||
startNode.NodePosition.Y
|
startNode.NodePosition.Y
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user