# -*- coding: utf-8 -*- u"""六对**长差分**的路径(USB1~5 的 U32↔U5n + UPSTREAM 的 U32↔J23)。 被 `pcb2_route_dp.py` import;校核 / 下发都用那个脚本,这里只放坐标。 ## 拓扑(2026-09-06 第三轮定,理由见设计文档 §25.13) U32 是 **QFN-64、贴在底层**,十根下游 D± 全部从**西边缘**出来(0.5 mm 脚距, 南→北 = port1..5)。目的地却是四面八方: | 对 | 去哪 | 走法 | |---|---|---| | 1 | site1 (61.5, 76.4) 西北 | 底层西行 → x=98.3/98.8 爬升 → 西行车道 74.70/75.55 | | 2 | site2 (133, 76.4) 东北 | 底层西行 → 爬升 → **北高速「高窗」** 78.29/78.77 → 下降 | | 3 | site3 (204.5, 76.4) 东北 | 底层西行 → 爬升 → **北高速「低窗」** 76.20/76.68 | | 4 | site4 (49.4, 58.8) 西南 | **换顶层**南下(唯一要穿过其它对的一对)→ 底层西行 60.5/61.0 | | 5 | site5 (120.9, 58.8) 东南 | 底层爬升 → **顶层**过隘口 75.69/76.12 → 底层南下 | | UP | J23 (212.4, 61~63) 正东 | U32 南边「竖井」下到 y≈57 → 东行 → 北抬到 60.5/61.2 | ### 为什么这么绕:x 112~116 那道隘口 C751(10 µF 1206,底层,(113.0,74.0))+ C815 的 GND 扇出孔 (113.047,77.5) 把北高速掐成两段。底层在 x=113 只剩两个窗口: * **低窗** y 76.045~76.855(线心 76.185~76.72,**只塞得下一对**) * **高窗** y 78.145~81.66(宽,但东边 (117.067,78.392) 那个孔又把 x=117 的天花板压到 77.75, 所以出了隘口必须马上降下来) 顶层同一段是通的(C751 在底层),y 75.44~76.3 还有一条**顶层窗口**—— 而且 x 80~116 / y 67.6~84 底下正好是 Inner1 的 `VO2_P` 整岛, **一整块实心参考面、不跨分割**(§10.1 的约束按「一块铜」判,不是按「必须是地」)。 所以第三对(pair 5)从这里过,两端的过孔都落在岛内(x ≤ 115.8)。 ### 排序规则(写路径时逐条对照,避免同层交叉) 一束平行线拐弯时**外侧的先拐**:西行右转向北 → 最东的先拐; 北行右转向东 → 最西的在最高的车道。由此从 U32 西边缘(南→北 = 1,2,3,4,5) 出发,爬升后 x 由西到东是 1,2,3,5,转东后车道由北到南是 **2,3,5** —— 正好满足「pair2 要在 x=133 北拐、pair3 继续东行、pair5 在 x=121 南下」。 ### 每个站点最后 2 mm 都是同一个花样 U5n 的 DP/DM 焊盘在**顶层**、相距 1.9 mm,而车道上的南北次序和焊盘的东西次序 永远差一次交叉。解法统一:**外侧那根先打过孔上顶层**,从另一根的**底层**车道 上方跨过去,另一根再在自己焊盘正下方上来。两根 1.9 mm 分开,过孔互不干涉。 """ import math L_TOP, L_BOT = 1, 2 W = 0.28 def chamfer(pts, r=0.35): u"""把折线的拐点切成 45°。换层点(相邻两点层不同)不切。 ⚠ 点的 layer/width 语义是「从这一点到下一点」,所以在拐点 b 处插入的 b1 用**前一段**的层/宽(它承担斜切段),b2 用 b 自己的。 ⚠ r 别调大:0.6 时 `USB1_DM` 那个让位拐角会被削进 C747 的 GND 扇出孔 (103.28, 62.868) 的净距圈里(0.178 < 0.20)。0.35 才留得住 0.209。 """ 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 # 共线就不用切 if abs(ax / la - cx / lc) < 1e-9 and abs(ay / la - cy / lc) < 1e-9: 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 # ══════════ U32 西边缘的逃逸 ══════════ # 焊盘 x=105.55,西端 105.125;脚距 0.5 mm,0.28 线正好留 0.22 净距(>0.20)。 # 让位在 x 104.9→104.4 这一小段做完(x ≤ 104.785 之后就离所有焊盘 ≥0.34)。 # # ⚠ `USB1_DM` 的原生 y=63.25 会撞上 C747 的 GND 扇出孔 (103.28, 62.868) # —— 净距只有 0.382,要 0.645。抬到 63.55 之后是 0.682。 # 抬了它,上面四根就得跟着让(对内 0.43 / 对间 0.48),一直让到 pair3。 # ⚠ 同一对的两根让位量必须**相等**(斜段平行),否则 0.50 的行距按垂直方向 # 折算会掉到 0.43 以下。pair1/2 统一 +0.30、pair3 统一 −0.20。 # ⚠ `USB5_DP` 的原生 68.75 离 U32 pin13 的 GND 扇出孔 (104.599, 69.25) 只有 0.50, # 要 0.645 ⇒ 压到 68.60;DM5 跟着到 68.15。这一压正好给 pair4 的过孔留出 # [DP3+0.645, DM5−0.645] = [66.695, 67.505] 这 0.81 mm 的窗口(两个孔要 0.76)。 ESC = { # net: (焊盘 y, 西行车道 y, 爬升 x) 'USB1_DM': (63.25, 63.55, 98.30), 'USB1_DP': (63.75, 64.05, 98.80), 'USB2_DM': (64.25, 64.55, 99.30), 'USB2_DP': (64.75, 65.05, 99.80), 'USB3_DM': (65.75, 65.55, 100.30), 'USB3_DP': (66.25, 66.05, 100.80), 'USB5_DM': (68.25, 68.05, 101.30), 'USB5_DP': (68.75, 68.55, 101.80), } PAD_X = 105.55 # ⚠ 让位的斜段**不能太早起步**:x=105.19 处斜上去时离隔壁焊盘只剩 0.183(要 0.20)。 # 所以先直着走到 105.00,再走到拐点 104.60 —— 这样 45° 切角被 |ab|/2 限成 0.20, # 斜段从 x=104.80 才开始(离隔壁焊盘 0.242)。 # ⚠ 让位段还**不能太陡**:相邻两根各让 0.28 / 0.26,斜段近似平行, # 0.50 的行距按垂直方向折算成 0.50·Δx/|d|。Δx=0.5 时只剩 0.436(净距 0.156,要 0.20), # Δx=1.2 时是 0.487(净距 0.207)。所以让位横跨 104.60 → 103.40。 JOG_X0, JOG_X1, JOG_X2 = 105.00, 104.60, 103.40 def esc(net): u"""焊盘 → 让位 → 西行到爬升点。 ⚠ pair5 是特例,三条约束把它挤成一条 **0.18 mm 的窄段**: · `USB5_DP` 出焊盘后要压到 (104.599, 69.25) 那个 GND 扇出孔的净距圈外; · 但它又不能压太低 —— 隔壁 `USB5_DM` 的焊盘上沿在 68.40,对内净距要 0.15; · `USB5_DM` 自己也不能往下让 —— 下面 0.5 mm 就是 U32 pin10 的焊盘。 0.28 线宽下这三条无解;收到 **0.18** 之后 DP 走 68.62、DM 保持 68.25 (净距 0.19),过了焊盘区(x ≤ 103.4)再放回 0.28 并拉开到 0.50。 """ pad_y, lane, cx = ESC[net] if net == 'USB5_DP': return [(PAD_X, 68.75, L_BOT, 0.18), (105.13, 68.75), (104.70, 68.62), (103.40, 68.62), (102.80, lane, L_BOT, W), (cx, lane)] if net == 'USB5_DM': return [(PAD_X, 68.25, L_BOT, 0.18), (103.40, 68.25), (102.80, lane, L_BOT, W), (cx, lane)] return [(PAD_X, pad_y), (JOG_X0, pad_y), (JOG_X1, pad_y), (JOG_X2, lane), (cx, lane)] # ══════════ pair 1 → site1(西北)══════════ # 西行车道 74.70(DM1, 南) / 75.55(DP1, 北)。 # U51 焊盘:pin4 DP x=61.55(西)、pin6 DM x=63.45(东),y=76.351,从南边进。 # 西行时先到的是**东**边的 DM1,它必须先北拐 —— 而 DP1 的车道在它北边, # 同层必交叉 ⇒ **DM1 在 (63.45, 74.70) 打孔上顶层**,从 DP1 的底层车道上方跨过去。 SITE1 = { 'USB1_DM': esc('USB1_DM') + [(98.30, 74.70), (63.45, 74.70), (63.45, 74.70, L_TOP), (63.45, 76.351)], 'USB1_DP': esc('USB1_DP') + [(98.80, 75.55), (61.55, 75.55), (61.55, 75.55, L_TOP), (61.55, 76.351)], } def build(): out = {} for grp in (SITE1, SITE2, SITE3, SITE4, SITE5, UP, J23_TIE): for net, pts in grp.items(): out[net] = chamfer(P(*pts)) return out # ══════════ pair 3 → site3(东北,走「低窗」)══════════ # 隘口 x 112~114 的底层低窗:线心只能落在 76.185~76.72 —— 只塞得下这一对。 # 取 76.20 / 76.68(间距 0.48,斜段上垂直净距 0.45 > 0.43)。 # 出了隘口先在 x 120→126 降到 73.90/74.38(要给 pair2 让出北边的车道, # 它得从北高速一路降到 75.05/75.70 才能从南边进 U52), # x 140→146 再抬回 75.00/75.70(避开 (187.065,73.863) 那个孔,也对齐 site3 的进站高度)。 SITE3 = { 'USB3_DM': esc('USB3_DM') + [(100.30, 76.63), (118.00, 76.63), (120.00, 76.63), (126.00, 75.20), (132.00, 74.35), (140.00, 74.35), (146.00, 75.70), (206.45, 75.70), (206.45, 75.70, L_TOP), (206.45, 76.351)], 'USB3_DP': esc('USB3_DP') + [(100.80, 76.20), (118.00, 76.16), (120.00, 76.16), (126.00, 74.73), (132.00, 73.85), (140.00, 73.85), (146.00, 75.00), (204.55, 75.00), (204.55, 75.00, L_TOP), (204.55, 76.351)], } # ══════════ pair 2 → site2(东北,走「高窗」)══════════ # 高窗 78.145~81.66 宽敞,但东边 (117.067, 78.392) 那个孔把 x=117 的天花板压到 77.75, # 所以过了隘口(x=114)就得斜下来:78.77/78.29 → 77.61/77.13(21°,垂直净距 0.448)。 # 之后一路缓降到 75.70/75.05,从南边进 U52 —— 全程都在 pair3 的北边。 SITE2 = { 'USB2_DM': esc('USB2_DM') + [(99.30, 78.83), (114.00, 78.83), (116.80, 77.70), (120.00, 77.70), (126.00, 76.30), (132.00, 75.70), (134.95, 75.70), (134.95, 75.70, L_TOP), (134.95, 76.351)], 'USB2_DP': esc('USB2_DP') + [(99.80, 78.29), (114.00, 78.29), (116.80, 77.16), (120.00, 77.16), (126.00, 75.76), (132.00, 75.05), (133.05, 75.05), (133.05, 75.05, L_TOP), (133.05, 76.351)], } # ══════════ pair 4 → site4(西南,全板唯一要「翻层穿越」的一对)══════════ # pair4 夹在 pair3(往北)和 pair5(往北)中间却要往南 —— 同层必然横切这两对, # 所以一出焊盘就**上顶层**南下,把交叉全部让给层间。 # # 过孔位置被四样东西同时夹住: # · 西边界 x ≥ 102.445(离 pair5 的爬升柱 x=101.80 要 0.645) # · DM4 的孔 ≥ DP3 车道 66.05 + 0.645 = 66.695 # · DP4 的孔 ≤ DM5 车道 68.15 − 0.645 = 67.505 # · 两孔互相 ≥ 0.76 ⇒ 只能**错开 x** 放:(102.50, 66.75) 与 (103.60, 67.48) # 顶层的两根竖线还得躲开 (103.28, 62.868) 那个孔(x ≤ 102.635), # 而且**不能压到对方的过孔上** ⇒ DP4 竖在 100.70、DM4 竖在 101.30, # DP4 从东边高处横过来时正好从 DM4 竖线的上方(y=67.48)越过。 # 顶层这一段(x 100.4~103.6, y 61~67.5)底下 Inner1 全是 GND 底填 # (VO2 岛从 y=67.6 起、VO4 岛到 x=100 止),**没有跨分割**。 SITE4 = { 'USB4_DM': [(105.55, 66.75), (104.60, 66.75), (102.50, 66.75), (102.50, 66.75, L_TOP), (101.30, 66.75), (101.30, 60.30), (101.30, 60.30, L_BOT), (49.39, 60.30), (49.39, 60.30, L_TOP), (49.39, 58.809)], 'USB4_DP': [(105.55, 67.25), (104.60, 67.25), (103.60, 67.40), (103.60, 67.40, L_TOP), (100.70, 67.40), (100.70, 61.00), (100.40, 61.00), (100.40, 61.00, L_BOT), (51.29, 61.00), (51.29, 61.00, L_TOP), (51.29, 58.809)], } # ══════════ pair 5 → site5(东南,过隘口走**顶层**)══════════ # 隘口的底层两个窗口分别被 pair3(低窗)和 pair2(高窗)占了, # 第三对只能走顶层 y 75.44~76.3 那条缝。那一段底下是 Inner1 的 `VO2_P` 整岛, # 一整块实心参考面 —— **为此把 VO2 的东边界从 x=116 放到 119**(§25.13), # 好让下沉的两个过孔 (118.0) 还落在岛内,顶层全程不跨分割。 # ⚠ 下坡不能早于 x=115.2:(114.476, 75.0) 那个 GND 孔要求 DP5 ≥ 75.645。 SITE5 = { 'USB5_DM': esc('USB5_DM') + [(101.30, 75.35), (103.00, 75.35), (103.00, 75.35, L_TOP), (108.00, 76.14), (115.20, 76.14), (118.00, 75.50), (118.00, 75.50, L_BOT), (120.89, 75.50), (120.89, 60.40), (120.89, 60.40, L_TOP), (120.89, 58.809)], 'USB5_DP': esc('USB5_DP') + [(101.80, 74.50), (103.00, 74.50), (103.00, 74.50, L_TOP), (108.00, 75.68), (115.20, 75.68), (118.00, 74.70), (118.00, 74.70, L_BOT), (120.00, 74.70), (120.00, 59.50), (122.79, 59.50), (122.79, 59.50, L_TOP), (122.79, 58.809)], } # ══════════ UPSTREAM → J23(U32 南边缘 → 板子正东)══════════ # ⚠ hub_layout 里写的「南侧竖井」(C743 与 C745 之间 2.16 mm)**在底层已经被 # C743/C745 自己的 GND 扇出孔堵死了**:(108.219,60.28) 与 (109.182,60.28) # 中间只剩 0.96 mm,一根 0.28 的线净距只有 0.036。 # ⇒ **UP 一出焊盘就上顶层**(南边那排 0402 都在底层,顶层 y 61.0~61.8 # 从 x=109 一路通到 130)。到 x=128 才沉回底层, # 这样五号站的 D± 在 x=120~123 竖下去时是**从 UP 底下穿过**,两不相干。 # 顶层这一段底下 Inner1 是 GND 底填(VO4 到 x=100、VO5 从 x=139.5 起、VO2 从 y=67.6 起), # 不跨分割。 # ⚠ J23 前面 R717 的 GND 扇出孔 (209.933, 61.642) 占住 y 61.0~62.3, # 所以东行车道压到 60.00/60.75,两根都在它**东边**(x ≥ 210.6)才抬头进焊盘。 # J23 侧 A6=UP_DP 在 61.75、A7=UP_DM 在 62.25,南北次序与车道一致,不用交叉。 UP = { 'UP_DP': [(108.75, 62.55), (108.75, 62.20), (108.75, 61.90), (108.30, 61.45), (108.30, 61.10), (108.30, 61.10, L_TOP), (110.20, 61.10), (128.00, 61.10), (128.00, 61.10, L_BOT), (128.00, 60.00), (211.25, 60.00), (211.25, 61.65), (211.25, 61.65, L_TOP), (211.60, 61.75), (212.392, 61.75)], 'UP_DM': [(109.25, 62.55), (109.25, 61.70), (109.25, 61.70, L_TOP), (110.80, 61.70), (128.60, 61.70), (128.60, 61.70, L_BOT), (128.60, 60.75), (210.65, 60.75), (210.65, 62.25), (210.65, 62.25, L_TOP), (212.392, 62.25)], } # ══════════ J23 的 A6↔B6 / A7↔B7 短接(USB-C 正反插的一对重复脚)══════════ # ⚠ 这两条**原来是自动布线器画的**,被 `pcb2_route_dp.py --push` 的「先删本网所有 # 1/2 层导线」连坐删掉了,必须自己补回来 —— 少了它们,反插方向就没有 D±。 # 四个焊盘在同一列上、间距 0.5:B7(61.25,DM) A6(61.75,DP) A7(62.25,DM) B6(62.75,DP), # 两两要短接的中间都隔着对方的脚,所以只能**绕到连接器东侧沉底层**跨过去 # (x > 213.55 那一带底层是空的,J23 自己的两个固定脚在 y 57.7 / 66.3)。 # ⚠ 上表贴焊盘只到 x=213.042,过孔要离它 0.505 ⇒ 最西只能放到 213.55。 # 两根出线在 y 上只差 0.5,一根的过孔必然贴着另一根的出线 —— # 所以出线要**边往东边分开**:DP 走到 214.40 并压到 61.55/62.95, # DM 停在 213.60 保持 62.25/60.95,四个孔两两 ≥0.76、线-孔 ≥0.62。 J23_TIE = { # ⚠ 出焊盘的那 0.65 mm 必须**平着走**:焊盘阵列 x 到 213.042 为止, # 在阵列上方就起坡的话会蹭到隔壁脚(实测差 0.04~0.14)。都在 x=213.30 之后再拐。 'UP_DM#2': [(212.392, 62.25, L_TOP, W), (213.85, 62.25), (213.85, 62.25, L_BOT), (213.85, 60.70), (213.85, 60.70, L_TOP), (213.50, 61.25), (213.20, 61.25), (212.392, 61.25)], 'UP_DP#2': [(212.392, 61.75, L_TOP, W), (213.20, 61.75), (213.50, 61.75), (214.25, 61.30), (214.75, 61.30), (214.75, 61.30, L_BOT), (214.75, 63.20), (214.75, 63.20, L_TOP), (214.25, 63.20), (213.50, 62.75), (213.20, 62.75), (212.392, 62.75)], }