← Commit history

GLB: hemisphere solder paste (was full spheres), cut-out letter counters (fixed dropped-glyph bug), PRESS-FIT callout with arrow showing hole Ø vs pin Ø + gas-tight explanation, removed fake baked shadows

John Lauer ·f479bf6a23 ·3mo ago ·parent 64c8739
5 files changed +56−22
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.png
⋯ 1 unchanged line ⋯
scripts/build_board_glb.py+56−22
@@ -10,12 +10,23 @@ 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."""+    """Extruded silk text with REAL cut-out counters (holes in D,R,0,e...), 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)+    raw=[Polygon(pp).buffer(0) for pp in tp.to_polygons(closed_only=True) if len(pp)>=3]+    # even-odd fill: a contour is a HOLE if it sits inside an odd number of others.+    # Robust: union all solids, union all holes, subtract -> correct counters, nothing dropped.+    solids=[]; holes=[]+    for i,s_ in enumerate(raw):+        # depth = how many OTHER contours fully contain this one (full-polygon containment;+        # a centroid point would fall in the counter for O/D/e and misclassify the outline).+        depth=sum(1 for j,o in enumerate(raw) if j!=i and o.contains(s_))+        (holes if depth%2 else solids).append(s_)+    geom=unary_union(solids)+    if holes: geom=geom.difference(unary_union(holes))     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])+    meshes=[trimesh.creation.extrude_polygon(p, height=thick_mm) for p in parts if p.area>1e-6]+    m=trimesh.util.concatenate(meshes)     m.apply_translation([-(m.bounds[0][0]+m.bounds[1][0])/2, -(m.bounds[0][1]+m.bounds[1][1])/2, 0])     return m @@ -115,30 +126,53 @@ for k,(c,barrel_r) in pin_info.items():     for j in range(n):         th=2*np.pi*j/n         s=trimesh.creation.icosphere(subdivisions=2,radius=BALL_R)-        # 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])+        s=s.slice_plane([0,0,0],[0,0,1],cap=True)   # HEMISPHERE: flat bottom sits on the pad+        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.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")+    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 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+# --- 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] 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}")+    y0=c[1]-barrel_r-0.0028+    place_text(name,   c[0], y0,          1.3, WHITE, f"silk_{k}_0")+    place_text(spec_s, c[0], y0-0.0018,   1.1, WHITE, f"silk_{k}_1")     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}")+# --- PRESS-FIT callout: amber text + leader line/arrow pointing at a hole, explaining pin>hole ---+AMBER=[0.98,0.74,0.10,1.0]+cxr,cyr=0.0245,0.0225                                 # empty board-centre anchor+place_text("PRESS-FIT",                 cxr, cyr+0.0022, 2.4, AMBER, "callout_0")+place_text("hole Ø1.1   pin Ø1.2", cxr, cyr,        1.5, AMBER, "callout_1")+place_text("pin 0.1mm wider = gas-tight fit", cxr, cyr-0.0020, 1.4, AMBER, "callout_2")+# leader line from the callout to the medium pin @ (40,10) base, on the board surface+tip=np.array([0.0385,0.0112]); start=np.array([0.0315,0.0175])+lead=trimesh.creation.cylinder(radius=0.00016, segment=[[start[0],start[1],board_top+0.00006],+                                                        [tip[0],tip[1],board_top+0.00006]])+add_mesh(lead.vertices,lead.faces,AMBER,"callout_leader",metal=False)+# arrowhead cone at the tip+d=tip-start; d=d/np.linalg.norm(d)+head=trimesh.creation.cone(radius=0.00045,height=0.0011,sections=16)+# orient cone (+Z) to lie flat pointing along d+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]])+    R=np.eye(3)+vx+vx@vx*((1-cth)/(s_*s_)); M=np.eye(4); M[:3,:3]=R+    head.apply_transform(M)+head.apply_translation([tip[0],tip[1],board_top+0.00006])+add_mesh(head.vertices,head.faces,AMBER,"callout_arrow",metal=False)+print("  callout: PRESS-FIT leader -> medium pin @ (40,10)")  scene.export(OUT) print("exported",OUT)