R2 底板改 4 层(D15):规则 / 几何 / 背面摆位 / 4 层布线与铺铜,已下发
- 规则:铜层 2→4,差分 0.28/0.18,间距 0.15,过孔 0.3/0.2(rules_base) - 几何:98×98 板框 + 4×M3,槽阵居中,L1 12V 大铜 + 指头(pcb_base) - 摆位:五插槽留 L1,其余 133 件翻到 L4;背面镜像规则实测;Hub 放走廊 2、 东移 2.5 mm;扇出圈 / 小件间距 1.5(去耦例外)/ 9 A 过孔阵禁区(place_base) - 叠层按用户决定改为 L1 槽面 / L2 整层 +3V3 / L3 整层 GND / L4 器件面 - 布线:D± 七对按构造布通(USB3 掉头、USB1/2 北去掉头、USB4/5 东绕、 J23 桥),+3V3 / GND / +12V 平面网每脚一孔,0.125 网格 + PathFinder 开关(route_base、remote_route.ps1);剩 37 个网待手工补 - 铺铜:4 层重写,入口链自南向北切段 + 断面校核(pour_base); 已下发布线 / 缝合孔 / 铜面轮廓,填铜未成功(待查) - 工具:plot_base / plot_routes / shot_base,board_check 通孔两层都算 Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_0168Bu6SEx3cXXCJLkdnE6zB
这个提交包含在:
@@ -0,0 +1,968 @@
|
||||
{
|
||||
"C1": {
|
||||
"n": 2,
|
||||
"w": 1.341,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C2": {
|
||||
"n": 2,
|
||||
"w": 5.1,
|
||||
"h": 1.6,
|
||||
"rot": -90.00000000000001,
|
||||
"fp": "CAP-TH_BD8.0-P3.50-D0.6-FD"
|
||||
},
|
||||
"C3": {
|
||||
"n": 2,
|
||||
"w": 5.1,
|
||||
"h": 1.6,
|
||||
"rot": -90.00000000000001,
|
||||
"fp": "CAP-TH_BD8.0-P3.50-D0.6-FD"
|
||||
},
|
||||
"C4": {
|
||||
"n": 2,
|
||||
"w": 3.411,
|
||||
"h": 1.349,
|
||||
"rot": 0,
|
||||
"fp": "C0805"
|
||||
},
|
||||
"C5": {
|
||||
"n": 2,
|
||||
"w": 3.409,
|
||||
"h": 1.349,
|
||||
"rot": 0,
|
||||
"fp": "C0805"
|
||||
},
|
||||
"C6": {
|
||||
"n": 2,
|
||||
"w": 1.339,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C7": {
|
||||
"n": 2,
|
||||
"w": 3.411,
|
||||
"h": 1.349,
|
||||
"rot": 90,
|
||||
"fp": "C0805"
|
||||
},
|
||||
"C8": {
|
||||
"n": 2,
|
||||
"w": 1.339,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C9": {
|
||||
"n": 2,
|
||||
"w": 4.671,
|
||||
"h": 1.727,
|
||||
"rot": 0,
|
||||
"fp": "C1206"
|
||||
},
|
||||
"C10": {
|
||||
"n": 2,
|
||||
"w": 4.671,
|
||||
"h": 1.727,
|
||||
"rot": 0,
|
||||
"fp": "C1206"
|
||||
},
|
||||
"C11": {
|
||||
"n": 2,
|
||||
"w": 1.341,
|
||||
"h": 0.541,
|
||||
"rot": 270,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C601": {
|
||||
"n": 2,
|
||||
"w": 4.671,
|
||||
"h": 1.727,
|
||||
"rot": 0,
|
||||
"fp": "C1206"
|
||||
},
|
||||
"C602": {
|
||||
"n": 2,
|
||||
"w": 1.339,
|
||||
"h": 0.541,
|
||||
"rot": 90,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C603": {
|
||||
"n": 2,
|
||||
"w": 1.339,
|
||||
"h": 0.541,
|
||||
"rot": 90,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C604": {
|
||||
"n": 2,
|
||||
"w": 1.341,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C607": {
|
||||
"n": 2,
|
||||
"w": 1.341,
|
||||
"h": 0.541,
|
||||
"rot": 270,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C608": {
|
||||
"n": 2,
|
||||
"w": 3.409,
|
||||
"h": 1.349,
|
||||
"rot": 180,
|
||||
"fp": "C0805"
|
||||
},
|
||||
"C609": {
|
||||
"n": 2,
|
||||
"w": 1.339,
|
||||
"h": 0.541,
|
||||
"rot": 90,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C610": {
|
||||
"n": 2,
|
||||
"w": 1.339,
|
||||
"h": 0.541,
|
||||
"rot": 270,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C611": {
|
||||
"n": 2,
|
||||
"w": 3.411,
|
||||
"h": 1.349,
|
||||
"rot": 90,
|
||||
"fp": "C0805"
|
||||
},
|
||||
"C612": {
|
||||
"n": 2,
|
||||
"w": 1.341,
|
||||
"h": 0.541,
|
||||
"rot": 90,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C731": {
|
||||
"n": 2,
|
||||
"w": 3.411,
|
||||
"h": 1.349,
|
||||
"rot": 270,
|
||||
"fp": "C0805"
|
||||
},
|
||||
"C732": {
|
||||
"n": 2,
|
||||
"w": 3.411,
|
||||
"h": 1.349,
|
||||
"rot": 180,
|
||||
"fp": "C0805"
|
||||
},
|
||||
"C733": {
|
||||
"n": 2,
|
||||
"w": 1.341,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C734": {
|
||||
"n": 2,
|
||||
"w": 4.671,
|
||||
"h": 1.727,
|
||||
"rot": 0,
|
||||
"fp": "C1206"
|
||||
},
|
||||
"C735": {
|
||||
"n": 2,
|
||||
"w": 4.671,
|
||||
"h": 1.727,
|
||||
"rot": 0,
|
||||
"fp": "C1206"
|
||||
},
|
||||
"C736": {
|
||||
"n": 2,
|
||||
"w": 1.339,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C737": {
|
||||
"n": 2,
|
||||
"w": 3.899,
|
||||
"h": 1.359,
|
||||
"rot": 270,
|
||||
"fp": "CASE-A_3216"
|
||||
},
|
||||
"C738": {
|
||||
"n": 2,
|
||||
"w": 4.671,
|
||||
"h": 1.727,
|
||||
"rot": 180,
|
||||
"fp": "C1206"
|
||||
},
|
||||
"C739": {
|
||||
"n": 2,
|
||||
"w": 1.341,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C740": {
|
||||
"n": 2,
|
||||
"w": 1.341,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C741": {
|
||||
"n": 2,
|
||||
"w": 1.341,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C742": {
|
||||
"n": 2,
|
||||
"w": 1.341,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C743": {
|
||||
"n": 2,
|
||||
"w": 1.341,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C744": {
|
||||
"n": 2,
|
||||
"w": 1.339,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C745": {
|
||||
"n": 2,
|
||||
"w": 1.339,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C746": {
|
||||
"n": 2,
|
||||
"w": 1.339,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C747": {
|
||||
"n": 2,
|
||||
"w": 1.341,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C748": {
|
||||
"n": 2,
|
||||
"w": 1.341,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C749": {
|
||||
"n": 2,
|
||||
"w": 1.341,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C750": {
|
||||
"n": 2,
|
||||
"w": 1.341,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"C751": {
|
||||
"n": 2,
|
||||
"w": 3.409,
|
||||
"h": 1.349,
|
||||
"rot": 0,
|
||||
"fp": "C0805"
|
||||
},
|
||||
"C752": {
|
||||
"n": 2,
|
||||
"w": 3.411,
|
||||
"h": 1.349,
|
||||
"rot": 180,
|
||||
"fp": "C0805"
|
||||
},
|
||||
"C753": {
|
||||
"n": 2,
|
||||
"w": 1.341,
|
||||
"h": 0.541,
|
||||
"rot": 90,
|
||||
"fp": "C0402"
|
||||
},
|
||||
"D1": {
|
||||
"n": 2,
|
||||
"w": 6.769,
|
||||
"h": 2.192,
|
||||
"rot": 0,
|
||||
"fp": "SMB_L4.6-W3.6-LS5.3-RD"
|
||||
},
|
||||
"D2": {
|
||||
"n": 2,
|
||||
"w": 6.398,
|
||||
"h": 1.999,
|
||||
"rot": 180,
|
||||
"fp": "SMA_L4.3-W2.6-LS5.2-RD"
|
||||
},
|
||||
"DZ1": {
|
||||
"n": 2,
|
||||
"w": 4.219,
|
||||
"h": 0.841,
|
||||
"rot": 180,
|
||||
"fp": "SOD-123_L2.7-W1.7-LS3.8-RD"
|
||||
},
|
||||
"DZ3": {
|
||||
"n": 2,
|
||||
"w": 4.597,
|
||||
"h": 0.95,
|
||||
"rot": 270,
|
||||
"fp": "SOD-123_L2.7-W1.6-LS3.7-RD-1"
|
||||
},
|
||||
"F1": {
|
||||
"n": 2,
|
||||
"w": 4.082,
|
||||
"h": 1.727,
|
||||
"rot": 0,
|
||||
"fp": "F1206"
|
||||
},
|
||||
"J1": {
|
||||
"n": 3,
|
||||
"w": 7.6,
|
||||
"h": 7.399,
|
||||
"rot": 90,
|
||||
"fp": "DC-IN-TH_DC-005-2"
|
||||
},
|
||||
"J23": {
|
||||
"n": 16,
|
||||
"w": 9.85,
|
||||
"h": 6.499,
|
||||
"rot": 0,
|
||||
"fp": "USB-C_SMD-TYPE-C-31-M-12_1"
|
||||
},
|
||||
"J26": {
|
||||
"n": 4,
|
||||
"w": 9.22,
|
||||
"h": 1.6,
|
||||
"rot": 180,
|
||||
"fp": "CONN-TH_4P-P2.54_CAX_2510-4A"
|
||||
},
|
||||
"J27": {
|
||||
"n": 4,
|
||||
"w": 9.22,
|
||||
"h": 1.6,
|
||||
"rot": 180,
|
||||
"fp": "CONN-TH_4P-P2.54_CAX_2510-4A"
|
||||
},
|
||||
"J33": {
|
||||
"n": 5,
|
||||
"w": 11.859,
|
||||
"h": 1.699,
|
||||
"rot": -90.00000000000001,
|
||||
"fp": "HDR-TH_5P-P2.54-V-M"
|
||||
},
|
||||
"J34": {
|
||||
"n": 66,
|
||||
"w": 41.999,
|
||||
"h": 9.197,
|
||||
"rot": 0,
|
||||
"fp": "PCIE-SMD_PCIE-64P11L"
|
||||
},
|
||||
"J35": {
|
||||
"n": 66,
|
||||
"w": 41.999,
|
||||
"h": 9.197,
|
||||
"rot": 0,
|
||||
"fp": "PCIE-SMD_PCIE-64P11L"
|
||||
},
|
||||
"J36": {
|
||||
"n": 66,
|
||||
"w": 41.999,
|
||||
"h": 9.197,
|
||||
"rot": 0,
|
||||
"fp": "PCIE-SMD_PCIE-64P11L"
|
||||
},
|
||||
"J37": {
|
||||
"n": 66,
|
||||
"w": 41.999,
|
||||
"h": 9.197,
|
||||
"rot": 0,
|
||||
"fp": "PCIE-SMD_PCIE-64P11L"
|
||||
},
|
||||
"J38": {
|
||||
"n": 66,
|
||||
"w": 41.999,
|
||||
"h": 9.197,
|
||||
"rot": 0,
|
||||
"fp": "PCIE-SMD_PCIE-64P11L"
|
||||
},
|
||||
"L2": {
|
||||
"n": 2,
|
||||
"w": 4.801,
|
||||
"h": 3.5,
|
||||
"rot": 180,
|
||||
"fp": "IND-SMD_L4.0-W4.0"
|
||||
},
|
||||
"L3": {
|
||||
"n": 2,
|
||||
"w": 7.6,
|
||||
"h": 3.401,
|
||||
"rot": 180,
|
||||
"fp": "IND-SMD_L7.1-W6.5"
|
||||
},
|
||||
"Q1": {
|
||||
"n": 8,
|
||||
"w": 4.384,
|
||||
"h": 7.569,
|
||||
"rot": 0,
|
||||
"fp": "SOP-8_L5.0-W4.0-P1.27-LS6.0-BL"
|
||||
},
|
||||
"Q3": {
|
||||
"n": 8,
|
||||
"w": 4.384,
|
||||
"h": 7.572,
|
||||
"rot": -90.00000000000001,
|
||||
"fp": "SOP-8_L5.0-W4.0-P1.27-LS6.0-BL"
|
||||
},
|
||||
"Q6": {
|
||||
"n": 3,
|
||||
"w": 3.0,
|
||||
"h": 2.55,
|
||||
"rot": 180,
|
||||
"fp": "SOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR"
|
||||
},
|
||||
"Q7": {
|
||||
"n": 3,
|
||||
"w": 3.302,
|
||||
"h": 2.7,
|
||||
"rot": 0,
|
||||
"fp": "SOT-23_L2.9-W1.3-P1.90-LS2.4-BR"
|
||||
},
|
||||
"Q8": {
|
||||
"n": 3,
|
||||
"w": 3.002,
|
||||
"h": 2.55,
|
||||
"rot": 180,
|
||||
"fp": "SOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR"
|
||||
},
|
||||
"Q9": {
|
||||
"n": 3,
|
||||
"w": 3.002,
|
||||
"h": 2.55,
|
||||
"rot": 180,
|
||||
"fp": "SOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR"
|
||||
},
|
||||
"Q10": {
|
||||
"n": 3,
|
||||
"w": 3.0,
|
||||
"h": 2.55,
|
||||
"rot": 90,
|
||||
"fp": "SOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR"
|
||||
},
|
||||
"R1": {
|
||||
"n": 2,
|
||||
"w": 1.43,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R2": {
|
||||
"n": 2,
|
||||
"w": 1.43,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R3": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R4": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 90,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R5": {
|
||||
"n": 2,
|
||||
"w": 1.43,
|
||||
"h": 0.541,
|
||||
"rot": 270,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R6": {
|
||||
"n": 2,
|
||||
"w": 1.43,
|
||||
"h": 0.541,
|
||||
"rot": 90,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R9A": {
|
||||
"n": 2,
|
||||
"w": 7.414,
|
||||
"h": 3.457,
|
||||
"rot": 0,
|
||||
"fp": "R2512"
|
||||
},
|
||||
"R9B": {
|
||||
"n": 2,
|
||||
"w": 7.414,
|
||||
"h": 3.457,
|
||||
"rot": 180,
|
||||
"fp": "R2512"
|
||||
},
|
||||
"R9C": {
|
||||
"n": 2,
|
||||
"w": 7.414,
|
||||
"h": 3.457,
|
||||
"rot": 0,
|
||||
"fp": "R2512"
|
||||
},
|
||||
"R9D": {
|
||||
"n": 2,
|
||||
"w": 7.414,
|
||||
"h": 3.457,
|
||||
"rot": 180,
|
||||
"fp": "R2512"
|
||||
},
|
||||
"R9E": {
|
||||
"n": 2,
|
||||
"w": 7.414,
|
||||
"h": 3.457,
|
||||
"rot": 0,
|
||||
"fp": "R2512"
|
||||
},
|
||||
"R601": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 270,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R602": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R603": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R604": {
|
||||
"n": 2,
|
||||
"w": 1.43,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R605": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R606": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 270,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R607": {
|
||||
"n": 2,
|
||||
"w": 1.43,
|
||||
"h": 0.541,
|
||||
"rot": 270,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R608": {
|
||||
"n": 2,
|
||||
"w": 1.43,
|
||||
"h": 0.541,
|
||||
"rot": 270,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R609": {
|
||||
"n": 2,
|
||||
"w": 1.43,
|
||||
"h": 0.541,
|
||||
"rot": 270,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R610": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R611": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R614": {
|
||||
"n": 2,
|
||||
"w": 1.43,
|
||||
"h": 0.541,
|
||||
"rot": 90,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R615": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R616": {
|
||||
"n": 2,
|
||||
"w": 1.43,
|
||||
"h": 0.541,
|
||||
"rot": 270,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R617": {
|
||||
"n": 2,
|
||||
"w": 1.43,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R660": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R661": {
|
||||
"n": 2,
|
||||
"w": 1.43,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R706": {
|
||||
"n": 2,
|
||||
"w": 1.43,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R707": {
|
||||
"n": 2,
|
||||
"w": 1.43,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R708": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R709": {
|
||||
"n": 2,
|
||||
"w": 1.43,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R710": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R711": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R712": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R716": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R717": {
|
||||
"n": 2,
|
||||
"w": 1.43,
|
||||
"h": 0.541,
|
||||
"rot": 90,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R718": {
|
||||
"n": 2,
|
||||
"w": 1.43,
|
||||
"h": 0.541,
|
||||
"rot": 90,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R719": {
|
||||
"n": 2,
|
||||
"w": 1.43,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R720": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R721": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R722": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R733": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R734": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R746": {
|
||||
"n": 2,
|
||||
"w": 1.43,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R747": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R748": {
|
||||
"n": 2,
|
||||
"w": 1.43,
|
||||
"h": 0.541,
|
||||
"rot": 90,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R749": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R750": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R751": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 0,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R752": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R753": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R754": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R755": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R756": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"R757": {
|
||||
"n": 2,
|
||||
"w": 1.433,
|
||||
"h": 0.541,
|
||||
"rot": 180,
|
||||
"fp": "R0402"
|
||||
},
|
||||
"Rs6": {
|
||||
"n": 2,
|
||||
"w": 7.414,
|
||||
"h": 3.457,
|
||||
"rot": 90,
|
||||
"fp": "R2512"
|
||||
},
|
||||
"RT601": {
|
||||
"n": 2,
|
||||
"w": 0.864,
|
||||
"h": 2.316,
|
||||
"rot": 89.99999999999997,
|
||||
"fp": "R0603"
|
||||
},
|
||||
"RT602": {
|
||||
"n": 2,
|
||||
"w": 2.316,
|
||||
"h": 0.864,
|
||||
"rot": 270,
|
||||
"fp": "R0603"
|
||||
},
|
||||
"U6": {
|
||||
"n": 17,
|
||||
"w": 3.8,
|
||||
"h": 3.8,
|
||||
"rot": 180,
|
||||
"fp": "WQFN-16_L3.0-W3.0-P0.50-TL-EP1.7"
|
||||
},
|
||||
"U7": {
|
||||
"n": 17,
|
||||
"w": 3.8,
|
||||
"h": 3.8,
|
||||
"rot": 180,
|
||||
"fp": "WQFN-16_L3.0-W3.0-P0.50-TL-EP1.7"
|
||||
},
|
||||
"U13": {
|
||||
"n": 10,
|
||||
"w": 2.281,
|
||||
"h": 5.842,
|
||||
"rot": 180,
|
||||
"fp": "MSOP-10_L3.0-W3.0-P0.50-LS5.0-BL"
|
||||
},
|
||||
"U19": {
|
||||
"n": 6,
|
||||
"w": 3.8,
|
||||
"h": 2.499,
|
||||
"rot": 180,
|
||||
"fp": "SOT-23-6_L2.9-W1.6-P0.95-LS2.8-BR"
|
||||
},
|
||||
"U20": {
|
||||
"n": 49,
|
||||
"w": 19.002,
|
||||
"h": 18.99,
|
||||
"rot": -90.00000000000001,
|
||||
"fp": "WIRELM-SMD_ESP32-S3-WROOM-1"
|
||||
},
|
||||
"U29": {
|
||||
"n": 3,
|
||||
"w": 3.002,
|
||||
"h": 2.55,
|
||||
"rot": 180,
|
||||
"fp": "SOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR"
|
||||
},
|
||||
"U30": {
|
||||
"n": 6,
|
||||
"w": 3.8,
|
||||
"h": 2.499,
|
||||
"rot": 180,
|
||||
"fp": "SOT-23-6_L2.9-W1.6-P0.95-LS2.8-BR"
|
||||
},
|
||||
"U31": {
|
||||
"n": 4,
|
||||
"w": 8.36,
|
||||
"h": 5.7,
|
||||
"rot": 180,
|
||||
"fp": "SOT-223-3_L6.5-W3.4-P2.30-LS7.0-BR"
|
||||
},
|
||||
"U32": {
|
||||
"n": 65,
|
||||
"w": 9.751,
|
||||
"h": 9.751,
|
||||
"rot": 0,
|
||||
"fp": "QFN-64_L9.0-W9.0-P0.50-TL-EP4.8"
|
||||
},
|
||||
"U35": {
|
||||
"n": 24,
|
||||
"w": 7.546,
|
||||
"h": 7.696,
|
||||
"rot": 180,
|
||||
"fp": "TSSOP-24_L7.8-W4.4-P0.65-LS6.4-BL"
|
||||
},
|
||||
"Y1": {
|
||||
"n": 4,
|
||||
"w": 3.599,
|
||||
"h": 2.901,
|
||||
"rot": 180,
|
||||
"fp": "CRYSTAL-SMD_4P-L3.2-W2.5-BL"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
文件差异因一行或多行过长而隐藏
@@ -0,0 +1,692 @@
|
||||
{
|
||||
"J38": [
|
||||
46.5,
|
||||
25.86,
|
||||
0
|
||||
],
|
||||
"J37": [
|
||||
46.5,
|
||||
46.18,
|
||||
0
|
||||
],
|
||||
"J36": [
|
||||
46.5,
|
||||
66.5,
|
||||
0
|
||||
],
|
||||
"J35": [
|
||||
46.5,
|
||||
86.82,
|
||||
0
|
||||
],
|
||||
"J34": [
|
||||
46.5,
|
||||
107.14,
|
||||
0
|
||||
],
|
||||
"U32": [
|
||||
89.111,
|
||||
52.903,
|
||||
0
|
||||
],
|
||||
"Y1": [
|
||||
91.611,
|
||||
45.903,
|
||||
180
|
||||
],
|
||||
"C740": [
|
||||
95.111,
|
||||
45.903,
|
||||
180
|
||||
],
|
||||
"C741": [
|
||||
88.111,
|
||||
44.903,
|
||||
180
|
||||
],
|
||||
"C743": [
|
||||
86.361,
|
||||
59.403,
|
||||
0
|
||||
],
|
||||
"R708": [
|
||||
83.861,
|
||||
46.903,
|
||||
180
|
||||
],
|
||||
"U20": [
|
||||
93.0,
|
||||
97.0,
|
||||
270
|
||||
],
|
||||
"J23": [
|
||||
94.572,
|
||||
5.123,
|
||||
0
|
||||
],
|
||||
"J1": [
|
||||
14.48,
|
||||
10.663,
|
||||
90
|
||||
],
|
||||
"F1": [
|
||||
24.73,
|
||||
3.503,
|
||||
0
|
||||
],
|
||||
"Q1": [
|
||||
30.73,
|
||||
6.503,
|
||||
0
|
||||
],
|
||||
"D1": [
|
||||
37.73,
|
||||
5.003,
|
||||
0
|
||||
],
|
||||
"C2": [
|
||||
55.245,
|
||||
6.731,
|
||||
270
|
||||
],
|
||||
"C3": [
|
||||
46.609,
|
||||
6.731,
|
||||
270
|
||||
],
|
||||
"C4": [
|
||||
30.61,
|
||||
12.703,
|
||||
0
|
||||
],
|
||||
"C5": [
|
||||
39.73,
|
||||
13.003,
|
||||
0
|
||||
],
|
||||
"C6": [
|
||||
26.797,
|
||||
11.938,
|
||||
0
|
||||
],
|
||||
"Rs6": [
|
||||
63.75,
|
||||
4.0,
|
||||
0
|
||||
],
|
||||
"U13": [
|
||||
63.75,
|
||||
10.5,
|
||||
180
|
||||
],
|
||||
"J26": [
|
||||
83.0,
|
||||
118.5,
|
||||
180
|
||||
],
|
||||
"J27": [
|
||||
70.0,
|
||||
118.5,
|
||||
180
|
||||
],
|
||||
"J33": [
|
||||
107.568,
|
||||
74.925,
|
||||
270
|
||||
],
|
||||
"U19": [
|
||||
58.5,
|
||||
116.5,
|
||||
180
|
||||
],
|
||||
"C8": [
|
||||
59.944,
|
||||
118.491,
|
||||
0
|
||||
],
|
||||
"L2": [
|
||||
53.0,
|
||||
116.5,
|
||||
180
|
||||
],
|
||||
"R2": [
|
||||
62.865,
|
||||
117.475,
|
||||
0
|
||||
],
|
||||
"R3": [
|
||||
62.75,
|
||||
115.75,
|
||||
180
|
||||
],
|
||||
"U30": [
|
||||
45.5,
|
||||
116.5,
|
||||
180
|
||||
],
|
||||
"C733": [
|
||||
45.5,
|
||||
120.5,
|
||||
180
|
||||
],
|
||||
"L3": [
|
||||
38.5,
|
||||
116.5,
|
||||
180
|
||||
],
|
||||
"R706": [
|
||||
46.99,
|
||||
118.999,
|
||||
180
|
||||
],
|
||||
"R707": [
|
||||
44.196,
|
||||
118.999,
|
||||
180
|
||||
],
|
||||
"Q3": [
|
||||
71.247,
|
||||
8.763,
|
||||
270
|
||||
],
|
||||
"R9A": [
|
||||
81.3077,
|
||||
4.4266,
|
||||
0
|
||||
],
|
||||
"R9B": [
|
||||
81.3077,
|
||||
8.9266,
|
||||
180
|
||||
],
|
||||
"R9C": [
|
||||
88.5,
|
||||
13.4266,
|
||||
0
|
||||
],
|
||||
"R9D": [
|
||||
88.5,
|
||||
17.9266,
|
||||
180
|
||||
],
|
||||
"R9E": [
|
||||
88.5,
|
||||
22.4266,
|
||||
0
|
||||
],
|
||||
"U29": [
|
||||
67.0,
|
||||
13.0,
|
||||
180
|
||||
],
|
||||
"R4": [
|
||||
73.406,
|
||||
12.8504,
|
||||
90
|
||||
],
|
||||
"R5": [
|
||||
72.136,
|
||||
12.827,
|
||||
270
|
||||
],
|
||||
"R6": [
|
||||
66.548,
|
||||
7.62,
|
||||
90
|
||||
],
|
||||
"U35": [
|
||||
7.239,
|
||||
53.975,
|
||||
180
|
||||
],
|
||||
"U6": [
|
||||
11.513,
|
||||
77.037,
|
||||
180
|
||||
],
|
||||
"U7": [
|
||||
12.402,
|
||||
66.402,
|
||||
180
|
||||
],
|
||||
"C608": [
|
||||
6.588,
|
||||
73.041,
|
||||
180
|
||||
],
|
||||
"C611": [
|
||||
8.001,
|
||||
61.722,
|
||||
90
|
||||
],
|
||||
"C612": [
|
||||
13.741,
|
||||
62.552,
|
||||
90
|
||||
],
|
||||
"C603": [
|
||||
98.552,
|
||||
108.458,
|
||||
90
|
||||
],
|
||||
"C736": [
|
||||
100.5,
|
||||
85.5,
|
||||
0
|
||||
],
|
||||
"C739": [
|
||||
98.5,
|
||||
85.5,
|
||||
180
|
||||
],
|
||||
"C738": [
|
||||
92.639,
|
||||
119.706,
|
||||
180
|
||||
],
|
||||
"C742": [
|
||||
87.611,
|
||||
46.903,
|
||||
0
|
||||
],
|
||||
"C609": [
|
||||
13.97,
|
||||
73.152,
|
||||
90
|
||||
],
|
||||
"R615": [
|
||||
12.227,
|
||||
80.029,
|
||||
0
|
||||
],
|
||||
"R617": [
|
||||
14.478,
|
||||
69.723,
|
||||
180
|
||||
],
|
||||
"C734": [
|
||||
31.0,
|
||||
114.0,
|
||||
0
|
||||
],
|
||||
"C735": [
|
||||
31.0,
|
||||
119.0,
|
||||
0
|
||||
],
|
||||
"C737": [
|
||||
85.5,
|
||||
67.0,
|
||||
270
|
||||
],
|
||||
"C752": [
|
||||
6.7111,
|
||||
66.4429,
|
||||
180
|
||||
],
|
||||
"C753": [
|
||||
7.9651,
|
||||
69.5459,
|
||||
90
|
||||
],
|
||||
"C10": [
|
||||
69.0,
|
||||
122.5,
|
||||
0
|
||||
],
|
||||
"C7": [
|
||||
55.75,
|
||||
120.75,
|
||||
90
|
||||
],
|
||||
"C731": [
|
||||
49.25,
|
||||
117.5,
|
||||
270
|
||||
],
|
||||
"C732": [
|
||||
41.021,
|
||||
122.174,
|
||||
180
|
||||
],
|
||||
"C9": [
|
||||
50.0,
|
||||
122.5,
|
||||
0
|
||||
],
|
||||
"DZ1": [
|
||||
31.0,
|
||||
1.65,
|
||||
0
|
||||
],
|
||||
"C1": [
|
||||
34.0,
|
||||
1.2,
|
||||
0
|
||||
],
|
||||
"R1": [
|
||||
36.0,
|
||||
1.2,
|
||||
0
|
||||
],
|
||||
"R614": [
|
||||
12.827,
|
||||
73.152,
|
||||
90
|
||||
],
|
||||
"C601": [
|
||||
96.774,
|
||||
111.379,
|
||||
0
|
||||
],
|
||||
"C602": [
|
||||
96.647,
|
||||
108.966,
|
||||
90
|
||||
],
|
||||
"R601": [
|
||||
94.7972,
|
||||
108.5832,
|
||||
270
|
||||
],
|
||||
"R609": [
|
||||
87.5518,
|
||||
108.5598,
|
||||
270
|
||||
],
|
||||
"R608": [
|
||||
89.662,
|
||||
108.585,
|
||||
270
|
||||
],
|
||||
"R607": [
|
||||
91.44,
|
||||
108.585,
|
||||
270
|
||||
],
|
||||
"R606": [
|
||||
93.3092,
|
||||
108.5832,
|
||||
270
|
||||
],
|
||||
"R610": [
|
||||
76.5,
|
||||
116.25,
|
||||
0
|
||||
],
|
||||
"R604": [
|
||||
90.0,
|
||||
85.5,
|
||||
0
|
||||
],
|
||||
"R605": [
|
||||
88.4126,
|
||||
84.23,
|
||||
180
|
||||
],
|
||||
"R611": [
|
||||
95.0,
|
||||
85.5,
|
||||
0
|
||||
],
|
||||
"R710": [
|
||||
101.111,
|
||||
51.403,
|
||||
0
|
||||
],
|
||||
"R711": [
|
||||
101.111,
|
||||
52.903,
|
||||
0
|
||||
],
|
||||
"R712": [
|
||||
101.111,
|
||||
54.403,
|
||||
0
|
||||
],
|
||||
"R720": [
|
||||
101.111,
|
||||
55.903,
|
||||
0
|
||||
],
|
||||
"R716": [
|
||||
101.111,
|
||||
57.903,
|
||||
0
|
||||
],
|
||||
"C604": [
|
||||
12.722,
|
||||
50.516,
|
||||
0
|
||||
],
|
||||
"R602": [
|
||||
12.722,
|
||||
52.016,
|
||||
0
|
||||
],
|
||||
"R603": [
|
||||
12.722,
|
||||
53.516,
|
||||
0
|
||||
],
|
||||
"C607": [
|
||||
10.5801,
|
||||
73.1911,
|
||||
270
|
||||
],
|
||||
"C610": [
|
||||
11.43,
|
||||
62.484,
|
||||
270
|
||||
],
|
||||
"R616": [
|
||||
12.573,
|
||||
62.517,
|
||||
270
|
||||
],
|
||||
"C744": [
|
||||
96.111,
|
||||
48.903,
|
||||
0
|
||||
],
|
||||
"C745": [
|
||||
96.111,
|
||||
50.403,
|
||||
0
|
||||
],
|
||||
"C746": [
|
||||
96.111,
|
||||
51.903,
|
||||
0
|
||||
],
|
||||
"C747": [
|
||||
98.111,
|
||||
51.903,
|
||||
0
|
||||
],
|
||||
"C748": [
|
||||
98.111,
|
||||
53.403,
|
||||
0
|
||||
],
|
||||
"C749": [
|
||||
98.111,
|
||||
54.903,
|
||||
0
|
||||
],
|
||||
"C750": [
|
||||
98.111,
|
||||
56.403,
|
||||
0
|
||||
],
|
||||
"C751": [
|
||||
97.111,
|
||||
59.903,
|
||||
0
|
||||
],
|
||||
"D2": [
|
||||
84.0,
|
||||
113.5,
|
||||
180
|
||||
],
|
||||
"R755": [
|
||||
94.0,
|
||||
113.5,
|
||||
180
|
||||
],
|
||||
"R757": [
|
||||
97.603,
|
||||
116.73,
|
||||
180
|
||||
],
|
||||
"R753": [
|
||||
78.5,
|
||||
113.5,
|
||||
180
|
||||
],
|
||||
"R748": [
|
||||
57.531,
|
||||
121.285,
|
||||
90
|
||||
],
|
||||
"RT601": [
|
||||
91.186,
|
||||
73.787,
|
||||
90
|
||||
],
|
||||
"RT602": [
|
||||
25.654,
|
||||
8.509,
|
||||
270
|
||||
],
|
||||
"Q6": [
|
||||
72.5,
|
||||
113.5,
|
||||
180
|
||||
],
|
||||
"R733": [
|
||||
68.0,
|
||||
113.5,
|
||||
180
|
||||
],
|
||||
"R734": [
|
||||
76.0,
|
||||
113.5,
|
||||
180
|
||||
],
|
||||
"Q10": [
|
||||
102.572,
|
||||
110.563,
|
||||
90
|
||||
],
|
||||
"R754": [
|
||||
91.5,
|
||||
113.5,
|
||||
180
|
||||
],
|
||||
"R756": [
|
||||
95.103,
|
||||
116.73,
|
||||
180
|
||||
],
|
||||
"R747": [
|
||||
100.103,
|
||||
116.73,
|
||||
180
|
||||
],
|
||||
"R660": [
|
||||
100.746,
|
||||
83.933,
|
||||
180
|
||||
],
|
||||
"R661": [
|
||||
98.298,
|
||||
83.947,
|
||||
180
|
||||
],
|
||||
"C11": [
|
||||
11.7231,
|
||||
73.2399,
|
||||
270
|
||||
],
|
||||
"U31": [
|
||||
85.746,
|
||||
73.562,
|
||||
180
|
||||
],
|
||||
"R717": [
|
||||
88.011,
|
||||
5.08,
|
||||
90
|
||||
],
|
||||
"R718": [
|
||||
101.346,
|
||||
5.461,
|
||||
90
|
||||
],
|
||||
"R719": [
|
||||
96.111,
|
||||
55.903,
|
||||
0
|
||||
],
|
||||
"R722": [
|
||||
76.5,
|
||||
118.0,
|
||||
180
|
||||
],
|
||||
"R709": [
|
||||
101.031,
|
||||
50.167,
|
||||
180
|
||||
],
|
||||
"R721": [
|
||||
100.865,
|
||||
48.087,
|
||||
180
|
||||
],
|
||||
"R749": [
|
||||
58.0,
|
||||
119.5,
|
||||
180
|
||||
],
|
||||
"R752": [
|
||||
52.0,
|
||||
120.0,
|
||||
180
|
||||
],
|
||||
"Q7": [
|
||||
98.044,
|
||||
119.634,
|
||||
0
|
||||
],
|
||||
"Q8": [
|
||||
61.468,
|
||||
120.904,
|
||||
180
|
||||
],
|
||||
"Q9": [
|
||||
94.234,
|
||||
82.296,
|
||||
180
|
||||
],
|
||||
"DZ3": [
|
||||
101.473,
|
||||
120.269,
|
||||
270
|
||||
],
|
||||
"R746": [
|
||||
96.9214,
|
||||
122.5,
|
||||
0
|
||||
],
|
||||
"R750": [
|
||||
81.3736,
|
||||
122.5,
|
||||
0
|
||||
],
|
||||
"R751": [
|
||||
78.5612,
|
||||
122.4798,
|
||||
0
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,830 @@
|
||||
{
|
||||
"C1": [
|
||||
29.42,
|
||||
1.123,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C10": [
|
||||
69.0,
|
||||
122.5,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C11": [
|
||||
11.7231,
|
||||
73.2399,
|
||||
270,
|
||||
1
|
||||
],
|
||||
"C2": [
|
||||
55.245,
|
||||
6.731,
|
||||
270,
|
||||
1
|
||||
],
|
||||
"C3": [
|
||||
46.609,
|
||||
6.731,
|
||||
270,
|
||||
1
|
||||
],
|
||||
"C4": [
|
||||
30.61,
|
||||
12.703,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C5": [
|
||||
39.73,
|
||||
13.003,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C6": [
|
||||
26.797,
|
||||
11.938,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C601": [
|
||||
96.774,
|
||||
111.379,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C602": [
|
||||
96.647,
|
||||
108.966,
|
||||
90,
|
||||
1
|
||||
],
|
||||
"C603": [
|
||||
98.552,
|
||||
108.458,
|
||||
90,
|
||||
1
|
||||
],
|
||||
"C604": [
|
||||
12.722,
|
||||
50.516,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C607": [
|
||||
10.5801,
|
||||
73.1911,
|
||||
270,
|
||||
1
|
||||
],
|
||||
"C608": [
|
||||
6.588,
|
||||
73.041,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"C609": [
|
||||
13.97,
|
||||
73.152,
|
||||
90,
|
||||
1
|
||||
],
|
||||
"C610": [
|
||||
11.43,
|
||||
62.484,
|
||||
270,
|
||||
1
|
||||
],
|
||||
"C611": [
|
||||
8.001,
|
||||
61.722,
|
||||
90,
|
||||
1
|
||||
],
|
||||
"C612": [
|
||||
13.741,
|
||||
62.552,
|
||||
90,
|
||||
1
|
||||
],
|
||||
"C7": [
|
||||
55.75,
|
||||
120.75,
|
||||
90,
|
||||
1
|
||||
],
|
||||
"C731": [
|
||||
49.25,
|
||||
117.5,
|
||||
270,
|
||||
1
|
||||
],
|
||||
"C732": [
|
||||
41.021,
|
||||
122.174,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"C733": [
|
||||
45.5,
|
||||
120.5,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"C734": [
|
||||
31.0,
|
||||
114.0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C735": [
|
||||
31.0,
|
||||
119.0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C736": [
|
||||
100.5,
|
||||
85.5,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C737": [
|
||||
85.5,
|
||||
67.0,
|
||||
270,
|
||||
1
|
||||
],
|
||||
"C738": [
|
||||
92.639,
|
||||
119.706,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"C739": [
|
||||
98.5,
|
||||
85.5,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"C740": [
|
||||
95.111,
|
||||
45.903,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"C741": [
|
||||
88.111,
|
||||
44.903,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"C742": [
|
||||
87.611,
|
||||
46.903,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C743": [
|
||||
86.361,
|
||||
59.403,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C744": [
|
||||
96.111,
|
||||
48.903,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C745": [
|
||||
96.111,
|
||||
50.403,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C746": [
|
||||
96.111,
|
||||
51.903,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C747": [
|
||||
98.111,
|
||||
51.903,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C748": [
|
||||
98.111,
|
||||
53.403,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C749": [
|
||||
98.111,
|
||||
54.903,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C750": [
|
||||
98.111,
|
||||
56.403,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C751": [
|
||||
97.111,
|
||||
59.903,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C752": [
|
||||
6.7111,
|
||||
66.4429,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"C753": [
|
||||
7.9651,
|
||||
69.5459,
|
||||
90,
|
||||
1
|
||||
],
|
||||
"C8": [
|
||||
59.944,
|
||||
118.491,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"C9": [
|
||||
50.0,
|
||||
122.5,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"D1": [
|
||||
37.73,
|
||||
5.003,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"D2": [
|
||||
84.0,
|
||||
113.5,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"DZ1": [
|
||||
36.96,
|
||||
1.653,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"DZ3": [
|
||||
101.473,
|
||||
120.269,
|
||||
270,
|
||||
1
|
||||
],
|
||||
"F1": [
|
||||
24.73,
|
||||
3.503,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"J1": [
|
||||
14.48,
|
||||
10.663,
|
||||
90,
|
||||
1
|
||||
],
|
||||
"J23": [
|
||||
94.572,
|
||||
5.123,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"J26": [
|
||||
83.0,
|
||||
118.5,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"J27": [
|
||||
70.0,
|
||||
118.5,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"J33": [
|
||||
107.568,
|
||||
74.925,
|
||||
270,
|
||||
1
|
||||
],
|
||||
"J34": [
|
||||
46.5,
|
||||
107.14,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"J35": [
|
||||
46.5,
|
||||
86.82,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"J36": [
|
||||
46.5,
|
||||
66.5,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"J37": [
|
||||
46.5,
|
||||
46.18,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"J38": [
|
||||
46.5,
|
||||
25.86,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"L2": [
|
||||
53.0,
|
||||
116.5,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"L3": [
|
||||
38.5,
|
||||
116.5,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"Q1": [
|
||||
30.73,
|
||||
6.503,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"Q10": [
|
||||
102.572,
|
||||
110.563,
|
||||
90,
|
||||
1
|
||||
],
|
||||
"Q3": [
|
||||
71.247,
|
||||
8.763,
|
||||
270,
|
||||
1
|
||||
],
|
||||
"Q6": [
|
||||
72.5,
|
||||
113.5,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"Q7": [
|
||||
98.044,
|
||||
119.634,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"Q8": [
|
||||
61.468,
|
||||
120.904,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"Q9": [
|
||||
94.234,
|
||||
82.296,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R1": [
|
||||
31.92,
|
||||
1.123,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"R2": [
|
||||
62.865,
|
||||
117.475,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"R3": [
|
||||
62.75,
|
||||
115.75,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R4": [
|
||||
73.406,
|
||||
12.8504,
|
||||
90,
|
||||
1
|
||||
],
|
||||
"R5": [
|
||||
72.136,
|
||||
12.827,
|
||||
270,
|
||||
1
|
||||
],
|
||||
"R6": [
|
||||
66.548,
|
||||
7.62,
|
||||
90,
|
||||
1
|
||||
],
|
||||
"R601": [
|
||||
94.7972,
|
||||
108.5832,
|
||||
270,
|
||||
1
|
||||
],
|
||||
"R602": [
|
||||
12.722,
|
||||
52.016,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"R603": [
|
||||
12.722,
|
||||
53.516,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"R604": [
|
||||
90.0,
|
||||
85.5,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"R605": [
|
||||
88.4126,
|
||||
84.23,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R606": [
|
||||
93.3092,
|
||||
108.5832,
|
||||
270,
|
||||
1
|
||||
],
|
||||
"R607": [
|
||||
91.44,
|
||||
108.585,
|
||||
270,
|
||||
1
|
||||
],
|
||||
"R608": [
|
||||
89.662,
|
||||
108.585,
|
||||
270,
|
||||
1
|
||||
],
|
||||
"R609": [
|
||||
87.5518,
|
||||
108.5598,
|
||||
270,
|
||||
1
|
||||
],
|
||||
"R610": [
|
||||
76.5,
|
||||
116.25,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"R611": [
|
||||
95.0,
|
||||
85.5,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"R614": [
|
||||
12.827,
|
||||
73.152,
|
||||
90,
|
||||
1
|
||||
],
|
||||
"R615": [
|
||||
12.227,
|
||||
80.029,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"R616": [
|
||||
12.573,
|
||||
62.517,
|
||||
270,
|
||||
1
|
||||
],
|
||||
"R617": [
|
||||
14.478,
|
||||
69.723,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R660": [
|
||||
100.746,
|
||||
83.933,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R661": [
|
||||
98.298,
|
||||
83.947,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R706": [
|
||||
46.99,
|
||||
118.999,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R707": [
|
||||
44.196,
|
||||
118.999,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R708": [
|
||||
83.861,
|
||||
46.903,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R709": [
|
||||
101.031,
|
||||
50.167,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R710": [
|
||||
101.111,
|
||||
51.403,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"R711": [
|
||||
101.111,
|
||||
52.903,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"R712": [
|
||||
101.111,
|
||||
54.403,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"R716": [
|
||||
101.111,
|
||||
57.903,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"R717": [
|
||||
88.011,
|
||||
5.08,
|
||||
90,
|
||||
1
|
||||
],
|
||||
"R718": [
|
||||
101.346,
|
||||
5.461,
|
||||
90,
|
||||
1
|
||||
],
|
||||
"R719": [
|
||||
96.111,
|
||||
55.903,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"R720": [
|
||||
101.111,
|
||||
55.903,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"R721": [
|
||||
100.865,
|
||||
48.087,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R722": [
|
||||
76.5,
|
||||
118.0,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R733": [
|
||||
68.0,
|
||||
113.5,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R734": [
|
||||
76.0,
|
||||
113.5,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R746": [
|
||||
96.9214,
|
||||
122.5,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"R747": [
|
||||
100.103,
|
||||
116.73,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R748": [
|
||||
57.531,
|
||||
121.285,
|
||||
90,
|
||||
1
|
||||
],
|
||||
"R749": [
|
||||
58.0,
|
||||
119.5,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R750": [
|
||||
81.3736,
|
||||
122.5,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"R751": [
|
||||
78.5612,
|
||||
122.4798,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"R752": [
|
||||
52.0,
|
||||
120.0,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R753": [
|
||||
78.5,
|
||||
113.5,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R754": [
|
||||
91.5,
|
||||
113.5,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R755": [
|
||||
94.0,
|
||||
113.5,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R756": [
|
||||
95.103,
|
||||
116.73,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R757": [
|
||||
97.603,
|
||||
116.73,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R9A": [
|
||||
81.3077,
|
||||
4.4266,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"R9B": [
|
||||
81.3077,
|
||||
8.9266,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R9C": [
|
||||
88.5,
|
||||
13.4266,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"R9D": [
|
||||
88.5,
|
||||
17.9266,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"R9E": [
|
||||
88.5,
|
||||
22.4266,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"RT601": [
|
||||
91.186,
|
||||
73.787,
|
||||
90,
|
||||
1
|
||||
],
|
||||
"RT602": [
|
||||
25.654,
|
||||
8.509,
|
||||
270,
|
||||
1
|
||||
],
|
||||
"Rs6": [
|
||||
63.75,
|
||||
4.0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"U13": [
|
||||
63.75,
|
||||
10.5,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"U19": [
|
||||
58.5,
|
||||
116.5,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"U20": [
|
||||
93.0,
|
||||
97.0,
|
||||
270,
|
||||
1
|
||||
],
|
||||
"U29": [
|
||||
67.0,
|
||||
13.0,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"U30": [
|
||||
45.5,
|
||||
116.5,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"U31": [
|
||||
85.746,
|
||||
73.562,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"U32": [
|
||||
89.111,
|
||||
52.903,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"U35": [
|
||||
7.239,
|
||||
53.975,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"U6": [
|
||||
11.513,
|
||||
77.037,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"U7": [
|
||||
12.402,
|
||||
66.402,
|
||||
180,
|
||||
1
|
||||
],
|
||||
"Y1": [
|
||||
91.611,
|
||||
45.903,
|
||||
180,
|
||||
1
|
||||
]
|
||||
}
|
||||
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
@@ -0,0 +1,673 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
u"""R2 底板 PCB(M8):板框 + 分区 + 插槽阵列 + 安装孔。
|
||||
|
||||
/c/Python314/python .claude/r2/pcb_base.py # 只算 + 自检
|
||||
/c/Python314/python .claude/r2/pcb_base.py --outline # 画/重画板框(幂等)
|
||||
/c/Python314/python .claude/r2/pcb_base.py --holes # 打安装孔
|
||||
/c/Python314/python .claude/r2/pcb_base.py --verify # 读回校验
|
||||
|
||||
与 `pcb_blade.py` 是同一套接口(`PCB` / `BW` / `BH` / `ZONES` / `mill_cuts()` …),
|
||||
下游的 `pcb_place` / `router` / `pcb_pour` 用 `--base` 切到这一份。
|
||||
|
||||
## 坐标系
|
||||
|
||||
**板左下角 = (0, 0),x 向右、y 向上**(EasyEDA PCB 里 y 向上为正,方案 §22.5)。
|
||||
x = 槽距方向(5 槽并排),y = 槽长方向 / 板深。方案 §6.5 那张分区图可以直接抄。
|
||||
|
||||
## ⛔ 板框必须是**折线**不是导线
|
||||
|
||||
`pcb_PrimitiveLine` 在嘉立创里是**导线(铜)**。M4b~M6 的刀片板框就是这么画的:
|
||||
渲染成品红、DRC 干净、四道几何校验全过,而客户端根本不认它是板框
|
||||
(自动布线一路弹「请先绘制板框!」)。见方案 §27.3。
|
||||
⇒ 板框只用 `pcb_PrimitivePolyline`。底板文档里现成那 4 条 `Line`(建工程时的默认框)
|
||||
属于同一个坑,`push_outline()` 会把它们删掉。
|
||||
|
||||
## 刀片投影是硬碰撞,不是净距问题
|
||||
|
||||
插槽把刀片底边抬到底板上方 ~11 mm ⇒ **投影带内元件 ≤ 8 mm**(方案 §6.6)。
|
||||
两颗 470 µF 直立电解、DC-005 座、TH 排针 / 风扇座都只能放到投影带**外面**
|
||||
(`y < PROJ[0]` 或 `y > PROJ[1]`)。`zone_of()` / `height_ok()` 就是干这个的。
|
||||
"""
|
||||
import io
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
# ⚠ 顺序不能反:`.claude` 下有同名的 R1 脚本,先插 .claude 会 import 到 R1 那一份且不报错。
|
||||
sys.path.insert(0, os.path.dirname(HERE))
|
||||
sys.path.insert(0, HERE)
|
||||
from eda import run # noqa: E402
|
||||
import fp_edge # noqa: E402
|
||||
|
||||
PCB = '7b9524b6aaf6f235' # BASE-PCB(r2-uuids.json)
|
||||
MIL = fp_edge.MIL
|
||||
|
||||
# ── 板框(mm,出处 方案 §3.1)─────────────────────────────────────────
|
||||
BW, BH = 110.0, 125.0
|
||||
CORNER_R = 5.0 # 底板四角 R5(刀片是 R2)
|
||||
CORNER_SEG = 10
|
||||
OUTLINE_W = 0.15
|
||||
|
||||
# ══ M9 新坐标模型(2026-09-09 用户拍板)══════════════════════════════════
|
||||
# ⛔ **M8 的分带式布局(槽阵横排 + ctrl 带在北)在两层板上是死的**,
|
||||
# 根因见方案 §29.1:单颗 Hub 扭出 5 路 D±,在扇出带里形成一道**横贯全板的墙**
|
||||
# (`USB5`/`USB1` 共用 y=41.4 那条道,两段拼起来 x 24.5~105.1),
|
||||
# 而墙以北没有横向通道(5 个槽体 + 每槽两端的屏蔽脚把槽阵横向堵死)。
|
||||
# ⇒ 12V / 5V / 3V3 和 25 条槽上信号全都过不去,实测 173/314 条布不出来,
|
||||
# 四轮打平(173/174/175/173),换 `BOT_PEN` 无效(178,更差)。
|
||||
#
|
||||
# ⇒ **槽阵转 90°**:槽体长轴沿 x,五片刀片沿 y 堆叠。于是
|
||||
# **D± 走西缘、电源走东缘,走廊里全是平行车道,一个交叉都没有。**
|
||||
#
|
||||
# 为什么这条能成立(针位实测,见 §29.2):把槽转成 A1 端**朝东**之后,
|
||||
# 每只脚落在哪一列 x 是定死的 ——
|
||||
# x 71.5 +12V(5) ← 紧贴东缘,12V 铜条只进走廊 4.5 mm
|
||||
# x 66.5 SCL/SDA x 64.0 +3V3(3) x 61.5 EFUSE_EN / INT#
|
||||
# x 56.0 D± x 54.5 VDAC x 51.5 / 38.5 +5V
|
||||
# x 39.5 PRSNT#
|
||||
# **x > 69 只有 `+12V` 一个网** ⇒ 12V 的铜条可以在走廊东端做成「顶天立地」的一堵墙
|
||||
# (同时够到上下两排焊盘),而不挡任何别的东西:其余全部从西边进来,最远只到 x=67。
|
||||
SLOT_PITCH = 20.32 # 刀片堆叠间距(沿 y),= 手机+刀片+间隙,机械定死
|
||||
SLOT_N = 5
|
||||
SLOT_ROT = 0 # 2026-09-10 用户要求:五个插槽从 rot 180 再转 180°
|
||||
# ⚠ 槽体长轴中心**不在板中心**:西区要塞下 ESP32 的「天线 7.5 + 焊盘 19 = 26.5」,
|
||||
# 所以整条阵列往东挪 3 mm。刀片 98 沿 x ⇒ 盖住 x 9~107,两边各留 3 mm。
|
||||
# ⛔ 再东移 2 mm(58→60):D± 通道要塞下**三条**并行车道。一对 1.2 mm 的线在占用图里
|
||||
# 要 `1.41 + 2×0.81 = 3.03 mm`,三条 = 9.6,8 mm 的通道装不下 —— 现象是每对永远
|
||||
# 只进得去一根(「DP 通、DM 出不来」,七对里六对)。刀片随之盖住 x 11~109。
|
||||
# ⛔ **2026-09-09 用户拍板 A 方案**:通道从 10 加宽到 13.5,槽阵再东移 3.5(60→63.5)。
|
||||
# 账:一对 1.0 mm 的 D± 在占用图里要 `1.21 + 2×0.71 = 2.63`;车道还要能被一根
|
||||
# 0.25 的逃逸线横穿(吃掉 1.67)而仍装得下一对 ⇒ 车道 4.5,三条 = 13.5。
|
||||
# 代价:刀片(98 宽)盖住 x 14.5~112.5,**伸出东板边 2.5 mm**(用户已确认)。
|
||||
SLOT_CX = 46.5 # 插槽翻转后整体西移,东侧留 13.5 mm D± 通道
|
||||
# ⚠ 阵列中心比板中心(62.5)**往北挪 1 mm**:南带要塞下 DC-005 的 14.5 mm 桶身,外加 0.6 净距,
|
||||
# 17.26 差 1 mm。北带少 1 mm 不影响(风扇块全是小件)。
|
||||
# M13:整组北移 3 mm,为最南槽 USB5 与 9 A 输入铜之间留出独立顶层走廊。
|
||||
# 槽距不变,并保留北侧辅助 Buck / 风扇服务带。
|
||||
ARRAY_CY = 66.5
|
||||
SLOT_BODY_L = 42.0 # 槽体沿 **x**(实测 `PCIE-64P11L` 焊盘外框)
|
||||
SLOT_BODY_W = 9.2 # 槽体沿 **y**
|
||||
# 两排焊盘:y = 槽心 ± 3.6;单盘 0.7(x) × 2.0(y),脚距 1.0
|
||||
PAD_ROW_DY = 3.6
|
||||
PAD_L, PAD_W = 0.7, 2.0
|
||||
# 长轴上的针位(u = 距槽体中心,正 = 西)。A1 端朝东 ⇒ 板上 x = SLOT_CX − u。
|
||||
PIN_U = {'+12V': (-16.5, -14.5), 'I2C': (-12.5, -11.5), '+3V3': (-9.5, -7.5),
|
||||
'EN': (-6.5, -6.5), 'DIFF': (-1.5, -0.5), 'VDAC': (0.5, 0.5),
|
||||
'+5V': (3.5, 16.5), 'PRSNT': (15.5, 15.5)}
|
||||
|
||||
|
||||
def pin_x(u):
|
||||
u"""长轴坐标 u(封装)→ 板上 x。rot 0 与 rot 180 互为镜像。"""
|
||||
return round(SLOT_CX + u if SLOT_ROT == 0 else SLOT_CX - u, 3)
|
||||
|
||||
|
||||
def slot_y(i):
|
||||
u"""槽 i(1 = 最南)的中心 y。"""
|
||||
return round(ARRAY_CY + (i - (SLOT_N + 1) / 2.0) * SLOT_PITCH, 3)
|
||||
|
||||
|
||||
def slot_ys():
|
||||
return [slot_y(i) for i in range(1, SLOT_N + 1)]
|
||||
|
||||
|
||||
def slot_body(i):
|
||||
y = slot_y(i)
|
||||
return (SLOT_CX - SLOT_BODY_L / 2, y - SLOT_BODY_W / 2,
|
||||
SLOT_CX + SLOT_BODY_L / 2, y + SLOT_BODY_W / 2)
|
||||
|
||||
|
||||
# ── 走廊:槽体之间那 11.12 mm,外加阵列两头 ────────────────────────────────
|
||||
# 走廊 k(0 = 阵列最南那条,5 = 最北)。内部四条各服务上下两个槽的一排焊盘。
|
||||
def corridor(k):
|
||||
y0 = 0.0 if k == 0 else slot_y(k) + SLOT_BODY_W / 2
|
||||
y1 = BH if k == SLOT_N else slot_y(k + 1) - SLOT_BODY_W / 2
|
||||
return (0.0, round(y0, 3), BW, round(y1, 3))
|
||||
|
||||
|
||||
def corridors():
|
||||
return [corridor(k) for k in range(SLOT_N + 1)]
|
||||
|
||||
|
||||
# ── 三条纵向分区(沿 x)───────────────────────────────────────────────────
|
||||
# 西区:12V 干道;中区:5 个槽体;东区:D± 通道 + Hub / 控制器。
|
||||
WEST = (0.0, 25.5) # 西侧 12V 服务区
|
||||
MID = (SLOT_CX - SLOT_BODY_L / 2, SLOT_CX + SLOT_BODY_L / 2) # 34 ~ 76
|
||||
EAST = (81.0, BW) # 东侧 Hub / ESP32 / mux / DAC 功能区
|
||||
|
||||
# 逻辑 USB 号 -> 插槽位号 / 物理槽位(南起 1..5)。
|
||||
# 插槽翻转后物理顺序与 USB 号反向;混用会让五对 D± 全交叉。
|
||||
USB_SLOT_REF = {1: 'J34', 2: 'J35', 3: 'J36', 4: 'J37', 5: 'J38'}
|
||||
USB_SLOT_POS = {i: SLOT_N + 1 - i for i in range(1, SLOT_N + 1)}
|
||||
|
||||
|
||||
def usb_corridor(i):
|
||||
u"""USB i 的 D± 所在插槽南侧走廊。"""
|
||||
return corridor(USB_SLOT_POS[i] - 1)
|
||||
|
||||
# ── 刀片投影:每片刀片盖住 x 6~104,沿 y 占 ±7.35 ──────────────────────────
|
||||
# 保守起见整条阵列带按投影处理(限高 8 mm,方案 §6.6)。
|
||||
BLADE_W = 98.0 # 刀片沿 x
|
||||
BLADE_X0 = round(SLOT_CX - BLADE_W / 2, 2) # 6.0
|
||||
PROJ = (round(slot_y(1) - SLOT_BODY_W / 2, 2),
|
||||
round(slot_y(SLOT_N) + SLOT_BODY_W / 2, 2)) # 17.26 ~ 107.74
|
||||
H_PROJ = 8.0
|
||||
|
||||
|
||||
def blade_x_to_base_x(bx):
|
||||
u"""刀片自身坐标 x → 底板 x(刀片 98 沿 x 铺开,居中对齐槽体)。"""
|
||||
return round(BLADE_X0 + bx, 3)
|
||||
|
||||
|
||||
# ── 分区(y 方向只剩三条:南带 / 阵列带 / 北带)────────────────────────────
|
||||
ZONES = {
|
||||
'input': (0.0, 0.0, BW, PROJ[0]), # 投影外,可放高件:DC 座 / F1 / Q1 / Φ8 电解
|
||||
'array': (0.0, PROJ[0], BW, PROJ[1]), # 槽体 + 走廊 + 西区器件 + 东区高速路
|
||||
'fan': (0.0, PROJ[1], BW, BH), # 投影外:风扇座 ×2 / 驱动 / J33 应急口
|
||||
}
|
||||
|
||||
# ── 12V(9 A):南带进料 → 西侧高速路 → 每条走廊分段进槽 ────────────────
|
||||
# M12 当前布局把 U35/U6/U7 放在 x=10~15;主干只能使用它们与槽体之间
|
||||
# x=15.5~24.5 的连续通道。实测无异网焊盘侵入,9 mm 净宽仍高于 8 mm 要求。
|
||||
# ⛔ 北端只到 y=110:再往北是风扇驱动 `Q6`/`Q7`(它们本来就该贴着风扇座),
|
||||
# 高速路铺过去会被它们的非 12V 脚掐到 **5.70 mm**(断面校核实测)。
|
||||
# 那一段只带风扇 + 两颗 Buck(~3 A,5.7 mm 够),但校核按 9 A 一刀切 ⇒
|
||||
# 与其放宽判据,不如让干道在 110 收口,北边的 12V 由走廊 5 那堵铜墙供。
|
||||
# ⛔ 铜墙必须**伸进上下两个槽体 2.0 mm**:焊盘中心距槽心 3.6、盘高 2.0,也就是占了槽体边缘往里 0~2.0 那一段。
|
||||
# 只画走廊本身的话**一只 12V 脚都碰不到**,而铺铜管理器 / DRC 都不会响
|
||||
# (只有 `board_check` 从板上读回铜做并查集才看得见)。
|
||||
RIB_OVER = 1.4
|
||||
# 旧 BUS_S 与定稿后的 J1/F1/Q1 输入链重叠,会把 VIN_RAW 直接短到 +12V。
|
||||
# 干道改从投影带南沿开始,Rs6 -> 干道由路由器单独连接。
|
||||
# 南端避开 y=16.25 的 2 mm VIN_SW 引线;向东加宽后,扣除其净距仍超过 8 mm。
|
||||
# 东界距 x=27、宽 3 mm 的槽外壳 GND 焊盘保留 0.25 mm 净距。
|
||||
# RIB_X 保持从 x=24.5 起,与主干重叠 0.75 mm,并继续避开 x=33 的 GND 针。
|
||||
BUS_W = (15.5, 18.75, 25.25, BH - 0.5)
|
||||
RIB_X = (24.5, 32.4)
|
||||
|
||||
|
||||
def rib_rects():
|
||||
u"""五个插槽的 12V 分段铜墙。
|
||||
|
||||
下排是 x=30/31/32,上排是 x=31/32;上排 x=30 紧邻 GND,槽心 x=27
|
||||
还有巨大的外壳 GND 盘。所以不能再用「整条走廊顶天立地」的矩形。
|
||||
每排用窄指覆盖电源脚,再在排外用宽帽接回西侧干道。
|
||||
"""
|
||||
out = []
|
||||
c = 0.25
|
||||
row_o = PAD_ROW_DY + PAD_W / 2
|
||||
row_i = PAD_ROW_DY - PAD_W / 2
|
||||
x_lower = round(pin_x(-16.5) - PAD_L / 2 - 0.05, 3) # x=30 焊盘左侧
|
||||
x_upper = round(pin_x(-15.5) - PAD_L / 2 - 0.05, 3) # x=31 焊盘左侧
|
||||
for i in range(1, SLOT_N + 1):
|
||||
cy = slot_y(i)
|
||||
cap_lo = round(cy - row_o - RIB_OVER, 3)
|
||||
cap_hi = round(cy - row_o - c, 3)
|
||||
# 最南宽帽直接接到主干,并停在 J38 外壳 GND 的净距边界以内。
|
||||
if i == 1:
|
||||
cap_lo = BUS_W[1]
|
||||
top_hi = round(cy + row_o + RIB_OVER, 3)
|
||||
# 北移后的 J34 靠近 C734;北帽只需覆盖针排并接到主干,不再向北多伸。
|
||||
if i == SLOT_N:
|
||||
top_hi = round(cy + row_o + 0.9, 3)
|
||||
out += [
|
||||
(RIB_X[0], cap_lo, RIB_X[1], cap_hi),
|
||||
(x_lower, round(cy - row_o - c - 0.05, 3), RIB_X[1],
|
||||
round(cy - row_i + c + 0.05, 3)),
|
||||
(x_upper, round(cy + row_i - c - 0.05, 3), RIB_X[1],
|
||||
round(cy + row_o + c + 0.05, 3)),
|
||||
(RIB_X[0], round(cy + row_o + c, 3), RIB_X[1], top_hi),
|
||||
]
|
||||
return out
|
||||
|
||||
|
||||
def bus_rects():
|
||||
return [BUS_W] + rib_rects()
|
||||
|
||||
|
||||
# ── +3V3 铜墙(M9b,2026-09-10 用户拍板)───────────────────────────────────
|
||||
# `+3V3` 是全板最大的网(47 只脚),A* 当一棵树布 ⇒ **整网崩**(M9 实测 46/47 没布通)。
|
||||
# 办法与 12V 同:把脚位那一段直接做成铜,脚从 A* 里摘掉。
|
||||
#
|
||||
# ⛔ 但 3V3 的墙**不能像 12V 那样顶天立地**,因为它不在东缘尽头,而在信号中间:
|
||||
# 南排 … EN(70) 3V3(71) 3V3(72) NC(73) NC(74) NC(75) NC(76) GND(77) 12V(78,79) …
|
||||
# 北排 … INT#(70) NC(71) NC(72) 3V3(73) GND(74) SDA(75) SCL(76) GND(77) 12V(78~80)
|
||||
# 每个槽的 `SDA_i`/`SCL_i` 在**北排 x 75/76**,要一路往西去 `U35`(x≈10~16) ——
|
||||
# 任何一条从 x>73 一直连到 x<70 的**顶层**南北向铜,都会把这十根线拦腰截断。
|
||||
# ⇒ 墙只在**槽体那一段**(顶层,够着三只脚),走廊那一段**走底层桥**(0.8 mm + 两个过孔)。
|
||||
# 代价是地平面上多五道 9.5 mm 长的窄缝(都在 x≈72,D± 全在 x<66,不碰 §6.2 第 1 条)。
|
||||
#
|
||||
# ⛔ 墙的 x 是**逐排不同的**(同一个矩形盖不住两排):
|
||||
# 南排的 3V3 在 71/72、北排的在 73,而它们各自旁边就是**没有网的 NC 针**
|
||||
# (南排 73、北排 71/72)。一个 70.6~73.4 的方块会同时压上 NC 针 ——
|
||||
# 铺铜会绕开它们、把墙在排上切碎,而铺铜管理器和 DRC 都只说「有铜」。
|
||||
# ⇒ 拆成「南窄 / 中宽 / 北窄」三段同网矩形(同网重叠是允许的,12V 那几块也是这么接的)。
|
||||
W3V3_CLR = 0.25 # 到异网针的净距(规则底线 0.20,留 0.05)
|
||||
W3V3_OVER = 1.4 # 墙伸进走廊多少:给底层桥的过孔留地方
|
||||
W3V3_BRIDGE_W = 0.8 # 走廊底层桥的线宽(3V3 全板 ~0.3 A,0.8 够 2 A)
|
||||
W3V3_VIA_DY = 5.4 # 过孔离槽心多远(针排外沿 4.6 + 0.55 环 + 余量)
|
||||
|
||||
|
||||
def wall3v3_rects():
|
||||
u"""五块 `+3V3` 槽墙,每块三段(南窄 / 中宽 / 北窄)+ 两块走廊帽。
|
||||
|
||||
x 的来历(全部从 `PIN_U` 算,不手抄):
|
||||
西边界 = `EN` 针右沿 + 净距;东边界 = `GND(74)` 针左沿 − 净距;
|
||||
南段东界 = 南排 NC(73) 针左沿 − 净距;北段西界 = 北排 NC(72) 针右沿 − 净距。
|
||||
"""
|
||||
c = W3V3_CLR
|
||||
# 新方向是旧铜墙相对槽心的水平镜像。
|
||||
x_w = round(SLOT_CX - 9.9, 3)
|
||||
x_e = round(SLOT_CX - 7.1, 3)
|
||||
x_s_e = round(SLOT_CX - 9.1, 3)
|
||||
x_n_w = round(SLOT_CX - 8.9, 3)
|
||||
row_o = PAD_ROW_DY + PAD_W / 2 # 针排外沿 4.6
|
||||
row_i = PAD_ROW_DY - PAD_W / 2 # 针排内沿 2.6
|
||||
out = []
|
||||
for i in range(1, SLOT_N + 1):
|
||||
cy = slot_y(i)
|
||||
out += [
|
||||
# 走廊帽(排外,没有任何针):整宽,给桥的过孔一个圆整的落点
|
||||
(x_w, round(cy - row_o - W3V3_OVER, 3), x_e, round(cy - row_o - c, 3)),
|
||||
# 南段:从走廊穿过南排(盖住 71/72)进到两排之间
|
||||
(x_w, round(cy - row_o - c - 0.05, 3), x_s_e, round(cy - row_i + c + 0.05, 3)),
|
||||
# 中段:两排之间那 5.2 mm,整宽
|
||||
(x_w, round(cy - row_i + c, 3), x_e, round(cy + row_i - c, 3)),
|
||||
# 北段:穿过北排(盖住 73)
|
||||
(x_n_w, round(cy + row_i - c - 0.05, 3), x_e, round(cy + row_o + c + 0.05, 3)),
|
||||
(x_w, round(cy + row_o + c, 3), x_e, round(cy + row_o + W3V3_OVER, 3)),
|
||||
]
|
||||
return out
|
||||
|
||||
|
||||
def wall3v3_bridges():
|
||||
u"""走廊里的**底层**桥:[(x, y0, y1, 宽)],以及两端的过孔 [(x, y)]。
|
||||
|
||||
只连相邻两块槽墙(4 段)。北端到 `U19`/`L2` 那一段交给 A*:
|
||||
它是一条普通的短网,不像槽脚那样被十根 I²C 挡着。
|
||||
"""
|
||||
x = round(SLOT_CX - 8.5, 3) # 38.0,新方向的 3V3 墙中心
|
||||
segs, vias = [], []
|
||||
for i in range(1, SLOT_N):
|
||||
y0 = round(slot_y(i) + W3V3_VIA_DY, 3)
|
||||
y1 = round(slot_y(i + 1) - W3V3_VIA_DY, 3)
|
||||
segs.append((x, y0, y1, W3V3_BRIDGE_W))
|
||||
vias += [(x, y0), (x, y1)]
|
||||
return segs, vias
|
||||
|
||||
|
||||
# ── D± 竖向扇出通道(西区):`U32` 在西区中部,五对沿 y 散开再往东进走廊 ────
|
||||
# ⛔ 这条带里**一件都不许放**(同 M8 的 FANOUT,理由一样:排布器会把去耦塞满,
|
||||
# 然后布线器报「某一对布了 0 段」,而摆位自检 / DRC / 几何验证一条都不响)。
|
||||
DP_CHAN = (67.5, PROJ[0], 81.0, PROJ[1])
|
||||
# Hub USB6 -> ESP32 原生 USB 的北向短通道。U20/U32 是端点,其他元件不得进入。
|
||||
USB6_CHAN = (80.5, 78.0, 84.8, 107.2)
|
||||
|
||||
# ⛔ 阵列**北边**还要留一条 4 mm 的过道:最北那个槽(槽 5)的 D± 焊盘在它的北排,
|
||||
# 北侧走廊就是风扇带本身。不留的话逃逸落点会掉进风扇块里 ——
|
||||
# 实测 `J38.launch` 起步格点 **0**,现象是「差分对 USB5 布了 0 段」。
|
||||
DP_N_LANE = (MID[0], PROJ[1], BW, PROJ[1] + 4.0)
|
||||
|
||||
|
||||
# ── 走廊「喉口」:西区器件与 D± 通道之间那一段,**一件都不许放** ────────────
|
||||
# ⛔ 2026-09-09 用户看出来的:`R605~R608` / `C9` / `C731` 那一窝落在走廊 2 的
|
||||
# y 段、x 20~28 上 —— 那正是西区**所有**去往槽 2 的信号(含它那一对 D±)
|
||||
# 往东挤进通道的唯一喉口。堵在这儿的后果是布线器报「出不来 / 没布通」,
|
||||
# 而摆位自检、DRC、几何校验一条都不会响。
|
||||
# ⇒ 每条内走廊在 x 17~29 这一段设成摆位禁区(器件退到槽体那几段 y 上去)。
|
||||
# ⚠ 宽度是权衡出来的:17~29(12 mm)会把西区挤爆(去耦平均从 22.9 涨到 33.3 mm),
|
||||
# 而喉口只要够信号穿过去就行 —— 6 mm 在 0.5 间距下可过十根线,够一条走廊的量。
|
||||
THROAT_X = (DP_CHAN[2], 87.0)
|
||||
|
||||
|
||||
# `U32` 东沿到 D± 通道之间那条**逃逸扇形带**:十根 0.15 的细线从 QFN 脚扇到车道,
|
||||
# 一件都不许放(放了就是 DRC「焊盘到导线」,实测 22 条压在 C602/C607 上)。
|
||||
HUB_ESC = (DP_CHAN[2], 55.0, 86.0, 78.0)
|
||||
|
||||
|
||||
def throat_rects():
|
||||
out = []
|
||||
for k in range(1, SLOT_N):
|
||||
c = corridor(k)
|
||||
out.append((THROAT_X[0], c[1], THROAT_X[1], c[3]))
|
||||
return out
|
||||
|
||||
|
||||
def keepout_rects():
|
||||
u"""摆位禁区(不是铺铜禁区):12V 高速路 + 铜墙 + 3V3 槽墙 + D± 竖向通道 + 天线缺口。"""
|
||||
return (bus_rects() + wall3v3_rects()
|
||||
+ [DP_CHAN, USB6_CHAN, DP_N_LANE, ANT_NOTCH, HUB_ESC] + throat_rects())
|
||||
|
||||
|
||||
# ── ESP32 天线缺口(西板边)────────────────────────────────────────────────
|
||||
# 见 §28.14:`U20` 转 rot=90 天线朝西、天线那 7.5 mm 下方板材整块挖掉。
|
||||
# 新布局里 `U20` 挪到西北(靠近风扇带的 FAN_*/NTC 和 J33),缺口跟着走。
|
||||
ANT_NOTCH = (BW - 7.5, 86.0, BW, 108.0)
|
||||
|
||||
|
||||
def notch_rect():
|
||||
return ANT_NOTCH
|
||||
|
||||
|
||||
# ── 安装孔(M3 尼龙立柱,NPTH)────────────────────────────────────────
|
||||
HOLE_D = 3.2
|
||||
HOLE_KEEP = 3.2
|
||||
HOLE_INSET = 4.0
|
||||
MOUNT_HOLES = {
|
||||
'H1': (HOLE_INSET, HOLE_INSET),
|
||||
'H2': (BW - HOLE_INSET, HOLE_INSET),
|
||||
'H3': (HOLE_INSET, BH - HOLE_INSET),
|
||||
'H4': (BW - HOLE_INSET, BH - HOLE_INSET),
|
||||
}
|
||||
|
||||
# ── 下游脚本要的空壳 ─────────────────────────────────────────────────────
|
||||
FLEX_W = {}
|
||||
SLOT_Y = None
|
||||
PHONE_ORG = None
|
||||
PHONE_WH = None
|
||||
|
||||
|
||||
def phone_holes():
|
||||
u"""两层都不许压的孔:四个 M3 安装孔(它们是 `pcb_PrimitivePad`,不在 pads.json 里)。"""
|
||||
return dict(MOUNT_HOLES)
|
||||
|
||||
|
||||
def mill_cuts():
|
||||
return []
|
||||
|
||||
|
||||
def notch_break():
|
||||
return None
|
||||
|
||||
|
||||
def corner(cx, cy, a0, a1):
|
||||
pts = []
|
||||
for k in range(CORNER_SEG + 1):
|
||||
a = math.radians(a0 + (a1 - a0) * k / CORNER_SEG)
|
||||
pts.append((cx + CORNER_R * math.cos(a), cy + CORNER_R * math.sin(a)))
|
||||
return pts
|
||||
|
||||
|
||||
def outline_pts():
|
||||
u"""板框点列(逆时针,从底边左端 (r,0) 起,闭合回 (r,0))。东边带天线缺口。"""
|
||||
r = CORNER_R
|
||||
pts = [(r, 0.0), (BW - r, 0.0)]
|
||||
pts += corner(BW - r, r, 270, 360)
|
||||
pts += [(BW, ANT_NOTCH[1]), (ANT_NOTCH[0], ANT_NOTCH[1]),
|
||||
(ANT_NOTCH[0], ANT_NOTCH[3]), (BW, ANT_NOTCH[3]),
|
||||
(BW, BH - r)]
|
||||
pts += corner(BW - r, BH - r, 0, 90)
|
||||
pts += [(r, BH)]
|
||||
pts += corner(r, BH - r, 90, 180)
|
||||
pts += [(0.0, r)]
|
||||
pts += corner(r, r, 180, 270)
|
||||
out = [pts[0]]
|
||||
for q in pts[1:]:
|
||||
if abs(q[0] - out[-1][0]) > 1e-9 or abs(q[1] - out[-1][1]) > 1e-9:
|
||||
out.append(q)
|
||||
assert abs(out[0][0] - out[-1][0]) < 1e-9 and abs(out[0][1] - out[-1][1]) < 1e-9, u'板框点列没有闭合'
|
||||
return out
|
||||
|
||||
|
||||
def outline_polygon():
|
||||
return outline_pts()
|
||||
|
||||
|
||||
def zone_of(y):
|
||||
for k, z in ZONES.items():
|
||||
if z[1] <= y < z[3]:
|
||||
return k
|
||||
return None
|
||||
|
||||
|
||||
def height_ok(y, h, x=None):
|
||||
u"""该位置允许 h mm 高的件吗(刀片投影是二维硬碰撞)。"""
|
||||
in_y = PROJ[0] <= y <= PROJ[1]
|
||||
in_x = x is None or BLADE_X0 <= x <= BLADE_X0 + BLADE_W
|
||||
return not (in_x and in_y and h > H_PROJ)
|
||||
|
||||
|
||||
def selfcheck():
|
||||
bad = []
|
||||
ys = slot_ys()
|
||||
# 阵列必须居中、间距是机械定死的 20.32
|
||||
for a, b in zip(ys, ys[1:]):
|
||||
if abs((b - a) - SLOT_PITCH) > 1e-6:
|
||||
bad.append(u'槽间距 %.3f != %.2f' % (b - a, SLOT_PITCH))
|
||||
if abs((ys[0] + ys[-1]) / 2.0 - ARRAY_CY) > 1e-6:
|
||||
bad.append(u'阵列没居中: %s' % ys)
|
||||
# 槽体不许出板边(南北两头)
|
||||
if ys[0] - SLOT_BODY_W / 2 < 2.0 or ys[-1] + SLOT_BODY_W / 2 > BH - 2.0:
|
||||
bad.append(u'槽体贴板边太近: %.2f / %.2f'
|
||||
% (ys[0] - SLOT_BODY_W / 2, ys[-1] + SLOT_BODY_W / 2))
|
||||
# 槽体长轴不许出板边(东西两头),且要给东区高速路让出地方
|
||||
if SLOT_CX - SLOT_BODY_L / 2 < WEST[1] - 1e-6 or SLOT_CX + SLOT_BODY_L / 2 > EAST[0] + 1e-6:
|
||||
bad.append(u'槽体 x %.1f~%.1f 越过了西/东区边界 %.1f / %.1f'
|
||||
% (SLOT_CX - SLOT_BODY_L / 2, SLOT_CX + SLOT_BODY_L / 2, WEST[1], EAST[0]))
|
||||
# 走廊必须首尾相接、铺满板
|
||||
cs = corridors()
|
||||
for a, b in zip(cs, cs[1:]):
|
||||
if a[3] > b[1] + 1e-6:
|
||||
bad.append(u'走廊重叠: %s / %s' % (a, b))
|
||||
if abs(cs[0][1]) > 1e-6 or abs(cs[-1][3] - BH) > 1e-6:
|
||||
bad.append(u'走廊没铺满 0~%.1f' % BH)
|
||||
for k in range(1, SLOT_N):
|
||||
w = cs[k][3] - cs[k][1]
|
||||
if abs(w - (SLOT_PITCH - SLOT_BODY_W)) > 1e-6:
|
||||
bad.append(u'走廊 %d 净宽 %.2f != %.2f' % (k, w, SLOT_PITCH - SLOT_BODY_W))
|
||||
# 分区必须首尾相接
|
||||
ks = sorted(ZONES, key=lambda k: ZONES[k][1])
|
||||
for a, b in zip(ks, ks[1:]):
|
||||
if abs(ZONES[a][3] - ZONES[b][1]) > 1e-6:
|
||||
bad.append(u'分区 %s 顶 %.1f != %s 底 %.1f' % (a, ZONES[a][3], b, ZONES[b][1]))
|
||||
if abs(ZONES[ks[0]][1]) > 1e-6 or abs(ZONES[ks[-1]][3] - BH) > 1e-6:
|
||||
bad.append(u'分区没铺满 0~%.1f' % BH)
|
||||
if abs(PROJ[0] - ZONES['input'][3]) > 1e-6 or abs(PROJ[1] - ZONES['fan'][1]) > 1e-6:
|
||||
bad.append(u'刀片投影 %s 与 input/fan 带边界对不上' % (PROJ,))
|
||||
# ⛔ 这条是新布局能成立的**判据本身**:x > RIB_X[0] 只许有 +12V。
|
||||
# 别的网只要有一只脚落到铜墙的 x 区间里,那堵墙就不能顶天立地了。
|
||||
for tag, (u0, u1) in PIN_U.items():
|
||||
if tag == '+12V':
|
||||
continue
|
||||
for u in (u0, u1):
|
||||
if pin_x(u) < RIB_X[1] + 0.5:
|
||||
bad.append(u'%s 的针落在 x=%.1f,侵入 12V 铜墙区(x<%.1f 只许有 +12V)'
|
||||
% (tag, pin_x(u), RIB_X[1]))
|
||||
# 12V 铜墙要够得着 12V 针
|
||||
if not (RIB_X[0] < pin_x(PIN_U['+12V'][0]) < RIB_X[1]):
|
||||
bad.append(u'12V 铜墙 x %.1f~%.1f 够不着 12V 针 x=%.1f'
|
||||
% (RIB_X[0], RIB_X[1], pin_x(PIN_U['+12V'][0])))
|
||||
# 东缘高速路净宽(1 oz 外层 9 A ΔT20K 要 ≥8 mm)
|
||||
if BUS_W[2] - BUS_W[0] < 8.0:
|
||||
bad.append(u'西缘 12V 高速路只有 %.1f mm,9 A 要 ≥8' % (BUS_W[2] - BUS_W[0]))
|
||||
if [USB_SLOT_REF[i] for i in range(1, SLOT_N + 1)] != ['J34', 'J35', 'J36', 'J37', 'J38']:
|
||||
bad.append(u'USB -> 插槽位号映射被改坏')
|
||||
for i in range(1, SLOT_N + 1):
|
||||
c = usb_corridor(i)
|
||||
if c[0] >= c[2] or c[1] >= c[3]:
|
||||
bad.append(u'USB%d 的南侧走廊无效: %s' % (i, c))
|
||||
# 安装孔:不许出板 / 压槽体 / 被圆角切 / 落进天线缺口
|
||||
for n, (hx, hy) in MOUNT_HOLES.items():
|
||||
if not (HOLE_KEEP < hx < BW - HOLE_KEEP and HOLE_KEEP < hy < BH - HOLE_KEEP):
|
||||
bad.append(u'安装孔 %s 出板或贴边: (%.1f, %.1f)' % (n, hx, hy))
|
||||
for i in range(1, SLOT_N + 1):
|
||||
b0, b1, b2, b3 = slot_body(i)
|
||||
if b0 - HOLE_KEEP < hx < b2 + HOLE_KEEP and b1 - HOLE_KEEP < hy < b3 + HOLE_KEEP:
|
||||
bad.append(u'安装孔 %s 压到槽 %d' % (n, i))
|
||||
for cx, cy in ((CORNER_R, CORNER_R), (BW - CORNER_R, CORNER_R),
|
||||
(CORNER_R, BH - CORNER_R), (BW - CORNER_R, BH - CORNER_R)):
|
||||
out_x = hx < cx if cx < BW / 2 else hx > cx
|
||||
out_y = hy < cy if cy < BH / 2 else hy > cy
|
||||
if out_x and out_y and math.hypot(hx - cx, hy - cy) > CORNER_R - HOLE_KEEP:
|
||||
bad.append(u'安装孔 %s 被 R%.0f 圆角切到' % (n, CORNER_R))
|
||||
if (ANT_NOTCH[0] - HOLE_KEEP < hx < ANT_NOTCH[2] + HOLE_KEEP and
|
||||
ANT_NOTCH[1] - HOLE_KEEP < hy < ANT_NOTCH[3] + HOLE_KEEP):
|
||||
bad.append(u'安装孔 %s 落进 ESP32 天线缺口 (%.1f, %.1f)' % (n, hx, hy))
|
||||
sep = chr(10) + ' '
|
||||
assert not bad, u'底板自检不过:' + sep + sep.join(bad)
|
||||
return True
|
||||
|
||||
|
||||
# ── 下发 ──────────────────────────────────────────────────────────────
|
||||
def call(js, timeout=180):
|
||||
r = run(js, timeout=timeout)
|
||||
if not r.get('success'):
|
||||
raise RuntimeError(json.dumps(r, ensure_ascii=False)[:800])
|
||||
return r['result']
|
||||
|
||||
|
||||
def mil(v):
|
||||
return round(v * MIL, 4)
|
||||
|
||||
|
||||
def polygon_src(pts):
|
||||
a = [mil(pts[0][0]), mil(pts[0][1])]
|
||||
for q in pts[1:]:
|
||||
a += ['L', mil(q[0]), mil(q[1])]
|
||||
if pts[0] != pts[-1]:
|
||||
a += ['L', mil(pts[0][0]), mil(pts[0][1])]
|
||||
return a
|
||||
|
||||
|
||||
def push_outline():
|
||||
u"""板框 = 一条闭合折线;顺带删掉建工程时留下的 4 条层 11 **导线**(§27.3 的坑)。"""
|
||||
src = polygon_src(outline_polygon())
|
||||
js = ("await eda.dmt_EditorControl.openDocument('%s');" % PCB +
|
||||
"await new Promise(r=>setTimeout(r,2500));" +
|
||||
"const oldL = (await eda.pcb_PrimitiveLine.getAll()||[]).filter(l=>l.layer===11)"
|
||||
".map(l=>l.primitiveId);" +
|
||||
"if (oldL.length) await eda.pcb_PrimitiveLine.delete(oldL);" +
|
||||
"const oldP = (await eda.pcb_PrimitivePolyline.getAll()||[])"
|
||||
".filter(p=>p.getState_Layer()===11).map(p=>p.getState_PrimitiveId());" +
|
||||
"if (oldP.length) await eda.pcb_PrimitivePolyline.delete(oldP);" +
|
||||
"const poly = await eda.pcb_MathPolygon.createPolygon(%s);" % json.dumps(src) +
|
||||
"const r = await eda.pcb_PrimitivePolyline.create('', 11, poly, %s);" % mil(OUTLINE_W) +
|
||||
"await eda.pcb_Document.save();" +
|
||||
"return {delLine: oldL.length, delPoly: oldP.length, made: !!r,"
|
||||
" now: (await eda.pcb_PrimitivePolyline.getAll()||[])"
|
||||
".filter(p=>p.getState_Layer()===11).length,"
|
||||
" line11: (await eda.pcb_PrimitiveLine.getAll()||[]).filter(l=>l.layer===11).length};")
|
||||
r = call(js, timeout=300)
|
||||
print(u'板框折线: %s(删旧导线 %d / 旧折线 %d);层 11 现有折线 %d / 导线 %d'
|
||||
% (u'建好' if r['made'] else u'失败', r['delLine'], r['delPoly'], r['now'], r['line11']))
|
||||
if not r['made'] or r['now'] != 1:
|
||||
raise SystemExit(u'!! 板框没画成')
|
||||
return r
|
||||
|
||||
|
||||
PUSH_HOLES_JS = u"""
|
||||
await eda.dmt_EditorControl.openDocument('%s');
|
||||
await new Promise(r=>setTimeout(r,2500));
|
||||
const H = %s, D = %s;
|
||||
for (const p of await eda.pcb_PrimitivePad.getAll()) {
|
||||
const num = p.getState_PadNumber();
|
||||
if (typeof num === 'string' && /^H[1-9]$/.test(num))
|
||||
await eda.pcb_PrimitivePad.delete(p.getState_PrimitiveId());
|
||||
}
|
||||
let k = 0;
|
||||
for (const [name, x, y] of H) {
|
||||
// layer 12 = 多层;末位 metallization=false ⇒ NPTH。参数位序照抄 pcb_blade.push_holes
|
||||
await eda.pcb_PrimitivePad.create(12, name, x, y, 0, ['ELLIPSE', D, D], '',
|
||||
['ROUND', D], 0, 0, 0, false);
|
||||
k++;
|
||||
}
|
||||
await eda.pcb_Document.save();
|
||||
return {made: k, now: (await eda.pcb_PrimitivePad.getAll())
|
||||
.filter(p => /^H[1-9]$/.test(String(p.getState_PadNumber()))).length};
|
||||
"""
|
||||
|
||||
|
||||
def push_holes():
|
||||
arr = [[k, mil(v[0]), mil(v[1])] for k, v in sorted(MOUNT_HOLES.items())]
|
||||
r = call(PUSH_HOLES_JS % (PCB, json.dumps(arr), mil(HOLE_D)), timeout=300)
|
||||
print(u'安装孔: 下发 %d / 读回 %d,NPTH Φ%.1f(M3 尼龙立柱)' % (r['made'], r['now'], HOLE_D))
|
||||
for k, v in sorted(MOUNT_HOLES.items()):
|
||||
print(u' %-3s (%6.2f, %6.2f)' % (k, v[0], v[1]))
|
||||
return r
|
||||
|
||||
|
||||
def verify():
|
||||
r = call("await eda.dmt_EditorControl.openDocument('%s');"
|
||||
"await new Promise(r=>setTimeout(r,2000));"
|
||||
"const ps = (await eda.pcb_PrimitivePolyline.getAll()||[])"
|
||||
".filter(p=>p.getState_Layer()===11);"
|
||||
"const line11 = (await eda.pcb_PrimitiveLine.getAll()||[])"
|
||||
".filter(l=>l.layer===11).length;"
|
||||
"const holes = (await eda.pcb_PrimitivePad.getAll())"
|
||||
".filter(p=>/^H[1-9]$/.test(String(p.getState_PadNumber())))"
|
||||
".map(p=>[String(p.getState_PadNumber()), p.getState_X(), p.getState_Y()]);"
|
||||
"return {n: ps.length, poly: ps.length ? ps[0].getState_Polygon() : null,"
|
||||
" line11, holes, comps: (await eda.pcb_PrimitiveComponent.getAll()).length};" % PCB,
|
||||
timeout=180)
|
||||
bad = []
|
||||
if r['n'] != 1:
|
||||
bad.append(u'层 11 上有 %d 条折线(应为 1)' % r['n'])
|
||||
if r['line11']:
|
||||
bad.append(u'层 11 上还有 %d 条**导线**(板框画成铜的老坑,§27.3)' % r['line11'])
|
||||
pts = []
|
||||
src = r['poly']
|
||||
if isinstance(src, dict):
|
||||
src = src.get('pointArr') or src.get('points') or src.get('polygon') or src
|
||||
if isinstance(src, list):
|
||||
i = 0
|
||||
while i < len(src):
|
||||
if src[i] in ('L', 'l'):
|
||||
i += 1
|
||||
continue
|
||||
if isinstance(src[i], (int, float)) and i + 1 < len(src) and isinstance(src[i + 1], (int, float)):
|
||||
pts.append((src[i] / MIL, src[i + 1] / MIL))
|
||||
i += 2
|
||||
else:
|
||||
i += 1
|
||||
if pts:
|
||||
xs = [p[0] for p in pts]
|
||||
ys = [p[1] for p in pts]
|
||||
if abs(min(xs)) > 0.01 or abs(max(xs) - BW) > 0.01 or \
|
||||
abs(min(ys)) > 0.01 or abs(max(ys) - BH) > 0.01:
|
||||
bad.append(u'外接矩形 %.2f~%.2f x %.2f~%.2f != 0~%.0f x 0~%.0f'
|
||||
% (min(xs), max(xs), min(ys), max(ys), BW, BH))
|
||||
if abs(pts[0][0] - pts[-1][0]) > 0.01 or abs(pts[0][1] - pts[-1][1]) > 0.01:
|
||||
bad.append(u'折线首尾不重合: %s vs %s' % (pts[0], pts[-1]))
|
||||
if abs(len(pts) - len(outline_polygon())) > 1:
|
||||
bad.append(u'点数 %d != %d' % (len(pts), len(outline_polygon())))
|
||||
print(u'读回板框折线 1 条 / %d 点,外接 %.2f~%.2f x %.2f~%.2f;层 11 遗留导线 %d'
|
||||
% (len(pts), min(xs), max(xs), min(ys), max(ys), r['line11']))
|
||||
else:
|
||||
bad.append(u'读不回折线点列(原始值 %s)' % str(r['poly'])[:120])
|
||||
got = {h[0]: (round(h[1] / MIL, 3), round(h[2] / MIL, 3)) for h in r['holes']}
|
||||
for n, (hx, hy) in sorted(MOUNT_HOLES.items()):
|
||||
if n not in got:
|
||||
bad.append(u'安装孔 %s 没打' % n)
|
||||
elif abs(got[n][0] - hx) > 0.02 or abs(got[n][1] - hy) > 0.02:
|
||||
bad.append(u'安装孔 %s 位置 %s != (%.1f, %.1f)' % (n, got[n], hx, hy))
|
||||
print(u'安装孔读回 %d 个;板上元件 %d 件' % (len(got), r['comps']))
|
||||
if bad:
|
||||
print(u'[X] 校验不过:')
|
||||
for b in bad:
|
||||
print(u' ' + b)
|
||||
else:
|
||||
print(u'[OK] 板框是一条闭合折线、外接矩形对上、安装孔齐')
|
||||
return bad
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
selfcheck()
|
||||
print(u'底板 %.0f x %.0f,四角 R%.0f;PCB=%s' % (BW, BH, CORNER_R, PCB))
|
||||
print(u'槽阵 5 槽 @%.2f(沿 y 堆叠,长轴沿 x,rot %d): %s'
|
||||
% (SLOT_PITCH, SLOT_ROT, u' / '.join('%.2f' % y for y in slot_ys())))
|
||||
print(u'槽体 x %.1f~%.1f;刀片投影 y %.1f~%.1f(带内元件 ≤ %.0f mm)'
|
||||
% (MID[0], MID[1], PROJ[0], PROJ[1], H_PROJ))
|
||||
print(u'西区 x %.0f~%.0f | D± 通道 %.0f~%.0f | 中区 %.0f~%.0f | 东区 %.0f~%.0f'
|
||||
% (WEST[0], WEST[1], DP_CHAN[0], DP_CHAN[2], MID[0], MID[1], EAST[0], EAST[1]))
|
||||
print(u'12V:Rs6 单独接入 → 西缘高速路 %s → %d 堵铜墙'
|
||||
% (BUS_W, len(rib_rects())))
|
||||
if '--outline' in sys.argv:
|
||||
push_outline()
|
||||
verify()
|
||||
elif '--holes' in sys.argv:
|
||||
push_holes()
|
||||
verify()
|
||||
elif '--verify' in sys.argv:
|
||||
verify()
|
||||
文件差异内容过多而无法显示
加载差异
@@ -0,0 +1,889 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
u"""R2 底板 PCB 的缝合过孔 + 铺铜 + 断面校核(M8e)。
|
||||
|
||||
/c/Python314/python .claude/r2/pour_base.py # 只算 + 自检 + 断面校核
|
||||
/c/Python314/python .claude/r2/pour_base.py --stitch # 打 GND 缝合过孔
|
||||
/c/Python314/python .claude/r2/pour_base.py --push # 清旧铜面 + 重建 + 重填
|
||||
/c/Python314/python .claude/r2/pour_base.py --rebuild # 只重填
|
||||
|
||||
⛔ **顺序**(刀片 M7 §27.10 的结论,两层板同样成立):
|
||||
摆位 → 布线 → 下发 → **缝合过孔** → 斜切 → **铺铜**。
|
||||
重跑布线器要把缝合过孔和斜切一起重跑。
|
||||
|
||||
## 叠层(方案 §6.1)
|
||||
|
||||
| 设计 | EasyEDA | id | 内容 |
|
||||
|---|---|---|---|
|
||||
| L1 | Top Layer | 1 | 全部元件 + 全部信号 + 12V 宽铜 + GND 底填 |
|
||||
| L2 | Bottom Layer | 2 | **完整地平面**(D± 走廊下方一刀不许割,§6.2 第 1 条)|
|
||||
|
||||
## 两个只有这块板才有的约束
|
||||
|
||||
1. **`U20` 天线净空**:ESP32-S3-WROOM-1 的天线那一头(焊盘北沿外 7.5 mm)
|
||||
**两层都不许有铜**。铜面不能带洞(`createComplexPolygon` 传多个环会把客户端挂死,
|
||||
刀片 §24 的坑),⇒ 用**若干块单环矩形**绕开它,同网接壤会自然连成一片。
|
||||
2. **入口链 9 A 靠铺铜**:`VIN_RAW`/`VIN_FUSED`/`VIN_SW`/`+12V` 在 input 带里各自
|
||||
铺一块顶层铜(优先级比 GND 高,先占地盘),断面要 ≥ 方案 §6.4 的要求。
|
||||
⚠ 这四个是**串联的四段**,不是一个网 —— 每段各铺各的,中间隔着 F1 / Q1 / Rs6。
|
||||
|
||||
## 断面校核(`--cuts`,办法沿用 R1 `pcb1_pour.py`)
|
||||
|
||||
在铺铜上画一条断面线,数「这条线上属于本网、且没有被别人的焊盘/过孔/导线占掉」的
|
||||
长度。⚠ 它算的是**计划的铜面**,不是板上填出来的铜 —— 填的时候还会被反焊盘再吃掉一点,
|
||||
所以判据留了 15% 余量。真正的判据仍是「铺完截图 + DRC」。
|
||||
"""
|
||||
import io
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, os.path.dirname(HERE))
|
||||
sys.path.insert(0, HERE)
|
||||
from eda import run # noqa: E402
|
||||
import pcb_base as B # noqa: E402
|
||||
import place_base as PL # noqa: E402
|
||||
|
||||
PCB = B.PCB
|
||||
MIL = B.MIL
|
||||
L_TOP, L_BOT = 1, 2
|
||||
|
||||
INSET = 0.5 # 铜面离板边
|
||||
CLR = 0.25 # 断面校核里别人的铜要让开多少
|
||||
VIA_D, VIA_HOLE = 0.6, 0.3
|
||||
STITCH_PITCH = 4.0
|
||||
STITCH_CLR = 0.5 # 缝合过孔到任何焊盘 / 导线的净距
|
||||
PADS = os.path.join(HERE, '_base_pads.json')
|
||||
ROUTES = os.path.join(HERE, '_base_routes.json')
|
||||
ROUTE_SCHEMA = 7
|
||||
|
||||
# 入口链四段的铺铜:从各自的焊盘外框算,不手抄坐标(摆位一改就跟着走)
|
||||
POWER_NETS = [('VIN_RAW', 1), ('VIN_FUSED', 2), ('VIN_SW', 3)]
|
||||
# ⚠ `+12V` **不按焊盘外框算** —— 它不是入口链里的一段,是遍布全板的母线。
|
||||
# 三段几何写在 `pcb_base.BUS_IN/BUS_UP/BUS_SLOT`(那里是唯一事实来源,
|
||||
# `place_base` 也拿它当摆位禁区)。
|
||||
# ⛔ 入口链的铜只能从 y=6.3 往上:**输入带最南那 5 mm 归 12V 横脊**(`pcb_base.SPINE`)。
|
||||
# 两者同层不同网,搭上就把 `Rs6` 短路了。
|
||||
# M9:南带整条都归入口链(横脊没了 —— 12V 不再需要穿过 D±,它全在东缘)
|
||||
POWER_BAND = (0.0, 0.8, B.BW, B.ZONES['input'][3])
|
||||
POWER_GROW = {'VIN_RAW': 1.2, 'VIN_FUSED': 1.2, 'VIN_SW': 1.2}
|
||||
|
||||
# Q1 栅极网络(DZ1 钳位 / C1 栅源电容 / R1 下拉)。它们的焊盘**不撑大**入口链铜面
|
||||
# (和 U13.10 同理:不是 9 A 主电流焊盘),而且整团从铜面里挖出一个缺口给 GATE 走线。
|
||||
# ⛔ 不挖的话 Q1.4 北、西、南三面是 VIN_FUSED、东面是 VIN_SW,中间只剩 0.25 mm 的缝 ——
|
||||
# 布线器报「GATE: Q1.4 出不来」,看着像拥塞,其实是铜面把栅极脚封死了。
|
||||
GATE_PARTS = ('DZ1', 'C1', 'R1')
|
||||
NOTCH_CLR = 0.3 # 缺口在 GATE 焊盘外框外再让多少
|
||||
NOTCH_HEAD = 1.5 # 缺口在栅极件北侧留多高:GATE 要从 C1 北边绕过去
|
||||
|
||||
# 落在硬功率铜面里 / GATE 缺口里的 **GND 贴片焊盘**:(位号, 脚, 过孔在哪一侧, 是否挖地岛)。
|
||||
# ⛔ GND 不走线、只靠铺铜 + 过孔。这几只脚四周全是 VIN_SW 铜(或是缺口里的一小块孤立地铜),
|
||||
# 没有专用过孔的话它们根本接不到地 —— D1 是 TVS,浪涌电流全靠这一只脚回地。
|
||||
# 通孔地脚(C2/C3 的电解、J1)自己穿到底层地平面,不在此列。
|
||||
# 地岛从铜面里挖一条缝通到铜面外(S = 向南到板边,N = 向北出 VIN_SW 顶边),过孔排在开口那一侧。
|
||||
GND_PAD_VIAS = [('D1', '2', 'S', True), ('C5', '1', 'N', True), ('R1', '1', 'N', False)]
|
||||
ISL_M = 0.6 # 地岛在焊盘外框外让多少
|
||||
ISL_VIA_GAP = 0.25 # 过孔铜边到焊盘边(别让焊锡流进孔里)
|
||||
ISL_VIA_PITCH = 0.9 # 同排过孔中心距(≥ 0.6 的同网孔距)
|
||||
|
||||
# 断面校核(方案 §6.4)。入口链电流方向是 +x ⇒ 断面是**竖直**的,量 y 方向的净铜高,
|
||||
# 而且要取沿途的**最小值**(第一版取了最大值,等于没查)。
|
||||
# (名字, 网, 是否强制)
|
||||
# (铜面名, 标签, 网, 沿哪个轴流 'x'/'y', 要求净铜, 是否强制)
|
||||
CUTS = [
|
||||
('PWR-VIN_RAW', u'J1→F1 入口', 'VIN_RAW', 'x', 8.0, False),
|
||||
('PWR-VIN_FUSED', u'F1→Q1', 'VIN_FUSED', 'x', 8.0, False),
|
||||
# ⚠ `VIN_SW` 的最窄点在 `Rs6`/`U13` 那一头(Kelvin 要求它们贴在一起)。
|
||||
# 6.5 mm 在 9 A 下 ΔT≈25 K 而不是 20 K,只有分流电阻前后 2~3 mm 一小段,可接受。
|
||||
('PWR-VIN_SW', u'Q1→储能→Rs6', 'VIN_SW', 'x', 6.5, True),
|
||||
# 西缘高速路的电流是**沿 y** 流的 ⇒ 断面是水平的,量 x 方向净铜宽。
|
||||
# 写成 'x' 的话量到的是它 105 mm 的长度,永远「通过」,等于没查。
|
||||
('PWR-12V-0', u'12V 西缘高速路', '+12V', 'y', 8.0, True),
|
||||
]
|
||||
CUT_MIN_W = 8.0 # IPC-2221 1 oz 外层 9 A ΔT20K ⇒ ≥8 mm 净铜宽
|
||||
|
||||
# ── 低压轨的「软铺铜」(2026-09-09 用户拍板:先按分布把电源网铺铜)────────────
|
||||
# ⛔ **为什么单独一类、名字不叫 `PWR-`**:
|
||||
# `PWR-` 是**硬铺铜** —— 几何算出来、要过断面校核,而且会当障碍喂给布线器
|
||||
# (`route_base.pour_blocks()`),别的网一律不许穿过去。12V 母线被一根信号线
|
||||
# 切断 = 9 A 没了,而铺铜管理器 / DRC / 连通性三个都只说「有铜」。
|
||||
# `RAIL-` 是**软铺铜**:只是「这一带的余铜归这个网」,铺铜时自然绕开已布的信号线,
|
||||
# **不喂给布线器** —— 喂了的话 ctrl 带 57 个件的信号一根都出不去。
|
||||
# 判据也换一个:硬铺铜看断面,软铺铜看 **`board_check` 从板上读回来的连通性**,
|
||||
# 因为它被信号切碎成孤岛是完全可能的,而那只有读铜才看得见。
|
||||
#
|
||||
# 依据是 `_base_pads.json` 实测的分布,不是画出来的(见 §28.15):
|
||||
# `+3V3` 47 只脚里 **26 只**在 ctrl 带 x 28~86 / y 86~112(其余 15 只在槽上、4 只在风扇带)
|
||||
# `+3V3_HUB` 21 只脚分成 U32/去耦组与 U31/C737 两簇
|
||||
# `+5V` ctrl 带东头 2 只(`R706` / `L3`,5V Buck 那一窝);槽上那 10 只要走走廊,不在这
|
||||
# ⚠ 三块互不重叠(不同网同层重叠 = 短路),`selfcheck()` 会查。
|
||||
# ⚠ M9 暂时清空:`+3V3` 与 `+3V3_HUB` 的脚在西区互相穿插(排布器按「离目标最近」摆的),
|
||||
# 两个网划不出互不重叠的矩形。新拓扑没有 M8 那道墙,先让布线器直接布,
|
||||
# 看失败分布再决定要不要为它们改摆位 + 加软铺铜。
|
||||
RAILS = [
|
||||
# U31 与 U32 相距较远,分成两块局部软铜,中间由 A* 明确连接。
|
||||
# 这比一块 24x60 mm 大面更不容易被控制信号切成孤岛。
|
||||
# M12 采用当前手工布局后旧坐标已失效:U31 移到 y=73.6,U32 移到 y=52.9。
|
||||
('RAIL-3V3HUB-LDO', '+3V3_HUB', (77.8, 71.5, 90.2, 75.7)),
|
||||
('RAIL-3V3HUB-HUB', '+3V3_HUB', (84.0, 47.8, 102.0, 60.7)),
|
||||
# ⛔ **`+3V3` 试过铺两块,反而更差**(45 → 58 条):软铺铜会逼每只**区外**的脚
|
||||
# 各自去够铜面,而原来布线器可以把它们串成一条链。
|
||||
# ⇒ 判据:软铺铜只在「大部分脚都在区内」时划算(`+3V3_HUB` 21 只脚全在西区 ⇒ 值),
|
||||
# `+3V3` 47 只脚里 15 只在槽上、6 只在风扇带 ⇒ 不值,留给布线器。
|
||||
]
|
||||
# ⚠ `VIN_RAW` / `VIN_FUSED` 两段**不强制**:它们的瓶颈是 DC-005 的三只脚和 1206 保险
|
||||
# 本身(座子额定 2~5 A、这块板要过 9 A,是用户 2026-09-08 明确接受的已知风险,
|
||||
# 设计文档 §22.6)。板上再宽的铜也救不了插头内芯与弹片的接触面。
|
||||
# 真正必须够的是它们之间那两段长铜:`VIN_SW`(储能到检流)和 `+12V`(母线)。
|
||||
|
||||
|
||||
def mil(v):
|
||||
return round(v * MIL, 4)
|
||||
|
||||
|
||||
def call(js, timeout=180):
|
||||
r = run(js, timeout=timeout)
|
||||
if not r.get('success'):
|
||||
print(u'失败: ' + json.dumps(r, ensure_ascii=False)[:600])
|
||||
sys.exit(1)
|
||||
return r['result']
|
||||
|
||||
|
||||
OPEN = ("await eda.dmt_EditorControl.openDocument('%s');"
|
||||
"await new Promise(r=>setTimeout(r,2500));" % PCB)
|
||||
|
||||
|
||||
_PADS = [None]
|
||||
|
||||
|
||||
def pads():
|
||||
u"""底板焊盘,**按 `place_base.ROUTE_FIX` 的计划位置**(与 `route_base` 的路由模型一致)。
|
||||
|
||||
⛔ 以前这里读的是实测焊盘,而布线器读的是改位后的:DZ1 从 x=38.6 挪到 29.3 之后,
|
||||
实测算出的 VIN_FUSED/VIN_SW 分界是 32.9,改位后算出的是 30.5(VIN_SW 盖住 Q1.3)——
|
||||
布线器看到的铜面和摆位下发后真正铺出来的对不上,而且哪一边都不报错。
|
||||
"""
|
||||
if _PADS[0] is None:
|
||||
_PADS[0], _moved = PL.route_fixed_pads(json.load(io.open(PADS, encoding='utf-8')))
|
||||
return _PADS[0]
|
||||
|
||||
|
||||
def routes():
|
||||
if not os.path.exists(ROUTES):
|
||||
return [], []
|
||||
c = json.load(io.open(ROUTES, encoding='utf-8'))
|
||||
if c.get('schema') != ROUTE_SCHEMA or c.get('slot_rot') != B.SLOT_ROT:
|
||||
return [], []
|
||||
return c['segs'], c['vias']
|
||||
|
||||
|
||||
# ── 铜面几何 ─────────────────────────────────────────────────────────────
|
||||
def keepouts():
|
||||
u"""两层都不许铺铜的地方:`U20` 天线净空 + **天线缺口** + 四个 M3 安装孔。
|
||||
|
||||
⛔ 缺口那一块**板材是挖掉的**,而这份脚本的铜面几何是按 `BW × BH` 的矩形算的
|
||||
(`tiles()` 只认矩形,不认板框折线)。不把缺口喂进来,GND 瓦片会照样铺到
|
||||
板外的空气里 —— 板框对不上铜面,DRC 报「铜到板边」,而铺铜管理器不报。
|
||||
"""
|
||||
sz = PL.sizes()
|
||||
n = B.notch_rect()
|
||||
out = [PL.antenna_keepout(sz),
|
||||
(n[0] - INSET, n[1] - INSET, n[2] + INSET, n[3] + INSET)]
|
||||
r = B.HOLE_D / 2.0 + 0.6
|
||||
for (hx, hy) in B.MOUNT_HOLES.values():
|
||||
out.append((hx - r, hy - r, hx + r, hy + r))
|
||||
return out
|
||||
|
||||
|
||||
def tiles(x0, y0, x1, y1, cuts):
|
||||
u"""把矩形按 `cuts` 切成若干块**单环**矩形。
|
||||
|
||||
⛔ 不能用带洞的复杂多边形:`createComplexPolygon()` 传多个环会把客户端挂死
|
||||
(刀片 §24,请求 30 s 超时、进度恒 0/0,和「按钮失灵」分不清)。
|
||||
同网的矩形接壤会自然连成一片铜,效果一样。
|
||||
"""
|
||||
xs = sorted(set([x0, x1] + [v for c in cuts for v in (c[0], c[2])
|
||||
if x0 < v < x1]))
|
||||
out = []
|
||||
for xa, xb in zip(xs, xs[1:]):
|
||||
if xb - xa < 0.6:
|
||||
continue
|
||||
# ⛔ **禁区的 y 区间必须先合并再配对**。原来是「按 c[1] 排序后把
|
||||
# [起, 止] 一对一对塞进 ys,再 `zip(ys[0::2], ys[1::2])`」——
|
||||
# 那套只在禁区互不重叠时成立。天线缺口 (84.5~107.5) 和天线净空 (86~106)
|
||||
# 是**嵌套**的,塞进去就变成 [.., 84.5, 107.5, 86.0, 106.0, ..],
|
||||
# 奇偶配对当场错位 ⇒ 瓦片照样盖住缺口,而**只有 `selfcheck` 那一条
|
||||
# 「压进净空区」看得出来**(铺铜管理器和 DRC 都不会响)。
|
||||
iv = []
|
||||
for c in cuts:
|
||||
if c[0] < xb - 1e-9 and xa < c[2] - 1e-9:
|
||||
iv.append([max(y0, c[1]), min(y1, c[3])])
|
||||
iv.sort()
|
||||
merged = []
|
||||
for a, b in iv:
|
||||
if merged and a <= merged[-1][1] + 1e-9:
|
||||
merged[-1][1] = max(merged[-1][1], b)
|
||||
else:
|
||||
merged.append([a, b])
|
||||
cur = y0
|
||||
for a, b in merged + [[y1, y1]]:
|
||||
if a - cur > 0.6:
|
||||
out.append((round(xa, 3), round(cur, 3), round(xb, 3), round(a, 3)))
|
||||
cur = max(cur, b)
|
||||
return out
|
||||
|
||||
|
||||
def power_rect(net):
|
||||
u"""入口链某一段的铜面:这个网在 input 带里全部焊盘的外框 + `POWER_GROW`。"""
|
||||
z = POWER_BAND
|
||||
xs, ys = [], []
|
||||
for p in pads():
|
||||
if p[7] != net or not (z[1] <= p[3] <= z[3]):
|
||||
continue
|
||||
# U13.10 是 Rs6 上游的 Kelvin/监测脚,不是 9 A 主电流焊盘。
|
||||
# 把它纳入包围盒会让 VIN_SW 铜面跨过 Rs6 的 +12V 端。
|
||||
if net == 'VIN_SW' and p[0] == 'U13':
|
||||
continue
|
||||
if p[0] in GATE_PARTS:
|
||||
continue
|
||||
xs += [p[2] - p[4] / 2, p[2] + p[4] / 2]
|
||||
ys += [p[3] - p[5] / 2, p[3] + p[5] / 2]
|
||||
if not xs:
|
||||
return None
|
||||
g = POWER_GROW[net]
|
||||
return (max(INSET, min(xs) - g), max(z[1] + INSET, min(ys) - g),
|
||||
min(B.BW - INSET, max(xs) + g), min(z[3], max(ys) + g))
|
||||
|
||||
|
||||
def clip_chain(chain):
|
||||
u"""入口链四段是**串联**的(中间隔着 F1 / Q1 / Rs6),铜面搭上就把器件短路了。
|
||||
|
||||
`chain` = [(net, rect)],已按 x 排好。相邻两段重叠时竖切一刀,两边各退 CLR/2。
|
||||
⛔ 刀口**按焊盘空隙定,不按重叠区中点**:在两块铜 y 方向重叠的那一带里,取
|
||||
「西段网最东的焊盘」与「东段网最西的焊盘」之间的中点。
|
||||
取重叠区中点的旧写法在 Q1 这里是错的 —— Q1 是南北两排脚(南排源极、北排漏极),
|
||||
VIN_SW 的外框又被北边的 C4/C6 拉到 x=25.8,中点直接落进 Q1 里面(x=30.6,
|
||||
VIN_SW 盖住 Q1.3)。以前没出事,只是因为 DZ1 旧位置把 VIN_FUSED 撑到了 x=40。
|
||||
⚠ 不能靠铺铜优先级解决:优先级只保证「低的那块被让开」,让出来的形状是不可预期的
|
||||
细条,断面校核就没意义了。
|
||||
"""
|
||||
out = [r for _n, r in chain]
|
||||
ps = pads()
|
||||
|
||||
def band(net, y0, y1):
|
||||
return [p for p in ps if p[7] == net and p[0] not in GATE_PARTS
|
||||
and not (net == 'VIN_SW' and p[0] == 'U13')
|
||||
and p[3] - p[5] / 2 < y1 and p[3] + p[5] / 2 > y0]
|
||||
|
||||
for i in range(len(out) - 1):
|
||||
a, b = out[i], out[i + 1]
|
||||
if a[2] <= b[0]:
|
||||
continue
|
||||
y0, y1 = max(a[1], b[1]), min(a[3], b[3])
|
||||
pa, pb = band(chain[i][0], y0, y1), band(chain[i + 1][0], y0, y1)
|
||||
ea = max([p[2] + p[4] / 2 for p in pa] or [None])
|
||||
wb = min([p[2] - p[4] / 2 for p in pb] or [None])
|
||||
if ea is not None and wb is not None and ea < wb:
|
||||
m = (ea + wb) / 2.0
|
||||
else:
|
||||
m = (a[2] + b[0]) / 2.0 # 没有干净的焊盘空隙时退回旧规则
|
||||
out[i] = (a[0], a[1], round(min(a[2], m - CLR / 2), 3), a[3])
|
||||
out[i + 1] = (round(max(b[0], m + CLR / 2), 3), b[1], b[2], b[3])
|
||||
return out
|
||||
|
||||
|
||||
def gate_notch():
|
||||
u"""GATE 缺口(矩形列表,喂给 `tiles()`)。
|
||||
|
||||
主块:GATE 焊盘 + 三颗栅极件的外框,向外让 NOTCH_CLR、北侧留 NOTCH_HEAD,南到板边。
|
||||
竖条:比主块更高的 GATE 焊盘(Q1.4 是 2 mm 长的 SOIC 脚)整只让出来 ——
|
||||
只挖主块的话 Q1.4 北半截仍在 VIN_FUSED 铜面里,模型里等于异网铜压在焊盘上。
|
||||
"""
|
||||
ps = pads()
|
||||
g = [p for p in ps if p[7] == 'GATE' and p[3] <= POWER_BAND[3]]
|
||||
sm = [p for p in ps if p[0] in GATE_PARTS]
|
||||
if not g or not sm:
|
||||
return []
|
||||
y1 = round(max(p[3] + p[5] / 2 for p in sm) + NOTCH_HEAD, 3)
|
||||
out = [(round(min(p[2] - p[4] / 2 for p in g) - NOTCH_CLR, 3), 0.0,
|
||||
round(max(p[2] + p[4] / 2 for p in g + sm) + NOTCH_CLR, 3), y1)]
|
||||
for p in g:
|
||||
if p[3] + p[5] / 2 + NOTCH_CLR > y1:
|
||||
out.append((round(p[2] - p[4] / 2 - NOTCH_CLR, 3), 0.0,
|
||||
round(p[2] + p[4] / 2 + NOTCH_CLR, 3),
|
||||
round(p[3] + p[5] / 2 + NOTCH_CLR, 3)))
|
||||
return out
|
||||
|
||||
|
||||
def gnd_pad_vias():
|
||||
u"""`GND_PAD_VIAS` 的几何:(要从入口链铜面里挖掉的地岛矩形, 专用 GND 过孔坐标)。"""
|
||||
ps = pads()
|
||||
islands, vias = [], []
|
||||
for ref, pin, side, cut in GND_PAD_VIAS:
|
||||
hit = [p for p in ps if p[0] == ref and str(p[1]) == pin and p[6] == L_TOP]
|
||||
if len(hit) != 1 or hit[0][7] != 'GND':
|
||||
raise RuntimeError(u'%s.%s 应是唯一的顶层 GND 贴片焊盘' % (ref, pin))
|
||||
p = hit[0]
|
||||
x0, y0 = p[2] - p[4] / 2, p[3] - p[5] / 2
|
||||
x1, y1 = p[2] + p[4] / 2, p[3] + p[5] / 2
|
||||
n = int((x1 - x0) / ISL_VIA_PITCH) + 1
|
||||
vy = y0 - ISL_VIA_GAP - VIA_D / 2 if side == 'S' else y1 + ISL_VIA_GAP + VIA_D / 2
|
||||
for k in range(n):
|
||||
vias.append((round(p[2] + (k - (n - 1) / 2.0) * ISL_VIA_PITCH, 3), round(vy, 3)))
|
||||
if cut:
|
||||
# 缝一直通到板边 / 板顶:`tiles()` 只拿它切入口链那几块铜,切不到别的东西
|
||||
islands.append((round(x0 - ISL_M, 3), 0.0 if side == 'S' else round(y0 - ISL_M, 3),
|
||||
round(x1 + ISL_M, 3), round(y1 + ISL_M, 3) if side == 'S' else B.BH))
|
||||
return islands, vias
|
||||
|
||||
|
||||
def drain_piece(outs):
|
||||
u"""Q1 漏极排(北排四脚,VIN_SW)的铜面。
|
||||
|
||||
⛔ 入口链是按 x 串起来切的,而 Q1 是**南北两排**:南排源极归 VIN_FUSED,北排漏极
|
||||
在 VIN_SW 那块的西边界之外 —— 9 A 只能靠 A* 的 2 mm 走线从四只漏极脚爬进铜面
|
||||
(2 mm 1 oz 外层只带 ~4 A)。这里按漏极排外框补一块,东头与 VIN_SW 已有的块搭接,
|
||||
南沿让开 VIN_FUSED。西沿只让 CLR:再往西是死头,断面校核会把它量成瓶颈。
|
||||
"""
|
||||
ps = [p for p in pads() if p[0] == 'Q1' and p[7] == 'VIN_SW']
|
||||
if not ps:
|
||||
return None
|
||||
g = POWER_GROW['VIN_SW']
|
||||
x0 = min(p[2] - p[4] / 2 for p in ps) - CLR
|
||||
x1 = max(p[2] + p[4] / 2 for p in ps) + g
|
||||
y0 = min(p[3] - p[5] / 2 for p in ps) - g
|
||||
y1 = max(p[3] + p[5] / 2 for p in ps) + g
|
||||
vf = [r for (_n, net, _l, r, _p) in outs if net == 'VIN_FUSED' and r[0] < x1 and x0 < r[2]]
|
||||
y0 = max([y0] + [r[3] + CLR for r in vf])
|
||||
sw = [r for (_n, net, _l, r, _p) in outs
|
||||
if net == 'VIN_SW' and r[1] < y1 and y0 < r[3] and r[0] >= x0]
|
||||
if sw:
|
||||
x1 = max(x1, min(r[0] for r in sw) + 0.6) # 保证与已有 VIN_SW 块搭接
|
||||
return (round(x0, 3), round(y0, 3), round(x1, 3), round(y1, 3))
|
||||
|
||||
|
||||
def plan():
|
||||
u"""[(name, net, layer, rect, priority)],priority 小的先铺、先占地盘。"""
|
||||
outs = []
|
||||
chain = [(net, prio, power_rect(net)) for net, prio in POWER_NETS]
|
||||
chain = [c for c in chain if c[2]]
|
||||
chain.sort(key=lambda c: (c[2][0] + c[2][2]) / 2.0)
|
||||
clipped = clip_chain([(c[0], c[2]) for c in chain])
|
||||
cuts = gate_notch() + gnd_pad_vias()[0]
|
||||
for (net, prio, _r), r in zip(chain, clipped):
|
||||
parts = tiles(r[0], r[1], r[2], r[3], cuts) if cuts else [r]
|
||||
# 最大一块沿用原名(CUTS 按名找),其余加序号;check_cuts 会把同名前缀的块一起算
|
||||
parts.sort(key=lambda t: -(t[2] - t[0]) * (t[3] - t[1]))
|
||||
for k, t in enumerate(parts):
|
||||
outs.append(('PWR-%s%s' % (net.strip('+'), '-%d' % k if k else ''),
|
||||
net, L_TOP, t, prio))
|
||||
dp = drain_piece(outs)
|
||||
if dp:
|
||||
outs.append(('PWR-VIN_SW-Q1', 'VIN_SW', L_TOP, dp, dict(POWER_NETS)['VIN_SW']))
|
||||
for k, r in enumerate(B.bus_rects()):
|
||||
outs.append(('PWR-12V-%d' % k, '+12V', L_TOP, r, 4))
|
||||
# `+3V3` 五块槽墙(几何与判据见 `pcb_base.wall3v3_rects` / `wall_check`)。
|
||||
# 与 12V 同为**硬铺铜**:喂给布线器当障碍,且脚从 A* 里摘掉。
|
||||
for k, r in enumerate(B.wall3v3_rects()):
|
||||
outs.append(('PWR-3V3-%d' % k, '+3V3', L_TOP, r, 4))
|
||||
# 低压轨的软铺铜:排在硬铺铜之后、GND 之前
|
||||
for name, net, r in RAILS:
|
||||
outs.append((name, net, L_TOP, r, 5))
|
||||
# ⚠ `PWR-12V-*` 里 0=输入带母线 1=横脊 2~7=五根齿(3 号槽那根是两块拼的 L)
|
||||
ko = keepouts()
|
||||
d = INSET
|
||||
for tag, lay in (('TOP', L_TOP), ('BOT', L_BOT)):
|
||||
for k, t in enumerate(tiles(d, d, B.BW - d, B.BH - d, ko)):
|
||||
outs.append(('GND-%s-%d' % (tag, k), 'GND', lay, t, 9))
|
||||
return outs
|
||||
|
||||
|
||||
def rrect_src(x0, y0, x1, y1):
|
||||
pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1), (x0, y0)] # ⚠ 必须显式闭合
|
||||
a = [mil(pts[0][0]), mil(pts[0][1])]
|
||||
for p in pts[1:]:
|
||||
a += ['L', mil(p[0]), mil(p[1])]
|
||||
return a
|
||||
|
||||
|
||||
def wall_check():
|
||||
u"""`+3V3` 槽墙的判据(几何算不出来,只有拿实测焊盘表比才算数)。
|
||||
|
||||
⛔ 三条都要过,缺一条墙就是白做:
|
||||
① **15 只槽脚全被盖住** —— 与 12V 那条「10 排焊盘全被盖住」同理:
|
||||
少盖一只,铺铜管理器和 DRC 都只说「有铜」,而那只脚是断的。
|
||||
② **一只异网针都不许压到**(`NC` 空网也算异网):南排 NC(73)、北排 NC(71/72)
|
||||
就贴在 3V3 针旁边,方块一大就压上去,铺铜会绕开它们**把墙在针排上切碎**。
|
||||
③ **桥的过孔要落在墙里**,否则过孔与铜面之间靠铺铜「碰巧」相接。
|
||||
"""
|
||||
bad = []
|
||||
rects = B.wall3v3_rects()
|
||||
ps = pads()
|
||||
c = B.W3V3_CLR - 0.05 # 判据比构造留的净距松 0.05
|
||||
|
||||
def inside(x0, y0, x1, y1):
|
||||
return any(r[0] - 1e-6 <= x0 and x1 <= r[2] + 1e-6 and
|
||||
r[1] - 1e-6 <= y0 and y1 <= r[3] + 1e-6 for r in rects)
|
||||
|
||||
own = 0
|
||||
for p in ps:
|
||||
px0, py0 = p[2] - p[4] / 2, p[3] - p[5] / 2
|
||||
px1, py1 = p[2] + p[4] / 2, p[3] + p[5] / 2
|
||||
if p[7] == '+3V3':
|
||||
if inside(px0, py0, px1, py1):
|
||||
own += 1
|
||||
continue
|
||||
for r in rects:
|
||||
if px0 - c < r[2] and r[0] < px1 + c and py0 - c < r[3] and r[1] < py1 + c:
|
||||
bad.append(u'3V3 槽墙压到异网针 %s.%s(%s) @%.2f,%.2f'
|
||||
% (p[0], p[1], p[7] or 'NC', p[2], p[3]))
|
||||
break
|
||||
slots = set(PL.SLOT_ORDER)
|
||||
slot_own = sum(1 for p in ps if p[7] == '+3V3' and p[0] in slots)
|
||||
if own != slot_own:
|
||||
bad.append(u'3V3 槽脚 %d 只,墙只盖住 %d 只' % (slot_own, own))
|
||||
_segs, vias = B.wall3v3_bridges()
|
||||
for (vx, vy) in vias:
|
||||
r = VIA_D / 2.0
|
||||
if not inside(vx - r, vy - r, vx + r, vy + r):
|
||||
bad.append(u'3V3 桥的过孔 (%.2f, %.2f) 不在墙里' % (vx, vy))
|
||||
return bad, own, slot_own
|
||||
|
||||
|
||||
def bus_check():
|
||||
u"""12V 干道/铜墙必须覆盖全部槽脚,且不能压到异网焊盘。"""
|
||||
rects = B.bus_rects()
|
||||
ps = pads()
|
||||
slots = set(B.USB_SLOT_REF.values())
|
||||
clear = CLR - 0.05
|
||||
|
||||
def inside(p):
|
||||
x0, y0 = p[2] - p[4] / 2, p[3] - p[5] / 2
|
||||
x1, y1 = p[2] + p[4] / 2, p[3] + p[5] / 2
|
||||
return any(r[0] - 1e-6 <= x0 and x1 <= r[2] + 1e-6 and
|
||||
r[1] - 1e-6 <= y0 and y1 <= r[3] + 1e-6 for r in rects)
|
||||
|
||||
own = [p for p in ps if p[0] in slots and p[7] == '+12V']
|
||||
missed = [p for p in own if not inside(p)]
|
||||
bad = [u'12V 铜墙没盖住 %s.%s @%.2f,%.2f' % (p[0], p[1], p[2], p[3])
|
||||
for p in missed]
|
||||
for p in ps:
|
||||
if p[7] == '+12V':
|
||||
continue
|
||||
px0, py0 = p[2] - p[4] / 2, p[3] - p[5] / 2
|
||||
px1, py1 = p[2] + p[4] / 2, p[3] + p[5] / 2
|
||||
if any(px0 - clear < r[2] and r[0] < px1 + clear and
|
||||
py0 - clear < r[3] and r[1] < py1 + clear for r in rects):
|
||||
bad.append(u'12V 干道/铜墙压到异网针 %s.%s(%s) @%.2f,%.2f'
|
||||
% (p[0], p[1], p[7] or 'NC', p[2], p[3]))
|
||||
return bad, len(own) - len(missed), len(own)
|
||||
|
||||
|
||||
def selfcheck(ps):
|
||||
bad = []
|
||||
ko = [k for k in keepouts() if k not in B.bus_rects()]
|
||||
for name, net, lay, r, _p in ps:
|
||||
if r[2] - r[0] < 0.6 or r[3] - r[1] < 0.6:
|
||||
bad.append(u'%s 太小: %s' % (name, r))
|
||||
if r[0] < INSET - 1e-6 or r[1] < INSET - 1e-6 or \
|
||||
r[2] > B.BW - INSET + 1e-6 or r[3] > B.BH - INSET + 1e-6:
|
||||
bad.append(u'%s 出了内缩线: %s' % (name, r))
|
||||
for k in ko:
|
||||
if r[0] < k[2] - 1e-6 and k[0] < r[2] - 1e-6 and \
|
||||
r[1] < k[3] - 1e-6 and k[1] < r[3] - 1e-6:
|
||||
bad.append(u'%s 压进净空区 %s' % (name, tuple(round(v, 1) for v in k)))
|
||||
# 入口链各段不许互相重叠(同层,靠 F1/Q1/Rs6 断开)。
|
||||
# ⚠ **只判不同网**:`+12V` 自己那几块(母线 / 横脊)是故意相接的,重叠才对。
|
||||
pw = [(n, net, r) for (n, net, _l, r, _p) in ps
|
||||
if n.startswith('PWR-') or n.startswith('RAIL-')]
|
||||
for i, (na, na_net, a) in enumerate(pw):
|
||||
for (nb, nb_net, b) in pw[i + 1:]:
|
||||
if na_net == nb_net:
|
||||
continue
|
||||
if a[0] < b[2] and b[0] < a[2] and a[1] < b[3] and b[1] < a[3]:
|
||||
bad.append(u'%s(%s) 与 %s(%s) 在顶层重叠 —— 串联的两段铜搭上就把器件短路了'
|
||||
% (na, na_net, nb, nb_net))
|
||||
return bad
|
||||
|
||||
|
||||
# ── 断面校核 ─────────────────────────────────────────────────────────────
|
||||
def cut_height(net, rect, x):
|
||||
u"""在 x 处竖切一刀,`rect` 这块铜面上属于 `net` 的**最宽一段**连续净铜高(mm)。
|
||||
|
||||
别人的焊盘 / 过孔 / 导线按 `CLR` 膨胀之后从这条线上扣掉。
|
||||
⚠ 取「最宽一段」而不是总和:被挖成两条 4 mm 的铜带,不等于一条 8 mm 的。
|
||||
"""
|
||||
if not (rect[0] <= x <= rect[2]):
|
||||
return 0.0
|
||||
spans = [(rect[1], rect[3])]
|
||||
blocks = []
|
||||
for p in pads():
|
||||
if p[7] == net or p[6] not in (1, 12):
|
||||
continue
|
||||
if p[2] - p[4] / 2 - CLR <= x <= p[2] + p[4] / 2 + CLR:
|
||||
blocks.append((p[3] - p[5] / 2 - CLR, p[3] + p[5] / 2 + CLR))
|
||||
segs, vias = routes()
|
||||
for s in segs:
|
||||
if s[6] == net or s[0] != 1:
|
||||
continue
|
||||
x0, x1 = sorted((s[1], s[3]))
|
||||
hw = s[5] / 2.0 + CLR
|
||||
if x0 - hw <= x <= x1 + hw:
|
||||
y0, y1 = sorted((s[2], s[4]))
|
||||
blocks.append((y0 - hw, y1 + hw))
|
||||
for v in vias:
|
||||
if len(v) > 2 and v[2] == net:
|
||||
continue
|
||||
r = VIA_D / 2.0 + CLR
|
||||
if abs(v[0] - x) <= r:
|
||||
blocks.append((v[1] - r, v[1] + r))
|
||||
for (b0, b1) in blocks:
|
||||
nxt = []
|
||||
for (a0, a1) in spans:
|
||||
if b1 <= a0 or b0 >= a1:
|
||||
nxt.append((a0, a1))
|
||||
continue
|
||||
if b0 > a0:
|
||||
nxt.append((a0, b0))
|
||||
if b1 < a1:
|
||||
nxt.append((b1, a1))
|
||||
spans = nxt
|
||||
return max([q[1] - q[0] for q in spans] or [0.0])
|
||||
|
||||
|
||||
def cut_width_y(net, rect, y):
|
||||
u"""在 y 处横切一刀,最宽一段连续净铜宽(mm)。"""
|
||||
if not (rect[1] <= y <= rect[3]):
|
||||
return 0.0
|
||||
spans = [(rect[0], rect[2])]
|
||||
blocks = []
|
||||
for p in pads():
|
||||
if p[7] == net or p[6] not in (1, 12):
|
||||
continue
|
||||
if p[3] - p[5] / 2 - CLR <= y <= p[3] + p[5] / 2 + CLR:
|
||||
blocks.append((p[2] - p[4] / 2 - CLR, p[2] + p[4] / 2 + CLR))
|
||||
segs, vias = routes()
|
||||
for q in segs:
|
||||
if q[6] == net or q[0] != 1:
|
||||
continue
|
||||
y0, y1 = sorted((q[2], q[4]))
|
||||
hw = q[5] / 2.0 + CLR
|
||||
if y0 - hw <= y <= y1 + hw:
|
||||
x0, x1 = sorted((q[1], q[3]))
|
||||
blocks.append((x0 - hw, x1 + hw))
|
||||
for v in vias:
|
||||
if len(v) > 2 and v[2] == net:
|
||||
continue
|
||||
rr = VIA_D / 2.0 + CLR
|
||||
if abs(v[1] - y) <= rr:
|
||||
blocks.append((v[0] - rr, v[0] + rr))
|
||||
for (b0, b1) in blocks:
|
||||
nxt = []
|
||||
for (a0, a1) in spans:
|
||||
if b1 <= a0 or b0 >= a1:
|
||||
nxt.append((a0, a1)); continue
|
||||
if b0 > a0:
|
||||
nxt.append((a0, b0))
|
||||
if b1 < a1:
|
||||
nxt.append((b1, a1))
|
||||
spans = nxt
|
||||
return max([q[1] - q[0] for q in spans] or [0.0])
|
||||
|
||||
|
||||
def series_windows(net):
|
||||
u"""这个网自己串联元件占的坐标窗口(本体的焊盘外框)。
|
||||
|
||||
⛔ 断面校核**必须跳过这些窗口**:最窄的一刀永远落在串联元件身上
|
||||
(2512 分流电阻自己就只有 3.2 mm 宽、DC 座三只脚挤在 5 mm 里),
|
||||
量到的是元件的宽度不是铜的宽度,判据就没意义了。
|
||||
元件本身的载流由选型保证(`Rs6` 是 2512 3 W、`F1` 是 12 A 保险),
|
||||
这里要查的是**元件之间那段铜**。
|
||||
"""
|
||||
# ⛔ 窗口要按**整颗器件**的焊盘外框算,不是只按它属于本网的那只脚。
|
||||
# 只按那只脚的话,`D1`(TVS:一端 VIN_SW、一端 GND)只有阴极那半边被跳过,
|
||||
# 阳极的 GND 盘照样把断面掐断 —— 实测 `VIN_SW` 被判成 4.35 mm。
|
||||
# 物理上电流是**穿过器件**的,整颗器件占的那一段本来就不该算铜宽。
|
||||
ps = pads()
|
||||
own = set(p[0] for p in ps if p[7] == net)
|
||||
by = {}
|
||||
for p in ps:
|
||||
if p[0] not in own:
|
||||
continue
|
||||
b = by.setdefault(p[0], [1e9, 1e9, -1e9, -1e9])
|
||||
b[0] = min(b[0], p[2] - p[4] / 2); b[2] = max(b[2], p[2] + p[4] / 2)
|
||||
b[1] = min(b[1], p[3] - p[5] / 2); b[3] = max(b[3], p[3] + p[5] / 2)
|
||||
return by
|
||||
|
||||
|
||||
def check_cuts(ps):
|
||||
print(u'')
|
||||
print(u'载流断面校核(逐刀切,取**最窄**一刀,跳过串联元件本身):')
|
||||
bad = []
|
||||
byname = {}
|
||||
for (n, _net, _l, r, _p) in ps:
|
||||
byname.setdefault(n, []).append(r)
|
||||
for key, label, net, axis, need, must in CUTS:
|
||||
# 挖了 GATE 缺口之后一段铜会被切成几块(`PWR-X`、`PWR-X-1`…):每一刀取包含它的那块
|
||||
rs = [r for n, v in byname.items() if n == key or n.startswith(key + '-') for r in v]
|
||||
if not rs:
|
||||
print(u' %-18s %-9s [X] 这一段没有铜面' % (label, net))
|
||||
bad.append(label)
|
||||
continue
|
||||
r = (min(q[0] for q in rs), min(q[1] for q in rs),
|
||||
max(q[2] for q in rs), max(q[3] for q in rs))
|
||||
skip = []
|
||||
for ref, b in series_windows(net).items():
|
||||
if b[0] > r[2] or b[2] < r[0] or b[1] > r[3] or b[3] < r[1]:
|
||||
continue
|
||||
skip.append((ref, (b[0] - CLR, b[2] + CLR) if axis == 'x' else (b[1] - CLR, b[3] + CLR)))
|
||||
lo, hi = (r[0], r[2]) if axis == 'x' else (r[1], r[3])
|
||||
hs = []
|
||||
t = lo + 0.25
|
||||
while t <= hi - 0.25:
|
||||
if not any(a <= t <= b for _ref, (a, b) in skip):
|
||||
f = cut_height if axis == 'x' else cut_width_y
|
||||
hs.append((max(f(net, q, t) for q in rs), t))
|
||||
t += 0.25
|
||||
worst = min(hs) if hs else (0.0, 0.0)
|
||||
ok = worst[0] >= need
|
||||
print(u' %-18s %-9s 铜面 %5.1f×%5.1f 最窄 %5.2f mm (%s=%.2f,要 %.1f) %s'
|
||||
% (label, net, r[2] - r[0], r[3] - r[1], worst[0], axis, worst[1], need,
|
||||
u'[OK]' if ok else (u'[X] 不够' if must else u'[~] 瓶颈在连接器,不强制')))
|
||||
if skip:
|
||||
print(u' (跳过串联元件 %s)' % ' '.join(sorted(ref for ref, _w in skip)))
|
||||
if not ok and must:
|
||||
bad.append(u'%s %s 最窄只有 %.2f mm' % (label, net, worst[0]))
|
||||
return bad
|
||||
|
||||
|
||||
# ── 缝合过孔 ─────────────────────────────────────────────────────────────
|
||||
def board_tracks():
|
||||
return call(OPEN + "return (await eda.pcb_PrimitiveLine.getAll()||[])"
|
||||
".filter(l=>l.layer===1||l.layer===2)"
|
||||
".map(l=>[l.startX/%s,l.startY/%s,l.endX/%s,l.endY/%s,"
|
||||
"l.lineWidth/%s,String(l.net||'')]);" % (MIL, MIL, MIL, MIL, MIL),
|
||||
timeout=300)
|
||||
|
||||
|
||||
def _seg_d(px, py, x0, y0, x1, y1):
|
||||
dx, dy = x1 - x0, y1 - y0
|
||||
L = dx * dx + dy * dy
|
||||
t = 0.0 if L < 1e-12 else max(0.0, min(1.0, ((px - x0) * dx + (py - y0) * dy) / L))
|
||||
return math.hypot(px - (x0 + t * dx), py - (y0 + t * dy))
|
||||
|
||||
|
||||
def stitch_points(tracks=None):
|
||||
u"""GND 缝合过孔的坐标。
|
||||
|
||||
⛔ **顶层的地铜被信号切成很多块岛,每块岛至少要有一个缝合过孔**,
|
||||
否则「移除孤岛」把它整块删掉 —— 而 `rebuildCopperRegion()` /
|
||||
铺铜管理器 / DRC **三个地方都不报错**(刀片 §24.4)。
|
||||
4 mm 网格是为了让最小的岛也有机会摊上一个。
|
||||
"""
|
||||
d = pads()
|
||||
if tracks is None:
|
||||
tracks = board_tracks()
|
||||
r = VIA_D / 2.0 + STITCH_CLR
|
||||
ko = keepouts()
|
||||
# ⛔ **硬功率铜面里不打缝合孔**:那里顶层没有地铜,GND 孔只会在 9 A 铜上掏洞
|
||||
# (4 mm 网格一刀能掏掉 3~4 mm 净铜),而断面校核看不到缝合孔。
|
||||
ko += [rr for (n, _net, _l, rr, _p) in plan() if n.startswith('PWR-')]
|
||||
# 专用 GND 过孔(地岛 / 缺口里的地脚)永远在,网格点让开它们
|
||||
own = gnd_pad_vias()[1]
|
||||
out = list(own)
|
||||
ko += [(vx - VIA_D, vy - VIA_D, vx + VIA_D, vy + VIA_D) for (vx, vy) in own]
|
||||
y = INSET + 2.0
|
||||
while y <= B.BH - INSET - 2.0:
|
||||
x = INSET + 2.0
|
||||
while x <= B.BW - INSET - 2.0:
|
||||
ok = True
|
||||
for k in ko:
|
||||
if k[0] - r < x < k[2] + r and k[1] - r < y < k[3] + r:
|
||||
ok = False
|
||||
break
|
||||
if ok:
|
||||
for p in d:
|
||||
# ⛔ **通孔地脚不能当「自己人」跳过**:缝合过孔落在它身上,
|
||||
# DRC 报「钻孔到钻孔 0 mil」(实测 J1_3 / C2_2 / J33_1 三处)。
|
||||
# 同网也要留钻孔间距,这是工艺规则不是电气规则。
|
||||
pass
|
||||
if p[2] - p[4] / 2 - r < x < p[2] + p[4] / 2 + r and \
|
||||
p[3] - p[5] / 2 - r < y < p[3] + p[5] / 2 + r:
|
||||
ok = False
|
||||
break
|
||||
if ok:
|
||||
for t in tracks:
|
||||
if t[5] == 'GND':
|
||||
continue
|
||||
if _seg_d(x, y, t[0], t[1], t[2], t[3]) < r + t[4] / 2.0:
|
||||
ok = False
|
||||
break
|
||||
if ok:
|
||||
out.append((round(x, 3), round(y, 3)))
|
||||
x += STITCH_PITCH
|
||||
y += STITCH_PITCH
|
||||
return out
|
||||
|
||||
|
||||
def stitch():
|
||||
pts = stitch_points()
|
||||
print(u'GND 缝合过孔 %d 个(%g mm 网格)' % (len(pts), STITCH_PITCH))
|
||||
JS = OPEN + u"""
|
||||
const MIL = %s, P = %s, D = %s, H = %s;
|
||||
let old = 0;
|
||||
for (const v of (await eda.pcb_PrimitiveVia.getAll()||[]))
|
||||
if (String(v.net) === 'GND') { await eda.pcb_PrimitiveVia.delete(v.primitiveId); old++; }
|
||||
let n = 0;
|
||||
for (const p of P) {
|
||||
try { await eda.pcb_PrimitiveVia.create('GND', p[0]*MIL, p[1]*MIL, H*MIL, D*MIL); n++; }
|
||||
catch (e) {}
|
||||
}
|
||||
await eda.pcb_Document.save();
|
||||
return {deleted: old, made: n, now: (await eda.pcb_PrimitiveVia.getAll()||[]).length};
|
||||
""" % (MIL, json.dumps(pts), VIA_D, VIA_HOLE)
|
||||
print(call(JS, timeout=900))
|
||||
|
||||
|
||||
# ── 下发铺铜 ─────────────────────────────────────────────────────────────
|
||||
JS_DEL = OPEN + u"""
|
||||
let n = 0;
|
||||
for (const old of await eda.pcb_PrimitivePour.getAll()) {
|
||||
await eda.pcb_PrimitivePour.delete(old.primitiveId); n++; }
|
||||
await eda.pcb_Document.save();
|
||||
return {deleted: n};
|
||||
"""
|
||||
|
||||
JS_MAKE = OPEN + u"""
|
||||
const PLAN = %s;
|
||||
for (const p of PLAN) {
|
||||
const poly = await eda.pcb_MathPolygon.createComplexPolygon([p.src]);
|
||||
if (!poly) continue;
|
||||
try { await eda.pcb_PrimitivePour.create(p.net, p.layer, poly, 'solid', false,
|
||||
p.name, p.prio, 10); } catch (e) {}
|
||||
}
|
||||
return {made: (await eda.pcb_PrimitivePour.getAll())
|
||||
.map(x => String(x.pourName || '').toUpperCase())};
|
||||
"""
|
||||
|
||||
JS_REBUILD = OPEN + u"""
|
||||
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
const propsOf = el => { for (const k of Object.keys(el)) if (k.startsWith('__reactProps$')) return el[k]; return null; };
|
||||
const fire = (el) => { let t = el; for (let i=0;i<8&&t;i++){const q=propsOf(t);
|
||||
if(q&&typeof q.onClick==='function'){q.onClick({target:t,currentTarget:t,stopPropagation(){},preventDefault(){},nativeEvent:{},button:0,detail:1});return true;} t=t.parentElement;} return false; };
|
||||
const leaf = (txt) => { for (const el of document.querySelectorAll('*'))
|
||||
if (el.children.length===0 && (el.textContent||'').trim()===txt) return el; return null; };
|
||||
const btn = (txt) => { for (const b of document.querySelectorAll('button'))
|
||||
if ((b.innerText||'').trim()===txt && b.offsetParent!==null) return b; return null; };
|
||||
if (!fire(leaf('工具'))) return {err: 'no 工具'};
|
||||
await sleep(900);
|
||||
if (!fire(leaf('铺铜管理器...'))) return {err: 'no 铺铜管理器'};
|
||||
await sleep(2500);
|
||||
const b = btn('重建所有');
|
||||
if (!b) return {err: 'no 重建所有'};
|
||||
return {fired: fire(b)};
|
||||
"""
|
||||
|
||||
JS_POLL = u"return {poured: (await eda.pcb_PrimitivePoured.getAll()).length};"
|
||||
|
||||
JS_DONE = u"""
|
||||
const propsOf = el => { for (const k of Object.keys(el)) if (k.startsWith('__reactProps$')) return el[k]; return null; };
|
||||
const fire = (el) => { let t = el; for (let i=0;i<8&&t;i++){const q=propsOf(t);
|
||||
if(q&&typeof q.onClick==='function'){q.onClick({target:t,currentTarget:t,stopPropagation(){},preventDefault(){},nativeEvent:{},button:0,detail:1});return true;} t=t.parentElement;} return false; };
|
||||
let ok = null;
|
||||
for (const b of document.querySelectorAll('button'))
|
||||
if ((b.innerText||'').trim()==='确认' && b.offsetParent!==null) { ok = b; break; }
|
||||
if (ok) fire(ok);
|
||||
await new Promise(r => setTimeout(r, 1500));
|
||||
await eda.pcb_Document.save();
|
||||
return {poured: (await eda.pcb_PrimitivePoured.getAll()).length,
|
||||
pour: (await eda.pcb_PrimitivePour.getAll()).length};
|
||||
"""
|
||||
|
||||
|
||||
def rebuild():
|
||||
print(u'重建所有铺铜(走 GUI —— `rebuildCopperRegion()` 是坏的,刀片 §24 已查清)…')
|
||||
print(call(JS_REBUILD, timeout=180))
|
||||
last, same = -1, 0
|
||||
for _ in range(40):
|
||||
time.sleep(6)
|
||||
n = call(JS_POLL, timeout=120)['poured']
|
||||
print(u' 已填 %d 块' % n)
|
||||
same = same + 1 if n == last else 0
|
||||
last = n
|
||||
if same >= 2 and n > 0:
|
||||
break
|
||||
print(call(JS_DONE, timeout=180))
|
||||
|
||||
|
||||
def push(ps):
|
||||
print(call(JS_DEL, timeout=300))
|
||||
arr = [{'name': n, 'net': net, 'layer': l, 'prio': p, 'src': rrect_src(*r)}
|
||||
for (n, net, l, r, p) in ps]
|
||||
made = []
|
||||
for k in range(0, len(arr), 12):
|
||||
made += call(JS_MAKE % json.dumps(arr[k:k + 12], ensure_ascii=False), timeout=600)['made']
|
||||
want = set(n.upper() for (n, _net, _l, _r, _p) in ps)
|
||||
got = set(made)
|
||||
print(u'铜面建了 %d / %d 块' % (len(want & got), len(want)))
|
||||
if want - got:
|
||||
print(u' [!] 没建上: %s' % ' '.join(sorted(want - got)))
|
||||
rebuild()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# 铜面按 ROUTE_FIX 的计划位置算(见 pads());板端还没挪件就铺,铜和件对不上。
|
||||
if ('--push' in sys.argv or '--stitch' in sys.argv) and PL.route_fix_moves():
|
||||
sys.exit(u'!! ROUTE_FIX 还有 %d 件没下发到板端(%s);先 place_base --push 再 '
|
||||
u'--adopt-live --dump,然后才能铺铜' % (len(PL.route_fix_moves()),
|
||||
' '.join(sorted(PL.route_fix_moves()))))
|
||||
if '--clear' in sys.argv:
|
||||
print(call(JS_DEL, timeout=300))
|
||||
sys.exit(0)
|
||||
ps = plan()
|
||||
bad = selfcheck(ps)
|
||||
print(u'计划建 %d 块铜面:' % len(ps))
|
||||
for name, net, lay, r, prio in ps:
|
||||
if not name.startswith('GND-') or name.endswith('-0'):
|
||||
print(u' %-12s net=%-9s L%d x %6.2f~%6.2f y %6.2f~%6.2f prio=%d'
|
||||
% (name, net, lay, r[0], r[2], r[1], r[3], prio))
|
||||
print(u' (GND 瓦片 %d 块,绕开天线净空与四个 M3 孔)'
|
||||
% sum(1 for p in ps if p[0].startswith('GND-')))
|
||||
if bad:
|
||||
print(u'[X] 自检 %d 条不过:' % len(bad))
|
||||
for t in bad:
|
||||
print(u' ' + t)
|
||||
else:
|
||||
print(u'[OK] 自检全过(不出内缩线 / 不压净空 / 入口链四段互不重叠)')
|
||||
wbad, wown, wtot = wall_check()
|
||||
bs, bv = B.wall3v3_bridges()
|
||||
print(u'3V3 槽墙:%d 块矩形,盖住 %d/%d 只槽脚;走廊底层桥 %d 段 / 过孔 %d'
|
||||
% (len(B.wall3v3_rects()), wown, wtot, len(bs), len(bv)))
|
||||
if wbad:
|
||||
print(u'[X] 3V3 槽墙自检 %d 条不过:' % len(wbad))
|
||||
for t in wbad:
|
||||
print(u' ' + t)
|
||||
bad += wbad
|
||||
else:
|
||||
print(u'[OK] 3V3 槽墙:槽脚全盖住 / 不压异网针 / 桥的过孔都在墙里')
|
||||
bbad, bown, btot = bus_check()
|
||||
print(u'12V 干道/铜墙:盖住 %d/%d 只插槽电源脚' % (bown, btot))
|
||||
if bbad:
|
||||
print(u'[X] 12V 干道/铜墙自检 %d 条不过:' % len(bbad))
|
||||
for t in bbad:
|
||||
print(u' ' + t)
|
||||
bad += bbad
|
||||
else:
|
||||
print(u'[OK] 12V 干道/铜墙:槽脚全盖住 / 不压异网针')
|
||||
cbad = check_cuts(ps)
|
||||
bad += cbad
|
||||
if '--stitch' in sys.argv:
|
||||
stitch()
|
||||
if '--push' in sys.argv:
|
||||
if bad:
|
||||
sys.exit(u'!! 自检不过,不下发')
|
||||
push(ps)
|
||||
elif '--rebuild' in sys.argv:
|
||||
rebuild()
|
||||
@@ -0,0 +1,163 @@
|
||||
param(
|
||||
[string]$Server = '10.5.5.13',
|
||||
[int]$Passes = 1,
|
||||
[switch]$PriorityCache,
|
||||
[switch]$DpOnly,
|
||||
# 服务器并行试算:每个元素是一组额外参数(如 '--repair --bot-pen-w 1'),
|
||||
# 各自在独立副本里跑,取未完成最少的那个走下面同一套校验 / 接受逻辑。
|
||||
[string[]]$Variants = @(),
|
||||
[int]$PollSec = 60,
|
||||
# 服务器上跑布线的解释器;PyPy 见 PROGRESS(纯 Python 的 A*,JIT 通常快数倍)
|
||||
[string]$Interp = 'python3'
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$Here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$Repo = Resolve-Path (Join-Path $Here '..\..')
|
||||
$Python = 'C:\Python314\python.exe'
|
||||
$Ssh = 'C:\Windows\System32\OpenSSH\ssh.exe'
|
||||
$Scp = 'C:\Windows\System32\OpenSSH\scp.exe'
|
||||
# 临时文件带 PID:允许同时起两批(快的单轮变体 + 慢的拆线重布),互不覆盖
|
||||
$Archive = Join-Path ([System.IO.Path]::GetTempPath()) "dcdc-remote-route-$PID.tgz"
|
||||
$RunScript = Join-Path ([System.IO.Path]::GetTempPath()) "dcdc-remote-run-$PID.sh"
|
||||
$RemoteArchive = Split-Path -Leaf $Archive
|
||||
$Incoming = Join-Path $Here "_base_routes.server.$PID.json"
|
||||
$Cache = Join-Path $Here '_base_routes.json'
|
||||
$Backup = Join-Path $Here '_base_routes.pre-server.json'
|
||||
$Rejected = Join-Path $Here '_base_routes.rejected.json'
|
||||
$remoteRoot = $null
|
||||
|
||||
if ($Passes -lt 1) {
|
||||
throw 'Passes must be at least 1.'
|
||||
}
|
||||
if ($DpOnly -and $Variants.Count -gt 0) {
|
||||
throw '-DpOnly cannot be combined with -Variants.'
|
||||
}
|
||||
|
||||
Push-Location $Repo
|
||||
try {
|
||||
& $Python '.claude\r2\route_base.py' --snapshot-vias
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Failed to refresh board-via snapshot.' }
|
||||
|
||||
$remoteRoot = (& $Ssh $Server 'mktemp -d /tmp/dcdc-route.XXXXXX').Trim()
|
||||
if ($LASTEXITCODE -ne 0 -or $remoteRoot -notmatch '^/tmp/dcdc-route\.[A-Za-z0-9]+$') {
|
||||
throw "Unexpected remote work directory: $remoteRoot"
|
||||
}
|
||||
|
||||
& tar.exe --exclude=*.png --exclude=*.bak --exclude=__pycache__ `
|
||||
--exclude=_remote_route.tgz -czf $Archive `
|
||||
'.claude\r2' '.claude\eda.py' '.claude\pcb_pour.py' '.claude\eda-parts.json'
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Failed to build routing bundle.' }
|
||||
|
||||
& $Scp $Archive "${Server}:${remoteRoot}/"
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Failed to upload routing bundle.' }
|
||||
& $Ssh $Server "tar -xzf ${remoteRoot}/${RemoteArchive} -C ${remoteRoot}"
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Failed to extract routing bundle.' }
|
||||
|
||||
$priority = if ($PriorityCache) { ' --priority-cache' } else { '' }
|
||||
$scope = if ($DpOnly) { ' --dp-only' } else { '' }
|
||||
$timeFmt = "/usr/bin/time -f 'REMOTE_TIME=%e sec CPU=%P RSS=%M KB'"
|
||||
$remoteResult = "${remoteRoot}/.claude/r2/_base_routes.json"
|
||||
|
||||
if ($Variants.Count -eq 0) {
|
||||
$command = "DCDC_ROUTE_SERVER=1 $timeFmt $Interp " +
|
||||
"${remoteRoot}/.claude/r2/route_base.py --offline --replan --passes $Passes$priority$scope"
|
||||
& $Ssh $Server $command
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Remote routing failed.' }
|
||||
}
|
||||
else {
|
||||
# 变体各自一份副本(缓存文件同名,不能共用目录),脱离 ssh 会话在后台跑:
|
||||
# 拆线重布可能要跑很久,ssh 断线不能把计算带走。
|
||||
$lines = @('#!/bin/bash', "cd ${remoteRoot}")
|
||||
for ($k = 0; $k -lt $Variants.Count; $k++) {
|
||||
$v = $Variants[$k]
|
||||
$lines += "mkdir -p v$k && cp -r .claude v$k/"
|
||||
$lines += "( DCDC_ROUTE_SERVER=1 $timeFmt $Interp ${remoteRoot}/v$k/.claude/r2/route_base.py " +
|
||||
"--offline --replan --passes $Passes$priority $v > v$k.log 2>&1; echo `$? > v$k.rc ) &"
|
||||
}
|
||||
$lines += 'wait'
|
||||
$lines += 'touch done'
|
||||
[System.IO.File]::WriteAllText($RunScript, (($lines -join "`n") + "`n"))
|
||||
& $Scp $RunScript "${Server}:${remoteRoot}/run.sh"
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Failed to upload run script.' }
|
||||
& $Ssh $Server "setsid nohup bash ${remoteRoot}/run.sh > /dev/null 2>&1 < /dev/null &"
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Failed to start remote variants.' }
|
||||
for ($k = 0; $k -lt $Variants.Count; $k++) { Write-Host "v${k}: $($Variants[$k])" }
|
||||
|
||||
$start = Get-Date
|
||||
$poll = 0
|
||||
while ($true) {
|
||||
Start-Sleep -Seconds $PollSec
|
||||
$poll++
|
||||
$state = (& $Ssh $Server "test -f ${remoteRoot}/done && echo DONE || echo RUN").Trim()
|
||||
if ($state -eq 'DONE') { break }
|
||||
if ($poll % 5 -eq 0) {
|
||||
$mins = [int]((Get-Date) - $start).TotalMinutes
|
||||
Write-Host "--- $mins min ---"
|
||||
& $Ssh $Server "cd ${remoteRoot} && for f in v*.log; do echo `"`$f: `$(tail -n 1 `$f)`"; done"
|
||||
}
|
||||
}
|
||||
|
||||
$summary = & $Ssh $Server ("cd ${remoteRoot} && for k in `$(seq 0 $($Variants.Count - 1)); do " +
|
||||
"echo `"v`$k rc=`$(cat v`$k.rc) `$(python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); " +
|
||||
"print(len(d[\`"logs\`"]), len(d[\`"segs\`"]), len(d[\`"vias\`"]))' " +
|
||||
"v`$k/.claude/r2/_base_routes.json 2>/dev/null || echo NA)`"; done")
|
||||
$best = $null
|
||||
foreach ($row in $summary) {
|
||||
Write-Host $row
|
||||
if ($row -match '^v(\d+) rc=0 (\d+) (\d+) (\d+)$') {
|
||||
$cand = @{ K = [int]$Matches[1]; Logs = [int]$Matches[2]; Vias = [int]$Matches[4] }
|
||||
if (-not $best -or $cand.Logs -lt $best.Logs -or
|
||||
($cand.Logs -eq $best.Logs -and $cand.Vias -lt $best.Vias)) {
|
||||
$best = $cand
|
||||
}
|
||||
}
|
||||
}
|
||||
for ($k = 0; $k -lt $Variants.Count; $k++) {
|
||||
Write-Host "===== v${k}: $($Variants[$k]) ====="
|
||||
& $Ssh $Server "grep -E '拆线重布|第 [0-9]+ 轮|REMOTE_TIME|Traceback|Error' ${remoteRoot}/v$k.log | tail -n 25"
|
||||
}
|
||||
if (-not $best) { throw 'No remote variant finished successfully.' }
|
||||
Write-Host "Best variant: v$($best.K) ($($Variants[$best.K])) with $($best.Logs) unfinished."
|
||||
$remoteResult = "${remoteRoot}/v$($best.K)/.claude/r2/_base_routes.json"
|
||||
}
|
||||
|
||||
if ($DpOnly) {
|
||||
Write-Host 'Server-side D+/- diagnostic completed; local route cache was not changed.'
|
||||
}
|
||||
else {
|
||||
& $Scp "${Server}:${remoteResult}" $Incoming
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Failed to download routing result.' }
|
||||
$result = Get-Content -LiteralPath $Incoming -Raw | ConvertFrom-Json
|
||||
if ($result.schema -ne 7 -or $result.slot_rot -ne 0 -or
|
||||
$result.usb_slot_ref.'1' -ne 'J34' -or $result.usb_slot_ref.'5' -ne 'J38') {
|
||||
throw 'Downloaded routing cache failed topology validation.'
|
||||
}
|
||||
$current = if (Test-Path -LiteralPath $Cache) {
|
||||
Get-Content -LiteralPath $Cache -Raw | ConvertFrom-Json
|
||||
} else { $null }
|
||||
if ($current -and $result.logs.Count -gt $current.logs.Count) {
|
||||
Move-Item -LiteralPath $Incoming -Destination $Rejected -Force
|
||||
Write-Host "Rejected worse server result: $($result.logs.Count) unfinished > current $($current.logs.Count)."
|
||||
}
|
||||
else {
|
||||
if ($current) {
|
||||
Copy-Item -LiteralPath $Cache -Destination $Backup -Force
|
||||
}
|
||||
Move-Item -LiteralPath $Incoming -Destination $Cache -Force
|
||||
Write-Host "Accepted server result: $($result.segs.Count) segments, $($result.vias.Count) vias, $($result.logs.Count) unfinished."
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
foreach ($f in @($Archive, $RunScript)) {
|
||||
if (Test-Path -LiteralPath $f) { Remove-Item -LiteralPath $f -Force }
|
||||
}
|
||||
if ($remoteRoot -and $remoteRoot -match '^/tmp/dcdc-route\.[A-Za-z0-9]+$') {
|
||||
# ⛔ 模式里的 `(v[0-9]+/)?[.]claude` 是故意的:直接写目录串的话,执行这条命令的
|
||||
# 远程 shell 命令行里也含这个串,pkill 先把自己杀了(ssh 返回 255),后面的 rm
|
||||
# 根本不执行。按路径匹配而不是按 `python3`,换成 pypy3 也杀得到。
|
||||
& $Ssh $Server "pkill -f '${remoteRoot}/(v[0-9]+/)?[.]claude/r2/route_base' ; rm -rf -- '${remoteRoot}'"
|
||||
}
|
||||
}
|
||||
文件差异内容过多而无法显示
加载差异
文件差异内容过多而无法显示
加载差异
@@ -0,0 +1,359 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
u"""R2 底板 PCB 的设计规则(M8b)。
|
||||
|
||||
/c/Python314/python .claude/r2/rules_base.py # 只反查 + 打印,不下发
|
||||
/c/Python314/python .claude/r2/rules_base.py --dump # 重抓「位号.引脚 → 网名」
|
||||
/c/Python314/python .claude/r2/rules_base.py --push # 下发规则 + 差分对 + 网络指派
|
||||
/c/Python314/python .claude/r2/rules_base.py --audit # 读回审计
|
||||
|
||||
做法与 `pcb_rules.py`(刀片)同源,**那一份的文件头是唯一的说明书**,四个坑一个没变:
|
||||
|
||||
· `overwriteCurrentRuleConfiguration(x)` 要 config **本体**,不要 `{name,config}` 包装
|
||||
· `getCurrentRuleConfiguration()` 是**嵌套形状**,`getRuleConfiguration(能力集)` 是**平形状**
|
||||
· 下发之后**别再去 GUI 换「配置」下拉框** —— 换一次整套重置,网络指派全部悬空
|
||||
· `overwriteNetRules` / `overwriteNetByNetRules` 都是**整表覆盖**,先读回再合并
|
||||
|
||||
## 和刀片那份不同的四处
|
||||
|
||||
1. **两层板**,当前配置是 `JLCPCB Capability(Two Layers Board)`:
|
||||
`Track` / `Safe Spacing` 下只有 `copperThickness1oz` / `2oz` 两条,
|
||||
**没有内层条目**,所以刀片那段「补 0.5 oz 内层」整块删掉。
|
||||
|
||||
2. **入口链是 9 A**(方案 §6.4),不是刀片的 1.45 A。线宽规则给到
|
||||
`strokeWidthMax = 12`,真正的载流靠 `pour_base` 的断面校核保证,不靠 DRC。
|
||||
|
||||
3. **七对差分**:五个槽 + ESP32 原生 USB(D13)+ 上行 USB-C。
|
||||
刀片那组 `W 0.28 / S 0.18` 是**四层薄介质**的数,两层 1.6 mm 板照搬会变成 ~137 Ω。
|
||||
按方案 §6.3 取 **W 1.2 / S 0.2 ⇒ ~94 Ω**。
|
||||
⚠ `min` 必须留到 0.25:1.2 的线接不进 U32 那颗 0.5 mm 间距 QFN-64 的焊盘,
|
||||
末端一定要缩颈,min 卡在 1.2 会把整条线判成违规。
|
||||
|
||||
4. **自动命名网一个都不指派**:底板上 22 个 `$xNyy` 全是小信号
|
||||
(泄放电阻串、Buck 反馈、DAC 的 RC、风扇 MOS 栅极),走默认线宽即可。
|
||||
⇒ 没有「按位号.引脚反查自动网名」那一步,也就没有那一类静默失效。
|
||||
"""
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, os.path.dirname(HERE))
|
||||
sys.path.insert(0, HERE)
|
||||
from eda import run # noqa: E402
|
||||
import pcb_base as B # noqa: E402
|
||||
|
||||
PCB = B.PCB
|
||||
LAYERS = 2
|
||||
NETS_FILE = os.path.join(HERE, '_base_pcb_nets.json')
|
||||
|
||||
# ── 线宽规则(mm,外层 1 oz)────────────────────────────────────────────
|
||||
# 载流靠灌铜断面保证、不靠 DRC;min 一律压到 0.15~0.3,否则 0402 焊盘扇不出去。
|
||||
TRACK_RULES = {
|
||||
'PWR9A': {'strokeWidthDefault': 2.0, 'strokeWidthMin': 0.30, 'strokeWidthMax': 12.0},
|
||||
'PWR12V': {'strokeWidthDefault': 1.5, 'strokeWidthMin': 0.30, 'strokeWidthMax': 12.0},
|
||||
'FAN12V': {'strokeWidthDefault': 0.8, 'strokeWidthMin': 0.25, 'strokeWidthMax': 3.0},
|
||||
'PWR5V': {'strokeWidthDefault': 0.6, 'strokeWidthMin': 0.20, 'strokeWidthMax': 3.0},
|
||||
'PWR3V3': {'strokeWidthDefault': 0.5, 'strokeWidthMin': 0.15, 'strokeWidthMax': 3.0},
|
||||
}
|
||||
|
||||
# ── 有名字的网络 → 线宽规则 ────────────────────────────────────────────
|
||||
# ⚠ `+12V` 是**一个网**:从 Rs6 下游一路到 5 个槽的 12V 针(43 只脚)。
|
||||
# 干路 9 A 那一段和「母线 → 单槽」1.5 A 那一段线宽不同,DRC 只能按一条规则判 ⇒
|
||||
# 取干路的 `PWR9A`(default 2.0 只是画线的起手宽度),断面由 `pour_base` 逐段校核。
|
||||
NET_TRACK = {
|
||||
'VIN_RAW': 'PWR9A', # J1 中心极 → F1
|
||||
'VIN_FUSED': 'PWR9A', # F1 → Q1 反接保护
|
||||
'VIN_SW': 'PWR9A', # Q1 → 储能 → Rs6 上游
|
||||
'+12V': 'PWR9A', # Rs6 下游 → 母线 → 5 槽 + 两颗 Buck + 风扇
|
||||
'FAN_12V_SW': 'FAN12V', # 风扇下管开关点(R1 §22.6 定的 0.8)
|
||||
'+5V': 'PWR5V', # U30 → 5 槽(每槽给刀片 U5 TPS22918)
|
||||
'+3V3': 'PWR3V3', # U19 → ESP32 / mux / DAC / 5 槽逻辑
|
||||
'+3V3_HUB': 'PWR3V3', # U31 AMS1117 → U32(与 +3V3 只共地)
|
||||
}
|
||||
|
||||
# ── 铺铜焊盘全连接(理由同刀片:不是怕十字花烧,是**铺完之后断面要量得准**)──
|
||||
PLANE_RULE = 'PLANE-DIRECT'
|
||||
PLANE_CONNECT_DIRECT = 1
|
||||
PLANE_NETS = ['VIN_RAW', 'VIN_FUSED', 'VIN_SW', '+12V']
|
||||
|
||||
# ── 差分对(mm)── 方案 §6.3:两层 1.6 mm 微带,W 1.2 / S 0.2 ⇒ ~94 Ω ──
|
||||
DIFF = {'strokeWidthDefault': 1.2, 'strokeWidthMin': 0.25, 'strokeWidthMax': 1.6,
|
||||
'differentailPairSpacingDefault': 0.20, 'differentailPairSpacingMin': 0.15,
|
||||
'differentailPairLenTolerMax': 1.0} # intra-pair skew 目标 ≤1 mm(§6.3)
|
||||
PAIR_RULE = 'USBPAIR'
|
||||
PAIR_CLEARANCE = 0.20
|
||||
PAIRS = [('USB1', 'USB1_DP', 'USB1_DM'),
|
||||
('USB2', 'USB2_DP', 'USB2_DM'),
|
||||
('USB3', 'USB3_DP', 'USB3_DM'),
|
||||
('USB4', 'USB4_DP', 'USB4_DM'),
|
||||
('USB5', 'USB5_DP', 'USB5_DM'),
|
||||
('USB6', 'USB6_DP', 'USB6_DM'), # D13:Hub 第 6 口 → ESP32 原生 USB
|
||||
('USBUP', 'UP_DP', 'UP_DM')] # J23 USB-C → U32 上行
|
||||
|
||||
# ⚠ 0.2,不是刀片的 0.15。两层板走 JLC 标准工艺(0.2/0.2)最便宜,
|
||||
# 而底板上最紧的封装是 U32 QFN-64(0.5 间距)与 J23 USB-C,实测都过得去。
|
||||
# 真刷出「贴片焊盘到贴片焊盘」的假错(全落在某个买来的封装内部)再降到 0.15。
|
||||
MIN_CLEARANCE = 0.20
|
||||
|
||||
OPEN = ("await eda.dmt_EditorControl.openDocument('%s');"
|
||||
"await new Promise(r=>setTimeout(r,2000));" % PCB)
|
||||
|
||||
|
||||
def call(body, timeout=300):
|
||||
r = run(OPEN + body, timeout=timeout)
|
||||
if not r.get('success'):
|
||||
print(u'失败: ' + json.dumps(r, ensure_ascii=False)[:600])
|
||||
sys.exit(1)
|
||||
return r['result']
|
||||
|
||||
|
||||
def dump_nets():
|
||||
u"""重抓「位号.引脚 → 网名」。**每次导入变更之后必跑**。"""
|
||||
r = call("const out = {};"
|
||||
"for (const c of await eda.pcb_PrimitiveComponent.getAll()) {"
|
||||
" const ps = await eda.pcb_PrimitiveComponent.getAllPinsByPrimitiveId("
|
||||
" c.getState_PrimitiveId());"
|
||||
" out[c.designator] = ps.map(p=>[p.padNumber, p.net]); }"
|
||||
"return out;")
|
||||
io.open(NETS_FILE, 'w', encoding='utf-8').write(json.dumps(r, ensure_ascii=False))
|
||||
print(u'已写 _base_pcb_nets.json:%d 件' % len(r))
|
||||
return r
|
||||
|
||||
|
||||
def nets():
|
||||
if not os.path.exists(NETS_FILE):
|
||||
return dump_nets()
|
||||
return json.load(io.open(NETS_FILE, encoding='utf-8'))
|
||||
|
||||
|
||||
def check_names():
|
||||
u"""下发前先确认:要指派的网名在板上真的存在(写错名字 = 规则悬空,DRC 不报)。"""
|
||||
have = set()
|
||||
for _ref, pads in nets().items():
|
||||
for _pn, net in pads:
|
||||
if net:
|
||||
have.add(net)
|
||||
miss = [n for n in sorted(set(list(NET_TRACK) + PLANE_NETS)) if n not in have]
|
||||
pmiss = [p for t in PAIRS for p in t[1:] if p not in have]
|
||||
if miss or pmiss:
|
||||
print(u' [!!] 板上没有这些网,**不下发**:线宽/铺铜 %s;差分 %s' % (miss, pmiss))
|
||||
sys.exit(1)
|
||||
return True
|
||||
|
||||
|
||||
CAP_JS = u"""
|
||||
const D = eda.pcb_Drc;
|
||||
const cur = await D.getCurrentRuleConfiguration();
|
||||
const c = JSON.parse(JSON.stringify(cur.config));
|
||||
const TRACKS = %s, DW = %s, MINC = %s, PAIRRULE = %s, PAIRC = %s;
|
||||
const PLANERULE = %s, PLANEMODE = %s;
|
||||
|
||||
const setTrack = (e, mn, df, mx) => {
|
||||
for (const k of Object.keys(e.form.data))
|
||||
Object.assign(e.form.data[k], {minValue: mn, defaultValue: df, maxValue: mx});
|
||||
};
|
||||
const T = c['Physics']['Track'], SS = c['Spacing']['Safe Spacing'];
|
||||
|
||||
setTrack(T['copperThickness1oz'], 0.127, 0.254, 2.54);
|
||||
Object.assign(c['Physics']['Via Size']['viaSize'].form,
|
||||
{viaOuterdiameterMin: 0.4, viaInnerdiameterMin: 0.3});
|
||||
for (const name of Object.keys(TRACKS)) {
|
||||
const f = TRACKS[name];
|
||||
const e = JSON.parse(JSON.stringify(T['copperThickness1oz']));
|
||||
e.editName = name; e.isSetDefault = false; e.unit = 'mm';
|
||||
setTrack(e, f.strokeWidthMin, f.strokeWidthDefault, f.strokeWidthMax);
|
||||
T[name] = e;
|
||||
}
|
||||
const dp = c['Physics']['Differential Pair']['differentialPair'].form;
|
||||
for (const k of Object.keys(dp.strokeWidthTables.data))
|
||||
Object.assign(dp.strokeWidthTables.data[k],
|
||||
{minValue: DW.strokeWidthMin, defaultValue: DW.strokeWidthDefault,
|
||||
maxValue: DW.strokeWidthMax});
|
||||
for (const k of Object.keys(dp.diffPairSpacingTables.data))
|
||||
Object.assign(dp.diffPairSpacingTables.data[k],
|
||||
{minValue: DW.differentailPairSpacingMin,
|
||||
defaultValue: DW.differentailPairSpacingDefault});
|
||||
dp.differentailPairLenTolerMax = DW.differentailPairLenTolerMax;
|
||||
if (!SS[PAIRRULE]) {
|
||||
const e = JSON.parse(JSON.stringify(SS['copperThickness1oz']));
|
||||
e.editName = PAIRRULE; e.isSetDefault = false;
|
||||
for (const tk of Object.keys(e.tables || {}))
|
||||
for (const row of (e.tables[tk].content || []))
|
||||
for (let i = 0; i < row.length; i++)
|
||||
if (typeof row[i] === 'number') row[i] = PAIRC;
|
||||
SS[PAIRRULE] = e;
|
||||
}
|
||||
// 安全间距全表**归一到 MINC**:≤0.2 的格子一律赋值成 MINC(不是「只抬不降」);
|
||||
// 0.254 / 0.3 那几格是孔/板边相关的,不动。
|
||||
let bumped = 0;
|
||||
for (const key of Object.keys(SS))
|
||||
if (key !== PAIRRULE)
|
||||
for (const tk of Object.keys(SS[key].tables || {}))
|
||||
for (const row of (SS[key].tables[tk].content || []))
|
||||
for (let i = 0; i < row.length; i++)
|
||||
if (typeof row[i] === 'number' && row[i] <= 0.2001 &&
|
||||
Math.abs(row[i] - MINC) > 1e-6) { row[i] = MINC; bumped++; }
|
||||
const CZ = c['Plane']['Copper Zone'];
|
||||
if (!CZ[PLANERULE]) {
|
||||
const e = JSON.parse(JSON.stringify(CZ['copperRegion']));
|
||||
e.editName = PLANERULE; e.isSetDefault = false;
|
||||
CZ[PLANERULE] = e;
|
||||
}
|
||||
for (const m of ['singleLayerPadModel', 'multiLayerPadModel'])
|
||||
for (const k of Object.keys(CZ[PLANERULE].form[m].data))
|
||||
CZ[PLANERULE].form[m].data[k].connectMode = PLANEMODE;
|
||||
|
||||
const ok = await D.overwriteCurrentRuleConfiguration(c);
|
||||
return {ok: ok === true, bumped: bumped, name: cur.name,
|
||||
track: Object.keys(T), spacing: Object.keys(SS), plane: Object.keys(CZ)};
|
||||
"""
|
||||
|
||||
|
||||
def push():
|
||||
check_names()
|
||||
r = call(CAP_JS % (json.dumps(TRACK_RULES), json.dumps(DIFF), MIN_CLEARANCE,
|
||||
json.dumps(PAIR_RULE), PAIR_CLEARANCE,
|
||||
json.dumps(PLANE_RULE), PLANE_CONNECT_DIRECT))
|
||||
if not r.get('ok'):
|
||||
print(u' [!!] overwriteCurrentRuleConfiguration 返回非 true,没生效')
|
||||
sys.exit(1)
|
||||
print(u' 规则配置已下发:线宽条目 %s' % ' '.join(r['track']))
|
||||
print(u' 安全间距归一 %d 格 → %.2f mm;配置名 "%s"' % (r['bumped'], MIN_CLEARANCE, r['name']))
|
||||
|
||||
n = call("""
|
||||
const P = %s;
|
||||
let k = 0;
|
||||
for (const d of await eda.pcb_Drc.getAllDifferentialPairs())
|
||||
await eda.pcb_Drc.deleteDifferentialPair(d.name);
|
||||
for (const [name, p, m] of P) { if (await eda.pcb_Drc.createDifferentialPair(name, p, m)) k++; }
|
||||
return k;
|
||||
""" % json.dumps(PAIRS))
|
||||
print(u' 差分对 %d / %d 对' % (n, len(PAIRS)))
|
||||
if n != len(PAIRS):
|
||||
print(u' [!!] 有对没建上 —— 网络名在这块板上不存在')
|
||||
|
||||
n = call("""
|
||||
const T = %s, PN = %s, PR = %s;
|
||||
const pset = new Set(PN);
|
||||
const rules = await eda.pcb_Drc.getNetRules();
|
||||
let k = 0, p = 0;
|
||||
const seen = [];
|
||||
for (const r of rules) {
|
||||
if (r.type !== 'net') continue;
|
||||
if (T[r.name]) { r['Track'] = T[r.name]; k++; seen.push(r.name); }
|
||||
if (pset.has(r.name)) { r['Copper Zone'] = PR; p++; }
|
||||
}
|
||||
await eda.pcb_Drc.overwriteNetRules(rules);
|
||||
return {track: k, plane: p, seen: seen};
|
||||
""" % (json.dumps(NET_TRACK, ensure_ascii=False), json.dumps(PLANE_NETS, ensure_ascii=False),
|
||||
json.dumps(PLANE_RULE)))
|
||||
print(u' 线宽指派 %d / %d 个网络' % (n['track'], len(NET_TRACK)))
|
||||
if n['track'] != len(NET_TRACK):
|
||||
print(u' [!!] 板上找不到: %s' % ' '.join(sorted(set(NET_TRACK) - set(n['seen']))))
|
||||
print(u' 铺铜全连接指派 %d / %d(%s)' % (n['plane'], len(PLANE_NETS), ' '.join(PLANE_NETS)))
|
||||
|
||||
n = call("""
|
||||
const PAIRS = %s, RULE = %s;
|
||||
const cur = await eda.pcb_Drc.getNetByNetRules();
|
||||
const old = (cur && cur['Safe Spacing']) ? cur['Safe Spacing'] : [];
|
||||
const key = e => [e.net1.name, e.net2.name].sort().join('|');
|
||||
const want = new Set(PAIRS.map(([n, dp, dm]) => [dp, dm].sort().join('|')));
|
||||
const list = [];
|
||||
let dropped = 0;
|
||||
for (const e of old) {
|
||||
if (e.rule === RULE && !want.has(key(e))) { dropped++; continue; }
|
||||
list.push(e);
|
||||
}
|
||||
const have = new Set(list.map(key));
|
||||
let k = 0;
|
||||
for (const [name, dp, dm] of PAIRS) {
|
||||
const e = {net1: {type: 'net', name: dp}, net2: {type: 'net', name: dm},
|
||||
rule: RULE, copperRuleName: null, planeRuleName: null};
|
||||
if (have.has(key(e))) continue;
|
||||
list.push(e); k++;
|
||||
}
|
||||
const ok = await eda.pcb_Drc.overwriteNetByNetRules(
|
||||
{'Safe Spacing': list, 'Creepage Distance': (cur && cur['Creepage Distance']) || []});
|
||||
const back = await eda.pcb_Drc.getNetByNetRules();
|
||||
return {added: k, dropped: dropped, ok: ok === true,
|
||||
now: (back['Safe Spacing'] || []).length};
|
||||
""" % (json.dumps(PAIRS), json.dumps(PAIR_RULE)))
|
||||
print(u' 对内间距:新增 %d / 清掉过期 %d,现共 %d 条(%s = %.2f mm)'
|
||||
% (n['added'], n['dropped'], n['now'], PAIR_RULE, PAIR_CLEARANCE))
|
||||
|
||||
call("await eda.pcb_Document.save(); return 1;")
|
||||
print(u' 已保存')
|
||||
print(u'')
|
||||
print(u'⚠ 别去 GUI 的 设计 → 设计规则… 里换「配置」下拉框:换一次整套重置,')
|
||||
print(u' %d 个网络的线宽指派立刻悬空,而 DRC 不会报错。' % len(NET_TRACK))
|
||||
print(u'⚠ 再导入一次变更之后顺序是:--dump → --push,少跑 --dump 就是拿旧网名指派。')
|
||||
|
||||
|
||||
AUDIT_JS = u"""
|
||||
const c = await eda.pcb_Drc.getCurrentRuleConfiguration();
|
||||
const rules = await eda.pcb_Drc.getNetRules();
|
||||
const nbn = await eda.pcb_Drc.getNetByNetRules();
|
||||
return {
|
||||
cfgName: c && c.name,
|
||||
layers: await eda.pcb_Layer.getTheNumberOfCopperLayers(),
|
||||
track: Object.keys(c.config.Physics['Track']),
|
||||
pairs: (await eda.pcb_Drc.getAllDifferentialPairs()).map(d => d.name).sort(),
|
||||
assigned: rules.filter(r => r['Track'] && r['Track'] !== 'default')
|
||||
.map(r => [r.name, r['Track']]).sort(),
|
||||
planeAssigned: rules.filter(r => r['Copper Zone'] && r['Copper Zone'] !== 'default')
|
||||
.map(r => [r.name, r['Copper Zone']]).sort(),
|
||||
nbn: ((nbn && nbn['Safe Spacing']) || []).length,
|
||||
};
|
||||
"""
|
||||
|
||||
|
||||
def audit():
|
||||
r = call(AUDIT_JS)
|
||||
bad = []
|
||||
|
||||
def chk(cond, good, msg):
|
||||
print((u' [OK] ' if cond else u' [!!] ') + (good if cond else msg))
|
||||
if not cond:
|
||||
bad.append(msg)
|
||||
|
||||
print(u'──── 审计(配置 "%s")────' % r['cfgName'])
|
||||
chk(r['layers'] == LAYERS, u'铜层数 %d' % LAYERS, u'铜层数是 %s,应为 %d' % (r['layers'], LAYERS))
|
||||
miss = [k for k in TRACK_RULES if k not in r['track']]
|
||||
chk(not miss, u'%d 条线宽条目都在' % len(TRACK_RULES), u'缺线宽条目 %s' % miss)
|
||||
got = dict(r['assigned'])
|
||||
wrong = [(n, got.get(n), v) for n, v in sorted(NET_TRACK.items()) if got.get(n) != v]
|
||||
chk(not wrong, u'%d 个网络的线宽指派全对' % len(NET_TRACK), u'指派不对: %s' % wrong[:6])
|
||||
chk(sorted(r['pairs']) == sorted([p[0] for p in PAIRS]),
|
||||
u'差分对 %d 对: %s' % (len(r['pairs']), ' '.join(r['pairs'])),
|
||||
u'差分对是 %s,应为 %s' % (r['pairs'], [p[0] for p in PAIRS]))
|
||||
chk(r['nbn'] == len(PAIRS), u'对内间距 %d 条' % r['nbn'],
|
||||
u'对内间距 %d 条,应为 %d' % (r['nbn'], len(PAIRS)))
|
||||
gotp = dict(r['planeAssigned'])
|
||||
missp = [n for n in PLANE_NETS if gotp.get(n) != PLANE_RULE]
|
||||
chk(not missp, u'铺铜全连接 %d 个网络' % len(PLANE_NETS), u'没指派上: %s' % missp)
|
||||
print(u' ' + (u'[OK] 全部通过' if not bad else u'[X] %d 条不过' % len(bad)))
|
||||
return bad
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(u'底板 PCB=%s(%d 层)' % (PCB, LAYERS))
|
||||
if '--dump' in sys.argv:
|
||||
dump_nets()
|
||||
if '--push' in sys.argv:
|
||||
push()
|
||||
audit()
|
||||
elif '--audit' in sys.argv:
|
||||
audit()
|
||||
elif '--dump' not in sys.argv:
|
||||
check_names()
|
||||
print(u'线宽规则:')
|
||||
for k, v in sorted(TRACK_RULES.items()):
|
||||
print(u' %-8s 默认 %.2f 最小 %.2f 最大 %.2f'
|
||||
% (k, v['strokeWidthDefault'], v['strokeWidthMin'], v['strokeWidthMax']))
|
||||
print(u'网络指派:')
|
||||
for k, v in sorted(NET_TRACK.items()):
|
||||
print(u' %-12s → %s' % (k, v))
|
||||
print(u'差分对 %d:%s' % (len(PAIRS), ' '.join(p[0] for p in PAIRS)))
|
||||
print(u'安全间距 %.2f;铺铜全连接 %s' % (MIN_CLEARANCE, ' '.join(PLANE_NETS)))
|
||||
在新工单中引用
屏蔽一个用户