molecule
AM/FM Receiver Molecule
Public Made by Adomby adom
USB-powered AM/FM receiver on an Adom Molecule: discrete BJT and op-amp superhet (10.7 MHz and 455 kHz IFs), STM32G0B1 tuning, self-calibration and USB audio over one USB-C cable. 96 x 96 mm pin grid, 4 layers, KiCad 10.
main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
"""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 ''))