文件
phonerail/.claude/p6_fan.py

547 行
23 KiB
Python

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# -*- coding: utf-8 -*-
u"""FAN-R2:P6-FAN-EDL 从五个 3 针风扇改成两个 4 线 PWM 风扇(山洋 9G0612P4S001 x2)。
用法:
/c/Python314/python .claude/p6_fan.py # 干跑,只打印统计
/c/Python314/python .claude/p6_fan.py --place # 阶段一:删/挪/放件,读回引脚存 p6-pins.json
/c/Python314/python .claude/p6_fan.py --wire # 阶段二:重摆到最终坐标 + 连线(本地自检后下发)
位号:kicad 侧把风扇电阻编到 R740~R751,和载板 EDL 下拉的 R741~R745 撞号。
立创侧是单一位号空间,所以整块改用 R746~R757(连续 12 个空号)。映射见设计文档 §22.6a。
方向:极性件一律读回 pinName 断言,不按角度表推(skill §6b/§6c)。本轮实测出两条新的:
create(rot=180, mirror=True) 的 SOT-23 -> G 左 / S 上 / D 下(高边 P-MOS 的标准画法)
create(rot=0, mirror=True) 的 SOT-23 -> G 右 / D 上 / S 下(栅极朝右接收信号)
单靠 rotation 这两种朝向都做不出来。
"""
import json
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from eda import run
PAGE = 'fd00f13e59336392'
LIB = '0819f05c4eef4c71ace90d822a990e87'
P = {
'AO3400': '2b6cc05daeb34c16b9e00706a375fc9f', # C20917 AO3400A N-MOS
'AO3401': 'be56508f80684877b262e15e4d50e175', # C15127 AO3401A P-MOS 高边
'ZD8V2': '46e19a04908e4d7f9d40ab2982f52315', # C2891776 BZT52C8V2 8.2V 钳位
'R100K': 'acc294b6661948399f609b41c77ed19f', # C25741
'R10K': 'c3b9baa5ef2e4070a4c0f9e9cd04fe6e', # C25744
'R15K': '6d104587f1cd4b509c6a5fb272570507', # C25756
'R1K': '65abbad5fa864933b552e0f016ece0db', # C11702
'CONN4': '04ed497706104166a1f88dbf2d16083e', # C2847671 2510-4A 直针 1x4P 2.54
}
DROP = ['J28', 'J29', 'J30', 'R730', 'R731', 'R732'] # 三个多余风扇座 + 旧 TACH 分压
SWAP = ['J26', 'J27'] # 3 针换 4 针:只能删掉重建
MOVE = [
('R733', 200, 330, 0),
('R734', 250, 270, 270),
('Q6', 320, 330, 0),
('D2', 940, 690, 270),
('C738', 1000, 690, 270),
]
PLACE = [
('Q7', 'AO3401', 300, 720, 0),
('R746', 'R100K', 220, 790, 0),
('DZ3', 'ZD8V2', 140, 720, 270),
('R747', 'R1K', 300, 640, 270),
('Q8', 'AO3400', 300, 560, 0),
('R748', 'R1K', 170, 560, 0),
('R749', 'R100K', 230, 490, 270),
('R750', 'R10K', 560, 760, 270),
('R751', 'R15K', 630, 700, 0),
('R752', 'R10K', 700, 640, 270),
('Q9', 'AO3400', 780, 700, 0),
('R753', 'R10K', 860, 770, 270),
('R754', 'R10K', 560, 500, 270),
('R755', 'R15K', 630, 440, 0),
('R756', 'R10K', 700, 380, 270),
('Q10', 'AO3400', 780, 440, 0),
('R757', 'R10K', 860, 510, 270),
('J26', 'CONN4', 1085, 730, 0),
('J27', 'CONN4', 1085, 470, 0),
]
# ---------------- 阶段二 ----------------
# 换方向的只能删掉重建(skill §6b)
REBUILD = [
('Q7', 'AO3401', 300, 730, 180, True),
('DZ3', 'ZD8V2', 150, 750, 90, False),
('R746', 'R100K', 240, 750, 270, False),
('Q9', 'AO3400', 720, 690, 0, True),
('Q10', 'AO3400', 720, 450, 0, True),
]
# 只平移、不转向(setState_X/Y,绝不碰 rotation)
RELOC = [
('R747', 150, 660), ('Q8', 280, 560), ('R748', 200, 560), ('R749', 230, 510),
('D2', 430, 660), ('C738', 490, 660),
('R750', 960, 720), ('R751', 810, 690), ('R752', 790, 650), ('R753', 710, 750),
('R754', 960, 480), ('R755', 810, 450), ('R756', 790, 410), ('R757', 710, 510),
('J26', 1085, 695), ('J27', 1085, 455),
]
EXPECT_NAMED = { # 极性件:按 pinName 断言
'Q7': {'G': (280, 730), 'S': (310, 750), 'D': (310, 710)},
'DZ3': {'K': (150, 770), 'A': (150, 730)},
'Q8': {'G': (260, 560), 'D': (290, 580), 'S': (290, 540)},
'Q6': {'G': (300, 330), 'D': (330, 350), 'S': (330, 310)},
'Q9': {'G': (740, 690), 'D': (710, 710), 'S': (710, 670)},
'Q10': {'G': (740, 450), 'D': (710, 470), 'S': (710, 430)},
'D2': {'K': (430, 680), 'A': (430, 640)},
}
EXPECT_XY = { # 无极性件:只核对引脚坐标集合
'R746': [(240, 770), (240, 730)], 'R747': [(150, 680), (150, 640)],
'R748': [(180, 560), (220, 560)], 'R749': [(230, 530), (230, 490)],
'R733': [(180, 330), (220, 330)], 'R734': [(250, 290), (250, 250)],
'C738': [(490, 675), (490, 645)],
'R750': [(960, 740), (960, 700)], 'R751': [(790, 690), (830, 690)],
'R752': [(790, 670), (790, 630)], 'R753': [(710, 770), (710, 730)],
'R754': [(960, 500), (960, 460)], 'R755': [(790, 450), (830, 450)],
'R756': [(790, 430), (790, 390)], 'R757': [(710, 530), (710, 490)],
'J26': [(1065, 710), (1065, 700), (1065, 690), (1065, 680)],
'J27': [(1065, 470), (1065, 460), (1065, 450), (1065, 440)],
}
FRAME = (20, 10, 1145, 812)
TITLE = (455, 10, 1145, 192)
SW = 'FAN_12V_SW'
def call(js, timeout=180):
r = run(js, timeout=timeout)
if not r.get('success'):
raise RuntimeError(json.dumps(r, ensure_ascii=False)[:900])
return r['result']
def build():
u"""返回 (导线, 旗, 端口, 文字)。导线元素是 ((x1,y1),(x2,y2),网络名)。"""
W, F, PT, TX = [], [], [], []
def w(net, *pts):
for i in range(len(pts) - 1):
a, b = pts[i], pts[i + 1]
assert (a[0] == b[0]) != (a[1] == b[1]), u'%s: %s->%s 非正交或零长' % (net, a, b)
for v in (a[0], a[1], b[0], b[1]):
assert v % 5 == 0, u'%s: %s->%s 坐标不是 5 的倍数' % (net, a, b)
W.append((a, b, net))
def pwr(net, x, y, rot=0):
F.append(('Power', net, x, y, rot))
def gnd(x, y, rot=0):
F.append(('Ground', 'GND', x, y, rot))
def port(net, x, y, rot):
PT.append(('BI', net, x, y, rot))
def txt(s, x, y, size=10):
TX.append((s, x, y, size))
# ---------- A 高边电源开关:默认关电 ----------
w('+12V', (150, 790), (390, 790))
w('+12V', (150, 770), (150, 790)) # DZ3.K
w('+12V', (240, 770), (240, 790)) # R746.p1
w('+12V', (310, 750), (310, 790)) # Q7.S
pwr('+12V', 390, 790)
w('G7', (150, 730), (280, 730)) # DZ3.A / R746.p2 / Q7.G
w('G7', (150, 730), (150, 680)) # -> R747.p1
w(SW, (310, 710), (310, 680)) # Q7.D
pwr(SW, 310, 680, 180)
w('Q8D', (150, 640), (150, 580), (290, 580)) # R747.p2 -> Q8.D
w('FAN_EN', (140, 560), (180, 560))
port('FAN_EN', 140, 560, 180)
w('Q8G', (220, 560), (260, 560)) # R748.p2 -> Q8.G
w('Q8G', (230, 560), (230, 530)) # -> R749.p1
w('GND', (230, 490), (230, 460))
gnd(230, 460)
w('GND', (290, 540), (290, 500))
gnd(290, 500)
# ---------- B PWM 低边开漏:只拉风扇的 PWM 线,不切电源 ----------
w('FAN_PWM', (140, 330), (180, 330))
port('FAN_PWM', 140, 330, 180)
w('Q6G', (220, 330), (300, 330)) # R733.p2 -> Q6.G
w('Q6G', (250, 330), (250, 290)) # -> R734.p1
w('GND', (250, 250), (250, 240)) # 地旗的 GND 字样会掉到符号下方约 15 个单位,
gnd(250, 240) # 放低一点才不会压住 y=190 的标题行
w('GND', (330, 310), (370, 310), (370, 280))
gnd(370, 280)
w('FAN_PWM_L', (330, 350), (330, 390), (400, 390))
port('FAN_PWM_L', 400, 390, 0)
# ---------- 开关后母线上的储能与负向钳位 ----------
w(SW, (430, 680), (430, 700))
pwr(SW, 430, 700)
w('GND', (430, 640), (430, 615))
gnd(430, 615)
w(SW, (490, 675), (490, 700))
pwr(SW, 490, 700)
w('GND', (490, 645), (490, 615))
gnd(490, 615)
# ---------- C 两路独立测速接收(进风 dy=0 / 排风 dy=-240)----------
for tag, dy, raw, sig in (('IN', 0, 'FAN_IN_TACH_RAW', 'FAN_TACH_IN'),
('OUT', -240, 'FAN_OUT_TACH_RAW', 'FAN_TACH_OUT')):
def y(v, dy=dy):
return v + dy
w(SW, (960, y(740)), (960, y(760)))
pwr(SW, 960, y(760))
w(raw, (1065, y(690)), (830, y(690))) # J2x.3 -> 串阻
w(raw, (960, y(700)), (960, y(690))) # 上拉电阻下端
w(raw, (990, y(690)), (990, y(620)))
port(raw, 990, y(620), 180)
w('G' + tag, (790, y(690)), (740, y(690))) # 串阻 -> 栅极
w('G' + tag, (790, y(690)), (790, y(670))) # -> 栅极对地电阻
w('GND', (790, y(630)), (790, y(600)))
gnd(790, y(600))
w('GND', (710, y(670)), (710, y(630)))
gnd(710, y(630))
w(sig, (710, y(710)), (710, y(730))) # 漏极 -> 3V3 上拉
w(sig, (710, y(720)), (650, y(720)))
port(sig, 650, y(720), 180)
w('+3V3', (710, y(770)), (710, y(790)))
pwr('+3V3', 710, y(790))
# ---------- D 风扇座 ----------
for d, y0 in (('J26', 695), ('J27', 455)):
w('GND', (1065, y0 + 15), (1050, y0 + 15), (1050, y0 + 45))
gnd(1050, y0 + 45, 180)
w(SW, (1065, y0 + 5), (1030, y0 + 5), (1030, y0 + 65))
pwr(SW, 1030, y0 + 65)
w('FAN_PWM_L', (1065, y0 - 15), (1020, y0 - 15), (1020, y0 - 55))
port('FAN_PWM_L', 1020, y0 - 55, 180)
# ---------- 说明 ----------
txt(u'【风扇】FAN-R2:两只 4 线 PWM 风扇,一进一出', 45, 190, 12)
txt(u'型号 山洋 9G0612P4S001 / C2920592。高边默认关电 + 独立 PWM + 两路独立测速。', 45, 168)
txt(u'Q7(AO3401A) 串在 +12V 与 FAN_12V_SW 之间,R746 把栅极拉回源极;', 45, 150)
txt(u'Q8 经 R748 由 FAN_EN(IO5) 驱动,R749 在复位/高阻时拉低 = 默认断电。', 45, 132)
txt(u'DZ3(BZT52C8V2) K 接源极、A 接栅极,钳位负 Vgs(绝对最大 ±12V)。', 45, 114)
txt(u'调速走风扇自己的 PWM 脚(pin4):Q6 只拉低这根线,不再切风扇回流。', 45, 92)
txt(u'栅极高 = 风扇 PWM 线低,固件必须反相:D_gpio = 1 - D_fan。', 45, 74)
txt(u'0% PWM 风扇仍转约 3300RPM,要停转只能撤 FAN_EN,不能靠占空比。', 45, 56)
txt(u'测速两路独立:TACH 上拉到 FAN_12V_SW,15k/10k 分压驱动 Q9/Q10,', 430, 300)
txt(u'漏极经 10k 上拉到 +3V3 —— 接收电路反相,TACH 低对应 GPIO 高。', 430, 282)
txt(u'风扇座 J26/J27(2510-4A 直针 1x4P):', 850, 595)
txt(u'1=GND 2=+12V(开关后) 3=TACH 4=PWM', 850, 575)
txt(u'注意:针序与旧的 3 针 J26~J30 不同,装配前对丝印。', 850, 555)
return W, F, PT, TX
# ---------------- 本地检查 ----------------
def _on(p, a, b, tol=0.6):
(px, py), (ax, ay), (bx, by) = p, a, b
if abs(ax - bx) < tol:
return abs(px - ax) < tol and min(ay, by) - tol <= py <= max(ay, by) + tol
if abs(ay - by) < tol:
return abs(py - ay) < tol and min(ax, bx) - tol <= px <= max(ax, bx) + tol
return False
def _tw(s, size):
n_cjk = sum(1 for ch in s if ord(ch) > 0x2000)
return size * (n_cjk + 0.5 * (len(s) - n_cjk))
def netcheck(W, F, PT, TX, pins):
u"""按几何反推网络并与声明比对;另查悬空、端点判据、越框与标题栏。"""
errs = []
n = len(W)
parent = list(range(n))
def find(i):
while parent[i] != i:
parent[i] = parent[parent[i]]
i = parent[i]
return i
for i in range(n):
for j in range(i + 1, n):
a1, a2, _ = W[i]
b1, b2, _ = W[j]
if _on(a1, b1, b2) or _on(a2, b1, b2) or _on(b1, a1, a2) or _on(b2, a1, a2):
ri, rj = find(i), find(j)
if ri != rj:
parent[ri] = rj
groups = {}
for i in range(n):
groups.setdefault(find(i), []).append(i)
for root, idxs in groups.items():
names = sorted({W[i][2] for i in idxs})
if len(names) > 1:
errs.append(u'短路: ' + u' + '.join(names) + u' :: ' +
u' | '.join(u'%s%s-%s' % (W[i][2], W[i][0], W[i][1]) for i in idxs))
cnt = {}
for root, idxs in groups.items():
k = W[idxs[0]][2]
cnt[k] = cnt.get(k, 0) + 1
for k, c in cnt.items():
if c > 1 and k not in ('GND', '+3V3', '+12V', SW, 'FAN_PWM_L'):
errs.append(u'网络 %s 被拆成 %d 块(漏连)' % (k, c))
# 元件引脚必须落在某条线段上
for d, v in pins.items():
if d.startswith('_'):
continue
for num, name, x, y in v['pins']:
if not any(_on((x, y), s[0], s[1]) for s in W):
errs.append(u'悬空引脚: %s.%s(%s) @ (%d,%d)' % (d, num, name, x, y))
# 旗/端口的引脚必须落在导线【端点】上(skill §8a-2:压中段等于没连)
ends = set()
for a, b, _ in W:
ends.add(a)
ends.add(b)
for kind, net, x, y, rot in F:
if (x, y) not in ends:
errs.append(u'旗不在导线端点: %s %s @ (%d,%d)' % (kind, net, x, y))
for dr, net, x, y, rot in PT:
if (x, y) not in ends:
errs.append(u'端口不在导线端点: %s @ (%d,%d)' % (net, x, y))
# 旗/端口的引脚不能和元件引脚同坐标(skill §8a:零长度导线)
pinxy = {}
for d, v in pins.items():
if d.startswith('_'):
continue
for num, name, x, y in v['pins']:
pinxy[(x, y)] = '%s.%s' % (d, num)
for kind, net, x, y, rot in F + [(p[0], p[1], p[2], p[3], p[4]) for p in PT]:
if (x, y) in pinxy:
errs.append(u'旗/端口压在元件引脚上: %s @ (%d,%d) = %s' % (net, x, y, pinxy[(x, y)]))
# 文字越框 / 压标题栏
for s, x, y, size in TX:
x2 = x + _tw(s, size)
if x < FRAME[0] or x2 > FRAME[2] or y < FRAME[1] or y > FRAME[3]:
errs.append(u'文字越框: %s @ (%d,%d) 右到 %.0f' % (s[:16], x, y, x2))
if x2 > TITLE[0] and y < TITLE[3]:
errs.append(u'文字压标题栏: %s @ (%d,%d) 右到 %.0f' % (s[:16], x, y, x2))
# 端口标签占位(BI: rot0 在右 / rot180 在左)
for dr, net, x, y, rot in PT:
wdt = 40 + 6.7 * len(net)
lo, hi = (x, x + wdt) if rot == 0 else (x - wdt, x)
if lo < FRAME[0] or hi > FRAME[2]:
errs.append(u'端口标签越框: %s @ (%d,%d) 占 %.0f~%.0f' % (net, x, y, lo, hi))
return errs
def crossings(W):
out = []
for i in range(len(W)):
for j in range(i + 1, len(W)):
a1, a2, na = W[i]
b1, b2, nb = W[j]
if na == nb:
continue
ha, hb = a1[1] == a2[1], b1[1] == b2[1]
if ha == hb:
continue
h, v = ((a1, a2), (b1, b2)) if ha else ((b1, b2), (a1, a2))
yy, xx = h[0][1], v[0][0]
if min(h[0][0], h[1][0]) < xx < max(h[0][0], h[1][0]) and \
min(v[0][1], v[1][1]) < yy < max(v[0][1], v[1][1]):
out.append((na, nb, xx, yy))
return out
# ---------------- 阶段一 ----------------
def readpins():
return call("""
const out={};
for (const c of await eda.sch_PrimitiveComponent.getAll()){
if (c.getState_ComponentType()!=='part') continue;
const ps=await c.getAllPins();
out[c.getState_Designator()] = {name:(c.getState_Component()||{}).name,
pins: ps.map(p=>[p.getState_PinNumber(), p.getState_PinName(),
Math.round(p.getState_X()), Math.round(p.getState_Y())])};
}
return out;
""")
def open_page():
call("""
await eda.dmt_EditorControl.openDocument('%s'); await new Promise(r=>setTimeout(r,1500));
const n=(await eda.dmt_Schematic.getCurrentSchematicPageInfo()).name;
if (n!=='P6-FAN-EDL') throw new Error('page='+n);
return 1;
""" % PAGE)
def place():
for it in PLACE:
assert len(it) == 5 and P.get(it[1]), u'放置表缺 libraryUuid: %s' % (it,)
open_page()
call("""
const kill=%s;
for (const c of await eda.sch_PrimitiveComponent.getAll()){
const t=c.getState_ComponentType();
if (t==='netflag'||t==='netport'){ await eda.sch_PrimitiveComponent.delete(c.getState_PrimitiveId()); continue; }
if (t!=='part') continue;
if (kill.includes(c.getState_Designator())) await eda.sch_PrimitiveComponent.delete(c.getState_PrimitiveId());
}
for (const w of await eda.sch_PrimitiveWire.getAll()) await eda.sch_PrimitiveWire.delete(w.getState_PrimitiveId());
for (const t of await eda.sch_PrimitiveText.getAll()) await eda.sch_PrimitiveText.delete(t.getState_PrimitiveId());
return 1;
""" % json.dumps(DROP + SWAP))
print(u'已清空导线/旗/端口/文字,已删 %s' % ','.join(DROP + SWAP))
call("""
const move=%s;
for (const c of await eda.sch_PrimitiveComponent.getAll()){
if (c.getState_ComponentType()!=='part') continue;
const m = move.find(v=>v[0]===c.getState_Designator());
if (m) await c.toAsync().setState_X(m[1]).setState_Y(m[2]).setState_Rotation(m[3]).done();
}
return 1;
""" % json.dumps(MOVE))
for i in range(0, len(PLACE), 5):
chunk = [[d, P[k], x, y, r] for d, k, x, y, r in PLACE[i:i + 5]]
call("""
for (const [d,uuid,x,y,rot] of %s){
await eda.sch_PrimitiveComponent.create({libraryUuid:'%s', uuid}, x, y, undefined, rot, false, true, true);
for (const c of await eda.sch_PrimitiveComponent.getAll()){
if (c.getState_ComponentType()!=='part') continue;
if (Math.round(c.getState_X())!==x || Math.round(c.getState_Y())!==y) continue;
await c.toAsync().setState_Designator(d).done();
}
}
return 1;
""" % (json.dumps(chunk), LIB))
print(u'已放置 %d 个新件' % len(PLACE))
pins = readpins()
json.dump(pins, open(os.path.join(HERE, 'p6-pins.json'), 'w', encoding='utf-8'),
ensure_ascii=False, indent=1)
for d in sorted(pins):
print(u'%-6s %s' % (d, ' '.join('%s/%s@%d,%d' % tuple(p) for p in pins[d]['pins'])))
# ---------------- 阶段二 ----------------
def wire(push):
open_page()
if push:
for it in REBUILD:
assert P.get(it[1]), u'REBUILD 缺 libraryUuid: %s' % (it,)
call("""
const kill=%s;
for (const c of await eda.sch_PrimitiveComponent.getAll()){
const t=c.getState_ComponentType();
if (t==='netflag'||t==='netport'){ await eda.sch_PrimitiveComponent.delete(c.getState_PrimitiveId()); continue; }
if (t!=='part') continue;
if (kill.includes(c.getState_Designator())) await eda.sch_PrimitiveComponent.delete(c.getState_PrimitiveId());
}
for (const w of await eda.sch_PrimitiveWire.getAll()) await eda.sch_PrimitiveWire.delete(w.getState_PrimitiveId());
for (const t of await eda.sch_PrimitiveText.getAll()) await eda.sch_PrimitiveText.delete(t.getState_PrimitiveId());
return 1;
""" % json.dumps([r[0] for r in REBUILD]))
chunk = [[d, P[k], x, y, rot, mir] for d, k, x, y, rot, mir in REBUILD]
call("""
for (const [d,uuid,x,y,rot,mir] of %s){
await eda.sch_PrimitiveComponent.create({libraryUuid:'%s', uuid}, x, y, undefined, rot, mir, true, true);
for (const c of await eda.sch_PrimitiveComponent.getAll()){
if (c.getState_ComponentType()!=='part') continue;
if (Math.round(c.getState_X())!==x || Math.round(c.getState_Y())!==y) continue;
await c.toAsync().setState_Designator(d).done();
}
}
return 1;
""" % (json.dumps(chunk), LIB))
call("""
const reloc=%s;
for (const c of await eda.sch_PrimitiveComponent.getAll()){
if (c.getState_ComponentType()!=='part') continue;
const m = reloc.find(v=>v[0]===c.getState_Designator());
if (m) await c.toAsync().setState_X(m[1]).setState_Y(m[2]).done();
}
return 1;
""" % json.dumps(RELOC))
print(u'已重建 %d 件、平移 %d 件' % (len(REBUILD), len(RELOC)))
pins = readpins()
json.dump(pins, open(os.path.join(HERE, 'p6-pins.json'), 'w', encoding='utf-8'),
ensure_ascii=False, indent=1)
bad = []
for d, want in EXPECT_NAMED.items():
got = {nm: (x, y) for _, nm, x, y in pins.get(d, {}).get('pins', [])}
for nm, xy in want.items():
if got.get(nm) != xy:
bad.append(u'%s.%s 期望 %s 实测 %s' % (d, nm, xy, got.get(nm)))
for d, want in EXPECT_XY.items():
got = sorted((x, y) for _, _, x, y in pins.get(d, {}).get('pins', []))
if got != sorted(want):
bad.append(u'%s 引脚坐标 期望 %s 实测 %s' % (d, sorted(want), got))
if bad:
raise SystemExit(u'引脚坐标/方向对不上:\n ' + u'\n '.join(bad))
print(u'引脚坐标与极性方向 %d 件全部核对通过' % (len(EXPECT_NAMED) + len(EXPECT_XY)))
W, F, PT, TX = build()
errs = netcheck(W, F, PT, TX, pins)
xs = crossings(W)
print(u'导线 %d 段 旗 %d 端口 %d 文字 %d 交叉 %d 处' % (len(W), len(F), len(PT), len(TX), len(xs)))
for a, b, x, y in xs:
print(u' 交叉(不相连) %s x %s @ (%d,%d)' % (a, b, x, y))
if errs:
print(u'\n本地检查 %d 条:' % len(errs))
for e in errs:
print(u' ' + e)
raise SystemExit(u'本地检查未通过,未下发')
print(u'本地连通性检查通过')
if not push:
return
def batch(items, size, js):
for i in range(0, len(items), size):
call(js % json.dumps(items[i:i + size], ensure_ascii=False))
batch([[a[0], a[1], b[0], b[1]] for a, b, _ in W], 40,
"for (const s of %s) await eda.sch_PrimitiveWire.create(s); return 1;")
batch([list(f) for f in F], 25,
"for (const f of %s) await eda.sch_PrimitiveComponent.createNetFlag(f[0],f[1],f[2],f[3],f[4],false); return 1;")
batch([list(p) for p in PT], 25,
"for (const p of %s) await eda.sch_PrimitiveComponent.createNetPort(p[0],p[1],p[2],p[3],p[4],false); return 1;")
batch([list(t) for t in TX], 12,
"for (const t of %s) await eda.sch_PrimitiveText.create(t[1],t[2],t[0],0,null,null,t[3],false,false,false,0); return 1;")
print(u'推送完成')
got = call("""
const out=[];
for (const c of await eda.sch_PrimitiveComponent.getAll()){
const t=c.getState_ComponentType();
if (t!=='netflag'&&t!=='netport') continue;
out.push([t, c.getState_Net(), Math.round(c.getState_X()), Math.round(c.getState_Y())]);
}
return out;
""")
want = sorted([('netflag', f[1], f[2], f[3]) for f in F] +
[('netport', p[1], p[2], p[3]) for p in PT])
if sorted(tuple(g) for g in got) != want:
raise SystemExit(u'旗/端口回读与下发不一致:\n 下发 %s\n 回读 %s'
% (json.dumps(want, ensure_ascii=False), json.dumps(got, ensure_ascii=False)))
print(u'旗/端口回读 %d 个,网络名逐个一致' % len(got))
if __name__ == '__main__':
if '--place' in sys.argv:
place()
elif '--wire' in sys.argv:
wire(True)
elif '--dry' in sys.argv:
wire(False)
else:
print(u'用法: --place | --dry | --wire')