project
Adom Custom Electronic Load
Public Made by Adomby adom
Independent compact digitally controlled load design for 3rdPartyFab; research and AIFlow evidence in progress
main
John Lauer
R0 engineering checkpoint: checked schematic, sourcing, servo evidence and safety-policy code; PCB and qualification incomplete
10054d3
1h ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
"""Minimal KiCad s-expression reader/writer (enough to lift symbols out of a library and write a schematic)."""
import re
_TOK = re.compile(r'\s*(?:(\()|(\))|("(?:[^"\\]|\\.)*")|([^\s()"]+))', re.S)
class Sym(str):
"""An unquoted atom (keyword, number)."""
def parse(text):
stack, cur = [], []
for m in _TOK.finditer(text):
lp, rp, qs, atom = m.groups()
if lp:
stack.append(cur); cur = []
elif rp:
done = cur; cur = stack.pop(); cur.append(done)
elif qs is not None:
cur.append(qs[1:-1].replace('\\"', '"').replace("\\\\", "\\"))
elif atom is not None:
cur.append(Sym(atom))
assert not stack
return cur[0] if len(cur) == 1 else cur
def q(s):
s = str(s).replace(chr(10), "__ADOM_NEWLINE__")
return '"' + str(s).replace("\\", "\\\\").replace('"', '\\"').replace("__ADOM_NEWLINE__", "\\n") + '"'
def dump(x, ind=0):
if isinstance(x, list):
if not x:
return "()"
head = dump(x[0])
simple = all(not isinstance(e, list) for e in x)
if simple:
return "(" + " ".join(dump(e) for e in x) + ")"
pad = "\t" * (ind + 1)
parts = [head] + [e for e in x[1:]]
out = "(" + head
for e in x[1:]:
out += ("\n" + pad + dump(e, ind + 1)) if isinstance(e, list) else (" " + dump(e))
return out + "\n" + "\t" * ind + ")"
if isinstance(x, Sym):
return str(x)
if isinstance(x, (int, float)):
return f"{x:g}" if isinstance(x, float) else str(x)
return q(x)
def find(node, key):
return [e for e in node if isinstance(e, list) and e and e[0] == key]
def first(node, key):
r = find(node, key)
return r[0] if r else None
def lib_symbols(path):
lib = parse(open(path, encoding="utf-8").read())
return {s[1]: s for s in find(lib, "symbol")}
def flatten(name, syms, libname):
"""Return the symbol `name` as a standalone lib_symbols entry named libname:name (extends resolved)."""
import copy
s = copy.deepcopy(syms[name])
ext = first(s, "extends")
if ext is not None:
base = copy.deepcopy(syms[ext[1]])
props = {p[1]: p for p in find(s, "property")}
body = [e for e in base[2:] if not (isinstance(e, list) and e and e[0] == "property")]
bprops = [props.get(p[1], p) for p in find(base, "property")]
for p in find(s, "property"):
if p[1] not in {bp[1] for bp in bprops}:
bprops.append(p)
s = [Sym("symbol"), name] + [e for e in s[2:] if isinstance(e, list) and e[0] not in ("extends", "property")] + bprops + body
# rename unit sub-symbols base_N_M -> name_N_M
for e in s:
if isinstance(e, list) and e and e[0] == "symbol" and isinstance(e[1], str) and e[1].startswith(ext[1] + "_"):
e[1] = name + e[1][len(ext[1]):]
s[1] = f"{libname}:{name}"
return s
def pins(sym):
"""[(number, name, x, y, angle)] across all units of a flattened symbol."""
out = []
for u in find(sym, "symbol"):
for p in find(u, "pin"):
at = first(p, "at"); nm = first(p, "name"); no = first(p, "number")
out.append((no[1], nm[1], float(at[1]), float(at[2]), float(at[3]) if len(at) > 3 else 0.0))
return out