This commit is contained in:
sonlt 2025-12-22 09:49:02 +07:00
parent f6f8a3bf65
commit 7e0c6af9d5
6 changed files with 315 additions and 18 deletions

View File

@ -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

View File

@ -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>
} }

View 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."
);
}
}
}

View File

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

View File

@ -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();
} }

View File

@ -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