文件
phonerail/.claude/pcb1_gnd_stitch.py
T
zhou yang和Claude Opus 5 2bfe308b30 初始导入:PhoneRail 五路手机直供电源板
原理图/PCB 已在 EasyEDA 里做到 12 页跨页 0 漏接,固件刚完成骨架包 A1。
这次是把已有的一切一次性纳入版本控制,没有改动任何设计内容。

纳入:
- 设计与需求文档、PROGRESS.md、小米8 参考原理图 PDF 与追脚脚本
- .claude/ 下的 EasyEDA 桥接工具链与几何/网表缓存(eda-parts.json、
  pcb-parts.json、pcb-pads.json、p2-ch1.json 等,是重画各页的唯一事实来源)
- 117 张验证截图(本项目所有方向问题都是靠人眼看截图发现的,属于证据不是垃圾)
- firmware/ 的 A1 骨架与 site/ 文档站源码

.gitignore 排除:构建产物(firmware/build、host/build、sdkconfig)、
site/node_modules、__pycache__、桥接日志 _bridge.log、脚本自标的 *.stale。

.gitattributes 默认 `* -text`:现有 .py/.json 大多是 CRLF,原样存,
不让 git 重写行尾。唯一例外是 *.sh 强制 LF —— 带 \r 的 shell 脚本 bash 跑不了。

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QYuExfgyYa6DzRRcUso8M8
2026-09-06 18:18:14 +08:00

121 行
5.2 KiB
Python
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- coding: utf-8 -*-
u"""Inner2 上**没有落脚点**的那些 GND 底填碎片 -> 补缝合孔(接到 L2 的完整地平面)。
/c/Python314/python .claude/pcb1_gnd_stitch.py # 只找位置,不动板
/c/Python314/python .claude/pcb1_gnd_stitch.py --push # 校核过了就下发
## 这块浮铜是怎么来的(2026-09-06 查清)
板子南沿 y 0.5~9 那一整条 GND 底填(**1312.8 mm²**)在 Inner2 上是孤立的:
南边是板框,北边被 `12V-SW+BR`(y9~34, x0.5~72.6)、`3V3-SE`(y9~41, x73~115)、
`12V-SE+RC`(y9~34, x116~159.5) 三块电源面封死,原本只剩两条缝通到别处 ——
x 72.6~73(**0.4 mm**,本来就走不了)与 x 115~116(1.0 mm)。
后者被 `K5_VUP(114.80,28.70)` / `VDAC5(116.70,29.50)` / `EFUSE_EN3(115.00,32.40)`
三个**信号布线打的过孔**的反焊盘掐断了。而这一条里**一个过孔、一个通孔焊盘都没有**,
于是整条铜浮着。
⚠️ 三道检查全都看不见它:
· 铺铜的孤岛判定按**铺铜对象**算,`GND-FILL-L3` 是一个对象铺出十块,
单独某一块有没有落脚点它不管;
· 连接性 DRC 只看网络连没连上(GND 靠别处照样连通);
· `pcb1_pour.py` 下发前的 `report()` 也看不见 —— 它的栅格**不含别的铺铜**,
算出来 GND 是一整块。**只有下发后 `verify()` 回读实测铜才量得出来。**
而这条浮铜正贴着 ESP32 的天线禁铺区(x 78~98, y 0.8~8.9),160 mm 长的悬空铜条
在 2.4 GHz 附近不是个能忽略的东西。
## 做法
每块浮铜里挑几个「三层都让得开、钻孔间距也够」的点打 GND 过孔。
过孔是通孔 ⇒ 一个孔就把这块 Inner2 的铜接到 **Inner1 的完整地平面**上。
落点判据直接借 `pcb1_i2c_l3.Field.via_ok()`(外层真净距 + L3 真铜 + 不分网络的钻孔间距)。
"""
import math
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import numpy as np # noqa: E402
import pcb1_i2c_l3 as M # noqa: E402
import pcb1_netlist as NL # noqa: E402
import pcb1_route_sig as RS # noqa: E402
#: 想在哪些地方缝。每条是 (说明, x0, y0, x1, y1, 要几个孔)。
#: 南沿那条避开 ESP32 天线禁铺区 x 78~98(`pcb_fanout.ANT`,全层不许有铜)。
ZONES = [
(u'南沿西段', 3.0, 2.0, 74.0, 8.0, 3),
(u'南沿东段', 101.0, 2.0, 157.0, 8.0, 3),
]
MINGAP = 8.0 # 同一次补的孔彼此至少隔这么远,别扎堆
def pick(zone, f, used):
u"""在一个矩形里挑 n 个合法孔位,尽量摊开。"""
name, x0, y0, x1, y1, n = zone
ok = f.via_ok()
out = []
for k in range(n):
# 目标位置:把区间等分,然后在目标点附近找最近的合法格
tx = x0 + (x1 - x0) * (k + 0.5) / n
ty = (y0 + y1) / 2.0
best = None
i0, j0 = f._idx(x0, y0)
i1, j1 = f._idx(x1, y1)
for j in range(max(0, j0), min(f.ny, j1 + 1)):
for i in range(max(0, i0), min(f.nx, i1 + 1)):
if not ok[j, i]:
continue
x, y = f.x0 + i * f.g, f.y0 + j * f.g
if any(math.hypot(x - ux, y - uy) < MINGAP for ux, uy in used + out):
continue
d = math.hypot(x - tx, y - ty)
if best is None or d < best[0]:
best = (d, round(x, 3), round(y, 3))
if best is None:
print(u' ⚠️ %s 第 %d 个孔位没找到(净距或钻孔间距都不够)' % (name, k + 1))
continue
out.append((best[1], best[2]))
return out
def main():
push = '--push' in sys.argv
pads, vias, lines = NL.geom(push or '--refresh' in sys.argv)
print(u'板上:过孔 %d / 走线 %d' % (len(vias), len(lines)))
got, used = [], []
for z in ZONES:
f = M.Field((z[1] - 2.0, z[2] - 2.0, z[3] + 2.0, z[4] + 2.0),
'GND', pads, vias, lines, [])
pts = pick(z, f, used)
used += pts
got += pts
print(u' %s:%s' % (z[0], ' '.join('(%.2f,%.2f)' % p for p in pts) or u'无'))
if not got:
sys.exit(u'一个孔位都没挑出来')
# 校核:借 pcb1_i2c_l3.verify 的孔判据(路径写成"原地换层",只出一个孔、没有线段)
routes = [('GND', [(x, y, M.L3, M.WIRE), (x, y, M.L_TOP, M.WIRE),
(x, y, M.L_TOP, M.WIRE)]) for x, y in got]
bad = M.verify(routes, pads, vias, lines)
for b in bad[:20]:
print(u' ✗ ' + b)
print(u' 校核:%s' % (u'✅ 全过(%d 个孔)' % len(got) if not bad
else u'❌ %d 条' % len(bad)))
if bad:
sys.exit(1)
if not push:
print(u'\n(只算不下发。要下发加 --push)')
return
RS.ROUTES = os.path.join(HERE, '_pcb1_gnd_stitch.json')
RS.push({('GND%s%03d' % (chr(0), i)): p for i, (_n, p) in enumerate(routes)})
print(u'⚠️ 新过孔穿过内层平面 —— 现在必须重铺一次:'
u'/c/Python314/python .claude/pcb1_pour.py --push --refresh')
if __name__ == '__main__':
main()