analyze current: single-pad contacts are dropped as side parts, so a contact-fed net can never pass the pour check

noah · ·closed by Colby Knox

adom-aiflow 0.2.56+noah.1 (local build of 0.2.56), KiCad 10.0.6, WSL2 (Hydrogen local workspace).

analyze current can never pass a net whose load enters through single-pad parts (machine contacts), however good its pour.

In crates/aiflow-analyze/src/pour_cover.rs, check() builds the load terminals as the net's pads that are not zero-amp and not a "side part". side_part(q) is board.pads.iter().filter(|o| o.reference == q.reference && o.net != net).all(...). For a single-pad part (MachineContactLarge, MachinePin) that iterator is empty, so .all() is true and the contact is dropped as a side part.

Board: a 4-in-1 ESC whose battery minus (BATN) enters through two MachineContactLarge (MC13, MC14, -16 A each in loads.BATN.terminals) and leaves through the shunt's BATN pad (R501.2, +32 A). After the filter only R501.2 is left, so terminals.len() >= 2 && missed.is_empty() can never hold, and the output is:

BATN fail: the BATN pour does not carry the load: it leaves the 0.20 mm piece on F.Cu ... uncovered ... and no one pour reaches every terminal (the best, on B.Cu+F.Cu, misses ).

Note the empty "misses" list: one pour does reach every real terminal. The 0.20 mm piece is the INA180 Kelvin trace (U501.4, declared 0 A). The same happens with --fields.

Suggested fix: a part with no other pads is never a side part (!others.is_empty() && others.all(...)), or treat machine contacts and pins as terminals by library.

2 Replies

noah ·

Tested fix (local build on top of 0.2.56+noah.1; cargo test -p aiflow-analyze: 59 passed). With it, BATN on adom/esc-am32-4in1-aiflow passes the pour check: one pour reaches all 3 terminals.

--- /home/adom/project/adom_modules/adom/adom-aiflow/crates/aiflow-analyze/src/pour_cover.rs	2026-11-06 15:30:51.000000000 -0600
+++ crates/aiflow-analyze/src/pour_cover.rs	2026-11-10 00:21:10.946575034 -0600
@@ -133,8 +133,12 @@
             && (t["at"].is_null() || (t["at"][0].as_f64() == Some(q.x) && t["at"][1].as_f64() == Some(q.y)))
     });
     let loaded = |n: &str| spec["loads"][n]["amps"].as_f64().is_some_and(|a| a > 0.0);
-    let side_part = |q: &aiflow_board::Pad| board.pads.iter().filter(|o| o.reference == q.reference && o.net != net)
-        .all(|o| o.net.is_empty() || (!loaded(&o.net) && !ground_like(&o.net)));
+    // issue #190: a single-pad part (a machine contact or pin) has no other pads; it is a terminal, never a side part
+    let side_part = |q: &aiflow_board::Pad| {
+        let others: Vec<_> = board.pads.iter().filter(|o| o.reference == q.reference && o.net != net).collect();
+        let single = board.pads.iter().filter(|o| o.reference == q.reference).count() == 1;
+        !single && others.iter().all(|o| o.net.is_empty() || (!loaded(&o.net) && !ground_like(&o.net)))
+    };
     let terminals: Vec<_> = pads.iter().filter(|q| !zero(q) && !side_part(q)).collect();
     let roots: std::collections::BTreeSet<usize> = (0..fills.len()).map(|i| find(&mut group, i)).collect();
     let mut best: Option<(String, Vec<String>)> = None;
Colby Knox marked this in progress ·
Colby Knox linked pull request #79, which closes this when it merges ·
Colby Knox closed this in pull request #79 ·
Colby Knox ·

Thanks Noah, your trace was exactly right. Fixed in adom-aiflow 0.2.62.

  • A part with no pads on other nets (a machine contact, a machine pin) is never a side part now, so MC13 and MC14 stay BATN terminals and one pour reaching all three passes.
  • A terminal in loads.<net>.terminals may also name the reference alone (no pad), which suits single-pad contacts.
  • New test: a one-pad contact joined to J1 by a pour beside a narrow track passes, both undeclared and declared by reference.

Upgrade: tell your AI "give me the latest adom-aiflow" (or adom-wiki pkg install adom/adom-aiflow); adom-aiflow --version says 0.2.62.

Log in to reply.