Compare commits
2 Commits
03a8532049
...
f9be56b99f
| Author | SHA1 | Date | |
|---|---|---|---|
| f9be56b99f | |||
| e3a1184805 |
@@ -3,14 +3,18 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
using FsCheck;
|
using FsCheck;
|
||||||
using FsCheck.Fluent;
|
using FsCheck.Fluent;
|
||||||
using FsCheck.Xunit;
|
using FsCheck.Xunit;
|
||||||
using Moq;
|
using Moq;
|
||||||
using XP.Common.Logging.Interfaces;
|
using XP.Common.Logging.Interfaces;
|
||||||
|
using XP.Hardware.Detector.Abstractions;
|
||||||
using XplorePlane.Models;
|
using XplorePlane.Models;
|
||||||
using XplorePlane.Services.Cnc;
|
using XplorePlane.Services.Cnc;
|
||||||
using XplorePlane.Services;
|
using XplorePlane.Services;
|
||||||
|
using XplorePlane.Services.AppState;
|
||||||
using XplorePlane.Services.InspectionResults;
|
using XplorePlane.Services.InspectionResults;
|
||||||
using XplorePlane.Services.MainViewport;
|
using XplorePlane.Services.MainViewport;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@@ -172,12 +176,13 @@ internal sealed class SynchronousProgress<T> : IProgress<T>
|
|||||||
|
|
||||||
public class CncExecutionServiceTests
|
public class CncExecutionServiceTests
|
||||||
{
|
{
|
||||||
private static (CncExecutionService Service, Mock<IInspectionResultStore> Store, Mock<ILoggerService> Logger)
|
private static (CncExecutionService Service, Mock<IInspectionResultStore> Store, Mock<ILoggerService> Logger, Mock<IMainViewportService> MainViewport, Mock<IAppStateService> AppState)
|
||||||
CreateService()
|
CreateService()
|
||||||
{
|
{
|
||||||
var mockStore = new Mock<IInspectionResultStore>();
|
var mockStore = new Mock<IInspectionResultStore>();
|
||||||
var mockLogger = new Mock<ILoggerService>();
|
var mockLogger = new Mock<ILoggerService>();
|
||||||
var mockMainViewportService = new Mock<IMainViewportService>();
|
var mockMainViewportService = new Mock<IMainViewportService>();
|
||||||
|
var mockAppStateService = new Mock<IAppStateService>();
|
||||||
var mockPipelineExecutionService = new Mock<IPipelineExecutionService>();
|
var mockPipelineExecutionService = new Mock<IPipelineExecutionService>();
|
||||||
var mockImageProcessingService = new Mock<IImageProcessingService>();
|
var mockImageProcessingService = new Mock<IImageProcessingService>();
|
||||||
mockLogger.Setup(l => l.ForModule<CncExecutionService>()).Returns(mockLogger.Object);
|
mockLogger.Setup(l => l.ForModule<CncExecutionService>()).Returns(mockLogger.Object);
|
||||||
@@ -204,9 +209,10 @@ internal sealed class SynchronousProgress<T> : IProgress<T>
|
|||||||
mockStore.Object,
|
mockStore.Object,
|
||||||
mockLogger.Object,
|
mockLogger.Object,
|
||||||
mockMainViewportService.Object,
|
mockMainViewportService.Object,
|
||||||
|
mockAppStateService.Object,
|
||||||
mockPipelineExecutionService.Object,
|
mockPipelineExecutionService.Object,
|
||||||
mockImageProcessingService.Object);
|
mockImageProcessingService.Object);
|
||||||
return (service, mockStore, mockLogger);
|
return (service, mockStore, mockLogger, mockMainViewportService, mockAppStateService);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Property 3: 预取消立即返回 ────────────────────────────────────────
|
// ── Property 3: 预取消立即返回 ────────────────────────────────────────
|
||||||
@@ -220,7 +226,7 @@ internal sealed class SynchronousProgress<T> : IProgress<T>
|
|||||||
CncProgramGenerators.CncProgramArb(1, 10),
|
CncProgramGenerators.CncProgramArb(1, 10),
|
||||||
program =>
|
program =>
|
||||||
{
|
{
|
||||||
var (service, mockStore, _) = CreateService();
|
var (service, mockStore, _, _, _) = CreateService();
|
||||||
|
|
||||||
using var cts = new CancellationTokenSource();
|
using var cts = new CancellationTokenSource();
|
||||||
cts.Cancel();
|
cts.Cancel();
|
||||||
@@ -247,7 +253,7 @@ internal sealed class SynchronousProgress<T> : IProgress<T>
|
|||||||
CncProgramGenerators.CncProgramArb(2, 8),
|
CncProgramGenerators.CncProgramArb(2, 8),
|
||||||
program =>
|
program =>
|
||||||
{
|
{
|
||||||
var (service, _, _) = CreateService();
|
var (service, _, _, _, _) = CreateService();
|
||||||
|
|
||||||
var runningReports = new List<Guid>();
|
var runningReports = new List<Guid>();
|
||||||
// Use SynchronousProgress to avoid async callback timing issues
|
// Use SynchronousProgress to avoid async callback timing issues
|
||||||
@@ -304,7 +310,7 @@ internal sealed class SynchronousProgress<T> : IProgress<T>
|
|||||||
gen.ToArbitrary(),
|
gen.ToArbitrary(),
|
||||||
program =>
|
program =>
|
||||||
{
|
{
|
||||||
var (service, _, _) = CreateService();
|
var (service, _, _, _, _) = CreateService();
|
||||||
|
|
||||||
var runningIds = new List<Guid>();
|
var runningIds = new List<Guid>();
|
||||||
var progress = new SynchronousProgress<CncNodeExecutionProgress>(p =>
|
var progress = new SynchronousProgress<CncNodeExecutionProgress>(p =>
|
||||||
@@ -355,7 +361,7 @@ internal sealed class SynchronousProgress<T> : IProgress<T>
|
|||||||
CncProgramGenerators.CncProgramArb(1, 8),
|
CncProgramGenerators.CncProgramArb(1, 8),
|
||||||
program =>
|
program =>
|
||||||
{
|
{
|
||||||
var (service, mockStore, _) = CreateService();
|
var (service, mockStore, _, _, _) = CreateService();
|
||||||
|
|
||||||
InspectionRunRecord capturedRecord = null;
|
InspectionRunRecord capturedRecord = null;
|
||||||
mockStore.Setup(s => s.BeginRunAsync(
|
mockStore.Setup(s => s.BeginRunAsync(
|
||||||
@@ -386,7 +392,7 @@ internal sealed class SynchronousProgress<T> : IProgress<T>
|
|||||||
CncProgramGenerators.CncProgramArb(1, 10),
|
CncProgramGenerators.CncProgramArb(1, 10),
|
||||||
program =>
|
program =>
|
||||||
{
|
{
|
||||||
var (service, mockStore, _) = CreateService();
|
var (service, mockStore, _, _, _) = CreateService();
|
||||||
|
|
||||||
service.ExecuteAsync(program, null, CancellationToken.None)
|
service.ExecuteAsync(program, null, CancellationToken.None)
|
||||||
.GetAwaiter().GetResult();
|
.GetAwaiter().GetResult();
|
||||||
@@ -421,7 +427,7 @@ internal sealed class SynchronousProgress<T> : IProgress<T>
|
|||||||
CncProgramGenerators.CncProgramArb(1, 10),
|
CncProgramGenerators.CncProgramArb(1, 10),
|
||||||
program =>
|
program =>
|
||||||
{
|
{
|
||||||
var (service, mockStore, _) = CreateService();
|
var (service, mockStore, _, _, _) = CreateService();
|
||||||
|
|
||||||
InspectionRunRecord capturedRecord = null;
|
InspectionRunRecord capturedRecord = null;
|
||||||
mockStore.Setup(s => s.BeginRunAsync(
|
mockStore.Setup(s => s.BeginRunAsync(
|
||||||
@@ -449,7 +455,7 @@ internal sealed class SynchronousProgress<T> : IProgress<T>
|
|||||||
CncProgramGenerators.CncProgramArb(1, 8),
|
CncProgramGenerators.CncProgramArb(1, 8),
|
||||||
program =>
|
program =>
|
||||||
{
|
{
|
||||||
var (service, mockStore, _) = CreateService();
|
var (service, mockStore, _, _, _) = CreateService();
|
||||||
|
|
||||||
bool? capturedOverallPass = default;
|
bool? capturedOverallPass = default;
|
||||||
bool callbackInvoked = false;
|
bool callbackInvoked = false;
|
||||||
@@ -482,7 +488,7 @@ internal sealed class SynchronousProgress<T> : IProgress<T>
|
|||||||
// Validates: Requirements 4.4, 4.5
|
// Validates: Requirements 4.4, 4.5
|
||||||
public void CompleteRunAsync_CalledWithNullOverallPass_WhenCancelled()
|
public void CompleteRunAsync_CalledWithNullOverallPass_WhenCancelled()
|
||||||
{
|
{
|
||||||
var (service, mockStore, _) = CreateService();
|
var (service, mockStore, _, _, _) = CreateService();
|
||||||
|
|
||||||
// Use a WaitDelayNode with long delay so cancellation happens during execution
|
// Use a WaitDelayNode with long delay so cancellation happens during execution
|
||||||
var waitNode = new WaitDelayNode(Guid.NewGuid(), 0, "LongWait", 5000);
|
var waitNode = new WaitDelayNode(Guid.NewGuid(), 0, "LongWait", 5000);
|
||||||
@@ -530,7 +536,7 @@ internal sealed class SynchronousProgress<T> : IProgress<T>
|
|||||||
CncProgramGenerators.CncProgramWithInspectionNodesArb(2),
|
CncProgramGenerators.CncProgramWithInspectionNodesArb(2),
|
||||||
program =>
|
program =>
|
||||||
{
|
{
|
||||||
var (service, mockStore, _) = CreateService();
|
var (service, mockStore, _, _, _) = CreateService();
|
||||||
|
|
||||||
// Make AppendNodeResultAsync always throw
|
// Make AppendNodeResultAsync always throw
|
||||||
mockStore.Setup(s => s.AppendNodeResultAsync(
|
mockStore.Setup(s => s.AppendNodeResultAsync(
|
||||||
@@ -587,7 +593,7 @@ internal sealed class SynchronousProgress<T> : IProgress<T>
|
|||||||
gen.ToArbitrary(),
|
gen.ToArbitrary(),
|
||||||
waitNode =>
|
waitNode =>
|
||||||
{
|
{
|
||||||
var (service, mockStore, _) = CreateService();
|
var (service, mockStore, _, _, _) = CreateService();
|
||||||
|
|
||||||
bool? capturedOverallPass = default;
|
bool? capturedOverallPass = default;
|
||||||
mockStore.Setup(s => s.CompleteRunAsync(
|
mockStore.Setup(s => s.CompleteRunAsync(
|
||||||
@@ -630,7 +636,7 @@ internal sealed class SynchronousProgress<T> : IProgress<T>
|
|||||||
CncProgramGenerators.CncProgramArb(1, 8),
|
CncProgramGenerators.CncProgramArb(1, 8),
|
||||||
program =>
|
program =>
|
||||||
{
|
{
|
||||||
var (service, _, _) = CreateService();
|
var (service, _, _, _, _) = CreateService();
|
||||||
|
|
||||||
// Build a map of NodeId → CncNodeViewModel
|
// Build a map of NodeId → CncNodeViewModel
|
||||||
var nodeVms = program.Nodes
|
var nodeVms = program.Nodes
|
||||||
@@ -700,7 +706,7 @@ internal sealed class SynchronousProgress<T> : IProgress<T>
|
|||||||
tuple =>
|
tuple =>
|
||||||
{
|
{
|
||||||
var (program, node, expectedPipelineName) = tuple;
|
var (program, node, expectedPipelineName) = tuple;
|
||||||
var (service, mockStore, _) = CreateService();
|
var (service, mockStore, _, _, _) = CreateService();
|
||||||
|
|
||||||
PipelineExecutionSnapshot capturedSnapshot = null;
|
PipelineExecutionSnapshot capturedSnapshot = null;
|
||||||
mockStore.Setup(s => s.AppendNodeResultAsync(
|
mockStore.Setup(s => s.AppendNodeResultAsync(
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
|
using XP.Common.Converters;
|
||||||
using XP.Common.Logging.Interfaces;
|
using XP.Common.Logging.Interfaces;
|
||||||
using XplorePlane.Models;
|
using XplorePlane.Models;
|
||||||
|
using XplorePlane.Services.AppState;
|
||||||
using XplorePlane.Services.InspectionResults;
|
using XplorePlane.Services.InspectionResults;
|
||||||
using XplorePlane.Services.MainViewport;
|
using XplorePlane.Services.MainViewport;
|
||||||
using XplorePlane.ViewModels;
|
using XplorePlane.ViewModels;
|
||||||
@@ -22,6 +24,7 @@ namespace XplorePlane.Services.Cnc
|
|||||||
private readonly IInspectionResultStore _store;
|
private readonly IInspectionResultStore _store;
|
||||||
private readonly ILoggerService _logger;
|
private readonly ILoggerService _logger;
|
||||||
private readonly IMainViewportService _mainViewportService;
|
private readonly IMainViewportService _mainViewportService;
|
||||||
|
private readonly IAppStateService _appStateService;
|
||||||
private readonly IPipelineExecutionService _pipelineExecutionService;
|
private readonly IPipelineExecutionService _pipelineExecutionService;
|
||||||
private readonly IImageProcessingService _imageProcessingService;
|
private readonly IImageProcessingService _imageProcessingService;
|
||||||
|
|
||||||
@@ -29,12 +32,14 @@ namespace XplorePlane.Services.Cnc
|
|||||||
IInspectionResultStore store,
|
IInspectionResultStore store,
|
||||||
ILoggerService logger,
|
ILoggerService logger,
|
||||||
IMainViewportService mainViewportService,
|
IMainViewportService mainViewportService,
|
||||||
|
IAppStateService appStateService,
|
||||||
IPipelineExecutionService pipelineExecutionService,
|
IPipelineExecutionService pipelineExecutionService,
|
||||||
IImageProcessingService imageProcessingService)
|
IImageProcessingService imageProcessingService)
|
||||||
{
|
{
|
||||||
_store = store ?? throw new ArgumentNullException(nameof(store));
|
_store = store ?? throw new ArgumentNullException(nameof(store));
|
||||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
_mainViewportService = mainViewportService;
|
_mainViewportService = mainViewportService;
|
||||||
|
_appStateService = appStateService ?? throw new ArgumentNullException(nameof(appStateService));
|
||||||
_pipelineExecutionService = pipelineExecutionService;
|
_pipelineExecutionService = pipelineExecutionService;
|
||||||
_imageProcessingService = imageProcessingService;
|
_imageProcessingService = imageProcessingService;
|
||||||
}
|
}
|
||||||
@@ -46,10 +51,7 @@ namespace XplorePlane.Services.Cnc
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
int inspectionNodeCount = program.Nodes.OfType<InspectionModuleNode>().Count();
|
int inspectionNodeCount = program.Nodes.OfType<InspectionModuleNode>().Count();
|
||||||
|
var sourceImage = TryGetSourceImage();
|
||||||
// 获取当前源图像(用于 run/source.bmp)
|
|
||||||
var sourceImage = _mainViewportService?.LatestManualImage as BitmapSource
|
|
||||||
?? _mainViewportService?.CurrentDisplayImage as BitmapSource;
|
|
||||||
|
|
||||||
Guid runId;
|
Guid runId;
|
||||||
try
|
try
|
||||||
@@ -105,7 +107,7 @@ namespace XplorePlane.Services.Cnc
|
|||||||
{
|
{
|
||||||
case ReferencePointNode rp:
|
case ReferencePointNode rp:
|
||||||
_logger.ForModule<CncExecutionService>().Info(
|
_logger.ForModule<CncExecutionService>().Info(
|
||||||
"执行参考点节点 [{Index}] {Name} | " +
|
"Executing reference point node [{Index}] {Name} | " +
|
||||||
"StageX={StageX} StageY={StageY} SourceZ={SourceZ} DetectorZ={DetectorZ} " +
|
"StageX={StageX} StageY={StageY} SourceZ={SourceZ} DetectorZ={DetectorZ} " +
|
||||||
"DetectorSwing={DetectorSwing} FDD={FDD} FOD={FOD} Magnification={Magnification} " +
|
"DetectorSwing={DetectorSwing} FDD={FDD} FOD={FOD} Magnification={Magnification} " +
|
||||||
"StageRotation={StageRotation} FixtureRotation={FixtureRotation} " +
|
"StageRotation={StageRotation} FixtureRotation={FixtureRotation} " +
|
||||||
@@ -119,7 +121,7 @@ namespace XplorePlane.Services.Cnc
|
|||||||
|
|
||||||
case SavePositionNode sp:
|
case SavePositionNode sp:
|
||||||
_logger.ForModule<CncExecutionService>().Info(
|
_logger.ForModule<CncExecutionService>().Info(
|
||||||
"执行保存位置节点 [{Index}] {Name} | " +
|
"Executing save-position node [{Index}] {Name} | " +
|
||||||
"StageX={StageX} StageY={StageY} SourceZ={SourceZ} DetectorZ={DetectorZ} " +
|
"StageX={StageX} StageY={StageY} SourceZ={SourceZ} DetectorZ={DetectorZ} " +
|
||||||
"DetectorSwing={DetectorSwing} FDD={FDD} FOD={FOD} Magnification={Magnification} " +
|
"DetectorSwing={DetectorSwing} FDD={FDD} FOD={FOD} Magnification={Magnification} " +
|
||||||
"StageRotation={StageRotation} FixtureRotation={FixtureRotation}",
|
"StageRotation={StageRotation} FixtureRotation={FixtureRotation}",
|
||||||
@@ -133,7 +135,7 @@ namespace XplorePlane.Services.Cnc
|
|||||||
|
|
||||||
case SaveNodeNode sn:
|
case SaveNodeNode sn:
|
||||||
_logger.ForModule<CncExecutionService>().Info(
|
_logger.ForModule<CncExecutionService>().Info(
|
||||||
"执行保存节点 [{Index}] {Name} | " +
|
"Executing save node [{Index}] {Name} | " +
|
||||||
"StageX={StageX} StageY={StageY} SourceZ={SourceZ} DetectorZ={DetectorZ} " +
|
"StageX={StageX} StageY={StageY} SourceZ={SourceZ} DetectorZ={DetectorZ} " +
|
||||||
"DetectorSwing={DetectorSwing} FDD={FDD} FOD={FOD} Magnification={Magnification} " +
|
"DetectorSwing={DetectorSwing} FDD={FDD} FOD={FOD} Magnification={Magnification} " +
|
||||||
"RayOn={RayOn} Voltage={Voltage}kV Power={Power}W",
|
"RayOn={RayOn} Voltage={Voltage}kV Power={Power}W",
|
||||||
@@ -147,7 +149,7 @@ namespace XplorePlane.Services.Cnc
|
|||||||
|
|
||||||
case SaveNodeWithImageNode sni:
|
case SaveNodeWithImageNode sni:
|
||||||
_logger.ForModule<CncExecutionService>().Info(
|
_logger.ForModule<CncExecutionService>().Info(
|
||||||
"执行保存节点(图像) [{Index}] {Name} | " +
|
"Executing save-with-image node [{Index}] {Name} | " +
|
||||||
"StageX={StageX} StageY={StageY} SourceZ={SourceZ} DetectorZ={DetectorZ} " +
|
"StageX={StageX} StageY={StageY} SourceZ={SourceZ} DetectorZ={DetectorZ} " +
|
||||||
"DetectorSwing={DetectorSwing} FDD={FDD} FOD={FOD} Magnification={Magnification} " +
|
"DetectorSwing={DetectorSwing} FDD={FDD} FOD={FOD} Magnification={Magnification} " +
|
||||||
"RayOn={RayOn} Voltage={Voltage}kV Power={Power}W ImageFile={ImageFile}",
|
"RayOn={RayOn} Voltage={Voltage}kV Power={Power}W ImageFile={ImageFile}",
|
||||||
@@ -218,7 +220,7 @@ namespace XplorePlane.Services.Cnc
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// InspectionModuleNode 完成时携带结果图像,供 ViewModel 缓存到节点上
|
// Carry the latest inspection result image so the ViewModel can cache it on the node.
|
||||||
var nodeResultImage = node is InspectionModuleNode ? lastResultImage : null;
|
var nodeResultImage = node is InspectionModuleNode ? lastResultImage : null;
|
||||||
var finalState = nodeSucceeded ? NodeExecutionState.Succeeded : NodeExecutionState.Failed;
|
var finalState = nodeSucceeded ? NodeExecutionState.Succeeded : NodeExecutionState.Failed;
|
||||||
progress?.Report(new CncNodeExecutionProgress(node.Id, finalState, nodeResultImage));
|
progress?.Report(new CncNodeExecutionProgress(node.Id, finalState, nodeResultImage));
|
||||||
@@ -242,6 +244,25 @@ namespace XplorePlane.Services.Cnc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BitmapSource TryGetSourceImage()
|
||||||
|
{
|
||||||
|
var viewportImage = _mainViewportService?.LatestManualImage as BitmapSource
|
||||||
|
?? _mainViewportService?.CurrentDisplayImage as BitmapSource;
|
||||||
|
if (viewportImage != null)
|
||||||
|
return viewportImage;
|
||||||
|
|
||||||
|
var detectorFrame = _appStateService?.LatestDetectorFrame;
|
||||||
|
if (detectorFrame?.ImageData == null || detectorFrame.Width <= 0 || detectorFrame.Height <= 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var bitmap = ImageConverter.ConvertGray16ToBitmapSource(
|
||||||
|
detectorFrame.ImageData,
|
||||||
|
(int)detectorFrame.Width,
|
||||||
|
(int)detectorFrame.Height);
|
||||||
|
bitmap.Freeze();
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<BitmapSource> ExecuteInspectionNodeAsync(
|
private async Task<BitmapSource> ExecuteInspectionNodeAsync(
|
||||||
Guid runId,
|
Guid runId,
|
||||||
InspectionModuleNode inspectionNode,
|
InspectionModuleNode inspectionNode,
|
||||||
@@ -304,7 +325,7 @@ namespace XplorePlane.Services.Cnc
|
|||||||
Height = resultImage.PixelHeight
|
Height = resultImage.PixelHeight
|
||||||
});
|
});
|
||||||
nodeResult.Status = InspectionNodeStatus.Succeeded;
|
nodeResult.Status = InspectionNodeStatus.Succeeded;
|
||||||
_mainViewportService?.SetManualImage(resultImage, $"CNC节点:{inspectionNode.Name}");
|
_mainViewportService?.SetManualImage(resultImage, $"CNC Node: {inspectionNode.Name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
Reference in New Issue
Block a user