main
John Lauer adom-fields 0.7.17: the open PRs merged onto 0.7.16. Plated pad barrels and drill voids with wall coupling (5, 9), expl… df40420
//! Plated-hole geometry and distributed layer coupling. No conducting disc in the drill.
use crate::board::Via;
use std::collections::BTreeMap;
const PLATING_MM: f64 = 0.025;

pub fn transform(v: &Via, x: f64, y: f64) -> (f64, f64) {
    let (s, c) = v.drill_angle.to_radians().sin_cos();
    (v.x + x * c + y * s, v.y - x * s + y * c)
}
pub fn axis(v: &Via) -> ((f64, f64), (f64, f64)) {
    let a = (v.drill_long.unwrap_or(v.drill) - v.drill) / 2.0;
    (transform(v, -a, 0.0), transform(v, a, 0.0))
}
/// Uniform arc-length quadrature at the middle of the plated wall. The weights
/// sum to one; multiplying by sigma * barrel area / length preserves conductance.
pub fn weights(v: &Via, x0: f64, y0: f64, pitch: f64, w: usize, h: usize) -> Vec<(usize, f64)> {
    let a = (v.drill_long.unwrap_or(v.drill) - v.drill) / 2.0;
    let r = v.drill / 2.0 + PLATING_MM / 2.0;
    let arc = std::f64::consts::PI * r;
    let perimeter = 4.0 * a + 2.0 * arc;
    let samples = ((perimeter / pitch * 32.0).ceil() as usize).max(128);
    let mut bins = BTreeMap::<usize, usize>::new();
    for j in 0..samples {
        let t = (j as f64 + 0.5) * perimeter / samples as f64;
        let (x, y) = if t < 2.0 * a {
            (-a + t, -r)
        } else if t < 2.0 * a + arc {
            let q = (t - 2.0 * a) / r - std::f64::consts::FRAC_PI_2;
            (a + r * q.cos(), r * q.sin())
        } else if t < 4.0 * a + arc {
            (a - (t - 2.0 * a - arc), r)
        } else {
            let q = (t - 4.0 * a - arc) / r + std::f64::consts::FRAC_PI_2;
            (-a + r * q.cos(), r * q.sin())
        };
        let (x, y) = transform(v, x, y);
        let c = ((x - x0) / pitch).floor() as isize;
        let row = ((y - y0) / pitch).floor() as isize;
        if c >= 0 && row >= 0 && c < (w as isize) && row < (h as isize) {
            *bins.entry(row as usize * w + c as usize).or_default() += 1;
        }
    }
    bins.into_iter()
        .map(|(i, n)| (i, n as f64 / samples as f64))
        .collect()
}
#[cfg(test)]
mod tests {
    use super::*;
    fn via() -> Via {
        Via {
            net: "N".into(),
            x: 3.0,
            y: 3.0,
            size: 1.2,
            drill: 0.6,
            drill_long: None,
            drill_angle: 0.0,
        }
    }
    #[test]
    fn coupling_is_conserved_and_avoids_center_at_both_meshes() {
        for pitch in [0.2, 0.1, 0.05] {
            let n = (6.0 / pitch) as usize;
            let ws = weights(&via(), 0.0, 0.0, pitch, n, n);
            assert!((ws.iter().map(|x| x.1).sum::<f64>() - 1.0).abs() < 1e-12);
            let center = (3.0 / pitch) as usize * n + (3.0 / pitch) as usize;
            assert!(!ws.iter().any(|(i, _)| *i == center));
            assert!(ws.len() > 4);
        }
    }
    #[test]
    fn rotated_slot_preserves_conductance_and_changes_axis() {
        let mut v = via();
        v.drill_long = Some(1.6);
        v.drill_angle = 90.0;
        let (a, b) = axis(&v);
        assert!((a.0 - b.0).abs() < 1e-12);
        assert!((a.1 - b.1).abs() > 0.99);
        assert!(
            (weights(&v, 0.0, 0.0, 0.1, 60, 60)
                .iter()
                .map(|x| x.1)
                .sum::<f64>()
                - 1.0)
                .abs()
                < 1e-12
        );
    }
}