# 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 预处理后的先后顺序