{
  "schema_version": 1,
  "type": "skill",
  "slug": "altium-pcblib",
  "title": "Altium PcbLib — native footprint parser",
  "brief": "Native Rust parser for Altium .PcbLib binary footprint libraries — used by chipsmith + concur for cross-source agreement.",
  "version": "1.0.1",
  "tags": [],
  "license": "MIT",
  "discovery_triggers": [
    "altium pcblib",
    "altium intlib",
    "parse altium binary footprint",
    "altium ole2",
    ".PcbLib",
    ".IntLib",
    "altium-pcblib",
    "altium svg debug",
    "altium-pcblib-svg",
    "why is my altium pad rotated wrong",
    "MFG vs IPC variant",
    "altium fat overshoot",
    "altium compound file",
    "read altium footprint in rust"
  ],
  "discovery_pitch": "Use when reading Altium .PcbLib / .IntLib binaries from Rust, debugging \"why is my altium pad rotated wrong\", or wiring a fourth source into a cross-source footprint check. Ships an authoritative SVG debug renderer.",
  "sample_prompts": [
    {
      "label": "Add to Cargo",
      "prompt": "Add altium-pcblib to my Cargo.toml as a git dependency"
    },
    {
      "label": "Render SVG",
      "prompt": "Render the parser's authoritative SVG for an Altium .PcbLib file"
    },
    {
      "label": "Compare to KiCad",
      "prompt": "Compare the Altium .PcbLib pad layout to the KiCad .kicad_mod for a chip and tell me where they disagree"
    },
    {
      "label": "Debug rotation",
      "prompt": "Why are my altium pads rotated 90 degrees wrong in chipsmith?"
    },
    {
      "label": "Pick variant",
      "prompt": "This .PcbLib has multiple variants — pick the one whose pad count matches the kicad footprint"
    }
  ],
  "source_path": "SKILL.md",
  "readme": "## What this is\n\n`altium-pcblib` is a private Adom Rust crate that reads Altium Designer's\nbinary footprint libraries (`.PcbLib`, and the PCB stream embedded inside\n`.IntLib`) and emits per-pad JSON shaped exactly like KiCad's\n`parse_kicad_mod` output. One source of truth instead of every Adom tool\nre-implementing OLE2 / CFBF walking + Altium's reverse-engineered binary\nrecord format.\n\nAlready wired into:\n- **adom-chipsmith** Tab 4 cross-source agreement matrix — fourth column\n  alongside KiCad / Fusion / Datasheet.\n- **concur** consensus check — auto-picks the right multi-variant\n  footprint (IPC vs MFG) by pad-count agreement with other parsed sources.\n\nSource lives in the private GitHub repo `adom-inc/altium-pcblib`. Skills\non the wiki cover discoverability + integration; the source itself is\nnot published here.\n\n## Why a skill page, not a library page\n\nIn Adom-wiki terms a **library** is an EDA part bundle (symbol +\nfootprint + 3D + variants + pad↔pin mapping). `altium-pcblib` is a Rust\ncrate — so its wiki page is a **skill**: it teaches Claude/the team how\nto use the crate, while the source stays private.\n\n## Depending on it\n\nIn the workspace (chipsmith, concur, anything else under\n`/home/adom/project/`):\n\n```toml\n[dependencies]\naltium-pcblib = { path = \"../altium-pcblib\" }\n```\n\nOn a build container without the workspace checkout:\n\n```toml\n[dependencies]\naltium-pcblib = { git = \"https://github.com/adom-inc/altium-pcblib\", branch = \"main\" }\n```\n\nSingle direct dep (`cfb`, the OLE2 reader) plus `anyhow` and `serde_json`.\n\n## Public Rust API\n\n```rust\n// Parse from a file on disk.\npub fn parse_altium_pcblib_path(path: &Path) -> anyhow::Result<serde_json::Value>;\n\n// Parse from raw bytes (handy for include_bytes! tests and HTTP bodies).\npub fn parse_altium_pcblib_bytes(bytes: &[u8]) -> anyhow::Result<serde_json::Value>;\n\n// Render the parser's view of the footprint as standalone SVG. THIS IS THE\n// AUTHORITATIVE GROUND TRUTH — if a downstream tool draws something different,\n// the downstream tool is the bug.\npub fn render_footprint_svg(footprint: &serde_json::Value) -> String;\n\n// Render primary + every alternate variant, stacked vertically in one SVG.\npub fn render_all_footprints_svg(parsed: &serde_json::Value) -> String;\n```\n\nThe returned `Value` is shaped exactly like\n`adom-chipsmith::view_library::parse_kicad_mod_text` output:\n\n```json\n{\n  \"module_name\": \"D8\",\n  \"pad_count\": 8,\n  \"pads\": [{\n    \"number\": \"1\", \"type\": \"smd\", \"shape\": \"rect\",\n    \"at\": [-2.4638, -1.905, 0.0],\n    \"size\": [1.9812, 0.5588],\n    \"layers\": [\"F.Cu\", \"F.Paste\", \"F.Mask\"],\n    \"drill\": null\n  }],\n  \"silk_markers\": [],\n  \"pin1_silk\": { \"found\": false },\n  \"_source\": \"altium_pcblib\",\n  \"_alternates\": [ /* other footprints, sorted by pad_count desc */ ]\n}\n```\n\n## CLI bin: `altium-pcblib-svg`\n\nSingle binary that ships with the crate. Use it whenever a downstream\nrenderer looks suspect:\n\n```bash\n# Primary footprint, SVG to stdout.\naltium-pcblib-svg /path/to/Foo.PcbLib > foo.svg\n\n# Primary + every alternate variant stacked vertically.\naltium-pcblib-svg /path/to/Foo.PcbLib --all -o foo-all.svg\n```\n\nSVG conventions:\n- 1 SVG unit = 1 mm. No scaling.\n- Y-down (matches both KiCad and our emitted JSON).\n- Rotation applied via `transform=\"rotate(deg cx cy)\"` clockwise on screen.\n- Pad numbers stay un-rotated for readability.\n- F.Cu pads orange, B.Cu blue, thru-hole green, drill holes white on top.\n- Origin crosshair at (0, 0) with 1 mm arms.\n\n## Format quirks worth knowing\n\nThese bit us during reverse-engineering — they're encoded in the parser\nalready, but if you extend it or debug a weird chip, here's what's going\non.\n\n### 1. FAT overshoot\n\nINA226AIDGSR's PcbLib declares 256 FAT entries but only has 213 sectors.\nThe `cfb` crate refuses with `Malformed FAT … FAT has N entries, but\nfile has only M sectors`. Fix: pad the input bytes to 1 MiB with `0xFF`\n(CFBF \"free sector\" sentinel) before opening. The unreferenced trailing\nsectors don't hold real data.\n\n### 2. Per-record-type sub-block count\n\nAltium records aren't uniform `[type][len][payload]`. Different record\ntypes carry different numbers of `[u32 len][bytes]` sub-blocks:\n\n- Pad (0x02): 6 sub-blocks (designator, layer-name pstring, four metadata blocks).\n- Text (0x05): 2 sub-blocks (main + style trailer).\n- Track / Arc / Via / Fill / Region / ComponentBody: 1 sub-block.\n\nIf you hit an unknown type byte mid-stream, the parser stops walking\ngracefully — pads always come first, so by then we already have what we\nneed.\n\n### 3. Pad block-5 layout (the 194-byte v3 / 202-byte v4 pad data)\n\n```\n[0]      u8 layer (1 = TOP_CU, 32 = BOTTOM_CU, ...)\n[1]      u8 net/flag (0x08 in v3, 0x0c in v4 — version probe)\n[2]      u8 reserved\n[3..13]  10 bytes of 0xFF (component / net id sentinels)\n[13..17] i32 LE x position (units = 1 / 10000 mil = 2.54e-6 mm)\n[17..21] i32 LE y position (Altium Y-up — flipped to KiCad Y-down at parse)\n[21..29] u32 LE x size, y size — top layer\n[29..37] u32 LE x size, y size — mid layer (multi-layer thru-hole pads)\n[37..45] u32 LE x size, y size — bottom layer\n[45..49] u32 LE hole size (0 → SMD; > 0 → thru-hole)\n[49..52] u8 × 3 shape codes (1 round, 2 rect, 3 octagonal, 9 rounded-rect)\n[52..60] f64 LE rotation in degrees — ONLY in 202-byte v4 layout\n```\n\nPast offset 60 the layout drifts between Altium releases (paste / mask\noffsets, plated bool, GUIDs). The parser stops at 60.\n\n### 4. Two equally valid rotation conventions in the wild\n\nThis is the one that *looks* like a parser bug but isn't. Altium gives\nlibrary authors freedom in how to encode a rotated pad:\n\n- **Convention A (matches KiCad):** store the pad as a \"vertical stick\"\n  (xsize < ysize), set rotation = 90° on the side pads.\n- **Convention B (post-rotation):** store the physical post-rotation\n  dimensions (xsize > ysize for a horizontal pad), set rotation = 0.\n\nBoth render to the same physical pad on the PCB. A downstream tool that\ncompares `size[]` element-wise without applying rotation will see a 90°\nmismatch in convention B → KiCad. The fix lives in the consumer (compute\nthe post-rotation bbox before comparing), **not** in this parser. When in\ndoubt, render the authoritative SVG and compare visually.\n\n### 5. Y-axis flip\n\nAltium Y-up vs KiCad Y-down. The parser flips Y at parse time so emitted\n`at[1]` is in KiCad convention. **Do not flip again downstream.**\n\n### 6. Round shape with unequal w/h → \"oval\"\n\nKiCad's `circle` implies w == h. Altium's \"round\" (shape byte 1) covers\nboth true circles and stadium-shaped SMD pads with rounded ends. The\nparser specializes: shape 1 with `(xsize - ysize).abs() < 1e-3` →\n`circle`, otherwise → `oval` (KiCad's stadium).\n\n## Trouble-shooting checklist when downstream renders pads wrong\n\n1. **Run the authoritative SVG first.** `altium-pcblib-svg <path>.PcbLib > /tmp/auth.svg`. If pads are right in the SVG, the bug is downstream.\n2. **Check which variant got picked.** `parsed.module_name` plus `_alternates[*].module_name`. Manufacturing variants often have extra pads vs the IPC pinout variant; concur uses `parse_with_consensus_hint` to pick the right one.\n3. **Check rotation convention.** If pad 1 looks correct but pad 13 looks rotated 90° wrong, you're probably hitting Convention B vs A — your downstream is comparing or rendering size literally without applying rotation.\n4. **Check Y direction.** If pads are mirrored top-to-bottom, your downstream double-flipped Y. The parser already emits KiCad Y-down.\n5. **Scale.** SVG units are mm. If the rendered size is off by 1000×, the consumer is treating mm as µm.\n\n## Tested chip corpus\n\n| Chip | Package | Variants | Notes |\n|---|---|---|---|\n| LM358D | SOIC-8 | D8 / D8-L / D8-M | Sanity baseline. 8 horizontal pads, rot=0 throughout. |\n| INA226AIDGSR | VSSOP-10 | SOP50P490X110-10N | v3 layout (Convention B). Triggers FAT overshoot quirk. |\n| ADS1115IDGSR | VSSOP-10 | DGS0010A_L / _M / _N | 3 alternate density variants. |\n| DRV8316RRGFR | VQFN-40 | RGF0040E-IPC_A / _B / _C / -MFG | IPC variants are Convention A; MFG variant is Convention B + extra fiducials/test points. |\n",
  "author": {
    "name": "Kyle Bergstedt",
    "email": "[email protected]"
  },
  "visibility": {
    "public": true
  },
  "hero": {
    "type": "image",
    "path": "altium-pcblib-hero.png"
  },
  "metadata": {},
  "created_at": "2026-05-28T05:29:36.057Z",
  "updated_at": "2026-06-25T01:59:29.394Z",
  "sub_skills": [],
  "parent_app": null,
  "description": "Native Rust parser for Altium .PcbLib binary footprint libraries — used by chipsmith + concur for cross-source agreement.",
  "dependencies": {},
  "scripts": {
    "install": "./install.sh",
    "uninstall": "./uninstall.sh"
  },
  "org": "adom"
}