← Commit history

Revert silkscreen/callouts to 3D extruded text: rotates with the assembly so labels match pins AND read correctly at default camera (texture UV kept desyncing). Fixed arrow apex-on-target.

John Lauer ·e3f5a381bf ·3mo ago ·parent 5b8e058
3 files changed +41−44
AdomMachinePins-4pin-animated.glb
⋯ 1 unchanged line ⋯
AdomMachinePins-complete.zip
⋯ 1 unchanged line ⋯
scripts/build_board_glb.py+41−44
@@ -165,61 +165,58 @@ for k,(c,barrel_r) in pin_info.items():         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} hemispheres x {BALL_D*1000:.2f}mm on ring r={ring_r*1000:.2f}mm") -# --- SILKSCREEN + CALLOUTS as a flat PRINTED TEXTURE on the board top (zero geometry => no-#     shadow-casting text). Arrows drawn in 2D so the head lands EXACTLY on its target. ----from PIL import Image, ImageDraw, ImageFont-import math-TEX=3000                                   # px across the 50mm board => 60 px/mm (crisp)-BW=(bx1-bx0)*1000; BH=(by1-by0)*1000       # board size in mm-img=Image.new("RGBA",(TEX,TEX),(13,107,31,120))   # transparent green soldermask-dr=ImageDraw.Draw(img)-def A(xmm,ymm):                            # board mm -> texture px (flip y so +y reads up)-    return (xmm/BW*TEX, (1.0-ymm/BH)*TEX)-def T(s,cx,cy,h_mm,rgba):-    f=ImageFont.truetype(FONT_PATH,int(round(h_mm/BW*TEX)))-    dr.text(A(cx,cy),s,font=f,fill=rgba,anchor="mm")-def ARR(sx,sy,tx,ty,rgba,w_mm=0.34,head_mm=1.3):-    p0=A(sx,sy); p1=A(tx,ty); ang=math.atan2(p1[1]-p0[1],p1[0]-p0[0])-    dr.line([p0,p1],fill=rgba,width=max(2,int(w_mm/BW*TEX)))-    hl=head_mm/BW*TEX; hw=0.6*head_mm/BW*TEX-    ax=p1[0]-hl*math.cos(ang); ay=p1[1]-hl*math.sin(ang)-    dr.polygon([p1,(ax-hw*math.sin(ang),ay+hw*math.cos(ang)),-                   (ax+hw*math.sin(ang),ay-hw*math.cos(ang))],fill=rgba)-WHITE=(247,247,247,255); AMBER=(250,189,26,255); STEEL=(150,205,250,255)+# --- SILKSCREEN + CALLOUTS as 3D extruded text on the board top. These go through add_mesh, so+#     they get the SAME 180 rotation as the pins -> labels stay matched to pins AND read left-to-right+#     at the default camera. (Solid geometry, unlike the flat-texture attempt whose UV kept desyncing.)+WHITE=[0.97,0.97,0.97,1.0]; AMBER=[0.98,0.74,0.10,1.0]; STEEL=[0.60,0.80,0.97,1.0]+def place_text(txt, cx_mm, cy_mm, h_mm, color, name):+    t=text_mesh(txt, h_mm); t.apply_scale(0.001)+    t.apply_translation([cx_mm/1000.0, cy_mm/1000.0, board_top+0.00006])+    add_mesh(t.vertices, t.faces, color, name)+def arrow(sx,sy,tx,ty,color,name):+    z=board_top+0.00006+    lead=trimesh.creation.cylinder(radius=0.00016,+        segment=[[sx/1000.0,sy/1000.0,z],[tx/1000.0,ty/1000.0,z]])+    add_mesh(lead.vertices,lead.faces,color,name+"_lead")+    d=np.array([tx-sx,ty-sy],float); d/=np.linalg.norm(d); HH=0.0012+    head=trimesh.creation.cone(radius=0.00048,height=HH,sections=16)   # apex at +Z*HH+    zc=np.array([0,0,1.]); axd=np.array([d[0],d[1],0.]); axd/=np.linalg.norm(axd)+    v=np.cross(zc,axd); s_=np.linalg.norm(v); cth=np.dot(zc,axd)+    if s_>1e-6:+        vx=np.array([[0,-v[2],v[1]],[v[2],0,-v[0]],[-v[1],v[0],0]])+        head.apply_transform(np.block([[np.eye(3)+vx+vx@vx*((1-cth)/(s_*s_)),np.zeros((3,1))],[np.zeros(3),1]]))+    # place the APEX exactly on the tip (base sits back toward the callout)+    head.apply_translation([tx/1000.0-HH*d[0], ty/1000.0-HH*d[1], z])+    add_mesh(head.vertices,head.faces,color,name+"_arrow")  LABELS={0:("MP-MED-STD","hole Ø1.1  pin Ø1.2"), 1:("MP-MED-SHORT","hole Ø1.1  pin Ø1.2"),         2:("MP-LRG-STD","hole Ø3.45  pin Ø3.6"), 3:("MP-LRG-SHORT","hole Ø3.45  pin Ø3.6")} for k,(c,barrel_r) in pin_info.items():     nm,sp=LABELS[k]; cx,cy=c[0]*1000,c[1]*1000; y0=cy-barrel_r*1000-2.8-    T(nm,cx,y0,1.35,WHITE); T(sp,cx,y0-1.9,1.15,WHITE)+    place_text(nm, cx, y0,     1.35, WHITE, f"silk_{k}_0")+    place_text(sp, cx, y0-1.9, 1.15, WHITE, f"silk_{k}_1") -# PRESS-FIT callouts (amber), arrow tip AT the pin/hole-T("PRESS-FIT",21.5,20.2,2.3,AMBER); T("hole Ø1.1   pin Ø1.2",21.5,18.0,1.5,AMBER)-T("pin 0.1mm wider = gas-tight",21.5,16.0,1.4,AMBER); ARR(30.0,14.0,39.2,10.7,AMBER)-T("PRESS-FIT",27.0,32.2,2.3,AMBER); T("hole Ø3.45   pin Ø3.6",27.0,30.0,1.5,AMBER)-T("pin 0.15mm wider = gas-tight",27.0,28.0,1.4,AMBER); ARR(19.5,34.0,12.6,38.4,AMBER)--# SOLDER PASTE callout (steel), arrow tip snapped to the nearest ball center @ (40,40)+def callout(title, ax, ay, spec, why, tx, ty, sx, sy, prefix, color):+    place_text(title, ax, ay+2.2, 2.3, color, prefix+"_t")+    place_text(spec,  ax, ay,     1.5, color, prefix+"_s")+    place_text(why,   ax, ay-2.0, 1.4, color, prefix+"_w")+    arrow(sx,sy,tx,ty,color,prefix)+# PRESS-FIT: medium -> pin @ (40,10); large -> pin @ (10,40)+callout("PRESS-FIT",21.5,18.0,"hole Ø1.1   pin Ø1.2","pin 0.1mm wider = gas-tight",+        39.2,10.7, 30.0,14.0, "cmed", AMBER)+callout("PRESS-FIT",27.0,30.0,"hole Ø3.45   pin Ø3.6","pin 0.15mm wider = gas-tight",+        12.6,38.4, 19.5,34.0, "clrg", AMBER)+# SOLDER PASTE -> nearest ball center on the large pad @ (40,40) _pc=np.array([40.0,40.0]); _ca=np.array([24.0,45.5]); _u=(_ca-_pc)/np.linalg.norm(_ca-_pc) _ringmm=(FP[3][0]+FP[3][1])/2.0*1000; _n=max(6,int(round(2*np.pi*(FP[3][0]+FP[3][1])/2.0/PITCH))) _ang=round(np.arctan2(_u[1],_u[0])/(2*np.pi/_n))*(2*np.pi/_n) _ball=_pc+_ringmm*np.array([np.cos(_ang),np.sin(_ang)])-T("SOLDER PASTE",24.0,47.7,2.3,STEEL); T("300µm (0.3mm)",24.0,45.5,1.5,STEEL)-T("jet-dispensed paste balls",24.0,43.5,1.4,STEEL); ARR(30.0,45.0,_ball[0],_ball[1],STEEL)-print(f"  silkscreen texture {TEX}px: 4 labels + 3 callouts; solder arrow -> ball ({_ball[0]:.1f},{_ball[1]:.1f})")-img.save("/tmp/v7_texture.png")-img=img.transpose(Image.FLIP_LEFT_RIGHT)   # GLB export mirrors this face in X; pre-flip so print + arrows read correct+callout("SOLDER PASTE",24.0,45.5,"300µm (0.3mm)","jet-dispensed paste balls",+        _ball[0],_ball[1], 30.0,45.0, "csol", STEEL)+print(f"  3D silk: 4 labels + 3 callouts; solder arrow -> ball ({_ball[0]:.1f},{_ball[1]:.1f})") -# add the board with the silkscreen texture (planar UVs; single-sided transparent print)-# UV from ORIGINAL vertex positions (planar), then rotate the board verts with the rest of the-# assembly so the printed silkscreen faces the viewer's default camera. UV stays per-vertex, so the-# texture rotates with the board. Node transform stays identity -> the pin lift animation is unaffected.-uv=np.column_stack([(slab.vertices[:,0]-bx0)/(bx1-bx0), 1.0-(slab.vertices[:,1]-by0)/(by1-by0)])-bmesh=trimesh.Trimesh(vertices=_rot(slab.vertices), faces=slab.faces, process=False)-bmesh.visual=trimesh.visual.TextureVisuals(uv=uv, material=trimesh.visual.material.PBRMaterial(-    baseColorFactor=[255,255,255,255], baseColorTexture=img, metallicFactor=0.2, roughnessFactor=0.55,-    alphaMode="BLEND", doubleSided=False))-scene.add_geometry(bmesh, node_name="board_fr4", geom_name="board_fr4")+# transparent FR4 board (solid colour; the 3D text above carries the print)+add_mesh(slab.vertices, slab.faces, [0.05,0.42,0.12,0.46], "board_fr4", double=False)  scene.export(OUT) print("exported",OUT)