sync: HP_BOWEI 2026-10-15
This commit is contained in:
@@ -0,0 +1,384 @@
|
||||
"""画原理图页的高层原语:Sheet 负责写文件,Canvas 负责记账做重叠/越界检查,两者成对更新。
|
||||
|
||||
用法(一个工程一个 Project,多页共享位号登记表与封装解析):
|
||||
|
||||
from schgen import Project
|
||||
prj = Project("MyBoard", out_dir=r"E:\\...\\MyBoard",
|
||||
fp_resolver=lambda ref, val: FPMAP[val])
|
||||
pg = prj.page("02 Power", "02_Power.kicad_sch", paper="A3")
|
||||
x, y, pp = pg.comp("Device:R", "R1", "10K", 100, 80)
|
||||
...
|
||||
pg.finish() # 有重叠/越界 -> 打印并返回 False,不写文件
|
||||
|
||||
画法约定(G-0035 / G-0037):两脚无源件必须画进它服务的电路里——
|
||||
串联件用 inline()/fan_series(),退耦/上下拉用 shunt()/run_taps() 挂在母线上。
|
||||
不要 two_pin() 摆在空地两端各挂一个标签(只留给确实孤立的跳线位)。
|
||||
"""
|
||||
import os
|
||||
|
||||
from .kicad_lib import (Sheet, placed_pins, pin_dir, symbol_bbox, rot_bbox, uid, snap)
|
||||
from .layout import Canvas, LBL_PAD, strw
|
||||
|
||||
GND = "GND"
|
||||
DEFAULT_PWRSYM = {"GND": "power:GND", "+3V3": "power:+3V3", "+5V": "power:+5V",
|
||||
"+12V": "power:+12V", "+1V2": "power:+1V2", "+1V8": "power:+1V8",
|
||||
"+2V5": "power:+2V5", "+3V3A": "power:+3V3A"}
|
||||
R, C, L = "Device:R", "Device:C", "Device:L"
|
||||
|
||||
|
||||
class Project:
|
||||
def __init__(self, name, out_dir, fp_resolver=None, pwrsym=None, company="", rev="A",
|
||||
props_resolver=None):
|
||||
"""fp_resolver(ref, val) -> "库:封装";props_resolver(ref, val) -> {"MPN": ..., ...}
|
||||
(额外隐藏属性,BOM 用)。"""
|
||||
self.name, self.out_dir = name, out_dir
|
||||
self.fp_resolver = fp_resolver or (lambda ref, val: "")
|
||||
self.props_resolver = props_resolver or (lambda ref, val: {})
|
||||
self.pwrsym = dict(DEFAULT_PWRSYM, **(pwrsym or {}))
|
||||
self.company, self.rev = company, rev
|
||||
self.refs = {} # 位号登记表:KiCad 对重名位号静默合并(G-0028)
|
||||
self.npwr = 0
|
||||
self.pages = []
|
||||
|
||||
def page(self, title, fname, paper="A3"):
|
||||
p = Page(self, title, fname, paper)
|
||||
self.pages.append(p)
|
||||
return p
|
||||
|
||||
def footprint(self, ref, val):
|
||||
if ref.startswith("#"):
|
||||
return ""
|
||||
fp = self.fp_resolver(ref, val)
|
||||
if fp is None:
|
||||
raise KeyError("没有为 %s (%s) 指定封装" % (ref, val))
|
||||
return fp
|
||||
|
||||
|
||||
def pwr_rot(lib, ux, uy):
|
||||
"""选放置角度,让电源符号顺着出线方向伸出去,不倒压回走线。"""
|
||||
bb = symbol_bbox(lib)
|
||||
for rot in (0, 90, 180, 270):
|
||||
b = rot_bbox(bb, rot)
|
||||
sx = (b[0] + b[2]) / 2.0
|
||||
sy = -(b[1] + b[3]) / 2.0
|
||||
if ux * sx + uy * sy > 0.05:
|
||||
return rot
|
||||
return 0
|
||||
|
||||
|
||||
class Page:
|
||||
def __init__(self, prj, title, fname, paper="A3"):
|
||||
self.prj = prj
|
||||
self.sh = Sheet(title, prj.name, paper=paper, rev=prj.rev, company=prj.company,
|
||||
sheet_uuid=uid("sh", fname))
|
||||
self.cv = Canvas(paper=paper, name=fname)
|
||||
self.fname = fname
|
||||
self.frames = []
|
||||
|
||||
# ---------- 基本元素 ----------
|
||||
def text(self, s, x, y, size=1.6):
|
||||
"""多行用 \\n;每行都登记占位。"""
|
||||
self.sh.text(s, x, y, size)
|
||||
lines = s.split("\n")
|
||||
for k, ln in enumerate(lines):
|
||||
yy = y - (len(lines) - 1 - k) * size * 1.35
|
||||
self.cv.add_text(x, yy, ln, "left", size, what="text:" + ln[:20])
|
||||
|
||||
def wire(self, x1, y1, x2, y2):
|
||||
x1, y1, x2, y2 = snap(x1), snap(y1), snap(x2), snap(y2)
|
||||
self.sh.wire(x1, y1, x2, y2)
|
||||
self.cv.add_wire(x1, y1, x2, y2)
|
||||
|
||||
@staticmethod
|
||||
def _lrot(ux, uy):
|
||||
if ux > 0.5:
|
||||
return 0
|
||||
if ux < -0.5:
|
||||
return 180
|
||||
return 270 if uy > 0 else 90
|
||||
|
||||
def label(self, name, x, y, ux, uy):
|
||||
"""在 (x,y) 放全局标签,标签体朝 (ux,uy) 伸出。"""
|
||||
lrot = self._lrot(ux, uy)
|
||||
self.sh.glabel(name, x, y, lrot)
|
||||
self.cv.add_label(x, y, name, lrot, what="lbl:" + name)
|
||||
|
||||
def power(self, net, x, y, ux, uy):
|
||||
"""电源符号:藏 #PWRxxxx,但网名要显示。"""
|
||||
lib = self.prj.pwrsym[net]
|
||||
rot = pwr_rot(lib, ux, uy)
|
||||
bb = rot_bbox(symbol_bbox(lib), rot)
|
||||
self.prj.npwr += 1
|
||||
ref = "#PWR%04d" % self.prj.npwr
|
||||
lrot = self._lrot(ux, uy)
|
||||
if lrot == 0:
|
||||
vx, vy, vj = x + bb[2] + 1.0, y, "left"
|
||||
elif lrot == 180:
|
||||
vx, vy, vj = x + bb[0] - 1.0, y, "right"
|
||||
elif lrot == 90:
|
||||
vx, vy, vj = x, y - bb[3] - 1.6, "center"
|
||||
else:
|
||||
vx, vy, vj = x, y - bb[1] + 1.6, "center"
|
||||
self.sh.comp(lib, ref, net, x, y, rot=rot, hide_ref=True,
|
||||
ref_at=(x, y, "left"), val_at=(vx, vy, vj))
|
||||
self.cv.add_sym(x, y, bb, "sym:" + ref)
|
||||
self.cv.add_text(vx, vy, net, vj, what="ref:" + ref + "=")
|
||||
|
||||
def nc(self, x, y):
|
||||
self.sh.nc(x, y)
|
||||
self.cv.add(x - 1.0, y - 1.0, x + 1.0, y + 1.0, "nc:NC")
|
||||
|
||||
def frame(self, x0, y0, x1, y1, title=None, size=1.4):
|
||||
"""虚线框圈出一组件(如"留位不贴")。框线不参与重叠检查,标题文字参与。"""
|
||||
self.sh.rect(x0, y0, x1, y1)
|
||||
if title:
|
||||
self.text(title, x0 + 1.5, y0 - 1.0, size)
|
||||
|
||||
# ---------- 元件 ----------
|
||||
def comp(self, lib, ref, val, x, y, rot=0, unit=1, fields="above", show=True,
|
||||
dnp=False, props=None, in_bom=True):
|
||||
"""fields: 'above' 位号上/数值下;'left' 叠在左侧;'right' 叠在右侧;
|
||||
'top' 都叠在上方;'none' 不显示;("xy", rx, ry, vx, vy) 手动指定(大芯片常用)。
|
||||
返回 (x, y, 已旋转的引脚表)。"""
|
||||
x, y = snap(x), snap(y)
|
||||
key = (ref, unit)
|
||||
prev = self.prj.refs.get(ref)
|
||||
if key in self.prj.refs or (prev and prev[1] != val):
|
||||
o = self.prj.refs.get(key, prev)
|
||||
raise SystemExit("位号撞车 %s:%s 的 %s 与 %s 的 %s" % (ref, self.fname, val, o[0], o[1]))
|
||||
self.prj.refs[key] = self.prj.refs[ref] = (self.fname, val)
|
||||
bb = rot_bbox(symbol_bbox(lib, unit), rot)
|
||||
fp = self.prj.footprint(ref, val)
|
||||
if not ref.startswith("#"):
|
||||
props = dict(self.prj.props_resolver(ref, val) or {}, **(props or {}))
|
||||
dval = val # DNP 件 KiCad 只画灰色 + 红叉,数值字段原样,不加 "DNP" 字样
|
||||
if not show or fields == "none":
|
||||
self.sh.comp(lib, ref, val, x, y, rot=rot, unit=unit, hide_ref=True, hide_val=True,
|
||||
dnp=dnp, footprint=fp, fields=props, in_bom=in_bom)
|
||||
else:
|
||||
if isinstance(fields, tuple): # ("xy", 位号x, 位号y, 数值x, 数值y),左对齐
|
||||
ra, va = (fields[1], fields[2], "left"), (fields[3], fields[4], "left")
|
||||
elif fields == "left":
|
||||
fx = x + bb[0] - 1.6
|
||||
ra, va = (fx, y - 1.0, "right"), (fx, y + 2.0, "right")
|
||||
elif fields == "right":
|
||||
fx = x + bb[2] + 1.6
|
||||
ra, va = (fx, y - 1.0, "left"), (fx, y + 2.0, "left")
|
||||
elif fields == "top":
|
||||
ra = (x, y - bb[3] - 4.1, "center")
|
||||
va = (x, y - bb[3] - 2.032, "center")
|
||||
else:
|
||||
ra = (x, y - bb[3] - 2.032, "center")
|
||||
va = (x, y - bb[1] + 2.032, "center")
|
||||
self.cv.add_text(ra[0], ra[1], ref, ra[2], what="ref:" + ref)
|
||||
self.cv.add_text(va[0], va[1], dval, va[2], what="ref:" + ref + "=")
|
||||
self.sh.comp(lib, ref, val, x, y, rot=rot, unit=unit, ref_at=ra, val_at=va,
|
||||
dnp=dnp, footprint=fp, fields=props, in_bom=in_bom)
|
||||
self.cv.add_sym(x, y, bb, "sym:" + ref)
|
||||
pp = placed_pins(lib, unit, rot)
|
||||
return x, y, pp
|
||||
|
||||
def pin_xy(self, cx, cy, pp, pin):
|
||||
return cx + pp[pin][0], cy + pp[pin][1]
|
||||
|
||||
def stub(self, cx, cy, pp, pin, net, length=6.35, stagger=False):
|
||||
"""从引脚按其方向拉线,末端接全局标签或电源符号。
|
||||
stagger:相邻行电源引线一长一短错开,免得电源符号肩并肩顶在一起。"""
|
||||
if pin not in pp:
|
||||
raise KeyError("引脚 %s 不存在" % pin)
|
||||
dx, dy, prot = pp[pin]
|
||||
px, py = cx + dx, cy + dy
|
||||
ux, uy = pin_dir(prot)
|
||||
if stagger and net in self.prj.pwrsym:
|
||||
row = int(round((dy if abs(ux) > 0.5 else dx) / 2.54))
|
||||
if row % 2:
|
||||
length += 7.62
|
||||
ex, ey = snap(px + ux * length), snap(py + uy * length)
|
||||
self.wire(px, py, ex, ey)
|
||||
self.term(net, ex, ey, ux, uy)
|
||||
return ex, ey
|
||||
|
||||
def nc_pin(self, cx, cy, pp, pin):
|
||||
px, py = self.pin_xy(cx, cy, pp, pin)
|
||||
self.nc(px, py)
|
||||
|
||||
def two_pin(self, lib, ref, val, x, y, na, nb, rot=0, stub=6.35, dnp=False):
|
||||
"""两脚件两端各挂标签。只用于确实孤立的件;外围件请用 inline/shunt。"""
|
||||
pp = placed_pins(lib, 1, rot)
|
||||
d1, d2 = pp["1"], pp["2"]
|
||||
vertical = abs(d1[1] - d2[1]) > abs(d1[0] - d2[0])
|
||||
x, y, pp = self.comp(lib, ref, val, x, y, rot=rot, dnp=dnp,
|
||||
fields="left" if vertical else "above")
|
||||
for pin, net in (("1", na), ("2", nb)):
|
||||
self.stub(x, y, pp, pin, net, stub)
|
||||
return x, y
|
||||
|
||||
# ---------- 子电路原语 ----------
|
||||
def _ends(self, lib, rot, ux, uy):
|
||||
"""沿 (ux,uy) 方向返回 (近端脚号, 远端脚号)。"""
|
||||
p = placed_pins(lib, 1, rot)
|
||||
a, b = sorted(p.keys())[:2]
|
||||
ja = p[a][0] * ux + p[a][1] * uy
|
||||
jb = p[b][0] * ux + p[b][1] * uy
|
||||
return (a, b) if ja < jb else (b, a)
|
||||
|
||||
def dot(self, x, y):
|
||||
x, y = snap(x), snap(y)
|
||||
self.sh.junction(x, y)
|
||||
self.cv.add(x - 0.6, y - 0.6, x + 0.6, y + 0.6, "jct")
|
||||
|
||||
def term(self, net, x, y, ux, uy):
|
||||
"""线的末端:标准轨用电源符号,其余用全局标签;net=None 表示什么都不接。"""
|
||||
if net is None:
|
||||
return
|
||||
if net in self.prj.pwrsym:
|
||||
self.power(net, snap(x), snap(y), ux, uy)
|
||||
else:
|
||||
self.label(net, snap(x), snap(y), ux, uy)
|
||||
|
||||
def seg(self, x1, y1, x2, y2):
|
||||
if abs(x1 - x2) > 0.01 or abs(y1 - y2) > 0.01:
|
||||
self.wire(snap(x1), snap(y1), snap(x2), snap(y2))
|
||||
|
||||
def path(self, *pts):
|
||||
"""折线:path((x0,y0), (x1,y1), ...)"""
|
||||
for a, b in zip(pts, pts[1:]):
|
||||
self.seg(a[0], a[1], b[0], b[1])
|
||||
|
||||
def fan_series(self, cx, cy, pp, items, side=1, dist=40.0, pitch=12.7,
|
||||
tail=6.35, ch0=18.0, chstep=3.81, taps=None):
|
||||
"""一组引脚上的串联件:引脚 -L形走线-> 元件 -> 标签,元件排成一列。
|
||||
items: [(引脚号, lib, 位号, 数值, 对外网名), ...];side +1 右 / -1 左。
|
||||
taps: {引脚号: 抽头点相对 cx 的偏移},在串阻外侧留接点挂上下拉。"""
|
||||
ys = [cy + pp[p][1] for p, _, _, _, _ in items]
|
||||
y0 = sum(ys) / len(ys) - pitch * (len(items) - 1) / 2.0
|
||||
ex = snap(cx + side * dist)
|
||||
out = {}
|
||||
near, far = self._ends(items[0][1], 90, side, 0)
|
||||
for k, (pin, lib, ref, val, net) in enumerate(items):
|
||||
px, py = cx + pp[pin][0], cy + pp[pin][1]
|
||||
x, y, epp = self.comp(lib, ref, val, ex, snap(y0 + k * pitch), rot=90, fields="above")
|
||||
nx, ny = x + epp[near][0], y + epp[near][1]
|
||||
mx = snap(cx + side * (ch0 + k * chstep))
|
||||
self.seg(px, py, mx, py)
|
||||
self.seg(mx, py, mx, ny)
|
||||
self.seg(mx, ny, nx, ny)
|
||||
fx, fy = x + epp[far][0], y + epp[far][1]
|
||||
toff = (taps or {}).get(pin)
|
||||
if toff is not None:
|
||||
hx = snap(cx + side * toff)
|
||||
self.seg(fx, fy, hx, fy)
|
||||
self.dot(hx, fy)
|
||||
fx, fy = hx, fy
|
||||
out[pin] = (hx, fy)
|
||||
tx = snap(fx + side * tail)
|
||||
self.seg(fx, fy, tx, fy)
|
||||
self.term(net, tx, fy, side, 0)
|
||||
return out
|
||||
|
||||
def shunt(self, bx, by, lib, ref, val, net, down=True, tail=6.35, dnp=False,
|
||||
drop=9.0, fields="left", rot=0):
|
||||
"""从母线上一点往下(或往上)挂一个件,末端接电源符号或标签。
|
||||
退耦电容、上下拉电阻都用它。返回 (件中心x, y, 远端x, 远端y)。"""
|
||||
sgn = 1 if down else -1
|
||||
near, far = self._ends(lib, rot, 0, sgn)
|
||||
x, y, epp = self.comp(lib, ref, val, snap(bx), snap(by + sgn * drop), rot=rot,
|
||||
fields=fields, dnp=dnp)
|
||||
nx, ny = x + epp[near][0], y + epp[near][1]
|
||||
fx, fy = x + epp[far][0], y + epp[far][1]
|
||||
self.seg(bx, by, nx, ny)
|
||||
ty = snap(fy + sgn * tail)
|
||||
if tail > 0:
|
||||
self.seg(fx, fy, fx, ty)
|
||||
self.term(net, fx, ty, 0, sgn)
|
||||
return x, y, fx, fy
|
||||
|
||||
def gnd_bus(self, cx, cy, pp, pins, side=1, dist=13.0, drop=9.0, net=GND):
|
||||
"""一侧若干地脚并到一条竖直母线上,底端只放一个地符号。"""
|
||||
ys = []
|
||||
bx = snap(cx + side * dist)
|
||||
for p in pins:
|
||||
px, py = cx + pp[p][0], cy + pp[p][1]
|
||||
self.seg(px, py, bx, py)
|
||||
ys.append(snap(py))
|
||||
y0, y1 = min(ys), max(ys)
|
||||
self.seg(bx, y0, bx, snap(y1 + drop))
|
||||
for yy in ys:
|
||||
if abs(yy - y0) > 0.01:
|
||||
self.dot(bx, yy)
|
||||
self.power(net, bx, snap(y1 + drop), 0, 1)
|
||||
|
||||
def run_taps(self, px, py, ux, stops, net=None, end=8.0):
|
||||
"""从 (px,py) 沿 ±x 拉线,沿途在指定 x 上打抽头挂件,末端出标签。
|
||||
stops: [(抽头 x, 'up'|'dn', lib, 位号, 数值, 另一端的网[, dnp]), ...]"""
|
||||
cur = px
|
||||
for k, st in enumerate(stops):
|
||||
hx = snap(st[0])
|
||||
self.seg(cur, py, hx, py)
|
||||
self.shunt(hx, py, st[2], st[3], st[4], st[5], down=(st[1] == "dn"),
|
||||
dnp=(st[6] if len(st) > 6 else False))
|
||||
if k < len(stops) - 1 or net is not None:
|
||||
self.dot(hx, py)
|
||||
cur = hx
|
||||
if net is not None:
|
||||
tx = snap(cur + ux * end)
|
||||
self.seg(cur, py, tx, py)
|
||||
self.term(net, tx, py, ux, 0)
|
||||
return cur
|
||||
|
||||
def gnd_row(self, cx, cy, pp, pins, drop=6.0, tail=5.0, at=0, net=GND):
|
||||
"""一排朝下的地脚各自下拉一小段并到横母线,母线上只落一个地符号。"""
|
||||
xs = []
|
||||
for p in pins:
|
||||
px, py = cx + pp[p][0], cy + pp[p][1]
|
||||
self.seg(px, py, px, snap(py + drop))
|
||||
xs.append(snap(px))
|
||||
yb = snap(cy + pp[pins[0]][1] + drop)
|
||||
self.seg(min(xs), yb, max(xs), yb)
|
||||
gx = xs[at]
|
||||
self.seg(gx, yb, gx, snap(yb + tail))
|
||||
self.power(net, gx, snap(yb + tail), 0, 1)
|
||||
for k, xx in enumerate(xs):
|
||||
interior = 0.01 < abs(xx - min(xs)) and abs(xx - max(xs)) > 0.01
|
||||
if interior or k == at:
|
||||
self.dot(xx, yb)
|
||||
|
||||
def inline(self, x0, y0, lib, ref, val, side=1, length=12.7, dnp=False, fields="above",
|
||||
rot=90):
|
||||
"""在水平线上串一个件:从 (x0,y0) 出发,返回线另一端坐标。
|
||||
二极管这类本身横放的符号传 rot=0/180 来定正反向。"""
|
||||
near, far = self._ends(lib, rot, side, 0)
|
||||
cx = snap(x0 + side * length / 2.0)
|
||||
x, y, epp = self.comp(lib, ref, val, cx, snap(y0), rot=rot, fields=fields, dnp=dnp)
|
||||
nx, ny = x + epp[near][0], y + epp[near][1]
|
||||
fx, fy = x + epp[far][0], y + epp[far][1]
|
||||
self.seg(x0, y0, nx, ny)
|
||||
return fx, fy
|
||||
|
||||
def vline(self, x0, y0, lib, ref, val, down=True, length=12.7, dnp=False, fields="left",
|
||||
rot=0):
|
||||
"""在竖直线上串一个件:从 (x0,y0) 出发,返回线另一端坐标。"""
|
||||
sgn = 1 if down else -1
|
||||
near, far = self._ends(lib, rot, 0, sgn)
|
||||
cy = snap(y0 + sgn * length / 2.0)
|
||||
x, y, epp = self.comp(lib, ref, val, snap(x0), cy, rot=rot, fields=fields, dnp=dnp)
|
||||
nx, ny = x + epp[near][0], y + epp[near][1]
|
||||
fx, fy = x + epp[far][0], y + epp[far][1]
|
||||
self.seg(x0, y0, nx, ny)
|
||||
return fx, fy
|
||||
|
||||
def finish(self, force=False):
|
||||
out, ov = self.cv.report()
|
||||
if out or ov:
|
||||
print(" !! %s 越界 %d 处, 重叠 %d 处" % (self.fname, len(out), len(ov)))
|
||||
for b in out[:15]:
|
||||
print(" 越界 %-28s (%.1f,%.1f)-(%.1f,%.1f) 图框 (%.0f,%.0f)-(%.0f,%.0f)"
|
||||
% (b[4], b[0], b[1], b[2], b[3],
|
||||
self.cv.x0, self.cv.y0, self.cv.x1, self.cv.y1))
|
||||
for o in ov[:25]:
|
||||
print(" 重叠 %-28s x %-28s @(%.1f,%.1f)" % o)
|
||||
if not force:
|
||||
return False
|
||||
self.sh.dump(os.path.join(self.prj.out_dir, self.fname))
|
||||
print(" OK %-26s 元素 %4d 个" % (self.fname, len(self.cv.boxes)))
|
||||
return True
|
||||
Reference in New Issue
Block a user