← Commit history

Delete design/gen/gen_schematic.py

John Lauer ·54173692e9 ·7d ago ·parent 49e2b11
1 file changed −361
design/gen/gen_schematic.pydeleted−361
@@ -1,361 +0,0 @@-#!/usr/bin/env python3-"""Generate kicad/ocp-oculink-riser.kicad_sch from design/netlist.json.--Every pin gets a short wire and a net label (a 168-pin card edge and four 42-pin receptacles read best as labelled-blocks); unused pins get no-connect flags; power nets get PWR_FLAG. Symbols are embedded (lib_symbols): standard-ones copied verbatim from KiCad 10's own libraries, project ones (OCP edge, OCuLink, the ICs) generated here with the-datasheet pin numbers and names."""-import json, os, re, sys, uuid, datetime-sys.path.insert(0, os.path.dirname(__file__))-from footprints import ocp_pinout--HERE = os.path.dirname(__file__)-ROOT = os.path.join(HERE, "..")-KSYM = "/mnt/c/Users/john/AppData/Local/Programs/KiCad/10.0/share/kicad/symbols"-OUT = os.path.join(ROOT, "kicad", "ocp-oculink-riser.kicad_sch")-NS = uuid.UUID("0b8b4a1e-7f6d-4f0e-9d3a-2a6a1c5b9e71")-nl = json.load(open(os.path.join(ROOT, "design", "netlist.json")))-pin_net = {p: n for n, ps in nl["nets"].items() for p in ps}-comp = {c["ref"]: c for c in nl["components"]}-ROOT_UUID = str(uuid.uuid5(NS, "root"))---def U(*k):-    return str(uuid.uuid5(NS, "/".join(map(str, k))))---def f(v):-    s = f"{v:.4f}".rstrip("0").rstrip(".")-    return "0" if s in ("", "-0") else s---def q(s):-    return '"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"'---# ------------------------------------------------------------------ s-expression extraction from KiCad libs-def extract(libfile, name):-    txt = open(libfile).read()-    m = re.search(r'\n[ \t]*\(symbol "' + re.escape(name) + r'"\s*\n', txt)-    if not m:-        raise SystemExit(f"{name} not in {libfile}")-    i = txt.index("(", m.start())-    depth, j, n = 0, i, len(txt)-    while j < n:-        c = txt[j]-        if c == '"':-            j += 1-            while j < n and txt[j] != '"':-                j += 2 if txt[j] == "\\" else 1-        elif c == "(":-            depth += 1-        elif c == ")":-            depth -= 1-            if depth == 0:-                return txt[i:j + 1]-        j += 1-    raise SystemExit("unbalanced")---def lib_symbol(lib, name):-    s = extract(os.path.join(KSYM, lib + ".kicad_sym"), name)-    return s.replace(f'(symbol "{name}"', f'(symbol "{lib}:{name}"', 1)---# ------------------------------------------------------------------ generated symbols-def gen_symbol(libid, refp, left, right, top=(), bottom=(), width=None, units=None, extra_text=None):-    """left/right: lists of (number, name) or None for a gap. Returns (sexp, pin_positions{number:(x,y,angle)})."""-    name = libid.split(":")[1]-    P = 2.54-    if units is None:-        units = [(left, right, top, bottom)]-    out = [f'(symbol {q(libid)} (pin_names (offset 1.016)) (exclude_from_sim no) (in_bom yes) (on_board yes)',-           f'  (property "Reference" {q(refp)} (at 0 0 0) (effects (font (size 1.27 1.27))))',-           f'  (property "Value" {q(name)} (at 0 0 0) (effects (font (size 1.27 1.27))))',-           f'  (property "Footprint" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))',-           f'  (property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) (hide yes)))']-    pos = {}-    for ui, (lf, rt, tp, bt) in enumerate(units, start=1):-        n = max(len(lf), len(rt))-        h = (n + 1) * P-        maxlen = max([len(x[1]) for x in lf + rt if x] + [4])-        w = width or max(20.32, round((maxlen * 1.27 * 2 + 6) / P) * P)-        tw = max(len(tp), len(bt))-        w = max(w, (tw + 1) * P)-        x0, y0 = -w / 2, h / 2-        out.append(f'  (symbol {q(name + f"_{ui}_1")}')-        out.append(f'    (rectangle (start {f(x0)} {f(y0)}) (end {f(-x0)} {f(-y0)}) (stroke (width 0.254) (type default)) (fill (type background)))')-        for side, lst in (("L", lf), ("R", rt)):-            for k, pn in enumerate(lst):-                if pn is None:-                    continue-                num, pname = pn-                y = y0 - (k + 1) * P-                if side == "L":-                    x, ang = x0 - P, 0-                else:-                    x, ang = -x0 + P, 180-                out.append(f'    (pin passive line (at {f(x)} {f(y)} {ang}) (length 2.54) (name {q(pname)} (effects (font (size 1.016 1.016)))) (number {q(num)} (effects (font (size 1.016 1.016)))))')-                pos[(ui, num)] = (x, y, ang)-        for side, lst in (("T", tp), ("B", bt)):-            for k, pn in enumerate(lst):-                num, pname = pn-                x = x0 + (k + 1) * P-                if side == "T":-                    y, ang = y0 + P, 270-                else:-                    y, ang = -y0 - P, 90-                out.append(f'    (pin passive line (at {f(x)} {f(y)} {ang}) (length 2.54) (name {q(pname)} (effects (font (size 1.016 1.016)))) (number {q(num)} (effects (font (size 1.016 1.016)))))')-                pos[(ui, num)] = (x, y, ang)-        out.append('  )')-    out.append(')')-    return "\n".join(out), pos---edge = ocp_pinout()---def edge_units():-    def pins(prefix, rng):-        return [(f"{prefix}{n}", edge[f"{prefix}{n}"].replace("+3.3V_EDGE", "+3V3_EDGE")) for n in rng]-    u1L = [(f"OCP_B{n}", edge[f"OCP_B{n}"]) for n in range(1, 15)] + [None] + pins("B", range(1, 17)) + [None] + pins("B", (42, 68, 69, 70))-    u1R = [(f"OCP_A{n}", edge[f"OCP_A{n}"]) for n in range(1, 15)] + [None] + pins("A", range(1, 17)) + [None] + pins("A", (42, 68, 69, 70))-    u2L = pins("B", [n for n in range(17, 42)]); u2R = pins("A", [n for n in range(17, 42)])-    u3L = pins("B", range(43, 68)); u3R = pins("A", range(43, 68))-    return [(u1L, u1R, (), ()), (u2L, u2R, (), ()), (u3L, u3R, (), ())]---def oculink_pins():-    A = {1: "PWR_3V3_RX", 2: "GND", 3: "PERp0", 4: "PERn0", 5: "GND", 6: "PERp1", 7: "PERn1", 8: "GND", 9: "BP_TYPE",-         10: "CWAKE#", 11: "GND", 12: "REFCLK+", 13: "REFCLK-", 14: "GND", 15: "PERp2", 16: "PERn2", 17: "GND",-         18: "PERp3", 19: "PERn3", 20: "GND", 21: "PWR_5V_2"}-    Bm = {1: "PWR_5V_1", 2: "GND", 3: "PETp0", 4: "PETn0", 5: "GND", 6: "PETp1", 7: "PETn1", 8: "GND", 9: "2W_CLK",-          10: "2W_DATA", 11: "GND", 12: "PERST#", 13: "CPRSNT#", 14: "GND", 15: "PETp2", 16: "PETn2", 17: "GND",-          18: "PETp3", 19: "PETn3", 20: "GND", 21: "PWR_3V3_TX"}-    return [(f"A{n}", A[n]) for n in range(1, 22)], [(f"B{n}", Bm[n]) for n in range(1, 22)], (), [("SH", "SHIELD")]---SYMS = {}-POS = {}-SYMS["Riser:OCP_NIC3_SFF_4C+"], POS["Riser:OCP_NIC3_SFF_4C+"] = gen_symbol("Riser:OCP_NIC3_SFF_4C+", "J", None, None, units=edge_units(), width=50.8)-l, r, t, b = oculink_pins()-SYMS["Riser:OCuLink_4i_Root"], POS["Riser:OCuLink_4i_Root"] = gen_symbol("Riser:OCuLink_4i_Root", "J", l, r, t, b, width=30.48)-SYMS["Riser:SN74LV165A"], POS["Riser:SN74LV165A"] = gen_symbol(-    "Riser:SN74LV165A", "U",-    [("1", "SH/~{LD}"), ("2", "CLK"), ("15", "CLK_INH"), ("10", "SER"), None, ("11", "A"), ("12", "B"), ("13", "C"),-     ("14", "D"), ("3", "E"), ("4", "F"), ("5", "G"), ("6", "H")],-    [("9", "QH"), ("7", "~{QH}")], [("16", "VCC")], [("8", "GND")], width=17.78)-SYMS["Riser:BR24H64FJ"], POS["Riser:BR24H64FJ"] = gen_symbol(-    "Riser:BR24H64FJ", "U", [("1", "A0"), ("2", "A1"), ("3", "A2"), ("7", "WP")], [("5", "SDA"), ("6", "SCL")],-    [("8", "VCC")], [("4", "GND")], width=15.24)-SYMS["Riser:TPS3808G33"], POS["Riser:TPS3808G33"] = gen_symbol(-    "Riser:TPS3808G33", "U", [("5", "SENSE"), ("3", "~{MR}"), ("4", "CT")], [("1", "~{RESET}")],-    [("6", "VDD")], [("2", "GND")], width=15.24)-SYMS["Riser:SN74LVC125A"], POS["Riser:SN74LVC125A"] = gen_symbol(-    "Riser:SN74LVC125A", "U",-    [("2", "1A"), ("1", "~{1OE}"), ("5", "2A"), ("4", "~{2OE}"), ("9", "3A"), ("10", "~{3OE}"), ("12", "4A"), ("13", "~{4OE}")],-    [("3", "1Y"), None, ("6", "2Y"), None, ("8", "3Y"), None, ("11", "4Y")], [("14", "VCC")], [("7", "GND")], width=15.24)-SYMS["Riser:TPD4E05U06"], POS["Riser:TPD4E05U06"] = gen_symbol(-    "Riser:TPD4E05U06", "U", [("1", "D1+"), ("2", "D1-"), ("4", "D2+"), ("5", "D2-")],-    [("10", "NC"), ("9", "NC"), ("7", "NC"), ("6", "NC")], (), [("3", "GND"), ("8", "GND")], width=12.7)-# Adom-style IC symbols (gen/ic_symbols.py -> adom-symbol create, chip outline baked in)-ADOMSYM = os.path.join(ROOT, "kicad", "lib", "adom-symbols")-for name in ("SN74LV165APWR", "BR24H64FJ-5ACE2", "TPS3808G33DBV", "SN74LVC125APWR", "TPD4E05U06QDQARQ1"):-    txt = extract(os.path.join(ADOMSYM, name, name + ".kicad_sym"), name)-    txt = txt.replace(f'(symbol "{name}"', f'(symbol "Riser:{name}"', 1)-    SYMS[f"Riser:{name}"] = txt-    POS[f"Riser:{name}"] = None    # filled below by std_pins-STD = {"R": ("Device", "R"), "C": ("Device", "C"), "LED": ("Device", "LED"), "TP": ("Connector", "TestPoint"),-       "JP": ("Jumper", "SolderJumper_2_Open"), "H": ("Mechanical", "MountingHole_Pad"), "PWR": ("power", "PWR_FLAG")}--# pin positions of standard symbols (read from the library text: (pin ... (at x y a) ... (number "n"-def std_pins(text):-    pos = {}-    for m in re.finditer(r'\(pin \w+ \w+\s*\(at ([-\d.]+) ([-\d.]+) ([-\d.]+)\).*?\(number "([^"]+)"', text, re.S):-        pos[(1, m.group(4))] = (float(m.group(1)), float(m.group(2)), float(m.group(3)))-    return pos---for key, (lib, name) in STD.items():-    txt = lib_symbol(lib, name)-    SYMS[f"{lib}:{name}"] = txt-    POS[f"{lib}:{name}"] = std_pins(txt)-for k in list(POS):-    if POS[k] is None:-        POS[k] = std_pins(SYMS[k])--SYMOF = {}-for c in nl["components"]:-    r = c["ref"]-    if r == "J1": SYMOF[r] = "Riser:OCP_NIC3_SFF_4C+"-    elif r in ("J2", "J3", "J4", "J5"): SYMOF[r] = "Riser:OCuLink_4i_Root"-    elif r == "U1": SYMOF[r] = "Riser:SN74LV165APWR"-    elif r == "U2": SYMOF[r] = "Riser:BR24H64FJ-5ACE2"-    elif r == "U3": SYMOF[r] = "Riser:TPS3808G33DBV"-    elif r == "U4": SYMOF[r] = "Riser:SN74LVC125APWR"-    elif r in ("U5", "U6", "U7", "U8"): SYMOF[r] = "Riser:TPD4E05U06QDQARQ1"-    elif r.startswith("R"): SYMOF[r] = "Device:R"-    elif r.startswith("C"): SYMOF[r] = "Device:C"-    elif r.startswith("D"): SYMOF[r] = "Device:LED"-    elif r.startswith("TP"): SYMOF[r] = "Connector:TestPoint"-    elif r.startswith("JP"): SYMOF[r] = "Jumper:SolderJumper_2_Open"-    elif r.startswith("H"): SYMOF[r] = "Mechanical:MountingHole_Pad"--# ------------------------------------------------------------------ placement on an A1 sheet (mm, y down; 2.54 grid)-G = 2.54-def g(v):-    return round(v / G) * G--PLACE = {}      # ref -> list of (unit, x, y, rot)-PLACE["J1"] = [(1, g(60), g(110), 0), (2, g(60), g(285), 0), (3, g(60), g(430), 0)]-for k in range(4):-    PLACE[f"J{2+k}"] = [(1, g(470 + 95 * k), g(105), 0)]-PLACE["U1"] = [(1, g(210), g(95), 0)]-PLACE["U2"] = [(1, g(210), g(190), 0)]-PLACE["U3"] = [(1, g(320), g(95), 0)]-PLACE["U4"] = [(1, g(320), g(190), 0)]-for k in range(4):-    PLACE[f"U{5+k}"] = [(1, g(470 + 95 * k), g(235), 0)]-# two-pin parts: vertical (rot 90) in rows under their blocks-row = {"scan": (g(175), g(150)), "fru": (g(175), g(240)), "power": (g(285), g(140)), "perst": (g(285), g(255)),-       "host": (g(175), g(285)), "test": (g(285), g(320))}-two = [c for c in nl["components"] if c["ref"] not in PLACE and c["ref"] not in ("H1", "H2")]-cursor = {}-for c in two:-    blk = c["block"]-    if blk.startswith("port"):-        k = int(blk[-1]); base = (g(452 + 95 * k), g(285))-    elif blk == "mech":-        base = (g(285), g(380))-    else:-        base = row.get(blk, (g(285), g(380)))-    n = cursor.get(base, 0); cursor[base] = n + 1-    PLACE[c["ref"]] = [(1, base[0] + g(15.24) * (n % 6), base[1] + g(22.86) * (n // 6), 0)]-PLACE["H1"] = [(1, g(400), g(400), 0)]-PLACE["H2"] = [(1, g(420), g(400), 0)]--# ------------------------------------------------------------------ emit-items = []-labels_at = set()---def pin_world(libid, unit, num, x, y, rot):-    px, py, ang = POS[libid][(unit, num)] if (unit, num) in POS[libid] else POS[libid][(1, num)]-    # symbol coords are y-up; rotation not used (all 0)-    return x + px, y - py, ang---def wire(x1, y1, x2, y2):-    items.append(f'  (wire (pts (xy {f(x1)} {f(y1)}) (xy {f(x2)} {f(y2)})) (stroke (width 0) (type default)) (uuid {q(U("w", x1, y1, x2, y2))}))')---def label(name, x, y, ang):-    just = "left" if ang in (0, 270) else "right"-    items.append(f'  (global_label {q(name)} (shape passive) (at {f(x)} {f(y)} {ang}) (fields_autoplaced yes) (effects (font (size 1.016 1.016)) (justify {just})) (uuid {q(U("l", name, x, y))})\n    (property "Intersheetrefs" "${{INTERSHEET_REFS}}" (at {f(x)} {f(y)} 0) (effects (font (size 1.016 1.016)) (hide yes))))')---def noconn(x, y):-    items.append(f'  (no_connect (at {f(x)} {f(y)}) (uuid {q(U("nc", x, y))}))')---syms_out = []-for c in nl["components"]:-    ref = c["ref"]; libid = SYMOF[ref]-    for (unit, x, y, rot) in PLACE[ref]:-        # snap: shift the instance so its pin ends land on the 2.54 mm grid (world x = X + px, world y = Y - py)-        _pp = [v for (u_, n_), v in POS[libid].items() if u_ == unit]-        if _pp:-            px0, py0 = _pp[0][0], _pp[0][1]-            x = x + ((-px0) % G if abs((-px0) % G - G) > 1e-6 else 0.0) - 0.0-            y = y + (py0 % G if abs(py0 % G - G) > 1e-6 else 0.0)-            x, y = round(x, 4), round(y, 4)-        props = [f'    (property "Reference" {q(ref)} (at {f(x)} {f(y - 3)} 0) (effects (font (size 1.27 1.27))))',-                 f'    (property "Value" {q(c["value"])} (at {f(x)} {f(y + 3)} 0) (effects (font (size 1.27 1.27))))',-                 f'    (property "Footprint" {q(c["fp"])} (at {f(x)} {f(y)} 0) (effects (font (size 1.27 1.27)) (hide yes)))',-                 f'    (property "Datasheet" "" (at {f(x)} {f(y)} 0) (effects (font (size 1.27 1.27)) (hide yes)))',-                 f'    (property "MPN" {q(c.get("mpn",""))} (at {f(x)} {f(y)} 0) (effects (font (size 1.27 1.27)) (hide yes)))',-                 f'    (property "Manufacturer" {q(c.get("mfr",""))} (at {f(x)} {f(y)} 0) (effects (font (size 1.27 1.27)) (hide yes)))',-                 f'    (property "Description" {q(c.get("desc",""))} (at {f(x)} {f(y)} 0) (effects (font (size 1.27 1.27)) (hide yes)))']-        if libid.startswith(("Device:", "Connector:", "Jumper:", "Mechanical:")):-            props[0] = f'    (property "Reference" {q(ref)} (at {f(x + 2.54)} {f(y - 1.27)} 0) (effects (font (size 1.27 1.27)) (justify left)))'-            props[1] = f'    (property "Value" {q(c["value"])} (at {f(x + 2.54)} {f(y + 1.27)} 0) (effects (font (size 1.27 1.27)) (justify left)))'-        pins_here = [num for (u_, num) in POS[libid] if u_ == unit]-        pinlines = "\n".join(f'    (pin {q(num)} (uuid {q(U("pin", ref, num))}))' for num in sorted(set(pins_here)))-        inbom = "no" if ref.startswith(("TP", "JP", "H")) or ref == "J1" else "yes"-        syms_out.append(f'  (symbol (lib_id {q(libid)}) (at {f(x)} {f(y)} {rot}) (unit {unit}) (exclude_from_sim no) (in_bom {inbom}) (on_board yes) (dnp no) (fields_autoplaced yes) (uuid {q(U("sym", ref, unit))})\n'-                        + "\n".join(props) + "\n" + pinlines +-                        f'\n    (instances (project "ocp-oculink-riser" (path {q("/" + ROOT_UUID)} (reference {q(ref)}) (unit {unit}))))\n  )')-        for num in sorted(set(pins_here)):-            wx, wy, ang = pin_world(libid, unit, num, x, y, rot)-            net = pin_net.get(f"{ref}.{num}")-            # direction away from the body-            d = {0: (-1, 0), 180: (1, 0), 90: (0, 1), 270: (0, -1)}[int(ang) % 360]-            ex, ey = wx + d[0] * G, wy + d[1] * G-            if net is None:-                noconn(wx, wy)-                continue-            wire(wx, wy, ex, ey)-            lang = {(-1, 0): 180, (1, 0): 0, (0, 1): 270, (0, -1): 90}[d]-            label(net, ex, ey, lang)--# PWR_FLAG on the off-board rails-pw = []-for i, net in enumerate(("GND", "+3V3_EDGE", "+12V_EDGE")):-    x, y = g(400 + 20 * i), g(440)-    syms_out.append(f'  (symbol (lib_id "power:PWR_FLAG") (at {f(x)} {f(y)} 0) (unit 1) (exclude_from_sim no) (in_bom yes) (on_board yes) (dnp no) (uuid {q(U("pwr", net))})\n'-                    f'    (property "Reference" {q(f"#FLG0{i+1}")} (at {f(x)} {f(y - 3)} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n'-                    f'    (property "Value" "PWR_FLAG" (at {f(x)} {f(y - 4.5)} 0) (effects (font (size 1.27 1.27))))\n'-                    f'    (property "Footprint" "" (at {f(x)} {f(y)} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n'-                    f'    (property "Datasheet" "" (at {f(x)} {f(y)} 0) (effects (font (size 1.27 1.27)) (hide yes)))\n'-                    f'    (pin "1" (uuid {q(U("pwrpin", net))}))\n'-                    f'    (instances (project "ocp-oculink-riser" (path {q("/" + ROOT_UUID)} (reference {q(f"#FLG0{i+1}")}) (unit 1))))\n  )')-    wire(x, y, x, y + G)-    label(net, x, y + G, 270)--# section titles and the design note-notes = [-    (g(30), g(20), "OCP NIC 3.0 SFF (Supermicro AIOM) to 4x OCuLink 4i riser - passive, PCIe Gen4 x16 as 4 x4", 3.0),-    (g(30), g(30), "Card edge J1: OCP NIC 3.0 v1.6.0 Table 20 (4C+). PRSNTB[3:0]# = 0b0011 -> 4 x4 card (Table 45). Host BIOS: set the AIOM slot to x4x4x4x4.", 1.8),-    (g(30), g(36), "Port k = PCIe lanes 4k..4k+3 with REFCLKk and PERSTk# (Table 47). OCuLink pinout: SFF-9402 Table 6-1, root side.", 1.8),-    (g(30), g(42), "No AC caps: host TX caps are on the baseboard, device TX caps are on the device. 85 ohm pairs, JLC06161H-3313 stack, see docs.", 1.8),-    (g(175), g(70), "Scan chain SR0 (mandatory, OCP 4.5.6)", 2.0),-    (g(175), g(170), "FRU EEPROM at 0xA0 + SLOT_ID (OCP 5.10)", 2.0),-    (g(285), g(70), "NIC_PWR_GOOD (OCP 4.5.7.6)", 2.0),-    (g(285), g(170), "PERST# buffers to the ports", 2.0),-    (g(470), g(70), "OCuLink ports (SFF-8611/8621), root side", 2.0),-    (g(470), g(215), "Port sideband ESD and termination", 2.0),-    (g(30), g(70), "Gold fingers - sideband and power", 2.0),-    (g(30), g(250), "Gold fingers - PCIe lanes 0-7", 2.0),-    (g(30), g(395), "Gold fingers - PCIe lanes 8-15", 2.0),-]-for (x, y, s, sz) in notes:-    items.append(f'  (text {q(s)} (exclude_from_sim no) (at {f(x)} {f(y)} 0) (effects (font (size {sz} {sz}) (thickness {f(sz*0.15)}) (bold yes)) (justify left bottom)) (uuid {q(U("t", s))}))')--today = datetime.date(2026, 10, 12).isoformat()-doc = ['(kicad_sch (version 20231120) (generator "riser-gen") (generator_version "1.0")',-       f'  (uuid {q(ROOT_UUID)})', '  (paper "A1")',-       f'  (title_block (title "OCP NIC 3.0 SFF to 4x OCuLink riser") (date {q(today)}) (rev "A") (company "Adom") (comment 1 "Generated from design/netlist.json by gen/gen_schematic.py"))',-       '  (lib_symbols']-for k, v in SYMS.items():-    doc.append("    " + v.replace("\n", "\n    "))-doc.append('  )')-doc += items-doc += syms_out-doc.append('  (sheet_instances (path "/" (page "1")))')-doc.append(')')-open(OUT, "w").write("\n".join(doc) + "\n")-print("wrote", OUT, len(syms_out), "symbol instances")--# ------------------------------------------------------------------ project symbol library and lib tables-lib = ['(kicad_symbol_lib (version 20231120) (generator "riser-gen") (generator_version "1.0")']-for k, v in SYMS.items():-    if k.startswith("Riser:"):-        lib.append("  " + v.replace(f'(symbol "{k}"', f'(symbol "{k.split(":")[1]}"', 1).replace("\n", "\n  "))-lib.append(")")-os.makedirs(os.path.join(ROOT, "kicad", "lib"), exist_ok=True)-open(os.path.join(ROOT, "kicad", "lib", "Riser.kicad_sym"), "w").write("\n".join(lib) + "\n")-open(os.path.join(ROOT, "kicad", "sym-lib-table"), "w").write(-    '(sym_lib_table\n  (version 7)\n  (lib (name "Riser")(type "KiCad")(uri "${KIPRJMOD}/lib/Riser.kicad_sym")(options "")(descr "OCP OCuLink riser project symbols"))\n)\n')-open(os.path.join(ROOT, "kicad", "fp-lib-table"), "w").write(-    '(fp_lib_table\n  (version 7)\n  (lib (name "Riser")(type "KiCad")(uri "${KIPRJMOD}/lib/Riser.pretty")(options "")(descr "OCP OCuLink riser project footprints"))\n)\n')-print("wrote Riser.kicad_sym and lib tables")