skill
Publish a Hardware Component to the Adom Wiki
Public Made by Adomby adom
The one-stop-shop for publishing a hardware component (or molecule/board) to the Adom Wiki. The whole lifecycle: page anatomy, the clone→edit→preview→push loop and its hard rules, making a great hero image (Hero Component Studio — 16:10, overlay-safe, datasheet-backed), the animated 3D composite, the interactive 3D viewer, ct thumbnails, the datasheet-accuracy audit, and every wiki push/pull gotcha we've hit. Now includes the worked Path B (board) pipeline: STEP-to-GLB, refdes tagging, layer de-
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
# Worked example — cap emboss + 2-material (plane-split) colour, swapped into a composite (0603B104J500CT).
# ADAPT paths. Companion to build_insertion_glb-style board/animation assembly.
"""
v2 emboss + clean swap:
- PART NUMBER 0603B104J500CT → sunken into the TOP (+Z), brown region only
- VALUE 100nF → sunken into a long SIDE (+Y), brown region only, LARGE
- silver/brown split at |x| >= 0.5 mm (the REAL termination boundary from the
original chip mesh) → no triangular color artifacts
- text kept within |x| <= 0.46 mm so it never touches the silver terminations
"""
import struct, json
import numpy as np
import cadquery as cq
import pygltflib
STEP_IN = "/tmp/claude-1001/-home-adom-project/7572280e-1088-44ed-9829-35fccaaabecb/scratchpad/wiki-fresh-0603b104j500ct/0603B104J500CT.step"
BACKUP = "/tmp/claude-1001/-home-adom-project/7572280e-1088-44ed-9829-35fccaaabecb/scratchpad/wiki-work-0603b104j500ct/_backup-working-composite.glb"
OUT = "/tmp/claude-1001/-home-adom-project/7572280e-1088-44ed-9829-35fccaaabecb/scratchpad/wiki-work-0603b104j500ct/0603B104J500CT.glb"
INSERTION = "/tmp/claude-1001/-home-adom-project/7572280e-1088-44ed-9829-35fccaaabecb/scratchpad/wiki-work-0603b104j500ct/0603B104J500CT.insertion.glb"
DEPTH = 0.06
FONT, FONT_PATH = "DejaVu Sans", "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
ORIG_GLB = "/tmp/claude-1001/-home-adom-project/7572280e-1088-44ed-9829-35fccaaabecb/scratchpad/wiki-fresh-0603b104j500ct/0603B104J500CT.glb"
TOL, ANG = 0.02, 0.35 # tessellation
# ---- Reference colours straight from the actual STEP (via its color-preserving GLB) ----
def load_ref():
f=open(ORIG_GLB,'rb'); f.read(12); ln=struct.unpack('<I',f.read(4))[0]; f.read(4)
j=json.loads(f.read(ln)); bl=struct.unpack('<I',f.read(4))[0]; f.read(4); bd=f.read(bl)
CT={5123:'H',5125:'I',5126:'f'}; SZ={5123:2,5125:4,5126:4}; NC={'SCALAR':1,'VEC3':3}
def acc(i):
a=j['accessors'][i]; bv=j['bufferViews'][a['bufferView']]
off=bv.get('byteOffset',0)+a.get('byteOffset',0); n=a['count']*NC[a['type']]
v=struct.unpack(f'<{n}'+CT[a['componentType']], bd[off:off+n*SZ[a['componentType']]])
return np.array(v).reshape(a['count'],NC[a['type']])
cents=[]; silver=[]
silver_mat = [m['name'] for m in j['materials']].index('mat_0')
for pr in j['meshes'][0]['primitives']:
pos=acc(pr['attributes']['POSITION']); idx=acc(pr['indices']).reshape(-1,3)
c=pos[idx].mean(axis=1)
cents.append(c); silver.append(np.full(len(c), pr.get('material')==silver_mat))
return np.vstack(cents).astype(np.float32), np.concatenate(silver)
REF_C, REF_SILVER = load_ref()
print(f"ref mesh: {len(REF_C)} tris ({REF_SILVER.sum()} silver / {(~REF_SILVER).sum()} brown)")
body = cq.importers.importStep(STEP_IN)
# ---- PART NUMBER on TOP (+Z), reading world -X + letter-tops toward -Y so it
# reads upright/consistent with the side value when the +Y face is toward you ----
pn = (cq.Workplane("XY").workplane(offset=0.8)
.text("0603B104J500CT", 0.10, -DEPTH, font=FONT, fontPath=FONT_PATH,
halign="center", valign="center", combine=False)
.rotate((0,0,0),(0,0,1), 180))
# ---- VALUE on +Y SIDE, LARGE, reads correctly when viewed from +Y ----
# Plane on the +Y face: local +X = world -X (so a +Y viewer reads left→right),
# local +Y = world +Z (upright), normal +Y; extrude -DEPTH cuts into the body.
side_plane = cq.Plane(origin=(0, 0.40, 0.40), xDir=(-1, 0, 0), normal=(0, 1, 0))
val = (cq.Workplane(side_plane)
.text("100nF", 0.28, -DEPTH, font=FONT, fontPath=FONT_PATH,
halign="center", valign="center", combine=False))
for nm, t in (("PN(top)", pn), ("100nF(side)", val)):
bb = t.val().BoundingBox()
print(f" {nm:12s} X[{bb.xmin:.3f},{bb.xmax:.3f}] Y[{bb.ymin:.3f},{bb.ymax:.3f}] Z[{bb.zmin:.3f},{bb.zmax:.3f}]")
embossed = body.cut(pn).cut(val)
# ---- tessellate + flat normals ----
vmm, tris = embossed.val().tessellate(TOL, ANG)
P = np.array([[v.x,v.y,v.z] for v in vmm]) * 0.001
tris = np.array(tris, dtype=np.int64)
tp = P[tris]
fn = np.cross(tp[:,1]-tp[:,0], tp[:,2]-tp[:,0]); L=np.linalg.norm(fn,axis=1,keepdims=True); L[L==0]=1; fn/=L
V = tp.reshape(-1,3).astype(np.float32); N = np.repeat(fn,3,axis=0).astype(np.float32)
# The actual STEP's silver/brown boundary is a clean plane at |x| = 0.5 mm (verified
# from the reference mesh: silver tris all |x|>=0.5, brown all <=0.5). Split there.
TERM_X = float(np.abs(REF_C[REF_SILVER][:,0]).min()) # inner edge of silver in the real STEP (m)
print(f"real silver inner edge |x| = {TERM_X*1000:.4f} mm (brown reaches {np.abs(REF_C[~REF_SILVER][:,0]).max()*1000:.4f} mm)")
cx = tp.mean(axis=1)[:,0]
term = np.abs(cx) >= TERM_X - 1e-6
ids = np.arange(len(tris)*3).reshape(-1,3)
term_idx = ids[term].reshape(-1).astype(np.uint32)
body_idx = ids[~term].reshape(-1).astype(np.uint32)
print(f"tessellated {len(tris)} tris → silver {term.sum()} / brown {(~term).sum()} (clean split at STEP boundary)")
# ---- splice into composite (2 prims: silver mat_0, brown mat_1) ----
def swap(path_in, path_out):
g = pygltflib.GLTF2().load(path_in); blob = bytearray(g.binary_blob())
chip = next(n for n in g.nodes if n.name=="0603B104J500CT"); mesh = g.meshes[chip.mesh]
names=[m.name for m in g.materials]; ms=names.index("mat_0"); mb=names.index("mat_1")
def av(d,t):
nonlocal blob
while len(blob)%4: blob+=b"\x00"
o=len(blob); blob+=d; g.bufferViews.append(pygltflib.BufferView(buffer=0,byteOffset=o,byteLength=len(d),target=t)); return len(g.bufferViews)-1
pbv=av(V.tobytes(),34962); nbv=av(N.tobytes(),34962)
pa=len(g.accessors); g.accessors.append(pygltflib.Accessor(bufferView=pbv,componentType=5126,count=len(V),type="VEC3",min=V.min(0).tolist(),max=V.max(0).tolist()))
na=len(g.accessors); g.accessors.append(pygltflib.Accessor(bufferView=nbv,componentType=5126,count=len(N),type="VEC3"))
def ia(a):
bv=av(a.tobytes(),34963); i=len(g.accessors); g.accessors.append(pygltflib.Accessor(bufferView=bv,componentType=5125,count=len(a),type="SCALAR",min=[int(a.min())],max=[int(a.max())])); return i
ta,ba=ia(term_idx),ia(body_idx)
mesh.primitives=[
pygltflib.Primitive(attributes=pygltflib.Attributes(POSITION=pa,NORMAL=na),indices=ta,material=ms,mode=4),
pygltflib.Primitive(attributes=pygltflib.Attributes(POSITION=pa,NORMAL=na),indices=ba,material=mb,mode=4)]
g.buffers[0].byteLength=len(blob); g.set_binary_blob(bytes(blob)); g.save(path_out)
print(f"OK: {path_out} ({len(blob):,} B)")
swap(BACKUP, OUT)
import shutil; shutil.copy(OUT, INSERTION); print("mirrored → insertion.glb")