main
noah amfm-receiver-molecule: KiCad project, fab files, 3D and README (7/7) ef18fb3 1d ago
"""Load KiCad symbols from .kicad_sym libraries, flatten 'extends', report pins."""
import copy, os
from sexp import parse, find, findall, Sym

SYMDIR = '/usr/share/kicad/symbols'
_cache = {}

def load_lib(path):
    if path not in _cache:
        with open(path) as f:
            _cache[path] = parse(f.read())
    return _cache[path]

def get_symbol(lib_id, extra_libs=None):
    """Return flattened symbol s-expr (list) named like the lib item (no lib prefix)."""
    lib, name = lib_id.split(':')
    path = (extra_libs or {}).get(lib, os.path.join(SYMDIR, lib + '.kicad_sym'))
    root = load_lib(path)
    syms = {s[1]: s for s in findall(root, 'symbol')}
    s = copy.deepcopy(syms[name])
    ext = find(s, 'extends')
    if ext:
        parent = copy.deepcopy(syms[ext[1]])
        # parent may itself extend
        while find(parent, 'extends'):
            gp = copy.deepcopy(syms[find(parent, 'extends')[1]])
            parent = merge(gp, parent)
        s = merge(parent, s)
    return s

def merge(parent, child):
    """Child properties override parent's; graphics/pins from parent (units renamed)."""
    pname, cname = parent[1], child[1]
    out = [Sym('symbol'), cname]
    child_props = {p[1]: p for p in findall(child, 'property')}
    for item in parent[2:]:
        if isinstance(item, list) and item and item[0] == 'property':
            out.append(child_props.pop(item[1], item))
        elif isinstance(item, list) and item and item[0] == 'symbol':
            sub = copy.deepcopy(item)
            sub[1] = sub[1].replace(pname, cname, 1)
            out.append(sub)
        else:
            out.append(item)
    for p in child_props.values():
        out.append(p)
    # other child-level keys (e.g. in_bom) override
    for item in child[2:]:
        if isinstance(item, list) and item and item[0] not in ('property', 'extends', 'symbol'):
            for i, o in enumerate(out):
                if isinstance(o, list) and o and o[0] == item[0]:
                    out[i] = item
                    break
    return out

def pins(sym):
    """List of dicts: unit, convert, number, name, type, x, y, angle, length."""
    res = []
    for sub in findall(sym, 'symbol'):
        nm = sub[1]
        parts = nm.rsplit('_', 2)
        unit, conv = int(parts[-2]), int(parts[-1])
        for p in findall(sub, 'pin'):
            at = find(p, 'at')
            res.append(dict(unit=unit, convert=conv, type=str(p[1]), shape=str(p[2]),
                            x=float(at[1]), y=float(at[2]), angle=float(at[3]) if len(at) > 3 else 0.0,
                            length=float(find(p, 'length')[1]),
                            name=find(p, 'name')[1], number=find(p, 'number')[1],
                            hidden=any(isinstance(z, Sym) and z == 'hide' for z in p) or
                                   bool(find(p, 'hide') and find(p, 'hide')[1] == 'yes')))
    return res

def prop(sym, key):
    for p in findall(sym, 'property'):
        if p[1] == key:
            return p[2]
    return None

if __name__ == '__main__':
    import sys
    for lid in sys.argv[1:]:
        s = get_symbol(lid)
        print('==', lid, '| footprint:', prop(s, 'Footprint'), '| ref:', prop(s, 'Reference'))
        units = sorted(set(p['unit'] for p in pins(s)))
        for p in sorted(pins(s), key=lambda p: (p['unit'], int(p['number']) if p['number'].isdigit() else 999, p['number'])):
            print('   u%d c%d pin %-4s %-14s %-14s at(%7.2f,%7.2f) ang %3d len %.2f %s' % (
                p['unit'], p['convert'], p['number'], p['name'], p['type'], p['x'], p['y'], p['angle'], p['length'], 'HIDDEN' if p['hidden'] else ''))