by adom
2020 Addressable RGB LED · 800 Kbps single-line cascade · 16 mA per channel (V1.0 sheet), 12 mA from V1.2
← Commit history
Scenes: the 300 um jet-paste solder balls replace the solder wedges; models.json without the deleted v1/v2 models
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))))))