diff --git a/XP.ReportEngine/Templates/CncInspectionReportTemplate.json b/XP.ReportEngine/Templates/CncInspectionReportTemplate.json
new file mode 100644
index 00000000..54a4c9f0
--- /dev/null
+++ b/XP.ReportEngine/Templates/CncInspectionReportTemplate.json
@@ -0,0 +1,28 @@
+{
+ "document": {
+ "pageSize": "A4",
+ "orientation": "Portrait",
+ "margins": { "top": 40, "bottom": 20, "left": 20, "right": 20 }
+ },
+ "pages": [
+ {
+ "type": "homepage",
+ "elements": [
+ { "type": "text", "content": "${hpOverallLine}", "positioning": "flow" },
+ { "type": "text", "content": "${hpProgramLine}", "positioning": "flow" },
+ { "type": "text", "content": "${hpSampleLine}", "positioning": "flow" }
+ ]
+ },
+ {
+ "type": "nodeDetail",
+ "repeat": "nodeSections",
+ "elements": [
+ { "type": "text", "content": "${nodeHeading}", "positioning": "flow" },
+ { "type": "text", "content": "${nodeInfoText}", "positioning": "flow" },
+ { "type": "text", "content": "${nodeResultLine}", "positioning": "flow" },
+ { "type": "table", "dataKey": "nodeMetricsTable", "positioning": "flow", "size": [170, 0], "columns": [] }
+ ]
+ }
+ ],
+ "styles": {}
+}
diff --git a/XplorePlane.Tests/Inspection/InspectionDefinitionRepositoryTests.cs b/XplorePlane.Tests/Inspection/InspectionDefinitionRepositoryTests.cs
index f69ee600..18f05406 100644
--- a/XplorePlane.Tests/Inspection/InspectionDefinitionRepositoryTests.cs
+++ b/XplorePlane.Tests/Inspection/InspectionDefinitionRepositoryTests.cs
@@ -41,7 +41,7 @@ namespace XplorePlane.Tests.Inspection
Assert.Equal(4, loaded.Geometry.BgaGrid.Rows);
Assert.Equal("BgaVoidRate", loaded.Analysis.AdvancedDetectorType);
- var json = await File.ReadAllTextAsync(Path.Combine(_directory, $"{definition.Id:D}.json"));
+ var json = await File.ReadAllTextAsync(Path.Combine(_directory, $"{definition.Id:D}.xppool"));
Assert.Contains("焊球空洞检测", json);
Assert.DoesNotContain("\\u710a", json, StringComparison.OrdinalIgnoreCase);
}
@@ -86,17 +86,17 @@ namespace XplorePlane.Tests.Inspection
Assert.Equal(2, loadedImage.PixelWidth);
Assert.NotEqual(source.ReferenceImage.RelativePath, clone.ReferenceImage.RelativePath);
- Assert.True(File.Exists(Path.Combine(_directory, clone.ReferenceImage.RelativePath)));
+ Assert.True(File.Exists(Path.Combine(_directory, clone.ReferenceImage.RelativePath)));
await _repository.DeleteAsync(clone.Id);
- Assert.False(Directory.Exists(Path.Combine(_directory, "Assets", clone.Id.ToString("D"))));
+ Assert.False(Directory.Exists(Path.Combine(_directory, "References", clone.Id.ToString("D"))));
}
[Fact]
public async Task List_RemovesOrphanReferenceAssetDirectories()
{
var orphanId = Guid.NewGuid();
- var orphanDirectory = Path.Combine(_directory, "Assets", orphanId.ToString("D"));
+ var orphanDirectory = Path.Combine(_directory, "References", orphanId.ToString("D"));
Directory.CreateDirectory(orphanDirectory);
await File.WriteAllTextAsync(Path.Combine(orphanDirectory, "reference.png"), "orphan");
diff --git a/XplorePlane.Tests/Services/CameraConfigServiceTests.cs b/XplorePlane.Tests/Services/CameraConfigServiceTests.cs
index fed884b9..e4f8d025 100644
--- a/XplorePlane.Tests/Services/CameraConfigServiceTests.cs
+++ b/XplorePlane.Tests/Services/CameraConfigServiceTests.cs
@@ -28,8 +28,8 @@ namespace XplorePlane.Tests.Services
var config = service.Load();
- Assert.Equal("Hikvision", config.CameraType);
- Assert.Equal(string.Empty, config.CameraSimulatedImagePath);
+ Assert.Equal(CameraConfig.Default.CameraType, config.CameraType);
+ Assert.Equal(CameraConfig.Default.CameraSimulatedImagePath, config.CameraSimulatedImagePath);
}
[Fact]
diff --git a/XplorePlane.Tests/XplorePlane.Tests.csproj b/XplorePlane.Tests/XplorePlane.Tests.csproj
index e7610ad4..356d56ea 100644
--- a/XplorePlane.Tests/XplorePlane.Tests.csproj
+++ b/XplorePlane.Tests/XplorePlane.Tests.csproj
@@ -31,10 +31,15 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
diff --git a/XplorePlane/Helpers/DependencyVersionProvider.cs b/XplorePlane/Helpers/DependencyVersionProvider.cs
index 9de7f359..cf4bca45 100644
--- a/XplorePlane/Helpers/DependencyVersionProvider.cs
+++ b/XplorePlane/Helpers/DependencyVersionProvider.cs
@@ -98,7 +98,7 @@ namespace XplorePlane.Services.Helpers
foreach (var assemblyName in descriptor.AssemblyNames)
{
- var path = Path.Combine(AppContext.BaseDirectory, assemblyName + ".dll");
+ var path = FindDeployedAssemblyPath(assemblyName);
if (!File.Exists(path))
continue;
@@ -122,6 +122,37 @@ namespace XplorePlane.Services.Helpers
false);
}
+ private static string FindDeployedAssemblyPath(string assemblyName)
+ {
+ var fileName = assemblyName + ".dll";
+ var roots = new List { AppContext.BaseDirectory };
+ var current = new DirectoryInfo(AppContext.BaseDirectory);
+ for (var i = 0; i < 6 && current.Parent != null; i++)
+ {
+ current = current.Parent;
+ roots.Add(current.FullName);
+ }
+
+ foreach (var root in roots.Distinct(StringComparer.OrdinalIgnoreCase))
+ {
+ foreach (var relativeDirectory in new[]
+ {
+ string.Empty,
+ "ExternalLibraries",
+ Path.Combine("ExternalLibraries", "Host"),
+ "ReleaseFiles",
+ Path.Combine("ReleaseFiles", "Host")
+ })
+ {
+ var candidate = Path.Combine(root, relativeDirectory, fileName);
+ if (File.Exists(candidate))
+ return candidate;
+ }
+ }
+
+ return Path.Combine(AppContext.BaseDirectory, fileName);
+ }
+
/// 读取应用目录中的原生依赖版本信息。
/// 依赖类别。
/// 显示名称。
diff --git a/XplorePlane/Services/Cnc/Execution/CncExecutionService.cs b/XplorePlane/Services/Cnc/Execution/CncExecutionService.cs
index a4f47303..99d639c1 100644
--- a/XplorePlane/Services/Cnc/Execution/CncExecutionService.cs
+++ b/XplorePlane/Services/Cnc/Execution/CncExecutionService.cs
@@ -700,9 +700,9 @@ namespace XplorePlane.Services.Cnc
bool succeeded,
string message)
{
- _eventAggregator.GetEvent().Publish(
+ _eventAggregator.GetEvent()?.Publish(
new StatusBarMessagePayload(message, !succeeded, 0));
- _eventAggregator.GetEvent().Publish(
+ _eventAggregator.GetEvent()?.Publish(
new TemplateMatchPreviewPayload
{
IsAlignmentResult = true,
@@ -2621,12 +2621,16 @@ namespace XplorePlane.Services.Cnc
roiResult.TransformedPointsInt?.Count ?? 0, "", order++));
}
- await _archive.AppendNodeResultAsync(nodeResult, metrics: metrics);
- }
- catch (Exception ex)
- {
- _logger.ForModule().Warn("对齐节点结果归档失败:{0}", ex.Message);
+ await _archive.AppendNodeResultAsync(
+ nodeResult,
+ metrics,
+ pipelineSnapshot: null,
+ assets: null);
}
+ catch (Exception ex)
+ {
+ _logger.ForModule().Warn("对齐节点结果归档失败:{0}", ex.Message);
+ }
}
/// 构造一个通用数值度量项(默认视为通过,不参与判定)。
diff --git a/XplorePlane/Services/Inspection/Execution/InspectionExecutionCoordinator.cs b/XplorePlane/Services/Inspection/Execution/InspectionExecutionCoordinator.cs
index 6b2c40ba..64d3c637 100644
--- a/XplorePlane/Services/Inspection/Execution/InspectionExecutionCoordinator.cs
+++ b/XplorePlane/Services/Inspection/Execution/InspectionExecutionCoordinator.cs
@@ -227,13 +227,7 @@ namespace XplorePlane.Services.Inspection
BuildTileImages(orderedTiles, tileImages),
rawOutcome.ResultImage16);
- // 运行时发布 Transforms 供主视口 Overlay 使用
- PublishRuntime(new InspectionPoolRuntimePayload(
- InspectionPoolRuntimeStage.TileCompleted,
- definition,
- TileId: "_stitched",
- Image: stitchResult.StitchedImage,
- TileTransforms: stitchResult.TileTransforms));
+ // 拼接结果通过 outcome 返回给调用方,避免再次发布 TileCompleted(该阶段只表示单个 Tile 完成)。
}
catch (Exception ex)
{
@@ -398,19 +392,10 @@ namespace XplorePlane.Services.Inspection
return await _acquisitionService.AcquireAsync(machine, acquisition, cancellationToken)
.ConfigureAwait(false);
}
- catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
+ catch (OperationCanceledException)
{
- // 采集超时
- var action = _poolInteraction.OnTileFailed(tileId, "采集超时", false);
- switch (action)
- {
- case TileInteractionResult.Retry:
- continue;
- case TileInteractionResult.Skip:
- return null; // 跳过 = 丢失一个 Tile
- case TileInteractionResult.Abort:
- throw new OperationCanceledException($"Tile [{tileId}] 用户中止");
- }
+ // 采集服务抛出的取消必须向上传播,确保检测池总能执行 Restore。
+ throw;
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
diff --git a/XplorePlane/Services/Inspection/Results/InspectionResultStore.cs b/XplorePlane/Services/Inspection/Results/InspectionResultStore.cs
index 1793d38d..9cc50508 100644
--- a/XplorePlane/Services/Inspection/Results/InspectionResultStore.cs
+++ b/XplorePlane/Services/Inspection/Results/InspectionResultStore.cs
@@ -199,11 +199,13 @@ WHERE run_id = @run_id";
}
public InspectionResultStoreCore(IDbContext db, ILoggerService logger, IXpDataPathService dataPathService, IInspectionAssetPathResolver pathResolver = null)
- : this(
- db,
- logger,
- dataPathService?.DataPath ?? throw new ArgumentNullException(nameof(dataPathService)),
- pathResolver)
+ : this(
+ db,
+ logger,
+ Path.Combine(
+ dataPathService?.DataPath ?? throw new ArgumentNullException(nameof(dataPathService)),
+ "InspectionResults"),
+ pathResolver)
{
}
diff --git a/XplorePlane/Services/Main/Recording/ViewportRecordingService.cs b/XplorePlane/Services/Main/Recording/ViewportRecordingService.cs
index 0df1dc4b..1c0bc874 100644
--- a/XplorePlane/Services/Main/Recording/ViewportRecordingService.cs
+++ b/XplorePlane/Services/Main/Recording/ViewportRecordingService.cs
@@ -307,10 +307,10 @@ namespace XplorePlane.Services.Main.Recording
///
/// 构造 VIDEO 文件夹完整路径
///
- internal static string GetVideoFolderPath(string rootPath)
- {
- return Path.Combine(rootPath, "Results", "Video");
- }
+ internal static string GetVideoFolderPath(string rootPath)
+ {
+ return Path.Combine(rootPath, "VIDEO");
+ }
///
/// 将总秒数格式化为 "MM:SS" 计时器显示文本
diff --git a/XplorePlane/Services/Reporting/CncReportContextBuilder.cs b/XplorePlane/Services/Reporting/CncReportContextBuilder.cs
index 366f54a1..cd78d2ee 100644
--- a/XplorePlane/Services/Reporting/CncReportContextBuilder.cs
+++ b/XplorePlane/Services/Reporting/CncReportContextBuilder.cs
@@ -156,14 +156,16 @@ namespace XplorePlane.Services.Reporting
OperatorName = EmptyText,
Description = FallbackText(run.ProgramName)
},
- // 不指定 TemplatePathOverride,使用默认的 StandardReportTemplate | No override, use default StandardReportTemplate
+ // CNC 报告使用专用模板:首页 + 按检测节点重复展开详情页。
+ TemplatePathOverride = CncTemplateRelativePath,
Formats = new List { ReportOutputFormat.Pdf },
CustomProperties = BuildHomepageProperties(run, reportableNodes.Count, overallResult, runSummaryRows, nodeSections),
FileNameParameters = new Dictionary
{
["ProgramName"] = (run.ProgramName ?? string.Empty).Trim(),
- ["WorkpieceId"] = (run.WorkpieceId ?? string.Empty).Trim(),
- ["SerialNumber"] = (run.SerialNumber ?? string.Empty).Trim(),
+ ["WorkpieceId"] = (run.WorkpieceId ?? string.Empty).Trim(),
+ ["SerialNumber"] = (run.SerialNumber ?? string.Empty).Trim(),
+ ["WorkpieceSN"] = (run.SerialNumber ?? string.Empty).Trim(),
["Result"] = overallResult,
["StartDate"] = run.StartedAt != default ? ToLocal(run.StartedAt).ToString("yyyyMMdd") : "",
["StartTime"] = run.StartedAt != default ? ToLocal(run.StartedAt).ToString("HHmmss") : "",
@@ -223,7 +225,10 @@ namespace XplorePlane.Services.Reporting
// 综合判定(完整行含前缀)| Overall result (full line with label)
props["hpOverallLine"] = $"综合判定 | Overall:{overallResult}";
- props["overallResult"] = overallResult;
+ props["overallResult"] = overallResult;
+ // 保留旧版报告模板/调用方使用的简短键名,避免模板升级造成兼容性破坏。
+ props["programName"] = (run.ProgramName ?? string.Empty).Trim();
+ props["inspectionNodeCount"] = reportableNodeCount;
// 汇总表标题 | Summary table title
props["hpSummaryTitle"] = runSummaryRows.Count > 0 ? "节点结果汇总 | Node Summary" : string.Empty;
diff --git a/XplorePlane/ViewModels/Cnc/Alignment/TemplateMatchAlignmentNodeEditorViewModel.cs b/XplorePlane/ViewModels/Cnc/Alignment/TemplateMatchAlignmentNodeEditorViewModel.cs
index 7810586b..0ab548f2 100644
--- a/XplorePlane/ViewModels/Cnc/Alignment/TemplateMatchAlignmentNodeEditorViewModel.cs
+++ b/XplorePlane/ViewModels/Cnc/Alignment/TemplateMatchAlignmentNodeEditorViewModel.cs
@@ -733,7 +733,7 @@ public class TemplateMatchAlignmentNodeEditorViewModel : BindableBase, IDisposab
if (string.IsNullOrWhiteSpace(programPath) && !string.IsNullOrWhiteSpace(_cncProgramName))
programPath = Path.Combine(_dataPathService.PlanPath, _cncProgramName + ".xpcnc");
string dir = string.IsNullOrWhiteSpace(programPath)
- ? Path.Combine(_dataPathService.PlanPath, "UnboundCnc.assets", "Templates")
+ ? Path.Combine(_dataPathService.ToolsPath, "Templates")
: Path.Combine(Path.GetDirectoryName(Path.GetFullPath(programPath))!, Path.GetFileNameWithoutExtension(programPath) + ".assets", "Templates");
Directory.CreateDirectory(dir);
string fileName = $"{_nodeId:N}_template.png";
diff --git a/XplorePlane/ViewModels/Cnc/Editor/CncEditorViewModel.cs b/XplorePlane/ViewModels/Cnc/Editor/CncEditorViewModel.cs
index 43c12ec4..986f9c46 100644
--- a/XplorePlane/ViewModels/Cnc/Editor/CncEditorViewModel.cs
+++ b/XplorePlane/ViewModels/Cnc/Editor/CncEditorViewModel.cs
@@ -1397,6 +1397,7 @@ namespace XplorePlane.ViewModels.Cnc
}
finally
{
+ ResetAllNodeStates();
IsRunning = false;
_cts?.Dispose();
_cts = null;