#!/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:])