# -*- 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())