Files
XplorePlane/XplorePlane/Services/Cnc/CncExecutionService.cs
T

415 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Imaging;
using XP.Common.Logging.Interfaces;
using XplorePlane.Models;
using XplorePlane.Services.InspectionResults;
using XplorePlane.Services.MainViewport;
using XplorePlane.ViewModels;
namespace XplorePlane.Services.Cnc
{
/// <summary>
/// Executes a CNC program node-by-node, reporting progress and persisting inspection results.
/// </summary>
public class CncExecutionService : ICncExecutionService
{
private readonly IInspectionResultStore _store;
private readonly ILoggerService _logger;
private readonly IMainViewportService _mainViewportService;
private readonly IPipelineExecutionService _pipelineExecutionService;
private readonly IImageProcessingService _imageProcessingService;
public CncExecutionService(
IInspectionResultStore store,
ILoggerService logger,
IMainViewportService mainViewportService,
IPipelineExecutionService pipelineExecutionService,
IImageProcessingService imageProcessingService)
{
_store = store ?? throw new ArgumentNullException(nameof(store));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_mainViewportService = mainViewportService;
_pipelineExecutionService = pipelineExecutionService;
_imageProcessingService = imageProcessingService;
}
public async Task ExecuteAsync(CncProgram program, IProgress<CncNodeExecutionProgress> progress, CancellationToken cancellationToken)
{
// Pre-cancellation check - do NOT call BeginRunAsync if already cancelled
if (cancellationToken.IsCancellationRequested)
return;
int inspectionNodeCount = program.Nodes.OfType<InspectionModuleNode>().Count();
// 获取当前源图像(用于 run/source.bmp
var sourceImage = _mainViewportService?.LatestManualImage as BitmapSource
?? _mainViewportService?.CurrentDisplayImage as BitmapSource;
Guid runId;
try
{
var runRecord = new InspectionRunRecord
{
ProgramName = program.Name,
NodeCount = inspectionNodeCount,
StartedAt = DateTime.UtcNow
};
InspectionAssetWriteRequest sourceAsset = null;
if (sourceImage != null)
{
sourceAsset = new InspectionAssetWriteRequest
{
AssetType = InspectionAssetType.RunSourceImage,
Content = EncodeBitmapToBmp(sourceImage),
FileFormat = "bmp",
Width = sourceImage.PixelWidth,
Height = sourceImage.PixelHeight
};
}
await _store.BeginRunAsync(runRecord, sourceAsset);
runId = runRecord.RunId;
}
catch (Exception ex)
{
_logger.ForModule<CncExecutionService>().Error(ex, "Failed to begin inspection run for program '{0}'", program.Name);
return;
}
bool cancelled = false;
bool allSucceeded = true;
BitmapSource lastResultImage = null;
foreach (var node in program.Nodes.OrderBy(n => n.Index))
{
if (cancellationToken.IsCancellationRequested)
{
cancelled = true;
break;
}
progress?.Report(new CncNodeExecutionProgress(node.Id, NodeExecutionState.Running, ProgressPercent: 0));
bool nodeSucceeded = true;
try
{
switch (node)
{
case ReferencePointNode rp:
_logger.ForModule<CncExecutionService>().Info(
"执行参考点节点 [{Index}] {Name} | " +
"StageX={StageX} StageY={StageY} SourceZ={SourceZ} DetectorZ={DetectorZ} " +
"DetectorSwing={DetectorSwing} FDD={FDD} FOD={FOD} Magnification={Magnification} " +
"StageRotation={StageRotation} FixtureRotation={FixtureRotation} " +
"RayOn={RayOn} Voltage={Voltage}kV Current={Current}uA",
rp.Index, rp.Name,
rp.StageX, rp.StageY, rp.SourceZ, rp.DetectorZ,
rp.DetectorSwing, rp.FDD, rp.FOD, rp.Magnification,
rp.StageRotation, rp.FixtureRotation,
rp.IsRayOn, rp.Voltage, rp.Current);
break;
case SavePositionNode sp:
_logger.ForModule<CncExecutionService>().Info(
"执行保存位置节点 [{Index}] {Name} | " +
"StageX={StageX} StageY={StageY} SourceZ={SourceZ} DetectorZ={DetectorZ} " +
"DetectorSwing={DetectorSwing} FDD={FDD} FOD={FOD} Magnification={Magnification} " +
"StageRotation={StageRotation} FixtureRotation={FixtureRotation}",
sp.Index, sp.Name,
sp.MotionState.StageX, sp.MotionState.StageY,
sp.MotionState.SourceZ, sp.MotionState.DetectorZ,
sp.MotionState.DetectorSwing, sp.MotionState.FDD,
sp.MotionState.FOD, sp.MotionState.Magnification,
sp.MotionState.StageRotation, sp.MotionState.FixtureRotation);
break;
case SaveNodeNode sn:
_logger.ForModule<CncExecutionService>().Info(
"执行保存节点 [{Index}] {Name} | " +
"StageX={StageX} StageY={StageY} SourceZ={SourceZ} DetectorZ={DetectorZ} " +
"DetectorSwing={DetectorSwing} FDD={FDD} FOD={FOD} Magnification={Magnification} " +
"RayOn={RayOn} Voltage={Voltage}kV Power={Power}W",
sn.Index, sn.Name,
sn.MotionState.StageX, sn.MotionState.StageY,
sn.MotionState.SourceZ, sn.MotionState.DetectorZ,
sn.MotionState.DetectorSwing, sn.MotionState.FDD,
sn.MotionState.FOD, sn.MotionState.Magnification,
sn.RaySourceState.IsOn, sn.RaySourceState.Voltage, sn.RaySourceState.Power);
break;
case SaveNodeWithImageNode sni:
_logger.ForModule<CncExecutionService>().Info(
"执行保存节点(图像) [{Index}] {Name} | " +
"StageX={StageX} StageY={StageY} SourceZ={SourceZ} DetectorZ={DetectorZ} " +
"DetectorSwing={DetectorSwing} FDD={FDD} FOD={FOD} Magnification={Magnification} " +
"RayOn={RayOn} Voltage={Voltage}kV Power={Power}W ImageFile={ImageFile}",
sni.Index, sni.Name,
sni.MotionState.StageX, sni.MotionState.StageY,
sni.MotionState.SourceZ, sni.MotionState.DetectorZ,
sni.MotionState.DetectorSwing, sni.MotionState.FDD,
sni.MotionState.FOD, sni.MotionState.Magnification,
sni.RaySourceState.IsOn, sni.RaySourceState.Voltage, sni.RaySourceState.Power,
sni.ImageFileName);
break;
case WaitDelayNode waitNode:
try
{
await ExecuteWaitDelayWithProgressAsync(waitNode, progress, cancellationToken);
}
catch (OperationCanceledException)
{
cancelled = true;
}
break;
case PauseDialogNode pauseNode:
await Application.Current.Dispatcher.InvokeAsync(() =>
MessageBox.Show(pauseNode.DialogMessage, pauseNode.DialogTitle));
if (cancellationToken.IsCancellationRequested)
cancelled = true;
break;
case InspectionModuleNode inspectionNode:
try
{
var img = await ExecuteInspectionNodeAsync(runId, inspectionNode, sourceImage, cancellationToken);
if (img != null) lastResultImage = img;
}
catch (Exception ex)
{
_logger.ForModule<CncExecutionService>().Error(ex,
"Failed to append node result for node '{0}' (Id={1})", inspectionNode.Name, inspectionNode.Id);
nodeSucceeded = false;
}
break;
case CompleteProgramNode:
// Report Succeeded before terminating the loop
progress?.Report(new CncNodeExecutionProgress(node.Id, NodeExecutionState.Succeeded));
goto endLoop;
default:
// Unknown node types are treated as succeeded
break;
}
}
catch (Exception ex)
{
_logger.ForModule<CncExecutionService>().Error(ex,
"Unexpected error executing node '{0}' (Id={1})", node.Name, node.Id);
if (cancellationToken.IsCancellationRequested)
cancelled = true;
else
nodeSucceeded = false;
}
if (cancelled)
{
progress?.Report(new CncNodeExecutionProgress(node.Id, NodeExecutionState.Failed));
break;
}
// InspectionModuleNode 完成时携带结果图像,供 ViewModel 缓存到节点上
var nodeResultImage = node is InspectionModuleNode ? lastResultImage : null;
var finalState = nodeSucceeded ? NodeExecutionState.Succeeded : NodeExecutionState.Failed;
progress?.Report(new CncNodeExecutionProgress(node.Id, finalState, nodeResultImage));
if (!nodeSucceeded)
allSucceeded = false;
}
endLoop:
bool? overallPass = cancelled ? null : (bool?)allSucceeded;
try
{
await _store.CompleteRunAsync(runId, overallPass);
}
catch (Exception ex)
{
_logger.ForModule<CncExecutionService>().Error(ex,
"Failed to complete inspection run '{0}'", runId);
}
}
private async Task<BitmapSource> ExecuteInspectionNodeAsync(
Guid runId,
InspectionModuleNode inspectionNode,
BitmapSource sourceImage,
CancellationToken cancellationToken)
{
var nodeResult = new InspectionNodeResult
{
RunId = runId,
NodeId = inspectionNode.Id,
NodeIndex = inspectionNode.Index,
NodeName = inspectionNode.Name,
PipelineName = inspectionNode.Pipeline?.Name ?? string.Empty
};
PipelineExecutionSnapshot pipelineSnapshot = null;
if (inspectionNode.Pipeline != null)
{
var pipelineJson = JsonSerializer.Serialize(inspectionNode.Pipeline);
pipelineSnapshot = new PipelineExecutionSnapshot
{
RunId = runId,
NodeId = inspectionNode.Id,
PipelineName = inspectionNode.Pipeline.Name,
PipelineDefinitionJson = pipelineJson
};
}
var assets = new System.Collections.Generic.List<InspectionAssetWriteRequest>();
if (sourceImage != null)
{
assets.Add(new InspectionAssetWriteRequest
{
AssetType = InspectionAssetType.NodeInputImage,
Content = EncodeBitmapToBmp(sourceImage),
FileFormat = "bmp",
Width = sourceImage.PixelWidth,
Height = sourceImage.PixelHeight
});
}
BitmapSource resultImage = null;
if (_pipelineExecutionService != null && inspectionNode.Pipeline?.Nodes?.Count > 0 && sourceImage != null)
{
try
{
var pipelineNodes = BuildPipelineNodeViewModels(inspectionNode.Pipeline);
resultImage = await _pipelineExecutionService.ExecutePipelineAsync(
pipelineNodes, sourceImage, null, cancellationToken);
if (resultImage != null)
{
assets.Add(new InspectionAssetWriteRequest
{
AssetType = InspectionAssetType.NodeResultImage,
Content = EncodeBitmapToBmp(resultImage),
FileFormat = "bmp",
Width = resultImage.PixelWidth,
Height = resultImage.PixelHeight
});
nodeResult.Status = InspectionNodeStatus.Succeeded;
_mainViewportService?.SetManualImage(resultImage, $"CNC节点:{inspectionNode.Name}");
}
}
catch (Exception ex)
{
_logger.ForModule<CncExecutionService>().Warn(
"Pipeline execution failed for node '{0}': {1}", inspectionNode.Name, ex.Message);
nodeResult.Status = InspectionNodeStatus.Failed;
}
}
await _store.AppendNodeResultAsync(nodeResult, pipelineSnapshot: pipelineSnapshot, assets: assets);
return resultImage;
}
private System.Collections.Generic.IEnumerable<PipelineNodeViewModel> BuildPipelineNodeViewModels(PipelineModel pipeline)
{
var nodes = new System.Collections.Generic.List<PipelineNodeViewModel>();
if (pipeline?.Nodes == null) return nodes;
foreach (var nodeModel in pipeline.Nodes.OrderBy(n => n.Order))
{
var displayName = _imageProcessingService?.GetProcessorDisplayName(nodeModel.OperatorKey) ?? nodeModel.OperatorKey;
var vm = new PipelineNodeViewModel(nodeModel.OperatorKey, displayName, string.Empty)
{
Order = nodeModel.Order,
IsEnabled = nodeModel.IsEnabled
};
if (_imageProcessingService != null)
{
var paramDefs = _imageProcessingService.GetProcessorParameters(nodeModel.OperatorKey);
if (paramDefs != null)
{
foreach (var def in paramDefs)
{
var paramVm = new ProcessorParameterVM(def);
if (nodeModel.Parameters != null && nodeModel.Parameters.TryGetValue(def.Name, out var saved))
paramVm.Value = ConvertSavedValue(saved, def.ValueType);
vm.Parameters.Add(paramVm);
}
}
}
nodes.Add(vm);
}
return nodes;
}
private static object ConvertSavedValue(object savedValue, Type targetType)
{
if (savedValue is not JsonElement jsonElement)
return savedValue;
try
{
if (targetType == typeof(int)) return jsonElement.GetInt32();
if (targetType == typeof(double)) return jsonElement.GetDouble();
if (targetType == typeof(bool)) return jsonElement.GetBoolean();
if (targetType == typeof(string)) return jsonElement.GetString() ?? string.Empty;
return jsonElement.ToString();
}
catch
{
return jsonElement.ToString();
}
}
private static byte[] EncodeBitmapToBmp(BitmapSource bitmap)
{
using var ms = new MemoryStream();
var encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
encoder.Save(ms);
return ms.ToArray();
}
private static async Task ExecuteWaitDelayWithProgressAsync(
WaitDelayNode waitNode,
IProgress<CncNodeExecutionProgress> progress,
CancellationToken cancellationToken)
{
int totalMs = waitNode.DelayMilliseconds;
if (totalMs <= 0)
{
progress?.Report(new CncNodeExecutionProgress(waitNode.Id, NodeExecutionState.Running, ProgressPercent: 100));
return;
}
const int tickMs = 50;
int elapsed = 0;
while (elapsed < totalMs)
{
cancellationToken.ThrowIfCancellationRequested();
int remaining = totalMs - elapsed;
int delay = Math.Min(tickMs, remaining);
await Task.Delay(delay, cancellationToken);
elapsed += delay;
progress?.Report(new CncNodeExecutionProgress(
waitNode.Id,
NodeExecutionState.Running,
ProgressPercent: elapsed * 100d / totalMs));
}
}
}
}