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
这个提交包含在:
zhou yang
2026-09-10 21:02:10 +08:00
共同撰写人 Claude Opus 5
父节点 18d3d69856
当前提交 efcba30a0c
共修改 280 个文件,包含 92007 行新增和 15053 行删除
+60 -22
查看文件
@@ -28,32 +28,26 @@
"EFUSE_EN2": 5, "EFUSE_EN2": 5,
"EFUSE_EN3": 6, "EFUSE_EN3": 6,
"EFUSE_EN4": 7, "EFUSE_EN4": 7,
"SCL": 8, "EFUSE_EN5": 12,
"FAN_PWM": 24,
"FAN_TACH": 25,
"IOEXP_INT#": 31,
"KEY_OE#": 23,
"KEY_RCLK": 11,
"KEY_SER": 9, "KEY_SER": 9,
"KEY_SRCLK": 10, "KEY_SRCLK": 10,
"KEY_RCLK": 11, "NTC1": 39,
"EFUSE_EN5": 12, "NTC2": 38,
"SCL": 8,
"SDA": 22,
"UP_VBUS_DET": 36,
"VBUS_EN1": 17, "VBUS_EN1": 17,
"VBUS_EN2": 18, "VBUS_EN2": 18,
"VBUS_EN3": 19, "VBUS_EN3": 19,
"VBUS_EN4": 20, "VBUS_EN4": 20,
"VBUS_EN5": 21, "VBUS_EN5": 21
"SDA": 22,
"KEY_OE#": 23,
"FAN_PWM": 24,
"FAN_TACH": 25,
"IOEXP_INT#": 31,
"UP_VBUS_DET": 36,
"NTC2": 38,
"NTC1": 39
}, },
"pin2io": { "pin2io": {
"4": "IO4",
"5": "IO5",
"6": "IO6",
"7": "IO7",
"8": "IO15",
"9": "IO16",
"10": "IO17", "10": "IO17",
"11": "IO18", "11": "IO18",
"12": "IO8", "12": "IO8",
@@ -67,12 +61,56 @@
"24": "IO47", "24": "IO47",
"25": "IO48", "25": "IO48",
"31": "IO38", "31": "IO38",
"36": "IO44",
"38": "IO2",
"39": "IO1",
"32": "IO39", "32": "IO39",
"33": "IO40", "33": "IO40",
"34": "IO41", "34": "IO41",
"35": "IO42" "35": "IO42",
"36": "IO44",
"38": "IO2",
"39": "IO1",
"4": "IO4",
"5": "IO5",
"6": "IO6",
"7": "IO7",
"8": "IO15",
"9": "IO16"
},
"r2_base": {
"EFUSE_EN1": 21,
"EFUSE_EN2": 20,
"EFUSE_EN3": 19,
"EFUSE_EN4": 18,
"EFUSE_EN5": 17,
"FAN_EN": 7,
"FAN_PWM": 6,
"FAN_TACH_IN": 5,
"FAN_TACH_OUT": 4,
"HUB_RST": 12,
"INA_ALERT": 31,
"INT#": 32,
"IO0": 27,
"MUX_RST#": 33,
"NTC1": 39,
"NTC2": 38,
"PRSNT1#": 11,
"PRSNT2#": 10,
"PRSNT3#": 9,
"PRSNT4#": 8,
"PRSNT5#": 22,
"SCL": 35,
"SDA": 34,
"UP_VBUS_DET": 23,
"USB6_DM": 13,
"USB6_DP": 14
},
"r2_base_nc": {
"15": "IO3 strapping(JTAG_SEL)",
"16": "IO46 strapping + input-only",
"24": "IO47 备用",
"25": "IO48 备用",
"26": "IO45 strapping(VDD_SPI)",
"28": "IO35 留给 R8 版八线 PSRAM",
"29": "IO36 同上",
"30": "IO37 同上"
} }
} }
+30 -1
查看文件
@@ -1,6 +1,35 @@
{ {
"opened": true, "opened": true,
"seq": [], "seq": [
{
"top": 0,
"txt": "DZ3\n\t\t\t\t\n\n\t修改封装\tSOD-123_L2.7-W1.6-LS3.7-RD-1:封装\tSOD-123_L2.7-W1.6-LS3.7-RD-1\tSOT-23_L2.9-W1.3-P1.90-LS2.4-BR\n\nJ1\n\t\t\t\t\n\n\t移除元件\tJ1\tJ1\t\n\nJ26\n\t\t\t\t\n\n\t修改封装\tCONN-TH_4P-P2.54_CAX_2510-4A:封装\tCONN-TH_4P-P2.54_CAX_2510-4A\tR0402\n\nJ27\n\t\t\t\t\n\n\t修改属性\tJ26:通道ID\t$17I175\t$17I174\n\n\t修改属性\tJ26:位号\tJ27\tJ26\n\n\t修改网络\tJ26:3\tFAN_OUT_TACH_RAW\tFAN_IN_TACH_RAW\n\nQ7\n\t\t\t\t\n\n\t修改封装\tSOT-23_L2.9-W1.3-P1.90-LS2.4-BR:封装\tSOT-23_L2.9-W1.3-P1.90-LS2.4-BR\tCONN-TH_4P-P2.54_CAX_2510-4A\n\nQ8\n\t\t\t\t\n\n\t修改封装\tSOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR:封装\tSOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR\tR0402"
},
{
"top": 188,
"txt": "DZ3\n\t\t\t\t\n\n\t修改封装\tSOD-123_L2.7-W1.6-LS3.7-RD-1:封装\tSOD-123_L2.7-W1.6-LS3.7-RD-1\tSOT-23_L2.9-W1.3-P1.90-LS2.4-BR\n\nJ1\n\t\t\t\t\n\n\t移除元件\tJ1\tJ1\t\n\nJ26\n\t\t\t\t\n\n\t修改封装\tCONN-TH_4P-P2.54_CAX_2510-4A:封装\tCONN-TH_4P-P2.54_CAX_2510-4A\tR0402\n\nJ27\n\t\t\t\t\n\n\t修改属性\tJ26:通道ID\t$17I175\t$17I174\n\n\t修改属性\tJ26:位号\tJ27\tJ26\n\n\t修改网络\tJ26:3\tFAN_OUT_TACH_RAW\tFAN_IN_TACH_RAW\n\nQ7\n\t\t\t\t\n\n\t修改封装\tSOT-23_L2.9-W1.3-P1.90-LS2.4-BR:封装\tSOT-23_L2.9-W1.3-P1.90-LS2.4-BR\tCONN-TH_4P-P2.54_CAX_2510-4A\n\nQ8\n\t\t\t\t\n\n\t修改封装\tSOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR:封装\tSOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR\tR0402\n\nQ9\n\t\t\t\t\n\n\t修改封装\tSOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR:封装\tSOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR\tR0402\n\nQ10\n\t\t\t\t\n\n\t修改属性\tQ9:通道ID\t$17I223\t$17I222\n\n\t修改属性\tQ9:位号\tQ10\tQ9\n\n\t修改网络\tQ9:1\t$17N233\t$17N226\n\n\t修改网络\tQ9:3\tFAN_TACH_OUT\tFAN_TACH_IN\n\n\t增加元件\tQ10\t-\tQ10\n\nR746\n\t\t\t\t\n\n\t修改封装\tR0402:封装\tR0402\tSOD-123_L2.7-W1.6-LS3.7-RD-1\n\nR747\n\t\t\t\t\n\n\t修改封装\tR0402:封装\tR0402\tDC-IN-TH_DC-005-2\n\nR748\n\t\t\t\t\n\n\t修改封装\tR0402:封装\tR0402\tSOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR\n\nR749\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1003TCE\t0402WGF1001TCE\n\nR750\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1002TCE\t0402WGF1003TCE"
},
{
"top": 376,
"txt": "\t修改封装\tCONN-TH_4P-P2.54_CAX_2510-4A:封装\tCONN-TH_4P-P2.54_CAX_2510-4A\tR0402\n\nJ27\n\t\t\t\t\n\n\t修改属性\tJ26:通道ID\t$17I175\t$17I174\n\n\t修改属性\tJ26:位号\tJ27\tJ26\n\n\t修改网络\tJ26:3\tFAN_OUT_TACH_RAW\tFAN_IN_TACH_RAW\n\nQ7\n\t\t\t\t\n\n\t修改封装\tSOT-23_L2.9-W1.3-P1.90-LS2.4-BR:封装\tSOT-23_L2.9-W1.3-P1.90-LS2.4-BR\tCONN-TH_4P-P2.54_CAX_2510-4A\n\nQ8\n\t\t\t\t\n\n\t修改封装\tSOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR:封装\tSOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR\tR0402\n\nQ9\n\t\t\t\t\n\n\t修改封装\tSOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR:封装\tSOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR\tR0402\n\nQ10\n\t\t\t\t\n\n\t修改属性\tQ9:通道ID\t$17I223\t$17I222\n\n\t修改属性\tQ9:位号\tQ10\tQ9\n\n\t修改网络\tQ9:1\t$17N233\t$17N226\n\n\t修改网络\tQ9:3\tFAN_TACH_OUT\tFAN_TACH_IN\n\n\t增加元件\tQ10\t-\tQ10\n\nR746\n\t\t\t\t\n\n\t修改封装\tR0402:封装\tR0402\tSOD-123_L2.7-W1.6-LS3.7-RD-1\n\nR747\n\t\t\t\t\n\n\t修改封装\tR0402:封装\tR0402\tDC-IN-TH_DC-005-2\n\nR748\n\t\t\t\t\n\n\t修改封装\tR0402:封装\tR0402\tSOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR\n\nR749\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1003TCE\t0402WGF1001TCE\n\nR750\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1002TCE\t0402WGF1003TCE\n\nR751\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1502TCE\t0402WGF1002TCE\n\nR752\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1002TCE\t0402WGF1502TCE\n\nR753\n\t\t\t\t\n\n\t修改属性\tR752:通道ID\t$17I168\t$17I166\n\n\t修改属性\tR752:位号\tR753\tR752\n\n\t修改网络\tR752:1\tFAN_TACH_IN\tGND"
},
{
"top": 564,
"txt": "Q8\n\t\t\t\t\n\n\t修改封装\tSOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR:封装\tSOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR\tR0402\n\nQ9\n\t\t\t\t\n\n\t修改封装\tSOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR:封装\tSOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR\tR0402\n\nQ10\n\t\t\t\t\n\n\t修改属性\tQ9:通道ID\t$17I223\t$17I222\n\n\t修改属性\tQ9:位号\tQ10\tQ9\n\n\t修改网络\tQ9:1\t$17N233\t$17N226\n\n\t修改网络\tQ9:3\tFAN_TACH_OUT\tFAN_TACH_IN\n\n\t增加元件\tQ10\t-\tQ10\n\nR746\n\t\t\t\t\n\n\t修改封装\tR0402:封装\tR0402\tSOD-123_L2.7-W1.6-LS3.7-RD-1\n\nR747\n\t\t\t\t\n\n\t修改封装\tR0402:封装\tR0402\tDC-IN-TH_DC-005-2\n\nR748\n\t\t\t\t\n\n\t修改封装\tR0402:封装\tR0402\tSOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR\n\nR749\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1003TCE\t0402WGF1001TCE\n\nR750\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1002TCE\t0402WGF1003TCE\n\nR751\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1502TCE\t0402WGF1002TCE\n\nR752\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1002TCE\t0402WGF1502TCE\n\nR753\n\t\t\t\t\n\n\t修改属性\tR752:通道ID\t$17I168\t$17I166\n\n\t修改属性\tR752:位号\tR753\tR752\n\n\t修改网络\tR752:1\tFAN_TACH_IN\tGND\n\n\t修改网络\tR752:2\t+3V3\t$17N226\n\nR754\n\t\t\t\t\n\n\t修改属性\tR753:通道ID\t$17I169\t$17I168\n\n\t修改属性\tR753:位号\tR754\tR753\n\n\t修改网络\tR753:1\tFAN_OUT_TACH_RAW\tFAN_TACH_IN\n\n\t修改网络\tR753:2\tFAN_12V_SW\t+3V3\n\nR755\n\t\t\t\t"
},
{
"top": 752,
"txt": "\t修改网络\tQ9:3\tFAN_TACH_OUT\tFAN_TACH_IN\n\n\t增加元件\tQ10\t-\tQ10\n\nR746\n\t\t\t\t\n\n\t修改封装\tR0402:封装\tR0402\tSOD-123_L2.7-W1.6-LS3.7-RD-1\n\nR747\n\t\t\t\t\n\n\t修改封装\tR0402:封装\tR0402\tDC-IN-TH_DC-005-2\n\nR748\n\t\t\t\t\n\n\t修改封装\tR0402:封装\tR0402\tSOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR\n\nR749\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1003TCE\t0402WGF1001TCE\n\nR750\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1002TCE\t0402WGF1003TCE\n\nR751\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1502TCE\t0402WGF1002TCE\n\nR752\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1002TCE\t0402WGF1502TCE\n\nR753\n\t\t\t\t\n\n\t修改属性\tR752:通道ID\t$17I168\t$17I166\n\n\t修改属性\tR752:位号\tR753\tR752\n\n\t修改网络\tR752:1\tFAN_TACH_IN\tGND\n\n\t修改网络\tR752:2\t+3V3\t$17N226\n\nR754\n\t\t\t\t\n\n\t修改属性\tR753:通道ID\t$17I169\t$17I168\n\n\t修改属性\tR753:位号\tR754\tR753\n\n\t修改网络\tR753:1\tFAN_OUT_TACH_RAW\tFAN_TACH_IN\n\n\t修改网络\tR753:2\tFAN_12V_SW\t+3V3\n\nR755\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1502TCE\t0402WGF1002TCE\n\nR756\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1002TCE\t0402WGF1502TCE\n\nR757\n\t\t\t\t\n\n\t修改属性\tR756:通道ID\t$17I173\t$17I171\n\n\t修改属性\tR756:位号\tR757\tR756\n\n\t修改网络\tR756:1\tFAN_TACH_OUT\tGND\n\n\t修改网络\tR756:2\t+3V3\t$17N233"
},
{
"top": 940,
"txt": "\t修改封装\tR0402:封装\tR0402\tSOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR\n\nR749\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1003TCE\t0402WGF1001TCE\n\nR750\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1002TCE\t0402WGF1003TCE\n\nR751\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1502TCE\t0402WGF1002TCE\n\nR752\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1002TCE\t0402WGF1502TCE\n\nR753\n\t\t\t\t\n\n\t修改属性\tR752:通道ID\t$17I168\t$17I166\n\n\t修改属性\tR752:位号\tR753\tR752\n\n\t修改网络\tR752:1\tFAN_TACH_IN\tGND\n\n\t修改网络\tR752:2\t+3V3\t$17N226\n\nR754\n\t\t\t\t\n\n\t修改属性\tR753:通道ID\t$17I169\t$17I168\n\n\t修改属性\tR753:位号\tR754\tR753\n\n\t修改网络\tR753:1\tFAN_OUT_TACH_RAW\tFAN_TACH_IN\n\n\t修改网络\tR753:2\tFAN_12V_SW\t+3V3\n\nR755\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1502TCE\t0402WGF1002TCE\n\nR756\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1002TCE\t0402WGF1502TCE\n\nR757\n\t\t\t\t\n\n\t修改属性\tR756:通道ID\t$17I173\t$17I171\n\n\t修改属性\tR756:位号\tR757\tR756\n\n\t修改网络\tR756:1\tFAN_TACH_OUT\tGND\n\n\t修改网络\tR756:2\t+3V3\t$17N233"
},
{
"top": 1000,
"txt": "R750\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1002TCE\t0402WGF1003TCE\n\nR751\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1502TCE\t0402WGF1002TCE\n\nR752\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1002TCE\t0402WGF1502TCE\n\nR753\n\t\t\t\t\n\n\t修改属性\tR752:通道ID\t$17I168\t$17I166\n\n\t修改属性\tR752:位号\tR753\tR752\n\n\t修改网络\tR752:1\tFAN_TACH_IN\tGND\n\n\t修改网络\tR752:2\t+3V3\t$17N226\n\nR754\n\t\t\t\t\n\n\t修改属性\tR753:通道ID\t$17I169\t$17I168\n\n\t修改属性\tR753:位号\tR754\tR753\n\n\t修改网络\tR753:1\tFAN_OUT_TACH_RAW\tFAN_TACH_IN\n\n\t修改网络\tR753:2\tFAN_12V_SW\t+3V3\n\nR755\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1502TCE\t0402WGF1002TCE\n\nR756\n\t\t\t\t\n\n\t替换器件\tR0402:器件\t0402WGF1002TCE\t0402WGF1502TCE\n\nR757\n\t\t\t\t\n\n\t修改属性\tR756:通道ID\t$17I173\t$17I171\n\n\t修改属性\tR756:位号\tR757\tR756\n\n\t修改网络\tR756:1\tFAN_TACH_OUT\tGND\n\n\t修改网络\tR756:2\t+3V3\t$17N233"
}
],
"buttons": [ "buttons": [
"刷新", "刷新",
"新增", "新增",
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
+1 -1
查看文件
@@ -1 +1 @@
[{"net": "GND", "x": 151.9259, "y": 48.8, "num": "A1B12", "layer": 1, "pts": [6006.923, 1933.068, "L", 6006.923, 1921.258, 6006.929, 1921.258, 6006.929, 1909.447, 5955.747, 1909.447, 5955.747, 1921.257, 5955.741, 1921.257, 5955.741, 1933.068, 6006.923, 1933.068]}, {"net": "GND", "x": 151.9259, "y": 55.2, "num": "B1A12", "layer": 1, "pts": [5955.741, 2161.413, "L", 5955.741, 2173.224, 5955.741, 2173.225, 5955.741, 2185.036, 6006.921, 2185.036, 6006.921, 2173.225, 6006.921, 2173.224, 6006.921, 2161.413, 5955.741, 2161.413]}, {"net": "", "x": 151.9259, "y": 54.4002, "num": "B4A9", "layer": 1, "pts": [6006.921, 2153.546, "L", 6006.921, 2141.735, 6006.921, 2141.734, 6006.921, 2129.923, 5955.741, 2129.923, 5955.741, 2141.734, 5955.741, 2141.735, 5955.741, 2153.546, 6006.921, 2153.546]}, {"net": "", "x": 151.9259, "y": 49.5998, "num": "A4B9", "layer": 1, "pts": [6006.927, 1964.564, "L", 6006.927, 1952.755, 6006.928, 1952.755, 6006.928, 1940.943, 5955.748, 1940.943, 5955.748, 1952.752, 5955.747, 1952.752, 5955.747, 1964.564, 6006.927, 1964.564]}, {"net": "GND", "x": 16.8001, "y": 27.5001, "num": "15", "layer": 1, "pts": [621.424, 1122.676, "L", 645.676, 1122.676, 645.676, 1167.715, 655.125, 1167.715, 655.125, 1139.369, 667.723, 1139.369, 667.723, 1167.715, 677.172, 1167.715, 677.172, 1122.676, 701.424, 1122.676, 701.424, 1042.676, 677.172, 1042.676, 677.172, 997.637, 667.723, 997.637, 667.723, 1025.983, 655.125, 1025.983, 655.125, 997.637, 645.676, 997.637, 645.676, 1042.676, 621.424, 1042.676, 621.424, 1122.676]}, {"net": "GND", "x": 51.0891, "y": 98.0001, "num": "15", "layer": 1, "pts": [2051.376, 3818.274, "L", 2027.124, 3818.274, 2027.124, 3773.235, 2017.675, 3773.235, 2017.675, 3801.581, 2005.077, 3801.581, 2005.077, 3773.235, 1995.628, 3773.235, 1995.628, 3818.274, 1971.376, 3818.274, 1971.376, 3898.274, 1995.628, 3898.274, 1995.628, 3943.313, 2005.077, 3943.313, 2005.077, 3914.967, 2017.675, 3914.967, 2017.675, 3943.313, 2027.124, 3943.313, 2027.124, 3898.274, 2051.376, 3898.274, 2051.376, 3818.274]}, {"net": "GND", "x": 139.4691, "y": 98.0001, "num": "15", "layer": 1, "pts": [5530.906, 3818.274, "L", 5506.654, 3818.274, 5506.654, 3773.235, 5497.205, 3773.235, 5497.205, 3801.581, 5484.607, 3801.581, 5484.607, 3773.235, 5475.158, 3773.235, 5475.158, 3818.274, 5450.906, 3818.274, 5450.906, 3898.274, 5475.158, 3898.274, 5475.158, 3943.313, 5484.607, 3943.313, 5484.607, 3914.967, 5497.205, 3914.967, 5497.205, 3943.313, 5506.654, 3943.313, 5506.654, 3898.274, 5530.906, 3898.274, 5530.906, 3818.274]}, {"net": "GND", "x": 48.469, "y": 27.5001, "num": "15", "layer": 1, "pts": [1868.234, 1122.676, "L", 1892.486, 1122.676, 1892.486, 1167.715, 1901.935, 1167.715, 1901.935, 1139.369, 1914.533, 1139.369, 1914.533, 1167.715, 1923.982, 1167.715, 1923.982, 1122.676, 1948.234, 1122.676, 1948.234, 1042.676, 1923.982, 1042.676, 1923.982, 997.637, 1914.533, 997.637, 1914.533, 1025.983, 1901.935, 1025.983, 1901.935, 997.637, 1892.486, 997.637, 1892.486, 1042.676, 1868.234, 1042.676, 1868.234, 1122.676]}, {"net": "GND", "x": 128, "y": 27.5001, "num": "15", "layer": 1, "pts": [4999.374, 1122.676, "L", 5023.626, 1122.676, 5023.626, 1167.715, 5033.075, 1167.715, 5033.075, 1139.369, 5045.673, 1139.369, 5045.673, 1167.715, 5055.122, 1167.715, 5055.122, 1122.676, 5079.374, 1122.676, 5079.374, 1042.676, 5055.122, 1042.676, 5055.122, 997.637, 5045.673, 997.637, 5045.673, 1025.983, 5033.075, 1025.983, 5033.075, 997.637, 5023.626, 997.637, 5023.626, 1042.676, 4999.374, 1042.676, 4999.374, 1122.676]}, {"net": "GND", "x": 79.5801, "y": 38.01, "num": "17", "layer": 1, "pts": [3166.541, 1529.919, "L", 3166.541, 1462.989, 3099.611, 1462.989, 3099.611, 1516.139, 3113.391, 1529.919, 3166.541, 1529.919]}, {"net": "GND", "x": 109.5701, "y": 34.71, "num": "17", "layer": 1, "pts": [4347.251, 1399.999, "L", 4347.251, 1333.069, 4280.321, 1333.069, 4280.321, 1386.219, 4294.101, 1399.999, 4347.251, 1399.999]}] [{"net": "GND", "x": 181.9258, "y": 48.8, "num": "A1B12", "layer": 1, "pts": [7188.023, 1933.068, "L", 7188.023, 1921.258, 7188.029, 1921.258, 7188.029, 1909.447, 7136.847, 1909.447, 7136.847, 1921.257, 7136.841, 1921.257, 7136.841, 1933.068, 7188.023, 1933.068]}, {"net": "GND", "x": 181.9258, "y": 55.2, "num": "B1A12", "layer": 1, "pts": [7136.841, 2161.413, "L", 7136.841, 2173.224, 7136.841, 2173.225, 7136.841, 2185.036, 7188.021, 2185.036, 7188.021, 2173.225, 7188.021, 2173.224, 7188.021, 2161.413, 7136.841, 2161.413]}, {"net": "", "x": 181.9258, "y": 54.4002, "num": "B4A9", "layer": 1, "pts": [7188.021, 2153.546, "L", 7188.021, 2141.735, 7188.021, 2141.734, 7188.021, 2129.923, 7136.841, 2129.923, 7136.841, 2141.734, 7136.841, 2141.735, 7136.841, 2153.546, 7188.021, 2153.546]}, {"net": "", "x": 181.9258, "y": 49.5998, "num": "A4B9", "layer": 1, "pts": [7188.027, 1964.564, "L", 7188.027, 1952.755, 7188.028, 1952.755, 7188.028, 1940.943, 7136.848, 1940.943, 7136.848, 1952.752, 7136.847, 1952.752, 7136.847, 1964.564, 7188.027, 1964.564]}, {"net": "GND", "x": 16.8001, "y": 27.5001, "num": "15", "layer": 1, "pts": [621.424, 1122.676, "L", 645.676, 1122.676, 645.676, 1167.715, 655.125, 1167.715, 655.125, 1139.369, 667.723, 1139.369, 667.723, 1167.715, 677.172, 1167.715, 677.172, 1122.676, 701.424, 1122.676, 701.424, 1042.676, 677.172, 1042.676, 677.172, 997.637, 667.723, 997.637, 667.723, 1025.983, 655.125, 1025.983, 655.125, 997.637, 645.676, 997.637, 645.676, 1042.676, 621.424, 1042.676, 621.424, 1122.676]}, {"net": "GND", "x": 51.0891, "y": 98.0001, "num": "15", "layer": 1, "pts": [2051.376, 3818.274, "L", 2027.124, 3818.274, 2027.124, 3773.235, 2017.675, 3773.235, 2017.675, 3801.581, 2005.077, 3801.581, 2005.077, 3773.235, 1995.628, 3773.235, 1995.628, 3818.274, 1971.376, 3818.274, 1971.376, 3898.274, 1995.628, 3898.274, 1995.628, 3943.313, 2005.077, 3943.313, 2005.077, 3914.967, 2017.675, 3914.967, 2017.675, 3943.313, 2027.124, 3943.313, 2027.124, 3898.274, 2051.376, 3898.274, 2051.376, 3818.274]}, {"net": "GND", "x": 139.4691, "y": 98.0001, "num": "15", "layer": 1, "pts": [5530.906, 3818.274, "L", 5506.654, 3818.274, 5506.654, 3773.235, 5497.205, 3773.235, 5497.205, 3801.581, 5484.607, 3801.581, 5484.607, 3773.235, 5475.158, 3773.235, 5475.158, 3818.274, 5450.906, 3818.274, 5450.906, 3898.274, 5475.158, 3898.274, 5475.158, 3943.313, 5484.607, 3943.313, 5484.607, 3914.967, 5497.205, 3914.967, 5497.205, 3943.313, 5506.654, 3943.313, 5506.654, 3898.274, 5530.906, 3898.274, 5530.906, 3818.274]}, {"net": "GND", "x": 48.469, "y": 27.5001, "num": "15", "layer": 1, "pts": [1868.234, 1122.676, "L", 1892.486, 1122.676, 1892.486, 1167.715, 1901.935, 1167.715, 1901.935, 1139.369, 1914.533, 1139.369, 1914.533, 1167.715, 1923.982, 1167.715, 1923.982, 1122.676, 1948.234, 1122.676, 1948.234, 1042.676, 1923.982, 1042.676, 1923.982, 997.637, 1914.533, 997.637, 1914.533, 1025.983, 1901.935, 1025.983, 1901.935, 997.637, 1892.486, 997.637, 1892.486, 1042.676, 1868.234, 1042.676, 1868.234, 1122.676]}, {"net": "GND", "x": 128, "y": 27.5001, "num": "15", "layer": 1, "pts": [4999.374, 1122.676, "L", 5023.626, 1122.676, 5023.626, 1167.715, 5033.075, 1167.715, 5033.075, 1139.369, 5045.673, 1139.369, 5045.673, 1167.715, 5055.122, 1167.715, 5055.122, 1122.676, 5079.374, 1122.676, 5079.374, 1042.676, 5055.122, 1042.676, 5055.122, 997.637, 5045.673, 997.637, 5045.673, 1025.983, 5033.075, 1025.983, 5033.075, 997.637, 5023.626, 997.637, 5023.626, 1042.676, 4999.374, 1042.676, 4999.374, 1122.676]}, {"net": "GND", "x": 79.5801, "y": 38.01, "num": "17", "layer": 1, "pts": [3166.541, 1529.919, "L", 3166.541, 1462.989, 3099.611, 1462.989, 3099.611, 1516.139, 3113.391, 1529.919, 3166.541, 1529.919]}, {"net": "GND", "x": 109.5701, "y": 34.71, "num": "17", "layer": 1, "pts": [4347.251, 1399.999, "L", 4347.251, 1333.069, 4280.321, 1333.069, 4280.321, 1386.219, 4294.101, 1399.999, 4347.251, 1399.999]}]
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
+1 -1
查看文件
@@ -4,7 +4,7 @@ import json, os, sys
HERE = os.path.dirname(os.path.abspath(__file__)); sys.path.insert(0, HERE) HERE = os.path.dirname(os.path.abspath(__file__)); sys.path.insert(0, HERE)
from eda import run from eda import run
JS = u""" JS = u"""
await eda.dmt_EditorControl.openDocument('f126ffb29c92f9b2'); await eda.dmt_EditorControl.openDocument('942ed1560d81c1e7');
await new Promise(r=>setTimeout(r,1800)); await new Promise(r=>setTimeout(r,1800));
const name = {}; const name = {};
for (const c of await eda.pcb_PrimitiveComponent.getAll()) for (const c of await eda.pcb_PrimitiveComponent.getAll())
-23
查看文件
@@ -1,23 +0,0 @@
await eda.dmt_EditorControl.openDocument('7e9415a16a909c51');
await new Promise(r => setTimeout(r, 1200));
const M = 39.3701;
const R = (x0,y0,x1,y1) => [x0*M,y0*M,'L',x1*M,y0*M,x1*M,y1*M,x0*M,y1*M];
const tries = [
['rect-L3-+12V', '+12V', 16, R(100,20,120,30)],
['rect-L3-$net', '$1N223', 16, R(100,35,120,45)],
['rect-L4-VO1_P', 'VO1_P', 2, R(100,50,120,60)],
];
const out = [];
for (const [name, net, layer, src] of tries) {
const row = {name};
try {
const poly = await eda.pcb_MathPolygon.createComplexPolygon([src]);
row.poly = !!poly;
const pour = await eda.pcb_PrimitivePour.create(net, layer, poly, 'solid', false, name, 3, 10);
row.pour = !!pour;
if (pour) await eda.pcb_PrimitivePour.delete(pour.primitiveId);
} catch (e) { row.err = String(e).slice(0, 140); }
out.push(row);
}
out.nPourNow = (await eda.pcb_PrimitivePour.getAll()).length;
return out;
-25
查看文件
@@ -1,25 +0,0 @@
await eda.dmt_EditorControl.openDocument('7e9415a16a909c51');
await new Promise(r => setTimeout(r, 1200));
const M = 39.3701;
const R = (x0,y0,x1,y1) => [x0*M,y0*M,'L',x1*M,y0*M,x1*M,y1*M,x0*M,y1*M];
const Rc= (x0,y0,x1,y1) => [x0*M,y0*M,'L',x1*M,y0*M,x1*M,y1*M,x0*M,y1*M,x0*M,y0*M]; // 显式闭合
const Rr= (x0,y0,x1,y1) => ['R',x0*M,y0*M,x1*M,y1*M,0,0]; // R 矩形模式
const cases = [
['L15-rect', 'GND', 15, R(100,20,120,30)],
['L16-rect-close','+12V',16, Rc(100,35,120,45)],
['L16-Rmode', '+12V',16, Rr(100,50,120,60)],
['L1-rect', '+12V', 1, R(100,65,120,75)],
];
const out = [];
for (const [name, net, layer, src] of cases) {
const row = {name};
try {
const poly = await eda.pcb_MathPolygon.createComplexPolygon([src]);
row.poly = !!poly;
const pour = await eda.pcb_PrimitivePour.create(net, layer, poly, 'solid', false, name, 3, 10);
row.pour = !!pour;
if (pour) await eda.pcb_PrimitivePour.delete(pour.primitiveId);
} catch (e) { row.err = String(e).slice(0, 90); }
out.push(row);
}
return out;
+63 -19
查看文件
@@ -3,7 +3,8 @@ u"""载板 C4-SITE1 ~ C4-SITE5:每台手机一页。
一页里三块: 一页里三块:
【A】电池排线 4 焊盘直焊座 J8n(P+/P−/TEMP/ID,2026-09-06 取代 TP8n1/2)+ TEMP 10k / ID 68k + §6.3 电容组(六颗) 【A】电池排线 4 焊盘直焊座 J8n(P+/P−/TEMP/ID,2026-09-06 取代 TP8n1/2)+ TEMP 10k / ID 68k + §6.3 电容组(六颗)
【B】四根侧键顶针(PWR / VUP / VDN + J604 三键公共地) 【B】四根侧键顶针(PWR / VUP / VDN + J604 三键公共地)+ 三颗按键 MOS(2N7002 + 1k 栅阻 + 100k 下拉)
—— 2026-09-06 随 74HC595 从下板 P2 搬来(§26.23),位号沿用 Q%d03~05 / R%d20~25
【C】EDL:1.25mm 2P 座 + TS5A3157 模拟开关 + 100k 下拉 + 去耦 【C】EDL:1.25mm 2P 座 + TS5A3157 模拟开关 + 100k 下拉 + 去耦
用法: /c/Python314/python .claude/c4_site.py [--push] [--only N] 用法: /c/Python314/python .claude/c4_site.py [--push] [--only N]
@@ -50,6 +51,8 @@ P = {
'CONN2P': '6461e6e5749d421bb997627984866425', # C225104 A1251WV-S-2P 'CONN2P': '6461e6e5749d421bb997627984866425', # C225104 A1251WV-S-2P
'SW': '5acafd8eb5a641fcaf05190902930635', # C353034 TS5A3157DBVR 'SW': '5acafd8eb5a641fcaf05190902930635', # C353034 TS5A3157DBVR
'R100K': 'acc294b6661948399f609b41c77ed19f', # C25741 100k 0402 'R100K': 'acc294b6661948399f609b41c77ed19f', # C25741 100k 0402
'NMOS': 'f8007837564a4f1fa0e6b57b81dccb0d', # C8545 2N7002(按键 MOS,从下板搬来)
'R1K': '65abbad5fa864933b552e0f016ece0db', # C11702 1k 0402(栅极串阻)
} }
# ---- 版面常量 ---- # ---- 版面常量 ----
@@ -63,9 +66,14 @@ J8 = (225, 630) # 排线直焊座:符号 4 脚朝**左**(实
COL = {'1': 100, '2': 130, '3': 170, '4': 190} COL = {'1': 100, '2': 130, '3': 170, '4': 190}
R78, R79 = (170, 575), (190, 575) # TEMP / ID 电阻(竖放),下端直接落在 GND 母线上 R78, R79 = (170, 575), (190, 575) # TEMP / ID 电阻(竖放),下端直接落在 GND 母线上
POGO_X = [220, 320, 420, 520] # 【B】四根顶针的元件中心 # 【B】三个按键列(PWR / VUP / VDN),每列:顶针在上、按键 MOS 在下、K 端口从最底下进来。
Y_POGO = 380 # 列内相对顶针中心 x_p:MOS (x_p-25, 360) → G(x_p-45,360) S(x_p-15,340) D(x_p-15,380);
POGO_PORT_Y = [330, 300, 270, 240] # 栅阻 R%20/22/24 (x_p-45, 330) 竖放;下拉 R%21/23/25 (x_p-75, 330) 竖放;
# K 端口 (x_p-45, 250);POGO 端口挂在漏极竖线旁的横支线末端 (x_p+5, 410)(给 PCB 网络起名)。
# 列距 120:一列占 x_p-85 ~ x_p+85(POGO 标签),最东一列的标签 515 < 【C】那根 x=530 的竖线。
KEY_X = [190, 310, 430]
GND_POGO_X = 80 # 第四根顶针(J604 公共地)
Y_POGO = 440
J6, U4 = (660, 400), (880, 400) # 【C】 J6, U4 = (660, 400), (880, 400) # 【C】
R74, C76 = (975, 370), (1060, 370) R74, C76 = (975, 370), (1060, 370)
@@ -88,6 +96,9 @@ def names(n):
'j8': 'J8%d' % n, 'r78': 'R78%d' % n, 'r79': 'R79%d' % n, 'j8': 'J8%d' % n, 'r78': 'R78%d' % n, 'r79': 'R79%d' % n,
'pogo': ['J%d' % (41 + (n - 1) * 4 + i) for i in range(4)], 'pogo': ['J%d' % (41 + (n - 1) * 4 + i) for i in range(4)],
'j6': 'J6%d' % n, 'u4': 'U4%d' % n, 'r74': 'R74%d' % n, 'c76': 'C76%d' % n, 'j6': 'J6%d' % n, 'u4': 'U4%d' % n, 'r74': 'R74%d' % n, 'c76': 'C76%d' % n,
'q': ['Q%d0%d' % (n, k) for k in (3, 4, 5)], # PWR / VUP / VDN
'rg': ['R%d2%d' % (n, k) for k in (0, 2, 4)], # 1k 栅阻
'rp': ['R%d2%d' % (n, k) for k in (1, 3, 5)], # 100k 下拉
} }
@@ -102,8 +113,12 @@ def placement(n):
# 和 skill §6 那张表相反 —— 所以传 90。下面 EXPECT 会再断言一次。 # 和 skill §6 那张表相反 —— 所以传 90。下面 EXPECT 会再断言一次。
rot = 90 if key == 'C2200U' else 270 rot = 90 if key == 'C2200U' else 270
out.append([d['caps'][i], P[key], CAP_X[i], Y_CAP, rot, LIB_POGO if key == 'C2200U' else LIB]) out.append([d['caps'][i], P[key], CAP_X[i], Y_CAP, rot, LIB_POGO if key == 'C2200U' else LIB])
for i, x in enumerate(POGO_X): for i, x in enumerate(KEY_X):
out.append([d['pogo'][i], P['POGO'], x, Y_POGO, 0, LIB_POGO]) out.append([d['pogo'][i], P['POGO'], x, Y_POGO, 0, LIB_POGO])
out.append([d['q'][i], P['NMOS'], x - 25, 360, 0, LIB])
out.append([d['rg'][i], P['R1K'], x - 45, 330, 90, LIB])
out.append([d['rp'][i], P['R100K'], x - 75, 330, 90, LIB])
out.append([d['pogo'][3], P['POGO'], GND_POGO_X, Y_POGO, 0, LIB_POGO])
out += [[d['j6'], P['CONN2P'], J6[0], J6[1], 0, LIB], out += [[d['j6'], P['CONN2P'], J6[0], J6[1], 0, LIB],
[d['u4'], P['SW'], U4[0], U4[1], 0, LIB], [d['u4'], P['SW'], U4[0], U4[1], 0, LIB],
[d['r74'], P['R100K'], R74[0], R74[1], 270, LIB], [d['r74'], P['R100K'], R74[0], R74[1], 270, LIB],
@@ -153,16 +168,41 @@ def wiring(n, pins):
w(top[0], top[1], top[0], Y_VO) w(top[0], top[1], top[0], Y_VO)
w(bot[0], bot[1], bot[0], Y_GND) w(bot[0], bot[1], bot[0], Y_GND)
# ---------- 【B】侧键顶针 ---------- # ---------- 【B】侧键顶针 + 按键 MOS ----------
labels = ['POGO%d_PWR' % n, 'POGO%d_VUP' % n, 'POGO%d_VDN' % n, None] for i, key in enumerate(('PWR', 'VUP', 'VDN')):
for i in range(4):
px, py = pins[d['pogo'][i]]['1'] px, py = pins[d['pogo'][i]]['1']
yy = POGO_PORT_Y[i] q = pins[d['q'][i]]
w(px, py, px, yy) g, sr, dr = q['1'], q['2'], q['3'] # 2N7002:1=G 2=S 3=D(下面 build_page 里断言过)
if labels[i]: assert px == dr[0] == sr[0] and dr[1] < py, (d['q'][i], q, px, py)
PT.append(['BI', labels[i], px, yy, 180]) # 漏极 -> 顶针一根直线;POGO 端口挂在旁边一根 20 长的横支线末端(给 PCB 网络起名)。
else: # ⚠️ 不能把端口放在直线的中点:两段共线导线会被 EasyEDA 合并成一段,端口就落在中段 =
F.append(['Ground', 'GND', px, yy, 0]) # 真断线(skill §8a-2,2026-09-06 第一版就是这么被 port_audit 抓到 15 处的)。
mid = dr[1] + 30
w(dr[0], dr[1], px, py)
w(dr[0], mid, dr[0] + 20, mid)
PT.append(['BI', 'POGO%d_%s' % (n, key), dr[0] + 20, mid, 0])
# 源极 -> 地
w(sr[0], sr[1], sr[0], 300)
F.append(['Ground', 'GND', sr[0], 300, 0])
# 栅极 <- 1k <- K 端口(从底下进来)
rg = pins[d['rg'][i]]
gt = max(rg.values(), key=lambda v: v[1])
gb = min(rg.values(), key=lambda v: v[1])
assert gt[0] == g[0] and gt[1] < g[1], (d['rg'][i], rg, g)
w(g[0], g[1], gt[0], gt[1])
w(gb[0], gb[1], gb[0], 250)
PT.append(['IN', 'K%d_%s' % (n, key), gb[0], 250, 180])
# 栅极 100k 下拉(复位期间 / 排针没插时不误按)
rp = pins[d['rp'][i]]
pt = max(rp.values(), key=lambda v: v[1])
pb = min(rp.values(), key=lambda v: v[1])
assert pt[0] < g[0] and pt[1] < g[1], (d['rp'][i], rp, g)
w(g[0], g[1], pt[0], g[1], pt[0], pt[1])
w(pb[0], pb[1], pb[0], 300)
F.append(['Ground', 'GND', pb[0], 300, 0])
px, py = pins[d['pogo'][3]]['1'] # J604 三键公共地
w(px, py, px, 400)
F.append(['Ground', 'GND', px, 400, 0])
# ---------- 【C】EDL ---------- # ---------- 【C】EDL ----------
j, u = pins[d['j6']], pins[d['u4']] j, u = pins[d['j6']], pins[d['u4']]
@@ -205,11 +245,11 @@ def wiring(n, pins):
% names(n)['j8'], 10), % names(n)['j8'], 10),
(45, 772, u' %s 10k 代替保护板上的 NTC(模拟 25 °C),%s 68k 照抄保护板上的 ID 电阻,都对 P−。' (45, 772, u' %s 10k 代替保护板上的 NTC(模拟 25 °C),%s 68k 照抄保护板上的 ID 电阻,都对 P−。'
% (names(n)['r78'], names(n)['r79']), 10), % (names(n)['r78'], names(n)['r79']), 10),
(45, 470, u'【B】四根顶针顶主板反面的 J601~J604(§13.3)。J604 是三键公共地,', 10), (45, 522, u'【B】四根顶针顶主板反面的 J601~J604(§13.3),J604 是三键公共地,直接接本路的电源返回地。按键 = MOS 把触点对地短接(主板上已串 1k)。', 10),
(45, 454, u' 直接接本路的电源返回地。驱动 MOS 在下板,这里只有顶针。', 10), (45, 508, u' %s 2N7002 由 C6-KEYS 的 74HC595 驱动(K%d_PWR/VUP/VDN),栅极 1k 串阻 + 100k 下拉:复位期间 / 排针没插时不会误按(2026-09-06 从下板搬来,§26.23)。'
(45, 438, u'顶针 = YTC1P-2510-01(立创 C5221288):自由高 3.5 / 行程 0.8 / 压到底 2.7 mm。', 10), % ('/'.join(names(n)['q']), n), 10),
(45, 422, u' 配 3.0mm 立柱 → 压缩 0.5mm(62% 行程,约 62g),屏蔽罩净空 1.6mm。', 10), (45, 494, u' 顶针 YTC1P-2510-01(C5221288):自由高 3.5 / 行程 0.8 / 压到底 2.7 mm;配 3.0mm 立柱压缩 0.5mm(62% 行程,约 62g),屏蔽罩净空 1.6mm。', 10),
(45, 406, u' 冠径只有 Ø0.9 球头,±1mm 落点公差靠首件实测校正(§22.3c / §22.3e)。', 10), (45, 480, u' 冠径只有 Ø0.9 球头,±1mm 落点公差靠首件实测校正(§22.3c / §22.3e)。', 10),
(45, 205, u'【C】%s 是 1.25mm 2P 座,两根线焊到主板正面的 TP1204 / TP1205(§22.4)。' (45, 205, u'【C】%s 是 1.25mm 2P 座,两根线焊到主板正面的 TP1204 / TP1205(§22.4)。'
% names(n)['j6'], 10), % names(n)['j6'], 10),
(45, 189, u' %s 导通即短接两点 → 手机进 9008。串了 1.8k,短路电流只有 1mA。' (45, 189, u' %s 导通即短接两点 → 手机进 9008。串了 1.8k,短路电流只有 1mA。'
@@ -332,6 +372,10 @@ return k;
for k, v in want.items(): for k, v in want.items():
if got.get(k, '').upper().replace('VCC', 'V+') != v: if got.get(k, '').upper().replace('VCC', 'V+') != v:
bad.append(u'%s.%s 引脚名期望 %s 实测 %s' % (d['u4'], k, v, got.get(k))) bad.append(u'%s.%s 引脚名期望 %s 实测 %s' % (d['u4'], k, v, got.get(k)))
for qd in d['q']:
q = pins[qd]
if not (sorted(q) == ['1', '2', '3'] and q['1'][0] < q['2'][0] == q['3'][0] and q['2'][1] < q['1'][1] < q['3'][1]):
bad.append(u'%s 2N7002 引脚应是 1=G(左) 2=S(右下) 3=D(右上),实测 %s' % (qd, q))
if bad: if bad:
raise SystemExit(pname + u' 引脚核对失败:\n ' + '\n '.join(bad)) raise SystemExit(pname + u' 引脚核对失败:\n ' + '\n '.join(bad))
+8 -1
查看文件
@@ -43,7 +43,14 @@ sys.path.insert(0, HERE)
from eda import run from eda import run
# 默认下板;载板加参数:/c/Python314/python .claude/drc_read.py --pcb2 # 默认下板;载板加参数:/c/Python314/python .claude/drc_read.py --pcb2
PCB = 'f126ffb29c92f9b2' if '--pcb2' in sys.argv else '7e9415a16a909c51' # 2026-09-08:加 `--blade` / `--base` 跑 R2 的两块板(UUID 从 .claude/r2/r2-uuids.json 取)
if '--blade' in sys.argv or '--base' in sys.argv:
import json as _j, io as _io, os as _os
_u = _j.load(_io.open(_os.path.join(_os.path.dirname(_os.path.abspath(__file__)),
'r2', 'r2-uuids.json'), encoding='utf-8'))
PCB = _u['boards']['BASE' if '--base' in sys.argv else 'BLADE']['pcb']['uuid']
else:
PCB = '942ed1560d81c1e7' if '--pcb2' in sys.argv else '7e9415a16a909c51'
JS_TREE = u""" JS_TREE = u"""
await eda.dmt_EditorControl.openDocument('%s'); await eda.dmt_EditorControl.openDocument('%s');
+1 -1
查看文件
@@ -38,7 +38,7 @@ HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE) sys.path.insert(0, HERE)
from eda import run from eda import run
# 默认下板;载板用 `python .claude/dump_pads.py f126ffb29c92f9b2 pcb2-copper.json` # 默认下板;载板用 `python .claude/dump_pads.py 942ed1560d81c1e7 pcb2-copper.json`
PCB = sys.argv[1] if len(sys.argv) > 1 else '7e9415a16a909c51' PCB = sys.argv[1] if len(sys.argv) > 1 else '7e9415a16a909c51'
MIL = 39.3701 MIL = 39.3701
OUT = os.path.join(HERE, sys.argv[2] if len(sys.argv) > 2 else 'pcb-copper.json') OUT = os.path.join(HERE, sys.argv[2] if len(sys.argv) > 2 else 'pcb-copper.json')
+122 -14
查看文件
@@ -1,6 +1,6 @@
{ {
"_note": "EasyEDA 器件与工程 UUID 缓存。新会话直接用,不必重新 lib_Device.search。role 里带 ⚠️ 的是踩过的坑。P2 单路取值见设计文档 §16.2。⚠️ 作废:C25907(5.23k)、C25743(107k) 已被 C407675(5.49k)、C25755(150k) 取代,不要再用。 ⚠️ Board3(自制转接板) 已删除,改走方案 D:载板上放 BM24-50DS 座 + 原厂排线。 ⚠️ BM24 座子已改用个人库自建封装版 771dbf00…(见 parts.BM24-HRS-PIN),立创原件 C424561 的封装编号是错的。", "_note": "EasyEDA 器件与工程 UUID 缓存。新会话直接用,不必重新 lib_Device.search。role 里带 ⚠️ 的是踩过的坑。P2 单路取值见设计文档 §16.2。⚠️ 作废:C25907(5.23k)、C25743(107k) 已被 C407675(5.49k)、C25755(150k) 取代,不要再用。 ⚠️ Board3(自制转接板) 已删除,改走方案 D:载板上放 BM24-50DS 座 + 原厂排线。 ⚠️ BM24 座子已改用个人库自建封装版 771dbf00…(见 parts.BM24-HRS-PIN),立创原件 C424561 的封装编号是错的。",
"_generated": "2026-09-05 夜:板对板连接器换件实施完毕 —— 下板 C55020601 公头 / 载板 C5307344 母座(对插 13.0mm、双双插装、公母同一套 IDC 奇偶式编号)。P5-BTB / C1-CONN 两页已重生成,载板 PCB 已重摆/重扇/重铺。", "_generated": "2026-09-08:R2 底板 M3 新增 4 个器件(TCA9548APWR / NCP18XH103F03RB / PCIE-064-02-F-D-TH 插槽 / 1x5 排针),引脚坐标均为空页实测。",
"library_uuid": "0819f05c4eef4c71ace90d822a990e87", "library_uuid": "0819f05c4eef4c71ace90d822a990e87",
"project": { "project": {
"uuid": "1e5745f4caff4b15a921694d2fd912e4", "uuid": "1e5745f4caff4b15a921694d2fd912e4",
@@ -107,7 +107,7 @@
"uuid": "9b2474b02bb94569a00ded395421ea45", "uuid": "9b2474b02bb94569a00ded395421ea45",
"lib": "0819f05c4eef4c71ace90d822a990e87", "lib": "0819f05c4eef4c71ace90d822a990e87",
"fp": "R0402", "fp": "R0402",
"role": "R 4.7k 0402 — ⚠️ 曾误当 eFuse ILIM 用;TPS25940 的 R_ILIM 合法范围是 16.9~150k,4.7k 非法。现无用途" "role": "R 4.7k 0402 — R2 刀片的 I²C 上拉(B2-CTRL 的 R40/R41,挂本槽 mux 分支)。⚠️ 曾误当 eFuse ILIM 用,那是非法的(合法范围 16.9~150k)"
}, },
"C2653873": { "C2653873": {
"name": "TPS25940ARVCR", "name": "TPS25940ARVCR",
@@ -526,16 +526,6 @@
"lcsc": "C424561", "lcsc": "C424561",
"role": "J71~J75 实际用的就是这一条(2026-09-04 自建)。焊盘按 **Hirose 奇偶行编号**:奇数 1,3,5..49 在 +Y 排、偶数 2,4,6..50 在 −Y 排,1 脚在 −X 端;pin51 = 1/2 脚那端的电源触点(x=−6.03),pin52 = 49/50 脚那端(x=+6.03)。几何来自 Hirose 官方样本反算,生成脚本 .claude/fp_bm24.py。⚠ 「哪一排是奇数排」还没有实证(§13.5 的 §MIRROR),打样前必须做那一次实测。" "role": "J71~J75 实际用的就是这一条(2026-09-04 自建)。焊盘按 **Hirose 奇偶行编号**:奇数 1,3,5..49 在 +Y 排、偶数 2,4,6..50 在 −Y 排,1 脚在 −X 端;pin51 = 1/2 脚那端的电源触点(x=−6.03),pin52 = 49/50 脚那端(x=+6.03)。几何来自 Hirose 官方样本反算,生成脚本 .claude/fp_bm24.py。⚠ 「哪一排是奇数排」还没有实证(§13.5 的 §MIRROR),打样前必须做那一次实测。"
}, },
"POGO-D1.9": {
"name": "YTC1P-2510-01 D1.9",
"uuid": "1c8e0ea1e66142a8860d6812b1dbe356",
"lib": "543725e8f0454017a5691e7e570aeef6",
"lib_note": "个人库",
"fp": "POGO-SMD1P_D1.9",
"fp_uuid": "2cbabda783f34133b4fe450e98f282bc",
"lcsc": "C5221288",
"role": "J41~J60 侧键顶针,**实际用的就是这一条**。立创原封装 SMD-1_BD2.0-H3.6 焊盘 Ø2.5,而小米8 四个侧键触点最近的一对(J601↔J604)只隔 2.217 mm → 焊盘会叠在一起把音量+ 永久短路。自建封装缩到 Ø1.9(净间距 0.317 mm)。法兰本来就只有 Ø2.0。见设计文档 §25.3。"
},
"C55020601": { "C55020601": {
"name": "AZ-PZ2.54-V11-2*20P", "name": "AZ-PZ2.54-V11-2*20P",
"uuid": "c0ab04240dc64fca8d6203d15f8a8a68", "uuid": "c0ab04240dc64fca8d6203d15f8a8a68",
@@ -575,6 +565,123 @@
"lib": "0819f05c4eef4c71ace90d822a990e87", "lib": "0819f05c4eef4c71ace90d822a990e87",
"fp": "R0402", "fp": "R0402",
"role": "R 68k 0402 — R79n 电池 ID 电阻(实测原保护板 ≈68k 对 P−)" "role": "R 68k 0402 — R79n 电池 ID 电阻(实测原保护板 ≈68k 对 P−)"
},
"MI8-KEY-PAD": {
"name": "MI8-KEY-PAD D1.5",
"uuid": "1c8e0ea1e66142a8860d6812b1dbe356",
"lib": "543725e8f0454017a5691e7e570aeef6",
"lib_note": "个人库",
"fp": "PAD-D1.5_MI8-KEY",
"fp_uuid": "e106d85830014c088c1fba82f91a26f6",
"sym_uuid": "f524af799aa247f2b91e8f56171fa171",
"lcsc": "",
"role": "J41~J60 侧键触点盘。**2026-09-06 方案改**:顶针取消,改成和中框一样的焊盘 —— 螺丝把主板压在载板上(1 mm 间隙),主板自己的弹片直接压在这个裸铜盘上。器件 uuid 没变(就是原来那颗顶针器件改的),所以**原理图一个字没动**,符号也没换。封装 Ø1.5:四触点实测最小间距 1.78 mm,净距 0.28。⚠️ 旧的 POGO-SMD1P_D1.9 是按「最近一对 2.217 mm」缩的,在真实 1.78 下是负净距。⚠️ 器件属性里的 BOM 字段(Add into BOM=yes / C5221288 / YIYUAN)**API 改不动**,要在客户端手工改成不进 BOM 并清掉料号,否则会照着买 20 颗用不上的顶针。⚠️ 工艺:这是接触盘,**钢网开口 0(不上锡膏)**,表面处理走 ENIG。"
},
"C15127": {
"name": "AO3401A_C15127",
"uuid": "be56508f80684877b262e15e4d50e175",
"lib": "0819f05c4eef4c71ace90d822a990e87",
"fp": "SOT-23_L2.9-W1.3-P1.90-LS2.4-BR",
"role": "Q7 P-MOS 高边开关(风扇 FAN_12V_SW)。Vgs 绝对最大 ±12V ⇒ 必须配 DZ3 钳位。⚠ 封装名和 AO3400A 那颗不一样(SOT-23_ 不是 SOT-23-3_),H_FP 要单独加一条"
},
"C2891776": {
"name": "BZT52C8V2",
"uuid": "46e19a04908e4d7f9d40ab2982f52315",
"lib": "0819f05c4eef4c71ace90d822a990e87",
"fp": "SOD-123_L2.7-W1.6-LS3.7-RD-1",
"role": "DZ3 8.2V/500mW 稳压管,钳 Q7 的负 Vgs。K 接源极、A 接栅极;create(rot=90) 才是 K 上 A 下"
},
"C25756": {
"name": "0402WGF1502TCE",
"uuid": "6d104587f1cd4b509c6a5fb272570507",
"lib": "0819f05c4eef4c71ace90d822a990e87",
"fp": "R0402",
"role": "R 15k 0402 1% — 两路 TACH 接收的栅极串阻(10k 会让高电平不够)"
},
"C2847671": {
"name": "2510-4A",
"uuid": "04ed497706104166a1f88dbf2d16083e",
"lib": "0819f05c4eef4c71ace90d822a990e87",
"fp": "CONN-TH_4P-P2.54_CAX_2510-4A",
"role": "J26/J27 四线风扇座(直针 1x4P 2.54)。1=GND 2=FAN_12V_SW 3=TACH 4=PWM,与旧 3 针针序不同。塑壳 11.5mm,下板上只有两个上角和 y 68~92 放得下"
},
"C116575": {
"name": "T491A226K010AT",
"uuid": "950f101b742e488fa85dafd0c53e49eb",
"lib": "0819f05c4eef4c71ace90d822a990e87",
"fp": "CASE-A_3216",
"role": "C737 AMS1117-3.3 输出 22uF/10V **固态钽**(手册 §Stability 要求,陶瓷不算数)。⚠ **1 脚是正极**(rot 0 时在上),装配时钽的条纹 = 正极,别按铝电解的习惯"
},
"PCIE-X4-EDGE-64P": {
"name": "PCIE-X4-EDGE-64P",
"uuid": "787309ccc49040b1b6383a339fd8c405",
"lib": "543725e8f0454017a5691e7e570aeef6",
"lib_note": "个人库,不是系统库 —— 放元件时 libraryUuid 要用这个",
"symbol": "1083524aa7614bde8c2394b7d1fe5bf3",
"role": "R2 刀片 J1:PCI-E x4 金手指(板边本身,不采购)。符号借自系统库 PCIE-064-02-F-D-TH,脚号 A1~A32 在左列(自上而下,pitch 10)、B1~B32 在右列,与 PCIe CEM 命名一一对应。⛔ 封装还没建(64 个板边焊盘,1.0 mm 间距,正反两面),属 M4 —— ✅ **T3 已定(2026-09-08):做斜边 20°/1.0 mm,金手指区电镀硬金(不是 ENIG),板厚必须 1.6 mm**,所以现在可以开画。⚠ 斜边会吃掉手指前端约 1.0 mm 的镀层 ⇒ 金手指长度按 PCIe CEM 画,别自己缩短。"
},
"C310990": {
"name": "F1206HC12A0TM",
"uuid": "507cd700b60b451caa0f3237e41baf6d",
"lib": "0819f05c4eef4c71ace90d822a990e87",
"fp": "1206",
"role": "下板 / R2 底板 F1:12A 贴片保险丝(35V,分断 150A)。整机最坏 9.99A(设计文档 §22.6),⚠ 与刀片上那颗 F1(2A 慢断,T7 还是 10A 占位件 C310989)不是一回事,别搞混。"
},
"C2570006": {
"name": "RHC2512FT2R00",
"uuid": "3c25870a08a64dc7b809655f380d8ce9",
"lib": "0819f05c4eef4c71ace90d822a990e87",
"fp": "2512",
"role": "R9A~R9E:2Ω 2W 2512,5 颗串联 = 10Ω/10W 母线泄放电阻。高 0.8mm —— 原来的立式水泥电阻站起来超高度预算,才拆成 5 颗贴片。"
},
"C9900017043": {
"name": "DC-005-5.5*2.5",
"uuid": "66ed565d4c984ace901de00bbc83af82",
"lib": "0819f05c4eef4c71ace90d822a990e87",
"fp": "DC-005",
"role": "J1 圆形 DC 电源座(2026-09-08 用户拍板换掉 XT30PW-M,不降载)。脚 1 = 中心极正 → 保险丝;脚 2/3 = 负极静/动触点,两个都接地并联降接触电阻。⚠ 家族额定 2~5A 而整机最坏 9.99A —— 已知风险、用户明确接受,发热点在插头内芯与弹片的接触面,板上加铜救不了。"
},
"C130026": {
"name": "TCA9548APWR",
"uuid": "98a0404e89ff4ecdb0427f14520ce65a",
"lib": "0819f05c4eef4c71ace90d822a990e87",
"fp": "TSSOP-24_L7.8-W4.4-P0.65-LS6.4-BL",
"role": "U35 TCA9548A 8 通道 I²C 多路器(R2 底板,D8:一槽一路)。实测引脚(rot 0,相对中心):左列自上而下 1 A0(-50,+55) 2 A1(+45) 3 RESET#(+35) 4 SD0(+25) 5 SC0(+15) 6 SD1(+5) 7 SC1(-5) 8 SD2(-15) 9 SC2(-25) 10 SD3(-35) 11 SC3(-45) 12 GND(-55);右列自下而上 13 SD4(+50,-55) 14 SC4(-45) 15 SD5(-35) 16 SC5(-25) 17 SD6(-15) 18 SC6(-5) 19 SD7(+5) 20 SC7(+15) 21 A2(+25) 22 SCL(+35) 23 SDA(+45) 24 VCC(+55)。⚠ SD/SC 成对,通道 n 的 SDn/SCn 脚号相邻但**SD 在前 SC 在后**,别按 SCL/SDA 的习惯顺序接反。"
},
"C13564": {
"name": "NCP18XH103F03RB",
"uuid": "26d83c83abc7457cb451b4cf76e659b1",
"lib": "0819f05c4eef4c71ace90d822a990e87",
"fp": "R0603",
"role": "RT601/RT602 NTC 10k B=3380K 0603(R1 P3-CONTROL 原件,R2 挪到 A6-FAN)。两脚件,rot 0 时 1 在左(-20,0)、2 在右(+20,0),无极性。"
},
"C4597619": {
"name": "PCIE-064-02-F-D-TH",
"uuid": "3ea1a15bda65451a97a33a1549965ce3",
"lib": "0819f05c4eef4c71ace90d822a990e87",
"fp": "插件 P=1mm(SAMTEC)",
"role": "J34~J38:R2 底板的 5 个 PCI-E x4 插槽。**符号与刀片 J1(PCIE-X4-EDGE-64P)同源**,脚号 A1~A32 在左列(-35,自上而下 pitch 10,A1 在 +155)、B1~B32 在右列(+35,同 y)。⛔ **T8 风险**:这是 SAMTEC 的贵料,客户端 lib_Device.search 够不着立创全库里那些几毛钱的PC 标准 PCI-E 4X 插槽(同 T7)。下单前必须换成便宜的同针位插槽并核对封装。"
},
"C50951": {
"name": "2.54mm 1*5P 直排针",
"uuid": "e2ae8b0c17dd425aa2f9a1952b667332",
"lib": "0819f05c4eef4c71ace90d822a990e87",
"fp": "HDR-TH_5P-P2.54",
"role": "J33 应急 UART 口(D13 的保命口:GND/TXD0/RXD0/EN/IO0)。rot 0 实测引脚全部朝左:1(-20,+20) 2(-20,+10) 3(-20,0) 4(-20,-10) 5(-20,-20)。位号前缀是 P?,放件后要显式 setState_Designator(\"J33\")。"
},
"C19188869": {
"name": "PCIE-64P11L",
"uuid": "568ba50f7a8e4226aed9ac603b235162",
"lib": "0819f05c4eef4c71ace90d822a990e87",
"fp": "PCIE-SMD_PCIE-64P11L",
"role": "J34~J38:R2 底板的 5 个 PCI-E x4 插槽(SOFNG 硕方,PCIE 64PIN 镀金加强 **贴片立式**)。2026-09-08 取代 SAMTEC PCIE-064-02-F-D-TH(C4597619)结掉 T8 —— 用户要 PC 标准便宜料。⚠ **脚号与刀片金手指不一样**:本符号的**脚号是 1~64**(脚名才是 A1~A32/B1~B32),而刀片 PCIE-X4-EDGE-64P 的脚号直接就是 A1~A32/B1~B32。换算:A(n) → 脚号 n,B(n) → 脚号 32+n。引脚**相对坐标与 Samtec 那颗完全一致**(A 排 x=-35、B 排 x=+35,y = 155-10*(n-1)),所以版图一个字没动。⚠ 还多一根 **65 脚 = 外壳/屏蔽,在 (0,+175)**(符号顶端正中),本项目接 GND。⛔ **贴片立式**:保持力只靠焊盘剪切,不如原来那颗直插 —— 五片刀片各驮一块手机主板,M4 布板时要给刀片加固定件(梳齿/压条),不能只靠插槽。"
},
"C310998": {
"name": "F1206SB2000V063TM",
"uuid": "256f75e322a94be48a90613406f07457",
"lib": "0819f05c4eef4c71ace90d822a990e87",
"fp": "1206",
"role": "刀片 B1-POWER 的 F1:**2A 慢断**贴片保险丝(AEM,SB = Slow Blow),1206 / 63V / 分断 50A / 熔断 I²t = 0.63 A²s。2026-09-08 结掉 T7,取代同封装的 10A 占位件 C310989(F1206HC10A0TM)。⚠ **封装 uuid 与占位件不同**(bceff889… vs 66116fae…,10A/150A 分断那颗焊盘更大),刀片 PCB 还没布(M4),所以无影响;原理图侧的导线是按读回引脚坐标算的,自动跟随。⚠ 刀片满载约 1.45 A(16 W 手机 ÷ 92% ÷ 12 V)= 额定的 73%,正压在保险丝「连续 ≤75%」的降额线上,机箱内 40~50 ℃ 还要再降 ——要更大裕量就换 F1206SB2500V032TM(C310999,2.5A/32V/I²t 0.9)。⚠ 别和底板那两颗混:底板 F1 是 C310990 F1206HC12A0TM(12A,整机 9.99 A)。"
} }
}, },
"schematic_boards": { "schematic_boards": {
@@ -586,7 +693,7 @@
}, },
"Board2": { "Board2": {
"schematic": "302e115180aaa8fc", "schematic": "302e115180aaa8fc",
"pcb": "f126ffb29c92f9b2", "pcb": "942ed1560d81c1e7",
"board": "c6594605c7cbe2b3", "board": "c6594605c7cbe2b3",
"role": "上板 载板(carrier)", "role": "上板 载板(carrier)",
"pages": { "pages": {
@@ -600,7 +707,8 @@
"C4-SITE5": "e58ef2c0a55e744a", "C4-SITE5": "e58ef2c0a55e744a",
"C5-J1401A": "967543b084075b4b", "C5-J1401A": "967543b084075b4b",
"C5-J1401B": "63413775cfc3aafc", "C5-J1401B": "63413775cfc3aafc",
"C5-AUX": "b1ffbaef65ef82c9" "C5-AUX": "b1ffbaef65ef82c9",
"C6-KEYS": "f44d1c8b0ad64fad"
} }
} }
} }
+40 -17
查看文件
@@ -1,35 +1,35 @@
{ {
"frame": "顶视,板左下角原点,mm", "frame": "顶视,板左下角原点,mm",
"board_w": 67.09, "board_w": 44.0,
"board_h": 44.66, "board_h": 67.0,
"holes": { "holes": {
"H1": [ "H1": [
54.2, 1.4,
42.3 9.9
], ],
"H2": [ "H2": [
2.6, 0.7,
38.2 27.3
], ],
"H3": [ "H3": [
61.9, 1.7,
33.7 38.7
], ],
"H4": [ "H4": [
62.1, 41.9,
29.2 1.9
], ],
"H5": [ "H5": [
2.6, 57.4,
27.0 41.2
], ],
"H6": [ "H6": [
4.0, 64.6,
10.8 28.1
], ],
"H7": [ "H7": [
42.6, 64.8,
3.7 32.4
] ]
}, },
"contacts_up": { "contacts_up": {
@@ -71,5 +71,28 @@
2.73, 2.73,
4.38 4.38
] ]
} },
"hole_d": 2.0,
"_source": "2026-09-06 测量底板扫描件反算(pcb2_measure_read.py,基准点单应,±0.5mm)。板长 67 = 裸板;描的轮廓 70.3 含 SIM 卡托伸出的 3.3mm(卡托在 u 大的那一端)",
"side_key_contacts": {
"PWR": [
4.72,
5.16
],
"VUP": [
3.42,
6.37
],
"VDN": [
3.49,
3.74
],
"GND": [
2.05,
4.99
]
},
"_side_key_note": "2026-09-06 实测:中框金焊盘 + 7 孔配准反算,残差 0.57mm。菱形 相邻1.87/对角2.65,焊盘 Φ1.35。簇心 (3.42,5.07),用户卡尺「H1 孔心到簇心 5mm」判死(另一条 2.88 的解已弃)。身份两条独立描述印证:GND 离短板边最近、PWR 与之正对、VDN 离长板边最近、VUP 次之。",
"frame_boss_to_pad_mm": 1.0,
"_frame_boss_note": "2026-09-06 用户实测:中框螺柱支撑面到侧键金焊盘面 1 mm = 载板面到主板贴合面该有的间隙"
} }
+23 -16
查看文件
@@ -56,14 +56,23 @@ HDR = 'c0ab04240dc64fca8d6203d15f8a8a68' # C55020601 AZ-PZ2.54-V11-2*20P
# 不是为了电流(2.8 A / 10 = 0.28 A/针),是为了**让它就近落地、 # 不是为了电流(2.8 A / 10 = 0.28 A/针),是为了**让它就近落地、
# 不必在 Inner1 上拉一条横贯全板的脊**(§25.10) # 不必在 Inner1 上拉一条横贯全板的脊**(§25.10)
# · GND 从 33 针减到 **26 针**,20 A 回流 = 0.77 A/针,仍很宽裕 # · GND 从 33 针减到 **26 针**,20 A 回流 = 0.77 A/针,仍很宽裕
# ## 2026-09-06 第三版:74HC595 + 15 颗按键 MOS 整窝挪到载板(§26.23)
#
# 跨排针的不再是 15 条 `POGO*` + 5 条 `EDL_EN*` + `HUB_RST`(21 针),
# 而是移位链的 4 根线 `KEY_SER` / `KEY_SRCLK` / `KEY_RCLK` / `KEY_OE#`。
# 净腾 17 针,全部补成 GND(回流从 26 针加到 43 针)。
# 4 根 KEY_* 放在 **J25 北排 3~6 行**(载板 x 139~147):595 摆在载板走廊中段
# (J31 与 J32 之间,x 110~133),从 J32 西端出线最近;下板这头 ESP32 在 (88,18),
# U21/U22 腾出来之后 x 88~97 / y 35~55 整片是空的,四根线北上到 J25 西端没有障碍。
# `POGO*` / `EDL_EN*` / `HUB_RST` 从此是**载板内部网络**。
J24 = [ J24 = [
('VO1_P', 'GND'), ('VO1_P', 'GND'), ('VO1_P', 'GND'), # 1~3 -> site1(北) ('VO1_P', 'GND'), ('VO1_P', 'GND'), ('VO1_P', 'GND'), # 1~3 -> site1(北)
('+5V', 'GND'), ('+5V', 'GND'), # 4~5 -> C701/U24(北) ('+5V', 'GND'), ('+5V', 'GND'), # 4~5 -> C701/U24(北)
('POGO1_PWR', 'POGO4_PWR'), # 6~10 北=CH1 信号 ('GND', 'GND'), # 6~9 原 POGO1/POGO4/EDL_EN1/EDL_EN4,
('POGO1_VUP', 'POGO4_VUP'), # 南=CH4 信号 ('GND', 'GND'), # 2026-09-06 随 595 上载板腾空 -> GND
('POGO1_VDN', 'POGO4_VDN'), ('GND', 'GND'),
('EDL_EN1', 'EDL_EN4'), ('GND', 'GND'),
('VBUS_EN1', 'VBUS_EN4'), ('VBUS_EN1', 'VBUS_EN4'), # 10
('SDA', '+5V'), ('SCL', '+5V'), # 11~12 南=U27/C704(南) ('SDA', '+5V'), ('SCL', '+5V'), # 11~12 南=U27/C704(南)
('GND', 'UP_VBUS_DET'), # 13 R722 在底层 y62.2 ('GND', 'UP_VBUS_DET'), # 13 R722 在底层 y62.2
('GND', 'GND'), # 14 ('GND', 'GND'), # 14
@@ -72,18 +81,16 @@ J24 = [
] ]
J25 = [ J25 = [
('+5V', '+5V'), ('+5V', '+5V'), # 1~2 北->C702/U25 南->U28/C705 ('+5V', '+5V'), ('+5V', '+5V'), # 1~2 北->C702/U25 南->U28/C705
('POGO2_PWR', 'VBUS_EN5'), # 3~4 南=CH5 的两个使能 ('KEY_SER', 'VBUS_EN5'), # 3~6 北=74HC595 移位链 4 根(原 POGO2_* / EDL_EN2)
('POGO2_VUP', 'EDL_EN5'), # (U28/U45 就在 x133~136) ('KEY_SRCLK', 'GND'), # 南=CH5 的 VBUS 使能(原 EDL_EN5 -> GND)
('POGO2_VDN', 'GND'), ('KEY_RCLK', 'GND'),
('EDL_EN2', 'GND'), ('KEY_OE#', 'GND'),
('VBUS_EN2', 'GND'), ('VBUS_EN2', 'GND'), # 7
('HUB_RST', 'GND'), # 8 ('GND', 'GND'), # 8 原 HUB_RST -> 载板内部
('GND', 'VO5_P'), ('GND', 'VO5_P'), ('GND', 'VO5_P'), # 9~11 -> site5(南) ('GND', 'VO5_P'), ('GND', 'VO5_P'), ('GND', 'VO5_P'), # 9~11 -> site5(南)
('POGO3_PWR', 'POGO5_PWR'), # 12~14 北=CH3 南=CH5 顶针 ('GND', 'GND'), ('GND', 'GND'), ('GND', 'GND'), # 12~14 原 POGO3_* / POGO5_* -> GND
('POGO3_VUP', 'POGO5_VUP'),
('POGO3_VDN', 'POGO5_VDN'),
('VO3_P', 'GND'), ('VO3_P', 'GND'), ('VO3_P', 'GND'), # 15~17 -> site3(北) ('VO3_P', 'GND'), ('VO3_P', 'GND'), ('VO3_P', 'GND'), # 15~17 -> site3(北)
('EDL_EN3', 'GND'), ('VBUS_EN3', 'GND'), # 18~19 ('GND', 'GND'), ('VBUS_EN3', 'GND'), # 18~19 原 EDL_EN3 -> GND
('+5V', '+5V'), # 20 -> C703/U26(北) ('+5V', '+5V'), # 20 -> C703/U26(北)
] ]
@@ -166,7 +173,7 @@ def plan():
(60, 698, u'⚠ 北排喂北边的站、南排喂南边的站,一路都不许跨行 —— 4A 的电源面穿不过排母的地针行(缝里只剩 0.44mm 铜)。', 10), (60, 698, u'⚠ 北排喂北边的站、南排喂南边的站,一路都不许跨行 —— 4A 的电源面穿不过排母的地针行(缝里只剩 0.44mm 铜)。', 10),
(60, 215, u'每路 VOn_P 用 3 针并联(4A / 3 = 1.33A 每针,母座额定 2.5A 每针),正对面 3 针地做本路回流(§15.4)。', 10), (60, 215, u'每路 VOn_P 用 3 针并联(4A / 3 = 1.33A 每针,母座额定 2.5A 每针),正对面 3 针地做本路回流(§15.4)。', 10),
(60, 195, u'+5V 用 10 针、分散在两颗排母的南北四处:不是为了电流(0.28A 每针),是为了就近落地,免得在 Inner1 上拉一条横贯全板的脊。', 10), (60, 195, u'+5V 用 10 针、分散在两颗排母的南北四处:不是为了电流(0.28A 每针),是为了就近落地,免得在 Inner1 上拉一条横贯全板的脊。', 10),
(60, 175, u'合计 54 个信号 + 26 个地(20A 回流 = 0.77A 每针)。', 10), (60, 175, u'合计 37 个信号 + 43 个地(2026-09-06 起:74HC595 + 按键 MOS 整窝在载板,跨板只剩 KEY_SER/SRCLK/RCLK/OE# 四根,§26.23)。', 10),
(60, 155, u'对插高度 = 2.5 + 10.5 = 13.0mm(塑料对塑料定位),满足 §22.3d 的净空要求。', 10), (60, 155, u'对插高度 = 2.5 + 10.5 = 13.0mm(塑料对塑料定位),满足 §22.3d 的净空要求。', 10),
(60, 135, u'⚠ 下板 C2/C3 罐高必须 <= 11.5mm,且 C2~C6 压在 J24 位置上、PCB 上要挪开(§22.3f)。', 10), (60, 135, u'⚠ 下板 C2/C3 罐高必须 <= 11.5mm,且 C2~C6 压在 J24 位置上、PCB 上要挪开(§22.3f)。', 10),
] ]
+375 -254
查看文件
@@ -1,343 +1,464 @@
{ {
"U33": [ "Q6": {
[ "name": "AO3400A_C20917",
"1", "pins": [
210,
635
],
[
"2",
210,
625
],
[ [
"3", "3",
210, "D",
615 330,
],
[
"4",
210,
605
],
[
"5",
210,
595
],
[
"6",
210,
585
],
[
"7",
210,
575
],
[
"8",
210,
565
],
[
"9",
290,
565
],
[
"10",
290,
575
],
[
"11",
290,
585
],
[
"12",
290,
595
],
[
"13",
290,
605
],
[
"14",
290,
615
],
[
"15",
290,
625
],
[
"16",
290,
635
]
],
"C739": [
[
"2",
120,
710
],
[
"1",
120,
670
]
],
"R735": [
[
"2",
430,
680
],
[
"1",
430,
640
]
],
"R736": [
[
"2",
490,
680
],
[
"1",
490,
640
]
],
"R737": [
[
"2",
550,
680
],
[
"1",
550,
640
]
],
"R738": [
[
"2",
610,
680
],
[
"1",
610,
640
]
],
"R739": [
[
"2",
670,
680
],
[
"1",
670,
640
]
],
"Q6": [
[
"3",
710,
350 350
], ],
[ [
"1", "1",
680, "G",
300,
330 330
], ],
[ [
"2", "2",
710, "S",
330,
310 310
] ]
], ]
"R733": [ },
"R733": {
"name": "0402WGF1001TCE",
"pins": [
[ [
"2", "2",
620, "2",
220,
330 330
], ],
[ [
"1", "1",
580, "1",
180,
330 330
] ]
], ]
"R734": [ },
"R734": {
"name": "0402WGF1003TCE",
"pins": [
[ [
"2", "2",
660, "2",
290 250,
],
[
"1",
660,
250 250
]
], ],
"D2": [
[ [
"1",
"1",
250,
290
]
]
},
"D2": {
"name": "SS34_C8678",
"pins": [
[
"2",
"A",
430,
640
],
[
"1",
"K",
430,
680
]
]
},
"C738": {
"name": "CL31A226KAHNNNE",
"pins": [
[
"1",
"1",
490,
675
],
[
"2",
"2",
490,
645
]
]
},
"R747": {
"name": "0402WGF1001TCE",
"pins": [
[
"2",
"2",
150,
680
],
[
"1",
"1",
150,
640
]
]
},
"Q8": {
"name": "AO3400A_C20917",
"pins": [
[
"3",
"D",
290,
580
],
[
"1",
"G",
260,
560
],
[
"2",
"S",
290,
540
]
]
},
"R748": {
"name": "0402WGF1001TCE",
"pins": [
[
"2",
"2",
220,
560
],
[
"1",
"1",
180,
560
]
]
},
"R749": {
"name": "0402WGF1003TCE",
"pins": [
[
"2",
"2",
230,
530
],
[
"1",
"1",
230,
490
]
]
},
"R750": {
"name": "0402WGF1002TCE",
"pins": [
[
"2",
"2",
960,
740
],
[
"1",
"1",
960,
700
]
]
},
"R751": {
"name": "0402WGF1502TCE",
"pins": [
[
"2",
"2",
830,
690
],
[
"1",
"1",
790,
690
]
]
},
"R752": {
"name": "0402WGF1002TCE",
"pins": [
[
"2",
"2", "2",
790, 790,
670
],
[
"1",
"1",
790,
630
]
]
},
"R753": {
"name": "0402WGF1002TCE",
"pins": [
[
"2",
"2",
710,
770
],
[
"1",
"1",
710,
730
]
]
},
"R754": {
"name": "0402WGF1002TCE",
"pins": [
[
"2",
"2",
960,
500
],
[
"1",
"1",
960,
460
]
]
},
"R755": {
"name": "0402WGF1502TCE",
"pins": [
[
"2",
"2",
830,
450 450
], ],
[ [
"1",
"1", "1",
790, 790,
410 450
] ]
], ]
"C738": [ },
[ "R756": {
"1", "name": "0402WGF1002TCE",
880, "pins": [
605
],
[ [
"2", "2",
880,
635
]
],
"R730": [
[
"2", "2",
950, 790,
320 430
], ],
[ [
"1", "1",
950, "1",
280 790,
390
] ]
], ]
"R731": [ },
"R757": {
"name": "0402WGF1002TCE",
"pins": [
[ [
"2", "2",
890, "2",
240 710,
530
], ],
[ [
"1", "1",
850, "1",
240 710,
490
] ]
], ]
"R732": [ },
[ "J26": {
"2", "name": "2510-4A",
950, "pins": [
220
],
[ [
"1", "1",
950,
180
]
],
"J26": [
[
"1", "1",
1065, 1065,
710 710
], ],
[ [
"2",
"2", "2",
1065, 1065,
700 700
], ],
[ [
"3",
"3", "3",
1065, 1065,
690 690
]
], ],
"J27": [ [
"4",
"4",
1065,
680
]
]
},
"J27": {
"name": "2510-4A",
"pins": [
[ [
"1", "1",
1065,
630
],
[
"2",
1065,
620
],
[
"3",
1065,
610
]
],
"J28": [
[
"1",
1065,
550
],
[
"2",
1065,
540
],
[
"3",
1065,
530
]
],
"J29": [
[
"1", "1",
1065, 1065,
470 470
], ],
[ [
"2",
"2", "2",
1065, 1065,
460 460
], ],
[ [
"3",
"3", "3",
1065, 1065,
450 450
]
], ],
"J30": [ [
"4",
"4",
1065,
440
]
]
},
"Q7": {
"name": "AO3401A_C15127",
"pins": [
[
"3",
"D",
310,
710
],
[ [
"1", "1",
1065, "G",
390 280,
730
], ],
[ [
"2", "2",
1065, "S",
380 310,
750
]
]
},
"DZ3": {
"name": "BZT52C8V2",
"pins": [
[
"1",
"K",
150,
770
], ],
[ [
"3", "2",
1065, "A",
370 150,
730
] ]
]
},
"R746": {
"name": "0402WGF1003TCE",
"pins": [
[
"2",
"2",
240,
770
], ],
"_page": "fd00f13e59336392" [
"1",
"1",
240,
730
]
]
},
"Q9": {
"name": "AO3400A_C20917",
"pins": [
[
"3",
"D",
710,
710
],
[
"1",
"G",
740,
690
],
[
"2",
"S",
710,
670
]
]
},
"Q10": {
"name": "AO3400A_C20917",
"pins": [
[
"3",
"D",
710,
470
],
[
"1",
"G",
740,
450
],
[
"2",
"S",
710,
430
]
]
}
} }
文件差异因一行或多行过长而隐藏
+967 -312
查看文件
文件差异内容过多而无法显示 加载差异
文件差异因一行或多行过长而隐藏
+6 -4
查看文件
@@ -25,7 +25,9 @@ from eda import run
DX, DY = 30.0, 5.0 # 下板原点在载板坐标里的位置 DX, DY = 30.0, 5.0 # 下板原点在载板坐标里的位置
GAP = 13.0 # 两板净空 GAP = 13.0 # 两板净空
MARGIN = 1.0 # 装配余量 MARGIN = 1.0 # 装配余量
BW, BH = 160.0, 125.0 # 2026-09-07:跟 pcb1_place 一起改成真板框(220x135,左下角 (-30,-5))。
BX0, BY0, BX1, BY1 = -30.0, -5.0, 190.0, 130.0
BW, BH = BX1 - BX0, BY1 - BY0
LANE_H = 1.0 # 排线(0.2 厚)+ 走线余量,按 1 mm 算 LANE_H = 1.0 # 排线(0.2 厚)+ 走线余量,按 1 mm 算
@@ -37,7 +39,7 @@ def shift(x0, y0, x1, y1):
def clip(r): def clip(r):
u"""裁到下板板框内;完全在框外返回 None。""" u"""裁到下板板框内;完全在框外返回 None。"""
x0 = max(r[0], 0.0); y0 = max(r[1], 0.0) x0 = max(r[0], 0.0); y0 = max(r[1], 0.0)
x1 = min(r[2], BW); y1 = min(r[3], BH) x1 = min(r[2], BX1); y1 = min(r[3], BY1)
if x1 <= x0 or y1 <= y0: if x1 <= x0 or y1 <= y0:
return None return None
return [round(x0, 2), round(y0, 2), round(x1, 2), round(y1, 2)] return [round(x0, 2), round(y0, 2), round(x1, 2), round(y1, 2)]
@@ -54,7 +56,7 @@ def header_anchors():
pads = json.load(io.open(os.path.join(HERE, 'pcb2-header-pads.json'), encoding='utf-8'))['pads'] pads = json.load(io.open(os.path.join(HERE, 'pcb2-header-pads.json'), encoding='utf-8'))['pads']
nets = {} nets = {}
r = run(""" r = run("""
await eda.dmt_EditorControl.openDocument('f126ffb29c92f9b2'); await eda.dmt_EditorControl.openDocument('942ed1560d81c1e7');
await new Promise(r=>setTimeout(r,2000)); await new Promise(r=>setTimeout(r,2000));
const out = {}; const out = {};
for (const c of await eda.pcb_PrimitiveComponent.getAll()) { for (const c of await eda.pcb_PrimitiveComponent.getAll()) {
@@ -116,7 +118,7 @@ def build():
data = { data = {
'frame': u'下板 PCB1 坐标系 mm,原点左下角,Y 向上。下板坐标 = 载板坐标 − (30,5)。' 'frame': u'下板 PCB1 坐标系 mm,原点左下角,Y 向上。下板坐标 = 载板坐标 − (30,5)。'
u'hmax = 该矩形内下板元件允许的最大高度 = 13(净空) − h(载板底面元件) − 1(余量)', u'hmax = 该矩形内下板元件允许的最大高度 = 13(净空) − h(载板底面元件) − 1(余量)',
'gap': GAP, 'board': [BW, BH], 'gap': GAP, 'board': [BW, BH], 'board_rect': [BX0, BY0, BX1, BY1],
'anchors': anchors, 'zones': zones, 'lanes': lanes, 'slots': slots, 'anchors': anchors, 'zones': zones, 'lanes': lanes, 'slots': slots,
} }
out = os.path.join(HERE, 'pcb1-env.json') out = os.path.join(HERE, 'pcb1-env.json')
+9 -2
查看文件
@@ -156,11 +156,18 @@ def main():
pours = R.pour_rects() pours = R.pour_rects()
obstacles = R.obstacles_of(pads, vias, lines, pours) obstacles = R.obstacles_of(pads, vias, lines, pours)
js = R.jobs(pads, want, skip=() if want else R.SKIP) js = R.jobs(pads, want, skip=() if want else R.SKIP)
# ⚠️ 2026-09-06 起 `route_sig` 把出线段也下发了 —— 布不通的焊盘在板上是
# 「一个焊盘 + 一小截自己的出线段」,不再是光焊盘。只认光焊盘会报「0 个」。
# 出线段全长 ≤ 1.2 mm、没有过孔的碎片,一样当孤立焊盘审。
need = set() need = set()
for net, frags in js: for net, frags in js:
for fr in frags: for fr in frags:
if len(fr['pads']) == 1 and not fr['vias'] and not fr['lines']: if len(fr['pads']) != 1 or fr['vias']:
need.add(fr['pads'][0]['tag']) continue
q = fr['pads'][0]
if all(max(math.hypot(l['x0'] - q['x'], l['y0'] - q['y']),
math.hypot(l['x1'] - q['x'], l['y1'] - q['y'])) <= 1.2 for l in fr['lines']):
need.add(q['tag'])
order = [n for n, _f in js] order = [n for n, _f in js]
stubs, alts = R.plan_stubs(order, pads, obstacles, need) stubs, alts = R.plan_stubs(order, pads, obstacles, need)
audit.stubs = stubs audit.stubs = stubs
+56 -2
查看文件
@@ -70,7 +70,10 @@ E = RS.E # 引擎(polish / _l_shapes / board_mask 从这
#: **但 2026-09-06 试着加进来没跑通**:它板上的铜整片都在顶层、且集中在控制块那一带, #: **但 2026-09-06 试着加进来没跑通**:它板上的铜整片都在顶层、且集中在控制块那一带,
#: 六条连线里有四条报「L2 源0 / L16 源0」—— 主碎片周围一个合法孔位都没有,扎不下去。 #: 六条连线里有四条报「L2 源0 / L16 源0」—— 主碎片周围一个合法孔位都没有,扎不下去。
#: 要挪它得先把控制块那一带的顶层拆开,不是改这一行的事。 #: 要挪它得先把控制块那一带的顶层拆开,不是改这一行的事。
NETS = ('SCL', 'SDA') NETS = ('SCL', 'SDA', 'BUCK_PG')
# ── 侧键模拟线 POGO3_PWR / POGO3_VDN / POGO4_VDN / POGO5_PWR / POGO5_VUP(第二十六轮加)
# 2026-09-06 随 74HC595 + 按键 MOS 整窝挪到载板(§26.23)之后,下板上不再有 POGO* 网络,
# 已从候选表拿掉;它们在 Inner2 上的铜由 pcb_prune_nets.py 一并清掉了。
L_TOP, L_BOT, L3 = 1, 2, 16 L_TOP, L_BOT, L3 = 1, 2, 16
LAYS = (L_TOP, L_BOT, L3) LAYS = (L_TOP, L_BOT, L3)
@@ -108,6 +111,14 @@ CLUSTERS = {
'U12': (121.5, 45.5, 129.5, 52.5), # CH5 INA226 'U12': (121.5, 45.5, 129.5, 52.5), # CH5 INA226
'U13': (3.0, 90.5, 11.0, 100.0), # 输入侧 INA226 'U13': (3.0, 90.5, 11.0, 100.0), # 输入侧 INA226
'J24': (30.0, 60.5, 38.5, 67.0), # 排针(通孔针,L3 上直接落脚) 'J24': (30.0, 60.5, 38.5, 67.0), # 排针(通孔针,L3 上直接落脚)
# 五路 Buck 控制器:`BUCK_PG` 在 U*.14 上。**不划框的话整条出线短线也被拆掉**,
# 而那几个脚在 0.5 mm 脚距的 QFN 上,`entry_stub` 重建不回来 ⇒ 整个网络断成 6 片
# (2026-09-06 实测,连通性保险当场拦下)。框只罩住芯片本体 + 0.6 mm。
'U1': (14.4, 25.1, 19.2, 29.9),
'U2': (48.7, 95.6, 53.5, 100.4),
'U3': (137.1, 95.6, 141.9, 100.4),
'U4': (46.1, 25.1, 50.9, 29.9),
'U5': (125.6, 25.1, 130.4, 29.9),
} }
@@ -964,6 +975,8 @@ def main():
pads, vias, lines = NL.geom(refresh) pads, vias, lines = NL.geom(refresh)
print(u'板上:焊盘 %d / 过孔 %d / 走线 %d' % (len(pads), len(vias), len(lines))) print(u'板上:焊盘 %d / 过孔 %d / 走线 %d' % (len(pads), len(vias), len(lines)))
# ⚠️ 只拆**这一轮要动的**网络(`--net` 限定时尤其重要)
globals()['NETS'] = tuple(nets)
keep_l, drop_l, keep_v, drop_v = split_old(pads, vias, lines) keep_l, drop_l, keep_v, drop_v = split_old(pads, vias, lines)
dlen = sum(math.hypot(lines[i]['x1'] - lines[i]['x0'], dlen = sum(math.hypot(lines[i]['x1'] - lines[i]['x0'],
lines[i]['y1'] - lines[i]['y0']) for i in drop_l) lines[i]['y1'] - lines[i]['y0']) for i in drop_l)
@@ -1016,7 +1029,48 @@ def main():
if worse: if worse:
# ⚠️ 这条保险是必需的:本脚本会把这些网络在簇框外的铜**全部拆掉**重来。 # ⚠️ 这条保险是必需的:本脚本会把这些网络在簇框外的铜**全部拆掉**重来。
# 拆得动、重连不上的话,板子会比动手之前更差,而净距校核照样全绿。 # 拆得动、重连不上的话,板子会比动手之前更差,而净距校核照样全绿。
sys.exit(u'⛔ %s 的连通性变差了 —— 不下发' % ' / '.join(worse)) #
# 但**不要因此把整轮毙掉** —— `NETS` 是一张「候选表」,里面难免有挪不动的
# (2026-09-06 实测 `BUCK_PG`:五个分支要去板子四角,而 L3 上 y<34 往东
# 只剩 x115~116 那条 1.0 mm 缝,`+12V` 把别的通道全占了)。
# 把变差的那几个**踢出去重算一遍**,剩下的照常下发。
print(u' → %s 挪进内层会变差,踢出候选表重算一轮' % ' / '.join(worse))
keep = [n for n in nets if n not in worse]
if not keep:
sys.exit(u'⛔ 候选表里一个都留不下 —— 不下发')
# ⚠️ **拆除清单必须跟着重算**。第一版只换了 `run_plan` 的网络表,
# 而 `drop_l/drop_v` 还是按完整候选表算的 ⇒ `--push` 会把被踢掉那个网络的
# 铜删掉、又不给它重布,板子直接变差(净距校核照样全绿)。
globals()['NETS'] = tuple(keep)
keep_l, drop_l, keep_v, drop_v = split_old(pads, vias, lines)
vlines, vvias = virtual(pads, vias, lines, drop_l, drop_v)
print(u' 重算后要拆:%d 段 / %d 孔(%d 个网络)'
% (len(drop_l), len(drop_v), len(keep)))
del BLOCK[:]
routes = run_plan(pads, vvias, vlines, keep)
routes = chamfer(routes, pads, vvias, vlines)
msgs, _r = plane_report(routes, pads_raw, obs_board)
for m in msgs:
print(u' ⚠️ ' + m)
if msgs:
sys.exit(u'⛔ 重算之后仍有孤岛 —— 不下发')
summarize(routes)
bad = verify(routes, pads, vvias, vlines)
for b in bad[:20]:
print(u' ✗ ' + b)
if bad:
sys.exit(u'⛔ 重算之后校核不过 —— 不下发')
nets = keep
worse2 = []
for net, n, det in connectivity(routes, pads, vvias, vlines):
was = len(frags(net, pads, vias, lines))
print(u' 连通性 %s:%d 个碎片 %s%s'
% (net, n, det if n > 1 else u'✅',
u'' if n <= was else u' ⛔ 仍然变差'))
if n > was and net in keep:
worse2.append(net)
if worse2:
sys.exit(u'⛔ %s 重算之后仍然变差 —— 不下发' % ' / '.join(worse2))
if not push: if not push:
print(u'\n(只算不下发。要下发加 --push)') print(u'\n(只算不下发。要下发加 --push)')
return return
+17 -8
查看文件
@@ -20,6 +20,14 @@ HERE = os.path.dirname(os.path.abspath(__file__)); sys.path.insert(0, HERE)
from eda import run from eda import run
PCB = '7e9415a16a909c51' PCB = '7e9415a16a909c51'
# 2026-09-06:加 `--board2` 就对载板 PCB2 做同一套事(BOM / 对话框 / 对账),产出文件带 _p2_ 前缀
BOARD2 = '--board2' in sys.argv
if BOARD2:
PCB = '942ed1560d81c1e7'
sys.argv = [a for a in sys.argv if a != '--board2']
BOM_FILE = '_p2_sch_bom.json' if BOARD2 else '_p1_sch_bom.json'
COMPS_FILE = '_pcb2_comps.json' if BOARD2 else '_pcb1_comps.json'
ROWS_FILE = '_p2_import_rows.json' if BOARD2 else '_p1_import_rows.json'
HELPERS = u""" HELPERS = u"""
const sleep = ms => new Promise(r => setTimeout(r, ms)); const sleep = ms => new Promise(r => setTimeout(r, ms));
@@ -90,14 +98,14 @@ JS_DUMP = u"""
// 少了这一行,紧跟在 --bom(最后停在某张原理图上)之后跑 --verify, // 少了这一行,紧跟在 --bom(最后停在某张原理图上)之后跑 --verify,
// `pcb_PrimitiveComponent.getAll()` 在原理图文档上直接抛 // `pcb_PrimitiveComponent.getAll()` 在原理图文档上直接抛
// `Cannot read properties of null (reading 'map')` —— 报错信息完全指不到根因。 // `Cannot read properties of null (reading 'map')` —— 报错信息完全指不到根因。
await eda.dmt_EditorControl.openDocument('7e9415a16a909c51'); await eda.dmt_EditorControl.openDocument('%s');
await new Promise(r => setTimeout(r, 1800)); await new Promise(r => setTimeout(r, 1800));
const cs = await eda.pcb_PrimitiveComponent.getAll(); const cs = await eda.pcb_PrimitiveComponent.getAll();
const out = []; const out = [];
for (const c of cs) out.push({d: c.designator, x: c.x, y: c.y, rot: c.rotation, lay: c.layer, for (const c of cs) out.push({d: c.designator, x: c.x, y: c.y, rot: c.rotation, lay: c.layer,
fp: (c.footprint && c.footprint.name) ? c.footprint.name : (c.footprint || null)}); fp: (c.footprint && c.footprint.name) ? c.footprint.name : (c.footprint || null)});
return out; return out;
""" """ % PCB
JS_BOM = u""" JS_BOM = u"""
const sleep = ms => new Promise(r => setTimeout(r, ms)); const sleep = ms => new Promise(r => setTimeout(r, ms));
@@ -123,7 +131,8 @@ def refresh_bom():
⚠️ **一页一个请求**,不要一次抓 11 页 —— `eda.py` 把返回值截到 20000 字符再打印, ⚠️ **一页一个请求**,不要一次抓 11 页 —— `eda.py` 把返回值截到 20000 字符再打印,
整本 BOM 装不下,截断之后 `json.load` 直接炸在中间(2026-09-06 踩过)。 整本 BOM 装不下,截断之后 `json.load` 直接炸在中间(2026-09-06 踩过)。
""" """
pages = json.load(io.open(os.path.join(HERE, 'eda-parts.json'), encoding='utf-8'))['pages'] ep = json.load(io.open(os.path.join(HERE, 'eda-parts.json'), encoding='utf-8'))
pages = ep['schematic_boards']['Board2']['pages'] if BOARD2 else ep['pages']
bom, bad = {}, [] bom, bad = {}, []
for name, uuid in pages.items(): for name, uuid in pages.items():
r = run(JS_BOM % (uuid, name.upper()), timeout=300) r = run(JS_BOM % (uuid, name.upper()), timeout=300)
@@ -141,9 +150,9 @@ def refresh_bom():
for b in bad: for b in bad:
print(u' · ' + b) print(u' · ' + b)
sys.exit(1) sys.exit(1)
io.open(os.path.join(HERE, '_p1_sch_bom.json'), 'w', encoding='utf-8').write( io.open(os.path.join(HERE, BOM_FILE), 'w', encoding='utf-8').write(
json.dumps(bom, ensure_ascii=False)) json.dumps(bom, ensure_ascii=False))
print(u'已写 _p1_sch_bom.json:%d 页 / %d 件' % (len(bom), sum(len(v) for v in bom.values()))) print(u'已写 ' + BOM_FILE + u':%d 页 / %d 件' % (len(bom), sum(len(v) for v in bom.values())))
def dump(): def dump():
@@ -153,8 +162,8 @@ def dump():
def verify(): def verify():
cs = dump() cs = dump()
io.open(os.path.join(HERE, '_pcb1_comps.json'), 'w', encoding='utf-8').write(json.dumps(cs, ensure_ascii=False)) io.open(os.path.join(HERE, COMPS_FILE), 'w', encoding='utf-8').write(json.dumps(cs, ensure_ascii=False))
bom = json.load(io.open(os.path.join(HERE, '_p1_sch_bom.json'), encoding='utf-8')) bom = json.load(io.open(os.path.join(HERE, BOM_FILE), encoding='utf-8'))
sch = {} sch = {}
for pg, v in bom.items(): for pg, v in bom.items():
for c in v: for c in v:
@@ -179,7 +188,7 @@ if __name__ == '__main__':
r = run(JS_PROBE, timeout=400) r = run(JS_PROBE, timeout=400)
if not r.get('success'): sys.exit(json.dumps(r, ensure_ascii=False)[:800]) if not r.get('success'): sys.exit(json.dumps(r, ensure_ascii=False)[:800])
res = r['result'] res = r['result']
io.open(os.path.join(HERE, '_p1_import_rows.json'), 'w', encoding='utf-8').write(json.dumps(res, ensure_ascii=False, indent=1)) io.open(os.path.join(HERE, ROWS_FILE), 'w', encoding='utf-8').write(json.dumps(res, ensure_ascii=False, indent=1))
print(u'opened=%s 快照 %d 屏' % (res.get('opened'), len(res.get('seq') or []))) print(u'opened=%s 快照 %d 屏' % (res.get('opened'), len(res.get('seq') or [])))
print(res.get('buttons')) print(res.get('buttons'))
elif a == '--apply': elif a == '--apply':
+8 -2
查看文件
@@ -166,8 +166,14 @@ def analyze(net, pads, vias, lines, pgrids):
play, g, cid = node play, g, cid = node
if play != lay: if play != lay:
return False return False
i0, i1 = int((x - hw) / G), int((x + hw) / G) + 1 # ⚠️ 必须走 pcb1_pour 的 gi/gj —— 板框左下角是 (−30, −5),不是 (0, 0)。
j0, j1 = int((y - hh) / G), int((y + hh) / G) + 1 # 2026-09-08 踩过:pcb1_pour 把 BW/BH 从假板框 160×125 改成真板框 220×135
# 并加了 BX0/BY0 之后,这里还在 `int(x / G)`,坐标整体偏了 30 mm ——
# 表现是「靠平面连通的网络(+12V/GND/+3V3/VOn_P)忽然全部碎成几十片」,
# 而 pcb1_pour --verify 回读的实测铺铜完全健康。**两边对不上时信实测那一边。**
# 更贵的是 pcb1_orphan_cu 走的就是这套栅格,据此误删过 12 个好过孔。
i0, i1 = PO.gi(x - hw), PO.gi(x + hw) + 1
j0, j1 = PO.gj(y - hh), PO.gj(y + hh) + 1
for j in range(max(0, j0), min(g.ny, j1 + 1)): for j in range(max(0, j0), min(g.ny, j1 + 1)):
for i in range(max(0, i0), min(g.nx, i1 + 1)): for i in range(max(0, i0), min(g.nx, i1 + 1)):
if g.lab[j][i] == cid: if g.lab[j][i] == cid:
+164 -41
查看文件
@@ -43,9 +43,20 @@ from eda import run
PCB = '7e9415a16a909c51' PCB = '7e9415a16a909c51'
MIL = 39.3700787401575 MIL = 39.3700787401575
BW, BH = 160.0, 125.0 # ⚠️⚠️ 2026-09-07 修正:这两个数从 2026-09-06「两板尺寸取大对齐」那一轮起就过期了 ——
# 板上实测的板框是 **220 x 135、左下角 (-30,-5)**(`pcb1_outline.py` 就是这么画的,
# 回读多段线 x -1181.1~7480.3 mil / y -196.9~5118.1 mil 逐条对上)。
# 旧值 (0,0,160,125) 是真板框的**子集**,所以越框检查一直是「偏严但不错」——
# 代价是**左右各 30 mm、上下各 6 mm 的板面从来没进过摆位视野**。
# 板边元件(风扇座、电源座、调试口)按旧值挑位置会挑到板子中间去。
BX0, BY0, BX1, BY1 = -30.0, -5.0, 190.0, 130.0 # 真板框,mm
BW, BH = BX1 - BX0, BY1 - BY0 # 220 x 135
CORNER_R = 5.0 CORNER_R = 5.0
MH = [(5.0, 5.0), (BW - 5.0, 5.0), (5.0, BH - 5.0), (BW - 5.0, BH - 5.0)] # 2026-09-06 用户指令「两板尺寸取大对齐、四角定位孔对齐」:板框改成和载板一样的 220×135,
# **坐标原点不动**(下板坐标 = 载板坐标 − (30,5),pcb1_env.py 那条约定不变),板框画到 (−30,−5)~(190,130)。
# 定位孔 = 载板 `pcb2_place.MH` 减 (30,5):四角 + 两侧中点。载板中跨那两个 (46.6,74.5)/(118.1,74.5)
# 落在下板 CH2 功率级 / 布好的线上,不打。板框与孔由 `pcb_place.py --outline` 下发(同一张表)。
MH = [(-22.0, 3.0), (182.0, 3.0), (-26.0, 126.0), (186.0, 126.0), (-23.0, 55.0), (183.0, 40.0)]
MH_HOLE, MH_PAD, MH_KEEPOUT = 3.2, 6.0, 6.5 MH_HOLE, MH_PAD, MH_KEEPOUT = 3.2, 6.0, 6.5
PARTS = {c['d']: c for c in json.load(io.open(os.path.join(HERE, 'pcb1-parts.json'), encoding='utf-8'))} PARTS = {c['d']: c for c in json.load(io.open(os.path.join(HERE, 'pcb1-parts.json'), encoding='utf-8'))}
@@ -63,8 +74,18 @@ FP_BODY = {
# 上面这几条当年是拿放大截图量出来的,六条全部对得上(误差 ≤0.31)。 # 上面这几条当年是拿放大截图量出来的,六条全部对得上(误差 ≤0.31)。
# ⇒ 新加板边连接器时先跑 `fp_body.py --scan <关键词>`,别再猜 ±4.5。 # ⇒ 新加板边连接器时先跑 `fp_body.py --scan <关键词>`,别再猜 ±4.5。
# ── 2026-09-06 换件:J1 → XT30PW-M,J26~J30 → 2510-3A 直针座 ── # ── 2026-09-06 换件:J1 → XT30PW-M,J26~J30 → 2510-3A 直针座 ──
'CONN-TH_XT30PW-M': (-6.65, -7.98, 6.65, 5.97), # 12V XT30 公头(90°板装) 'CONN-TH_XT30PW-M': (-6.65, -7.98, 6.65, 5.97), # 12V XT30 公头(已换掉,见下一条)
'CONN-TH_2510-3A': (-3.81, -2.88, 3.81, 2.93), # 风扇 3P **直针**座 # ── 2026-09-08 换件:J1 → 圆形 DC 电源座 DC-005-5.5*2.5(用户拍板,不降载)──
# ⚠️ 这个数不是量截图量出来的,是 `pcb_Primitive.getPrimitivesBBox([pid])` 直接读的
# (把件建在板外 (200,0) rot 0 读完再删)。**它含丝印/装配层,比塑壳本体大一圈** ——
# 同法读 XT30 得 14.85 x 13.55,而本体表里是 13.30 x 13.95,x 方向多 0.90。
# 偏大的方向是安全的(净距检查偏严),本体真值待打样时拿实物量(§12 待确认项)。
# 本体几乎全在 −X 侧、三个脚挤在 +X 端 ⇒ **桶口朝 −X**,朝西板边用 rot 0,不用转。
'DC-IN-TH_DC-005-2': (-10.68, -7.16, 3.92, 3.22), # 圆形 DC 座 5.5x2.5
'CONN-TH_2510-3A': (-3.81, -2.88, 3.81, 2.93), # 风扇 3P **直针**座(已换掉,见下一条)
# ── 2026-09-07 FAN-R2:五个 3 针风扇座 → 两个 4 针(一进一出,山洋 9G0612P4S001)──
'CONN-TH_4P-P2.54_CAX_2510-4A': (-5.08, -2.79, 5.08, 3.01), # 风扇 4P 直针座(fp_body --scan 读出)
'SOD-123_L2.7-W1.6-LS3.7-RD-1': (-1.93, -0.85, 1.93, 0.85), # DZ3 BZT52C8V2;本体比焊盘框宽 0.4
# 下面两条是换下来的,留着当对照(**别再用**): # 下面两条是换下来的,留着当对照(**别再用**):
'CONN-TH_P5.08_KF128-5.08-2P': (-5.20, -5.20, 5.20, 5.20), # 旧 12V 螺钉端子 'CONN-TH_P5.08_KF128-5.08-2P': (-5.20, -5.20, 5.20, 5.20), # 旧 12V 螺钉端子
# ⚠️ KF2510-3A 是**弯针**座:焊盘一排,塑壳整个往 +Y 伸 11.7 mm,开口在远端。 # ⚠️ KF2510-3A 是**弯针**座:焊盘一排,塑壳整个往 +Y 伸 11.7 mm,开口在远端。
@@ -113,8 +134,13 @@ H_FP = {
# ⚠️⚠️ 换成立式**省的是板面、付的是高度**:直针座塑壳竖起来。 # ⚠️⚠️ 换成立式**省的是板面、付的是高度**:直针座塑壳竖起来。
# `3D Model Transform` 只给 XY,**Z 读不出来** —— 下面两个数是按同系列 # `3D Model Transform` 只给 XY,**Z 读不出来** —— 下面两个数是按同系列
# 常规值取的保守估计,**打样前必须拿实物量**(§12 待确认项)。 # 常规值取的保守估计,**打样前必须拿实物量**(§12 待确认项)。
'CONN-TH_XT30PW-M': 11.0, # XT30 90° 板装公头,本体躺着 'CONN-TH_XT30PW-M': 11.0, # XT30 90° 板装公头,本体躺着(已换掉)
'CONN-TH_2510-3A': 9.0, # 2510 直针座,塑壳竖立 'DC-IN-TH_DC-005-2': 11.0, # 圆形 DC 座:同系列常规值,**打样前拿实物量**
# 放在西条(x −29~−1)上,那一条 hmax = 99(载板底面没东西),所以这个数目前不吃紧
'CONN-TH_2510-3A': 9.0, # 2510 直针座,塑壳竖立(3P,已换掉)
'CONN-TH_4P-P2.54_CAX_2510-4A': 11.5, # 2510 直针 4P:厂家属性 Z-Height of the Board = 11.5
'SOT-23_L2.9-W1.3-P1.90-LS2.4-BR': 1.2, # Q7 AO3401A(和另一个 SOT-23-3 同高,封装名不同)
'SOD-123_L2.7-W1.6-LS3.7-RD-1': 1.2, # DZ3 BZT52C8V2
# 排针本身就是对插件,不参与高度核算 # 排针本身就是对插件,不参与高度核算
'HDR-TH_40P-P2.54_AZ-PZ2.54-V11-2X20P': None, 'HDR-TH_40P-P2.54_AZ-PZ2.54-V11-2X20P': None,
} }
@@ -238,12 +264,7 @@ CH1 = [
# 放电联锁 # 放电联锁
('Q%01', 13.8, 35.5, 0), ('R%18', 16.4, 35.5, 0), ('Q%01', 13.8, 35.5, 0), ('R%18', 16.4, 35.5, 0),
('Q%02', 13.8, 39.5, 0), ('R%19', 17.8, 40.0, 90), ('Q%02', 13.8, 39.5, 0), ('R%19', 17.8, 40.0, 90),
# 按键驱动(远离 SW) # 按键驱动 Q%03~05 / R%20~25:2026-09-06 随 74HC595 整窝挪到载板(§26.23),这一片空出来了
('Q%03', 24.0, 12.0, 0), ('Q%04', 28.0, 12.0, 0),
('Q%05', 24.0, 16.0, 0), ('R%20', 28.0, 16.0, 0),
('R%21', 24.0, 19.5, 0), ('R%22', 28.0, 19.5, 0),
('R%23', 24.0, 22.5, 0), ('R%24', 28.0, 22.5, 0),
('R%25', 24.0, 25.5, 0),
] ]
@@ -294,7 +315,20 @@ put('J25', 128.0, 62.5, 0)
# **原来 F1 那个位置 (14.5,71.5) 正好被吃掉**。 # **原来 F1 那个位置 (14.5,71.5) 正好被吃掉**。
# 而东边的墙是硬的:北排 CH2 从 **x 29.6** 起,D1(SMB) 再往东就撞上去。 # 而东边的墙是硬的:北排 CH2 从 **x 29.6** 起,D1(SMB) 再往东就撞上去。
# ⇒ J1 东边只剩 15 mm 给 F1/Q1/D1/DZ1/C1/R1 六件,一行排不下(要 15+ mm),改**两行**。 # ⇒ J1 东边只剩 15 mm 给 F1/Q1/D1/DZ1/C1/R1 六件,一行排不下(要 15+ mm),改**两行**。
put('J1', 8.5, 72.0, 270) # 12V XT30 公头,插口朝左板边(rot 270) # 2026-09-08 ⛔ **硬缺陷修复**:J1 原来在 x=8.5(本体 x 0.5~14.7)——
# 那是按**过期的假板框 160×125**挑的“西板边”。真板框西边在 x=−30,
# 于是这个 **90° 板装** XT30 的插口缩在板内 30.5 mm,上方 13 mm 处还压着载板
# —— **插头根本插不进去**。不是优化问题,是板子能不能用的问题。
# → 整个输入首级(J1/F1/Q1/D1 + Q1 的栅极卫星 DZ1/C1/R1)西移 30 mm。
# 西条 x −29~−1 整条 hmax = 99(载板底面没东西),高件随便放。
# 代价:加权 HPWL +1~3%(源头离负载远了)—— 这是板子可装配的必要代价。
# 2026-09-08 换成圆形 DC 电源座(用户拍板:DC-005 5.5x2.5,**不降载**,接受插座超额定)。
# ⚠️ 整机 12V 最坏 9.99 A(§22.6),而 DC-005 家族额定 2~5 A —— 这是全板唯一一处
# 明知超额定还要装的地方,发热在**插头内芯与插座弹片的接触面**,板上加铜救不了。
# 板端能做的只有一件:pin2 / pin3 两个负极触点**都接地并联**,接触电阻减半(见 p1_dcjack.py)。
# 桶口朝 −X ⇒ **rot 0**(XT30 那颗要 rot 270,别照抄)。本体 x −10.68~+3.92 相对中心,
# 放 x=−18.8 时本体 x −29.48~−14.88,离真西板边 (−30) 0.52 mm,和原来 XT30 的 0.5 一致。
put('J1', -18.8, 72.0, 0) # 12V 圆形 DC 座,桶口朝**真**西板边(本体 x −29.5~−14.9)
# ⚠⚠ **v11 的两行排法把 8 A 链卡死了**(2026-09-06 第二十三轮重排)。 # ⚠⚠ **v11 的两行排法把 8 A 链卡死了**(2026-09-06 第二十三轮重排)。
# XT30 rot 270 之后 **GND 在北针 (13.5,74.5)、VIN 在南针 (13.5,69.5)**, # XT30 rot 270 之后 **GND 在北针 (13.5,74.5)、VIN 在南针 (13.5,69.5)**,
# 而 F1 卵在 (17.5,74.5) 卧着放 ⇒ VIN_RAW 要从南针绕到 GND 盘东侧再北上, # 而 F1 卵在 (17.5,74.5) 卧着放 ⇒ VIN_RAW 要从南针绕到 GND 盘东侧再北上,
@@ -304,12 +338,12 @@ put('J1', 8.5, 72.0, 270) # 12V XT30 公头,插口朝左板边(rot 270
# ② D1 挪到东边空列 ⇒ J1.2 → F1.1 一片 6.5 × 7.9 mm 的铜,不再有颈; # ② D1 挪到东边空列 ⇒ J1.2 → F1.1 一片 6.5 × 7.9 mm 的铜,不再有颈;
# ③ C4/C5/C6 一起东移 ⇒ 腾出 y 78.85~83.5 那条 4.65 mm 的 VIN_SW 西行干道 # ③ C4/C5/C6 一起东移 ⇒ 腾出 y 78.85~83.5 那条 4.65 mm 的 VIN_SW 西行干道
# (干道北提是因为 J1 的机械脚 J1.4 在 (3.5,77.5),占到 y 78.6)。 # (干道北提是因为 J1 的机械脚 J1.4 在 (3.5,77.5),占到 y 78.6)。
put('F1', 17.5, 74.5, 90) # 立着放:F1.1(VIN_RAW) 在南、F1.2(VIN_FUSED) 在北 put('F1', -12.5, 74.5, 90) # 立着放:F1.1(VIN_RAW) 在南、F1.2(VIN_FUSED) 在北
put('Q1', 24.0, 74.5, 270) # SOP-8 P-MOS 防反接 put('Q1', -6.0, 74.5, 270) # SOP-8 P-MOS 防反接
put('D1', 31.0, 74.5, 270) # SMBJ13A TVS(挪出 VIN_RAW 通道,贴在漏极铜东缘) put('D1', 1.0, 74.5, 270) # SMBJ13A TVS(挪出 VIN_RAW 通道,贴在漏极铜东缘)
put('DZ1', 22.0, 68.0, 0) # 栅极钳位 put('DZ1', -8.0, 68.0, 0) # 栅极钳位
put('C1', 25.5, 68.0, 0) put('C1', -4.5, 68.0, 0)
put('R1', 28.0, 71.5, 0) put('R1', -2.0, 71.5, 0)
put('C2', 6.5, 84.5, 0) # 470µF/25V put('C2', 6.5, 84.5, 0) # 470µF/25V
put('C3', 16.0, 84.5, 0) put('C3', 16.0, 84.5, 0)
# C4/C5/C6:rot 180 让 $1N236 那一脚朝西迎着漏极铜,GND 脚朝东好打过孔 # C4/C5/C6:rot 180 让 $1N236 那一脚朝西迎着漏极铜,GND 脚朝东好打过孔
@@ -360,28 +394,73 @@ put('R707', 60.0, 71.5, 180)
put('L3', 70.5, 78.0, 0) put('L3', 70.5, 78.0, 0)
put('C734', 77.5, 78.0, 90) put('C734', 77.5, 78.0, 90)
put('C735', 80.0, 78.0, 90) put('C735', 80.0, 78.0, 90)
# 第三片 595(EDL_EN1~5)+ 风扇低边驱动 # ── 风扇 FAN-R2(2026-09-07):两只 4 线 PWM 风扇,一进一出 ──────────────
put('U33', 92.0, 71.0, 0) # 旧的「低边切回流 + 5 个 3 针座 + TACH 分压」整块作废:R730~R732、J28~J30 已删。
# 现在是「高边默认关电 + 独立 PWM 开漏 + 两路独立测速接收」,见设计文档 §22.6。
# ⚠️ 摆位约束不只是「元件之间不重叠」:这一块北中区 y 64~100 底下压着已布好的
# +12V / +3V3 / GND 铜和过孔,而 `pcb1_route_sig --ripup` **只拆自己画过的信号线**,
# 拆不掉电源铜。第一版把新件摆进 y 66~88 直接刷出 65 条间距错误。
# ⇒ 新件整体上移到 **y 104~121 那条空带**(J28 删掉之后腾出来的),
# 留用件 Q6 / R733 / R734 原地不动(它们原来的位置本来就是干净的)。
# 【A】高边电源开关:+12V --Q7(P-MOS)--> FAN_12V_SW,MCU 高阻时默认断电
put('R746', 91.0, 120.0, 0) # 100k 栅极拉回源极
put('Q7', 95.0, 120.0, 0) # AO3401A
put('DZ3', 99.5, 120.0, 0) # BZT52C8V2 钳位负 Vgs(Q7 的 Vgs 绝对最大 ±12V)
put('C738', 105.0, 120.0, 0) # 22uF/25V,开关后母线本地储能
put('D2', 111.5, 120.0, 0) # SS34:K 接 FAN_12V_SW、A 接 GND(负向钳位,不再是续流)
put('R747', 91.0, 116.0, 0) # 1k 栅极串阻 -> Q8 漏极
put('Q8', 95.0, 116.0, 0) # AO3400A 拉低 Q7 栅极
put('R748', 99.5, 116.0, 0) # 1k FAN_EN(IO5) -> Q8 栅极
put('R749', 103.0, 116.0, 0) # 100k Q8 栅极下拉 = 复位默认断电
# 【B】两路独立测速接收(10k 上拉到 FAN_12V_SW -> 15k/10k 分压 -> N-MOS -> 3V3 上拉)
# 2026-09-08:**跟着各自的风扇座走到真板边**。
# 链路是 J2n.TACH → R75x(上拉到 FAN_12V_SW) → 分压 → Q(N-MOS) → FAN_TACH_xx(3V3 逻辑)。
# `FAN_IN_TACH_RAW` / `FAN_OUT_TACH_RAW` 是**座子到分压器**的本地网络:
# 接收电路留在中间的话,这两个网络要横跨大半块板(121 / 104 mm);
# 挪到座子旁边只剩几毫米。代价是 `FAN_TACH_IN/OUT`(已经是 3.3 V 逻辑、
# 经 N-MOS 整形过)各长约 16 mm —— 净赚一百多毫米,而且换掉的是**未整形的
# 12 V 摆幅裸信号**,抗扰上也是对的。
put('R750', -26.0, 108.0, 0) # 进风:贴着西板边的 J26
put('R751', -21.0, 108.0, 0)
put('Q9', -26.0, 104.0, 0)
put('R752', -21.0, 104.0, 0)
put('R753', -26.0, 100.0, 0)
put('R754', 181.0, 108.0, 0) # 排风:贴着东板边的 J27
put('R755', 186.0, 108.0, 0)
put('Q10', 181.0, 104.0, 0)
put('R756', 186.0, 104.0, 0)
put('R757', 181.0, 100.0, 0)
# 【C】PWM 低边开漏:Q6 / R733 / R734 保持 2026-09-06 的原位(现在只拉风扇 PWM 脚)
put('Q6', 102.0, 69.5, 0) put('Q6', 102.0, 69.5, 0)
put('D2', 108.5, 69.5, 0)
put('C738', 102.0, 74.0, 0)
put('C739', 107.0, 74.0, 0)
put('R730', 98.0, 78.0, 0)
put('R731', 101.5, 78.0, 0)
put('R732', 105.0, 78.0, 0)
put('R733', 98.0, 80.5, 0) put('R733', 98.0, 80.5, 0)
put('R734', 101.5, 80.5, 0) put('R734', 101.5, 80.5, 0)
# 风扇座:4 个侧吸 + 1 个顶排,全部放板边(§22.6) # 风扇座:一进一出,分列**真**左右板边(顶排风孔取消,见 THERMAL_TWO_FAN)
put('J26', 5.0, 16.0, 0) # 左板边(南) # ⚠️ 4P 座比 3P 宽 2.54 ⇒ 半宽 3.81 → 5.08。本体 (-5.08,-2.79)-(5.08,3.01),rot 0。
put('J27', 5.0, 30.0, 0) #
put('J29', 154.9, 16.0, 0) # 右板边(南) # 2026-09-08 挪位:原来在 (6.3,121) / (151,121) —— 那是按**过期的假板框 160×125** 挑的
put('J30', 154.9, 30.0, 0) # 「两个上角」,在真板框 220×135 / 左下角 (−30,−5) 里其实都在**板子中间**,
put('J28', 100.0, 112.7, 0) # 顶板边一侧;弯针塑壳往 +Y 伸 11.7,壳顶顶到 y 124.4 # 风扇线缆没法横着出去(直针座是垂直插拔,板边的意义就在于让线缆走得掉)。
#
# 当年退到「上角」的理由是:4P 座塑壳 11.5 mm(厂家 Z-Height),而下板 hmax 由
# **载板底面元件**的投影压出来(pcb1-env.json zones),假板框上 hmax >= 11.5 的
# 板边只有 y 68~92 一段,还被输入链占满。**真板框下这个约束消失**:
# 左条 x −30~0 整条 hmax 无限制,右条 x 160~190 有 84% 无限制。
# 实扫(`_fanpos2.py`):西边 y 2~49 与 61~120、东边 y 2~34 与 67~120 都可用。
#
# ⚠️ 压降复核(原注释担心的那一条):`FAN_12V_SW` 总共 2×0.67 A,但 Q7 居中、
# 两支**各只走 0.67 A**。0.8 mm 线宽下 R = ρL/(Wt):
# 西支 125 mm → 0.077 Ω → 51 mV;东支 95 mm → 0.058 Ω → 39 mV
# 都远在 FAN_CIRCUIT 那 0.2 V 预算之内。当年算的 0.26 V 是「单条 196 mm 走全电流」,
# 分成两支之后不成立。⇒ **这条线要按 0.8 mm 下发,别当普通信号走 0.20。**
put('J26', -23.9, 115.0, 0) # 真西板边 = 进风(本体 x −29.0~−18.8,离板框 1.0)
put('J27', 183.9, 115.0, 0) # 真东板边 = 排风(本体 x 178.8~189.0)
# ══════════════════ 【S-M】P3 / P3B 控制块 ══════════════════ # ══════════════════ 【S-M】P3 / P3B 控制块 ══════════════════
# 南中块 x 69.5~119.5 / y 0.5~59 # 南中块 x 69.5~119.5 / y 0.5~59
put('U20', 88.0, 18.2, 180) # ESP32-S3:天线端转到 −Y 朝下板边 put('U20', 88.0, 18.2, 180) # ESP32-S3:天线端转到 −Y 朝下板边
put('J22', 154.4, 52.0, 90) # 调试 USB-C,rot 90 = 开口朝 +X 右板边 # 2026-09-08 ⛔ 同一类硬缺陷:J22 原来在 x=154.4(本体到 159.6)= 假板框的东边,
# 真板框东边在 x=190,于是 USB-C 开口缩在板内 30.4 mm,线插不进去。
put('J22', 184.4, 52.0, 90) # 调试 USB-C,rot 90 = 开口朝**真**东板边(本体到 189.6)
# (下板边放不下:C841 罐的 1.5 mm 禁区压到 y 8.4) # (下板边放不下:C841 罐的 1.5 mm 禁区压到 y 8.4)
put('RT601', 70.8, 36.0, 90) # 电感区测温:贴着 CH4 的电感那一排 put('RT601', 70.8, 36.0, 90) # 电感区测温:贴着 CH4 的电感那一排
put('RT602', 70.8, 26.0, 90) # MOS 区测温 put('RT602', 70.8, 26.0, 90) # MOS 区测温
@@ -413,8 +492,7 @@ put('RT602', 70.8, 26.0, 90) # MOS 区测温
DECAP = [ DECAP = [
('C602', 'U20', '2', 3.0), # 100 nF 0402,ESP32 高频去耦 —— 先占位 ('C602', 'U20', '2', 3.0), # 100 nF 0402,ESP32 高频去耦 —— 先占位
('C601', 'U20', '2', 8.0), # 22 µF 1206,ESP32 储能 ('C601', 'U20', '2', 8.0), # 22 µF 1206,ESP32 储能
('C604', 'U21', '10', 3.0), # 74HC595 #1 VCC # C604 / C605(74HC595 去耦)2026-09-06 随 595 挪到载板
('C605', 'U22', '10', 3.0), # 74HC595 #2 VCC
('C606', 'U23', '24', 3.0), # PCA9555 VDD ('C606', 'U23', '24', 3.0), # PCA9555 VDD
('C607', 'U6', '15', 3.0), # DAC63204 #1 VDD ('C607', 'U6', '15', 3.0), # DAC63204 #1 VDD
('C610', 'U7', '15', 3.0), # DAC63204 #2 VDD ('C610', 'U7', '15', 3.0), # DAC63204 #2 VDD
@@ -457,8 +535,7 @@ def affine(pagelist, x0, y0, w, h):
# 给走线留通道),结果钉在下面这张表里。附属的 22 个 R/C 不再跟着仿射走, # 给走线留通道),结果钉在下面这张表里。附属的 22 个 R/C 不再跟着仿射走,
# 改成 `SAT` 表逐个贴到**它真正的对端引脚**旁边。 # 改成 `SAT` 表逐个贴到**它真正的对端引脚**旁边。
CTRL = { CTRL = {
'U21': (92.25, 52.37), # U21 (92.25,52.37) / U22 (92.84,38.01) 2026-09-06 挪到载板(§26.23)—— x 88~97 / y 35~55 空出来
'U22': (92.84, 38.01),
'U23': (76.50, 49.63), 'U23': (76.50, 49.63),
'U6': (79.58, 38.01), 'U6': (79.58, 38.01),
'U7': (109.57, 34.71), 'U7': (109.57, 34.71),
@@ -489,7 +566,6 @@ SAT = [
('R608', 'U23', '1', 6.0, u'IOEXP_INT# 上拉'), ('R608', 'U23', '1', 6.0, u'IOEXP_INT# 上拉'),
('R606', 'U23', '9', 6.0, u'BUCK_PG 上拉'), ('R606', 'U23', '9', 6.0, u'BUCK_PG 上拉'),
('R607', 'U23', '10', 6.0, u'INA_ALERT 上拉'), ('R607', 'U23', '10', 6.0, u'INA_ALERT 上拉'),
('R609', 'U21', '13', 7.0, u'KEY_OE# 上拉(三片 595 共用)'),
('C608', 'U6', '13', 4.0, u'DAC #1 内部基准/输出滤波'), ('C608', 'U6', '13', 4.0, u'DAC #1 内部基准/输出滤波'),
('C609', 'U6', '16', 4.0, u'DAC #1'), ('C609', 'U6', '16', 4.0, u'DAC #1'),
('R614', 'U6', '16', 5.0, u'DAC #1'), ('R614', 'U6', '16', 5.0, u'DAC #1'),
@@ -586,7 +662,7 @@ def _fits(des, cx, cy, rot, clear=0.3):
u"""摆在这里会不会撞人 / 越框 / 压定位孔 / 超高。""" u"""摆在这里会不会撞人 / 越框 / 压定位孔 / 超高。"""
b = box(des, rot) b = box(des, rot)
ab = (cx + b[0], cy + b[1], cx + b[2], cy + b[3]) ab = (cx + b[0], cy + b[1], cx + b[2], cy + b[3])
if ab[0] < 0.4 or ab[2] > BW - 0.4 or ab[1] < 0.4 or ab[3] > BH - 0.4: if ab[0] < BX0 + 0.4 or ab[2] > BX1 - 0.4 or ab[1] < BY0 + 0.4 or ab[3] > BY1 - 0.4:
return False return False
r = MH_KEEPOUT / 2.0 r = MH_KEEPOUT / 2.0
for hx, hy in MH: for hx, hy in MH:
@@ -756,7 +832,7 @@ def check():
for d in sorted(POS): for d in sorted(POS):
x0, y0, x1, y1 = abs_box(d) x0, y0, x1, y1 = abs_box(d)
m = 0.4 m = 0.4
if x0 < m or x1 > BW - m or y0 < m or y1 > BH - m: if x0 < BX0 + m or x1 > BX1 - m or y0 < BY0 + m or y1 > BY1 - m:
errs.append(u'越出板框: %-6s 框 %.2f..%.2f / %.2f..%.2f' % (d, x0, x1, y0, y1)) errs.append(u'越出板框: %-6s 框 %.2f..%.2f / %.2f..%.2f' % (d, x0, x1, y0, y1))
r = MH_KEEPOUT / 2.0 r = MH_KEEPOUT / 2.0
@@ -840,6 +916,53 @@ def mate_report():
% (p, v[2], v[0], v[3], u'✔' if v[2] == v[3] else u'✘')) % (p, v[2], v[0], v[3], u'✔' if v[2] == v[3] else u'✘'))
# ══════════ 出线道加宽(2026-09-06 第二十六轮,用户拍板)══════════
#: **「够净距」不等于「出得去线」。** `pad_clearance_errs` 判的是焊盘净距 ≥ 0.20,
#: 而一根 0.2 mm 的线要从两个焊盘中间出去,需要 0.20 + 0.20 + 0.20 = **0.60**。
#: U6 / U7(WQFN-16 3×3,四边出脚)周围的卫星件正好卡在 **0.31 ~ 0.38** ——
#: 本地六项全过、DRC 也没有间距错误,可 `pcb1_escape_audit` 报「被围死」、
#: 布线器报「几何上就出不去」,`SCL`(U6.6) / `$6N532`(U6.16) / `$6N547`(U7.16)
#: 三个网络因此永远布不通。这里把堵住出线道的六颗按**背离芯片**的方向各挪 0.30 mm。
#:
#: ⚠️ 挪完 `C607` / `R617` 会分别压到 `SDA` / `SCL` 的换层孔(净距 0.029 / 0.113)——
#: 那两个孔是 `pcb1_i2c_l3.py` 每轮重算的,**挪件之后必须重跑它**,孔会自己让开。
#:
#: ⚠️ §26.22b:门槛不是 0.60 而是 **0.741**(CLR + 半线宽 + 栅格量化余量 QERR,两侧各 0.3707)。
#: 0.30 挪出来的 0.612/0.615/0.676 理论上塞得下、0.1 栅格上塞不下 ⇒ 2026-09-06 第二十八轮
#: 改成 **0.50(东列 0.45)**,三个方向都 ≥ 0.80。板上现在(`_pcb1_place_now.json`)是按 0.30 摆的,
#: 再挪 0.20 / 0.15 会压到的铜见 `.claude/_pcb1_unrouted_report.md` §A(VDAC4 孔、SDA 线+孔,
#: 以及 10 段要按新位置重做的扇出短线)。
#: ⚠️⚠️ **这一手救不了 `U6.16` / `U7.16`**(`pcb1_ch_reroute.py` 本地实测:挪完照样「几何上就出不去」)——
#: 它们被 C609 / C612 的 GND 脚 + GND 扇出孔从西边、C607 / C610 从北边、U6.15 的 +3V3 出线段从东边
#: 三面围死,北挪 C607 只是把北墙推远,西墙东墙不动。真正的解法是下面的 `SAT_FLIP`。
ESCAPE_NUDGE = {
'R615': (0.0, -0.50), # U6 南排(U6.5 / U6.6=SCL) 0.312 -> 0.812
'C607': (0.0, +0.50), # U6 北排(U6.13/15/16) 0.315 -> 0.815
'C608': (+0.45, 0.0), # U6 东列(VDAC2 / VDAC4) 0.376 -> 0.826
'R617': (0.0, -0.50), # U7 南排(U7.5 / U7.6) 0.315 -> 0.815
'C610': (0.0, +0.50), # U7 北排(U7.13/15/16) 0.312 -> 0.812
'C611': (+0.45, 0.0), # U7 东列(VDAC3 / VDAC5) 0.376 -> 0.826
}
for _d, (_dx, _dy) in sorted(ESCAPE_NUDGE.items()):
if _d in POS:
_x, _y, _r = POS[_d]
POS[_d] = (round(_x + _dx, 3), round(_y + _dy, 3), _r)
# ══════════ VREF 电容转 180°(2026-09-06 第二十八轮,A 类唯一真正的几何解)══════════
#: `C609` / `C612` 是 DAC63204 的 VREF 电容(pin 16),`place_sats()` 把它们竖着(rot 90)贴在
#: U6.16 / U7.16 的西北角:**GND 脚在南(离引脚近)、VREF 脚在北(离引脚远)**,
#: GND 扇出孔又打在 GND 脚正南 —— 于是 U6.16 往西北去找 VREF 脚的那条道被自家 GND 孔 + GND 短线堵死。
#: 转 180°(rot 90 -> 270)之后 VREF 脚朝南正对 U6.16(0.8 mm),GND 脚朝北。
#: `pcb1_ch_reroute.py --win 73,32,87,47 --nets '$6N532' --move C609:0:0:180 --drop-via 78.03,39.61`
#: 本地实测:`$6N532` 3.6 mm 一次布通、净距 0;U7 同理。下发时要删 2 段扇出短线 + 补 1 个 GND 孔
#: (见 `_pcb1_unrouted_report.md` §A)。
SAT_FLIP = {'C609': 180, 'C612': 180}
for _d, _dr in sorted(SAT_FLIP.items()):
if _d in POS:
_x, _y, _r = POS[_d]
POS[_d] = (_x, _y, (_r + _dr) % 360)
def summary(): def summary():
ext = ch_extent() ext = ch_extent()
print(u'母版局部包围盒 x %.2f~%.2f y %.2f~%.2f(%.1f x %.1f)' print(u'母版局部包围盒 x %.2f~%.2f y %.2f~%.2f(%.1f x %.1f)'
+157 -64
查看文件
@@ -71,6 +71,11 @@ J24 焊盘 x 6.97~57.03、J25 x 102.97~153.03,两排 y 61.2 / 63.8,Ø1.8 焊
**南边要 5.6 A**(CH1 + CH4 + CH5 各 1.6 A,加左右四个风扇约 0.4+0.4 A)。 **南边要 5.6 A**(CH1 + CH4 + CH5 各 1.6 A,加左右四个风扇约 0.4+0.4 A)。
分法:**中桥喂 CH1 + CH4 + 左风扇 = 3.6 A,右通道喂 CH5 + 右风扇 = 2.0 A。** 分法:**中桥喂 CH1 + CH4 + 左风扇 = 3.6 A,右通道喂 CH5 + 右风扇 = 2.0 A。**
> ⚠️ 2026-09-07 FAN-R2:五个 3 针风扇座删成两个 4 针,而且挪到了**北边两个上角**,
> 供电也从 `+12V` 改成 Q7 开关之后的 `FAN_12V_SW`。所以**南边现在只有 4.8 A**
> (中桥 3.2 / 右通道 1.6),风扇那 1.34 A 全部走北带经 Q7。上面两个 3.6 / 2.0 的
> 判据没改,现在只是更保守,不用重算。
左通道不用 —— 它的北端要从 J1 (6.0,74) 和 C2.1 (4.8,82) 两个通孔旁边挤过去, 左通道不用 —— 它的北端要从 J1 (6.0,74) 和 C2.1 (4.8,82) 两个通孔旁边挤过去,
只剩 2.8 mm、带 2.06 A,够不上一路通道的量,而且那一片正好在 8 A 输入链的顶层大铜下面, 只剩 2.8 mm、带 2.06 A,够不上一路通道的量,而且那一片正好在 8 A 输入链的顶层大铜下面,
留成 GND 底填**比塞一条 12V 细带有用得多**。 留成 GND 底填**比塞一条 12V 细带有用得多**。
@@ -135,7 +140,15 @@ VIN_RAW, VIN_FUSED, VIN_SW = AUTO['VIN_RAW'], AUTO['VIN_FUSED'], AUTO['VIN_SW']
CHAIN = ((VIN_RAW, 'VIN_RAW'), (VIN_FUSED, 'VIN_FUSED'), (VIN_SW, 'VIN_SW')) CHAIN = ((VIN_RAW, 'VIN_RAW'), (VIN_FUSED, 'VIN_FUSED'), (VIN_SW, 'VIN_SW'))
MIL = 39.3700787401575 MIL = 39.3700787401575
BW, BH = 160.0, 125.0 # ⚠️⚠️ 2026-09-08:这里原来写的是 `BW, BH = 160.0, 125.0`,栅格从 (0,0) 起索引 ——
# 那是**过期的假板框**,和 `pcb1_place.py` 当年那条是同一个病(它已在第三十轮改掉)。
# 真板框是 **220 × 135、左下角 (−30, −5)**(`pcb1_outline.py` 就是这么画的)。
# 后果:**本地校核看不见 x<0 / y<0 那两条边**,落在那里的铜一律算成 0 ——
# 板上的铜不受影响(多边形是原样下发给 EasyEDA 的、由它去填),
# 但**任何摆到负坐标的东西,断面/连通性校核都会静悄悄地给出 0**。
# 第三十四轮把输入首级西移到 x −17~2 之后当场撞上:三块顶层铜全报「碎成 0 块」。
BX0, BY0 = -30.0, -5.0 # 板框左下角
BW, BH = 220.0, 135.0 # 板框尺寸
INSET = 0.5 # 铜面离板边 INSET = 0.5 # 铜面离板边
CLEAR = 0.2 # 安全间距(pcb1_rules.MIN_CLEARANCE) CLEAR = 0.2 # 安全间距(pcb1_rules.MIN_CLEARANCE)
@@ -167,6 +180,24 @@ def rect(x0, y0, x1, y1):
return [(x0, y0), (x1, y0), (x1, y1), (x0, y1)] return [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
# ── 毫米 ↔ 栅格:**一律走这四个函数,不要再写 int(x / G)** ──────────────
# 板框左下角不是 (0,0),直接除 G 会把负坐标截成 0(见上面 BX0/BY0 那段)。
def gi(x):
return int((x - BX0) / G)
def gj(y):
return int((y - BY0) / G)
def gx(i):
return BX0 + (i + 0.5) * G
def gy(j):
return BY0 + (j + 0.5) * G
CORNER_R = 5.0 CORNER_R = 5.0
@@ -179,10 +210,10 @@ def board_poly(inset=INSET, seg=6):
""" """
r = CORNER_R - inset r = CORNER_R - inset
pts = [] pts = []
for cx, cy, a0 in ((BW - CORNER_R, CORNER_R, -90.0), for cx, cy, a0 in ((BX0 + BW - CORNER_R, BY0 + CORNER_R, -90.0),
(BW - CORNER_R, BH - CORNER_R, 0.0), (BX0 + BW - CORNER_R, BY0 + BH - CORNER_R, 0.0),
(CORNER_R, BH - CORNER_R, 90.0), (BX0 + CORNER_R, BY0 + BH - CORNER_R, 90.0),
(CORNER_R, CORNER_R, 180.0)): (BX0 + CORNER_R, BY0 + CORNER_R, 180.0)):
for k in range(seg + 1): for k in range(seg + 1):
a = math.radians(a0 + 90.0 * k / seg) a = math.radians(a0 + 90.0 * k / seg)
pts.append((cx + r * math.cos(a), cy + r * math.sin(a))) pts.append((cx + r * math.cos(a), cy + r * math.sin(a)))
@@ -201,12 +232,18 @@ REGIONS = [
# 载板 `pcb2_pour.py` 文件头第 4 条)。 # 载板 `pcb2_pour.py` 文件头第 4 条)。
# ⇒ 把中桥并进南西、右通道并进南东、风扇指并进北带,凑成三个 L / T 形多边形, # ⇒ 把中桥并进南西、右通道并进南东、风扇指并进北带,凑成三个 L / T 形多边形,
# 每块各自含 J28.1 / J26.1+J27.1 / J29.1+J30.1,**不依赖扇出就活得下来**。 # 每块各自含 J28.1 / J26.1+J27.1 / J29.1+J30.1,**不依赖扇出就活得下来**。
# ⚠️⚠️ **2026-09-07 FAN-R2 之后上面这句不再成立**:J28/J29/J30 删了,J26/J27 挪到两个上角
# 而且 pin2 已经从 `+12V` 改成开关后的 `FAN_12V_SW`(经 Q7)——
# 三块 +12V 里**一个风扇座的通孔焊盘都不剩了**,现在靠 `pcb_fanout` 打的过孔活着
# (实测三块各填出 1 段,没被当孤岛删)。⇒ **重铺之前必须先扇出**,
# 否则会退回「边框还在、nPoured=0」那个状态。整板重布时把这三块各自含一个本网
# 通孔焊盘的条件重新落实一次,别再依赖扇出的运气。
dict(net='+12V', layer=L_PWR, name='12V-N', poly=[ dict(net='+12V', layer=L_PWR, name='12V-N', poly=[
# 北大块 y 88~124.5 通板 + 风扇指下伸到 y 66.5,**挖掉 U13 那一角**(Kelvin 脚,§10.4) # 北大块 y 88~124.5 通板 + 风扇指下伸到 y 66.5,**挖掉 U13 那一角**(Kelvin 脚,§10.4)
(0.5, 88.0), (96.5, 88.0), (96.5, 66.5), (112.0, 66.5), (112.0, 88.0), (0.5, 88.0), (96.5, 88.0), (96.5, 66.5), (112.0, 66.5), (112.0, 88.0),
(159.5, 88.0), (159.5, 124.5), (0.5, 124.5), (159.5, 88.0), (159.5, 124.5), (0.5, 124.5),
(0.5, 102.4), (12.5, 102.4), (12.5, 95.6), (0.5, 95.6)], (0.5, 102.4), (12.5, 102.4), (12.5, 95.6), (0.5, 95.6)],
note=u'北带(CH2/CH3 的 Cin、U19、U29、J28)+ 风扇指(R730/C738/D2);' note=u'北带(CH2/CH3 的 Cin、U19、U29;FAN-R2 之后还含 Q7 源极 = 风扇 1.34 A 的入口)+ 风扇指(现已空载);'
u'**源 Rs6.2 的焊盘是 (8.93~10.21, 91.77~95.23)** —— U13 那个缺口原来写成 y 93.0~101.5,' u'**源 Rs6.2 的焊盘是 (8.93~10.21, 91.77~95.23)** —— U13 那个缺口原来写成 y 93.0~101.5,'
u'正好把 Rs6.2 的中心咬掉,扇出于是把这颗 8 A 源判成「不在铜面里」一个过孔都没打(2026-09-06 抓到)。' u'正好把 Rs6.2 的中心咬掉,扇出于是把这颗 8 A 源判成「不在铜面里」一个过孔都没打(2026-09-06 抓到)。'
u'缺口收成 y 95.6~102.4,只罩住 U13 十个脚(96.08~101.92)与 C11'), u'缺口收成 y 95.6~102.4,只罩住 U13 十个脚(96.08~101.92)与 C11'),
@@ -225,11 +262,11 @@ REGIONS = [
# 把 +12V 推到 72.6,缝就没了;离 3V3-SE 还有 0.4,够。 # 把 +12V 推到 72.6,缝就没了;离 3V3-SE 还有 0.4,够。
(0.5, 9.0), (72.6, 9.0), (72.6, 88.0), (61.5, 88.0), (61.5, 40.7), (0.5, 9.0), (72.6, 9.0), (72.6, 88.0), (61.5, 88.0), (61.5, 40.7),
(54.0, 40.7), (54.0, 34.0), (0.5, 34.0)], (54.0, 40.7), (54.0, 34.0), (0.5, 34.0)],
note=u'CH1 + CH4 的 Cin、J26/J27 左风扇;中桥穿中间通道下来,喂 3.6 A'), note=u'CH1 + CH4 的 Cin(FAN-R2 之后南边没有风扇了);中桥穿中间通道下来,喂 3.2 A,校核仍按 3.6 A 卡'),
dict(net='+12V', layer=L_PWR, name='12V-SE+RC', poly=[ dict(net='+12V', layer=L_PWR, name='12V-SE+RC', poly=[
# 南东块 + 右通道:L 形。J22 只有两个无网络的壳体通孔在 x=151.9,不挡 x≥153.5 # 南东块 + 右通道:L 形。J22 只有两个无网络的壳体通孔在 x=151.9,不挡 x≥153.5
(116.0, 9.0), (159.5, 9.0), (159.5, 88.0), (153.5, 88.0), (153.5, 34.0), (116.0, 34.0)], (116.0, 9.0), (159.5, 9.0), (159.5, 88.0), (153.5, 88.0), (153.5, 34.0), (116.0, 34.0)],
note=u'CH5 的 Cin、J29/J30 右风扇;右通道接回北带,喂 2.0 A'), note=u'CH5 的 Cin(FAN-R2 之后南边没有风扇了);右通道接回北带,喂 1.6 A,校核仍按 2.0 A 卡'),
# ── +3V3:南部两块,各自从北边的源单独引底层干线(见文件头「绕不过去的结论」)── # ── +3V3:南部两块,各自从北边的源单独引底层干线(见文件头「绕不过去的结论」)──
dict(net='+3V3', layer=L_PWR, name='3V3-SW', poly=rect(0.8, 41.0, 58.5, 58.5), dict(net='+3V3', layer=L_PWR, name='3V3-SW', poly=rect(0.8, 41.0, 58.5, 58.5),
@@ -273,19 +310,36 @@ REGIONS = [
# ⚠️ 顶层和内层挡路的东西不一样(Grid 的表):这一片里 J1/C2/C3 的通孔、 # ⚠️ 顶层和内层挡路的东西不一样(Grid 的表):这一片里 J1/C2/C3 的通孔、
# D1/C4/C5/C6/R1 的 GND 贴片焊盘、以及扇出给它们打的 9 个 GND 过孔 + 短线, # D1/C4/C5/C6/R1 的 GND 贴片焊盘、以及扇出给它们打的 9 个 GND 过孔 + 短线,
# **每一样都要躲**。下面每一条边界后面括号里写的就是它躲的那个东西。 # **每一样都要躲**。下面每一条边界后面括号里写的就是它躲的那个东西。
# ⚠️⚠️ 2026-09-08 第三十四轮**整段重画**:输入首级(J1/F1/Q1/D1 + 栅极卫星)西移了 30 mm
# (J1 换圆形 DC 座并挪到真西板边)。旧的那一版坐标写在 x 11.9~22.5,
# 那一片现在**一个本网焊盘都没有** —— 铺出来是孤岛、会被整块删掉,
# 而断面校核量到的是空气(报 0.00 mm)。这是本文件第二次踩同一条,
# 第一次是第二十三轮(注释还在下面 CUTS 里)。⇒ **摆位一改就要回来重画这三块。**
#
# 新链路是**单向**的,比旧版顺:J1(−18.8) → F1(−12.5) → Q1(−6) → D1(1) → 东北去 C2/C3/Rs6。
# 旧版是 J1 一路向东到 Q1、再折回西边找 Rs6,多绕了一圈。
dict(net=VIN_RAW, layer=L_TOP, name='VINRAW-TOP', dict(net=VIN_RAW, layer=L_TOP, name='VINRAW-TOP',
poly=[(11.9, 65.4), (19.6, 65.4), (19.6, 73.08), (18.58, 73.08), poly=[(-17.0, 70.7), (-11.5, 70.7), (-11.5, 73.80),
(18.58, 73.78), (15.0, 73.78), (15.0, 73.0), (11.9, 73.0)], (-13.65, 73.80), (-13.65, 76.5), (-17.0, 76.5)],
note=u'J1.2(13.5,69.5) → F1.1(17.5,73.06)。F1 竖着放之后这一段是一片 7.7×7.7 的大铜;' note=u'J1.1(−15.80,74.35,中心正极,通孔盘 1.6×3.8) → F1.1(−12.50,73.06)。'
u'西北那个阶梯是躲 J1.1 的 GND 通孔(Φ2.5, 净距后 r=1.45),' u'南边 70.7 躲 J1.3 的 GND 通孔盘(顶 70.450);'
u'东边 19.6 躲 Q1.4 栅极脚(20.215)与 DZ1.1(19.890),' u'东边 −11.5 刚好盖住 F1.1(东 −11.637),离 Q1 源极三脚(西 −9.786) 还有 1.7;'
u'北边两段台阶各给 VINFUSED 让 0.2'), u'x −13.65 那级台阶是躲 F1.2(西 −13.364, 南 75.350) —— 台阶以东只铺到 73.80,'
u'盖住 F1.1(顶 73.651) 并给 VIN_FUSED 让 0.30。'
u'**载流断面在 J1.1 与 F1.1 之间**(x≈−14.2),那里是满高 5.8 mm;'
u'台阶以东那 3.10 mm 不走穿过电流,F1.1 就是终点。'
u'⚠️ 两块的**竖直边**也要拉开:VINRAW 东界 −11.5 与 VINFUSED 的台阶 −11.15 '
u'差 0.35 —— 第一版这两条边只隔 0.20(正好压在下限上),是校核报出来的'),
dict(net=VIN_FUSED, layer=L_TOP, name='VINFUSED-TOP', dict(net=VIN_FUSED, layer=L_TOP, name='VINFUSED-TOP',
poly=[(16.4, 74.0), (18.8, 74.0), (18.8, 73.3), (22.5, 73.3), poly=[(-13.4, 74.10), (-11.15, 74.10), (-11.15, 73.3),
(22.5, 78.5), (16.4, 78.5)], (-7.5, 73.3), (-7.5, 77.8), (-13.4, 77.8)],
note=u'F1.2(17.5,75.95) → Q1.1~3(源极三脚, x 20.2~22.2)。' note=u'F1.2(−12.50,75.95) → Q1.1~3(源极三脚, x −9.786~−7.751)。'
u'南边两段:西半 74.0 躲 F1.1(顶 73.651),东半 73.3 躲 Q1.4(顶 72.882) ' u'南边两段:西半 74.10 同时躲 F1.1(顶 73.651) 和 VINRAW 的台阶(顶 73.80,让 0.30),'
u'并盖住 Q1.3(底 73.578);北边 78.5 给 VIN_SW 西行干道让 0.35'), u'东半 73.3 躲 Q1.4 栅极脚(顶 72.882) 并盖住 Q1.3(底 73.578);'
u'东边 −7.5 离 Q1 漏极四脚(西 −4.250) 还有 3.25。'
u'断面在 F1.2 与源极之间(x≈−10.7)= 4.5 mm。'
u'⚠️ DZ1.1/C1.1 这两个栅极钳位件同属本网但在 y≈68,**不进这块铜** ——'
u'µA 级,交给 pcb1_route_sig 拉一条细线'),
# ⚠ `VIN_SW` 必须是**一个铺铜对象**,不能拆成几块靠互相重叠连起来。 # ⚠ `VIN_SW` 必须是**一个铺铜对象**,不能拆成几块靠互相重叠连起来。
# 同层同网两块铜重叠时,低优先级那块会被高优先级那块裁掉 —— 裁的时候 # 同层同网两块铜重叠时,低优先级那块会被高优先级那块裁掉 —— 裁的时候
@@ -294,16 +348,17 @@ REGIONS = [
# ⇒ 宁可手写 18 个点。下面的 `rects` 是这 18 个点该等于的矩形并集, # ⇒ 宁可手写 18 个点。下面的 `rects` 是这 18 个点该等于的矩形并集,
# `report()` 里逐格对照,写错一个点当场就报(check_poly_rects)。 # `report()` 里逐格对照,写错一个点当场就报(check_poly_rects)。
dict(net=VIN_SW, layer=L_TOP, name='VINSW-TOP', dict(net=VIN_SW, layer=L_TOP, name='VINSW-TOP',
poly=[(25.6, 72.1), (29.68, 72.1), (29.68, 73.45), (32.3, 73.45), poly=[(-4.45, 72.10), (-0.30, 72.10), (-0.30, 73.40), (2.40, 73.40),
(32.3, 78.85), (30.47, 78.85), (30.47, 86.47), (28.6, 86.47), (2.40, 78.85), (30.46, 78.85), (30.46, 86.47), (28.6, 86.47),
(28.6, 83.49), (16.72, 83.49), (16.72, 85.5), (12.5, 85.5), (28.6, 83.49), (16.72, 83.49), (16.72, 85.5), (12.5, 85.5),
(12.5, 83.49), (6.65, 83.49), (6.65, 87.0), (5.3, 87.0), (12.5, 83.49), (6.65, 83.49), (6.65, 87.0), (5.3, 87.0),
(5.3, 95.25), (1.5, 95.25), (1.5, 78.85), (25.6, 78.85)], (5.3, 95.25), (1.5, 95.25), (1.5, 83.49), (-4.45, 83.49)],
rects=[(25.6, 72.1, 29.68, 78.85), # Q1 漏极四脚(南边 72.1 躲 R1 的 GND 过孔 26.675,71.5) rects=[(-4.45, 72.10, -0.30, 78.85), # Q1 漏极四脚(x −4.250~−2.215)。南边 72.10 躲 R1 两脚(顶 71.772),留 0.328
(25.6, 73.45, 32.3, 78.85), # D1.1 TVS(南边 73.45 躲 D1.2 的 GND 盘顶 73.163 与它 6 个扇出孔) (-0.30, 73.40, 2.40, 78.85), # D1.1 TVS(−0.096~2.096, 75.838~77.885)。
(28.6, 78.85, 30.47, 86.47), # C4.2/C5.2/C6.2 去耦(东边 30.47 躲 C6.1 的 GND 盘 30.670) # 南边抬到 73.40 是躲 D1.2 的 GND 盘(顶 73.163) 和它正下方 6 个扇出孔
(1.5, 78.85, 28.6, 83.49), # ★ 8 A 西行干道,y 净宽 4.64 mm; (-4.45, 78.85, 28.6, 83.49), # ★ 8 A 干道,y 净宽 4.64 mm。**西端由 1.5 延到 −4.45**:
# 南边 78.85 躲 J1 的机械脚 J1.4(顶 78.401),北边 83.49 躲 C2/C3 的四个通孔(底 83.700) # 旧版西端卡在 1.5 是因为老 J1 的机械脚 J1.4(顶 78.401) 顶着,那颗件已经挪走
(28.6, 78.85, 30.46, 86.47), # C4.2/C5.2/C6.2 去耦(东边 30.46 躲 C6.1 的 GND 盘 30.670)
(12.5, 83.49, 16.72, 85.5), # C3.1 口袋(东边 16.72 躲 C3.2 GND 通孔 16.950) (12.5, 83.49, 16.72, 85.5), # C3.1 口袋(东边 16.72 躲 C3.2 GND 通孔 16.950)
(1.5, 83.49, 6.65, 87.0), # C2.1 口袋(净宽 5.15 mm) (1.5, 83.49, 6.65, 87.0), # C2.1 口袋(净宽 5.15 mm)
(1.5, 87.0, 5.3, 95.25)], # 北柱到 Rs6.1(净宽 **3.8 mm** = 8.5 A) (1.5, 87.0, 5.3, 95.25)], # 北柱到 Rs6.1(净宽 **3.8 mm** = 8.5 A)
@@ -312,8 +367,11 @@ REGIONS = [
# 顶到 y 95.4 —— 离 U13.2 只剩 0.379 mm,`pcb1_escape_audit` 报「被围死」。 # 顶到 y 95.4 —— 离 U13.2 只剩 0.379 mm,`pcb1_escape_audit` 报「被围死」。
# 收窄之后 U13 南边是一整片 x 5.5~14 / y 91.6~96 的空地。 # 收窄之后 U13 南边是一整片 x 5.5~14 / y 91.6~96 的空地。
# (Rs6.2 那两根往西甩的 1 mm 扇出短线也一起没了 —— 见 pcb_fanout.FORCE_ARRAY) # (Rs6.2 那两根往西甩的 1 mm 扇出短线也一起没了 —— 见 pcb_fanout.FORCE_ARRAY)
note=u'**8 A 主干**:Q1 漏极 → 北上 → 西行干道 → C3.1/C2.1 → 北柱 → Rs6.1,一路无过孔;' note=u'**8 A 主干**:Q1 漏极(−3.2) → 东经 D1 → 北上 → **东行**干道 → C2.1/C3.1 → 北柱 → Rs6.1,'
u'东边挂 D1(TVS) 与 C4/C5/C6 去耦。干道比 v10 北提了 2.8 mm —— XT30 的机械脚占到 y 78.4'), u'一路无过孔;东端挂 C4/C5/C6 去耦。'
u'⚠️ 第三十四轮之后干道是**东行**不是西行 —— 源头 Q1 从 x 24 挪到了 x −6,'
u'而 C2/C3/Rs6/C4~C6 一颗没动,于是电流方向整个翻过来了。'
u'旧注释「干道比 v10 北提 2.8 mm 躲 XT30 机械脚」已作废:那颗 XT30 已经不在这块板上'),
] ]
# ── 断面校核:每一条都是「这里必须能过多少安」────────────────────────── # ── 断面校核:每一条都是「这里必须能过多少安」──────────────────────────
@@ -332,7 +390,9 @@ CUTS = [
(u'南西块 → CH1', '+12V', L_PWR, 'x', 33.0, (9.0, 34.0), 2.0), (u'南西块 → CH1', '+12V', L_PWR, 'x', 33.0, (9.0, 34.0), 2.0),
(u'南西块 → CH4+CH1', '+12V', L_PWR, 'x', 61.0, (9.0, 34.0), 3.6), (u'南西块 → CH4+CH1', '+12V', L_PWR, 'x', 61.0, (9.0, 34.0), 3.6),
(u'南东块 → CH5', '+12V', L_PWR, 'x', 140.0, (9.0, 34.0), 1.6), (u'南东块 → CH5', '+12V', L_PWR, 'x', 140.0, (9.0, 34.0), 1.6),
(u'风扇指 @接北带', '+12V', L_PWR, 'y', 87.0, (94.0, 114.0), 1.0), # FAN-R2 之后风扇指下面已经没有 +12V 负载(风扇改吃开关后的 FAN_12V_SW)。
# 这一条留着当**几何回归**:北带往南这一指还在,断面别被后来的布线啃掉。
(u'风扇指 @接北带(现空载)', '+12V', L_PWR, 'y', 87.0, (94.0, 114.0), 1.0),
(u'北大块 @Rs6 东出', '+12V', L_PWR, 'x', 20.0, (86.0, 124.5), 8.0), (u'北大块 @Rs6 东出', '+12V', L_PWR, 'x', 20.0, (86.0, 124.5), 8.0),
(u'3V3 南西块', '+3V3', L_PWR, 'x', 33.0, (41.0, 58.5), 0.5), (u'3V3 南西块', '+3V3', L_PWR, 'x', 33.0, (41.0, 58.5), 0.5),
(u'3V3 南东块 @控制块', '+3V3', L_PWR, 'x', 95.0, (9.0, 58.5), 0.5), (u'3V3 南东块 @控制块', '+3V3', L_PWR, 'x', 95.0, (9.0, 58.5), 0.5),
@@ -341,10 +401,14 @@ CUTS = [
# 8 A 输入链(顶层 1 oz)。J1 → F1 → Q1 → D1/C4~C6 → C3/C2 → Rs6,分叉之前全程 8 A。 # 8 A 输入链(顶层 1 oz)。J1 → F1 → Q1 → D1/C4~C6 → C3/C2 → Rs6,分叉之前全程 8 A。
# ⚠ 这一排坐标跟着摆位走,**摆位一改就要重定**:2026-09-06 第二十三轮把 # ⚠ 这一排坐标跟着摆位走,**摆位一改就要重定**:2026-09-06 第二十三轮把
# F1 竖过来、D1 与 C4/C5/C6 挪到东列之后,旧的 `x=10` 那一刀量到的是空气(报 0.00 mm)。 # F1 竖过来、D1 与 C4/C5/C6 挪到东列之后,旧的 `x=10` 那一刀量到的是空气(报 0.00 mm)。
(u'RAW J1→F1 颈', VIN_RAW, L_TOP, 'y', 73.4, (14.0, 20.0), 8.0), # 2026-09-08:输入首级西移 30 mm,下面前四条**全部重定**。
(u'RAW J1.2 东出', VIN_RAW, L_TOP, 'x', 15.5, (64.0, 76.0), 8.0), # ⚠️ 断面要卡在「电流真正流过的那一段」,不是随便找个地方量:
(u'FUSED F1.2→Q1 源极', VIN_FUSED, L_TOP, 'x', 19.5, (72.0, 80.0), 8.0), # VIN_RAW 的载流段是 J1.1 与 F1.1 **之间**(x≈−14.2);台阶以东那 3.15 mm
(u'SW Q1 漏极北出', VIN_SW, L_TOP, 'y', 77.5, (24.0, 34.0), 8.0), # 是 F1.1 的落脚区、不走穿过电流,在那里量会得到一个偏严且没意义的数。
(u'RAW J1.1→F1.1', VIN_RAW, L_TOP, 'x', -14.2, (68.0, 79.0), 8.0),
(u'FUSED F1.2→Q1 源极', VIN_FUSED, L_TOP, 'x', -10.7, (70.0, 80.0), 8.0),
(u'SW Q1 漏极东出', VIN_SW, L_TOP, 'x', -1.5, (69.0, 81.0), 8.0),
(u'SW 过 D1 北上', VIN_SW, L_TOP, 'x', 2.0, (70.0, 86.0), 8.0),
(u'SW 干道 @x24 东端', VIN_SW, L_TOP, 'x', 24.0, (74.0, 86.0), 8.0), (u'SW 干道 @x24 东端', VIN_SW, L_TOP, 'x', 24.0, (74.0, 86.0), 8.0),
(u'SW 干道 @x12', VIN_SW, L_TOP, 'x', 12.0, (74.0, 86.0), 8.0), (u'SW 干道 @x12', VIN_SW, L_TOP, 'x', 12.0, (74.0, 86.0), 8.0),
(u'SW 干道拐北 @y84', VIN_SW, L_TOP, 'y', 84.0, (0.5, 12.0), 8.0), (u'SW 干道拐北 @y84', VIN_SW, L_TOP, 'y', 84.0, (0.5, 12.0), 8.0),
@@ -373,7 +437,6 @@ ROUTED_PADS = {
'U12.9': u'Kelvin(IN−):单独引线到 Rs501.1 的焊盘,不进 VO5_P 小岛', 'U12.9': u'Kelvin(IN−):单独引线到 Rs501.1 的焊盘,不进 VO5_P 小岛',
'C11.1': u'陪 U13 一起挖出去,走顶层到 L2 输出', 'C11.1': u'陪 U13 一起挖出去,走顶层到 L2 输出',
'U13.6': u'INA226 的 VS+,陪 U13 一起挖出去(C11 就在旁边)', 'U13.6': u'INA226 的 VS+,陪 U13 一起挖出去(C11 就在旁边)',
'C739.1': u'孤零零落在风扇指里,走 11 mm 顶层线到 U33.10',
# U23 西侧那两颗(v11 控制块退火重排之后落到 x 71.9~72.4): # U23 西侧那两颗(v11 控制块退火重排之后落到 x 71.9~72.4):
# 那一条 x 是 `12V-SW+BR`(东界 72.6) 与 `3V3-SE`(西界 73.0) 中间的 0.4 mm 缝, # 那一条 x 是 `12V-SW+BR`(东界 72.6) 与 `3V3-SE`(西界 73.0) 中间的 0.4 mm 缝,
# 两块内层平面谁都够不着。**贴片焊盘对内层透明,所以不是错误**,走顶层短线即可: # 两块内层平面谁都够不着。**贴片焊盘对内层透明,所以不是错误**,走顶层短线即可:
@@ -385,12 +448,24 @@ ROUTED_PADS = {
u'不能从大铜面上随便找个点接', u'不能从大铜面上随便找个点接',
'DZ1.1': u'Q1 栅极钳位齐纳的上端,µA 级;夹在 F1.1 与 Q1.4 之间只有 3.1 mm,走细线', 'DZ1.1': u'Q1 栅极钳位齐纳的上端,µA 级;夹在 F1.1 与 Q1.4 之间只有 3.1 mm,走细线',
'C1.1': u'Q1 栅极 RC 的上端,µA 级;同 DZ1.1', 'C1.1': u'Q1 栅极 RC 的上端,µA 级;同 DZ1.1',
# 2026-09-08 第三十四轮:两路 TACH 接收跟着风扇座搬到了东西**板边**
# (R753 在 x −25.6、R757 在 x 181,都在 y≈100),离 `3V3-SW`/`3V3-SE` 两块内层
# 平面各有 80~100 mm。**不要为这两颗把平面拉过去** —— 10 kΩ 上拉到 3.3 V
# 只有 **0.33 mA**,外层 0.2 mm 线绰绰有余,而拉一条 100 mm 的内层电源面
# 会把 Inner2 的分割搅乱。它们是「接收电路在信号源头整形」那条设计决定的代价。
'R753.2': u'进风 TACH 的 3.3V 上拉(10k, 0.33 mA),随风扇座在西板边;走顶层细线',
'R757.2': u'排风 TACH 的 3.3V 上拉(10k, 0.33 mA),随风扇座在东板边;走顶层细线',
} }
# 整块「有意不进平面」的区域(位号前缀 → 说明) # 整块「有意不进平面」的区域(位号前缀 → 说明)
ROUTED_BLOCKS = [ ROUTED_BLOCKS = [
((14.0, 100.0, 30.0, 124.0), u'北左辅助 Buck 那一窝:12V 与 3V3 在 16×24 mm 里交错,整窝走顶层'), ((14.0, 100.0, 30.0, 124.0), u'北左辅助 Buck 那一窝:12V 与 3V3 在 16×24 mm 里交错,整窝走顶层'),
((36.0, 70.0, 58.0, 84.0), u'CH2 的 +3V3(5 个,50 mA):沿 y≈84 走顶层到中间那块'), ((36.0, 70.0, 58.0, 84.0), u'CH2 的 +3V3(5 个,50 mA):沿 y≈84 走顶层到中间那块'),
((126.0, 70.0, 146.0, 84.0), u'CH3 的 +3V3(5 个,50 mA):同上'), ((126.0, 70.0, 146.0, 84.0), u'CH3 的 +3V3(5 个,50 mA):同上'),
# `R518.1`(CH5 的 +3V3):2026-09-06 第二十六轮起不再落在 Inner2 的 +3V3 上 ——
# 它本来就是「短线 + 扇出孔」那一类,只是以前**碰巧**还压着平面所以没被点名。
# 实测通路:R518.1 → 顶层 0.66 mm → +3V3 扇出孔 (134.575, 51.000) → 平面,
# `pcb1_netlist` 里 +3V3 整网连通(不在「还差」名单里)。框只罩这一颗。
((135.0, 50.4, 136.0, 51.6), u'CH5 的 R518.1:顶层 0.66 mm 短线到 (134.575,51.000) 的 +3V3 扇出孔'),
] ]
@@ -531,6 +606,16 @@ def inside(poly, x, y):
# ══════════════════ 栅格化 + 校核 ══════════════════ # ══════════════════ 栅格化 + 校核 ══════════════════
G = 0.25 # 栅格(mm)。0.25 下 45 mm 的通道是 180 格,够细 G = 0.25 # 栅格(mm)。0.25 下 45 mm 的通道是 180 格,够细
#: 浮铜检查的**最小面积**(mm²)。小于它的当栅格碎屑放过,只打印不判红。
#:
#: ⚠️ 这不是"把检查关小声点",是给它一个说得出理由的下限:布线一多,
#: 过孔的反焊盘之间必然夹出 0.25×0.25(**正好一个栅格**)这种碎屑 ——
#: 2026-09-06 第二十五轮实测两粒:Inner1 (68.9,74.6) 0.06 mm²、Inner2 (138.5,75.2) 0.19 mm²,
#: 都比一个过孔盘还小,电气上没有意义,而且嘉立创铺铜自己也会按死铜阈值清掉。
#: 门槛定在 1.0 mm²(1×1 mm):这一轮真正抓到的两条 —— 南沿那条 **1312.8 mm²** 的
#: GND 底填、`3V3-SW` 里那块 **7.2 mm²** 的孤岛 —— 都远在门槛之上,一条都不会漏。
MIN_FLOAT_AREA = 1.0
class Grid(object): class Grid(object):
u"""某一层上某个网络的铜:先按区域填,再挖异网的反焊盘 / 过孔 / 走线。 u"""某一层上某个网络的铜:先按区域填,再挖异网的反焊盘 / 过孔 / 走线。
@@ -554,11 +639,11 @@ class Grid(object):
for r in regions: for r in regions:
xs = [p[0] for p in r['poly']] xs = [p[0] for p in r['poly']]
ys = [p[1] for p in r['poly']] ys = [p[1] for p in r['poly']]
i0, i1 = int(min(xs) / G), int(max(xs) / G) + 1 i0, i1 = gi(min(xs)), gi(max(xs)) + 1
j0, j1 = int(min(ys) / G), int(max(ys) / G) + 1 j0, j1 = gj(min(ys)), gj(max(ys)) + 1
for j in range(max(0, j0), min(self.ny, j1)): for j in range(max(0, j0), min(self.ny, j1)):
for i in range(max(0, i0), min(self.nx, i1)): for i in range(max(0, i0), min(self.nx, i1)):
if not self.c[j][i] and inside(r['poly'], (i + .5) * G, (j + .5) * G): if not self.c[j][i] and inside(r['poly'], gx(i), gy(j)):
self.c[j][i] = True self.c[j][i] = True
# 反焊盘:异网焊盘(内层只算通孔,外层连贴片一起算) # 反焊盘:异网焊盘(内层只算通孔,外层连贴片一起算)
self.carved = 0 self.carved = 0
@@ -580,21 +665,21 @@ class Grid(object):
# 天线禁铺(全层) # 天线禁铺(全层)
for j in range(self.ny): for j in range(self.ny):
for i in range(self.nx): for i in range(self.nx):
if self.c[j][i] and ANT[0] <= (i + .5) * G <= ANT[2] \ if self.c[j][i] and ANT[0] <= gx(i) <= ANT[2] \
and ANT[1] <= (j + .5) * G <= ANT[3]: and ANT[1] <= gy(j) <= ANT[3]:
self.c[j][i] = False self.c[j][i] = False
self.label() self.label()
def _carve(self, x0, y0, x1, y1): def _carve(self, x0, y0, x1, y1):
u"""挖掉一个矩形(已经把净距算进去了)。返回有没有真挖到东西。""" u"""挖掉一个矩形(已经把净距算进去了)。返回有没有真挖到东西。"""
hit = False hit = False
for j in range(max(0, int(y0 / G)), min(self.ny, int(y1 / G) + 1)): for j in range(max(0, gj(y0)), min(self.ny, gj(y1) + 1)):
yc = (j + .5) * G yc = gy(j)
if not (y0 <= yc <= y1): if not (y0 <= yc <= y1):
continue continue
row = self.c[j] row = self.c[j]
for i in range(max(0, int(x0 / G)), min(self.nx, int(x1 / G) + 1)): for i in range(max(0, gi(x0)), min(self.nx, gi(x1) + 1)):
if row[i] and x0 <= (i + .5) * G <= x1: if row[i] and x0 <= gx(i) <= x1:
row[i] = False row[i] = False
hit = True hit = True
return hit return hit
@@ -628,15 +713,15 @@ class Grid(object):
u"""沿一条线量本网铜的总宽与最长连续段(mm)。""" u"""沿一条线量本网铜的总宽与最长连续段(mm)。"""
tot = run_max = run = 0 tot = run_max = run = 0
if axis == 'y': if axis == 'y':
j = int(coord / G) j = gj(coord)
for i in range(int(span[0] / G), int(span[1] / G) + 1): for i in range(gi(span[0]), gi(span[1]) + 1):
v = 0 <= i < self.nx and 0 <= j < self.ny and self.c[j][i] v = 0 <= i < self.nx and 0 <= j < self.ny and self.c[j][i]
tot += 1 if v else 0 tot += 1 if v else 0
run = run + 1 if v else 0 run = run + 1 if v else 0
run_max = max(run_max, run) run_max = max(run_max, run)
else: else:
i = int(coord / G) i = gi(coord)
for j in range(int(span[0] / G), int(span[1] / G) + 1): for j in range(gj(span[0]), gj(span[1]) + 1):
v = 0 <= i < self.nx and 0 <= j < self.ny and self.c[j][i] v = 0 <= i < self.nx and 0 <= j < self.ny and self.c[j][i]
tot += 1 if v else 0 tot += 1 if v else 0
run = run + 1 if v else 0 run = run + 1 if v else 0
@@ -645,8 +730,8 @@ class Grid(object):
def hit(self, x, y, hx, hy): def hit(self, x, y, hx, hy):
u"""这个焊盘有没有落在本网主连通域上(焊盘框内任一格)。""" u"""这个焊盘有没有落在本网主连通域上(焊盘框内任一格)。"""
i0, i1 = int((x - hx) / G), int((x + hx) / G) + 1 i0, i1 = gi(x - hx), gi(x + hx) + 1
j0, j1 = int((y - hy) / G), int((y + hy) / G) + 1 j0, j1 = gj(y - hy), gj(y + hy) + 1
for j in range(max(0, j0), min(self.ny, j1 + 1)): for j in range(max(0, j0), min(self.ny, j1 + 1)):
for i in range(max(0, i0), min(self.nx, i1 + 1)): for i in range(max(0, i0), min(self.nx, i1 + 1)):
if self.lab[j][i]: if self.lab[j][i]:
@@ -1004,7 +1089,9 @@ def ascii_map_top(pads, obs=()):
看不出「是被 GND 扇出孔啃的还是被自己的边界切的」。 看不出「是被 GND 扇出孔啃的还是被自己的边界切的」。
""" """
g = 0.5 g = 0.5
x0, x1, y0, y1 = 1.0, 31.0, 65.0, 95.0 # 2026-09-08:窗口跟着输入首级西移 —— 旧值 (1,31,65,95) 现在只剩右半条链。
# 链路实际占 x −17~30.5 / y 70~95.3,取 0.5 mm/格 一行 96 字,够宽。
x0, x1, y0, y1 = -18.0, 32.0, 69.0, 96.0
sym = [(VIN_RAW, 'R'), (VIN_FUSED, 'F'), (VIN_SW, 'S')] sym = [(VIN_RAW, 'R'), (VIN_FUSED, 'F'), (VIN_SW, 'S')]
grids = [(s, Grid(net, L_TOP, grids = [(s, Grid(net, L_TOP,
[r for r in REGIONS if r['layer'] == L_TOP and r['net'] == net], [r for r in REGIONS if r['layer'] == L_TOP and r['net'] == net],
@@ -1029,7 +1116,7 @@ def ascii_map_top(pads, obs=()):
x = x0 + (i + .5) * g x = x0 + (i + .5) * g
ch = '.' ch = '.'
for s, gr in grids: for s, gr in grids:
ii, jj = int(x / G), int(y / G) ii, jj = gi(x), gj(y)
if 0 <= ii < gr.nx and 0 <= jj < gr.ny and gr.c[jj][ii]: if 0 <= ii < gr.nx and 0 <= jj < gr.ny and gr.c[jj][ii]:
ch = s ch = s
break break
@@ -1058,10 +1145,10 @@ def ascii_map(pads, obs=()):
for j in range(ny - 1, -1, -1): for j in range(ny - 1, -1, -1):
row = '' row = ''
for i in range(nx): for i in range(nx):
x, y = (i + .5) * g, (j + .5) * g x, y = BX0 + (i + .5) * g, BY0 + (j + .5) * g
ch = ' ' ch = ' '
for net, gr in grids: for net, gr in grids:
ii, jj = int(x / G), int(y / G) ii, jj = gi(x), gj(y)
if 0 <= ii < gr.nx and 0 <= jj < gr.ny and gr.c[jj][ii]: if 0 <= ii < gr.nx and 0 <= jj < gr.ny and gr.c[jj][ii]:
ch = sym[net] ch = sym[net]
break break
@@ -1214,10 +1301,10 @@ def _cells_from_rings(rings):
xs_ = pts[0::2] xs_ = pts[0::2]
ys_ = pts[1::2] ys_ = pts[1::2]
n = len(xs_) n = len(xs_)
j0 = max(0, int(min(ys_) / G)) j0 = max(0, gj(min(ys_)))
j1 = min(ny - 1, int(max(ys_) / G) + 1) j1 = min(ny - 1, gj(max(ys_)) + 1)
for j in range(j0, j1 + 1): for j in range(j0, j1 + 1):
y = (j + 0.5) * G y = gy(j)
cr = [] cr = []
for k in range(n): for k in range(n):
y1, y2 = ys_[k], ys_[(k + 1) % n] y1, y2 = ys_[k], ys_[(k + 1) % n]
@@ -1227,10 +1314,10 @@ def _cells_from_rings(rings):
cr.sort() cr.sort()
row = acc[j] row = acc[j]
for a in range(0, len(cr) - 1, 2): for a in range(0, len(cr) - 1, 2):
ia = max(0, int(cr[a] / G)) ia = max(0, gi(cr[a]))
ib = min(nx - 1, int(cr[a + 1] / G)) ib = min(nx - 1, gi(cr[a + 1]))
for i in range(ia, ib + 1): for i in range(ia, ib + 1):
xc = (i + 0.5) * G xc = gx(i)
if cr[a] <= xc <= cr[a + 1]: if cr[a] <= xc <= cr[a + 1]:
row[i] ^= 1 row[i] ^= 1
return [[bool(v) for v in r] for r in acc] return [[bool(v) for v in r] for r in acc]
@@ -1317,16 +1404,22 @@ def verify(refresh=True):
g = G_[k] g = G_[k]
live = set() live = set()
for x, y in anchors.get(k, ()): for x, y in anchors.get(k, ()):
i, j = int(x / G), int(y / G) i, j = gi(x), gj(y)
if 0 <= i < g.nx and 0 <= j < g.ny and g.lab[j][i]: if 0 <= i < g.nx and 0 <= j < g.ny and g.lab[j][i]:
live.add(g.lab[j][i]) live.add(g.lab[j][i])
dead = [(cid, n) for cid, n in g.sizes.items() if cid not in live] dead = [(cid, n) for cid, n in g.sizes.items() if cid not in live]
big = [(c, n) for c, n in dead if n * G * G >= MIN_FLOAT_AREA]
dust = [(c, n) for c, n in dead if n * G * G < MIN_FLOAT_AREA]
lay = {1: 'Top', 2: 'Bottom', 15: 'Inner1', 16: 'Inner2'}[k[1]] lay = {1: 'Top', 2: 'Bottom', 15: 'Inner1', 16: 'Inner2'}[k[1]]
chk(not dead, if dust:
print(u' (%s %s 另有 %d 粒 < %.1f mm² 的碎屑,按 MIN_FLOAT_AREA 放过:%s mm²)'
% (lay, k[0], len(dust), MIN_FLOAT_AREA,
' '.join('%.2f' % (n * G * G) for _c, n in sorted(dust, key=lambda t: -t[1])[:6])))
chk(not big,
u'%s %s 的 %d 块碎片各自都有本网焊盘/过孔落脚' % (lay, k[0], g.ncomp), u'%s %s 的 %d 块碎片各自都有本网焊盘/过孔落脚' % (lay, k[0], g.ncomp),
u'%s %s 有 %d 块**浮铜**(没有任何本网落脚点):%s mm²' u'%s %s 有 %d 块**浮铜**(没有任何本网落脚点):%s mm²'
% (lay, k[0], len(dead), % (lay, k[0], len(big),
' '.join('%.1f' % (n * G * G) for _c, n in sorted(dead, key=lambda t: -t[1])[:6]))) ' '.join('%.1f' % (n * G * G) for _c, n in sorted(big, key=lambda t: -t[1])[:6])))
grid_checks(G_, pads, chk) grid_checks(G_, pads, chk)
return bad return bad
+12 -1
查看文件
@@ -82,6 +82,7 @@ TRACK_RULES = {
'PWR4A': {'strokeWidthDefault': 1.5, 'strokeWidthMin': 0.3, 'strokeWidthMax': 6.0}, 'PWR4A': {'strokeWidthDefault': 1.5, 'strokeWidthMin': 0.3, 'strokeWidthMax': 6.0},
'PWR5V': {'strokeWidthDefault': 1.2, 'strokeWidthMin': 0.3, 'strokeWidthMax': 6.0}, 'PWR5V': {'strokeWidthDefault': 1.2, 'strokeWidthMin': 0.3, 'strokeWidthMax': 6.0},
'PWR1A': {'strokeWidthDefault': 0.6, 'strokeWidthMin': 0.15, 'strokeWidthMax': 3.0}, 'PWR1A': {'strokeWidthDefault': 0.6, 'strokeWidthMin': 0.15, 'strokeWidthMax': 3.0},
'FAN12V': {'strokeWidthDefault': 0.8, 'strokeWidthMin': 0.3, 'strokeWidthMax': 3.0},
} }
# ── 有名字的网络 → 线宽规则 ──────────────────────────────────────────── # ── 有名字的网络 → 线宽规则 ────────────────────────────────────────────
@@ -93,6 +94,13 @@ NET_TRACK['+12V'] = 'PWR12V'
NET_TRACK['+5V'] = 'PWR5V' NET_TRACK['+5V'] = 'PWR5V'
NET_TRACK['+3V3'] = 'PWR1A' NET_TRACK['+3V3'] = 'PWR1A'
NET_TRACK['SW3V3'] = 'PWR1A' # U19 SW → L2,0.5 A NET_TRACK['SW3V3'] = 'PWR1A' # U19 SW → L2,0.5 A
# 2026-09-08 第三十三轮:风扇高边开关的下游母线。Q7 居中、东西两支**各只走 0.67 A**
# (FAN-R2 两把风扇 2 x 0.67 A),0.8 mm 外层 1 oz 下西支 125 mm -> 51 mV、东支 95 mm -> 39 mV,
# 在 `FAN_CIRCUIT` 的 0.2 V 预算之内。**默认信号线 0.20 mm 扛不住** ——
# 0.20 mm 下同样两支是 204 / 155 mV,单是西支就吃掉全部预算。
# ⚠️ 早年注释里担心的 0.26 V 是「一条 196 mm 走全电流 1.33 A」的算法,
# J26/J27 分到东西两板边、Q7 居中之后不成立。
NET_TRACK['FAN_12V_SW'] = 'FAN12V'
# ── 自动命名网 → 线宽规则(按「位号.引脚」反查,见文件头第 4 条)────── # ── 自动命名网 → 线宽规则(按「位号.引脚」反查,见文件头第 4 条)──────
# 值是 (探针, 校核端):两端查出来的必须是同一个网,否则说明位号/引脚写错了。 # 值是 (探针, 校核端):两端查出来的必须是同一个网,否则说明位号/引脚写错了。
@@ -105,7 +113,10 @@ for _n, _u in zip(range(1, 6), (1, 2, 3, 4, 5)):
# TPS25940(WQFN-20) 的 OUT 是 pin 4~8,Rs*01 的上游脚是 pin 2。 # TPS25940(WQFN-20) 的 OUT 是 pin 4~8,Rs*01 的上游脚是 pin 2。
AUTO_TRACK['VO%d_F' % _n] = ('PWR4A', ('Rs%d' % _c, '2'), ('U%d' % (13 + _n), '5')) AUTO_TRACK['VO%d_F' % _n] = ('PWR4A', ('Rs%d' % _c, '2'), ('U%d' % (13 + _n), '5'))
# 8 A 输入链(§10.2b):J1 → F1 → Q1 → C2/C3/D1 → Rs6。分叉之前全程 8 A。 # 8 A 输入链(§10.2b):J1 → F1 → Q1 → C2/C3/D1 → Rs6。分叉之前全程 8 A。
AUTO_TRACK['VIN_RAW'] = ('PWR12V', ('J1', '2'), ('F1', '1')) # ⚠️ 2026-09-08 J1 换成圆形 DC 座之后**正极从 pin2 挪到了 pin1**
# (XT30:1=`−` / 2=`+`;DC-005:1=中心正极 / 2、3=两个负极触点)。
# 探针写错时这里是硬失败("J1.2 上是 GND,不是自动命名网"),不会静默放过 —— 这是对的。
AUTO_TRACK['VIN_RAW'] = ('PWR12V', ('J1', '1'), ('F1', '1'))
AUTO_TRACK['VIN_FUSED'] = ('PWR12V', ('F1', '2'), ('Q1', '1')) AUTO_TRACK['VIN_FUSED'] = ('PWR12V', ('F1', '2'), ('Q1', '1'))
AUTO_TRACK['VIN_SW'] = ('PWR12V', ('Rs6', '1'), ('C2', '1')) AUTO_TRACK['VIN_SW'] = ('PWR12V', ('Rs6', '1'), ('C2', '1'))
# 第二路辅助 Buck 的 SW(§9.4,12V→5V,最坏 2.8 A) # 第二路辅助 Buck 的 SW(§9.4,12V→5V,最坏 2.8 A)
文件差异内容过多而无法显示 加载差异
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
+1 -1
查看文件
@@ -27,7 +27,7 @@ HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE) sys.path.insert(0, HERE)
from eda import run from eda import run
PCB = 'f126ffb29c92f9b2' PCB = '942ed1560d81c1e7'
MIL = 39.3700787401575 MIL = 39.3700787401575
COPPER = (1, 15, 16, 2) COPPER = (1, 15, 16, 2)
+1 -1
查看文件
@@ -27,7 +27,7 @@ sys.path.insert(0, HERE)
from eda import run from eda import run
from pcb2_place import SITES, to_board from pcb2_place import SITES, to_board
PCB = 'f126ffb29c92f9b2' PCB = '942ed1560d81c1e7'
MIL = 39.3700787401575 MIL = 39.3700787401575
+7 -5
查看文件
@@ -47,6 +47,8 @@ U5n 朝 Hub 那排的 DP/DM 相距 1.9 mm,车道并排 0.48 到最后 1.4 mm
(转弯规则:向右拐时左边那根变成外侧,连拐两次回到原样)。 (转弯规则:向右拐时左边那根变成外侧,连拐两次回到原样)。
""" """
import math import math
import sys as _s, os as _o; _s.path.insert(0, _o.path.dirname(_o.path.abspath(__file__)))
KSEP = chr(0) # 路径 key 的分隔符,与 pcb2_route_dp.KSEP 同值(不能 import:route_dp --dump 会反过来 import 本模块,循环)
L_TOP, L_BOT = 1, 2 L_TOP, L_BOT = 1, 2
W = 0.28 W = 0.28
@@ -183,10 +185,10 @@ UP = {
# ⚠ 表贴焊盘只到 x=213.04,过孔要离它 0.505 ⇒ 最西只能放到 213.55;出线前 0.65 mm 必须平着走(阵列上方起坡会蹭隔壁脚)。 # ⚠ 表贴焊盘只到 x=213.04,过孔要离它 0.505 ⇒ 最西只能放到 213.55;出线前 0.65 mm 必须平着走(阵列上方起坡会蹭隔壁脚)。
# 另一根的 61.75 横线离 (213.85, 62.25) 那个孔只有 0.165 —— 这是同一对的两根(UP_DP vs UP_DM),按对内 0.15 判。 # 另一根的 61.75 横线离 (213.85, 62.25) 那个孔只有 0.165 —— 这是同一对的两根(UP_DP vs UP_DM),按对内 0.15 判。
J23_TIE = { J23_TIE = {
'UP_DP#2': [(212.392, 62.25, L_BOT, W), (213.85, 62.25), (213.85, 62.25, L_TOP), 'UP_DP' + KSEP + '2': [(212.392, 62.25, L_BOT, W), (213.85, 62.25), (213.85, 62.25, L_TOP),
(213.85, 60.70), (213.85, 60.70, L_BOT), (213.50, 61.25), (213.85, 60.70), (213.85, 60.70, L_BOT), (213.50, 61.25),
(213.20, 61.25), (212.392, 61.25)], (213.20, 61.25), (212.392, 61.25)],
'UP_DM#2': [(212.392, 61.75, L_BOT, W), (213.20, 61.75), (213.50, 61.75), 'UP_DM' + KSEP + '2': [(212.392, 61.75, L_BOT, W), (213.20, 61.75), (213.50, 61.75),
(214.25, 61.30), (214.75, 61.30), (214.75, 61.30, L_TOP), (214.25, 61.30), (214.75, 61.30), (214.75, 61.30, L_TOP),
(214.75, 63.20), (214.75, 63.20, L_BOT), (214.25, 63.20), (214.75, 63.20), (214.75, 63.20, L_BOT), (214.25, 63.20),
(213.50, 62.75), (213.20, 62.75), (212.392, 62.75)], (213.50, 62.75), (213.20, 62.75), (212.392, 62.75)],
@@ -223,12 +225,12 @@ def power_lanes(pads):
if p['ref'] == 'U31' and p['num'] == '2': if p['ref'] == 'U31' and p['num'] == '2':
assert abs(p['x'] - 90.47) < 0.05 and abs(p['y'] - 60.5) < 0.05, u'U31 pin2 挪了,改 V_TRUNK' assert abs(p['x'] - 90.47) < 0.05 and abs(p['y'] - 60.5) < 0.05, u'U31 pin2 挪了,改 V_TRUNK'
return { return {
'+3V3_HUB#p5': [(PAD_X, 66.25), (90.40, 66.25), (V_P5[0], V_P5[1]), '+3V3_HUB' + KSEP + 'p5': [(PAD_X, 66.25), (90.40, 66.25), (V_P5[0], V_P5[1]),
(V_P5[0], V_P5[1], L_TOP), (V_P5W[0], V_P5W[1]), (V_P5[0], V_P5[1], L_TOP), (V_P5W[0], V_P5W[1]),
(V_P5W[0], V_P5W[1], L_BOT), (c747[0], c747[1])], (V_P5W[0], V_P5W[1], L_BOT), (c747[0], c747[1])],
'+3V3_HUB#p10': [(PAD_X, 68.75), (92.95, 68.75), (92.95, 69.55), (V_P10[0], V_P10[1]), '+3V3_HUB' + KSEP + 'p10': [(PAD_X, 68.75), (92.95, 68.75), (92.95, 69.55), (V_P10[0], V_P10[1]),
(c748[0], c748[1])], (c748[0], c748[1])],
'+3V3_HUB#trunk': [(90.30, 61.00), (V_TRUNK[0], V_TRUNK[1]), '+3V3_HUB' + KSEP + 'trunk': [(90.30, 61.00), (V_TRUNK[0], V_TRUNK[1]),
(V_TRUNK[0], V_TRUNK[1], L_TOP), (90.30, 70.40), (V_P5[0], V_P5[1]), (V_TRUNK[0], V_TRUNK[1], L_TOP), (90.30, 70.40), (V_P5[0], V_P5[1]),
(V_P10[0], V_P10[1]), (V_P10[0], V_P10[1], L_BOT), (c748[0], c748[1])], (V_P10[0], V_P10[1]), (V_P10[0], V_P10[1], L_BOT), (c748[0], c748[1])],
} }
+3 -3
查看文件
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
u"""载板 PCB2-CARRIER 的扇出 —— 复用下板 `pcb_fanout.py` 的规划器,只换掉板子相关的常量。 u"""载板 PCB2-CARRIER 的扇出 —— 复用下板 `pcb_fanout.py` 的规划器,只换掉板子相关的常量。
/c/Python314/python .claude/dump_pads.py f126ffb29c92f9b2 pcb2-copper.json # 1. 抓障碍表 /c/Python314/python .claude/dump_pads.py 942ed1560d81c1e7 pcb2-copper.json # 1. 抓障碍表
/c/Python314/python .claude/pcb2_fanout.py # 2. 只规划 /c/Python314/python .claude/pcb2_fanout.py # 2. 只规划
/c/Python314/python .claude/pcb2_fanout.py --push # 3. 下发 /c/Python314/python .claude/pcb2_fanout.py --push # 3. 下发
@@ -47,7 +47,7 @@ F = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(F) _spec.loader.exec_module(F)
# ── 换掉板子相关的常量 ──────────────────────────────────────────────── # ── 换掉板子相关的常量 ────────────────────────────────────────────────
F.PCB = 'f126ffb29c92f9b2' # PCB2-CARRIER F.PCB = '942ed1560d81c1e7' # PCB2-CARRIER
F.SRC = os.path.join(HERE, 'pcb2-copper.json') F.SRC = os.path.join(HERE, 'pcb2-copper.json')
F.BOARD = (0.0, 0.0, 220.0, 135.0) F.BOARD = (0.0, 0.0, 220.0, 135.0)
F.R_CORNER = 5.0 F.R_CORNER = 5.0
@@ -168,7 +168,7 @@ def clean2():
def main(): def main():
if not os.path.exists(F.SRC): if not os.path.exists(F.SRC):
sys.exit(u'先抓障碍表:/c/Python314/python .claude/dump_pads.py ' sys.exit(u'先抓障碍表:/c/Python314/python .claude/dump_pads.py '
u'f126ffb29c92f9b2 pcb2-copper.json') u'942ed1560d81c1e7 pcb2-copper.json')
print(u'载板扇出:板框 %.0f × %.0f,元件面 %s,GND -> layer %d' print(u'载板扇出:板框 %.0f × %.0f,元件面 %s,GND -> layer %d'
% (F.BOARD[2], F.BOARD[3], F.FANOUT_LAYERS, F.TARGET['GND'])) % (F.BOARD[2], F.BOARD[3], F.FANOUT_LAYERS, F.TARGET['GND']))
if SKIP_HDR: if SKIP_HDR:
+22 -3
查看文件
@@ -94,8 +94,27 @@ def clusters(pads, vias, lines):
return False return False
return touch(b, a) return touch(b, a)
for i in range(n): # 2026-09-07:包围盒预筛。原来纯两两比对,第三十轮 GND 件数上去之后跑了 20 多分钟没出来。
for j in range(i + 1, n): # 每件算一个外扩 EPS 的包围盒,先按 x 排序,只比 x 区间相交的对。
def _bb(it):
k, _, o = it
if k == 'line':
r = o['w'] / 2.0
return (min(o['x0'], o['x1']) - r, min(o['y0'], o['y1']) - r,
max(o['x0'], o['x1']) + r, max(o['y0'], o['y1']) + r)
if k == 'via':
return (o['x'] - o['r'], o['y'] - o['r'], o['x'] + o['r'], o['y'] + o['r'])
return (o['x'] - o['hw'], o['y'] - o['hh'], o['x'] + o['hw'], o['y'] + o['hh'])
bbs = [_bb(it) for it in items]
order = sorted(range(n), key=lambda i: bbs[i][0])
for oi, i in enumerate(order):
bi = bbs[i]
for j in order[oi + 1:]:
bj = bbs[j]
if bj[0] > bi[2] + EPS:
break
if bj[1] > bi[3] + EPS or bj[3] < bi[1] - EPS:
continue
if touch(items[i], items[j]): if touch(items[i], items[j]):
a, b = find(i), find(j) a, b = find(i), find(j)
if a != b: if a != b:
@@ -223,7 +242,7 @@ def main():
pad, lay, w, segs, (vx, vy) = best pad, lay, w, segs, (vx, vy) = best
other = R.L_BOT if lay == R.L_TOP else R.L_TOP other = R.L_BOT if lay == R.L_TOP else R.L_TOP
pts = [(segs[0][1], segs[0][2], lay, w)] + [(x1, y1, lay, w) for _l, _a, _b, x1, y1, _w in segs] pts = [(segs[0][1], segs[0][2], lay, w)] + [(x1, y1, lay, w) for _l, _a, _b, x1, y1, _w in segs]
routes[u'%s#patch%d' % (NET, k)] = pts + [(vx, vy, other, w), (vx, vy, other, w)] routes[u'%s%spatch%d' % (NET, R.KSEP, k)] = pts + [(vx, vy, other, w), (vx, vy, other, w)]
for l_, x0, y0, x1, y1, w_ in segs: for l_, x0, y0, x1, y1, w_ in segs:
mine.append((NET, l_, x0, y0, x1, y1, w_)) mine.append((NET, l_, x0, y0, x1, y1, w_))
mvias.append((NET, vx, vy)) mvias.append((NET, vx, vy))
+1 -1
查看文件
@@ -12,7 +12,7 @@ HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE) sys.path.insert(0, HERE)
from eda import run from eda import run
PCB = 'f126ffb29c92f9b2' # PCB2-CARRIER PCB = '942ed1560d81c1e7' # PCB2-CARRIER
OUT = os.path.join(HERE, 'pcb2-parts.json') OUT = os.path.join(HERE, 'pcb2-parts.json')
JS = u""" JS = u"""
+160 -12
查看文件
@@ -79,28 +79,95 @@ HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE) sys.path.insert(0, HERE)
from eda import run from eda import run
PCB = 'f126ffb29c92f9b2' # PCB2-CARRIER PCB = '942ed1560d81c1e7' # PCB2-CARRIER
MIL = 39.3700787401575 MIL = 39.3700787401575
BW, BH = 220.0, 135.0 # 板框 mm BW, BH = 220.0, 135.0 # 板框 mm
CORNER_R = 5.0 CORNER_R = 5.0
# 载板自己的 M3 安装孔(来历见 obsolete/pcb2_place_topside_2026-09-06.py 文件头,位置没动)。 # 载板自己的 M3 安装孔(来历见 obsolete/pcb2_place_topside_2026-09-06.py 文件头,位置没动)。
MH = [(8.0, 8.0), (212.0, 8.0), (7.0, 60.0), (213.0, 45.0), MH = [(8.0, 8.0), (212.0, 8.0), (7.0, 60.0), (213.0, 45.0),
(76.6, 79.5), (148.1, 79.5)] # v3:中跨孔随上排下移 0.6(焊盘顶 82.5 < 83.0) (76.6, 79.5), (148.1, 79.5), # v3:中跨孔随上排下移 0.6(焊盘顶 82.5 < 83.0)
# 2026-09-06 用户指令「两板尺寸取大对齐、四角定位孔对齐」:下板改成同样的 220×135、
# 四角孔与本板重合。底部两角本来就有 (8,8)/(212,8);顶部两角被上排主板(y ≤ 127.66)和
# 尾插槽(site1 x 8.09~27.09,见 SLOTS 注释)夹住,只剩板角 4×4 那一块:
# 孔心 (4,131)/(216,131),焊盘 Ø6 → 离板边 1.0、离主板边 0.34(主板架在 3 mm 立柱上,螺钉头 ≤ 2)、离槽 1.09。
(4.0, 131.0), (216.0, 131.0)]
MH_HOLE, MH_PAD = 3.2, 6.0 MH_HOLE, MH_PAD = 3.2, 6.0
MH_KEEP = 3.5 # 底层元件离 M3 孔中心的最小距离(螺钉头 Ø6) MH_KEEP = 3.5 # 底层元件离 M3 孔中心的最小距离(螺钉头 Ø6)
# ---- 小米8 主板(顶视,板左下角原点,mm)---- # ---- 小米8 主板(顶视,板左下角原点,mm)----
PW, PH = 67.09, 44.66 # 2026-09-06 实测替换(旧值的 7 孔是**从照片认的**,最大错 4.8 mm)。
HOLES = {'H1': (54.2, 42.3), 'H2': (2.6, 38.2), 'H3': (61.9, 33.7), 'H4': (62.1, 29.2), # 新值来源:测量底板(四基准点 Φ6,圆心矩形 160x110)上描轮廓 + 每孔戳一点 -> 扫描 ->
'H5': (2.6, 27.0), 'H6': (4.0, 10.8), 'H7': (42.6, 3.7)} # `pcb2_measure_read.py` 单应反算。**再拿真主板平板扫描件做了一次独立复核**:
HOLE_D = 2.5 # 载板上的定位孔,NPTH # 用这 7 个孔去套扫描件上的孔,**7/7 命中、平均残差 0.38 mm**(`_fit.png`)。
HOLE_KEEP = 2.8 # 底层元件离定位孔中心的最小距离(M2.5 尼龙螺钉头 Ø4.5 + 余量) # ⚠️ 编号整体重排过,别拿旧 H 号对新 H 号。
# ⚠️ 板长 67 = **裸板**。用户描轮廓时 SIM 卡托还插着,卡托在 u 大的那一端伸出约 3.3 mm,
# 所以描出来是 70.3。他早先给的 u 值是拿 66 / 70.3 混着换算的,才有那 3~4 mm 的怪差。
PW, PH = 67.0, 44.0
HOLES = {'H1': (1.4, 9.9), 'H2': (0.7, 27.3), 'H3': (1.7, 38.7), 'H4': (41.9, 1.9),
'H5': (57.4, 41.2), 'H6': (64.6, 28.1), 'H7': (64.8, 32.4)}
HOLE_D = 1.8 # 载板上的定位孔,NPTH。2026-09-06 用户卡尺:**主板孔 Φ2.0**
# (先前口头的 Φ1.5 作废)。Φ2.0 是手机自己的过孔 ⇒ 配 **M1.6 尼龙立柱**
# (M2 螺杆 Φ2.0 塞 Φ2.0 没余量,不选);载板这侧是螺钉过孔,取 M1.6 的通孔 Φ1.8。
# 原来整套按 Φ2.5 / M2.5 做的,是错的。
HOLE_KEEP = 2.2 # 底层元件离定位孔中心的最小距离(M1.6 尼龙螺钉头 Ø3.2 + 余量)
# 2026-09-07:**H5 不打**。J61~J65(EDL 座)离 P*H5 只有 0.92(要 2.2),二选一里「不打 H5」代价为零:
# 每台还有 6 颗螺钉,H5 在 u 大 / v 大那个角(离侧键触点最远的角),那里没有需要压紧的东西。
# 模型里 HOLES 保留 7 个(它是实测数据),只在下发孔 / 螺钉头避让时跳过这里列的。
SKIP_HOLES = {'H5'}
# 侧键触点(§22.3b,已折算成顶视投影)。顶针顺序 = c4_site.py 的 pogo[0..3] # 侧键触点。旧值(§22.3b 那四个点)是从照片认的,**内部排布是错的**,见下。
POGO_XY = [(3.11, 6.91), (2.70, 4.60), (5.41, 6.52), (2.12, 2.46)] #
# ---- 已经问清楚的(可信)----
# 触点是 **4 个 Φ1.35 的圆盘,排成菱形**:相邻 1.87 mm、对角 2.65 mm。
# 来源:中框(配合件)上那四个金色焊盘的平板扫描,边界干净,k-means 分开后直接量。
# ⚠️ **顶针本体直径必须 < 1.87 mm**,常规 Φ2 以上的排不下 —— 选型硬约束。
#
# ---- 位置:**中框螺柱配准**那一版(已被卡尺判定为正确)----
# 2026-09-06:用户卡尺实测 **H1 孔心到四触点菱形中心 = 5 mm**,判死了两条路之争:
# 中框配准版 (3.42, 5.07) -> 离 H1 5.24 mm ✔ 采信
# 红漆配准版 (0.81, 7.08) -> 离 H1 2.88 mm �’ 弃
# 红漆那条当时的论据是「同图内量到最近孔 2.7 mm,不经配准也不吃手性」,**论据是错的**:
# 那个「最近的孔」是环形滤波器几百个候选里的一个,多半是过孔/焊盘误报,不是真孔。
# ⚠️ 教训:环形滤波器在这块板上误报率极高,**任何"离最近的孔多少"都不能直接用**,
# 必须先确认那个候选真的是 7 孔之一。
# 旁证:中框版四个点全在板内(红漆版算出 GND 在板边外 0.5 mm,与用户「GND 离短板边最近」矛盾),
# 而且和旧 §22.3b 的簇心 (3.34, 5.12) 只差 0.1 mm —— 旧数据的**簇心本来就是对的,错的是内部排布**。
# 下面四个坐标直接取中框配准的实测值(不是理想菱形),残差 0.57 mm。
POGO_XY = [(4.72, 5.16), # PWR 电源 u+
(3.42, 6.37), # VUP 音量+ v+
(3.49, 3.74), # VDN 音量- v-
(2.05, 4.99)] # GND u-
POGO_TAG = ['J603 PWR', 'J601 VUP', 'J602 VDN', 'J604 GND'] POGO_TAG = ['J603 PWR', 'J601 VUP', 'J602 VDN', 'J604 GND']
# ---- 2026-09-06 用户改方案:**顶针取消,改成和中框一样的焊盘** ----
# 装配方式跟着改:不再靠顶针弹力,而是**用螺丝把主板压在载板上**,
# 由主板自己的侧键弹片直接压在载板的焊盘上 —— 完全复刻中框的接触方式。
# 好处:顶针那个「本体必须 < 1.87 mm」的硬卡点直接消失(现有 POGO-SMD1P_D1.9 的 Ø1.9
# 在真实 1.78 mm 最小间距下是负净距,正是那 20 条重叠的来源)。
#
# 焊盘定径:四点实测最小间距 **1.78 mm**(不是理想菱形的 1.87)⇒
# 净距 0.20 -> 最大 Ø1.58 · 净距 0.25 -> Ø1.53 · 净距 0.30 -> Ø1.48
# 中框原盘 Ø1.35。取 **Ø1.5**:净距 0.28(好做),容许位置误差 0.50 mm。
POGO_PAD_D = 1.5
# ⚠️ **位置不确定度 ±0.6 mm 已经吃掉全部余量**(Ø1.5 只容许 0.50)。
# 打样前必须把触点位置收到 ±0.3:办法是「**一次装夹**」——
# 主板放测量底板上,先戳 7 个孔、再把弹片涂红盖印,孔和触点落进同一套基准圆,一次量完。
# ⚠️ **立柱高度 3.0 -> 1.0 mm**:2026-09-06 用户实测中框「螺柱支撑面 到 金焊盘面 = 1 mm」,
# 这就是载板面到主板贴合面该有的间隙。原来是 3.0 mm 立柱 + 顶针压缩 0.5,整套作废。
# ✅ **不用挖空**:2026-09-06 用户实测「贴合面上元件全都低于 1 mm」(屏蔽罩、SIM 卡座都算过),
# 所以载板**保持平板**,1.0 mm 间隙直接成立。曾担心的「挖空会和底层 BAND 那组元件打架」不存在了。
# ✅ 排线反而更宽裕(绕边下落 5 -> 3 mm):
# 尾插 2.9 + 3 + 2.9 + 1.6 + 34.1 ≈ 44.5 ≤ 50(原 46.5)
# 电池 4.6 + 3 + ... ≈ 29.6 ≤ 35(原 31.6)
# ⇒ §25.16 那串长度核算只会更松,不用改路径。
# ⚠️ **1 mm 立柱不好买**:M1.6 尼龙立柱通常从 3 mm 起。可行做法是
# 「螺钉从上穿过手机孔 + 1 mm 尼龙垫片 + 穿过载板 Φ1.8 孔 + 底面尼龙螺母」。垫片可叠。
# ⚠️ 顶角那两个 M3 孔(4,131)/(216,131) 的旧注释写着「主板架在 3 mm 立柱上,螺钉头 ≤ 2」——
# 现在只剩 1 mm,那两个孔真要补的话螺钉头必须 ≤ 1 或者挪开。(它们本来就没打到板上。)
POGO_STANDOFF_H = 1.0
# ---- 五个手机位:本体左下角 + 旋转(0 = 接口边朝下,180 = 接口边朝上;to_board 里 rot 180 是绕本体中心转,位置矩形不变)---- # ---- 五个手机位:本体左下角 + 旋转(0 = 接口边朝下,180 = 接口边朝上;to_board 里 rot 180 是绕本体中心转,位置矩形不变)----
# v3(2026-09-06 用户拍板):穿线槽挪到**主板投影外侧、靠板边**那一条,排线(按 6 cm 算) # v3(2026-09-06 用户拍板):穿线槽挪到**主板投影外侧、靠板边**那一条,排线(按 6 cm 算)
# 跨过主板正面到远端边、下穿、再折回底面。上排下移 / 下排上移各 2.5 mm,让板边那条留出 # 跨过主板正面到远端边、下穿、再折回底面。上排下移 / 下排上移各 2.5 mm,让板边那条留出
@@ -132,7 +199,9 @@ SLOTS = {
# 2026-09-06 v4c(用户拍板):尾插槽挪到**和电池槽同一条线**(接口边外侧 v −4.2~−1.2),排线翻过接口边直接下穿, # 2026-09-06 v4c(用户拍板):尾插槽挪到**和电池槽同一条线**(接口边外侧 v −4.2~−1.2),排线翻过接口边直接下穿,
# 不再从主板反面 3.0 mm 的缝里钻。背面到 J7n 变长 5.6,J7n 从 v 37 收到 **v 35**: # 不再从主板反面 3.0 mm 的缝里钻。背面到 J7n 变长 5.6,J7n 从 v 37 收到 **v 35**:
# 2.86 + 绕边 5 + 到槽心 2.7 + 穿板 1.6 + 背面 37.7 ≈ 49.9 ≤ 50 ✔(实长 55)。 # 2.86 + 绕边 5 + 到槽心 2.7 + 穿板 1.6 + 背面 37.7 ≈ 49.9 ≤ 50 ✔(实长 55)。
'tail': (45.0, 65.0, -4.2, -1.2), # 尾插排线:接口边外侧,与电池槽同线,正对 J1401 (u 47.5~59.7) # 2026-09-06 顶角 M3 孔 (4,131):site1 rot 180 下槽的 x = 72.09 − u,u=65 → x 7.09 会压到 Ø6 焊盘(边 7.0)。
# 东端 65 → 64(x 8.09,离焊盘 1.09)。排线 18 宽盖 u 44.6~62.6,64 端还余 1.4。
'tail': (45.0, 64.0, -4.2, -1.2), # 尾插排线:接口边外侧,与电池槽同线,正对 J1401 (u 47.5~59.7)
} }
# 排线在背面占的车道(本体坐标)。**车道里不放任何元件**(排线要平贴着走到插头 / 焊盘,0402 也别放): # 排线在背面占的车道(本体坐标)。**车道里不放任何元件**(排线要平贴着走到插头 / 焊盘,0402 也别放):
# 尾插排线 u 44.6~62.6,v 4.4 ~ J7n 北沿(EDL 组 / VBUS 组因此整体西移到 u ≤ 44); # 尾插排线 u 44.6~62.6,v 4.4 ~ J7n 北沿(EDL 组 / VBUS 组因此整体西移到 u ≤ 44);
@@ -195,6 +264,19 @@ BAND = {
'r76': (52.233, 38.0, 0), # R76n 56k CC1→VBUS,pin1 (51.800) = J7n.22 正北 'r76': (52.233, 38.0, 0), # R76n 56k CC1→VBUS,pin1 (51.800) = J7n.22 正北
'r77': (54.8, 38.0, 0), # R77n 100k,pin1 (54.367) 收 THERM 'r77': (54.8, 38.0, 0), # R77n 100k,pin1 (54.367) 收 THERM
'j6': (62.5, 41.0, 0), # J6n EDL 1.25mm 2P,J7n 东北、H1 keep-out 东侧;EDL 飞线随尾插排线穿槽走背面 ~40 mm 'j6': (62.5, 41.0, 0), # J6n EDL 1.25mm 2P,J7n 东北、H1 keep-out 东侧;EDL 飞线随尾插排线穿槽走背面 ~40 mm
# ── 按键 MOS 三列(2026-09-06 随 74HC595 从下板搬来,§26.23):顶针(顶层,u 2.1~5.4 / v 2.5~7)背面那片空地。
# 列距 3.5;SOT-23 框 ±1.5×±1.27,0402 框 ±0.72×±0.27(竖放 ±0.27×±0.72)。
# 东沿 15.5+1.5 = 17.0 < 电池排线车道 18.8;离 H6 (4,10.8) 最近的是 R%21 (8.5,10):4.6 > 2.8;
# 漏极→顶针焊盘要换层(顶针是顶层单面焊盘),过孔由布线器自己打。
'q3': (8.5, 4.0, 0), # Q%03 PWR
'q4': (12.0, 4.0, 0), # Q%04 VUP
'q5': (15.5, 4.0, 0), # Q%05 VDN
'r20': (8.5, 7.5, 90), # R%20 1k 栅阻
'r22': (12.0, 7.5, 90),
'r24': (15.5, 7.5, 90),
'r21': (8.5, 10.0, 90), # R%21 100k 栅极下拉
'r23': (12.0, 10.0, 90),
'r25': (15.5, 10.0, 90),
} }
# SMD 罐体 keep-out(相对元件原点):10.3 见方 + 单边 0.25 # SMD 罐体 keep-out(相对元件原点):10.3 见方 + 单边 0.25
CAN_BODY = (-5.4, -5.4, 5.4, 5.4) CAN_BODY = (-5.4, -5.4, 5.4, 5.4)
@@ -211,6 +293,9 @@ def desig(n):
'j7': 'J7%d' % n, 'u5': 'U5%d' % n, 'r76': 'R76%d' % n, 'r77': 'R77%d' % n, 'j7': 'J7%d' % n, 'u5': 'U5%d' % n, 'r76': 'R76%d' % n, 'r77': 'R77%d' % n,
'u2': 'U%d' % (23 + n), 'c70': 'C70%d' % n, 'c71': 'C71%d' % n, 'u2': 'U%d' % (23 + n), 'c70': 'C70%d' % n, 'c71': 'C71%d' % n,
'c72': 'C72%d' % n, 'r70': 'R70%d' % n, 'c72': 'C72%d' % n, 'r70': 'R70%d' % n,
'q3': 'Q%d03' % n, 'q4': 'Q%d04' % n, 'q5': 'Q%d05' % n,
'r20': 'R%d20' % n, 'r21': 'R%d21' % n, 'r22': 'R%d22' % n,
'r23': 'R%d23' % n, 'r24': 'R%d24' % n, 'r25': 'R%d25' % n,
} }
@@ -246,7 +331,23 @@ GLOBAL = {
# v3 布线:U31 组挪到 J31 东端**南**侧(走廊北带 y 70~80 留给差分对上顶层北上) # v3 布线:U31 组挪到 J31 东端**南**侧(走廊北带 y 70~80 留给差分对上顶层北上)
'U31': (87.5, 60.5, 0), # 底边 57.65 让开 CH4 电池槽(y ≤ 56.36 + 0.5);USB1 车道改走 y 64.0/64.46 'U31': (87.5, 60.5, 0), # 底边 57.65 让开 CH4 电池槽(y ≤ 56.36 + 0.5);USB1 车道改走 y 64.0/64.46
'C736': (80.5, 58.5, 0), # +5V 输入 0805(U31 西侧) 'C736': (80.5, 58.5, 0), # +5V 输入 0805(U31 西侧)
'C737': (93.0, 54.0, 0), # +3V3 输出 1206,Hub 晶振窝西南、CH4 电池槽东侧 # ── 2026-09-07:U32 的 PRT_SWP1~5 五个 strap 脚的 100k 下拉(R900~R904,见 §21.6a)──
# BOOST/GANG_EN/SWP6-7 那五颗用户拍板不装(采到 1 也无功能后果)。
# ⚠️ 第一版把十颗全摆在 U32 北边 y=79 那一排(那一片唯一的整块空地),
# `pcb2_route_sig` **10 条只布通 3 条** —— 南边(49/50/51)、西边(15/17)、东南(47/48)
# 那七个脚要绕过整颗 QFN 和一圈去耦,绕不出去。改成**每颗贴着自己的引脚**。
# 坐标是 `pcb2_strap_place.py` 按布线器的障碍表搜出来的(以引脚为心一圈圈往外找,
# 判据:两个焊盘离异网铜 >= 0.20 且不落进任何已有元件的本体投影)。改摆位请重跑它。
'R904': (103.458, 73.632, 0), # U32.31 PRT_SWP5,离脚 1.2 mm
'R903': (105.632, 71.958, 90), # U32.33 PRT_SWP4,离脚 1.2 mm
'R902': (106.635, 62.145, 0), # U32.47 PRT_SWP3,离脚 3.4 mm
'R901': (103.021, 59.414, 0), # U32.49 PRT_SWP2,离脚 4.2 mm
'R900': (102.750, 58.550, 0), # U32.51 PRT_SWP1,离脚 5.0 mm
# 2026-09-07:C737 由 1206 陶瓷换成 KEMET T491A226K010AT 固态钽(AMS1117 手册 §Stability
# 要求的 22uF 固态钽)。钽是极性件且 1 脚 = 正极,而板上 +3V3_HUB 的铜在东、GND 在西,
# 所以必须 rot 180;再东移 0.17 mm,让 1 脚(+3V3_HUB)西沿离那颗 GND 过孔 0.265 mm。
# 改这两个数之前先读 §26.25:0.17 是算出来的净距,不是随手摆的。
'C737': (93.17, 54.0, 180), # +3V3 输出,CASE-A_3216 固态钽
# LM75BD 测温 + 100nF(SCL/SDA 在 J31.21/23 = x 63.3/65.8,北排;走线从排母针缝里穿到南边) # LM75BD 测温 + 100nF(SCL/SDA 在 J31.21/23 = x 63.3/65.8,北排;走线从排母针缝里穿到南边)
# v2 第二版:挪到 J31 **南**边(走廊北带 y 70~76 要留给差分高速路)。CH4 行 B 只剩 U54(48.3)/R764/R774,这里是空的。 # v2 第二版:挪到 J31 **南**边(走廊北带 y 70~76 要留给差分高速路)。CH4 行 B 只剩 U54(48.3)/R764/R774,这里是空的。
# v3 布线:J31 **南**边(USB1 那对在底层 y 62.9/63.6 从排母南边一路西行,U34 顶边 62.2) # v3 布线:J31 **南**边(USB1 那对在底层 y 62.9/63.6 从排母南边一路西行,U34 顶边 62.2)
@@ -255,6 +356,30 @@ GLOBAL = {
# 上游 VBUS 分压 -> UP_VBUS_DET -> J31 pin36 (81.049, 65.100),跟着高阻端走 # 上游 VBUS 分压 -> UP_VBUS_DET -> J31 pin36 (81.049, 65.100),跟着高阻端走
'R721': (80.0, 61.5, 180), # pin1 = VBUS_UP(v3:让开 U31 与 USB1 车道) 'R721': (80.0, 61.5, 180), # pin1 = VBUS_UP(v3:让开 U31 与 USB1 车道)
'R722': (78.0, 61.5, 180), # pin1 = UP_VBUS_DET, pin2 = GND 'R722': (78.0, 61.5, 180), # pin1 = UP_VBUS_DET, pin2 = GND
# ── 三片 74HC595(2026-09-06 从下板搬来,§26.23)。SOIC-16 框 9.5 × 7.44(rot 90 后 7.44 宽 × 9.5 高)。
# 走廊中段 x 113~132 里能用的其实只有 **y 60.8~75.26 那条带**:UP_DP/UP_DM(底层 y 60.1/60.6)从 x 99 一路横穿到 205,
# 下面 y 52.5/53.0 是 USB5,上面 y 75.4/77.0 是 USB3(第一版只查了端点落在框里的线,把 UP 这对漏了,
# U21 rot 0 摆在 y 58.5 直接压在 UP 上;再上一版 rot 90 面对面缝 1.56,16 个脚的出线口互相堵死)。
# U21 (117,66.5) / U22 (128,66.5) rot 90:x 113.28~120.72 / 124.28~131.72(缝 3.56,出线段各 1.3 还剩 1 mm),
# y 61.75~71.25:离 UP_DM 线边 60.74 有 1.0,离 USB3_DP 线边 75.26 有 4.0;离 USB5 竖段 111.44 有 1.84、离 J32 焊盘边 133.0 有 1.28。
# U33 单独放 **J32 东侧**(x 183~208 / y 52~86 只有 UP 那对在 y 60.1/60.6 横穿):(193.5,68.5) rot 90 → y 63.75~73.25,离 UP_DM 3.0。
# 四根控制线从 J32 北排 3~6 行(x 138.95~146.57)进来,正好夹在两处中间。
# 2026-09-07 第三十轮:rot 90 的两片各 8 个西排脚全从 x 113~114 / 124 那两条竖缝出线,8 个网络布不通
# (EDL_EN2/3/5、K5_VDN 等,§26.23 落地末尾)。改成 **rot 0 / 180:引脚排朝南北**,16 根脚沿 9 mm 宽的边出线:
# 北带 y 70.23~75.26(5 mm,C604/R609/C605 在 y 73)、南带 y 60.74~62.77(2 mm,够打一排换层孔到顶层)。
# 底层 rot 0:脚 1~8(K/EDL 输出)在**北**排、9~16(控制/+3V3)在南排;rot 180 反过来。
# U21 rot 0:K1 去 site1(西北)、EDL_EN2/3 去 site2/3(北),输出朝北顺路;控制脚南排沿 y≈63 东行去 J32。
# U22 rot 180:K4/K5 去 site4/5(南),输出朝南,从南带换层到顶层跨过 UP 差分;控制脚北排。
# U21 (116.5,66.5):焊盘 x 111.56~121.44,离 USB5 竖段线边 111.4 有 0.5(≥0.2);
# U22 (127.5,66.5):焊盘 x 122.56~132.44,离 J32.1 盘边 ~133.1 有 0.6;两片焊盘之间 1.1。
'U21': (116.5, 66.5, 0),
'U22': (127.5, 66.5, 180),
'U33': (193.5, 68.5, 90),
# 去耦 / OE# 上拉都放两片北边那条带(y 72.73~73.27,离芯片上沿 71.25 有 1.5、离 USB3_DP 有 2.0)
'C604': (117.0, 73.0, 0),
'C605': (128.0, 73.0, 0),
'R609': (122.5, 73.0, 0),
'C739': (199.5, 68.5, 90),
} }
HDR = {'J31': (62.0, 67.5, 0), 'J32': (158.0, 67.5, 0)} HDR = {'J31': (62.0, 67.5, 0), 'J32': (158.0, 67.5, 0)}
HUB_CENTER = (100.0, 68.0) # v1 是 (110, 67):西移 10 让开 CH2 行 A / CH5 行 B,北移 1 让 C741 离开 CH4 电池槽 HUB_CENTER = (100.0, 68.0) # v1 是 (110, 67):西移 10 让开 CH2 行 A / CH5 行 B,北移 1 让 C741 离开 CH4 电池槽
@@ -416,9 +541,20 @@ def check(pos):
errs.append(u'越框: %s @ (%.1f, %.1f)' % (d, pos[d][0], pos[d][1])) errs.append(u'越框: %s @ (%.1f, %.1f)' % (d, pos[d][0], pos[d][1]))
# 同层重叠 # 同层重叠
# 侧键触点盘(J41~J60)是 Ø1.5 的圆盘、盘心距最小 1.78 ⇒ 净距 0.28。
# 包围盒判会把菱形里相邻的两个盘报成重叠(dx 1.30 < 1.50),所以这一组按**圆**判:
# 盘心距 >= POGO_PAD_D + 0.20(DRC 净距)。
pogo = set()
for n in SITES:
pogo |= set(desig(n)['pogo'])
for i in range(len(items)): for i in range(len(items)):
for j in range(i + 1, len(items)): for j in range(i + 1, len(items)):
a, b_ = items[i], items[j] a, b_ = items[i], items[j]
if a in pogo and b_ in pogo:
dd = math.hypot(pos[a][0] - pos[b_][0], pos[a][1] - pos[b_][1])
if dd < POGO_PAD_D + 0.20:
errs.append(u'触点盘太近: %s x %s 盘心距 %.2f(要 >= %.2f)' % (a, b_, dd, POGO_PAD_D + 0.20))
continue
shared = set(layers(a, pos[a][3])) & set(layers(b_, pos[b_][3])) shared = set(layers(a, pos[a][3])) & set(layers(b_, pos[b_][3]))
for L in sorted(shared): for L in sorted(shared):
if _overlap(abs_box(a, L), abs_box(b_, L)): if _overlap(abs_box(a, L), abs_box(b_, L)):
@@ -474,6 +610,8 @@ def check(pos):
b = abs_box(d, 2) b = abs_box(d, 2)
for n in SITES: for n in SITES:
for hn, (u_, v_) in HOLES.items(): for hn, (u_, v_) in HOLES.items():
if hn in SKIP_HOLES:
continue
hx, hy, _ = to_board(n, u_, v_) hx, hy, _ = to_board(n, u_, v_)
dd = _dist_box_pt(b, hx, hy) dd = _dist_box_pt(b, hx, hy)
if dd < HOLE_KEEP: if dd < HOLE_KEEP:
@@ -641,11 +779,13 @@ return k;
def mech_items(): def mech_items():
u"""35 个手机定位孔(NPTH)+ 5 个手机位的丝印外框与标签。""" u"""手机定位孔(NPTH,7 孔模型减去 SKIP_HOLES ⇒ 30 个)+ 5 个手机位的丝印外框与标签。"""
holes, lines, texts = [], [], [] holes, lines, texts = [], [], []
for n in SITES: for n in SITES:
x0, y0, rho = SITES[n] x0, y0, rho = SITES[n]
for hn, (u, v) in HOLES.items(): for hn, (u, v) in HOLES.items():
if hn in SKIP_HOLES: # 2026-09-07:H5 不打(push_mech 会先删掉板上全部 P*H* 再建)
continue
x, y, _ = to_board(n, u, v) x, y, _ = to_board(n, u, v)
holes.append(['P%d%s' % (n, hn), round(x * MIL, 2), round(y * MIL, 2)]) holes.append(['P%d%s' % (n, hn), round(x * MIL, 2), round(y * MIL, 2)])
c = [(x0, y0), (x0 + PW, y0), (x0 + PW, y0 + PH), (x0, y0 + PH)] c = [(x0, y0), (x0 + PW, y0), (x0 + PW, y0 + PH), (x0, y0 + PH)]
@@ -791,6 +931,7 @@ return out;
HEIGHT = { HEIGHT = {
'J31': 8.5, 'J32': 8.5, # 直插排母本体(C5307344) 'J31': 8.5, 'J32': 8.5, # 直插排母本体(C5307344)
'J23': 3.2, 'U31': 1.8, 'U32': 0.9, 'Y1': 1.2, 'U34': 1.6, 'J23': 3.2, 'U31': 1.8, 'U32': 0.9, 'Y1': 1.2, 'U34': 1.6,
'C737': 1.8, # KEMET A 壳 EIA-3216-18,最大高 1.8(不是 1206 的 1.7)
} }
@@ -850,7 +991,14 @@ if __name__ == '__main__':
print(' ', e) print(' ', e)
if len(errs) > 40: if len(errs) > 40:
print(u' ...还有 %d 条' % (len(errs) - 40)) print(u' ...还有 %d 条' % (len(errs) - 40))
# --force:只下发 --mech / --outline(孔、槽、丝印)时,元件级的报错不该挡路 ——
# 那些是「元件 vs 孔」「元件 vs 元件」,和要写的机械图元本身无关。
# ⚠️ 但 --push(重摆元件)绝不允许 force,摆错了会连着布线一起烂。
if '--force' in sys.argv and '--push' not in sys.argv:
print(u'--force:以上按警告处理,继续下发机械图元(元件没动)')
else:
sys.exit(1) sys.exit(1)
else:
print(u'本地检查通过:无重叠、无越框、没人压槽、顶层只有顶针、底层件避开了螺钉头') print(u'本地检查通过:无重叠、无越框、没人压槽、顶层只有顶针、底层件避开了螺钉头')
if '--layers' in sys.argv: if '--layers' in sys.argv:
push_layers() push_layers()
+4 -2
查看文件
@@ -108,7 +108,7 @@ HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE) sys.path.insert(0, HERE)
from eda import run from eda import run
PCB = 'f126ffb29c92f9b2' # PCB2-CARRIER PCB = '942ed1560d81c1e7' # PCB2-CARRIER
MIL = 39.3700787401575 MIL = 39.3700787401575
L_TOP, L_PWR, L_GND, L_BOT = 1, 15, 16, 2 # ⚠ 载板:Inner1=电源 / Inner2=地 L_TOP, L_PWR, L_GND, L_BOT = 1, 15, 16, 2 # ⚠ 载板:Inner1=电源 / Inner2=地
@@ -441,7 +441,9 @@ def main():
r = call(JS_REBUILD) r = call(JS_REBUILD)
if r.get('err'): if r.get('err'):
sys.exit(u'!! 打不开铺铜管理器: ' + r['err']) sys.exit(u'!! 打不开铺铜管理器: ' + r['err'])
for i in range(60): # 2026-09-07:正常一次填充 7 块 < 30 s;卡在 0/7 超过 ~90 s 就是客户端卡住了(§9k),
# 再等到 4 分钟也不会好,早点退出去重载。
for i in range(22):
time.sleep(4) time.sleep(4)
n = call(JS_POLL, timeout=40)['poured'] n = call(JS_POLL, timeout=40)['poured']
print(u' 填充 %d / %d' % (n, len(ps))) print(u' 填充 %d / %d' % (n, len(ps)))
+41 -2
查看文件
@@ -10,7 +10,7 @@ const nat = b => { const o={bubbles:true,cancelable:true,view:window,button:0};
b.dispatchEvent(new (t.startsWith('pointer')?PointerEvent:MouseEvent)(t,o)); }; b.dispatchEvent(new (t.startsWith('pointer')?PointerEvent:MouseEvent)(t,o)); };
for (const b of document.querySelectorAll('button')) for (const b of document.querySelectorAll('button'))
if ((b.innerText||'').trim()==='取消' && b.offsetParent!==null) { nat(b); await sleep(800); } if ((b.innerText||'').trim()==='取消' && b.offsetParent!==null) { nat(b); await sleep(800); }
await eda.dmt_EditorControl.openDocument('f126ffb29c92f9b2'); await eda.dmt_EditorControl.openDocument('942ed1560d81c1e7');
await sleep(1200); await sleep(1200);
let n = 0; let n = 0;
for (const o of await eda.pcb_PrimitivePour.getAll()) { await eda.pcb_PrimitivePour.delete(o.primitiveId); n++; } for (const o of await eda.pcb_PrimitivePour.getAll()) { await eda.pcb_PrimitivePour.delete(o.primitiveId); n++; }
@@ -29,9 +29,48 @@ def wid():
w0 = wid() w0 = wid()
r = run(JS, timeout=60) r = run(JS, timeout=60)
print(u'重载前 windowId %s,删铺铜边框 %s' % (w0, (r.get('result') or {}).get('deletedPours'))) print(u'重载前 windowId %s,删铺铜边框 %s' % (w0, (r.get('result') or {}).get('deletedPours')))
COUNT_JS = u"""
await eda.dmt_EditorControl.openDocument('942ed1560d81c1e7');
await new Promise(r => setTimeout(r, 9000));
let nc=0, nl=0, nv=0, npad=0;
for (const c of await eda.pcb_PrimitiveComponent.getAll()) nc++;
for (const l of await eda.pcb_PrimitiveLine.getAll()) nl++;
for (const v of await eda.pcb_PrimitiveVia.getAll()) nv++;
for (const p of await eda.pcb_PrimitivePad.getAll()) npad++;
return {doc: (await eda.dmt_Pcb.getCurrentPcbInfo() || {}).name, nc, nl, nv, npad};
"""
def assert_alive():
u"""⚠⚠ 重载之后**必须**先数一遍图元,确认桥接和文档真的接上了再往下走。
2026-09-07 就是漏了这一步把载板弄丢的:重载回来后铜层读回全 0
(skill §9ae 那个陈旧上下文),而紧接着跑的 `pcb2_pour.py --push`
又在那个状态下调了一次 `save()` —— 把“空”写回了文档,
301 件的下板没事、223 件的载板只剩下丝印和板框,只能走工程历史恢复。
“重载成功”的判据不是 windowId 变了,是**图元数不为零**。
不过关、绝不允许后面任何一步 save()。
"""
r = run(COUNT_JS, timeout=120)
if not r.get('success'):
sys.exit(u'!! 重载后数图元失败,**不要再跑任何会 save() 的脚本**: %s'
% json.dumps(r, ensure_ascii=False)[:400])
d = r['result']
print(u'重载后回查:%s 元件 %s / 导线 %s / 过孔 %s / 焊盘 %s'
% (d.get('doc'), d.get('nc'), d.get('nl'), d.get('nv'), d.get('npad')))
if not d.get('nc') or not d.get('nl') or not d.get('npad'):
sys.exit(u'!! 铜层读回 0 —— 桥接与文档失同步(skill §9ae)。\n'
u' **立刻停手,不要 save(),不要跑 pcb2_pour.py** —— 那一步会把空写回去。\n'
u' 先 API Gateway → 重新连接,不行就重启客户端,再跑一次本脚本确认数字回来了。')
for i in range(30): for i in range(30):
time.sleep(5) time.sleep(5)
w = wid() w = wid()
if w and w != w0: if w and w != w0:
print(u'已重载,新 windowId %s(%d s)' % (w, (i + 1) * 5)); time.sleep(10); sys.exit(0) print(u'已重载,新 windowId %s(%d s)' % (w, (i + 1) * 5))
time.sleep(10)
assert_alive()
sys.exit(0)
sys.exit(u'!! 150 s 内 windowId 没变,重载没成功') sys.exit(u'!! 150 s 内 windowId 没变,重载没成功')
+1 -1
查看文件
@@ -40,7 +40,7 @@ HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE) sys.path.insert(0, HERE)
from eda import run from eda import run
PCB = 'f126ffb29c92f9b2' PCB = '942ed1560d81c1e7'
SNAP = os.path.join(HERE, '_pcb2_snap_prerouting.json') SNAP = os.path.join(HERE, '_pcb2_snap_prerouting.json')
# 11 对差分 = 22 个网络(和 pcb2_rules.PAIRS 同源,这里独立列一遍并断言一致) # 11 对差分 = 22 个网络(和 pcb2_rules.PAIRS 同源,这里独立列一遍并断言一致)
+14 -2
查看文件
@@ -46,7 +46,7 @@ from eda import run
from pcb2_clear import half, deg from pcb2_clear import half, deg
from pcb2_place import SITES, to_board from pcb2_place import SITES, to_board
PCB = 'f126ffb29c92f9b2' PCB = '942ed1560d81c1e7'
MIL = 39.3700787401575 MIL = 39.3700787401575
GEOM = os.path.join(HERE, '_pcb2_route_geom.json') GEOM = os.path.join(HERE, '_pcb2_route_geom.json')
@@ -73,10 +73,15 @@ PAIR_OF['UP_DP'] = 'UP_DM'
PAIR_OF['UP_DM'] = 'UP_DP' PAIR_OF['UP_DM'] = 'UP_DP'
KSEP = chr(0)
def netof(key): def netof(key):
u"""路径的 key -> 网络名。`NET#2` 这样的后缀表示**同一个网络的第二条支路** u"""路径的 key -> 网络名。`NET#2` 这样的后缀表示**同一个网络的第二条支路**
(J23 的 A6↔B6 / A7↔B7 短接线就是这么加的),下发时按 `NET` 建线。""" (J23 的 A6↔B6 / A7↔B7 短接线就是这么加的),下发时按 `NET` 建线。"""
return key.split('#')[0] # ⚠️ 分隔符不能用 '#':载板上有 `KEY_OE#`(2026-09-06 595 上载板之后),按 '#' 拆会把它拆成
# `KEY_OE`,校核时和自己的焊盘当成异网(下板 §26.19 踩过同一条)。用 chr(0)。
return key.split(KSEP)[0]
# ── 站内坐标(u, v):J7n ↔ U5n 的短差分(v5:全部**底层**,接口边朝板边)──────── # ── 站内坐标(u, v):J7n ↔ U5n 的短差分(v5:全部**底层**,接口边朝板边)────────
@@ -165,6 +170,13 @@ def geom():
lines = [{'id': i, 'net': n or '', 'lay': l, 'x0': a / MIL, 'y0': b / MIL, lines = [{'id': i, 'net': n or '', 'lay': l, 'x0': a / MIL, 'y0': b / MIL,
'x1': c / MIL, 'y1': e / MIL, 'w': w / MIL} 'x1': c / MIL, 'y1': e / MIL, 'w': w / MIL}
for i, n, l, a, b, c, e, w in d['lines']] for i, n, l, a, b, c, e, w in d['lines']]
# 2026-09-07:封装自带的非金属化孔(J23 里两个 Ø0.60,`pcb2_npth.py --write` 产出)当成无网络的通孔盘,
# 两个布线器就都会躲开(原来只有 pcb2_vo_vias 读它,`$16N224` 的过孔直接打在孔上,DRC「挖槽区域到过孔 0mil」)。
npth = os.path.join(HERE, '_pcb2_npth.json')
if os.path.exists(npth):
for cx, cy, r in json.load(open(npth, encoding='utf-8')):
pads.append({'ref': 'NPTH', 'num': '', 'net': '', 'lay': 12,
'x': cx, 'y': cy, 'hw': r, 'hh': r})
return pads, vias, lines return pads, vias, lines
+463 -31
查看文件
@@ -4,6 +4,7 @@ u"""载板电源 + 杂散信号布线:**栅格迷宫布线器**(`pcb2_route_
/c/Python314/python .claude/pcb2_route_sig.py # 全部算 + 本地校核,不下发 /c/Python314/python .claude/pcb2_route_sig.py # 全部算 + 本地校核,不下发
/c/Python314/python .claude/pcb2_route_sig.py --net +5V # 只算这几个网络(可多个) /c/Python314/python .claude/pcb2_route_sig.py --net +5V # 只算这几个网络(可多个)
/c/Python314/python .claude/pcb2_route_sig.py --push # 下发(只删本脚本负责的网络) /c/Python314/python .claude/pcb2_route_sig.py --push # 下发(只删本脚本负责的网络)
/c/Python314/python .claude/pcb2_route_sig.py --refresh ... # 先 pcb2_route_dp.dump() 刷障碍表再算(**每次 --push 之后板子就变了,下一次 --net 必须带**)
/c/Python314/python .claude/pcb2_route_sig.py --plot out.png # 画一张自己算的图 /c/Python314/python .claude/pcb2_route_sig.py --plot out.png # 画一张自己算的图
## 为什么这里用布线器,差分对却是手写坐标 ## 为什么这里用布线器,差分对却是手写坐标
@@ -43,9 +44,9 @@ from scipy import ndimage
HERE = os.path.dirname(os.path.abspath(__file__)) HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE) sys.path.insert(0, HERE)
from pcb2_route_dp import (geom, check, expand, netof, push, MIL, PCB, from pcb2_route_dp import (geom, check, expand, netof, push, MIL, PCB, KSEP,
L_TOP, L_BOT, CLR, VIA_D, VIA_H, seg_rect, seg_pt, seg_seg, L_TOP, L_BOT, CLR, VIA_D, VIA_H, seg_rect, seg_pt, seg_seg,
chamfer_routes, sharp_corners) chamfer_routes, sharp_corners, turn_deg, SHARP, CHAMFER_R, seg_slack)
from pcb2_place import SITES, to_board, BW, BH, PW, PH from pcb2_place import SITES, to_board, BW, BH, PW, PH
GRID = 0.1 # 栅格步长 mm GRID = 0.1 # 栅格步长 mm
@@ -55,10 +56,32 @@ CORNER_R = 5.0
VIA_R = VIA_D / 2.0 VIA_R = VIA_D / 2.0
HOLE_CLR = 0.30 # 钻孔到钻孔(规则 otherClearance.hole2Hole),**不分网络** HOLE_CLR = 0.30 # 钻孔到钻孔(规则 otherClearance.hole2Hole),**不分网络**
VIA_COST = 2.5 # 换一次层折算成多少 mm 路程(压住"没事就打孔") VIA_COST = 2.5 # 换一次层折算成多少 mm 路程(压住"没事就打孔")
# 2026-09-07 用户定的布线规则:**顶层走水平、底层走纵向**(经典 H/V 分层)。
# 做成**软代价**而不是硬禁止:逆向走法每 mm 按 DIR_PENALTY 倍计价,45° 斜段按中间值计。
# 硬禁止会让出线段 / 1~2 mm 的短跳全布不通(焊盘出线方向由封装决定,不由层决定)。
# 取 2.0:一段 5 mm 的逆向直线代价 10 mm,正好等于「打两个孔翻到顺向层再走 5 mm」(2×2.5+5),
# 所以 >5 mm 的逆向走线会被自动翻层,<5 mm 的短跳留在原层 —— 这就是想要的分界。
# ⚠️ pcb1_route_sig.py 复用本引擎的 route_grid,下板同样生效。
DIR_PREF = {L_TOP: 'H', L_BOT: 'V'}
DIR_PENALTY = 2.0
# 斜步:第一版取 (1+DIR_PENALTY)/2 = 1.5(按分量计价的「一致」值),实测 15 根 POGO 顶层斜段 73 mm、
# 水平 31 mm —— 45° 斜走等于同时吃到横竖两个分量,比「先横后竖」便宜,规则形同虚设。
# 改成 DIAG_K = DIR_PENALTY × 1.2:斜走 1 mm 的代价 (2.4×√2 = 3.4) 略高于横 1 + 竖 2 = 3,
# 长斜段被拆成横竖(polish 再并成 L 形),只剩出线口和 45° 倒角那种短斜段。
DIAG_K = DIR_PENALTY * 1.2
# 过孔禁区 (x0, y0, x1, y1) mm。2026-09-07:`VBUS_EN2` 的过孔 (87.1, 70.2) 压在 Inner1 `VO2_P` 岛的颈
# (`pcb2_pour.ISLANDS` 的 VO2 第一块矩形 x 80~87.3,是这块岛去 J31 的唯一通道)东沿上,
# 反焊盘把颈从 7.3 掐到 6.30(要 ≥ 7.0,4 A / ΔT 20 °C)。外层布线器不知道内层有岛,只能显式禁掉。
# 范围比矩形各放 0.5(反焊盘半径 0.305 + 净距 0.2)。别的岛的颈目前 ≥ 7.2,没列。
# 2026-09-07 第三十轮:VO2 的第二块矩形(Hub 北边横带 x 87~100.5 / y 75.5~83,高 7.5)也要禁:
# K1_PWR (97.6,78.4) / K1_VDN (97.0,79.2) / +3V3_HUB (98.7,77.7) 三个孔把这条带从 7.5 掐到 5.30。
VIA_KEEPOUT = [(79.5, 67.1, 87.8, 83.5), (86.5, 75.0, 101.0, 83.5)]
TURN_COST = 0.30 # 拐一次弯折算成多少 mm(换来长直段,少几百条碎线) TURN_COST = 0.30 # 拐一次弯折算成多少 mm(换来长直段,少几百条碎线)
BBOX_PAD = 10.0 # 每次连线的搜索窗口比两端包围盒大多少 BBOX_PAD = 10.0 # 每次连线的搜索窗口比两端包围盒大多少
ASTAR_W = 1.3 # 加权 A*:h 乘这个数。放弃严格最优换速度 ASTAR_W = 1.3 # 加权 A*:h 乘这个数。放弃严格最优换速度
MAX_CELLS = 1200000 # 单层格数超过这个才把栅格放粗一档(只有跨半块板的网络会碰到) MAX_CELLS = 3200000 # 单层格数超过这个才把栅格放粗一档(只有跨半块板的网络会碰到)
# 2026-09-07:1.2M 时全板窗口 (220×135) 在 0.1 栅格下 2.97M 格 -> 被放粗到 0.2,
# 排母 2.54 间距的 0.84 缝在 0.2 栅格上整片不可达;3.2M 让全板窗口保住 0.1。
W_ARR = 0.11 # 还在 BM24 焊盘阵列里那一段 W_ARR = 0.11 # 还在 BM24 焊盘阵列里那一段
W_ESC = 0.20 # 刚出阵列 W_ESC = 0.20 # 刚出阵列
@@ -377,8 +400,15 @@ class Field(object):
return f return f
def via_ok(self): def via_ok(self):
return ((self.viafree >= CLR + VIA_R + self.qerr) ok = ((self.viafree >= CLR + VIA_R + self.qerr)
& (self.holefree >= VIA_H / 2.0 + HOLE_CLR + self.qerr)) & (self.holefree >= VIA_H / 2.0 + HOLE_CLR + self.qerr))
# 过孔禁区(Inner1 电源岛的颈):外层布线器看不见内层的岛,一个过孔的反焊盘就能把 4 A 的颈掐窄。
if VIA_KEEPOUT:
xs = self.x0 + np.arange(self.nx) * self.g
ys = self.y0 + np.arange(self.ny) * self.g
for x0, y0, x1, y1 in VIA_KEEPOUT:
ok &= ~(((xs >= x0) & (xs <= x1))[None, :] & ((ys >= y0) & (ys <= y1))[:, None])
return ok
def clear_seg(self, x0, y0, x1, y1, lay, w): def clear_seg(self, x0, y0, x1, y1, lay, w):
u"""一段任意角度的线是否安全(沿线采样,EDT 是 1-Lipschitz 所以可加采样余量)。""" u"""一段任意角度的线是否安全(沿线采样,EDT 是 1-Lipschitz 所以可加采样余量)。"""
@@ -447,12 +477,20 @@ def route_grid(f, src, tgt, w):
N = nx * ny N = nx * ny
rows, cols, vals = [], [], [] rows, cols, vals = [], [], []
for li in (0, 1): for li in (0, 1):
pref = DIR_PREF.get(LAY[li])
for dx, dy in ((1, 0), (0, 1), (1, 1), (1, -1)): for dx, dy in ((1, 0), (0, 1), (1, 1), (1, -1)):
fr, to = _edges(ok[li], dx, dy, nx, ny, li * N) fr, to = _edges(ok[li], dx, dy, nx, ny, li * N)
if len(fr): if len(fr):
# H/V 分层:顶层纵向步 / 底层横向步按 DIR_PENALTY 计价,斜步取中间值
if dx and dy:
k = DIAG_K if pref else 1.0
elif (pref == 'H' and dy) or (pref == 'V' and dx):
k = DIR_PENALTY
else:
k = 1.0
rows.append(fr) rows.append(fr)
cols.append(to) cols.append(to)
vals.append(np.full(len(fr), g * (math.sqrt(2) if dx and dy else 1.0), vals.append(np.full(len(fr), g * k * (math.sqrt(2) if dx and dy else 1.0),
np.float32)) np.float32))
vm = f.via_ok() & ok[0] & ok[1] vm = f.via_ok() & ok[0] & ok[1]
jj, ii = np.nonzero(vm) jj, ii = np.nonzero(vm)
@@ -901,15 +939,33 @@ def _connect(net, a, b, obstacles, w, pad=BBOX_PAD, relax=None):
# 窗口一大栅格就被放粗到 0.2 以上,QFN 东侧 0.71 mm 的走廊(U32.46 的出线端) # 窗口一大栅格就被放粗到 0.2 以上,QFN 东侧 0.71 mm 的走廊(U32.46 的出线端)
# 在粗栅格上整片不可达 —— 单独布它能通、全量布就不通,根子在这里不在拥挤。 # 在粗栅格上整片不可达 —— 单独布它能通、全量布就不通,根子在这里不在拥挤。
# 小窗口里只有离目标近的那部分 `a` 当种子,别的部分对这一段本来也没用。 # 小窗口里只有离目标近的那部分 `a` 当种子,别的部分对这一段本来也没用。
# 2026-09-07:加一档 **'near'**:窗口 = 目标 + 「已连上那坨里离目标最近的一件」。
# 大网络(+3V3_HUB 33 个焊盘)的远支路(site1 的 U41)在 'tgt' 小窗里没有种子,
# 直接退到 'all' 全板大窗 -> 栅格放粗 -> 排母缝 / 元件簇进不去。'near' 窗只框住真正要连的那一段。
tries = [('tgt', 0, 1), ('tgt', 0, 2), ('tgt', 0, 4), ('tgt', 1, 2), ('tgt', 1, 4), tries = [('tgt', 0, 1), ('tgt', 0, 2), ('tgt', 0, 4), ('tgt', 1, 2), ('tgt', 1, 4),
('near', 0, 1), ('near', 0, 2), ('near', 1, 1), ('near', 1, 2), ('near', 2, 2),
('all', 0, 1), ('all', 0, 2), ('all', 0, 4), ('all', 1, 1), ('all', 1, 2), ('all', 0, 1), ('all', 0, 2), ('all', 0, 4), ('all', 1, 1), ('all', 1, 2),
('all', 1, 4), ('all', 2, 1), ('all', 2, 4)] ('all', 1, 4), ('all', 2, 1), ('all', 2, 4)]
bc = _bbox(b.shapes, 0.0)
bcx, bcy = (bc[0] + bc[2]) / 2.0, (bc[1] + bc[3]) / 2.0
def _d2(s_):
bb = _bbox([s_], 0.0)
return (((bb[0] + bb[2]) / 2.0 - bcx) ** 2 + ((bb[1] + bb[3]) / 2.0 - bcy) ** 2)
a_near = min(a.shapes, key=_d2) if a.shapes else None
for mode, grow, fine in tries: for mode, grow, fine in tries:
base = b.shapes if mode == 'tgt' else a.shapes + b.shapes if mode == 'tgt':
base = b.shapes
elif mode == 'near':
if a_near is None:
continue
base = b.shapes + [a_near]
else:
base = a.shapes + b.shapes
x0, y0, x1, y1 = _bbox(base, pad + grow * 20.0) x0, y0, x1, y1 = _bbox(base, pad + grow * 20.0)
x0, y0 = max(0.0, x0), max(0.0, y0) x0, y0 = max(0.0, x0), max(0.0, y0)
x1, y1 = min(BW, x1), min(BH, y1) x1, y1 = min(BW, x1), min(BH, y1)
if mode == 'tgt': if mode in ('tgt', 'near'):
a_in = [s_ for s_ in a.shapes if _bbox([s_], 0.0)[2] > x0 and _bbox([s_], 0.0)[0] < x1 a_in = [s_ for s_ in a.shapes if _bbox([s_], 0.0)[2] > x0 and _bbox([s_], 0.0)[0] < x1
and _bbox([s_], 0.0)[3] > y0 and _bbox([s_], 0.0)[1] < y1] and _bbox([s_], 0.0)[3] > y0 and _bbox([s_], 0.0)[1] < y1]
if not a_in: if not a_in:
@@ -941,6 +997,8 @@ def _connect(net, a, b, obstacles, w, pad=BBOX_PAD, relax=None):
# ⚠️ 端点处可能有**一串重合点**(换层),要整串一起挪,不能只挪最后一个。 # ⚠️ 端点处可能有**一串重合点**(换层),要整串一起挪,不能只挪最后一个。
if a.center: if a.center:
_snap_end(path, a.center, False) _snap_end(path, a.center, False)
else:
_anchor_start(path, a.shapes)
if b.center: if b.center:
_snap_end(path, b.center, True) _snap_end(path, b.center, True)
path = _end_via(path) path = _end_via(path)
@@ -997,6 +1055,261 @@ def _snap_end(path, xy, at_end):
path[i] = (round(xy[0], 4), round(xy[1], 4), path[i][2], path[i][3]) path[i] = (round(xy[0], 4), round(xy[1], 4), path[i][2], path[i][3])
def via_corners(routes, limit=SHARP):
u"""换层点两侧的线在平面上成"直角"的那些:[(key, i, (x, y), 转向角)]。
`sharp_corners` 故意跳过换层点(那是过孔);用户 2026-09-07 要求换层处也不许 90°。"""
out = []
for key, path in sorted(routes.items()):
for i in range(1, len(path) - 2):
b, b2 = path[i], path[i + 1]
if not _same(b, b2) or b[2] == b2[2]:
continue
a, c = path[i - 1], path[i + 2]
if _same(a, b) or _same(b2, c):
continue
t = turn_deg(a, b, c)
if t > limit:
out.append((key, i, (b[0], b[1]), t))
return out
def via_chamfer_routes(routes, pads, vias, lines):
u"""换层点的 45° 化(2026-09-07 用户要求:「两层中的走线连接处不要垂直,增加一段锐角走线」)。
顶层横线进孔、底层竖线出孔,在平面上就是一个直角。做法和普通拐角一样切一刀,
但过孔要挪到斜切段的一端:
A 型:进线层上 R->Q 走 45°,过孔放在 Q(= 原孔位沿出线方向挪 d),出线从 Q 直着走;
B 型:过孔放在 R(= 原孔位沿进线方向退 d),出线层上 R->Q 走 45°。
先试 A 再试 B,半径按 CHAMFER_R 逐档退让;斜切段过 `seg_slack`,新孔位过 `_via_legal`。
校核用的是**改之前**的其它段(保守)。返回 (新 routes, 切掉的数量)。
"""
mine, mvias = [], []
for key, path in routes.items():
for x0, y0, x1, y1, lay, w in expand(path)[0]:
mine.append((netof(key), lay, x0, y0, x1, y1, w))
for vx, vy in expand(path)[1]:
mvias.append((netof(key), vx, vy))
def via_legal(x, y, net, old=None):
for x0, y0, x1, y1 in VIA_KEEPOUT:
if x0 <= x <= x1 and y0 <= y <= y1:
return False
for q in pads:
if q['net'] != net and q['lay'] in (12, L_TOP, L_BOT):
if seg_rect((x, y), (x, y), q['x'], q['y'], q['hw'], q['hh']) - VIA_R < CLR - 1e-4:
return False
if q['lay'] == 12 and math.hypot(x - q['x'], y - q['y']) < q['hw'] + VIA_H / 2.0 + HOLE_CLR:
return False # 钻孔间距不分网络;焊盘半宽当孔半径,保守
for v in vias:
d = math.hypot(x - v['x'], y - v['y'])
if v['net'] != net and d - VIA_R - v['r'] < CLR - 1e-4:
return False
if d < VIA_H + HOLE_CLR - 1e-4:
return False
for n2, vx, vy in mvias:
if old is not None and abs(vx - old[0]) < 1e-6 and abs(vy - old[1]) < 1e-6:
continue # 本路径自己原来那个孔(马上要挪走)
d = math.hypot(x - vx, y - vy)
if n2 != net and d - 2 * VIA_R < CLR - 1e-4:
return False
if d < VIA_H + HOLE_CLR - 1e-4:
return False
for l in lines:
if l['net'] != net and seg_pt(l['x0'], l['y0'], l['x1'], l['y1'], x, y) - VIA_R - l['w'] / 2.0 < CLR - 1e-4:
return False
for n2, lay, x0, y0, x1, y1, w in mine:
if n2 != net and seg_pt(x0, y0, x1, y1, x, y) - VIA_R - w / 2.0 < CLR - 1e-4:
return False
return True
out, ncut = {}, 0
for key, path in routes.items():
net = netof(key)
ok = lambda x0, y0, x1, y1, lay, w, _n=net: (
seg_slack(x0, y0, x1, y1, lay, w, _n, pads, vias, lines, mine, mvias) >= -1e-4)
res = []
i = 0
while i < len(path):
b = path[i]
if not (1 <= i <= len(path) - 3):
res.append(b)
i += 1
continue
b2 = path[i + 1]
a, c = path[i - 1], path[i + 2]
if not _same(b, b2) or b[2] == b2[2] or _same(a, b) or _same(b2, c) or turn_deg(a, b, c) <= SHARP:
res.append(b)
i += 1
continue
ax, ay = b[0] - a[0], b[1] - a[1]
cx, cy = c[0] - b[0], c[1] - b[1]
la, lc = math.hypot(ax, ay), math.hypot(cx, cy)
wa, wb = a[3], b2[3]
done = None
for r in CHAMFER_R:
d = min(r, la / 2.0, lc / 2.0)
if d < 0.05:
break
R = (round(b[0] - ax / la * d, 4), round(b[1] - ay / la * d, 4))
Q = (round(b[0] + cx / lc * d, 4), round(b[1] + cy / lc * d, 4))
# A 型:斜段在进线层,孔在 Q
if ok(R[0], R[1], Q[0], Q[1], a[2], wa) and via_legal(Q[0], Q[1], net, (b[0], b[1])):
done = [(R[0], R[1], a[2], wa), (Q[0], Q[1], a[2], wa), (Q[0], Q[1], b2[2], wb)]
break
# B 型:孔在 R,斜段在出线层
if ok(R[0], R[1], Q[0], Q[1], b2[2], wb) and via_legal(R[0], R[1], net, (b[0], b[1])):
done = [(R[0], R[1], a[2], wa), (R[0], R[1], b2[2], wb), (Q[0], Q[1], b2[2], wb)]
break
if done is None:
res.append(b)
i += 1
continue
res.extend(done)
ncut += 1
i += 2 # 跳过 b2(旧孔位的第二个点)
out[key] = res
return out, ncut
def reanchor_routes(routes, pads):
u"""切角 / 焊盘矩形角两类问题的兜底:让每条路径的两端都落在本网的铜里。返回补的段数。"""
by_net = {}
for key, path in routes.items():
by_net.setdefault(netof(key), []).append(key)
padix = {}
for q in pads:
if q['net']:
padix.setdefault(q['net'], []).append(q)
nfix = 0
for net, keys in by_net.items():
segs, vias = [], []
for k in keys:
sg, vv = expand(routes[k])
segs += [(k, x0, y0, x1, y1, lay, w) for x0, y0, x1, y1, lay, w in sg]
vias += [(k, x, y) for x, y in vv]
for k in keys:
path = routes[k]
if len(path) < 2:
continue
for at_end in (False, True):
x, y, lay, w = path[-1] if at_end else path[0]
inside = False
for q in padix.get(net, []):
if (q['lay'] == 12 or q['lay'] == lay) and math.hypot(x - q['x'], y - q['y']) <= min(q['hw'], q['hh']) + 1e-6:
inside = True
break
if not inside:
for k2, vx, vy in vias:
if math.hypot(x - vx, y - vy) <= VIA_R + 1e-6:
inside = True
break
if not inside:
for k2, x0, y0, x1, y1, l2, w2 in segs:
if k2 == k or l2 != lay:
continue
if seg_pt(x0, y0, x1, y1, x, y) <= w2 / 2.0 + 1e-6:
inside = True
break
if inside:
continue
best = None
for k2, x0, y0, x1, y1, l2, w2 in segs:
if k2 == k or l2 != lay:
continue
dx, dy = x1 - x0, y1 - y0
L2 = dx * dx + dy * dy
tt = 0.0 if L2 < 1e-12 else max(0.0, min(1.0, ((x - x0) * dx + (y - y0) * dy) / L2))
qx, qy = x0 + tt * dx, y0 + tt * dy
d = math.hypot(x - qx, y - qy)
if best is None or d < best[0]:
best = (d, qx, qy)
for q in padix.get(net, []):
if q['lay'] == 12 or q['lay'] == lay:
d = math.hypot(x - q['x'], y - q['y'])
if best is None or d < best[0]:
best = (d, q['x'], q['y'])
if best is None or best[0] > 1.5:
print(u' [!!] %s 的%s点 (%.3f,%.3f) L%d 不在本网铜里,附近 1.5 mm 内也没有可接的铜'
% (k.replace(KSEP, '|'), u'终' if at_end else u'起', x, y, lay))
continue
pt = (round(best[1], 4), round(best[2], 4), lay, w)
if at_end:
path.append(pt)
else:
path.insert(0, pt)
nfix += 1
return nfix
def _anchor_start(path, shapes):
u"""从「已连上的一坨」出发的路径,起点若落在某个**矩形焊盘形状**里但没在圆心,补一个圆心点。
2026-09-07 第三十轮实测:Comp 的注释早就写了「圆焊盘存的是外接矩形,停在角上搭不到铜」,
单焊盘的起终点因此钉在圆心;但 MST 后续每条边的**起点**是整坨铜的掩膜(矩形),
Dijkstra 挑离目标最近的种子 = 矩形角,离 Ø1.7 排母脚圆心 1.2 mm,铜外 0.2 ——
KEY_SRCLK/KEY_RCLK/+5V 在 J32 上、VBUS2/+3V3_HUB 在贴片盘上各留了一个孤岛(DRC 连接性 10 条)。
补的这一段全程在本网焊盘内部 / 焊盘自己的净距圈里,不会碰别的网。
"""
if not path:
return
x, y, lay, w = path[0]
if os.environ.get('ANCHOR_DEBUG'):
nb = []
for s_ in shapes:
if s_[0] == 'seg':
d = seg_pt(s_[1], s_[2], s_[3], s_[4], x, y) - s_[5]
elif s_[0] == 'via':
d = math.hypot(x - s_[1], y - s_[2]) - s_[3]
else:
d = seg_rect((x, y), (x, y), s_[1], s_[2], s_[3], s_[4])
if d < 1.0:
nb.append((round(d, 3), s_))
nb.sort(key=lambda t: t[0])
print(u' [dbg] 起点 (%.3f,%.3f) L%d 附近形状: %s' % (x, y, lay, nb[:4]))
# 起点已经落在本网的导线 / 过孔铜里(比如出线段的末端)就不用补
for s_ in shapes:
if lay not in s_[-1]:
continue
if s_[0] == 'seg' and seg_pt(s_[1], s_[2], s_[3], s_[4], x, y) <= s_[5] + 1e-6:
return
if s_[0] == 'via' and math.hypot(x - s_[1], y - s_[2]) <= s_[3] + 1e-6:
return
best = None
for s_ in shapes:
if s_[0] != 'rect' or lay not in s_[-1]:
continue
_, cx, cy, hw, hh, _ = s_
d = math.hypot(x - cx, y - cy)
if d <= math.hypot(hw, hh) + 0.05 and (best is None or d < best[0]):
best = (d, cx, cy)
if best is not None and best[0] >= 1e-3:
path.insert(0, (round(best[1], 4), round(best[2], 4), lay, w))
return
if best is not None:
return
if os.environ.get('ANCHOR_DEBUG'):
cont = []
for s_ in shapes:
if lay not in s_[-1]:
continue
if s_[0] == 'seg' and seg_pt(s_[1], s_[2], s_[3], s_[4], x, y) <= s_[5] + 1e-6:
cont.append(s_)
elif s_[0] == 'via' and math.hypot(x - s_[1], y - s_[2]) <= s_[3] + 1e-6:
cont.append(s_)
print(u' [dbg] 起点 (%.3f,%.3f) L%d 落在: %s' % (x, y, lay, cont[:3]))
# 诊断:起点不在本网任何形状里(2026-09-07 追 VBUS2 / +3V3_HUB 的孤岛)
near = []
for s_ in shapes:
if s_[0] == 'seg':
near.append((seg_pt(s_[1], s_[2], s_[3], s_[4], x, y) - s_[5], s_))
elif s_[0] == 'via':
near.append((math.hypot(x - s_[1], y - s_[2]) - s_[3], s_))
else:
near.append((math.hypot(x - s_[1], y - s_[2]), s_))
near.sort(key=lambda t: t[0])
print(u' [?] 起点 (%.3f,%.3f) L%d 不在本网铜里;最近 %s' % (x, y, lay, near[:2]))
def _end_via(path): def _end_via(path):
u"""路径**最后一个点上的过孔**要补一个重复点,`expand()` 才认得出来。 u"""路径**最后一个点上的过孔**要补一个重复点,`expand()` 才认得出来。
@@ -1131,10 +1444,13 @@ def net_order():
# **结果更糟**:它有 33 个焊盘,先布就把 Hub 周围铺满, # **结果更糟**:它有 33 个焊盘,先布就把 Hub 周围铺满,
# `XTAL1`/`CFGSEL0`/`PRTDIS6`/`PRTDIS7`/`HUB_RST` 五根配置线全布不通 # `XTAL1`/`CFGSEL0`/`PRTDIS6`/`PRTDIS7`/`HUB_RST` 五根配置线全布不通
# (没布通的从 4 个涨到 7 个)。⇒ 大网络必须排在点对点的小网络之后。 # (没布通的从 4 个涨到 7 个)。⇒ 大网络必须排在点对点的小网络之后。
hub = ['GND'] + lanes0 + ['VBUS_UP'] + [ # 2026-09-07:`HUB_RST` 提到 `VBUS_UP` 之前。R716.1 唯一的出口是 R716/R711 之间 0.66 的底层缝,
# 缝东头是 USB5 竖线墙、必须换层;VBUS_UP 先布时从 R709.1 出来在顶层 y 67.2 横穿卫星圈东行,
# 和 +3V3 前置线(顶层 y 66.2)夹成 0.8 的带,塞不下换层孔 -> HUB_RST 布不通(第三十轮实测)。
hub = ['GND'] + lanes0 + ['HUB_RST', 'VBUS_UP'] + [
'RBIAS', 'VDD18', 'VDD18PLL', 'XTAL1', 'XTAL2', 'RBIAS', 'VDD18', 'VDD18PLL', 'XTAL1', 'XTAL2',
'CFGSEL0', 'CFGSEL1', 'NONREM0', 'NONREM1', 'PRTDIS6', 'PRTDIS7', 'CFGSEL0', 'CFGSEL1', 'NONREM0', 'NONREM1', 'PRTDIS6', 'PRTDIS7',
'HUB_RST', '+3V3_HUB', 'VBUS_DET'] '+3V3_HUB', 'VBUS_DET']
# `EDL_ENn` / `VBUS_ENn` 在 U4n / U2n 上是**紧挨着 +3V3 脚的那一针**, # `EDL_ENn` / `VBUS_ENn` 在 U4n / U2n 上是**紧挨着 +3V3 脚的那一针**,
# 排在 `+3V3_HUB` 之前,否则 3V3 的长支干会从这 0.95 mm 的缝里横穿过去。 # 排在 `+3V3_HUB` 之前,否则 3V3 的长支干会从这 0.95 mm 的缝里横穿过去。
en = ['EDL_EN%d' % n for n in range(1, 6)] + ['VBUS_EN%d' % n for n in range(1, 6)] en = ['EDL_EN%d' % n for n in range(1, 6)] + ['VBUS_EN%d' % n for n in range(1, 6)]
@@ -1156,9 +1472,19 @@ def net_order():
# 让它先挑路;长的绕得起。site4/site5 上这条规则把 `VDN` 提到最前 —— # 让它先挑路;长的绕得起。site4/site5 上这条规则把 `VDN` 提到最前 ——
# 它的源在最东、靶点却在最西(J59 比 J57/J58 靠里),后布就被另外两根夹死 # 它的源在最东、靶点却在最西(J59 比 J57/J58 靠里),后布就被另外两根夹死
# (`POGO5_VDN` 原来就是这么布不通的)。 # (`POGO5_VDN` 原来就是这么布不通的)。
# 2026-09-06 595 上载板:移位链 4 根(各 4 个焊盘:J32 + 三片)+ 两根级联 + 15 根 K 栅极线(点对点),
# 排在 POGO 之前 —— K 线从走廊中段扇到五个站,比站内的 POGO 短线更需要先挑路。
# 2026-09-07 第三十轮(U21 rot 0 / U22 rot 180 之后):**K 线排在移位链控制线之前**。
# U22 南排的 K4/K5 六根只能从南带(UP 差分线边 60.74 ~ 焊盘下沿 62.77,2 mm)换层出去;
# KEY_* 先布时把这条带在顶层横着占满(U21 南排 -> U22 北排 -> J32 北排的直路),K4_VDN/K5_VUP/K5_VDN 布不通。
# 控制线有别的路(U22 两排脚之间 y 64.75~68.25 那 3.5 mm 的通道、J32 北侧),K 线没有。
keys = ['K%d_%s' % (n, k) for n in range(1, 6) for k in ('PWR', 'VUP', 'VDN')]
keys += ['KEY_SER', 'KEY_SRCLK', 'KEY_RCLK', 'KEY_OE#', 'SR_CAS', 'SR_CAS2']
ctrl = [n for n in _pogo_by_length()] + ['SDA', 'SCL'] ctrl = [n for n in _pogo_by_length()] + ['SDA', 'SCL']
# 2026-09-06:K 线 / 移位链提前到 `up` 之前 —— 它们是点对点细线,单独布 0.6 s 就通,
# 排在 +5V / VBUS / 站内局部网之后就被挤死(实测两轮各 11~12 根布不通)。
out, seen = [], set() out, seen = [], set()
for n in hub + up + site + power + ctrl: for n in hub + keys + up + site + power + ctrl:
if n not in seen: if n not in seen:
seen.add(n) seen.add(n)
out.append(n) out.append(n)
@@ -1205,11 +1531,13 @@ def gnd_boxes():
return out return out
JS_PUSH_SIG = u""" # ⚠️ 2026-09-06:原来一个请求里「逐条删 + 逐条建 1589 段」,桥接 30 s 超时(EasyEDA 那边还在继续画),
const NETS = %s, BOXES = %s, SEGS = %s, VIAS = %s, MIL = %f; # 下一步抓几何抓到画了一半的板子。现在拆成:一次性批量删 -> 每 250 段一个请求建线 -> 建孔 -> 存盘。
JS_PUSH_DEL = u"""
const NETS = %s, BOXES = %s, MIL = %f;
const S = new Set(NETS); const S = new Set(NETS);
const inBox = (x, y) => BOXES.some(b => x >= b[0] && x <= b[2] && y >= b[1] && y <= b[3]); const inBox = (x, y) => BOXES.some(b => x >= b[0] && x <= b[2] && y >= b[1] && y <= b[3]);
let dl = 0, dv = 0; const dl = [], dv = [];
for (const o of await eda.pcb_PrimitiveLine.getAll()) { for (const o of await eda.pcb_PrimitiveLine.getAll()) {
const L = o.getState_Layer(); if (L !== 1 && L !== 2) continue; const L = o.getState_Layer(); if (L !== 1 && L !== 2) continue;
const n = o.getState_Net(); const n = o.getState_Net();
@@ -1217,23 +1545,35 @@ for (const o of await eda.pcb_PrimitiveLine.getAll()) {
? !(inBox(o.getState_StartX()/MIL, o.getState_StartY()/MIL) && ? !(inBox(o.getState_StartX()/MIL, o.getState_StartY()/MIL) &&
inBox(o.getState_EndX()/MIL, o.getState_EndY()/MIL)) inBox(o.getState_EndX()/MIL, o.getState_EndY()/MIL))
: !S.has(n); : !S.has(n);
if (!keep) { await eda.pcb_PrimitiveLine.delete(o.getState_PrimitiveId()); dl++; } if (!keep) dl.push(o.getState_PrimitiveId());
} }
for (const o of await eda.pcb_PrimitiveVia.getAll()) { for (const o of await eda.pcb_PrimitiveVia.getAll()) {
const n = o.getState_Net(); const n = o.getState_Net();
const keep = n === 'GND' ? !inBox(o.getState_X()/MIL, o.getState_Y()/MIL) : !S.has(n); const keep = n === 'GND' ? !inBox(o.getState_X()/MIL, o.getState_Y()/MIL) : !S.has(n);
if (!keep) { await eda.pcb_PrimitiveVia.delete(o.getState_PrimitiveId()); dv++; } if (!keep) dv.push(o.getState_PrimitiveId());
} }
let nl = 0, nv = 0; for (let i = 0; i < dl.length; i += 300) await eda.pcb_PrimitiveLine.delete(dl.slice(i, i + 300));
for (let i = 0; i < dv.length; i += 300) await eda.pcb_PrimitiveVia.delete(dv.slice(i, i + 300));
return {del_lines: dl.length, del_vias: dv.length};
"""
JS_PUSH_LINES = u"""
const SEGS = %s, MIL = %f; let nl = 0;
for (const [net, lay, x0, y0, x1, y1, w] of SEGS) { for (const [net, lay, x0, y0, x1, y1, w] of SEGS) {
await eda.pcb_PrimitiveLine.create(net, lay, x0*MIL, y0*MIL, x1*MIL, y1*MIL, w*MIL); nl++; await eda.pcb_PrimitiveLine.create(net, lay, x0*MIL, y0*MIL, x1*MIL, y1*MIL, w*MIL); nl++;
} }
return nl;
"""
JS_PUSH_VIAS = u"""
const VIAS = %s, MIL = %f; let nv = 0;
for (const [net, x, y] of VIAS) { for (const [net, x, y] of VIAS) {
await eda.pcb_PrimitiveVia.create(net, x*MIL, y*MIL, %f*MIL, %f*MIL); nv++; await eda.pcb_PrimitiveVia.create(net, x*MIL, y*MIL, %f*MIL, %f*MIL); nv++;
} }
return nv;
"""
JS_PUSH_SAVE = u"""
await eda.pcb_Document.save(); await eda.pcb_Document.save();
await new Promise(r => setTimeout(r, 2500)); await new Promise(r => setTimeout(r, 2500));
return {del_lines: dl, del_vias: dv, lines: nl, vias: nv}; return 1;
""" """
@@ -1243,21 +1583,47 @@ def push_sig(routes):
net = netof(key) net = netof(key)
s_, v_ = expand(path) s_, v_ = expand(path)
for x0, y0, x1, y1, lay, w in s_: for x0, y0, x1, y1, lay, w in s_:
segs.append([net, lay, round(x0, 4), round(y0, 4), round(x1, 4), round(y1, 4), w]) # 2026-09-07:穿过**本网焊盘中心**的线段在中心处拆成两段。EasyEDA 建一条穿过焊盘的线时会在焊盘中心截断、
# 后半段直接丢掉(KEY_RCLK 穿 U22.12 去接出线段末端,板上只剩到焊盘中心那一半,DRC 连接性 2 条)。
pts = [(x0, y0)]
L = math.hypot(x1 - x0, y1 - y0)
cuts = []
for q in PADS_ALL:
if q['net'] != net or (q['lay'] != 12 and q['lay'] != lay) or L < 1e-6:
continue
t = ((q['x'] - x0) * (x1 - x0) + (q['y'] - y0) * (y1 - y0)) / (L * L)
if 0.02 < t < 0.98 and seg_pt(x0, y0, x1, y1, q['x'], q['y']) < 0.15: # 0.15 < 焊盘半宽 0.3,拆出的小折角仍在焊盘里
cuts.append((t, q['x'], q['y']))
for _t, qx, qy in sorted(cuts):
pts.append((qx, qy))
pts.append((x1, y1))
for (ax, ay), (bx, by) in zip(pts, pts[1:]):
segs.append([net, lay, round(ax, 4), round(ay, 4), round(bx, 4), round(by, 4), w])
for x, y in v_: for x, y in v_:
vs.append([net, round(x, 4), round(y, 4)]) vs.append([net, round(x, 4), round(y, 4)])
nets = sorted(set(netof(k) for k in routes) - {'GND'}) nets = sorted(set(netof(k) for k in routes) - {'GND'})
js = ("await eda.dmt_EditorControl.openDocument('%s');"
"await new Promise(r=>setTimeout(r,1800));" % PCB) + (
JS_PUSH_SIG % (json.dumps(nets), json.dumps(gnd_boxes()), json.dumps(segs),
json.dumps(vs), MIL, VIA_H, VIA_D))
from eda import run from eda import run
r = run(js, timeout=1800) OPEN = ("await eda.dmt_EditorControl.openDocument('%s');"
"await new Promise(r=>setTimeout(r,1800));" % PCB)
def call(js, what):
r = run(OPEN + js, timeout=600)
if not r.get('success'): if not r.get('success'):
sys.exit(u'下发失败: ' + json.dumps(r, ensure_ascii=False)[:600]) sys.exit(u'下发失败(%s): %s' % (what, json.dumps(r, ensure_ascii=False)[:600]))
d = r['result'] return r['result']
print(u'下发完成:先删 %d 线 / %d 孔,再画 %d 线 / %d 孔' # 2026-09-07:BM24 框里的 GND 梳子只有在这一轮**重画 GND** 时才删。
% (d['del_lines'], d['del_vias'], d['lines'], d['vias'])) # 原来无条件删:`--net HUB_RST ...` 补布把 65 个 BM24 GND 脚的逃逸段全删了没重画(gnd_patch 报 65 个悬空域)。
has_gnd = any(netof(k) == 'GND' for k in routes)
d = call(JS_PUSH_DEL % (json.dumps(nets), json.dumps(gnd_boxes() if has_gnd else []), MIL), u'删旧铜')
nl = 0
for i in range(0, len(segs), 250):
nl += call(JS_PUSH_LINES % (json.dumps(segs[i:i + 250]), MIL), u'建线 %d~' % i)
nv = 0
for i in range(0, len(vs), 250):
nv += call(JS_PUSH_VIAS % (json.dumps(vs[i:i + 250]), MIL, VIA_H, VIA_D), u'建孔 %d~' % i)
call(JS_PUSH_SAVE, u'存盘')
print(u'下发完成:先删 %d 线 / %d 孔,再画 %d 线 / %d 孔(分批,每批 250)'
% (d['del_lines'], d['del_vias'], nl, nv))
def _to_site_uv(n, x, y): def _to_site_uv(n, x, y):
@@ -1268,7 +1634,7 @@ def _to_site_uv(n, x, y):
return (PW - (x - x0), PH - (y - y0)) return (PW - (x - x0), PH - (y - y0))
def j7_assert(pads): def j7_assert(pads, strict=True):
u"""**把上面那一堆 J7_* 常量和板上的实际焊盘对一遍**(五个站逐一)。 u"""**把上面那一堆 J7_* 常量和板上的实际焊盘对一遍**(五个站逐一)。
⚠️ 这个断言是 2026-09-06 补的,起因是它不在的时候踩的坑: ⚠️ 这个断言是 2026-09-06 补的,起因是它不在的时候踩的坑:
@@ -1323,22 +1689,34 @@ def j7_assert(pads):
if bad: if bad:
for t in bad[:20]: for t in bad[:20]:
print(u' !! ' + t) print(u' !! ' + t)
if strict:
sys.exit(u'\nBM24 的手写常量和板上对不上(%d 条)—— 先改常量再布线。' % len(bad)) sys.exit(u'\nBM24 的手写常量和板上对不上(%d 条)—— 先改常量再布线。' % len(bad))
# 2026-09-07:pcb2_place 的 PW/PH 改成 67x44 后,站 1~3(rot 180)的元件在板上仍按旧 67.09x44.66 摆着,
# 和 plan() 差 (0.09, 0.66)。只重布不碰 J7n 的网络(如 POGO*)时降级成警告;
# 整板 --push 或重画 BM24 梳子之前必须先 pcb2_place.py --push 把站 1~3 摆到新坐标。
print(u' ⚠ BM24 常量和板上对不上(%d 条),但这一轮 --net 的网络都不碰 J7n,按警告放行' % len(bad))
return
print(u'BM24 手写常量与板上实测一致(五个站 × 13 个 GND 脚 + THERM/CC1 + R76n/R77n)✔') print(u'BM24 手写常量与板上实测一致(五个站 × 13 个 GND 脚 + THERM/CC1 + R76n/R77n)✔')
def main(): def main():
only = [a for a in sys.argv[1:] if not a.startswith('--')] only = [a for a in sys.argv[1:] if not a.startswith('--')]
if '--refresh' in sys.argv:
from pcb2_route_dp import dump as _dump
_dump()
pads, vias, lines = geom() pads, vias, lines = geom()
ALL_NETS.update(p['net'] for p in pads if p['net']) ALL_NETS.update(p['net'] for p in pads if p['net'])
PADS_ALL[:] = pads PADS_ALL[:] = pads
j7_assert(pads) j7nets = set(p['net'] for p in pads if p['ref'].startswith('J7') and p['net'])
j7_assert(pads, strict=not only or bool(set(only) & j7nets))
# ⚠️ 下发时要删掉五个 BM24 框里的旧 GND 铜(§25.8 自动扇出的产物), # ⚠️ 下发时要删掉五个 BM24 框里的旧 GND 铜(§25.8 自动扇出的产物),
# 所以**算的时候就得当它们已经没了** —— 否则手写的车道会跟一堆 # 所以**算的时候就得当它们已经没了** —— 否则手写的车道会跟一堆
# 马上就要消失的过孔"撞车",而真正的冲突反而被淹没。 # 马上就要消失的过孔"撞车",而真正的冲突反而被淹没。
boxes = gnd_boxes() boxes = gnd_boxes()
inbox = lambda x, y: any(b[0] <= x <= b[2] and b[1] <= y <= b[3] for b in boxes) inbox = lambda x, y: any(b[0] <= x <= b[2] and b[1] <= y <= b[3] for b in boxes)
mine = set(net_order()) - {'GND'} # ⚠️ 2026-09-06:带 `--net` 补布时只能摘**这一轮要重画的**网络,其余 115 个网络的旧铜是真障碍——
# 原来按完整候选表摘,第二遍把 +3V3_HUB / +5V 直接画到第一遍的线上(DRC 319 条 0 mil)。
mine = (set(only) if only else set(net_order())) - {'GND'}
n0 = len(vias) + len(lines) n0 = len(vias) + len(lines)
# ① 本脚本负责的网络:下发时整网删掉重画,所以**算的时候就当它们不存在**。 # ① 本脚本负责的网络:下发时整网删掉重画,所以**算的时候就当它们不存在**。
# 不摘的话,第二次跑会把自己上一轮的线当成障碍 —— 结果既不幂等, # 不摘的话,第二次跑会把自己上一轮的线当成障碍 —— 结果既不幂等,
@@ -1472,7 +1850,7 @@ def main():
if not ok: if not ok:
failed.append(net) failed.append(net)
for k, p in enumerate(ps): for k, p in enumerate(ps):
routes[net if k == 0 else '%s#%d' % (net, k + 1)] = p routes[net if k == 0 else '%s%s%d' % (net, KSEP, k + 1)] = p
print(u' %-12s %2d 段路径 / %3d 导线 / %2d 过孔 %5.1fs %s' print(u' %-12s %2d 段路径 / %3d 导线 / %2d 过孔 %5.1fs %s'
% (net, len(ps), sum(len(expand(q)[0]) for q in ps), % (net, len(ps), sum(len(expand(q)[0]) for q in ps),
sum(len(expand(q)[1]) for q in ps), time.time() - t, sum(len(expand(q)[1]) for q in ps), time.time() - t,
@@ -1488,7 +1866,61 @@ def main():
# 斜切只会拿掉铜,所以拿"未切之前"的其它段做校核是保守且正确的。 # 斜切只会拿掉铜,所以拿"未切之前"的其它段做校核是保守且正确的。
n0 = len(sharp_corners(routes)) n0 = len(sharp_corners(routes))
t = time.time() t = time.time()
raw = dict(routes)
routes = chamfer_routes(routes, pads, vias, lines, smooth=True) routes = chamfer_routes(routes, pads, vias, lines, smooth=True)
# ⚠️ `smooth=True` 会挪动路径(最多 0.15 mm),不只是拿掉铜;而它的净距校核拿的是切之前的自身路径
# ⇒ 切完再 check(),把牵涉到违规的那几条整条退回未切版本(下板 pcb1_route_sig §26.15 同款;
# 2026-09-06 载板 `$22N139` 就这么被抹到 GND 线上 −0.200)。
for _round in range(4):
bad0 = check(routes, pads, vias, lines, verbose=False)
hurt = set()
for t_, n1, n2, lay_, d_, a_, b_ in bad0:
for key, path in routes.items():
if netof(key) not in (n1, n2) or key not in raw or routes[key] == raw[key]:
continue
for x0, y0, x1, y1, ll, ww in expand(path)[0]:
if seg_seg((x0, y0), (x1, y1), a_, b_) < 1e-6:
hurt.add(key)
break
if not hurt:
break
print(u' 45° 化后有 %d 条压线,把 %s 退回未切的版本' % (len(bad0), ' '.join(sorted(netof(k) for k in hurt))))
for k in hurt:
routes[k] = raw[k]
# ── 换层点 45° 化(2026-09-07 用户要求)──
nv0 = len(via_corners(routes))
raw2 = dict(routes)
routes, ncut = via_chamfer_routes(routes, pads, vias, lines)
for _round in range(4):
bad0 = check(routes, pads, vias, lines, verbose=False)
hurt = set()
for t_, n1, n2, lay_, d_, a_, b_ in bad0:
for key, path in routes.items():
if netof(key) not in (n1, n2) or key not in raw2 or routes[key] == raw2[key]:
continue
for x0, y0, x1, y1, ll, ww in expand(path)[0]:
if seg_seg((x0, y0), (x1, y1), a_, b_) < 1e-6:
hurt.add(key)
break
for vx, vy in expand(path)[1]:
if seg_seg((vx, vy), (vx, vy), a_, b_) < 1e-6:
hurt.add(key)
break
if not hurt:
break
print(u' 换层 45° 化后有 %d 条压线,把 %s 退回' % (len(bad0), ' '.join(sorted(netof(k) for k in hurt))))
for k in hurt:
routes[k] = raw2[k]
nv1 = len(via_corners(routes))
print(u'换层点直角:%d -> %d(切了 %d 处)' % (nv0, nv1, ncut))
# ── 端点复接(2026-09-07 第三十轮)──
# 切角会挪走直角处的铜,而后布的支路起点可能正钉在那个角上(VBUS2 (105.9,99.1)、+3V3_HUB (198.1,70.0));
# 另外从整坨出发的路径起点也可能落在圆焊盘外接矩形的角上(J32.7/9、J32.40)。
# 统一在这里兜底:路径两端若不在本网任何铜(别的路径 / 过孔 / 焊盘)里,补一段接到最近的本网铜。
nfix = reanchor_routes(routes, pads)
if nfix:
print(u' 端点复接:补了 %d 段' % nfix)
left = sharp_corners(routes) left = sharp_corners(routes)
print(u'\n45° 化:直角拐点 %d -> %d(%.0f 秒)' % (n0, len(left), time.time() - t)) print(u'\n45° 化:直角拐点 %d -> %d(%.0f 秒)' % (n0, len(left), time.time() - t))
for key, i, xy, ang in left[:20]: for key, i, xy, ang in left[:20]:
+16 -1
查看文件
@@ -11,6 +11,21 @@ u"""载板 PCB2-CARRIER 设计规则 + D± 差分对(2026-09-05)。
· 写进去的 mm 会被换算成 mil 再换回来,`0.2` 回读是 `0.19999959999999997`, · 写进去的 mm 会被换算成 mil 再换回来,`0.2` 回读是 `0.19999959999999997`,
任何几何比较都留 1e-4 容差 任何几何比较都留 1e-4 容差
## ⚠⚠ 必须在「从原理图导入变更」**之后**跑(2026-09-08 实测)
网络指派(差分对 / 线宽 / 铺铜连接方式)是**按网络名**挂上去的,
而网络是元件导入时才在 PCB 上出现的。在空板上跑本脚本:
差分对 0 / 11 对
线宽指派 0 / 12 个网络
铺铜全连接指派 0 / 5 个网络
—— **而“规则配置已下发”那几行照常打印成功**,看上去像做完了。
导入元件后重跑一次就是 11/11、12/12、5/5。
⇒ 重建板子的顺序是:**切 4 层 → 导入元件 → 下规则 → 摆位**。
判据就是上面那三个分数,不是“没报错”。
## 和下板不同的三处 ## 和下板不同的三处
1. **差分对 11 对,不是 7 对。** 载板上每一路 D± 被 ESD 器件 `U5n` 切成两段: 1. **差分对 11 对,不是 7 对。** 载板上每一路 D± 被 ESD 器件 `U5n` 切成两段:
@@ -51,7 +66,7 @@ HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE) sys.path.insert(0, HERE)
from eda import run from eda import run
PCB = 'f126ffb29c92f9b2' # PCB2-CARRIER PCB = '942ed1560d81c1e7' # PCB2-CARRIER
# ── 线宽规则(mm)────────────────────────────────────────────────────── # ── 线宽规则(mm)──────────────────────────────────────────────────────
TRACK_RULES = { TRACK_RULES = {
+1 -1
查看文件
@@ -11,7 +11,7 @@ HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE) sys.path.insert(0, HERE)
from eda import run from eda import run
PCB = 'f126ffb29c92f9b2' PCB = '942ed1560d81c1e7'
JS = u""" JS = u"""
await eda.dmt_EditorControl.openDocument('%s'); await eda.dmt_EditorControl.openDocument('%s');
await new Promise(r=>setTimeout(r,1500)); await new Promise(r=>setTimeout(r,1500));
+2 -1
查看文件
@@ -363,7 +363,8 @@ def solve(pads, vias, lines):
want = N_VIA.get(ref, 1) want = N_VIA.get(ref, 1)
for k in range(want): for k in range(want):
# push()/check() 靠 netof(key) 取网络名,所以 '#' 前面必须是网络名 # push()/check() 靠 netof(key) 取网络名,所以 '#' 前面必须是网络名
key = u'%s#%s' % (net, ref) if want == 1 else u'%s#%s-%d' % (net, ref, k + 1) # 2026-09-07:分隔符改用 R.KSEP(chr(0)),'#' 会和 KEY_OE# 撞、也让 netof() 拆不开(第三十轮实测 13 条假「过孔-焊盘」)
key = u'%s%s%s' % (net, R.KSEP, ref) if want == 1 else u'%s%s%s-%d' % (net, R.KSEP, ref, k + 1)
# ⚠️ 出线画在**焊盘自己那一层**,不是写死顶层。 # ⚠️ 出线画在**焊盘自己那一层**,不是写死顶层。
# v1 时代全板器件在顶层,写死 L_TOP 没暴露问题;v2 之后 # v1 时代全板器件在顶层,写死 L_TOP 没暴露问题;v2 之后
# VOn_P 的 35 个焊盘**全在底层**,照旧写 L_TOP 会画出 35 条 # VOn_P 的 35 个焊盘**全在底层**,照旧写 L_TOP 会画出 35 条
+14 -9
查看文件
@@ -32,10 +32,14 @@ HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE) sys.path.insert(0, HERE)
PCB = '7e9415a16a909c51' PCB = '7e9415a16a909c51'
MIL = 39.3701 # 1 mm = 39.3701 mil MIL = 39.3701 # 1 mm = 39.3701 mil
BW, BH = 160.0, 125.0 # 板框 mm BW, BH = 160.0, 125.0 # 元件区 mm(原点左下角,坐标系不变)
# 2026-09-06 用户指令:两板尺寸取大对齐 —— 下板板框放大到载板的 220×135,原点不动,
# 板框左下角在 (−30,−5)(下板坐标 = 载板坐标 − (30,5),见 pcb1_env.py)。
OX, OY, OW, OH = -30.0, -5.0, 220.0, 135.0
CORNER_R = 5.0 # 四角圆角半径 mm CORNER_R = 5.0 # 四角圆角半径 mm
# 定位孔:M3 金属化,孔 Φ3.2 / 焊盘 Φ6.0 / 禁布 Φ6.5,孔心与圆角同心 # 定位孔:M3 金属化,孔 Φ3.2 / 焊盘 Φ6.0 / 禁布 Φ6.5,孔心与圆角同心
MH = [(5.0, 5.0), (BW - 5.0, 5.0), (5.0, BH - 5.0), (BW - 5.0, BH - 5.0)] # 定位孔与载板 pcb2_place.MH 对齐(减 (30,5)):四角 + 两侧中点;事实来源是 pcb1_place.MH,这里同表
MH = [(-22.0, 3.0), (182.0, 3.0), (-26.0, 126.0), (186.0, 126.0), (-23.0, 55.0), (183.0, 40.0)]
MH_HOLE, MH_PAD, MH_KEEPOUT = 3.2, 6.0, 6.5 MH_HOLE, MH_PAD, MH_KEEPOUT = 3.2, 6.0, 6.5
# ── 封装占位尺寸(mm,含焊盘;未旋转时 x 向宽 w、y 向高 h)────────────── # ── 封装占位尺寸(mm,含焊盘;未旋转时 x 向宽 w、y 向高 h)──────────────
@@ -469,14 +473,15 @@ def outline_poly():
要求闭合,不闭合就建不出来)。ARC 的角度**正值 = 逆时针**(本工程 Y 轴朝上)。 要求闭合,不闭合就建不出来)。ARC 的角度**正值 = 逆时针**(本工程 Y 轴朝上)。
""" """
R = CORNER_R R = CORNER_R
X0, Y0, X1, Y1 = OX, OY, OX + OW, OY + OH
pts = [] pts = []
L = lambda x, y: pts.append(('L', x, y)) L = lambda x, y: pts.append(('L', x, y))
A = lambda x, y: pts.append(('ARC', 90.0, x, y)) A = lambda x, y: pts.append(('ARC', 90.0, x, y))
start = (R, 0.0) start = (X0 + R, Y0)
L(BW - R, 0.0); A(BW, R) L(X1 - R, Y0); A(X1, Y0 + R)
L(BW, BH - R); A(BW - R, BH) L(X1, Y1 - R); A(X1 - R, Y1)
L(R, BH); A(0.0, BH - R) L(X0 + R, Y1); A(X0, Y1 - R)
L(0.0, R); A(R, 0.0) # 回到起点 L(X0, Y0 + R); A(X0 + R, Y0) # 回到起点
src, mode = [start[0] * MIL, start[1] * MIL], None src, mode = [start[0] * MIL, start[1] * MIL], None
for p in pts: for p in pts:
@@ -549,8 +554,8 @@ return {del, made: r ? r.primitiveId : null, left};
if n['left'] != 1: if n['left'] != 1:
print(u' !! layer 11 上有 %d 条多段线,应为 1 —— 板框没建成或有残留' % n['left']) print(u' !! layer 11 上有 %d 条多段线,应为 1 —— 板框没建成或有残留' % n['left'])
sys.exit(1) sys.exit(1)
print(u'板框: 1 条闭合多段线(4 直边 + 4 圆角 R%.0f),%.0f x %.0f mm;' print(u'板框: 1 条闭合多段线(4 直边 + 4 圆角 R%.0f),%.0f x %.0f mm,左下角 (%.0f,%.0f);'
u'顺带删掉 %d 个旧图元' % (CORNER_R, BW, BH, n['del'])) u'顺带删掉 %d 个旧图元' % (CORNER_R, OW, OH, OX, OY, n['del']))
n = call(""" n = call("""
const MH = %s; const MH = %s;
+7 -1
查看文件
@@ -43,7 +43,13 @@ from eda import run
TOL = 0.6 TOL = 0.6
_CACHE = json.load(io.open(os.path.join(HERE, 'eda-parts.json'), encoding='utf-8')) _CACHE = json.load(io.open(os.path.join(HERE, 'eda-parts.json'), encoding='utf-8'))
# 默认扫下板;`--board Board2` 扫载板(两块板的页表都在 eda-parts.json 里) # 默认扫下板;`--board Board2` 扫载板(两块板的页表都在 eda-parts.json 里)
if '--board' in sys.argv: # `--r2 BLADE` / `--r2 BASE` 扫 R2 刀片式工程(页表在 .claude/r2/r2-uuids.json)——
# ⚠ 它只按 uuid 打开文档,不切工程:跑之前先让客户端停在 PhoneRail-R2 上。
if '--r2' in sys.argv:
_b = sys.argv[sys.argv.index('--r2') + 1]
PAGES = json.load(io.open(os.path.join(HERE, 'r2', 'r2-uuids.json'),
encoding='utf-8'))['boards'][_b]['pages']
elif '--board' in sys.argv:
_b = sys.argv[sys.argv.index('--board') + 1] _b = sys.argv[sys.argv.index('--board') + 1]
PAGES = _CACHE['schematic_boards'][_b]['pages'] PAGES = _CACHE['schematic_boards'][_b]['pages']
else: else:
+225
查看文件
@@ -0,0 +1,225 @@
# R2 底板(BASE)剩余四页的**跨页契约** — 2026-09-08
`A1-INPUT` / `A2-AUX` 已画完。本文件钉死 `A3-CTRL` / `A4-HUB` / `A5-SLOTS` / `A6-FAN`
四页之间的**网络名、方向、器件归属、位号区段**。四页由四个子代理并行设计,
**只有主代理能下发到 EasyEDA**(全机只有一个 EDA 客户端,`openDocument` 是全局状态,
两个人同时推必然把图元放到别人的页上)。
⛔ **子代理绝对不许**:跑 `.claude/eda.py`、`dump_page.py`、`xpage.py`、`shot.py`、
`verify_sch.py`、任何带 `--push` 的脚本,或任何会连 `127.0.0.1:4962x` 的东西。
你的交付物是**一个能本地自检通过的 Python 脚本**,主代理负责下发与几何验证。
---
## 0. 已经存在的东西(不要重画,也不要改名)
| 页 | 内容 | 对外端口 / 旗 |
|---|---|---|
| `A1-INPUT` | J1 DC-005 → F1 → Q1 反接 → C2~C6 → Rs6 → 12V 母线;U13 INA226(0x43);U29 TL431 泄放 | `+12V`(旗) `GND`(旗) `+3V3`(旗) `SDA` `SCL` `INA_ALERT`(端口) |
| `A2-AUX` | U19 12V→3V3(0.5A);U30 12V→5V(2A) | `+12V` `+3V3` `GND`(旗) `+5V`(端口,OUT) |
`xpage` 现在的 4 个单实例 `+5V` / `SDA` / `SCL` / `INA_ALERT` 就是等你们补对端的。
---
## 1. 网络名总表(**唯一事实来源**)
### 1.1 电源
| 网络 | 图元类型 | 说明 |
|---|---|---|
| `+12V` | **Power 旗** | 全板 12V 母线 |
| `+3V3` | **Power 旗** | U19 出来的控制轨(0.5A),给 ESP32 / mux / DAC / 刀片逻辑 |
| `+5V` | **端口** | U30 出来的 5V(2A)。A2 已发 OUT,A4/A5 用 **IN** |
| `+3V3_HUB` | **Power 旗**(仅 A4 页内) | U31 AMS1117 给 U32 专用,**不许**和 `+3V3` 并 |
| `FAN_12V_SW` | **Power 旗**(仅 A6 页内) | Q7 高边开关之后的风扇 12V |
| `GND` | **Ground 旗** | — |
### 1.2 跨页信号(发出方 → 接收方)
| 网络 | 发出 | 接收 | 说明 |
|---|---|---|---|
| `SDA` / `SCL` | A1(U13) | **A3**(上拉 + ESP32 + U35 上游 + U6/U7 DAC) | I²C **主干**,不经 mux |
| `INA_ALERT` | A1(U13) | **A3**(ESP32 GPIO) | 开漏,上拉放在 A3 |
| `SDA1`~`SDA5` / `SCL1`~`SCL5` | **A3**(U35 通道 0~4) | **A5**(槽 1~5) | mux 分支,**上拉在刀片上**(B2-CTRL 的 R40/R41),底板这一侧不加 |
| `VDAC1`~`VDAC5` | **A3**(U6/U7) | **A5** | 模拟量,跨金手指进刀片 |
| `EFUSE_EN1`~`EFUSE_EN5` | **A3**(ESP32 直驱) | **A5** | 不经 IO 扩展 |
| `PRSNT1#`~`PRSNT5#` | **A5**(槽 **B31** 一针) | **A3**(ESP32 输入 + 上拉) | ⚠️ **2026-09-08 修正**:底板是**主板侧**,`A1` 与 `B31` 必须是**两个节点** —— `A1 → GND`、`B31 → PRSNT{n}#`。短接 A1↔B31 的是**刀片**(`b4_edge.py`,PCIe add-in card 的标准做法),插上刀片才把 B31 拉到地。若底板也照字面「镜像」把两针并成一个网,回路里一个对地通路都没有 ⇒ **插不插都读「未插入」,而 DRC/ERC/几何验证/跨页对账四道全过**。见 `a5_slots.py` 表头的对照表 |
| `INT#` | **A5**(五槽 B11 并联) | **A3**(ESP32 + **唯一一颗 10k 上拉到 +3V3**) | 开漏 wire-OR,五槽共一个网络 |
| `HUB_RST` | **A3**(ESP32) | **A4**(U32) | ⚠ 见 §3 的保命约束 |
| `UP_VBUS_DET` | **A4** | **A3**(ESP32) | 电脑在不在 |
| `USB1_DP/DM` ~ `USB5_DP/DM` | **A4**(U32 下行口 1~5) | **A5**(槽 B14/B15) | |
| `USB6_DP` / `USB6_DM` | **A4**(U32 下行口 6) | **A3**(ESP32 原生 USB,IO19=D−/IO20=D+) | D13 |
| `FAN_EN` | **A3**(ESP32) | **A6** | 高边总开关,复位态必须**关电** |
| `FAN_PWM` | **A3**(ESP32) | **A6** | 两只风扇共用(A6 里经反相成 `FAN_PWM_L`) |
| `FAN_TACH_IN` / `FAN_TACH_OUT` | **A6** | **A3**(ESP32) | 两路独立测速 |
| `NTC1` / `NTC2` | **A6**(RT601/RT602 分压节点) | **A3**(ESP32 **ADC1** 脚) | ⚠ 必须 ADC1(IO1~IO10),ADC2 与 WiFi 冲突 |
### 1.3 页内网络(不跨页,别在别的页上用这些名字)
- A3:`MUX_RST#`(U35 的 RESET#,10k 上拉 + 一个 ESP32 GPIO)、`EN` / `IO0` / `TXD0` / `RXD0`(J33)
- A4:`UP_DP` `UP_DM` `VBUS_UP` `VBUS_DET` `XTAL1` `XTAL2` `RBIAS` `VDD18` `VDD18PLL`
`CFGSEL0` `CFGSEL1` `NONREM0` `NONREM1`(全部沿用 R1 `C2-HUB` 的名字)
- A6:`FAN_PWM_L` `FAN_IN_TACH_RAW` `FAN_OUT_TACH_RAW`
- A5:无(全是 §1.2 的网络 + 电源 + 地)
### 1.4 **明确不存在**的网络(R1 有、R2 没有,写了就是错)
`BUCK_PG`(A2 的 U19 没引 PG)、`IOEXP_INT#`(改叫 `INT#`)、
`EFUSE_PG*` / `EFUSE_FLT*`(eFuse 在刀片上,状态走刀片自己的 PCA9555)、
`VBUS_EN*`(在刀片 B2-CTRL 上)、`KEY_*`(74HC595 已作废,D9)、
`USB_DP` / `USB_DM` 不带编号的形式(那是刀片页内的名字)。
---
## 2. 位号区段(防撞)
| 页 | 允许使用 |
|---|---|
| A1(已画) | `J1` `F1` `Q1` `Q3` `DZ1` `D1` `R1` `R4` `R5` `R6` `R9A~R9E` `C1~C6` `C11` `Rs6` `U13` `U29` |
| A2(已画) | `U19` `U30` `L2` `L3` `R2` `R3` `R706` `R707` `C7~C10` `C731~C735` |
| **A3-CTRL** | `U20` `U35` `U6` `U7` `J33`,新增无源件用 `R601~R650` / `C601~C650` |
| **A4-HUB** | `U31` `U32` `Y1` `J23`,无源件 `R708~R730` / `C737` `C740~C759` |
| **A5-SLOTS** / **A5B-SLOTS** | 插槽 `J34~J38`(槽 1~5)、D± 退路座 `J39~J43`,无源件 `R760~R790` / `C761~C790` |
| **A6-FAN** | `Q6~Q10` `D2` `DZ3` `J26` `J27` `RT601` `RT602`,电容 `C736` `C738` `C739`,电阻 `R733` `R734` `R746~R757` + 新增 `R660~R699` |
> ⚠️ **2026-09-08 修订**:原表里 A4 的 `C740~C760` 与 A5 的 `C760~C790` 在 `C760` 上撞车(A6 代理发现),
> 已改成 `C740~C759` / `C761~C790`。A6 原来只点名了 `C738`,现补 `C736` / `C739`(全表仅剩的两个无主号)。
> `A5B-SLOTS` 是 2026-09-08 新开的第七页(`e493f8bce3a51aae`),与 A5 共用同一段号。
---
## 3. 三条会烧板 / 会自断后路的硬约束
1. **`HUB_RST` 必须外部上拉到 `+3V3_HUB`(10k),且 ESP32 复位期间 Hub 保持工作。**
ESP32 的控制台就挂在这颗 Hub 后面(D13),误复位 = 自断控制台 + 下次烧不进去。
固件侧只在启动自检脉冲一次 —— 硬件侧要保证「不驱动 = 不复位」。
2. **`FAN_EN` 复位态必须是关电。** Q7 是 P-MOS 高边,栅极靠 Q6 拉低才导通;
Q6 栅极要有 100k 下拉,ESP32 高阻时风扇不转(R1 P6 已经是这个拓扑,照抄)。
3. **`+3V3_HUB` 与 `+3V3` 是两条轨**(U32 满载 ≈420 mA,挂不进 U19 那条 0.5A 轨)。
两者只共地。A4 页上不许出现把它们并起来的导线或同名旗。
---
## 4. 器件与引脚几何(全部实测,直接用,不要自己猜)
`.claude/eda-parts.json` 是器件表;下面是**这四页要用到的新器件**的引脚相对坐标(rot 0,相对元件中心):
```
TCA9548APWR (C130026) 左列 x=-50,右列 x=+50
1 A0 (-50, 55) 13 SD4 (50,-55)
2 A1 (-50, 45) 14 SC4 (50,-45)
3 RESET#(-50, 35) 15 SD5 (50,-35)
4 SD0 (-50, 25) 16 SC5 (50,-25)
5 SC0 (-50, 15) 17 SD6 (50,-15)
6 SD1 (-50, 5) 18 SC6 (50, -5)
7 SC1 (-50, -5) 19 SD7 (50, 5)
8 SD2 (-50,-15) 20 SC7 (50, 15)
9 SC2 (-50,-25) 21 A2 (50, 25)
10 SD3 (-50,-35) 22 SCL (50, 35)
11 SC3 (-50,-45) 23 SDA (50, 45)
12 GND (-50,-55) 24 VCC (50, 55)
NCP18XH103F03RB (C13564) 1 (-20,0) 2 (+20,0) 无极性
2.54mm 1*5P 直排针 (C50951) 1(-20,20) 2(-20,10) 3(-20,0) 4(-20,-10) 5(-20,-20) 全部朝左
AMS1117-3.3 (C6186) 1 GND(-45,10) 2 VOUT(-45,0) 3 VIN(-45,-10) 4 VOUT(+45,0)
PCIE-064-02-F-D-TH (C4597619) A1~A32: x=-35, y=155-10*(n-1)
B1~B32: x=+35, y=155-10*(n-1)
```
其余器件(ESP32-S3-WROOM-1 / DAC63204 / USB2517I / Y1 / AO3400A / AO3401A /
SS34 / BZT52C8V2 / TYPE-C-31-M-12 / 2510-4A / A1251WV-S-4P / 各种 0402)的引脚坐标
**从 R1 的实测 dump 里直接读**,见 §5。
---
## 5. R1 参照版图(已 dump 成 JSON,直接读,**不要连 EDA**)
| 文件 | 是哪一页 | 给谁用 |
|---|---|---|
| `.claude/r2/_p3_live.json` | R1 `P3-CONTROL`(U20 ESP32 + J22 + RT601/602) | **A3**(ESP32 那一块整块搬)、**A6**(NTC 分压那一小块搬走) |
| `.claude/r2/_p3b_live2.json` | R1 `P3B-EXPAND`(U6/U7 DAC + U23 PCA9555) | **A3**(两颗 DAC 整块搬,U23 不要) |
| `.claude/r2/_c2hub_live.json` | R1 `C2-HUB`(U32 + Y1 + J23 + 一堆去耦) | **A4**(整页搬,加第 6 个下行口) |
| `.claude/r2/_p6_live.json` | R1 `P6-FAN-EDL`(风扇部分 + EDL 部分) | **A6**(**只要风扇部分**,EDL 已挪去刀片 B3-PHONE) |
| `.claude/r2/b4_edge.py` | R2 刀片 `B4-EDGE`(金手指 64 针分配) | **A5**(针位表照抄并做**逐脚对账**) |
JSON 结构见 `.claude/r2/dump_page.py` 的文档串:
`parts` = `[位号, 器件名, 子部件, x, y, rot, mirror, uuid, libraryUuid, [[脚号,脚名,x,y],...]]`,
`wires` = **端点对**(不是折线),`flags` = `[种类, 网名, x, y, rot]`,
`ports` = `[dir, 网名, x, y, rot]`,`texts` = `[x, y, 内容, rot, 字号]`。
⚠️ **搬版图时引脚坐标是绝对坐标**:算出 `(引脚x - 元件x, 引脚y - 元件y)` 得到相对量,
再加上你的新元件中心。**不要相信任何"方向表"**(skill §6c:同一个 `rot` 在不同型号上语义相反)。
---
## 6. 每页交付物
一个 `.claude/r2/aN_xxx.py`,**照抄 `a1_input.py` / `a2_aux.py` 的骨架**:
```python
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from schcheck import Sheet, by_rel, two_pin, pins_of, _on # 按需
PAGE, NAME = '<页uuid>', '<页名>'
def placement(): ... # [[位号, eda-parts的键, x, y, rot], ...]
def expect(): ... # {位号: {脚号: (绝对x, 绝对y)}} 放件后逐脚回读比对用
def build(): ... # 返回填好 W/F/PT/TX 的 Sheet
def main():
s = build(); s.assert_grid()
errs = s.netcheck(PINS, NC, ALLOW_SPLIT)
s.report(len(placement()), errs)
if '--push' in sys.argv:
s.clear(); s.place(placement(), expect()); s.push_geometry()
```
**页 uuid**(`.claude/r2/r2-uuids.json` 的 `boards.BASE.pages`):
| 页 | uuid |
|---|---|
| A3-CTRL | `38327f806bc13133` |
| A4-HUB | `09d583ff95cd35d9` |
| A5-SLOTS | `2c73c5bf77f34053` |
| A6-FAN | `68c9ffaa578572d3` |
跑 `/c/Python314/python .claude/r2/aN_xxx.py`(**不带 `--push`**)必须打印出:
```
元件 N 导线 N 旗 N 端口 N 文字 N
交叉点(不相连,供人工确认): N
本地自检通过(短路 0 / 漏连 0 / 悬空 0 / 旗端口全在端点)
```
---
## 7. 画图硬规矩(全部来自 `.claude/skills/easyeda-sch-geometry/SKILL.md`,**先读它**)
1. **Y 轴向上**(§1)。
2. 所有坐标必须是 **5 的倍数**(§3、§8a-2b)—— 否则旗/端口静默不连。
3. 导线只能**横平竖直**,用 `s.w(net, p1, p2, p3...)` 逐段声明。
4. 旗 / 端口的锚点必须落在**某段导线的端点**上,不能压在中段(§8a-2),
也不能和元件引脚**同坐标**(§8a:那会变成零长度导线,验证过得去、重画就断)。
5. 同一个锚点上如果有两段**共线**的导线,合并后就成了中段 —— `netcheck` 会报。
6. 页面范围 `x 20~1145` / `y 10~812`;**标题栏**在 `x>455 且 y<192`,文字不许伸进去(§8c)。
7. 文字宽度经验值:`右边界 ≈ x + 字号 × (中文字数 + 0.5×其余字符数)`。
8. **叠字是这个项目的常见事故**:端口的符号+标签往锚点**左边**伸出去约 70 个单位,
`flag_clash` 那种「横向 <20」的判据够不着。排版时给端口左边留 ≥80 的空白。
9. 电源/地旗的 `rot`:`0` = 符号朝上(往上挂),`180` = 朝下。极性件别信方向表,按**脚号**断言。
10. `ALLOW_SPLIT` 只放行「靠同名旗/端口成网、导线上天然分块」的网络(`GND` `+12V` `+3V3` `+5V` 这类)。
信号网络出现「被拆成 N 块」就是真漏连,不许往 `ALLOW_SPLIT` 里加来消错。
---
## 8. 汇报格式
做完后回报(**不要贴整段脚本**):
1. 脚本路径 + dry-run 的那三行统计输出(原样贴)。
2. 本页画了什么(一句话一块)。
3. **你发出/接收的端口清单**(网名 + dir),主代理要拿它对账。
4. 你做的**设计决定**与依据(尤其是 GPIO 分配、strap 电平、上下拉取值)。
5. 你发现的**风险 / 与方案文档不一致的地方**(B3=+12V 那种错就是这么揪出来的)。
6. 交叉点清单(如果有),说明为什么可接受。
二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 564 KiB

二进制
查看文件
二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 205 KiB

二进制
查看文件
二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 524 KiB

二进制
查看文件
二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 457 KiB

二进制
查看文件
二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 509 KiB

+38
查看文件
@@ -0,0 +1,38 @@
# M6 手工补线作业单(按板上实际铜算,5 个网)
布线方向约定:底层(器件面)纵向、顶层横向;信号线宽 0.15,过孔 0.6/0.3。
⚠ 这些脚的出口都被**自家邻脚的引出脚 / 过孔**封死(如 `U2.15` 被 `EFUSE_EN` 的孔 (89.5,31) 卡到只剩 0.04 mm),脚本布线器四种办法(换顺序 / PathFinder / 只补线 / 过孔扇出)都到不了。交互式布线时先挪开挡路的那根引出脚或过孔再拉线。
## VDAC(2 只脚)— R8.2 一根铜都没有
- `J1.A16` (49.50, 3.45) 底层
- `R8.2` (93.50, 49.85) 底层
现有铜 7.8 mm
## $1N147(3 只脚)— U2.15 一根铜都没有
- `R9.2` (77.20, 27.35) 底层
- `R10.1` (79.33, 27.35) 底层
- `U2.15` (89.75, 30.02) 底层
现有铜 2.1 mm
## $1N156(2 只脚)— R11.1 U2.17 一根铜都没有
- `R11.1` (82.33, 27.35) 底层
- `U2.17` (91.00, 29.27) 底层
现有铜 0.0 mm
## EFUSE_PG(3 只脚)— U2.2 一根铜都没有
- `R15.1` (79.33, 31.35) 底层
- `U2.2` (89.75, 27.02) 底层
- `U4.16` (79.32, 20.12) 底层
现有铜 52.7 mm
## $1N165(8 只脚)— R16.1 一根铜都没有
- `R16.1` (79.33, 29.35) 底层
- `Rs1.2` (87.87, 23.04) 底层
- `U2.6` (87.75, 27.02) 底层
- `U2.5` (88.25, 27.02) 底层
- `U2.4` (88.75, 27.02) 底层
- `U2.8` (87.00, 28.27) 底层
- `U2.7` (87.00, 27.77) 底层
- `U3.10` (85.05, 19.10) 底层
现有铜 23.1 mm
+92
查看文件
@@ -0,0 +1,92 @@
# 手工布线作业单(13 条)
布线方向约定:**底层(器件面)纵向、顶层横向**;线宽 ≥ 规则下限(信号 0.15)。
## VBUS_EN(3 只脚)
- `R62.2` (59.20, 14.85) 底层
- `U4.8` (73.28, 19.47) 底层
- `U5.3` (42.45, 21.45) 底层
现有铜 0.00 mm;一根都没有,整条要画
## EFUSE_EN(4 只脚)
- `J1.A11` (42.50, 3.45) 底层
- `Q1.1` (78.55, 35.28) 底层
- `R14.1` (82.33, 31.35) 底层
- `U2.14` (89.25, 30.02) 底层
现有铜 88.19 mm;已有部分,缺的是把它们连成一片
## $1N164(3 只脚)
- `R16.2` (80.20, 29.35) 底层
- `R17.1` (82.33, 29.35) 底层
- `U2.3` (89.25, 27.02) 底层
现有铜 2.13 mm;已有部分,缺的是把它们连成一片
## USB_DPJ(2 只脚)
- `J4.6` (48.26, 19.94) 顶层
- `U7.3` (49.04, 17.75) 底层
现有铜 0.00 mm;一根都没有,整条要画
## $1N151(2 只脚)
- `R12.2` (77.20, 29.35) 底层
- `U2.19` (91.00, 28.27) 底层
现有铜 0.00 mm;一根都没有,整条要画
## SCL(4 只脚)
- `J1.B5` (36.50, 3.45) 顶层
- `R41.1` (77.83, 25.45) 底层
- `U3.5` (83.05, 14.87) 底层
- `U4.22` (79.32, 16.23) 底层
现有铜 245.61 mm;已有部分,缺的是把它们连成一片
## KEY_PWR(2 只脚)
- `R42.2` (2.30, 22.85) 底层
- `U4.4` (73.28, 16.88) 底层
现有铜 0.00 mm;一根都没有,整条要画
## INT#(2 只脚)
- `J1.B11` (42.50, 3.45) 顶层
- `U4.1` (73.28, 14.93) 底层
现有铜 7.75 mm;已有部分,缺的是把它们连成一片
## PRSNT#(2 只脚)
- `J1.A1` (32.50, 4.00) 底层
- `J1.B31` (64.50, 4.00) 顶层
现有铜 15.50 mm;已有部分,缺的是把它们连成一片
## +3V3(14 只脚)
- `C13.1` (86.38, 18.00) 底层
- `C40.1` (72.30, 25.45) 底层
- `C69.1` (67.30, 22.35) 底层
- `J1.A10` (41.50, 3.45) 底层
- `J1.A9` (40.50, 3.45) 底层
- `J1.B8` (39.50, 3.45) 顶层
- `R13.1` (76.33, 31.35) 底层
- `R15.2` (80.20, 31.35) 底层
- `R18.1` (80.83, 33.35) 底层
- `R40.2` (75.70, 25.45) 底层
- `R41.2` (78.70, 25.45) 底层
- `U3.6` (83.05, 19.10) 底层
- `U4.24` (79.32, 14.93) 底层
- `U6.5` (65.30, 21.30) 底层
现有铜 131.15 mm;已有部分,缺的是把它们连成一片
## VBUS(6 只脚)
- `C67.1` (44.80, 14.85) 底层
- `J4.51` (45.73, 21.00) 顶层
- `R64.2` (45.70, 16.85) 底层
- `U5.5` (45.15, 20.50) 底层
- `U5.6` (45.15, 19.55) 底层
- `U7.5` (48.09, 15.45) 底层
现有铜 21.65 mm;已有部分,缺的是把它们连成一片
## EFUSE_FLT(3 只脚)
- `R13.2` (77.20, 31.35) 底层
- `U2.20` (91.00, 27.77) 底层
- `U4.13` (79.32, 22.07) 底层
现有铜 57.31 mm;已有部分,缺的是把它们连成一片
## EFUSE_PG(3 只脚)
- `R15.1` (79.33, 31.35) 底层
- `U2.2` (89.75, 27.02) 底层
- `U4.16` (79.32, 20.12) 底层
现有铜 49.57 mm;已有部分,缺的是把它们连成一片
二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 440 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 282 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 281 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 264 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 284 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 272 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 595 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 522 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 608 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 97 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 134 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 243 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 366 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 468 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 482 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 243 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 138 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 141 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 188 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 474 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 319 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 592 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 404 KiB

二进制
查看文件
二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 524 KiB

二进制
查看文件
二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 734 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 426 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 426 KiB

二进制
查看文件
二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 273 KiB

二进制
查看文件
二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 413 KiB

二进制
查看文件
二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 423 KiB

二进制
查看文件
二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 392 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 302 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 379 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 270 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 422 KiB

二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 350 KiB

二进制
查看文件
二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 458 KiB

二进制
查看文件
二进制文件未显示。

之后

宽度:  |  高度:  |  大小: 372 KiB

某些文件未显示,因为此 diff 中更改的文件太多 显示更多