main
noah Add datasheet, figures, hero card, provenance, verification log and README a043b0e 10h ago
--- a/scripts/hero_glb.py
+++ b/scripts/hero_glb.py
@@ -328,12 +328,18 @@
     cx, cy = (min(xs) + max(xs)) / 2, (min(ys) + max(ys)) / 2
     hx, hy = (max(xs) - min(xs)) / 2 or 1e-6, (max(ys) - min(ys)) / 2 or 1e-6
     plan, seen = [], set()
+    # a single column (or row) of more than two pads (1xN pin header, SIP): every pad is on the one line, so the
+    # extreme-based split below would push interior labels into the gaps between pads. Label them all on one side.
+    one_col = len(sig) > 2 and max(xs) - min(xs) < 0.3
+    one_row = len(sig) > 2 and not one_col and max(ys) - min(ys) < 0.3
     for p in sig:
         if p['number'] in seen: continue        # multi-pad numbers (split EP, shield legs) labelled once
         seen.add(p['number'])
         # side = the extreme it sits closest to (normalised), so a 2-row part splits left/right or top/bottom
         dxn, dyn = (p['x'] - cx) / hx, (p['y'] - cy) / hy
-        if len(sig) == 2 or abs(dxn) >= abs(dyn): side = 'R' if p['x'] >= cx else 'L'
+        if one_col: side = 'R'
+        elif one_row: side = 'B'
+        elif len(sig) == 2 or abs(dxn) >= abs(dyn): side = 'R' if p['x'] >= cx else 'L'
         else: side = 'B' if p['y'] >= cy else 'T'
         same = [q for q in sig if q is not p and ((side in 'LR' and abs(q['x'] - p['x']) < 0.3) or (side in 'TB' and abs(q['y'] - p['y']) < 0.3))]
         pitch = min([abs((q['y'] - p['y']) if side in 'LR' else (q['x'] - p['x'])) for q in same] or [99])
@@ -355,6 +361,12 @@
             if side == 'R': lb['x'] = hit[1] + gap
             if side == 'T': lb['y'] = hit[2] - gap
             if side == 'B': lb['y'] = hit[3] + gap
+    if plan and one_col:     # one column: every label starts on the same line, clear of whatever pushed any of them out
+        mx = max(lb['x'] for lb in plan)
+        for lb in plan: lb['x'] = mx
+    if plan and one_row:
+        my = max(lb['y'] for lb in plan)
+        for lb in plan: lb['y'] = my
     return plan, None
 
 
@@ -530,7 +542,7 @@
         x, y, r, _ = dot
         ring = [(x + r * math.cos(t), y + r * math.sin(t)) for t in np.linspace(0, 2 * math.pi, 33)[:-1]]
         silk_tris += fill_tris([ring])
-    obstacles = [(min(q[0] for q in pts), max(q[0] for q in pts), min(q[1] for q in pts), max(q[1] for q in pts)) for kind, pts, _ in silk if kind == 'fill']
+    obstacles = [(min(q[0] for q in pts) - w / 2, max(q[0] for q in pts) + w / 2, min(q[1] for q in pts) - w / 2, max(q[1] for q in pts) + w / 2) for kind, pts, w in silk]
     if dot and not existing_mark: obstacles.append((dot[0] - dot[2], dot[0] + dot[2], dot[1] - dot[2], dot[1] + dot[2]))
     plan, why_no_labels = label_plan(pads, names, obstacles)
     name_up, name_down = [], []
@@ -563,7 +575,7 @@
                                     omitted=why_no_labels, fallback_to_pad_number=sum(1 for lb in plan if lb['text'] == lb['pad'])))
     hero = dict(spec=SPEC, mpn=mpn, kind='component + footprint reference art', board_use=False, plane_z_mm=Z, units='metre', upAxis='Z',
                 layers=layers, align=chk, sources=dict(glb=os.path.basename(src_glb), glb_sha256=sha(src_glb),
-                footprint=os.path.basename(kmod), footprint_sha256=sha(kmod), pins='page.json component.pins'), generator='factory/hero_glb.py')
+                footprint=os.path.basename(kmod), footprint_sha256=sha(kmod), pins='page.json component.pins'), generator='factory/hero_glb.py + single-row label patch')
     ex = g.asset.extras if isinstance(g.asset.extras, dict) else {}
     ex['adomHero'] = hero; g.asset.extras = ex
     g.save_binary(out)