# -*- coding: utf-8 -*- u"""补线结果的本机校验:按真实几何自算净距,**下发前必跑**。 /c/Python314/python .claude/r2/fan_check.py # 读 _base_fan_repair.json + 板上铜快照 /c/Python314/python .claude/r2/fan_check.py --push # 过了就下发(导线 + 过孔) 判三样(`router_check.py` 只判前两样,第三样是这块板特有的): 1. 新导线 ↔ 板上异网铜(L1/L4 的线、过孔、焊盘):净距 ≥ `MIN_CLR` 2. 新过孔 ↔ 板上异网铜 / 异网孔(孔到孔按钻孔间距 `DRILL_CLR`) 3. ⛔ **新过孔 ↔ 内层(L15/L16)上的线**:过孔是通孔、穿过内层,而布线器只认 L1/L4, 这一层关系它**根本不查**。底板的内层名义上是整层地、不布线,但板上实测有 125 段 用户手布的内层线(HUB_RST / +3V3_HUB / IO0 / INT# …)⇒ 不查就是往人家线上扎孔。 """ import io import json import math import os import sys sys.stdout.reconfigure(encoding='utf-8') HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, os.path.dirname(HERE)) sys.path.insert(0, HERE) import fp_edge # noqa: E402 MIL = fp_edge.MIL MIN_CLR = 0.15 # 规则底线(rules_base.MIN_CLEARANCE) DRILL_CLR = 0.25 # 钻孔到钻孔 VIA_D, VIA_HOLE = 0.6, 0.3 REPAIR = os.path.join(HERE, '_base_fan_repair.json') INPUT = os.path.join(HERE, '_base_fan_input.json') INNER = os.environ.get('DCDC_INNER_JSON') or os.path.join(HERE, '_base_fan_inner.json') PADS = os.path.join(HERE, '_base_pads.json') def seg_d(px, py, ax, ay, bx, by): dx, dy = bx - ax, by - ay L2 = dx * dx + dy * dy t = 0.0 if L2 < 1e-12 else max(0.0, min(1.0, ((px - ax) * dx + (py - ay) * dy) / L2)) return math.hypot(px - (ax + t * dx), py - (ay + t * dy)) def ss_d(a, b): return min(seg_d(a[0], a[1], *b), seg_d(a[2], a[3], *b), seg_d(b[0], b[1], *a), seg_d(b[2], b[3], *a)) def main(): rep = json.load(io.open(REPAIR, encoding='utf-8')) cu = json.load(io.open(INPUT, encoding='utf-8')) pads = json.load(io.open(PADS, encoding='utf-8')) inner = json.load(io.open(INNER, encoding='utf-8')) if os.path.exists(INNER) else [] new_s, new_v = rep['segs'], rep['vias'] print(u'补线结果:%d 段 / %d 孔;未完成 %d 条;未连通 %d 条' % (len(new_s), len(new_v), len(rep.get('logs') or []), len(rep.get('unconnected') or []))) for m in (rep.get('logs') or []): print(u' [!] ' + m) bad = [] for s in new_s: lay, x1, y1, x2, y2, w, net = s for t in cu['segs']: if t[6] == net or t[0] != lay: continue d = ss_d((x1, y1, x2, y2), (t[1], t[2], t[3], t[4])) - w / 2 - t[5] / 2 if d < MIN_CLR: bad.append(u'新线 %s@L%s 距 %s 的线 %.3f' % (net, lay, t[6] or '?', d)) for v in cu['vias']: if v[2] == net: continue d = seg_d(v[0], v[1], x1, y1, x2, y2) - w / 2 - VIA_D / 2 if d < MIN_CLR: bad.append(u'新线 %s 距 %s 的过孔 %.3f' % (net, v[2] or '?', d)) for p in pads: if p[7] == net or p[6] not in (lay, 12): continue d = seg_d(p[2], p[3], x1, y1, x2, y2) - w / 2 - max(p[4], p[5]) / 2 if d < MIN_CLR: bad.append(u'新线 %s 距焊盘 %s.%s(%s) %.3f' % (net, p[0], p[1], p[7] or '-', d)) for v in new_v: vx, vy, net = v[0], v[1], v[2] for t in cu['segs']: if t[6] == net: continue d = seg_d(vx, vy, t[1], t[2], t[3], t[4]) - VIA_D / 2 - t[5] / 2 if d < MIN_CLR: bad.append(u'新孔 %s 距 %s@L%s 的线 %.3f' % (net, t[6] or '?', t[0], d)) for t in inner: # ⛔ 布线器不查的那一项 if t[6] == net: continue d = seg_d(vx, vy, t[1], t[2], t[3], t[4]) - VIA_D / 2 - t[5] / 2 if d < MIN_CLR: bad.append(u'新孔 %s 扎在**内层** L%s 的 %s 线上(净距 %.3f)' % (net, t[0], t[6] or '?', d)) for o in cu['vias']: if o[2] == net: continue d = math.hypot(vx - o[0], vy - o[1]) - VIA_HOLE if d < DRILL_CLR: bad.append(u'新孔 %s 距 %s 的孔(钻孔间距)%.3f' % (net, o[2] or '?', d)) if bad: print(u'[X] 净距不过 %d 条:' % len(bad)) for t in bad[:25]: print(u' ' + t) sys.exit(1) print(u'[OK] 新铜与板上现有铜逐项净距达标(线 ≥ %.2f / 钻孔 ≥ %.2f / 内层线已查)' % (MIN_CLR, DRILL_CLR)) if '--push' in sys.argv: import router as RT import route_base as RB # noqa: F401 顶层把 router 配成底板 RT.push([tuple(s) for s in new_s], [tuple(v) for v in new_v]) print(u'已下发 %d 段 / %d 孔' % (len(new_s), len(new_v))) if __name__ == '__main__': main()