app
Adom Library
Public Made by Adomby adom
adom-lbr — the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad ⇄ Altium ⇄ EAGLE/Fusion) through one canonical adom-lbr JSON — symbol + footp
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
#!/usr/bin/env python3
"""Make a delivered KiCad project self-contained.
Extracts the symbols embedded in the schematics' (lib_symbols ...) and the
footprints embedded in the .kicad_pcb into a project library named <libnick>,
re-points every lib_id / footprint reference to that library, and writes
project-local sym-lib-table / fp-lib-table (using ${KIPRJMOD}). Reads/writes the
files in <work>; prints JSON {root:[...], pretty:[...], symbols, footprints}.
usage: selfcontain.py <work_dir> <libnick> <file...>
"""
import sys, os, re, json
work, nick = sys.argv[1], sys.argv[2]
names = sys.argv[3:]
def balanced(s, start):
d = 0
for i in range(start, len(s)):
if s[i] == '(':
d += 1
elif s[i] == ')':
d -= 1
if d == 0:
return i + 1
return len(s)
def top_symbols(section):
"""Top-level (symbol "NAME" ...) blocks inside a section (skips nested)."""
out, i = [], 0
while True:
p = section.find('(symbol "', i)
if p < 0:
break
ns = p + len('(symbol "')
ne = section.find('"', ns)
e = balanced(section, p)
out.append((section[ns:ne], section[p:e]))
i = e
return out
def repoint(content, needle, nick):
"""Rewrite the library part of `needle "LIB:NAME"` occurrences to `nick`."""
out, i = [], 0
n = len(needle)
while True:
p = content.find(needle, i)
if p < 0:
out.append(content[i:])
break
vs = p + n
ve = content.find('"', vs)
val = content[vs:ve]
out.append(content[i:vs])
out.append(nick + ':' + val.split(':', 1)[1] if ':' in val else val)
i = ve
return ''.join(out)
schs = [n for n in names if n.endswith('.kicad_sch')]
pcbs = [n for n in names if n.endswith('.kicad_pcb')]
# ---- 1. collect symbols from every schematic's lib_symbols ----
symbols = {}
for f in schs:
c = open(os.path.join(work, f), encoding='utf-8').read()
ls = c.find('(lib_symbols')
if ls < 0:
continue
section = c[ls:balanced(c, ls)]
for name, block in top_symbols(section):
bare = name.split(':', 1)[1] if ':' in name else name
if bare not in symbols:
symbols[bare] = block.replace('"' + name + '"', '"' + bare + '"', 1)
lib = '(kicad_symbol_lib\n\t(version 20231120)\n\t(generator "adom-lbr")\n'
for b in symbols.values():
lib += '\t' + b + '\n'
lib += ')\n'
open(os.path.join(work, nick + '.kicad_sym'), 'w', encoding='utf-8').write(lib)
# ---- 2. re-point the schematics ----
for f in schs:
path = os.path.join(work, f)
c = open(path, encoding='utf-8').read()
c = repoint(c, '(lib_id "', nick)
c = repoint(c, '(symbol "', nick) # lib_symbols keys (the colon'd ones)
c = repoint(c, '(property "Footprint" "', nick)
open(path, 'w', encoding='utf-8').write(c)
# ---- 3. extract footprints from the PCB into <nick>.pretty ----
pretty = os.path.join(work, nick + '.pretty')
os.makedirs(pretty, exist_ok=True)
pfiles = []
UUID = re.compile(r'\s*\(uuid "[^"]*"\)')
NET = re.compile(r'\s*\(net \d+ "[^"]*"\)')
ATPLACE = re.compile(r'\(at [-\d.eE ]+\)')
HEADER = '\n\t(version 20240108)\n\t(generator "adom-lbr")\n\t(generator_version "9.0")'
for f in pcbs:
path = os.path.join(work, f)
c = open(path, encoding='utf-8').read()
i, seen = 0, set()
while True:
p = c.find('(footprint "', i)
if p < 0:
break
ns = p + len('(footprint "')
ne = c.find('"', ns)
name = c[ns:ne]
bare = name.split(':', 1)[1] if ':' in name else name
e = balanced(c, p)
if bare not in seen:
seen.add(bare)
blk = c[p:e].replace('"' + name + '"', '"' + bare + '"', 1)
blk = ATPLACE.sub('', blk, count=1) # drop the footprint's board placement (first (at ...))
blk = UUID.sub('', blk) # drop uuids (instance-specific)
blk = NET.sub('', blk) # drop pad nets (instance-specific)
# a *library* footprint needs a header right after the name
blk = blk.replace('(footprint "' + bare + '"', '(footprint "' + bare + '"' + HEADER, 1)
open(os.path.join(pretty, bare + '.kicad_mod'), 'w', encoding='utf-8').write(blk + '\n')
pfiles.append(os.path.join(pretty, bare + '.kicad_mod'))
i = e
c = repoint(c, '(footprint "', nick)
open(path, 'w', encoding='utf-8').write(c)
# ---- 4. project-local lib tables ----
open(os.path.join(work, 'sym-lib-table'), 'w', encoding='utf-8').write(
'(sym_lib_table\n (version 7)\n (lib (name "%s")(type "KiCad")(uri "${KIPRJMOD}/%s.kicad_sym")(options "")(descr "adom-lbr self-contained project symbols"))\n)\n' % (nick, nick))
open(os.path.join(work, 'fp-lib-table'), 'w', encoding='utf-8').write(
'(fp_lib_table\n (version 7)\n (lib (name "%s")(type "KiCad")(uri "${KIPRJMOD}/%s.pretty")(options "")(descr "adom-lbr self-contained project footprints"))\n)\n' % (nick, nick))
root = [os.path.join(work, f) for f in names] + [
os.path.join(work, nick + '.kicad_sym'),
os.path.join(work, 'sym-lib-table'),
os.path.join(work, 'fp-lib-table'),
]
print(json.dumps({'root': root, 'pretty': pfiles, 'symbols': len(symbols), 'footprints': len(pfiles)}))