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 + footprint + 3D + pin↔pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.
← Commit history
2.26.0: EAGLE alignment vocabulary for group captions; invalid-align lint
7 files changed
+108−15
Cargo.lock+1−1@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-lbr"-version = "2.25.0"+version = "2.26.0" dependencies = [ "altium-codec", "anyhow",
Cargo.toml+1−1@@ -1,6 +1,6 @@ [package] name = "adom-lbr"-version = "2.25.0"+version = "2.26.0" edition = "2021" description = "adom-lbr generator / EDA translator: the canonical adom-lbr JSON is the hub, with native converters to Altium (.SchLib/.PcbLib/.IntLib), EAGLE/Fusion (.lbr), and KiCad. Altium output is written natively — no Altium needed at conversion time."
VERSION+1−1@@ -1 +1 @@-2.25.0+2.26.0
bin/adom-lbr⋯ 1 unchanged line ⋯
package.json+1−1@@ -1,6 +1,6 @@ { "slug": "adom-lbr",- "version": "2.25.0",+ "version": "2.26.0", "type": "app", "description": "adom-lbr \u2014 the EDA library translator. Bring a component in from any supported EDA tool and convert it to any other (KiCad \u21c4 Altium \u21c4 EAGLE/Fusion) through one canonical adom-lbr JSON \u2014 symbol + footprint + 3D + pin\u2194pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.", "dependencies": {},
page.json+2−2@@ -1,6 +1,6 @@ { "slug": "adom-lbr",- "version": "2.25.0",+ "version": "2.26.0", "type": "app", "description": "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 + footprint + 3D + pin↔pad map. Altium is written natively (no Altium needed). Includes the adom-lbr Manager: browse wiki components and view symbol/footprint/3D + the per-EDA layer stackup.", "dependencies": {},@@ -88,4 +88,4 @@ "label": "Browse parts" } ]-}+}
src/main.rs+102−9@@ -356,6 +356,12 @@ struct GroupLabel { text: String, x: f64, y: f64,+ /// Rotation from the source `(at x y ANGLE)`. adom-symbol writes 0 for the left and+ /// right captions and 90 for the top and bottom ones.+ angle: u32,+ /// KiCad `(justify left|right)`. It is the anchored END of the text, so it decides+ /// which EAGLE align value is faithful; KiCad's vertical default is centre.+ justify_right: bool, } #[derive(Debug, Serialize, Deserialize)]@@ -474,14 +480,33 @@ pub(crate) fn parse_kicad_sym(content: &str, name_override: Option<&str>) -> Res } let mut group_labels = Vec::new();+ // Capture the caption's ANGLE and JUSTIFY too, not just its position. adom-symbol+ // places these deliberately (1.02 mm inside the body edge, rotated 90 on the top and+ // bottom edges) and states which end is anchored; re-deriving the side from x alone+ // threw that away and got the right-hand captions wrong. let text_re = regex_lite::Regex::new(- r#"\(text "([^"]+)"\s+\(at\s+([-\d.]+)\s+([-\d.]+)"#+ r#"\(text "([^"]+)"\s+\(at\s+([-\d.]+)\s+([-\d.]+)\s+([-\d.]+)\)"# ).unwrap();- for cap in text_re.captures_iter(content) {+ let text_starts: Vec<usize> = text_re.find_iter(content).map(|m| m.start()).collect();+ for (i, cap) in text_re.captures_iter(content).enumerate() {+ // `justify` lives in the (effects (font ...) (justify ...)) block AFTER the (at ...),+ // so read the window from this element up to the next one rather than trying to+ // close the s-expression with a regex: a non-greedy match stops at the first ')',+ // which is the end of (size 0.762 0.762), long before the justify.+ let start = text_starts.get(i).copied().unwrap_or(0);+ let end = text_starts.get(i + 1).copied().unwrap_or(content.len());+ let window = &content[start..end];+ // TENTHS OF A DEGREE: KiCad writes a text angle as 900 for 90 degrees here, while+ // pin angles in the same file are plain degrees. Taking it as degrees turned every+ // rotated caption into 900 % 360 = 180 and lost the rotation entirely.+ let raw: f64 = cap[4].parse().unwrap_or(0.0);+ let deg = if raw.abs() > 360.0 { raw / 10.0 } else { raw }; group_labels.push(GroupLabel { text: cap[1].to_string(), x: cap[2].parse().unwrap_or(0.0), y: cap[3].parse().unwrap_or(0.0),+ angle: ((deg.round() as i64 % 360 + 360) % 360) as u32,+ justify_right: window.contains("justify right"), }); } @@ -1722,16 +1747,27 @@ fn emit_symbol_block(xml: &mut String, sym: &ParsedSymbol, symbol_art: Option<&S let body_left = sym.body.as_ref().map(|b| b.left).unwrap_or(-10.0); let body_right = sym.body.as_ref().map(|b| b.right).unwrap_or(10.0); for label in &sym.group_labels {- let (lx, align) = if label.x > body_center_x {- (body_right - 1.0, " align=\"right\"")- } else {- (body_left + 1.0, "")- };+ // FAITHFUL MAPPING of adom-symbol's four caption forms, rather than re-deriving the+ // side from x. EAGLE's align vocabulary is the nine {bottom|center|top}-{left|+ // center|right} values; a bare "right" is not one of them, so Fusion logged a+ // warning per caption, ignored it, and fell back to LEFT anchoring, which made every+ // right-hand caption run outward across the body line (John saw it on 2CG and SQG).+ // KiCad's vertical default is centre, so center-* is the faithful vertical anchor.+ // left (at x y 0) justify left -> R0, center-left+ // right (at x y 0) justify right -> R0, center-right+ // top (at x y 90) justify right -> R90, center-right+ // bottom (at x y 90) justify left -> R90, center-left+ let align = if label.justify_right { "center-right" } else { "center-left" };+ let rot = if label.angle == 90 || label.angle == 270 { " rot=\"R90\"" } else { "" };+ // The source already places captions 1.02 mm inside the edge. Use its coordinates+ // instead of recomputing them from the body, which is how the old code lost the+ // top and bottom captions entirely. xml.push_str(&format!(- " <text x=\"{}\" y=\"{}\" size=\"0.762\" layer=\"94\" font=\"vector\" ratio=\"15\"{}>{}</text>\n",- lx, label.y, align, esc_xml(&label.text)+ " <text x=\"{:.4}\" y=\"{:.4}\" size=\"0.762\" layer=\"94\" font=\"vector\" ratio=\"15\"{} align=\"{}\">{}</text>\n",+ label.x, label.y, rot, align, esc_xml(&label.text) )); }+ let _ = (body_center_x, body_left, body_right); xml.push_str("</symbol>\n"); deduped_names@@ -1976,6 +2012,30 @@ pub(crate) fn lint_lbr(content: &str) -> LintResult { errors.push(LintError { code: "missing-devicesets".into(), message: "No <devicesets> section found".into() }); } + // EAGLE's align vocabulary is exactly nine values. Anything else makes Fusion log a+ // warning per element on open and fall back to bottom-left, so text anchored to an+ // edge runs OUTWARD across the body line instead of inward. adom-lbr shipped+ // align="right" on right-side group captions until 2.26.0; this stops any align value+ // reaching a library without a name EAGLE knows.+ {+ const VALID: [&str; 9] = ["bottom-left", "bottom-center", "bottom-right",+ "center-left", "center", "center-right",+ "top-left", "top-center", "top-right"];+ let re = regex_lite::Regex::new(r#"align="([^"]*)""#).unwrap();+ let mut bad: Vec<String> = Vec::new();+ for c in re.captures_iter(content) {+ let v = c[1].to_string();+ if !VALID.contains(&v.as_str()) && !bad.contains(&v) { bad.push(v); }+ }+ if !bad.is_empty() {+ errors.push(LintError {+ code: "invalid-align".into(),+ message: format!("align={:?} is not an EAGLE alignment. Valid: {}. Fusion warns per element and falls back to bottom-left, so edge-anchored text runs across the body line.",+ bad.join(", "), VALID.join(", ")),+ });+ }+ }+ // Check for empty packages. Matching the literal "<packages></packages>" missed // a library whose section held only whitespace or a comment, which shipped with // zero packages while its .kicad_mod had two perfectly good SMD pads. Ask the@@ -3304,6 +3364,39 @@ mod lint_tests { assert!(out.contains("2.5 x 0.5 mm"), "expected the copper extent: {out}"); } + #[test]+ fn group_captions_map_to_eagle_alignment_not_a_bare_right() {+ use super::parse_kicad_sym;+ // adom-symbol's four caption forms, verbatim in shape: left and right unrotated,+ // top and bottom rotated, with the angle written in TENTHS of a degree (900 = 90).+ let src = r#"(kicad_symbol_lib (version 20211014) (generator adom)+ (symbol "T" (property "Reference" "U" (id 0) (at 0 0 0))+ (symbol "T_1_1"+ (text "Handshake" (at -11.68 0.00 0)+ (effects (font (size 0.762 0.762)) (justify left)))+ (text "UART" (at 11.68 0.00 0)+ (effects (font (size 0.762 0.762)) (justify right)))+ (text "Power" (at -7.72 7.08 900)+ (effects (font (size 0.762 0.762)) (justify right)))+ (text "Ground" (at -7.72 -22.65 900)+ (effects (font (size 0.762 0.762)) (justify left)))+ (pin passive line (at -15.24 0 0) (length 2.54)+ (name "A" (effects (font (size 1.27 1.27)))) (number "1" (effects (font (size 1.27 1.27)))))+ )))"#;+ let sym = parse_kicad_sym(src, None).expect("parses");+ let by = |t: &str| sym.group_labels.iter().find(|g| g.text == t).expect(t).clone();+ // left: unrotated, anchored on its left end+ let l = by("Handshake"); assert_eq!(l.angle, 0); assert!(!l.justify_right);+ // right: unrotated, anchored on its RIGHT end. Emitting a bare align="right" here+ // is what made Fusion warn and fall back to left anchoring, so the caption ran out+ // across the body line.+ let r = by("UART"); assert_eq!(r.angle, 0); assert!(r.justify_right);+ // top and bottom: rotated. 900 is tenths of a degree; reading it as degrees gives+ // 900 % 360 = 180 and loses the rotation.+ let t = by("Power"); assert_eq!(t.angle, 90); assert!(t.justify_right);+ let b = by("Ground"); assert_eq!(b.angle, 90); assert!(!b.justify_right);+ }+ #[test] fn a_symbol_that_draws_itself_is_not_relaid_out() { use super::{relayout_two_columns, KicadPin, ParsedSymbol, SymGraphic};