by adom
0603 SMD Blue LED · 2.8 V drop @ 5 mA test · 25 mA max
← Commit history
Fix: keep the insertion animation in the rebuilt page GLBs
3 files changed
+18−6
IN-S63BT5B.insertion.glb⋯ 1 unchanged line ⋯
provenance/in-s63bt5b-led-pass.provenance.json+4−3@@ -96,8 +96,8 @@ } }, "IN-S63BT5B.insertion.glb": {- "sha256": "111968f4f23e7a656e42ffb72e474f0c0558837574a9fa1e91d0c9ef64f8b112",- "bytes": 58640,+ "sha256": "542e9b493ac91952ee1766935e4a054574bd133ffb3d53bfdf938b29659479ce",+ "bytes": 58968, "bytes_before": 414244, "tris_before": 4806, "tris_after": 1601,@@ -138,7 +138,8 @@ } } }- }+ },+ "animation_fix": "2026-09-30: the pick-and-place insertion animation (sampler keyframe accessors) is carried over unchanged; the earlier rebuilds of this pass had dropped those accessors, leaving the animation pointing at geometry data" }, "hero_glb": { "file": "in-s63bt5b-hero.glb",
provenance/tools/gltfio.py+14−3@@ -49,12 +49,17 @@ def save(path, d, arrays): k = 1 if arr.ndim == 1 else arr.shape[1] acc = {'bufferView': len(d['bufferViews']) - 1, 'componentType': ct, 'count': int(arr.shape[0]), 'type': {1: 'SCALAR', 2: 'VEC2', 3: 'VEC3', 4: 'VEC4'}[k]}- if pos or key.endswith('POSITION'): acc['min'] = [float(v) for v in arr.min(0)]; acc['max'] = [float(v) for v in arr.max(0)]+ if pos or key.endswith('POSITION'):+ mn, mx = arr.min(0), arr.max(0)+ acc['min'] = [float(v) for v in np.atleast_1d(mn)]; acc['max'] = [float(v) for v in np.atleast_1d(mx)] d['accessors'].append(acc); idx[key] = len(d['accessors']) - 1; return idx[key] for m in d['meshes']: for p in m['primitives']: p['attributes'] = {k: add(v, k == 'POSITION') for k, v in p['attributes'].items()} if 'indices' in p: p['indices'] = add(p['indices'])+ for an in d.get('animations', []): # keyframe times need min/max (glTF requires it on sampler input)+ for sm in an['samplers']:+ sm['input'] = add(sm['input'], True); sm['output'] = add(sm['output']) while len(blob) % 4: blob.append(0) d['buffers'] = [{'byteLength': len(blob)}] j = json.dumps(d, separators=(',', ':')).encode(); j += b' ' * ((-len(j)) % 4)@@ -70,6 +75,10 @@ def explode(d, binc): key = f'a{a}_{k}'; arrays[key] = (accessor(d, binc, a), 34962); p['attributes'][k] = key if 'indices' in p: key = f'a{p["indices"]}_IDX'; arrays[key] = (accessor(d, binc, p['indices']), 34963); p['indices'] = key+ for an in d.get('animations', []): # animation keyframes are accessors too: carry them (bug fixed 2026-09-30)+ for sm in an['samplers']:+ for k in ('input', 'output'):+ key = f'a{sm[k]}_ANIM'; arrays[key] = (accessor(d, binc, sm[k]), None); sm[k] = key for k in ('accessors', 'bufferViews', 'buffers'): d.pop(k, None) return arrays @@ -84,8 +93,10 @@ def flatten(d, binc): R = W[:3, :3]; Nm = np.linalg.inv(R).T for p in d['meshes'][n['mesh']]['primitives']: P = accessor(d, binc, p['attributes']['POSITION']).astype(np.float64) @ R.T + W[:3, 3]- N = accessor(d, binc, p['attributes']['NORMAL']).astype(np.float64) @ Nm.T- N /= np.maximum(np.linalg.norm(N, axis=1, keepdims=True), 1e-12)+ N = None+ if 'NORMAL' in p['attributes']:+ N = accessor(d, binc, p['attributes']['NORMAL']).astype(np.float64) @ Nm.T+ N /= np.maximum(np.linalg.norm(N, axis=1, keepdims=True), 1e-12) out.append((p.get('material'), P, N, accessor(d, binc, p['indices']).astype(np.int64))) for c in n.get('children', []): walk(c, W) for r in d['scenes'][d.get('scene', 0)]['nodes']: walk(r, np.eye(4))