← Commit history

Scenes: the 300 um jet-paste solder balls replace the solder wedges; models.json without the deleted v1/v2 models

John Lauer ·7723e885b9 ·16d ago ·parent 233d40d
5 files changed +104−101
WS2812B-2020.glb
⋯ 1 unchanged line ⋯
WS2812B-2020.insertion.glb
⋯ 1 unchanged line ⋯
models.json+6−91
@@ -64,14 +64,14 @@     "surface": "side wall",     "colour": "dark grey"    },-   "sha256": "f4a737153730df799d94e6c731caf97a51a8976d1bb76642818f910e2208b2d1",-   "bytes": 1283048,+   "sha256": "4d1d380e59593676a4dba9e25805ad3978db0a0f711cc04d3e5ff3377e37130e",+   "bytes": 1376272,    "role": "derived",    "colours": true,    "derived_from": "WS2812B-2020-part.glb",    "source": "provenance/tools/rebuild_ws_scene.py, chip swapped in by the marking pass",    "thumb": "docs/models/WS2812B-2020.glb.png",-   "git_blob": "5a7f84b03a2d7f8b81e7dc9bc65f8895ecee8e04"+   "git_blob": "620e33a1830731564033a76cc4bcad49acbf1b75"   },   {    "path": "WS2812B-2020.insertion.glb",@@ -90,14 +90,14 @@     "surface": "side wall",     "colour": "dark grey"    },-   "sha256": "4a169fa9e8937283457b80993523a41c8c90741ffbfb9d1c501dd58fcd45540a",-   "bytes": 1283056,+   "sha256": "83d1112305668d6f8054ed0710ed2a1426b6b825fd54a0b4b51694e13957845b",+   "bytes": 1376280,    "role": "derived",    "colours": true,    "derived_from": "WS2812B-2020-part.glb",    "source": "provenance/tools/rebuild_ws_scene.py",    "thumb": "docs/models/WS2812B-2020.insertion.glb.png",-   "git_blob": "60f676dfcef8b184bba6b593972fa0dfaffc8862"+   "git_blob": "d230394433899e02585e0d6d3cd047c14349a315"   },   {    "path": "WS2812B-2020.step",@@ -125,91 +125,6 @@    "thumb": "docs/models/WS2812B-2020.step.png",    "git_blob": "9dc6db15bdb4fa1cf210b10623f8cdae59a0461b"   },-  {-   "path": "provenance/WS2812B-2020-v1-2026-09-30-original-etched.step",-   "format": "step",-   "title": "First model v1, marked on top (30 Sep)",-   "status": "previous",-   "slot": null,-   "variant": "etched",-   "use": [-    "archive"-   ],-   "unique": "v1 with the part number across the top, which hid the dies; replaced by the side-wall mark.",-   "marking": {-    "text": "WS2812B-2020",-    "surface": "top"-   },-   "sha256": "1e47152c6b07c6c9d50e31ae878f812d004e6b33b4cba9ce37dc7b4021ea7bc3",-   "bytes": 728090,-   "role": "datasheet-built",-   "colours": true,-   "derived_from": "provenance/WS2812B-2020-v1-2026-09-30-original.step",-   "thumb": "docs/models/provenance_WS2812B-2020-v1-2026-09-30-original-etched.step.png",-   "git_blob": "a7a453e02a041cac66ddf41161e4c3dc9b4fa53d"-  },-  {-   "path": "provenance/WS2812B-2020-v1-2026-09-30-original.step",-   "format": "step",-   "title": "First model v1, unmarked (30 Sep)",-   "status": "previous",-   "slot": null,-   "variant": "original",-   "use": [-    "archive"-   ],-   "unique": "The first datasheet-built model, superseded: simpler terminal shapes than the traced v2.",-   "marking": null,-   "sha256": "bed6fd877ef81859c60d1e095f4ef4b3751d52b5442cddeb179f486c4390b86a",-   "bytes": 354168,-   "role": "datasheet-built",-   "colours": false,-   "thumb": "docs/models/provenance_WS2812B-2020-v1-2026-09-30-original.step.png",-   "git_blob": "fcacd86ed44d3fdc804a7a05036f093e8f020e4e"-  },-  {-   "path": "provenance/WS2812B-2020-v2-2026-10-03-original-etched.step",-   "format": "step",-   "title": "Previous model v2, side-wall mark (3 Oct)",-   "status": "previous",-   "slot": null,-   "variant": "etched",-   "use": [-    "archive"-   ],-   "unique": "As v2 unmarked, with coloured dies and the part number on the side wall.",-   "marking": {-    "text": "WS2812B-2020",-    "surface": "side wall",-    "colour": "dark grey"-   },-   "sha256": "beb8642327c54f0389ea24aad7cc5122b2420933118dba6dcbf6802a365f755c",-   "bytes": 2726486,-   "role": "datasheet-built",-   "colours": true,-   "derived_from": "provenance/WS2812B-2020-v2-2026-10-03-original.step",-   "thumb": "docs/models/provenance_WS2812B-2020-v2-2026-10-03-original-etched.step.png",-   "git_blob": "102d260a84698388508cb18995bb6fa736cdc432"-  },-  {-   "path": "provenance/WS2812B-2020-v2-2026-10-03-original.step",-   "format": "step",-   "title": "Previous model v2, unmarked (3 Oct)",-   "status": "previous",-   "slot": null,-   "variant": "original",-   "use": [-    "archive"-   ],-   "unique": "Drawing-only model traced from Worldsemi's Mechanical Dimensions, before the photo pass: no lead-frame stubs, dark stripe, no logo, datasheet-sketch die positions.",-   "marking": null,-   "sha256": "8613a36aa1ebee66b71f63bf33c5b15bc7ccb0432f4f802ceec41120dccd3321",-   "bytes": 2360610,-   "role": "datasheet-built",-   "colours": false,-   "thumb": "docs/models/provenance_WS2812B-2020-v2-2026-10-03-original.step.png",-   "git_blob": "5007e897dfee33cd93f91ef49a96bc5f2442a15b"-  },   {    "path": "ws2812b-2020-hero.glb",    "format": "glb",
provenance/tools/rebuild_ws_scene.py+7−10
@@ -8,7 +8,7 @@ animation on the LED and LED_lit nodes, the LED_light point light, the board mes - pad_top: the four 0.70 x 0.70 mm pads of the new WS2812B-2020.kicad_mod (datasheet p.2 PCB Solder Pad),   copper 0.04 mm; KiCad Y-down flipped to Y-up (same frame as the STEP and the hero overlay). - silk: 0.12 mm ribbons from the new footprint's F.SilkS lines (pin-1 corner bracket).-- solder_top: a fillet wedge on the outboard part of each pad rising into the castellated terminal.+- solder_top: the 300 um jet-paste solder balls, 2 x 2 per pad (scene_solderballs.py, Adom solder-ball standard). - WS2812B-2020 (LED node): geometry from `step2glb convert` of the coloured, laser-etched STEP, merged per   material (body, window, terminal, polarity mark, R/G/B dies, IC die); real height (0.84 mm), no Z scale.   The laser etch is a child node Adom.LaserEtch.MPN (rides the insertion animation).@@ -73,15 +73,12 @@ for x0, y0, x1, y1 in silk:     if abs(y0 - y1) < 1e-9: silk_parts.append(box((min(x0, x1) - SW / 2) * MM, (y0 - SW / 2) * MM, 0.03 * MM, (max(x0, x1) + SW / 2) * MM, (y0 + SW / 2) * MM, 0.06 * MM))     else: silk_parts.append(box((x0 - SW / 2) * MM, (min(y0, y1) - SW / 2) * MM, 0.03 * MM, (x0 + SW / 2) * MM, (max(y0, y1) + SW / 2) * MM, 0.06 * MM)) silk_geo = merge(silk_parts)-BODY_X = 1.10                                     # castellated edge of the part (datasheet 2.20 mm)-sol = []-for _, x, y, w, h in pads:-    s = 1 if x > 0 else -1; xin, xout = x - s * w / 2, x + s * w / 2-    if s > 0: prof = [(xin, CU), (xout, CU), (BODY_X, 0.17), (xin, 0.06)]-    else: prof = [(xout, CU), (xin, CU), (xin, 0.06), (-BODY_X, 0.17)]-    prof = [(px * MM, pz * MM) for px, pz in prof]-    sol.append(prism(prof, (y - h / 2 + 0.03) * MM, (y + h / 2 - 0.03) * MM))-sol_geo = merge(sol)+# solder: the 300 um jet-paste balls (Adom solder-ball standard), the same ones the hero shows (scene_solderballs.py)+import os as _os+sys.path.insert(0, _os.path.dirname(_os.path.abspath(__file__)))+import scene_solderballs as _sb+_P, _N, _I = _sb.hemispheres(_sb.balls(kmod), CU)+sol_geo = (np.array(_P) * MM, np.array(_N, float), np.array(_I)) glow_geo = box(-0.74 * MM, -1.01 * MM, 0.29 * MM, 0.74 * MM, 1.01 * MM, 0.85 * MM)  # ---- LED from step2glb ----
provenance/tools/scene_solderballs.pyadded+91
@@ -0,0 +1,91 @@+#!/usr/bin/env python3+"""Replace the solder wedges in a WS2812B-2020 scene GLB (WS2812B-2020.glb, WS2812B-2020.insertion.glb) with the+300 um jet-paste solder balls, the same ones the hero's 'Solder balls' layer shows.++Adom footprint standard (john/adom-solder-ball-layer-standard): balls 0.30 mm across at 0.25 mm pitch (adjacent balls+overlap slightly, by design), each a hemisphere with its flat face on the pad, dull leaded solder grey (63Sn/37Pb).+Positions follow adom-footprint's paste-dot rule (1.0.26): per SMD pad a grid at 0.25 mm pitch centred on the pad,+kept 0.06 mm inside the pad edge, so 2 x 2 per 0.7 mm pad here. The scene keeps its node 'solder_top' (same name,+same place in the tree), only its geometry and material change. Scene frame: metres, Z up, pad copper top at+z = 0.04 mm, KiCad Y-down flipped to Y up.+usage: scene_solderballs.py SCENE.glb FOOTPRINT.kicad_mod   (in place; idempotent)"""+import json, math, re, struct, sys++D, PITCH, MARGIN, CU = 0.30, 0.25, 0.06, 0.04+MM = 0.001+SEG, RINGS = 16, 6+++def read_glb(p):+    b = open(p, "rb").read(); n = struct.unpack("<I", b[12:16])[0]; js = json.loads(b[20:20 + n]); o = 20 + n+    return js, bytearray(b[o + 8:o + 8 + struct.unpack("<I", b[o:o + 4])[0]] if o < len(b) else b"")+++def write_glb(p, js, bin_):+    j = json.dumps(js, separators=(",", ":")).encode(); j += b" " * (-len(j) % 4); bin_ += b"\0" * (-len(bin_) % 4)+    open(p, "wb").write(struct.pack("<III", 0x46546C67, 2, 12 + 8 + len(j) + 8 + len(bin_)) + struct.pack("<II", len(j), 0x4E4F534A) + j+                       + struct.pack("<II", len(bin_), 0x004E4942) + bytes(bin_))+++def pads(mod):+    t = open(mod).read()+    return [(float(m.group(1)), -float(m.group(2)), float(m.group(3)), float(m.group(4))) for m in re.finditer(+        r'\(pad\s+"?[^"\s]*"?\s+smd\s+\w+\s+\(at\s+([-\d.]+)\s+([-\d.]+)(?:\s+[-\d.]+)?\)\s+\(size\s+([-\d.]+)\s+([-\d.]+)\)', t)]+++def grid(c, span):+    n = max(1, int(math.floor((span - 2 * MARGIN - D) / PITCH + 1e-9)) + 1)+    return [c + (i - (n - 1) / 2) * PITCH for i in range(n)]+++def balls(mod):+    return [(x, y) for px, py, w, h in pads(mod) for y in grid(py, h) for x in grid(px, w)]+++def hemispheres(centres, z0):+    r = D / 2; P, N, I = [], [], []+    for cx, cy in centres:+        b0 = len(P)+        for k in range(RINGS + 1):+            th = (math.pi / 2) * k / RINGS+            for s in range(SEG + 1):+                ph = 2 * math.pi * s / SEG; nx, ny, nz = math.cos(th) * math.cos(ph), math.cos(th) * math.sin(ph), math.sin(th)+                P.append((cx + r * nx, cy + r * ny, z0 + r * nz)); N.append((nx, ny, nz))+        for k in range(RINGS):+            for s in range(SEG):+                a = b0 + k * (SEG + 1) + s; b = a + SEG + 1+                I += [a, a + 1, b, a + 1, b + 1, b]+        c0 = len(P); P.append((cx, cy, z0)); N.append((0, 0, -1))+        for s in range(SEG + 1):+            ph = 2 * math.pi * s / SEG; P.append((cx + r * math.cos(ph), cy + r * math.sin(ph), z0)); N.append((0, 0, -1))+        for s in range(SEG):+            I += [c0, c0 + 2 + s, c0 + 1 + s]+    return P, N, I+++if __name__ == "__main__":+    glb, mod = sys.argv[1:3]+    js, bin_ = read_glb(glb)+    names = [n.get("name") for n in js["nodes"]]+    node = js["nodes"][names.index("solder_top")]+    C = balls(mod); P, N, I = hemispheres(C, CU)++    def buf(data, target):+        global bin_+        bin_ += b"\0" * (-len(bin_) % 4)+        js["bufferViews"].append(dict(buffer=0, byteOffset=len(bin_), byteLength=len(data), target=target)); bin_ += data+        return len(js["bufferViews"]) - 1+    acc = js["accessors"]+    xs = [p[0] * MM for p in P]; ys = [p[1] * MM for p in P]; zs = [p[2] * MM for p in P]+    acc.append(dict(bufferView=buf(b"".join(struct.pack("<3f", x * MM, y * MM, z * MM) for x, y, z in P), 34962), componentType=5126,+                    count=len(P), type="VEC3", min=[min(xs), min(ys), min(zs)], max=[max(xs), max(ys), max(zs)]))+    acc.append(dict(bufferView=buf(b"".join(struct.pack("<3f", *n) for n in N), 34962), componentType=5126, count=len(N), type="VEC3"))+    acc.append(dict(bufferView=buf(b"".join(struct.pack("<I", i) for i in I), 34963), componentType=5125, count=len(I), type="SCALAR"))+    prim = js["meshes"][node["mesh"]]["primitives"][0]+    prim.update(attributes=dict(POSITION=len(acc) - 3, NORMAL=len(acc) - 2), indices=len(acc) - 1, mode=4)+    js["materials"][prim["material"]].update(name="solder_63Sn37Pb_jet_paste", doubleSided=True,+                                             pbrMetallicRoughness=dict(baseColorFactor=[0.60, 0.61, 0.63, 1.0], metallicFactor=0.55, roughnessFactor=0.6))+    node["extras"] = dict(node.get("extras") or {}, tooltip="Jet-paste solder ball, 0.30 mm across, 0.25 mm pitch\n(Adom 300 um solder-ball standard), 2 x 2 per pad")+    js["buffers"][0]["byteLength"] = len(bin_) + (-len(bin_) % 4)+    write_glb(glb, js, bin_)+    print(json.dumps(dict(glb=glb, balls=len(C), per_pad=len(C) // max(1, len(pads(mod))))))