← Commit history

Silkscreen + callouts now a flat PRINTED texture on the board (real silkscreen, zero geometry) so text casts NO shadow; arrows drawn in 2D so the solder arrowhead lands exactly on a ball

John Lauer ·2e19dca32b ·3mo ago ·parent 1acfc4b
3 files changed +53−51
AdomMachinePins-4pin-animated.glb
⋯ 1 unchanged line ⋯
AdomMachinePins-complete.zip
⋯ 1 unchanged line ⋯
scripts/build_board_glb.py+53−51
@@ -5,9 +5,10 @@ baked silkscreen text geometry, animated contact shadows, and a SUBTLE insertion 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 matplotlib.font_manager import FontProperties, findfont from shapely.geometry import Polygon, MultiPolygon from shapely.ops import unary_union+FONT_PATH=findfont(FontProperties(family="DejaVu Sans", weight="bold"))  def text_mesh(s, height_mm, thick_mm=0.12):     """Extruded silk text with REAL cut-out counters (holes in D,R,0,e...), normal +Z,@@ -106,7 +107,8 @@ for i,(cx,cy) in enumerate(PIN_CENTERS):     cyl.apply_translation([cx,cy,board_top-FR4_T/2]); cutters.append(cyl) slab=slab.difference(trimesh.util.concatenate(cutters), engine='manifold')  # REAL through-holes slab.fix_normals()   # outward normals so single-sided transparency renders the clean outer shell-add_mesh(slab.vertices, slab.faces, [0.05,0.42,0.12,0.46], "board_fr4", double=False)+# NOTE: the board is added at the very END with a silkscreen TEXTURE (flat print) instead of a+# solid colour, so the text/callouts are surface print (zero geometry) and cast NO shadow.  # REAL copper: 1oz (~35um) annular ring PROUD on the FR4 top AND bottom, + a plated via barrel # lining the drilled hole (sticks out a hair top/bottom). Not buried in the FR4.@@ -156,57 +158,57 @@ 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: real white text with cut-out counters; shows hole Ø + pin Ø per pad ----def place_text(txt, cx, cy, h_mm, color, name):-    t=text_mesh(txt, h_mm); t.apply_scale(0.001)-    t.apply_translation([cx, cy, board_top+0.00005])-    add_mesh(t.vertices, t.faces, color, name)--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")}-WHITE=[0.97,0.97,0.97,1.0]+# --- 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)++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():-    name,spec_s=LABELS[k]-    y0=c[1]-barrel_r-0.0028-    place_text(name,   c[0], y0,          1.3, WHITE, f"nocast_silk_{k}_0")-    place_text(spec_s, c[0], y0-0.0018,   1.1, WHITE, f"nocast_silk_{k}_1")-    print(f"  silk PIN_{k}: '{name}' / '{spec_s}'")--# --- PRESS-FIT callouts: amber text + leader arrow to a hole, explaining pin>hole (both sizes) ----AMBER=[0.98,0.74,0.10,1.0]; STEEL=[0.60,0.80,0.97,1.0]-def callout(title, anchor, spec_line, why_line, tip, start, prefix, color=AMBER):-    place_text(title,     anchor[0], anchor[1]+0.0022, 2.2, color, prefix+"_t")-    place_text(spec_line, anchor[0], anchor[1],        1.5, color, prefix+"_s")-    place_text(why_line,  anchor[0], anchor[1]-0.0020, 1.4, color, prefix+"_w")-    tip=np.array(tip); start=np.array(start); z=board_top+0.00006-    lead=trimesh.creation.cylinder(radius=0.00016, segment=[[start[0],start[1],z],[tip[0],tip[1],z]])-    add_mesh(lead.vertices,lead.faces,color,prefix+"_lead",metal=False)-    d=tip-start; d=d/np.linalg.norm(d)-    head=trimesh.creation.cone(radius=0.00045,height=0.0011,sections=16)-    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]]))-    head.apply_translation([tip[0],tip[1],z])-    add_mesh(head.vertices,head.faces,color,prefix+"_arrow",metal=False)--# PRESS-FIT: medium (0.1mm) -> pin @ (40,10); large (0.15mm) -> pin @ (10,40)-callout("PRESS-FIT",(0.0215,0.0180), "hole Ø1.1   pin Ø1.2",  "pin 0.1mm wider = gas-tight",-        (0.0385,0.0112), (0.0300,0.0135), "nocast_cmed")-callout("PRESS-FIT",(0.0270,0.0300), "hole Ø3.45   pin Ø3.6", "pin 0.15mm wider = gas-tight",-        (0.0132,0.0378), (0.0195,0.0340), "nocast_clrg")-# SOLDER PASTE: snap the arrow tip to the ACTUAL nearest ball center (balls are discrete-# 300um dots), so the arrowhead lands squarely on a ball. large pad @ (40,40), 16 balls, ring 2.1625mm.-_pc=np.array([0.040,0.040]); _ca=np.array([0.0240,0.0455]); _u=(_ca-_pc)/np.linalg.norm(_ca-_pc)-_ring=0.0021625; _n=16+    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)++# 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)+_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)-_tip=_pc+_ring*np.array([np.cos(_ang),np.sin(_ang)])   # exact ball center facing the callout-callout("SOLDER PASTE",(0.0240,0.0455), "300µm (0.3mm)", "jet-dispensed paste balls",-        (_tip[0],_tip[1]), (0.0300,0.0446), "nocast_csol", STEEL)-print("  callouts: PRESS-FIT x2 + SOLDER PASTE -> balls @ (40,40)")+_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")++# add the board with the silkscreen texture (planar UVs; single-sided transparent print)+uv=np.column_stack([(slab.vertices[:,0]-bx0)/(bx1-bx0), 1.0-(slab.vertices[:,1]-by0)/(by1-by0)])+bmesh=trimesh.Trimesh(vertices=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")  scene.export(OUT) print("exported",OUT)