sync: HP_BOWEI 2026-10-15
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user