文件
phonerail/.claude/inner1_check.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

165 行
7.1 KiB
Python
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- coding: utf-8 -*-
u"""载板 Inner1 五块 VOn_P 电源岛的**本地几何校核**(不连 EDA,纯算)。
/c/Python314/python .claude/inner1_check.py [--grid 0.05]
判据(每块岛):
① 本网所有「内层可见」的焊盘(layer 12 通孔)都落在**同一个连通域**里;
② 扣掉**异网焊盘 / 过孔的反焊盘**(半径 + 0.2 安全间距)之后,
沿电流方向的**最窄断面 >= 7.0 mm**(IPC-2221 内层 0.5oz / 4 A / ΔT20K)。
数据来自 `_pcb2_pads_full.json` / `_pcb2_vias.json`(`pcb2_pour.py --dump` 刷新)。
岛的多边形定义是 `pcb2_pour.py` 的 `ISLANDS`,这里 import 过来,**不重复写一遍**。
"""
import json, os, sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
MIL = 39.3700787401575
CLEAR = 0.2 # MIN_CLEARANCE,见 pcb2_rules.py
NECK_MIN = 7.0
def load_geo():
pads = json.load(open(os.path.join(HERE, '_pcb2_pads_full.json'), encoding='utf-8'))
vias = json.load(open(os.path.join(HERE, '_pcb2_vias.json'), encoding='utf-8'))
P = []
for ref, num, net, layer, x, y, shape, hole in pads:
if layer not in (12, 15): # 只有多层(通孔)焊盘在 Inner1 上占位
continue
kind = shape[0]
if kind == 'ELLIPSE':
hx = hy = shape[1] / MIL / 2.0
else: # RECT / OVAL / POLYGON -> 按外接矩形保守算
hx, hy = shape[1] / MIL / 2.0, shape[2] / MIL / 2.0
P.append((net, x / MIL, y / MIL, hx, hy, '%s.%s' % (ref, num)))
V = [(net, x / MIL, y / MIL, (d / MIL / 2.0) if d else 0.305)
for net, x, y, d, h in vias]
return P, V
def inside(poly, x, y):
n, c = len(poly), False
for i in range(n):
x1, y1 = poly[i]
x2, y2 = poly[(i + 1) % n]
if (y1 > y) != (y2 > y) and x < (x2 - x1) * (y - y1) / (y2 - y1) + x1:
c = not c
return c
def check(name, net, poly, P, V, g, region=None):
xs = [p[0] for p in poly]; ys = [p[1] for p in poly]
x0, x1, y0, y1 = min(xs), max(xs), min(ys), max(ys)
nx, ny = int(round((x1 - x0) / g)), int(round((y1 - y0) / g))
cx = [x0 + (i + 0.5) * g for i in range(nx)]
cy = [y0 + (j + 0.5) * g for j in range(ny)]
cell = [[inside(poly, cx[i], cy[j]) for i in range(nx)] for j in range(ny)]
# 反焊盘:异网的通孔焊盘 / 过孔
def carve(px, py, hx, hy, r=None):
a, b = (hx + CLEAR, hy + CLEAR) if r is None else (r + CLEAR, r + CLEAR)
i0 = max(0, int((px - a - x0) / g)); i1 = min(nx - 1, int((px + a - x0) / g) + 1)
j0 = max(0, int((py - b - y0) / g)); j1 = min(ny - 1, int((py + b - y0) / g) + 1)
for j in range(j0, j1 + 1):
for i in range(i0, i1 + 1):
if not cell[j][i]:
continue
dx, dy = abs(cx[i] - px), abs(cy[j] - py)
if r is None:
if dx <= a and dy <= b:
cell[j][i] = False
elif (dx * dx + dy * dy) <= a * a:
cell[j][i] = False
carved = 0
for pnet, px, py, hx, hy, tag in P:
if pnet == net:
continue
if px + hx + CLEAR < x0 or px - hx - CLEAR > x1: continue
if py + hy + CLEAR < y0 or py - hy - CLEAR > y1: continue
carve(px, py, hx, hy); carved += 1
for vnet, vx, vy, vr in V:
if vnet == net: continue
if vx + vr + CLEAR < x0 or vx - vr - CLEAR > x1: continue
if vy + vr + CLEAR < y0 or vy - vr - CLEAR > y1: continue
carve(vx, vy, 0, 0, r=vr); carved += 1
# 连通域(4 邻接,迭代式洪泛,避免递归深度)
lab = [[0] * nx for _ in range(ny)]
k = 0; sizes = {}
for j in range(ny):
for i in range(nx):
if not cell[j][i] or lab[j][i]:
continue
k += 1; st = [(j, i)]; lab[j][i] = k; n_ = 0
while st:
a, b = st.pop(); n_ += 1
for da, db in ((1, 0), (-1, 0), (0, 1), (0, -1)):
p, q = a + da, b + db
if 0 <= p < ny and 0 <= q < nx and cell[p][q] and not lab[p][q]:
lab[p][q] = k; st.append((p, q))
sizes[k] = n_
main = max(sizes, key=sizes.get) if sizes else 0
# 本网焊盘是否都在主连通域里
own, bad = [], []
for pnet, px, py, hx, hy, tag in P:
if pnet != net: continue
if not (x0 - 1 <= px <= x1 + 1 and y0 - 1 <= py <= y1 + 1): continue
own.append(tag)
i = int((px - x0) / g); j = int((py - y0) / g)
hit = False
rr = int((max(hx, hy) + 0.3) / g) + 1
for q in range(max(0, j - rr), min(ny, j + rr + 1)):
for p in range(max(0, i - rr), min(nx, i + rr + 1)):
if lab[q][p] == main: hit = True; break
if hit: break
if not hit: bad.append(tag)
# 断面:逐行 / 逐列统计主连通域的铜宽
def scan(rows):
out = []
for idx, line in rows:
tot = sum(1 for v in line if v == main) * g
run = m = 0
for v in line:
run = run + 1 if v == main else 0
m = max(m, run)
if tot > 0: out.append((tot, m * g, idx))
return out
# v2:7 mm 颈宽只在走廊主体(region = body_bbox)里量;穿筋的 3 mm 颈只走纹波电流
rx0, ry0, rx1, ry1 = region if region else (x0, y0, x1, y1)
rowsY = scan([(cy[j], [lab[j][i] if rx0 <= cx[i] <= rx1 else 0 for i in range(nx)])
for j in range(ny) if ry0 <= cy[j] <= ry1])
rowsX = scan([(cx[i], [lab[j][i] if ry0 <= cy[j] <= ry1 else 0 for j in range(ny)])
for i in range(nx) if rx0 <= cx[i] <= rx1])
my = min(rowsY); mx = min(rowsX)
ok = (not bad) and my[0] >= NECK_MIN and mx[0] >= NECK_MIN
print(u' %-4s %-7s %2d 焊盘 / 挖 %3d 个反焊盘 / %d 个连通域(主域 %.0f mm²)'
% (name, net, len(own), carved, k, sizes.get(main, 0) * g * g))
print(u' 最窄横断面 %5.2f mm(连续段 %.2f)@ y=%.2f '
u'最窄纵断面 %5.2f mm(连续段 %.2f)@ x=%.2f %s'
% (my[0], my[1], my[2], mx[0], mx[1], mx[2], u'✅' if ok else u'❌'))
if bad:
print(u' [!!] 这些本网焊盘不在主连通域里: %s' % ' '.join(bad))
return ok
def main():
g = 0.05
if '--grid' in sys.argv: g = float(sys.argv[sys.argv.index('--grid') + 1])
from pcb2_pour import ISLANDS, island_poly, body_bbox
P, V = load_geo()
print(u'Inner1 电源岛几何校核(栅格 %.2f mm,安全间距 %.2f,颈宽下限 %.1f mm)'
% (g, CLEAR, NECK_MIN))
allok = True
for name, net, body, branch in ISLANDS:
allok &= check(name, net, island_poly(body, branch), P, V, g, region=body_bbox(body))
print(u'%s' % (u'全部通过。' if allok else u'!! 有不达标的岛,先改 ISLANDS 再 --push。'))
return 0 if allok else 1
if __name__ == '__main__':
sys.exit(main())