← Commit history

Delete design/gen/silk.py

John Lauer ·c6832ad6ed ·7d ago ·parent 58f7852
1 file changed −299
design/gen/silk.pydeleted−299
@@ -1,299 +0,0 @@-"""Silkscreen layout for the riser (run with KiCad 10's python.exe).--Reads the saved board, builds per-face obstacles (pads with their mask openings, vias, holes, part courtyards, the-board edge, the OCP long-edge strips, the finger field) and places, greedily with a bounded search of positions,-rotations and sizes measured with KiCad's own stroke-font bounds:-  - every reference (and every R/C value beside it), test-point net labels, port and LED legends on F.SilkS,-  - finger pin numbers at the ends of each finger group on both faces,-  - the board identity, link and the BIOS note on B.SilkS.-Writes design/silk_plan.json (field updates by native field UUID, standalone texts) and, with "apply", a copy of the-board with the plan applied for DRC (design/silk_check.kicad_pcb). Every number in the plan is from this board."""-import json, os, sys, math-import pcbnew--HERE = os.path.dirname(os.path.abspath(__file__))-ROOT = os.path.dirname(HERE)-SRC = sys.argv[1]-MODE = sys.argv[2] if len(sys.argv) > 2 else "plan"-MM, TO = pcbnew.FromMM, pcbnew.ToMM-b = pcbnew.LoadBoard(SRC)--STROKE = 0.15          # JLC pack: min text thickness 0.15 mm-REF_SIZES = (1.0, 0.8)  # JLC pack: min text height 0.8 mm-VAL_SIZES = (0.8,)-CLR = 0.15             # silk to exposed copper / other silk--KEYED = {"R11", "R12", "R13", "R14", "R15", "R16", "R17", "R18"}   # PERST# series 33R and pull-down 100k block-TP_LABEL = {"TP1": "3V3", "TP2": "GND", "TP3": "SMCLK", "TP4": "SMDAT", "TP5": "12V", "TP6": "PWRGD",-            "TP7": "PERST0", "TP8": "PERST1", "TP9": "PERST2", "TP10": "PERST3", "TP11": "SCLK", "TP12": "SDOUT"}-LED_LABEL = {"D1": "3V3", "D2": "12V"}---def box(x0, y0, x1, y1):-    return (min(x0, x1), min(y0, y1), max(x0, x1), max(y0, y1))---def grow(r, d):-    return (r[0] - d, r[1] - d, r[2] + d, r[3] + d)---def hit(r, s):-    return not (r[2] <= s[0] or s[2] <= r[0] or r[3] <= s[1] or s[3] <= r[1])---def bb(item):-    e = item.GetBoundingBox()-    return box(TO(e.GetLeft()), TO(e.GetTop()), TO(e.GetRight()), TO(e.GetBottom()))---# ------------------------------------------------------------------ obstacles per face-obst = {"F": [], "B": []}-for fp in b.GetFootprints():-    for side, lay in (("F", pcbnew.F_CrtYd), ("B", pcbnew.B_CrtYd)):-        cy = fp.GetCourtyard(lay)-        if cy.OutlineCount():-            e = cy.BBox()-            r = box(TO(e.GetLeft()), TO(e.GetTop()), TO(e.GetRight()), TO(e.GetBottom()))-            if fp.GetReference() != "J1":           # J1's courtyard is the finger field, handled below-                obst[side].append(("crtyd", fp.GetReference(), r))-    for p in fp.Pads():-        r = grow(bb(p), CLR + 0.05)                 # mask expansion + clearance-        if p.IsOnLayer(pcbnew.F_Cu) or p.GetAttribute() in (pcbnew.PAD_ATTRIB_PTH, pcbnew.PAD_ATTRIB_NPTH):-            obst["F"].append(("pad", fp.GetReference(), r))-        if p.IsOnLayer(pcbnew.B_Cu) or p.GetAttribute() in (pcbnew.PAD_ATTRIB_PTH, pcbnew.PAD_ATTRIB_NPTH):-            obst["B"].append(("pad", fp.GetReference(), r))-# vias are tented on both faces (board setup), so silk may cross them; only exposed copper is an obstacle-W, H = 76.0, 115.0-for side in ("F", "B"):-    obst[side] += [("edge", "", (-1, -1, 3.05, H + 1)), ("edge", "", (W - 3.05, -1, W + 1, H + 1)),   # strips + margin-                   ("edge", "", (-1, -1, W + 1, 0.4)), ("fingers", "", (-1, 111.7, W + 1, H + 1))]--placed = {"F": [], "B": []}---def free(side, r, ignore=()):-    if r[0] < 0.4 or r[2] > W - 0.4 or r[1] < 0.4 or r[3] > H - 0.4:-        return False-    for kind, ref, s in obst[side]:-        if ref in ignore and kind == "crtyd":-            continue-        if hit(r, s):-            return False-    return not any(hit(grow(r, CLR / 2), grow(s, CLR / 2)) for s in placed[side])---def text_box(text, x, y, size, rot, side, align="center"):-    t = pcbnew.PCB_TEXT(b)-    t.SetText(text); t.SetTextSize(pcbnew.VECTOR2I(MM(size), MM(size))); t.SetTextThickness(MM(STROKE))-    t.SetPosition(pcbnew.VECTOR2I(MM(x), MM(y))); t.SetTextAngleDegrees(rot)-    t.SetLayer(pcbnew.F_SilkS if side == "F" else pcbnew.B_SilkS)-    t.SetHorizJustify(pcbnew.GR_TEXT_H_ALIGN_CENTER if align == "center" else pcbnew.GR_TEXT_H_ALIGN_LEFT)-    if side == "B":-        t.SetMirrored(True)-    return bb(t)---def search(text, cx, cy, around, side, sizes, ignore=(), prefer=None):-    """Positions on a ring around `around` (a box), nearest first, at each size and rotation."""-    best = None-    for size in sizes:-        for rot in (0, 90):-            for gap in (0.2, 0.5, 0.9, 1.4, 2.0, 2.8):-                cands = []-                x0, y0, x1, y1 = around-                mx, my = (x0 + x1) / 2, (y0 + y1) / 2-                tb = text_box(text, 0, 0, size, rot, side)-                hw, hh = (tb[2] - tb[0]) / 2, (tb[3] - tb[1]) / 2-                pts = []-                for k in range(-6, 7):          # slide along each side of the part-                    o = 0.35 * k-                    pts += [(mx + o, y0 - gap - hh), (mx + o, y1 + gap + hh), (x0 - gap - hw, my + o), (x1 + gap + hw, my + o)]-                for (px, py) in pts:-                    cands.append((math.hypot(px - cx, py - cy), px, py))-                if prefer:-                    cands.sort(key=lambda c: (c[0] + (0 if prefer(c[1], c[2]) else 50)))-                else:-                    cands.sort()-                for _, px, py in cands:-                    r = text_box(text, px, py, size, rot, side)-                    # centre the box on (px, py): KiCad positions by the text anchor, so shift-                    dx, dy = px - (r[0] + r[2]) / 2, py - (r[1] + r[3]) / 2-                    r = (r[0] + dx, r[1] + dy, r[2] + dx, r[3] + dy)-                    if free(side, r, ignore):-                        return dict(x=round(px + dx, 3), y=round(py + dy, 3), size=size, rot=rot, box=r)-    return best---plan = {"fields": [], "texts": [], "unresolved": []}-# labels that failed on an earlier pass go first (a bounded repack instead of a single greedy order)-_pri_path = os.path.join(ROOT, "design", "silk_priority.json")-PRI = json.load(open(_pri_path)) if os.path.exists(_pri_path) else []-fps = sorted(b.GetFootprints(), key=lambda f: (f.GetReference() not in PRI, PRI.index(f.GetReference()) if f.GetReference() in PRI else 0,-                                               f.GetReference()[0], f.GetPosition().y, f.GetPosition().x))--# ------------------------------------------------------------------ fixed legends first (they anchor the reading)-def add_text(text, x, y, size, side, rot=0, align="center", why=""):-    r = text_box(text, x, y, size, rot, side, align)-    placed[side].append(r)-    plan["texts"].append(dict(text=text, layer=f"{side}.SilkS", x=round(x, 3), y=round(y, 3), height=size,-                              stroke=STROKE, rotation=rot, align=align, why=why))---# finger group ends, both faces: B pins on top, A pins below (A under B)-J1 = b.FindFootprintByReference("J1")-jx, jy = TO(J1.GetPosition().x), TO(J1.GetPosition().y)-ends = [("OCP_B1", -30.400), ("OCP_B14", -30.400 + 13 * 0.6), ("B1", -18.205), ("B28", -2.005), ("B29", 2.005),-        ("B42", 9.805), ("B43", 14.715), ("B70", 30.915)]-for k, (name, dx) in enumerate(ends):-    # first pin of a group: text runs inward (right) from the finger; last pin: inward (left); clear of the slots-    for side, nm in (("F", name), ("B", name.replace("B", "A"))):-        r = text_box(nm, 0, 0, 0.8, 0, side)-        half = (r[2] - r[0]) / 2-        x = jx + dx + (half - 0.19 if k % 2 == 0 else -(half - 0.19))-        yy = 109.75 if name == "OCP_B14" else 110.9      # the short OCP group: its two labels on two rows-        add_text(nm, x, yy, 0.8, side, why=f"finger pin number at the {'start' if k % 2 == 0 else 'end'} of its group ({'top = B' if side == 'F' else 'bottom = A'} side)")--# the ports on the bottom face too (a mounted card may show only its underside)-for _k, _r in enumerate(("J2", "J3", "J4", "J5")):-    _fp = b.FindFootprintByReference(_r)-    _x = TO(_fp.GetPosition().x)-    for _y in (12.0, 12.8, 13.6, 14.4, 15.2):-        if free("B", text_box(f"PORT {_k}", _x, _y, 1.0, 0, "B")):-            add_text(f"PORT {_k}", _x, _y, 1.0, "B", why=f"{_r} port number, bottom face")-            break-    else:-        plan["unresolved"].append(f"{_r}: bottom-face port legend found no space")--# ------------------------------------------------------------------ references, values, labels-for fp in fps:-    ref = fp.GetReference()-    if ref in ("J1", "H1", "H2"):-        continue-    side = "B" if fp.IsFlipped() else "F"-    cy_ = fp.GetCourtyard(pcbnew.F_CrtYd if side == "F" else pcbnew.B_CrtYd)-    e = cy_.BBox() if cy_.OutlineCount() else fp.GetBoundingBox()-    around = box(TO(e.GetLeft()), TO(e.GetTop()), TO(e.GetRight()), TO(e.GetBottom()))-    cx, cy = (around[0] + around[2]) / 2, (around[1] + around[3]) / 2-    label = ref-    if ref in TP_LABEL:-        label = f"{ref} {TP_LABEL[ref]}"-    elif ref in LED_LABEL:-        label = f"{ref} {LED_LABEL[ref]}"-    elif ref == "JP1":-        label = "JP1 FRU WRITE"-    elif ref[0] == "J" and ref in ("J2", "J3", "J4", "J5"):-        label = f"{ref} PORT {int(ref[1]) - 2}"-    # the connectors face the faceplate: their labels go on the board side of the ESD parts-    prefer = (lambda x, y: y > 11.5) if ref in ("J2", "J3", "J4", "J5") else None-    got = search(label, cx, cy, around, side, REF_SIZES, ignore=(ref,), prefer=prefer)-    split = None-    if not got and label != ref:-        got = search(ref, cx, cy, around, side, REF_SIZES, ignore=(ref,))-        if got:-            split = label[len(ref) + 1:]       # the function goes beside the reference, as its own text-            label = ref-    if not got:-        plan["unresolved"].append(f"{ref}: no free spot for its reference at 0.8 mm")-        continue-    placed[side].append(got["box"])-    if label == ref:-        f = fp.Reference()-        plan["fields"].append(dict(ref=ref, field="Reference", id=f.m_Uuid.AsString(), x=got["x"], y=got["y"],-                                   rotation=got["rot"], layer=f"{side}.SilkS", height=got["size"], stroke=STROKE,-                                   visible=True))-    else:-        add_text(label, got["x"], got["y"], got["size"], side, got["rot"], why=f"{ref} with its function")-        placed[side].pop()   # add_text appended the same box again-    if split:-        sg = search(split, got["x"], got["y"], got["box"], side, (0.8,), ignore=(ref,))-        if sg:-            add_text(split, sg["x"], sg["y"], sg["size"], side, sg["rot"], why=f"{ref} function, beside its reference")-        else:-            plan["unresolved"].append(f"{ref}: its function '{split}' has no spot beside the reference")-    # the value beside the reference, for R and C (the dense PERST# resistor block gets one key instead)-    if ref[0] in "RC" and ref not in KEYED:-        val = fp.GetValue()-        vg = search(val, got["x"], got["y"], got["box"], side, VAL_SIZES, ignore=(ref,))-        if vg:-            placed[side].append(vg["box"])-            f = fp.Value()-            plan["fields"].append(dict(ref=ref, field="Value", id=f.m_Uuid.AsString(), x=vg["x"], y=vg["y"],-                                       rotation=vg["rot"], layer=f"{side}.SilkS", height=vg["size"], stroke=STROKE,-                                       visible=True))-        else:-            plan["unresolved"].append(f"{ref}: value {val} has no free spot beside its reference (in the bottom key)")--# one value key for the PERST# resistor block, as near the block as it fits-_xs = [TO(b.FindFootprintByReference(r).GetPosition().x) for r in KEYED]; _ys = [TO(b.FindFootprintByReference(r).GetPosition().y) for r in KEYED]-_blk = (min(_xs) - 0.6, min(_ys) - 0.4, max(_xs) + 0.6, max(_ys) + 0.4)-for _txt in ("R11-R14 33  R15-R18 100k", "R11-14 33 / R15-18 100k"):-    kg = search(_txt, (_blk[0] + _blk[2]) / 2, (_blk[1] + _blk[3]) / 2, _blk, "F", (0.8,))-    if kg:-        add_text(_txt, kg["x"], kg["y"], 0.8, "F", kg["rot"], why="values of the PERST# resistor block (33 ohm series, 100k pull-down)")-        break-else:-    # under the block on the bottom face (mirrored, reads from below), still beside the parts it names-    for _txt in ("R11-R14 33  R15-R18 100k", "R11-14 33 / R15-18 100k"):-        kg = search(_txt, (_blk[0] + _blk[2]) / 2, (_blk[1] + _blk[3]) / 2, grow(_blk, -1.0), "B", (0.8,))-        if kg:-            add_text(_txt, kg["x"], kg["y"], 0.8, "B", kg["rot"], why="values of the PERST# resistor block, under it on the bottom face")-            plan["unresolved"].append("R11-R18: values are keyed on the bottom face under the block (no top-side room)")-            break-    else:-        plan["unresolved"].append("R11-R18: the value key found no spot near the block")--# ------------------------------------------------------------------ bottom: identity, link, BIOS note, value key-ident = [("OCP NIC 3.0 SFF  >  4x OCuLink 4i", 1.6), ("PCIe Gen4 x16 as 4 x4   rev A", 1.2),-         ("Set the slot to x4x4x4x4 in BIOS", 1.0), ("wiki.adom.inc/john/oculink-aiom-riser", 1.0)]-y = 40.0-for text, size in ident:-    r = text_box(text, 38.0, y, size, 0, "B")-    while not free("B", r) and y < 100:-        y += 0.5; r = text_box(text, 38.0, y, size, 0, "B")-    if free("B", r):-        add_text(text, 38.0, y, size, "B", why="board identity / service note")-        y += size * 1.9-    else:-        plan["unresolved"].append(f"bottom: '{text}' found no space")-missing_vals = [u.split(":")[0] for u in plan["unresolved"] if "value " in u and b.FindFootprintByReference(u.split(":")[0]) is not None]-if missing_vals:-    for chunk_start in range(0, len(missing_vals), 6):-        chunk = missing_vals[chunk_start:chunk_start + 6]-        line = "  ".join(f"{r}={b.FindFootprintByReference(r).GetValue()}" for r in chunk)-        r = text_box(line, 38.0, y, 0.8, 0, "B")-        while not free("B", r) and y < 105:-            y += 0.5; r = text_box(line, 38.0, y, 0.8, 0, "B")-        if free("B", r):-            add_text(line, 38.0, y, 0.8, "B", why="value key for parts whose value did not fit beside them")-            y += 1.6--json.dump(plan, open(os.path.join(ROOT, "design", "silk_plan.json"), "w"), indent=1)-newpri = PRI + [u.split(":")[0] for u in plan["unresolved"] if u.split(":")[0] not in PRI and not u.startswith("bottom")]-json.dump(newpri, open(_pri_path, "w"))-print(f"fields {len(plan['fields'])}, texts {len(plan['texts'])}, unresolved {len(plan['unresolved'])}")-for u in plan["unresolved"]:-    print("  unresolved:", u)--if MODE == "apply":-    for fl in plan["fields"]:-        fp = b.FindFootprintByReference(fl["ref"])-        f = fp.Reference() if fl["field"] == "Reference" else fp.Value()-        f.SetVisible(True)-        f.SetLayer(pcbnew.F_SilkS if fl["layer"] == "F.SilkS" else pcbnew.B_SilkS)-        f.SetTextSize(pcbnew.VECTOR2I(MM(fl["height"]), MM(fl["height"]))); f.SetTextThickness(MM(fl["stroke"]))-        f.SetTextAngleDegrees(fl["rotation"])-        f.SetHorizJustify(pcbnew.GR_TEXT_H_ALIGN_CENTER)-        f.SetPosition(pcbnew.VECTOR2I(MM(fl["x"]), MM(fl["y"])))-    for tx in plan["texts"]:-        t = pcbnew.PCB_TEXT(b)-        t.SetText(tx["text"]); t.SetTextSize(pcbnew.VECTOR2I(MM(tx["height"]), MM(tx["height"])))-        t.SetTextThickness(MM(tx["stroke"])); t.SetPosition(pcbnew.VECTOR2I(MM(tx["x"]), MM(tx["y"])))-        t.SetTextAngleDegrees(tx["rotation"])-        t.SetLayer(pcbnew.F_SilkS if tx["layer"] == "F.SilkS" else pcbnew.B_SilkS)-        t.SetHorizJustify(pcbnew.GR_TEXT_H_ALIGN_CENTER if tx["align"] == "center" else pcbnew.GR_TEXT_H_ALIGN_LEFT)-        if tx["layer"] == "B.SilkS":-            t.SetMirrored(True)-        b.Add(t)-    out = os.path.join(ROOT, "design", "silk_check.kicad_pcb")-    pcbnew.SaveBoard(out, b)-    print("applied ->", out)