app
Adom Library
Public Made by Adomby adom
adom-lbr — the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad ⇄ Altium ⇄ EAGLE/Fusion) through one canonical adom-lbr JSON — symbol + footp
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
//! The **adom-lbr** part model — the in-memory form of the adom-lbr json, the
//! canonical Adom library format. Every EDA adapter (Altium, EAGLE/Fusion,
//! OrCAD) converts to and from this one shape. It is Adom's own format; the
//! per-tool files are just generated views of it.
//!
//! Units: symbols use Altium schematic units (1 = 1/100 inch grid step, i.e.
//! "mils/10" as Altium stores Location.X/Y as i16 in 1/100-inch? — see schlib.rs
//! for the exact scale). Footprints use millimetres at the model layer; the
//! PcbLib encoder converts mm → Altium's 1/10000-mil integer units.
use serde::{Deserialize, Serialize};
/// A schematic symbol: a body rectangle plus pins. One SchLib holds many.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Symbol {
/// LibReference — the component's library name.
pub name: String,
/// Pins, in declaration order.
pub pins: Vec<Pin>,
/// Body rectangle in mils (lo corner, hi corner). Defaults to a sane box.
#[serde(default)]
pub body: Option<BodyRect>,
/// Generic body graphics (polylines/rects/circles/arcs). When present, these
/// are drawn instead of the default body box. Format-neutral.
#[serde(default)]
pub graphics: Vec<Graphic>,
/// Designator prefix without the '?' (e.g. "U", "R"). Default "U".
#[serde(default)]
pub designator_prefix: Option<String>,
/// Optional footprint name to link (PCBLIB implementation).
#[serde(default)]
pub footprint: Option<String>,
/// Component property fields (Altium RECORD=41 parameters) — e.g. Value,
/// Manufacturer, MPN. Shown in the component's Properties panel.
#[serde(default)]
pub parameters: Vec<Parameter>,
/// Whether pin numbers are hidden on the symbol (KiCad `(pin_numbers
/// (hide yes))`). `None` → the exporter picks a passive-friendly default.
#[serde(default)]
pub pin_numbers_hidden: Option<bool>,
/// Whether pin names are hidden on the symbol. `None` → default.
#[serde(default)]
pub pin_names_hidden: Option<bool>,
}
/// A component parameter / property field (Altium RECORD=41). Appears in the
/// component's Properties. `visible=false` → the field exists but isn't drawn
/// on the symbol (Altium `IsHidden=T`), which is the norm for metadata.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Parameter {
/// Property name (e.g. "Value", "Manufacturer").
pub name: String,
/// Property value/text.
pub value: String,
/// Whether the field is drawn on the symbol. Metadata defaults to hidden.
#[serde(default)]
pub visible: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Pin {
/// Pin number / designator (e.g. "1", "A4").
pub number: String,
/// Pin name / label (e.g. "VDD", "GND").
pub name: String,
/// Location.X in mils.
pub x: i32,
/// Location.Y in mils.
pub y: i32,
/// Pin length in mils. Default 200.
#[serde(default)]
pub length: Option<i32>,
/// Orientation in degrees: 0 / 90 / 180 / 270.
#[serde(default)]
pub rotation: u16,
}
/// A generic symbol graphic primitive (the symbol body / decorations). Units are
/// mils. This is format-agnostic: each EDA adapter maps it to its own primitive
/// (KiCad polyline/rectangle/circle/arc ↔ Altium RECORD=6/14/11/12). Works for
/// ANY component — nothing is cap-specific.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "lowercase")]
pub enum Graphic {
/// Connected line segments through `points` (mils).
Polyline { points: Vec<[f64; 2]>, #[serde(default)] width: f64 },
/// Axis-aligned rectangle (mils).
Rect { x1: f64, y1: f64, x2: f64, y2: f64, #[serde(default)] width: f64, #[serde(default)] filled: bool },
/// Circle (mils).
Circle { cx: f64, cy: f64, r: f64, #[serde(default)] width: f64 },
/// Arc, angles in degrees (mils).
Arc { cx: f64, cy: f64, r: f64, start: f64, end: f64, #[serde(default)] width: f64 },
}
impl Symbol {
/// Shift the symbol so its bounding box (pins + graphics) is centered on the
/// origin, snapped to the 50-mil grid so pins stay grid-aligned.
pub fn center(&mut self) {
let (mut minx, mut maxx) = (f64::INFINITY, f64::NEG_INFINITY);
let (mut miny, mut maxy) = (f64::INFINITY, f64::NEG_INFINITY);
let mut upd = |x: f64, y: f64| {
minx = minx.min(x);
maxx = maxx.max(x);
miny = miny.min(y);
maxy = maxy.max(y);
};
for p in &self.pins {
upd(p.x as f64, p.y as f64);
}
for g in &self.graphics {
match g {
Graphic::Polyline { points, .. } => {
for pt in points {
upd(pt[0], pt[1]);
}
}
Graphic::Rect { x1, y1, x2, y2, .. } => {
upd(*x1, *y1);
upd(*x2, *y2);
}
Graphic::Circle { cx, cy, r, .. } | Graphic::Arc { cx, cy, r, .. } => {
upd(cx - r, cy - r);
upd(cx + r, cy + r);
}
}
}
if !minx.is_finite() {
return;
}
let cx = (((minx + maxx) / 2.0) / 50.0).round() * 50.0;
let cy = (((miny + maxy) / 2.0) / 50.0).round() * 50.0;
for p in &mut self.pins {
p.x -= cx as i32;
p.y -= cy as i32;
}
for g in &mut self.graphics {
match g {
Graphic::Polyline { points, .. } => {
for pt in points.iter_mut() {
pt[0] -= cx;
pt[1] -= cy;
}
}
Graphic::Rect { x1, y1, x2, y2, .. } => {
*x1 -= cx;
*x2 -= cx;
*y1 -= cy;
*y2 -= cy;
}
Graphic::Circle { cx: gcx, cy: gcy, .. }
| Graphic::Arc { cx: gcx, cy: gcy, .. } => {
*gcx -= cx;
*gcy -= cy;
}
}
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct BodyRect {
pub x1: i32,
pub y1: i32,
pub x2: i32,
pub y2: i32,
}
impl Default for BodyRect {
fn default() -> Self {
BodyRect { x1: 0, y1: -50, x2: 200, y2: 50 }
}
}
/// A PCB footprint: pads plus silk / courtyard / fab graphics. One PcbLib holds
/// many.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Footprint {
/// Pattern / footprint name.
pub name: String,
pub pads: Vec<Pad>,
/// Non-copper graphics (silkscreen, courtyard, fab/assembly outlines). Kept
/// layer-tagged so every EDA target lands them on the right layer.
#[serde(default)]
pub graphics: Vec<FpGraphic>,
}
/// Footprint layer role — format-neutral. Each EDA adapter maps it to its own
/// layer (KiCad `F.SilkS`/`F.CrtYd`/`F.Fab` ↔ Altium TopOverlay(33)/Mech15(71)/
/// Mech13(69)). `Other` carries the raw Altium layer byte so unknown layers
/// survive a round trip.
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(tag = "role", rename_all = "lowercase")]
pub enum FpLayer {
Silk,
Courtyard,
Fab,
Copper,
Other {
/// Raw Altium layer id (for round-trip fidelity).
#[serde(default)]
altium: u8,
},
}
/// A footprint graphic primitive on a non-copper layer. Units: mm, Y-down
/// (neutral convention). Format-neutral; each adapter maps it to its own record
/// (KiCad fp_line/fp_rect/fp_circle/fp_arc ↔ Altium Track/Arc).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "lowercase")]
pub enum FpGraphic {
/// Straight segment.
Line { layer: FpLayer, x1: f64, y1: f64, x2: f64, y2: f64, #[serde(default)] width: f64 },
/// Axis-aligned rectangle (emitted as 4 lines in most targets).
Rect { layer: FpLayer, x1: f64, y1: f64, x2: f64, y2: f64, #[serde(default)] width: f64 },
/// Circle.
Circle { layer: FpLayer, cx: f64, cy: f64, r: f64, #[serde(default)] width: f64 },
/// Arc, angles in degrees (CCW from start to end).
Arc { layer: FpLayer, cx: f64, cy: f64, r: f64, start: f64, end: f64, #[serde(default)] width: f64 },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Pad {
/// Pad designator (e.g. "1", "2", "GND").
pub number: String,
/// Centre X in mm (KiCad/neutral convention, Y-down).
pub x_mm: f64,
/// Centre Y in mm (Y-down — the encoder flips to Altium's Y-up).
pub y_mm: f64,
/// Pad width / height in mm.
pub w_mm: f64,
pub h_mm: f64,
/// Drill diameter in mm. 0 → SMD; > 0 → through-hole.
#[serde(default)]
pub drill_mm: f64,
/// Rotation in degrees.
#[serde(default)]
pub rotation: f64,
/// "rect" | "circle" | "oval" | "roundrect".
#[serde(default = "default_shape")]
pub shape: String,
/// Through-hole plating. `None`/`Some(true)` → plated (normal TH pad);
/// `Some(false)` → NPTH (KiCad `np_thru_hole`, e.g. a mounting hole).
/// Only meaningful when `drill_mm > 0`. NOTE: the Altium binary encoder
/// currently emits NPTH as a plated TH pad (the plated flag byte in the
/// pad record is not yet calibrated) — the hole survives, the plating
/// distinction doesn't. KiCad export round-trips it faithfully.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub plated: Option<bool>,
}
fn default_shape() -> String {
"rect".to_string()
}
/// The unified "Adom Library" part — one document carrying identity + symbol +
/// footprint + the pin↔pad link. This is the adom-lbr json the wiki stores and
/// every EDA adapter converts to/from.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdomLbrPart {
pub schema_version: u32,
pub mpn: String,
#[serde(default)]
pub manufacturer: String,
#[serde(default)]
pub package: String,
#[serde(default)]
pub value: String,
#[serde(default)]
pub category: String,
pub symbol: Symbol,
pub footprint: Footprint,
#[serde(default)]
pub connect: Vec<ConnectEntry>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub model_3d: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub provenance: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectEntry {
pub pin: String,
pub pad: String,
}