altium-pcblib
UnreviewedNative Rust parser for Altium .PcbLib binary footprint libraries — used by chipsmith + concur for cross-source agreement.
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'sparse_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 likeadom-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 emittedat[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
- Run the authoritative SVG first.
altium-pcblib-svg <path>.PcbLib > /tmp/auth.svg. If pads are right in the SVG, the bug is downstream. - Check which variant got picked.
parsed.module_nameplus_alternates[*].module_name. Manufacturing variants often have extra pads vs the IPC pinout variant; concur usesparse_with_consensus_hintto pick the right one. - 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.
- Check Y direction. If pads are mirrored top-to-bottom, your downstream double-flipped Y. The parser already emits KiCad Y-down.
- 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. |