1
1个像素代表几微米
值越小,放大倍率越大
即:imgResolution (μm/pixel) ↓ → 同物理尺寸对应更多像素 → 放大倍率 ↑ → 细节更精细。
2
Zoffset
元件级 zoffset → BaseZ
com_zoffset 是每个元件所在 FOV 的真实 Z 高度,跟全局填的 ZOffset 做差:
差值(mm) ÷ 分辨率(mm/层) = 层偏移量
然后用这个偏移修正算法 BaseZ------从基准层往上或往下挪几个像素层,保证搜索起点对准实际位置。
就这么简单。
对,因为板子有翘曲,不同位置的元件实际 Z 高度不一样。
全局 ZOffset = 1.5 ← 用户填的,假设整板是平的
实际:
左边翘起来了 → 左边元件实际 Z = 1.8 → component_zoffset = 1.8
右边塌下去了 → 右边元件实际 Z = 1.2 → component_zoffset = 1.2
如果全用全局 1.5 算 BaseZ,左边元件搜偏低了(找不着基准层),右边元件搜偏高了(也找不着)。
所以路径规划给每个元件算自己的 zoffset,一键配置时逐个换算成各自的 BaseZ,消除翘曲影响。
3
1. Shape(形状)
PCB 上一切可检测的图形元素,是系统的最小操作单元。
Shape = 图形(能画出来) + 检测数据(能算出来)
- Board:基板,PCB 的物理边界
- PadCircle:检测窗,划定一个圆形/椭圆形检测区域
--
2. 坐标
同一位置有两套坐标表达:
| 坐标类型 | 含义 | 类比 |
|---|---|---|
| 物理坐标 (StartX, EndX, mm) | PCB 上的真实位置 | 现实世界的位置 |
| 像素坐标 (PixelStartX, PixelEndX, px) | 图像上的位置 | 照片上的位置 |
通过 Matrix(变换矩阵)互相转换。
3. Status(状态)
每个 Shape 在检测过程中会经历一系列阶段,用位标志叠加表示:
待检测 → 扫描中 → 扫描完 → 重建中 → 重建完 → 检测中 → 结果(OK/NG)
| 状态 | 含义 |
|---|---|
ReadyToExecute |
准备好了,等待执行 |
Scaning |
正在拍照扫描 |
Reconstructing |
正在 CT 算法重建 3D 图像 |
Detecting |
正在分析检测 |
ResultOK |
检测通过 ✅ |
ResultNG |
检测不通过 ❌ |
UI 根据 Status 自动变色
4. CT(计算机断层扫描)
用 X 光从不同角度拍摄,重建出物体内部的 3D 图像。
三个关键参数:
| 参数 | 含义 | 单位 |
|---|---|---|
| imgResolution | 分辨率,每个像素代表多大 | mm/pixel |
| zoffset | Z 轴偏移,焦平面上下移动 | mm |
| 层数 | 重建多少层切片 | 层 |
imgResolution 越小 → 放大倍率越高 → 看得越精细
zoffset 正 → 焦平面上移;负 → 焦平面下移
6. Drill(钻孔)
PCB 上需要检测的孔位,分两种:
| 类型 | 说明 |
|---|---|
| 通孔 (Through Hole) | 从上面钻穿到下面 |
| 背钻 (Backdrill) | 钻到某一层就停,不钻穿 |
背钻根据方向再分:上钻 (从上面钻)、下钻 (从下面钻)、上下都钻。
DrillStandard = 同一个钻孔规格 { 深度, 半径 },同类钻孔归为一组,共用检测算法。
概念关系总图
Board(基板,PCB 物理边界)
│
├─ Component(元件)
│ └─ 一组相关的检测窗
│
├─ PadCircle(检测窗)
│ ├── 坐标(物理 ↔ 像素)
│ ├── CT 参数(分辨率 + Z偏移)
│ ├── Status(检测生命周期状态 → 驱动颜色)
│ ├── AlgorithmViewShapes(子形状)
│ └── 检测结果
│
├─ DrillData(钻孔数据)
│ └── 孔类型(通孔/背钻)+ 钻深 + 层厚
│
└─ Template(检测模板)
└── 复用配置到同类 Shape
一句话总结
Shape 是基本单元,通过 CT 重建 (分辨率 + Z 偏移)获取内部图像,经历 Status 生命周期(扫描→重建→检测→结果),UI 随状态)。
3
三个全局 SemaphoreSlim,全部并发数 = 1:
_reconstructLock (1) → 重建:engine.setupResolver → 提交投影 → pollReconDone
_extractGlobalLock (1) → 提取:CT 体数据从 C++ 引擎拉到内存
_detectGlobalLock (1) → 检测:ExecuteAlgoProcess 调 C++ 算法
为什么都设成 1?
C++ 重建/提取/算法引擎底层不是线程安全的,多个 Job 同时调会崩。所以每个阶段同一时刻只允许一个 Job 在跑。
实际跑法:
扫图完了之后,10 个 Job 并行跑,但每到重建/提取/检测这仨环节,谁先抢到锁谁先进,剩下的 await 排队:
Job1 重建 ──┐
Job2 重建 ──┤ → _reconstructLock.WaitAsync() → 抢到的先跑,其他排队
Job3 重建 ──┘
↓
Job1 提取 → Job2 提取 → Job3 提取 (_extractGlobalLock 排队)
↓
(保存体数据 和 检测 并行)
Job1 检测 → Job2 检测 → Job3 检测 (_detectGlobalLock 排队)
保存体数据不依赖 C++ 引擎,所以不和检测抢锁,直接并行跑。
跟同事一句话:C++ 引擎不线程安全,三个全局锁各管一个阶段,保证同一时刻只有一个 Job 在调引擎。