skill / altium-pcblib
!

Not installable via adompkg

This skill has no published release. adompkg install kyle/altium-pcblib will not work until a maintainer publishes a tarball with install.sh and uninstall.sh.

See the publishing docs for the package.json schema and tarball layout required to ship this skill.

What this is

altium-pcblib is a private Adom Rust crate that reads Altium Designer's
binary footprint libraries (.PcbLib, and the PCB stream embedded inside
.IntLib) and emits per-pad JSON shaped exactly like KiCad's
parse_kicad_mod output. One source of truth instead of every Adom tool
re-implementing OLE2 / CFBF walking + Altium's reverse-engineered binary
record format.

Already wired into:

  • adom-chipsmith Tab 4 cross-source agreement matrix — fourth column
    alongside KiCad / Fusion / Datasheet.
  • concur consensus check — auto-picks the right multi-variant
    footprint (IPC vs MFG) by pad-count agreement with other parsed sources.

Source lives in the private GitHub repo adom-inc/altium-pcblib. Skills
on the wiki cover discoverability + integration; the source itself is
not published here.

Why a skill page, not a library page

In Adom-wiki terms a library is an EDA part bundle (symbol +
footprint + 3D + variants + pad↔pin mapping). altium-pcblib is a Rust
crate — so its wiki page is a skill: it teaches Claude/the team how
to use the crate, while the source stays private.

Depending on it

In the workspace (chipsmith, concur, anything else under
/home/adom/project/):

[dependencies]
altium-pcblib = { path = "../altium-pcblib" }

On a build container without the workspace checkout:

[dependencies]
altium-pcblib = { git = "https://github.com/adom-inc/altium-pcblib", branch = "main" }

Single direct dep (cfb, the OLE2 reader) plus anyhow and serde_json.

Public Rust API

// Parse from a file on disk.
pub fn parse_altium_pcblib_path(path: &Path) -> anyhow::Result<serde_json::Value>;

// Parse from raw bytes (handy for include_bytes! tests and HTTP bodies).
pub fn parse_altium_pcblib_bytes(bytes: &[u8]) -> anyhow::Result<serde_json::Value>;

// Render the parser's view of the footprint as standalone SVG. THIS IS THE
// AUTHORITATIVE GROUND TRUTH — if a downstream tool draws something different,
// the downstream tool is the bug.
pub fn render_footprint_svg(footprint: &serde_json::Value) -> String;

// Render primary + every alternate variant, stacked vertically in one SVG.
pub fn render_all_footprints_svg(parsed: &serde_json::Value) -> String;

The returned Value is shaped exactly like
adom-chipsmith::view_library::parse_kicad_mod_text output:

{
  "module_name": "D8",
  "pad_count": 8,
  "pads": [{
    "number": "1", "type": "smd", "shape": "rect",
    "at": [-2.4638, -1.905, 0.0],
    "size": [1.9812, 0.5588],
    "layers": ["F.Cu", "F.Paste", "F.Mask"],
    "drill": null
  }],
  "silk_markers": [],
  "pin1_silk": { "found": false },
  "_source": "altium_pcblib",
  "_alternates": [ /* other footprints, sorted by pad_count desc */ ]
}

CLI bin: altium-pcblib-svg

Single binary that ships with the crate. Use it whenever a downstream
renderer looks suspect:

# Primary footprint, SVG to stdout.
altium-pcblib-svg /path/to/Foo.PcbLib > foo.svg

# Primary + every alternate variant stacked vertically.
altium-pcblib-svg /path/to/Foo.PcbLib --all -o foo-all.svg

SVG conventions:

  • 1 SVG unit = 1 mm. No scaling.
  • Y-down (matches both KiCad and our emitted JSON).
  • Rotation applied via transform="rotate(deg cx cy)" clockwise on screen.
  • Pad numbers stay un-rotated for readability.
  • F.Cu pads orange, B.Cu blue, thru-hole green, drill holes white on top.
  • Origin crosshair at (0, 0) with 1 mm arms.

Format quirks worth knowing

These bit us during reverse-engineering — they're encoded in the parser
already, but if you extend it or debug a weird chip, here's what's going
on.

1. FAT overshoot

INA226AIDGSR's PcbLib declares 256 FAT entries but only has 213 sectors.
The cfb crate refuses with Malformed FAT … FAT has N entries, but file has only M sectors. Fix: pad the input bytes to 1 MiB with 0xFF
(CFBF "free sector" sentinel) before opening. The unreferenced trailing
sectors don't hold real data.

2. Per-record-type sub-block count

Altium records aren't uniform [type][len][payload]. Different record
types carry different numbers of [u32 len][bytes] sub-blocks:

  • Pad (0x02): 6 sub-blocks (designator, layer-name pstring, four metadata blocks).
  • Text (0x05): 2 sub-blocks (main + style trailer).
  • Track / Arc / Via / Fill / Region / ComponentBody: 1 sub-block.

If you hit an unknown type byte mid-stream, the parser stops walking
gracefully — pads always come first, so by then we already have what we
need.

3. Pad block-5 layout (the 194-byte v3 / 202-byte v4 pad data)

[0]      u8 layer (1 = TOP_CU, 32 = BOTTOM_CU, ...)
[1]      u8 net/flag (0x08 in v3, 0x0c in v4 — version probe)
[2]      u8 reserved
[3..13]  10 bytes of 0xFF (component / net id sentinels)
[13..17] i32 LE x position (units = 1 / 10000 mil = 2.54e-6 mm)
[17..21] i32 LE y position (Altium Y-up — flipped to KiCad Y-down at parse)
[21..29] u32 LE x size, y size — top layer
[29..37] u32 LE x size, y size — mid layer (multi-layer thru-hole pads)
[37..45] u32 LE x size, y size — bottom layer
[45..49] u32 LE hole size (0 → SMD; > 0 → thru-hole)
[49..52] u8 × 3 shape codes (1 round, 2 rect, 3 octagonal, 9 rounded-rect)
[52..60] f64 LE rotation in degrees — ONLY in 202-byte v4 layout

Past offset 60 the layout drifts between Altium releases (paste / mask
offsets, plated bool, GUIDs). The parser stops at 60.

4. Two equally valid rotation conventions in the wild

This is the one that looks like a parser bug but isn't. Altium gives
library authors freedom in how to encode a rotated pad:

  • Convention A (matches KiCad): store the pad as a "vertical stick"
    (xsize < ysize), set rotation = 90° on the side pads.
  • Convention B (post-rotation): store the physical post-rotation
    dimensions (xsize > ysize for a horizontal pad), set rotation = 0.

Both render to the same physical pad on the PCB. A downstream tool that
compares size[] element-wise without applying rotation will see a 90°
mismatch in convention B → KiCad. The fix lives in the consumer (compute
the post-rotation bbox before comparing), not in this parser. When in
doubt, render the authoritative SVG and compare visually.

5. Y-axis flip

Altium Y-up vs KiCad Y-down. The parser flips Y at parse time so emitted
at[1] is in KiCad convention. Do not flip again downstream.

6. Round shape with unequal w/h → "oval"

KiCad's circle implies w == h. Altium's "round" (shape byte 1) covers
both true circles and stadium-shaped SMD pads with rounded ends. The
parser specializes: shape 1 with (xsize - ysize).abs() < 1e-3
circle, otherwise → oval (KiCad's stadium).

Trouble-shooting checklist when downstream renders pads wrong

  1. Run the authoritative SVG first. altium-pcblib-svg <path>.PcbLib > /tmp/auth.svg. If pads are right in the SVG, the bug is downstream.
  2. 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.
  3. 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.
  4. Check Y direction. If pads are mirrored top-to-bottom, your downstream double-flipped Y. The parser already emits KiCad Y-down.
  5. Scale. SVG units are mm. If the rendered size is off by 1000×, the consumer is treating mm as µm.

Tested chip corpus

Chip Package Variants Notes
LM358D SOIC-8 D8 / D8-L / D8-M Sanity baseline. 8 horizontal pads, rot=0 throughout.
INA226AIDGSR VSSOP-10 SOP50P490X110-10N v3 layout (Convention B). Triggers FAT overshoot quirk.
ADS1115IDGSR VSSOP-10 DGS0010A_L / _M / _N 3 alternate density variants.
DRV8316RRGFR VQFN-40 RGF0040E-IPC_A / _B / _C / -MFG IPC variants are Convention A; MFG variant is Convention B + extra fiducials/test points.