R2 底板改 4 层(D15):规则 / 几何 / 背面摆位 / 4 层布线与铺铜,已下发

- 规则:铜层 2→4,差分 0.28/0.18,间距 0.15,过孔 0.3/0.2(rules_base)
- 几何:98×98 板框 + 4×M3,槽阵居中,L1 12V 大铜 + 指头(pcb_base)
- 摆位:五插槽留 L1,其余 133 件翻到 L4;背面镜像规则实测;Hub 放走廊 2、
  东移 2.5 mm;扇出圈 / 小件间距 1.5(去耦例外)/ 9 A 过孔阵禁区(place_base)
- 叠层按用户决定改为 L1 槽面 / L2 整层 +3V3 / L3 整层 GND / L4 器件面
- 布线:D± 七对按构造布通(USB3 掉头、USB1/2 北去掉头、USB4/5 东绕、
  J23 桥),+3V3 / GND / +12V 平面网每脚一孔,0.125 网格 + PathFinder
  开关(route_base、remote_route.ps1);剩 37 个网待手工补
- 铺铜:4 层重写,入口链自南向北切段 + 断面校核(pour_base);
  已下发布线 / 缝合孔 / 铜面轮廓,填铜未成功(待查)
- 工具:plot_base / plot_routes / shot_base,board_check 通孔两层都算

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0168Bu6SEx3cXXCJLkdnE6zB
这个提交包含在:
zhou yang
2026-09-10 21:02:10 +08:00
共同撰写人 Claude Opus 5
父节点 18d3d69856
当前提交 efcba30a0c
共修改 280 个文件,包含 92007 行新增和 15053 行删除
+165 -42
查看文件
@@ -43,9 +43,20 @@ from eda import run
PCB = '7e9415a16a909c51'
MIL = 39.3700787401575
BW, BH = 160.0, 125.0
# ⚠️⚠️ 2026-09-07 修正:这两个数从 2026-09-06「两板尺寸取大对齐」那一轮起就过期了 ——
# 板上实测的板框是 **220 x 135、左下角 (-30,-5)**(`pcb1_outline.py` 就是这么画的,
# 回读多段线 x -1181.1~7480.3 mil / y -196.9~5118.1 mil 逐条对上)。
# 旧值 (0,0,160,125) 是真板框的**子集**,所以越框检查一直是「偏严但不错」——
# 代价是**左右各 30 mm、上下各 6 mm 的板面从来没进过摆位视野**。
# 板边元件(风扇座、电源座、调试口)按旧值挑位置会挑到板子中间去。
BX0, BY0, BX1, BY1 = -30.0, -5.0, 190.0, 130.0 # 真板框,mm
BW, BH = BX1 - BX0, BY1 - BY0 # 220 x 135
CORNER_R = 5.0
MH = [(5.0, 5.0), (BW - 5.0, 5.0), (5.0, BH - 5.0), (BW - 5.0, BH - 5.0)]
# 2026-09-06 用户指令「两板尺寸取大对齐、四角定位孔对齐」:板框改成和载板一样的 220×135,
# **坐标原点不动**(下板坐标 = 载板坐标 − (30,5),pcb1_env.py 那条约定不变),板框画到 (−30,−5)~(190,130)。
# 定位孔 = 载板 `pcb2_place.MH` 减 (30,5):四角 + 两侧中点。载板中跨那两个 (46.6,74.5)/(118.1,74.5)
# 落在下板 CH2 功率级 / 布好的线上,不打。板框与孔由 `pcb_place.py --outline` 下发(同一张表)。
MH = [(-22.0, 3.0), (182.0, 3.0), (-26.0, 126.0), (186.0, 126.0), (-23.0, 55.0), (183.0, 40.0)]
MH_HOLE, MH_PAD, MH_KEEPOUT = 3.2, 6.0, 6.5
PARTS = {c['d']: c for c in json.load(io.open(os.path.join(HERE, 'pcb1-parts.json'), encoding='utf-8'))}
@@ -63,8 +74,18 @@ FP_BODY = {
# 上面这几条当年是拿放大截图量出来的,六条全部对得上(误差 ≤0.31)。
# ⇒ 新加板边连接器时先跑 `fp_body.py --scan <关键词>`,别再猜 ±4.5。
# ── 2026-09-06 换件:J1 → XT30PW-M,J26~J30 → 2510-3A 直针座 ──
'CONN-TH_XT30PW-M': (-6.65, -7.98, 6.65, 5.97), # 12V XT30 公头(90°板装)
'CONN-TH_2510-3A': (-3.81, -2.88, 3.81, 2.93), # 风扇 3P **直针**座
'CONN-TH_XT30PW-M': (-6.65, -7.98, 6.65, 5.97), # 12V XT30 公头(已换掉,见下一条)
# ── 2026-09-08 换件:J1 → 圆形 DC 电源座 DC-005-5.5*2.5(用户拍板,不降载)──
# ⚠️ 这个数不是量截图量出来的,是 `pcb_Primitive.getPrimitivesBBox([pid])` 直接读的
# (把件建在板外 (200,0) rot 0 读完再删)。**它含丝印/装配层,比塑壳本体大一圈** ——
# 同法读 XT30 得 14.85 x 13.55,而本体表里是 13.30 x 13.95,x 方向多 0.90。
# 偏大的方向是安全的(净距检查偏严),本体真值待打样时拿实物量(§12 待确认项)。
# 本体几乎全在 −X 侧、三个脚挤在 +X 端 ⇒ **桶口朝 −X**,朝西板边用 rot 0,不用转。
'DC-IN-TH_DC-005-2': (-10.68, -7.16, 3.92, 3.22), # 圆形 DC 座 5.5x2.5
'CONN-TH_2510-3A': (-3.81, -2.88, 3.81, 2.93), # 风扇 3P **直针**座(已换掉,见下一条)
# ── 2026-09-07 FAN-R2:五个 3 针风扇座 → 两个 4 针(一进一出,山洋 9G0612P4S001)──
'CONN-TH_4P-P2.54_CAX_2510-4A': (-5.08, -2.79, 5.08, 3.01), # 风扇 4P 直针座(fp_body --scan 读出)
'SOD-123_L2.7-W1.6-LS3.7-RD-1': (-1.93, -0.85, 1.93, 0.85), # DZ3 BZT52C8V2;本体比焊盘框宽 0.4
# 下面两条是换下来的,留着当对照(**别再用**):
'CONN-TH_P5.08_KF128-5.08-2P': (-5.20, -5.20, 5.20, 5.20), # 旧 12V 螺钉端子
# ⚠️ KF2510-3A 是**弯针**座:焊盘一排,塑壳整个往 +Y 伸 11.7 mm,开口在远端。
@@ -113,8 +134,13 @@ H_FP = {
# ⚠️⚠️ 换成立式**省的是板面、付的是高度**:直针座塑壳竖起来。
# `3D Model Transform` 只给 XY,**Z 读不出来** —— 下面两个数是按同系列
# 常规值取的保守估计,**打样前必须拿实物量**(§12 待确认项)。
'CONN-TH_XT30PW-M': 11.0, # XT30 90° 板装公头,本体躺着
'CONN-TH_2510-3A': 9.0, # 2510 直针座,塑壳竖立
'CONN-TH_XT30PW-M': 11.0, # XT30 90° 板装公头,本体躺着(已换掉)
'DC-IN-TH_DC-005-2': 11.0, # 圆形 DC 座:同系列常规值,**打样前拿实物量**
# 放在西条(x −29~−1)上,那一条 hmax = 99(载板底面没东西),所以这个数目前不吃紧
'CONN-TH_2510-3A': 9.0, # 2510 直针座,塑壳竖立(3P,已换掉)
'CONN-TH_4P-P2.54_CAX_2510-4A': 11.5, # 2510 直针 4P:厂家属性 Z-Height of the Board = 11.5
'SOT-23_L2.9-W1.3-P1.90-LS2.4-BR': 1.2, # Q7 AO3401A(和另一个 SOT-23-3 同高,封装名不同)
'SOD-123_L2.7-W1.6-LS3.7-RD-1': 1.2, # DZ3 BZT52C8V2
# 排针本身就是对插件,不参与高度核算
'HDR-TH_40P-P2.54_AZ-PZ2.54-V11-2X20P': None,
}
@@ -238,12 +264,7 @@ CH1 = [
# 放电联锁
('Q%01', 13.8, 35.5, 0), ('R%18', 16.4, 35.5, 0),
('Q%02', 13.8, 39.5, 0), ('R%19', 17.8, 40.0, 90),
# 按键驱动(远离 SW)
('Q%03', 24.0, 12.0, 0), ('Q%04', 28.0, 12.0, 0),
('Q%05', 24.0, 16.0, 0), ('R%20', 28.0, 16.0, 0),
('R%21', 24.0, 19.5, 0), ('R%22', 28.0, 19.5, 0),
('R%23', 24.0, 22.5, 0), ('R%24', 28.0, 22.5, 0),
('R%25', 24.0, 25.5, 0),
# 按键驱动 Q%03~05 / R%20~25:2026-09-06 随 74HC595 整窝挪到载板(§26.23),这一片空出来了
]
@@ -294,7 +315,20 @@ put('J25', 128.0, 62.5, 0)
# **原来 F1 那个位置 (14.5,71.5) 正好被吃掉**。
# 而东边的墙是硬的:北排 CH2 从 **x 29.6** 起,D1(SMB) 再往东就撞上去。
# ⇒ J1 东边只剩 15 mm 给 F1/Q1/D1/DZ1/C1/R1 六件,一行排不下(要 15+ mm),改**两行**。
put('J1', 8.5, 72.0, 270) # 12V XT30 公头,插口朝左板边(rot 270)
# 2026-09-08 ⛔ **硬缺陷修复**:J1 原来在 x=8.5(本体 x 0.5~14.7)——
# 那是按**过期的假板框 160×125**挑的“西板边”。真板框西边在 x=−30,
# 于是这个 **90° 板装** XT30 的插口缩在板内 30.5 mm,上方 13 mm 处还压着载板
# —— **插头根本插不进去**。不是优化问题,是板子能不能用的问题。
# → 整个输入首级(J1/F1/Q1/D1 + Q1 的栅极卫星 DZ1/C1/R1)西移 30 mm。
# 西条 x −29~−1 整条 hmax = 99(载板底面没东西),高件随便放。
# 代价:加权 HPWL +1~3%(源头离负载远了)—— 这是板子可装配的必要代价。
# 2026-09-08 换成圆形 DC 电源座(用户拍板:DC-005 5.5x2.5,**不降载**,接受插座超额定)。
# ⚠️ 整机 12V 最坏 9.99 A(§22.6),而 DC-005 家族额定 2~5 A —— 这是全板唯一一处
# 明知超额定还要装的地方,发热在**插头内芯与插座弹片的接触面**,板上加铜救不了。
# 板端能做的只有一件:pin2 / pin3 两个负极触点**都接地并联**,接触电阻减半(见 p1_dcjack.py)。
# 桶口朝 −X ⇒ **rot 0**(XT30 那颗要 rot 270,别照抄)。本体 x −10.68~+3.92 相对中心,
# 放 x=−18.8 时本体 x −29.48~−14.88,离真西板边 (−30) 0.52 mm,和原来 XT30 的 0.5 一致。
put('J1', -18.8, 72.0, 0) # 12V 圆形 DC 座,桶口朝**真**西板边(本体 x −29.5~−14.9)
# ⚠⚠ **v11 的两行排法把 8 A 链卡死了**(2026-09-06 第二十三轮重排)。
# XT30 rot 270 之后 **GND 在北针 (13.5,74.5)、VIN 在南针 (13.5,69.5)**,
# 而 F1 卵在 (17.5,74.5) 卧着放 ⇒ VIN_RAW 要从南针绕到 GND 盘东侧再北上,
@@ -304,12 +338,12 @@ put('J1', 8.5, 72.0, 270) # 12V XT30 公头,插口朝左板边(rot 270
# ② D1 挪到东边空列 ⇒ J1.2 → F1.1 一片 6.5 × 7.9 mm 的铜,不再有颈;
# ③ C4/C5/C6 一起东移 ⇒ 腾出 y 78.85~83.5 那条 4.65 mm 的 VIN_SW 西行干道
# (干道北提是因为 J1 的机械脚 J1.4 在 (3.5,77.5),占到 y 78.6)。
put('F1', 17.5, 74.5, 90) # 立着放:F1.1(VIN_RAW) 在南、F1.2(VIN_FUSED) 在北
put('Q1', 24.0, 74.5, 270) # SOP-8 P-MOS 防反接
put('D1', 31.0, 74.5, 270) # SMBJ13A TVS(挪出 VIN_RAW 通道,贴在漏极铜东缘)
put('DZ1', 22.0, 68.0, 0) # 栅极钳位
put('C1', 25.5, 68.0, 0)
put('R1', 28.0, 71.5, 0)
put('F1', -12.5, 74.5, 90) # 立着放:F1.1(VIN_RAW) 在南、F1.2(VIN_FUSED) 在北
put('Q1', -6.0, 74.5, 270) # SOP-8 P-MOS 防反接
put('D1', 1.0, 74.5, 270) # SMBJ13A TVS(挪出 VIN_RAW 通道,贴在漏极铜东缘)
put('DZ1', -8.0, 68.0, 0) # 栅极钳位
put('C1', -4.5, 68.0, 0)
put('R1', -2.0, 71.5, 0)
put('C2', 6.5, 84.5, 0) # 470µF/25V
put('C3', 16.0, 84.5, 0)
# C4/C5/C6:rot 180 让 $1N236 那一脚朝西迎着漏极铜,GND 脚朝东好打过孔
@@ -360,28 +394,73 @@ put('R707', 60.0, 71.5, 180)
put('L3', 70.5, 78.0, 0)
put('C734', 77.5, 78.0, 90)
put('C735', 80.0, 78.0, 90)
# 第三片 595(EDL_EN1~5)+ 风扇低边驱动
put('U33', 92.0, 71.0, 0)
# ── 风扇 FAN-R2(2026-09-07):两只 4 线 PWM 风扇,一进一出 ──────────────
# 旧的「低边切回流 + 5 个 3 针座 + TACH 分压」整块作废:R730~R732、J28~J30 已删。
# 现在是「高边默认关电 + 独立 PWM 开漏 + 两路独立测速接收」,见设计文档 §22.6。
# ⚠️ 摆位约束不只是「元件之间不重叠」:这一块北中区 y 64~100 底下压着已布好的
# +12V / +3V3 / GND 铜和过孔,而 `pcb1_route_sig --ripup` **只拆自己画过的信号线**,
# 拆不掉电源铜。第一版把新件摆进 y 66~88 直接刷出 65 条间距错误。
# ⇒ 新件整体上移到 **y 104~121 那条空带**(J28 删掉之后腾出来的),
# 留用件 Q6 / R733 / R734 原地不动(它们原来的位置本来就是干净的)。
# 【A】高边电源开关:+12V --Q7(P-MOS)--> FAN_12V_SW,MCU 高阻时默认断电
put('R746', 91.0, 120.0, 0) # 100k 栅极拉回源极
put('Q7', 95.0, 120.0, 0) # AO3401A
put('DZ3', 99.5, 120.0, 0) # BZT52C8V2 钳位负 Vgs(Q7 的 Vgs 绝对最大 ±12V)
put('C738', 105.0, 120.0, 0) # 22uF/25V,开关后母线本地储能
put('D2', 111.5, 120.0, 0) # SS34:K 接 FAN_12V_SW、A 接 GND(负向钳位,不再是续流)
put('R747', 91.0, 116.0, 0) # 1k 栅极串阻 -> Q8 漏极
put('Q8', 95.0, 116.0, 0) # AO3400A 拉低 Q7 栅极
put('R748', 99.5, 116.0, 0) # 1k FAN_EN(IO5) -> Q8 栅极
put('R749', 103.0, 116.0, 0) # 100k Q8 栅极下拉 = 复位默认断电
# 【B】两路独立测速接收(10k 上拉到 FAN_12V_SW -> 15k/10k 分压 -> N-MOS -> 3V3 上拉)
# 2026-09-08:**跟着各自的风扇座走到真板边**。
# 链路是 J2n.TACH → R75x(上拉到 FAN_12V_SW) → 分压 → Q(N-MOS) → FAN_TACH_xx(3V3 逻辑)。
# `FAN_IN_TACH_RAW` / `FAN_OUT_TACH_RAW` 是**座子到分压器**的本地网络:
# 接收电路留在中间的话,这两个网络要横跨大半块板(121 / 104 mm);
# 挪到座子旁边只剩几毫米。代价是 `FAN_TACH_IN/OUT`(已经是 3.3 V 逻辑、
# 经 N-MOS 整形过)各长约 16 mm —— 净赚一百多毫米,而且换掉的是**未整形的
# 12 V 摆幅裸信号**,抗扰上也是对的。
put('R750', -26.0, 108.0, 0) # 进风:贴着西板边的 J26
put('R751', -21.0, 108.0, 0)
put('Q9', -26.0, 104.0, 0)
put('R752', -21.0, 104.0, 0)
put('R753', -26.0, 100.0, 0)
put('R754', 181.0, 108.0, 0) # 排风:贴着东板边的 J27
put('R755', 186.0, 108.0, 0)
put('Q10', 181.0, 104.0, 0)
put('R756', 186.0, 104.0, 0)
put('R757', 181.0, 100.0, 0)
# 【C】PWM 低边开漏:Q6 / R733 / R734 保持 2026-09-06 的原位(现在只拉风扇 PWM 脚)
put('Q6', 102.0, 69.5, 0)
put('D2', 108.5, 69.5, 0)
put('C738', 102.0, 74.0, 0)
put('C739', 107.0, 74.0, 0)
put('R730', 98.0, 78.0, 0)
put('R731', 101.5, 78.0, 0)
put('R732', 105.0, 78.0, 0)
put('R733', 98.0, 80.5, 0)
put('R733', 98.0, 80.5, 0)
put('R734', 101.5, 80.5, 0)
# 风扇座:4 个侧吸 + 1 个顶排,全部放板边(§22.6)
put('J26', 5.0, 16.0, 0) # 左板边(南)
put('J27', 5.0, 30.0, 0)
put('J29', 154.9, 16.0, 0) # 右板边(南)
put('J30', 154.9, 30.0, 0)
put('J28', 100.0, 112.7, 0) # 顶板边一侧;弯针塑壳往 +Y 伸 11.7,壳顶顶到 y 124.4
# 风扇座:一进一出,分列**真**左右板边(顶排风孔取消,见 THERMAL_TWO_FAN)
# ⚠️ 4P 座比 3P 宽 2.54 ⇒ 半宽 3.81 → 5.08。本体 (-5.08,-2.79)-(5.08,3.01),rot 0。
#
# 2026-09-08 挪位:原来在 (6.3,121) / (151,121) —— 那是按**过期的假板框 160×125** 挑的
# 「两个上角」,在真板框 220×135 / 左下角 (−30,−5) 里其实都在**板子中间**,
# 风扇线缆没法横着出去(直针座是垂直插拔,板边的意义就在于让线缆走得掉)。
#
# 当年退到「上角」的理由是:4P 座塑壳 11.5 mm(厂家 Z-Height),而下板 hmax 由
# **载板底面元件**的投影压出来(pcb1-env.json zones),假板框上 hmax >= 11.5 的
# 板边只有 y 68~92 一段,还被输入链占满。**真板框下这个约束消失**:
# 左条 x −30~0 整条 hmax 无限制,右条 x 160~190 有 84% 无限制。
# 实扫(`_fanpos2.py`):西边 y 2~49 与 61~120、东边 y 2~34 与 67~120 都可用。
#
# ⚠️ 压降复核(原注释担心的那一条):`FAN_12V_SW` 总共 2×0.67 A,但 Q7 居中、
# 两支**各只走 0.67 A**。0.8 mm 线宽下 R = ρL/(Wt):
# 西支 125 mm → 0.077 Ω → 51 mV;东支 95 mm → 0.058 Ω → 39 mV
# 都远在 FAN_CIRCUIT 那 0.2 V 预算之内。当年算的 0.26 V 是「单条 196 mm 走全电流」,
# 分成两支之后不成立。⇒ **这条线要按 0.8 mm 下发,别当普通信号走 0.20。**
put('J26', -23.9, 115.0, 0) # 真西板边 = 进风(本体 x −29.0~−18.8,离板框 1.0)
put('J27', 183.9, 115.0, 0) # 真东板边 = 排风(本体 x 178.8~189.0)
# ══════════════════ 【S-M】P3 / P3B 控制块 ══════════════════
# 南中块 x 69.5~119.5 / y 0.5~59
put('U20', 88.0, 18.2, 180) # ESP32-S3:天线端转到 −Y 朝下板边
put('J22', 154.4, 52.0, 90) # 调试 USB-C,rot 90 = 开口朝 +X 右板边
# 2026-09-08 ⛔ 同一类硬缺陷:J22 原来在 x=154.4(本体到 159.6)= 假板框的东边,
# 真板框东边在 x=190,于是 USB-C 开口缩在板内 30.4 mm,线插不进去。
put('J22', 184.4, 52.0, 90) # 调试 USB-C,rot 90 = 开口朝**真**东板边(本体到 189.6)
# (下板边放不下:C841 罐的 1.5 mm 禁区压到 y 8.4)
put('RT601', 70.8, 36.0, 90) # 电感区测温:贴着 CH4 的电感那一排
put('RT602', 70.8, 26.0, 90) # MOS 区测温
@@ -413,8 +492,7 @@ put('RT602', 70.8, 26.0, 90) # MOS 区测温
DECAP = [
('C602', 'U20', '2', 3.0), # 100 nF 0402,ESP32 高频去耦 —— 先占位
('C601', 'U20', '2', 8.0), # 22 µF 1206,ESP32 储能
('C604', 'U21', '10', 3.0), # 74HC595 #1 VCC
('C605', 'U22', '10', 3.0), # 74HC595 #2 VCC
# C604 / C605(74HC595 去耦)2026-09-06 随 595 挪到载板
('C606', 'U23', '24', 3.0), # PCA9555 VDD
('C607', 'U6', '15', 3.0), # DAC63204 #1 VDD
('C610', 'U7', '15', 3.0), # DAC63204 #2 VDD
@@ -457,8 +535,7 @@ def affine(pagelist, x0, y0, w, h):
# 给走线留通道),结果钉在下面这张表里。附属的 22 个 R/C 不再跟着仿射走,
# 改成 `SAT` 表逐个贴到**它真正的对端引脚**旁边。
CTRL = {
'U21': (92.25, 52.37),
'U22': (92.84, 38.01),
# U21 (92.25,52.37) / U22 (92.84,38.01) 2026-09-06 挪到载板(§26.23)—— x 88~97 / y 35~55 空出来
'U23': (76.50, 49.63),
'U6': (79.58, 38.01),
'U7': (109.57, 34.71),
@@ -489,7 +566,6 @@ SAT = [
('R608', 'U23', '1', 6.0, u'IOEXP_INT# 上拉'),
('R606', 'U23', '9', 6.0, u'BUCK_PG 上拉'),
('R607', 'U23', '10', 6.0, u'INA_ALERT 上拉'),
('R609', 'U21', '13', 7.0, u'KEY_OE# 上拉(三片 595 共用)'),
('C608', 'U6', '13', 4.0, u'DAC #1 内部基准/输出滤波'),
('C609', 'U6', '16', 4.0, u'DAC #1'),
('R614', 'U6', '16', 5.0, u'DAC #1'),
@@ -586,7 +662,7 @@ def _fits(des, cx, cy, rot, clear=0.3):
u"""摆在这里会不会撞人 / 越框 / 压定位孔 / 超高。"""
b = box(des, rot)
ab = (cx + b[0], cy + b[1], cx + b[2], cy + b[3])
if ab[0] < 0.4 or ab[2] > BW - 0.4 or ab[1] < 0.4 or ab[3] > BH - 0.4:
if ab[0] < BX0 + 0.4 or ab[2] > BX1 - 0.4 or ab[1] < BY0 + 0.4 or ab[3] > BY1 - 0.4:
return False
r = MH_KEEPOUT / 2.0
for hx, hy in MH:
@@ -756,7 +832,7 @@ def check():
for d in sorted(POS):
x0, y0, x1, y1 = abs_box(d)
m = 0.4
if x0 < m or x1 > BW - m or y0 < m or y1 > BH - m:
if x0 < BX0 + m or x1 > BX1 - m or y0 < BY0 + m or y1 > BY1 - m:
errs.append(u'越出板框: %-6s 框 %.2f..%.2f / %.2f..%.2f' % (d, x0, x1, y0, y1))
r = MH_KEEPOUT / 2.0
@@ -840,6 +916,53 @@ def mate_report():
% (p, v[2], v[0], v[3], u'✔' if v[2] == v[3] else u'✘'))
# ══════════ 出线道加宽(2026-09-06 第二十六轮,用户拍板)══════════
#: **「够净距」不等于「出得去线」。** `pad_clearance_errs` 判的是焊盘净距 ≥ 0.20,
#: 而一根 0.2 mm 的线要从两个焊盘中间出去,需要 0.20 + 0.20 + 0.20 = **0.60**。
#: U6 / U7(WQFN-16 3×3,四边出脚)周围的卫星件正好卡在 **0.31 ~ 0.38** ——
#: 本地六项全过、DRC 也没有间距错误,可 `pcb1_escape_audit` 报「被围死」、
#: 布线器报「几何上就出不去」,`SCL`(U6.6) / `$6N532`(U6.16) / `$6N547`(U7.16)
#: 三个网络因此永远布不通。这里把堵住出线道的六颗按**背离芯片**的方向各挪 0.30 mm。
#:
#: ⚠️ 挪完 `C607` / `R617` 会分别压到 `SDA` / `SCL` 的换层孔(净距 0.029 / 0.113)——
#: 那两个孔是 `pcb1_i2c_l3.py` 每轮重算的,**挪件之后必须重跑它**,孔会自己让开。
#:
#: ⚠️ §26.22b:门槛不是 0.60 而是 **0.741**(CLR + 半线宽 + 栅格量化余量 QERR,两侧各 0.3707)。
#: 0.30 挪出来的 0.612/0.615/0.676 理论上塞得下、0.1 栅格上塞不下 ⇒ 2026-09-06 第二十八轮
#: 改成 **0.50(东列 0.45)**,三个方向都 ≥ 0.80。板上现在(`_pcb1_place_now.json`)是按 0.30 摆的,
#: 再挪 0.20 / 0.15 会压到的铜见 `.claude/_pcb1_unrouted_report.md` §A(VDAC4 孔、SDA 线+孔,
#: 以及 10 段要按新位置重做的扇出短线)。
#: ⚠️⚠️ **这一手救不了 `U6.16` / `U7.16`**(`pcb1_ch_reroute.py` 本地实测:挪完照样「几何上就出不去」)——
#: 它们被 C609 / C612 的 GND 脚 + GND 扇出孔从西边、C607 / C610 从北边、U6.15 的 +3V3 出线段从东边
#: 三面围死,北挪 C607 只是把北墙推远,西墙东墙不动。真正的解法是下面的 `SAT_FLIP`。
ESCAPE_NUDGE = {
'R615': (0.0, -0.50), # U6 南排(U6.5 / U6.6=SCL) 0.312 -> 0.812
'C607': (0.0, +0.50), # U6 北排(U6.13/15/16) 0.315 -> 0.815
'C608': (+0.45, 0.0), # U6 东列(VDAC2 / VDAC4) 0.376 -> 0.826
'R617': (0.0, -0.50), # U7 南排(U7.5 / U7.6) 0.315 -> 0.815
'C610': (0.0, +0.50), # U7 北排(U7.13/15/16) 0.312 -> 0.812
'C611': (+0.45, 0.0), # U7 东列(VDAC3 / VDAC5) 0.376 -> 0.826
}
for _d, (_dx, _dy) in sorted(ESCAPE_NUDGE.items()):
if _d in POS:
_x, _y, _r = POS[_d]
POS[_d] = (round(_x + _dx, 3), round(_y + _dy, 3), _r)
# ══════════ VREF 电容转 180°(2026-09-06 第二十八轮,A 类唯一真正的几何解)══════════
#: `C609` / `C612` 是 DAC63204 的 VREF 电容(pin 16),`place_sats()` 把它们竖着(rot 90)贴在
#: U6.16 / U7.16 的西北角:**GND 脚在南(离引脚近)、VREF 脚在北(离引脚远)**,
#: GND 扇出孔又打在 GND 脚正南 —— 于是 U6.16 往西北去找 VREF 脚的那条道被自家 GND 孔 + GND 短线堵死。
#: 转 180°(rot 90 -> 270)之后 VREF 脚朝南正对 U6.16(0.8 mm),GND 脚朝北。
#: `pcb1_ch_reroute.py --win 73,32,87,47 --nets '$6N532' --move C609:0:0:180 --drop-via 78.03,39.61`
#: 本地实测:`$6N532` 3.6 mm 一次布通、净距 0;U7 同理。下发时要删 2 段扇出短线 + 补 1 个 GND 孔
#: (见 `_pcb1_unrouted_report.md` §A)。
SAT_FLIP = {'C609': 180, 'C612': 180}
for _d, _dr in sorted(SAT_FLIP.items()):
if _d in POS:
_x, _y, _r = POS[_d]
POS[_d] = (_x, _y, (_r + _dr) % 360)
def summary():
ext = ch_extent()
print(u'母版局部包围盒 x %.2f~%.2f y %.2f~%.2f(%.1f x %.1f)'