# -*- coding: utf-8 -*- u"""下板 PCB1:USB 差分对(U20 IO19/IO20 → J22)的**束走廊**在哪(纯本地,不碰桥接)。 /c/Python314/python .claude/pcb1_usb_corridor.py # 跑一遍 usb_pair() + 两层走廊图 /c/Python314/python .claude/pcb1_usb_corridor.py --a 104,25.18 --b 147,52 # 换束的两端锚点再试 `pcb1_route_sig.usb_pair()` 把 DP/DM 当**一整束**(2W+S = 0.74 mm)布一条中心线, 布不通就整对跳过。这里: 1. 原样调一次 `usb_pair()`,把它卡在哪打印出来; 2. 在 x 95~156 / y 15~58 开窗,按引擎判据算「0.74 的束能走的格」(顶层 / 底层各一张), 压成 0.5 mm 一格的 ASCII 图 —— 一眼看出哪条走廊是通的; 3. 束宽从 0.74 往下试(0.60 = 两根 0.20 + 对内 0.20),看走廊差多少。 ⚠️ 只读本地缓存,`eda.run` 被换成抛异常的桩。 """ import math import os import sys HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) import eda # noqa: E402 def _blocked(*a, **k): raise RuntimeError(u'pcb1_usb_corridor.py 是纯本地工具,禁止碰桥接') eda.run = _blocked eda.port = _blocked import numpy as np # noqa: E402 import pcb1_route_sig as R # noqa: E402 E = R.E L_TOP, L_BOT = R.L_TOP, R.L_BOT WIN = (95.0, 15.0, 156.0, 58.0) CELL = 0.5 # ASCII 图一格 def ascii_map(f, ok, vok, a, b, title): print(u'\n%s (每格 %.1f mm;. 束能走 v 还能打孔 # 不能;A/B 束两端)' % (title, CELL)) step = int(round(CELL / f.g)) xs = [f.x0 + i * f.g for i in range(0, f.nx, step)] print(u' ' + ''.join(('%-10d' % int(x)) if abs(x - round(x)) < 1e-6 and int(round(x)) % 5 == 0 else ' ' for x in xs)[:len(xs)]) for j in range(f.ny - 1, -1, -step): y = f.y0 + j * f.g row = [] for i in range(0, f.nx, step): x = f.x0 + i * f.g ch = '.' if ok[j, i] else '#' if ok[j, i] and vok[j, i]: ch = 'v' if abs(x - a[0]) < CELL / 2 and abs(y - a[1]) < CELL / 2: ch = 'A' if abs(x - b[0]) < CELL / 2 and abs(y - b[1]) < CELL / 2: ch = 'B' row.append(ch) print(u'%6.1f %s' % (y, ''.join(row))) def main(): argv = sys.argv[1:] a, b = R.USB_A, R.USB_B if '--a' in argv: a = tuple(float(v) for v in argv[argv.index('--a') + 1].split(',')) if '--b' in argv: b = tuple(float(v) for v in argv[argv.index('--b') + 1].split(',')) pads, vias, lines = R.board(False) pours = R.pour_rects() obstacles = R.obstacles_of(pads, vias, lines, pours) bytag = {p['tag']: p for p in pads} print(u'板上 %d 焊盘 / %d 孔 / %d 线;束锚点 A%s B%s,束宽 %.2f(W %.2f / S %.2f)' % ( len(pads), len(vias), len(lines), a, b, 2 * R.USB_W + R.USB_S, R.USB_W, R.USB_S)) R.USB_A, R.USB_B = a, b up = R.usb_pair(pads, obstacles, bytag) print(u'usb_pair() 结果:%s' % (u'布通 %d 条' % len(up) if up else u'**没布通**(原因见上一行)')) obs = [(s, n) for s, n in obstacles if n not in ('USB_DP', 'USB_DM')] f = E.Field(obs, WIN, skip_net='USB_BUNDLE', grid=0.1) vok = f.via_ok() for wb in (0.74, 0.60, 0.50): okt, okb = f.free(L_TOP, wb), f.free(L_BOT, wb) nt, nb = int(okt.sum()), int(okb.sum()) print(u'\n束宽 %.2f:顶层可走格 %d / 底层 %d(窗 %d x %d)' % (wb, nt, nb, f.nx, f.ny)) if wb == 0.74: ascii_map(f, okt, vok, a, b, u'顶层 L1,束宽 %.2f' % wb) ascii_map(f, okb, vok, a, b, u'底层 L2,束宽 %.2f' % wb) # 两层一起能不能从 A 走到 B src = {L_TOP: np.zeros((f.ny, f.nx), bool), L_BOT: np.zeros((f.ny, f.nx), bool)} tgt = {L_TOP: np.zeros((f.ny, f.nx), bool), L_BOT: np.zeros((f.ny, f.nx), bool)} ia, ja = f._idx(a[0], a[1]) ib, jb = f._idx(b[0], b[1]) for L in (L_TOP, L_BOT): src[L][max(0, ja - 3):ja + 4, max(0, ia - 3):ia + 4] = True tgt[L][max(0, jb - 3):jb + 4, max(0, ib - 3):ib + 4] = True cells = E.route_grid(f, src, tgt, wb) if cells is None: print(u' → A 到 B **不通**') else: pts = E._runs(cells) ln = sum(math.hypot((q[0] - p[0]) * f.g, (q[1] - p[1]) * f.g) for p, q in zip(pts, pts[1:])) nvia = sum(1 for p, q in zip(cells, cells[1:]) if p[2] != q[2]) print(u' → A 到 B 通:%.1f mm,换层 %d 次;折点:' % (ln, nvia)) print(u' ' + ' '.join('(%.1f,%.1f)L%d' % (f.x0 + i * f.g, f.y0 + j * f.g, lay) for i, j, lay in pts)) return 0 if __name__ == '__main__': sys.exit(main())