# 进度与交接 更新:2026-09-06 **第二十四轮(I²C 干线改走 Inner2)**。 详见设计文档 **§26.20**(上一轮是 §26.17 / §26.18 / §26.19)。 --- ## ⭐ 新会话从这里开始 ### 一句话现状 原理图 **11 页全部验证通过**。 下板 PCB1:摆位 342 件、扇出、15 块铺铜、五路功率级 195 段全部下发并回读过。 这一轮把 **I²C 的跨板长干线挪到了 Inner2**(302 mm 内层,§26.20)—— 目的是**腾外层通道给布不通的线**,然后立刻跑了第三遍信号布线(`--ripup`)验证效果: | | 挪之前 | 挪之后 + 第三遍布线 | |---|---|---| | 已连通网络 | 148 | **163** | | 还欠连接 | 87 条 / 74 网络 | **68 条 / 59 网络** | | DRC 连接性错误 | 1044 | **855** | | 板上铜 | 3196 线 / 1000 孔 | 3516 线 / 1084 孔 | 铺铜已重铺回读(Inner2 浮铜从 1312.8 mm² 降到 **0.2 mm²**,剩的那点是新布线蹭出来的碎屑)。 ### ⬅️ 下一步 **⛔ 那 74 条 DRC 间距错误的真因已查清(§26.21):漏跑 `pcb1_parts_dump.py`。** §26.18 重排的 44 个焊盘**导进 PCB 了,但缓存没重抓** ⇒ 后面两轮布线里 **33 个网络的铜连的是错信号**。已拆掉那 530 段 / 2482 mm / 138 孔并重布(跑完对一遍 DRC)。 ⚠️ **规矩:`pcb1_import.py --apply` 之后下一条命令就是 `pcb1_parts_dump.py`。** 中间插别的操作,后面全部几何和布线都建在沙子上,而**六道自检一条都不会响** (它们读的是同一份缓存,自洽得很)。 > 期间走过两条弯路,都记在 §26.21 末尾:**多边形焊盘不许按包围盒判** > (QFN 散热盘的盒子会套住邻脚,凭空报 21 处假「重叠」); > **DRC 的「间距」那一支不陈旧**(重载前后逐字相同,不能当旧数据糊弄)。 剩下 **68 条连接性**(59 个网络,大多各欠 1~2 条)。还能腾外层的两个大头: | 候选 | 现状 | 说明 | |---|---|---| | `BUCK_PG` | 已连通,**顶层 234 mm**、跨 121 mm | 剩下最大的一块外层占用,挪进 Inner2 能再腾一大片 | | `INA_ALERT` | 欠 2 条 | **试过,没跑通**:铜整片在顶层且挤在控制块,主碎片周围一个合法孔位都没有 | | `USB_DP/DM` | 各欠 2 条 | **不许进内层**(90 Ω 以 L2 为参考,§10.1),只能在外层解 | 常规循环(一步都不能省): /c/Python314/python -u .claude/pcb1_route_sig.py --refresh --ripup --push /c/Python314/python .claude/pcb1_pour.py --push --refresh /c/Python314/python .claude/drc_read.py ⚠️ **`pcb1_route_sig.py --ripup` 认「哪些铜是自己画的」靠覆盖判据,不是坐标比对** —— `pcb1_i2c_l3.py` 画的 **Inner2 干线**和 `pcb1_gnd_stitch.py` 的 6 个缝合孔它都不认得。 2026-09-06 实测跑完一轮 `--ripup` 之后 **55 段内层线一段没丢**,但这不是保证 —— 跑完对一遍 `_i2c_l3_snapshot.json`(或 `pcb1_i2c_l3.py` 不带参数跑一遍数碎片)。 ⚠️ **引擎的层数已经不是写死的 2 了**(2026-09-06 改):`pcb2_route_sig.Field` 现在按 障碍表里真正出现过的层建数组、`viafree` 取**全层**最小值。不这么改的话,板上一有 layer 16 的走线就 `KeyError: 16` 当场崩;就算滤掉,它打的新过孔也**不会**对内层干线 做净距判断(旧的 `viafree` 只看 Top/Bottom),孔会直接压在内层线上。 ⚠️ **`pcb1_route_pwr.py --push --verify` 是只 verify,不下发** —— `main()` 里 `--verify` 在 `--push` 之前 `return`,而它照样打「✅ 全部校核通过」, 只在最后一行回读里露馅(「195 段里没被实测铜盖住的 **195** 条」)。**要下发就只写 `--push`。** ⚠️ **`pcb_fanout.py --clean` 会把板上全部走线删光**,包括 `pcb1_route_pwr` 的 195 段。 所以「改了扇出」= 扇出三步 → **重铺铜 → 重跑 route_pwr** → 再 route_sig,一步都不能省。 ⏱ **实测速率**:`route_sig` 前两个网络(`SDA`/`SCL`,各挂 14 个焊盘横跨整板)要 **1516 s**, 之后 5 个网络只要 **68 s**。**别拿头几个网络外推总时长**;全程约 60~90 分钟。 它是**跑完才一次性下发**的 —— 中途停掉板上一根铜都不会变,但也等于全白跑。 --- ### ✅ 第二十四轮(2026-09-06,用户拍板):I²C 干线改走 Inner2(§26.20) `SDA`/`SCL` 是全板最长的两个网络(各 14 个焊盘、横跨整板), 既吃布线时间(1516 s)又占着外层最好的通道。用户拍板:**走内层,只挪跨板长干线**。 新工具 **`.claude/pcb1_i2c_l3.py`**(三层迷宫布线 Top / Bottom / Inner2)。 | | 改之前 | 改之后 | |---|---|---| | I²C 总铜长 | 596 mm,**内层 0** | 609 mm | | Inner2 | — | **302 mm** | | 顶层 / 底层 | 596 mm | 157 / 150 mm | | 连通性 | SCL 3 片 / SDA 4 片 | **一模一样** | **中桥那 13 mm 是唯一留在外层的一段**:`12V-SW+BR` 的中桥(x 61.5~72.6,喂 3.6 A) 是 Inner2 上东西两半唯一不能割的隔断,I²C 过去就必须横着割 ⇒ 只能外层跳。 (上一轮 `route_sig` 给 `SDA` 布的就是这个跳法,殊途同归。) 三条这一轮踩到的坑(细节全在 §26.20): 1. **`PEN_3V3` 决定的不是长度,是平面会不会被切出孤岛。** 1.8 的时候两根线在 `3V3-SW` 里围出 7.2 mm² 孤岛,**里面有 R415.2 的 +3V3 焊盘** —— 浮铜检查 / 焊盘覆盖检查 / 连接性 DRC **三样全都不响,只有数连通域看得见**。 堵孤岛重绕**不收敛**(7.2 → 25.4 → 26.8 → 32.5 mm²)。改成 **4.0**: 干线走 GND 底填,只在器件旁边**扎盲端**进平面。 2. **`expand()` 会静默丢掉最后一点上的过孔** —— 引擎 `_end_via` 的注释早写着,又踩一遍。 3. **`own`(本网铜放行)要按半线宽内缩**,过孔则根本不吃这条放行。 **顺带修掉一块和这次无关的浮铜**:板子南沿 y 0.5~9 那条 GND 底填(**1312.8 mm²**) 在 Inner2 上是孤立的,一个落脚点都没有 —— 它原本靠 x 115~116 那条 1.0 mm 缝接出去, 被上一轮信号布线的三个过孔的反焊盘掐断了。新脚本 **`.claude/pcb1_gnd_stitch.py`** 在南沿补了 6 个 GND 缝合孔。⚠️ 这一条**只有铺铜下发后 `verify()` 回读实测铜才量得出来**, 下发前的 `report()` 栅格不含别的铺铜,算出来 GND 是一整块。 **验收**:净距/拐角/孔距 ✅ · 平面完整性 ✅(+12V 1 块 / +3V3 2 块)· `pcb1_pour.py --push --verify` ✅ 实测和计划对得上 · DRC 里**没有一条**牵涉 SDA/SCL。 --- ### ✅ 这一轮 C(2026-09-06):8 A 的源只有 3 个过孔(§26.19) 第一次跑信号布线跑到 35/196 中止 —— 不是布线器的问题,是它报出来的失败把两个上游问题照了出来。 1. **`Rs6.2` → +12V 平面只有 3 个 Ø0.3 过孔 ≈ 3~4.5 A,而它要过 8 A。** 它是全板 +12V 平面**唯一的源**。这一条**四道验证一条都不管**:铺铜断面只量平面本身、 扇出只报「孔放下了没有」、连接性 DRC 只看「连上没有」、逃逸审计只看「出得去吗」。 ⇒ `pcb_fanout.py` 新增 **`FORCE_ARRAY`**(整片阵列钉死,逐个仍走全套净距校核), Rs6.2 钉 **12 个孔(≈12 A)**,摆在焊盘正南那片空地。 **规矩:>4 A 的「焊盘 → 平面」转换点,过孔数必须手写。** 2. **顺带把 U13 的 I²C 放了出来。** 原来那 3 个孔有两个甩到西边,两根 1 mm 引线 (端帽再往西探 0.5)压死了 U13 南排五个脚唯一的通道;`VINSW-TOP` 北柱又只差 0.379 mm。 12 个孔改朝南 + 北柱在 y>87 收窄到 x 1.5~5.3(3.8 mm → 8.4 A), U13 南边变成一整片 x 5.5~14 / y 91.6~96 的空地。 3. **扇出不对称又发作了一次**:`C%06.2` 的 GND 孔在 rot 0 的三路往东落到局部 (10.02,27.10)、 在 rot 180 的 CH2/CH3 往西落到 (6.75,27.10),正压在 eFuse 脚 14/15 的出线口上。 已加进 `_CH_FORCE_LOCAL`。 4. **剩下 10 个「被围死」不是板上的铜** —— 报出来的挡路物去 `pcb-copper.json` 里查不到, 是**审计自己给邻脚预留的出线段**。判据就是这个:**查得到才是真障碍**。 --- ### ✅ 这一轮 A(2026-09-06):v11 的两行排法把 8 A 输入链卡死了,已修(§26.17) §26.16-E 换 XT30 之后把 N-L 块改成两行,摆位的**本地检查六项全过**, 可是回去重画 §26.11 的顶层宽铜时第一刀就下不去: **`VIN_RAW` 从 J1 出来根本没有 3.49 mm 的路** —— XT30 rot 270 之后 GND 在北针 (13.5,74.5)、 VIN 在南针 (13.5,69.5),而 F1 卧着放和 GND 针同一行,中间又被 D1(17.5,69.5) 堵住, 实测通道只剩 **1.674 mm**(1 oz 外层约 4.5 A,这里要 8 A)。 > **这一类错误几何检查一条都报不出来** —— 不重叠、不越框、高度也够。 > 抓到它的是「照新坐标把顶层宽铜的多边形重推一遍」这个动作本身。 > ⇒ **摆位改完不能只看本地检查,必须把 §26.11 那三块多边形重推一次**。 三处改动(`pcb1_place.py` 的 N-L 表): | # | 改动 | 拿回了什么 | |---|---|---| | ① | **F1 卧 → 竖**(rot 0 → **90**),(17.5,74.5) 不动 | `F1.1` 落到 (17.5, **73.06**),正对 J1.2 那片空地 | | ② | **D1 (17.5,69.5) → (31.0,74.5)** rot 270 | 通道里最后一个障碍搬走,TVS 贴到漏极铜东缘 | | ③ | **C4/C5/C6 x 25 → 30.5,rot 0 → 180** | 让出 y 78.85~83.5 那条 **4.64 mm** 的 VIN_SW 西行干道 | 顶层三块宽铜重画后实测断面全过:RAW 颈 **3.50**(要 3.49)、FUSED 5.25、SW 干道 4.75、北柱 5.25 mm。 面积 59 / 30 / 263 mm²,各 1 个连通域,`check_top_clearance()` 零侵入。 **⚠️ 顺带抓到一个静默失效:`Rs6.2` 一个过孔都没打。** `12V-N` 为了让开 U13 的 Kelvin 脚,在西边咬了个缺口 `(0.5,93.0)-(12.5,101.5)`, 而 Rs6.2 的焊盘中心 (9.57,93.50) **正落在缺口里** ⇒ 扇出判成「不在铜面上」, **全板唯一的 8 A 源不接电源平面**。`--push` 照样报「失败 0 个」,连接性 DRC 也不响 (+12V 靠别的 55 个焊盘照样连通,只有**源**那一端是断的)。缺口收成 `(0.5,95.6)-(12.5,102.4)`。 --- ### ✅ 这一轮 B(2026-09-06,用户拍板):三组引脚按几何重排(§26.18) 「哪个信号落在哪个物理脚」有三处是自由的,而原来全是按**逻辑顺序**排的, 和它们在板上要去哪儿一点关系都没有。目标函数用**绕开芯片本体的直角最短路** (HPWL 量不出「东边的脚要去西边」有多贵:同一批改动 HPWL 说省 7%,绕障路长说省 15%)。 | 组 | 结果 | 挪了几个 | |---|---|---| | **ESP32-S3 U20** 22 个信号 | 1195 → **1020 mm**(−15%),绕模块的线 3 根 → **0** | 12 | | **DAC63204 U6/U7** 5 个通道 | 422 → **352 mm**(−17%),**一根线都不用新画** | 4 | | **74HC595 U21/U22/U33** 21 个位 | 1286 → **1141 mm**(−11%),每路三键成束 | 17 | 合计省 **约 390 mm**。三处关键决定: 1. **放开 IO39~IO42**(原留给引脚 JTAG)—— 西边从 4 个坑变 8 个。 调试本来就走**原生 USB-Serial-JTAG**(IO19/IO20 → J22,出厂 eFuse 默认就是它), 板上没有 JTAG 排针。⚠️ **永远别烧 `DIS_USB_JTAG`**,烧了两边都没有。 让出去的只是「USB 起不来时还能进去」这个兜底。 `EFUSE_EN1 → IO39`、`VBUS_EN1 → IO40`、`EFUSE_EN4 → IO41`、`VBUS_EN4 → IO42`。 空出来的是 **IO4 / IO5 / IO7(三个都带 ADC1)+ RXD0/IO44**。 2. **DAC 只在已画好线的 5 个通道里置换**(U6 管去西半板的 VDAC1/2/4,U7 管去东半板的 VDAC3/5)。 动用 U6.OUT3 / U7.OUT2/3 还能再省 6 mm,但要新画 OUT/FB 两根线,不值。 3. **595 加了「每路 3 个按键必须落在同一片的连续三个南脚上」的约束** —— 比自由指派多 40 mm, 换来三根线出芯片就是一束。U33(y≈70)从「EDL 专用片」变成「北边那一片」, 接管 CH2/CH3 的六个按键 + HUB_RST,因为它正对 y 87~94 的北排两路。 **⛔ U33 现在在 `P6-FAN-EDL` 那一页上带着 CH2/CH3 的按键网络** —— 页面语义乱了一点, 但三片本来就是一条 24 位链,功能上没区别。 #### 落地时踩到的两条 API 1. ⚠️⚠️ **`setState_Net` 对 netport 是坏的**:调用之后网络名变成**空字符串**,标签也没了 (和 skill §3b 的 `setState_X/Y` 同一个毛病)。⇒ 端口只能 **删掉重建**。 2. ✅ **skill §8b 那句「端口的 dir 读不回来」是错的**: `getState_Component().name` 就是 `Netport-BI` / `Netport-IN` / `Netport-OUT`。 重建前读它原样传回去即可 —— `p1_add.py` 当年「端口只增不重建」的限制可以解除了。 #### 顺手治了:图面文字叠了好几代 P3 有 **44** 条 `sch_PrimitiveText`、P3B **64** 条、P6 **76** 条,大量是**同坐标同内容的完全重复** (历代脚本只 `create` 不 `delete`),还有互相矛盾的旧说明叠在一起。 **四道验证(verify_sch / xpage / port_audit / DRC)一条都不管文字**,所以它烂了很久没人发现。 `.claude/sch_text.py` 整页重写:44→13 / 64→14 / 76→16,顺带改掉换脚后变成假话的几条。 ### 这一轮新增的脚本 | 文件 | 用途 | |---|---| | **`pinmap.py`** | ⭐ 三组「逻辑信号 → 物理脚」映射的**事实来源** + 落地 + `--check` 回读对账 | | **`sch_text.py`** | 三页图面文字整页重写(删光旧的再写一套) | ### ⚠️ 还欠人眼确认 - `PCB1-v11-NL.png`(北左输入链改动前)与改动后的截图 —— **还没截改动后的** - `P3-CONTROL-v2.png` / `P3B-EXPAND-v2.png` / `P6-FAN-EDL-v2.png` 三张换脚后的原理图 - 摆位总图 `PCB1-v11-place.png`(v11 那一版,D1/C4/C5/C6 的新位置还没进图) --- ### 上一轮(2026-09-06 第二十二轮):拆线重布 + 两处换件 + 控制块按几何重排(§26.16) | # | 改动 | 依据 | |---|---|---| | 1 | **J1**:`KF128-5.08-2P` → **`XT30PW-M`**(C53373776),(8.5, 72.0) **rot 270** | 用户要插拔式;12V 是 8 A,DC-002 那类只有 1 A | | 2 | **J26~J30**:`KF2510-3A`(弯针) → **`2510-3A`(直针)**(C2803349) | 用户要立式;进深 12.01 → **5.81** | | 3 | **N-L 块重排** | XT30 比 KF128 宽一圈,原位放不下 ⇒ **这一版被第二十三轮推翻,见上面 A** | | 4 | **控制块**:`affine()` → `CTRL` 表(退火求解,`pcb1_ctrl_place.py`)| 原来 U21/U22 中心只隔 8.04 mm,中间塞不进一根线 | | 5 | **22 个附属 R/C**:`affine()` → `SAT` 表(逐个贴对端引脚)| `R602`/`R603` 是 USB-C 的 CC 下拉,**离 J22 有 77 mm** | 全板 HPWL **9416 → 8935 mm**。摆位大改之后 `pcb1_wipe.py --push` 把板上**全部铜**清空过一次 (走线 / 过孔 / 铺铜,元件 / 板框 / 丝印不动),13 个旧账本改名成 `.stale`。 **这一轮沉淀的脚本**(仍然有效): | 文件 | 用途 | |---|---| | **`fp_body.py`** | ⭐ 从器件的 `3D Model Transform` 直接读**本体框**(`--scan` / `--check`)。⚠️ 只给 XY,**Z 读不出来** | | **`p1p6_conn.py`** | J1 / J26~J30 换件(做到一根导线不动) | | **`pcb1_ctrl_place.py`** | 控制块五颗芯片退火求位置(目标 HPWL)。⚠️ 它写的 `_pcb1_ctrl_pos.json` **只是参考**,`pcb1_place.CTRL` 是硬编码的事实来源 | | **`pcb1_wipe.py`** | 清空板上全部铜(铜层白名单 1/2/15/16) | **⛔ 三个要记住的地雷(仍然有效)** 1. `pcb1_route_sig.py` 的 `--net` / `--limit` 试水以前会把计划缓存清成 `{}`(已修)。 2. `clean()` 以前只删得掉 59% 的自己的铜(已修,改成覆盖判据)—— EasyEDA 会合并共线段、在 T 形接点劈线,坐标精确比对认不出来。 3. 「两个端点钉死 ⇒ 连通性不变」是破的 —— 链会 T 在别的链**中段**上。 现在先按搭接点切开(409 → 797 条),拆完还要用 `pcb1_netlist.analyze()` 实算碎片数兜底。 **⚠️ 已知偏差(是决定,不是遗漏)** - **立式风扇座把装配顺序锁死了**(用户 2026-09-06 拍板接受):立式插口朝上正对载板底面, 座 + 胶壳约 11.5 mm,13 mm 净空里插拔只剩 1.5 mm ⇒ **必须先插好五根风扇线再合载板**。 - **`H_FP` 里 XT30 的 11.0 和直针座的 9.0 是估计值** —— `3D Model Transform` 给不出 Z, **打样前拿实物量**。 - ~~控制块的通道分配还没改~~ → **第二十三轮已经做完**(§26.18)。 - `POGO2_VUP` / `POGO2_VDN` 离 SW 节点 0.54 / 0.74 mm(§10.3 要「远离」), `sw_keepaway()` 会一直报,当提示不当错误。 --- ### ✅ 这一轮(2026-09-06 第二十一轮):五路母版三处整改 + 全链重跑(用户拍板 A+B+C) 设计文档 **§26.15**。§26.14 抓出的 22 个「几何上没有出路」的引脚,这一轮动手改掉, 改的全是**母版**(= 五路一起改),改完整条链重跑。 | # | 改了什么 | 结果 | |---|---|---| | A | **SW 不再往南潜**:`C%05` 北移 0.6,`SW-run` 平着走 y 12.500(1.2 宽),删掉 `SW-down` | 五路 `EN` 全部解放;SW 节点 9.6 → **8.9 mm** / 铜面积 10.9 → **9.4 mm²** | | B | **`U%8` + `Rs%01` + `C%13` 整体北移 0.8** | eFuse 北排那条缝 0.33 → **1.13 mm** | | C | 跟着 B 改的三处:`_CH_FORCE_LOCAL['U%__8','1']`、`F-trunk` 北端、`K-IN±` | 相对几何一格没变 | | — | 全链重跑:place → clear → dump → fanout → pour → route_pwr | 342 件 / 498 孔 / 15 铜面,功率级 195 段回读 **0 条对不上** | **「几何上出不去」的焊盘:22 → 8**(`pcb1_escape_audit.py` 实测)。 剩下 8 个是**和邻脚预留出线段抢地方**,不是硬几何 —— 单独布都能通。 #### ⭐ 这一轮踩到的三条 **1. 「端帽比端点往外探半个线宽」这一条,一轮里踩了两次。** 先是 `SW-run` 1.5 mm 的西端帽把 `EN` 封死(在 y=11.5 那一行往西探到 x 11.007, 比端点多探 0.743);改完起点又发现同一个端帽离 `U%1.13`(BOOT 引脚) 只有 **0.080**。 ⇒ **凡是宽线的端点,心里都要按「端点 ± w/2 的圆」去想**,§26.11 已经写过一次了。 **2. 挪 0.1 mm 解决不了拓扑问题。** A 组先试过「`SW-run` 起点东移 0.35」和「收窄到 0.9」, 两次都卡在 5~20 µm 上 —— 因为 COMP 和 EN **两个脚**要并排出来,缺的不是余量是**车道**。 把 `C%05` 北移 0.6、让 SW 整条不再下潜,才是解。 **3. `verify()` 只认「同宽」的线会报假阳性。** 一段窄线整个落在宽线的圆头端帽里时 EasyEDA 会**直接吞掉它** —— 铜一点没少, 回读却报 5 条对不上。判据改成「实测线不窄于计划线,且中心距 ≤ (o_w − p_w)/2」。 **4. `chamfer_routes(smooth=True)` 会挪线(最多 0.15 mm),不只是拿掉铜。** 而它的净距校核拿的是**切之前**的自身路径 ⇒ 两条本轮新画的线会被各自抹到对方身上 (实测 `$1N227` vs `POGO1_PWR` 压成 **−0.199**,2255 段里 2 段)。 ⇒ 切完再跑一遍 `check()`,**把牵涉到违规的整条退回未切的版本**,最多退 4 轮。 --- ### ✅ 这一轮(2026-09-06 第二十轮):下板信号布线 —— 工具全套建好,⛔ 没下发 | # | 做了什么 | 结果 | |---|---|---| | 1 | 新增 **`pcb1_netlist.py`**:按**实测铜**(含回读的铺铜)算全板连通性 | 还欠 **200 个网络 / 384 条连接**;顺带发现 GND 其实早就通了 | | 2 | 新增 **`pcb1_route_sig.py`**:下板信号布线器(复用载板栅格引擎,只加不拆)| 379 条里布通 **284 条(75%)**,净距 / 钻孔 / 铺铜 **三样全 0** | | 3 | 新增 **`pcb1_escape_audit.py`**:逐个焊盘算「能不能走得出去」 | 563 个待接焊盘里 **22 个几何上出不去** | | 4 | Kelvin 三条(`U13.8/9/10` ↔ `Rs6`)单独钉死,不许就近扎平面 | 3 / 3 条,w 0.30 | | 5 | USB 差分对「**一整束布线 + 平行偏移**」 | 束长 58.1 mm,W 0.28 / S 0.18,**对内偏斜 0.00 mm** | | 6 | 修掉 4 个真 bug(见下) | — | #### ⭐⭐ 这一轮最值得记住的四条 **1. POLYGON 焊盘的**包围盒**不是它的形状 —— 又一次。** §26.13 抓的是它的 `hole` 是假的;这一次是 `pad[1], pad[2]` 那对宽高。 TPS54622 的散热盘是个**带四个缺口的多边形**(缺口正是让给 pin 1/7/8/14 的), 按 2.032 × 4.320 的包围盒当障碍,这四个脚**整个泡在 GND 铜里**: `entry_stub` 一个出线方向都找不到,`BUCK_PG` 报「五路全布不通」。 ⇒ **POLYGON 焊盘的任何标量属性都不能直接当几何用**,只能读 `pad[1]` 那串点。 现在 `pcb1_route_sig.poly_pads()` 把 11 个多边形盘按真实轮廓光栅化成矩形条。 **2. 网名里带 `#` 的网络会被引擎的 `netof()` 拆坏。** 引擎用 `key.split('#')[0]` 从「路径 key」还原网名,而下板有 `KEY_OE#` 和 `IOEXP_INT#`。 `'IOEXP_INT#' + '#0'` 拆回来是 `IOEXP_INT` ⇒ 校核时它和**自己的焊盘**被当成异网, 报「线-焊盘 净距 −0.100」,看上去像压线,其实两者本来就该连在一起。 ⇒ 分隔符改成 `chr(0)`。**凡是「用某个字符拼 key」的地方,都要先问一句这个字符会不会出现在数据里。** **3. 45° 切角会切进铺铜里 —— `seg_slack()` 不认铺铜。** 切角是往拐角**内侧**补铜(不是只拿掉铜),而引擎的切角校核只看焊盘 / 过孔 / 走线。 实测 `SDA` 在 (6.10, 65.50) 被切进 `VO1_P` 小岛,只剩 0.043。 ⇒ 把铺铜矩形条包装成「假焊盘」一起喂给 `chamfer_routes()` 和 `check()`。 **4. 0.5 脚距的脚,「放得下」和「栅格上走得通」是两回事。** `U8.3`(INA226 的 ALERT):西边 1.00 mm 是 GND 扇出孔,东边 1.00 mm 是邻脚的出线段。 0.20 的线需要 **1.005 mm**(差 5 µm 过不去),0.15 的线只要 0.955 mm —— 可 0.1 mm 栅格上判据要各加一个 QERR 0.0707,变成 1.096 mm,**整片不可达**。 0.025 mm 栅格的 QERR 只有 0.0177,同一条缝就通了。 ⇒ 新增「**细栅格逃逸**」:0.1 栅格上出不来的脚,先在 4 mm 内用 0.025 栅格单独走出去。 实测 47 个脚里救回 **25 个**。 #### ⛔ 剩下 22 个脚是**几何上就没有出路**,必须动板子 | 组 | 焊盘 | 卡在哪 | 差多少 | |---|---|---|---| | **A** | `U1.10 U2.10 U3.10 U4.10 U5.10`(Buck 的 `EN`)| `pcb1_route_pwr` 的 **SW-run 1.5 mm 铜**,圆头端帽从 (20.05,27.10) 往西探到 x 19.30 | 脚东边净空 0.376,一根 0.2 的线要 0.6 | | **B** | `U14.2/3` `U15.2/3/14/15` `U16.2/3/14/15` `U17.2/3` `U18.2/3`(eFuse 北排)| 上面 INA226 的焊盘边 **只隔 0.33 mm**,下面自己的散热盘 **0.25 mm** | 要 ≥ 0.6,缺 ~0.7 mm | | **C** | `U10.3`(ALERT)`U13.4`(SDA) `U13.5`(SCL) | 隔壁 GND 扇出孔正对着出线走廊 | **5 µm** | **A 组的解**:EN 是每路都有的信号,本来就该进 §10.2 母版 —— 在 `pcb1_route_pwr.R` 里 加一条 `EN` 路径,并把 `SW-run` 的起点往东挪 ~0.35 mm(或前 2 mm 收窄到 1.2)。 收窄反而**符合** §10.2-2「SW 铜箔面积压到刚好够载流」。 **B 组的解**:CH1 那一列从下往上是 `C106(顶 43.46) → U14(43.85~47.75) → U8(48.08~53.92) → Rs101(54.27~57.73)`,缝一律 0.33~0.39 mm,**一点余量都没有**。 唯一的空地在 Rs101 顶到通道北界 y 59 之间的 **1.27 mm** ⇒ 把 `U8 + Rs101` 整体北移 **0.7~0.8 mm**(母版改一处 = 五路一起改)。 ⚠️ 这会连带作废 **扇出 + 铺铜 + 五路功率级布线**(`U*.9 → Rs*01.1` 的 Kelvin 线也在母版里)。 **C 组的解**:给那两三个 GND 扇出孔在 `pcb_fanout._CH_FORCE_LOCAL` 里钉一个偏 0.1~0.2 mm 的位置。 #### ⚠️ 另外 95 条是**拥挤**,要拆线重布才能再往前 失败集中在**控制块(U21~U23,x 75~107 / y 43~58)**和 **J24/J25 排针**周边; 按网络族数:`$14N` 9 / `$8N` 7 / `$12N` 7 / `EFUSE_PG` 6 / `$10N` 6 / `INA_ALERT` 5 … 现在这个布线器是**一遍过、只加不拆**(这是有意的:板上已有 691 条线不能被抹掉), 所以先布的把后布的堵死了 —— 这正是商业布线器用 **rip-up & reroute**(协商式拥塞)解决的那一类。 ⇒ 下一轮要么加拆线重布,要么等 A/B/C 三组改完再看还剩多少。 #### 顺带记两条 - `POGO2_VUP` / `POGO2_VDN` 离 SW 节点只有 **0.54 / 0.64 mm**(§10.3 要求「远离」)。 已经有 `sw_keepaway()` 在报,不当错误、只列出来。 - 8 个 45° 切不动的拐角(转向 89~90°),都在细缝里,`chamfer45` 试到 0.05 半径仍撞墙。 #### 这一轮的产出(都在 `.claude/` 下,**没有一个动过板子**) | 文件 | 用途 | |---|---| | **`pcb1_netlist.py`** | 按**实测铜**算连通性:焊盘 / 过孔 / 走线 / **回读的铺铜**当图的节点,`--todo` 列还欠几条,`--json` 出任务表。⚠️ 判据不能只测焊盘中心那一格:同网通孔盘在回读的环里是个**洞** | | **`pcb1_route_sig.py`** | 下板信号布线器。`--net` / `--limit` / `--push` / `--clean --push` / `--verify` / `--refresh`。内含 Kelvin 强制表 `FORCE`、USB 束 + 偏移、细栅格逃逸、铺铜障碍、`sw_keepaway()` | | **`pcb1_escape_audit.py`** | 逐焊盘的**逃逸可达性**:两层泛洪 + `via_ok()` 打通,走不到 4 mm 外就报。`--why <焊盘>` 列出挡路的铜。⚠️ 判据环不能取自由区最外圈(那一圈 `free()` 永远是假的)| --- ### ✅ 这一轮(2026-09-06 第十九轮):五路功率级精调(§26.13) | # | 做了什么 | 结果 | |---|---|---| | 1 | **修 POLYGON 焊盘的假 `hole`**(`dump_pads` / `pcb_fanout` / `pcb1_parts_dump` 三处)| 七个散热盘回到扇出名单 | | 2 | 盘内阵列改成**按真实多边形**撒点 + 自适应间距 + 补中心孔 | U1~U5 **0 → 5** 个孔;U6/U7 0 → 4;eFuse 2 → 6 | | 3 | 摆位 v9:`L*01` / `U*14` / `Rs*01` 各转 180°,`C*05` 与三颗输出电容挪位 | 四条回路的朝向全部理顺 | | 4 | 新增 **`pcb1_route_pwr.py`**:母版 27 条路径 × 5 路 = 210 段,全部顶层 | 一路 74.8 mm,**SW 13.1 → 9.7 mm** | | 5 | 新增 `pcb_fanout._CH_FORCE_LOCAL`:5 个焊盘 × 5 路 = 25 个 `FORCE_VIA` | 五路的扇出终于**真的一样** | | 6 | 重跑全链:place → 扇出三步 → 铺铜 → 布线 → DRC | 495 孔 / 691 线 / 间距 0 / 物理 0 / 连接性 594 | #### ⭐⭐ 这一轮最值得记住的四条 **1. 「有孔 = 通孔盘」是错的判据 —— POLYGON 盘的 `hole` 是假的。** 板上 11 个 POLYGON 盘无一例外满足 `hole = 0.600 × min(w,h)`(U1 的 2.032×4.320 → 1.2192, U6 的 1.70×1.70 → 1.02,J22 的 1.30×0.60 → 0.36),EasyEDA 渲染出来是实心的。 判据应该是**层号**:`layer == 12` 才是多层通孔盘。 **2. 五路的扇出结果并不相同 —— 明明摆位一模一样。** 扇出规划器的候选角度按**板坐标**排,而 CH2/CH3 整路转了 180°, 同一个焊盘在 CH1 往东扇、在 CH2 就往西扇。母版里算好的走廊于是在 CH2/CH3 上被堵死, 而报出来的是「CH2 的 F-trunk 撞到一个 GND 过孔」——**看上去像布线画错了**。 ⇒ 关键焊盘按 CH1 的解钉死(`FORCE_VIA`),钉死之后照样走全套净距校核。 **3. DRC 的「物理错误」那一支平时是空的,容易漏看。** 0.28/0.25 的线下发之后:**间距错误 0、连接性还降了 125 条** —— 而 `物理错误·导线` 里躺着 50 条「线宽 11mil,应该 ≥ 11.8mil」 (`SWNODE`/`PWR4A` 的 `strokeWidthMin` 都是 0.30)。加宽到 0.30 清零。 **4. 回读走线不能按端点比:EasyEDA 会在 T 形接点上把已有的线劈成两条。** 西侧那条 +12V 母线下发之后在板上是两条(支线接在中间)。 按端点比报「210 段里 40 段对不上」,而铜一点没少。 ⇒ `verify()` 用**覆盖判据**:每 0.2 mm 取一个点,要求被同网同层同线宽的实测线盖住。 #### ⚠️ Kelvin 的那一条:`U*8.9` 不许有过孔 `U*8.9`(INA226 的 IN−)已经写进 `pcb1_pour.ROUTED_PADS`。 让它走 `VO*_P` 小岛的话,取样点在岛上而不是分流电阻的焊盘上,中间隔着 `Rs*01` 那三个扇出孔(并联约 0.5 mΩ)。Rs 只有 5 mΩ、满载才 20 mV, **0.5 mΩ × 4 A = 2 mV = 10% 的读数误差**,而 DRC 和连通性检查都看不出来 (IN− 和 `VO*_P` 本来就是同一个网)。pin 8 是 VBUS,照旧进岛。 --- ### ✅ 上一轮(2026-09-06 第十八轮):F1 → 12 A / R9 → 5 × 2Ω 2512 两件都是用户拍板的选型改动,走完整链路:改原理图 → 导入变更 → 重推摆位 → 重扇出 → 重铺铜 → DRC。详见设计文档 **§26.12**。 | # | 做了什么 | 结果 | |---|---|---| | 1 | F1 `F1206HC10A0TM` → **`F1206HC12A0TM`**(C310990)| 同是 1206,**摆位一格没动**;封装对象换了(`F1206`)但焊盘几乎一样 | | 2 | R9 立式水泥电阻 → **R9A~R9E 5 × 2Ω 2512 串联** | 10Ω/**10W**,高 **22 → 0.8 mm** | | 3 | `pcb1_import.py` 加 **`--bom`** | 原来 `_p1_sch_bom.json` 在仓库里**没有生产者** | | 4 | `pcb_fanout.py` 加 **`guard_not_pushed_twice()`** | 我推重了一次,888 个过孔(见下)| | 5 | P1-INPUT 文字收拾 | 66 → 14 条,清掉一整代旧说明 | | 6 | 结果 | 342 件 / 444 过孔(重复 0)/ 15 块铜面 / DRC **间距 0** + 连接性 725 | #### ⚠️ 先纠一条我上一轮写错的 「F1 封装是 1206 但 BOM 写 10A、1206 做不到 10A」—— **错的**。 `F1206HC10A0TM` 本来就是 1206/10 A,BOM 和封装从来没对不上。 换 12 A 的真实理由是**降额**:8/10 = 80% 超过保险丝常规的 75% 线,且 I²t 只有 15 A²s。 #### ⭐⭐ 这一轮最值得记住的两条 **1. `pcb_fanout.py --net all --push` 不幂等 —— 我连推了两遍。** 板上变成 **888 个过孔**(每个都有同坐标同网的副本),而**短线数没变**(444): EasyEDA 把共线重叠的新线并进了旧线。**连接性 DRC 一个字不报**(副本同网,电气上「没错」)。 唯一判据是**回读过孔数**。现在 `--push` 前会硬拦;重推必须 `--clean → dump_pads.py → --push` 一次跑完。 **2. `H_FP` 里那个 9.5 是照封装名 `W9.5` 抄的,而 R9 是立式件。** `RES-TH_L13.5-W9.5-P5.80-D0.7`:**本体 13.5 长、脚距只有 5.80 ⇒ 它站着** (同族 3D 模型标 `H52.0`)。那一片 hmax 只有 12.0, 所以「高度预算全过」在它身上假了整整一轮,**是用户看出来的,不是检查看出来的**。 ⇒ 往 `H_FP` 加直插件的判据:**脚距和本体长度对不上就是立式**, 高度不是封装名里的 W,是那个对不上的方向。 #### ⛔ 新增的两个地雷标记 - **`p1_add.py` 已过期**,底稿 `p1-base.json` 停在 2026-09-04。再推一次会把 R9A~R9E 之间那 6 段导线**静默回退**。已加硬闸门(要跑得带 `--i-know-it-is-stale`)。 ⇒ **改完一页就问一遍:还有哪个脚本会重画这一页。** - **改过原理图必须 `pcb1_import.py --bom` 再 `--verify`**,否则是拿新 PCB 和旧原理图对账。 截图:**`PCB1-v8-r9f1.png`**(R9A~R9E 的 1+2+2 蛇形)/ **`P1-INPUT-v2.png`**(原理图)。 **还没等到用户回话。** --- ### ✅ 这一轮(2026-09-06 第十七轮):8 A 顶层宽铜 | # | 做了什么 | 结果 | |---|---|---| | 1 | `pcb1_pour.REGIONS` 加三块顶层铜面 | `VINRAW-TOP` 46 / `VINFUSED-TOP` 19 / `VINSW-TOP` 284 mm²(实测)| | 2 | 网名用 `pcb1_rules.resolve_auto()` 当场反查 | `$1N223` / `$1N227` / `$1N236`,**一个字没写死** | | 3 | **`Grid` 分内层 / 外层两套障碍模型** | 外层连贴片焊盘一起挡;新增 `obstacles()` 把 438 个过孔 + 441 条走线算进去 | | 4 | 新校核 **`check_top_clearance()`**(不过格子)| 当场抓出两处侵入,**其中一处栅格看不见** | | 5 | 新校核 **`poly_vs_rects()`** | 手写的 24 个点 vs 它该等于的 8 个矩形,0.05 mm 逐格对照 | | 6 | 下发 + 回读实测铜 | 15 块铜面全部铺出来,三段各 **1 个连通域** | | 7 | DRC | 间距 **0** / 连接性 **719**(铺顶层铜前是 733)| #### ⭐⭐ 这一轮最值得记住的:**线的端点不是铜的端点** 北柱东缘写在 x 6.8,Rs6.2 那条 +12V 出线的端点在 x 7.401 —— 看起来隔 0.6 mm。 但那条线 **1.0 mm 宽、圆头端帽往外再探 0.5 mm**,实际铜到 x **6.901**, 真实净距只有 **0.101 mm**。而 0.25 mm 的栅格上这 0.1 mm 一个格心都落不进去 ⇒ **栅格全绿**。真下发下去 EDA 自己会往回缩一点点铜,板子不会报 DRC, 但**板上那块铜就不再是计划里那块铜,后面所有断面数字全部作废**。 ⇒ 凡是手画的铜,都要过一遍 `check_top_clearance()` 这种**真实几何**的校核, 栅格只能用来算连通域和断面。 #### 顶层三段长什么样(实测断面,顶层 1 oz) 最窄两处:干道 x=12 处 **4.75 mm(10.0 A)**、北柱 y=92 处 **5.00 mm(10.4 A)**, 8 A 下 ΔT 分别约 12 / 11 °C。其余各点 8.75~21.5 mm。 截图:**`PCB1-v7-top8A.png`**(只开顶层 + 焊盘 + 顶丝印)。**还没等到用户回话。** #### ⚠️ 顺带看出来的一件:**F1 的封装是 1206,但 BOM 写的是 10 A** `FUSE-SMD_L3.2-W1.6-1` = 1206,而 §7 的 BOM 写「保险丝 10A 慢断」。 **1206 的贴片保险丝一般做到 5 A 左右就到顶了**。8 A 的铜宽本身没问题, 是**器件选型和封装对不上**。换大封装会动 J1→F1 那一段的摆位 ⇒ 要**重推摆位 + 重扇出 + 重铺铜**。这一条留给用户拍板。 --- ### ✅ 这一轮(2026-09-06 第十六轮)做完的 | # | 做了什么 | 结果 | |---|---|---| | 1 | **修去耦电容摆位**:新增 `pcb1_place.DECAP` 表 + `place_decaps()` | 7 颗跟着本尊走,最远的储能 3.31 mm,其余 0.76~2.24 mm | | 2 | **修 `pcb1_parts_dump.py` 漏了焊盘自身旋转** | 8 个元件的 bb 各长 0.26~0.30 mm(U1~U5 / U6 / U7 / U20)| | 3 | 新增 **`pad_clearance_errs()`**:焊盘到焊盘净距 0.2 mm | 全板 1171 个焊盘一条不报 | | 4 | 修 `pcb_fanout.TARGET`(还停在 §26.8 之前)| 删掉 `VOn` / `+5V` / 8 A 三段,它们不上 Inner2 | | 5 | 修 `_pour_filter()`(指着单板时代的 `pcb_pour.py`)| 改指 `pcb1_pour.REGIONS`;实测滤掉 17 个铜面外的 `+3V3` 焊盘 | | 6 | 扇出规划器补五条(见 §26.9)| GND/+12V/+3V3/VOnP **全部失败 0 个** | | 7 | 扇出下发 | 438 过孔 + 441 短线,**DRC 间距 0** | | 8 | **实现 `pcb1_pour.py --push` / `--verify`** | 12 块铜面已下发;回读实测铜 **24 项全 OK** | | 9 | 下发前 / 下发后**共用同一段 `grid_checks()`** | 分两套写只能证明两套代码各自自洽 | | 10 | 新校核:**每块碎片都要有本网过孔落脚**(浮铜)| 9 组 (网络,层) 的 12 块碎片全部有落脚 | #### ⭐⭐ 第 1 条是这一轮最重要的发现:ESP32 的储能电容在 48 mm 外 追「U6.15 为什么扇不出去」追出来的。全板 20 个 IC 的 `+3V3` 电源脚里 **只有 1 个的去耦在 3 mm 以内**;`C601`(22 µF「ESP32 储能」)离 U20.2 **48.2 mm**, `C602`(100 nF)**45 mm**,U20 电源脚周围 12 mm 内一个元件都没有。 根因:控制块是 `affine(['P3-CONTROL','P3B-EXPAND'], ...)` 按**原理图坐标**仿射映射摆的, 它保「原理图上挨着的板上也挨着」,保不住「去耦贴着电源脚」; 而 **U20 是上面手工单独摆的 —— 芯片被拎出了仿射集合,去耦却留在原地**。 ⇒ **动控制块布局时先看 `DECAP` 表**,它是这件事的事实来源,`check()` 里有硬校核。 表的顺序 = 抢位置的顺序,**小的高频那颗必须排在大的储能之前**(理由见 §26.9)。 #### ⭐⭐ 第 2/3 条:本地检查曾经「全绿而板子是错的」 改完摆位本地六项全过,DRC 却报 4 条间距错误:`C607_2` 到 `U6_13` 只有 **0.096 mm**。 两个原因叠在一起 —— `pcb1-parts.json` 的包围盒漏了**焊盘自身的旋转**(QFN 侧边脚是长条焊盘转 90° 用的, U6 的 bb 在 y 上短 0.26 mm);而 `check()` 只查「包围盒重不重叠」,**不重叠不等于够净距**。 现在两条都修了,新检查直接照 DRC 那条 0.2 mm 的规则算,量焊盘不量包围盒。 > ⚠️ 同一套 API 里 `getAllPinsByPrimitiveId` 的 rotation 是**度**, > `pcb_PrimitivePad.getAll()` 的是**弧度**。 ### 下板 PCB1 接下来的顺序 0. **让用户人眼确认摆位 v9 + 功率级布线**(截图已出,见上面「一句话现状」)。 不算硬阻塞,但这一轮动过五路的摆位,值得看一眼。 1. ~~设计规则~~ ✅ §26.7 2. ~~定铺铜几何~~ ✅ §26.8 3. ~~扇出~~ ✅ **§26.9 / §26.13** 4. ~~铺铜~~ ✅ **§26.10,已下发并回读复核** 5. ~~8 A 输入链顶层宽铜~~ ✅ **§26.11,已下发并回读复核** 6. ~~五路功率级精调~~ ✅ **§26.13,已下发并回读复核** 7. ~~写下板信号布线器~~ ✅ **`pcb1_route_sig.py` 已建好并跑通 75%(第二十轮)**, `pcb1_route_pwr.py` 那 210 段一根没动(这个布线器只加不拆,删除只按坐标删自己画过的)。 8. ~~A/B/C 三组几何整改~~ ✅ **§26.15,用户拍板 A+B+C,已全链重跑并下发**。 全链的正确顺序(动过摆位就照这个走,一步都不能省): `pcb1_place --push` → `pcb1_clear --push` → `dump_pads` → `pcb_fanout --net all --push` → `pcb1_pour --refresh --push` → `pcb1_route_pwr --push` → `pcb1_route_sig --push` → `pcb1_pour --refresh --push`(**给新过孔补反焊盘**)→ `pcb1_reload` → `drc_read`。 9. ⬅️ **下一步:给 `pcb1_route_sig.py` 加拆线重布**(rip-up & reroute),收掉剩下的 91 条。 ⚠️ 直接再跑一遍 `pcb1_route_sig.py --refresh --push` 也会有进展(它读板上的铜、 只补还缺的),但一遍过的策略已经到顶了 —— 真要清零得能拆别人的线。 ⚠️ **北左角的顶层现在被 8 A 宽铜占了大半**(x 1.5~28.9 / y 68.6~93)。 U13 的 I2C、U29、R9 那几条信号得**走底层**—— 底层整层基本是空的,不占地方。 三条还没布的细线已经有名有姓地写在 `pcb1_pour.ROUTED_PADS` 里: `U13.10`(Kelvin VIN+,接北柱顶端那一小截死头)/ `DZ1.1` / `C1.1`(栅极)。 ⚠️ **动了铺铜或摆位就要重跑 `pcb1_pour.py --push`**,它自己会先把计划跑绿再下发; 下发完自动 `--verify` 回读实测铜。只想看不想改板就 `--verify`(加 `--cache` 用上次的回读)。 ⚠️ **动过摆位就要重跑扇出三步,不能省也不能并**: `pcb_fanout.py --clean` → `dump_pads.py` → `pcb_fanout.py --net all --push`。 ⚠️ **连接性数字扇出之后会先涨再跌**:布线前基线 1145 → 扇出完 **1336** → 铺完铜 **733**。 过孔本身是新对象,铺铜之前每一个都是自己网络里的孤岛。 **只看扇出完那一步会以为扇出把事情弄坏了。** ⚠️ **EasyEDA 客户端这一轮掉线过三次**(`EDA window ... disconnected`)。 掉线时命令**可能已经执行完**只是回话丢了 —— 别直接重试,先回读板上对象数再决定。 ### ⚠️⚠️ 一条永远别忘的:J24 / J25 的针号是**故意错一位**的(§26.5,用户已拍板选 A) `p5_btb.py` 的 **`MATE_SWAP = True`** 让下板这一侧的网络按针号奇偶对调 (两颗封装焊盘编号同手性 + 母座在载板底层、顶视图镜像一次,不改的话下板 `VO1_P` 会顶在载板 `GND` 上)。`c1_conn.py`(载板 C1-CONN)用的是原始表,**一个字没动**。 ⇒ **判据是网络不是针号**:下板 pin(2r−1) 正对载板 pin(2r),差一位是**预期的**。 每次动完摆位跑 `pcb1_place.py --verify` 逐个焊盘核。 ### Inner2 分块的结论(详见设计文档 §26.8) | 层 | 网络 | 名称 | 形状 | |---|---|---|---| | Inner2 | `+12V` | `12V-N` | 北带 y 88~124.5 通板 + 风扇指下伸到 y 66.5,挖掉 U13 那一角 | | Inner2 | `+12V` | `12V-SW+BR` | 南西块 (0.5~72, 9~34) + 中桥 (61.5~72, 34~88),⌐ 形 | | Inner2 | `+12V` | `12V-SE+RC` | 南东块 (116~159.5, 9~34) + 右通道 (153.5~159.5, 34~88),L 形 | | Inner2 | `+3V3` | `3V3-SW` / `3V3-SE` | 南部两块,**故意不连通**(见下)| | Inner2 | `GND` | `GND-FILL-L3` | 剩余全部 | | Bottom | `VOn_P` | 五块小岛 | 各约 18 × 13 mm | | Inner1 | `GND` | `GND-L2` | 整板,只挖 ESP32 天线区 | **退回走线的四类**(都算过,不是省事):`+5V` 底层 1.2 mm 干线 / `VOn` 顶层 1.5~2.0 mm / `VOn_P` 底层小岛 / **8 A 输入链顶层宽铜 ≥3.5 mm**。 关键数:内层 0.5 oz 与外层 1 oz 差约 **4 倍**(8 A 内层要 18.2 mm,外层只要 3.49 mm), 而这块板**底层整层是空的** ⇒ 短距离大电流根本不该上内层。 ⚠️ **`+3V3` 在 Inner2 上是 2 个连通域,是设计意图不是漏接。** 中桥必须纵向穿过 y 41~58.5 那条带,而 `+3V3` 的南部焊盘横向铺满同一条带 —— 同一层上两个都要连通的 2-D 区域,交叉拓扑上避不开。让 0.5 A 的那个换层:南西 / 南东各自从北边引一条底层干线。 ### ⭐ 这一轮校核当场抓到的一个错:孤岛判定是**按铺铜对象**算的,不是按网络 第一版把 +12V 写成六个矩形,栅格连通性说「1 个连通域」,看着完全正常。 新加的孤岛规则校核报出来:`12V-RC`(右通道)**里一个 +12V 焊盘都没有** —— 哪怕它和北带紧挨着、铺出来是连续铜,它自己那一块里没有本网焊盘/过孔照样被整块删光 (边框还在、`nPoured` 是 0)。⇒ 并成三个 L / ⌐ 形多边形,每块各自含风扇座的通孔针。 另外两条:**只有通孔焊盘在内层挖反焊盘**(贴片对内层透明,板上实测 122 个通孔); **区域间净距不能用包围盒估**(L 形的 `3V3-SE` 包围盒把 `12V-FAN` 整个罩住, 算出来 0.00 mm 像严重违规,实际相距 0.50 mm)。 ### 下板还欠的几件(都不影响现在动手) - **C2 / C3 罐高必须 ≤ 11.5 mm**(现在按 12.0 建模,`H_FP` 里那一条)。 载板底面在那块地方是光板,hmax = 12.0,卡着上限过;换一颗矮罐才有余量。§22.3f - **CH2 的 `POGO2_*` 三根要横穿 60 mm**(信号在 J25、电源在 J24,载板那边决定的,§26.3)。 - U20 的天线端离下板边只有 1.5 mm,而且上面还盖着载板 —— **这块板的 WiFi 本来就指望不上**, 真要用就得把天线引出机箱。 - 北中块 x 58~118 / y 85~125 有一块约 60 × 40 的空地(CH2/CH3 的排线车道从这里过), 留给电源面和风扇线,暂时不填。 ### 下板 v6 的布局长什么样(设计文档 §26.3 / §26.4) ``` y 125 ┌──────────────────────────────────────────────────────────────┐ │ P1-INPUT │ CH2 (北,rot180) │ P1B + P6 + J28 │ CH3 (北,rot180) │ │ J1/F1/Q1/D1 │ x 30.1~57.3 │ x 58~118 │ x 118.5~145.7 │ │ C2/C3/R9/U19 │ │ │ │ y 66 ├───────────────┴──────────────────┴────────────────┴─────────────────┤ 62.5 │ ==== J24 (x 5.3~58.7) ==== ==== J25 (x 101.3~154.7) ==== │ y 59 ├───────────────┬──────────────┬─────────────────┬────────────────────┤ │ CH1 (南) │ CH4 (南) │ P3/P3B 控制块 │ CH5 (南) │ │ x 10.6~37.8 │ x 42.3~69.5 │ U20/U6/U21~23 │ x 121.8~149.0 │ y 0 └───────────────┴──────────────┴─────────────────┴────────────────────┘ J26/J27 左板边 J29/J30 右板边 J28 顶板边 J22 调试口在右板边 ``` ### 下板新加的工具(`.claude/` 下) | 文件 | 用途 | |---|---| | **`pcb1_import.py`** | 下板「从原理图导入变更」:`--probe` 读全部变更行 / `--apply` 真的导 / **`--verify` 拿 PCB 元件表和原理图 BOM 逐条对账** | | **`pcb1_parts_dump.py`** | 抓 `pcb1-parts.json`(每件的真实焊盘框 + 每个焊盘的本地坐标)。**导入变更之后必须跑** | | **`pcb1_env.py`** | 把载板底面折算成下板的**高度预算图** `pcb1-env.json`(zones / lanes / slots / J24-J25 锚点);`hmap()` 打一张 ASCII 高度地图 | | **`pcb1_place.py`** | 下板摆位模型 + 本地六项检查 + `--push` / `--verify`(回读焊盘核对插)| | **`pcb1_clear.py`** | 拆掉下板全部铜(导线 / 过孔 / 铺铜边框),保留板框、M3 焊盘、丝印 | | **`pcb1_rules.py`** | 下板设计规则:线宽 5 档 / 差分 1 对 / 铺铜全连接;**自动命名网按位号+引脚反查**。`--push` / `--check` | | **`pcb1_place.DECAP`** | **去耦电容跟着本尊走**的事实来源(第十六轮加)。表里的电容不参与仿射摆位,芯片摆完之后自动贴到电源脚旁边;`check()` 里有硬校核 | | **`pcb1_place.pad_clearance_errs()`** | 全板**焊盘到焊盘**净距(0.2 mm,照 DRC 那条规则)。「不重叠」不等于「够净距」 | | **`FANOUT_WHY=U6.15`** | 环境变量。打印这个焊盘所有候选孔位**卡在哪一道校核上**,按次数排序 | | **`p1_r9f1.py`** | P1-INPUT 换件(F1 → 12 A / R9 → R9A~R9E)。**增量**改法:先 create 后 delete、旗删掉重建、坐标全 5 的倍数、引脚坐标断言不过就停手 | | **`p1_text_fix.py`** | P1-INPUT 文字去重 + 挪位。幂等(新增块也走「先删再建」),逐条对账最终 14 条 | | **`pcb1_import.py --bom`** | 逐页重抓原理图 BOM(`_p1_sch_bom.json`)。**改过原理图先跑它再 `--verify`**,一页一个请求(`eda.py` 截 20000 字符)| | **`pcb_fanout.guard_not_pushed_twice()`** | 板上已有过孔就拒绝 `--push`。这脚本**只加不删不幂等**,推两遍会叠出同坐标副本而**短线数和 DRC 都看不出来** | | **`pcb1_pour.check_top_clearance()`** | 顶层手画铜面到异网铜的**真实几何净距**(不过格子)。0.25 mm 栅格漏得掉 0.1 mm 的侵入;**走线的圆头端帽比端点还往外探半个线宽** | | **`pcb1_pour.poly_vs_rects()`** | 手写的复杂多边形 vs 它该等于的矩形并集,0.05 mm 逐格对照。写错一个坐标,别的检查一个都发现不了 | | **`pcb1_pour.obstacles()`** | 板上已有的铜(438 过孔 + 441 走线)。**顶层铺铜要躲它们**,内层只躲过孔 | | **`pcb1_route_pwr.py`** | 五路功率级手工布线的**事实来源**(§10.2 四条回路)。路径写在 CH1 母版局部坐标里,仿射到五路。不带参数 = 计划 + 五项校核;`--push` 下发(`--clean --push` 先删本轮旧线);`--verify` 按实测铜核**覆盖**;`--ch N` 只看一路 | | **`pcb1_netlist.py`** | 下板连通性的**事实来源**:按实测铜(含回读的铺铜)算每个网络还欠几条。`--todo` / `--json` / `--refresh`(第二十轮加)| | **`pcb1_route_sig.py`** | 下板信号布线器(**只加不拆**)。Kelvin 强制表 / USB 束+偏移 / 细栅格逃逸 / 铺铜当障碍 / `sw_keepaway()`(第二十轮加)| | **`pcb1_escape_audit.py`** | 逐焊盘的**逃逸可达性**。「布不通」到底是这一头出不来还是中间挤,只有它分得清(第二十轮加)| | `pcb1_reload.py` | 下板专用的客户端重载(DRC 的连接性那一支要 reload 之后才准)。⚠️ **别拿 `pcb2_reload.py` 当通用工具**:那一份写死载板 uuid,而且会把板上全部铺铜边框删掉 | | `pcb_fanout._CH_FORCE_LOCAL` | 五路通道里必须**五路一样**的那几个孔(按 CH1 的解钉死再展开)。不钉的话 CH2/CH3 会往反方向扇 | | **`pcb1_pour.py`** | 下板铺铜的**事实来源**:`REGIONS` 定义每一块。不带参数 = 计划 + 五类校核 + ASCII 分块图;**`--push`** = 校核跑绿才下发,完事自动回读复核;**`--verify`**(`--cache` 用上次回读)= 只按**实测铜**再跑一遍。下发前后走的是同一段 `grid_checks()` | ⚠️ `pcb_rules.py` 是**单板时代**的,已被 `pcb1_rules.py` 取代,别再 `--push` 它。 ⚠️ `pcb_fanout.py` **仍然是下板在用的**(`PCB = 7e94…`),载板的 `pcb2_fanout.py` 是它的壳。 ⚠️ `pcb_place.py` / `pcb-parts.json` / `pcb-pads.json` 是**单板时代**的,别再拿来摆下板。 ### ✅ 已拍板并落地:C745 往南挪 2.7 mm(2026-09-06,用户选 A) `C745` 从 A 圈上的 (100.05, 61.70) 挪到 **(100.0, 59.02)**(`pcb2_place.hub_layout()` 里单列一条, 不再跟着 A 圈的 `for` 循环走)。GND 脚 (100.0, 58.60) / +3V3 脚 (100.0, 59.44), 扇出孔实际打在**正东** (100.87, 58.60),引线 0.65。 `pcb2_place --push` → `pcb2_clear`(跨元件净距全过)→ `pcb2_fanout --only C745.2 --push` → reload → pour → `inner1_verify`(五块岛仍各 1 连通域)→ DRC **115 间距(基线)+ 332 连接性**。 ⇒ **载板上除了 J7n 那 65 个 GND 脚(归 `bm24_pre()` 管),贴片 GND 焊盘已经全部扇出。** `pcb_fanout.py` 顺手加了 `--only 位号[.引脚],...`:板上已经有整轮扇出、只想补一两个漏网的时候用, 不会重复下发另外那 247 个。
当初为什么非挪不可(留作记录) | 方向 | 挡住的东西 | 余量 | |---|---|---| | 南 | `UP_DM` 东行段 y=60.60(宽 0.28)| 孔要 0.64,只有 0.41 | | 北 | 自己的 pin1 → 再往北是 U32 南排焊盘 y=63.55 | 缝 0.73,要 1.0 | | 东 | R715 pin2 | 缝 0.667,要 1.0 | | 西 | `UP_DM` 竖段 x=99.25 / `UP_DP` 竖段 x=98.75(相距 0.5)| 塞不下 Ø0.6 的孔 | 并到邻近同网焊盘也不行 —— 最近的 GND 是 C743 pin2(2.70 mm 外),中间横着这两条竖段。
### (已作废)等用户拍板:C745 的 GND 脚被 UP 差分对堵死 `C745`(Hub 的 +3V3_HUB 去耦 0402,GND 脚在 (100.05, 61.28))是全板**唯一**扇不出去的焊盘。 不是搜索不够狠,是几何上真的没有位置: | 方向 | 挡住的东西 | 余量 | |---|---|---| | 南 | `UP_DM` 东行段 y=60.60(宽 0.28)| 孔要 0.64,只有 0.68−0.27=0.41 | | 北 | 自己的 pin1(+3V3_HUB)→ 再往北是 U32 南排焊盘 y=63.55 | 缝 0.73,要 1.0 | | 东 | R715 pin2 | 缝 0.667,要 1.0 | | 西 | `UP_DM` 竖段 x=99.25 / `UP_DP` 竖段 x=98.75(相距 0.5)| 两条之间塞不下 Ø0.6 的孔;西边那条缝只有 0.01 mm 宽 | 并到邻近同网焊盘也不行 —— 最近的 GND 是 C743 pin2(2.70 mm 外),中间横着这两条竖段。 **三个选项**(都要改布局,所以留给用户定): - **A. C745 转 90° 并东移到 (100.0, 61.7) 一带**(脚变成东西向,GND 脚在西)。 实算过:孔打在 (100.0, 62.47) 时离 UP_DM 竖段 0.75、离 U32 pin56 焊盘框 0.15,都过。 - **B. 把 UP_DM / UP_DP 那两条东行段整体往南挪 0.3~0.5 mm**(要重推 `pcb2_dp_long.py`, 下游 J23 那一头也要跟着动)。 - **C. 不管它** —— 那一颗去耦电容的地就只能靠 `pcb2_gnd_patch.py` 想办法, 想不出来就是一条连接性 DRC,实物上等于这颗电容白焊。**不推荐**。 ### ✅ 这一轮(2026-09-06 第十三轮):**载板 77 个信号网络全部布通并下发,DRC 回到基线** | | 结果 | |---|---| | `pcb2_route_sig.py` | **87 个网络 / 459 条路径 / 1030 线 / 210 孔**,一个没布通的都没有 | | 本地净距校核 | 全部通过 ✔ | | `pcb2_gnd_patch --check` | 板上悬空的 GND 连通域 **0 个** | | `inner1_verify` | 五块岛各 1 主域、3 焊盘 + 11 过孔全在主域,最窄 29.20 / 7.05 / 7.20 / 7.70 / 7.60 | | `pcb2_route_dp`(回归) | 11 对差分偏斜全部 ✔(最大 4.75 mm) | | **DRC** | **115 条 = 全部是封装自带的「贴片焊盘到贴片焊盘」基线;间距 0 新增、连接性 0** | ⇒ **载板 PCB2 布线完成。** 截图 `PCB2-v6-routed-all.png` / `PCB2-v6-hub.png` / `PCB2-v6-site4.png`。 #### BM24 那一段是整个重写的(v4 的常量在 v5 上全错) v5 把 J7n 从 rot 180 改成 rot 0 又挪了位置,站内坐标整体差 **`u' = u − 5.00` / `v' = 32.00 − v`(v 轴是镜像的)**。 照抄映射也不行 —— v 一镜像,「前排外侧那条缝」换到了另一边,而那边站着 R76n/R77n。 v5 的走法(和 v4 反过来,因为**后排 25 个脚在 v5 里全是 GND**,没有信号脚要往外挤): - **前排 5 个 GND(pin 2/8/12/16/24)→ 往内进中央通道**,v=35.00 的 0.30 梳子,4 个孔(跳过 u 50.05) - **后排 7 个 GND(pin 1/19/23/29/37/41/47)→ 往外**,v=33.10 的梳子,5 个孔(那一带一路空到 v 26) - **pin52** 往东平出到 u=59.60 打一个孔 - **THERM / CC1 独占前排外侧**:CC1 一根直线上去(R76n 中心特意取 52.233,让 pin1 正好落在 J7n.22 正北); THERM 打两个孔走顶层跨过 CC1 新增 `j7_assert()`:**每次跑都拿板上实际焊盘核一遍那一堆手写常量**(五个站 × 13 个 GND 脚 + THERM/CC1 + R76n/R77n)。 没有它的时候,坐标失配的表现是「在空地上画了五套梳子」—— 净距校核当然全过(空地不撞人), 只有 DRC 的连接性会含糊地多出几十条。 #### 这一轮修掉的四个真 bug(都在 `pcb2_route_sig.py` 里) 1. **搜索窗口边上没有墙**。障碍是裁到窗口里再栅格化的,**完全落在窗外的铜一格都不标**, 于是贴着窗口边走的线看不见它。实测 `SCL` 的窗口从 x=44.02 起,J31.6 的焊盘框(42.10~43.80) 整个在窗外,布线器就在 (44.10, 67.40) 打了个孔,离那焊盘只有 0.134。 改法:数组往外扩 `RIM=1.2` 标障碍,**再把外面这一圈整体标成占用** —— 可走区域不变,但看得见窗外的铜。 2. **钻孔间距不分网络,但 `viafree` 只看异网铜**。于是布线器紧挨着 `pcb2_dp_long` 已经打好的孔又打一个 ((92.500,70.000) 与 (92.400,70.500) 中心只隔 0.510),DRC 报「钻孔到钻孔 8.1mil,应 ≥ 11.8mil」, 净距校核一声不吭。新增 `holefree` 距离场(**所有过孔、不分网络**)。 3. **手写前置段没有一开始就进障碍表**。原来只有 GND 的在循环里补进去,THERM/CC1/+3V3 的要等轮到自己 —— 排在前面的网络照样从这些手写坐标上碾过去。 4. **`+3V3_HUB` 上 `pcb2_dp_long` 画的三条会被静默删掉**。`push_sig()` 是「本脚本负责的网络整网删掉重画」, 而那三条被 D± 夹着、后面再也挤不进去。改成从**同一个事实来源**(`pcb2_dp_long.build`)取回来当前置段, 既让最小生成树知道那些焊盘已经连上,下发时也原样重画。 #### 四段手写坐标(栅格布线器进不去的地方,理由都写在代码注释里) | 网络 | 为什么手写 | |---|---| | `THERM_n` | 出阵列后第一次拐弯的 v **只有 0.053 mm 的窗口**(下受前排焊盘、上受 `*_DPJ` 换层孔),而且那一段必须保持 0.11 | | `+3V3_HUB` U32.52→U32.46 | pin46 在东排被 VBUS_DET/NONREM0 围死,唯一出路是往 QFN **肚子里**走(南排焊盘上沿与散热盘之间那条 1.6 mm 死带)| | `+3V3_HUB` C750.1→R716.2 | R716.2 是最东那颗 +3V3 焊盘,南北被两个 GND 脚夹住,只能翻顶层斜拉过来 | | `HUB_RST` / `VBUS_DET` | R709 与 R712 之间只有 **0.66 mm** 的缝。0.15 的线走得过去(上下各 0.23),但栅格判据要 0.371 而半缝只有 0.33 | #### 一处已知的口子(不是错,是要记住的事) `+3V3_HUB` 现在由 **`pcb2_route_sig.py` 下发**(含 `pcb2_dp_long` 那三条)。 ⚠️ **再单独跑 `pcb2_dp_long.py --push` 会把 route_sig 布的整个 +3V3_HUB 删掉重画**(它按网络整删)。 要改差分的话,顺序是 `dp_long --push` → `route_sig --push`,不能只跑前一半。 另外 45° 化把那条顶层干线的近直角拐点切成了两个 **51°** 的转折(`sharp_corners` 报「切不动」)—— 净距校核过、不是直角,属于可接受;但它意味着板上那条干线的几何**已经和 `pcb2_dp_long.py` 里写的不完全一样**。 #### 为此动的两处布局(都验过并重扇出) - **R76n/R77n:v 37.15 → 38.0**,R76n 的 u 顺带改成 52.233。 理由:J7n 前排逃逸带原来只剩 0.58 mm,THERM 和 CC1 一个往东一个往北**必然交叉**,同层无解, 想让 THERM 翻顶层又打不下孔(要离前排 0.5 ⇒ v ≥ 36.80,又要离 R76n 0.5 ⇒ v ≤ 36.38)。 挪完逃逸带 1.43 mm,两条各走各的。挪之前先用新写的 `pcb2_del_box.py` 把 R77n.2 的旧扇出孔删掉。 - **C745 往南 2.7 mm**(用户拍板,见下)。 #### 新工具 `pcb2_del_box.py`:删掉指定框里某个网络的线与过孔(板坐标或**站内坐标**,站内会自动展开到五个位)。 板上的铜是好几个脚本各画各的,挪了元件之后那颗扇出孔就成了没人认领的孤儿 —— 谁的删除逻辑都不管,DRC 只报一条「过孔没连上任何东西」,很容易被当噪声放过。 判据是**完全落在框里才删**(半进半出一律不动)。 `pcb_fanout.py --only 位号[.引脚],...`:板上已经有整轮扇出、只想补一两个漏网的时候用。 ### 上一轮(2026-09-06 第十二轮):GND 扇出 + VOn_P 进岛,两步都下发并复核 | # | 做了什么 | 结果 | |---|---|---| | 1 | `pcb_fanout.py` 补三个缺口(见下);`pcb2_fanout.py --push` | **247 孔 / 122 短线**,只有 C745 pin2 扇不出去 | | 2 | `pcb2_vo_vias.py` 扩到 35 个焊盘(补 `C8n1.1` 与 `J8n.1`,各 3 孔)并修出线层 | **55 孔 / 55 线**,净距全过、0 个直角 | | 3 | `pcb2_reload` → `pcb2_pour --push` → `_refresh_geo` → `inner1_verify` | 五块岛各 **1 连通域**,3 焊盘 + 11 过孔全在主域;最窄 31.30 / 7.05 / 7.20 / 7.70 / 7.60(下限 7.0)| | 4 | `drc_read --pcb2` | **450 = 间距 115(与基线一模一样,无新增)+ 连接性 335**(布线前 519,−184)| | 5 | 截图 `PCB2-v5-fanout-all.png` / `PCB2-v5-fanout-site1.png` / `PCB2-v5-inner1.png` | | #### 这一轮修掉的四个「一直在但没暴露」的 bug - **扇出规划器从来不看板上已有的走线**(只看焊盘 / 过孔 / 钻孔)。下板做扇出时板上一根线都没有, 所以从没暴露;载板 v5 是先布了 178 条差分 + 3V3 才扇出的,不修的话过孔会直接压在 D± 上, 而**几何验证抓不到**(它只查连通性)。现在 `EXIST_LAYERS` 把 1/2/15/16 层的线全登记成障碍, **线对线分层比、过孔对线不分层**(过孔贯穿)。 - **`dump_pads.py` 有 88 个焊盘认不到位号**:pad 坐标是干净的、pin 坐标带 1 µm 噪声, 两边各自 `Math.round` 到 0.01,落在 `.xx5` 上的值进了不同的桶。改成 Python 端最近邻配(容差 0.02), 846 个里认到 805(剩下 41 个是螺钉孔和手机定位柱,本来就不属于任何元件)。 **这个 bug 会静默传染**:J75 pin15/16 认不到主人,「J7n 的 GND 脚整批跳过」就漏掉两个。 - **第三遍「并到旁边的同网络焊盘」没有长度上限、也不查走线/钻孔**,实测把 C745 pin2 一路并到 **9.78 mm** 外的 J64 pin3(而且那是另一层的焊盘,画出来根本不通)。 现在 `LINK_MAX = 3.0`,并且要求目标是通孔焊盘或同层。 - **`pcb2_vo_vias.py` 的出线层写死 `L_TOP`**。v1 时代器件全在顶层没事;v2 之后 VOn_P 的 35 个焊盘 **全在底层**,照旧会画出 35 条悬在顶层、一头不搭焊盘的线 —— 而且 `fits()` 拿这个 lay 去筛障碍, 连净距校核都是照着空荡荡的顶层算的,一条都不会报。 #### 两处和上一轮的计划不一样(都是实算之后改的) - **U32 pin13 的孔位没有钉在 (94.6, 71.4)。** 那个位置过孔放得下(西边走廊 x ∈ [94.59, 94.625]), 但**引线出不去**:pin13 与 pin14 之间只有 0.20 mm,斜着往西北一定切进 pin14 的净距圈; 平着往西的话过孔又躲不开 `USB5_DP` 的横段(y 69.75,只差 0.5 < 0.64)。 正解是往**东**打进 QFN 肚子里 —— 引脚环东沿 95.975 到散热盘西沿 97.600 有 1.625 mm 净宽, 自动搜索把 pin13 放在 (96.50, 70.25)、pin19 放在 (97.25, 71.50),离差分车道都在 2 mm 以上。 - **J7n 的 65 个 GND 脚整批不扇了**(`SKIP_PAD`)。它们是 `bm24_pre()` 手写死的两条梳子 + pin52 单独出线; 让自动扇出去碰的后果实测是「十个必然失败 + pin 1/2/52 三个『成功』的孔正好落在梳子和车道上」, route_sig 还得写个删除框把它们收回去。 #### 一处对设计文档 §10.3 的有意偏离 §10.3 写的是「功率网络每个焊盘打 3 个过孔」。这一轮 `VOn_P` 只给 **`J8n.1`(这一路 4 A 的唯一去路)和 `C8n1.1`(2200 µF 主储能)各 3 个**, `C8n2`~`C8n6` 仍是各 1 个 —— 全按 3 个来会往五块岛里再钻 50 个反焊盘, 最窄断面(下限 7.0)扛不住。这五颗只承担高频纹波,单孔 1~1.5 A 够用。 ### 上一轮(2026-09-06 第十一轮):v5 差分布线全部落地 **详见设计文档 §25.16 补 6。** 走法表、顶层参考面、45° 斜段规矩都在那里,这里只记做了什么和踩了什么。 | # | 做了什么 | 结果 | |---|---|---| | 1 | `pcb2_dp_long.py` 整个重写(v3 存 `obsolete/`):pair 1 直西、pair 2 正北、pair 3 绕 Hub 北边东行、**pair 4 整段顶层**、pair 5 绕北边再南下跨 UP、UP 底层直东 | 本地净距全过 | | 2 | `SITE_J` 按 v5 站内坐标重写:DMJ 底层 45° 斜进、**DPJ 翻顶层**跳过去(两排之间只有 1.515 mm,孔只能落在 y 36.805~37.31)| 五站同构 | | 3 | pin5 / pin10 两条 +3V3 车道 + 顶层干线一起画(被 D± 夹住,后面的布线器进不去);C747 → (88.2, 71.5)、C748 → (92.5, 71.2) | `pcb2_place --push`、`pcb2_clear` 过 | | 4 | VO4 岛主体东界 89 → 87(顶层段不许跨岛边);`topref` 检查 0 处跨界 | reload → pour → `inner1_verify` 五块全 ✅ | | 5 | `--push`:182 线 / 26 孔;DRC 634 = 间距 115(基线)+ 连接性 519 | 无新增间距错误 | #### ⚠️ 这一轮踩到的 - **0.5 脚距的车道之间打不了孔**(孔要离两边车道各 0.645)。出 QFN 的次序只能是「最外侧先拐走,邻居走了才打孔」, pair 4 的两个孔为此上下错 0.9、DP4 先抬 0.6 再打。想改走法先在纸上按这条排一遍。 - **`chamfer()` 不能对 45° 拐再切一刀**:切出来的 22.5° 小段向内挪 0.1,实测把 DP4 抬升段切进 pin10 车道净距圈。已改成转向角 ≤ 46° 不切。 - 上一版脚本里的 U32 坐标(x 105.55)是 v1 的,U32 早搬到 (100, 68) 了 —— 改布局后**所有写死坐标的布线脚本都要重推**,别只改一处。 - 同宽共线的两段下发后被 EDA 合并成一条(182 → 178),但**不同宽**的共线段(BM24 出线 0.11 / 0.20 / 0.28 三档)保留 —— 换挡点安全。 ### ✅ 这一轮(2026-09-06 第八轮):载板 v4 —— 尾插槽到 J1401 正下方,J7n 到主板下方远处 **详见设计文档 §25.16「2026-09-06 补 3」。** 用户指令:尾插槽放在主板尾插接口下方;载板尾插座放在主板下方远处、50 mm 排线能到的地方;连接时排线走载板背面。 | # | 做了什么 | 结果 | |---|---|---| | 1 | `SLOTS['tail']` → u 45~65 / v 1.4~4.4(J1401 正下方);`TAIL_LANE` u 44.6~62.6 / v 4.4~38.3 不放元件(`check()` 新增一条)| 本地检查过 | | 2 | J7n → (53.5, 37.0) **rot 0**;U5n (49.8, 41.5, rot 180) / R76n (52.8, 39.15) / R77n (55.8, 39.15) 挪到 J7n 北边;J6n (62.5, 41);行 D 西移 4.5 | 离 H1 4.0 / H3 2.9,`pcb2_clear` 816 焊盘净距 ≥ 0.20 | | 3 | `--outline --push --verify --heights` | 槽 10/10;J71 回读 pin51(VBUS) 西端 (52.47)、pin4/6 北排西端 (54.65/55.0, 121.06)、奇数排南 ✔ | | 4 | `pcb2_reload` → `pcb2_pour --push` → `_refresh_geo` → `inner1_verify` | 五块岛各 1 连通域、焊盘全在主域,最窄 7.05 / 7.20 / 7.70 / 7.60 / 20.85(下限 7.0)| | 5 | 截图 `PCB2-v4-bottom.png` / `PCB2-v4-ch1.png` | 自己看过:槽在 J1401 下、J7n 在远端、U5n 在北 —— **还要用户确认** | #### 为什么 J7n 从 rot 180 改成 rot 0(不是拍脑袋) 排线只绕 u 轴弯(接口边一个 U 形回折 + 槽里一个台阶),不在面内转 ⇒ J7n 的触点图案是 J1401 图案的**平移**; J1401 的 pin1/2 端(G1)在 +u ⇒ J7n 的 pin1/2 端要在 −u;封装 pin51 在本地 −X、底层翻层不改 x ⇒ rot 0。 v3b 的 rot 180 把 VBUS 端放到了东边,和 §24.3「VBUS 落到 −X 端」矛盾。翻面次数(回折 1 次、台阶 0 次)算出来公头朝 +z,正好插朝 −z 的 J7n。 剩下唯一没定的是封装奇/偶排分配(§24.3 镜像位),错了只改焊盘编号,D± 换到南排西端。 ### 上一轮(2026-09-06 第七轮):载板 v3 + C8n1 换矮封装(槽位置已被 v4 取代) **详见设计文档 §25.16 末尾「2026-09-06 补」。** | # | 做了什么 | 结果 | |---|---|---| | 1 | 槽挪到主板远端边(v 45.5~48.5),手机位内移 2.5,器件全部收进主板下方 | 本地检查 / 净距 / 镜像回读全过 | | 2 | `pcb2_pour.py` ISLANDS v3:走廊主体 + 主板下方整条带,VO2/VO4 为顶层差分收边 | `inner1_check` / `inner1_verify` 五块全 ✅(最窄 7.05)| | 3 | C8n1 → Rubycon 6.3TPV2200M10X10.5(C3828985):个人库建器件、`c4_site.py` 五页重画、PCB 导入 80+80 | 极性丝印核过(`PCB2-v3-c801.png`)| | 4 | 为差分让位挪 C747/C748、U31 组、U34/C760、R721/722、R717/718 | 见 `pcb2_place.py` 注释 | | 5 | 新工具 `pcb2_reload.py`(重载客户端解卡)、`pcb2_snapshot.py` | | #### ⚠️ 这一轮踩到的 - **`lib_Device.create()` 返回 undefined 但器件已建出来**;`copy()` 从系统库复制返回 null 且真没建; 个人库内 `copy()` 可用;给个人库器件 `modify(association.footprint = 系统库封装)` 会把封装**复制进个人库**(新 uuid)。 建完一律 `search` + `get` 核对,别看返回值(skill §9y)。 - **铺铜「重建所有」在大量 API 改动之后必卡**(本轮三次),重载后一次就通 ⇒ `pcb2_reload.py` 然后 `--push`。 - `pcb2_place.py --push` 的 `modify()` 里 layer 和 x/y/rot 分两次下发(一次下发 rot 偶发不生效)。 ### 上一轮(2026-09-06 第六轮):载板 v2 —— 全部到底层 + 穿线槽(已被 v3 部分取代) **详见设计文档 §25.16。** 用户两条指令:正面器件全放背面并在背面重新布局;排线通过板内细长槽穿到载板。 中途补充:器件可以放到主板投影下方(底层),方便布线。 | # | 做了什么 | 结果 | |---|---|---| | 1 | `pcb2_snapshot.py`:先把 v1 全部铜/元件抓成 `_pcb2_snap_topside.json` | 161 件 / 1586 线 / 416 孔 / 7 铺铜,可回滚 | | 2 | 实测底层镜像语义(U51/J71/R761 翻到底层读焊盘再翻回来) | 本地 y 翻 → 再转 rot;两脚件 pin1 方向不变,SOT23/BM24 南北排对调 | | 3 | `pcb2_place.py` 重写成 v2 | 顶层 20 / 底层 141;槽 10 条;C8n1 卧倒 keep-out;新增「压槽 / 螺钉头 / 顶层只许顶针」三项检查 | | 4 | `--clean-copper --outline --mech --push --verify --heights` | 清 1416 线 + 416 孔 + 7 铺铜(另有 16 条漏网线第二遍才删掉);槽建 10/10;镜像回读全 ✔ | | 5 | `pcb2_clear.py` | 跨元件净距全部 ≥ 0.20 | | 6 | `pcb2-bottom-heights.json` | 底层 141 件高度图 + 10 条槽,给下板布局避让 | #### ⚠️ 这一轮踩到的三条(都写进 skill) - **底层元件的焊盘位置不能推**:翻层不是绕 y 轴镜像,是本地 y 翻。J23 用顶层那版的 rot 90 放到底层, 口会朝板内;改 rot 270 并把 R717/R718 的 y 对调,`--verify` 回读核过。 - **挖槽区域 = `pcb_PrimitiveFill.create(12, polygon)`**(layer 12 = MULTI)。跑道形用两段 90° 弧, 和板框一样逆时针、正角度。 - **批量 `delete()` 一遍删不干净**:1416 条按 `getAll()` 删完,回读还剩 16 条(旧 BM24 GND 梳子, 正好压在新槽上)。删除循环要跑到剩余为 0 再收。 ### 上一轮(2026-09-06 第五轮):载板收尾 —— **已被 v2 整体作废,只留作历史** **详见设计文档 §25.15。** | # | 做了什么 | 结果 | |---|---|---| | 1 | `.claude/pcb2_vo_vias.py`:25 个贴片电容各打一个过孔下到 Inner1 的岛 | 25 线 / 25 孔,**全部一段直线**,连接性 −31 | | 2 | `.claude/pcb2_gnd_patch.py`:自己算出悬空的 GND 连通域再补孔 | 5 个域(`C749.2`/`R710.2`/`U32.19`/`J23.A1B12`/`J23.B1A12`)→ 6 线 / 5 孔,连接性 −10 | | 3 | `.claude/pcb2_npth.py`:从铺铜反焊盘反解**封装自带的非金属化孔** | J23 里两个 **Ø0.600 @ (213.660, 59.100) / (213.660, 64.900)** | | 4 | `inner1_verify.py` 加了**过孔**校核 | 五块岛 **5 焊盘 + 5 过孔全在主域**,最窄断面 7.20/9.10/10.15/7.35/7.60(下限 7.0)| | 5 | `pcb2_gnd_patch.py --check` | 板上悬空 GND 连通域 **0 个** | | 6 | DRC | **125** = 同封装内 115(与基线一模一样)+ 连接性 10 + 间距 0 + 物理 0 | #### ⚠️⚠️ 这一轮最值得记住的:**封装自带的孔,API 一个都读不出来** J23 的封装里有两个 Ø0.60 非金属化定位孔,正卡在 `A1B12`/`B1A12` 两个 GND 焊盘的外侧角上。 `pcb_PrimitivePad.getAll()` 里没有、`pcb_PrimitiveRegion`/`pcb_PrimitiveFill` 是空的、 `getPrimitiveByPrimitiveId('e55')` 返回空、`getPrimitiveAtPoint()` 只回最上面那条线。 **只有 DRC(「挖槽区域 到 导线」)和内层铺铜的反焊盘看得见它们。** ⇒ `pcb2_npth.py` 拟合铺铜环里的圆弧,用「已知过孔的反焊盘半径 − 过孔半径」标定净距 (实测 0.3583,234 个样本,离散度 0.005),再反推孔径。 #### 另外三条 - **净距校核只看铜,钻孔是另一条规则。** 第一版打的孔落在同网 `J23_4` 的**焊盘里**, 校核全过,DRC 报「钻孔 到 钻孔 距离 0mil」。同网的通孔焊盘/过孔都要按 `DRILL=0.33` 再判一遍。 - **金属化的槽孔焊盘和非金属化孔待遇完全不同**:线可以压在焊盘自己的孔上, 不能压在非金属化孔上。混在一起判的话,`J23.1`/`J23.4` 这种槽孔焊盘**连碰都碰不得** (焊盘上任何一点离自己的槽都不足 0.80 mm)。 - **只加不删的下发必须记账。** `pcb2_gnd_patch` 把自己画的坐标存 `_pcb2_gnd_patch.json`, 下次先删再画。不记账实测多出一对旧坐标的过孔,而且新画的细线被 EasyEDA 并进旧的粗线里。 ### ✅ 这一轮(2026-09-06 第四轮):栅格迷宫布线器,载板剩下的 77 个网络 **详见设计文档 §25.14。** 事实来源是 `.claude/pcb2_route_sig.py`(BM24 周围的车道 是手写死的几何,其余交给布线器)。 | # | 做了什么 | 结果 | |---|---|---| | 1 | 新写布线器 `pcb2_route_sig.py` | 栅格 0.1/0.05/0.025 三档 + scipy 稀疏图 Dijkstra + 45° 整形;**产出过同一份 `check()`** | | 2 | 77 个网络 | 431 条路径 / **991 段导线** / **160 个过孔**,净距校核全过 | | 3 | BM24 的 65 个 GND 脚(§25.8 的待办)| 两条梳子 + 12 个过孔/站,**中央通道那 1.64 mm 是这一轮才用上的** | | 4 | 抓到并修掉 `R76n` 装反 | rot 0 → **180**。不改的话五个站的 `VBUSn` 全布不通 | | 5 | `+5V` 线宽 0.80 → **0.60** | §25.10 说了段电流 ≤0.6 A;0.80 在走廊里挤不过去 | | 6 | `PWR1A` 的 `strokeWidthMin` 0.25 → **0.15** | `+3V3_HUB` 要穿 Hub 卫星圈 0.659 mm 的缝;载流够(420 mA / 0.15 mm 可载 ~0.8 A)| | 7 | 布完线重新铺铜 | 不铺的话 DRC 多出 **445 条**「铺铜区域到过孔/钻孔」(新过孔没有反焊盘)| | 8 | DRC | **166** = 同封装内 115(**与 §25.8 基线一模一样**)+ 连接性 51 + 间距 0 + 物理 0 | | 9 | **45° 化**(用户拍板)| `sharp_corners` / `chamfer45` / `smooth_micro` 三个工具放在 `pcb2_route_dp.py`,两个脚本共用。差分对 10 → 0、信号线 72 → 0,**DRC 不变** | #### ⚠️⚠️ 这一轮最值得记住的一条:**`expand()` 会静默丢掉路径末端的过孔** `pcb2_route_dp.expand()` 打过孔的条件里有 `i + 1 < n - 1` —— **最后一个点被显式跳过**。 于是「走到目标正上方再换层落下去」这种路径,过孔没了,两层的铜各画各的、中间断开。 **三道校核全都抓不到**:净距校核只看撞没撞(断开当然不撞)、 `inner1_verify` 只看铜面、DRC 的连接性又要 `location.reload()` 才刷新。 踩到两次:布线器自己的路径断了 5 个网络;**手写的 `bm24_pre()` 断了 50 个 GND 过孔** (DRC 里 GND 55 条,补上之后掉到 10)。 ⇒ `_end_via()`:末点上有换层就补一个重复点,**所有**路径(含手写的)都要过这一道。 #### 另外三条(都写在 §25.14 里) - **障碍要先胀 `QERR` 再栅格化**,否则距离场高估 0.08 mm,净距校核在下发前一刻才炸。 - **`location.reload()` 会被「离开该页面」确认框挡下来**(文档有未保存改动), 点「留在该页面」= 根本没重载,客户端仍卡在后台任务上, 于是 `铺铜管理器…` 点了没反应。**重载前先 `save()`。** - **先给所有焊盘留好出线段再开始布线**(escape routing 先行), 否则先布的网络会把后布的网络出焊盘那 0.6 mm 占掉。 ### ✅ 这一轮(2026-09-06):载板 Inner1 分割电源面 **详见设计文档 §25.11。** 事实来源是 `.claude/pcb2_pour.py` 的 `ISLANDS`,改铜面改那里再 `--push`。 | # | 做了什么 | 结果 | |---|---|---| | 1 | 五块 `VOn_P` 岛 + GND 底填落到 Inner1 | 优先级 L15 `VO1= 0.2 mm ✔ 814 个焊盘 pcb2_clear.py 同封装内负间距 ✔ 0 对 铜层数 ✔ 2 -> 4(id 1/15/16/2) 截图 `PCB2-carrier-v2.png`。 ### 上一轮(2026-09-04 晚)做完的两件 1. ✅ **顶针占位型号换掉了**:`C9900001338` → **YTC1P-2510-01(立创 C5221288)**, 配 **3.0 mm 立柱**(原来文档写的 2.0 mm **买不到**,见 §22.3e 的整族对照表)。 `c4_site.py` 已改,C4-SITE1~5 五页重生成,六项验证 + 跨页对账 + port_audit 全过。 2. ✅ **C1-CONN 抓到并修好一处真断线**:GND 旗写在 y=362,旗被吸附到 360、导线端点留在 362, 旗压在导线中段 → **J31 pin1 的地是断的**。`verify_sch` / DRC / 跨页对账全过, 只有 `port_audit.py` 抓得到。已在 `c1_conn.py` / `c4_site.py` 加「坐标必须是 5 的倍数」断言, 成因写回 skill §8a-2b。⚠️ **上一轮记的「可疑 0」不能当数,每轮都要重跑。** ### 上一轮(2026-09-04 深夜):USB 定为方案 D,载板改造已完成 **方案 D**(设计文档 §13.5):手机 J1401 ──原厂「主板→尾插」排线── 载板上一颗同型号 BM24-50DS 座 ──> 载板上的 USB 电路。 用户拍板:天线自己另买插主板上,麦克风 / 震动马达不要,「只要能开机正常运行系统就行」。 **载板改造做完了,三道验证全绿**(verify_sch 每页 PASS / xpage 0 漏接 / port_audit 载板 330 个可疑 0、下板 513 个可疑 0): | 页 | 改了什么 | 脚本 | |---|---|---| | **C5-J1401A / B**(新)| 五颗 **J71~J75** BM24-50DS 座(`C424561`),A 放 CH1~3、B 放 CH4~5 | `.claude/c5_j1401.py` | | **C5-AUX**(新)| 每路 **U5n** USBLC6 ESD + **R76n** 56k(CC1→VBUS) + **R77n** 100k(口温→GND) | `.claude/c5_aux.py` | | **C3-USB**(改)| **删掉 J17~J21** 五个 1.25mm 出线座;D± 不再经过这一页,只留 VBUS 负载开关 | `.claude/c3_usb.py` | USB 链路现在是:`C2-HUB 的 U32 → C5-AUX 的 U5n → C5-J1401 的 J7n → 原厂排线 → 手机 J1401`。 NC 清单 `.claude/c5-nets-nc.json`(每颗座子 34 个悬空脚,含必须悬空的 CC2)。 截图:`C3-USB.png` / `C5-J1401A.png` / `C5-AUX.png`,都看过。 **调试串口(pin24 TX / pin29 RX)故意没引**:1.8V 电平接 ESP32 要加电平转换, 且 pin29 夹在两个地脚中间会把排版挤垮。要用是一次改板。 ### 上一轮(2026-09-04 深夜):Board3 画完又作废 —— USB 方案改回 A(后又改成 D) 先按方案 C 画完了 Board3(J1401 转接小板),当晚用户确认「用排线连接」之后 **改回方案 A:装原厂尾插小板 + 一根改端子的 USB-A 转 C 线**。 Board3 / `phonerail-usbtap` / `PCB3-USBTAP` 已从工程删除, 生成脚本挪到 `.claude/obsolete/t1_usbtap.py`。理由对照表见设计文档 **§13.5**。 方案 A 当时是最优的,但用户随后提出「直接用原厂排线连主板和载板」—— 那就是方案 D,比 A 更紧凑、电路全在载板上好改。**方案 A 已作废**, 它唯一的优势(保留天线馈点)被「用户自己另买天线」抵消了。 作废但仍有效的四条结论留在 **§23**:立创 BM24 封装编号是错的(同时佐证了 §13.4 的引脚表)、 BM24 只有 10 次插拔寿命、Rp/Rd 分压核算、以及两条 EasyEDA API 经验(skill §8b / §8c)。 ### 上一轮(2026-09-04 深夜):BM24-50DS 封装自建完成 **下一步四件里的第 2 件做完了。** 器件在个人库:`BM24-50DS/2-0.35V(51) HRS-PIN` (`771dbf002b714bb79cde47cf86aa0fb1`,封装 `940f093c1578426aa81e91ebf0bfbd76`), `c5_j1401.py` 已改用它,C5-J1401A/B 重生成,**三道验证全绿** (verify_sch 两页 PASS / xpage 0 漏接 / port_audit 载板 330 个可疑 0)。 生成脚本 `.claude/fp_bm24.py`,封装截图 `FP-BM24.png`。逐项尺寸与来源写在设计文档 **§24**。 几何**不是外推、也不是抄立创**:从 Hirose 官方样本(D47680_en)的 PDF 矢量图里 逐段反算的,50 芯的 A/B/C 表直接可用。信号焊盘 0.18 × 0.48 @ 0.35 间距、两排 y = ±1.06、 电源焊盘 0.49 × 0.76 @ x = ±6.03。 #### ⚠️⚠️ 但是多出一件必须做的事:**镜像位实测**(§24.3) 原来文档里写「两种候选正好差一个 180° 旋转,转贴片方向就能切换」——**这句是错的**, 候选有 **4 个**:2(1 脚在哪一端)× 2(**奇数脚在哪一排**)。 前一位靠旋转能纠,后一位是**镜像,错了要重打载板**(D+ 会落到 45 脚 `SPKR_OUT_P`)。 BM24 的 DS 座是**完全对称件**(Hirose 样本注 2:*no polarity*,本体两端与上下两排全一样), 所以「哪一排是奇数排」查图纸查不到 —— 它是**小米8 那块板的布局事实**,只能量。 **量法(两个探点,零成本零风险,打样前必做)**: 1. 两个电源触点(焊盘大好戳)分别对主板地量电阻,~0 Ω 的是 **G1 = 1/2 脚那一端**。 2. 从 G1 端数,两排各自的**第 2 个信号触点**对地量:通的那排是奇数排(pin3 = GND; 偶数排第 2 个是 pin4 = `VREG_L19A_3P0`,不通)。 结果若是「奇数排在 −Y」,把 `fp_bm24.py` 的 `ODD_ROW_TOP` 改成 `False` 重跑一遍即可。 ### 上一轮(2026-09-04 深夜):载板 PCB 从零布起 **板框 220 × 135(R5 圆角)**,五块主板 **3+2 双排面对面、第二排居中**(用户拍板)。 原理图 161 个元件全部导入并摆好,本地四项检查全过(位号齐全 / 不越框 / 分层重叠 / 顶层不压主板投影)。 逐项见设计文档 **§25**,脚本 `.claude/pcb2_place.py`,截图 `PCB2-carrier-v1.png`(全板)/ `PCB2-site1.png`(CH1 位)/ `PCB2-j23.png`。 已落盘的东西:板框 1 条闭合多段线、载板自己 4 个 M3、**35 个手机定位孔(NPTH Φ2.5)**、 5 个手机位丝印外框 + 标签、161 个元件(顶层 133 / 底层 28)。 #### ⚠️ 抓到一个会让侧键永久短路的封装错误(§25.3) 立创给顶针 `C5221288` 的封装焊盘是 **Ø2.5**,而小米8 四个侧键触点最近的一对 (J601↔J604)只隔 **2.217 mm** —— 两个焊盘直接叠在一起,音量+ 会被永久按住。 顶针法兰其实只有 Ø2.0,是立创把焊盘开大了。**原理图侧完全看不出来**。 已自建 `POGO-SMD1P_D1.9`(焊盘 Ø1.9,净间距 0.317 mm),`c4_site.py` 改用新器件, C4-SITE1~5 重生成、三道验证全绿、PCB 重新导入。 #### 三条布局上的硬决定 1. **J31/J32 排母在底层**(下板公头朝上,载板母座必须朝下)。 「第 r 行对第 r 行」不靠脑补:`--push` 会把载板侧 80 个焊盘坐标导出到 `.claude/pcb2-header-pads.json`,下板照数值摆即可(下板目标 = 导出值 − (30,5))。 2. **USB Hub 也在底层、压在通道正中 (110,67)** —— 放左右口袋的话最远那台 D± 要走 200+ mm,超 USB2517I 手册的 152 mm 上限;放中间后最长 92 mm。 ⚠️ 布下板时别让 C2/C3(12 mm 电解)撞上载板底面的元件。 3. **35 个定位孔做 NPTH,立柱必须用尼龙** —— 金属化 + 金属立柱 = 五台手机的地 在载板上并成一根,4 A 回流会绕开专用 `P−` 针,违反 §6.4。 #### 载板这一版还欠的(详见 §25.5) ✅ J23 已定稿(rot=270、x=5.134,壳体前端面与板边齐平)· ⬜ Hub 那 26 个件是网格粗排、布线前要手调 · ⬜ 设计规则 / 铺铜 / 布线一根没做 · ⬜ 主板 7 个定位孔的孔径没量 ### 上一轮写的「下一步四件」(第 1/2/5 件已完成,剩下的搬到文首) | # | 做什么 | 备注 | |---|---|---| | 1 | ✅ **排母选型 + 换件实施全部完成**(2026-09-05 夜,见文首)| | 2 | ✅ **自建 BM24-50DS 封装 + 镜像位实测**(§24.3)—— 全部完成,结论是当前的 `ODD_ROW_TOP = True` 就对 | | 3 | **下板 PCB 导入变更** | 删了 15 个连接器 + 53 个 7xx 器件,新增 J24~J30 / U33,接口区要重排,最后统一重铺铜。⚠️ 下板的铜面 2026-09-05 被误重铺过一次(无损失,本来就要重做)| | 4 | 🟡 **载板 PCB** —— 摆位 + 规则 + 差分对 + Inner2 地平面 + 6 个 M3 + GND 扇出 + 换件重摆/重扇/重铺 + **Inner1 分割电源面**都已完成。⬜ 只剩 **布线** | | 5 | ✅ **载板上半部分的安装点** —— 已加两个中跨 M3 (76.6, 80.1) / (148.1, 80.1),§25.7 ③ | 改完照例:`verify_sch.py`(当前页)+ 跨页对账 + `port_audit.py` (载板加 `--board Board2`),三个都干净了再让用户看截图。 **动 PCB 的话再加一条 `pcb2_clear.py`**(焊盘级净间距,自由焊盘只有它管)。 #### 载板布线的下一步,按顺序 1. ✅ **换设计规则** —— `.claude/pcb2_rules.py --push`,`--check` 十一项全过(§25.7 ①)。 2. ✅ **建差分对** —— **11 对**(5 长 + 5 短 `*J` + UPSTREAM),短段也建了对。 3. ✅ **Inner2 整板 GND** —— 一块 `GND-INNER2`,81 块填充,已存盘(`.claude/pcb2_pour.py --push`)。 4. ✅ **GND 扇出** —— `.claude/pcb2_fanout.py`,换件后已 clean → dump → push 重来一遍, **210 个过孔**。J31/J32 那 33 个 GND 脚**不再需要扇出**(通孔件自己就是过孔, `one_net()` 的 `todo` 带 `not p['hole']`)。 ⬜ 只剩五颗 BM24 的 51 个 GND 脚(**过孔塞不进 0.35 间距,只能逃逸布线**,§25.8)。 5. ✅ **Inner1 分割电源面** —— **2026-09-06 铺完**(五块 `VOn_P` 岛 + GND 底填, §25.11 / `pcb2_pour.py` 的 `ISLANDS`)。三个校核脚本全过: `inner1_check.py`(下发前预测)/ `inner1_verify.py`(下发后实测,最窄颈 7.20 mm)/ `inner1_diffref.py`(§10.1 参考面)。 ⇒ `+5V` / `+3V3_HUB` / `VOn_P` 的扇出现在可以做了(之前扇过去全是悬空过孔)。 6. 🟡 **布线** —— 先 D±(长的先走),再电源,最后杂散信号。 ⚠️ **BM24 的逃逸布线单独排一项**:前排往南、后排往北,同排横向穿不过去(净距 0.17 mm)。 #### Inner1 分割电源面的规划(⚠️ 这是 09-05 的规划稿;**落地结果与实测见设计文档 §25.11**) **层角色**:Inner1 = 电源面(分割),Inner2 = 完整地平面(§25.6 ①,和下板是镜像的)。 **只做三类岛,其余全部 GND 底填**(不是原来写的「+5V / +3V3_HUB / VO1~5_P / VBUS1~5」 十二块)——理由是内层是 0.5 oz,按 IPC-2221 内层算 4 A 要 **6.97 mm** 净宽、 2.8 A 要 **4.3 mm**,而两排手机之间的走廊(y 49.66~85.5)只有 35.8 mm 高: | 岛 | 网络 | 为什么要 | 最小颈宽 | |---|---|---|---| | 5 块 | `VO1_P`~`VO5_P` | 每路 4 A,这是上四层的头号理由 | ≥7.0 mm | | 底填 | `GND` | 剩余全部,**优先级数字给最大** | — | > ⚠️ **2026-09-05 夜简化:`+5V` 和 `+3V3_HUB` 两块岛取消了**(§25.10)。 > 排母逐针分配按几何重排之后,`+5V` 从 3 针变成 **10 针、分散在两颗排母的南北四处**, > 每个入口只喂本站的 `U2n` + `C70n`、段电流 ≤0.6 A ⇒ **顶层 0.5~0.8 mm 走线就够**, > 内层不用给它留地方。`+3V3_HUB`(420 mA、局部)同理。 > ⇒ Inner1 只剩「**五块 `VOn_P` + GND 底填**」,五路 4 A 各走各的,互不挤。 **`VBUS1~5` 不做岛**:每口 0.5 A、走线只在本服务带内约 18 mm,`PWR1A`(0.6 mm) 走线足够。 额外好处:顶层那 5 段 `*J` 差分(U5n→J7n,5 mm)下方的 Inner1 就是**完整的 GND 底填**, 参考面比分割的电源面干净得多(§25.7 ① 那条警告因此自动消解)。 **走廊里已知的障碍**(Inner1 只怕直插焊盘,不怕表贴件): `C801/C811/C821` 三个 Ø12.5 电解在 y 68.25~80.75,`C831/C841` 在 y 54.41~66.91, 两个新中跨 M3 的 Ø6 焊盘在 (76.6, 80.1) / (148.1, 80.1), 外加 J31/J32 的 **80 个通孔焊盘**(坐标已定稿:J31 x 37.869~86.129 / J32 x 133.869~182.129, 两行 y = 66.23 / 68.77,见 `pcb2-header-pads.json`)。 ⚠️ 2.54 间距的通孔在内层会把铜面打成筛子(Ø1.7 焊盘 + 0.2 净距 ⇒ 相邻反焊盘之间只剩 0.44 mm 网), 电源岛的颈宽要绕开这两条带,别指望从排母底下横穿过去。 ### 还欠用户的五件事 0. ~~BM24 的镜像位实测~~ → ✅ **2026-09-05 已完成,结论「不用改」**(§24.3)。 可选的零成本复核(还没做):奇数排上从 G1 端数,位置 17-18-19 与 22-23-24 各自三连导通 (`SPKR_OUT_N` 33/35/37 和 `SPKR_OUT_P` 43/45/47),偶数排没有这种三连。 0b. ~~量排线两个公头之间的距离~~ → ✅ 用户拍板不管,装配时折叠(余量约 80 mm,会占手机上方高度)。 1. **h_up**:主板**正面**(朝上那面)最高元件高出板面多少。只影响压板高度与整机层高。 2. ✅ **排针/排母选型已定**(§22.3f):`C55020601` + `C5307344`,对插 13.0 mm。顶针已定稿。 ⬜ **到货要核两件**:母座触头镀层(商城只写了「黄铜」,没写镀层)、 对插后实测板间距是否真的 13.0 mm(确认是塑料贴合、不是针先顶死)。 2b. **首件上电前量一次**:`VBUSn` 对 GND 的电阻(确认电源触点 G1/G2 没接反,§13.5)。 负载开关默认关断,所以这一步零风险、零成本。 3. **顶针首件必验**:±1 mm 公差链对 1.2~2.2 mm 的侧键触点是危险的,而且 **选定的顶针冠径只有 Ø0.9 球头、又是贴片不可调**,所以首件实测是**唯一**对策(§22.3c)。 4. **用卡尺重量那 7 个定位孔**:±1 mm 里有 ±0.5 来自「孔位是从照片提的」, 量一遍能直接砍掉一半误差 —— 这是眼下性价比最高的一项。 --- ## 这一轮做了什么(2026-09-04 傍晚,载板画完) ### 载板 8 页 `C1-CONN`(排母 J31/J32 + U31 5V→3.3V + U34 LM75BD 温度芯片 0x48)、 `C2-HUB`、`C3-USB`、`C4-SITE1~5`(每台手机一页:P+/P− 焊盘 + §6.3 电容组 + 4 根侧键顶针 + EDL 座/模拟开关/下拉/去耦)。逐页内容与位号见 §22.7。 **手机位一台一页**,和下板「一路一页」保持同一结构;五页由 `.claude/c4_site.py` 同一段代码生成,改一处五页一起变。 ### 这一轮自己发现并补上的两个洞 1. **`UP_VBUS_DET` 在载板上没有源头。** §22.2 给它留了一根排针,但 J23 的 VBUS 是 5V, 排针那头是 3.3V 域的 IO44。在 C2-HUB 上补了 **R721 10k / R722 20k 分压**(3.33V)。 2. **载板上原来没有 3.3V。** 排针只送 +5V,而 Hub、温度芯片、五颗 EDL 开关都要 3.3V。 U31 从下板搬过来后画在 **C1-CONN**(+5V 进来的那一页);§9.5 要求的「就近 Hub 供电」 是 PCB 摆位约束,与分页无关,已在文档里说清楚。 ### 两个明知不对、故意先放着的占位型号(⚠️ 顶针那条已于当晚解决,见文首) | 占位 | 差在哪 | |---|---| | ~~顶针 `C9900001338`~~ ✅已换 | 自由高 **9.0 mm**,§22.3e 要的是工作高 2.0 / 自由高 3.0~3.5 / 冠径 Ø1.5~2 | | 排母 `C9900025309` | 高度没按 13 mm 净空选;而且编号体系和公头不同(§22.2 有对照表)| 放占位是为了让网表先连通、能跑完整验证;**两个都在 §22.7 里单列了一节,打样前必换。** ### ⚠ 一个必须记住的编号陷阱 下板公头(DIP 式)第 r 行 = 引脚 r / 41−r;载板母座(IDC 奇偶式)第 r 行 = 引脚 2r−1 / 2r。 **只能按行对,不能按引脚号对**(`VO1_P` 在下板是 1/2/3 脚,在载板是 1/3/5 脚)。 原理图这边不会错 —— `c1_conn.py` 直接 import `p5_btb.py` 的行表,两边同一个事实来源。 **风险在 PCB**:焊盘编号 + 摆放镜像决定第 r 行会不会真对上第 r 行,布板时要实打实核一次。 ### 新踩的两条 EDA 坑(已写进 skill §6c / §6d) 1. **同一颗电解、同样 `create(270)`,在两个图页里 pin1 方向相反。** 方向表一张都别信, 极性元件放完必须断言(`c4_site.py` 里断言「电解 pin1 的 y 必须大于 pin2」)。 2. **红黑两个测试点的引脚偏移不一样**(−20 / −10),按名义值算线会画出斜线, 而 `create()` 对斜线直接抛 `create failed!` 并让整批下发中途断掉。 现在下发前统一断言「横平竖直且非零长度」。 --- ## 状态一览 | 阶段 | 状态 | |---|---| | 方案设计 | ✅ 定稿 v1.6(`五路直供电源板设计.md`) | | 小米8 侧接口引脚定义 | ✅ 已从原理图逐脚追踪(设计文档 §13) | | 器件选型 | ✅ 全部立创有货,UUID 缓存在 `.claude/eda-parts.json` | | **P1-INPUT** | ✅ **补完输入侧 INA226(U13+Rs6+C11,§19),六项验证全过,需重新看一眼** | | **P2-CHANNEL(CH1)** | ✅ **已删 J2/J7,侧键漏极改挂 `POGO1_*` 端口**,六项全过 | | **P2B-CH2 ~ P2E-CH5** | ✅ **同样删了 J3~J6 / J8~J11,改挂 `POGO2~5_*`**,各自六项全过 | | **P3-CONTROL** | ✅ 画完,六项验证全过,**等用户看截图** | | **P3B-EXPAND** | ✅ 画完,六项验证全过,**等用户看截图** | | ~~P4-USB~~ | ➡️ **已整页搬到 Board2 载板,改名 C3-USB**;下板已删 | | ~~P4B-HUB~~ | ➡️ **已整页搬到 Board2 载板,改名 C2-HUB**;下板已删。⬜ 还要在载板上补 U31 | | **P1B-5V** | ✅ **已摘掉 U31 + C736/C737**(跟 Hub 一起上载板),现在是纯 12V→5V,9 元件,六项全过 | | **P5-BTB**(原 P5-HARNESS)| ✅ **整页重画**:J24/J25 两条 2×20,47 信号 + 33 地,六项全过(截图 `P5-BTB.png`)| | **P6-FAN-EDL** | ✅ **新建**:U33 第三片 595(EDL_EN1~5)+ 风扇低边驱动 + J26~J30,六项全过(截图 `P6-FAN-EDL.png`)| | 跨页网络对账 | ✅ 下板 11 页 **0 漏接 / 0 待补**;✅ 载板 62 个网络 **0 漏接** | | `port_audit.py` | ✅ 下板 513 个、载板 260 个,**可疑 0 个**(载板加 `--board Board2`)。⚠️ 2026-09-04 晚重跑时载板曾报 1 个(C1-CONN 的 GND 旗),已修 —— **每轮都要重跑,别信上一轮的数** | | **Board2 载板原理图** | ✅ **8 页全部画完**:C1-CONN / C2-HUB / C3-USB / C4-SITE1~5,六项验证 + 跨页对账 + port_audit 全过 | | **Board2 载板 PCB** | 🟡 4 层 / 220×135 板框 / 35 个定位孔 / **6 个 M3**(含 2 个中跨)/ 161 个元件已摆完(§25、§25.6、§25.7);⬜ 未布线 | | **Board2 载板 GND 扇出** | 🟡 **210 个过孔已落盘**(§25.8),脚本 `pcb2_fanout.py`;⬜ 差 J31/J32 的 33 个(等换件)+ BM24 的 51 个(要逃逸布线)| | **Board2 载板 设计规则** | ✅ **整套已下发,`pcb2_rules.py --check` 十三项全过**(四层能力集 / PWR4A·PWR5V·PWR1A / 11 对差分 / USBPAIR 0.15 / 12 个网络指派 / **`PLANE-DIRECT` 铺铜全连接 + 5 个网络指派**)| | **Board2 载板 铺铜** | ✅ **两层内层都铺完**:Inner2 整板 GND + **Inner1 五块 `VOn_P` 岛 + GND 底填**(§25.11)。脚本 `pcb2_pour.py`(`ISLANDS` 是事实来源,`--verify` 回读)+ 三个校核脚本 `inner1_check` / `inner1_verify` / `inner1_diffref` | | **BM24-50DS 封装** | ✅ **自建完成**(§24),个人库器件 `771dbf00…`,几何来自 Hirose 官方样本。⬜ 镜像位待实测(§24.3)| | **PCB 转板** | ⚠️ **已过期**:原理图这一轮删了 15 个连接器 + 53 个 7xx 器件、新增 J24~J30/U33,**还没导入变更** | | PCB 板框 | ✅ **160 × 125 mm**,四角 R5 圆角 + 4 个 M3 金属化定位孔(接 GND)| | PCB 布局 | 🟡 **第五遍:8 A 输入链整条重排(见下),等用户看 `PCB-layout-v5.png`** | | 设计规则 | ✅ **整套已下发,`--check` 七项全 OK**(能力集 / PWR4A / PWR12V / 差分对 / 0.2 mm 间距 / 7 对 / 12 个指派)。上一轮说的「只能手动」是错的,见下 | | **PCB 板框** | ⚠️ **改成一条闭合 polyline 了**(原来的 4 直线 + 4 圆弧自动布线器不认,见下)| | 设计规则 | ✅ 等长容差 5 mm;新增 `USBPAIR` 0.15 mm,**只挂在每一对自己的两个网络之间** | | 铺铜 | ✅ **19 块**(新增 `+5V` / `+3V3_HUB` / `+3V3`×2),三条低压轨的 91 个焊盘已扇出 | | **7 对 USB 差分** | ✅ **6 / 7 布通**(USB1~5 + UPSTREAM)。⬜ 只剩 ESP32 调试口 `USB_DP/DM` | | PCB 布线 | 🟡 **1208 过孔 / 5814 导线已落盘**(save 后重开验证过)。⚠️ 铺铜是旧填充、接口区马上要大改 —— **先别继续布** | | Kelvin / 灌铜网络的漏网焊盘 | ⬜ **要手工布**:INA226 的 VIN±、五路泄放电阻 R*19、输入侧 U13 —— 5 mΩ 分流器满量程才 20 mV,多 1 mΩ 走线就是 20% 误差 | | GND 漏网焊盘 | ⬜ 20 个(459 个扇出过孔之外的)| | 丝印整理 | ⬜ 位号大面积压字,打样前要单独跑一遍 | | **原理图 SCL 断线** | ✅ **U13 的 SCL 压在导线中段、根本没接上,已修**(设计文档 §19.4)。新增全工程扫描 `.claude/port_audit.py` | | **板载 USB Hub 改造** | ✅ **原理图 + PCB 布局都做完了** | | **两板架构(2026-09-04 拍板)** | ✅ 设计文档 §22 定稿;✅ **下板原理图改造已做完(§22.7)**;⬜ 载板 C1-CONN 未画 | | **上板(载板)** | ✅ 原理图 8 页画完;⬜ PCB 还是空板;✅ 顶针已定稿 YTC1P-2510-01;⬜ 只剩排母占位(§22.7)| | 散热 | ✅ 定稿 + **电路已画**(P6-FAN-EDL:Q6/D2/J26~J30 + TACH 分压)。⬜ 风扇型号未定(若选 4 线风扇,§22.6 那段 MOS+分压可整段删)| | 小米8 主板机械数据 | ✅ **全部到手**:44.66×67.09 实测、7 个定位孔 ±0.5、触点 ±0.3(§22.3)。数据在 `.claude/mi8_mech.json` / `mi8_contacts.json` | | **EDL(9008) 控制口** | ✅ **两侧都画完**:下板 U33(第三片 595)→ `EDL_EN1~5` → 排针 → 载板 U41~U45(TS5A3157DBVR)+ J61~J65(1.25mm 2P)+ R741~R745 下拉 | 下板原理图 **11 页**(顺序已排): P1-INPUT / P1B-5V / P2-CHANNEL / P2B-CH2 / P2C-CH3 / P2D-CH4 / P2E-CH5 / P3-CONTROL / P3B-EXPAND / **P5-BTB** / **P6-FAN-EDL**。 载板原理图 **8 页**(顺序已排): **C1-CONN** / C2-HUB / C3-USB / **C4-SITE1~5**。 截图:**12 页里已截 10 页**(缺 P2B/P2C/P2D,它们是 P2 的机械复制)—— `P1-INPUT.png`、`P1B-5V.png`、`P2-CHANNEL.png`、`P2E-CH5.png`、`P3-CONTROL.png`、 `P3B-EXPAND.png`、`P4-USB.png`、`P4B-HUB.png`、`P5-HARNESS.png`,PCB 最新是 `PCB-layout-v4.png`。 (上一轮那三张空白 PNG 是渲染没出来,不是图元缺失;这一轮重截,都是好的。) ## 这一轮做了什么(2026-09-04,方案转向) ### 一、自动布线(转向前完成的部分) 见下面「自动布线终于跑起来了」那节。**结果保留着**:1208 过孔 / 5814 导线, 6/7 对 USB 差分布通,间距/物理错误 0。但接口区马上要大改,别在这上面继续投入。 **三条最贵的教训**(每条都卡了很久): 1. **板框必须是一条闭合 polyline**,不能是 4 直线 + 4 圆弧。 自动布线器取板框走 `fu()`,只扫 polygon 管理器;独立线/弧不在那个表里 → `getFilterBoard()` 返回空 → 弹「请先绘制板框!」然后**静默退出**。 正确写法:`pcb_MathPolygon.createPolygon(SRC)` → `pcb_PrimitivePolyline.create('', 11, poly, 5)`。 ⚠️ 直接把源数组喂给 `create()` 会抛「无法创建多边形图元」而且**真的没建** —— 跟铺铜那条「抛了也算成功」方向相反。 2. **布线跑的时候什么都别碰**。不要 `save()`、不要 `pcb_Drc.check()`、 连 `getAll()` 这种只读调用都别做。被打断时 `routerResult()` 里 `state!==1` 会**直接丢弃**结果 —— 画布上看得见、`getAll()` 数得到, 但没进文档模型。**判据只有一个:save 后重新 `openDocument` 再数一次。** 3. **差分对要一次统筹全部 7 对**。一对一对布会互相堵死(`保留` 模式下先布的成了障碍)。 正确姿势:全选 7 对的焊盘 + 「已有导线/过孔 = **移除**」+「布线网络 = 所选网络」。 其它:重载客户端会清空自动布线对话框的设置;DRC 面板刷不出来时走 `设计 → 清除错误` → `设计 → 检查DRC`。 ### 二、方案转向:两块板(用户 2026-09-04 拍板) 用户的原始需求分四次说清楚: 1. 「做成两块板,五路 usb 移到上板,排针对接,上板加五块主板定位」 2. 「进入 9008 的控制接口也接入」 3. 「与主板连接的 usbhub 移到上板,不用排针,排针只需要把下板的电源部分接上来」 4. 「侧键做顶针,EDL 做 2 针 1.25mm 座子我自己接线,VBUS 通断走排针」 5. 「不要散热垫,加风扇」→「侧面两侧各 2 个小风扇吸入,顶部 1 个大风扇排出」→「风扇装 3D 打印外壳上」 **完整规格写在设计文档 §22**,这里不重复。 ### 三、⭐ 小米8 主板机械数据:**一直就在我们自己的 PDF 里** **旧结论「PDF 是电气原理图、不含机械尺寸、只能实测」是错的。** `小米8原理图/Xiaomi_Mi_8_Schematic.pdf` 共 46 页, **前 2 页是正反面位号图**(含完整板框轮廓 + 全部位号),后 44 页才是原理图。 当初只按「原理图」去读,`get_text()` 一看全是位号就跳过了,**从没渲染出来看一眼**。 拿到的数据(方法与精度分档见 §22.3a / §22.3b): - 比例尺 **17.0 pt/mm**(从 464 个 0201 封装反推,卡尺验证 0.7%) - 主板 **44.66 × 67.09 mm**(卡尺实测) - **7 个定位孔**(从用户标红框的照片提取,±0.5 mm) - EDL / 尾插 / 侧键触点(从**器件本体图元**提取,±0.3 mm,并被两个卡尺读数交叉验证) ### 四、⚠️ 这一轮我判错了四次,都是同一个毛病 **没有实物对照就从矢量图元硬推。** 记下来,别再犯: | 判错 | 错因 | |---|---| | 「主板没有定位孔」 | 轮廓用「逐行取最外侧墨点」提取,**这方法看不见板内的孔和挖空** | | 「定位孔就是图纸上那 7 个圆」 | 抓到的是测试点和圆焊盘。摆成横向和用户照片一对就露馅 | | 把两个卡尺读数当成「板厚」 | 用户说的是**侧键到板边的距离** | | 把 **7.61** 读成 **1.76** | 卡尺屏是旋转的,数字看反了 | **规矩**:卡尺读数先跟用户确认再落档;从图纸推的几何结论, 必须有一个实物基准交叉验证才能写进文档。 ### 五、EDL(9008) 电路(原理图 Sheet 17,比维修站的付费短接图可靠) ``` VREG_S4A_1P8 ──[R1204 1.8kΩ 0201]── TP1204 ← 0.75 mm 测试点 FORCED_USB_BOOT ──┬── TP1205 ← 0.75 mm 测试点 └── CR1209 PESD3V3V1BCSF ── GND ``` 短接两点 = 把 `FORCED_USB_BOOT` 拉到 1.8V → 进 9008。 **串了 1.8 kΩ,短路电流只有 1 mA**,一颗模拟开关就够。详见 §22.4。 --- ## 这一轮:自动布线终于跑起来了(2026-09-04) ### ⚠️⚠️ 头号发现:**板框必须是一条闭合多段线,不能是 4 直线 + 4 圆弧** `布线 → 自动布线... → 运行` **静默失败**:对话框正常关闭、一根导线不画、 `console` 没有任何异常、也没有网络请求。三次尝试(React onClick / 原生 dispatchEvent / 等 90 秒)现象完全一样。 顺着客户端 bundle 追下去才找到: ``` pcb_ManufactureData.getAutoRouteJsonFile() → ui.js getAutoRouteJson() → windowBridge.rpcCall('getNewAutoRouteJson', …) → pcb.js vP.autoRouting() → createPangolinJson() → getFilterBoard() → fu() → [] ← 就是这里 → 弹 "Please draw a board outline first!" 然后 return undefined ``` `fu()` 的判据是(pcb.js 反编译): ```js [Ni.getInstance().getAllItem(), tn…, Yi…] // 只有 polygon 管理器 .forEach(t => t.forEach(e => { !e.isEmpty && (e.type == "Board Outline" || e.type == "Line" && e.layerId == 11) && o.push(e) })) ``` **它只扫多段线(polygon)管理器**。我们的板框是 `pcb_PrimitiveLine.create()` × 4 + `pcb_PrimitiveArc.create()` × 4, 八个独立图元,压根不在那个表里 —— 于是 `fu()` 恒返回空。 这个错**极难往板框上想**,因为除了自动布线,板框在别处全都是好的: 渲染正常、铺铜按它的几何铺好了、DRC/ERC 全过、Gerber 也认。 同一个 `fu()` 还卡着 `getDsnFile()` 和 `getAutoRouteJsonFile()` (两个都返回 `undefined`,等于 freerouting 那条路也被堵死了)。 **正确姿势(两步,缺一不可)**: ```js const poly = await eda.pcb_MathPolygon.createPolygon(SRC); // ⚠️ 必须先过这一步 await eda.pcb_PrimitivePolyline.create('', 11, poly, 5); ``` ⚠️ 把 `TPCB_PolygonSourceArray` **直接**喂给 `pcb_PrimitivePolyline.create()` 会抛「错误:无法创建多边形图元」,而且是**真的没建**(`getAll()` 仍是 0)—— 注意这跟铺铜那条「`create()` 抛了也算成功」的经验**方向正好相反**,别混用。 `pcb_place.py` 的 `push_outline()` 已经改成这个写法(新增 `outline_poly()`)。 ### 一路排查中间踩到的三件事 1. **`pcb_PrimitiveArc.getAll()` / `getAllPrimitiveId()` 在本工程恒返回 0。** 排查时我以为四个 R5 圆角不存在,又补建了四段 —— 于是 layer 11 上现在有 **8 段重叠圆弧 + 4 条重叠直线**(几何完全重合,Gerber 上是同一条路径描两遍)。 它们不在 `fu()` 的表里,不影响布线;但**删不掉**(枚举不到就没有 primitiveId), 要清只能在客户端里手删,或者隐藏其它层后框选 layer 11 全删再重建 polyline。 2. **桥接的 WS 请求 30 秒硬超时**,`eda.py` 的 `timeout=` 只管 HTTP 那一段。 JS 里 `sleep` 超过 30 s 一律拿到 `Request … timed out after 30000ms`。 要等就分多次短调用轮询。 3. **客户端重连会换 `windowId`**。`curl http://127.0.0.1:49620/eda-windows` 可以确认 当前只有一个窗口 —— 排查「是不是打到另一个窗口去了」时先看这个,别猜。 ### 抓静默失败的通用手法(这轮就是靠它破的) 页面里挂三层钩子,再触发一次,就能看见吞掉的错误: ```js // ① console 全接管 ② window.onerror / unhandledrejection ③ fetch / XHR / Worker // ④ 最关键:postMessage —— 父窗口与编辑器 iframe 之间的 rpc 都走这里 window.addEventListener('message', e => { …记录 topic 与 message… }, true); document.querySelector('iframe.editframe').contentWindow .addEventListener('message', …, true); ``` 回包里 `{"topic":"getNewAutoRouteJson_MSG_BUS_RPC_64600","type":"push"}` **没有 message 字段** —— 一眼就能断定是 iframe 里的处理函数返回了 undefined, 而不是参数没传对或者按钮没点上。 再配合 `console.warn` 里打 `new Error().stack`,能直接定位到 `ui.js:1819` 的 `getAutoRouteJson`,然后把 `pro-ui/…/js/ui.js`(17 MB)和 `pro-pcb/…/js/pcb.js`(10 MB)下下来 grep —— 这两个包是公开 CDN,随便下。 ### 自动布线的设置(对话框里的状态是持久化的,改一次就记住) | 项 | 取值 | 为什么 | |---|---|---| | 布线网络 | 所有网络 | | | 布线拐角 | 45° | | | 已有导线/过孔 | **保留** | 708 个扇出过孔和平面层不能被冲掉 | | 效果优先级 | 完成度优先 | | | 布线图层 | **只勾顶层 + 底层** | 内层1 是完整地平面、内层2 是电源平面,**不许被信号线割开** | | 忽略网络 | **15 个** | `GND`(459 个扇出过孔已经接到地平面了,让它布会刷出一堆顶层地线)+ 7 对差分的 14 个网络(`USB1~5_DP/DM`、`UP_DP/DM`、`USB_DP/DM`,打算之后按对单独布) | 对话框是 React 的,`忽略网络` 那个下拉可以直接从 fiber 上取 `onChange` 逐个灌: ```js const p = /* 从 [data-id^="selectOptionKey_"] 往上找带 {list, onChange} 的 fiber */; p.onChange('GND'); // 每加一个 list 就少一个,要重新取 fiber ``` ### ⚠️ 自动布线的结果**不到最后不落盘** —— 中途被打断就白跑 第二趟(补差分对)跑完,`getAll()` 报 6196 条导线、几何上逐段查过 **USB1_DP 从 U32.2 一路连到 J17.3、并查集只有一个连通块**, 但 DRC 咬定这两个焊盘断开。`pcb_Document.save()` 之后 **重载客户端,板子回到了第一趟的 1095 过孔 / 3455 导线** —— 第二趟一根线都没进文档。 原因在 `routerResult()`: ```js routerResult(t){ if(this.state!==1){ console.error("Auto Routing is stop!", this.state); return; } … } ``` 布线结果是一批一批推回来的,`state!==1`(被打断)时**直接丢掉**。 画布上看得见、`getAll()` 数得到,但那只是 `historyGroup` 里的预览,没有进文档模型 —— **所以 DRC 是对的,错的是我**。 **判据只有一个:save 之后重新 `openDocument`,再数一次导线/过孔。** 数字对不上就是没落盘。别信 `getAll()`,也别信画布。 跑的时候不要碰客户端:不要 `save()`、不要 `pcb_Drc.check()`、 连 `pcb_PrimitiveVia.getAll()` 这种只读调用都先别做 —— 第二趟就是这么被打断的 (第一趟跑满 ~50 分钟、进度条自己消失,结果就落盘了)。 盯进度只读 DOM 文本:`document.body.innerText` 里那行 `自动布线中...(NN%)`。 ### 顺带两条 - **重载客户端会把自动布线对话框的设置清空**(忽略网络清零、四个布线图层全勾回来)。 重载之后必须重新配一遍再跑。 - **DRC 面板刷不出来时走菜单**:`设计 → 清除错误`(`全部 (0)`)→ `设计 → 检查DRC`。 面板上那个「刷新」按钮不是 DRC 的,`pcb_Drc.check()` 单独调有时也不刷新。 ### 局部返工:把 7 对 USB 差分抢回通道(2026-09-04,用户拍板) 第一趟自动布线之后,**7 对差分只布上 2 对**(USB1/USB2),其余 5 对 连「所选网络」单独重跑都一根线不画 —— 板子已经被普通信号线占满了。 用户拍板走「局部返工」而不是整体回滚。四步: **① 先改规则**(`pcb_rules.py --push`),否则布完还是一堆假违规: | 改动 | 从 | 到 | 为什么 | |---|---|---|---| | `differentailPairLenTolerMax` | 0.5 mm | **5 mm** | USB 2.0 HS 对内偏斜预算 ≈100 ps ≈ 15 mm;实测偏差 1.6~1.9 mm ≈ 10 ps。为凑 0.5 去绕蛇形线是拿串扰换数字 | | 新增安全间距条目 `USBPAIR` | — | **0.15 mm** | 只通过 `overwriteNetByNetRules` 挂在**每一对自己的两个网络之间**;对其它网络仍是 0.2 mm | `USBPAIR` 那条**不能**用全表抬下限的循环扫到(会被抬回 0.2),代码里单独跳过了。 net-by-net 的条目形状文档没写,是从 `pcb.js` 的 `getBetweenNetDataItem()` 反出来的: ```js {net1:{type:'net',name:'USB1_DP'}, net2:{type:'net',name:'USB1_DM'}, rule:'USBPAIR', copperRuleName:null, planeRuleName:null} ``` ⚠️ `overwriteNetByNetRules` 是**整表覆盖**,要先 `getNetByNetRules()` 读回来再合并。 改完 DRC 172 → 166,4 条对内间距 + 2 条等长违规一起消失。 **② 三条低压轨改成铺铜**(铜面 15 → **19** 块): | 铜面 | 网络 | 层 | 范围 | 盖住 | |---|---|---|---|---| | 5V-L3 | `+5V` | L3 | x 11~151 / y 5.5~14 | 10 / 16 | | 3V3HUB-L3 | `+3V3_HUB` | L3 | x 59~111 / y 15~31 | **21 / 21** | | 3V3-CTRL-L3 | `+3V3` | L3 | x 95.5~150.5 / y 33.5~67 | 合计 45 / 61 | | 3V3-CH-L3 | `+3V3` | L3 | x 5~155 / y 100.3~124 | 同上 | 都塞在 12V 那三条带子之间的空地里,互相与 12V 都留 ≥0.5 mm。 `3V3-CTRL-L3` 的右边界 150.5 是被 ESP32 天线禁铺区(x≥151)逼的。 这带出两个工具改动: - **`pcb_pour.audit()` 要按网络做并集**。`+3V3` 是**两块**铜面,逐块判「焊盘在不在里面」 会让每一块都把另一块盖住的焊盘报成「意外漏掉」。 - **新增 `POUR_PARTIAL = ('+5V', '+3V3')`**:这两条轨的铜面**故意只盖一部分**, 漏在外面的焊盘靠走线接,不算错。 **③ `pcb_fanout.py` 只对「落在自己铜面里」的焊盘扇出**(新增 `_pour_filter()`)。 在没有铜的地方打一个下到 L3 的过孔 = 一个悬空过孔,DRC 反而多一条断连。 三条轨一共扇出 91 个过孔,5 个焊盘周围太挤放不下,留给走线。 **④ 局部返工的选取判据**(`_snap_before.json` 是基准): ``` 删掉「布线器加的」且满足下面任一条的**整个网络**的线与过孔: a) 网络已经改成铺铜(+5V / +3V3 / +3V3_HUB) b) 该网络在 y < 28 mm 的下带里有走线,且不是 GND/12V/VOn 这类扇出网络、也不是差分对 ``` 结果:**34 个网络 / 678 条导线 / 68 个过孔**,只占布线器产物的 18%。 整网删、不留断头,剩下的 80% 一根没动。 ### ⚠️ 差分对必须**一次统筹全部 7 对**,一对一对布会互相堵死 下带清空之后,选中 5 对跑「所选网络」,**只布上 USB4/USB5**;再跑一次, 一根线都不加。原因是 `保留` 模式下先布好的那两对成了后面的障碍 —— USB3 与上游 UP 要横穿它们,而下带就那么宽。 正确姿势是**把 7 对全选中 + 「已有导线/过孔 = 移除」**,让布线器把已布的两对 一起拆掉重排。`clearRoute(false, undefined, this.selectNets)` 只清选中的网络, 不会波及别处(pcb.js 里确认过)。一趟下来 **6 / 7 对布通**(USB1~5 + UPSTREAM), 只剩 ESP32 那条调试口 `USB_DP/DM`。 顺带澄清一条之前的误判:**「所选网络」模式是好的**。早先它「跑完却一根线不画」 不是模式坏了,是那时候板子上真的没路了 —— 日志里 `routingRange:1`、选中 24 个焊盘 都对,弹的也是「自动布线已完成」而不是报错。 ### 布线跑完之后要做的(按顺序) 1. **DRC** —— `python .claude/drc_read.py`。看「间距错误」和「物理错误」两支, 连接性错误剩多少 = 还有多少没布上。 2. **七对差分单独布**(`USB1~5_DP/DM`、`UP_DP/DM`、`USB_DP/DM`)。 规则里已经建好 7 对、W 0.28 / S 0.18 / 等长容差 0.5 mm。 ⚠️ **顺序上其实应该先布差分再自动布线**——这一轮是为了先验证布线器能不能跑才反过来的。 如果跑完发现差分对已经无路可走,就按快照回滚,改成「先差分后自动」重跑。 3. **重新铺铜**。底层(layer 2)上有 VO1~5_P 五块铜面,自动布线在底层加了信号线, 铜面要重建才会让开它们:`工具 → 铺铜管理器... → 重建所有` (`pcb_pour.py --rebuild` 里已经把这一串打好了;`rebuildCopperRegion()` API 是坏的)。 4. **丝印整理** —— 位号大面积压字,打样前要单独跑一遍。 5. Gerber 预览复核板框闭合 + 四个 M3 定位孔。 ### 跑之前先存了个快照(`.claude/_snap_before.json`) 布线前把 708 个过孔 + 575 条导线的 `primitiveId` 全存了下来。 万一自动布线的结果不能要,**删掉不在这张表里的线和过孔**就能精确回滚, 比 `清除布线`(会连扇出一起清)干净得多。 --- ## 上一轮:开始布线(2026-09-03) 顺序是「先把面铺好,再拉线」—— 平面定了,信号线的参考面和过孔位置才有依据。 ### 1. 先抓到一个真的断线:U13 的 SCL 没接上 ⚠️ 追 12V 母线的网络时发现 PCB 里 `U13.2 / U13.5` 挂在一个只有两个焊盘的 `$1N257` 上,而 `SCL` 网络 11 个焊盘里没有 U13。**输入侧检流那片 INA226 是死的。** 根因不是漏画线,是 **`SCL` 端口的引脚压在一条导线的中段而不是端点** (端口在 (920,340),导线是 `[920,320 → 920,570]`)。EasyEDA 只在端点认连接。 同一页的 SDA 端口恰好落在端点上,所以 SDA 是好的 —— **两个端口在图上一模一样**。 DRC / ERC / `verify_sch --nets` / 跨页对账**四道全过**,因为 `verify_sch` 的判据是「引脚落在某条导线段上」,中段搭接满足这一条。 新增 **`.claude/port_audit.py`**:全工程 572 个旗/端口逐个判「是不是某条导线段的端点」。 修之前 1 个不合格(就是它),修完 0 个。写进 `easyeda-sch-geometry` **§8a-2** 与设计文档 **§19.4**。 > 写这个检查时踩了个自伤的坑:第一版按折线解析导线,572 个里报出 **197 个可疑**, > 其中一大片是工作正常的 GND 旗。`getState_Line()` 返回的是**端点对**(skill §5), > 按折线解析会凭空造出斜线段。误报到这个程度的检查等于没有检查。 ### 2. 8 A 输入链整条重排 原来这一串是仿射映射甩出来的,`J1(x5.8) → F1(x46.6) → Q1(x53) → 体电容(x66/75) → Rs6(x84.6~90.7)`:**8 A 要从 x5.8 走到 x90.7,85 mm**,其中 `$1N223`(J1→F1) 单独一段就 42 mm。8 A 在 0.5 oz 内层要 ΔT=20 °C 得 **18 mm 宽**铜面 (IPC-2221 内层按外层 50% 取,面积翻倍),band B 一共才 36 mm 高,塞不下四条。 而且这一串在 y 上来回折(F1.2 在 y60.08,Q1 源极在 y62.85,中间还夹着 `$1N236` 在 y60.56 的一个焊盘),L3 上按 y 切开分块,切线最窄处只剩 0.2 mm。 **改成 J1 右手边一条走廊 x 11.5~46 / y 49.5~68.5**(栅极网络放 J1 上方口袋): | | 之前 | 现在 | |---|---|---| | `$1N223` J1→F1 | 42 mm | **6.9 mm** | | 8 A 段的横跨 | x 5.8 ~ 90.7 | x 12 ~ 40 | | 分叉点 | x 90.7 | x ≈ 40(Rs6 下游)| | 母线最大截面电流 | 8 A 全程 | 顶带 ≤ 6.4 A(往左喂 CH1、往右喂 CH2~5)| 顺带把 13.7 × 9.7 的立式泄放电阻 R9 也钉死了 —— 仿射区右缩之后它被推到 x95, **本体框伸到 x102 压进 P3 区**。`relax()` 只夹中心坐标、不看外框,这类件必须手钉。 ### 3. 平面层(`.claude/pcb_pour.py`) | 铜面 | 层 | 网络 | 范围 | |---|---|---|---| | GND-L2 | L2 | GND | 整板(板框内缩 0.5,挖 ESP32 天线区)| | VIN-RAW-L3 | L3 | `$1N223` | x 3~14 / y 45.5~55.5 | | VIN-FUSED-L3 | L3 | `$1N227` | x 14.6~19 / y 49.5~56.5 | | VIN-SW-L3 | L3 | `$1N236` | L 形,x 14.4~35 / y 49~66.5 | | 12V-L3 | L3 | `+12V` | 下带 x14.6~95 y33~48.3 + 中带 x38.5~95 + 顶带全板 y67.5~81.9 | | VO1~5-L3 | L3 | `VOn` | 每路 30 mm 宽 / y 82.5~99.5 | | VO1~5_P-L4 | **L4** | `VOn_P` | 每路 30 mm 宽 / y 110~116.2,带一条下伸臂到分流电阻 | 分界线是**焊盘边**定的不是整数:+12V 顶边 81.9 卡在 C102 那排 1210 焊盘(y80.2~81.7); VOn 底边 82.5 是因为 C107 pin1 是 rot90 的 1210,焊盘跨到 y83.07~84.55 —— 按「元件中心在 y85.4」算会把它漏在两块铜面之间的缝里。 **U8~U13 的 0.5 mm 间距 Kelvin 脚一律不进铜面**(U13.10 是 VIN+、U13.9 是 VIN−, 相距 0.5 mm,任何分界线都塞不进去),走顶层细线,本来也不载流。 `pcb_pour.py` 里 `audit()` 会把「本网焊盘在铜面外」的逐条列出来, `EXPECT_OUT` 是白名单,漏在名单外的就要查。 ### 4. 铺铜 API 的三个坑(都实测过) 1. **`rebuildCopperRegion()` 是坏的**,四种写法都抛 `Cannot read properties of undefined (reading 'length')`。 唯一能填充的路子是 GUI:**工具 → 铺铜管理器... → 重建所有 → 确认**, 已用 React 的 `onClick` 打进 `pcb_pour.py`。判据是 `nPoured` 不是 `nPour`。 2. **多边形必须显式闭合**(末尾把首点再写一遍)。文档说「首尾不重合会自动重合」 对铺铜边框**不成立**:不闭合就直接不建。`board_poly()` 一直能用是因为 它最后那段圆弧正好回到起点。 3. **`create()` 成功了也会抛** `错误:无法创建覆铜边框图元`。返回值和 try/catch 都不能当判据,只能建完 `getAll()` 按名字核对 —— 而且**名字会被转成大写**。 排查时分不清 1 和 2 的办法:不闭合的矩形建完 `getAll()` 数量不变,闭合的 +1, **两者抛一模一样的异常**。 ### 5. GND 扇出:改布局之后必须按这个顺序重跑 ```bash python .claude/pcb_fanout.py --clean # 1. 先清空板上的过孔与走线 python .claude/dump_pads.py # 2. 再重新抓障碍表 python .claude/pcb_fanout.py --push # 3. 最后规划 + 下发 ``` `--clean --push` 写成一条**会用旧的 `pcb-copper.json`**:清是清干净了, 规划器看的还是那 459 个已经不存在的过孔,把它们当障碍绕 —— 实测 459 → 292 个过孔、五路 Buck 的 pin2 全扇不出去、27 个散热盘只打进 4 个孔。 反过来先 dump 再 `--clean --push` 更糟,板上会剩两套(747 个)。 正确顺序的结果(15 个网络一趟跑完):**708 个过孔 / 0 个失败**, 其中 GND 459、+12V 128、输入链三条 48、五路 VOn 各 17、五路 VOn_P 各 8。 ### 6b. 出线宽度必须**按焊盘**算,不能按网络算 第一版给功率网络一刀切 1.0 mm(= 线宽规则的最小值),结果 **29 个焊盘扇不出去**: `+12V` 上挂着 R104 那种 0402 分压抽头,焊盘只有 0.57 × 0.54 mm, **1.0 mm 的线比焊盘还宽**。退回 0.3 mm 硬扇出去,又被判 「线宽 11.8 mil,应在 39.4 ~ 393.7 mil」—— 34 条物理错误。 两处一起改才对: 1. `PWR4A` / `PWR12V` 的**最小值 1.0 → 0.3**(默认值仍是 1.5 / 2.0)。 这就是 §10.1 那条「载流靠灌铜保证,不靠 DRC」再走一步。 2. 出线宽度按**焊盘短边 × 0.9** 取,夹到 [0.3, 1.0];过孔数按焊盘短边 ≥ 0.8 mm 给 3 个、 否则 1 个 —— 0402 抽头本来就不载功率电流,多打过孔只会把平面打成筛子。 ### 6c. 扇出的障碍模型漏掉的两件事(DRC 抓出来的) | 现象 | 根因 | |---|---| | 10 条「导线到过孔 3.4 mil,应 ≥ 7.9 mil」 | 板上已有的过孔只按**钻孔半径 0.15** 登记进 holes,没按**焊盘半径 0.3** 登记进 grid。差的正好是 0.15 mm | | 1 条「导线到导线 6.2 mil」 | **出线之间的间距压根没查**过 —— 原来只查了线对焊盘、线对钻孔 | 两条都补进 `pcb_fanout.py` 了(`trk_grid` + `seg_seg_dist`)。 补完重跑:**间距错误 0、物理错误 0**。 ### 6. 顺手发现的两条 EasyEDA 路子 - **设计 → 从原理图导入变更(Alt+I)** 能用 DOM 打开并读出变更表, 逐条列 `U13:2 $1N257 → SCL`。改完原理图必须走这一步,PCB 才会跟着变。 - **布线 → 扇出布线... / 自动布线... / 差分对布线 / 等长调节** 都在菜单里。 信号线那 400 多条不必自己写布线器,下一轮直接驱动它。 ## 最新一轮:设计规则不用手动了(2026-09-03 深夜,收尾) 上一轮结论「规则配置只能在 GUI 里手建」**是错的**。原因是参数形状给错了, 现在 `python .claude/pcb_rules.py --push` 一条命令把五项全下发,`--check` 七项全 OK: ``` [OK] 四层板能力集已就位(最小线宽 0.102 / 过孔 0.30-0.20,含 0.5oz 内层条目) [OK] 线宽规则 PWR12V 存在 [OK] 线宽规则 PWR4A 存在 [OK] 差分对几何 W 0.280 / S 0.180 mm [OK] 安全间距下限 0.2000 mm [OK] 差分对 7 对: DEBUG UPSTREAM USB1 USB2 USB3 USB4 USB5 [OK] 线宽指派 12 个网络 ``` ### 当初为什么以为不行 —— 两个坑叠在一起 1. **`overwriteCurrentRuleConfiguration(x)` 要 config 本体,不要 `{name, config}` 包装。** 传包装返回 `undefined` 且静默无效;传本体返回 `true`。 (`saveRuleConfiguration(cfg, name, allowOverwrite)` 确实是三个参数 —— 上一轮只传了一个 —— 但补齐了照样返回 `false`。**它是真的不能用**, 好在我们并不需要存命名配置。) 2. **两种 config 的形状不一样,混用会被默默吃掉。** | 来源 | 线宽 | 安全间距表 | |---|---|---| | `getRuleConfiguration('<系统能力集>')` | **平**:`form.strokeWidthDefault` | `entry.table` | | `getCurrentRuleConfiguration()` | **嵌套**:`form.data['1'].defaultValue` | `entry.tables['1'].content` | 正确姿势:**拿 `getCurrentRuleConfiguration().config` 改,原形状送回去**。 系统能力集只当数据源用(那张内层 0.5 oz 间距表是从它身上抄的)。 ### 「切到多层板能力集」其实只是五个字段 把两个能力集逐值 diff 过:**只差 3 个数 + 2 条默认不启用的条目**,其余 300 多个数完全一致。 | 项 | Two Layers | Multiple Layers | |---|---|---| | `Track/copperThickness1oz` 最小线宽 | 0.127 | **0.102** | | 过孔外径最小 | 0.5 | **0.3** | | 过孔内径最小 | 0.3 | **0.2** | | `Track/copperThickness0.5oz` | 无 | 有(min 0.089,`isSetDefault=false`)| | `Safe Spacing/copperThicknessInner0.5oz` | 无 | 有(`isSetDefault=false`)| 也就是说,能力集之间的差别**全是放宽限制**,两层板那套只是更保守。 真正必要的是最小线宽 0.102(U32 那个 0.5 mm 间距 QFN-64 的扇出要用)。 ### ⚠️ 下发之后千万别做的一件事 **别再去 GUI 的 设计 → 设计规则… 里动“配置”下拉框。** 换一下就是整套重置:`PWR4A` / `PWR12V` 两条线宽规则会被冲掉, 12 个功率网络的指派立刻变回悬空(**DRC 不报错,线宽也就不生效** —— 查不出来的那种坏)。 要改规则就改 `pcb_rules.py` 再 `--push`。 配置名下发后会自动变成 **“自定义配置”**,这是对的 —— 说明它已经从系统能力集上摘下来了。 ### 顺带踩到的一个坑:mm 值会被换算成 mil 再换回来 写进去 `0.2`,回读是 `0.19999959999999997`(差 4e-7 mm)。 **任何几何值都不能做精确比较**,`--check` 的容差已放到 1e-4。 (第一版容差 1e-9,结果安全间距那项在明明写对的情况下报红。) ### DRC 结果读得回来(`.claude/drc_read.py`),而且真抓到了 9 个错 先说一句:本轮早些时候写的「DRC 结果读不回来」**是错的**,已推翻。 `pcb_Drc.check()` 确实只返回一个 boolean,40 多个方法里没有结果 getter, 也没有 DRC 标记层可读。**但 DRC 面板在 DOM 里是挂载着的** (跟设计规则对话框不一样,那个窗口不前台就不挂载)。三个手法: 1. 左边分类树要用 **React 的 onClick** 点 —— 从元素上找 `__reactProps$*`, 一路往上找到带 `onClick` 的那层直接调。`el.click()` 不管用,不点就不选中, 右边明细区一直是「空列表」。 2. **「清除错误」那个反过来**,它是原生 `