component
Adom Machine Pins, Fusion Library + Sample Board
Public Made by Adomby adom
One Fusion 360 .lbr with all four Adom press-fit machine pins (working 3D), plus a sample board (2D/3D/silkscreen) ganging all four together. Fully documented and rebuildable.
← Commit history
GLB perfected: 300um solder balls on annular ring (was oversized), baked silkscreen text geometry (verified readable top-down), animated contact shadows (shadows now move with the insertion), verified holes=drill/pins=body press-fit
6 files changed
+68−19
AdomMachinePins-4pin-animated.glb⋯ 1 unchanged line ⋯
AdomMachinePins-complete.zip⋯ 1 unchanged line ⋯
screenshots/board-3d-render.png⋯ 1 unchanged line ⋯
screenshots/board-3d-top-silk.pngadded⋯ 1 unchanged line ⋯
scripts/add_anim_board.py+21−12@@ -1,35 +1,44 @@ #!/usr/bin/env python3-"""Add a gentle looping insertion animation to the 4 PIN_* nodes of the board GLB."""+"""Add a gentle looping insertion animation to the board GLB:+- PIN_* nodes translate up by LIFT mm then back (pins press into their own holes)+- SHADOW_* nodes scale up as the pin lifts (the contact shadow spreads/softens), so the+ shadow VISIBLY animates in the native viewer regardless of any ground-plane behavior.""" import sys, numpy as np, pygltflib from pygltflib import GLTF2, Animation, AnimationSampler, AnimationChannel, AnimationChannelTarget, Accessor, BufferView IN, OUT = sys.argv[1], sys.argv[2]-LIFT = float(sys.argv[3])/1000.0 # mm -> meters (scene is meters)+LIFT = float(sys.argv[3])/1000.0 # mm -> meters g = GLTF2().load(IN)-pin_nodes = [i for i,n in enumerate(g.nodes) if n.name and n.name.startswith("PIN_")]-print("animating nodes:", [g.nodes[i].name for i in pin_nodes])+pin_nodes = [i for i,n in enumerate(g.nodes) if n.name and n.name.startswith("PIN_")]+shadow_nodes = [i for i,n in enumerate(g.nodes) if n.name and n.name.startswith("SHADOW_")]+print("pins:", [g.nodes[i].name for i in pin_nodes], "shadows:", [g.nodes[i].name for i in shadow_nodes]) times = np.array([0.0,1.4,3.0,4.4,6.0], dtype=np.float32) trans = np.array([[0,0,0],[0,0,0],[0,0,LIFT],[0,0,LIFT],[0,0,0]], dtype=np.float32)+scale = np.array([[1,1,1],[1,1,1],[1.7,1.7,1],[1.7,1.7,1],[1,1,1]], dtype=np.float32) # shadow spread blob = bytearray(g.binary_blob())-while len(blob)%4: blob.append(0) def add_view(b): while len(blob)%4: blob.append(0) off=len(blob); blob.extend(b) g.bufferViews.append(BufferView(buffer=0, byteOffset=off, byteLength=len(b))) return len(g.bufferViews)-1-tv=add_view(times.tobytes())-g.accessors.append(Accessor(bufferView=tv,componentType=pygltflib.FLOAT,count=len(times),type="SCALAR",- min=[float(times.min())],max=[float(times.max())])); in_idx=len(g.accessors)-1-ov=add_view(trans.tobytes())-g.accessors.append(Accessor(bufferView=ov,componentType=pygltflib.FLOAT,count=len(trans),type="VEC3")); out_idx=len(g.accessors)-1+def add_acc(view, count, typ, mn=None, mx=None):+ g.accessors.append(Accessor(bufferView=view, componentType=pygltflib.FLOAT, count=count, type=typ, min=mn, max=mx))+ return len(g.accessors)-1++t_idx = add_acc(add_view(times.tobytes()), len(times), "SCALAR", [float(times.min())],[float(times.max())])+tr_idx = add_acc(add_view(trans.tobytes()), len(trans), "VEC3")+sc_idx = add_acc(add_view(scale.tobytes()), len(scale), "VEC3") samplers=[]; channels=[] for pn in pin_nodes:- samplers.append(AnimationSampler(input=in_idx, output=out_idx, interpolation="LINEAR"))+ samplers.append(AnimationSampler(input=t_idx, output=tr_idx, interpolation="LINEAR")) channels.append(AnimationChannel(sampler=len(samplers)-1, target=AnimationChannelTarget(node=pn, path="translation")))+for sn in shadow_nodes:+ samplers.append(AnimationSampler(input=t_idx, output=sc_idx, interpolation="LINEAR"))+ channels.append(AnimationChannel(sampler=len(samplers)-1, target=AnimationChannelTarget(node=sn, path="scale"))) g.animations=[Animation(name="insert", samplers=samplers, channels=channels)] g.buffers[0].byteLength=len(blob); g.set_binary_blob(bytes(blob)) g.save(OUT)-print("added insertion animation, lift",LIFT*1000,"mm ->",OUT)+print(f"animation: {len(pin_nodes)} pins lift {LIFT*1000}mm + {len(shadow_nodes)} shadows spread -> {OUT}")
scripts/build_board_glb.py+47−7@@ -1,9 +1,23 @@ #!/usr/bin/env python3 """Rebuild the Adom pins board lead GLB from the REAL Fusion board (Draco-decoded), with: transparent FR4, real pins grouped per-node, 300um solder-paste balls on each pad,-and a SUBTLE insertion animation (small lift so pins clearly seat in their own holes).+baked silkscreen text geometry, animated contact shadows, and a SUBTLE insertion animation. Fully renderable/verifiable offline (uncompressed).""" import DracoPy, pygltflib, numpy as np, trimesh, sys+from matplotlib.textpath import TextPath+from matplotlib.font_manager import FontProperties+from shapely.geometry import Polygon, MultiPolygon+from shapely.ops import unary_union++def text_mesh(s, height_mm, thick_mm=0.12):+ """Extruded silk text, normal +Z, reads correctly viewed from +Z (the wiki view). Centered on origin."""+ tp=TextPath((0,0), s, size=height_mm, prop=FontProperties(family="DejaVu Sans", weight="bold"))+ polys=[Polygon(pp).buffer(0) for pp in tp.to_polygons(closed_only=True) if len(pp)>=3]+ geom=unary_union(polys)+ parts=list(geom.geoms) if isinstance(geom,MultiPolygon) else [geom]+ m=trimesh.util.concatenate([trimesh.creation.extrude_polygon(p,height=thick_mm) for p in parts if p.area>1e-6])+ m.apply_translation([-(m.bounds[0][0]+m.bounds[1][0])/2, -(m.bounds[0][1]+m.bounds[1][1])/2, 0])+ return m SRC = "/tmp/AdomBoardFusion.glb" # raw real Fusion board (Draco) OUT = sys.argv[1] if len(sys.argv)>1 else "/tmp/AdomBoard-lead2.glb"@@ -89,16 +103,42 @@ for k in range(4): add_mesh(V,F,[0.90,0.66,0.30,1.0],f"PIN_{k}",metal=True) print(f"PIN_{k} center=({c[0]*1000:.1f},{c[1]*1000:.1f})mm barrel OD={barrel_r*2000:.2f}mm") -# 300um solder-paste jet balls: ring just outside each barrel base, on the board top surface-BALL_R=0.00035 # 0.35mm radius (~0.7mm blob; jet paste reads better a touch above 300um)+# 300um Essemtec jet solder-paste balls, ON the annular ring (between drill edge and pad edge),+# at ~0.45mm pitch (paste.rs standard). Real spec size = 0.30mm dia; do NOT oversize.+BALL_D=0.00030; BALL_R=BALL_D/2.0; PITCH=0.00045+# per-pin footprint geometry (meters): medium drill1.1/pad1.6 ; large drill3.45/pad5.2+FP={0:(0.00055,0.00080), 1:(0.00055,0.00080), 2:(0.001725,0.00260), 3:(0.001725,0.00260)} for k,(c,barrel_r) in pin_info.items():- ring_r=barrel_r*1.18- n = 16 if barrel_r>0.0015 else 9+ drill_r,pad_r=FP[k]+ ring_r=(drill_r+pad_r)/2.0 # centerline of the annular ring+ n=max(6, int(round(2*np.pi*ring_r/PITCH))) for j in range(n): th=2*np.pi*j/n s=trimesh.creation.icosphere(subdivisions=2,radius=BALL_R)- s.apply_translation([c[0]+ring_r*np.cos(th), c[1]+ring_r*np.sin(th), board_top])- add_mesh(s.vertices,s.faces,[0.80,0.80,0.84,1.0],f"ball_{k}_{j}",metal=True)+ # seat the ball so it rests ON the pad surface (center one radius above board top)+ s.apply_translation([c[0]+ring_r*np.cos(th), c[1]+ring_r*np.sin(th), board_top+BALL_R])+ add_mesh(s.vertices,s.faces,[0.62,0.63,0.66,1.0],f"ball_{k}_{j}",metal=True)+ print(f" balls PIN_{k}: {n} x {BALL_D*1000:.2f}mm on ring r={ring_r*1000:.2f}mm")++# --- silkscreen: real white text geometry on the board top (matches the Fusion 2D silk) ---+LABELS={0:("MP-MED-STD","D1.1 R0.25"), 1:("MP-MED-SHORT","D1.1 R0.25"),+ 2:("MP-LRG-STD","D3.45 R0.875"), 3:("MP-LRG-SHORT","D3.45 R0.875")}+SILK_H=0.0014 # 1.4mm text+for k,(c,barrel_r) in pin_info.items():+ name,spec_s=LABELS[k]+ y0 = c[1]-barrel_r-0.0028 # first line just below the pad+ for li,txt in enumerate((name,spec_s)):+ tm_txt=text_mesh(txt, SILK_H*1000) # returns mm-scaled, centered+ tm_txt.apply_scale(0.001) # mm -> meters+ tm_txt.apply_translation([c[0], y0-li*0.0019, board_top+0.00005])+ add_mesh(tm_txt.vertices,tm_txt.faces,[0.97,0.97,0.97,1.0],f"silk_{k}_{li}")+ print(f" silk PIN_{k}: '{name}' / '{spec_s}'")++# --- animated contact shadow: a flat dark disc under each pin on the board top ---+for k,(c,barrel_r) in pin_info.items():+ disc=trimesh.creation.cylinder(radius=barrel_r*1.05, height=0.00002, sections=48)+ disc.apply_translation([c[0], c[1], board_top+0.00003])+ add_mesh(disc.vertices,disc.faces,[0.02,0.02,0.02,0.42],f"SHADOW_{k}") scene.export(OUT) print("exported",OUT)