sync: HP_BOWEI 2026-10-15

This commit is contained in:
Myfro9
2026-10-15 01:59:40 +08:00
parent 771acd4171
commit a0a3445bfd
21 changed files with 3042 additions and 16 deletions
+138
View File
@@ -0,0 +1,138 @@
"""工程封装库:从参考设计的 .kicad_pcb 按位号提取封装 / 从 KiCad 官方库拷贝。
**必须用 KiCad 自带的 python 跑**(要 import pcbnew):~/skills_env.md 的 KICAD_PYTHON。
from schgen.fplib import FpLib
lib = FpLib(r"E:\\...\\lib\\myboard.pretty")
lib.from_pcb(r"E:\\...\\SMOD_V1.1.kicad_pcb", [("WSON-10-0R5", "U703", "TPS7A9201")])
lib.from_kicad([("SOT-23", "Package_TO_SOT_SMD", "SOT-23", "Q")])
lib.report()
取封装的顺序(G-0033):① 项目已有参考设计 ② KiCad 官方库(本地与线上)③ 第三方
④ 按 datasheet 自己画。厂商私有 case style 往往就是某个标准封装,先看 datasheet 末页。
pcbnew swig 的坑(G-0038):便捷函数 FootprintLoad() 不能用,要走 PCB_IO_MGR 插件;
对 BOARD Remove() 过 FOOTPRINT 之后 swig 类型注册会坏,所以这里只读板子、不改板子。
"""
import os
import pcbnew
MIN_TXT_H = pcbnew.FromMM(0.8) # 丝印下限(嘉立创):字高 0.8 / 笔宽 0.15
MIN_TXT_W = pcbnew.FromMM(0.15)
class FpLib:
def __init__(self, pretty):
self.out = pretty
os.makedirs(pretty, exist_ok=True)
self.io = pcbnew.PCB_IO_MGR.FindPlugin(pcbnew.PCB_IO_MGR.KICAD_SEXP)
self.ok, self.miss = [], []
self._boards = {}
@staticmethod
def fixup(fp):
# 无编号且孔环为 0 的"焊盘"其实是机械定位孔,改成 NPTH,否则 DRC 报 annular_width
for pad in fp.Pads():
if pad.GetNumber():
continue
d, s = pad.GetDrillSize(), pad.GetSize()
if d.x and d.x >= s.x - 1 and d.y >= s.y - 1:
pad.SetAttribute(pcbnew.PAD_ATTRIB_NPTH)
pad.SetLayerSet(pcbnew.PAD.UnplatedHoleMask())
texts = [fp.Reference(), fp.Value()]
for it in fp.GraphicalItems():
if it.GetClass() in ("PCB_TEXT", "FP_TEXT"):
texts.append(it)
for it in texts:
if it.GetTextHeight() < MIN_TXT_H:
it.SetTextSize(pcbnew.VECTOR2I(MIN_TXT_H, MIN_TXT_H))
if it.GetTextThickness() < MIN_TXT_W:
it.SetTextThickness(MIN_TXT_W)
def save(self, fp, name, fix=None):
fp.SetOrientationDegrees(0) # 板上实例还原成库里的朝向
if fp.IsFlipped():
fp.Flip(fp.GetPosition(), pcbnew.FLIP_DIRECTION_TOP_BOTTOM
if hasattr(pcbnew, "FLIP_DIRECTION_TOP_BOTTOM") else False)
fp.SetPosition(pcbnew.VECTOR2I(0, 0))
fp.SetFPID(pcbnew.LIB_ID("", name))
fp.SetReference("REF**")
fp.SetValue(name)
fp.SetPath(pcbnew.KIID_PATH())
self.fixup(fp)
if fix:
fix(fp)
self.io.FootprintSave(self.out, fp)
return sorted({p.GetNumber() for p in fp.Pads()}, key=lambda s: (len(s), s))
def from_pcb(self, pcb, items, fix=None):
"""items: [(目标封装名, 参考板上的位号, 说明), ...]"""
if pcb not in self._boards:
print("载入 %s ..." % pcb)
self._boards[pcb] = pcbnew.LoadBoard(pcb)
brd = self._boards[pcb]
for name, ref, note in items:
fp = brd.FindFootprintByReference(ref)
if fp is None:
self.miss.append((name, "%s 里找不到位号 %s" % (os.path.basename(pcb), ref)))
continue
src = "%s:%s(%s)" % (os.path.basename(pcb), fp.GetFPID().GetUniStringLibItemName(), ref)
self.ok.append((name, src, self.save(fp, name, fix), note))
def from_kicad(self, items, fp_dir=None, fix=None):
"""items: [(目标封装名, 库名, 库内封装名, 说明), ...];目标名给 None 表示沿用原名。"""
from .env import kicad_footprint_dir
fp_dir = fp_dir or kicad_footprint_dir()
for name, lib, src, note in items:
try:
fp = self.io.FootprintLoad(os.path.join(fp_dir, lib + ".pretty"), src)
if fp is None:
raise RuntimeError("FootprintLoad 返回 None")
self.ok.append((name or src, "KiCad:%s/%s" % (lib, src),
self.save(fp, name or src, fix), note))
except Exception as e:
self.miss.append((name or src, "%s: %s" % (type(e).__name__, e)))
def add_courtyard(self, name, rect=None, margin=0.25, silk_rect=False):
"""给库里的封装补 F.CrtYd(参考设计提来的常常没有,DRC 就查不到它和别人重叠)。
rect=(x0, y0, x1, y1) 给实物外形(封装本地坐标,mm,如简牛护套);不给就按焊盘+丝印外框。
silk_rect=True 时同时在 F.SilkS 画出这个外形。已有 courtyard 的先清掉再画。"""
fp = self.io.FootprintLoad(self.out, name)
for g in list(fp.GraphicalItems()):
if g.GetLayer() == pcbnew.F_CrtYd:
fp.Remove(g)
if rect is None:
xs, ys = [], []
for p in fp.Pads():
bb = p.GetBoundingBox()
xs += [bb.GetX(), bb.GetRight()]; ys += [bb.GetY(), bb.GetBottom()]
for g in fp.GraphicalItems():
if g.GetLayer() in (pcbnew.F_SilkS, pcbnew.F_Fab) and g.GetClass() != "PCB_TEXT":
bb = g.GetBoundingBox()
xs += [bb.GetX(), bb.GetRight()]; ys += [bb.GetY(), bb.GetBottom()]
rect = (pcbnew.ToMM(min(xs)), pcbnew.ToMM(min(ys)), pcbnew.ToMM(max(xs)), pcbnew.ToMM(max(ys)))
x0, y0, x1, y1 = rect[0] - margin, rect[1] - margin, rect[2] + margin, rect[3] + margin
layers = [pcbnew.F_CrtYd] + ([pcbnew.F_SilkS] if silk_rect else [])
for lay in layers:
s = pcbnew.PCB_SHAPE(fp)
s.SetShape(pcbnew.SHAPE_T_RECT)
s.SetStart(pcbnew.VECTOR2I(pcbnew.FromMM(x0 if lay == pcbnew.F_CrtYd else rect[0]),
pcbnew.FromMM(y0 if lay == pcbnew.F_CrtYd else rect[1])))
s.SetEnd(pcbnew.VECTOR2I(pcbnew.FromMM(x1 if lay == pcbnew.F_CrtYd else rect[2]),
pcbnew.FromMM(y1 if lay == pcbnew.F_CrtYd else rect[3])))
s.SetLayer(lay)
s.SetWidth(pcbnew.FromMM(0.05 if lay == pcbnew.F_CrtYd else 0.15))
fp.Add(s)
fp.Reference().SetTextAngleDegrees(0) # 参考设计里常是 180°,看着像镜像
self.io.FootprintSave(self.out, fp)
print(" courtyard %-20s (%.2f,%.2f)-(%.2f,%.2f)" % (name, x0, y0, x1, y1))
def report(self):
print("\n写出 %s" % self.out)
for name, src, pads, note in self.ok:
ps = ",".join(pads) if len(pads) <= 12 else "%s..%s (%d)" % (pads[0], pads[-1], len(pads))
print(" %-34s pads=%-22s <- %-40s %s" % (name, ps, src, note))
for name, why in self.miss:
print(" !! %-31s %s" % (name, why))
return 0 if not self.miss else 1