← All Pull Requests

Large contacts take large corner pins unless the spec says why not #47

Merged Released in 0.2.28 on 31 Oct 2026 opened by Drew Owens

A large contact (MachineContactLarge, 4.4 mm pad, 2.62 mm drill, 14 AWG solid wire) is how a power net over 3.5 A comes onto a molecule, but the aiflow-molecule skill said "every contact is MachineContactMedium" and nothing tied a large contact to the corner pins. A student designing a motor board with AI help would be steered to medium contacts for a 12 A rail. Drew's rule of thumb: a large contact on a board forces the corner pins to large, and the designer can override that explicitly when medium is right.

Checker (crates/adom-aiflow/src/checks.rs, pin_class_check)

  • New corner-pin reason 6: any MachineContactLarge on the board requires large corner pins. The error names the contacts, and when it is the only reason, a hint points at the override.
  • Override: spec.molecule.largeContactMediumPins: "<why medium is right>". It must be a non-empty sentence; true or another non-string is ignored with a warning. It covers rule 6 only, so mass, size, seat, loads and current still require large pins. Setting it on a board with no large contact warns.
  • Current rule: a loaded net carried only by large pins and contacts is judged against the large class rating (20.8 A, new LARGE_CLASS_CURRENT_A) instead of the medium 3.5 A. A 12 A rail on a large contact no longer counts against medium corners; over 20.8 A it warns. A net that also touches a medium pin or contact is judged as before.
  • pinClass in the molecule outcome gains largeContacts and largeContactMediumPins.
  • The footprint hint names MachineContactLarge 4.4 / 2.62 mm (the pad and drill molecule_part already accepted).
  • Three new tests: pin_class_large_contact_needs_large_corners, pin_class_large_contact_override_keeps_medium, pin_class_large_only_net_is_judged_against_the_large_rating. cargo test -p adom-aiflow: 298 passed, 0 failed, 3 ignored.

Skill (skills/aiflow-molecule/SKILL.md)

  • Rule 6 in the corner-pin list, a short "Large contacts" paragraph (when to use one, the override, the large-only current rule), the new spec field, the Contacts row of the interface table, and the check list line. Description and trigger words updated to match.

Not in this PR: a version bump, a release note and rebuilt binaries in bin/. Those are yours at publish. Whether 6 should be absolute (no override) or the threshold should differ is your call, John; this follows Drew's ask.

Opened by Drew's AI agent at Drew's request.

🤖 Generated with Claude Code

Diff Skip to comments (2)

--- a/crates/adom-aiflow/src/checks.rs+++ b/crates/adom-aiflow/src/checks.rs@@ -1,1311 +1,1416 @@⋯ 188 unchanged lines ⋯     let grid = m["gridMm"].as_f64().unwrap_or(2.0);     let margin = m["edgeMarginMm"].as_f64().unwrap_or(2.0);     let pin_net = m["cornerPinsNet"].as_str().unwrap_or("GND").to_string();-    let hint_fp = "Use the Adom KiCad Library 1.2.3 footprints (lib/Molecule.pretty): MachinePinMediumShort 1.6 mm pad / 1.2 mm drill (or MachinePinLargeShort / LargeStandard 5.2 mm pad / 3.45 mm drill when the corner-pin class rule needs large pins) for MP1 to MP4, MachineContactMedium 1.3 mm pad / 0.78 mm drill for contacts; spec.molecule.pinPadMm / pinDrillMm / contactPadMm / contactDrillMm override the expected sizes (skill aiflow-molecule).";+    let hint_fp = "Use the Adom KiCad Library 1.2.3 footprints (lib/Molecule.pretty): MachinePinMediumShort 1.6 mm pad / 1.2 mm drill (or MachinePinLargeShort / LargeStandard 5.2 mm pad / 3.45 mm drill when the corner-pin class rule needs large pins) for MP1 to MP4, MachineContactMedium 1.3 mm pad / 0.78 mm drill for contacts (MachineContactLarge 4.4 mm pad / 2.62 mm drill for a power net over 3.5 A, which takes large corner pins); spec.molecule.pinPadMm / pinDrillMm / contactPadMm / contactDrillMm override the expected sizes (skill aiflow-molecule).";     let pad_of = |r: &str| b.pads_of(r).into_iter().next().cloned();     let mut pins: BTreeMap<String, (f64, f64)> = BTreeMap::new();     for k in 1..=4 {⋯ 161 unchanged lines ⋯ pub const LARGE_PIN_CURRENT_A: f64 = 3.5; /// A heatsink heavier than this is a load that pushes, pulls or twists the board. pub const LARGE_PIN_HEATSINK_G: f64 = 20.0;+/// The large pin and contact class rating: a net carried only by large parts is judged against this.+pub const LARGE_CLASS_CURRENT_A: f64 = 20.8; const MECH_KINDS: [&str; 5] = ["motor", "actuator", "heatsink", "cabled-connector", "button"]; +/// A large machine pin or contact (MachinePinLarge*, MachineContactLarge), by footprint name.+fn is_large_machine_part(lib: &str) -> bool {+    let name = lib.rsplit(':').next().unwrap_or(lib).to_ascii_lowercase();+    name.starts_with("machinepinlarge") || name == "machinecontactlarge"+}+ /// The board's corner-pin class: "medium", "large", "mixed", or None when a pin is missing or not a MachinePin. fn corner_pin_class(b: &Board) -> Option<&'static str> {     let mut classes = BTreeSet::new();⋯ 44 unchanged lines ⋯ /// MachinePinLargeStandard on LrgMed user-scaffold corners) when ANY of: the board seats directly in /// the base scaffold; the board with its parts weighs more than 100 g; it carries a load that pushes, /// pulls or twists it (motor, actuator, heatsink over 20 g, cabled connector, button pressed hard);-/// its longest side is over 100 mm; a pin carries more than 3.5 A. Otherwise medium (MediumShort).+/// its longest side is over 100 mm; a pin carries more than 3.5 A; it has a large contact+/// (MachineContactLarge), unless spec.molecule.largeContactMediumPins says why medium is right.+/// Otherwise medium (MediumShort). fn pin_class_check(b: &Board, spec: &Value, m: &Value, bom: Option<&str>, f: &mut Findings) {     let mut reasons: Vec<String> = Vec::new();     let mut notes: Vec<String> = Vec::new();⋯ 26 unchanged lines ⋯             if !e.contains(r) { e.push(r.clone()); }         }     }+    // a net whose every carrier is a large pin or contact is within the large class rating, not the+    // medium one: it says nothing about the corner pins (a 12 A rail on large contacts)+    let all_large = |refs: &[String]| refs.iter().all(|r| b.footprints.get(r).map(|fp| is_large_machine_part(&fp.lib)).unwrap_or(false));     let mut pin_amps: Option<(f64, String)> = None;     if let Some(loads) = spec["loads"].as_object() {         for (net, ld) in loads {             let amps = ld["amps"].as_f64().or_else(|| ld.as_f64()).unwrap_or(0.0);             if let Some(refs) = carriers.get(&norm_net(net)) {-                if pin_amps.as_ref().map(|p| amps > p.0).unwrap_or(true) { pin_amps = Some((amps, format!("net {} ({})", norm_net(net), refs.join(", ")))); }+                let what = format!("net {} ({})", norm_net(net), refs.join(", "));+                if all_large(refs) {+                    if amps > LARGE_CLASS_CURRENT_A {+                        f.warn(format!("{what} carries {amps} A (spec.loads), over the large class's {LARGE_CLASS_CURRENT_A} A"), "Share that rail across more large contacts, or declare the reviewed per-contact current (skill aiflow-molecule, corner-pin class).");+                    } else { notes.push(format!("{what} carries {amps} A on large parts only (rated {LARGE_CLASS_CURRENT_A} A)")); }+                    continue;+                }+                if pin_amps.as_ref().map(|p| amps > p.0).unwrap_or(true) { pin_amps = Some((amps, what)); }             }         }     }⋯ 81 unchanged lines ⋯             notes.push(format!("{} confirmed as no mechanical load (not in spec.molecule.mechanicalLoads)", undeclared.join(", ")));         }     }+    // (6) a large contact: the board takes heavy current or a heavy wire, so its corners go large too,+    // unless spec.molecule.largeContactMediumPins says why medium corners are right for this board+    let large_contacts: Vec<String> = b.footprints.iter()+        .filter(|(_, fp)| fp.lib.rsplit(':').next().unwrap_or(&fp.lib).eq_ignore_ascii_case("machinecontactlarge"))+        .map(|(r, _)| r.clone()).collect();+    let keep_medium = match m.get("largeContactMediumPins") {+        Some(Value::String(s)) if !s.trim().is_empty() => Some(s.trim().to_string()),+        None | Some(Value::Null) => None,+        Some(_) => { f.warn("spec.molecule.largeContactMediumPins must be a sentence saying why medium corner pins are right with a large contact; it is ignored".into(), declare_hint); None }+    };+    let override_hint = "When medium corner pins are right for this board anyway (a light board in a LrgMed user scaffold, say), keep them on purpose: set spec.molecule.largeContactMediumPins to the reason (skill aiflow-molecule, corner-pin class).";+    let mut overridable = false;+    match (large_contacts.is_empty(), &keep_medium) {+        (false, None) => { reasons.push(format!("{} is a large contact (MachineContactLarge); a board with a large contact takes large corner pins", large_contacts.join(", "))); overridable = true; }+        (false, Some(why)) => notes.push(format!("large contact {}, medium corner pins kept on purpose (spec.molecule.largeContactMediumPins: {why})", large_contacts.join(", "))),+        (true, Some(_)) => f.warn("spec.molecule.largeContactMediumPins is set but the board has no MachineContactLarge".into(), "Remove it, or add the large contact it was written for."),+        (true, None) => {}+    }     // the decision     let class = corner_pin_class(b);     let required = if reasons.is_empty() { "medium" } else { "large" };     match (required, class) {-        ("large", Some("medium")) => f.error(format!("corner pins are medium but this board needs LARGE pins: {}", reasons.join("; ")),-            "Use MachinePinLargeShort for MP1 to MP4 (MachinePinLargeStandard where this board is a LrgMed user scaffold's corners), seated on the base scaffold's 32 mm grid. A large pin is not a footprint-name swap: update the schematic, library, BOM, layout, models and fixture (skill aiflow-molecule, corner-pin class)."),+        ("large", Some("medium")) => {+            f.error(format!("corner pins are medium but this board needs LARGE pins: {}", reasons.join("; ")),+                "Use MachinePinLargeShort for MP1 to MP4 (MachinePinLargeStandard where this board is a LrgMed user scaffold's corners), seated on the base scaffold's 32 mm grid. A large pin is not a footprint-name swap: update the schematic, library, BOM, layout, models and fixture (skill aiflow-molecule, corner-pin class).");+            // only the large-contact reason can be overridden; say so when it is the only one+            if overridable && reasons.len() == 1 { f.hints.push(override_hint.to_string()); }+        }         ("large", Some("large")) => f.ok(format!("large corner pins, as needed: {}", reasons.join("; "))),         ("medium", Some("large")) => f.ok(format!("large corner pins; medium would do (no large-pin reason: {}). Large is allowed", if notes.is_empty() { "nothing declared".to_string() } else { notes.join(", ") })),         ("medium", Some("medium")) => f.ok(format!("medium corner pins: no large-pin reason ({})", if notes.is_empty() { "nothing declared".to_string() } else { notes.join(", ") })),⋯ 4 unchanged lines ⋯     }     f.pin_class = json!({"required": required, "pins": class, "reasons": reasons, "notes": notes, "seat": seat,         "longestSideMm": longest.map(|v| (v * 100.0).round() / 100.0), "massG": mass.as_ref().map(|x| x.0), "massSource": mass.as_ref().map(|x| x.1.clone()),-        "pinCurrentA": pin_amps.as_ref().map(|x| x.0), "pinCurrentFrom": pin_amps.as_ref().map(|x| x.1.clone())});+        "pinCurrentA": pin_amps.as_ref().map(|x| x.0), "pinCurrentFrom": pin_amps.as_ref().map(|x| x.1.clone()),+        "largeContacts": large_contacts, "largeContactMediumPins": keep_medium}); }  // ----------------------------------------------------------------------------- fab rules (#10)⋯ 473 unchanged lines ⋯             .replace("124", "132").replace(" 84", " 68").replace("(start 98 82)", "(start 96 64)").replace("(end 126 102)", "(end 136 104)")     } +    /// A MachineContactLarge J2 on VMOT, 6 mm right of J1 (on the 2 mm grid from MP1).+    fn with_large_contact(text: &str) -> String {+        text.replacen(" (footprint \"Resistor_SMD", " (footprint \"Molecule:MachineContactLarge\" (layer \"F.Cu\") (at 106 88) (property \"Reference\" \"J2\") (pad \"1\" thru_hole circle (at 0 0) (size 4.4 4.4) (drill 2.62) (layers \"*.Cu\") (net \"VMOT\")))\n (footprint \"Resistor_SMD", 1)+    }+     #[test]+    fn pin_class_large_contact_needs_large_corners() {+        let f = molecule_check(&board(&with_large_contact(BOARD)), &calm(json!({})), None);+        let e = pin_errors(&f);+        assert!(e.len() == 1 && e[0].contains("J2 is a large contact"), "{:?}", f.lines);+        assert!(f.hints.iter().any(|h| h.contains("largeContactMediumPins")), "{:?}", f.hints);+        assert_eq!(f.pin_class["required"], "large");+        assert_eq!(f.pin_class["largeContacts"], json!(["J2"]));+        // the contact itself passes the contact checks (4.4 mm pad, 2.62 mm drill, on the grid)+        assert!(f.lines.iter().any(|l| l.starts_with("OK") && l.contains("J2 MachineContactLarge")), "{:?}", f.lines);+        // large corners: as needed+        let f = molecule_check(&board(&with_large_contact(&large_board())), &calm(json!({"seat": "base"})), None);+        assert!(f.errors.is_empty(), "{:?}", f.lines);+        assert!(f.lines.iter().any(|l| l.contains("large corner pins, as needed") && l.contains("J2")), "{:?}", f.lines);+    }++    #[test]+    fn pin_class_large_contact_override_keeps_medium() {+        let why = "light 28 x 20 mm board in a LrgMed scaffold; J2 is only the 14 AWG motor feed";+        let mut spec = calm(json!({"largeContactMediumPins": why}));+        spec["loads"] = json!({"VMOT": {"amps": 12}});+        let f = molecule_check(&board(&with_large_contact(BOARD)), &spec, None);+        // 12 A on a net only the large contact carries is within the large rating, not a medium-pin reason+        assert!(f.errors.is_empty() && f.warnings.is_empty(), "{:?}", f.lines);+        assert_eq!(f.pin_class["required"], "medium");+        assert_eq!(f.pin_class["largeContactMediumPins"], why);+        assert!(f.pin_class["notes"].as_array().unwrap().iter().any(|n| n.as_str().unwrap().contains("kept on purpose")), "{}", f.pin_class);+        // the override covers the large contact only, never another reason+        let f = molecule_check(&board(&with_large_contact(BOARD)), &calm(json!({"largeContactMediumPins": why, "massG": 150})), None);+        let e = pin_errors(&f);+        assert!(e.len() == 1 && e[0].contains("150 g") && !e[0].contains("J2"), "{:?}", f.lines);+        // it has to say why+        let f = molecule_check(&board(&with_large_contact(BOARD)), &calm(json!({"largeContactMediumPins": true})), None);+        assert!(f.warnings.iter().any(|w| w.contains("must be a sentence")), "{:?}", f.lines);+        assert!(!pin_errors(&f).is_empty());+        // and it needs a large contact to apply to+        let f = molecule_check(&board(BOARD), &calm(json!({"largeContactMediumPins": why})), None);+        assert!(f.errors.is_empty() && f.warnings.iter().any(|w| w.contains("no MachineContactLarge")), "{:?}", f.lines);+    }++    #[test]+    fn pin_class_large_only_net_is_judged_against_the_large_rating() {+        let text = with_large_contact(&large_board());+        let mut spec = calm(json!({"seat": "base"}));+        spec["loads"] = json!({"VMOT": {"amps": 12}});+        let f = molecule_check(&board(&text), &spec, None);+        assert!(f.errors.is_empty() && f.warnings.is_empty(), "{:?}", f.lines);+        spec["loads"] = json!({"VMOT": {"amps": 25}});+        let f = molecule_check(&board(&text), &spec, None);+        assert!(f.warnings.iter().any(|w| w.contains("net VMOT (J2) carries 25 A") && w.contains("20.8 A")), "{:?}", f.lines);+        // a medium contact on the same heavy net still counts against the medium rating+        let shared = with_large_contact(BOARD).replace("(net \"VIN\")))", "(net \"VMOT\")))");+        let f = molecule_check(&board(&shared), &calm(json!({"largeContactMediumPins": "test"})).tap_loads(json!({"VMOT": {"amps": 12}})), None);+        assert!(pin_errors(&f).iter().any(|e| e.contains("net VMOT (J1, J2) carries 12 A")), "{:?}", f.lines);+    }++    #[test]     fn pin_class_large_pins_with_no_reason_pass() {         let f = molecule_check(&board(&large_board()), &calm(json!({"seat": "board"})), None);         assert!(f.errors.is_empty(), "{:?}", f.lines);⋯ 271 unchanged lines ⋯         assert!(!flat_chip("Capacitor_SMD:CP_Elec_6.3x5.8"));     } }+--- a/skills/aiflow-molecule/SKILL.md+++ b/skills/aiflow-molecule/SKILL.md@@ -1,196 +1,202 @@ --- name: aiflow-molecule description: >--  Make an adom-aiflow board an Adom molecule that fits the scaffold: the 2 mm grid, the origin at the MP1 (front-left) machine pin, the corner-pin class rule (large MachinePinLargeShort when the board seats in the base scaffold, weighs over 100 g, carries a motor, actuator, heatsink over 20 g, cabled connector or hard-pressed button, is over 100 mm long or a pin carries over 3.5 A; otherwise MachinePinMediumShort, 1.6 mm pad, 1.2 mm drill) at the corners as MP1 to MP4, MachineContactMedium (1.3 mm pad, 0.78 mm drill) for the signal contacts, footprints from the Adom KiCad Library 1.2.3, the fab-rules profile (3rd party fab default, jlcpcb optional), then STEP export, `step2glb convert --molecule` with its stats gates, real copper pads (`adom-aiflow board-glb copper`: pads and test points are copper, the mask opens and paste shows only where the footprint layers say, with a gate), and molecule-publish. Medium-pin molecules mount on a LrgMed user scaffold (8 mm medium contact grid, offset 4 mm from its front-left large pin, large pins on a 32 mm base grid). Trigger words: aiflow molecule, make it a molecule, fits our scaffold, machine pins, large or medium pins, MachinePinLargeShort, MachinePinMediumShort, MachineContactMedium, MP1, molecule grid, molecule outline, fab rules profile, kicad_dru, molecule conformance, molecule export, step2glb molecule, publish molecule, LrgMed scaffold, copper pads, grey pads, test points, solder mask openings, paste layer, board GLB, board-glb.+  Make an adom-aiflow board an Adom molecule that fits the scaffold: the 2 mm grid, the origin at the MP1 (front-left) machine pin, the corner-pin class rule (large MachinePinLargeShort when the board seats in the base scaffold, weighs over 100 g, carries a motor, actuator, heatsink over 20 g, cabled connector or hard-pressed button, is over 100 mm long, a pin carries over 3.5 A, or it has a large contact unless spec.molecule.largeContactMediumPins says why medium is right; otherwise MachinePinMediumShort, 1.6 mm pad, 1.2 mm drill) at the corners as MP1 to MP4, MachineContactMedium (1.3 mm pad, 0.78 mm drill) for the signal contacts, MachineContactLarge (4.4 mm pad, 2.62 mm drill, 14 AWG solid wire) for a power net over 3.5 A, footprints from the Adom KiCad Library 1.2.3, the fab-rules profile (3rd party fab default, jlcpcb optional), then STEP export, `step2glb convert --molecule` with its stats gates, real copper pads (`adom-aiflow board-glb copper`: pads and test points are copper, the mask opens and paste shows only where the footprint layers say, with a gate), and molecule-publish. Medium-pin molecules mount on a LrgMed user scaffold (8 mm medium contact grid, offset 4 mm from its front-left large pin, large pins on a 32 mm base grid). Trigger words: aiflow molecule, make it a molecule, fits our scaffold, machine pins, large or medium pins, MachinePinLargeShort, MachinePinMediumShort, MachineContactMedium, MachineContactLarge, large contact, 14 AWG, high-current contact, MP1, molecule grid, molecule outline, fab rules profile, kicad_dru, molecule conformance, molecule export, step2glb molecule, publish molecule, LrgMed scaffold, copper pads, grey pads, test points, solder mask openings, paste layer, board GLB, board-glb. ---  # aiflow-molecule: a board that drops into the scaffold⋯ 9 unchanged lines ⋯ 3. It carries anything that pushes, pulls or twists it: a motor or actuator, a heatsink over 20 g, a cabled connector people unplug (barrel jack, USB), or a button pressed hard. 4. Its longest side is over 100 mm. 5. Any pin carries more than 3.5 A.+6. It has a large contact (`MachineContactLarge`), unless the spec overrides it (below).  Otherwise use MEDIUM pins (`MachinePinMediumShort`): small boards that sit in another board or a LrgMed user scaffold. Do not invent other thresholds. +**Large contacts.** A power net over 3.5 A comes onto the board through `MachineContactLarge` (4.4 mm pad, 2.62 mm drill), wired with 14 AWG solid wire, which carries well above the medium contact's rating (the large class is rated 20.8 A). A board that needs a large contact is a heavy-current board, so as a rule of thumb its corner pins go large too (rule 6). When medium corners are right anyway (a light board in a LrgMed user scaffold whose one large contact is a motor feed, say), keep them on purpose: set `spec.molecule.largeContactMediumPins` to a sentence saying why. The override covers rule 6 only; any other reason still needs large pins. A net carried only by large contacts and large pins is judged against the large class's 20.8 A, not the medium 3.5 A, so a 12 A rail on a large contact does not count against medium corners; a medium contact on the same net still does.+ | | Medium | Large | |---|---|---| | Tail / shaft | 0.51 / 1.2 mm diameter | 1.85 / 3.6 mm diameter (about 27 times the bending stiffness) |⋯ 10 unchanged lines ⋯   "seat": "lrgmed",   "massG": 42,   "mechanicalLoads": [{"ref": "M1", "kind": "motor"}, {"ref": "HS1", "kind": "heatsink", "massG": 25}],-  "pinCurrentA": 1.2+  "pinCurrentA": 1.2,+  "largeContactMediumPins": "light board in a LrgMed scaffold; J2 is only the motor feed" } ``` ⋯ 2 unchanged lines ⋯ - `mechanicalLoads`: every part that pushes, pulls or twists the board, `{ref, kind, massG?}` with kind `motor`, `actuator`, `heatsink`, `cabled-connector` or `button`. A heatsink counts only when its `massG` is over 20. Footprint names, values and BOM text that mention a motor, actuator, barrel jack, USB connector or heatsink are a hint only: undeclared, they are a warning; declare them, or confirm medium is fine with `"mechanicalLoads": []`. - Pin current: a pin's current is taken from the loaded nets it is on: the highest `spec.loads` current (`amps`, the shape `analyze current` reads) on a net that a machine pin or machine contact carries, the whole net's current with no sharing between pins (conservative). `pinCurrentA` declares the reviewed per-pin current and, when present, governs instead: declare it when several pins share a net's current (four GND corner pins sharing a 5 A return carry about 1.25 A each). Over 3.5 A requires large pins. - Size: the longest side of the outline's bounding box (the outer Edge.Cuts loop).+- `largeContactMediumPins`: only with a large contact on the board: the reason medium corner pins are kept. It must be a sentence (`true` is ignored with a warning), and it warns when the board has no large contact. -`molecule check` errors when large pins are required and the corner pins are medium, naming every reason and the fix; large pins with no reason are allowed (an OK line). The decision is recorded in the run's molecule outcome as `pinClass` (`required`, `pins`, `reasons`, `notes`, `seat`, `longestSideMm`, `massG`, `massSource`, `pinCurrentA`).+`molecule check` errors when large pins are required and the corner pins are medium, naming every reason and the fix; large pins with no reason are allowed (an OK line). The decision is recorded in the run's molecule outcome as `pinClass` (`required`, `pins`, `reasons`, `notes`, `seat`, `longestSideMm`, `massG`, `massSource`, `pinCurrentA`, `largeContacts`, `largeContactMediumPins`).  Large pins mate directly to the base scaffold on a 32 mm grid. Medium pins require the chosen LrgMed scaffold's actual 8 mm contact grid and available span. Check all four sockets, seating height, pin model, drill/pad dimensions and outline clearance. A large-pin replacement is not a footprint-name substitution: update schematic/library/BOM, layout, models, fixture and electrical checks. Use the component page's engineering assets, not its animated inspection hero. Keep a compatible free medium GND contact for the probe tether when large corner heads are not verified compatible. ⋯ 6 unchanged lines ⋯ | Grid | 2 mm for every pin and contact, measured from MP1 | | Origin | the centre of MP1, the front-left (FL) corner pin; aux and grid origin on it; y up in the design frame, y down in KiCad | | Corner pins | MP1 (FL), MP2 (FR), MP3 (BL), MP4 (BR), net GND; the class from the rule above: `MachinePinMediumShort` (1.6 mm pad, 1.2 mm drill) or `MachinePinLargeShort` (5.2 mm pad, 3.45 mm drill; `MachinePinLargeStandard` on LrgMed user-scaffold corners) |-| Contacts | footprint `MachineContactMedium`: 1.3 mm pad, 0.78 mm drill, one per signal the Control Panel wires to |+| Contacts | footprint `MachineContactMedium`: 1.3 mm pad, 0.78 mm drill, one per signal the Control Panel wires to; `MachineContactLarge` (4.4 mm pad, 2.62 mm drill, 14 AWG solid wire) for a power net over 3.5 A, which takes large corner pins (rule 6) | | Library | the Adom KiCad Library 1.2.3 (KiCad plugin manager); copy the footprints and their STEP models into the project (`lib/Molecule.pretty`, `3d/`) so the project travels | | Markers | MP1 to MP4 are the anchoring markers the step2glb molecule conversion reads; keep those exact references and the library's 3D models on them | | Outline | pin span plus an edge margin all round (24 x 16 mm span + 2 mm = 28 x 20 mm here), with **rounded corners**: an arc of radius = the edge margin, centred on each corner pin, so the edge stays the same distance from the pin all the way round (2 mm here; the 1.6 mm pin pad then clears the edge by 1.2 mm). Suggest this by default; never 45 degree chamfers, which read as unfinished. |⋯ 26 unchanged lines ⋯  - MP1 to MP4 present, references exact, one MachinePin class (medium or large, with its pad and drill), at the four corners of the span, all on GND. - The corner-pin class: large pins when any large-pin reason applies (seat, mass, mechanical loads, size, pin current; see above), else a warning for an unknown mass or an undeclared part that looks like a mechanical load.-- Every contact is `MachineContactMedium`, on the 2 mm grid from MP1.+- Every contact is `MachineContactMedium` or `MachineContactLarge` with its pad and drill, on the 2 mm grid from MP1. A large contact requires large corner pins unless `largeContactMediumPins` gives the reason. - Pin span on 8 mm multiples with medium pins (LrgMed), 32 mm multiples with large pins (base scaffold); outline = span + margin with rounded corners (radius = margin, centred on the corner pins); board origin on MP1. - All interface parts locked and in `fixedRefs`. - Every pin and contact has its STEP model bound (`adom-aiflow models`).⋯ 95 unchanged lines ⋯ ```  `interface` is `swd`, `uart-boot` or `usb-dfu`; `pins` maps each interface signal to the reference that carries it; `target` is the MPN being programmed. `step schematic` and `step placement` print the Hint; `finish` refuses the board when a programmable part has no `programming` record, a required pin is missing, or a pin is neither a machine contact nor a labelled test pad.+

Comments

Colby Knox ·

aiflow-lane review (the new AI Flow maintainer thread): approved and merged. Thanks, Drew.

  • Base: this is a file-set PR, so I rebuilt its "before" side from the diff. checks.rs (1311 lines) and skills/aiflow-molecule/SKILL.md (196 lines) both match today's HEAD byte for byte (0.2.27, 09b1480), so nothing anyone else wrote is lost.
  • Checks on HEAD plus this PR: cargo check --workspace --bins is clean, and cargo test --workspace gives 501 passed, 0 failed, including the three new pin-class tests.
  • Review: the override must be a written reason, covers rule 6 only (mass, seat, loads, size and current still force large pins), and warns when it's set without a large contact or isn't a sentence. Judging a net carried only by large parts against 20.8 A is scoped correctly: a net that also touches a medium pin or contact is judged as before. Keeping rule 6 overridable, as Drew asked, is a sound call.

It ships in the next AI Flow release (0.2.28) with the version bump and rebuilt binary. I'll note it here when it's out.

Colby Knox ·

Released in adom-aiflow 0.2.28 (published; Linux asset SHA-256 0c58b627…, matching the installed CLI). Upgrade: tell your AI "give me the latest adom-aiflow" (or adom-wiki pkg install adom/adom-aiflow); adom-aiflow --version says 0.2.28.

Log in to comment.