main
John Lauer README: component table per the wiki format, open-source section, no duplicate hero; generators at gen/ 39a59ef 7d ago
#!/usr/bin/env python3
"""Board outline, keepouts and component placement for the riser (board frame: x 0..76, y 0 faceplate .. 115 fingers)."""
import json, math, os, sys
sys.path.insert(0, os.path.dirname(__file__))
import hs

W, L = 76.0, 115.0
H = 38.0


def outline():
    """Closed list of (kind, ...) segments: ('line', x1,y1,x2,y2) or ('arc', sx,sy, mx,my, ex,ey).
    OCP NIC 3.0 v1.6.0 Fig. 8/9 + SFF-TA-1002 Fig. 5-39 (tongue, keys, lead-in chamfers)."""
    segs = []
    F = hs.F
    tongue = [(F - 31.480, F - 20.4 - 1.125), (F - 20.4 + 1.125, F - 0.925),
              (F + 0.925, F + 12.26 - 1.375), (F + 12.26 + 1.375, F + 31.995)]
    slots = [(F - 20.4, 2.25, 7.0), (F, 1.85, 7.0), (F + 12.26, 2.75, 7.0)]
    ch = 0.75     # 45 degree lead-in on every tongue corner
    # ears: x [0, 3.76] and [72.24, 76]; outer slots 2.50 wide, 6.10 deep; 30 degree chamfer 3.00 long
    ear_w, oslot_w, oslot_d = 3.7625, 2.5, 6.10
    c30 = 3.00 * math.tan(math.radians(30))
    pts = []

    def P(x, y):
        pts.append((x, y))

    # start at faceplate edge, top-left, go clockwise in board frame (x right, y down)
    P(0, 0); P(W, 0)
    # right long edge down to the latch notch: notch 3.50 wide, 2.75 deep, ends 30.00 from the card edge
    ny1, ny0 = L - 30.0 - 3.5, L - 30.0
    P(W, ny1); P(W - 2.75, ny1); P(W - 2.75, ny0); P(W, ny0)
    # right ear with 30 degree chamfer
    P(W, L - 3.00); P(W - c30, L); P(W - ear_w, L)
    # right outer slot (round bottom)
    segs_pts = list(pts)
    return segs_pts, tongue, slots, (ear_w, oslot_w, oslot_d, c30, ny1, ny0, ch)


def outline_poly():
    """The outline as a simple polygon (arcs as polylines) for checks, built independently of outline_segments."""
    F = hs.F
    pts = []
    W_, L_ = W, L
    c30 = 3.00 * math.tan(math.radians(30))
    ear = 3.7625
    ow, od = 2.5, 6.10
    ch = 0.75
    tongue = [(F - 31.480, F - 20.4 - 1.125), (F - 20.4 + 1.125, F - 0.925),
              (F + 0.925, F + 12.26 - 1.375), (F + 12.26 + 1.375, F + 31.995)]
    keys = [(F - 20.4, 2.25, 7.0), (F, 1.85, 7.0), (F + 12.26, 2.75, 7.0)]
    ny1, ny0 = L_ - 33.5, L_ - 30.0

    def roundslot(xc, w, d, east_to_west=True, n=12):
        r = w / 2
        out = [(xc + r, L_), (xc + r, L_ - d + r)]
        for i in range(1, n):
            a = math.pi * i / n
            out.append((xc + r * math.cos(a), L_ - d + r - r * math.sin(a)))
        out += [(xc - r, L_ - d + r), (xc - r, L_)]
        return out

    pts += [(0, 0), (W_, 0), (W_, ny1), (W_ - 2.75, ny1), (W_ - 2.75, ny0), (W_, ny0), (W_, L_ - 3.00), (W_ - c30, L_),
            (W_ - ear, L_)]
    # right outer slot
    pts += roundslot(tongue[3][1] + ow / 2, ow, od)[1:-1]
    for i in range(3, -1, -1):
        a, b = tongue[i]
        pts += [(b, L_ - ch), (b - ch, L_), (a + ch, L_), (a, L_ - ch)]
        if i > 0:
            xc, w, d = keys[i - 1]
            pts += roundslot(xc, w, d)[1:-1]
    pts += roundslot(tongue[0][0] - ow / 2, ow, od)[1:-1]
    pts += [(ear, L_), (c30, L_), (0, L_ - 3.00), (0, ny0), (2.75, ny0), (2.75, ny1), (0, ny1)]
    return pts


# ------------------------------------------------------------------------------------------ placement
def placement():
    """ref -> (x, y, rot_deg, side). rot in KiCad degrees (counter-clockwise on screen)."""
    P = {}
    P["J1"] = (hs.F, L, 0, "F")
    for k in range(4):
        P[f"J{2+k}"] = (hs.CX[k], 0.0, 0, "F")
    P["H1"] = (5.5, 115 - 109.76, 0, "F")
    P["H2"] = (70.5, 115 - 109.76, 0, "F")
    # west region (top side): scan chain, FRU, supervisor, PERST buffer
    P["U1"] = (8.8, 60.0, 0, "F")       # TSSOP-16
    P["C1"] = (13.3, 56.4, 90, "F")
    P["R3"] = (4.2, 55.4, 0, "F"); P["R4"] = (4.2, 56.75, 0, "F"); P["R5"] = (4.6, 64.0, 0, "F")
    P["R6"] = (13.3, 62.6, 90, "F"); P["R7"] = (4.6, 65.4, 0, "F")
    P["U2"] = (8.8, 72.0, 0, "F")       # SOIC-8
    P["C2"] = (13.4, 69.0, 90, "F"); P["R8"] = (4.6, 75.6, 0, "F"); P["JP1"] = (5.2, 78.2, 0, "F")
    P["U3"] = (8.8, 48.6, 0, "F")       # SOT-23-6
    P["C3"] = (12.4, 48.6, 90, "F"); P["R9"] = (5.2, 50.8, 0, "F")
    P["U4"] = (8.8, 39.0, 0, "F")       # TSSOP-14
    P["C4"] = (13.3, 35.6, 90, "F")
    for k in range(4):
        P[f"R{11+k}"] = (13.6, 38.2 + 1.3 * k, 0, "F")    # 33R series
    # power indication and bulk
    P["C5"] = (14.2, 92.0, 90, "F")
    P["D1"] = (5.0, 28.0, 90, "F"); P["R23"] = (5.0, 31.0, 90, "F")
    P["D2"] = (7.4, 28.0, 90, "F"); P["R24"] = (7.4, 31.0, 90, "F")
    P["TP1"] = (4.6, 87.0, 0, "F"); P["TP2"] = (4.6, 89.8, 0, "F"); P["TP3"] = (4.6, 92.6, 0, "F")
    P["TP4"] = (4.6, 95.4, 0, "F"); P["TP5"] = (4.6, 98.2, 0, "F")
    # 0.5 mm probe pads on their nets' routed top-layer traces (design/tp_spots.json, found by clearance search)
    _sp = json.load(open(os.path.join(os.path.dirname(__file__), "..", "design", "tp_spots.json")))
    for _ref, _net in (("TP6", "NIC_PWR_GOOD0"), ("TP7", "PORT0_PERST#"), ("TP8", "PORT1_PERST#"), ("TP9", "PORT2_PERST#"),
                       ("TP10", "PORT3_PERST#"), ("TP11", "SCAN0_CLK"), ("TP12", "SCAN0_DATA_OUT")):
        P[_ref] = (_sp[_net][1], _sp[_net][2], 0, "F")
    # PRSNT straps beside B70 (east)
    P["R1"] = (71.6, 101.6, 90, "F"); P["R2"] = (70.4, 101.6, 90, "F")
    # per-port ESD between the connector and the PERST# jog (pins 1..5 face the connector at rotation 270)
    for k in range(4):
        P[f"U{5+k}"] = (hs.CX[k] - 3.0, 9.6, 270, "F")
        P[f"R{15+k}"] = (15.8, 38.2 + 1.3 * k, 0, "F")      # PERST# pull-downs beside the 33 ohm series parts
    return P


if __name__ == "__main__":
    pl = placement()
    import outline2
    json.dump({"placement": pl, "outline": outline2.polygon(), "edges": outline2.fillet()[0]}, open(os.path.join(os.path.dirname(__file__), "..", "design", "layout.json"), "w"), indent=1)
    print(len(pl), "placed;", len(outline_poly()), "outline points")