altium-format
Rust library for reading and writing Altium Designer files with a trait-based API and agent-friendly CLI.
Architecture
┌─────────────────────────────────────────────────────────────┐
│ altium-cli │
│ (clap binary with subcommands: inspect, query, export) │
└─────────────────────────┬───────────────────────────────────┘
│
┌─────────────────────────▼───────────────────────────────────┐
│ altium-format │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ api/ │ │ ops/ │ │ edit/ │ │
│ │ (3-layer) │ │ (queries) │ │ (sessions) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ ┌──────▼────────────────▼────────────────▼──────┐ │
│ │ records/ │ │
│ │ SchPrimitive trait ◄── SchRecord variants │ │
│ │ PcbPrimitive trait ◄── PcbRecord variants │ │
│ └──────────────────────┬────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼────────────────────────┐ │
│ │ io/ │ │
│ │ reader.rs, writer.rs, schdoc.rs, pcbdoc.rs │ │
│ └──────────────────────┬────────────────────────┘ │
│ │ │
│ ┌──────────────────────▼────────────────────────┐ │
│ │ types/ │ │
│ │ Coord, Color, Layer, ParameterCollection │ │
│ └───────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Data Flow
Altium File (.SchLib, .PcbDoc, etc.)
│
▼
cfb crate (OLE compound document)
│
▼
io/reader.rs (block parsing, decompression)
│
▼
api/generic/ (ParameterCollection, Value)
│
▼
FromParams/FromBinary traits
│
▼
Typed records (SchComponent, PcbPad, etc.)
│
▼
SchPrimitive/PcbPrimitive trait access
│
▼
ops/ queries and transforms
│
▼
CLI JSON output
Core Abstractions
Three-Layer API
Users choose their abstraction level:
-
Layer 1 (CFB): Raw access to compound file structure
- Use when: Reverse engineering unknown file types
- Example:
doc.cfb().streams()lists all internal files
-
Layer 2 (Generic): Dynamic access without type knowledge
- Use when: Schema is unknown or varies
- Example:
record.get("LIBREFERENCE")accesses any parameter
-
Layer 3 (Typed): Full deserialization with derive macros
- Use when: Working with known record types
- Example:
component.lib_referenceprovides type-safe access
Trait Hierarchy
SchPrimitive and PcbPrimitive traits enable polymorphic dispatch:
RECORD_ID/OBJECT_ID: Type identifier constantowner_index()/set_owner_index(): Hierarchy managementlocation(): Optional coordinate accessrecord_type_name(): Diagnostic type nameget_property(): Dynamic property lookupcalculate_bounds(): Bounding rectangle
Implemented by 30 schematic types and 15 PCB types. Adding a new record type requires only implementing the trait, not updating scattered match statements.
State Types Over Boolean Pairs
MaskExpansion replaces implicit state machines:
// Before: manual + value creates invalid states
pub paste_mask_expansion_manual: bool,
pub paste_mask_expansion: Coord,
// After: type system prevents invalid states
pub paste_mask_expansion: MaskExpansion, // Auto | Manual(Coord)
Validation happens at construction, not at use. Corrupted data from files returns Result::Err rather than panicking for recoverability.
Coordinate System
Altium uses fixed-point coordinates:
- 10,000 internal units = 1 mil = 0.001 inch
- All coordinate operations use
Coordnewtype wrapper Coord::from_mils(10)= 100,000 internal unitsCoord::from_mm(2.54)= 1,000,000 internal units (1 inch)
Integer storage avoids floating-point precision issues. Extract raw value with .to_raw() for arithmetic.
Roundtrip Fidelity Invariants
-
FromParams + ToParams must be inverses
- Property-based tests verify:
from_params(to_params(x)) == x - Unknown fields preserved in
UnknownFields
- Property-based tests verify:
-
FromBinary + ToBinary must be inverses
- Binary format must match byte-for-byte
- Golden file tests lock format compatibility
-
OWNERINDEX consistency
- Parent-child relationships encoded via OWNERINDEX
- Tree operations preserve hierarchy integrity
- Editing sessions track index mutations
Operations Design
ops/ modules provide pure query functions that return data, not formatted output:
components_by_category()returnsHashMap<Category, Vec<Component>>net_connections()returnsVec<NetConnection>power_map()returnsHashMap<NetName, Count>
CLI commands compose these operations. Same logic powers programmatic API and command-line tools.
Deterministic UUID Context
Standalone library uses uuid::Uuid::new_v4() for standard random UUIDs. Cadatomic fork replaces () parameter with DeterminismContext for event sourcing replay.
Signature: build_deterministic(&mut ()) accepts unit type; fork swaps in context type for deterministic generation.
Format Quirks
Display Mode Bounds
display_mode indexes into array of display_mode_count elements. Out-of-bounds access causes undefined behavior when rendering symbol views.
Validation: set_display_mode() returns Result::Err for invalid indices rather than panicking. Corrupted files should be recoverable.
Manual Flag Values
Altium binary format uses flag value 2 (not 1) for manual mode, 0 for auto mode. Observed from PCB1.PcbLib test files. MaskExpansion implementation uses != 0 check for robustness against format variations.
Parameter vs Binary Records
-
Schematic records: Parameter-based (key=value pairs)
- Format:
|RECORD=1|NAME=Test|X=100mil| - Trait:
FromParams,ToParams
- Format:
-
PCB records: Binary-based (fixed structs)
- Format: Block header + packed binary data
- Trait:
FromBinary,ToBinary
Different file types require different deserialization paths.
CLI Agent-Friendly Design
JSON-first output for programmatic consumption:
|
Selector queries for filtering:
Stable schemas locked by golden file tests. Breaking changes increment semver.
Build Instructions
Library
CLI Binary
Shell Completions
Usage Examples
Reading a Library
use SchLib;
use File;
use BufReader;
let file = open?;
let lib = open?;
for component in &lib.components
Creating a Footprint
use FootprintBuilder;
use PcbPadShape;
let mut builder = new;
builder.add_dual_row_smd;
let component = builder.build_deterministic;
Querying Components
use query_records;
use SchLib;
let lib = open?;
let resistors = query_records?;
for comp in resistors
Editing Sessions
use EditSession;
let mut session = new;
session.set_component_property;
session.commit?;
session.save?;
Testing Strategy
- Property-based tests: Verify trait contracts across all record types
- Real file integration: Test with actual Altium libraries
- Golden file tests: Lock CLI output format for stability
- Roundtrip tests: Ensure serialization is lossless
License
GPL-3.0-only