PCB 拼版系统近期迭代复盘:大小拼跃迁、横直料重构与引擎打磨
最近一周主要干了四件事:大小拼从概念验证做到了完整方案闭环、横直料和花式拼的算法整个推倒重写了一版、PCS 到 SET 的枚举逻辑从固定配置改成了全组合搜索、以及前端 UI 和底层架构做了一轮精修。这篇文章是这段时间的复盘,重点聊聊几个踩过的坑和做过的技术决策。
一、大小拼:从「一刀两片」到完整方案闭环
这是单拼版模块变动量最大、业务价值最高的一条主线。
背景
单料号拼版中,当用户开启「含大小拼」开关后,引擎在 SET 上做直切分割,产生 WPnl-A(大面积)和 WPnl-B(小面积)两种工作板,分别排入不同数量的 PCS,以提升整体利用率。
之前的大小拼实现基本是概念验证级别:单一切割线一刀切出 A 和 B,各占一块固定区域。存在几个明显的短板------排布僵化(A 只有 1 片、B 只有 1 片,无法多片组合)、方向固定(WPnl 不能旋转 90°)、B 不可见(开料图上只能看到 A)、不可编辑(B 板的 PCS 排列只能在概念上计算,无法在编辑器中调整)。
多片组合排布:从 1+1 到 M+N
核心突破是把一刀两片改成了多片组合排布。算法枚举 {cutColsA, cutColsB} 和 {cutRowsA, cutRowsB} 的组合,找到最优的填满方案。
水平布局时,A 和 B 在同一行按各自列数排列:[A][A][B][B][B],同行同高排列。垂直布局时,A 和 B 按各自行数在同列排列。枚举时以 A 的列数为主,用剩余宽度反算 B 能放多少片,两尺寸都受工作板尺寸范围约束,取总 PCS 最多、利用率最高的组合。这极大地释放了大小拼的潜力------不再是固定的一刀两片,而是根据板材尺寸自动适配最佳的 A/B 排列组合。
WPnl 旋转 90°:宽高互换多放一片
大小拼中的 WPnl 允许旋转 90° 后,宽高互换能产生更多排列可能。比如 Wpnl-A 尺寸 406×457,Wpnl-B 尺寸 304×406,正常方向放不下时,把 B 旋转 90° 让宽度变成 406,或者 A 旋转让宽度变成 457,宽高互换后可能刚好紧凑排列,甚至多放一片。
算法对每种尺寸组合分别尝试正常方向和旋转 90° 方向,自动选取最优。
引擎合并:代码减半
大小拼引擎中的正常方向和旋转方向原本是两套几乎重复的代码,重构后合并为一个循环------用 boolean[] rotStates = {false, true} 遍历正常和旋转两种状态,共用同一套布局逻辑。代码量从 346 行缩到 160 行。
WPnl-B 完整闭环
这是大小拼的收尾工作------让 WPnl-B 在前端各个模块中都有完整呈现。之前拼版图只能看 WPnl-A,现在加了 A/B 切换按钮自由切换视图;开料图之前 A 单色渲染、B 不可见,改成分组渲染,B 用橙色块显示,切割线跨组绘制;编辑器之前不支持 B,现在 A/B 双向编辑加独立撤销栈;配色也做了区分,A=蓝、B=橙黄。
开料图分组渲染是前端最复杂的变更。水平布局时每组 = colsA 个 WPnl-A + bPerGroup 个 WPnl-B,组内水平排列,板满后换行再排下一组,切割线在每组内部正确绘制。垂直布局同理。关键修复是去掉了单行限制,仅凭高度比例判断布局类型,使多行多列的大小拼开料图正确渲染,不再出现 WPnl 超出大板边界的问题。
二、横直料和花式拼的重写
这是改动最密集、也是来回最多的一块。
先搞清楚两个概念
之前花式拼和横直料混在一起,变量名也互相套用,调试的时候经常搞混。这次做了彻底的分离:
- 花式拼 工作在 WPnl 内部,影响的是 SET 在 WPnl 上的排列方式。包括错位拼(奇数行偏移半宽)和旋转交替(相邻 SET 一个正常方向、一个转 90°)。开关是
enableStaggered。 - 横直料 工作在 Sheet 层面,影响的是 WPnl 在大板上的排列方式。部分行的 WPnl 保持正常方向,部分行旋转 90°,目标是把大板尽量填满。开关是
enableHvMaterial。
为什么推倒重来
横直料第一版其实已经写完了,后端生成逻辑加前端定制化的开料图渲染(正常区和旋转区分开画、余料分级标注),代码量也不小。
但测试的时候发现一个硬伤------某些板材尺寸下,开料图上的 WPnl 会画出大板边界。根因是切割行/列数用的是 WPnl 的最小尺寸算的,而实际占位应该取正常方向和旋转方向的最大尺寸。这个 bug 本质上是数据结构设计的问题,修的话要改好几个地方的坐标计算。
当时做了一个判断:与其在这个架构上打补丁,不如撤回重来。于是 git revert 了整块代码,保留 DTO 里的字段做框架占位,然后重新设计了数据流。
重写后的版本,花式拼放在 tryCompactPlace 和 optimizeBySizeTable 两条路径里分别实现------紧凑路径里枚举正常行和旋转行的组合,表驱动路径里反算 SET 在目标 WPnl 尺寸内的最优排列。横直料则集中在 Sheet 层,分区排布。去重的 key 也加了 :STAG 和 :HV 后缀,避免不同策略产生的方案被误去重掉。
两个值得记的 Bug
花式拼功能完善后又踩了俩坑。
第一个 :minUtilization 提前 return。tryCompactPlace 里,纯单向方案利用率不达标时直接 return null 了,问题在于横直料和花式拼的代码写在这个 return 的下边------纯单向不达标根本没机会执行优化策略。开关明明是开着的,但策略被静默跳过了。排查这个花了不少时间,因为现象是"横直料没有效果",根本不会往利用率的条件判断上去想。修法是把这个 return 下移,只包裹纯单向的 variant 生成,横直料和花式拼自己独立判断利用率。
第二个 :折断边颠倒引起的 SET 图重叠。autoReverseBreakaway 会交换上下、左右的折断边来寻找更紧凑的 SET 配置。问题是交换之后生成了新的折断边组合,但 SET 的实际宽高没有同步更新,导致前端渲染时 PCS 位置用的折断边值跟实际容器尺寸不一致,SET 图上出现了重叠。修法是交换折断边时同步更新对应的宽高。
三、PCS→SET 枚举引擎
PCS 到 SET 这一层之前只按固定的行列数算一个配置。比如 100×150 的 PCS,固定 4×3 就出一个 460×540 的 SET。
实际场景里,PCS 的排列组合远不止一种。不同的行列数会对应不同的 SET 宽高,而 SET 尺寸又直接影响后续 WPnl 的利用率。改为枚举所有合法的行列组合后,同样的 PCS 可能产生十几种不同的 SET 尺寸,全部受 SET 最小/最大尺寸约束过滤,取利用率最高的进入下游管线。
折断边的自动颠倒也在这一层起作用------上下折断边互换会产生不同的 SET 长度,配合行列枚举可以覆盖更多有效组合。
PDF 导出这边做了一些配套优化:SET 图的每个 PCS 中央画了三角旗方向标识(和 Canvas 上的 F 字母标记对应的逻辑),大小拼的 A/B 区域在排版图上左右并排,以及解决了一个老问题------嵌入了微软雅黑字体文件到 Docker 镜像,PDF 里的中文终于不是方框了。
四、WPnl 尺寸序列:从被动过滤到主动驱动
PCB 工厂通常有固定的 WPnl 规格表------比如 406×457、"18寸标准板" 等。拼版结果的可制造性取决于 WPnl 尺寸是否在工厂已有的治具/工艺能力范围内。
之前的实现是「事后过滤」模式:先自由枚举所有可能的 WPnl 尺寸,然后过滤出表中存在的尺寸,丢弃不匹配的。这意味着大量计算被浪费了。
重构做了两阶段改造。第一阶段建立过滤机制,从模板参数解析尺寸表,WPnl 计算后查表匹配(±5mm 容差),不在表中就跳过。但发现过滤位置不对------最初放在 tryCompactPlace 内部,只覆盖紧凑排布路径,常规方案、大小拼、花式拼、横直料方案的 WPnl 都没有被校验。修复方式是把过滤统一放到 singleOptimize 末尾,覆盖所有方案类型,大小拼时 A 和 B 都必须匹配。
第二阶段是核心翻转------从「过滤」改为「凑表值驱动生成」。不再自由枚举再过滤,而是直接遍历表中尺寸作为目标,反算 PCS 排列。给定目标 WPnl 尺寸,在这个尺寸内能放多少行多少列 PCS,留边等于表尺寸减去 PCS 网格,精确命中目标尺寸而非 ±5mm 模糊匹配。验证结果中,100×150mm PCS + spacing=0.8 精确命中了 406×457 和 304×406 两个工厂标准尺寸。
从过滤模式到驱动模式的转变,本质上是从"算完再筛"变成了"靶向计算",零浪费、全覆盖。
五、阻抗条放置:五次位置演化
阻抗条是 PCB 制造中用于阻抗测试的工艺条,需要在 WPnl 上占一个位置。它的放置看似简单,实际经历了 5 次位置调整:
scss
底部(旧) → 居中(避免SET重叠) → 工艺边(避免SET重叠)
→ 行空隙(最优) → 间距计算修复
第一次放在 WPnl 底部工艺边,但会和某些大尺寸 SET 重叠。第二次改居中,在行间隙中放置,但破坏了 SET 的网格对齐。第三次放回工艺边。第四次最终确定最优方案------放在 SET 行空隙中央,需要前后端协同:后端以 midRow 为界分裂 SET,下半部下移为阻抗条腾空隙;前端通过 items 的 Y 值找最大空隙,阻抗条绘制在空隙精确中央。第五次修复了间距计算,多条阻抗条的总高度需要包含 spacing。
一个"放哪里"的问题,需要前后端协同加多轮验证才能确定最优方案。工程中没有一蹴而就的答案。
六、方向标记:从文字到矢量 F 字母
PCS 上的方向标记经历了三次进化:
css
▲▼◀▶ 文本符号 → Canvas 三角旗 → F 字母矢量图
最初用 Unicode 文本符号,简单但无法表达旋转加镜像。三角旗方案(旗面加旗杆组合)支持旋转,但辨识度不够。
最终方案是 F 字母矢量------F 的开口天然指示方向(开口向上=UP、向右=RIGHT、向下=DOWN、向左=LEFT)。旋转 90°/270° 时 F 整体旋转,开口方向自动跟随。镜像时 F 水平翻转加红色绘制,清晰区分正常和镜像状态。
F 字母的 Canvas 绘制就是竖线加上横线加中横线,参数 s 基于 item 尺寸和 scale 自适应缩放。后续又做了三次精细调整:上下横线间距增大避免粘连、整体放大两次(size 从 0.38 到 0.50 再到 0.65)。
七、架构层面的修正
开关逻辑的调整
WPnl 尺寸序列、阻抗条、铜箔开料、PCS→SET 枚举这四个高级功能,之前的实现方式是"开关关了就把对应数据删掉"。这导致用户关掉开关再打开,自己填的数据全没了。而且前端的删除行为和后端的判断逻辑不一致,经常出现数据传过去了但因为开关状态不匹配而被后端忽略的情况。
改成前后端各管各的------前端无论开关状态都原样发送数据,后端新增对应的 enableXxx 字段来判断是否启用。开关只是控制"用不用这个功能",不应该干预数据本身。
参数模板的完善
参数模板里很多字段之前没有映射到单拼页面------留边范围、WPnl 尺寸限制、花式拼/大小拼/横直料的开关状态、AB 拼的旋转镜像参数、铜箔开料的留边和间距、PCS 四边折断边、阻抗条列表等等。这次一次补全了,选了模板之后页面上所有控件自动填充,不用再手动改参数。
八、前端 UI 的调整
单拼页做了一次比较大的布局调整。参数面板之前是平铺的,折叠起来占空间,展开就挤到右边的图。现在改成卡片式(带阴影和圆角),折叠时彻底隐藏,为三图区域腾空间。Set 宽度和长度从参数组里拆出来放到了尺寸留边区,更符合填写逻辑。
三图的顺序调成了 SET 图(左)→ 拼版图(中)→ 开料图(右),从左到右刚好是从 PCS 级到工作板级再到大板级,逻辑上顺了很多。
编辑器加了中裁设置和交替拼。中裁支持水平/垂直方向加槽宽和行数,留边不够的时候会给红色警告。交替拼可以按行或列统一对偶数行做旋转或镜像。Canvas 编辑模式也新增了裁切线的渲染------半透明底色加双向箭头引线。
全局样式统一了一轮------顶部导航高度缩小、表格表头字重减了一点、卡片阴影微调、加了骨架屏 token 和空的空状态样式。不显眼,但整体会整洁不少。
这一周做的都不是那种一眼就能看到的大功能,更多是打磨和补漏------把大小拼从概念验证做到生产可用、把横直料的逻辑推倒理顺、把 PCS 层从不灵活的单一模式改成完整的搜索空间、把架构里不一致的地方统一起来。拼版引擎的竞争力最终不靠单个惊艳算法,而是靠这些细节堆出来的。