Adom Chipsmith
Public Made by Adomby adom
STEP-native chip validator — 5-source cross-validation, OCCT-generated copyright-free 3D models with embedded signal annotations, datasheet-to-STEP pipeline.
adom-chipsmith — STEP-native pin/leg/pad/tab detection + footprint validation + sign-off
Pin/feature DETECTION was removed from chipsmith (commit ~2026-05-06). The Mode 0 named-PRODUCT walk and Mode 1 surface-type clustering didn't generalize across enough chips to be trustworthy — STEPs vary too much in how they name (or don't name) sub-products and how they model pads (discrete face entities vs material changes on shared body faces). The Detection tab (Tab 3) is gone. References to Mode 0 / Mode 1 / Mode 2 / coverage gates / pin-reveal strobe in this document are historical context, not current behavior.
What still works without detection:
- Tab 1 Source — manifest + spec editor + datasheet via adom-pdf-viewer
- Tab 2 Measurements — auto-rescale negotiation
- Tab 3 Footprint — multi-source overview + 3D pad overlays + faux PCB
- Tab 4 Pin-1 — user-placed synthetic indicator (NW/NE/SW/SE) + fp silk
- Tab 5 Sign-off — gates + JSON sidecar + .chipsmith.step bake
- Tab 6 Heatsink — auto-grid under EP from footprint data
- Tab 7 Solder-jet — 300µm dome layout from footprint pads Tabs renumbered 1..7.
Context
You want a higher-level app on top of adom-step that loads any STEP file + footprint + datasheet + manifest, then:
- Operates on the STEP file's B-rep + assembly hierarchy data, NOT on triangulated GLB meshes. Both
adom-chipfitandadom-chiplintertried the GLB-triangle approach and both failed. Triangle meshes lose named features, lose surface type (CYLINDER vs PLANE — which is exactly how you tell a BGA ball from a QFN pad), lose assembly hierarchy precision, and lose unit semantics. We will read the STEP B-rep directly. - STEP becomes the canonical artifact end-to-end across Adom going forward. The sign-off bakes a fresh
.chipsmith.step(role-tagged via XCAF named-data attributes, unit-normalized to mm, orientation corrected) — this is the file every downstream Adom tool will consume. A "shadow".chipsmith.glbis derived from it purely so Babylon has something to render in the viewer. GLBs are a presentation format only; they never re-enter the validation loop. - Detects every electrical-contact feature (legs / pads / balls / tabs / EP / through-hole pins / reinforcement tabs).
- Validates that the chip's actual measurements match the declared
body_dims_mm— catches mm-vs-m-vs-inch unit mismatches that infect random downloaded STEP files. Auto-rescales when it can identify the conversion factor; flags + blocks sign-off when it can't. - Auto-recovers from Y-up vs Z-up STEP convention mismatches.
- Tags scene graph with semantic role tags (
chipsmith_lead_1,chipsmith_pad_A1,chipsmith_body,chipsmith_ep,chipsmith_pin1_indicator, …) so downstream tools re-walk without re-detecting. - Renders the footprint in the same scene, projects it up to the chip bottom, checks bbox fit.
- Renders a 1.6mm faux PCB so you can see how the chip will sit on a real board.
- Cross-checks vs an authoritative stock-library footprint for the same MPN (sourced internally via service-kicad — surfaced to the user just as "stock library footprint" with no KiCad branding).
- Detects pin-1 (named entity in the STEP product hierarchy first; geometric corner-dot search second; cross-references vs footprint silkscreen).
- Lets the user sign off — locking orientation, footprint, pin-1, and measurements — emitting a
<mpn>.chipsmith.jsonvalidated sidecar + a baked role-tagged GLB. - Lays out InstaPCB solder-jet half-spheres on each pad per the solder-jetting skill.
- Auto-generates a heatsink-via grid under any detected EP / thermal pad — 0.3mm drill diameter, 1.2mm pitch, square grid centered on the EP with a 0.4mm edge margin. EEs hate placing 9–25 vias by hand; chipsmith does it for them. This is a convenience layer — the vias aren't on the chip, they're a pre-computed footprint addition the user can adopt downstream when designing their board.
- Optional laser-etch of the MPN onto the chip's top surface — toggleable per-chip cosmetic feature that engraves the part number into the body's top face (50µm-deep recess, slightly darker than body color, auto-sized + auto-positioned to avoid the pin-1 indicator). EEs love seeing the part number on the chip when they're laying out a board downstream — it's the warm-and-cozy "yes, this is the correct chip" reassurance that a plain matte-black QFN can't give them.
- Optional user-placed pin-1 indicator — when the manufacturer's STEP doesn't carry a pin-1 dot (or the chip is so plain you can't tell which end is which), the user can place a synthetic indicator. Default suggested position is derived from the footprint silkscreen marker so chip and footprint agree by construction; user can also click anywhere on the top surface to place manually. Shape options: dimple (recessed cylinder, default), raised dot, or corner chamfer. Tagged
pin1_indicator_user_placedso downstream tools can tell synthetic from manufacturer-original, and baked into the.chipsmith.steppermanently so the indicator survives every future load.
Display name: "Chipsmith". CLI: adom-chipsmith. Default port: 8871.
Why STEP-native, not GLB
| Data the STEP B-rep carries | What survives in a GLB triangle mesh |
|---|---|
PRODUCT_DEFINITION names per assembly child (lead, pad, EP, body, pin1_indicator…) |
Sometimes preserved by OCCT XCAF → glTF, often munged or dropped. Triangulation collapses sub-products. |
Surface type per face (PLANE, CYLINDER, CONICAL, TOROIDAL, B_SPLINE) |
Lost — every face is a flat triangle list. A BGA ball is no longer "a sphere"; it's just 200 triangles. |
Exact extents in declared length units (millimetre, metre, inch) |
Lost — units stripped during glTF write. The bbox you read in Babylon is in "scene units" with no metadata. |
| Assembly transform tree with named instance occurrences | Partially preserved as transformNode names, BUT only if XCAF kept the right tags through the conversion. |
| Per-face material assignments | Reduced to per-mesh PBR materials; same material on multiple faces collapses them into one mesh. |
Both adom-chipfit and adom-chiplinter work at the GLB layer. Both fail in characteristic ways: they can't tell a BGA ball from a QFN pad without a sphere-fit heuristic; they can't recover unit information from the STEP file; they fight collapsed-by-material faces (bug surface area: chiplinter's Mode 0 is still TODO in memory because the GLB hierarchy is too fragile to anchor on).
Chipsmith pivots: read the STEP file directly for detection, render the GLB in the viewer for visualization. Two parallel artifacts from the same STEP, used for different purposes.
Architecture
New repo: /home/adom/project/adom-chipsmith/. Rust CLI + tiny_http server + Hydrogen webview shell + vendored Babylon9 viewer.
Three-channel pipeline (STEP-first, GLB shadow):
.step (input) + .kicad_mod + chipsmith-spec.json + .pdf
│
├─► service-step2glb /step-meta → step-meta.json
│ (NEW endpoint — dumps OCCT XCAF data:
│ • full PRODUCT_DEFINITION tree (id, name, parent, transform)
│ • per-solid bbox in declared units + mm-converted
│ • per-face surface-type + bbox + area + parent solid
│ • declared length unit + conversion factor to mm
│ • material assignments per face)
│
└─► service-step2glb /convert → shadow .glb (Babylon viewer only)
│
▼
chipsmith reads step-meta.json
│
├─► measurement check (try ×1, ÷25.4, ÷1000, ×25.4, ×1000 — pick best fit)
├─► Y/Z auto-retry (rotate root, re-detect if first pass <50%)
├─► Detection:
│ Mode 0 (named PRODUCT walk) → match lead_*, pad_*, ball_*, ep, body, pin1*
│ Mode 1 (surface-type cluster) → cylinders → balls; coplanar planes at z≈0 → pads;
│ tall thin extrusions → through-hole pins
│ Mode 2 (face-merge fallback) → union-find on (material, adjacency) — only when
│ Modes 0 & 1 underperform
├─► Role-tag every solid (in-memory)
├─► Pin-1 detection (chip-side via PRODUCT name → top CYLINDER/CONICAL fallback;
│ cross-reference vs footprint F.SilkS marker)
└─► Footprint overlay + 1.6mm PCB plate + service-kicad ghost + solder-jet domes
─── on user sign-off ──────────────────────────────────────────────────────────
│
├─► service-step2glb /bake → <mpn>.chipsmith.step (CANONICAL)
│ (NEW endpoint — POST {original_step, applied_scale, applied_rotation,
│ role_tags_per_product[], pin1_marker_solid_id, sign_off_metadata}
│ OCCT writes a fresh STEP via XCAF:
│ • LENGTH_UNIT normalized to mm (coords ×= applied_scale)
│ • root AXIS2_PLACEMENT_3D rotated to fix Y-up if needed
│ • PRODUCT_DEFINITION names extended with chipsmith.role tags
│ • XCAF named-data attributes carry the structured role + confidence
│ + detected_via per solid → STEP custom property entities
│ • a chipsmith_signoff document-level attribute carries the JSON sidecar
│ Future Adom tools that consume the chip MUST prefer this file over
│ the original input.)
│
├─► service-step2glb /convert → <mpn>.chipsmith.glb (shadow, derived)
│ (Re-render the baked STEP through the existing /convert path so the
│ GLB's hierarchy/materials match the baked STEP exactly.)
│
└─► chipsmith writes → <mpn>.chipsmith.json (sidecar)
(Same content embedded in the STEP's chipsmith_signoff attribute, surfaced
here as a flat JSON for tools that don't link OCCT.)
Why this is fundamentally different from chipfit/chiplinter: detection runs on step-meta.json (B-rep), not on the GLB. The GLB is purely for visualization. We never ask the GLB triangle mesh "is this a sphere?" — the STEP already told us. And on sign-off, we re-emit a STEP file (not just a tagged GLB), so the validated chip flows downstream as a STEP — preserving B-rep precision through every subsequent Adom step.
File-explorer reveal (every file is one click from disk)
Every file chipsmith reads OR writes — input STEP, footprint, datasheet PDF, manifest JSON, output .chipsmith.step / .chipsmith.glb / .chipsmith.json, the per-chip cache, the source info.json, the ScreenShot folder, the wiki page assets, the SKILL.md it auto-installed — gets a tiny reveal-in-explorer button next to its label wherever it appears (Source tab, hierarchy rows, sign-off HUD, status bar, error toasts). Click → invokes adom-vscode reveal <abs path> (per the adom-vscode skill) which highlights the file in the VS Code Explorer sidebar.
| Where the reveal button shows up | What it reveals |
|---|---|
| Tab 1 Source — STEP file row | Source <mpn>.step |
| Tab 1 Source — Footprint row | <mpn>.kicad_mod |
| Tab 1 Source — Datasheet row | <mpn>.pdf |
| Tab 1 Source — Spec row | info.json (or chipsmith-spec.json) |
| Sign-off HUD — after sign-off | <mpn>.chipsmith.step, <mpn>.chipsmith.glb, <mpn>.chipsmith.json (one button each) |
| Sign-off HUD — "open enclosing folder" | the chip-fetcher library directory itself |
| Object-hierarchy header | the cache folder /tmp/adom-chipsmith-cache/ |
| Error toasts mentioning a file | that exact file |
CLI-reported paths (adom-chipsmith sign-off …) |
every emitted path is a clickable terminal link |
A small "📂 reveal" icon button (with an explanatory tooltip) sits to the right of every path. Right-click the path text → context menu with "Reveal in Explorer / Copy path / Open in editor / Show in datasheet (if PDF)".
CLI parity: adom-chipsmith reveal <chip-or-file> — opens the explorer sidebar with the requested file highlighted, so AI sessions can guide a user to the artifact without copy-pasting paths.
UI design compliance — follow the canonical Adom skills
Every UI surface in chipsmith follows the canonical Adom design system. No bespoke visuals. The relevant skills (read each before writing the corresponding subsystem):
adom-app-header— single 44px header row with brand block + subject + actions. Hover-popover withOpen wiki ↗+GitHub ↗links (verified end-to-end at publish time per the skill's verification snippet). Familjen Grotesk for the brand and section headers, JetBrains Mono for numeric data, Satoshi for body. Adom teal#00b8b1accent. Required@font-faceloaders at the top of the stylesheet.human-ui-patterns— every interactive element gets a tooltip. 600 ms reveal delay on hover (intentional hovers only, not fly-bys). Z-index 99999 for tooltips. Never ALL CAPS. Click previews. Draggable / collapsible / dismissible HUDs (the sign-off HUD and hover-inspect HUD honor this). Group toggles for long lists (the object hierarchy honors this for SMD pads / heatsink vias / solder-jet domes). Multi-unit displays (mm primary + mils secondary; per the EE skill rule that mils is always the secondary, never None). Feedback for every action. AI-drivability — every UI verb has an HTTP and JS shadow.Icons sub-rule (load-bearing) — read
playbooks/icons-and-displays.md§4b before writing any new button. Never use Unicode emoji (📏 🔍 🎬 ⎚ 🔧 …) as UI icons. Emoji render multi-color (Twemoji / Apple Color / Noto) and mix miserably next to hand-drawn SVG. Every chipsmith icon is a monochromecurrentColorSVG atviewBox="0 0 24 24", custom-drawn for the specific concept (folder for "reveal", open-book for "open datasheet viewer", calipers for measurements, magnifier-with-data-dot for inspect, etc.). MDI is the fallback only for fully-generic things (eye, cog). Pick one stroke style per surface (1.5–2 px) and stick to it. If you find yourself reaching for📂or📖in a JSX/HTML string, stop — add the SVG to theICONSmap inchipsmith-overlay.jsand usesvgIcon('name')instead.electrical-engineering— package family table, ref-designator conventions, mm + MILS primary/secondary, JLCPCB DRC defaults, BOM column ordering, pin-1 silkscreen marker conventions.adom-cli-design— every CLI subcommand printsOK:/ERROR:/HINT:lines for human and AI consumers, plus a JSON companion line on--json. Default port + cleanhealth/status/start/stoptriple.installsubcommand deploys SKILL.md.measure-tool-design— Fusion 360-style smart picking (vertex > edge > face > body within 8 px), mm primary + mils secondary, 0.01 / 0.001 / 0.0001 mm precision toggle, AI hooks (measure(nameA, nameB)).3d-viewer-design— section planes, view cube, named views, projection toggle, ground plane, pin-1 visual conventions.adom-vscode—adom-vscode reveal <path>for the file-explorer reveal capability.pup— pup-driven testing patterns for the verification plan.tool-publisher— wiki-first publish flow with auto-discovery metadata.
The sign-off HUD, hover-inspect HUD, object hierarchy, sidebar tabs, sign-off button, and every input field MUST visually rhyme with adom-step / video-post / adom-tts / chip-fetcher when the four are placed side-by-side. If they don't rhyme, the skill needs an update or chipsmith needs a follow-up.
Object hierarchy (left sidebar — always visible)
The left sidebar renders a rich, deeply-detailed component tree of EVERY object in the scene — STEP-derived solids AND every overlay layer chipsmith adds (footprint pads, faux PCB, solder-jet domes, KiCad ghost overlay, pin-1 marker). Each row carries the per-object metadata up-front so the user can scan the whole chip without clicking.
adom-chipsmith
├─ ⬛ Body 4.40×5.40×1.50 mm matte black
├─ ⭐ Pin-1 indicator dimple Ø 0.40×0.08mm corner NE conf high source mfr
├─ 🟧 Exposed pad / EP (1)
│ └─ ep_thermal 3.20×3.20 mm area 10.24 mm² Cu (gold)
├─ 🟦 SMD pads (16)
│ ├─ pad_A1 ⭐ pin-1 0.30×0.45 mm c=( 1.95, 2.45) Cu →fp 1 Δ 0.012mm jets 4
│ ├─ pad_A2 0.30×0.45 mm c=( 1.45, 2.45) Cu →fp 2 Δ 0.008mm jets 4
│ ├─ pad_A3 0.30×0.45 mm c=( 0.95, 2.45) Cu →fp 3 Δ 0.014mm jets 4
│ └─ … (13 more)
├─ 🟪 SMD legs (0)
├─ ⚪ BGA balls (0)
├─ 🔻 Through-hole pins (0)
├─ 🔩 Reinforcement tabs (0)
│
├─ 📐 Faux PCB (overlay)
│ ├─ FR4 plate 15.0×17.0×1.6 mm translucent green
│ ├─ Plated holes (PTH) 0
│ ├─ Unplated holes (NPTH) 0
│ ├─ Plated slots 0
│ └─ Unplated slots 0
│
├─ 🟩 Footprint overlay (16 pads + 1 EP)
│ ├─ fp pad 1 (A1) ⭐ silk-marked rect 0.30×0.45 F.Cu+F.Mask+F.Paste →chip pad_A1
│ ├─ fp pad 2 rect 0.30×0.45 F.Cu+F.Mask+F.Paste →chip pad_A2
│ ├─ … (14 more)
│ └─ fp ep rect 3.20×3.20 F.Cu+F.Mask+F.Paste →chip ep_thermal
│
├─ 👻 KiCad stock overlay (ghost) Package_LGA/LGA-16_4.4x5.4mm_Pitch0.5mm
│ ├─ kicad pad 1 (A1) max XY Δ vs mfr: 0.018 mm
│ ├─ kicad pad 2 max XY Δ vs mfr: 0.012 mm
│ └─ … (15 more)
│
├─ 💧 Solder-jet (64 domes total) 0.300mm Ø each intra-pad ≥0.10 inter-pad ≥0.30
│ ├─ pad_A1: 4 domes c-c 0.10mm vol Σ 56.5 nL
│ ├─ pad_A2: 4 domes c-c 0.10mm vol Σ 56.5 nL
│ ├─ … (14 more)
│ └─ ep_thermal: 36 domes c-c 0.10mm vol Σ 508.7 nL
│
├─ 🏷 Laser etch (optional) "VL53L8CX" 3.0×0.6mm depth 0.05mm centered v1.1
│
├─ 🔩 Heatsink vias (9, auto-generated) 0.30mm drill 1.2mm pitch under ep_thermal
│ ├─ via 1 (-1.2, -1.2) plated PTH 0.30 drill / 0.60 outer Cu
│ ├─ via 2 ( 0.0, -1.2) plated PTH 0.30 drill / 0.60 outer Cu
│ ├─ via 3 ( 1.2, -1.2) plated PTH 0.30 drill / 0.60 outer Cu
│ ├─ via 4 (-1.2, 0.0) plated PTH 0.30 drill / 0.60 outer Cu
│ ├─ via 5 ( 0.0, 0.0) plated PTH 0.30 drill / 0.60 outer Cu
│ ├─ via 6 ( 1.2, 0.0) plated PTH 0.30 drill / 0.60 outer Cu
│ ├─ via 7 (-1.2, 1.2) plated PTH 0.30 drill / 0.60 outer Cu
│ ├─ via 8 ( 0.0, 1.2) plated PTH 0.30 drill / 0.60 outer Cu
│ └─ via 9 ( 1.2, 1.2) plated PTH 0.30 drill / 0.60 outer Cu
│
└─ 🟢 Sign-off
├─ Source VL53L8CX.step sha256 a1b2c3…
├─ Spec 16 pads + EP, lead_with_ep
├─ Measurements ✓ matched (no rescale) 4.398×5.401×1.503 mm
├─ Detection ✓ 17 / 17 features (Mode 0)
├─ Footprint ✓ all pads inside ±0.04 mm
├─ Pin-1 ✓ chip NE = fp A1 (agree)
└─ Status Draft → [ Sign off ]
Per-row metadata shown inline
Every row carries an at-a-glance summary on the row itself (size + position + material + the most relevant cross-reference). Each row is also clickable / hoverable / right-clickable:
- Click → isolate this object (everything else fades to ~10% opacity).
- Ctrl+Click → multi-select (compound isolate).
- Hover → highlight in 3D + show a richer Properties popover with the FULL metadata blob:
- For a detected pad: role, structural_type, confidence, detected_via, material name + RGB, parent PRODUCT name in original STEP, surface-type breakdown ("1 plane top, 4 planes side"), bbox in mm (x,y,z), center coords (mm) relative to chip origin, distance to projected footprint pad center, mapped footprint pad ID, mapped KiCad-stock pad ID + delta, jet count + jet positions.
- For pin-1 indicator: position (x,y,z relative to body center), shape (cylinder/cone/sphere), diameter, height, body-corner classification (NE/NW/SE/SW), confidence + reason ("found PRODUCT named 'pin1_indicator'"), cross-reference to footprint silk marker.
- For a through-hole pin: leg length below body, plated-through hole match in
.kicad_mod, ≥1.6mm gate result. - For a footprint pad: pad number, pad name, pad shape, pad size, position, layer assignments (
F.Cu,F.Mask,F.Paste,B.Cu, etc.), drill (for THT pads), net name (if assigned in.kicad_mod), mapped chip feature, raycast Δ. - For a faux PCB plate: dimensions, FR4 thickness assumption (1.6mm by default; configurable), drill count, drilled-pad list.
- For solder-jet: dome count, drop diameter, intra-pad spacing, inter-pad spacing, total dispensed volume in nL, per-dome XY coords on the pad's local frame.
- For KiCad ghost overlay: KiCad library path, footprint name, per-pad delta vs manufacturer, max delta.
- Right-click → context menu: Isolate / Hide Others / Hide / Show / Zoom To / Copy as JSON (full metadata) / Copy Name / Show in Datasheet (jumps to the datasheet PDF page that mentions this pin).
Per-row badges
Compact icons up-front signal status without clicking:
| Badge | Meaning |
|---|---|
| ⭐ | This is pin 1 (chip-side or fp-side) |
| ✓ | Verified — passes its tab's check |
| ◐ | Partial — passes some checks, others pending |
| ✗ | Fails the gate |
| 🔒 | Locked by sign-off (loaded from baked STEP, immutable) |
| 🆕 | Synthesized (Mode F face-merged or chipsmith-added overlay) |
| ↻ | Re-detected after Y-up auto-rotate |
Filters + group toggles
Top-of-tree search box (debounced 80ms): dim non-matching rows, auto-expand ancestors. Plus per-group master visibility toggle (per the group toggles memory) — collapse the SMD pads node and toggle all 16 visible/hidden in one click; same for solder-jet, footprint overlay, KiCad ghost, faux PCB.
Live updates
The hierarchy is the debugging surface during detection. As Mode 0 → Mode 1 → Mode 2 runs, rows appear in real-time with an animated "✨ detecting…" spinner that resolves to ✓ + bbox + role as each feature lands. Rebuild on every state change; never let it go stale relative to what's on canvas.
UI — 8-tab sidebar (left-to-right validation flow)
Status pill on each tab (●/◐/○/✗). User proceeds left-to-right.
Overlay layers are first-class Outline citizens (load-bearing)
Every 3D overlay chipsmith adds to the scene MUST appear as a real row (or section) in the left-side Outline panel — not just as detached Babylon meshes the user can't see in the tree. This applies to footprint pad overlays (one section per source), the 1.6mm faux PCB plate, heatsink vias, solder-jet domes, KiCad-stock ghost, laser-etch text, and the user-placed pin-1 indicator.
Each Outline entry carries the same affordances as a regular GLB-derived node:
- Eye toggle — flips overlay visibility as a unit (no half-states where the row says "visible" but the meshes are gone).
- Click-to-isolate — fades the rest of the scene to ~10% opacity so the user can study the overlay alone.
- Right-click context menu — Isolate / Hide Others / Hide / Show
/ Zoom To / Copy as JSON / Copy Name (matches the regular tree-row
menu — see
human-ui-patterns§1d). - Searchable — the panel's debounced search filter dims non-matching overlay rows the same way it dims non-matching GLB-derived rows.
- Tooltip / hover-inspect — shows the overlay's metadata (source, pad count, FR4 thickness, via grid dimensions, etc.) in the same hover popover style.
- Per-section group toggle when the overlay is a multi-row collection (e.g. 16 BGA solder-jet domes get a "Solder-jet" parent with a master-eye that toggles all 16 at once, plus per-row eyes per the group-toggles memory).
Implementation contract: window.adomStep.addOverlay({ group, meshes, label, parent?, kind? }) registers the meshes AND creates a
matching outline entry in one call. Internally the entry is pushed
to the assembly tree with synthetic ids in the overlay-id range
(< -10000 to never collide with GLB-derived ids). On chip-reload
(clearScene), every overlay group is disposed AND every overlay
entry is removed from the tree.
Why this rule exists: an overlay-only mesh that's invisible in the Outline is a power-user trap — the user can't tell what was added, can't toggle it, can't right-click it, can't search for it. Every single object in the scene needs to be discoverable + controllable from the Outline. No exceptions.
Right-pane shape: floating draggable HUD, not a docked column
The 8-tab pane is a floating HUD, not a fixed-width docked column. It floats over the canvas (transparent / translucent bg with text-shadow for legibility, same design language as the Outline) and must be positioned so it never covers the ViewCube (top-right of canvas, viewport (0.85, 0.85, 0.15, 0.15)). Default landing position: ~130 px from the top of the canvas, ~10 px from the right edge — clear of the cube.
The HUD is:
- Draggable — grab anywhere in the tab strip / title area and drag to a new position. Position persists in
localStorage["chipsmith.pane.pos"] = {x, y}so the user's preferred docking spot survives reloads. - Collapsible — chevron at the right of the tab row slides the panel off-screen (translateX) and reveals a slim vertical expand-tab at the canvas's right edge. Keyboard
Ptoggles. State persists inlocalStorage["chipsmith.pane.collapsed"]. - Resizable (v1.1) — drag the bottom-right corner to resize. Width persists similarly. v1.0 ships at fixed 360×(canvas-130).
- Pin-1-corner-aware — when the user manually drags the HUD over the ViewCube area, the cube auto-shifts left to make room (per the same
body.cs-collapsedstyle used to nudge the toolbar). v1.0 just blocks the default landing position from overlapping the cube; auto-shift on drag is v1.1.
Why floating + draggable instead of a docked column:
- The user can move the HUD off the part of the chip they're inspecting.
- The 3D scene gets the entire canvas — the HUD never claws back real estate by docking.
- EE workflows often have the user comparing left-side outline rows ↔ right-side HUD tabs; a floating HUD lets them dock it wherever the comparison flow feels natural.
- Matches Fusion 360's "floating dialog" affordance for non-modal panels (Browser, ContextualEnvironment, Properties).
Tab 1 — Source
Manifest + provenance row. MPN, package family, declared pin_count, declared structural_type, declared body_dims_mm. Datasheet PDF embedded inline (PDF.js lazy-loaded). Tab is red until pin_count, structural_type, AND body_dims_mm are present.
Tab 2 — Measurements (new — was missing from prior draft)
The measurement-validation gate. Reads:
- Declared
body_dims_mmfrom the manifest (e.g.,[4.4, 5.4, 1.5]) - Detected body bbox from
step-meta.json(in declared STEP units), with the unit string from the STEP header (millimetre,metre,inch,centimetre)
Then:
- Direct compare — convert detected to mm using the STEP's declared unit, compare to manifest. If within ±5% on every axis, pass.
- Mismatch → try common rescales in priority order, picking the conversion factor that lands closest:
÷25.4(STEP says mm but is really inches — common when STEP came from a US-mfr CAD export that lied about units)÷1000(STEP says mm but is really metres — common with downstream Fusion exports)×1000(STEP says metres but is really mm — rarer but real)×25.4(STEP says inches but is really mm)÷10(STEP says mm but is really cm)
- If a rescale gets within ±5% on all three axes → flag "auto-rescaled by ÷25.4 (file was inches not mm)" in the HUD, apply the scale to BOTH the rendered GLB and every detected feature, mark Tab 2 amber (verified-after-rescale).
- If no rescale lands within tolerance → fail, surface "detected 110×135×38 mm vs declared 4.4×5.4×1.5 mm — no rescale matches" and block sign-off. User can manually pick a scale or reject the STEP.
The applied scale is recorded in <mpn>.chipsmith.json so re-loads of the same chip skip rescale negotiation. The scale also propagates to leg-length checks (a leg is "≥1.6mm" only after rescale).
Tab 3 — Detection
Runs Mode 0 → Mode 1 → Mode 2 in order, stopping when ≥(declared pin count) features have been classified.
- Mode 0 — Named PRODUCT walk (the canonical path). Walk
step-meta.json's PRODUCT_DEFINITION tree, look for names matchinglead_*|pin_*|pad_*|ball_*|ep|exposed_pad|thermal_pad|body|pin1*|indicator*. Most modern manufacturer STEPs name these. If ≥70% of declared pins matched by name → done. (This is what chipfit'sfeedback_chipfit_use_named_scene_graph_firstmemory entry advocated; we ship it for real here.) - Mode 1 — Surface-type cluster (the killer feature). Per face in
step-meta.json:- CYLINDER + small radius (≤1mm) + dome top → BGA ball candidate. Cluster by neighbour adjacency in the same z-band → one cluster per ball.
- PLANE + horizontal + at z≈seat_plane → SMD pad candidate. Cluster coplanar planes → one per pad.
- PLANE + vertical + tall (≥1.6mm) + thin → through-hole pin / reinforcement tab candidate.
- PLANE + horizontal + large + at center → exposed pad / thermal pad.
- CONICAL or CYLINDER on top surface, small (≤1mm radius), elevated → pin-1 indicator candidate. This works because the STEP told us each face's exact surface type — the GLB couldn't.
- Mode 2 — Face-merge fallback — for STEPs whose product hierarchy is flat (one solid per chip, no sub-products) AND whose face surface types don't disambiguate: union-find on (material, mutual adjacency by ≤50µm gap). Only invoked when Modes 0 & 1 cover <50%.
After detection, the sidebar lists every detected feature with role + confidence + bbox + click-to-isolate.
Pin-reveal strobe (the magical moment)
The instant detection succeeds, play a celebratory reveal animation so the user can SEE chipsmith got it right — instead of just trusting the number in the sidebar:
- Fade everything except detected pins/pads/balls/legs to ~5% opacity (body + EP + indicator + viewer helpers).
- Sequentially highlight each detected feature for 50ms in detected order — bright accent emissive, brief overshoot in scale (1.0 → 1.15 → 1.0 with ease-out). Index tag floats above each one as it strobes ("1", "2", … "48").
- After the last one, hold the full set lit for ~600ms with a count badge: "48 / 48 pins ✓".
- Fade body + EP back to full opacity over 400ms; pins remain accented for 1s before settling.
Math: 48 pins × 50ms ≈ 2.4s sequence + 1s tail ≈ 3.4s total — long enough to be satisfying, short enough not to annoy on every reload. For high-count chips (BGA ≥200 balls), batch into groups so total stays under ~4s while preserving the strobe feel.
Replayable via a ↻ replay reveal button on the Detection tab; auto-plays once on first successful detection per session. Can be disabled by user setting chipsmith.reveal_animation = false in localStorage for power users.
The strobe is also the "oh, awesome, he really did figure out there's 48 pins" moment a screen recording wants to capture — so an explicit window.adomChipsmith.replayReveal() JS hook lets demo scripts trigger it on cue.
Y-up vs Z-up auto-retry
Re-orient logic: if first detection pass returns <50% of declared pin count, rotate the root 90° about X and re-run. Take the better result. HUD surfaces "auto-rotated 90° (file was Y-up)."
Advanced manual orient & translate (hidden by default)
Some manufacturer STEPs are so weirdly authored that auto Y-up retry isn't enough — the chip might be sitting on its side, rotated 45°, mirrored, or with the body's origin offset from where it should be (some manufacturers place the origin at the corner instead of the body center, others put it at a random tooling reference point hundreds of millimeters away from the body itself).
These are rare edge cases, so the controls live behind a collapsed Advanced… disclosure at the bottom of Tab 3. The disclosure auto-expands when chipsmith detects an obvious fault — measurement check fails on every rescale attempt + detection coverage <30% even after Y-up retry — so users who need it find it without spelunking, but everyone else never sees it.
Inside the disclosure:
Advanced — manual orient & translate
Rotate Translate (mm relative to current origin)
┌────────┐ ┌────────┐ X [ 0.000 ] ◀ ▶
│ X 90°↺ │ │ X 90°↻ │ Y [ 0.000 ] ◀ ▶
└────────┘ └────────┘ Z [ 0.000 ] ◀ ▶
┌────────┐ ┌────────┐
│ Y 90°↺ │ │ Y 90°↻ │ Center on body bbox [⟲ snap]
└────────┘ └────────┘ Drop to z=0 [⟲ seat]
┌────────┐ ┌────────┐
│ Z 90°↺ │ │ Z 90°↻ │
└────────┘ └────────┘ Reset all transforms [↺]
Mirror [X] [Y] [Z]
- 90° rotation buttons about X, Y, Z (CCW + CW) — six buttons, each one triggers an instant re-render and re-runs detection in the new pose. Combinable into any of the 24 axis-aligned poses.
- Translate inputs for X / Y / Z in mm, with click-and-hold ◀ ▶ steppers (0.1 mm per click, hold for 1 mm/s drift). Supports negative values.
- Quick-snaps — "Center on body bbox" zeros the X/Y of the body's bbox center; "Drop to z=0" puts the body's bottom face at z=0 (the seat plane). Together they fix the two most common manufacturer-origin pathologies.
- Mirror toggles per-axis mirror (rare, but some tooling exports come out flipped).
- Reset all restores the as-loaded pose.
After every change, detection re-runs on the new pose and the sign-off HUD updates live. The applied composite transform — auto-rescale × auto-rotation × manual-rotation × manual-translation × manual-mirror — is recorded in the manifest as a single 4×4 matrix:
"applied_transform": {
"scale": 1.0,
"rotation_quat": [0, 0, 0, 1],
"translation_mm": [0, 0, 0],
"mirror_axes": [],
"matrix4x4": [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]
}
… and baked into .chipsmith.step via OCCT XCAF on sign-off, so the chip permanently sits in the correct pose for every downstream tool. The original STEP is preserved untouched — chipsmith never edits source files in place.
The manual-transform sign-off HUD row reads ─ none when no manual transform was applied (the common case), ◐ manual rotate + translate applied when the user reached for the advanced controls. Hovering shows the matrix.
A short "Why am I doing this?" note at the top of the disclosure explains the situation in plain language ("This chip's STEP file appears to be authored with the body lying on its side and the origin offset 47 mm from the body. You can fix it manually below — the rotation and shift will be saved as part of the sign-off so future loads are correct.") — so users who hit this rare path aren't disoriented.
Industry-standard pin numbering
Detection finds geometric features; numbering assigns each feature its canonical pin designator per the chip's package family. This matters: an EE who sees "pad_3" expects exactly the pin that the datasheet calls pin 3, exactly where every other tool (KiCad, Altium, Fusion 360, manufacturer reference designs) calls it pin 3. Wrong numbering is silently catastrophic.
Numbering is driven by package_family from the manifest (so chipsmith doesn't have to guess; the user already declared it). Numbering starts from the detected pin-1 location and walks the perimeter / grid in the canonical direction:
| Package family | Numbering convention | Walk pattern |
|---|---|---|
| SOIC / SOP / SSOP / TSSOP / DIP / PDIP (dual-row) | Pin 1 at one end of the left row (datasheet-marked end). Numbers run down the left row, then continue up the right row, ending at pin N opposite pin 1. So an 8-pin SOIC: 1-2-3-4 down the left, 5-6-7-8 up the right (pin 8 is top-right, opposite pin 1 top-left). | Left column top→bottom, then right column bottom→top |
| QFN / DFN / LGA-perimeter / QFP / LQFP / TQFP (4-side perimeter) | Pin 1 at the dot/dimple corner (typically top-left when viewed from the top). Numbers run counter-clockwise around the perimeter. | Pin 1 corner → down the left side → across the bottom → up the right side → across the top, ending where it started |
| BGA / LGA-grid | Letter-number grid (A1, A2, … B1, B2, …). Letters index rows (top to bottom), numbers index columns (left to right). Letters skip I, O, Q, S, X, Z to avoid confusion with 1, 0, 5, etc. So a 100-ball BGA goes A1…A10, B1…B10, C1…C10, … (no I row), J1…J10, K1…K10, … |
Row-major: assign each ball to its (row_letter, col_number) by clustering XY positions into a grid |
| SOT-23 (3-pin) | Pin 1 top-left, pin 2 bottom (single-pin side), pin 3 top-right. | Datasheet-canonical |
| SOT-23-5 / SOT-23-6 | Three pins along the long bottom (1, 2, 3 left-to-right) + two/three pins along the long top (5, 4 right-to-left for SOT-23-5; 6, 5, 4 for SOT-23-6). | Bottom row left→right, then top row right→left |
| TO-220 / TO-247 (through-hole tab) | 3 legs numbered 1, 2, 3 left-to-right when viewed from the labeled / printed face. | Datasheet-canonical |
| DPAK / D2PAK (SMD tab) | Same as TO-220 — 3 legs left-to-right when viewed from the top with the tab pointing up. | Datasheet-canonical |
| USB-C receptacle | Spec-defined: A1-A12 on one row, B1-B12 on the other, plus mechanical reinforcement tabs on the corners (not numbered, tagged tab_*). |
Per USB-C spec (USB-C R2.1 §3.2) |
| DIP/SIP through-hole headers (e.g., WAGO 2601) | Pin 1 at the manufacturer-marked corner; numbers run sequentially along the row(s). Single-row: 1→N. Dual-row: same as DIP (down the left, up the right). | Manifest-declared per part |
| Connectors (RJ45, DC-barrel, screw-terminal) | Per-part — fall back to manifest's declared pin map. | pin_map field in manifest |
| Passives (R / C / L / 0402-1206) | Pins 1 and 2; pin 1 is arbitrary unless the part is polarized (electrolytic cap, LED, diode), in which case pin 1 is the marked / longer / cathode end per datasheet convention. | 2 pins only |
Algorithm per family is implemented in src/pin_numbering.rs. Each family is a small function that takes the detected feature centroids + the pin-1 corner and returns Vec<(feature_id, pin_designator)>. Roles get rewritten from pad_<n> to pad_A1, pad_3, lead_5, ball_C7, etc.
When package_family is unknown / unsupported, chipsmith falls back to counter-clockwise from pin-1 with sequential integers, surfaces an amber warning in the HUD ("falling back to default numbering — declare package_family to use the canonical pattern"), and lets sign-off proceed only after the user manually confirms the numbering by clicking each pin in order to verify.
Pin numbering during the reveal strobe
The pin-reveal strobe animation now shows the pin designator as it strobes each feature. So as the user watches, they see:
⭐ pin 1 (A1) … pin 2 (A2) … pin 3 (A3) … pin 4 (A4) … …
The designator floats above the strobing feature for the duration of its 50ms highlight, then transitions to the next. For BGA, the designator includes the letter-number cell (e.g., A1, B5). For perimeter SOIC/QFN/QFP, the designator is just the number with the side abbreviated as a small superscript: 1ᴸ, 4ᴮ, 8ᵀ (left/bottom/right/top). For TO-220, the leg name (G, D, S for a MOSFET when datasheet-known via the manifest's pin_map).
Because the designator IS the trust signal — the user instantly sees "yes, that's the order I expect a SOIC-8 to walk" — this also doubles as a numbering verification step. If chipsmith picked the wrong corner for pin 1, the strobe sequence will visibly count wrong (e.g., counter-clockwise instead of clockwise, or starting from the bottom-right instead of top-left), and the user flags it before sign-off.
A "Replay reveal with numbers" button in the Detection tab re-runs the animation any time. Also exposed via window.adomChipsmith.replayReveal({showNumbers: true}).
Semantic role tagging
Every detected feature gets metadata.chipsmith populated on the corresponding GLB node:
{
"role": "lead_1" | "pad_A1" | "ball_B7" | "ep" | "body" | "pin1_indicator" | "tab_left" | …,
"structural_type": "smd_leg" | "smd_pad" | "ball" | "thru_hole_pin" | "thru_hole_tab" | "ep_thermal" | "reinforcement_tab",
"confidence": "high" | "ambiguous" | "assumed",
"detected_via": "named" | "surface_type" | "face_merge"
}
Baked into the GLB on sign-off so re-loads skip detection.
Tab 4 — Footprint
Loads .kicad_mod, parses pads (vendored mini parser, no shelling to KiCad), renders each as thin extruded shape at z=0. Two layout modes (side-by-side / aligned). Bbox-fit check raycasts up from each footprint pad's bbox; every detected pin centroid must fall inside its projected pad. Tolerance = expected_pitch_mm × 0.4. Failures highlighted red. 1.6mm PCB plate — translucent green FR4 plate, CSG-drilled with the four canonical hole/slot types found in the footprint:
| Hole type | Geometry | Copper ring? | Visual cue | Use case |
|---|---|---|---|---|
| Plated round hole (PTH) | Cylinder | yes — annular ring | drilled hole + amber/gold inner-bore plating ring + same-color top/bottom annular pad | DIP / PDIP through-hole pins, electrical signals |
| Unplated round hole (NPTH) | Cylinder | no | drilled hole, bare green FR4 walls | Mounting holes, M2/M3 screw clearance |
| Plated slot (PT slot) | Capsule (rounded rectangle) | yes — capsule plating ring | slot cut + amber plating around inside walls + plated annular pad | USB-C reinforcement tabs (the strong solder tab anchors), DC barrel jack tabs |
| Unplated slot (NPT slot) | Capsule | no | slot cut, bare green walls | Mechanical alignment slots, key-cutout for connectors |
Rendering: union the body of the plate, then CSG-subtract every hole/slot footprint in one pass; for plated holes/slots, also union a thin annular-ring shell on top + bottom faces (Cu-tone amber 0xC79A4F) and a thin inner-bore cylinder/capsule shell (same Cu-tone) so the copper plating reads visibly when the user zooms in. This matters because USB-C is hybrid_smd_thru and its reinforcement tabs are plated slots, not round holes — getting the slot geometry right is the difference between "USB-C will solder mechanically" and "USB-C will fall off the board."
The footprint parser tags each pad with its hole type (drill.shape: oval|circle, drill.size: [w,h] for slots, pad.layers: ["F.Cu","B.Cu","*.Cu"] to detect plating). Chipsmith reads these and chooses the correct CSG primitive + copper ring per hole. For pads with no drill.shape declared but drill.size is set with width != height, chipsmith infers a slot. Stock-library overlay (v1.1) — fetch the authoritative footprint + STEP for this MPN from the internal stock library (service-kicad under the hood), render as a translucent ghost in a different tint, surface per-pad delta. The user just sees "Stock library footprint" — no KiCad branding in the label.
Tab 5 — Pin-1
Two columns:
- Chip-side: search
step-meta.jsonfor a PRODUCT namedpin1_indicator|pin1|indicator|polarity_mark. If found → isolate + circle. Otherwise scan top surface for CYLINDER or CONICAL features ≥0.1mm above the body, ≤1mm radius, scored by proximity to a body corner. - Footprint-side: parse
F.SilkSpolygons; look for circles, triangles, dots, "1" text, asymmetric pad shape (rounded square, polarity stripe).
Cross-reference: both must agree on which pad is pin 1. If they disagree, banner shows the mismatch. User picks truth or marks override.
"Place a pin-1 indicator" — for chips that don't have one
When chip-side detection finds nothing (the manufacturer didn't include a 3D pin-1 mark — common with bargain QFN STEPs and some BGA models), the tab shows a Place pin-1 indicator button. Three placement modes:
- Use footprint marker (one-click default) — chipsmith projects the silkscreen pin-1 marker's pad position up to the chip's top surface and places the indicator at the body corner closest to that pad. Chip and footprint agree by construction.
- Click to place — user clicks anywhere on the body's top face; chipsmith snaps to the nearest corner (or uses the click point if
Alt-held). Live preview while hovering. - Pick a corner — quick-buttons for NE / NW / SE / SW with a small thumbnail showing the resulting position.
Shape options (radio):
- Dimple (default) — recessed cylinder, Ø 0.40 mm × 0.05 mm deep, slightly darker tint.
- Raised dot — protruding cylinder, Ø 0.40 mm × 0.05 mm tall, body color.
- Corner chamfer — angled cut on the body's top corner edge.
Per the new pin1-indicator skill, the default size + offset varies by package family (a tiny QFN-16 wants a 0.30 mm dimple inset 0.40 mm; a fat LQFP-128 wants 0.60 mm inset 1.0 mm). Defaults are derived from package_family + body_dims_mm.
Synthesized indicators are tagged pin1_indicator_user_placed (vs pin1_indicator for manufacturer-originals) so downstream tools can distinguish — but for sign-off purposes, both pass the same gate. The synthesized geometry is baked into .chipsmith.step via OCCT on sign-off, surviving every future load.
A small undo button removes the placed indicator if the user wants to retry.
(New skill) — gallia/skills/pin1-indicator/SKILL.md. Per package family: where pin-1 normally lives, what the dot shape is, common variants (dimple vs raised dot vs corner chamfer vs molded notch on SOIC), how to distinguish a real 3D pin-1 dot from a decorative texture map. Plus silkscreen conventions: dot, triangle, "1", pad-shape difference, polarity stripe.
Hover-inspect HUD (cursor-following)
A small floating card that follows the cursor whenever it's over the 3D canvas. Names what the user is looking at, in plain words, before they have to click anything. The user moves the mouse, the HUD updates at 60fps, no tab switching required.
Layout
┌────────────────────────────────┐
│ ⭐ pin-1 indicator │
│ kind cylinder · top │
│ size Ø 0.40 × 0.08 mm │
│ corner NE │
│ confidence high │
│ source named PRODUCT │
└────────────────────────────────┘
↗ cursor
Auto-positioned 12px down-right of cursor; flips left/up when near canvas edges so it never clips.
Kind labels by category
The first line is a plain-language kind label so a non-AI user can instantly read what they're hovering. Color-coded by category for at-a-glance scanning:
| Object kind | Label shown | Tint |
|---|---|---|
body |
"chip body" | ⬛ |
pin1_indicator |
"pin-1 indicator" ⭐ | ⭐ amber |
smd_pad (pad_A1, …) |
"SMD pad — A1" | 🟦 teal |
smd_leg (lead_3, …) |
"SMD leg — pin 3" | 🟪 violet |
ball (ball_B7, …) |
"BGA ball — B7" | ⚪ silver |
thru_hole_pin |
"through-hole pin — 4" | 🔻 red |
thru_hole_tab |
"reinforcement tab" | 🔩 grey |
ep_thermal |
"exposed pad / thermal" | 🟧 orange |
| Footprint pad (overlay) | "footprint pad — A1" | 🟩 green |
| Footprint silk marker | "silkscreen pin-1 marker" | white |
| Faux PCB plate | "FR4 board (1.6mm faux)" | green-grey |
| Plated PTH (faux PCB) | "plated through-hole" | amber |
| NPTH (faux PCB) | "unplated mounting hole" | grey |
| Plated slot (faux PCB) | "plated slot — reinforcement" | amber |
| NPT slot (faux PCB) | "unplated slot" | grey |
| Solder-jet dome | "solder-jet dome — 0.30 mm" | 💧 light blue |
| Heatsink via | "heatsink via — 0.30 drill" | 🔩 cu-tone |
| Stock-library ghost pad | "stock library pad (ghost)" | translucent |
| Laser-etch text | "laser-etched MPN" | dark slate |
| User-placed pin-1 indicator | "pin-1 indicator (added by you)" 🆕 | ⭐ amber |
Categories are derived from metadata.chipsmith.role and metadata.chipsmith.structural_type baked into the GLB on detection / sign-off; non-chipsmith objects (sky-box, axis cube, gizmos, ground plane, anything in the deny-list from adom-step's inspector) suppress the HUD.
Body content
The lines after the kind label are picked from the object's metadata, prioritized by what's most relevant for that category. Roughly:
- Pads / pins / balls / legs: size, center coords (mm relative to chip origin), material, mapped footprint pad ID + Δ, jet count, parent PRODUCT name.
- Pin-1 indicator: shape, dimensions, corner, confidence + reason.
- Through-hole pin: leg length below body, ≥1.6mm gate result, plated-or-not.
- Footprint pad: pad number, shape, size, layers, drill (if any), mapped chip feature.
- Faux PCB hole: hole type, drill diameter (or slot dimensions), plated/unplated, parent footprint pad.
- Solder-jet dome: drop diameter, position on pad's local frame, parent pad role.
- Heatsink via: drill, outer Cu, position relative to EP center, grid index (e.g.,
5 of 9). - Body: bbox dimensions, volume, material name + RGB swatch, "click to isolate."
Three lines max per HUD by default — the user can pin the HUD (click → docks; second click → undocks) to see the full metadata blob in expanded form.
Behaviour
- Throttled to one update per render frame (60fps cap); raycast under the cursor uses Babylon's
scene.pickfiltered to chipsmith-tagged meshes only. - Suppressed during camera drag (orbit / pan / zoom) so it doesn't strobe while the user is just looking around.
Eschides it permanently for the session; toggle viai(or right-side toolbarinspectbutton).- AI-drivable:
window.adomChipsmith.hoverAt({x, y})returns the same payload the HUD shows;window.adomChipsmith.lastHover()returns the most recent. Demo scripts and tests both consume this.
Per-package mode "explainer line"
When a non-trivial detection inference applies — Mode 1 surface-type clustering, Mode 2 face-merge, post-rescale, post-Y-up-rotate — the HUD adds a small italic "how we know" line:
detected by clustering 14 cylinders at z=0 (Mode 1) re-detected after auto-rotating 90° about X (file was Y-up) synthesized from 12 orphan faces sharing material (Mode 2 face-merge)
So the user understands not just what this is, but why chipsmith calls it that. Trust comes from showing the work.
Sign-off HUD (always visible)
A floating verification card pinned to the top-right of the canvas, visible from every tab. The user can see at a glance which gates are passed, which are pending, and which have failed — without leaving the tab they're on. Each row maps 1:1 to a gate and is clickable (click → jump to the relevant tab).
┌─ Sign-off checklist ─────────────────────── 9 / 17 ─────────┐
│ │
│ Inputs │
│ ☑ STEP file loaded VL53L8CX.step │
│ ☑ footprint loaded LGA-16.kicad_mod│
│ ☑ Datasheet loaded VL53L8CX.pdf │
│ ☑ Spec complete pin_count=16… │
│ │
│ STEP integrity │
│ ☑ Measurements match declared Δ < 0.1% │
│ ☑ Orientation Z-up native │
│ │
│ Detection │
│ ☑ Coverage 17 / 17 Mode 0 │
│ ☑ EP detected 1 × ep_thermal │
│ ─ Through-hole legs ≥1.6mm N/A (no THT) │
│ ─ Reinforcement tabs N/A │
│ │
│ Footprint │
│ ☑ Bbox-fit (chip ⊂ fp) max Δ 0.014 mm │
│ ◐ KiCad library cross-check fetching… │
│ │
│ Pin-1 │
│ ☑ Chip-side detected NE corner │
│ ☑ Footprint silk marker detected pad A1 │
│ ☑ Chip ↔ footprint agree pin 1 = A1 │
│ │
│ Manufacturing │
│ ☑ Heatsink vias generated 9 × 0.3mm grid │
│ ─ Laser-etch disabled (opt-in)│
│ ◐ Solder-jet layout legal v1.1 │
│ │
│ ─────────────────────────────────────────────────────────────│
│ ☐ Sign off this chip [ Sign off ▶ ] │
└────────────────────────────────────────────────────────────────┘
Row states
| Glyph | Meaning |
|---|---|
| ☑ | Pass — gate satisfied |
| ☐ | Pending — gate not yet evaluated, or evaluation failed |
| ◐ | Partial / warning — gate runs but with a caveat (e.g., rescale applied, manual override) |
| ─ | Not applicable — manifest declared this gate doesn't apply (e.g., no THT pins → leg-length gate is ─) |
| 🔒 | Locked — chip is signed off; gate is immutable until re-detect |
| ✗ | Failed — gate evaluated and rejected (red) |
Behavior
- Click a row → switches to the tab that owns that gate, scrolls to the relevant control, flashes a brief outline so the user sees what they need to address.
- Hover a row → tooltip shows the underlying numeric reason ("body Δx=0.002, Δy=-0.001, Δz=0.003 — within ±5%").
- Collapse button → top-right corner. Collapsed state is a tiny chip badge
9 / 17 ✓that stays pinned and re-expands on hover. - Drag → the HUD is draggable to any corner; remembers position in
localStorage. - Keyboard →
stoggles collapse,Sjumps focus into the HUD.
Sign-off button
The final row is the Sign off this chip button. It is disabled until every checkbox above it is ☑ or ─ (no ☐, no ◐ on hard gates, no ✗). Hovering the disabled button surfaces a tooltip listing exactly which gates are blocking ("3 gates pending: KiCad cross-check fetching, solder-jet not yet computed, ☐"). Clicking the button when enabled fires the same flow as the Sign-off tab below.
After sign-off, every row flips to 🔒 — the chip is now locked. A "Re-open for editing" link appears at the bottom; clicking it unlocks (with confirmation) and clears the locks so the user can re-validate.
AI-drivability
Every row is exposed via JS: window.adomChipsmith.signoff.gates() returns the structured array; window.adomChipsmith.signoff.gate('pin1_agreement') returns the row state + reason; window.adomChipsmith.signoff.canSignOff() returns boolean + blocking-gate list. Demo scripts and CI both consume this.
Tab 6 — Sign-off
Big green Sign off this chip button. Disabled until ALL of:
- Tab 1 Source: spec complete (
pin_count+structural_type+body_dims_mmpresent) - Tab 2 Measurements: detected body matches declared within ±5% (or post-rescale match flagged)
- Tab 3 Detection: coverage ≥ declared pin count
- Tab 4 Footprint: bbox-fit passes
- Tab 5 Pin-1: chip-side detected ≥ ambiguous, fp-side detected, agreement (or manual override)
On click:
- Bake
<mpn>.chipsmith.step(canonical) — POST toservice-step2glb /bakewith the original STEP + applied scale + applied rotation + per-solid role tags + pin-1 marker. OCCT/XCAF re-emits a STEP that has: LENGTH_UNIT normalized to mm, root rotated upright if Y-up, PRODUCT_DEFINITION names extended with role tags, XCAF named-data attributes per solid carrying structured{role, structural_type, confidence, detected_via}, and a document-levelchipsmith_signoffattribute holding the full sidecar JSON. - Derive
<mpn>.chipsmith.glb(shadow) — POST the baked STEP back through/convertso the GLB matches the canonical STEP exactly (same hierarchy, same materials, same units). Babylon renders this; nothing else reads it. - Write
<mpn>.chipsmith.jsonsidecar (mirror of thechipsmith_signoffattribute embedded in the STEP, surfaced flat for tools that don't link OCCT):{ "mpn": "VL53L8CX", "step_sha256": "…", "step_meta_sha256": "…", "applied_unit_scale": 1.0, "applied_orientation_rotation_deg": 0, "detected_pin_count": 17, "detected_breakdown": { "smd_pad": 16, "ep_thermal": 1 }, "body_dims_mm_detected": [4.398, 5.401, 1.503], "body_dims_mm_declared": [4.4, 5.4, 1.5], "body_dims_match_within_pct": 0.06, "pin1_chip": { "side": "top", "corner": "NE", "node": "pin1_indicator" }, "pin1_footprint": { "pad": "A1", "marker": "silkscreen_circle" }, "pin1_agreement": "agree", "kicad_footprint_diff": { "pads_match": 17, "pads_off_by": 0, "pads_missing": 0, "max_xy_delta_mm": 0.012 }, "thru_hole_leg_lengths_mm": [], "signed_at": "2026-05-05T17:34:21Z", "signed_by": "[email protected]", "chipsmith_version": "0.1.0" } - Hard-confirm dialog: "Sign off VL53L8CX as 16 pads + 1 EP, pin-1 confirmed at NE corner, KiCad-stock matches?" — yes / cancel.
- Update chip-fetcher's
info.jsonwith"chipsmith_signed_at": "…", "chipsmith_step": "<path>", "chipsmith_glb": "<path>".
Until signed off, chip is "draft." Signed-off chips get a green badge in chip-fetcher's library view. Re-opening a signed chip parses the baked STEP's XCAF attributes, skipping detection / rescale / orient negotiation entirely — the sign-off survives.
Downstream contract: every Adom tool that consumes a chip-fetcher entry going forward MUST prefer <mpn>.chipsmith.step over the original <mpn>.step when present. Tools that don't link OCCT can read <mpn>.chipsmith.json instead of round-tripping through STEP. The shadow .chipsmith.glb is for Babylon viewers only; nothing else should read it.
Tab 7 — Heatsink vias (v1.0 — auto-generated under any EP)
For any detected EP / ep_thermal / lead_with_ep, chipsmith auto-generates a square grid of 0.3mm-drill plated through-hole vias under the pad:
- Drill: 0.30 mm
- Outer copper diameter: 0.60 mm (annular ring 0.15 mm)
- Pitch: 1.20 mm (square grid; configurable via spec)
- Edge margin: 0.40 mm from pad edge (configurable via spec)
- Layout: regular
n × ngrid centered on the EP —n = floor((ep_side - 2×margin) / pitch) + 1
For a 3.2×3.2 mm EP that's floor((3.2 - 0.8) / 1.2) + 1 = 3 → 3×3 = 9 vias.
For a 5.0×5.0 mm QFN-48 EP that's floor((5.0 - 0.8) / 1.2) + 1 = 4 → 4×4 = 16 vias.
Each via is placed at the EP's local frame, plated with copper plating + annular pad on top + bottom. The vias are an overlay layer — they're NOT physically on the chip, they're chipsmith's pre-computed gift to the EE for when they design their board around this chip later. Toggleable in the hierarchy. The Sign-off sidecar embeds the via grid (positions + drill + pitch) so a downstream board-design tool can ingest them as a single "place this heatsink-via grid centered under U7" action.
The user's reaction we want: "oh, how nice, the AI gave me some heatsink vias under the EP."
Spec override fields (manifest, optional):
"heatsink_vias": {
"enabled": true,
"drill_mm": 0.30,
"outer_cu_mm": 0.60,
"pitch_mm": 1.20,
"margin_mm": 0.40
}
Defaults applied when the field is absent.
Tab 8 — Solder-jet (v1.1)
300µm half-sphere domes per pad per the solder-jetting skill lines 32, 49, 50, 62-77 (intra-pad ≥100µm edge, inter-pad ≥300µm edge, grid floor-division). Toggleable layer.
Manifest schema — chipsmith-spec.json (or fields inside info.json)
{
"mpn": "VL53L8CX",
"manufacturer": "STMicroelectronics",
"package_family": "LGA-16",
"pin_count": 16,
"structural_type": "lead_with_ep",
"has_ep": true,
"has_thru_hole_pins": false,
"has_reinforcement_tabs": false,
"expected_pitch_mm": 0.5,
"body_dims_mm": [4.4, 5.4, 1.5],
"expected_pin1_kind": "dimple_top_corner",
// Stock-library lookup keys. The KiCad library is footprint-keyed and
// 3D-chip-name-keyed — NOT MPN-keyed. So chipsmith MUST receive these from
// chip-fetcher (which gets them from manufacturer / SnapEDA / SnapMagic /
// Ultra Librarian / Component Search Engine pages, all of which carry the
// package-family name). MPN alone is not enough to look up the stock entry.
"footprint_name": "Package_LGA/LGA-16_4.4x5.4mm_Pitch0.5mm", // KiCad footprint library path
"kicad_3d_name": "Package_LGA.3dshapes/LGA-16_4.4x5.4mm_Pitch0.5mm", // KiCad packages3d STEP path
"package_kind": "LGA", // canonical short tag — SOIC | QFN | QFP | LGA | BGA | DIP | SOT | TO | USB-C | …
"stock_footprint_id": "Package_LGA/LGA-16_4.4x5.4mm_Pitch0.5mm", // legacy alias for footprint_name
"laser_etch": {
"enabled": false,
"text": "VL53L8CX", // defaults to the MPN
"depth_mm": 0.05,
"max_size_mm": [3.0, 0.6], // auto-fits within these bounds; chipsmith picks the largest font that fits
"position": "auto" // "auto" centers and avoids the pin-1 indicator; or {x,y} in body local frame
},
"user_placed_pin1": {
"enabled": false, // true when manufacturer STEP had no pin-1 indicator and user placed one
"shape": "dimple", // dimple | raised_dot | corner_chamfer
"diameter_mm": 0.40,
"depth_or_height_mm": 0.05,
"corner": "NE", // NE | NW | SE | SW | custom
"custom_xy_mm": null // when corner == custom
}
"datasheet_pdf": "VL53L8CX.pdf"
}
structural_type enum: smd_leg (SOIC/QFP/SOT), smd_pad (QFN/LGA/DFN), ball (BGA), thru_hole_pin (DIP/PDIP), thru_hole_tab (USB-C reinforcement), hybrid_smd_thru (USB-C with both), ep_thermal (only EP), lead_with_ep (QFN with center EP).
Recommended location: extend chip-fetcher's info.json (additive — old files stay valid). chip-fetcher learns to populate at fetch time when it has the data; otherwise leaves null and chipsmith prompts the user.
Upstream provenance — where each field comes from
The chip-fetcher → chipsmith handoff carries a strict provenance chain. chipsmith never tries to invent footprint identity; the upstream source already knew it because it was scraped from a page that named it. Here's the responsibility split:
| Field | Source | How chip-fetcher derives it |
|---|---|---|
mpn |
manufacturer | URL slug or product page H1 |
manufacturer |
manufacturer | site domain / page metadata |
package_family ("LGA-16", "QFN-32", "SOIC-8", …) |
manufacturer page OR distributor parametric search | Mouser/DigiKey/JLCPCB parametric "Package / Case" field; ST/TI/NXP product-tab "Package" cell |
package_kind (canonical short — LGA / QFN / SOIC / etc.) |
derived from package_family |
regex normalize (LGA-16 → LGA) |
pin_count |
manufacturer datasheet OR distributor parametric "Number of Terminations" | datasheet PDF text extract; distributor parametric scrape |
body_dims_mm |
manufacturer datasheet (mechanical drawing page) | PDF page text extract — the "Dimensions" or "Mechanical Drawing" page typically carries nominal × nominal × nominal mm |
expected_pitch_mm |
manufacturer datasheet | same mechanical drawing page |
has_ep |
manufacturer datasheet | "Exposed Pad" / "EPAD" / "Thermal Pad" mentions in pin-table or mechanical |
expected_pin1_kind |
per the pin1-indicator skill table by package_kind |
lookup |
footprint_name |
upstream library that supplied the .kicad_mod |
always preserved by the scrape: SnapEDA / SnapMagic / Ultra Librarian / Component Search Engine / Mouser CAD / DigiKey CAD all expose the footprint name on the download page. KiCad's stock library uses standard names like Package_QFN/QFN-32-1EP_5x5mm_P0.5mm_EP3.7x3.7mm. |
kicad_3d_name |
upstream library that supplied the .step |
same scrape — Ultra Librarian / SnapMagic give you the canonical .step path. For chips that came from KiCad's stock library directly, this is the Package_<family>.3dshapes/<name>.step path. |
Hard rule: chip-fetcher MUST persist footprint_name, kicad_3d_name, and package_kind into info.json at fetch time. chipsmith refuses to do stock-library cross-check without them — there's no MPN→KiCad-library mapping in service-kicad, and inventing one would silently mismatch.
When chip-fetcher pulls from a source that DOESN'T expose the footprint name (rare — only some bargain SnapEDA pages), it leaves the fields null and chipsmith prompts the user to fill them in or pick a closest match from the KiCad library browser (v1.1).
Decorative geometry — branch, never strip
Many manufacturer STEP files carry marketing geometry alongside the canonical chip body — vendor logos embossed on the chip top, decorative ornamentation on connector housings, raised "ST" / "TI" / "NXP" lettering, lens-bezel cosmetic features. chipsmith preserves all of this. It's potentially load-bearing (some EE workflows need the laser-etched manufacturer mark to verify authenticity at goods-receipt) and tossing it would be a one-way data loss.
Instead, on /bake chipsmith moves the decorative products into a chipsmith_decorative branch of the STEP scene graph:
<MPN>.chipsmith.step
├── chipsmith_canonical (the validated, role-tagged chip — body + pads + EP + pin-1 …)
└── chipsmith_decorative (the moved-aside marketing / lens-cosmetic / vendor-mark geometry)
The downstream contract:
- Default visibility OFF for the
chipsmith_decorativebranch in every Adom tool that renders the chip (chipfit, adom-tsci, InstaPCB preview, etc.). Cleaner default rendering, no clutter. - Toggleable on via a single visibility flag — the user can flip it back on whenever needed.
- Round-trips through service-step2glb's
/convertbecause XCAF carries the assembly tree intact through to glTF; downstream Babylon code just hides nodes whose parent ischipsmith_decorative.
Detection of "decorative" candidates (the products that get moved):
- Products NOT classified by Mode 0 / 1 / 2 detection (the residual NAUOs that today get tagged
lens_or_connector/subassembly). - Products whose name matches
/logo|brand|trademark|copyright|^[A-Z]{2,3}$/i(manufacturer-mark patterns). - User-flagged products via the hierarchy's right-click → "Move to decorative branch" context-menu action.
The chipsmith bake-meta JSON gains a decorative_product_ids: [int] field. When /bake receives this list, it reparents those XCAF labels under a new chipsmith_decorative assembly node before re-emitting STEP. No products are deleted — moving only.
Multi-source footprint cross-validation — the trust signal
Chip-fetcher already pulls four independent footprint sources for each chip (or can; this section codifies the requirement). chipsmith renders all four into the scene as separately-branched overlays in different tints, then runs a per-pad cross-validation. Agreement across all four is the strongest possible "chipsmith nailed this; user doesn't need to review further" signal. Disagreement is a high-value warning — it's almost always either a footprint authoring bug in one of the EDA libraries OR a real chip-vs-datasheet mismatch worth flagging.
The four sources:
| Source | Where it comes from | File on disk (in chip-fetcher dir) | Branch in baked STEP |
|---|---|---|---|
| Datasheet-derived | chip-fetcher generates from the manufacturer datasheet's mechanical drawing page (PDF text + line extraction → KiCad-style pad list) | <MPN>.datasheet-fp.kicad_mod |
chipsmith_overlay_datasheet |
| KiCad library | service-kicad library lookup by footprint_name |
<MPN>.kicad_mod (already today) |
chipsmith_overlay_kicad |
| Fusion 360 library | chip-fetcher's .lbr parse (SnapEDA / SnapMagic / Ultra Librarian) |
<MPN>-fusion.lbr (already today; needs per-pad parser) |
chipsmith_overlay_fusion |
| Altium library | chip-fetcher's .IntLib / .PcbLib parse |
<MPN>.IntLib / <MPN>.PcbLib (already today; needs per-pad parser) |
chipsmith_overlay_altium |
Required upstream fields (chip-fetcher persists into info.json):
{
"footprint_sources": {
"datasheet": { "path": "VL53L8CX.datasheet-fp.kicad_mod", "derived_from": "datasheet PDF page 47 mechanical drawing", "confidence": "high|medium|low" },
"kicad": { "path": "VL53L8CX.kicad_mod", "library": "Package_LGA/LGA-16_4.4x5.4mm_Pitch0.5mm" },
"fusion": { "path": "VL53L8CX-fusion.lbr", "package_node": "LGA16_440x540_E_05" },
"altium": { "path": "VL53L8CX.PcbLib", "package_node": "LGA16-N_4P40X5P40MM" }
}
}
When a source is unavailable (e.g., chip-fetcher couldn't reach SnapEDA), the field is null and chipsmith renders only the available overlays + downgrades the agreement gate to "partial" instead of "fail."
Cross-validation algorithm
For every pair of available sources (A, B) and every pad number that exists in both:
- Position delta: distance between A's pad center and B's pad center, in mm.
- Size delta: max of
|w_A - w_B|,|h_A - h_B|in mm. - Shape delta:
1if shape strings disagree (rect≠oval), else0. - Layer delta: count of layer assignments that differ (
F.Cuvs*.Cu, etc.). - Drill delta (PTH only):
|drill_A - drill_B|in mm, +1 if shape (round vs slot) disagrees.
Tolerances (mostly from the chipsmith spec):
- Position ≤
expected_pitch_mm × 0.10(10% of pitch) - Size ≤ 0.05 mm
- Shape = exact match
- Layers = exact match (canonical-set normalized —
*.CumatchesF.Cu+B.Cu) - Drill ≤ 0.02 mm
Any pad whose A↔B comparison exceeds tolerance is flagged.
Result UX
The chipsmith_overlay_* branches all render simultaneously in the scene:
- Datasheet-derived — bright orange ghost (the canonical truth)
- KiCad — translucent green ghost
- Fusion 360 — translucent blue ghost
- Altium — translucent purple ghost
Then in Tab 4 the cross-validation matrix renders:
datasheet kicad fusion altium
datasheet ● ✓ ✓ ✓
kicad ● ✓ ✓
fusion ● ⚠ pad 7 +0.18mm
altium ●
Per-pad max Δ: 0.018 mm (pad 12, kicad↔fusion)
All cells green → the badge in the sign-off HUD reads "🎯 4-way footprint agreement — chipsmith nailed this chip." The user's mental load drops to near zero; they can sign off with strong confidence the chip is correctly characterized across every EDA tool downstream.
Any cell red → the offending pair gets a banner with the per-pad detail. The user is offered three actions:
- "Trust the datasheet" — adopt the datasheet-derived footprint as authoritative; the disagreeing EDA-lib branches stay rendered but with a "deviation" tag.
- "Trust the EDA lib" — accept the EDA-side data; flag the datasheet-derived footprint as needing regeneration (chip-fetcher gets a hint to retry the PDF parse).
- "Manual fix" — open the offending footprint file in the user's EDA tool to fix and re-fetch.
Sign-off gates (added to the existing 17)
fp_datasheet_loaded— datasheet-derived footprint existsfp_kicad_loaded— KiCad-library footprint existsfp_fusion_loaded— Fusion-library footprint existsfp_altium_loaded— Altium-library footprint existsfp_cross_agree— pairwise cross-validation passes for every available source pair (highest-trust signal)
Hard gate fp_cross_agree blocks sign-off when any pair disagrees beyond tolerance, unless the user has explicitly picked "Trust the datasheet" or "Trust the EDA lib" override (which records the override + reason in the .chipsmith.json sidecar).
chip-fetcher's new responsibility — datasheet-derived footprint
Chip-fetcher already grabs the datasheet PDF. It now also needs to derive a footprint from the mechanical drawing page and persist it as <MPN>.datasheet-fp.kicad_mod. Steps:
- Locate the datasheet's mechanical drawing page (PDF page that contains "Mechanical Drawing" / "Package Drawing" / "Dimensions" headings + the footprint outline + dimensioned arrows).
- Extract the package outline + pad positions + pad sizes via PDF text + vector extraction (pdfminer.six for text; pdf2image + classic CV or LLM-assisted vision for the geometry).
- Generate a
.kicad_modfrom those measurements (KiCad's s-expr is the canonical interchange format chipsmith uses; Fusion/Altium consumers can convert from KiCad). - Persist alongside the other footprints. Tag with
derived_from: "datasheet PDF page N"so the provenance is traceable.
This is its own chip-fetcher feature flag (per-chip toggle) and likely needs a vision-assisted pipeline for accuracy; chip-fetcher can fall back to "no datasheet-derived footprint" gracefully and chipsmith just won't render that branch.
Stock-library cross-check — keyed on footprint name, not MPN
The KiCad library is keyed by footprint name and 3D model name, not by manufacturer part number. service-kicad exposes:
GET /footprints/<library>/<name>.kicad_mod— fetches a footprint by library + nameGET /models/<library>.3dshapes/<name>.step— fetches a STEP by library + name
So chipsmith's stock-library overlay (Tab 4 second pane, v1.1) takes footprint_name + kicad_3d_name from the spec, calls those two endpoints, renders the result as a translucent ghost. There is no ?mpn=… lookup — that's the responsibility of upstream chip-fetcher to resolve and persist into info.json.
Terminology rule for the UI: the user passes a "footprint file" — full stop. Internally chipsmith parses .kicad_mod, but every visible label, tooltip, error message, button, popover, and CLI hint just says "footprint." The "stock library overlay" is the authoritative library footprint we ghost on top — never labeled as "KiCad" in the UI. Adom users come from Altium / Fusion 360 / Eagle backgrounds and shouldn't feel like they wandered into a KiCad-only tool. We pick KiCad's .kicad_mod as the implementation format because the AI handles it well; that's an internal decision, not a user-facing one. The word "KiCad" only appears in:
- file extension hints (when accepting a file:
.kicad_mod) - developer docs / SKILL.md (so future AI sessions know what format to write)
- internal field names in JSON (
stock_footprint_idresolved via the KiCad library indirectly)
pin_count, structural_type, AND body_dims_mm are required. Without body_dims_mm we can't run the measurement check, which means we can't trust the STEP's units, which means every downstream measurement is suspect.
What we build new (and what's foundational vs derived)
Foundational (must exist before chipsmith works at all):
service-step2glb extension — new
/step-metaendpoint that emitsstep-meta.json(PRODUCT tree, per-face surface type + bbox + parent solid, declared unit + mm-conversion factor, materials). This is THE enabling change. Without it, chipsmith is back to GLB triangle inspection. Lives in/home/adom/project/service-step2glb/— extend, version-bump, redeploy.Measurement check + auto-rescale — Tab 2 logic. Records the applied scale in the sign-off sidecar so re-loads skip the negotiation.
Mode 0 (named PRODUCT walk) — reads
step-meta.json, matches against the regex set, scores coverage.Mode 1 (surface-type cluster) — the new approach the user is asking for: cluster faces by surface type to identify balls vs pads vs pins vs EPs. Only possible because we read STEP-native data.
Derived (built on the foundation):
- Y-up auto-retry — second-pass detection with root rotated 90° about X.
- Through-hole leg-length validation — check ≥1.6mm on detected
thru_hole_pinafter applied scale. - Sign-off UX — button + confirmation + JSON sidecar + GLB role-bake.
- service-kicad library overlay — fetch + render KiCad's stock entries.
- Pin-1 indicator skill (
gallia/skills/pin1-indicator/SKILL.md) — research-backed conventions per package family. - Pin-1 chip ↔ silkscreen agreement check.
- Multi-page datasheet inline viewer — PDF.js embed in Tab 1.
Files to create
adom-chipsmith/
├── Cargo.toml # clap, tiny_http, ureq, serde, sha2, ctrlc, image
├── VERSION # 0.1.0
├── src/
│ ├── main.rs
│ ├── cli/
│ │ ├── mod.rs # ok/err/hint, DEFAULT_PORT=8871
│ │ ├── view.rs # view <step> --footprint <kicad_mod> --spec <chipsmith-spec.json> --datasheet <pdf>
│ │ ├── view_library.rs # view-library <chip-fetcher-dir>
│ │ ├── start.rs / stop.rs / status.rs / health.rs
│ │ ├── install.rs
│ │ ├── eval.rs / console.rs
│ │ ├── detect.rs # runs Mode 0/1/2 pipeline, prints JSON
│ │ ├── measure.rs # measurement check (and auto-rescale negotiation)
│ │ ├── verify_footprint.rs
│ │ ├── pin1.rs
│ │ ├── sign_off.rs
│ │ └── kicad_diff.rs
│ ├── server/
│ │ ├── mod.rs # tiny_http + ServerState + routes
│ │ ├── convert.rs # service-step2glb proxy
│ │ ├── step_meta.rs # NEW — calls service-step2glb /step-meta, parses, caches
│ │ ├── kicad.rs # service-kicad client
│ │ └── cache.rs # /tmp/adom-chipsmith-cache/
│ ├── kicad_mod.rs # minimal .kicad_mod parser (pads only)
│ ├── orient.rs # Y↔Z auto-retry
│ ├── measure.rs # auto-rescale negotiation (mm/m/inch/cm/µm)
│ ├── face_merge.rs # Mode 2 fallback
│ ├── detect_modes.rs # Mode 0/1/2 coordinator
│ ├── pin1.rs # pin-1 detection
│ ├── solder_jet.rs # 300µm dome layout
│ ├── manifest.rs # chipsmith-spec.json schema + I/O
│ └── assets/
│ ├── shell.html # 7-tab sidebar + canvas + role HUD + sign-off dialog
│ ├── adom.css # canonical Adom tokens
│ ├── icon.svg
│ └── js/adom-3d-viewer.min.js
├── vendor/
│ ├── 3d-viewer/ # vendored copy of adom-3d-viewer-babylon9
│ └── build-viewer.sh
├── wiki/{page.json, body.md, hero.png}
├── SKILL.md
├── BUILD-SKILL.md
├── publish.sh
└── README.md
Plus, in upstream repos:
service-step2glb/— new/step-metaendpoint (OCCT XCAF dump as JSON). Version bump.chip-fetcher/src/library.rs— additive schema fields ininfo.json. chip-fetcher populates what it can at fetch time.gallia/skills/pin1-indicator/SKILL.md— new skill.gallia/skills/adom-chipsmith/SKILL.md— usage skill.gallia/skills/adom-chipsmith-build/SKILL.md— release playbook.
v1 scope
Must-have (v1.0):
- service-step2glb
/step-metaendpoint shipped + redeployed. - Drop zone for
.step+.kicad_mod+chipsmith-spec.json(+ optional.pdf). - Tab 1 Source.
- Tab 2 Measurements — direct compare + ÷25.4 + ÷1000 rescale negotiation. Record applied scale.
- Tab 3 Detection — Mode 0 + Mode 1 (Mode 2 fallback) + Y-up auto-retry + role tagging + pin-reveal strobe animation on first success.
- Tab 4 Footprint — bbox-fit + 1.6mm PCB plate (with plated-PTH / NPTH / plated-slot / NPT-slot rendering). (Stock-library overlay v1.1.)
- Tab 7 Heatsink vias — auto-generated 0.3mm grid under any detected EP (1.2mm pitch, configurable).
- Tab 5 Pin-1 — chip-side + fp-side + agreement check.
- Tab 6 Sign-off — button + JSON sidecar + role-tagged GLB bake. ALL gates wired (measurements among them).
- AI-drivability for every UI verb.
- SKILL.md + wiki publish.
Deferred to v1.1+:
- Tab 8 Solder-jet (algo lives in
solder_jet.rsfrom v1; the UI rendering is the stretch). - Stock-library overlay (Tab 4 second pane — service-kicad-backed under the hood).
- Through-hole leg-length validation gate (≥1.6mm enforced on
thru_hole_pin). - Hybrid USB-C visualization.
- Multi-MPN batch sign-off.
- Datasheet auto-extract (read pin count, package, pin-1 from PDF — saves the user from typing the spec).
Verification plan
End-to-end in gallia per the test-in-gallia memory:
Z-up baseline —
adom-chipsmith view chip-fetcher/library/VL53L8CX/...(LGA-16 + EP, well-formed STEP). Mode 0 finds 16 pads + 1 EP from PRODUCT names. Measurements pass within 0.1%. Bbox-fit passes. Pin-1 chip-side + fp-side agree. Sign-off enables. Click →VL53L8CX.chipsmith.json+VL53L8CX.chipsmith.glb.Measurement rescale — pick (or synthesize) a STEP whose declared unit is wrong. Possible sources:
- SnapEDA-fetched STEPs that came in inches but say "mm" in the header.
- Some Ultra Librarian outputs in metres.
Tab 2 detects the mismatch, tries ÷25.4, ÷1000, ×25.4, ×1000, picks the one that matches declared
body_dims_mmwithin ±5%. HUD shows "auto-rescaled by 1/25.4 (file was inches not mm)." Sign-off allowed; sidecar records the applied scale. Reload skips negotiation.
Y-up retry — pick a chip flagged in
nuances-report.mdas "Y-as-height" (BME688, ESP32-S3-WROOM-1-N4, nRF52840-CKAA). Detection fails Z-up, auto-retries Y-up, succeeds. HUD shows the rotation.Mode 1 surface-type — BGA — find or fetch a BGA chip.
step-meta.jsonreports many CYLINDER faces with small radii grouped at z=0. Mode 1 clusters them into balls; count matchespin_count.Mode 1 surface-type — QFN — VL53L8CX is LGA so this works the same; for QFN pick something like CC2640R2FRGZR. Many small horizontal PLANE faces at z=0 → pads.
Mode 2 face-merge fallback — pick a STEP that has a flat scene (one solid, no sub-products) AND mixed surface types in a way Mode 1 can't disambiguate. Mode 2 union-finds by (material, adjacency). Coverage ≥ declared.
Through-hole —
adom-chipsmith view chip-fetcher/library/PMOD-ESP32/...or WAGO 2601-3105. Detect 12 thru_hole_pins. Each ≥1.6mm. PCB plate CSG-drilled.USB-C hybrid (v1.1) — fetch a USB-C connector. Detect smd pads + thru-hole reinforcement tabs separately.
structural_type = hybrid_smd_thru.Pin-1 mismatch — manually edit a footprint to put silkscreen dot on pad A4 instead of A1. Tab 5 banners "pin-1 mismatch." User can override.
Sign-off persistence — sign off VL53L8CX, restart chipsmith, reload. Status pills all green; detection skipped; role tags + applied scale loaded straight from baked GLB.
service-kicad overlay (v1.1) — chip with
kicad_lib_nameset. Tab 4 fetches stock footprint + STEP, renders translucent ghost, per-pad delta.Solder-jet (v1.1) — Tab 7 toggles 300µm domes; spacing rules verified by zoom.
API surface —
adom-chipsmith eval "return Object.keys(window.adomChipsmith)"lists every UI verb.Performance gate — a 50-pin LQFP detects + tags in ≤2s after
step-meta.jsonarrives.
Critical files
- adom-step/src/assets/shell.html — viewer template (lines 600-787 = scene-walker reference, but chipsmith does NOT copy this for detection — only for rendering).
- adom-step/src/server/mod.rs — tiny_http server template + route table.
- adom-step/src/server/convert.rs — service-step2glb proxy with SHA cache.
- service-step2glb/api/server.py —
/convertcontract; we add/step-metahere. - chip-fetcher/library/VL53L8CX/info.json — manifest schema baseline; we extend this.
- chip-fetcher/src/library.rs —
Entrystruct that needs the new fields. - service-kicad/docs/service-kicad-api.md —
/footprints/<path>,/models/<path>endpoints we'll call fromkicad.rs. - gallia/skills/solder-jetting/SKILL.md — 300µm dome spec + spacing rules (lines 32, 49, 50, 62-77).
- gallia/skills/electrical-engineering/SKILL.md — package family table (123-145), pin-1 conventions (357-379).
- gallia/skills/adom-app-header/SKILL.md — header layout, popover with verified-working wiki + GitHub links.
Decisions to confirm before/during build
- Where does
chipsmith-speclive?- Recommended: fields inside
info.json+ chip-fetcher learns to populate. One source of truth, no sidecar to forget. - Alternative: separate
<mpn>.chipsmith-spec.json.
- Recommended: fields inside
step-meta.jsonlives where?- Recommended: cached at
/tmp/adom-chipsmith-cache/<sha256>.step-meta.jsonnext to the GLB. Not committed alongside source.step. - Alternative: written next to source
.stepso chip-fetcher can persist it across machines.
- Recommended: cached at
- Pin-1 disagreement default behavior?
- Recommended: block sign-off with manual-override checkbox.
- Alternative: warn but allow.
- Measurement rescale tolerance?
- Recommended: ±5% on every axis (forgiving enough for tessellation rounding, tight enough to catch real wrong-unit cases).
- Alternative: ±1% or ±10%.
- service-step2glb
/step-meta— emit alongside/convert(one call returns both) OR as a separate call?- Recommended: separate
/step-metaso existing/convertcallers (adom-step, step2glb CLI) aren't broken. New endpoint, additive. - Alternative: extend
/convertresponse with a new optional field.
- Recommended: separate
If you accept the recommendations above, I'll proceed with that shape. Otherwise call out which one to flip.
# adom-chipsmith — STEP-native pin/leg/pad/tab detection + footprint validation + sign-off
> **Pin/feature DETECTION was removed from chipsmith** (commit ~2026-05-06).
> The Mode 0 named-PRODUCT walk and Mode 1 surface-type clustering didn't
> generalize across enough chips to be trustworthy — STEPs vary too much
> in how they name (or don't name) sub-products and how they model pads
> (discrete face entities vs material changes on shared body faces).
> The Detection tab (Tab 3) is gone. References to Mode 0 / Mode 1 /
> Mode 2 / coverage gates / pin-reveal strobe in this document are
> historical context, not current behavior.
>
> What still works without detection:
> - Tab 1 Source — manifest + spec editor + datasheet via adom-pdf-viewer
> - Tab 2 Measurements — auto-rescale negotiation
> - Tab 3 Footprint — multi-source overview + 3D pad overlays + faux PCB
> - Tab 4 Pin-1 — user-placed synthetic indicator (NW/NE/SW/SE) + fp silk
> - Tab 5 Sign-off — gates + JSON sidecar + .chipsmith.step bake
> - Tab 6 Heatsink — auto-grid under EP from footprint data
> - Tab 7 Solder-jet — 300µm dome layout from footprint pads
> Tabs renumbered 1..7.
## Context
You want a higher-level app on top of [adom-step](../../../project/adom-step/) that loads any STEP file + footprint + datasheet + manifest, then:
- **Operates on the STEP file's B-rep + assembly hierarchy data, NOT on triangulated GLB meshes.** Both `adom-chipfit` and `adom-chiplinter` tried the GLB-triangle approach and **both failed**. Triangle meshes lose named features, lose surface type (CYLINDER vs PLANE — which is exactly how you tell a BGA ball from a QFN pad), lose assembly hierarchy precision, and lose unit semantics. We will read the STEP B-rep directly.
- **STEP becomes the canonical artifact end-to-end across Adom going forward.** The sign-off bakes a fresh `.chipsmith.step` (role-tagged via XCAF named-data attributes, unit-normalized to mm, orientation corrected) — this is the file every downstream Adom tool will consume. A "shadow" `.chipsmith.glb` is derived from it purely so Babylon has something to render in the viewer. GLBs are a presentation format only; they never re-enter the validation loop.
- Detects every electrical-contact feature (legs / pads / balls / tabs / EP / through-hole pins / reinforcement tabs).
- Validates that the chip's actual measurements match the declared `body_dims_mm` — catches mm-vs-m-vs-inch unit mismatches that infect random downloaded STEP files. Auto-rescales when it can identify the conversion factor; flags + blocks sign-off when it can't.
- Auto-recovers from Y-up vs Z-up STEP convention mismatches.
- Tags scene graph with semantic role tags (`chipsmith_lead_1`, `chipsmith_pad_A1`, `chipsmith_body`, `chipsmith_ep`, `chipsmith_pin1_indicator`, …) so downstream tools re-walk without re-detecting.
- Renders the footprint in the same scene, projects it up to the chip bottom, checks bbox fit.
- Renders a 1.6mm faux PCB so you can see how the chip will sit on a real board.
- Cross-checks vs an authoritative stock-library footprint for the same MPN (sourced internally via service-kicad — surfaced to the user just as "stock library footprint" with no KiCad branding).
- Detects pin-1 (named entity in the STEP product hierarchy first; geometric corner-dot search second; cross-references vs footprint silkscreen).
- Lets the user sign off — locking orientation, footprint, pin-1, **and measurements** — emitting a `<mpn>.chipsmith.json` validated sidecar + a baked role-tagged GLB.
- Lays out InstaPCB solder-jet half-spheres on each pad per the [solder-jetting skill](../../../project/gallia/skills/solder-jetting/SKILL.md).
- **Auto-generates a heatsink-via grid under any detected EP / thermal pad** — 0.3mm drill diameter, 1.2mm pitch, square grid centered on the EP with a 0.4mm edge margin. EEs hate placing 9–25 vias by hand; chipsmith does it for them. This is a *convenience layer* — the vias aren't on the chip, they're a pre-computed footprint addition the user can adopt downstream when designing their board.
- **Optional laser-etch of the MPN onto the chip's top surface** — toggleable per-chip cosmetic feature that engraves the part number into the body's top face (50µm-deep recess, slightly darker than body color, auto-sized + auto-positioned to avoid the pin-1 indicator). EEs love seeing the part number on the chip when they're laying out a board downstream — it's the warm-and-cozy "yes, this is the correct chip" reassurance that a plain matte-black QFN can't give them.
- **Optional user-placed pin-1 indicator** — when the manufacturer's STEP doesn't carry a pin-1 dot (or the chip is so plain you can't tell which end is which), the user can place a synthetic indicator. Default suggested position is derived from the footprint silkscreen marker so chip and footprint agree by construction; user can also click anywhere on the top surface to place manually. Shape options: dimple (recessed cylinder, default), raised dot, or corner chamfer. Tagged `pin1_indicator_user_placed` so downstream tools can tell synthetic from manufacturer-original, and baked into the `.chipsmith.step` permanently so the indicator survives every future load.
**Display name:** "Chipsmith". **CLI:** `adom-chipsmith`. **Default port:** 8871.
## Why STEP-native, not GLB
| Data the STEP B-rep carries | What survives in a GLB triangle mesh |
|---|---|
| `PRODUCT_DEFINITION` names per assembly child (lead, pad, EP, body, pin1_indicator…) | Sometimes preserved by OCCT XCAF → glTF, often munged or dropped. Triangulation collapses sub-products. |
| Surface type per face (`PLANE`, `CYLINDER`, `CONICAL`, `TOROIDAL`, `B_SPLINE`) | Lost — every face is a flat triangle list. A BGA ball is no longer "a sphere"; it's just 200 triangles. |
| Exact extents in declared length units (`millimetre`, `metre`, `inch`) | Lost — units stripped during glTF write. The bbox you read in Babylon is in "scene units" with no metadata. |
| Assembly transform tree with named instance occurrences | Partially preserved as transformNode names, BUT only if XCAF kept the right tags through the conversion. |
| Per-face material assignments | Reduced to per-mesh PBR materials; same material on multiple faces collapses them into one mesh. |
Both `adom-chipfit` and `adom-chiplinter` work at the GLB layer. Both fail in characteristic ways: they can't tell a BGA ball from a QFN pad without a sphere-fit heuristic; they can't recover unit information from the STEP file; they fight collapsed-by-material faces (bug surface area: chiplinter's Mode 0 is *still TODO* in memory because the GLB hierarchy is too fragile to anchor on).
Chipsmith pivots: **read the STEP file directly** for detection, render the GLB in the viewer for visualization. Two parallel artifacts from the same STEP, used for different purposes.
## Architecture
**New repo:** `/home/adom/project/adom-chipsmith/`. Rust CLI + tiny_http server + Hydrogen webview shell + vendored Babylon9 viewer.
**Three-channel pipeline (STEP-first, GLB shadow):**
```
.step (input) + .kicad_mod + chipsmith-spec.json + .pdf
│
├─► service-step2glb /step-meta → step-meta.json
│ (NEW endpoint — dumps OCCT XCAF data:
│ • full PRODUCT_DEFINITION tree (id, name, parent, transform)
│ • per-solid bbox in declared units + mm-converted
│ • per-face surface-type + bbox + area + parent solid
│ • declared length unit + conversion factor to mm
│ • material assignments per face)
│
└─► service-step2glb /convert → shadow .glb (Babylon viewer only)
│
▼
chipsmith reads step-meta.json
│
├─► measurement check (try ×1, ÷25.4, ÷1000, ×25.4, ×1000 — pick best fit)
├─► Y/Z auto-retry (rotate root, re-detect if first pass <50%)
├─► Detection:
│ Mode 0 (named PRODUCT walk) → match lead_*, pad_*, ball_*, ep, body, pin1*
│ Mode 1 (surface-type cluster) → cylinders → balls; coplanar planes at z≈0 → pads;
│ tall thin extrusions → through-hole pins
│ Mode 2 (face-merge fallback) → union-find on (material, adjacency) — only when
│ Modes 0 & 1 underperform
├─► Role-tag every solid (in-memory)
├─► Pin-1 detection (chip-side via PRODUCT name → top CYLINDER/CONICAL fallback;
│ cross-reference vs footprint F.SilkS marker)
└─► Footprint overlay + 1.6mm PCB plate + service-kicad ghost + solder-jet domes
─── on user sign-off ──────────────────────────────────────────────────────────
│
├─► service-step2glb /bake → <mpn>.chipsmith.step (CANONICAL)
│ (NEW endpoint — POST {original_step, applied_scale, applied_rotation,
│ role_tags_per_product[], pin1_marker_solid_id, sign_off_metadata}
│ OCCT writes a fresh STEP via XCAF:
│ • LENGTH_UNIT normalized to mm (coords ×= applied_scale)
│ • root AXIS2_PLACEMENT_3D rotated to fix Y-up if needed
│ • PRODUCT_DEFINITION names extended with chipsmith.role tags
│ • XCAF named-data attributes carry the structured role + confidence
│ + detected_via per solid → STEP custom property entities
│ • a chipsmith_signoff document-level attribute carries the JSON sidecar
│ Future Adom tools that consume the chip MUST prefer this file over
│ the original input.)
│
├─► service-step2glb /convert → <mpn>.chipsmith.glb (shadow, derived)
│ (Re-render the baked STEP through the existing /convert path so the
│ GLB's hierarchy/materials match the baked STEP exactly.)
│
└─► chipsmith writes → <mpn>.chipsmith.json (sidecar)
(Same content embedded in the STEP's chipsmith_signoff attribute, surfaced
here as a flat JSON for tools that don't link OCCT.)
```
**Why this is fundamentally different from chipfit/chiplinter:** detection runs on `step-meta.json` (B-rep), not on the GLB. The GLB is purely for visualization. We never ask the GLB triangle mesh "is this a sphere?" — the STEP already told us. And on sign-off, we re-emit a **STEP file** (not just a tagged GLB), so the validated chip flows downstream as a STEP — preserving B-rep precision through every subsequent Adom step.
## File-explorer reveal (every file is one click from disk)
Every file chipsmith reads OR writes — input STEP, footprint, datasheet PDF, manifest JSON, output `.chipsmith.step` / `.chipsmith.glb` / `.chipsmith.json`, the per-chip cache, the source `info.json`, the ScreenShot folder, the wiki page assets, the SKILL.md it auto-installed — gets a tiny **reveal-in-explorer button** next to its label wherever it appears (Source tab, hierarchy rows, sign-off HUD, status bar, error toasts). Click → invokes `adom-vscode reveal <abs path>` (per the [`adom-vscode` skill](../../../project/gallia/skills/adom-vscode/SKILL.md)) which highlights the file in the VS Code Explorer sidebar.
| Where the reveal button shows up | What it reveals |
|---|---|
| Tab 1 Source — STEP file row | Source `<mpn>.step` |
| Tab 1 Source — Footprint row | `<mpn>.kicad_mod` |
| Tab 1 Source — Datasheet row | `<mpn>.pdf` |
| Tab 1 Source — Spec row | `info.json` (or `chipsmith-spec.json`) |
| Sign-off HUD — after sign-off | `<mpn>.chipsmith.step`, `<mpn>.chipsmith.glb`, `<mpn>.chipsmith.json` (one button each) |
| Sign-off HUD — "open enclosing folder" | the chip-fetcher library directory itself |
| Object-hierarchy header | the cache folder `/tmp/adom-chipsmith-cache/` |
| Error toasts mentioning a file | that exact file |
| CLI-reported paths (`adom-chipsmith sign-off …`) | every emitted path is a clickable terminal link |
A small "📂 reveal" icon button (with an explanatory tooltip) sits to the right of every path. Right-click the path text → context menu with "Reveal in Explorer / Copy path / Open in editor / Show in datasheet (if PDF)".
CLI parity: `adom-chipsmith reveal <chip-or-file>` — opens the explorer sidebar with the requested file highlighted, so AI sessions can guide a user to the artifact without copy-pasting paths.
## UI design compliance — follow the canonical Adom skills
Every UI surface in chipsmith follows the canonical Adom design system. No bespoke visuals. The relevant skills (read each before writing the corresponding subsystem):
- **[`adom-app-header`](../../../project/gallia/skills/adom-app-header/SKILL.md)** — single 44px header row with brand block + subject + actions. Hover-popover with `Open wiki ↗` + `GitHub ↗` links (verified end-to-end at publish time per the skill's verification snippet). Familjen Grotesk for the brand and section headers, JetBrains Mono for numeric data, Satoshi for body. Adom teal `#00b8b1` accent. Required `@font-face` loaders at the top of the stylesheet.
- **[`human-ui-patterns`](../../../project/gallia/skills/human-ui-patterns/SKILL.md)** — every interactive element gets a tooltip. 600 ms reveal delay on hover (intentional hovers only, not fly-bys). Z-index 99999 for tooltips. **Never ALL CAPS.** Click previews. Draggable / collapsible / dismissible HUDs (the sign-off HUD and hover-inspect HUD honor this). Group toggles for long lists (the object hierarchy honors this for SMD pads / heatsink vias / solder-jet domes). Multi-unit displays (mm primary + mils secondary; per the EE skill rule that mils is always the secondary, never None). Feedback for every action. AI-drivability — every UI verb has an HTTP and JS shadow.
**Icons sub-rule (load-bearing)** — read [`playbooks/icons-and-displays.md`](../../../project/gallia/skills/human-ui-patterns/playbooks/icons-and-displays.md) §4b before writing any new button. **Never use Unicode emoji** (📏 🔍 🎬 ⎚ 🔧 …) as UI icons. Emoji render multi-color (Twemoji / Apple Color / Noto) and mix miserably next to hand-drawn SVG. Every chipsmith icon is a monochrome `currentColor` SVG at `viewBox="0 0 24 24"`, custom-drawn for the specific concept (folder for "reveal", open-book for "open datasheet viewer", calipers for measurements, magnifier-with-data-dot for inspect, etc.). MDI is the fallback only for fully-generic things (eye, cog). Pick one stroke style per surface (1.5–2 px) and stick to it. If you find yourself reaching for `📂` or `📖` in a JSX/HTML string, stop — add the SVG to the `ICONS` map in [`chipsmith-overlay.js`](src/assets/chipsmith-overlay.js) and use `svgIcon('name')` instead.
- **[`electrical-engineering`](../../../project/gallia/skills/electrical-engineering/SKILL.md)** — package family table, ref-designator conventions, mm + MILS primary/secondary, JLCPCB DRC defaults, BOM column ordering, pin-1 silkscreen marker conventions.
- **[`adom-cli-design`](../../../project/gallia/skills/adom-cli-design/SKILL.md)** — every CLI subcommand prints `OK:` / `ERROR:` / `HINT:` lines for human and AI consumers, plus a JSON companion line on `--json`. Default port + clean `health` / `status` / `start` / `stop` triple. `install` subcommand deploys SKILL.md.
- **[`measure-tool-design`](../../../project/gallia/skills/measure-tool-design/SKILL.md)** — Fusion 360-style smart picking (vertex > edge > face > body within 8 px), mm primary + mils secondary, 0.01 / 0.001 / 0.0001 mm precision toggle, AI hooks (`measure(nameA, nameB)`).
- **[`3d-viewer-design`](../../../project/gallia/skills/3d-viewer-design/SKILL.md)** — section planes, view cube, named views, projection toggle, ground plane, pin-1 visual conventions.
- **[`adom-vscode`](../../../project/gallia/skills/adom-vscode/SKILL.md)** — `adom-vscode reveal <path>` for the file-explorer reveal capability.
- **[`pup`](../../../project/gallia/skills/pup/SKILL.md)** — pup-driven testing patterns for the verification plan.
- **[`tool-publisher`](../../../project/gallia/skills/tool-publisher/SKILL.md)** — wiki-first publish flow with auto-discovery metadata.
The sign-off HUD, hover-inspect HUD, object hierarchy, sidebar tabs, sign-off button, and every input field MUST visually rhyme with adom-step / video-post / adom-tts / chip-fetcher when the four are placed side-by-side. If they don't rhyme, the skill needs an update or chipsmith needs a follow-up.
## Object hierarchy (left sidebar — always visible)
The left sidebar renders a **rich, deeply-detailed component tree** of EVERY object in the scene — STEP-derived solids AND every overlay layer chipsmith adds (footprint pads, faux PCB, solder-jet domes, KiCad ghost overlay, pin-1 marker). Each row carries the per-object metadata up-front so the user can scan the whole chip without clicking.
```
adom-chipsmith
├─ ⬛ Body 4.40×5.40×1.50 mm matte black
├─ ⭐ Pin-1 indicator dimple Ø 0.40×0.08mm corner NE conf high source mfr
├─ 🟧 Exposed pad / EP (1)
│ └─ ep_thermal 3.20×3.20 mm area 10.24 mm² Cu (gold)
├─ 🟦 SMD pads (16)
│ ├─ pad_A1 ⭐ pin-1 0.30×0.45 mm c=( 1.95, 2.45) Cu →fp 1 Δ 0.012mm jets 4
│ ├─ pad_A2 0.30×0.45 mm c=( 1.45, 2.45) Cu →fp 2 Δ 0.008mm jets 4
│ ├─ pad_A3 0.30×0.45 mm c=( 0.95, 2.45) Cu →fp 3 Δ 0.014mm jets 4
│ └─ … (13 more)
├─ 🟪 SMD legs (0)
├─ ⚪ BGA balls (0)
├─ 🔻 Through-hole pins (0)
├─ 🔩 Reinforcement tabs (0)
│
├─ 📐 Faux PCB (overlay)
│ ├─ FR4 plate 15.0×17.0×1.6 mm translucent green
│ ├─ Plated holes (PTH) 0
│ ├─ Unplated holes (NPTH) 0
│ ├─ Plated slots 0
│ └─ Unplated slots 0
│
├─ 🟩 Footprint overlay (16 pads + 1 EP)
│ ├─ fp pad 1 (A1) ⭐ silk-marked rect 0.30×0.45 F.Cu+F.Mask+F.Paste →chip pad_A1
│ ├─ fp pad 2 rect 0.30×0.45 F.Cu+F.Mask+F.Paste →chip pad_A2
│ ├─ … (14 more)
│ └─ fp ep rect 3.20×3.20 F.Cu+F.Mask+F.Paste →chip ep_thermal
│
├─ 👻 KiCad stock overlay (ghost) Package_LGA/LGA-16_4.4x5.4mm_Pitch0.5mm
│ ├─ kicad pad 1 (A1) max XY Δ vs mfr: 0.018 mm
│ ├─ kicad pad 2 max XY Δ vs mfr: 0.012 mm
│ └─ … (15 more)
│
├─ 💧 Solder-jet (64 domes total) 0.300mm Ø each intra-pad ≥0.10 inter-pad ≥0.30
│ ├─ pad_A1: 4 domes c-c 0.10mm vol Σ 56.5 nL
│ ├─ pad_A2: 4 domes c-c 0.10mm vol Σ 56.5 nL
│ ├─ … (14 more)
│ └─ ep_thermal: 36 domes c-c 0.10mm vol Σ 508.7 nL
│
├─ 🏷 Laser etch (optional) "VL53L8CX" 3.0×0.6mm depth 0.05mm centered v1.1
│
├─ 🔩 Heatsink vias (9, auto-generated) 0.30mm drill 1.2mm pitch under ep_thermal
│ ├─ via 1 (-1.2, -1.2) plated PTH 0.30 drill / 0.60 outer Cu
│ ├─ via 2 ( 0.0, -1.2) plated PTH 0.30 drill / 0.60 outer Cu
│ ├─ via 3 ( 1.2, -1.2) plated PTH 0.30 drill / 0.60 outer Cu
│ ├─ via 4 (-1.2, 0.0) plated PTH 0.30 drill / 0.60 outer Cu
│ ├─ via 5 ( 0.0, 0.0) plated PTH 0.30 drill / 0.60 outer Cu
│ ├─ via 6 ( 1.2, 0.0) plated PTH 0.30 drill / 0.60 outer Cu
│ ├─ via 7 (-1.2, 1.2) plated PTH 0.30 drill / 0.60 outer Cu
│ ├─ via 8 ( 0.0, 1.2) plated PTH 0.30 drill / 0.60 outer Cu
│ └─ via 9 ( 1.2, 1.2) plated PTH 0.30 drill / 0.60 outer Cu
│
└─ 🟢 Sign-off
├─ Source VL53L8CX.step sha256 a1b2c3…
├─ Spec 16 pads + EP, lead_with_ep
├─ Measurements ✓ matched (no rescale) 4.398×5.401×1.503 mm
├─ Detection ✓ 17 / 17 features (Mode 0)
├─ Footprint ✓ all pads inside ±0.04 mm
├─ Pin-1 ✓ chip NE = fp A1 (agree)
└─ Status Draft → [ Sign off ]
```
### Per-row metadata shown inline
Every row carries an at-a-glance summary on the row itself (size + position + material + the most relevant cross-reference). Each row is also clickable / hoverable / right-clickable:
- **Click** → isolate this object (everything else fades to ~10% opacity).
- **Ctrl+Click** → multi-select (compound isolate).
- **Hover** → highlight in 3D + show a richer **Properties** popover with the FULL metadata blob:
- For a detected pad: role, structural_type, confidence, detected_via, material name + RGB, parent PRODUCT name in original STEP, surface-type breakdown ("1 plane top, 4 planes side"), bbox in mm (x,y,z), center coords (mm) relative to chip origin, distance to projected footprint pad center, mapped footprint pad ID, mapped KiCad-stock pad ID + delta, jet count + jet positions.
- For pin-1 indicator: position (x,y,z relative to body center), shape (cylinder/cone/sphere), diameter, height, body-corner classification (NE/NW/SE/SW), confidence + reason ("found PRODUCT named 'pin1_indicator'"), cross-reference to footprint silk marker.
- For a through-hole pin: leg length below body, plated-through hole match in `.kicad_mod`, ≥1.6mm gate result.
- For a footprint pad: pad number, pad name, pad shape, pad size, position, layer assignments (`F.Cu`, `F.Mask`, `F.Paste`, `B.Cu`, etc.), drill (for THT pads), net name (if assigned in `.kicad_mod`), mapped chip feature, raycast Δ.
- For a faux PCB plate: dimensions, FR4 thickness assumption (1.6mm by default; configurable), drill count, drilled-pad list.
- For solder-jet: dome count, drop diameter, intra-pad spacing, inter-pad spacing, total dispensed volume in nL, per-dome XY coords on the pad's local frame.
- For KiCad ghost overlay: KiCad library path, footprint name, per-pad delta vs manufacturer, max delta.
- **Right-click** → context menu: Isolate / Hide Others / Hide / Show / Zoom To / Copy as JSON (full metadata) / Copy Name / Show in Datasheet (jumps to the datasheet PDF page that mentions this pin).
### Per-row badges
Compact icons up-front signal status without clicking:
| Badge | Meaning |
|---|---|
| ⭐ | This is pin 1 (chip-side or fp-side) |
| ✓ | Verified — passes its tab's check |
| ◐ | Partial — passes some checks, others pending |
| ✗ | Fails the gate |
| 🔒 | Locked by sign-off (loaded from baked STEP, immutable) |
| 🆕 | Synthesized (Mode F face-merged or chipsmith-added overlay) |
| ↻ | Re-detected after Y-up auto-rotate |
### Filters + group toggles
Top-of-tree search box (debounced 80ms): dim non-matching rows, auto-expand ancestors. Plus per-group **master visibility toggle** (per the [`group toggles` memory](../projects/-home-adom-project/memory/feedback_group_toggles.md)) — collapse the SMD pads node and toggle all 16 visible/hidden in one click; same for solder-jet, footprint overlay, KiCad ghost, faux PCB.
### Live updates
The hierarchy is **the** debugging surface during detection. As Mode 0 → Mode 1 → Mode 2 runs, rows appear in real-time with an animated "✨ detecting…" spinner that resolves to ✓ + bbox + role as each feature lands. Rebuild on every state change; never let it go stale relative to what's on canvas.
---
## UI — 8-tab sidebar (left-to-right validation flow)
Status pill on each tab (●/◐/○/✗). User proceeds left-to-right.
### Overlay layers are first-class Outline citizens (load-bearing)
**Every 3D overlay chipsmith adds to the scene MUST appear as a real
row (or section) in the left-side Outline panel** — not just as
detached Babylon meshes the user can't see in the tree. This applies
to footprint pad overlays (one section per source), the 1.6mm faux
PCB plate, heatsink vias, solder-jet domes, KiCad-stock ghost,
laser-etch text, and the user-placed pin-1 indicator.
Each Outline entry carries the same affordances as a regular
GLB-derived node:
- **Eye toggle** — flips overlay visibility as a unit (no half-states
where the row says "visible" but the meshes are gone).
- **Click-to-isolate** — fades the rest of the scene to ~10% opacity
so the user can study the overlay alone.
- **Right-click context menu** — Isolate / Hide Others / Hide / Show
/ Zoom To / Copy as JSON / Copy Name (matches the regular tree-row
menu — see [`human-ui-patterns`](../../../project/gallia/skills/human-ui-patterns/SKILL.md) §1d).
- **Searchable** — the panel's debounced search filter dims
non-matching overlay rows the same way it dims non-matching
GLB-derived rows.
- **Tooltip / hover-inspect** — shows the overlay's metadata
(source, pad count, FR4 thickness, via grid dimensions, etc.)
in the same hover popover style.
- **Per-section group toggle** when the overlay is a multi-row
collection (e.g. 16 BGA solder-jet domes get a "Solder-jet" parent
with a master-eye that toggles all 16 at once, plus per-row eyes
per the [group-toggles memory](../projects/-home-adom-project/memory/feedback_group_toggles.md)).
Implementation contract: `window.adomStep.addOverlay({ group,
meshes, label, parent?, kind? })` registers the meshes AND creates a
matching outline entry in one call. Internally the entry is pushed
to the assembly tree with synthetic ids in the overlay-id range
(< -10000 to never collide with GLB-derived ids). On chip-reload
(`clearScene`), every overlay group is disposed AND every overlay
entry is removed from the tree.
Why this rule exists: an overlay-only mesh that's invisible in the
Outline is a power-user trap — the user can't tell what was added,
can't toggle it, can't right-click it, can't search for it. Every
single object in the scene needs to be discoverable + controllable
from the Outline. No exceptions.
### Right-pane shape: floating draggable HUD, not a docked column
The 8-tab pane is a **floating HUD**, not a fixed-width docked column. It floats over the canvas (transparent / translucent bg with text-shadow for legibility, same design language as the Outline) and **must be positioned so it never covers the ViewCube** (top-right of canvas, viewport `(0.85, 0.85, 0.15, 0.15)`). Default landing position: ~130 px from the top of the canvas, ~10 px from the right edge — clear of the cube.
The HUD is:
- **Draggable** — grab anywhere in the tab strip / title area and drag to a new position. Position persists in `localStorage["chipsmith.pane.pos"] = {x, y}` so the user's preferred docking spot survives reloads.
- **Collapsible** — chevron at the right of the tab row slides the panel off-screen (translateX) and reveals a slim vertical expand-tab at the canvas's right edge. Keyboard `P` toggles. State persists in `localStorage["chipsmith.pane.collapsed"]`.
- **Resizable** *(v1.1)* — drag the bottom-right corner to resize. Width persists similarly. v1.0 ships at fixed 360×(canvas-130).
- **Pin-1-corner-aware** — when the user manually drags the HUD over the ViewCube area, the cube auto-shifts left to make room (per the same `body.cs-collapsed` style used to nudge the toolbar). v1.0 just blocks the default landing position from overlapping the cube; auto-shift on drag is v1.1.
Why floating + draggable instead of a docked column:
1. The user can move the HUD off the part of the chip they're inspecting.
2. The 3D scene gets the entire canvas — the HUD never claws back real estate by docking.
3. EE workflows often have the user comparing left-side outline rows ↔ right-side HUD tabs; a floating HUD lets them dock it wherever the comparison flow feels natural.
4. Matches Fusion 360's "floating dialog" affordance for non-modal panels (Browser, ContextualEnvironment, Properties).
### Tab 1 — Source
Manifest + provenance row. MPN, package family, declared `pin_count`, declared `structural_type`, declared `body_dims_mm`. Datasheet PDF embedded inline (PDF.js lazy-loaded). Tab is red until `pin_count`, `structural_type`, AND `body_dims_mm` are present.
### Tab 2 — Measurements *(new — was missing from prior draft)*
The measurement-validation gate. Reads:
- Declared `body_dims_mm` from the manifest (e.g., `[4.4, 5.4, 1.5]`)
- Detected body bbox from `step-meta.json` (in declared STEP units), with the unit string from the STEP header (`millimetre`, `metre`, `inch`, `centimetre`)
Then:
1. **Direct compare** — convert detected to mm using the STEP's declared unit, compare to manifest. If within ±5% on every axis, pass.
2. **Mismatch → try common rescales** in priority order, picking the conversion factor that lands closest:
- `÷25.4` (STEP says mm but is really inches — common when STEP came from a US-mfr CAD export that lied about units)
- `÷1000` (STEP says mm but is really metres — common with downstream Fusion exports)
- `×1000` (STEP says metres but is really mm — rarer but real)
- `×25.4` (STEP says inches but is really mm)
- `÷10` (STEP says mm but is really cm)
3. If a rescale gets within ±5% on all three axes → flag "auto-rescaled by ÷25.4 (file was inches not mm)" in the HUD, apply the scale to BOTH the rendered GLB and every detected feature, mark Tab 2 amber (verified-after-rescale).
4. If no rescale lands within tolerance → fail, surface "detected 110×135×38 mm vs declared 4.4×5.4×1.5 mm — no rescale matches" and **block sign-off**. User can manually pick a scale or reject the STEP.
The applied scale is recorded in `<mpn>.chipsmith.json` so re-loads of the same chip skip rescale negotiation. The scale also propagates to leg-length checks (a leg is "≥1.6mm" only after rescale).
### Tab 3 — Detection
Runs Mode 0 → Mode 1 → Mode 2 in order, stopping when ≥(declared pin count) features have been classified.
- **Mode 0 — Named PRODUCT walk** (the canonical path). Walk `step-meta.json`'s PRODUCT_DEFINITION tree, look for names matching `lead_*|pin_*|pad_*|ball_*|ep|exposed_pad|thermal_pad|body|pin1*|indicator*`. Most modern manufacturer STEPs name these. If ≥70% of declared pins matched by name → done. (This is what chipfit's `feedback_chipfit_use_named_scene_graph_first` memory entry advocated; we ship it for real here.)
- **Mode 1 — Surface-type cluster** (the killer feature). Per face in `step-meta.json`:
- **CYLINDER + small radius (≤1mm) + dome top** → BGA ball candidate. Cluster by neighbour adjacency in the same z-band → one cluster per ball.
- **PLANE + horizontal + at z≈seat_plane** → SMD pad candidate. Cluster coplanar planes → one per pad.
- **PLANE + vertical + tall (≥1.6mm) + thin** → through-hole pin / reinforcement tab candidate.
- **PLANE + horizontal + large + at center** → exposed pad / thermal pad.
- **CONICAL or CYLINDER on top surface, small (≤1mm radius), elevated** → pin-1 indicator candidate.
This works because the STEP told us each face's exact surface type — the GLB couldn't.
- **Mode 2 — Face-merge fallback** — for STEPs whose product hierarchy is flat (one solid per chip, no sub-products) AND whose face surface types don't disambiguate: union-find on (material, mutual adjacency by ≤50µm gap). Only invoked when Modes 0 & 1 cover <50%.
After detection, the sidebar lists every detected feature with role + confidence + bbox + click-to-isolate.
#### Pin-reveal strobe (the magical moment)
The instant detection succeeds, play a celebratory reveal animation so the user can SEE chipsmith got it right — instead of just trusting the number in the sidebar:
1. Fade everything except detected pins/pads/balls/legs to ~5% opacity (body + EP + indicator + viewer helpers).
2. Sequentially highlight each detected feature for **50ms** in detected order — bright accent emissive, brief overshoot in scale (1.0 → 1.15 → 1.0 with ease-out). Index tag floats above each one as it strobes ("1", "2", … "48").
3. After the last one, hold the full set lit for ~600ms with a count badge: "48 / 48 pins ✓".
4. Fade body + EP back to full opacity over 400ms; pins remain accented for 1s before settling.
Math: 48 pins × 50ms ≈ 2.4s sequence + 1s tail ≈ 3.4s total — long enough to be satisfying, short enough not to annoy on every reload. For high-count chips (BGA ≥200 balls), batch into groups so total stays under ~4s while preserving the strobe feel.
Replayable via a `↻ replay reveal` button on the Detection tab; auto-plays once on first successful detection per session. Can be disabled by user setting `chipsmith.reveal_animation = false` in localStorage for power users.
The strobe is also the "oh, awesome, he really did figure out there's 48 pins" moment a screen recording wants to capture — so an explicit `window.adomChipsmith.replayReveal()` JS hook lets demo scripts trigger it on cue.
#### Y-up vs Z-up auto-retry
Re-orient logic: if first detection pass returns <50% of declared pin count, rotate the root 90° about X and re-run. Take the better result. HUD surfaces "auto-rotated 90° (file was Y-up)."
#### Advanced manual orient & translate (hidden by default)
Some manufacturer STEPs are so weirdly authored that auto Y-up retry isn't enough — the chip might be sitting on its side, rotated 45°, mirrored, or with the body's origin offset from where it should be (some manufacturers place the origin at the corner instead of the body center, others put it at a random tooling reference point hundreds of millimeters away from the body itself).
These are **rare edge cases**, so the controls live behind a collapsed **Advanced…** disclosure at the bottom of Tab 3. The disclosure auto-expands when chipsmith detects an obvious fault — measurement check fails on every rescale attempt + detection coverage <30% even after Y-up retry — so users who need it find it without spelunking, but everyone else never sees it.
Inside the disclosure:
```
Advanced — manual orient & translate
Rotate Translate (mm relative to current origin)
┌────────┐ ┌────────┐ X [ 0.000 ] ◀ ▶
│ X 90°↺ │ │ X 90°↻ │ Y [ 0.000 ] ◀ ▶
└────────┘ └────────┘ Z [ 0.000 ] ◀ ▶
┌────────┐ ┌────────┐
│ Y 90°↺ │ │ Y 90°↻ │ Center on body bbox [⟲ snap]
└────────┘ └────────┘ Drop to z=0 [⟲ seat]
┌────────┐ ┌────────┐
│ Z 90°↺ │ │ Z 90°↻ │
└────────┘ └────────┘ Reset all transforms [↺]
Mirror [X] [Y] [Z]
```
- **90° rotation buttons** about X, Y, Z (CCW + CW) — six buttons, each one triggers an instant re-render and re-runs detection in the new pose. Combinable into any of the 24 axis-aligned poses.
- **Translate inputs** for X / Y / Z in mm, with click-and-hold ◀ ▶ steppers (0.1 mm per click, hold for 1 mm/s drift). Supports negative values.
- **Quick-snaps** — "Center on body bbox" zeros the X/Y of the body's bbox center; "Drop to z=0" puts the body's bottom face at z=0 (the seat plane). Together they fix the two most common manufacturer-origin pathologies.
- **Mirror** toggles per-axis mirror (rare, but some tooling exports come out flipped).
- **Reset all** restores the as-loaded pose.
After every change, detection re-runs on the new pose and the sign-off HUD updates live. The applied composite transform — auto-rescale × auto-rotation × manual-rotation × manual-translation × manual-mirror — is recorded in the manifest as a single 4×4 matrix:
```json
"applied_transform": {
"scale": 1.0,
"rotation_quat": [0, 0, 0, 1],
"translation_mm": [0, 0, 0],
"mirror_axes": [],
"matrix4x4": [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]
}
```
… and baked into `.chipsmith.step` via OCCT XCAF on sign-off, so the chip permanently sits in the correct pose for every downstream tool. The original STEP is preserved untouched — chipsmith never edits source files in place.
The manual-transform sign-off HUD row reads `─ none` when no manual transform was applied (the common case), `◐ manual rotate + translate applied` when the user reached for the advanced controls. Hovering shows the matrix.
A short **"Why am I doing this?"** note at the top of the disclosure explains the situation in plain language ("This chip's STEP file appears to be authored with the body lying on its side and the origin offset 47 mm from the body. You can fix it manually below — the rotation and shift will be saved as part of the sign-off so future loads are correct.") — so users who hit this rare path aren't disoriented.
#### Industry-standard pin numbering
Detection finds geometric features; **numbering** assigns each feature its canonical pin designator per the chip's package family. This matters: an EE who sees "pad_3" expects exactly the pin that the datasheet calls pin 3, exactly where every other tool (KiCad, Altium, Fusion 360, manufacturer reference designs) calls it pin 3. Wrong numbering is silently catastrophic.
Numbering is driven by `package_family` from the manifest (so chipsmith doesn't have to guess; the user already declared it). Numbering starts from the detected pin-1 location and walks the perimeter / grid in the canonical direction:
| Package family | Numbering convention | Walk pattern |
|---|---|---|
| **SOIC / SOP / SSOP / TSSOP / DIP / PDIP** (dual-row) | Pin 1 at one end of the left row (datasheet-marked end). Numbers run **down the left row**, then continue **up the right row**, ending at pin N opposite pin 1. So an 8-pin SOIC: 1-2-3-4 down the left, 5-6-7-8 up the right (pin 8 is top-right, opposite pin 1 top-left). | Left column top→bottom, then right column bottom→top |
| **QFN / DFN / LGA-perimeter / QFP / LQFP / TQFP** (4-side perimeter) | Pin 1 at the **dot/dimple corner** (typically top-left when viewed from the top). Numbers run **counter-clockwise** around the perimeter. | Pin 1 corner → down the left side → across the bottom → up the right side → across the top, ending where it started |
| **BGA / LGA-grid** | **Letter-number grid** (A1, A2, … B1, B2, …). Letters index rows (top to bottom), numbers index columns (left to right). Letters skip **I, O, Q, S, X, Z** to avoid confusion with `1`, `0`, `5`, etc. So a 100-ball BGA goes A1…A10, B1…B10, C1…C10, … (no I row), J1…J10, K1…K10, … | Row-major: assign each ball to its (row_letter, col_number) by clustering XY positions into a grid |
| **SOT-23 (3-pin)** | Pin 1 top-left, pin 2 bottom (single-pin side), pin 3 top-right. | Datasheet-canonical |
| **SOT-23-5 / SOT-23-6** | Three pins along the long bottom (1, 2, 3 left-to-right) + two/three pins along the long top (5, 4 right-to-left for SOT-23-5; 6, 5, 4 for SOT-23-6). | Bottom row left→right, then top row right→left |
| **TO-220 / TO-247** (through-hole tab) | 3 legs numbered **1, 2, 3 left-to-right** when viewed from the labeled / printed face. | Datasheet-canonical |
| **DPAK / D2PAK** (SMD tab) | Same as TO-220 — 3 legs left-to-right when viewed from the top with the tab pointing up. | Datasheet-canonical |
| **USB-C receptacle** | Spec-defined: **A1-A12** on one row, **B1-B12** on the other, plus mechanical reinforcement tabs on the corners (not numbered, tagged `tab_*`). | Per USB-C spec ([USB-C R2.1 §3.2](https://www.usb.org/document-library/usb-type-c-cable-and-connector-specification-22)) |
| **DIP/SIP through-hole headers (e.g., WAGO 2601)** | Pin 1 at the manufacturer-marked corner; numbers run sequentially along the row(s). Single-row: 1→N. Dual-row: same as DIP (down the left, up the right). | Manifest-declared per part |
| **Connectors (RJ45, DC-barrel, screw-terminal)** | Per-part — fall back to manifest's declared pin map. | `pin_map` field in manifest |
| **Passives (R / C / L / 0402-1206)** | Pins 1 and 2; pin 1 is arbitrary unless the part is polarized (electrolytic cap, LED, diode), in which case pin 1 is the marked / longer / cathode end per datasheet convention. | 2 pins only |
Algorithm per family is implemented in `src/pin_numbering.rs`. Each family is a small function that takes the detected feature centroids + the pin-1 corner and returns `Vec<(feature_id, pin_designator)>`. Roles get rewritten from `pad_<n>` to `pad_A1`, `pad_3`, `lead_5`, `ball_C7`, etc.
When `package_family` is unknown / unsupported, chipsmith falls back to **counter-clockwise from pin-1 with sequential integers**, surfaces an amber warning in the HUD ("falling back to default numbering — declare package_family to use the canonical pattern"), and lets sign-off proceed only after the user manually confirms the numbering by clicking each pin in order to verify.
#### Pin numbering during the reveal strobe
The pin-reveal strobe animation now **shows the pin designator** as it strobes each feature. So as the user watches, they see:
> ⭐ pin 1 (A1) … pin 2 (A2) … pin 3 (A3) … pin 4 (A4) … …
The designator floats above the strobing feature for the duration of its 50ms highlight, then transitions to the next. For BGA, the designator includes the letter-number cell (e.g., `A1`, `B5`). For perimeter SOIC/QFN/QFP, the designator is just the number with the side abbreviated as a small superscript: `1ᴸ`, `4ᴮ`, `8ᵀ` (left/bottom/right/top). For TO-220, the leg name (`G`, `D`, `S` for a MOSFET when datasheet-known via the manifest's `pin_map`).
Because the designator IS the trust signal — the user instantly sees "yes, that's the order I expect a SOIC-8 to walk" — this also doubles as a **numbering verification step**. If chipsmith picked the wrong corner for pin 1, the strobe sequence will visibly count wrong (e.g., counter-clockwise instead of clockwise, or starting from the bottom-right instead of top-left), and the user flags it before sign-off.
A "Replay reveal with numbers" button in the Detection tab re-runs the animation any time. Also exposed via `window.adomChipsmith.replayReveal({showNumbers: true})`.
#### Semantic role tagging
Every detected feature gets `metadata.chipsmith` populated on the corresponding GLB node:
```json
{
"role": "lead_1" | "pad_A1" | "ball_B7" | "ep" | "body" | "pin1_indicator" | "tab_left" | …,
"structural_type": "smd_leg" | "smd_pad" | "ball" | "thru_hole_pin" | "thru_hole_tab" | "ep_thermal" | "reinforcement_tab",
"confidence": "high" | "ambiguous" | "assumed",
"detected_via": "named" | "surface_type" | "face_merge"
}
```
Baked into the GLB on sign-off so re-loads skip detection.
### Tab 4 — Footprint
Loads `.kicad_mod`, parses pads (vendored mini parser, no shelling to KiCad), renders each as thin extruded shape at z=0. Two layout modes (side-by-side / aligned). **Bbox-fit check** raycasts up from each footprint pad's bbox; every detected pin centroid must fall inside its projected pad. Tolerance = `expected_pitch_mm × 0.4`. Failures highlighted red. **1.6mm PCB plate** — translucent green FR4 plate, CSG-drilled with the four canonical hole/slot types found in the footprint:
| Hole type | Geometry | Copper ring? | Visual cue | Use case |
|---|---|---|---|---|
| Plated round hole (PTH) | Cylinder | yes — annular ring | drilled hole + amber/gold inner-bore plating ring + same-color top/bottom annular pad | DIP / PDIP through-hole pins, electrical signals |
| Unplated round hole (NPTH) | Cylinder | no | drilled hole, bare green FR4 walls | Mounting holes, M2/M3 screw clearance |
| Plated slot (PT slot) | Capsule (rounded rectangle) | yes — capsule plating ring | slot cut + amber plating around inside walls + plated annular pad | USB-C reinforcement tabs (the strong solder tab anchors), DC barrel jack tabs |
| Unplated slot (NPT slot) | Capsule | no | slot cut, bare green walls | Mechanical alignment slots, key-cutout for connectors |
Rendering: union the body of the plate, then CSG-subtract every hole/slot footprint in one pass; for plated holes/slots, also union a thin annular-ring shell on top + bottom faces (Cu-tone amber 0xC79A4F) and a thin inner-bore cylinder/capsule shell (same Cu-tone) so the copper plating reads visibly when the user zooms in. This matters because USB-C is `hybrid_smd_thru` and its reinforcement tabs are **plated slots**, not round holes — getting the slot geometry right is the difference between "USB-C will solder mechanically" and "USB-C will fall off the board."
The footprint parser tags each pad with its hole type (`drill.shape: oval|circle`, `drill.size: [w,h]` for slots, `pad.layers: ["F.Cu","B.Cu","*.Cu"]` to detect plating). Chipsmith reads these and chooses the correct CSG primitive + copper ring per hole. For pads with no `drill.shape` declared but `drill.size` is set with `width != height`, chipsmith infers a slot. **Stock-library overlay** (v1.1) — fetch the authoritative footprint + STEP for this MPN from the internal stock library (service-kicad under the hood), render as a translucent ghost in a different tint, surface per-pad delta. The user just sees "Stock library footprint" — no KiCad branding in the label.
### Tab 5 — Pin-1
Two columns:
- **Chip-side**: search `step-meta.json` for a PRODUCT named `pin1_indicator|pin1|indicator|polarity_mark`. If found → isolate + circle. Otherwise scan top surface for CYLINDER or CONICAL features ≥0.1mm above the body, ≤1mm radius, scored by proximity to a body corner.
- **Footprint-side**: parse `F.SilkS` polygons; look for circles, triangles, dots, "1" text, asymmetric pad shape (rounded square, polarity stripe).
**Cross-reference**: both must agree on which pad is pin 1. If they disagree, banner shows the mismatch. User picks truth or marks override.
#### "Place a pin-1 indicator" — for chips that don't have one
When chip-side detection finds nothing (the manufacturer didn't include a 3D pin-1 mark — common with bargain QFN STEPs and some BGA models), the tab shows a **Place pin-1 indicator** button. Three placement modes:
1. **Use footprint marker** (one-click default) — chipsmith projects the silkscreen pin-1 marker's pad position up to the chip's top surface and places the indicator at the body corner closest to that pad. Chip and footprint agree by construction.
2. **Click to place** — user clicks anywhere on the body's top face; chipsmith snaps to the nearest corner (or uses the click point if `Alt`-held). Live preview while hovering.
3. **Pick a corner** — quick-buttons for NE / NW / SE / SW with a small thumbnail showing the resulting position.
Shape options (radio):
- **Dimple** (default) — recessed cylinder, Ø 0.40 mm × 0.05 mm deep, slightly darker tint.
- **Raised dot** — protruding cylinder, Ø 0.40 mm × 0.05 mm tall, body color.
- **Corner chamfer** — angled cut on the body's top corner edge.
Per the new [`pin1-indicator` skill](../../../project/gallia/skills/pin1-indicator/SKILL.md), the default size + offset varies by package family (a tiny QFN-16 wants a 0.30 mm dimple inset 0.40 mm; a fat LQFP-128 wants 0.60 mm inset 1.0 mm). Defaults are derived from `package_family` + `body_dims_mm`.
Synthesized indicators are tagged `pin1_indicator_user_placed` (vs `pin1_indicator` for manufacturer-originals) so downstream tools can distinguish — but for sign-off purposes, both pass the same gate. The synthesized geometry is baked into `.chipsmith.step` via OCCT on sign-off, surviving every future load.
A small undo button removes the placed indicator if the user wants to retry.
**(New skill)** — `gallia/skills/pin1-indicator/SKILL.md`. Per package family: where pin-1 normally lives, what the dot shape is, common variants (dimple vs raised dot vs corner chamfer vs molded notch on SOIC), how to distinguish a real 3D pin-1 dot from a decorative texture map. Plus silkscreen conventions: dot, triangle, "1", pad-shape difference, polarity stripe.
## Hover-inspect HUD (cursor-following)
A small floating card that follows the cursor whenever it's over the 3D canvas. Names what the user is looking at, in plain words, **before** they have to click anything. The user moves the mouse, the HUD updates at 60fps, no tab switching required.
### Layout
```
┌────────────────────────────────┐
│ ⭐ pin-1 indicator │
│ kind cylinder · top │
│ size Ø 0.40 × 0.08 mm │
│ corner NE │
│ confidence high │
│ source named PRODUCT │
└────────────────────────────────┘
↗ cursor
```
Auto-positioned 12px down-right of cursor; flips left/up when near canvas edges so it never clips.
### Kind labels by category
The first line is a **plain-language kind label** so a non-AI user can instantly read what they're hovering. Color-coded by category for at-a-glance scanning:
| Object kind | Label shown | Tint |
|---|---|---|
| `body` | "chip body" | ⬛ |
| `pin1_indicator` | "pin-1 indicator" ⭐ | ⭐ amber |
| `smd_pad` (`pad_A1`, …) | "SMD pad — A1" | 🟦 teal |
| `smd_leg` (`lead_3`, …) | "SMD leg — pin 3" | 🟪 violet |
| `ball` (`ball_B7`, …) | "BGA ball — B7" | ⚪ silver |
| `thru_hole_pin` | "through-hole pin — 4" | 🔻 red |
| `thru_hole_tab` | "reinforcement tab" | 🔩 grey |
| `ep_thermal` | "exposed pad / thermal" | 🟧 orange |
| Footprint pad (overlay) | "footprint pad — A1" | 🟩 green |
| Footprint silk marker | "silkscreen pin-1 marker" | white |
| Faux PCB plate | "FR4 board (1.6mm faux)" | green-grey |
| Plated PTH (faux PCB) | "plated through-hole" | amber |
| NPTH (faux PCB) | "unplated mounting hole" | grey |
| Plated slot (faux PCB) | "plated slot — reinforcement" | amber |
| NPT slot (faux PCB) | "unplated slot" | grey |
| Solder-jet dome | "solder-jet dome — 0.30 mm" | 💧 light blue |
| Heatsink via | "heatsink via — 0.30 drill" | 🔩 cu-tone |
| Stock-library ghost pad | "stock library pad (ghost)" | translucent |
| Laser-etch text | "laser-etched MPN" | dark slate |
| User-placed pin-1 indicator | "pin-1 indicator (added by you)" 🆕 | ⭐ amber |
Categories are derived from `metadata.chipsmith.role` and `metadata.chipsmith.structural_type` baked into the GLB on detection / sign-off; non-chipsmith objects (sky-box, axis cube, gizmos, ground plane, anything in the deny-list from adom-step's inspector) suppress the HUD.
### Body content
The lines after the kind label are picked from the object's metadata, prioritized by what's most relevant for that category. Roughly:
- **Pads / pins / balls / legs**: size, center coords (mm relative to chip origin), material, mapped footprint pad ID + Δ, jet count, parent PRODUCT name.
- **Pin-1 indicator**: shape, dimensions, corner, confidence + reason.
- **Through-hole pin**: leg length below body, ≥1.6mm gate result, plated-or-not.
- **Footprint pad**: pad number, shape, size, layers, drill (if any), mapped chip feature.
- **Faux PCB hole**: hole type, drill diameter (or slot dimensions), plated/unplated, parent footprint pad.
- **Solder-jet dome**: drop diameter, position on pad's local frame, parent pad role.
- **Heatsink via**: drill, outer Cu, position relative to EP center, grid index (e.g., `5 of 9`).
- **Body**: bbox dimensions, volume, material name + RGB swatch, "click to isolate."
Three lines max per HUD by default — the user can pin the HUD (click → docks; second click → undocks) to see the full metadata blob in expanded form.
### Behaviour
- Throttled to one update per render frame (60fps cap); raycast under the cursor uses Babylon's `scene.pick` filtered to chipsmith-tagged meshes only.
- Suppressed during camera drag (orbit / pan / zoom) so it doesn't strobe while the user is just looking around.
- `Esc` hides it permanently for the session; toggle via `i` (or right-side toolbar `inspect` button).
- AI-drivable: `window.adomChipsmith.hoverAt({x, y})` returns the same payload the HUD shows; `window.adomChipsmith.lastHover()` returns the most recent. Demo scripts and tests both consume this.
### Per-package mode "explainer line"
When a non-trivial detection inference applies — Mode 1 surface-type clustering, Mode 2 face-merge, post-rescale, post-Y-up-rotate — the HUD adds a small italic "**how we know**" line:
> _detected by clustering 14 cylinders at z=0 (Mode 1)_
> _re-detected after auto-rotating 90° about X (file was Y-up)_
> _synthesized from 12 orphan faces sharing material (Mode 2 face-merge)_
So the user understands not just *what* this is, but *why* chipsmith calls it that. Trust comes from showing the work.
---
## Sign-off HUD (always visible)
A floating verification card pinned to the top-right of the canvas, **visible from every tab**. The user can see at a glance which gates are passed, which are pending, and which have failed — without leaving the tab they're on. Each row maps 1:1 to a gate and is clickable (click → jump to the relevant tab).
```
┌─ Sign-off checklist ─────────────────────── 9 / 17 ─────────┐
│ │
│ Inputs │
│ ☑ STEP file loaded VL53L8CX.step │
│ ☑ footprint loaded LGA-16.kicad_mod│
│ ☑ Datasheet loaded VL53L8CX.pdf │
│ ☑ Spec complete pin_count=16… │
│ │
│ STEP integrity │
│ ☑ Measurements match declared Δ < 0.1% │
│ ☑ Orientation Z-up native │
│ │
│ Detection │
│ ☑ Coverage 17 / 17 Mode 0 │
│ ☑ EP detected 1 × ep_thermal │
│ ─ Through-hole legs ≥1.6mm N/A (no THT) │
│ ─ Reinforcement tabs N/A │
│ │
│ Footprint │
│ ☑ Bbox-fit (chip ⊂ fp) max Δ 0.014 mm │
│ ◐ KiCad library cross-check fetching… │
│ │
│ Pin-1 │
│ ☑ Chip-side detected NE corner │
│ ☑ Footprint silk marker detected pad A1 │
│ ☑ Chip ↔ footprint agree pin 1 = A1 │
│ │
│ Manufacturing │
│ ☑ Heatsink vias generated 9 × 0.3mm grid │
│ ─ Laser-etch disabled (opt-in)│
│ ◐ Solder-jet layout legal v1.1 │
│ │
│ ─────────────────────────────────────────────────────────────│
│ ☐ Sign off this chip [ Sign off ▶ ] │
└────────────────────────────────────────────────────────────────┘
```
### Row states
| Glyph | Meaning |
|---|---|
| ☑ | Pass — gate satisfied |
| ☐ | Pending — gate not yet evaluated, or evaluation failed |
| ◐ | Partial / warning — gate runs but with a caveat (e.g., rescale applied, manual override) |
| ─ | Not applicable — manifest declared this gate doesn't apply (e.g., no THT pins → leg-length gate is `─`) |
| 🔒 | Locked — chip is signed off; gate is immutable until re-detect |
| ✗ | Failed — gate evaluated and rejected (red) |
### Behavior
- **Click a row** → switches to the tab that owns that gate, scrolls to the relevant control, flashes a brief outline so the user sees what they need to address.
- **Hover a row** → tooltip shows the underlying numeric reason ("body Δx=0.002, Δy=-0.001, Δz=0.003 — within ±5%").
- **Collapse button** → top-right corner. Collapsed state is a tiny chip badge `9 / 17 ✓` that stays pinned and re-expands on hover.
- **Drag** → the HUD is draggable to any corner; remembers position in `localStorage`.
- **Keyboard** → `s` toggles collapse, `S` jumps focus into the HUD.
### Sign-off button
The final row is the **Sign off this chip** button. It is **disabled** until every checkbox above it is ☑ or ─ (no ☐, no ◐ on hard gates, no ✗). Hovering the disabled button surfaces a tooltip listing exactly which gates are blocking ("3 gates pending: KiCad cross-check fetching, solder-jet not yet computed, ☐"). Clicking the button when enabled fires the same flow as the Sign-off tab below.
After sign-off, every row flips to 🔒 — the chip is now locked. A "Re-open for editing" link appears at the bottom; clicking it unlocks (with confirmation) and clears the locks so the user can re-validate.
### AI-drivability
Every row is exposed via JS: `window.adomChipsmith.signoff.gates()` returns the structured array; `window.adomChipsmith.signoff.gate('pin1_agreement')` returns the row state + reason; `window.adomChipsmith.signoff.canSignOff()` returns boolean + blocking-gate list. Demo scripts and CI both consume this.
---
### Tab 6 — Sign-off
Big green **Sign off this chip** button. Disabled until ALL of:
- **Tab 1 Source**: spec complete (`pin_count` + `structural_type` + `body_dims_mm` present)
- **Tab 2 Measurements**: detected body matches declared within ±5% (or post-rescale match flagged)
- **Tab 3 Detection**: coverage ≥ declared pin count
- **Tab 4 Footprint**: bbox-fit passes
- **Tab 5 Pin-1**: chip-side detected ≥ ambiguous, fp-side detected, agreement (or manual override)
On click:
1. **Bake `<mpn>.chipsmith.step`** (canonical) — POST to `service-step2glb /bake` with the original STEP + applied scale + applied rotation + per-solid role tags + pin-1 marker. OCCT/XCAF re-emits a STEP that has: LENGTH_UNIT normalized to mm, root rotated upright if Y-up, PRODUCT_DEFINITION names extended with role tags, XCAF named-data attributes per solid carrying structured `{role, structural_type, confidence, detected_via}`, and a document-level `chipsmith_signoff` attribute holding the full sidecar JSON.
2. **Derive `<mpn>.chipsmith.glb`** (shadow) — POST the baked STEP back through `/convert` so the GLB matches the canonical STEP exactly (same hierarchy, same materials, same units). Babylon renders this; nothing else reads it.
3. Write `<mpn>.chipsmith.json` sidecar (mirror of the `chipsmith_signoff` attribute embedded in the STEP, surfaced flat for tools that don't link OCCT):
```json
{
"mpn": "VL53L8CX",
"step_sha256": "…",
"step_meta_sha256": "…",
"applied_unit_scale": 1.0,
"applied_orientation_rotation_deg": 0,
"detected_pin_count": 17,
"detected_breakdown": { "smd_pad": 16, "ep_thermal": 1 },
"body_dims_mm_detected": [4.398, 5.401, 1.503],
"body_dims_mm_declared": [4.4, 5.4, 1.5],
"body_dims_match_within_pct": 0.06,
"pin1_chip": { "side": "top", "corner": "NE", "node": "pin1_indicator" },
"pin1_footprint": { "pad": "A1", "marker": "silkscreen_circle" },
"pin1_agreement": "agree",
"kicad_footprint_diff": { "pads_match": 17, "pads_off_by": 0, "pads_missing": 0, "max_xy_delta_mm": 0.012 },
"thru_hole_leg_lengths_mm": [],
"signed_at": "2026-05-05T17:34:21Z",
"signed_by": "[email protected]",
"chipsmith_version": "0.1.0"
}
```
4. Hard-confirm dialog: "Sign off VL53L8CX as 16 pads + 1 EP, pin-1 confirmed at NE corner, KiCad-stock matches?" — yes / cancel.
5. Update chip-fetcher's `info.json` with `"chipsmith_signed_at": "…", "chipsmith_step": "<path>", "chipsmith_glb": "<path>"`.
Until signed off, chip is "draft." Signed-off chips get a green badge in chip-fetcher's library view. Re-opening a signed chip parses the baked STEP's XCAF attributes, skipping detection / rescale / orient negotiation entirely — the sign-off survives.
**Downstream contract:** every Adom tool that consumes a chip-fetcher entry going forward MUST prefer `<mpn>.chipsmith.step` over the original `<mpn>.step` when present. Tools that don't link OCCT can read `<mpn>.chipsmith.json` instead of round-tripping through STEP. The shadow `.chipsmith.glb` is for Babylon viewers only; nothing else should read it.
### Tab 7 — Heatsink vias *(v1.0 — auto-generated under any EP)*
For any detected EP / `ep_thermal` / `lead_with_ep`, chipsmith auto-generates a square grid of 0.3mm-drill plated through-hole vias under the pad:
- **Drill**: 0.30 mm
- **Outer copper diameter**: 0.60 mm (annular ring 0.15 mm)
- **Pitch**: 1.20 mm (square grid; configurable via spec)
- **Edge margin**: 0.40 mm from pad edge (configurable via spec)
- **Layout**: regular `n × n` grid centered on the EP — `n = floor((ep_side - 2×margin) / pitch) + 1`
For a 3.2×3.2 mm EP that's `floor((3.2 - 0.8) / 1.2) + 1 = 3` → 3×3 = 9 vias.
For a 5.0×5.0 mm QFN-48 EP that's `floor((5.0 - 0.8) / 1.2) + 1 = 4` → 4×4 = 16 vias.
Each via is placed at the EP's local frame, plated with copper plating + annular pad on top + bottom. The vias are an **overlay layer** — they're NOT physically on the chip, they're chipsmith's pre-computed gift to the EE for when they design their board around this chip later. Toggleable in the hierarchy. The Sign-off sidecar embeds the via grid (positions + drill + pitch) so a downstream board-design tool can ingest them as a single "place this heatsink-via grid centered under U7" action.
The user's reaction we want: "oh, how nice, the AI gave me some heatsink vias under the EP."
Spec override fields (manifest, optional):
```json
"heatsink_vias": {
"enabled": true,
"drill_mm": 0.30,
"outer_cu_mm": 0.60,
"pitch_mm": 1.20,
"margin_mm": 0.40
}
```
Defaults applied when the field is absent.
### Tab 8 — Solder-jet *(v1.1)*
300µm half-sphere domes per pad per the [`solder-jetting` skill](../../../project/gallia/skills/solder-jetting/SKILL.md) lines 32, 49, 50, 62-77 (intra-pad ≥100µm edge, inter-pad ≥300µm edge, grid floor-division). Toggleable layer.
## Manifest schema — `chipsmith-spec.json` (or fields inside `info.json`)
```json
{
"mpn": "VL53L8CX",
"manufacturer": "STMicroelectronics",
"package_family": "LGA-16",
"pin_count": 16,
"structural_type": "lead_with_ep",
"has_ep": true,
"has_thru_hole_pins": false,
"has_reinforcement_tabs": false,
"expected_pitch_mm": 0.5,
"body_dims_mm": [4.4, 5.4, 1.5],
"expected_pin1_kind": "dimple_top_corner",
// Stock-library lookup keys. The KiCad library is footprint-keyed and
// 3D-chip-name-keyed — NOT MPN-keyed. So chipsmith MUST receive these from
// chip-fetcher (which gets them from manufacturer / SnapEDA / SnapMagic /
// Ultra Librarian / Component Search Engine pages, all of which carry the
// package-family name). MPN alone is not enough to look up the stock entry.
"footprint_name": "Package_LGA/LGA-16_4.4x5.4mm_Pitch0.5mm", // KiCad footprint library path
"kicad_3d_name": "Package_LGA.3dshapes/LGA-16_4.4x5.4mm_Pitch0.5mm", // KiCad packages3d STEP path
"package_kind": "LGA", // canonical short tag — SOIC | QFN | QFP | LGA | BGA | DIP | SOT | TO | USB-C | …
"stock_footprint_id": "Package_LGA/LGA-16_4.4x5.4mm_Pitch0.5mm", // legacy alias for footprint_name
"laser_etch": {
"enabled": false,
"text": "VL53L8CX", // defaults to the MPN
"depth_mm": 0.05,
"max_size_mm": [3.0, 0.6], // auto-fits within these bounds; chipsmith picks the largest font that fits
"position": "auto" // "auto" centers and avoids the pin-1 indicator; or {x,y} in body local frame
},
"user_placed_pin1": {
"enabled": false, // true when manufacturer STEP had no pin-1 indicator and user placed one
"shape": "dimple", // dimple | raised_dot | corner_chamfer
"diameter_mm": 0.40,
"depth_or_height_mm": 0.05,
"corner": "NE", // NE | NW | SE | SW | custom
"custom_xy_mm": null // when corner == custom
}
"datasheet_pdf": "VL53L8CX.pdf"
}
```
`structural_type` enum: `smd_leg` (SOIC/QFP/SOT), `smd_pad` (QFN/LGA/DFN), `ball` (BGA), `thru_hole_pin` (DIP/PDIP), `thru_hole_tab` (USB-C reinforcement), `hybrid_smd_thru` (USB-C with both), `ep_thermal` (only EP), `lead_with_ep` (QFN with center EP).
**Recommended location**: extend `chip-fetcher`'s `info.json` (additive — old files stay valid). chip-fetcher learns to populate at fetch time when it has the data; otherwise leaves null and chipsmith prompts the user.
### Upstream provenance — where each field comes from
The chip-fetcher → chipsmith handoff carries a strict provenance chain. **chipsmith never tries to invent footprint identity**; the upstream source already knew it because it was scraped from a page that named it. Here's the responsibility split:
| Field | Source | How chip-fetcher derives it |
|---|---|---|
| `mpn` | manufacturer | URL slug or product page H1 |
| `manufacturer` | manufacturer | site domain / page metadata |
| `package_family` (`"LGA-16"`, `"QFN-32"`, `"SOIC-8"`, …) | manufacturer page OR distributor parametric search | Mouser/DigiKey/JLCPCB parametric "Package / Case" field; ST/TI/NXP product-tab "Package" cell |
| `package_kind` (canonical short — `LGA` / `QFN` / `SOIC` / etc.) | derived from `package_family` | regex normalize (`LGA-16` → `LGA`) |
| `pin_count` | manufacturer datasheet OR distributor parametric "Number of Terminations" | datasheet PDF text extract; distributor parametric scrape |
| `body_dims_mm` | manufacturer datasheet (mechanical drawing page) | PDF page text extract — the "Dimensions" or "Mechanical Drawing" page typically carries `nominal × nominal × nominal mm` |
| `expected_pitch_mm` | manufacturer datasheet | same mechanical drawing page |
| `has_ep` | manufacturer datasheet | "Exposed Pad" / "EPAD" / "Thermal Pad" mentions in pin-table or mechanical |
| `expected_pin1_kind` | per the [`pin1-indicator`](../../../project/gallia/skills/pin1-indicator/SKILL.md) skill table by package_kind | lookup |
| `footprint_name` | upstream library that supplied the `.kicad_mod` | **always** preserved by the scrape: SnapEDA / SnapMagic / Ultra Librarian / Component Search Engine / Mouser CAD / DigiKey CAD all expose the footprint name on the download page. KiCad's stock library uses standard names like `Package_QFN/QFN-32-1EP_5x5mm_P0.5mm_EP3.7x3.7mm`. |
| `kicad_3d_name` | upstream library that supplied the `.step` | same scrape — Ultra Librarian / SnapMagic give you the canonical `.step` path. For chips that came from KiCad's stock library directly, this is the `Package_<family>.3dshapes/<name>.step` path. |
**Hard rule**: chip-fetcher MUST persist `footprint_name`, `kicad_3d_name`, and `package_kind` into `info.json` at fetch time. chipsmith refuses to do stock-library cross-check without them — there's no MPN→KiCad-library mapping in service-kicad, and inventing one would silently mismatch.
When chip-fetcher pulls from a source that DOESN'T expose the footprint name (rare — only some bargain SnapEDA pages), it leaves the fields null and chipsmith prompts the user to fill them in or pick a closest match from the KiCad library browser (v1.1).
### Decorative geometry — branch, never strip
Many manufacturer STEP files carry **marketing geometry** alongside the canonical chip body — vendor logos embossed on the chip top, decorative ornamentation on connector housings, raised "ST" / "TI" / "NXP" lettering, lens-bezel cosmetic features. **chipsmith preserves all of this.** It's potentially load-bearing (some EE workflows need the laser-etched manufacturer mark to verify authenticity at goods-receipt) and tossing it would be a one-way data loss.
Instead, on `/bake` chipsmith **moves** the decorative products into a `chipsmith_decorative` branch of the STEP scene graph:
```
<MPN>.chipsmith.step
├── chipsmith_canonical (the validated, role-tagged chip — body + pads + EP + pin-1 …)
└── chipsmith_decorative (the moved-aside marketing / lens-cosmetic / vendor-mark geometry)
```
The downstream contract:
- **Default visibility OFF** for the `chipsmith_decorative` branch in every Adom tool that renders the chip (chipfit, adom-tsci, InstaPCB preview, etc.). Cleaner default rendering, no clutter.
- **Toggleable on** via a single visibility flag — the user can flip it back on whenever needed.
- **Round-trips** through service-step2glb's `/convert` because XCAF carries the assembly tree intact through to glTF; downstream Babylon code just hides nodes whose parent is `chipsmith_decorative`.
Detection of "decorative" candidates (the products that get moved):
1. Products NOT classified by Mode 0 / 1 / 2 detection (the residual NAUOs that today get tagged `lens_or_connector` / `subassembly`).
2. Products whose name matches `/logo|brand|trademark|copyright|^[A-Z]{2,3}$/i` (manufacturer-mark patterns).
3. User-flagged products via the hierarchy's right-click → "Move to decorative branch" context-menu action.
The chipsmith bake-meta JSON gains a `decorative_product_ids: [int]` field. When `/bake` receives this list, it reparents those XCAF labels under a new `chipsmith_decorative` assembly node before re-emitting STEP. **No products are deleted** — moving only.
### Multi-source footprint cross-validation — the trust signal
Chip-fetcher already pulls **four independent footprint sources** for each chip (or can; this section codifies the requirement). chipsmith renders all four into the scene as separately-branched overlays in different tints, then runs a per-pad cross-validation. **Agreement across all four is the strongest possible "chipsmith nailed this; user doesn't need to review further" signal.** Disagreement is a high-value warning — it's almost always either a footprint authoring bug in one of the EDA libraries OR a real chip-vs-datasheet mismatch worth flagging.
The four sources:
| Source | Where it comes from | File on disk (in chip-fetcher dir) | Branch in baked STEP |
|---|---|---|---|
| **Datasheet-derived** | chip-fetcher generates from the manufacturer datasheet's mechanical drawing page (PDF text + line extraction → KiCad-style pad list) | `<MPN>.datasheet-fp.kicad_mod` | `chipsmith_overlay_datasheet` |
| **KiCad library** | service-kicad library lookup by `footprint_name` | `<MPN>.kicad_mod` (already today) | `chipsmith_overlay_kicad` |
| **Fusion 360 library** | chip-fetcher's `.lbr` parse (SnapEDA / SnapMagic / Ultra Librarian) | `<MPN>-fusion.lbr` (already today; needs per-pad parser) | `chipsmith_overlay_fusion` |
| **Altium library** | chip-fetcher's `.IntLib` / `.PcbLib` parse | `<MPN>.IntLib` / `<MPN>.PcbLib` (already today; needs per-pad parser) | `chipsmith_overlay_altium` |
**Required upstream fields** (chip-fetcher persists into `info.json`):
```json
{
"footprint_sources": {
"datasheet": { "path": "VL53L8CX.datasheet-fp.kicad_mod", "derived_from": "datasheet PDF page 47 mechanical drawing", "confidence": "high|medium|low" },
"kicad": { "path": "VL53L8CX.kicad_mod", "library": "Package_LGA/LGA-16_4.4x5.4mm_Pitch0.5mm" },
"fusion": { "path": "VL53L8CX-fusion.lbr", "package_node": "LGA16_440x540_E_05" },
"altium": { "path": "VL53L8CX.PcbLib", "package_node": "LGA16-N_4P40X5P40MM" }
}
}
```
When a source is unavailable (e.g., chip-fetcher couldn't reach SnapEDA), the field is `null` and chipsmith renders only the available overlays + downgrades the agreement gate to "partial" instead of "fail."
### Cross-validation algorithm
For every pair of available sources `(A, B)` and every pad number that exists in both:
1. **Position delta**: distance between A's pad center and B's pad center, in mm.
2. **Size delta**: max of `|w_A - w_B|`, `|h_A - h_B|` in mm.
3. **Shape delta**: `1` if shape strings disagree (`rect` ≠ `oval`), else `0`.
4. **Layer delta**: count of layer assignments that differ (`F.Cu` vs `*.Cu`, etc.).
5. **Drill delta** (PTH only): `|drill_A - drill_B|` in mm, +1 if shape (round vs slot) disagrees.
Tolerances (mostly from the chipsmith spec):
- Position ≤ `expected_pitch_mm × 0.10` (10% of pitch)
- Size ≤ 0.05 mm
- Shape = exact match
- Layers = exact match (canonical-set normalized — `*.Cu` matches `F.Cu+B.Cu`)
- Drill ≤ 0.02 mm
Any pad whose A↔B comparison exceeds tolerance is **flagged**.
### Result UX
The `chipsmith_overlay_*` branches all render simultaneously in the scene:
- **Datasheet-derived** — bright orange ghost (the canonical truth)
- **KiCad** — translucent green ghost
- **Fusion 360** — translucent blue ghost
- **Altium** — translucent purple ghost
Then in Tab 4 the cross-validation matrix renders:
```
datasheet kicad fusion altium
datasheet ● ✓ ✓ ✓
kicad ● ✓ ✓
fusion ● ⚠ pad 7 +0.18mm
altium ●
Per-pad max Δ: 0.018 mm (pad 12, kicad↔fusion)
```
**All cells green** → the badge in the sign-off HUD reads "🎯 4-way footprint agreement — chipsmith nailed this chip." The user's mental load drops to near zero; they can sign off with strong confidence the chip is correctly characterized across every EDA tool downstream.
**Any cell red** → the offending pair gets a banner with the per-pad detail. The user is offered three actions:
- "Trust the datasheet" — adopt the datasheet-derived footprint as authoritative; the disagreeing EDA-lib branches stay rendered but with a "deviation" tag.
- "Trust the EDA lib" — accept the EDA-side data; flag the datasheet-derived footprint as needing regeneration (chip-fetcher gets a hint to retry the PDF parse).
- "Manual fix" — open the offending footprint file in the user's EDA tool to fix and re-fetch.
### Sign-off gates (added to the existing 17)
- `fp_datasheet_loaded` — datasheet-derived footprint exists
- `fp_kicad_loaded` — KiCad-library footprint exists
- `fp_fusion_loaded` — Fusion-library footprint exists
- `fp_altium_loaded` — Altium-library footprint exists
- `fp_cross_agree` — pairwise cross-validation passes for every available source pair (highest-trust signal)
Hard gate `fp_cross_agree` blocks sign-off when any pair disagrees beyond tolerance, unless the user has explicitly picked "Trust the datasheet" or "Trust the EDA lib" override (which records the override + reason in the `.chipsmith.json` sidecar).
### chip-fetcher's new responsibility — datasheet-derived footprint
Chip-fetcher already grabs the datasheet PDF. It now also needs to **derive a footprint from the mechanical drawing page** and persist it as `<MPN>.datasheet-fp.kicad_mod`. Steps:
1. Locate the datasheet's mechanical drawing page (PDF page that contains "Mechanical Drawing" / "Package Drawing" / "Dimensions" headings + the footprint outline + dimensioned arrows).
2. Extract the package outline + pad positions + pad sizes via PDF text + vector extraction (pdfminer.six for text; pdf2image + classic CV or LLM-assisted vision for the geometry).
3. Generate a `.kicad_mod` from those measurements (KiCad's s-expr is the canonical interchange format chipsmith uses; Fusion/Altium consumers can convert from KiCad).
4. Persist alongside the other footprints. Tag with `derived_from: "datasheet PDF page N"` so the provenance is traceable.
This is its own chip-fetcher feature flag (per-chip toggle) and likely needs a vision-assisted pipeline for accuracy; chip-fetcher can fall back to "no datasheet-derived footprint" gracefully and chipsmith just won't render that branch.
### Stock-library cross-check — keyed on footprint name, not MPN
The KiCad library is keyed by **footprint name** and **3D model name**, not by manufacturer part number. service-kicad exposes:
- `GET /footprints/<library>/<name>.kicad_mod` — fetches a footprint by library + name
- `GET /models/<library>.3dshapes/<name>.step` — fetches a STEP by library + name
So chipsmith's stock-library overlay (Tab 4 second pane, v1.1) takes `footprint_name` + `kicad_3d_name` from the spec, calls those two endpoints, renders the result as a translucent ghost. There is no `?mpn=…` lookup — that's the responsibility of upstream chip-fetcher to resolve and persist into `info.json`.
**Terminology rule for the UI:** the user passes a "footprint file" — full stop. Internally chipsmith parses `.kicad_mod`, but every visible label, tooltip, error message, button, popover, and CLI hint just says "footprint." The "stock library overlay" is the authoritative library footprint we ghost on top — never labeled as "KiCad" in the UI. Adom users come from Altium / Fusion 360 / Eagle backgrounds and shouldn't feel like they wandered into a KiCad-only tool. We pick KiCad's `.kicad_mod` as the implementation format because the AI handles it well; that's an internal decision, not a user-facing one. The word "KiCad" only appears in:
- file extension hints (when accepting a file: `.kicad_mod`)
- developer docs / SKILL.md (so future AI sessions know what format to write)
- internal field names in JSON (`stock_footprint_id` resolved via the KiCad library indirectly)
`pin_count`, `structural_type`, AND `body_dims_mm` are **required**. Without `body_dims_mm` we can't run the measurement check, which means we can't trust the STEP's units, which means every downstream measurement is suspect.
## What we build new (and what's foundational vs derived)
**Foundational (must exist before chipsmith works at all):**
1. **service-step2glb extension** — new `/step-meta` endpoint that emits `step-meta.json` (PRODUCT tree, per-face surface type + bbox + parent solid, declared unit + mm-conversion factor, materials). This is THE enabling change. Without it, chipsmith is back to GLB triangle inspection. Lives in `/home/adom/project/service-step2glb/` — extend, version-bump, redeploy.
2. **Measurement check + auto-rescale** — Tab 2 logic. Records the applied scale in the sign-off sidecar so re-loads skip the negotiation.
3. **Mode 0 (named PRODUCT walk)** — reads `step-meta.json`, matches against the regex set, scores coverage.
4. **Mode 1 (surface-type cluster)** — the new approach the user is asking for: cluster faces by surface type to identify balls vs pads vs pins vs EPs. **Only possible because we read STEP-native data.**
**Derived (built on the foundation):**
5. **Y-up auto-retry** — second-pass detection with root rotated 90° about X.
6. **Through-hole leg-length validation** — check ≥1.6mm on detected `thru_hole_pin` after applied scale.
7. **Sign-off UX** — button + confirmation + JSON sidecar + GLB role-bake.
8. **service-kicad library overlay** — fetch + render KiCad's stock entries.
9. **Pin-1 indicator skill** (`gallia/skills/pin1-indicator/SKILL.md`) — research-backed conventions per package family.
10. **Pin-1 chip ↔ silkscreen agreement check**.
11. **Multi-page datasheet inline viewer** — PDF.js embed in Tab 1.
## Files to create
```
adom-chipsmith/
├── Cargo.toml # clap, tiny_http, ureq, serde, sha2, ctrlc, image
├── VERSION # 0.1.0
├── src/
│ ├── main.rs
│ ├── cli/
│ │ ├── mod.rs # ok/err/hint, DEFAULT_PORT=8871
│ │ ├── view.rs # view <step> --footprint <kicad_mod> --spec <chipsmith-spec.json> --datasheet <pdf>
│ │ ├── view_library.rs # view-library <chip-fetcher-dir>
│ │ ├── start.rs / stop.rs / status.rs / health.rs
│ │ ├── install.rs
│ │ ├── eval.rs / console.rs
│ │ ├── detect.rs # runs Mode 0/1/2 pipeline, prints JSON
│ │ ├── measure.rs # measurement check (and auto-rescale negotiation)
│ │ ├── verify_footprint.rs
│ │ ├── pin1.rs
│ │ ├── sign_off.rs
│ │ └── kicad_diff.rs
│ ├── server/
│ │ ├── mod.rs # tiny_http + ServerState + routes
│ │ ├── convert.rs # service-step2glb proxy
│ │ ├── step_meta.rs # NEW — calls service-step2glb /step-meta, parses, caches
│ │ ├── kicad.rs # service-kicad client
│ │ └── cache.rs # /tmp/adom-chipsmith-cache/
│ ├── kicad_mod.rs # minimal .kicad_mod parser (pads only)
│ ├── orient.rs # Y↔Z auto-retry
│ ├── measure.rs # auto-rescale negotiation (mm/m/inch/cm/µm)
│ ├── face_merge.rs # Mode 2 fallback
│ ├── detect_modes.rs # Mode 0/1/2 coordinator
│ ├── pin1.rs # pin-1 detection
│ ├── solder_jet.rs # 300µm dome layout
│ ├── manifest.rs # chipsmith-spec.json schema + I/O
│ └── assets/
│ ├── shell.html # 7-tab sidebar + canvas + role HUD + sign-off dialog
│ ├── adom.css # canonical Adom tokens
│ ├── icon.svg
│ └── js/adom-3d-viewer.min.js
├── vendor/
│ ├── 3d-viewer/ # vendored copy of adom-3d-viewer-babylon9
│ └── build-viewer.sh
├── wiki/{page.json, body.md, hero.png}
├── SKILL.md
├── BUILD-SKILL.md
├── publish.sh
└── README.md
```
Plus, in upstream repos:
- `service-step2glb/` — new `/step-meta` endpoint (OCCT XCAF dump as JSON). Version bump.
- `chip-fetcher/src/library.rs` — additive schema fields in `info.json`. chip-fetcher populates what it can at fetch time.
- `gallia/skills/pin1-indicator/SKILL.md` — new skill.
- `gallia/skills/adom-chipsmith/SKILL.md` — usage skill.
- `gallia/skills/adom-chipsmith-build/SKILL.md` — release playbook.
## v1 scope
**Must-have (v1.0):**
- service-step2glb `/step-meta` endpoint shipped + redeployed.
- Drop zone for `.step` + `.kicad_mod` + `chipsmith-spec.json` (+ optional `.pdf`).
- Tab 1 Source.
- Tab 2 Measurements — direct compare + ÷25.4 + ÷1000 rescale negotiation. Record applied scale.
- Tab 3 Detection — Mode 0 + Mode 1 (Mode 2 fallback) + Y-up auto-retry + role tagging + pin-reveal strobe animation on first success.
- Tab 4 Footprint — bbox-fit + 1.6mm PCB plate (with plated-PTH / NPTH / plated-slot / NPT-slot rendering). (Stock-library overlay v1.1.)
- Tab 7 Heatsink vias — auto-generated 0.3mm grid under any detected EP (1.2mm pitch, configurable).
- Tab 5 Pin-1 — chip-side + fp-side + agreement check.
- Tab 6 Sign-off — button + JSON sidecar + role-tagged GLB bake. ALL gates wired (measurements among them).
- AI-drivability for every UI verb.
- SKILL.md + wiki publish.
**Deferred to v1.1+:**
- Tab 8 Solder-jet (algo lives in `solder_jet.rs` from v1; the UI rendering is the stretch).
- Stock-library overlay (Tab 4 second pane — service-kicad-backed under the hood).
- Through-hole leg-length validation gate (≥1.6mm enforced on `thru_hole_pin`).
- Hybrid USB-C visualization.
- Multi-MPN batch sign-off.
- Datasheet auto-extract (read pin count, package, pin-1 from PDF — saves the user from typing the spec).
## Verification plan
End-to-end in gallia per the [test-in-gallia memory](../projects/-home-adom-project/memory/feedback_test_in_gallia_as_user.md):
1. **Z-up baseline** — `adom-chipsmith view chip-fetcher/library/VL53L8CX/...` (LGA-16 + EP, well-formed STEP). Mode 0 finds 16 pads + 1 EP from PRODUCT names. Measurements pass within 0.1%. Bbox-fit passes. Pin-1 chip-side + fp-side agree. Sign-off enables. Click → `VL53L8CX.chipsmith.json` + `VL53L8CX.chipsmith.glb`.
2. **Measurement rescale** — pick (or synthesize) a STEP whose declared unit is wrong. Possible sources:
- SnapEDA-fetched STEPs that came in inches but say "mm" in the header.
- Some Ultra Librarian outputs in metres.
Tab 2 detects the mismatch, tries ÷25.4, ÷1000, ×25.4, ×1000, picks the one that matches declared `body_dims_mm` within ±5%. HUD shows "auto-rescaled by 1/25.4 (file was inches not mm)." Sign-off allowed; sidecar records the applied scale. Reload skips negotiation.
3. **Y-up retry** — pick a chip flagged in `nuances-report.md` as "Y-as-height" (BME688, ESP32-S3-WROOM-1-N4, nRF52840-CKAA). Detection fails Z-up, auto-retries Y-up, succeeds. HUD shows the rotation.
4. **Mode 1 surface-type — BGA** — find or fetch a BGA chip. `step-meta.json` reports many CYLINDER faces with small radii grouped at z=0. Mode 1 clusters them into balls; count matches `pin_count`.
5. **Mode 1 surface-type — QFN** — VL53L8CX is LGA so this works the same; for QFN pick something like CC2640R2FRGZR. Many small horizontal PLANE faces at z=0 → pads.
6. **Mode 2 face-merge fallback** — pick a STEP that has a flat scene (one solid, no sub-products) AND mixed surface types in a way Mode 1 can't disambiguate. Mode 2 union-finds by (material, adjacency). Coverage ≥ declared.
7. **Through-hole** — `adom-chipsmith view chip-fetcher/library/PMOD-ESP32/...` or WAGO 2601-3105. Detect 12 thru_hole_pins. Each ≥1.6mm. PCB plate CSG-drilled.
8. **USB-C hybrid** (v1.1) — fetch a USB-C connector. Detect smd pads + thru-hole reinforcement tabs separately. `structural_type = hybrid_smd_thru`.
9. **Pin-1 mismatch** — manually edit a footprint to put silkscreen dot on pad A4 instead of A1. Tab 5 banners "pin-1 mismatch." User can override.
10. **Sign-off persistence** — sign off VL53L8CX, restart chipsmith, reload. Status pills all green; detection skipped; role tags + applied scale loaded straight from baked GLB.
11. **service-kicad overlay** (v1.1) — chip with `kicad_lib_name` set. Tab 4 fetches stock footprint + STEP, renders translucent ghost, per-pad delta.
12. **Solder-jet** (v1.1) — Tab 7 toggles 300µm domes; spacing rules verified by zoom.
13. **API surface** — `adom-chipsmith eval "return Object.keys(window.adomChipsmith)"` lists every UI verb.
14. **Performance gate** — a 50-pin LQFP detects + tags in ≤2s after `step-meta.json` arrives.
## Critical files
- [adom-step/src/assets/shell.html](../../../project/adom-step/src/assets/shell.html) — viewer template (lines 600-787 = scene-walker reference, but **chipsmith does NOT copy this for detection** — only for rendering).
- [adom-step/src/server/mod.rs](../../../project/adom-step/src/server/mod.rs) — tiny_http server template + route table.
- [adom-step/src/server/convert.rs](../../../project/adom-step/src/server/convert.rs) — service-step2glb proxy with SHA cache.
- [service-step2glb/api/server.py](../../../project/service-step2glb/api/server.py) — `/convert` contract; **we add `/step-meta` here**.
- [chip-fetcher/library/VL53L8CX/info.json](../../../project/chip-fetcher/library/VL53L8CX/info.json) — manifest schema baseline; we extend this.
- [chip-fetcher/src/library.rs](../../../project/chip-fetcher/src/library.rs) — `Entry` struct that needs the new fields.
- [service-kicad/docs/service-kicad-api.md](../../../project/service-kicad/docs/service-kicad-api.md) — `/footprints/<path>`, `/models/<path>` endpoints we'll call from `kicad.rs`.
- [gallia/skills/solder-jetting/SKILL.md](../../../project/gallia/skills/solder-jetting/SKILL.md) — 300µm dome spec + spacing rules (lines 32, 49, 50, 62-77).
- [gallia/skills/electrical-engineering/SKILL.md](../../../project/gallia/skills/electrical-engineering/SKILL.md) — package family table (123-145), pin-1 conventions (357-379).
- [gallia/skills/adom-app-header/SKILL.md](../../../project/gallia/skills/adom-app-header/SKILL.md) — header layout, popover with verified-working wiki + GitHub links.
## Decisions to confirm before/during build
1. **Where does `chipsmith-spec` live?**
- Recommended: **fields inside `info.json`** + chip-fetcher learns to populate. One source of truth, no sidecar to forget.
- Alternative: separate `<mpn>.chipsmith-spec.json`.
2. **`step-meta.json` lives where?**
- Recommended: cached at `/tmp/adom-chipsmith-cache/<sha256>.step-meta.json` next to the GLB. Not committed alongside source `.step`.
- Alternative: written next to source `.step` so chip-fetcher can persist it across machines.
3. **Pin-1 disagreement default behavior?**
- Recommended: **block sign-off** with manual-override checkbox.
- Alternative: warn but allow.
4. **Measurement rescale tolerance?**
- Recommended: ±5% on every axis (forgiving enough for tessellation rounding, tight enough to catch real wrong-unit cases).
- Alternative: ±1% or ±10%.
5. **service-step2glb `/step-meta` — emit alongside `/convert` (one call returns both) OR as a separate call?**
- Recommended: **separate `/step-meta`** so existing `/convert` callers (adom-step, step2glb CLI) aren't broken. New endpoint, additive.
- Alternative: extend `/convert` response with a new optional field.
If you accept the recommendations above, I'll proceed with that shape. Otherwise call out which one to flip.