文件
phonerail/.claude/pcb2_strap_place.py

199 行
9.2 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- coding: utf-8 -*-
u"""给 R900~R909 就近找合法位置 —— 用布线器自己的障碍表搜,不靠肉眼看空地。
/c/Python314/python .claude/pcb2_strap_place.py # 搜 + 打印建议坐标
## 为什么要重摆
第一版把十颗全摆在 U32 北边 y=79 那一排(那里确实是唯一的整块空地),
结果 `pcb2_route_sig.py` **10 条只布通 3 条** —— 南边(pin 49/50/51)、西边(15/17)、
东南(47/48)那七个脚要绕过整颗 QFN 和一圈去耦,绕不出去。
⇒ 改成**每颗贴着自己的引脚放**。空地是按障碍表搜出来的:
以引脚为心一圈圈往外找,第一个「两个焊盘都离异网铜 >= 0.20 mm」的位置就用。
判据和 `pcb2_clear.py` / 扇出用的是同一套(`pcb2_route_dp.seg_rect` + `CLR`)。
"""
import io
import json
import math
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from eda import run
import pcb2_route_dp as D
import pcb2_keypads as K
CLR = D.CLR
PW, PH = 0.5664, 0.5410 # R0402 单个焊盘 mm(22.3 x 21.3 mil)
POFF = 0.433 # 焊盘中心离元件中心
BODY = 0.55 # 本体半长,用来判两颗新件之间
# 位号 -> U32 脚号
MAP = [('R906', '15'), ('R905', '17'), ('R904', '31'), ('R903', '33'), ('R909', '34'),
('R902', '47'), ('R908', '48'), ('R901', '49'), ('R907', '50'), ('R900', '51')]
def call(js, timeout=240):
r = run("await eda.dmt_EditorControl.openDocument('942ed1560d81c1e7');"
"await new Promise(r=>setTimeout(r,2000));" + js, timeout=timeout)
if not r.get('success'):
sys.exit(u'桥接失败: ' + json.dumps(r, ensure_ascii=False)[:600])
return r['result']
def u32_pins():
return call("""
let pid = null;
for (const c of await eda.pcb_PrimitiveComponent.getAll())
if (c.getState_Designator() === 'U32') pid = c.getState_PrimitiveId();
const M = 39.3700787401575, out = {};
for (const p of await eda.pcb_PrimitivePad.getAll()) {
if ((p.primitiveId || '').indexOf(pid) !== 0) continue;
out[String(p.getState_PadNumber ? p.getState_PadNumber() : p.number)] =
[Math.round(p.getState_X()/M*1000)/1000, Math.round(p.getState_Y()/M*1000)/1000];
}
return out;""")
def pad_rects(cx, cy, rot):
u"""返回 [pad1(GND), pad2(strap)]。rot 0/90/180/270 四个方向都要给 ——
⚠️ 第一版只搜 0 和 90,等于默认「GND 端朝西/朝南」,结果 strap 线要跨过自己的
GND 焊盘 + GND 短线 + GND 过孔才够得着 U32 的脚,十条里九条过不了净距校核。"""
r = rot % 360
if r == 0:
return [(cx - POFF, cy, PW / 2, PH / 2), (cx + POFF, cy, PW / 2, PH / 2)]
if r == 180:
return [(cx + POFF, cy, PW / 2, PH / 2), (cx - POFF, cy, PW / 2, PH / 2)]
if r == 90:
return [(cx, cy - POFF, PH / 2, PW / 2), (cx, cy + POFF, PH / 2, PW / 2)]
return [(cx, cy + POFF, PH / 2, PW / 2), (cx, cy - POFF, PH / 2, PW / 2)]
def bodies(pads, mine):
u"""⚠️ 障碍表只有焊盘/过孔/导线,**不知道元件本体**。第一版没这一条,
搜索结果里 R906/R909 直接落进了 U32 那颗 QFN 的本体底下(焊盘确实没撞,物理上装不了)。
这里用「同一个 ref 的所有焊盘的包围盒」当本体,QFN/SOIC 都够准。"""
bb = {}
for q in pads:
if q['ref'] in mine or not q['ref']:
continue
b = bb.setdefault(q['ref'], [1e9, 1e9, -1e9, -1e9])
b[0] = min(b[0], q['x'] - q['hw']); b[1] = min(b[1], q['y'] - q['hh'])
b[2] = max(b[2], q['x'] + q['hw']); b[3] = max(b[3], q['y'] + q['hh'])
return [(r, b) for r, b in bb.items()]
def clear(cx, cy, rot, pads, vias, lines, mine, taken, bods):
u"""两个焊盘离**所有**异网铜 >= CLR(GND 也算,0402 本体不能骑在 GND 盘上),
而且不许落进任何已有元件的本体投影。"""
for px, py, hw, hh in pad_rects(cx, cy, rot):
for ref, b in bods:
if b[0] - CLR < px + hw and px - hw < b[2] + CLR and b[1] - CLR < py + hh and py - hh < b[3] + CLR:
return False, u'%s 本体' % ref
for px, py, hw, hh in pad_rects(cx, cy, rot):
for q in pads:
if q['ref'] in mine:
continue
if D.seg_rect((px, py), (px, py), q['x'], q['y'], q['hw'], q['hh']) < hw + hh + CLR:
return False, u'焊盘 %s.%s(%s)' % (q['ref'], q['num'], q['net'])
for v in vias:
if math.hypot(v['x'] - px, v['y'] - py) < max(hw, hh) + v['r'] + CLR:
return False, u'过孔(%s)' % v['net']
for l in lines:
if l['lay'] not in (2, 12):
continue
d = D.seg_pt(l['x0'], l['y0'], l['x1'], l['y1'], px, py)
if d < max(hw, hh) + l['w'] / 2 + CLR:
return False, u'导线(%s)' % l['net']
# ⚠️ 新件之间也要按**真实焊盘矩形**判,不能用「中心距 > 本体」这种糙判据 ——
# 第一版用 ±1.3 mm 的方框,push 之后 pcb2_clear 立刻抓出三对 0.03~0.15 mm。
for tx, ty, trot in taken:
for ax, ay, ahw, ahh in pad_rects(cx, cy, rot):
for bx, by, bhw, bhh in pad_rects(tx, ty, trot):
dx = abs(ax - bx) - (ahw + bhw)
dy = abs(ay - by) - (ahh + bhh)
d = math.hypot(max(dx, 0.0), max(dy, 0.0)) if (dx > 0 or dy > 0) else max(dx, dy)
if d < CLR:
return False, u'挨着另一颗新件 (%.3f)' % d
return True, ''
def gnd_via_ok(cx, cy, rot, pads, vias, lines, mine, placed):
u"""⚠️ 摆得下 ≠ 接得出去:地端还得能落一个合法过孔到 Inner2。
第一版没这一条,R903/R906 摆完之后地过孔被 NONREM0 / USB5_DM 围死,
`pcb2_strap_gnd.py` 直接报「找不到扇出位置」。现在把它并进摆位判据。
返回 (可行, 过孔坐标)。"""
gx, gy = pad_rects(cx, cy, rot)[0][:2] # pad1 = GND 端
for dist in [d / 10.0 for d in range(9, 31)]:
for a in range(0, 360, 15):
th = math.radians(a)
vx, vy = round(gx + math.cos(th) * dist, 3), round(gy + math.sin(th) * dist, 3)
if any(math.hypot(vx - ax, vy - ay) < 0.33 + K.VIA_H for ax, ay in placed):
continue
ok, _ = K._clear(vx, vy, K.VIA_D / 2, 12, pads, vias, lines, None)
if not ok:
continue
ok, _ = K._seg_clear((gx, gy), (vx, vy), K.W_STUB, pads, vias, lines, None)
if ok:
return True, (vx, vy)
return False, None
def main():
if '--nodump' not in sys.argv:
D.dump()
pads, vias, lines = D.geom()
pins = u32_pins()
mine = {d for d, _ in MAP}
bods = bodies(pads, mine)
taken, out, gvias = [], {}, []
for des, pin in MAP:
px, py = pins[pin]
best, why = None, []
for r in [x / 10.0 for x in range(12, 81, 2)]:
for a in range(0, 360, 10):
th = math.radians(a)
cx, cy = round(px + r * math.cos(th), 3), round(py + r * math.sin(th), 3)
for rot in (0, 90, 180, 270):
# ⭐ strap 端(pad2)必须比 GND 端(pad1)更靠近 U32 的脚,
# 否则那条 strap 线只能绕过自己的 GND 焊盘/短线/过孔 —— 走不通。
g, t = pad_rects(cx, cy, rot)
if math.hypot(t[0] - px, t[1] - py) >= math.hypot(g[0] - px, g[1] - py):
continue
ok, msg = clear(cx, cy, rot, pads, vias, lines, mine, taken, bods)
if ok:
vok, vxy = gnd_via_ok(cx, cy, rot, pads, vias, lines, mine, gvias)
if not vok:
if len(why) < 6:
why.append(u'(%.2f,%.2f)r%d 地过孔出不去' % (cx, cy, rot))
continue
best = (cx, cy, rot, r, vxy)
gvias.append(vxy)
break
if len(why) < 6:
why.append(u'(%.2f,%.2f)r%d 撞 %s' % (cx, cy, rot, msg))
if best:
break
if best:
break
if not best:
print(u'!! %s (U32.%s) 找不到位置' % (des, pin))
for w in why:
print(u' ' + w)
sys.exit(1)
out[des] = best
taken.append((best[0], best[1], best[2]))
print(u' %-5s U32.%-3s 脚(%7.3f,%6.3f) -> (%7.3f,%6.3f) rot%-3d 距 %.1f mm 地过孔(%7.3f,%6.3f)'
% (des, pin, px, py, best[0], best[1], best[2], best[3], best[4][0], best[4][1]))
io.open(os.path.join(HERE, '_pcb2_strap_place.json'), 'w', encoding='utf-8').write(
json.dumps(out, ensure_ascii=False, indent=1))
print(u'\n粘到 pcb2_place.GLOBAL 里:')
for des, pin in MAP:
cx, cy, rot, r = out[des][:4]
print(u" '%s': (%.3f, %.3f, %d), # U32.%s" % (des, cx, cy, rot, pin))
main()