文件

150 行
7.1 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- coding: utf-8 -*-
u"""BLADE-PCB_1(2026-09-15 新摆位 + 上拉电阻挪到 U4 东南之后)的**位号丝印重排**。
/c/Python314/python .claude/r2/silk_blade1.py # 只算 + 体检
/c/Python314/python .claude/r2/silk_blade1.py --push # 下发 + 读回核对
复用 `silk_blade.py` 的快照 / 文字框 / 板框与禁区判据 / 下发,但**换了排位器**:
`silk_blade.plan()` 只看焊盘和过孔,不看**别的元件本体** ⇒ 在新摆位上把 C11 的位号放到了 C6 的本体上
(离 C6 中心 0.29)、C12 放到 F1 上 —— 体检四项全 0,但读板的人会认错件。
这里的判据(按优先级):
硬约束:不越框 / 不入金手指区 / 不压任何焊盘(外扩 0.1)/ 不与已放位号重叠
软约束(计分):到本元件本体的距离 + 压别的元件本体 3.0 + 每压一个过孔 1.5
候选:本体四周 0.1 mm 网格、±3.0 mm、字 0° / 90°;大件先排。
J1 位号手工定在 (43.0, 10.5):金手指居中后原来的 (40.28, 8.25) 落进台阶以下、压着地母线孔。
"""
import io
import json
import math
import sys
import silk_blade as S # noqa: E402 (同目录,会顺带配好 sys.path)
import pcb_place as PL # noqa: E402
RANGE, STEP = 3.0, 0.1
J1_POS = (43.0, 10.5, 0)
def bodies(d, pads, sz):
u"""{位号: 本体框}:焊盘外包框与 pcb_place 本体尺寸取大,外扩 0.1。"""
by = {}
for q in pads:
if q[6] == 12:
continue
b = by.setdefault(q[0], [1e9, 1e9, -1e9, -1e9])
b[0] = min(b[0], q[2] - q[4] / 2.)
b[1] = min(b[1], q[3] - q[5] / 2.)
b[2] = max(b[2], q[2] + q[4] / 2.)
b[3] = max(b[3], q[3] + q[5] / 2.)
out = {}
for c in d['comps']:
r = c['ref']
w, h = sz.get(r, (0.6, 0.6))
if int(round(c['rot'])) % 180:
w, h = h, w
b = by.get(r, [c['x'], c['y'], c['x'], c['y']])
out[r] = (min(b[0], c['x'] - w / 2.) - 0.1, min(b[1], c['y'] - h / 2.) - 0.1,
max(b[2], c['x'] + w / 2.) + 0.1, max(b[3], c['y'] + h / 2.) + 0.1, c['lay'])
return out
def box_dist(a, b):
dx = max(b[0] - a[2], a[0] - b[2], 0.0)
dy = max(b[1] - a[3], a[1] - b[3], 0.0)
return math.hypot(dx, dy)
def plan(d, pads, sz):
attrs = {a['ref']: a for a in d['attrs'] if a['vis']}
body = bodies(d, pads, sz)
# 丝印层 3 = 顶、4 = 底;元件层 1 = 顶、2 = 底
side = {3: 1, 4: 2}
padsL = S._pads_by_layer(pads)
out, placed, stat = {}, [], {}
j = attrs.get('J1')
if j:
out['J1'] = {'x': J1_POS[0], 'y': J1_POS[1], 'rot': J1_POS[2], 'id': j['id'], 'lay': j['lay']}
placed.append((j['lay'], S.tbox('J1', *J1_POS, fs=j['fs'])))
order = sorted([r for r in attrs if r != 'J1' and r in body],
key=lambda r: -((body[r][2] - body[r][0]) * (body[r][3] - body[r][1])))
for r in order:
a = attrs[r]
ob = body[r]
lay = a['lay']
others = [b for k, b in body.items() if k != r and b[4] == side.get(lay)]
best = None
cx, cy = (ob[0] + ob[2]) / 2., (ob[1] + ob[3]) / 2.
hw, hh = (ob[2] - ob[0]) / 2. + RANGE, (ob[3] - ob[1]) / 2. + RANGE
nx, ny = int(hw / STEP), int(hh / STEP)
for rot in (0, 90):
for i in range(-nx, nx + 1):
x = cx + i * STEP
for jj in range(-ny, ny + 1):
y = cy + jj * STEP
bb = S.tbox(r, x, y, rot, a['fs'])
dist = box_dist(bb, ob)
if dist > RANGE or S.ov(bb, ob[:4]):
continue
if best is not None and dist >= best[0]:
continue
if S.outside(bb) or any(L == lay and S.ov(bb, b) for L, b in placed):
continue
if any(S.ov(bb, (q[2] - q[4] / 2. - 0.1, q[3] - q[5] / 2. - 0.1,
q[2] + q[4] / 2. + 0.1, q[3] + q[5] / 2. + 0.1)) for q in padsL.get(lay, [])):
continue
nb = sum(1 for b in others if S.ov(bb, b[:4]))
nv = sum(1 for v in d['vias'] if S.ov(bb, (v[0] - v[3] / 2., v[1] - v[3] / 2.,
v[0] + v[3] / 2., v[1] + v[3] / 2.)))
sc = dist + 3.0 * nb + 1.5 * nv
if best is None or sc < best[0]:
best = (sc, x, y, rot, nb, nv, dist)
if best:
placed.append((lay, S.tbox(r, best[1], best[2], best[3], a['fs'])))
out[r] = {'x': round(best[1], 4), 'y': round(best[2], 4), 'rot': best[3], 'id': a['id'], 'lay': lay,
'nb': best[4], 'nv': best[5], 'dist': round(best[6], 2)}
k = u'干净' if not best[4] and not best[5] else (u'压别的本体' if best[4] else u'压过孔')
stat[k] = stat.get(k, 0) + 1
else:
stat[u'无解'] = stat.get(u'无解', 0) + 1
print(u' [!] %s 无解,保留原位' % r)
return out, stat
def main():
d = S.snap()
pads = json.load(io.open(S.PADS, encoding='utf-8'))
sz = PL.sizes()
vis = [a for a in d['attrs'] if a['vis']]
before = [(a['ref'], a['lay'], S.tbox(a['ref'], a['x'], a['y'], a['rot'], a['fs'])) for a in vis]
print(u'改前体检: %s' % json.dumps(S.audit(before, pads, d['vias']), ensure_ascii=False))
p, stat = plan(d, pads, sz)
print(u'重排 %d 个: %s' % (len(p), json.dumps(stat, ensure_ascii=False)))
for r, q in sorted(p.items()):
if q.get('nb') or q.get('nv') or q.get('dist', 0) > 1.5:
print(u' %-4s (%.2f, %.2f) rot %d 离本体 %.2f 压本体 %d 压孔 %d'
% (r, q['x'], q['y'], q['rot'], q.get('dist', 0), q.get('nb', 0), q.get('nv', 0)))
after = [(a['ref'], a['lay'], S.tbox(a['ref'], p.get(a['ref'], a)['x'], p.get(a['ref'], a)['y'],
p.get(a['ref'], a)['rot'], a['fs'])) for a in vis]
print(u'改后体检: %s' % json.dumps(S.audit(after, pads, d['vias']), ensure_ascii=False))
io.open(S.PLAN, 'w', encoding='utf-8').write(json.dumps(p, ensure_ascii=False, indent=0))
if '--push' not in sys.argv:
print(u'(只算;加 --push 下发)')
return
rows = [[v['id'], v['x'] / S.MIL, v['y'] / S.MIL, v['rot']] for v in p.values()]
n = 0
for k in range(0, len(rows), 60):
r = S.run(S.PUSH % (S.B.PCB, json.dumps(rows[k:k + 60])), timeout=600)
n += r.get('result') or 0
S.run(u"await eda.pcb_Document.save(); return 1;", timeout=300)
print(u'modify 成功 %d / %d' % (n, len(rows)))
d2 = S.snap()
now = {a['ref']: a for a in d2['attrs']}
bad = [k for k, v in p.items()
if k not in now or abs(now[k]['x'] - v['x']) > 0.02 or abs(now[k]['y'] - v['y']) > 0.02
or int(round(now[k]['rot'])) % 360 != v['rot'] % 360]
print(u'读回核对:%d 个对上,%d 个没对上 %s' % (len(p) - len(bad), len(bad), bad[:10]))
if __name__ == '__main__':
main()