app
AI Flow: Reverse Engineering
Public Made by Adomby adom
Photos of a PCB to a tagged KiCad copy: parts placed, copper and nets with their evidence, a schematic, a measured confidence score, bench tests and a video of the run.
main
Caleb
Publish 0.5.0
1984b40
//! Which way the board was turned over, for the scale paths that do not frame through mounting holes (`scale --ruler`, `scale --body`).
//!
//! `prep` reads the flip from plated holes both faces show. A board whose header rows are all populated shows pins on one face and solder or sockets on the other,
//! and no hole agrees either way (test boards 7 and 8, 2026-11-10: 0 and 0). Then the flip is the one stated with `--flip`, or the one under which what shows through
//! the board on both faces (via rims, copper edges on plain mask) correlates, as `scale --holes` has always decided it.
use image::RgbImage;
use serde_json::Value;
/// Some((axis, how)) when the flip is settled here: stated, or read from through features. None leaves what `prep` found from the holes.
/// `frame` builds the bottom face in the board frame for a given axis.
pub fn decide(prep_doc: &Value, stated: Option<&str>, top: &RgbImage, frame: &dyn Fn(&str) -> RgbImage) -> Option<(String, &'static str)> {
if let Some(a) = stated { if a == "x" || a == "y" { return Some((a.to_string(), "stated with --flip")); } }
let (m, o) = (prep_doc["flip"]["matches"].as_u64().unwrap_or(0), prep_doc["flip"]["other"].as_u64().unwrap_or(0));
if holes_decide(m, o) { return None; }
let (bx, by) = (frame("x"), frame("y"));
let (axis, cx, cy) = crate::board::flip_by_detail(top, &bx, &by);
eprintln!("note: the holes do not say which way the board was turned over ({m} agree one way, {o} the other); plain mask detail correlates {cx:.3} mirrored in x, {cy:.3} in y");
axis.map(|a| (a, "through features: the holes cannot say"))
}
/// The holes decide when one way has at least three and three times the other: the rule `check prep` applies.
pub fn holes_decide(m: u64, o: u64) -> bool { m >= 3 && m >= 3 * o.max(1) }
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn a_stated_flip_stands_and_clear_holes_are_left_alone() {
let top = RgbImage::new(64, 64);
let never = |_: &str| -> RgbImage { panic!("the bottom is not framed when the answer is known") };
assert_eq!(decide(&json!({"flip": {"axis": "x", "matches": 0, "other": 0}}), Some("y"), &top, &never), Some(("y".to_string(), "stated with --flip")));
assert_eq!(decide(&json!({"flip": {"axis": "x", "matches": 9, "other": 1}}), None, &top, &never), None);
assert!(holes_decide(3, 0) && holes_decide(6, 2) && !holes_decide(0, 0) && !holes_decide(2, 0) && !holes_decide(4, 2));
}
}