← Commit history

Publish 2.1.0

barrett-land ·5bde39041d ·2mo ago ·parent 3e870b3
7 files changed +589−118
README.md+55−16
@@ -1,18 +1,57 @@ # altium-export-molecule -Adapter skill: Altium Designer board → Adom wiki molecule → Hydrogen.--There is no Altium bridge, so the supported route converts once through-**KiCad 10's native Altium importer** (driven on the laptop via the-adom-desktop KiCad bridge), adds the molecule furniture (MP1-MP4 markers +-corner machine pins, locally-resolving 3D models), and then runs the-`kicad-export-molecule` pipeline end to end: OCCT molecule conversion-(anchored GLB + footprint + symbol), `adom-wiki pkg publish`, and-`adom-molecule-import` into Hydrogen.--Install: `adom-wiki pkg install adom/altium-export-molecule`-(pulls `adom/kicad-export-molecule`, which pulls adom-desktop + adom-molecule).--Siblings: `kicad-export-molecule`, `fusion-export-molecule`,-`adom-molecule-import`. Footprint libraries (.PcbLib) are a different job —-see `altium-pcblib`.+Take an Altium Designer board to a GLB that actually looks like the board — pads, silkscreen and+all — and on to a wiki molecule.++![Altium board exported to GLB with pads and silkscreen](docs/hero.png)++## The thing everyone gets wrong++**Altium's STEP export contains the board substrate and component 3D bodies. Nothing else.**+No copper, no silkscreen. Convert it on its own and you get a bare green slab with no pads and+no designators, and it is not obvious why.++Copper and silk have to be rebuilt from Gerbers and composited onto the board's top face:++- **silkscreen** = `.GTO`+- **exposed pads** = `.GTL` ∩ `.GTS` — `.GTS` is `TF.FilePolarity,Negative`, so its features are+  the mask *openings*. Using all of `.GTL` paints the covered ground pour gold (~76% of the+  board) instead of the ~8% that is really exposed metal.++`make_overlay.py` does that composite, aligned to the `.GM1` board rect so it registers exactly.++## Which path to use today++| Goal | Status |+|---|---|+| GLB / render of an Altium board (pads + silkscreen) | ✅ proven end to end |+| Full molecule (anchored GLB + footprint.json + symbol.json) | ⚠️ blocked — Stage ② needs `.kicad_pcb` emit from `altium-pcbdoc`; use the v1 KiCad-importer route |++## Getting the files out of Altium++| What | How | Headless? |+|---|---|---|+| STEP | OutJob with an `ExportSTEP` output | ❌ one human click on *Generate content* |+| Gerbers | OutJob with a `Gerber` output | ✅ fully headless via `altium_run_outjob` |++There is **no** headless STEP-export process in Altium. `Pcb:ExportSTEP` does not exist, and+because Altium silently ignores unknown process ids, calling it reports success and writes+nothing. The real `PCB:Export` is index-based and opens a file dialog. This is settled — do not+go hunting for a process id.++## Convert++```bash+python3 make_overlay.py --gerber-dir gerber --out overlay_top.png+step2glb convert board.step -o board.glb --molecule --silk-top overlay_top.png --compress quantize+```++`--silk-top` is **silently ignored** without `--molecule` (the GLB comes back byte-identical).+Check the response says `silkscreen_applied: true`. `--compress quantize` avoids the Draco+default, which needs a decoder plain three.js viewers do not have.++The overlay renders coplanar with the board face, so it z-fights: in your viewer set+`polygonOffset` and `depthWrite = false` on the `silkscreen_top` material, and keep the camera's+near/far tight (`0.05 / 20`, not `0.001 / 100`).++See [SKILL.md](SKILL.md) for the full stage-by-stage recipe and the transfer gotchas.
SKILL.md+365−52
@@ -1,71 +1,384 @@ --- name: altium-export-molecule-description: Ship an Altium Designer board to the Adom wiki as a molecule — route the .PcbDoc through KiCad 10's native Altium importer on the user's laptop, make it molecule-ready (markers + machine-pin 3D models), then follow kicad-export-molecule / molecule-publish for the OCCT convert, wiki publish, and Hydrogen import. Use when the user says "altium molecule", "publish my altium board", "altium to hydrogen", "altium to the wiki", "PcbDoc molecule", "export molecule from altium". For KiCad sources see kicad-export-molecule; for Fusion see fusion-export-molecule.-user-invocable: true-argument-hint: "<.PcbDoc path> [--owner <hydrogen owner>]"+description: >+  Direct Altium → OCCT molecule path. Takes an Altium Designer board (.PcbDoc)+  that carries Adom molecule furniture (MP1–MP4 markers + corner machine pins+  from adom-altium-library-importer), exports a native STEP with the 3D+  machine-pin/contact/marker models baked in, derives the board metadata OCCT+  needs, and POSTs both to the step2glb /convert endpoint in molecule mode.+  Returns the anchored GLB + per-pad footprint/symbol JSON, then hands off to+  adom/molecule-publish for packaging + wiki publish. This REPLACES the interim+  KiCad-importer pass-through — no .PcbDoc → KiCad round-trip.+  Trigger words: altium molecule, altium export molecule, .PcbDoc molecule,+  molecule from altium, altium step2glb, altium occt, direct altium molecule,+  altium board to molecule, machine pin altium, MP marker altium, adom molecule+  altium, convert altium board molecule. --- -# altium-export-molecule — Altium board → KiCad → wiki molecule+# altium-export-molecule — the direct Altium → OCCT molecule path -> **Status: adapter — direct-path redesign underway (owner: Barrett).** The-> KiCad pass-through below works today but is interim; the target flow is-> Altium-native (his Adom library importer + native STEP export + an Altium-> board-metadata importer) with no KiCad in the loop. This page will be-> rewritten when that lands.->-> **Original adapter note.** There is no Altium bridge and the molecule board parser-> (molecule-metadata) reads `.kicad_pcb`/`.brd`, not `.PcbDoc`. The supported-> route is: **convert to KiCad once, then run the KiCad flow.** This skill-> covers the conversion + molecule-readiness fixups; everything after that is-> [`kicad-export-molecule`](https://wiki.adom.inc/adom/kicad-export-molecule).+This skill owns **stages 1–2** of the molecule pipeline for boards designed in+**Altium Designer**: -## Stage 0 — Preflight+```+Altium (.PcbDoc, molecule furniture placed)+  │  ① native STEP export (models baked in)   ← this skill+  │  ② board metadata for OCCT                 ← this skill+  ▼+step2glb /convert?molecule=true               ← this skill calls it+  │  MP-marker anchoring · gold machine-pin material · silk overlay ·+  │  footprint+symbol JSON (per-pad wire-attach Z from geometry)+  ▼+GLB + footprint.json + symbol.json  ──▶  adom/molecule-publish  (NOT this skill)+                                          └▶ adom-molecule-import → Hydrogen+``` -- `adom-desktop ping` works and the **KiCad bridge** is installed (the-  conversion runs in KiCad on the laptop; Altium itself is never driven).-- **KiCad 10** on the laptop — its Altium importer is what we use.-- Have the `.PcbDoc` (and `.SchDoc` if a symbol is wanted) on the laptop.+It **does not** package or publish — that is `adom/molecule-publish`. This skill+ends the moment the OCCT job artifacts are pulled down. -## Stage 1 — Import the Altium board into KiCad (laptop)+It supersedes the v1 `altium-export-molecule` that routed `.PcbDoc` through KiCad 10's+Altium importer — but see **Which path to use today**: the export/render half is proven,+while full molecule metadata still needs the v1 fallback. -KiCad's native importer (GUI, driven via the bridge): **File → Import →-Non-KiCad Board File → Altium** on the `.PcbDoc`, then save as a KiCad-project. `kicad-cli` has no headless project-import, so this step goes-through the KiCad bridge (`kicad_open_*` + window verbs) or the user does it-by hand once.+---++## Which path to use today++This skill supersedes the KiCad-passthrough v1, but **only the export half is fully proven.**+Pick by what the user actually needs:++| Goal | Path | Status |+|---|---|---|+| A **GLB/render** of an Altium board (pads + silkscreen, human looks at it) | Stages ① and ③ here, skipping ② | ✅ **proven end to end** |+| A **full molecule** (anchored GLB **+ footprint.json + symbol.json**) | Stage ② needs `.kicad_pcb` from `altium-pcbdoc`, whose emit is still pending | ⚠️ **blocked at ②** |++If the user needs footprint/symbol JSON **today**, say so and fall back to the v1 route —+`.PcbDoc` through KiCad 10's Altium importer, then `kicad-export-molecule`. That path still+works and is the honest recommendation until `altium-pcbdoc` emits a board file. Do not claim+this skill replaces it for molecule metadata yet.++---++## ⚠️ Readiness — what is built vs. pending++Be honest with the user about where this will block *today*. Two pieces of+underlying machinery are net-new and gate an end-to-end run:++| Piece | Purpose in this skill | Status |+|---|---|---|+| `altium-pcblib` / `altium-schlib` | OLE2/CFBF walker + Altium binary-record decoder (reused) | **Built** |+| `step2glb` service `/convert` | molecule-mode conversion | **Built** (raw HTTP contract live) |+| `adom-library-importer` (over `adom/adom-machine-parts-library`) | puts MP markers + machine pins into Altium | **Built** (3-EDA; replaces the deprecated `adom-altium-library-importer`) |+| **`altium-pcbdoc`** | parse the board `.PcbDoc` → placements + pads (then → `.kicad_pcb`) | **PARSER BUILT + VALIDATED** (`altium-writer/altium-pcbdoc`; Components6 text + Pads6 via altium-pcblib; verified on 115-comp board). `.kicad_pcb` *emit* still pending the importer schema. |+| **Altium STEP export** (via `adom-desktop-altium-bridge`) | native STEP export driven from the container | **WORKS, WITH ONE HUMAN CLICK.** An OutJob carrying an `ExportSTEP` output produces the file; `GenerateReport` no-ops on that output type, so a person must press *Generate content* once. There is **no** headless STEP process — see Stage ①. |+| **Gerber export** (same bridge) | copper + silkscreen, which the STEP does NOT contain | **FULLY HEADLESS** via `altium_run_outjob` — Gerber outputs generate with no click |+| `step2glb --molecule` CLI flags | molecule conversion + silk overlay | **LANDED** — `--molecule`, `--silk-top/-bottom`, `--board`, `--pin`, `--compress` all work on the installed CLI |++If a user asks to run this before `altium-pcbdoc` + the Altium export verb exist,+say so and stop at the first missing dependency rather than faking it.++---++## Preflight — before you export anything++1. **The board must carry molecule furniture.** Confirm the `.PcbDoc` has the+   `adom-altium-library-importer` parts placed:+   - **MP1–MP4** anchor markers (OCCT uses these for Z-up, front-left-pin origin+     anchoring — no markers, no anchor, garbage transform).+   - **Corner machine pins/contacts** with their 3D models resolved locally in+     Altium (so the native STEP bakes them in — this is the whole reason we skip+     KiCad).+   If they're missing, route the user to `adom-library-importer` (the 3-EDA+   importer over `adom/adom-machine-parts-library`; it builds the Altium PcbLib+   at install). Do not proceed — a molecule without markers cannot be anchored.++2. **Pick the pin size.** OCCT takes `pin=medium|large` (machine-pin material ++   contact geometry class). Default **`medium`**; use `large` for high-current /+   large-contact molecules. Ask the user if ambiguous.++3. **Plan for the overlay — this is not optional if anyone will LOOK at the result.**+   The STEP carries no copper and no silkscreen, so a GLB built from it alone is a+   bare green board: no pads, no designators. Export the Gerbers too (Stage ①b) and+   composite them (Stage ③). Skip this only for a geometry-only molecule that will+   never be rendered for a human.++4. **Reachability.** Two hops must be live: (a) the **design machine** (BARRETTLAND,+   where Altium + the resolved 3D models live) via the `adom-desktop` relay — that's+   where STEP export runs; (b) the **OCCT service** — check with `step2glb health`+   (expects `OK: service-step2glb … reachable`). Confirm both before starting.++---++## Stage ① — export from Altium (STEP + Gerbers)++Everything runs on the design machine through the **`adom-desktop-altium-bridge`**, because+Altium resolves its 3D models from local libraries. Every verb needs `--ai-thread`.++### ⚠️ The STEP is bodies only++Altium's STEP contains the **board substrate and component 3D bodies — no copper, no+silkscreen.** Converting it alone yields a bare green board with no pads and no designators.+That is not a bug in the conversion; the data is simply not in the file. You need **two**+exports and you composite them in Stage ③.++### 1a. STEP — via an ExportSTEP OutJob (needs one human click)++There is **no headless STEP-export process.** Every `Command='...'` in AD26's resource files+was enumerated: `Pcb:ExportSTEP` does not exist (Altium silently ignores unknown process ids,+so a call "succeeds" and writes nothing), and the real `PCB:Export` is index-based and opens a+file dialog. Do not go hunting for a process id — this is settled.++Use an OutJob with an `ExportSTEP` output. Open it, then ask the user to click:++```bash+adom-desktop --ai-thread "<thread>" altium_run_outjob '{+  "outjob":  "C:\\...\\AdomMoleculeStep2.OutJob",+  "project": "C:\\...\\Board.PrjPcb",+  "reload":  false+}'+# then: "please click Generate content in the OutJob's output container"+```++`GenerateReport` no-ops specifically on `ExportSTEP` (Gerber outputs are fine). Verified with+ObjectKind=OutputBatch/OutputMedium/Document, with and without OutputMedium, and with the+OutJob already a member of a compiled project — so **do not rebuild the OutJob**, it is correct.++The OutJob's `RelativeOutputPath1` decides where the file lands, and it goes in an+**`ExportSTEP\` subfolder** under that path — search recursively, not just the project dir.++### 1b. Gerbers — fully headless, no click++```bash+adom-desktop --ai-thread "<thread>" altium_run_outjob '{"outjob":"C:\\...\\AdomSilkGerber.OutJob"}'+```++Pull these four:++| Layer | File | Used for |+|---|---|---|+| Top overlay | `.GTO` | silkscreen artwork |+| Top copper | `.GTL` | pads (intersected with the mask) |+| Top solder mask | `.GTS` | **negative polarity** — its features are the mask *openings* |+| Unit PCB Boundary | `.GM1` | the board rect that aligns everything |++### Getting files back++`pull_file` fails on paths containing **spaces**, and `send_files` refuses `C:\Users\Public`+(`dest` is confined to `%USERPROFILE%`/`%TEMP%`/`%LOCALAPPDATA%`/`%APPDATA%`). So copy to a+space-free temp name on the desktop first, then pull:++```bash+# on the desktop: Copy-Item -LiteralPath "<the file>" -Destination C:\Users\barre\AppData\Local\Temp\x\board.step+adom-desktop --ai-thread "<thread>" pull_file '{"filePaths":["C:\\...\\board.step"],"saveTo":"./"}'+```++Deep `Get-ChildItem -Recurse` over `C:\Users` exceeds the default shell timeout — pass+`timeoutSeconds` inside the args, not just the outer CLI timeout.++---++## Stage ② — board metadata for OCCT++OCCT's `/convert` needs a **board file it can parse** to generate the standardized+footprint+symbol JSON (it fuses that with geometry-measured per-pad wire-attach+Z). **The endpoint only auto-sniffs `.kicad_pcb` / `.brd` — it does NOT accept a+`.PcbDoc` and it does NOT accept pre-parsed JSON** (confirmed in adom-step2glb+discussion #99). So the `.PcbDoc` must be turned into something the endpoint eats. -Verify after import: board outline, copper, and drills survived; footprints-are sane. Altium importer output is good but not perfect — eyeball it in the-board editor before proceeding.+### v1 (current design): transcode `.PcbDoc` → `.kicad_pcb` -## Stage 2 — Make it molecule-ready (usually required)+Use the new `altium-pcbdoc` crate (reuses `altium-pcblib`'s OLE2/CFBF + binary+record machinery) to emit a **minimal** `.kicad_pcb` carrying exactly what+`molecule-metadata-importer` reads: component placements, pad stacks, reference+designators, and the MP1–MP4 markers. Full routing/nets are NOT needed. -Altium boards arrive WITHOUT the Adom molecule furniture. Check and fix:+```bash+# PENDING crate. Emits a .kicad_pcb in Altium's coordinate frame.+altium-pcbdoc-kicad "Molecule.PcbDoc" -o molecule.kicad_pcb+``` -- **4 corner machine pins + MP1–MP4 marker models** — if absent, this is a-  board-design task: use `kicad-to-molecule` (adds wings + corner machine-  pins) or place them manually from the Adom KiCad library.-- **Machine-pin/contact 3D models resolve locally** — they must load in-  KiCad's 3D viewer to be baked into the STEP.-- Pad drills for machine pins must match the standard buckets so the-  footprint parser classifies them (medium/large).+**`altium-pcbdoc` implementation map** (confirmed against real AD26 `.PcbDoc`+files — a board is the same CFBF/binary-record family as `.PcbLib`):+- `Components6/Data` — **TEXT** `|KEY=VAL|` records, u32-len framed. Carries each+  placement: `PATTERN=` (footprint name), `X=…mil`/`Y=…mil`, `ROTATION=`,+  `LAYER=TOP|BOTTOM`, designator. This is the placement table → trivial to parse.+- `Pads6/Data` — **binary pad records, identical format to `.PcbLib`** → reuse+  `altium-pcblib`'s pad decoder verbatim (owner-component index links pad→component).+- `ComponentBodies6/Data` / `Models/0` — 3D body links + embedded STEP (same as PcbLib).+- Board sections are per-type storages (`<Type>6/Data` + `<Type>6/Header`); routing+  streams (`Tracks6`/`Arcs6`/`Nets6`/`Vias6`) are NOT needed for molecule metadata. -## Stage 3 — Hand off to the KiCad flow+Both `molecule.step` and `molecule.kicad_pcb` come from the **same `.PcbDoc`**, so+they share Altium's coordinate frame; OCCT re-anchors both to the MP markers, so+absolute origin choice doesn't matter as long as the two agree. **Do not** hand-+edit coordinates between the two. -From here the board IS a KiCad molecule. Follow-**`kicad-export-molecule`** end to end: STEP export on the laptop → OCCT-molecule-mode convert (anchored GLB + footprint + symbol) → `adom-wiki pkg-publish` → `adom-molecule-import` into Hydrogen.+### The seam (future v2): standardized board JSON direct -## Footprint-library note (different job)+The cleaner long-term contract is to emit Colby's standardized board JSON straight+from `altium-pcbdoc` and skip the `.kicad_pcb` middle-format. That requires OCCT+to add a `--board-json` ingress (belongs in discussion #99). **Until that ingress+exists, do not attempt it** — the endpoint will reject it. When it lands, swap the+`board` multipart part for the JSON and drop the transcode. Keep this seam+obvious; it's the only planned change to this stage. -If the ask is just Altium **footprints** (`.PcbLib`/`.IntLib`) rather than a-board, that's `altium-pcblib` territory (native Rust parser → KiCad-shaped-pad JSON, used by chipsmith/concur) — not this skill.+### Validation (do this while offline)++Cross-source, concur-style: take one board that exists in both Altium and KiCad,+run this transcode and the KiCad flow, and diff the resulting board JSON. Same+pads in the same places → the transcode is faithful. This is the `concur` /+`chipsmith` agreement pattern applied at board level and needs no Altium at run+time.++---++## Stage ②.5 — schematic (source completeness only)++`molecule-publish` wants the schematic bundled so the molecule is reproducible.+For Altium that's the `.SchDoc` (+ project `.PrjPcb`). **This is source, not+parsed** — the footprint/symbol JSON comes from OCCT geometry, not the schematic.+Just carry the `.SchDoc` through to the publish handoff; no `.SchDoc` parser is+required here.++---++## Stage ③ — convert (OCCT molecule mode)++### Build the top-face overlay first (pads + silkscreen)++The STEP has no copper or silk, so rebuild both from the Gerbers into ONE RGBA image. step2glb+passes the texture's RGB through untouched, so the overlay can be full-colour — it does **not**+have to be the white-only artwork the flag name suggests.++**Exposed metal is `.GTL` ∩ `.GTS`, not all of `.GTL`.** `.GTS` is `TF.FilePolarity,Negative`,+so its drawn features are the mask *openings*. Using all the copper paints the covered ground+pour gold (~76% of the board); the intersection gives real pad coverage (~8%).++Use the bundled script — it handles the alignment and the gerbv quirks:++```bash+python3 make_overlay.py --gerber-dir gerber --out overlay_top.png --dpi 600+# board rect 0.0000,0.0000 -> 7.0866,1.9882 in (180.00 x 50.50 mm)+# copper 75.9%  openings 10.5%  pads 8.1%  silk 7.2%+```++Alignment is the whole game: every layer is rendered through gerbv with the **same** explicit+window taken from the `.GM1` board rect, never each layer's own extents (those differ per layer+and would misregister the artwork). Two gerbv traps the script already handles — it **fails+outright** on full float repr like `1.9881900000000001` (round to ~5 dp), and it alpha-blends+the foreground so "white" peaks near luma 177, not 255 (normalise by the real peak or every+layer comes out semi-transparent).++### Convert++```bash+step2glb convert molecule.step -o molecule.glb \+  --molecule --pin medium \+  --board molecule.kicad_pcb \+  --silk-top overlay_top.png \+  --compress quantize+```++- ⚠️ **`--silk-top` is silently ignored without `--molecule`.** In plain convert mode the GLB+  comes back byte-identical and no `silkscreen_applied` field appears. Always check the response+  says `silkscreen_applied: true`.+- On a board with no MP markers, `--molecule` warns `anchor skipped: found 0 MP markers` and+  skips anchoring — harmless if you only want the overlay, fatal if you actually need a molecule.+- `--compress quantize` matters: molecule mode defaults to **Draco**, which needs a DRACOLoader+  that plain three.js viewers (and the wiki `/readme/` CSP) do not have.++### Rendering it (and the z-fighting you will hit)++step2glb lays the overlay **coplanar** with the board's top face, so it z-fights. Fix it in the+viewer, not the asset — on the `silkscreen_top` material set `polygonOffset = true`,+`polygonOffsetFactor/Units = -4`, `depthWrite = false`, `renderOrder = 1`, and set the texture's+anisotropy to the renderer max. Just as important, keep the camera's near/far tight: a+`0.001 / 100` range destroys depth precision on its own, and `0.05 / 20` is right for a model+normalised to a unit box.++`step2glb thumbnail-batch` is **broken server-side** for any file big enough to queue+(`name 'job_type' is not defined`). Use `render_board_glb.mjs` (puppeteer + three.js) instead.++### Raw HTTP fallback++If you need the POST directly, **every request REQUIRES `X-Client` and `X-Job-Name`** or it+returns `400 Missing required headers` before doing any work:++```bash+curl -sS -X POST \+  "https://step2glb-gmdoncpxdwx0.adom.cloud/convert?molecule=true&pin=medium" \+  -H "X-Client: altium-export-molecule/<user-or-container>" \+  -H "X-Job-Name: convert-<mpn>-altium-molecule-to-glb" \+  -F "[email protected]" -F "[email protected]_pcb" \+  -F "silk_top=@overlay_top.png"+```++- `X-Client`: `<tool>/<user-or-container>`. `X-Job-Name`: lowercase-hyphen, **include the MPN**+  so operators can spot a runaway job in `/health`.+- The CDN **403s python-urllib's default User-Agent** — verify downloads with `curl`, not+  urllib, or a working artifact looks like a failed one.++Artifacts you must come away with: **`molecule.glb`**, **`molecule_footprint.json`**,+**`molecule_symbol.json`**. If footprint/symbol 404, OCCT couldn't parse the board+— re-check Stage ② (usually the transcoded `.kicad_pcb` is missing pads or the MP+markers didn't survive).++---++## Stage ④ — hand off to molecule-publish++This skill stops here. Hand the four inputs `molecule-publish` expects to that+skill:++1. `molecule.step` — the anchored STEP (source)+2. `molecule.kicad_pcb` — the board file (source + what OCCT parsed)+3. `Molecule.SchDoc` (+ `.PrjPcb`) — the schematic (source completeness)+4. metadata: `molecule.glb` + `molecule_footprint.json` + `molecule_symbol.json`,+   plus the `package.json` `molecule-publish` authors.++`molecule-publish` enforces the stats gates and does `adom-wiki pkg publish` ++`repo push`, then `adom-molecule-import` registers it in Hydrogen. Don't+duplicate any of that here.++---++## Coordinate-frame cheatsheet (why anchoring works)++- OCCT anchors to **MP1–MP4**: Z-up, origin at the **front-left pin**. Both the+  STEP and the board file are re-anchored to these markers, so the two only need+  to *agree with each other*, not with any absolute origin.+- Altium is Y-up; KiCad/OCCT board JSON is Y-down. If you're transcoding to+  `.kicad_pcb`, flip Y at transcode time (same rule `altium-pcblib` already+  applies to pads — **flip once, never twice**).+- Units: Altium stores pad geometry in `1/10000 mil`; emit millimetres. A 1000×+  scale error means a mil/µm/mm mixup — the classic `altium-pcblib` footgun.++---++## Troubleshooting++| Symptom | Likely cause | Fix |+|---|---|---|+| `400 Missing required headers` on convert | hand-rolled POST without identity headers | add `-H "X-Client: …"` + `-H "X-Job-Name: …"` (see Stage ③) |+| STEP has no pin/contact bodies | 3D models unresolved in Altium | re-run `adom-altium-library-importer` on the design machine |+| OCCT returns un-anchored / rotated molecule | MP1–MP4 missing or mislabeled in the `.PcbDoc` | fix furniture placement, re-export |+| `/jobs/{id}/footprint` 404 | OCCT couldn't parse `board` | transcode dropped pads / used a `.PcbDoc` directly (endpoint won't sniff it) |+| Pads mirrored top↔bottom | Y flipped twice in the transcode | flip once (Altium Y-up → KiCad Y-down) |+| Everything 1000× too big/small | mil↔mm↔µm unit error | emit mm from `1/10000 mil` |+| Publish gates fail downstream | that's `molecule-publish`, not this skill | route to `adom/molecule-publish` |++--- -## Related+## Relationship to sibling skills -- `kicad-export-molecule` — the flow this adapter feeds into-- `fusion-export-molecule` — Fusion 360 sibling-- `kicad-to-molecule` — add wings + machine pins to a plain board-- `adom-molecule-import` — the Hydrogen import half+- **`adom/kicad-export-molecule`** — the flow this replaces for Altium boards.+  Same stages ①–②; that one uses `kicad-cli` + a native `.kicad_pcb`. Keep it as+  the reference template for the OCCT contract.+- **`adom/molecule-publish`** — the downstream owner of packaging + wiki publish.+  This skill hands off to it and does not overlap.+- **`altium-pcblib` / `altium-schlib`** — the read crates whose OLE2/record+  machinery `altium-pcbdoc` reuses.+- **`adom-library-importer`** (over `adom/adom-machine-parts-library`) — the+  prerequisite that puts molecule furniture into Altium in the first place.+  Replaces the deprecated `adom-altium-library-importer`.+- **`step2glb`** — the CLI/service this calls for the OCCT conversion.
install.sh+13−6
@@ -1,9 +1,16 @@ #!/bin/bash+# Symlink the skill (and its helper script) into ~/.claude/skills so edits in the module+# dir propagate and a reinstall cannot clobber local work. set -e PKG_DIR="$(cd "$(dirname "$0")" && pwd)"-SKILLS_DIR="$HOME/.claude/skills"-mkdir -p "$SKILLS_DIR/altium-export-molecule"-rm -rf "$SKILLS_DIR/altium-export-molecule"-mkdir -p "$SKILLS_DIR/altium-export-molecule"-cp "$PKG_DIR/SKILL.md" "$SKILLS_DIR/altium-export-molecule/SKILL.md"-echo "Installed skill → $SKILLS_DIR/altium-export-molecule/SKILL.md"+DEST="$HOME/.claude/skills/altium-export-molecule"+mkdir -p "$DEST"++ln -sfn "$PKG_DIR/SKILL.md" "$DEST/SKILL.md"+# make_overlay.py rebuilds pads + silkscreen from Gerbers; the skill's Stage ③ invokes it+# by name, so it has to travel with the skill rather than living in a scratch directory.+[ -f "$PKG_DIR/make_overlay.py" ] && ln -sfn "$PKG_DIR/make_overlay.py" "$DEST/make_overlay.py"+[ -d "$PKG_DIR/reference" ] && ln -sfn "$PKG_DIR/reference" "$DEST/reference"++echo "Installed altium-export-molecule (symlinked from $PKG_DIR)"+echo "  overlay helper: $DEST/make_overlay.py   (needs: gerbv, python3 pillow, numpy)"
make_overlay.pyadded+105
@@ -0,0 +1,105 @@+#!/usr/bin/env python3+"""Build a top-face overlay PNG (exposed copper pads + silkscreen) from Altium Gerbers.++Altium's STEP export contains only the board substrate and component 3D bodies -- no copper+and no silkscreen. Both are recovered from the Gerbers and composited into one RGBA image+that step2glb lays onto the board's top face via --silk-top.++    python3 make_overlay.py [--gerber-dir gerber] [--out overlay_top.png] [--dpi 600]++Alignment is the whole game: the image must map 1:1 onto the board outline, so every layer+is rendered through gerbv with the SAME explicit window, taken from the board rect rather+than from each layer's own extents.+"""++import argparse+import re+import subprocess+import sys+from pathlib import Path++import numpy as np+from PIL import Image++# Exposed-metal and silkscreen colours. The board's own solder mask green comes from the+# STEP, so anything left transparent here shows the substrate through.+PAD_RGB = (206, 168, 76)      # ENIG-ish gold+SILK_RGB = (245, 245, 240)    # slightly warm white, matches real silkscreen ink++COORD = re.compile(rb'X(-?\d+)Y(-?\d+)')+++def board_rect_inches(gm1: Path):+    """Board outline rect from the Unit PCB Boundary layer (%FSLAX25Y25% -> 5 decimals).++    Only the positive quadrant is taken: the layer also carries an annotation below the+    board (negative Y) that would otherwise inflate the rect and shrink the artwork.+    """+    xs, ys = [], []+    for m in COORD.finditer(gm1.read_bytes()):+        xs.append(int(m.group(1)) * 1e-5)+        ys.append(int(m.group(2)) * 1e-5)+    if not xs:+        sys.exit(f'no coordinates in {gm1}')+    return max(0.0, min(xs)), max(0.0, min(ys)), max(xs), max(ys)+++def render(gerber: Path, out: Path, rect, dpi: int):+    x0, y0, x1, y1 = rect+    # Round the geometry: gerbv fails outright on full float repr like "1.9881900000000001".+    f = lambda v: f'{v:.5f}'+    subprocess.run(+        ['gerbv', '-x', 'png', '-o', str(out),+         '-O', f'{f(x0)}x{f(y0)}', '-W', f'{f(x1 - x0)}x{f(y1 - y0)}',+         '-D', str(dpi), '-B', '0', '-b', '#000000', '-f', '#FFFFFF', str(gerber)],+        check=True, capture_output=True)+    a = np.asarray(Image.open(out).convert('L')).astype(np.float32)+    # gerbv alpha-blends the foreground, so "white" peaks well below 255; normalise by the+    # real peak or every layer comes out semi-transparent.+    return np.clip(a / max(a.max(), 1.0), 0.0, 1.0)+++def main():+    ap = argparse.ArgumentParser()+    ap.add_argument('--gerber-dir', default='gerber')+    ap.add_argument('--out', default='overlay_top.png')+    ap.add_argument('--dpi', type=int, default=600)+    args = ap.parse_args()++    g = Path(args.gerber_dir)+    rect = board_rect_inches(g / 'board.GM1')+    w_mm, h_mm = (rect[2] - rect[0]) * 25.4, (rect[3] - rect[1]) * 25.4+    print(f'board rect {rect[0]:.4f},{rect[1]:.4f} -> {rect[2]:.4f},{rect[3]:.4f} in '+          f'({w_mm:.2f} x {h_mm:.2f} mm)')++    tmp = Path('.overlay-tmp'); tmp.mkdir(exist_ok=True)+    cu   = render(g / 'board.GTL', tmp / 'cu.png',   rect, args.dpi)   # top copper, positive+    mask = render(g / 'board.GTS', tmp / 'mask.png', rect, args.dpi)   # solder mask, NEGATIVE+    silk = render(g / 'board.GTO', tmp / 'silk.png', rect, args.dpi)   # top overlay++    # The mask layer is negative polarity: its drawn features are the openings. Exposed metal+    # is therefore copper AND opening -- not all copper, most of which is a covered pour.+    pad = np.minimum(cu, mask)++    h, w = pad.shape+    rgba = np.zeros((h, w, 4), dtype=np.float32)++    # Pads first, silkscreen painted over them (that is the physical stack-up).+    for i, c in enumerate(PAD_RGB):+        rgba[..., i] = c+    rgba[..., 3] = pad++    for i, c in enumerate(SILK_RGB):+        rgba[..., i] = rgba[..., i] * (1 - silk) + c * silk+    rgba[..., 3] = np.maximum(rgba[..., 3], silk)++    out = Image.fromarray(+        np.concatenate([rgba[..., :3], rgba[..., 3:4] * 255], axis=2).astype(np.uint8), 'RGBA')+    out.save(args.out)+    print(f'copper {(cu > .5).mean() * 100:.1f}%  openings {(mask > .5).mean() * 100:.1f}%  '+          f'pads {(pad > .5).mean() * 100:.1f}%  silk {(silk > .5).mean() * 100:.1f}%')+    print(f'wrote {args.out}  {out.size[0]}x{out.size[1]}')+++if __name__ == '__main__':+    main()
package.json+26−28
@@ -1,44 +1,42 @@ {   "slug": "altium-export-molecule",-  "version": "1.0.1",+  "version": "2.1.0",   "type": "skill",-  "description": "Ship an Altium Designer board to the Adom wiki as a molecule: KiCad 10's native Altium importer converts the .PcbDoc, molecule furniture (markers + machine pins) gets added, then the kicad-export-molecule flow handles OCCT conversion, wiki publish, and Hydrogen import.",-  "tags": [-    "molecule",-    "altium",-    "export",-    "kicad",-    "skill"-  ],+  "description": "Direct Altium -> OCCT molecule/board path: STEP export (bodies) + Gerber export (copper + silkscreen) -> composited top-face overlay -> step2glb molecule mode. Includes make_overlay.py. Hands off to molecule-publish.",   "dependencies": {-    "adom/kicad-export-molecule": "^1.0.0"+    "adom/adom-step2glb": "^1.1.0",+    "adom/adom-desktop": "*",+    "barrett-land/adom-desktop-altium-bridge": "*"   },   "scripts": {     "install": "./install.sh",     "uninstall": "./uninstall.sh"   },-  "hero": {-    "headline": "Altium board to wiki molecule",-    "subhead": "PcbDoc through KiCad's importer, then the molecule pipeline end to end",-    "screenshot": "docs/shot.png"-  },   "discovery_triggers": [-    "altium molecule",-    "publish my altium board",-    "altium to hydrogen",-    "altium to the wiki",-    "PcbDoc molecule",-    "export molecule from altium"+    "export a molecule from altium",+    "altium board to glb",+    "altium step export",+    "render my altium board",+    "altium silkscreen overlay",+    "pads missing from altium export",+    "altium molecule"   ],-  "discovery_pitch": "Take an Altium .PcbDoc to a published wiki molecule and a Hydrogen account: KiCad-10 Altium import, molecule-readiness fixups, then the kicad-export-molecule pipeline.",+  "discovery_pitch": "Use when exporting an Altium board to a GLB/molecule \u2014 covers the STEP export, rebuilding pads and silkscreen from Gerbers, and the step2glb conversion.",   "sample_prompts": [     {-      "label": "Publish an Altium board",-      "prompt": "Publish my Altium board sensor-hub.PcbDoc as a molecule"+      "label": "Export a board",+      "prompt": "Export my Altium board to a GLB I can look at"     },     {-      "label": "Altium to Hydrogen",-      "prompt": "Get this PcbDoc into my Hydrogen account as a molecule"+      "label": "Fix missing pads",+      "prompt": "My Altium export has no pads or silkscreen, fix it"+    },+    {+      "label": "Ship a molecule",+      "prompt": "Publish this Altium molecule to the wiki"     }-  ]-}\ No newline at end of file+  ],+  "hero": {+    "path": "docs/hero.png"+  }+}
page.json+21−14
@@ -1,8 +1,8 @@ {   "slug": "altium-export-molecule",-  "version": "1.0.1",+  "version": "2.1.0",   "type": "skill",-  "description": "Ship an Altium Designer board to the Adom wiki as a molecule: KiCad 10's native Altium importer converts the .PcbDoc, molecule furniture (markers + machine pins) gets added, then the kicad-export-molecule flow handles OCCT conversion, wiki publish, and Hydrogen import.",+  "description": "Direct Altium -> OCCT molecule/board path: STEP export (bodies) + Gerber export (copper + silkscreen) -> composited top-face overlay -> step2glb molecule mode. Includes make_overlay.py. Hands off to molecule-publish.",   "tags": [     "molecule",     "altium",@@ -11,7 +11,9 @@     "skill"   ],   "dependencies": {-    "adom/kicad-export-molecule": "^1.0.0"+    "adom/adom-step2glb": "^1.1.0",+    "adom/adom-desktop": "*",+    "barrett-land/adom-desktop-altium-bridge": "*"   },   "scripts": {     "install": "./install.sh",@@ -24,22 +26,27 @@     "screenshot": "docs/shot.png"   },   "discovery_triggers": [-    "altium molecule",-    "publish my altium board",-    "altium to hydrogen",-    "altium to the wiki",-    "PcbDoc molecule",-    "export molecule from altium"+    "export a molecule from altium",+    "altium board to glb",+    "altium step export",+    "render my altium board",+    "altium silkscreen overlay",+    "pads missing from altium export",+    "altium molecule"   ],-  "discovery_pitch": "Take an Altium .PcbDoc to a published wiki molecule and a Hydrogen account: KiCad-10 Altium import, molecule-readiness fixups, then the kicad-export-molecule pipeline.",+  "discovery_pitch": "Use when exporting an Altium board to a GLB/molecule — covers the STEP export, rebuilding pads and silkscreen from Gerbers, and the step2glb conversion.",   "sample_prompts": [     {-      "label": "Publish an Altium board",-      "prompt": "Publish my Altium board sensor-hub.PcbDoc as a molecule"+      "label": "Export a board",+      "prompt": "Export my Altium board to a GLB I can look at"     },     {-      "label": "Altium to Hydrogen",-      "prompt": "Get this PcbDoc into my Hydrogen account as a molecule"+      "label": "Fix missing pads",+      "prompt": "My Altium export has no pads or silkscreen, fix it"+    },+    {+      "label": "Ship a molecule",+      "prompt": "Publish this Altium molecule to the wiki"     }   ],   "org": "adom",
uninstall.sh+4−2
@@ -1,3 +1,5 @@ #!/bin/bash-rm -rf "$HOME/.claude/skills/altium-export-molecule"-echo "Removed altium-export-molecule skill"+set -e+SKILLS_DIR="$HOME/.claude/skills"+rm -rf "$SKILLS_DIR/altium-export-molecule"+echo "Uninstalled altium-export-molecule"