1use serde::{Deserialize, Serialize};
13
14use crate::CalcError;
15
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub struct ThruHoleInput {
21 pub hole_diameter_mils: f64,
23 pub annular_ring_mils: f64,
25 pub isolation_width_mils: f64,
27}
28
29#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33pub struct ThruHoleResult {
34 pub pad_external_mils: f64,
38 pub pad_internal_signal_mils: f64,
42 pub pad_internal_plane_mils: f64,
46}
47
48pub fn thru_hole(input: &ThruHoleInput) -> Result<ThruHoleResult, CalcError> {
56 if input.hole_diameter_mils <= 0.0 {
57 return Err(CalcError::NegativeDimension {
58 name: "hole_diameter_mils",
59 value: input.hole_diameter_mils,
60 });
61 }
62 if input.annular_ring_mils < 0.0 {
63 return Err(CalcError::NegativeDimension {
64 name: "annular_ring_mils",
65 value: input.annular_ring_mils,
66 });
67 }
68 if input.isolation_width_mils < 0.0 {
69 return Err(CalcError::NegativeDimension {
70 name: "isolation_width_mils",
71 value: input.isolation_width_mils,
72 });
73 }
74
75 let pad_external_mils =
76 input.hole_diameter_mils + 2.0 * input.annular_ring_mils;
77 let pad_internal_signal_mils = pad_external_mils;
78 let pad_internal_plane_mils =
79 pad_external_mils + 2.0 * input.isolation_width_mils;
80
81 Ok(ThruHoleResult {
82 pad_external_mils,
83 pad_internal_signal_mils,
84 pad_internal_plane_mils,
85 })
86}
87
88pub fn corner_to_corner(a_mils: f64, b_mils: f64) -> Result<f64, CalcError> {
99 if a_mils < 0.0 {
100 return Err(CalcError::NegativeDimension {
101 name: "a_mils",
102 value: a_mils,
103 });
104 }
105 if b_mils < 0.0 {
106 return Err(CalcError::NegativeDimension {
107 name: "b_mils",
108 value: b_mils,
109 });
110 }
111
112 Ok((a_mils * a_mils + b_mils * b_mils).sqrt())
113}
114
115#[cfg(test)]
116mod tests {
117 use approx::assert_relative_eq;
118
119 use super::*;
120
121 #[test]
128 fn saturn_page23_thru_hole_vector() {
129 let input = ThruHoleInput {
130 hole_diameter_mils: 32.0,
131 annular_ring_mils: 12.0,
132 isolation_width_mils: 12.0,
133 };
134 let result = thru_hole(&input).unwrap();
135 assert_relative_eq!(result.pad_external_mils, 56.0, epsilon = 1e-10);
136 assert_relative_eq!(result.pad_internal_signal_mils, 56.0, epsilon = 1e-10);
137 assert_relative_eq!(result.pad_internal_plane_mils, 80.0, epsilon = 1e-10);
138 }
139
140 #[test]
141 fn corner_to_corner_3_4_5() {
142 let d = corner_to_corner(3.0, 4.0).unwrap();
143 assert_relative_eq!(d, 5.0, epsilon = 1e-10);
144 }
145
146 #[test]
147 fn corner_to_corner_zero() {
148 let d = corner_to_corner(0.0, 0.0).unwrap();
149 assert_relative_eq!(d, 0.0, epsilon = 1e-10);
150 }
151
152 #[test]
153 fn error_on_zero_hole() {
154 let input = ThruHoleInput {
155 hole_diameter_mils: 0.0,
156 annular_ring_mils: 12.0,
157 isolation_width_mils: 12.0,
158 };
159 assert!(thru_hole(&input).is_err());
160 }
161
162 #[test]
163 fn error_on_negative_annular_ring() {
164 let input = ThruHoleInput {
165 hole_diameter_mils: 32.0,
166 annular_ring_mils: -1.0,
167 isolation_width_mils: 12.0,
168 };
169 assert!(thru_hole(&input).is_err());
170 }
171
172 #[test]
173 fn error_on_negative_corner_dimension() {
174 assert!(corner_to_corner(-1.0, 4.0).is_err());
175 assert!(corner_to_corner(3.0, -1.0).is_err());
176 }
177}