app
Adom Fields
Public Made by Adomby adom
Adom Fields: where the current and the heat go on your board.
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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
//! 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
);
}
}