R2:底板 / 刀片布局布线脚本、快照、截图与规划文档更新(含外壳、打印件、布线归档)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
这个提交包含在:
zhou yang
2026-10-04 19:24:45 +08:00
共同撰写人 Claude Opus 5.5
父节点 cd01640c40
当前提交 cf7cc9cf90
共修改 759 个文件,包含 76962 行新增和 5299 行删除
@@ -0,0 +1,494 @@
{
"C1": {
"n": 2,
"w": 3.41,
"h": 1.35,
"fp": "C0805"
},
"C2": {
"n": 2,
"w": 3.41,
"h": 1.35,
"fp": "C0805"
},
"C3": {
"n": 2,
"w": 3.41,
"h": 1.35,
"fp": "C0805"
},
"C4": {
"n": 2,
"w": 1.34,
"h": 0.54,
"fp": "C0402"
},
"C5": {
"n": 2,
"w": 1.34,
"h": 0.54,
"fp": "C0402"
},
"C6": {
"n": 2,
"w": 4.67,
"h": 1.73,
"fp": "C1206"
},
"C7": {
"n": 2,
"w": 4.67,
"h": 1.73,
"fp": "C1206"
},
"C8": {
"n": 2,
"w": 1.34,
"h": 0.54,
"fp": "C0402"
},
"C9": {
"n": 2,
"w": 1.34,
"h": 0.54,
"fp": "C0402"
},
"C10": {
"n": 2,
"w": 1.88,
"h": 0.54,
"fp": "C0402_NEW"
},
"C11": {
"n": 2,
"w": 1.34,
"h": 0.54,
"fp": "C0402"
},
"C12": {
"n": 2,
"w": 1.34,
"h": 0.54,
"fp": "C0402"
},
"C13": {
"n": 2,
"w": 1.34,
"h": 0.54,
"fp": "C0402"
},
"C14": {
"n": 2,
"w": 1.88,
"h": 0.54,
"fp": "C0402_NEW"
},
"C40": {
"n": 2,
"w": 1.34,
"h": 0.54,
"fp": "C0402"
},
"C60": {
"n": 2,
"w": 12.9,
"h": 1.65,
"fp": "CAP-SMD_BD10.0-L10.3-W10.3-LS11.2-FD_H10.5"
},
"C61": {
"n": 2,
"w": 4.67,
"h": 1.73,
"fp": "C1206"
},
"C62": {
"n": 2,
"w": 4.67,
"h": 1.73,
"fp": "C1206"
},
"C63": {
"n": 2,
"w": 4.67,
"h": 1.73,
"fp": "C1206"
},
"C64": {
"n": 2,
"w": 4.67,
"h": 1.73,
"fp": "C1206"
},
"C65": {
"n": 2,
"w": 1.34,
"h": 0.54,
"fp": "C0402"
},
"C66": {
"n": 2,
"w": 3.41,
"h": 1.35,
"fp": "C0805"
},
"C67": {
"n": 2,
"w": 1.34,
"h": 0.54,
"fp": "C0402"
},
"C68": {
"n": 2,
"w": 1.34,
"h": 0.54,
"fp": "C0402"
},
"C69": {
"n": 2,
"w": 1.34,
"h": 0.54,
"fp": "C0402"
},
"D1": {
"n": 2,
"w": 6.77,
"h": 2.19,
"fp": "SMB_L4.6-W3.6-LS5.3-RD"
},
"F1": {
"n": 2,
"w": 4.08,
"h": 1.73,
"fp": "F1206"
},
"J1": {
"n": 64,
"w": 33.7,
"h": 4.3,
"fp": "EDGE-64P-P1.00_PCIE-X4-GF"
},
"J3": {
"n": 4,
"w": 8.35,
"h": 5.2,
"fp": "CONN-SMD_2P-P1.25_A1251WV-S-2P"
},
"J4": {
"n": 52,
"w": 12.55,
"h": 2.6,
"fp": "BM24-50DS_HIROSE-PIN"
},
"J5": {
"n": 4,
"w": 7.1,
"h": 4,
"fp": "FPC-4P-P2.0_MI8-BAT"
},
"K1": {
"n": 1,
"w": 1.5,
"h": 1.5,
"fp": "PAD-D1.5_MI8-KEY"
},
"K2": {
"n": 1,
"w": 1.5,
"h": 1.5,
"fp": "PAD-D1.5_MI8-KEY"
},
"K3": {
"n": 1,
"w": 1.5,
"h": 1.5,
"fp": "PAD-D1.5_MI8-KEY"
},
"K4": {
"n": 1,
"w": 1.5,
"h": 1.5,
"fp": "PAD-D1.5_MI8-KEY"
},
"L1": {
"n": 2,
"w": 7.6,
"h": 3.4,
"fp": "IND-SMD_L7.1-W6.5"
},
"Q1": {
"n": 3,
"w": 3,
"h": 2.55,
"fp": "SOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR"
},
"Q2": {
"n": 3,
"w": 3,
"h": 2.55,
"fp": "SOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR"
},
"Q3": {
"n": 3,
"w": 3,
"h": 2.55,
"fp": "SOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR"
},
"Q4": {
"n": 3,
"w": 3,
"h": 2.55,
"fp": "SOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR"
},
"Q5": {
"n": 3,
"w": 3,
"h": 2.55,
"fp": "SOT-23-3_L2.9-W1.3-P1.90-LS2.4-BR"
},
"R1": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R2": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R3": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R4": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R5": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R6": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R7": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R8": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R9": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R10": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R11": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R12": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R13": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R14": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R15": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R16": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R17": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R18": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R19": {
"n": 2,
"w": 7.41,
"h": 3.46,
"fp": "R2512"
},
"R40": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R41": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R42": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R43": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R44": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R45": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R46": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R47": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R60": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R61": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R62": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R63": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R64": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"R65": {
"n": 2,
"w": 1.43,
"h": 0.54,
"fp": "R0402"
},
"Rs1": {
"n": 2,
"w": 7.42,
"h": 3.46,
"fp": "R2512"
},
"U1": {
"n": 15,
"w": 4.25,
"h": 4.25,
"fp": "VQFN-14_L3.5-W3.5-P0.50-TL-EP_TPS54622RHLT"
},
"U2": {
"n": 21,
"w": 4.9,
"h": 3.9,
"fp": "WQFN-20_L4.0-W3.0-P0.50-BL-EP"
},
"U3": {
"n": 10,
"w": 2.28,
"h": 5.84,
"fp": "MSOP-10_L3.0-W3.0-P0.50-LS5.0-BL"
},
"U4": {
"n": 24,
"w": 7.55,
"h": 7.7,
"fp": "TSSOP-24_L7.8-W4.4-P0.65-LS6.4-BL"
},
"U5": {
"n": 6,
"w": 3.8,
"h": 2.5,
"fp": "SOT-23-6_L2.9-W1.6-P0.95-LS2.8-BR"
},
"U6": {
"n": 6,
"w": 3.8,
"h": 2.5,
"fp": "SOT-23-6_L2.9-W1.6-P0.95-LS2.8-BR"
},
"U7": {
"n": 6,
"w": 2.43,
"h": 3.37,
"fp": "SOT-23-6_L2.9-W1.6-P0.95-LS2.8-BL"
}
}
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
@@ -0,0 +1 @@
[]
@@ -0,0 +1,306 @@
{
"C1": [
-0.001,
-0.101
],
"C10": [
0.015,
0.099
],
"C11": [
-0.091,
0.101
],
"C12": [
-0.091,
0.1
],
"C13": [
0.029,
-0.001
],
"C14": [
-0.125,
0.051
],
"C2": [
-0.001,
-0.1
],
"C3": [
-0.001,
-0.101
],
"C4": [
-0.125,
0.025
],
"C40": [
0.099,
0.03
],
"C5": [
-0.12,
-0.1
],
"C6": [
-0.125,
0.089
],
"C60": [
-0.05,
0.001
],
"C61": [
-0.125,
-0.014
],
"C62": [
-0.125,
-0.015
],
"C63": [
-0.125,
-0.015
],
"C64": [
-0.125,
-0.016
],
"C65": [
0.009,
0.05
],
"C66": [
-0.106,
-0.074
],
"C67": [
0.01,
0.049
],
"C68": [
0.01,
0.049
],
"C69": [
-0.09,
-0.1
],
"C7": [
-0.125,
-0.031
],
"C8": [
-0.125,
0.035
],
"C9": [
-0.12,
0.071
],
"D1": [
-0.05,
-0.039
],
"F1": [
0.001,
-0.019
],
"J2": [
-0.1,
-0.125
],
"J3": [
-0.024,
-0.049
],
"L1": [
0.0,
-0.026
],
"Q1": [
0.051,
0.076
],
"Q2": [
0.101,
0.075
],
"Q3": [
0.001,
0.101
],
"Q4": [
0.001,
0.101
],
"Q5": [
0.001,
-0.099
],
"R1": [
-0.125,
0.101
],
"R10": [
-0.098,
0.051
],
"R11": [
-0.098,
0.049
],
"R12": [
-0.098,
0.049
],
"R13": [
-0.099,
0.05
],
"R14": [
-0.098,
0.051
],
"R15": [
-0.098,
0.051
],
"R16": [
-0.098,
0.05
],
"R17": [
-0.098,
0.05
],
"R18": [
-0.097,
0.05
],
"R19": [
-0.04,
0.12
],
"R2": [
-0.125,
0.1
],
"R3": [
-0.125,
0.101
],
"R4": [
-0.125,
0.1
],
"R40": [
-0.048,
0.051
],
"R41": [
-0.048,
0.051
],
"R42": [
-0.125,
0.05
],
"R43": [
-0.125,
0.05
],
"R44": [
-0.125,
0.05
],
"R45": [
-0.125,
0.05
],
"R46": [
-0.125,
0.051
],
"R47": [
-0.125,
0.049
],
"R5": [
-0.125,
0.1
],
"R6": [
-0.125,
0.101
],
"R60": [
-0.047,
0.05
],
"R61": [
-0.047,
0.051
],
"R62": [
-0.049,
0.051
],
"R63": [
-0.125,
-0.1
],
"R64": [
-0.048,
0.05
],
"R65": [
-0.049,
0.05
],
"R7": [
-0.125,
0.101
],
"R8": [
-0.125,
0.099
],
"R9": [
-0.098,
0.051
],
"Rs1": [
-0.125,
-0.035
],
"U1": [
0.0,
0.1
],
"U2": [
0.036,
0.024
],
"U3": [
-0.05,
-0.124
],
"U4": [
-0.06,
0.075
],
"U5": [
0.051,
0.1
],
"U6": [
-0.049,
-0.05
],
"U7": [
-0.114,
0.017
]
}
@@ -0,0 +1,40 @@
{
"opened": true,
"seq": [
{
"top": 0,
"txt": "C66\n\t\t\t\t\n\n\t修改属性\tC66:通道ID\t$5I20\t$5I55\n\nC67\n\t\t\t\t\n\n\t修改属性\tC67:通道ID\t$5I21\t$5I56\n\n\t修改网络\tC67:1\tVBUS\tGND\n\n\t修改网络\tC67:2\tGND\tVBUS\n\nC68\n\t\t\t\t\n\n\t修改属性\tC68:通道ID\t$5I22\t$5I57\n\n\t修改网络\tC68:1\t$5N33\tGND\n\n\t修改网络\tC68:2\tGND\t$5N71\n\nJ1\n\t\t\t\t\n\n\t修改属性\tJ1:供应商编号\t\tPCIE-064-02-F-D-TH.1\n\n\t修改属性\tJ1:描述\t[object Object]\t\n\n\t修改属性\tJ1:通道ID\t$4I55\t$4I79"
},
{
"top": 188,
"txt": "C66\n\t\t\t\t\n\n\t修改属性\tC66:通道ID\t$5I20\t$5I55\n\nC67\n\t\t\t\t\n\n\t修改属性\tC67:通道ID\t$5I21\t$5I56\n\n\t修改网络\tC67:1\tVBUS\tGND\n\n\t修改网络\tC67:2\tGND\tVBUS\n\nC68\n\t\t\t\t\n\n\t修改属性\tC68:通道ID\t$5I22\t$5I57\n\n\t修改网络\tC68:1\t$5N33\tGND\n\n\t修改网络\tC68:2\tGND\t$5N71\n\nJ1\n\t\t\t\t\n\n\t修改属性\tJ1:供应商编号\t\tPCIE-064-02-F-D-TH.1\n\n\t修改属性\tJ1:描述\t[object Object]\t\n\n\t修改属性\tJ1:通道ID\t$4I55\t$4I79\n\n\t修改属性\tJ1:提醒\t刀片板边金手指,不采购;封装待建(M4)\t\n\n\t修改属性\tJ1:加入BOM\tyes\tno\n\nJ2\n\t\t\t\t\n\n\t移除元件\tJ2\tJ2\t\n\nJ4\n\t\t\t\t\n\n\t修改属性\tJ4:通道ID\t$5I14\t$5I49\n\nR62\n\t\t\t\t\n\n\t修改属性\tR62:通道ID\t$5I17\t$5I52\n\n\t修改网络\tR62:1\tGND\tVBUS_EN\n\n\t修改网络\tR62:2\tVBUS_EN\tGND\n\nR64\n\t\t\t\t\n\n\t修改属性\tR64:通道ID\t$5I18\t$5I53\n\n\t修改网络\tR64:1\tCC1\tVBUS\n\n\t修改网络\tR64:2\tVBUS\tCC1\n\nR65\n\t\t\t\t\n\n\t修改属性\tR65:通道ID\t$5I19\t$5I54\n\n\t修改网络\tR65:1\tGND\tUSB_THERM\n\n\t修改网络\tR65:2\tUSB_THERM\tGND"
},
{
"top": 376,
"txt": "\t修改网络\tC67:2\tGND\tVBUS\n\nC68\n\t\t\t\t\n\n\t修改属性\tC68:通道ID\t$5I22\t$5I57\n\n\t修改网络\tC68:1\t$5N33\tGND\n\n\t修改网络\tC68:2\tGND\t$5N71\n\nJ1\n\t\t\t\t\n\n\t修改属性\tJ1:供应商编号\t\tPCIE-064-02-F-D-TH.1\n\n\t修改属性\tJ1:描述\t[object Object]\t\n\n\t修改属性\tJ1:通道ID\t$4I55\t$4I79\n\n\t修改属性\tJ1:提醒\t刀片板边金手指,不采购;封装待建(M4)\t\n\n\t修改属性\tJ1:加入BOM\tyes\tno\n\nJ2\n\t\t\t\t\n\n\t移除元件\tJ2\tJ2\t\n\nJ4\n\t\t\t\t\n\n\t修改属性\tJ4:通道ID\t$5I14\t$5I49\n\nR62\n\t\t\t\t\n\n\t修改属性\tR62:通道ID\t$5I17\t$5I52\n\n\t修改网络\tR62:1\tGND\tVBUS_EN\n\n\t修改网络\tR62:2\tVBUS_EN\tGND\n\nR64\n\t\t\t\t\n\n\t修改属性\tR64:通道ID\t$5I18\t$5I53\n\n\t修改网络\tR64:1\tCC1\tVBUS\n\n\t修改网络\tR64:2\tVBUS\tCC1\n\nR65\n\t\t\t\t\n\n\t修改属性\tR65:通道ID\t$5I19\t$5I54\n\n\t修改网络\tR65:1\tGND\tUSB_THERM\n\n\t修改网络\tR65:2\tUSB_THERM\tGND\n\nU5\n\t\t\t\t\n\n\t修改属性\tU5:通道ID\t$5I16\t$5I51\n\n\t修改网络\tU5:4\t$5N33\t$5N71\n\nU7\n\t\t\t\t\n\n\t修改属性\tU7:通道ID\t$5I15\t$5I50\n\n\t修改网络\tU7:1\tUSB_DMJ\tUSB_DM\n\n\t修改网络\tU7:3\tUSB_DPJ\tUSB_DP\n\n\t修改网络\tU7:4\tUSB_DP\tUSB_DPJ"
},
{
"top": 564,
"txt": "\t修改属性\tJ1:描述\t[object Object]\t\n\n\t修改属性\tJ1:通道ID\t$4I55\t$4I79\n\n\t修改属性\tJ1:提醒\t刀片板边金手指,不采购;封装待建(M4)\t\n\n\t修改属性\tJ1:加入BOM\tyes\tno\n\nJ2\n\t\t\t\t\n\n\t移除元件\tJ2\tJ2\t\n\nJ4\n\t\t\t\t\n\n\t修改属性\tJ4:通道ID\t$5I14\t$5I49\n\nR62\n\t\t\t\t\n\n\t修改属性\tR62:通道ID\t$5I17\t$5I52\n\n\t修改网络\tR62:1\tGND\tVBUS_EN\n\n\t修改网络\tR62:2\tVBUS_EN\tGND\n\nR64\n\t\t\t\t\n\n\t修改属性\tR64:通道ID\t$5I18\t$5I53\n\n\t修改网络\tR64:1\tCC1\tVBUS\n\n\t修改网络\tR64:2\tVBUS\tCC1\n\nR65\n\t\t\t\t\n\n\t修改属性\tR65:通道ID\t$5I19\t$5I54\n\n\t修改网络\tR65:1\tGND\tUSB_THERM\n\n\t修改网络\tR65:2\tUSB_THERM\tGND\n\nU5\n\t\t\t\t\n\n\t修改属性\tU5:通道ID\t$5I16\t$5I51\n\n\t修改网络\tU5:4\t$5N33\t$5N71\n\nU7\n\t\t\t\t\n\n\t修改属性\tU7:通道ID\t$5I15\t$5I50\n\n\t修改网络\tU7:1\tUSB_DMJ\tUSB_DM\n\n\t修改网络\tU7:3\tUSB_DPJ\tUSB_DP\n\n\t修改网络\tU7:4\tUSB_DP\tUSB_DPJ\n\n\t修改网络\tU7:6\tUSB_DM\tUSB_DMJ"
},
{
"top": 650,
"txt": "J2\n\t\t\t\t\n\n\t移除元件\tJ2\tJ2\t\n\nJ4\n\t\t\t\t\n\n\t修改属性\tJ4:通道ID\t$5I14\t$5I49\n\nR62\n\t\t\t\t\n\n\t修改属性\tR62:通道ID\t$5I17\t$5I52\n\n\t修改网络\tR62:1\tGND\tVBUS_EN\n\n\t修改网络\tR62:2\tVBUS_EN\tGND\n\nR64\n\t\t\t\t\n\n\t修改属性\tR64:通道ID\t$5I18\t$5I53\n\n\t修改网络\tR64:1\tCC1\tVBUS\n\n\t修改网络\tR64:2\tVBUS\tCC1\n\nR65\n\t\t\t\t\n\n\t修改属性\tR65:通道ID\t$5I19\t$5I54\n\n\t修改网络\tR65:1\tGND\tUSB_THERM\n\n\t修改网络\tR65:2\tUSB_THERM\tGND\n\nU5\n\t\t\t\t\n\n\t修改属性\tU5:通道ID\t$5I16\t$5I51\n\n\t修改网络\tU5:4\t$5N33\t$5N71\n\nU7\n\t\t\t\t\n\n\t修改属性\tU7:通道ID\t$5I15\t$5I50\n\n\t修改网络\tU7:1\tUSB_DMJ\tUSB_DM\n\n\t修改网络\tU7:3\tUSB_DPJ\tUSB_DP\n\n\t修改网络\tU7:4\tUSB_DP\tUSB_DPJ\n\n\t修改网络\tU7:6\tUSB_DM\tUSB_DMJ"
}
],
"buttons": [
"刷新",
"新增",
"报告错误",
"批量操作",
"弹出窗口",
"放置",
"S",
"F",
"P",
"3D",
"导出报告",
"应用修改",
"取消"
]
}
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
@@ -0,0 +1,412 @@
{
"K1": [
63.87,
50.5,
0
],
"K2": [
65.17,
49.29,
0
],
"K3": [
65.1,
51.92,
0
],
"K4": [
66.54,
50.67,
0
],
"J5": [
44.75,
52.749,
180
],
"J4": [
15.0,
51.75,
180
],
"C60": [
31.79,
61.501,
0
],
"J3": [
20.125,
63.451,
0
],
"J1": [
45.0,
0.051,
0
],
"U1": [
80.0,
27.6,
90
],
"C4": [
80.0,
24.525,
0
],
"C1": [
80.0,
22.9,
0
],
"C2": [
80.0,
20.9,
0
],
"C3": [
80.0,
18.9,
0
],
"F1": [
80.0,
15.445,
90
],
"L1": [
80.249,
34.951,
90
],
"C6": [
80.125,
40.251,
180
],
"C7": [
80.25,
42.72,
180
],
"C8": [
80.25,
44.535,
180
],
"U2": [
80.0,
49.5,
0
],
"Rs1": [
84.075,
55.5,
180
],
"U3": [
85.0,
61.61,
0
],
"C5": [
85.331,
34.001,
180
],
"C9": [
76.581,
29.249,
180
],
"D1": [
84.251,
24.375,
270
],
"U4": [
60.075,
37.977,
0
],
"R3": [
83.05,
29.7,
270
],
"C13": [
82.081,
60.499,
180
],
"U7": [
18.75,
47.352,
180
],
"Q3": [
64.59,
54.76,
270
],
"Q4": [
68.001,
54.751,
270
],
"Q5": [
64.501,
58.499,
270
],
"C40": [
54.999,
32.579,
270
],
"R40": [
57.0,
32.566,
270
],
"R41": [
59.249,
32.566,
270
],
"R19": [
42.066,
45.249,
0
],
"Q2": [
49.501,
45.249,
0
],
"C14": [
75.015,
49.25,
180
],
"R11": [
75.316,
46.751,
180
],
"R12": [
75.318,
51.75,
0
],
"R13": [
75.317,
54.251,
0
],
"U6": [
12.6,
62.501,
0
],
"R63": [
11.317,
66.0,
0
],
"C69": [
7.829,
62.499,
0
],
"Q1": [
72.251,
43.25,
0
],
"R10": [
71.816,
46.75,
180
],
"R14": [
71.316,
49.251,
180
],
"R15": [
71.816,
51.749,
180
],
"R16": [
75.318,
56.75,
0
],
"R17": [
71.816,
54.251,
180
],
"R18": [
70.817,
39.75,
0
],
"R9": [
74.316,
39.751,
180
],
"C10": [
70.79,
15.501,
0
],
"R1": [
72.818,
27.001,
0
],
"R2": [
75.818,
26.499,
180
],
"R4": [
74.816,
35.001,
0
],
"R5": [
74.818,
31.0,
180
],
"R6": [
72.817,
24.501,
180
],
"R7": [
75.816,
24.499,
180
],
"R8": [
71.066,
13.5,
180
],
"C11": [
73.831,
29.0,
180
],
"C12": [
74.329,
20.999,
0
],
"R42": [
57.568,
50.25,
0
],
"R43": [
61.068,
54.249,
0
],
"R44": [
58.568,
47.749,
0
],
"R45": [
61.068,
50.25,
0
],
"R46": [
62.066,
47.749,
180
],
"R47": [
65.568,
61.75,
0
],
"C61": [
30.11,
40.251,
0
],
"C62": [
30.11,
44.249,
0
],
"C63": [
30.11,
48.251,
0
],
"C64": [
32.11,
52.25,
0
],
"R60": [
38.068,
50.249,
0
],
"R61": [
38.068,
52.751,
0
],
"C65": [
35.079,
39.75,
0
],
"U5": [
13.85,
39.749,
0
],
"C66": [
13.999,
43.5,
0
],
"R62": [
9.068,
41.249,
180
],
"R64": [
14.066,
46.749,
180
],
"R65": [
10.568,
47.751,
180
],
"C67": [
9.581,
43.751,
0
],
"C68": [
9.081,
38.75,
0
]
}
@@ -0,0 +1,412 @@
{
"K1": [
63.87,
50.5,
0
],
"K2": [
65.17,
49.29,
0
],
"K3": [
65.1,
51.92,
0
],
"K4": [
66.54,
50.67,
0
],
"J5": [
44.75,
52.749,
180
],
"J4": [
15.0,
51.75,
180
],
"C60": [
31.79,
61.501,
0
],
"J3": [
20.125,
63.451,
0
],
"J1": [
45.0,
0.051,
0
],
"U1": [
80.0,
27.6,
90
],
"C4": [
80.0,
24.525,
0
],
"C1": [
80.0,
22.9,
0
],
"C2": [
80.0,
20.9,
0
],
"C3": [
80.0,
18.9,
0
],
"F1": [
80.0,
15.445,
90
],
"L1": [
80.249,
34.951,
90
],
"C6": [
80.125,
40.251,
180
],
"C7": [
80.25,
42.72,
180
],
"C8": [
80.25,
44.535,
180
],
"U2": [
80.0,
49.5,
0
],
"Rs1": [
84.075,
55.5,
180
],
"U3": [
85.0,
61.61,
0
],
"C5": [
85.331,
34.001,
180
],
"C9": [
76.581,
29.249,
180
],
"D1": [
84.251,
24.375,
270
],
"U4": [
60.075,
37.977,
0
],
"R3": [
83.05,
29.7,
270
],
"C13": [
82.081,
60.499,
180
],
"U7": [
18.75,
47.352,
180
],
"Q3": [
64.59,
54.76,
270
],
"Q4": [
68.001,
54.751,
270
],
"Q5": [
64.501,
58.499,
270
],
"C40": [
54.999,
32.579,
270
],
"R40": [
57.0,
32.566,
270
],
"R41": [
59.249,
32.566,
270
],
"R19": [
42.066,
45.249,
0
],
"Q2": [
49.501,
45.249,
0
],
"C14": [
75.015,
49.25,
180
],
"R11": [
75.316,
46.751,
180
],
"R12": [
75.318,
51.75,
0
],
"R13": [
75.317,
54.251,
0
],
"U6": [
12.6,
62.501,
0
],
"R63": [
11.317,
66.0,
0
],
"C69": [
7.829,
62.499,
0
],
"Q1": [
72.251,
43.25,
0
],
"R10": [
71.816,
46.75,
180
],
"R14": [
71.316,
49.251,
180
],
"R15": [
71.816,
51.749,
180
],
"R16": [
75.318,
56.75,
0
],
"R17": [
71.816,
54.251,
180
],
"R18": [
70.817,
39.75,
0
],
"R9": [
74.316,
39.751,
180
],
"C10": [
70.79,
15.501,
0
],
"R1": [
72.818,
27.001,
0
],
"R2": [
75.818,
26.499,
180
],
"R4": [
74.816,
35.001,
0
],
"R5": [
74.818,
31.0,
180
],
"R6": [
72.817,
24.501,
180
],
"R7": [
75.816,
24.499,
180
],
"R8": [
71.066,
13.5,
180
],
"C11": [
73.831,
29.0,
180
],
"C12": [
74.329,
20.999,
0
],
"R42": [
57.568,
50.25,
0
],
"R43": [
61.068,
54.249,
0
],
"R44": [
58.568,
47.749,
0
],
"R45": [
61.068,
50.25,
0
],
"R46": [
62.066,
47.749,
180
],
"R47": [
65.568,
61.75,
0
],
"C61": [
30.11,
40.251,
0
],
"C62": [
30.11,
44.249,
0
],
"C63": [
30.11,
48.251,
0
],
"C64": [
32.11,
52.25,
0
],
"R60": [
38.068,
50.249,
0
],
"R61": [
38.068,
52.751,
0
],
"C65": [
35.079,
39.75,
0
],
"U5": [
13.85,
39.749,
0
],
"C66": [
13.999,
43.5,
0
],
"R62": [
9.068,
41.249,
180
],
"R64": [
14.066,
46.749,
180
],
"R65": [
10.568,
47.751,
180
],
"C67": [
9.581,
43.751,
0
],
"C68": [
9.081,
38.75,
0
]
}
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
文件差异因一行或多行过长而隐藏
@@ -0,0 +1,569 @@
{
"C60": {
"x": 31.79,
"y": 55.2795,
"rot": 0,
"id": "f62512fd3811f468e24",
"lay": 4
},
"U4": {
"x": 64.9337,
"y": 37.977,
"rot": 0,
"id": "c4fc99e0380ef00ce86",
"lay": 4
},
"L1": {
"x": 80.249,
"y": 30.4295,
"rot": 0,
"id": "b64072e208172ffee17",
"lay": 4
},
"J4": {
"x": 7.5413,
"y": 51.75,
"rot": 0,
"id": "759cca67d59ff93fee0",
"lay": 4
},
"J3": {
"x": 25.1587,
"y": 63.451,
"rot": 0,
"id": "1839216a7d8c52e3e20",
"lay": 4
},
"J5": {
"x": 44.75,
"y": 55.4705,
"rot": 0,
"id": "58d3d140a9889a29ee0",
"lay": 4
},
"Rs1": {
"x": 84.075,
"y": 53.0485,
"rot": 0,
"id": "b70b348611220dd0e16",
"lay": 4
},
"R19": {
"x": 37.148,
"y": 45.249,
"rot": 0,
"id": "ff34f2e9f58cc199e16",
"lay": 4
},
"U2": {
"x": 80.0,
"y": 46.8285,
"rot": 0,
"id": "3e8d87b8e46306b9e78",
"lay": 4
},
"U1": {
"x": 77.8902,
"y": 25.4902,
"rot": 0,
"id": "b219a43d0c29f006e44",
"lay": 4
},
"D1": {
"x": 84.251,
"y": 28.4815,
"rot": 0,
"id": "bb4bedde23c2a269e18",
"lay": 4
},
"U3": {
"x": 86.9987,
"y": 61.61,
"rot": 0,
"id": "02c49cbc938e3a8de41",
"lay": 4
},
"U5": {
"x": 13.85,
"y": 41.7205,
"rot": 0,
"id": "e91aa9fd4c0d5f0be26",
"lay": 4
},
"U6": {
"x": 12.6,
"y": 64.4725,
"rot": 0,
"id": "d427d74d26cc5348e26",
"lay": 4
},
"U7": {
"x": 18.75,
"y": 49.7585,
"rot": 0,
"id": "1956c12c0df66abfe26",
"lay": 4
},
"C6": {
"x": 80.125,
"y": 41.8375,
"rot": 0,
"id": "9b491b8b92cb1604e20",
"lay": 4
},
"C7": {
"x": 83.4437,
"y": 42.72,
"rot": 0,
"id": "83b683092fa9b45fe20",
"lay": 4
},
"C61": {
"x": 30.11,
"y": 41.8375,
"rot": 0,
"id": "192f9d0a1784ce11e20",
"lay": 4
},
"C62": {
"x": 30.11,
"y": 45.8355,
"rot": 0,
"id": "8a3bf7098cd002c5e20",
"lay": 4
},
"C63": {
"x": 30.11,
"y": 49.8375,
"rot": 0,
"id": "7d2173f1676b7504e20",
"lay": 4
},
"C64": {
"x": 32.11,
"y": 53.8365,
"rot": 0,
"id": "4114c6b5663a6629e20",
"lay": 4
},
"Q1": {
"x": 74.6097,
"y": 43.25,
"rot": 0,
"id": "a69c073558c3d2c2e19",
"lay": 4
},
"Q2": {
"x": 49.501,
"y": 47.2455,
"rot": 0,
"id": "4a28f4395bd9c7b0e19",
"lay": 4
},
"Q3": {
"x": 64.59,
"y": 52.5385,
"rot": 0,
"id": "4ae5ce02be91bad3e19",
"lay": 4
},
"Q4": {
"x": 68.001,
"y": 56.9725,
"rot": 0,
"id": "1d3fe88caec51459e19",
"lay": 4
},
"Q5": {
"x": 66.0097,
"y": 60.0077,
"rot": 0,
"id": "a1b4c007fb4fe204e19",
"lay": 4
},
"F1": {
"x": 81.7237,
"y": 15.445,
"rot": 0,
"id": "7859eb6ce7dd856fe16",
"lay": 4
},
"C1": {
"x": 82.4265,
"y": 22.9,
"rot": 90,
"id": "384b1a1b43a6d638e20",
"lay": 4
},
"C2": {
"x": 82.4265,
"y": 20.9,
"rot": 90,
"id": "bd524f2ba5a76257e20",
"lay": 4
},
"C3": {
"x": 82.5637,
"y": 18.9,
"rot": 0,
"id": "f4b7236d526aa3ece20",
"lay": 4
},
"C66": {
"x": 13.999,
"y": 44.8965,
"rot": 0,
"id": "3919d0660d14f1e0e20",
"lay": 4
},
"K1": {
"x": 62.2022,
"y": 52.1678,
"rot": 0,
"id": "4999cd7834049a75ee0",
"lay": 3
},
"K2": {
"x": 66.7787,
"y": 49.29,
"rot": 0,
"id": "03c8c9933ea9bc63ee0",
"lay": 3
},
"K3": {
"x": 65.1,
"y": 53.3915,
"rot": 0,
"id": "298df9dbb038d3deee0",
"lay": 3
},
"K4": {
"x": 68.0115,
"y": 50.67,
"rot": 90,
"id": "b97d3b8074a50e31ee0",
"lay": 3
},
"C10": {
"x": 68.637,
"y": 15.501,
"rot": 0,
"id": "1fe8cd059107de4ae20",
"lay": 4
},
"C14": {
"x": 75.015,
"y": 50.2715,
"rot": 0,
"id": "53b52daef2331e64e20",
"lay": 4
},
"R1": {
"x": 72.818,
"y": 28.0225,
"rot": 0,
"id": "4b2ab050ad1ed798e16",
"lay": 4
},
"R2": {
"x": 75.818,
"y": 25.4775,
"rot": 0,
"id": "e2230379851c3a29e16",
"lay": 4
},
"R3": {
"x": 83.05,
"y": 31.1365,
"rot": 0,
"id": "28d7e683dcaf4ca7e16",
"lay": 4
},
"R4": {
"x": 74.816,
"y": 36.0225,
"rot": 0,
"id": "d0e66eb0bf3a05c2e16",
"lay": 4
},
"R5": {
"x": 74.818,
"y": 29.9785,
"rot": 0,
"id": "1196695266b7193be16",
"lay": 4
},
"R6": {
"x": 73.9297,
"y": 25.6137,
"rot": 0,
"id": "eb7196bb67c80c92e16",
"lay": 4
},
"R7": {
"x": 75.816,
"y": 23.4775,
"rot": 0,
"id": "6150f809bb1fb84fe16",
"lay": 4
},
"R8": {
"x": 71.066,
"y": 12.4785,
"rot": 0,
"id": "21c9d7fbc7e05d6de16",
"lay": 4
},
"R9": {
"x": 74.316,
"y": 41.2725,
"rot": 0,
"id": "d446937ed3074fa0e16",
"lay": 4
},
"R10": {
"x": 71.816,
"y": 47.7715,
"rot": 0,
"id": "cb21cc0a86c38d5de16",
"lay": 4
},
"R11": {
"x": 75.316,
"y": 47.7725,
"rot": 0,
"id": "b92d024b248e50e6e16",
"lay": 4
},
"R12": {
"x": 77.2116,
"y": 53.6436,
"rot": 0,
"id": "487c9266d64c0e89e16",
"lay": 4
},
"R13": {
"x": 75.317,
"y": 55.2725,
"rot": 0,
"id": "0a5e8067803191b3e16",
"lay": 4
},
"R14": {
"x": 71.316,
"y": 50.5225,
"rot": 0,
"id": "a06ae7ea42f7727ce16",
"lay": 4
},
"R15": {
"x": 71.816,
"y": 52.7705,
"rot": 0,
"id": "a5de62884b2432eee16",
"lay": 4
},
"R16": {
"x": 77.246,
"y": 56.75,
"rot": 0,
"id": "f43f8c07ac28b805e16",
"lay": 4
},
"R17": {
"x": 69.9224,
"y": 56.1446,
"rot": 0,
"id": "509318cb30386072e16",
"lay": 4
},
"R18": {
"x": 69.2769,
"y": 41.2901,
"rot": 0,
"id": "fdf85969d9d63e75e16",
"lay": 4
},
"R40": {
"x": 57.0,
"y": 31.1295,
"rot": 0,
"id": "28b4fe86eeacdcb6e16",
"lay": 4
},
"R41": {
"x": 59.249,
"y": 31.1295,
"rot": 0,
"id": "d0eb88459e2ace58e16",
"lay": 4
},
"R42": {
"x": 57.568,
"y": 51.2715,
"rot": 0,
"id": "08e233ebf912422de16",
"lay": 4
},
"R43": {
"x": 61.068,
"y": 53.2275,
"rot": 0,
"id": "abbad8a256c9afb0e16",
"lay": 4
},
"R44": {
"x": 60.2848,
"y": 46.0322,
"rot": 0,
"id": "60271166dc19d071e16",
"lay": 4
},
"R45": {
"x": 62.4313,
"y": 51.6133,
"rot": 0,
"id": "0a0499e391145469e16",
"lay": 4
},
"R46": {
"x": 63.5025,
"y": 47.749,
"rot": 90,
"id": "bc8c3c1a747eebaee16",
"lay": 4
},
"R47": {
"x": 65.568,
"y": 62.7715,
"rot": 0,
"id": "9171e7d5c466e836e16",
"lay": 4
},
"R60": {
"x": 38.068,
"y": 49.2275,
"rot": 0,
"id": "18770cb0df6028dee16",
"lay": 4
},
"R61": {
"x": 38.068,
"y": 53.7725,
"rot": 0,
"id": "16b8bb1b09391368e16",
"lay": 4
},
"R62": {
"x": 7.14,
"y": 41.249,
"rot": 0,
"id": "f886c01969678390e16",
"lay": 4
},
"R63": {
"x": 9.7769,
"y": 64.4599,
"rot": 0,
"id": "04a5b907b5049b23e16",
"lay": 4
},
"R64": {
"x": 12.138,
"y": 46.749,
"rot": 0,
"id": "c7be7917ff49bc40e16",
"lay": 4
},
"R65": {
"x": 9.1315,
"y": 47.751,
"rot": 90,
"id": "d62fcaae03751ba3e16",
"lay": 4
},
"C4": {
"x": 81.5287,
"y": 24.525,
"rot": 0,
"id": "29b5921ba6d580fbe18",
"lay": 4
},
"C5": {
"x": 85.331,
"y": 35.0225,
"rot": 0,
"id": "8c1a4ab69c778519e18",
"lay": 4
},
"C8": {
"x": 80.25,
"y": 45.5565,
"rot": 0,
"id": "a958fdc605987214e18",
"lay": 4
},
"C9": {
"x": 76.581,
"y": 30.2705,
"rot": 0,
"id": "a59f591511e1e568e18",
"lay": 4
},
"C11": {
"x": 72.4995,
"y": 30.3315,
"rot": 0,
"id": "5016665d8c988382e18",
"lay": 4
},
"C12": {
"x": 74.329,
"y": 22.0205,
"rot": 0,
"id": "ee633a182703d105e18",
"lay": 4
},
"C13": {
"x": 82.081,
"y": 61.5205,
"rot": 0,
"id": "5e94ec96e3605532e18",
"lay": 4
},
"C40": {
"x": 54.999,
"y": 33.9705,
"rot": 0,
"id": "e821d1c2196b9f22e18",
"lay": 4
},
"C65": {
"x": 35.079,
"y": 40.7715,
"rot": 0,
"id": "e742e2f1edb4fb1ae18",
"lay": 4
},
"C67": {
"x": 9.581,
"y": 44.7725,
"rot": 0,
"id": "75c1d7695bb273f4e18",
"lay": 4
},
"C68": {
"x": 9.081,
"y": 39.7715,
"rot": 0,
"id": "ddc854a3a69e0fc9e18",
"lay": 4
},
"C69": {
"x": 7.829,
"y": 61.4775,
"rot": 0,
"id": "fd631f5cd4c8533ee18",
"lay": 4
}
}
文件差异因一行或多行过长而隐藏
@@ -0,0 +1 @@
[[2.5, 13.0], [7.5, 13.0], [12.5, 13.0], [17.5, 13.0], [22.5, 13.0], [67.5, 13.0], [77.5, 13.0], [82.5, 13.0], [87.5, 13.0], [2.5, 18.0], [7.5, 18.0], [12.5, 18.0], [17.5, 18.0], [22.5, 18.0], [27.5, 18.0], [42.5, 18.0], [52.5, 18.0], [57.5, 18.0], [62.5, 18.0], [87.5, 18.0], [7.5, 23.0], [12.5, 23.0], [17.5, 23.0], [22.5, 23.0], [27.5, 23.0], [42.5, 23.0], [52.5, 23.0], [57.5, 23.0], [62.5, 23.0], [67.5, 23.0], [87.5, 23.0], [7.5, 28.0], [12.5, 28.0], [17.5, 28.0], [22.5, 28.0], [42.5, 28.0], [52.5, 28.0], [57.5, 28.0], [62.5, 28.0], [87.5, 28.0], [2.5, 33.0], [7.5, 33.0], [12.5, 33.0], [17.5, 33.0], [27.5, 33.0], [87.5, 33.0], [2.5, 38.0], [57.5, 38.0], [87.5, 38.0], [2.5, 43.0], [7.5, 43.0], [22.5, 43.0], [42.5, 43.0], [47.5, 43.0], [87.5, 43.0], [2.5, 48.0], [7.5, 48.0], [42.5, 48.0], [47.5, 48.0], [87.5, 48.0], [2.5, 53.0], [7.5, 53.0], [22.5, 53.0], [57.5, 53.0], [2.5, 58.0], [27.5, 58.0], [32.5, 58.0], [37.5, 58.0], [67.5, 58.0], [72.5, 58.0], [2.5, 63.0], [87.5, 63.0]]
@@ -0,0 +1 @@
[[2.5, 13.0], [7.5, 13.0], [12.5, 13.0], [17.5, 13.0], [22.5, 13.0], [67.5, 13.0], [77.5, 13.0], [82.5, 13.0], [87.5, 13.0], [2.5, 18.0], [7.5, 18.0], [12.5, 18.0], [17.5, 18.0], [22.5, 18.0], [27.5, 18.0], [42.5, 18.0], [52.5, 18.0], [57.5, 18.0], [62.5, 18.0], [87.5, 18.0], [7.5, 23.0], [12.5, 23.0], [17.5, 23.0], [22.5, 23.0], [27.5, 23.0], [42.5, 23.0], [52.5, 23.0], [57.5, 23.0], [62.5, 23.0], [67.5, 23.0], [87.5, 23.0], [7.5, 28.0], [12.5, 28.0], [17.5, 28.0], [22.5, 28.0], [42.5, 28.0], [52.5, 28.0], [57.5, 28.0], [62.5, 28.0], [87.5, 28.0], [2.5, 33.0], [7.5, 33.0], [12.5, 33.0], [17.5, 33.0], [27.5, 33.0], [87.5, 33.0], [2.5, 38.0], [57.5, 38.0], [87.5, 38.0], [2.5, 43.0], [7.5, 43.0], [22.5, 43.0], [42.5, 43.0], [47.5, 43.0], [87.5, 43.0], [2.5, 48.0], [7.5, 48.0], [42.5, 48.0], [47.5, 48.0], [87.5, 48.0], [2.5, 53.0], [7.5, 53.0], [22.5, 53.0], [57.5, 53.0], [2.5, 58.0], [27.5, 58.0], [32.5, 58.0], [37.5, 58.0], [67.5, 58.0], [72.5, 58.0], [2.5, 63.0], [87.5, 63.0]]
@@ -0,0 +1,85 @@
{
"C1": "gge4",
"C2": "gge5",
"C3": "gge6",
"C4": "gge7",
"C5": "gge8",
"C6": "gge15",
"C7": "gge16",
"C8": "gge9",
"C9": "gge22",
"C10": "gge26",
"C11": "gge28",
"C12": "gge23",
"C13": "gge10",
"C14": "gge27",
"C40": "gge44",
"C60": "gge63",
"C61": "gge64",
"C62": "gge65",
"C63": "gge66",
"C64": "gge67",
"C65": "gge68",
"C66": "gge81",
"C67": "gge82",
"C68": "gge83",
"C69": "gge62",
"D1": "gge42",
"F1": "gge41",
"J1": "gge73",
"J2": "gge74",
"J3": "gge59",
"J4": "gge75",
"J5": "gge56",
"K1": "gge69",
"K2": "gge70",
"K3": "gge71",
"K4": "gge72",
"L1": "gge14",
"Q1": "gge36",
"Q2": "gge37",
"Q3": "gge47",
"Q4": "gge50",
"Q5": "gge53",
"R1": "gge29",
"R2": "gge39",
"R3": "gge11",
"R4": "gge17",
"R5": "gge19",
"R6": "gge24",
"R7": "gge40",
"R8": "gge25",
"R9": "gge18",
"R10": "gge33",
"R11": "gge31",
"R12": "gge20",
"R13": "gge34",
"R14": "gge12",
"R15": "gge35",
"R16": "gge32",
"R17": "gge21",
"R18": "gge13",
"R19": "gge38",
"R40": "gge45",
"R41": "gge46",
"R42": "gge48",
"R43": "gge49",
"R44": "gge51",
"R45": "gge52",
"R46": "gge54",
"R47": "gge55",
"R60": "gge57",
"R61": "gge58",
"R62": "gge78",
"R63": "gge61",
"R64": "gge79",
"R65": "gge80",
"Rs1": "gge30",
"U1": "gge1",
"U2": "gge3",
"U3": "gge2",
"U4": "gge43",
"U5": "gge77",
"U6": "gge60",
"U7": "gge76"
}
@@ -0,0 +1,634 @@
# -*- coding: utf-8 -*-
u"""R2 刀片 PCB(M4):板框 + 分区参考 + 摆位常量。
/c/Python314/python .claude/r2/pcb_blade.py # 只算 + 自检
/c/Python314/python .claude/r2/pcb_blade.py --outline # 画/重画板框(幂等)
/c/Python314/python .claude/r2/pcb_blade.py --verify # 读回校验板框 + 挖槽区域
/c/Python314/python .claude/r2/pcb_blade.py --slots # 只重画两条排线槽(layer 12 填充)
## 坐标系
**板左下角 = (0, 0),x 向右、y 向上**(EasyEDA PCB 里 y 向上为正,见方案 §22.5)。
这正好是方案 §3.1 那张分区图的坐标,图里的数可以直接抄。
⛔ **2026-09-11 用户拍板:98×76 → 90×68**(方案 §34)。
⛔⛔ **2026-09-11 晚 用户拍板:主板转 180°,两条排线槽挪到顶部**(方案 §35)。
接口边(J600 / J1401 / 侧键)朝**上**,服务带(C60 + 两条槽 + J3)在板子**顶部**,
手机远端边贴着金手指区上沿(y 11.0)。与手机绑定的一切(七个孔 / 两条槽 / J4 / J5 / K1~K4)
一律经 `phone_xy()` 从本体坐标 (u, v) 推,**不许写死、不许自己算旋转**。
```
y 68.00 ┬──────────────────────────────────┬─────────────┐
│ 服务带 12.34 尾插槽 / C60 / 电池槽 │ 功率列 19.4 │
y 55.66 ┼──── 接口边(朝上)─────────────────┤ U3/Rs1 (顶) │
│ │ eFuse │
│ 小米8 主板 67.09 × 44.66(转 180)│ L / Buck │
│ │ Cin / F1(底)│
y 11.00 ┼──────────────────────────────────┴─────────────┤
│ 金手指区 11mm(槽体 ~9mm 要让开,这一带不许放件) │
y 0.00 ┴───────────────── 板边(卡边)──────────────────┘
x 0 1.5 ─── 手机主板 ─── 68.59 70.59 ─ 功率列 ─ 90
```
## 板框的底边是**断开**的
金手指的防呆缺口画在封装 `EDGE-64P-P1.00_PCIE-X4-GF` 的板框层里(方案 §22.3),
所以这里画的底边必须在缺口口部让开一段:断点 x 由 `fp_edge.notch_mouth()` + `J1_X` 算出,
**不要手抄**。两段拼起来才是一个闭合板框;拼不上 DRC 会报开口。
## ⛔ 不用 pcb_PrimitiveArc
实测(方案 §22.4):`pcb_PrimitiveArc.create()` 在**封装编辑器里画得出但读不回、删不掉**,
在 **PCB 文档里干脆什么都不画**(返回值仍然是成功的)。⇒ 四角 R2 用折线近似,
弦高误差 < 0.01 mm。
"""
import io
import json
import math
import os
import sys
sys.stdout.reconfigure(encoding='utf-8')
HERE = os.path.dirname(os.path.abspath(__file__))
# ⚠ 顺序不能反:`.claude` 下有同名的 R1 脚本(pcb_pour / pcb_rules / pcb_place),
# 先插 .claude 再插 HERE 的话,`import pcb_pour` 会拿到 R1 那一份,
# 而且**不报错**,只在调到 R2 专有的函数时才炸。
sys.path.insert(0, os.path.dirname(HERE))
sys.path.insert(0, HERE)
from eda import run # noqa: E402
import fp_edge # noqa: E402
PCB = '3b9c4e39c8d164db' # BLADE-PCB
MIL = fp_edge.MIL
# ── 板框(mm,出处 方案 §3.1)─────────────────────────────────────────
BW, BH = 89.5, 67.0 # 2026-09-11 由 98×76 压缩;2026-09-12 深夜 90×68 → 89.5×67(用户拍板)
# ⛔ 2026-09-12 深夜这次只裁**东侧 0.5** 和**北侧 1.0**,器件 / 金手指 / 舌头一律不动:
# · 南边不能裁 —— y=0 就是金手指所在的板边(指尖在 y=1.30),从南裁等于切金手指。
# · 西边不裁 —— 保持居中就得全板西移(899 段铜 + 346 孔 + 164 件),无现成工具、
# 还会作废一批 `_blade_*.json` 缓存;用户拍板改为接受偏心(现为 **0.25**,板宽的 0.3%)。
# · **东侧为什么只裁 0.5 而不是 1.0**:卡点是 `VO_P`(4 A 主通道,宽 1.283)那条铜的
# 东沿到 **88.391**。裁到 89.0 时铜面界 88.5,而 88.391 + 0.15 = 88.541 > 88.5
# ⇒ `INA_ALERT` 的贴边竖线在 y≈54.1~54.25 处**无解,收线宽也救不了**(0.279/0.20/0.15 全试过)。
# 唯一的外层出路是少裁 0.5:铜面界 89.0 时窗口 [88.68, 88.86] 宽 0.18,够走。
# 裁到 89.0 则必须把那段 12.4 mm 挪到 L15 内层(多 2 个换层孔 + 在 GND 内层切一道缝),
# 而多裁的 0.5 mm 只多省 0.6% 面积 —— 用户拍板不拿地平面完整性换。
# · 67 零越界:北侧最高的 `J3.4` 焊盘顶在 66.400,新铜面边界 66.5 刚好让过(余 0.10)。
# 再往 66.5 走就开始撞 `J3` / `R63` / `EDL_EN`。
# ⚠ `IF_Y` 跟手机走(= PHONE_ORG[1]+PHONE_WH[1] = 55.66)、**不跟 BH**,所以改 BH 只是
# 把服务带从 12.34 压到 **11.34** —— 正好是 §34 算过的下限「C60 本体 11 + 0.34」,
# 手机投影与两条排线槽都不受影响。再压就放不下 C60。
# ⚠ 判据教训:算东侧窗口时**只看过孔会得出错误结论**。第一版漏了 `VO_P` 那条 1.283 宽的铜
# (只按它的过孔东沿 88.055 算),得出「裁 1.0 可行」;按线段实际占用逐 y 重扫才发现无解。
CORNER_R = 2.0
# ── 金手指两侧的台阶(2026-09-12 用户指出「金手指两侧没有开槽,插不进插槽」)──────────
# PCI-E 卡在金手指那一段是「凸」字形:只有舌头伸进插槽,两侧板材必须收到槽体顶面以上。
# 刀片原来是一整块 90×68 的圆角矩形,底边一条直线 ⇒ 塑料体(39 mm,两端封闭)会顶住
# x<27 / x>63 的板材,**插不到底**。
# 舌头原为 36.0(用户拍板,比金手指 33.70 两侧各多 1.15);台阶高 12.0(槽体 ~9,余 3)。
# ⛔ 2026-09-12 晚:**36.0 插不进去**。SolidWorks 量厂商 STEP 里 `PCIE-64P11L` 的
# 顶面内环(= 卡槽开口)得 **35.08**(x ±17.540,中间 0.78 是防呆键筋),
# 36.0 比它宽 **0.92**,舌头两端会压在腔的端壁上 —— 这也解释了干涉扫描里
# 「刀片底边停在塑料体顶面、插入深度 d=0」的现象。⇒ 用户拍板收到 **34.5**。
# · 金手指铜 28.15~61.85(33.70 宽)⇒ 两侧余量 1.15 → **0.40**,
# 仍大于 DRC 的板边规则 0.3(`pcb_rules` 只把 ≤0.2 的格子归一到 MINC,
# 0.254/0.3 那几格是孔/板边相关、不动),可做。
# · 对插槽开口留 (35.08−34.5)/2 = **0.29/侧**。
# ⚠ 35.08 是**厂商 STEP 的标称值、不含公差**,待卡尺实测复核(PROGRESS 待办 ⭐①)。
# 内凹角必须倒圆角:外形铣刀下不去直角,取 R1.0。
# ⚠ 台阶 12.0 > 手机主板落点 y0 = 11.0 ⇒ 主板投影底边在舌头两侧**悬空 1.0 mm**
# (左 x 1.50~27.00 / 右 x 63.00~68.59)。7 个定位孔最南 y=14.46 全在板材上、
# 主板靠 M1.6 尼龙立柱固定而不是靠边缘托住 ⇒ 无害,但 selfcheck 的分区断言要按舌头口径判。
TONGUE_X0, TONGUE_X1 = 27.75, 62.25 # 34.5 宽(2026-09-12 晚由 36.0 收窄,见上)
# ⛔ 2026-09-12 深夜 用户拍板:台阶高 **12.0 → 9.9**(「让舌头变短」)。
# `STEP_H` 是**台阶切掉的高度**:y <= STEP_H 只有舌头(x TONGUE_X0~X1)有板材,y > STEP_H 是全宽板主体。
# ⇒ 降低它 = 舌头变短、两侧原本被切掉的部分**恢复成板材** ⇒ **板材反而多 115.5 mm²**
# (两侧切掉 660.0 → 544.5),外接尺寸 89.5×67 不变。想减面积的话方向是反的,别搞错。
# ## 9.9 是下限,再低就出结构问题
# · **桥宽** = STEP_H − NOTCH_D = 9.9 − 8.3 = **1.60**:缺口从板边往上挖 8.3 深,
# 缺口顶到台阶肩部这段板材是**舌头左右两半唯一的连接**。8.31 时桥宽只有 0.01 ⇒ 等于切断
# (那个数只保证「缺口不被切开」,没保证「缺口上方还有板材」—— 定档时踩过这个坑)。
# · **肩部余量** = STEP_H − 插入深度 8.00 = **1.90**(零件本地坐标:塑料体顶面 10.000 / 槽底 2.000)。
# 8.31 时只剩 0.31,太浅。⚠ 别拿底板坐标的 z=12.33 算这个数,那是另一套坐标系。
# · **丝印** 带上沿 9.40,离肩部正好 0.5(= silk_blade.EDGE)。
# ## 实判:丝印与 J1 位号都不会被铣掉
# 丝印 8.40~9.40 完全在舌头内;J1 位号 9.56~10.70 **跨过肩部线**,但下半在舌头上、
# 上半在全宽主体上,四角都有板材。⚠ `silk_blade` 会因「离轮廓 0」多报 1 条越框 ——
# 那是判据保守(轮廓线穿过文字框),不是真悬空;J1 已在 KEEP 白名单,算法不会乱挪它。
STEP_H = 9.9
STEP_R = 1.0 # 台阶内凹角(铣刀半径,嘉立创外形最小 R0.5,取 1.0 稳)
CORNER_SEG = 8 # 每个 R2 圆角用几段折线
OUTLINE_W = 0.15
# 金手指阵列中心。**居中**:刀片 98 宽、插槽体只有 39,居中让两侧悬臂等长
# (左边是手机主板、右边是功率列,两头都不轻)。⚠ 底板的 5 个插槽必须按同一个数摆。
# ⛔ 2026-09-12 深夜:原来是 `BW / 2.0`(跟着板宽居中)。板宽 90→89 之后那会算出 44.5,
# **金手指连同舌头整体平移 0.5,全板器件与 64 根线立刻对不上**。这次是「只裁东侧、
# 器件不动」,所以把它**解耦成常数 45.0**(= 原来的 90/2,金手指绝对位置一点没变)。
# 代价是金手指不再严格居中:左 45.00 / 右 44.50,偏心 **0.25** = 板宽的 0.3%,用户已拍板接受。
# ⚠ 以后再改 BW,J1_X **不会**自动跟着走 —— 要居中就手动改这个数,并且记住那意味着全板平移。
J1_X, J1_Y, J1_ROT = 45.0, 0.0, 0
# ── 小米8 主板的落点 + 定位孔(板级事实,摆位/布线/铺铜都从这里取)────────
# 主板 67.09 × 44.66,长边水平、**接口边(v=0,J600/J1401/侧键都在上面)朝下**。
# ⛔ 2026-09-11 晚:**主板转 180°**(接口边朝上,方案 §35)。`PHONE_ORG` 仍是手机投影矩形的
# **左下角**(板坐标),但本体 (u, v) → 板 (x, y) 要经 `phone_xy()`:u 从右往左、v 从上往下。
# y 的取法:手机远端边贴金手指区上沿 11.0(原来 C60 也是贴这条线放的,同一条禁布规则);
# 接口边 = 11 + 44.66 = 55.66,上面 12.34 的服务带放 C60(本体 11)+ 两条槽 + J3。
# x 不变(1.5):H6/H7(u≈64.7)转过来落在左边,螺钉头离板边 2.2 − 1.6 = 0.6 以上。
# ⚠ SIM 卡托在 u 大的那一端(mi8_mech 注释:轮廓 70.3 含卡托 3.3)⇒ 转过来在**左边**,
# 装着卡托的话会伸出板左沿 ~1.8 mm(机箱侧壁要让)。
PHONE_ORG = (1.5, 11.0)
PHONE_WH = (67.09, 44.66)
PHONE_ROT = 180
IF_Y = round(PHONE_ORG[1] + PHONE_WH[1], 3) # 接口边(v=0)在板上的 y:55.66
def phone_xy(u, v):
u"""主板本体坐标 (u, v) → 刀片板坐标 (x, y)。**唯一的旋转实现**,别处不许自己算。
PHONE_ROT = 180:本体绕投影中心转半圈 ⇒ x = 右沿 − u,y = 上沿 − v(接口边 v=0 在上)。"""
if PHONE_ROT == 180:
return (round(PHONE_ORG[0] + PHONE_WH[0] - u, 3), round(PHONE_ORG[1] + PHONE_WH[1] - v, 3))
return (round(PHONE_ORG[0] + u, 3), round(PHONE_ORG[1] + v, 3))
# ⛔ **主板定位孔**。整个 §3.2 的高度预算 / §21.1 的槽距结论都建立在
# 「螺丝把主板压在刀片上、螺柱间隙 1.0 mm」(R1 §22.3e 的中框式焊盘)之上 ——
# 没有这几个孔,`K1~K4` 那四个裸铜盘就没有压力,整套接触方式不成立。
# 孔位是实测值(`.claude/mi8_mech.json` 的 `holes`,u/v 本体坐标)。
# 直径沿用 R1 已经定稿的一组(`pcb2_place.py`):
# 主板自己的孔 **Φ2.0**(用户卡尺)⇒ 配 **M1.6 尼龙立柱**(M2 螺杆塞 Φ2.0 没余量),
# 刀片这一侧是螺钉过孔,取 M1.6 的通孔 **Φ1.8 NPTH**。
# ⚠ R1 载板上 `H5` 没打(撞 EDL 座),**刀片上七个孔全都够得开**(实测最近焊盘 3.06 mm
# 是 H1 到 K2),所以七个全打。
HOLE_D = 1.8
HOLE_KEEP = 2.2 # 螺钉头避让(M1.6 尼龙螺钉头 Ø3.2 + 余量)
HOLE_VIA_KEEP = 1.5 # 孔到过孔/铜的最小中心距(孔半径 0.9 + 过孔半径 0.3 + 净距)
def phone_holes():
u"""{名字: (x, y)},刀片坐标。"""
m = json.load(io.open(os.path.join(os.path.dirname(HERE), 'mi8_mech.json'),
encoding='utf-8'))
return {k: phone_xy(u, v) for k, (u, v) in m['holes'].items()}
# ── 排线穿板槽(2026-09-09 用户拍板:屏蔽窗去掉、器件全到 B 面、给两条排线开槽)──
# ⛔ **屏蔽罩避让窗已删**。它是「屏蔽罩轮廓没实测所以全挖」的保守项,而 R1 §22.3e 的
# 定稿是「载板不挖空:用户实测贴合面元件全低于 1 mm」。去掉之后 L2 地平面整块、
# 板子恢复刚度,B 面手机投影 67×44 全部可放件(高度预算见 pcb_place.H_B_*)。
#
# **排线走法**(照搬 R1 `pcb2_place.SLOTS`,2026-09-06 实测排线:电池 8×38、尾插 18×55):
# J600/J1401 在主板朝上那一面 → 朝接口边(y=29)走 → 翻过板边落到刀片 A 面
# → 到接口边外侧 2.7 mm 的槽 → 穿板 → **U 形折回,贴着 B 面往 +y 走**到手机投影下方的
# `J5` / `J4`。折回是绕 x 轴翻 180°:x 不变、y 镜像 —— 而立创底层正是「先 y 镜像再转」
# (方案 §25.3),所以 `J5`/`J4` 在底层 **rot 0** 就是对的(R1 §25.16 v4 同一结论)。
# ⚠ 槽是 **layer 12 (MULTI) 的填充 = 挖槽区域**(`pcb_PrimitiveFill`),不是板框内环。
# DRC 认它(「挖槽区域 到 导线」),内层铺铜也会给它让反焊盘。
# ⚠ J1401 的 x 取设计文档 §22.3b 的**本体中心 53.58**(±0.3),不是 `mi8_mech.json`
# `contacts_up.J1401 = 48.76` —— 那是座子西端(两者差 4.82 ≈ 半本体 6.1 − 1.3)。
# TP1204/1205 两份数据吻合到 0.1,说明坐标系相同、只是 J1401 取的点不同。
# 按 53.58 定,尾插车道西沿 47.6 正好让开 H4 螺钉头 (44.9 + 2.2),与 R1 自洽。
J600_U = 23.77 # 电池座中心(两份数据一致;§22.3b 标 ±2,槽放宽到 12)
J1401_U = 53.58 # 尾插座本体中心(§22.3b,±0.3)
# 槽的 y 区间:接口边外侧 1.2~4.2,中心离边 2.7(R1 同)。**跟着接口边走**。
# ⛔ 2026-09-11 晚主板转 180°:接口边朝上 ⇒ 槽在接口边**以北**(顶部服务带里),
# 排线翻过接口边 → 穿槽 → 在 B 面 U 形折回、往 **−y** 走到 J5 / J4。
SLOT_Y = (round(IF_Y + 1.2, 3), round(IF_Y + 4.2, 3))
SLOT_RIB = 2.0 # 槽顶到板边留的肋(顶边导轨也咬在这条肋上)
SLOT_W = {'BAT': 12.0, 'TAIL': 20.0} # 排线 8 / 18,各留余量
FLEX_W = {'BAT': 8.0, 'TAIL': 18.0}
SLOT_R = 1.0 # 槽内圆角(铣刀半径,嘉立创内槽最小 R0.5)
def flex_x():
u"""两条排线的中心 x(刀片坐标)。"""
return {'BAT': phone_xy(J600_U, 0.0)[0], 'TAIL': phone_xy(J1401_U, 0.0)[0]}
def flex_slots():
u"""{名字: (x0, y0, x1, y1)},刀片坐标,铣穿。"""
out = {}
for k, cx in flex_x().items():
w = SLOT_W[k]
out[k] = (round(cx - w / 2, 3), SLOT_Y[0], round(cx + w / 2, 3), SLOT_Y[1])
return out
def mill_cuts():
u"""板上全部铣穿的内槽 [(x0,y0,x1,y1)] —— 布线 / 铺铜 / 摆位都从这里取障碍。"""
return list(flex_slots().values())
def slot_check():
u"""槽不许啃到螺钉孔避让圈,两条槽之间要留肋,槽不许进手机投影。"""
bad = []
for k, w in flex_slots().items():
for hn, (hx, hy) in phone_holes().items():
dx = max(w[0] - hx, 0.0, hx - w[2])
dy = max(w[1] - hy, 0.0, hy - w[3])
if math.hypot(dx, dy) < HOLE_KEEP:
bad.append(u'槽 %s 离螺钉孔 %s 只有 %.2f < %.1f' % (k, hn, math.hypot(dx, dy), HOLE_KEEP))
if w[1] < IF_Y:
bad.append(u'槽 %s 底 %.1f 进了手机投影(接口边 %.1f)' % (k, w[1], IF_Y))
if w[3] > BH - SLOT_RIB:
bad.append(u'槽 %s 顶 %.1f 离板边不到 %.1f 的肋' % (k, w[3], SLOT_RIB))
s = flex_slots()
a, b = sorted(s.values())
rib = b[0] - a[2]
if rib < 3.0:
bad.append(u'两槽之间只剩 %.1f mm 肋(要 >= 3)' % rib)
return bad
# 分区(只是参考矩形,画在文档层,摆位脚本从这里取)
PHONE_X1 = round(PHONE_ORG[0] + PHONE_WH[0], 3) # 手机右沿 68.59
POWER_X0 = round(PHONE_X1 + 2.0, 3) # 功率列西沿 70.59(离手机 2 mm)
ZONES = {
'finger': (TONGUE_X0, 0.0, TONGUE_X1, STEP_H), # 舌头(唯一伸进插槽的那块):不许放件
'service': (0.0, IF_Y, POWER_X0, BH), # 服务带(2026-09-11 晚起在顶部):C60 / 两条排线槽 / J3
'phone': (PHONE_ORG[0], PHONE_ORG[1], PHONE_X1, round(PHONE_ORG[1] + PHONE_WH[1], 3)),
'power': (POWER_X0, STEP_H, BW, BH), # 功率列
}
def corner(cx, cy, a0, a1, r=None):
u"""圆角折线点列(角度 deg,逆时针)。`r` 省略时用 `CORNER_R`(板四角)。
⛔⛔ 2026-09-12 晚修的 bug:这里原来**写死 `CORNER_R`**,而台阶那两个内凹角
调用时只把 `STEP_R` 用在了**弧心偏移**上 ⇒ 弧心 (TONGUE_X0−1.0, 11.0) 配半径 2.0,
弧的终点落在 x = 26.75+2.0 = **28.75** 而不是 27.75,于是舌头侧边变成一条
从 (28.75, 11.0) 斜到 (27.75, 0.0) 的**斜线** —— 舌头尖端 34.5 宽,到 y=11 处
只剩 **32.5**。几何校验、DRC 在旧尺寸下都不报(斜边最大只到 x=28.0,够不着线),
**把舌头收窄之后才显形**:x=28.50 的 +12V / PRSNT# 在 y>8.25 处被斜边切到板外。
⇒ 教训:**参数传进去了不等于用上了**;`STEP_R` 只影响弧心、不影响半径,
而这类错误画出来的形状仍然“看着像个圆角”,靠截图也看不出来。"""
rr = CORNER_R if r is None else r
pts = []
for k in range(CORNER_SEG + 1):
a = math.radians(a0 + (a1 - a0) * k / CORNER_SEG)
pts.append((cx + rr * math.cos(a), cy + rr * math.sin(a)))
return pts
def outline_pts():
u"""板框点列(逆时针,从舌头左下角起,闭合)。**凸字形**:底边只剩舌头那一段。
⚠ 相邻重合点必须去掉:圆角的首点和前一条直边的终点是同一个点,留着就会生成
一条**零长度线段**,编辑器会静默把它吃掉(下发 41 段读回 37 段),
于是「读回条数 != 期望」变成一个没完没了的假警报。这正是 CLAUDE.md 里
原理图那条「零长度导线」坑的 PCB 版。
⛔ 第一段必须仍是底边 `(TONGUE_X0, 0) -> (TONGUE_X1, 0)`:`segments()` 和
`outline_polygon()` 都按这个前提把防呆缺口插进去。
"""
r, ri, hs = CORNER_R, STEP_R, STEP_H
pts = [(TONGUE_X0, 0.0), (TONGUE_X1, 0.0)] # 舌头底边(缺口在中间断开)
pts += [(TONGUE_X1, hs - ri)]
pts += corner(TONGUE_X1 + ri, hs - ri, 180, 90, ri) # 右侧台阶内凹角(半径必须传 ri)
pts += [(BW - r, hs)]
pts += corner(BW - r, hs + r, 270, 360) # 右下外角
pts += [(BW, BH - r)]
pts += corner(BW - r, BH - r, 0, 90) # 右上
pts += [(r, BH)]
pts += corner(r, BH - r, 90, 180) # 左上
pts += [(0.0, hs + r)]
pts += corner(r, hs + r, 180, 270) # 左下外角
pts += [(TONGUE_X0 - ri, hs)]
pts += corner(TONGUE_X0 - ri, hs - ri, 90, 0, ri) # 左侧台阶内凹角(半径必须传 ri)
pts += [(TONGUE_X0, 0.0)]
out = [pts[0]]
for q in pts[1:]:
if abs(q[0] - out[-1][0]) > 1e-9 or abs(q[1] - out[-1][1]) > 1e-9:
out.append(q)
if abs(out[0][0] - out[-1][0]) > 1e-9 or abs(out[0][1] - out[-1][1]) > 1e-9:
out.append(out[0])
assert abs(out[0][0] - out[-1][0]) < 1e-9 and abs(out[0][1] - out[-1][1]) < 1e-9, u'板框点列没有闭合'
return out
def notch_break():
u"""底边要断开的区间(板坐标 mm)。"""
ml, mr = fp_edge.notch_mouth()
return round(J1_X + ml, 4), round(J1_X + mr, 4)
def segments():
u"""板框全部线段。底边在缺口口部断开。"""
pts = outline_pts()
segs = []
for a, b in zip(pts, pts[1:]):
segs.append((a[0], a[1], b[0], b[1]))
# 第一段是底边 (r,0)->(BW-r,0),切成两段
bl, br = notch_break()
x0, y0, x1, y1 = segs[0]
assert (y0, y1) == (0.0, 0.0) and x0 < bl < br < x1, u'底边不是第一段或断点跑出去了'
segs[0:1] = [(x0, 0.0, bl, 0.0), (br, 0.0, x1, 0.0)]
# ⚠ 排线槽**不在这里**:它们是 layer 12 的填充(挖槽区域),见 push_slots()
return segs
def notch_pts():
u"""缺口的点列(板坐标),**自左向右**:口部左 → 导入角 → 左壁 → 顶半圆 → 右壁 → 口部右。
几何仍然只有 `fp_edge` 一个出处,这里只做「封装坐标 + J1_X」的平移和顺序调整。
"""
hw = fp_edge.NOTCH_W / 2.0
ml, mr = notch_break()
cx = J1_X + fp_edge.NOTCH_CX
top = fp_edge.NOTCH_D - hw
pts = [(ml, 0.0), (cx - hw, fp_edge.LEAD_IN), (cx - hw, top)]
for k in range(fp_edge.ARC_SEG, -1, -1): # 半圆从左(π)走到右(0)
a = math.pi * k / fp_edge.ARC_SEG
pts.append((cx + hw * math.cos(a), top + hw * math.sin(a)))
pts += [(cx + hw, fp_edge.LEAD_IN), (mr, 0.0)]
out = [pts[0]]
for q in pts[1:]:
if abs(q[0] - out[-1][0]) > 1e-9 or abs(q[1] - out[-1][1]) > 1e-9:
out.append(q)
return out
def outline_polygon():
u"""**一条闭合折线**的完整板框点列:圆角矩形 + 底边中间的金手指防呆缺口。
⛔ 为什么不用 `segments()` 那 37 段线(2026-09-09,M7):
`pcb_PrimitiveLine` 在嘉立创里是**导线(铜)**,不是图形线 —— 选中它属性面板写的是
「导线」,还带网络 / 网络长度,`图层` 那一格 `data-value=11` 但**显示是空的**
(下拉里只有铜层,板框层不在选项里)。渲染成品红、DRC 也不报,
所以四道几何校验全过,而**客户端根本不把它当板框**:
自动布线一路弹「请先绘制板框!」。板框要用 `pcb_PrimitivePolyline`(折线)画。
"""
pts = outline_pts() # 闭合,第一段是底边 (r,0)->(BW-r,0)
bl, br = notch_break()
assert pts[0][1] == 0.0 and pts[1][1] == 0.0 and pts[0][0] < bl < br < pts[1][0], u'底边不是第一段或断点跑出去了'
return [pts[0]] + notch_pts() + list(pts[1:])
def polygon_src(pts):
u"""点列 -> pcb_MathPolygon.createPolygon 的源数组(mil,显式闭合)。"""
a = [mil(pts[0][0]), mil(pts[0][1])]
for q in pts[1:]:
a += ['L', mil(q[0]), mil(q[1])]
if pts[0] != pts[-1]:
a += ['L', mil(pts[0][0]), mil(pts[0][1])]
return a
def rrect_pts(x0, y0, x1, y1, r, seg=6):
u"""圆角矩形闭合点列。内槽必须圆角:铣刀下不去直角,嘉立创内槽最小 R0.5。"""
pts = []
for cx, cy, a0 in ((x1 - r, y0 + r, 270), (x1 - r, y1 - r, 0),
(x0 + r, y1 - r, 90), (x0 + r, y0 + r, 180)):
for k in range(seg + 1):
a = math.radians(a0 + 90.0 * k / seg)
pts.append((cx + r * math.cos(a), cy + r * math.sin(a)))
out = [pts[0]]
for q in pts[1:]:
if abs(q[0] - out[-1][0]) > 1e-9 or abs(q[1] - out[-1][1]) > 1e-9:
out.append(q)
out.append(out[0])
return out
def selfcheck():
bl, br = notch_break()
assert round(br - bl, 4) == round(fp_edge.NOTCH_W + 2 * fp_edge.LEAD_IN, 4), (bl, br)
# 金手指必须整个落在板内,且不侵占服务带
for pin in ('A1', 'A32'):
x, y, w, h = fp_edge.pad_geom(pin)
ax = J1_X + x
assert 0.5 < ax < BW - 0.5, u'%s 跑出板外: x=%.2f' % (pin, ax)
top = fp_edge.EDGE_CLR + fp_edge.LONG_L
assert top < ZONES['finger'][3], u'手指顶到服务带'
assert fp_edge.NOTCH_D < ZONES['finger'][3], u'缺口比金手指区还深'
# 分区不许互相重叠(除了 finger/service 只共边)
z = ZONES
assert z['service'][1] == z['phone'][3] == IF_Y
# ⚠ 台阶之后 finger 区只剩舌头那一块(x TONGUE_X0~TONGUE_X1)。手机投影 y0=11.0
# 低于台阶 12.0,但它在舌头正上方那一段是有板材的;两侧那 1 mm 是**有意的悬空**
# (定位孔最南 14.46 全在板材上,主板靠尼龙立柱固定)⇒ 只判「不压舌头」。
# 主板投影底边在舌头两侧的**悬空量**(有意为之,见 STEP_H 处的说明):只判别超标。
_hang = max(0.0, STEP_H - z['phone'][1])
assert _hang <= 1.5, u'主板投影底边悬空 %.2f mm 过多(台阶 %.1f vs 主板 y0 %.2f)' % (
_hang, STEP_H, z['phone'][1])
assert min(v[1] for v in phone_holes().values()) > STEP_H, u'有定位孔落进台阶切掉的区域'
assert fp_edge.EDGE_CLR + fp_edge.LONG_L < STEP_H, u'金手指比台阶还高'
assert z['phone'][2] <= z['power'][0], u'手机投影压进功率列'
assert z['service'][2] <= z['power'][0], u'服务带压进功率列'
assert z['phone'][3] <= BH, u'手机投影超出板顶'
# 台阶:金手指与防呆缺口必须整个落在舌头里,内凹角要铣得出来
_fx0 = J1_X + fp_edge.PIN_X['A1'] - fp_edge.FW / 2
_fx1 = J1_X + fp_edge.PIN_X['A32'] + fp_edge.FW / 2
assert TONGUE_X0 < _fx0 and _fx1 < TONGUE_X1, u'金手指 %.2f~%.2f 超出舌头 %.1f~%.1f' % (_fx0, _fx1, TONGUE_X0, TONGUE_X1)
_bl, _br = notch_break()
assert TONGUE_X0 < _bl < _br < TONGUE_X1, u'防呆缺口超出舌头'
assert STEP_R >= 0.5, u'台阶内凹角 R%.1f 铣刀下不去' % STEP_R
assert TONGUE_X1 - TONGUE_X0 < BW, u'舌头没窄于板宽,等于没开台阶'
bad = slot_check()
assert not bad, u'排线槽校核不过: %s' % bad
return True
def call(js, timeout=180):
r = run(js, timeout=timeout)
if not r.get('success'):
raise RuntimeError(json.dumps(r, ensure_ascii=False)[:800])
return r['result']
def mil(v):
return round(v * MIL, 4)
def push_outline():
u"""画板框:**一条闭合折线**(`pcb_PrimitivePolyline`),含金手指缺口。
顺带把层 11 上遗留的**导线**全删掉 —— M4b~M6 一直是用 `pcb_PrimitiveLine`
画的,那是铜,客户端不认(见 `outline_polygon()` 的说明)。
"""
src = polygon_src(outline_polygon())
js = ("await eda.dmt_EditorControl.openDocument('%s');" % PCB +
"await new Promise(r=>setTimeout(r,2500));" +
"const oldL = (await eda.pcb_PrimitiveLine.getAll()||[]).filter(l=>l.layer===11)"
".map(l=>l.primitiveId).filter(i=>i.length<=20);" +
"if (oldL.length) await eda.pcb_PrimitiveLine.delete(oldL);" +
"const oldP = (await eda.pcb_PrimitivePolyline.getAll()||[])"
".filter(p=>p.getState_Layer()===11).map(p=>p.getState_PrimitiveId());" +
"if (oldP.length) await eda.pcb_PrimitivePolyline.delete(oldP);" +
"const poly = await eda.pcb_MathPolygon.createPolygon(%s);" % json.dumps(src) +
"const r = await eda.pcb_PrimitivePolyline.create('', 11, poly, %s);" % mil(OUTLINE_W) +
"await eda.pcb_Document.save();" +
"return {delLine: oldL.length, delPoly: oldP.length, made: !!r,"
" now: (await eda.pcb_PrimitivePolyline.getAll()||[])"
".filter(p=>p.getState_Layer()===11).length};")
r = call(js, timeout=300)
print(u'板框折线: 建 %s(删旧导线 %d / 旧折线 %d);层 11 上现有折线 %d 条'
% (u'成功' if r['made'] else u'失败', r['delLine'], r['delPoly'], r['now']))
if not r['made'] or r['now'] != 1:
raise SystemExit(u'!! 板框没画成')
return r
PUSH_SLOTS_JS = u"""
await eda.dmt_EditorControl.openDocument('%s');
await new Promise(r=>setTimeout(r,2500));
const S = %s;
let del = 0;
for (const f of await eda.pcb_PrimitiveFill.getAll())
if (f.getState_Layer() === 12) { await eda.pcb_PrimitiveFill.delete(f.getState_PrimitiveId()); del++; }
let made = 0, fail = [];
for (const [name, src] of S) {
const poly = await eda.pcb_MathPolygon.createPolygon(src);
const r = await eda.pcb_PrimitiveFill.create(12, poly);
if (r) made++; else fail.push(name);
}
await eda.pcb_Document.save();
const now = (await eda.pcb_PrimitiveFill.getAll()).filter(f => f.getState_Layer() === 12).length;
return {del, made, fail, now};
"""
def slot_src(x0, y0, x1, y1):
u"""圆角矩形 -> pcb_MathPolygon 的点列源(mil,显式闭合)。照抄 R1 pcb2_place.slot_poly。"""
pts = rrect_pts(x0, y0, x1, y1, SLOT_R)
a = [mil(pts[0][0]), mil(pts[0][1])]
for p in pts[1:]:
a += ['L', mil(p[0]), mil(p[1])]
return a
def push_slots():
S = [[k, slot_src(*w)] for k, w in sorted(flex_slots().items())]
r = call(PUSH_SLOTS_JS % (PCB, json.dumps(S)), timeout=300)
print(u'排线槽(layer 12 挖槽区域): 建 %d / 失败 %s(删旧 %d);板上现有 %d 个'
% (r['made'], r['fail'] or u'无', r['del'], r['now']))
for k, w in sorted(flex_slots().items()):
print(u' %-5s x %.2f~%.2f y %.2f~%.2f (%.1f × %.1f,排线 %.0f 宽)'
% (k, w[0], w[2], w[1], w[3], w[2] - w[0], w[3] - w[1], FLEX_W[k]))
if r['now'] != len(S):
raise SystemExit(u'!! 槽没建全')
return r
def verify():
u"""读回板框折线,逐点和 `outline_polygon()` 对账 + 校验闭合与外接矩形。"""
r = call("await eda.dmt_EditorControl.openDocument('%s');"
"await new Promise(r=>setTimeout(r,2000));"
"const ps = (await eda.pcb_PrimitivePolyline.getAll()||[])"
".filter(p=>p.getState_Layer()===11);"
"const line11 = (await eda.pcb_PrimitiveLine.getAll()||[])"
".filter(l=>l.layer===11).length;"
"const fills = (await eda.pcb_PrimitiveFill.getAll()).filter(f=>f.getState_Layer()===12).length;"
"return {n: ps.length, poly: ps.length ? ps[0].getState_Polygon() : null,"
" line11, fills};" % PCB, timeout=180)
bad = []
if r['n'] != 1:
bad.append(u'层 11 上有 %d 条折线(应为 1 条)' % r['n'])
# ⚠ 层 11 上**允许**留 16 条导线:那是 J1 金手指封装内部的缺口线(fp_edge 画的),
# 删不掉也不必删 —— 板框已经由折线自己闭合,它们只是重复的图形。
if r['line11'] not in (0, 16):
bad.append(u'层 11 上还有 %d 条导线(只该剩 J1 封装那 16 条或 0 条)' % r['line11'])
if r['fills'] != len(flex_slots()):
bad.append(u'layer 12 挖槽区域 %d 个 != %d' % (r['fills'], len(flex_slots())))
pts = []
src = r['poly']
if isinstance(src, dict):
src = src.get('pointArr') or src.get('points') or src.get('polygon') or src
if isinstance(src, list):
i, cur = 0, None
while i < len(src):
if src[i] == 'L' or src[i] == 'l':
i += 1
continue
if isinstance(src[i], (int, float)) and i + 1 < len(src) and isinstance(src[i + 1], (int, float)):
pts.append((src[i] / MIL, src[i + 1] / MIL))
i += 2
else:
i += 1
want = outline_polygon()
if pts:
xs = [p[0] for p in pts]
ys = [p[1] for p in pts]
if abs(min(xs)) > 0.01 or abs(max(xs) - BW) > 0.01 or abs(min(ys)) > 0.01 or abs(max(ys) - BH) > 0.01:
bad.append(u'板框外接矩形 %.2f~%.2f x %.2f~%.2f != 0~%.1f x 0~%.1f'
% (min(xs), max(xs), min(ys), max(ys), BW, BH))
if abs(pts[0][0] - pts[-1][0]) > 0.01 or abs(pts[0][1] - pts[-1][1]) > 0.01:
bad.append(u'折线首尾不重合:%s vs %s' % (pts[0], pts[-1]))
if abs(len(pts) - len(want)) > 1:
bad.append(u'点数 %d != %d' % (len(pts), len(want)))
print(u'读回板框折线 1 条 / %d 点,外接 %.2f~%.2f x %.2f~%.2f;'
u'层 11 遗留导线 %d 条;挖槽区域 %d 个'
% (len(pts), min(xs), max(xs), min(ys), max(ys), r['line11'], r['fills']))
else:
bad.append(u'读不回折线点列(原始值 %s)' % str(r['poly'])[:120])
if bad:
print(u'[X] 校验不过:')
for b in bad:
print(u' ' + b)
else:
print(u'[OK] 板框是一条闭合折线(缺口在里面),外接矩形对上,挖槽区域齐')
return bad
PUSH_HOLES_JS = u"""
await eda.dmt_EditorControl.openDocument('%s');
await new Promise(r=>setTimeout(r,2500));
const H = %s, D = %s;
for (const p of await eda.pcb_PrimitivePad.getAll()) {
const num = p.getState_PadNumber();
if (typeof num === 'string' && /^H[1-7]$/.test(num))
await eda.pcb_PrimitivePad.delete(p.getState_PrimitiveId());
}
let k = 0;
for (const [name, x, y] of H) {
// layer 12 = 多层;metallization=false ⇒ NPTH。参数位序照抄 R1 pcb2_place.py
await eda.pcb_PrimitivePad.create(12, name, x, y, 0, ['ELLIPSE', D, D], '',
['ROUND', D], 0, 0, 0, false);
k++;
}
await eda.pcb_Document.save();
return {made: k, now: (await eda.pcb_PrimitivePad.getAll())
.filter(p => /^H[1-7]$/.test(String(p.padNumber))).length};
"""
def push_holes():
hs = phone_holes()
arr = [[k, round(v[0] * MIL, 4), round(v[1] * MIL, 4)] for k, v in sorted(hs.items())]
r = call(PUSH_HOLES_JS % (PCB, json.dumps(arr), round(HOLE_D * MIL, 4)), timeout=300)
print(u'主板定位孔: 下发 %d 个 / 读回 %d 个,NPTH Φ%.1f(⚠ 必须配 M1.6 尼龙立柱)'
% (r['made'], r['now'], HOLE_D))
return r
if __name__ == '__main__':
selfcheck()
if '--holes' in sys.argv:
push_holes()
for k, v in sorted(phone_holes().items()):
print(u' %-3s (%6.2f, %6.2f)' % (k, v[0], v[1]))
sys.exit(0)
bl, br = notch_break()
# ⚠ 必须 %.1f:2026-09-12 深夜板宽变成 89.5 之后,原来的 %.0f 把它印成「90」,
# 而板宽正是这次改动的主角 —— 下个会话一眼看过去会以为没改成。
print(u'刀片 %.1f x %.1f,四角 R%.1f;金手指中心 x=%.2f(偏心 %.2f)'
% (BW, BH, CORNER_R, J1_X, abs(J1_X - BW / 2.0)))
print(u'底边断点: %.2f ~ %.2f(缺口口部)' % (bl, br))
print(u'金手指跨度: x %.2f ~ %.2f,指顶 y=%.2f,缺口顶 y=%.2f'
% (J1_X + fp_edge.PIN_X['A1'] - fp_edge.FW / 2,
J1_X + fp_edge.PIN_X['A32'] + fp_edge.FW / 2,
fp_edge.EDGE_CLR + fp_edge.LONG_L, fp_edge.NOTCH_D))
for k, z in ZONES.items():
print(u' %-8s x %6.2f~%6.2f y %6.2f~%6.2f' % (k, z[0], z[2], z[1], z[3]))
for k, w in sorted(flex_slots().items()):
print(u' 槽 %-5s x %6.2f~%6.2f y %6.2f~%6.2f' % (k, w[0], w[2], w[1], w[3]))
if '--outline' in sys.argv:
push_outline()
push_slots()
verify()
elif '--slots' in sys.argv:
push_slots()
verify()
elif '--verify' in sys.argv:
verify()
+475
查看文件
@@ -0,0 +1,475 @@
# -*- coding: utf-8 -*-
u"""R2 刀片 PCB 的铺铜(M4c-3)。
/c/Python314/python .claude/r2/pcb_pour.py # 只算 + 自检
/c/Python314/python .claude/r2/pcb_pour.py --push # 清旧铜面 + 重建 + 重填
/c/Python314/python .claude/r2/pcb_pour.py --rebuild # 只重填(铜面没改,只是扇出/布线改了)
## 叠层(方案 §7.1)
| 设计 | EasyEDA | id | 内容 |
|---|---|---|---|
| L1 | Top Layer | 1 | 功率 + 信号;**D± 走这层、以 L2 为参考** |
| L2 | Inner1 | 15 | **完整地平面,不许割断** |
| L3 | Inner2 | 16 | `VO_P` 岛 + `+12V` 岛,其余 GND 底填 |
| L4 | Bottom | 2 | 控制信号 + GND 底填 |
## 三个只有这块板才有的约束
1. **金手指区不许铺外层铜**:`J1` 的 64 个指是 L1/L2 上的焊盘,间距只有 0.3 mm。
外层铜面一旦下探到 y < 指顶(5.6)+间距,就会把相邻的指连起来 —— 而且**是不是短路
要看它连到哪两根**,DRC 只报间距不报「这两根本来就该分开」。
⇒ 外层铜面一律 `y >= TOP_POUR_Y`(11.5,比缺口顶 7.45 还高一截)。
内层(L2/L3)没有指,可以一直铺到板边内缩,只要避开缺口。
2. **缺口要挖掉**:防呆缺口是真的铣穿的,四层都要让开(含 0.5 mm 内缩)。
3. **优先级是「数字小的先铺、先占地盘」**(R1 `pcb_pour.py` 头上那条已经查清的翻案)。
⇒ `VO_P` / `+12V` 两个岛给 1 / 2,GND 底填给 9。
## 三个 API 坑(全部照抄 R1 `pcb_pour.py` 的结论,别再踩一遍)
· 多边形**必须显式闭合**(末尾把首点再写一遍),否则 `create()` 一块都不建;
· `create()` **抛异常不等于失败** —— 判据只能是建完 `getAll()` 按名字(会被转成大写)核对;
· `rebuildCopperRegion()` 是坏的,**只能走 GUI**:工具 → 铺铜管理器... → 重建所有 → 确认。
判据是 `pcb_PrimitivePoured` 的数量,不是 `pcb_PrimitivePour`。
"""
import json
import math
import os
import sys
import time
sys.stdout.reconfigure(encoding='utf-8')
HERE = os.path.dirname(os.path.abspath(__file__))
# ⚠ 顺序不能反:`.claude` 下有同名的 R1 脚本(pcb_pour / pcb_rules / pcb_place),
# 先插 .claude 再插 HERE 的话,`import pcb_pour` 会拿到 R1 那一份,
# 而且**不报错**,只在调到 R2 专有的函数时才炸。
sys.path.insert(0, os.path.dirname(HERE))
sys.path.insert(0, HERE)
from eda import run # noqa: E402
import pcb_blade as B # noqa: E402
import fp_edge # noqa: E402
PCB = B.PCB
MIL = B.MIL
L_TOP, L_GND, L_PWR, L_BOT = 1, 15, 16, 2
INSET = 0.5 # 铜面离板边
TOP_POUR_Y = 11.5 # 外层铜面的下沿(金手指区一点外层铜都不许有)
# ⛔ 2026-09-12:板框开了台阶(`pcb_blade.STEP_H` / `TONGUE_X0/X1`)之后,**舌头两侧根本没有板材**
# ⇒ 那两段的铜下沿必须抬到 `STEP_H + INSET`,否则铜会悬在被切掉的地方。
# 舌头那一段仍按 TOP_POUR_Y / INSET 走(外层不进金手指区、内层绕缺口)。
STEP_POUR_Y = round(B.STEP_H + INSET, 3)
NOTCH_KEEP = 0.5 # 缺口周围再让 0.5
# L3 两个岛(x0, y0, x1, y1)
# ⛔⛔ 2026-09-11 晚(方案 §35):主板转 180°、功率链转 180° ⇒ 两个岛上下对调。
# · `VO_P` 岛在**上半**:功率链顶上的 Rs1 / U3 → 西去 J5 / 阻尼组 / DIS → 两槽之间的 C60。
# 电池槽是铣穿的,岛做成**绕开电池槽的 U 形单环**(三块矩形拼,`VO_RECTS`),不用带洞多边形。
# · `+12V` 岛在**下半**:金手指 12V 三指的 launch 孔(x 28.5~30.5, y 11.75)→ 东去 F1 的 +12V 脚(功率链最底)。
# 12V 从此**不在 L1 / L4 上走线**(原来那根 L4 x≈42.5 的竖线把整块板东西切成两半)。
# 下沿 9.0:金手指地母线的孔在 y 7.0 / 8.2,孔 + 净距到 8.8;上沿 20:信号 launch 孔在 y 11.75 一字排开
# (1 mm 一个,反焊盘把 11.25~12.25 那一条几乎切断),电流要从它上面 8 mm 宽的那条过。
VO_Y0 = 38.0 # 岛主体下沿(DIS / 阻尼组最南到 ~39.8)
VO_TOP_Y = round(B.SLOT_Y[0] - 0.56, 3) # 主体上沿:离电池槽底 0.56(槽边到铜 ≥ 0.5)
_bat = B.flex_slots()['BAT']
VO_RECTS = [
(26.0, VO_Y0, B.BW - 0.5, VO_TOP_Y), # 主体:J5 / 阻尼组 / DIS / U2 以北
(25.6, VO_TOP_Y, round(_bat[0] - 0.56, 3), B.BH - 0.5), # 两槽之间往上:C60
(round(_bat[2] + 0.56, 3), VO_TOP_Y, B.BW - 0.5, B.BH - 0.5), # 电池槽以东往上:Rs1 / U3 / C13
]
VO_ISLAND, VO_LOBE = VO_RECTS[0], VO_RECTS[1] # 老名字留给别的脚本(board_check 等)
# ⛔ 2026-09-12 台阶之后改成**阶梯形**:舌头那一段(x TONGUE_X0~TONGUE_X1)仍下探到 9.0 去接
# 12V 三指的 launch 孔(x 28.5~30.5 / y 11.75,全在舌头内);舌头以东那一段抬到 STEP_POUR_Y
# —— 那边的板材已经被台阶铣掉了。断面:东段由 11.0 收到 7.5 mm 宽(1 oz 内层,1.5 A 峰值远够)。
# ⛔ 2026-09-12 晚:x0 原来写死 27.0(= 当时的 TONGUE_X0)。舌头一改(36.0 → 34.5)
# `v12_poly()` 里的 `assert x0 >= B.TONGUE_X0` 就会失败。**改成直接跟着舌头走**,
# 从此不会再因为舌头变动而漏改这一处。12V 三指的 launch 孔在 x 28.5~30.5,
# 比任何合理的 TONGUE_X0 都靠内,覆盖不受影响。
V12_ISLAND = (B.TONGUE_X0, 9.0, 84.0, 20.0) # 老名字留着(board_check 等按它取包围盒)
V12_LOW_Y, V12_TOP_Y = 9.0, 20.0
def mil(v):
return round(v * MIL, 4)
def rrect(x0, y0, x1, y1, r=0.0, seg=8):
u"""圆角矩形点列(逆时针,闭合)。r=0 就是普通矩形。"""
if r <= 0:
pts = [(x0, y0), (x1, y0), (x1, y1), (x0, y1)]
else:
pts = []
for cx, cy, a0 in ((x1 - r, y0 + r, 270), (x1 - r, y1 - r, 0),
(x0 + r, y1 - r, 90), (x0 + r, y0 + r, 180)):
for k in range(seg + 1):
a = math.radians(a0 + 90.0 * k / seg)
pts.append((cx + r * math.cos(a), cy + r * math.sin(a)))
out = [pts[0]]
for p in pts[1:]:
if abs(p[0] - out[-1][0]) > 1e-9 or abs(p[1] - out[-1][1]) > 1e-9:
out.append(p)
out.append(out[0]) # ⚠ 必须显式闭合
return out
def notch_cut():
u"""缺口在铜面里要挖掉的矩形(含让位)。"""
ml, mr = B.notch_break()
return (ml - NOTCH_KEEP, -1.0, mr + NOTCH_KEEP, fp_edge.NOTCH_D + NOTCH_KEEP)
def inner_poly():
u"""内层铜面:板框内缩 INSET,底边在缺口处绕过去(一条闭合折线,不用挖孔)。"""
d = INSET
r = B.CORNER_R - d
x0, y0, x1, y1 = d, d, B.BW - d, B.BH - d
nx0, _ny0, nx1, ny1 = notch_cut()
pts = [(nx1, y0)] # 缺口右侧起步,沿底边往右
pts.append((x1 - r, y0))
for cx, cy, a0 in ((x1 - r, y0 + r, 270), (x1 - r, y1 - r, 0),
(x0 + r, y1 - r, 90), (x0 + r, y0 + r, 180)):
for k in range(9):
a = math.radians(a0 + 90.0 * k / 8)
pts.append((cx + r * math.cos(a), cy + r * math.sin(a)))
pts.append((nx0, y0)) # 底边左段走到缺口左沿
pts.append((nx0, ny1)) # 绕缺口
pts.append((nx1, ny1))
out = [pts[0]]
for p in pts[1:]:
if abs(p[0] - out[-1][0]) > 1e-9 or abs(p[1] - out[-1][1]) > 1e-9:
out.append(p)
out.append(out[0])
return out
def outer_poly():
u"""外层铜面:只在 y >= TOP_POUR_Y 的部分,金手指区一点铜都没有。"""
d = INSET
r = B.CORNER_R - d
x0, x1, y1 = d, B.BW - d, B.BH - d
pts = [(x0, TOP_POUR_Y), (x1, TOP_POUR_Y)]
for cx, cy, a0 in ((x1 - r, y1 - r, 0), (x0 + r, y1 - r, 90)):
for k in range(9):
a = math.radians(a0 + 90.0 * k / 8)
pts.append((cx + r * math.cos(a), cy + r * math.sin(a)))
out = [pts[0]]
for p in pts[1:]:
if abs(p[0] - out[-1][0]) > 1e-9 or abs(p[1] - out[-1][1]) > 1e-9:
out.append(p)
out.append(out[0])
return out
def src(pts):
u"""点列 -> TPCB_PolygonSourceArray(mil): x1 y1 'L' x2 y2 'L' ..."""
a = [mil(pts[0][0]), mil(pts[0][1])]
for p in pts[1:]:
a += ['L', mil(p[0]), mil(p[1])]
return a
def gnd_tiles(y_lo):
u"""地铜的**若干块单环矩形**,绕开铣穿的排线槽。
⛔ **不能用带洞的复杂多边形**:`pcb_MathPolygon.createComplexPolygon()` 传多个环时
会把客户端挂死 —— 请求 30 s 超时、铺铜边框停在半路、进度恒为 0/0,
和「GUI 按钮失灵」完全无法区分(2026-09-08 在这上面绕了很久)。
⇒ 一律单环。要挖洞就拆成几块矩形拼,铜面之间同网互相接壤会自然连成一片。
做法:按所有槽的 x 边把板切成竖条,每条里按 y 找槽之间的空隙。
(2026-09-09 改:原来的「上下横带」写法假设各窗 y 区间不重叠,两条并排的排线槽
会把槽本身当成一条带铺进去。)
`y_lo` = 这一层铜面的下沿(外层 11.5 / 内层 0.5)。
"""
d = INSET
x0, x1 = d, B.BW - d
y1 = B.BH - d
cuts = [(a - INSET, b - INSET, c + INSET, e + INSET) for (a, b, c, e) in B.mill_cuts()]
# ⛔ 台阶两端也是切分点:舌头外的竖条下沿要抬到 STEP_POUR_Y(那边没有板材)
xs = sorted(set([x0, x1, B.TONGUE_X0, B.TONGUE_X1] + [v for c in cuts for v in (c[0], c[2])]))
tiles = []
for xa, xb in zip(xs, xs[1:]):
if xb - xa < 0.6:
continue
in_tongue = (xa >= B.TONGUE_X0 - 1e-9 and xb <= B.TONGUE_X1 + 1e-9)
ys = [y_lo if in_tongue else max(y_lo, STEP_POUR_Y)]
for c in sorted(cuts, key=lambda c: c[1]):
if c[0] < xb - 1e-9 and xa < c[2] - 1e-9:
ys += [c[1], c[3]]
ys.append(y1)
for k in range(0, len(ys) - 1, 2):
ya, yb = ys[k], ys[k + 1]
if yb - ya > 0.6:
tiles.append((xa, ya, xb, yb))
# ⛔ 内层的下沿会盖住金手指的**防呆缺口**(铣穿的)。把缺口那一段单独让开:
# y 抬到缺口顶之上,再补两块把缺口左右两侧的底部填回来。
nx0, _a, nx1, ny1 = notch_cut()
if y_lo < ny1:
cut = ny1
out = []
for (a, b, c, e) in tiles:
if e <= cut:
continue
if b < cut and (a < nx1 and c > nx0):
out.append((a, cut, c, e))
if a < nx0:
out.append((a, b, nx0, cut))
if c > nx1:
out.append((nx1, b, c, cut))
else:
out.append((a, b, c, e))
tiles = [t for t in out if t[2] - t[0] > 0.6 and t[3] - t[1] > 0.6]
return tiles
def v12_poly():
u"""`+12V` 岛(L3):**阶梯形单环**。舌头段下探到 V12_LOW_Y 接金手指的 launch 孔,
舌头以东抬到 STEP_POUR_Y(板框台阶把那块板材铣掉了)。
⛔ 一律单环(带洞的复杂多边形会把客户端挂死,见 `gnd_tiles` 的说明)。"""
x0, x1 = V12_ISLAND[0], V12_ISLAND[2]
xt = B.TONGUE_X1
assert x0 >= B.TONGUE_X0 and xt < x1, u'+12V 岛的阶梯点跑到舌头外了'
pts = [(x0, V12_LOW_Y), (xt, V12_LOW_Y), (xt, STEP_POUR_Y), (x1, STEP_POUR_Y),
(x1, V12_TOP_Y), (x0, V12_TOP_Y)]
out = [pts[0]]
for q in pts[1:]:
if abs(q[0] - out[-1][0]) > 1e-9 or abs(q[1] - out[-1][1]) > 1e-9:
out.append(q)
out.append(out[0])
return out
def vo_poly():
u"""VO_P 岛:主体 + 两个上叶(夹着电池槽),拼成一个 U 形**单环**(逆时针,显式闭合)。
2026-09-11 晚起(方案 §35)。上叶 w 骑在主体上沿、西沿可以比主体再往西一点(C60 的 VO_P 盘)。"""
a, w, e = VO_RECTS
assert abs(a[3] - w[1]) < 1e-9 and abs(a[3] - e[1]) < 1e-9 and e[2] == a[2] and w[2] < e[0], u'上叶必须骑在主体上沿'
pts = [(a[0], a[1]), (a[2], a[1]), (e[2], e[3]), (e[0], e[3]), (e[0], a[3]),
(w[2], a[3]), (w[2], w[3]), (w[0], w[3]), (w[0], a[3]), (a[0], a[3])]
out = [pts[0]]
for q in pts[1:]:
if abs(q[0] - out[-1][0]) > 1e-9 or abs(q[1] - out[-1][1]) > 1e-9:
out.append(q)
out.append(out[0])
return out
def plan():
u"""[(name, net, layer, [环...], priority)],priority 小的先铺、先占地盘。"""
outs = []
outs.append(('VO-L3', 'VO_P', L_PWR, [src(vo_poly())], 1))
outs.append(('V12-L3', '+12V', L_PWR, [src(v12_poly())], 2))
for tag, layer, y_lo in (('L2', L_GND, INSET), ('TOP', L_TOP, TOP_POUR_Y),
('BOT', L_BOT, TOP_POUR_Y)):
for k, (a, b, c, d2) in enumerate(gnd_tiles(y_lo)):
outs.append(('GND-%s-%d' % (tag, k), 'GND', layer,
[src(rrect(a, b, c, d2))], 9))
return outs
def _ring_pts(ring):
u"""`src()` 生成的 [x, y, 'L', x, y, 'L', ...](mil)还原成 [(x, y)] 点列(mm)。"""
pts = [(float(ring[0]) / MIL, float(ring[1]) / MIL)]
for k in range(3, len(ring), 3):
pts.append((float(ring[k]) / MIL, float(ring[k + 1]) / MIL))
return pts
def selfcheck(ps):
bad = []
for name, _net, _lay, rings, _p in ps:
for k, s in enumerate(rings):
if s[0] != s[-2] or s[1] != s[-1]:
bad.append(u'%s 第 %d 个环没闭合' % (name, k))
# 外层铜面不许下探进金手指区
top = fp_edge.EDGE_CLR + fp_edge.LONG_L
if TOP_POUR_Y <= top:
bad.append(u'外层铜下沿 %.2f 压到指顶 %.2f' % (TOP_POUR_Y, top))
if TOP_POUR_Y <= fp_edge.NOTCH_D:
bad.append(u'外层铜下沿 %.2f 低于缺口顶 %.2f' % (TOP_POUR_Y, fp_edge.NOTCH_D))
# ⛔ 台阶:舌头两侧不许有任何铜低于 STEP_POUR_Y(那块板材已经铣掉了)。
# ⚠ 判据必须**逐边**走,不能按整个环的包围盒判:`+12V` 岛是阶梯形,
# 它的 y=9.0 那一段只在舌头内,按包围盒(min y 9.0 且 max x 84)会误报。
for name, _net, _lay, rings, _p in ps:
for ring in rings:
pts = _ring_pts(ring)
worst = None
for (ax, ay), (bx, by) in zip(pts, pts[1:]):
n = max(2, int(math.hypot(bx - ax, by - ay) / 0.25) + 1)
for i in range(n + 1):
t = i / float(n)
x, y = ax + (bx - ax) * t, ay + (by - ay) * t
if y < STEP_POUR_Y - 1e-6 and (x < B.TONGUE_X0 - 1e-6 or x > B.TONGUE_X1 + 1e-6):
if worst is None or y < worst[1]:
worst = (x, y)
if worst:
bad.append(u'%s 伸进台阶切掉的区域(最深处 x %.2f / y %.2f < %.2f)'
% (name, worst[0], worst[1], STEP_POUR_Y))
# 内层铜面必须绕开缺口
nx0, _a, nx1, ny1 = notch_cut()
if nx1 - nx0 < fp_edge.NOTCH_W + 2 * fp_edge.LEAD_IN:
bad.append(u'缺口挖得比口部还窄')
# 两个岛不许互相重叠(同层,优先级只有 1/2 之差,重叠就说不清谁裁谁)
b = V12_ISLAND
for a in VO_RECTS:
if a[0] < b[2] and b[0] < a[2] and a[1] < b[3] and b[1] < a[3]:
bad.append(u'VO 岛和 +12V 岛在 L3 上重叠')
# 岛不许压到铣穿的槽(含 0.5 净距)—— 客户端会自己让,但让出来的形状谁也没核过
for (x0, y0, x1, y1) in B.mill_cuts():
for a in VO_RECTS + [b]:
if a[0] < x1 + 0.5 and x0 - 0.5 < a[2] and a[1] < y1 + 0.5 and y0 - 0.5 < a[3]:
bad.append(u'L3 岛 %s 压到槽 (%.1f~%.1f, %.1f~%.1f)' % (a, x0, x1, y0, y1))
return bad
OPEN = ("await eda.dmt_EditorControl.openDocument('%s');"
"await new Promise(r=>setTimeout(r,2000));" % PCB)
JS_DEL = OPEN + u"""
let n = 0;
for (const old of await eda.pcb_PrimitivePour.getAll()) {
await eda.pcb_PrimitivePour.delete(old.primitiveId); n++; }
return {deleted: n};
"""
JS_MAKE = OPEN + u"""
const PLAN = %s;
for (const p of PLAN) {
const poly = await eda.pcb_MathPolygon.createComplexPolygon(p.src);
if (!poly) continue;
try { await eda.pcb_PrimitivePour.create(p.net, p.layer, poly, 'solid', false,
p.name, p.prio, 10); } catch (e) {}
}
return {made: (await eda.pcb_PrimitivePour.getAll())
.map(x => String(x.pourName || '').toUpperCase())};
"""
JS_PRIO = OPEN + u"""
const P = %s;
let k = 0;
for (const o of await eda.pcb_PrimitivePour.getAll()) {
const want = P[String(o.pourName || '').toUpperCase()];
if (want === undefined) continue;
try { await eda.pcb_PrimitivePour.modify(o.primitiveId, {pourPriority: want}); k++; }
catch (e) {}
}
return {set: k, now: (await eda.pcb_PrimitivePour.getAll())
.map(o => [String(o.pourName||''), o.pourPriority])};
"""
JS_REBUILD = OPEN + u"""
const sleep = ms => new Promise(r => setTimeout(r, ms));
const propsOf = el => { for (const k of Object.keys(el)) if (k.startsWith('__reactProps$')) return el[k]; return null; };
const fire = (el) => { let t = el; for (let i=0;i<8&&t;i++){const q=propsOf(t);
if(q&&typeof q.onClick==='function'){q.onClick({target:t,currentTarget:t,stopPropagation(){},preventDefault(){},nativeEvent:{},button:0,detail:1});return true;} t=t.parentElement;} return false; };
const leaf = (txt) => { for (const el of document.querySelectorAll('*'))
if (el.children.length===0 && (el.textContent||'').trim()===txt) return el; return null; };
const btn = (txt) => { for (const b of document.querySelectorAll('button'))
if ((b.innerText||'').trim()===txt && b.offsetParent!==null) return b; return null; };
if (!fire(leaf('工具'))) return {err: 'no 工具'};
await sleep(900);
if (!fire(leaf('铺铜管理器...'))) return {err: 'no 铺铜管理器'};
await sleep(2500);
const b = btn('重建所有');
if (!b) return {err: 'no 重建所有'};
return {fired: fire(b)};
"""
JS_POLL = u"return {poured: (await eda.pcb_PrimitivePoured.getAll()).length};"
JS_DONE = u"""
const propsOf = el => { for (const k of Object.keys(el)) if (k.startsWith('__reactProps$')) return el[k]; return null; };
const fire = (el) => { let t = el; for (let i=0;i<8&&t;i++){const q=propsOf(t);
if(q&&typeof q.onClick==='function'){q.onClick({target:t,currentTarget:t,stopPropagation(){},preventDefault(){},nativeEvent:{},button:0,detail:1});return true;} t=t.parentElement;} return false; };
let ok = null;
for (const b of document.querySelectorAll('button'))
if ((b.innerText||'').trim()==='确认' && b.offsetParent!==null) { ok = b; break; }
if (ok) fire(ok);
await new Promise(r => setTimeout(r, 1500));
await eda.pcb_Document.save();
return {poured: (await eda.pcb_PrimitivePoured.getAll()).length,
pour: (await eda.pcb_PrimitivePour.getAll()).length};
"""
def call(js, timeout=120):
r = run(js, timeout=timeout)
if not r.get('success'):
print(u'失败: ' + json.dumps(r, ensure_ascii=False)[:600])
sys.exit(1)
return r['result']
def main():
ps = plan()
print(u'计划建 %d 块铜面:' % len(ps))
for name, net, layer, rings, prio in ps:
npt = sum((len(r) + 1) // 3 for r in rings)
print(u' %-8s net=%-6s layer=%-3d %d 环 / %3d 点 prio=%d'
% (name, net, layer, len(rings), npt, prio))
bad = selfcheck(ps)
if bad:
print(u'[X] 自检不过:')
for b in bad:
print(u' ' + b)
sys.exit(1)
print(u'[OK] 自检通过:多边形全闭合 / 外层铜不进金手指区 / 内层绕开缺口 / 两岛不重叠')
if '--push' not in sys.argv and '--rebuild' not in sys.argv:
print(u'(未加 --push,没有下发)')
return
if '--rebuild' not in sys.argv:
print(u'清掉旧铜面: %d 块' % call(JS_DEL)['deleted'])
for k in range(0, len(ps), 3): # 3 块一批,稳在超时以内
batch = [{'name': n, 'net': net, 'layer': l, 'src': s, 'prio': pr}
for n, net, l, s, pr in ps[k:k + 3]]
made = call(JS_MAKE % json.dumps(batch), timeout=180)['made']
print(u' 已建 %d 块' % len(made))
want = {n.upper() for n, _, _, _, _ in ps}
got = set(call("return (await eda.pcb_PrimitivePour.getAll())"
".map(x=>String(x.pourName||'').toUpperCase());"))
if not want <= got:
print(u'[X] 没建上: %s' % (want - got))
sys.exit(1)
# ⚠ create() 的 pourPriority 参数不生效(R1 skill §9j),建完再逐块设
r = call(JS_PRIO % json.dumps({n.upper(): pr for n, _, _, _, pr in ps}), timeout=180)
print(u' 优先级已设 %d 块: %s' % (r['set'], r['now']))
before = len(call("return (await eda.pcb_PrimitivePour.getAll()).map(o=>o.pourName);"))
r = call(JS_REBUILD, timeout=120)
if r.get('err'):
print(u'[X] 打不开铺铜管理器: %s' % r['err'])
sys.exit(1)
# ⚠ 退出条件是「**数量稳定**」,不是「全部填上」。
# 有的铜面本来就填不出来(`V12-L3` 在 `+12V` 还没有过孔扎进 L3 之前是孤岛,
# 会被「移除孤岛」删掉),写成 `n >= len(ps)` 的话每次都要空转满 200 秒,
# 而且看上去像是卡死。
prev, same = -1, 0
for _i in range(40):
time.sleep(5)
n = call(JS_POLL, timeout=60)['poured']
print(u' 填充 %d / %d' % (n, len(ps)))
if n >= len(ps):
break
same = same + 1 if n == prev else 0
prev = n
if same >= 2 and n > 0:
print(u' (%d 块已稳定,另 %d 块没有可接的铜,属正常)' % (n, len(ps) - n))
break
fin = call(JS_DONE, timeout=120)
print(u' 收尾: %s' % fin)
# ⛔ 铺铜**边框**会莫名其妙变少(实测 6→3、5→2)。哪一步删的还没查清,
# 但「静默变少」是最坏的形态:铜面没了,DRC 只报连接性,看上去像布线没做完。
# 这里把它变成硬报错。
if fin['pour'] < before:
print(u' [X] 铺铜边框从 %d 变成 %d —— 有东西把它们删了,**不要在这个状态下导出**'
% (before, fin['pour']))
sys.exit(1)
if __name__ == '__main__':
main()
文件差异内容过多而无法显示 加载差异