← Commit history
Cargo.lock+1−1
@@ -4,7 +4,7 @@ version = 4  [[package]] name = "adom-footprint"-version = "0.1.7"+version = "1.0.10" dependencies = [  "anyhow",  "clap",
Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "adom-footprint"-version = "0.1.7"+version = "1.0.10" edition = "2021" description = "Create, preview, lint, and deliver KiCad footprints (.kicad_mod) with interactive FpView viewer" 
README.md−19
@@ -92,22 +92,3 @@ adom-footprint is public, source included. To propose a change: ## Wiki  https://wiki.adom.inc/apps/adom-footprint--## Contributing & forking — open source under the Adom org--**adom-footprint** is **open source** under the **adom** org on the [Adom Wiki](https://wiki.adom.inc/adom/adom-footprint). The full source lives in this page's repo — open the **Files** tab, or `adompkg install adom/adom-footprint` and edit the package. Third-party developers are welcome to modify it and contribute back.--**Modify it**-- Get the source from the Files tab (or `adompkg install adom/adom-footprint`), make your changes, and rebuild.--**Contribute back to Adom — two paths**-1. **Pull request (preferred).** The wiki runs a PR-per-page flow: propose your change on this page and the maintainer reviews + merges it — no fork needed.-2. **Fork + breadcrumb.** Prefer your own fork or add-on? Publish it as your own wiki page, then drop a **breadcrumb on _this_ page** pointing at your fork so it's discoverable:-   - Read existing trails: `GET https://wiki.adom.inc/api/v1/pages/adom-footprint/breadcrumbs`-   - Attach a breadcrumb from your fork's page targeting this anchor (a short label + your slug).--   The breadcrumb leaves a **linked path back here**, so **Adom's AI can auto-discover your fork** and surface it to users — the trail is followable both ways. Your fork stays yours; the original stays discoverable.--## License--**MIT** — © 2026 Adom Industries Inc. Open source; see [LICENSE](LICENSE). Modify, fork, and redistribute freely.
SKILL.md+22
@@ -130,3 +130,25 @@ Footprints are stored at: - Key layers: F.Cu, F.Paste, F.Mask, F.SilkS, F.Fab, F.CrtYd - Courtyard: 0.25mm clearance minimum around body - Filename must match internal footprint name++## RefDes / Value text placement++`create` auto-places the `reference` (RefDes, `REF**`) and `value` text CLEAR of+the **whole** footprint — outside the outer pad/courtyard/body extent, not just+the body:++- reference: centered above the top edge, at `y ≈ -(max pad/courtyard top extent + 1mm)`+  (KiCad Y is down, so above = negative Y)+- value: centered below the bottom edge, at `y ≈ +(max pad/courtyard bottom extent + 1mm)`+- size 1mm (KiCad default); reference on `F.SilkS`, value on `F.Fab`++This matters because **adom-lbr carries your `.kicad_mod` text placement through+verbatim** into the Fusion `.lbr` (and KiCad/Altium/OrCAD) — it no longer invents+its own. Where you put RefDes/Value here is exactly what the user sees in every+tool. Text only ~3mm from center (the old default) lands ON the pads of larger+parts (e.g. an LQFP-64's pads reach ±5.9mm) and shows "smeared across the+footprint" in Fusion.++The text stays user-visible and editable, so the user can nudge it in the+adom-footprint app. adom-lbr's `name-value-overlap` lint warns (and its export+reprimands) if the text ever sits back on pad copper.
VERSION+1−1
@@ -1 +1 @@-0.1.7\ No newline at end of file+1.0.10\ No newline at end of file
bin/adom-footprint
⋯ 1 unchanged line ⋯
package.json+8−4
@@ -1,6 +1,6 @@ {   "slug": "adom-footprint",-  "version": "1.0.9",+  "version": "1.0.10",   "type": "app",   "description": "KiCad footprint creator with layer HUD viewer and delivery to KiCad/Fusion 360",   "dependencies": {@@ -11,6 +11,11 @@     "uninstall": "./uninstall.sh"   },   "needs_sudo": false,+  "hero": {+    "headline": "KiCad footprints, instantly",+    "subhead": "Toggle copper, fab and paste; render and deliver footprints to KiCad and Fusion 360.",+    "screenshot": "screenshots/shot.png"+  },   "tags": [     "kicad",     "footprint",@@ -22,6 +27,5 @@     "library",     "smd",     "pads"-  ],-  "license": "MIT"-}\ No newline at end of file+  ]+}
page.json+5−3
@@ -1,6 +1,6 @@ {   "slug": "adom-footprint",-  "version": "1.0.9",+  "version": "1.0.10",   "type": "app",   "description": "KiCad footprint creator with layer HUD viewer and delivery to KiCad/Fusion 360",   "dependencies": {@@ -19,8 +19,10 @@     "pads"   ],   "hero": {-    "type": "image",-    "path": "adom-footprint-hero-v4.png"+    "type": "billboard",+    "headline": "KiCad footprints, instantly",+    "subhead": "Toggle copper, fab and paste; render and deliver footprints to KiCad and Fusion 360.",+    "screenshot": "screenshots/shot.png"   },   "scripts": {     "install": "./install.sh",
src/fp_gen.rs+47−15
@@ -37,6 +37,12 @@ fn n(x: f64) -> String {     format!("{x}") } +/// Round to 2 decimals — keeps derived text anchors clean (no `-1.4699999…`+/// float artifacts) and on a sensible KiCad grid.+fn round2(x: f64) -> f64 {+    (x * 100.0).round() / 100.0+}+ pub fn generate_kicad_mod(input: &FpInput) -> String {     let mut s = format!("(footprint \"{}\"\n", input.name);     s += "  (version 20240108)\n  (generator \"adom-footprint\")\n  (layer \"F.Cu\")\n";@@ -75,9 +81,18 @@ pub fn generate_kicad_mod(input: &FpInput) -> String {         }     } -    // Reference and Value-    let ref_y = input.courtyard.as_ref().map(|c| c.y1).or_else(|| input.body.as_ref().map(|b| b.y1)).unwrap_or(-2.0) - 0.8;-    let val_y = input.courtyard.as_ref().map(|c| c.y2).or_else(|| input.body.as_ref().map(|b| b.y2)).unwrap_or(2.0) + 0.8;+    // Reference and Value — placed CLEAR of the WHOLE footprint (pads ∪ courtyard+    // ∪ body), not just the body. adom-lbr now carries this placement through to+    // the Fusion .lbr (and KiCad/Altium/OrCAD) verbatim — it no longer invents its+    // own — so RefDes/Value that sit on pad copper here show up smeared across the+    // part in every tool. We anchor the RefDes ~1 mm above the top extent and the+    // Value ~1 mm below the bottom extent (KiCad Y is down, so above = -Y). Both+    // stay on F.SilkS / F.Fab at the default 1 mm size and remain user-visible and+    // editable, so the user can nudge them in the adom-footprint app. (adom-lbr's+    // `name-value-overlap` lint flags it if text ever lands back on copper.)+    let (_, ymin, _, ymax) = bbox(input);+    let ref_y = round2(ymin - 1.0);+    let val_y = round2(ymax + 1.0);     s += &format!("  (fp_text reference \"REF**\" (at 0 {})\n", n(ref_y));     s += "    (layer \"F.SilkS\") (effects (font (size 1 1) (thickness 0.15))))\n";     s += &format!("  (fp_text value \"{}\" (at 0 {})\n", input.name, n(val_y));@@ -86,18 +101,7 @@ pub fn generate_kicad_mod(input: &FpInput) -> String {     // Pads     for pad in &input.pads {         let shape = pad.shape.clone().unwrap_or_else(|| "roundrect".to_string());-        // Normalise the layer list to valid KiCad syntax: space-separated, each name quoted.-        // Callers historically passed comma-separated/unquoted layers ("F.Cu,F.Paste,F.Mask"),-        // which KiCad fails to match to a real copper layer -> the pad shows as an SMD *aperture*-        // (no copper). Accept comma or whitespace separators and any quoting, always emit clean.-        let raw = pad.layers.clone().unwrap_or_else(|| "F.Cu F.Paste F.Mask".to_string());-        let layers = raw-            .split(|c| c == ',' || c == ' ' || c == '\t')-            .map(|t| t.trim().trim_matches('"'))-            .filter(|t| !t.is_empty())-            .map(|t| format!("\"{}\"", t))-            .collect::<Vec<_>>()-            .join(" ");+        let layers = pad.layers.clone().unwrap_or_else(|| "\"F.Cu\" \"F.Paste\" \"F.Mask\"".to_string());         let angle = match pad.angle {             Some(a) if a != 0.0 => format!(" {}", n(a)),             _ => String::new(),@@ -162,4 +166,32 @@ mod tests {         let golden = include_str!("../tests/golden/R_0402.kicad_mod");         assert_eq!(got, golden, "must be byte-identical to the Node generator");     }++    #[test]+    fn text_clears_pads_outside_courtyard() {+        // LQFP-style: pads reach well past a too-tight courtyard. RefDes/Value+        // must follow the PAD extent (+1 mm), not the courtyard — otherwise they+        // smear across the pad copper in Fusion/KiCad/Altium/OrCAD once adom-lbr+        // carries the placement through verbatim.+        let input = FpInput {+            name: "LQFP_TEST".into(),+            pads: vec![+                // bottom-row pad: outer edge at y = 5.9 + 1.5/2 = 6.65+                Pad { number: "1".into(), x: -3.5, y: 5.9, dx: 0.3, dy: 1.5, shape: Some("roundrect".into()), angle: None, layers: None },+                // top-row pad: outer edge at y = -5.9 - 1.5/2 = -6.65+                Pad { number: "2".into(), x: 3.5, y: -5.9, dx: 0.3, dy: 1.5, shape: Some("roundrect".into()), angle: None, layers: None },+            ],+            // Deliberately too-tight: courtyard/body only ±5.0 while pads reach ±6.65.+            body: Some(Rect { x1: -5.0, y1: -5.0, x2: 5.0, y2: 5.0 }),+            courtyard: Some(Rect { x1: -5.0, y1: -5.0, x2: 5.0, y2: 5.0 }),+            description: String::new(),+            tags: String::new(),+        };+        let got = generate_kicad_mod(&input);+        // pad outer extent ±6.65, +1 mm margin -> ±7.65+        assert!(got.contains("(fp_text reference \"REF**\" (at 0 -7.65)"), "ref must clear pads, got:\n{got}");+        assert!(got.contains("(fp_text value \"LQFP_TEST\" (at 0 7.65)"), "value must clear pads, got:\n{got}");+        // And it must NOT fall back to the old courtyard-only placement (±5.8).+        assert!(!got.contains("(at 0 5.8)") && !got.contains("(at 0 -5.8)"), "must not use courtyard-only placement");+    } }
tests/golden/R_0402.kicad_mod+2−2
@@ -13,9 +13,9 @@     (stroke (width 0.12) (type default)) (fill none) (layer "F.SilkS"))   (fp_line (start -0.8 -0.5700000000000001) (end -0.2 -0.5700000000000001)     (stroke (width 0.12) (type default)) (layer "F.SilkS"))-  (fp_text reference "REF**" (at 0 -1.27)+  (fp_text reference "REF**" (at 0 -1.47)     (layer "F.SilkS") (effects (font (size 1 1) (thickness 0.15))))-  (fp_text value "R_0402_1005Metric" (at 0 1.27)+  (fp_text value "R_0402_1005Metric" (at 0 1.47)     (layer "F.Fab") (effects (font (size 1 1) (thickness 0.15))))   (pad "1" smd roundrect (at -0.485 0) (size 0.6 0.7)     (layers "F.Cu" "F.Paste" "F.Mask") (roundrect_rratio 0.25))