386 lines
11 KiB
Plaintext
386 lines
11 KiB
Plaintext
# MainShell/Process/DieTransfer/Planning 说明与代码审阅
|
||
|
||
## 1. 范围
|
||
本次说明覆盖 `MainShell/Process/DieTransfer/Planning` 目录下的规划相关实现,包含以下文件:
|
||
|
||
- `DieTransferPathGenerator.cs`
|
||
- `DieTransferPlanningContext.cs`
|
||
- `DieTransferPathRequest.cs`
|
||
- `DieTransferPathPlan.cs`
|
||
- `DieTransferPathRegionPlan.cs`
|
||
- `DieTransferPathStep.cs`
|
||
- `DieTransferRegion.cs`
|
||
- `DieTransferRow.cs`
|
||
- `PadTransferRow.cs`
|
||
- `DieTransferRowDirection.cs`
|
||
- `SubstrateRowDirectionStrategy.cs`
|
||
- `IDieTransferPathGenerator.cs`
|
||
|
||
## 2. 模块定位
|
||
该模块是 Die Transfer 的“规划层”,职责不是执行运动,而是把候选 Die 集合和候选 Pad 集合转换成一份可执行的路径计划。
|
||
|
||
输入侧负责提供:
|
||
|
||
- 候选 Die 集合
|
||
- 候选 Pad 集合
|
||
- Die 区域
|
||
- Substrate 区域
|
||
- 路径生成策略
|
||
- 是否跳过 NG Die
|
||
- 基板行方向策略
|
||
|
||
输出侧负责提供:
|
||
|
||
- 已生成的配对步骤 `Steps`
|
||
- 过滤后的可用 Die / Pad 数量
|
||
- 剩余未配对的 Die / Pad
|
||
- 剩余区域的行结构和区域边界
|
||
|
||
整体设计是合理的:规划逻辑与运动执行解耦,后续无论手动模式还是自动流程,都可以复用同一套路径生成器。
|
||
|
||
## 3. 核心对象职责
|
||
|
||
### 3.1 `DieTransferPathRequest`
|
||
路径规划输入对象。
|
||
|
||
关键字段:
|
||
|
||
- `DieCandidates`:候选 Die 集合
|
||
- `PadCandidates`:候选 Pad 集合
|
||
- `DieRegion`:Die 过滤区域
|
||
- `SubstrateRegion`:Pad 过滤区域
|
||
- `TransPathType`:路径生成模式
|
||
- `SubstrateRowDirectionStrategy`:Pad 行遍历方向
|
||
- `SkipNgDie`:是否跳过 NG Die
|
||
|
||
默认值设置比较合理:
|
||
|
||
- 默认模式是 `Sequence`
|
||
- 默认 Pad 行方向是 `AllPositive`
|
||
- 默认跳过 NG Die
|
||
|
||
### 3.2 `DieTransferPlanningContext`
|
||
这是当前模块最核心的中间对象。它的作用不是对外暴露,而是把 request 预处理成“可直接生成路径”的内部上下文。
|
||
|
||
它主要做三件事:
|
||
|
||
1. 把 `RegionModel` 转成内部使用的 `DieTransferRegion`
|
||
2. 把原始候选点整理成按行分组的 `DieTransferRow` / `PadTransferRow`
|
||
3. 根据行方向规则,把每一行的点组织成确定顺序
|
||
|
||
换句话说,真正的“排序、过滤、按行组织”都不在 generator 本体里,而是在 context 创建阶段完成。
|
||
|
||
### 3.3 `DieTransferPathGenerator`
|
||
路径生成入口。
|
||
|
||
对外现有四个方法:
|
||
|
||
- `Generate()`:返回 `DieTransferPathPlan`
|
||
- `GenerateByRegion()`:返回 `DieTransferPathRegionPlan`
|
||
- `GenerateByCandidates()`:根据传入的 Pad 集合和 Die 集合生成 `DieTransferPathRegionPlan`
|
||
- `GenerateByRows()`:根据传入的 `PadTransferRow` / `DieTransferRow` 直接生成 `DieTransferPathRegionPlan`
|
||
|
||
其中 `Generate()` 实际只是转调 `GenerateByRegion()`,只是把返回类型收窄成基类。因此:
|
||
|
||
- 如果调用方只关心步骤列表和统计信息,用 `Generate()` 即可
|
||
- 如果调用方还关心剩余 Die / Pad 和剩余区域,应该使用 `GenerateByRegion()`
|
||
|
||
两个新增入口的定位如下:
|
||
|
||
- `GenerateByCandidates()`:适合调用方手里只有原始候选点集合,但不想自己组装 `DieTransferPathRequest` 的场景。该方法内部仍会走现有的 request/context 预处理逻辑,生成的路径统一从返回结果的 `Steps` 中获取。
|
||
- `GenerateByRows()`:适合调用方已经完成按行分组、方向整理、NG 过滤策略配置的场景。该方法不会重新构建 planning context,而是直接把传入行展开为有序 Die / Pad,再生成步骤和剩余区域。
|
||
|
||
### 3.4 `DieTransferPathPlan` / `DieTransferPathRegionPlan`
|
||
`DieTransferPathPlan` 表示基础规划结果,包含:
|
||
|
||
- `TransPathType`
|
||
- `Steps`
|
||
- `AvailableDieCount`
|
||
- `AvailablePadCount`
|
||
- `GeneratedStepCount`
|
||
|
||
`DieTransferPathRegionPlan` 在此基础上扩展了剩余信息:
|
||
|
||
- `RemainingDies`
|
||
- `RemainingPads`
|
||
- `RemainingDieRows`
|
||
- `RemainingPadRows`
|
||
- `RemainingDieRegion`
|
||
- `RemainingSubstrateRegion`
|
||
|
||
这意味着该模块不仅能生成一次路径,还能给下一轮继续规划留下上下文。
|
||
|
||
### 3.5 `DieTransferPathStep`
|
||
单步配对结果对象,记录:
|
||
|
||
- 步序号 `StepIndex`
|
||
- Die 行列和坐标
|
||
- Pad 行列和坐标
|
||
- 当前路径类型
|
||
|
||
这个对象设计得比较直接,后续 UI 或执行层都容易消费。
|
||
|
||
## 4. 生成方法总流程
|
||
路径生成的主流程如下:
|
||
|
||
1. 调用方构造 `DieTransferPathRequest`
|
||
2. `DieTransferPathGenerator.GenerateByRegion()` 校验 request 非空
|
||
3. `DieTransferPlanningContext.Create(request)` 生成规划上下文
|
||
4. 从 context 中取出有序的 Die 列表和 Pad 列表
|
||
5. 按 `TransPathType` 选择生成策略
|
||
6. 生成 `DieTransferPathStep` 列表
|
||
7. 计算未参与配对的剩余 Die / Pad
|
||
8. 计算剩余行结构和剩余区域
|
||
9. 组装 `DieTransferPathRegionPlan` 返回
|
||
|
||
可以把它理解为两段式流程:
|
||
|
||
- 第一段:预处理输入,得到稳定顺序
|
||
- 第二段:按策略做 Die 与 Pad 的一一配对
|
||
|
||
新增的两个入口,本质上只是改变“输入落点”,不改变“路径生成算法”:
|
||
|
||
- `GenerateByCandidates()`:从候选集合进入,再复用 `GenerateByRegion()` 的完整流程
|
||
- `GenerateByRows()`:跳过 request/context 构建,直接从行对象展开为有序点集合后进入配对阶段
|
||
|
||
## 5. 预处理阶段讲解
|
||
|
||
### 5.1 区域过滤
|
||
`DieTransferPlanningContext.Create()` 首先把 `request.DieRegion` 和 `request.SubstrateRegion` 转成 `DieTransferRegion`。
|
||
|
||
`DieTransferRegion.Contains(row, column)` 负责判断点是否落在区域内。
|
||
|
||
当前语义如下:
|
||
|
||
- 区域不为空:按区域过滤
|
||
- 区域为空:视为不过滤,所有候选点均可参与规划
|
||
|
||
这一点在实现上是清晰的,但最好在文档或接口注释中明确说明,因为这是一个业务约定。
|
||
|
||
### 5.2 按行分组
|
||
Pad 和 Die 都不是直接整体排序,而是先按 `Row` 分组,再在每一行内部决定遍历方向。
|
||
|
||
Pad 通过 `CreatePadRows()` 生成 `PadTransferRow`:
|
||
|
||
- 先过滤空对象和区域外点
|
||
- 按 `Row` 分组
|
||
- 每组按 `Column` 升序保存原始集合
|
||
- 再根据策略决定当前行最终读取方向
|
||
|
||
Die 通过 `CreateDieRows()` 生成 `DieTransferRow`:
|
||
|
||
- 先过滤空对象和区域外点
|
||
- 按 `Row` 分组
|
||
- 每组按 `Column` 升序保存
|
||
- 行方向固定按蛇形方式决定
|
||
- `SkipNgDie` 在行对象中生效
|
||
|
||
### 5.3 行方向规则
|
||
Pad 的行方向由 `SubstrateRowDirectionStrategy` 决定:
|
||
|
||
- `AllPositive`:所有行都从小列到大列
|
||
- `AllNegative`:所有行都从大列到小列
|
||
- `Serpentine`:奇数行正向,偶数行反向
|
||
|
||
Die 的行方向当前固定采用蛇形规则,因为 `CreateDieRows()` 被调用时 `useSerpentineDirection` 固定传入 `true`:
|
||
|
||
- 奇数行正向
|
||
- 偶数行反向
|
||
|
||
这意味着当前设计里,Die 的遍历顺序比 Pad 更“强约束”,调用方无法在 request 中单独配置 Die 行方向。
|
||
|
||
### 5.4 可用点提取
|
||
在分组完成后,context 通过以下方法展平成最终有序列表:
|
||
|
||
- `GetOrderedPads()`
|
||
- `GetOrderedDies()`
|
||
|
||
这两个方法都会按 `RowIndex` 升序遍历各行,再拼接每行的可用点序列。
|
||
|
||
其中 Die 侧的“可用”还额外受 `SkipNgDie` 影响:
|
||
|
||
- `SkipNgDie = true` 时,`DieStatus.Ng` 会被排除
|
||
- `SkipNgDie = false` 时,所有 Die 都会参与规划
|
||
|
||
因此,后续真正参与生成的并不是 request 里的原始候选集合,而是“经过区域过滤、方向排序、NG 过滤后的线性序列”。
|
||
|
||
## 6. 两种生成方法讲解
|
||
|
||
### 6.1 顺序生成 `BuildSequenceSteps`
|
||
这是最直接的策略。
|
||
|
||
处理方式:
|
||
|
||
1. 先得到 `orderedDies` 和 `orderedPads`
|
||
2. 取 `Math.Min(dies.Count, pads.Count)` 作为可生成步数
|
||
3. 第 `i` 个 Die 与第 `i` 个 Pad 直接配对
|
||
4. 调用 `CreateStep()` 生成步骤对象
|
||
|
||
伪代码如下:
|
||
|
||
```text
|
||
stepCount = min(orderedDies.Count, orderedPads.Count)
|
||
for i in [0 .. stepCount - 1]
|
||
step = CreateStep(i + 1, orderedDies[i], orderedPads[i])
|
||
steps.Add(step)
|
||
```
|
||
|
||
该策略的特点是:
|
||
|
||
- 完全依赖预处理后的顺序
|
||
- 算法简单,复杂度低
|
||
- 结果稳定,便于追溯
|
||
- 不考虑实际几何距离,只考虑排好序后的对应关系
|
||
|
||
适用场景:
|
||
|
||
- 生产节拍更看重顺序一致性
|
||
- 上游已经保证 Die / Pad 序列一一对应
|
||
- 希望路径可预测、容易校验和复现
|
||
|
||
### 6.2 最近生成 `BuildNearestSteps`
|
||
这是更偏几何优化的策略。
|
||
|
||
处理方式:
|
||
|
||
1. 先复制一份 `remainingDies`
|
||
2. 按 Pad 的既定顺序逐个处理每个 Pad
|
||
3. 在当前剩余 Die 中,找出距离该 Pad 最近的 Die
|
||
4. 生成一步配对后,把该 Die 从 `remainingDies` 中移除
|
||
5. 继续处理下一个 Pad,直到 Die 用完或 Pad 遍历完毕
|
||
|
||
伪代码如下:
|
||
|
||
```text
|
||
remainingDies = orderedDies
|
||
for each pad in orderedPads
|
||
if remainingDies is empty
|
||
break
|
||
|
||
die = SelectNearestDie(pad, remainingDies)
|
||
steps.Add(CreateStep(stepIndex, die, pad))
|
||
remainingDies.Remove(die)
|
||
stepIndex++
|
||
```
|
||
|
||
这个策略不是全局最优匹配,而是“按 Pad 顺序逐个贪心选择最近 Die”。
|
||
|
||
它的优点是:
|
||
|
||
- 实现简单
|
||
- 对局部移动距离有优化作用
|
||
- 比完全顺序配对更贴近空间位置
|
||
|
||
它的限制也很明确:
|
||
|
||
- 它不是匈牙利算法一类的全局最优解
|
||
- 前面 Pad 的选择会影响后面 Pad 的可选 Die
|
||
- 最终总路程不一定最短
|
||
|
||
因此,这个“最近策略”更准确的表述应该是:
|
||
|
||
- 按 Pad 顺序执行的局部贪心最近匹配
|
||
|
||
### 6.3 最近 Die 的选取规则 `SelectNearestDie`
|
||
`SelectNearestDie()` 遍历当前所有剩余 Die,并选择距离最小的那个。
|
||
|
||
当前距离计算公式为:
|
||
|
||
```text
|
||
(die.X - pad.X)^2 + (die.Y - pad.Y)^2
|
||
```
|
||
|
||
这里返回的是平方距离,而不是开方后的欧氏距离。对于“比较谁更近”这个问题,平方距离是等价的,且计算更省。
|
||
|
||
需要注意两点:
|
||
|
||
1. `CalculateDistance()` 这个命名不够精确,实际更像 `CalculateSquaredDistance()`
|
||
2. 当距离相等时,当前代码按“先遍历到谁就保留谁”处理
|
||
|
||
第二点尤其需要讲清楚:
|
||
|
||
- 当前 tie-break 不是直接按 `Row`、`Column` 比较
|
||
- 它依赖 `remainingDies` 的当前顺序
|
||
- 而 `remainingDies` 的顺序,又来自 Die 行的蛇形组织结果
|
||
|
||
也就是说,当两个 Die 与同一个 Pad 等距时,最终胜出者本质上由“Die 预处理后的线性顺序”决定。
|
||
|
||
## 7. 剩余结果的生成方式
|
||
路径步骤生成完成后,`GenerateByRegion()` 还会继续做一层“剩余状态提取”。
|
||
|
||
对新增入口来说,剩余结果生成规则分别是:
|
||
|
||
- `GenerateByCandidates()`:与 `GenerateByRegion()` 完全一致,因为内部就是组装 request 后复用区域入口
|
||
- `GenerateByRows()`:保留调用方传入行对象中的 `RowIndex`、`Direction`、`SkipNgDie` 等行级配置,只过滤掉本轮已使用点位,再构造 `RemainingPadRows` / `RemainingDieRows`
|
||
|
||
### 7.1 剩余 Die / Pad 计算
|
||
通过 `CreateRemainingPads()` 和 `CreateRemainingDies()`:
|
||
|
||
- 从步骤列表中提取已经使用过的 `(row, column)` 键
|
||
- 对原有有序集合做差集
|
||
- 得到未参与本轮规划的点
|
||
|
||
这里使用 `(row, column)` 作为唯一键,说明当前模块默认一个点位由行列唯一标识。
|
||
|
||
### 7.2 剩余行结构重建
|
||
剩余 Pad / Die 并不只是简单返回列表,还会重新生成:
|
||
|
||
- `RemainingPadRows`
|
||
- `RemainingDieRows`
|
||
|
||
这非常有价值,因为后续如果要做二次规划,不需要重新整理全部数据结构。
|
||
|
||
### 7.3 剩余区域重建
|
||
`CreateRegionFromPads()` 和 `CreateRegionFromDies()` 会根据剩余点重新计算最小包围区域:
|
||
|
||
- 最小行
|
||
- 最小列
|
||
- 最大行
|
||
- 最大列
|
||
|
||
如果没有剩余点,则返回 `null`。
|
||
|
||
这个设计能让调用方快速判断:
|
||
|
||
- 当前是否已全部规划完成
|
||
- 还剩下哪一片区域未处理
|
||
|
||
## 8. 代码审阅结论
|
||
|
||
### 8.1 优点
|
||
该模块的优点比较明确:
|
||
|
||
- 职责单一,专注于规划而非执行
|
||
- request / context / result 三层结构清楚
|
||
- 顺序策略和最近策略边界明确
|
||
- 剩余区域输出增强了复用性
|
||
- 代码整体可读性较好,扩展入口也比较清晰
|
||
|
||
### 8.2 需要注意的点
|
||
当前实现没有明显功能性缺陷,但有几个维护层面的注意点:
|
||
|
||
1. `CalculateDistance()` 实际是平方距离,建议改名或补注释
|
||
2. 最近策略是局部贪心,不是全局最优匹配,建议在文档中明确
|
||
3. 等距时的选择规则依赖 Die 预处理顺序,建议明确写入说明
|
||
4. `AvailableDieCount` / `AvailablePadCount` 表示过滤后的可用数量,不是原始候选数
|
||
5. 区域为 `null` 时表示不过滤,这属于隐式约定,建议补充说明
|
||
6. `Generate()` 返回基类,但实际内部生成的是区域计划对象,调用方需明确自己是否需要剩余区域信息
|
||
|
||
### 8.3 可改进但不必立即重构的点
|
||
以下问题存在,但不建议为此做大改:
|
||
|
||
- 结果对象是可写模型,存在被外部修改的可能
|
||
- `DieTransferPathGenerator` 中保留了未被使用的 `OrderDies()` / `OrderPads()` 私有方法,可在后续清理
|
||
- 命名空间仍统一使用 `MainShell.Process`,与目录层次不是完全一一对应,但符合当前仓库惯例
|
||
|
||
## 9. 总结
|
||
Planning 模块的核心思路可以概括为一句话:
|
||
|
||
先把候选 Die / Pad 依据区域、行方向、NG 过滤整理成稳定顺序,再按“顺序配对”或“局部贪心最近配对”生成 `DieTransferPathStep` 列表,最后补充剩余点和剩余区域信息。
|
||
|
||
如果从可维护性看,这套实现已经具备较好的基础。当前更需要的是把策略语义讲清楚,而不是立即做结构性重构。尤其是以下三点,建议作为后续文档或注释的固定说明:
|
||
|
||
- 最近模式是局部贪心,不是全局最优
|
||
- 距离比较使用平方距离
|
||
- 等距时遵循 Die 预处理后的先后顺序
|