修复主界面;增加报告数据源

This commit is contained in:
zhengxuan.zhang
2026-04-23 07:14:10 +08:00
parent 40b229f5aa
commit e0326c2d80
3 changed files with 509 additions and 4 deletions
+2 -2
View File
@@ -106,5 +106,5 @@ dotnet build XplorePlane.sln -c Release
- [x] 相机实时影像集成(连接、采集、Live View、像素坐标显示)
- [x] 相机参数设置对话框(曝光、增益、分辨率、像素格式)
- [x] 主界面硬件栏相机设置按钮
- [ ] 打通与硬件层的调用流程
- [ ] 打通与图像层的调用流程
- [x] 打通与硬件层的调用流程
- [x] 打通与图像层的调用流程
+505
View File
@@ -0,0 +1,505 @@
# CNC 多检测结果归档与报告取数说明
## 1. 目标
为 CNC 执行结果提供一套适合报告模块直接取数的归档结构。
设计目标:
- 以“一次工件检测实例”作为主归档单位
- 同时保留到“检测节点级别”的明细
- 支持保存:
- CNC 程序名
- 工件号 / 序列号
- 检测节点信息
- 节点使用的 Pipeline / 配方快照
- 原图、节点输入图、节点最终结果图
- 节点输出的多个数值结果
- 节点判定和整次实例判定
- 方便后续报告模块直接读取,不依赖运行时最新配方
当前实现采用:
- SQLite 保存结构化索引数据
- 文件系统保存图片资产和 `manifest.json`
---
## 2. 总体设计
一次检测实例会生成:
1. 一组数据库记录
2. 一组文件目录和图像文件
3. 一份 `manifest.json` 快照文件
归档核心对象:
- `InspectionRunRecord`
- 一次完整检测实例
- `InspectionNodeResult`
- 一个 CNC 检测节点的结果
- `InspectionMetricResult`
- 节点输出的数值结果
- `InspectionAssetRecord`
- 图像或附件索引
- `PipelineExecutionSnapshot`
- 节点执行时使用的 Pipeline 快照
默认图片保留策略:
- 整次实例原图
- 每个节点的输入图
- 每个节点的最终结果图
不默认保存每个算法步骤的中间图。
---
## 3. 文件存储结构
### 3.1 根目录
默认根目录:
```text
%AppData%\XplorePlane\InspectionResults
```
每次检测实例按日期和 `RunId` 分层:
```text
Results/{yyyy}/{MM}/{dd}/{RunId}/
```
### 3.2 示例目录
假设:
- `ProgramName = NewCncProgram`
- `WorkpieceId = QFN_1`
- `SerialNumber = SN-001`
- `RunId = 7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a`
- 检测节点共 2 个
则文件结构大致为:
```text
%AppData%\XplorePlane\InspectionResults\
└─ Results\
└─ 2026\
└─ 04\
└─ 21\
└─ 7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a\
├─ manifest.json
├─ run\
│ └─ source.bmp
└─ nodes\
├─ 001_检测节点1\
│ ├─ input.bmp
│ └─ result_overlay.bmp
└─ 002_检测节点2\
└─ result_overlay.bmp
```
### 3.3 文件说明
- `run/source.bmp`
- 本次工件检测实例的原始输入图
- `nodes/001_检测节点1/input.bmp`
- 节点 1 输入图
- `nodes/001_检测节点1/result_overlay.bmp`
- 节点 1 最终结果图
- `nodes/002_检测节点2/result_overlay.bmp`
- 节点 2 最终结果图
- `manifest.json`
- 本次检测完整快照,便于离线查看、调试和导出
---
## 4. 数据库表设计
当前实现包含 5 张主表。
### 4.1 `inspection_runs`
用途:保存一次完整检测实例的主记录。
| 字段 | 类型 | 说明 |
|---|---|---|
| `run_id` | `TEXT` | 主键,检测实例 IDGUID |
| `program_name` | `TEXT` | CNC 程序名 |
| `workpiece_id` | `TEXT` | 工件号 |
| `serial_number` | `TEXT` | 序列号 |
| `started_at` | `TEXT` | 开始时间,ISO 8601 |
| `completed_at` | `TEXT` | 结束时间,ISO 8601,可空 |
| `overall_pass` | `INTEGER` | 整体判定,`0/1` |
| `source_image_path` | `TEXT` | 原图相对路径 |
| `result_root_path` | `TEXT` | 本次结果包根目录相对路径 |
| `node_count` | `INTEGER` | 节点数量 |
样例数据:
| run_id | program_name | workpiece_id | serial_number | started_at | completed_at | overall_pass | source_image_path | result_root_path | node_count |
|---|---|---|---|---|---|---:|---|---|---:|
| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `NewCncProgram` | `QFN_1` | `SN-001` | `2026-04-21T10:00:00.0000000Z` | `2026-04-21T10:00:03.2000000Z` | `0` | `Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a/run/source.bmp` | `Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `2` |
### 4.2 `inspection_node_results`
用途:保存一次检测实例中的节点级结果。
| 字段 | 类型 | 说明 |
|---|---|---|
| `run_id` | `TEXT` | 所属检测实例 ID |
| `node_id` | `TEXT` | 节点 IDGUID |
| `node_index` | `INTEGER` | 节点序号 |
| `node_name` | `TEXT` | 节点名称 |
| `pipeline_id` | `TEXT` | Pipeline IDGUID |
| `pipeline_name` | `TEXT` | Pipeline 名称 |
| `pipeline_version_hash` | `TEXT` | Pipeline 快照 hash |
| `node_pass` | `INTEGER` | 节点判定,`0/1` |
| `source_image_path` | `TEXT` | 节点输入图相对路径 |
| `result_image_path` | `TEXT` | 节点结果图相对路径 |
| `status` | `TEXT` | 节点状态:`Succeeded / Failed / PartialSuccess / AssetMissing` |
| `duration_ms` | `INTEGER` | 节点耗时 |
样例数据:
| run_id | node_id | node_index | node_name | pipeline_name | pipeline_version_hash | node_pass | source_image_path | result_image_path | status | duration_ms |
|---|---|---:|---|---|---|---:|---|---|---|---:|
| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `11111111-1111-1111-1111-111111111111` | `1` | `检测节点1` | `Recipe-A` | `A1B2C3...` | `1` | `Results/2026/04/21/.../nodes/001_检测节点1/input.bmp` | `Results/2026/04/21/.../nodes/001_检测节点1/result_overlay.bmp` | `Succeeded` | `135` |
| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `22222222-2222-2222-2222-222222222222` | `2` | `检测节点2` | `Recipe-B` | `D4E5F6...` | `0` | `` | `Results/2026/04/21/.../nodes/002_检测节点2/result_overlay.bmp` | `Failed` | `240` |
### 4.3 `inspection_metric_results`
用途:保存节点输出的结构化数值结果。
| 字段 | 类型 | 说明 |
|---|---|---|
| `run_id` | `TEXT` | 所属检测实例 ID |
| `node_id` | `TEXT` | 所属节点 ID |
| `metric_key` | `TEXT` | 指标 key |
| `metric_name` | `TEXT` | 指标名称 |
| `metric_value` | `REAL` | 指标值 |
| `unit` | `TEXT` | 单位 |
| `lower_limit` | `REAL` | 下限,可空 |
| `upper_limit` | `REAL` | 上限,可空 |
| `is_pass` | `INTEGER` | 单指标判定,`0/1` |
| `display_order` | `INTEGER` | 展示顺序 |
样例数据:
| run_id | node_id | metric_key | metric_name | metric_value | unit | lower_limit | upper_limit | is_pass | display_order |
|---|---|---|---|---:|---|---:|---:|---:|---:|
| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `11111111-1111-1111-1111-111111111111` | `bridge.rate` | `Bridge Rate` | `0.12` | `%` | | `0.2` | `1` | `1` |
| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `11111111-1111-1111-1111-111111111111` | `void.area` | `Void Area` | `5.6` | `px` | | `8` | `1` | `2` |
| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `22222222-2222-2222-2222-222222222222` | `solder.height` | `Solder Height` | `1.7` | `mm` | `1.8` | | `0` | `1` |
### 4.4 `inspection_assets`
用途:保存文件资产索引。
| 字段 | 类型 | 说明 |
|---|---|---|
| `run_id` | `TEXT` | 所属检测实例 ID |
| `node_id` | `TEXT` | 所属节点 ID,可空 |
| `asset_type` | `TEXT` | 资产类型 |
| `relative_path` | `TEXT` | 相对路径 |
| `file_format` | `TEXT` | 文件格式 |
| `width` | `INTEGER` | 宽度 |
| `height` | `INTEGER` | 高度 |
约定的 `asset_type`
- `RunSourceImage`
- `NodeInputImage`
- `NodeResultImage`
样例数据:
| run_id | node_id | asset_type | relative_path | file_format | width | height |
|---|---|---|---|---|---:|---:|
| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | | `RunSourceImage` | `Results/2026/04/21/.../run/source.bmp` | `bmp` | `0` | `0` |
| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `11111111-1111-1111-1111-111111111111` | `NodeInputImage` | `Results/2026/04/21/.../nodes/001_检测节点1/input.bmp` | `bmp` | `0` | `0` |
| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `11111111-1111-1111-1111-111111111111` | `NodeResultImage` | `Results/2026/04/21/.../nodes/001_检测节点1/result_overlay.bmp` | `bmp` | `0` | `0` |
| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `22222222-2222-2222-2222-222222222222` | `NodeResultImage` | `Results/2026/04/21/.../nodes/002_检测节点2/result_overlay.bmp` | `bmp` | `0` | `0` |
### 4.5 `pipeline_execution_snapshots`
用途:保存节点执行时的 Pipeline 快照,避免后续配方修改影响历史报告。
| 字段 | 类型 | 说明 |
|---|---|---|
| `run_id` | `TEXT` | 所属检测实例 ID |
| `node_id` | `TEXT` | 所属节点 ID |
| `pipeline_name` | `TEXT` | Pipeline 名称 |
| `pipeline_definition_json` | `TEXT` | Pipeline 序列化 JSON |
| `pipeline_hash` | `TEXT` | Pipeline JSON 的 SHA-256 |
样例数据:
| run_id | node_id | pipeline_name | pipeline_hash |
|---|---|---|---|
| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `11111111-1111-1111-1111-111111111111` | `Recipe-A` | `A1B2C3...` |
| `7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a` | `22222222-2222-2222-2222-222222222222` | `Recipe-B` | `D4E5F6...` |
---
## 5. `manifest.json` 示例
每次 `CompleteRunAsync` 后,会在结果包目录下生成 `manifest.json`。
示例:
```json
{
"Run": {
"RunId": "7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a",
"ProgramName": "NewCncProgram",
"WorkpieceId": "QFN_1",
"SerialNumber": "SN-001",
"StartedAt": "2026-04-21T10:00:00.0000000Z",
"CompletedAt": "2026-04-21T10:00:03.2000000Z",
"OverallPass": false,
"SourceImagePath": "Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a/run/source.bmp",
"ResultRootPath": "Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a",
"NodeCount": 2
},
"Nodes": [
{
"RunId": "7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a",
"NodeId": "11111111-1111-1111-1111-111111111111",
"NodeIndex": 1,
"NodeName": "检测节点1",
"PipelineName": "Recipe-A",
"PipelineVersionHash": "A1B2C3...",
"NodePass": true,
"SourceImagePath": "Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a/nodes/001_检测节点1/input.bmp",
"ResultImagePath": "Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a/nodes/001_检测节点1/result_overlay.bmp",
"Status": 0,
"DurationMs": 135
},
{
"RunId": "7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a",
"NodeId": "22222222-2222-2222-2222-222222222222",
"NodeIndex": 2,
"NodeName": "检测节点2",
"PipelineName": "Recipe-B",
"PipelineVersionHash": "D4E5F6...",
"NodePass": false,
"SourceImagePath": "",
"ResultImagePath": "Results/2026/04/21/7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a/nodes/002_检测节点2/result_overlay.bmp",
"Status": 1,
"DurationMs": 240
}
],
"Metrics": [
{
"RunId": "7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a",
"NodeId": "11111111-1111-1111-1111-111111111111",
"MetricKey": "bridge.rate",
"MetricName": "Bridge Rate",
"MetricValue": 0.12,
"Unit": "%",
"LowerLimit": null,
"UpperLimit": 0.2,
"IsPass": true,
"DisplayOrder": 1
},
{
"RunId": "7d7d8d7d-1234-4567-89ab-9f0e1d2c3b4a",
"NodeId": "22222222-2222-2222-2222-222222222222",
"MetricKey": "solder.height",
"MetricName": "Solder Height",
"MetricValue": 1.7,
"Unit": "mm",
"LowerLimit": 1.8,
"UpperLimit": null,
"IsPass": false,
"DisplayOrder": 1
}
]
}
```
说明:
- `manifest.json` 是文件侧的完整快照
- SQLite 是主索引
- 报告模块可以优先查库,再按需读取文件
---
## 6. 报告模块如何取数
### 6.1 首页列表 / 历史查询
建议通过:
- `inspection_runs`
- 必要时联查 `inspection_node_results`
可支持筛选条件:
- 时间范围
- 程序名
- 工件号
- 序列号
- Pipeline 名称
### 6.2 单份报告生成
建议按 `RunId` 调用:
- `GetRunDetailAsync(runId)`
得到:
- `Run`
- `Nodes`
- `Metrics`
- `Assets`
- `PipelineSnapshots`
即可直接组装报告:
- 报告首页
- 工件号、序列号、程序名、开始/结束时间、整体判定
- 节点章节
- 节点名称
- 配方名
- 结果图
- 关键指标值
- 节点判定
- 追溯信息
- Pipeline 快照 hash
- 原图路径
- 结果图路径
### 6.3 离线导出
若后续需要将单个检测实例导出给第三方或留档,可直接复制:
- 整个 `Results/{yyyy}/{MM}/{dd}/{RunId}` 目录
这样会同时带走:
- `manifest.json`
- 原图
- 节点图
---
## 7. 当前实现接口
当前服务接口:
- `BeginRunAsync(...)`
- 创建实例记录和结果目录
- `AppendNodeResultAsync(...)`
- 写入节点结果、指标、图片索引、Pipeline 快照
- `CompleteRunAsync(...)`
- 回填结束时间、整体判定,并写出 `manifest.json`
- `QueryRunsAsync(...)`
- 查询检测实例列表
- `GetRunDetailAsync(...)`
- 查询单个实例的完整报告数据
当前 DI 注册:
- `IInspectionResultStore -> InspectionResultStore`
---
## 8. 设计约束与说明
### 8.1 为什么不直接扩展 `MeasurementDataService`
因为原有 `MeasurementRecord` 只适合:
- 单值统计
- 简单的 pass/fail 汇总
它不适合承载:
- 多节点
- 多图像
- 多指标
- Pipeline 快照
- 报告导出
所以当前设计中:
- `MeasurementDataService` 继续保留给旧统计用途
- `InspectionResultStore` 作为报告归档主通道
### 8.2 为什么图片不直接进 SQLite
因为图片数据量大,直接入库会带来:
- 数据库膨胀
- 查询性能下降
- 迁移和备份成本变高
因此使用:
- SQLite 存索引
- 文件系统存图片
这是更适合报告场景的折中方案。
### 8.3 为什么要保存 Pipeline 快照
因为报告要可追溯。
如果只保存 `PipelineName`,后续配方被修改后,历史报告就无法复原当时的真实算法链。
因此需要保存:
- `pipeline_definition_json`
- `pipeline_hash`
---
## 9. 后续可扩展方向
后续可继续扩展:
1. 增加 `manifest.json` 中的设备上下文
- 运动位置
- 射线源状态
- 探测器状态
2. 增加中间图可选保留策略
- 默认关闭
- 调试模式开启
3. 增加结果导出包
- ZIP 打包
- 单份报告 PDF
4. 增加报告模板字段映射
- 将 `MetricKey` 映射到报告模板占位符
5. 增加数据清理策略
- 保留天数
- 自动清理旧图片
- 保留数据库索引或同时删除
---
## 10. 结论
当前这套归档设计的核心特点是:
- 以“检测实例”为主组织数据
- 以“检测节点”为明细展开
- 以“结构化指标 + 图片文件 + Pipeline 快照”支撑报告
- 通过 SQLite 和文件系统混合存储兼顾查询效率和图片落盘
对于后续报告模块,这套结构已经可以直接支持:
- 历史列表查询
- 单次检测报告生成
- 结果图展示
- 节点级指标展示
- 历史结果可追溯
+2 -2
View File
@@ -472,7 +472,7 @@
<ColumnDefinition x:Name="NavColumn" Width="0" />
<ColumnDefinition Width="{Binding ViewportPanelWidth}" />
<ColumnDefinition Width="{Binding ImagePanelWidth}" />
<ColumnDefinition Width="300" />
<ColumnDefinition Width="350" />
</Grid.ColumnDefinitions>
<!-- 左侧: 计划导航 (默认隐藏,点击CNC AccountingNumberFormatButton显示) -->
@@ -500,7 +500,7 @@
<!-- 右侧: 属性面板 -->
<Grid Grid.Column="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250*" />
<ColumnDefinition Width="350*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />