# -*- coding: utf-8 -*- u"""六对**长差分**(USB1~5 的 U32↔U5n + UPSTREAM 的 U32↔J23)+ Hub 西缘被 D± 夹住的两条 +3V3 车道。 **v5 版(2026-09-06,载板全部底层、接口边朝板边)。** v3 版存档在 `obsolete/pcb2_dp_long_v3_2026-09-06.py`。 被 `pcb2_route_dp.py` import;校核 / 下发都用那个脚本,这里只放坐标。 ## 出发点:U32 西缘的 12 条车道 U32(QFN-64,**底层**,中心 (100, 68))的 D± 全在**西缘 x 95.55**,0.5 脚距、南→北: 1DM 64.25 | 1DP 64.75 | 2DM 65.25 | 2DP 65.75 | pin5 +3V3 66.25 | 3DM 66.75 | 3DP 67.25 | 4DM 67.75 | 4DP 68.25 | pin10 +3V3 68.75 | 5DM 69.25 | 5DP 69.75 | pin13 GND 70.25 | 14~16 NC 目的地却是四面八方:1→西北远处 (U51 22/24, 87)、2→正北 (U52 94/96, 87)、3→东北 (U53 165/167, 87)、 4→西南 (U54 88.6/90.5, 48)、5→东南 (U55 160/162, 48)、UP→正东 (J23 212, 61~62)。 ### 定下来的走法(理由都在代码注释里) | 车道 | 层 | 走法 | |---|---|---| | pair 1 | 底 | 直着西行 y 64.25/64.75 到 x 22.34/22.82,北上进 U51 | | pair 2 | 底 | 西行到 89.27/89.75 北上,y 80 处 45° 斜到 93.84/95.74 进 U52 | | pin 5 | 底→顶 | 西行到 90.4,跟着 pair 3 北上到 70.5 打孔翻顶层,西去 (88.2, 70.5) 再下来进 C747 | | pair 3 | 底 | 西行到 91.05/91.53 北上,y 76.50/76.98 东行,过了 pair 5 之后压到 75.40/75.88 从 MH 焊盘下面钻过去,x 165 北上 | | pair 4 | **顶** | 西行到 x 92.2 打孔上顶层(DM 67.95 / DP 68.85),沿 x 91.5/91.98 南下到 y 52,斜到 (89.6/90.5, 50.3) 下来进 U54 | | pin 10 | 底 | 西行到 92.95,跟着 pair 5 北上,斜到 (92.5, 70.0) 进 C748;(92.5, 70.0) 上有一个孔接顶层 +3V3 干线 | | pair 5 | 底 | 西行到 93.47/93.95 北上,y 75.50/75.98 东行到 110.30/110.78 南下,**y 59.2~61.6 翻顶层跨过 UP**,y 52.5/52.98 东行,x 160.1/162 南下进 U55 | | UP | 底 | 南缘 pin58/59 从 C743/C745 之间的竖井下到 60.12/60.60,一路东行,x 205 抬到 61.25/61.75 进 J23 B6/A7 | | +3V3 干线 | 顶 | U31 pin2 旁 (90.3, 61.65) 打孔上顶层,北上到 (90.4, 70.5)(与 pin5 的孔汇合),东去 (92.5, 70.0) 下来进 C748 | ### 三条关键约束 1. **0.5 脚距的车道之间打不了孔**:孔要离两边车道各 0.645,所以只有当邻居车道都拐走之后才能打。 北边的先拐(pair 5 → pin 10 → pair 4 打孔 → pair 3 → pin 5 → pair 2),像剥洋葱。 pair 4 的两个孔卡在 DP3 的横车道(y 67.25,要求孔心 ≥ 67.895)和 pin10 北上车道(x 92.95,要求孔心 x ≤ 92.305)之间, 所以 DP4 得先在 x 93.1→92.5 抬到 68.85 再打孔,两孔上下错 0.9。 2. **顶层只走 Inner1 是 GND 的地方**:Hub 周围 x ≥ 87(VO4 岛东界已从 89 收到 87)、y < 75.5(VO2 的 Hub 北横带以南)是一整片 GND; pair 4 全程、pair 5 跨 UP 的那 2.4 mm、+3V3 干线都在这片里。pair 5 的跨越点 x 110~111.5 也在 VO4/VO5 之间的 GND 窗口里。 3. **平行 45° 斜段要保持 0.48 间距**:两根线各自的拐点不能同 x/同 y,第一拐**内侧**那根要早拐 0.2 (0.48·(√2−1)),否则斜段上的垂直间距只剩 0.34。文件里所有成对斜段都按这个写。 ### 每个站点最后一段 U5n 朝 Hub 那排的 DP/DM 相距 1.9 mm,车道并排 0.48 到最后 1.4 mm 再用 45° 分开, 分开的那根一律是**转弯时的外侧**,所以到站不用交叉 —— 五对都核过了 (转弯规则:向右拐时左边那根变成外侧,连拐两次回到原样)。 """ import math import sys as _s, os as _o; _s.path.insert(0, _o.path.dirname(_o.path.abspath(__file__))) KSEP = chr(0) # 路径 key 的分隔符,与 pcb2_route_dp.KSEP 同值(不能 import:route_dp --dump 会反过来 import 本模块,循环) L_TOP, L_BOT = 1, 2 W = 0.28 def chamfer(pts, r=0.35): u"""把折线的拐点切成 45°。换层点(相邻两点层不同)不切。 ⚠ 点的 layer/width 语义是「从这一点到下一点」,所以在拐点 b 处插入的 b1 用**前一段**的层/宽(它承担斜切段),b2 用 b 自己的。 """ if len(pts) < 3: return list(pts) out = [pts[0]] for i in range(1, len(pts) - 1): a, b, c = pts[i - 1], pts[i], pts[i + 1] if a[2] != b[2] or b[2] != c[2]: # 拐点两侧不同层 -> 这是过孔点 out.append(b) continue ax, ay = b[0] - a[0], b[1] - a[1] cx, cy = c[0] - b[0], c[1] - b[1] la, lc = math.hypot(ax, ay), math.hypot(cx, cy) if la < 1e-9 or lc < 1e-9: out.append(b) continue # 共线就不用切;**已经是 45° 的拐(正 + 斜)也不切** —— 再切一刀会变成两个 22.5°, # 而且切出来的那一小段会向内侧挪 0.1 mm,实测把 DP4 的抬升段切进了 pin10 车道的净距圈(0.125)。 dot = max(-1.0, min(1.0, (ax * cx + ay * cy) / (la * lc))) if math.degrees(math.acos(dot)) <= 46.0: out.append(b) continue d = min(r, la / 2.0, lc / 2.0) out.append((round(b[0] - ax / la * d, 4), round(b[1] - ay / la * d, 4), a[2], a[3])) out.append((round(b[0] + cx / lc * d, 4), round(b[1] + cy / lc * d, 4), b[2], b[3])) out.append(pts[-1]) return out def P(*pts): u"""(x, y[, layer[, width]]) -> 规范化四元组;层/宽省略时沿用上一点。""" out, lay, w = [], L_BOT, W for t in pts: x, y = t[0], t[1] lay = t[2] if len(t) > 2 else lay w = t[3] if len(t) > 3 else w out.append((round(x, 4), round(y, 4), lay, w)) return out PAD_X = 95.55 # U32 西缘焊盘中心(焊盘 x 95.125~95.975,hh 0.15) # 0.28 的线直着从 0.5 脚距的焊盘出来,两边净距 0.21,不用让位。 # ══════════ pair 1 → U51(西北远处)══════════ # 直着西行:y 64.25/64.75 从 x 95.55 到 x 22~23 一路无障碍(U31 pin1 顶 63.35、J31 底排焊盘底 65.38、 # R721/R722 顶 61.77、U34 顶 ~62)。西行时 DM 在南 = 左侧,右拐北上后左侧变西 ⇒ DM 走西车道 22.34(正对 U51.6), # DP 在 22.82,到 y 84.3 再 45° 分到 24.24 进 U51.4。 SITE1 = { 'USB1_DM': [(PAD_X, 64.25), (22.34, 64.25), (22.34, 86.475)], 'USB1_DP': [(PAD_X, 64.75), (22.82, 64.75), (22.82, 84.30), (24.24, 85.72), (24.24, 86.475)], } # ══════════ pair 2 → U52(正北)══════════ # 西行到 x 89.27/89.75 右拐北上(DM 西)。北上车道左边是 J31 最东一列焊盘(x ≤ 86.98),右边是 pin5 的北上车道 90.4。 # 一直到 y 80 才 45° 斜向东北 —— 必须过了 pair 3 / pair 5 东行车道(y ≤ 76.97)之后才能往东挪, # 而且要绕开 site2 的定位孔 H1 (89.39, 85.36)。斜段直接斜到各自焊盘正下方:DM 到 93.84、DP 一口气到 95.74。 # 内侧(DP,东)早拐 0.2 保持斜段 0.48 间距。 SITE2 = { 'USB2_DM': [(PAD_X, 65.25), (89.27, 65.25), (89.27, 80.00), (93.84, 84.57), (93.84, 86.475)], 'USB2_DP': [(PAD_X, 65.75), (89.75, 65.75), (89.75, 79.80), (95.74, 85.79), (95.74, 86.475)], } # ══════════ pair 3 → U53(东北)══════════ # 西行到 91.05/91.53 右拐北上(DM 西),再右拐东行(西车道变北车道 ⇒ DM 76.98 / DP 76.50), # 这一段在 pair 5 东行车道(75.50/75.98)的北边;pair 5 在 x 110.78 南下之后,pair 3 在 x 112 压到 75.40/75.88 —— # 因为 MH (148.1, 79.5) 的 Ø6 焊盘底在 76.5,北车道上沿必须 ≤ 76.16。 # 到 x 165 左拐北上:北车道(DM)是内侧变西 ⇒ DM 165.34 正对 U53.6,DP 在 165.82 到 y 84.6 再分到 167.24。 # 下压的斜段:第一拐向右(东→东南),内侧是南车道 DP,早拐 0.2。 SITE3 = { 'USB3_DM': [(PAD_X, 66.75), (91.05, 66.75), (91.05, 76.98), (112.00, 76.98), (113.10, 75.88), (165.34, 75.88), (165.34, 86.475)], 'USB3_DP': [(PAD_X, 67.25), (91.53, 67.25), (91.53, 76.50), (111.80, 76.50), (112.90, 75.40), (165.82, 75.40), (165.82, 84.60), (167.24, 86.02), (167.24, 86.475)], } # ══════════ pair 4 → U54(西南)—— 全程顶层 ══════════ # 它北边是 pin10(要跟 pair 5 北上)、南边是 pair 3(北上)、pair 2、pair 1 全都横在西边 —— 底层无路, # 一出焊盘就打孔上顶层。两个孔:DM4 (92.2, 67.95)(离 DP3 横车道 67.39 有 0.255)、DP4 (92.2, 68.85)(上下错 0.9), # DP4 先在 x 93.1→92.5 斜抬到 68.85 再西行到孔,避开 DM4 的孔(孔顶 68.255 vs 车道底 68.11 才 0.055)。 # 顶层:DP 西车道 91.5 / DM 91.98 南下到 y 52,45° 斜到 (89.6, 50.3) / (90.5, 50.3) 打孔回底层, # 进 U54.4 (88.6, 48.149) / U54.6 (90.5, 48.149)。DM 的孔正对焊盘、直下;DP 的孔再斜 1 mm 到 88.6。 # 顶层这一段底下 Inner1:x ≥ 87 全是 GND(VO4 岛东界 87、site4 岛 x ≤ 84.75、VO2 从 y 75.5 起)。 SITE4 = { 'USB4_DM': [(PAD_X, 67.75), (92.60, 67.75), (92.40, 67.95), (92.20, 67.95), (92.20, 67.95, L_TOP), (91.98, 67.73), (91.98, 52.00), (90.50, 50.52), (90.50, 50.30), (90.50, 50.30, L_BOT), (90.50, 48.685)], 'USB4_DP': [(PAD_X, 68.25), (93.10, 68.25), (92.50, 68.85), (92.20, 68.85), (92.20, 68.85, L_TOP), (91.50, 68.85), (91.50, 52.50), (89.60, 50.60), (89.60, 50.30), (89.60, 50.30, L_BOT), (88.60, 49.30), (88.60, 48.685)], } # ══════════ pair 5 → U55(东南)—— 绕 Hub 北边走 ══════════ # 最北的一对,出来就右拐北上(DM 西 93.47 / DP 东 93.95;DP 的东沿离 C751 焊盘 94.295 只剩 0.205, # 再东一点 pin13 的 GND 就打不了孔了 —— 给它留的孔位是 (94.6, 71.4))。 # 北上到 75.50/75.98(C751 顶 74.974,DP 拐角的 45° 切角离它 0.24)右拐东行(DM 北),到 x 110.30/110.78 右拐南下(南车道 DP 是内侧变西 ⇒ DP 110.30)。 # 南下时 x 110.3 离 R711/R716/R720 的东沿 109.62 有 0.54、离 J64 焊盘 107.43 有 2.7。 # **y 59.2~61.6 翻到顶层跨过 UP 的两根横线**(60.12/60.60):DP 的孔在 110.30,DM 的孔错到 111.3(两孔 1.0)。 # 到 y 52.5/52.98 左拐东行(东车道 DM 是内侧变北 ⇒ DM 52.98),DP 在 x 160.10 南下进 U55.4,DM 到 162.00 进 U55.6。 SITE5 = { 'USB5_DM': [(PAD_X, 69.25), (93.47, 69.25), (93.47, 75.98), (110.78, 75.98), (110.78, 62.60), (111.30, 62.08), (111.30, 61.60), (111.30, 61.60, L_TOP), (111.30, 59.20), (111.30, 59.20, L_BOT), (111.30, 58.72), (110.78, 58.20), (110.78, 52.98), (162.00, 52.98), (162.00, 48.685)], 'USB5_DP': [(PAD_X, 69.75), (93.95, 69.75), (93.95, 75.50), (110.30, 75.50), (110.30, 61.60), (110.30, 61.60, L_TOP), (110.30, 59.20), (110.30, 59.20, L_BOT), (110.30, 52.50), (160.10, 52.50), (160.10, 48.685)], } # ══════════ UPSTREAM → J23(U32 南缘 → 板子正东)══════════ # pin59 UP_DP (98.75, 63.55) / pin58 UP_DM (99.25, 63.55) 直着南下,从 C743(x ≤ 97.62)与 C745(x ≥ 99.78)之间的 # 竖井下到 y 60.12/60.60(那排 0402 的焊盘底 60.98),一路东行到 x 205 —— 中间 y 60~61 从 x 99 到 208 全空。 # J23(底层 rot 270):B6 UP_DP 61.25 / A7 UP_DM 61.75 / A6 UP_DP 62.25 / B7 UP_DM 62.75,B5 CC2 在 60.25、A8 空脚 60.75。 # 车道压在 60.17 会把 B5 的 CC2 走线堵死(R718 在 59.5),所以 x 205 就抬到 61.25/61.75 平着进 B6/A7。 # 抬升是左拐再右拐,第一拐内侧是北车道 DM ⇒ DM 早拐 0.2。 UP = { 'UP_DP': [(98.75, 63.55), (98.75, 60.12), (205.00, 60.12), (206.13, 61.25), (212.392, 61.25)], 'UP_DM': [(99.25, 63.55), (99.25, 60.60), (204.80, 60.60), (205.95, 61.75), (212.392, 61.75)], } # ══════════ J23 的 A6↔B6 / A7↔B7 短接(USB-C 正反插的一对重复脚)══════════ # 几何照抄 v3(那一版过了 DRC),只是 J23 翻到底层之后网络对调:61.25↔62.25 这一对现在是 UP_DP(B6/A6), # 61.75↔62.75 是 UP_DM(A7/B7);出线在底层,跨越段翻到**顶层**。 # ⚠ 表贴焊盘只到 x=213.04,过孔要离它 0.505 ⇒ 最西只能放到 213.55;出线前 0.65 mm 必须平着走(阵列上方起坡会蹭隔壁脚)。 # 另一根的 61.75 横线离 (213.85, 62.25) 那个孔只有 0.165 —— 这是同一对的两根(UP_DP vs UP_DM),按对内 0.15 判。 J23_TIE = { 'UP_DP' + KSEP + '2': [(212.392, 62.25, L_BOT, W), (213.85, 62.25), (213.85, 62.25, L_TOP), (213.85, 60.70), (213.85, 60.70, L_BOT), (213.50, 61.25), (213.20, 61.25), (212.392, 61.25)], 'UP_DM' + KSEP + '2': [(212.392, 61.75, L_BOT, W), (213.20, 61.75), (213.50, 61.75), (214.25, 61.30), (214.75, 61.30), (214.75, 61.30, L_TOP), (214.75, 63.20), (214.75, 63.20, L_BOT), (214.25, 63.20), (213.50, 62.75), (213.20, 62.75), (212.392, 62.75)], } # ══════════ Hub 西缘 pin5 / pin10 的 +3V3(被 D± 夹住,只能在这里一起画)══════════ # 后面的信号布线器(pcb2_route_sig.py)把这些线当**已有障碍**,别删。 # pin5 (66.25) 夹在 DP2 (65.75) 与 DM3 (66.75) 之间:西行到 90.4(pair 3 拐走之后)跟着北上,到 (90.4, 70.5) 打孔翻顶层 # —— 孔离 DP2 车道 89.75 / DM3 车道 91.05 各 0.65。顶层西去 (88.2, 70.5) 再下来进 C747(底层 rot 90,x 88.2, # 在 J31 最东列 86.98 与 DM2 车道 89.27 之间)。(88.2, 70.5) 的孔离 VO2 岛东界 87.3 有 0.9,反焊盘不碰岛。 # pin10 (68.75) 夹在 DP4 (68.25) 与 DM5 (69.25) 之间:西行到 92.95 跟着 pair 5 北上(离 DM5 车道 0.24、离 pair 4 的孔 0.305), # y 69.55 起 45° 斜到 (92.5, 70.0),直进 C748(底层 rot 90,x 92.5,夹在 DP3 车道 91.53 与 DM5 车道 93.47 中间)。 # +3V3 干线:U31 pin2(+3V3 输出,(90.47, 60.5) 焊盘 y ≤ 61.05)旁 (90.3, 61.65) 打孔上顶层,沿 x 90.3 北上 # (pair 4 的顶层车道在 91.5 以东),到 (90.4, 70.5) 与 pin5 的孔汇合,再东去 (92.5, 70.0) 打孔下来进 C748。 # 顶层这三段底下 Inner1 都是 GND(x 87.3~100.5、y 65.15~75.5 之间没有任何岛)。 # 电容的哪个焊盘是 +3V3 由 build() 从障碍表里查,不写死。 V_P5, V_P5W = (90.40, 70.50), (88.20, 70.50) V_P10 = (92.50, 70.00) V_TRUNK = (90.30, 61.65) def _pad(pads, ref, net): for p in pads: if p['ref'] == ref and p['net'] == net: return (p['x'], p['y']) raise SystemExit(u'障碍表里找不到 %s 的 %s 焊盘(先 --dump)' % (ref, net)) def power_lanes(pads): c747 = _pad(pads, 'C747', '+3V3_HUB') c748 = _pad(pads, 'C748', '+3V3_HUB') for p in pads: # 干线起点必须真的落在 U31 pin2(+3V3 输出)里 if p['ref'] == 'U31' and p['num'] == '2': assert abs(p['x'] - 90.47) < 0.05 and abs(p['y'] - 60.5) < 0.05, u'U31 pin2 挪了,改 V_TRUNK' return { '+3V3_HUB' + KSEP + 'p5': [(PAD_X, 66.25), (90.40, 66.25), (V_P5[0], V_P5[1]), (V_P5[0], V_P5[1], L_TOP), (V_P5W[0], V_P5W[1]), (V_P5W[0], V_P5W[1], L_BOT), (c747[0], c747[1])], '+3V3_HUB' + KSEP + 'p10': [(PAD_X, 68.75), (92.95, 68.75), (92.95, 69.55), (V_P10[0], V_P10[1]), (c748[0], c748[1])], '+3V3_HUB' + KSEP + 'trunk': [(90.30, 61.00), (V_TRUNK[0], V_TRUNK[1]), (V_TRUNK[0], V_TRUNK[1], L_TOP), (90.30, 70.40), (V_P5[0], V_P5[1]), (V_P10[0], V_P10[1]), (V_P10[0], V_P10[1], L_BOT), (c748[0], c748[1])], } def build(pads=None): out = {} for grp in (SITE1, SITE2, SITE3, SITE4, SITE5, UP, J23_TIE): for net, pts in grp.items(): out[net] = chamfer(P(*pts)) if pads is not None: for net, pts in power_lanes(pads).items(): out[net] = chamfer(P(*pts)) return out