文件

124 行
4.8 KiB
Python

# -*- coding: utf-8 -*-
u"""给风扇那一窝重排后的 **GND 脚补过孔**(扎进 L2/L3 整层地)。
/c/Python314/python .claude/r2/fan_gnd_vias.py # 只算 + 报告
/c/Python314/python .claude/r2/fan_gnd_vias.py --push # 下发
## 为什么要单独一个脚本
`route_base.plane_vias()` 是**全板口径**(给所有 GND 贴片焊盘打孔),直接调会在整块板上
重打一遍孔;而 `repair_base.py` 只补信号网、根本不做平面网扇出。
于是 2026-09-12 重排之后实测:22 件里有 **9 只 GND 脚新位置底下没有孔**
(原来的孔还留在旧脚位上,成了孤立的缝合孔)。这种漏法 DRC 不报、
`board_check` 也只会说「GND 靠铜面成网」,要靠这一支单独查。
落孔点:焊盘中心优先;中心放不下就在焊盘外圈按 0.25 网格找一个既不压异网铜、
也不压别的孔的点,离本焊盘边不超过 `MAX_OUT`(太远就不是这只脚的回流孔了)。
"""
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
import place_base as PL # noqa: E402
MIL = fp_edge.MIL
PCB = '7b9524b6aaf6f235'
VIA_D, VIA_HOLE = 0.6, 0.3
CLR = 0.20 # 孔到异网铜
DRILL = 0.25 # 钻孔到钻孔
MAX_OUT = 1.2 # 落孔点离焊盘边最远多少
STEP = 0.125
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 main():
cu = json.load(io.open(os.path.join(HERE, '_base_fan_input.json'), encoding='utf-8'))
pads = json.load(io.open(os.path.join(HERE, '_base_pads.json'), encoding='utf-8'))
segs = cu['segs']
vias = [(v[0], v[1], v[2]) for v in cu['vias']]
gnd = [p for p in pads if p[0] in PL.FAN_REFS and p[7] == 'GND']
print(u'这一窝的 GND 脚:%d 只' % len(gnd))
def free(vx, vy, net='GND'):
for s in segs:
if s[6] == net:
continue
if seg_d(vx, vy, s[1], s[2], s[3], s[4]) < VIA_D / 2 + s[5] / 2 + CLR:
return False
for p in pads:
if p[7] == net or p[6] == 12:
continue
dx = abs(vx - p[2]) - p[4] / 2 - VIA_D / 2
dy = abs(vy - p[3]) - p[5] / 2 - VIA_D / 2
d = math.hypot(max(dx, 0), max(dy, 0)) if (dx > 0 and dy > 0) else max(dx, dy)
if d < CLR:
return False
for v in vias:
if math.hypot(vx - v[0], vy - v[1]) < VIA_HOLE + DRILL:
return False
return True
made, miss = [], []
for p in gnd:
px, py, w, h = p[2], p[3], p[4], p[5]
if any(math.hypot(px - v[0], py - v[1]) < max(w, h) / 2 + 0.3 for v in vias if v[2] == 'GND'):
continue # 脚上已经有孔
cand = [(0.0, px, py)]
k = 1
while k * STEP <= MAX_OUT + max(w, h) / 2:
r = k * STEP
for ang in range(0, 360, 15):
vx = round(px + r * math.cos(math.radians(ang)), 4)
vy = round(py + r * math.sin(math.radians(ang)), 4)
dx, dy = abs(vx - px) - w / 2, abs(vy - py) - h / 2
out = math.hypot(max(dx, 0), max(dy, 0))
if out <= MAX_OUT:
cand.append((r, vx, vy))
k += 1
got = None
for _r, vx, vy in cand:
if free(vx, vy):
got = (vx, vy)
break
if got:
made.append([got[0], got[1], 'GND'])
vias.append((got[0], got[1], 'GND'))
print(u' %s.%s (%.2f,%.2f) → 孔 (%.2f,%.2f)' % (p[0], p[1], px, py, got[0], got[1]))
else:
miss.append(u'%s.%s (%.2f,%.2f)' % (p[0], p[1], px, py))
print(u'\n要补 %d 个孔;找不到落点 %d 只' % (len(made), len(miss)))
for t in miss:
print(u' ⛔ ' + t)
if not made or '--push' not in sys.argv:
return
from eda import run
r = run("""
await eda.dmt_EditorControl.openDocument('%s');
await new Promise(r=>setTimeout(r,2500));
const V = %s, MIL = %s;
let n = 0;
for (const [x, y, net] of V) {
try { await eda.pcb_PrimitiveVia.create(net, x*MIL, y*MIL, %s*MIL, %s*MIL); n++; } catch (e) {}
}
await eda.pcb_Document.save();
return {made: n, now: (await eda.pcb_PrimitiveVia.getAll()||[]).length};
""" % (PCB, json.dumps(made), MIL, VIA_HOLE, VIA_D), timeout=600)
print(json.dumps(r.get('result'), ensure_ascii=False))
if __name__ == '__main__':
main()