board
OCuLink Riser for the Supermicro AIOM Slot
Public Unreviewedby John Lauer
Passive OCP NIC 3.0 SFF card that breaks a Supermicro AIOM slot's PCIe x16 into four OCuLink 4i ports (4 x4, Gen4): 6-layer, 85 ohm, length-matched, DRC-clean KiCad project with Gerbers, BOM and CPL.
main
John Lauer
README: component table per the wiki format, open-source section, no duplicate hero; generators at gen/
39a59ef
7d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
#!/usr/bin/env python3
"""OCP NIC 3.0 SFF outline as lines and arcs: every corner filleted (slot bottoms are exact semicircles,
latch notch R0.50 outer / R0.25 inner per OCP Fig. 9, 0.2-0.3 mm on the chamfer vertices)."""
import math
import hs
W, L = 76.0, 115.0
def vertices():
F = hs.F
c30 = 3.00 * math.tan(math.radians(30))
ear, ow, od, ch = 3.7625, 2.5, 6.10, 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
V = []
def slot(xc, w, d):
r = w / 2
V.extend([(xc + r, L - d, r), (xc - r, L - d, r)])
V += [(0, 0, 0.5), (W, 0, 0.5), (W, ny1, 0.5), (W - 2.75, ny1, 0.25), (W - 2.75, ny0, 0.25), (W, ny0, 0.5),
(W, L - 3.00, 0.3), (W - c30, L, 0.3), (W - ear, L, 0.25)]
slot(tongue[3][1] + ow / 2, ow, od)
for i in range(3, -1, -1):
a, b = tongue[i]
V += [(b, L - ch, 0.2), (b - ch, L, 0.2), (a + ch, L, 0.2), (a, L - ch, 0.2)]
if i > 0:
slot(*keys[i - 1])
slot(tongue[0][0] - ow / 2, ow, od)
V += [(ear, L, 0.25), (c30, L, 0.3), (0, L - 3.00, 0.3), (0, ny0, 0.5), (2.75, ny0, 0.25), (2.75, ny1, 0.25), (0, ny1, 0.5)]
return V
def fillet():
V = vertices()
n = len(V)
prims = []
pts_after = [] # (start_of_next_line) per vertex
arcs = []
for i in range(n):
p0 = V[i - 1]; p1 = V[i]; p2 = V[(i + 1) % n]
d1 = (p1[0] - p0[0], p1[1] - p0[1]); l1 = math.hypot(*d1); d1 = (d1[0] / l1, d1[1] / l1)
d2 = (p2[0] - p1[0], p2[1] - p1[1]); l2 = math.hypot(*d2); d2 = (d2[0] / l2, d2[1] / l2)
cosang = max(-1, min(1, d1[0] * d2[0] + d1[1] * d2[1]))
turn = math.acos(cosang)
r = p1[2]
t = r * math.tan(turn / 2)
assert t <= l1 / 2 + 1e-3 and t <= l2 / 2 + 1e-3, (i, p1, t, l1, l2)
a = (p1[0] - d1[0] * t, p1[1] - d1[1] * t)
b = (p1[0] + d2[0] * t, p1[1] + d2[1] * t)
# centre: from a, perpendicular toward the inside of the turn
cross = d1[0] * d2[1] - d1[1] * d2[0]
nrm = (-d1[1], d1[0]) if cross > 0 else (d1[1], -d1[0])
c = (a[0] + nrm[0] * r, a[1] + nrm[1] * r)
bis = ((a[0] + b[0]) / 2 - c[0], (a[1] + b[1]) / 2 - c[1]); bl = math.hypot(*bis)
m = (c[0] + bis[0] / bl * r, c[1] + bis[1] / bl * r)
arcs.append((a, m, b, c, r))
for i in range(n):
a_prev = arcs[i - 1][2]
a_cur = arcs[i][0]
s, m, e, c, r = arcs[i]
if math.hypot(a_cur[0] - a_prev[0], a_cur[1] - a_prev[1]) > 1e-3:
prims.append(("line", a_prev[0], a_prev[1], a_cur[0], a_cur[1]))
else:
s = a_prev # tangent arcs meet: start exactly where the previous arc ended
prims.append(("arc", s[0], s[1], m[0], m[1], e[0], e[1]))
return prims, arcs
def polygon(seg_per_arc=8):
prims, arcs = fillet()
pts = []
for pr in prims:
if pr[0] == "line":
pts.append((pr[1], pr[2]))
else:
sx, sy, mx, my, ex, ey = pr[1:]
# sample the arc through its three points
import numpy as np
ax, ay, bx, by, cx_, cy_ = sx, sy, mx, my, ex, ey
d = 2 * (ax * (by - cy_) + bx * (cy_ - ay) + cx_ * (ay - by))
ux = ((ax * ax + ay * ay) * (by - cy_) + (bx * bx + by * by) * (cy_ - ay) + (cx_ * cx_ + cy_ * cy_) * (ay - by)) / d
uy = ((ax * ax + ay * ay) * (cx_ - bx) + (bx * bx + by * by) * (ax - cx_) + (cx_ * cx_ + cy_ * cy_) * (bx - ax)) / d
r = math.hypot(ax - ux, ay - uy)
a0 = math.atan2(ay - uy, ax - ux); a1 = math.atan2(by - uy, bx - ux); a2 = math.atan2(cy_ - uy, cx_ - ux)
# go from a0 through a1 to a2
def norm(t):
return (t + 2 * math.pi) % (2 * math.pi)
ccw = norm(a1 - a0) < norm(a2 - a0)
span = norm(a2 - a0) if ccw else -norm(a0 - a2)
for k in range(seg_per_arc):
t = a0 + span * k / seg_per_arc
pts.append((ux + r * math.cos(t), uy + r * math.sin(t)))
return pts
if __name__ == "__main__":
prims, _ = fillet()
print(len(prims), "primitives;", sum(1 for p in prims if p[0] == "arc"), "arcs;", len(polygon()), "polygon points")