1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-3.0-or-later
"""Add a pin-compatible variant of a symbol to a KiCad symbol library.
The variant keeps the geometry of the base symbol (every pin number at the
same position), so "Change symbol" in the schematic swaps the two without
breaking wires. Pin names and the description come from the variant's kipart
CSV (as written by lattice2kipart.py).
derive_symbol.py LIB.kicad_sym BASE_NAME VARIANT.csv > OUT.kicad_sym
"""
import csv
import re
import sys
def match_paren(t, i):
depth = 0
j = i
while True:
c = t[j]
if c == '"':
j += 1
while t[j] != '"':
j += 2 if t[j] == "\\" else 1
elif c == "(":
depth += 1
elif c == ")":
depth -= 1
if depth == 0:
return j + 1
j += 1
lib_path, base, var_csv = sys.argv[1:4]
lib = open(lib_path).read()
rows = list(csv.reader(open(var_csv, newline="")))
variant = rows[0][0]
props = {r[0][:-1].replace("*", ""): r[1] for r in rows[1:] if r and r[0].endswith(":")}
hdr = next(i for i, r in enumerate(rows) if r and r[0] == "pin")
cols = rows[hdr]
pins = {r[cols.index("pin")]: (r[cols.index("name")], r[cols.index("type")])
for r in rows[hdr + 1:] if r and r[0]}
m = re.search(r'\(symbol "' + re.escape(base) + r'"', lib)
start = m.start()
block = lib[start:match_paren(lib, start)]
base_types = {n: t for t, n in re.findall(r'\(pin (\w+) \w+ .*?\(number "([^"]+)"', block, re.S)}
new = block.replace(f'(symbol "{base}"', f'(symbol "{variant}"', 1)
new = new.replace(f'(symbol "{base}_', f'(symbol "{variant}_')
new = re.sub(r'(\(property "Value" )"[^"]*"', rf'\1"{variant}"', new, count=1)
for key in ("Description", "Source"):
if key in props:
new = re.sub(r'(\(property "' + key + r'" )"[^"]*"', lambda mm: f'{mm.group(1)}"{props[key]}"',
new, count=1)
def rename(mm):
num = mm.group(3)
name, typ = pins[num]
if typ != base_types[num] and not (base_types[num] == "passive"): # stacked pins are passive
sys.exit(f"pin {num}: type {typ} differs from {base_types[num]}")
return f'{mm.group(1)}(name "{name}"{mm.group(2)}(number "{num}"'
new, n = re.subn(r'(\(pin [^\n]*\n(?:(?!\(pin ).)*?)\(name "[^"]*"(.*?)\(number "([^"]+)"',
rename, new, flags=re.S)
if n != len(pins):
sys.exit(f"renamed {n} pins, variant has {len(pins)}")
end = match_paren(lib, start)
sys.stdout.write(lib[:end] + "\n " + new + lib[end:])
|