app
Adom Concur
Public Made by Adomby adom
Cross-source consensus check for chip libraries — scores whether the KiCad, EAGLE, and Altium libraries AND the adom-ds2sf datasheet extraction all AGREE on pins, pads, names, and package, across seve
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
//! EAGLE `.lbr` (Fusion 360 Library) parser.
//!
//! XML format. Parts of interest:
//! - `<deviceset name="...">` — the device's catalog name (closest to MPN).
//! - `<symbol name="...">` containing `<pin name="..."/>` — symbol-side pin labels.
//! - `<package name="...">` containing `<smd name="N"/>` and/or `<pad name="N"/>`.
//! - `<connect gate="..." pin="VDD" pad="8"/>` — the bridge from symbol pin to
//! footprint pad. Lets us reconstruct a per-pin {number, name} map.
use anyhow::{Context as _, Result};
use std::collections::BTreeMap;
use std::fs;
use std::path::Path;
use super::{Pad, PackageInfo, Part, Pin};
pub fn parse(path: &Path) -> Result<Part> {
let raw = fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?;
// Older EAGLE files (7.x and earlier) carry a `<!DOCTYPE eagle SYSTEM "eagle.dtd">`
// declaration. roxmltree disallows DTDs by default, so opt in to parsing them.
let opts = roxmltree::ParsingOptions { allow_dtd: true, ..Default::default() };
let doc = roxmltree::Document::parse_with_options(&raw, opts).context("parsing EAGLE XML")?;
let mut deviceset_name: Option<String> = None;
// Per-symbol: map<pin_name, _>. We only need names for now.
// Per-package: list<pad_name>.
let mut package_pads: BTreeMap<String, Vec<String>> = BTreeMap::new();
let mut package_dims: BTreeMap<String, (Option<f64>, Option<f64>)> = BTreeMap::new();
// Connections: deviceset -> Vec<(pin_name, pad_number)>.
let mut connections: Vec<(String, String)> = Vec::new();
let mut chosen_package: Option<String> = None;
// Walk every node (DFS, document order).
for node in doc.descendants() {
let tag = node.tag_name().name();
match tag {
"deviceset" => {
if deviceset_name.is_none() {
if let Some(n) = node.attribute("name") {
deviceset_name = Some(n.to_string());
}
}
}
"package" => {
if let Some(name) = node.attribute("name") {
let mut pads: Vec<String> = Vec::new();
let mut min_x = f64::INFINITY;
let mut max_x = f64::NEG_INFINITY;
let mut min_y = f64::INFINITY;
let mut max_y = f64::NEG_INFINITY;
for child in node.descendants() {
let ct = child.tag_name().name();
if ct == "smd" || ct == "pad" {
if let Some(n) = child.attribute("name") {
pads.push(n.to_string());
}
if let (Some(xs), Some(ys)) =
(child.attribute("x"), child.attribute("y"))
{
if let (Ok(x), Ok(y)) = (xs.parse::<f64>(), ys.parse::<f64>()) {
min_x = min_x.min(x);
max_x = max_x.max(x);
min_y = min_y.min(y);
max_y = max_y.max(y);
}
}
}
}
let bx = if max_x.is_finite() { Some((max_x - min_x).abs()) } else { None };
let by = if max_y.is_finite() { Some((max_y - min_y).abs()) } else { None };
package_pads.insert(name.to_string(), pads);
package_dims.insert(name.to_string(), (bx, by));
}
}
"connect" => {
if let (Some(pin), Some(pad)) = (node.attribute("pin"), node.attribute("pad")) {
connections.push((pin.to_string(), pad.to_string()));
}
}
"device" => {
// <device name="..." package="..."> — pick the first package referenced.
if chosen_package.is_none() {
if let Some(p) = node.attribute("package") {
chosen_package = Some(p.to_string());
}
}
}
_ => {}
}
}
// Reconstruct pins: prefer the connect[]'s name+pad mapping. Dedupe on (name, pad).
let mut pins: Vec<Pin> = Vec::new();
let mut seen: std::collections::HashSet<(String, String)> = std::collections::HashSet::new();
for (pin_name, pad_num) in connections.iter() {
let key = (pad_num.clone(), pin_name.clone());
if seen.insert(key) {
pins.push(Pin {
number: pad_num.clone(),
name: pin_name.clone(),
is_stub_name: pin_name.is_empty() || pin_name == pad_num,
});
}
}
// Choose a package. Prefer the one referenced by the first <device package=...>, else
// the first declared in document order.
let pkg_key = chosen_package
.clone()
.or_else(|| package_pads.keys().next().cloned());
let (pads, package) = if let Some(k) = pkg_key {
let pads_vec = package_pads
.get(&k)
.cloned()
.unwrap_or_default()
.into_iter()
.map(|n| Pad { number: n })
.collect();
let (bx, by) = package_dims.get(&k).cloned().unwrap_or((None, None));
(
pads_vec,
Some(PackageInfo { raw_name: k, body_x_mm: bx, body_y_mm: by }),
)
} else {
(Vec::new(), None)
};
Ok(Part {
source: "eagle".to_string(),
source_part_name: deviceset_name,
pads,
pins,
package,
})
}