Skip to main content

pcb_toolkit/
copper.rs

1//! Copper weight to thickness conversion tables.
2
3use serde::{Deserialize, Serialize};
4
5use crate::CalcError;
6
7/// Standard copper weight options (oz/ft²).
8#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
9pub enum CopperWeight {
10    /// 0.25 oz/ft²
11    Oz025,
12    /// 0.5 oz/ft²
13    Oz05,
14    /// 1 oz/ft²
15    Oz1,
16    /// 1.5 oz/ft²
17    Oz15,
18    /// 2 oz/ft²
19    Oz2,
20    /// 2.5 oz/ft²
21    Oz25,
22    /// 3 oz/ft²
23    Oz3,
24    /// 4 oz/ft²
25    Oz4,
26    /// 5 oz/ft²
27    Oz5,
28}
29
30impl CopperWeight {
31    /// Copper thickness in mils.
32    pub fn thickness_mils(self) -> f64 {
33        match self {
34            Self::Oz025 => 0.35,
35            Self::Oz05 => 0.70,
36            Self::Oz1 => 1.40,
37            Self::Oz15 => 2.10,
38            Self::Oz2 => 2.80,
39            Self::Oz25 => 3.50,
40            Self::Oz3 => 4.20,
41            Self::Oz4 => 5.60,
42            Self::Oz5 => 7.00,
43        }
44    }
45
46    /// Copper thickness in mm.
47    pub fn thickness_mm(self) -> f64 {
48        match self {
49            Self::Oz025 => 0.009,
50            Self::Oz05 => 0.018,
51            Self::Oz1 => 0.035,
52            Self::Oz15 => 0.053,
53            Self::Oz2 => 0.070,
54            Self::Oz25 => 0.088,
55            Self::Oz3 => 0.106,
56            Self::Oz4 => 0.142,
57            Self::Oz5 => 0.178,
58        }
59    }
60
61    /// Parse from a string like "1oz", "0.5oz", "2.5oz".
62    pub fn from_str_oz(s: &str) -> Result<Self, CalcError> {
63        match s.trim().to_lowercase().trim_end_matches("oz").trim() {
64            "0.25" => Ok(Self::Oz025),
65            "0.5" => Ok(Self::Oz05),
66            "1" => Ok(Self::Oz1),
67            "1.5" => Ok(Self::Oz15),
68            "2" => Ok(Self::Oz2),
69            "2.5" => Ok(Self::Oz25),
70            "3" => Ok(Self::Oz3),
71            "4" => Ok(Self::Oz4),
72            "5" => Ok(Self::Oz5),
73            _ => Err(CalcError::UnknownCopperWeight(s.to_string())),
74        }
75    }
76}
77
78/// Plating thickness options.
79#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
80pub enum PlatingThickness {
81    Bare,
82    Oz05,
83    Oz1,
84    Oz15,
85    Oz2,
86    Oz25,
87    Oz3,
88}
89
90impl PlatingThickness {
91    /// Plating thickness in mils.
92    pub fn thickness_mils(self) -> f64 {
93        match self {
94            Self::Bare => 0.0,
95            Self::Oz05 => 0.70,
96            Self::Oz1 => 1.40,
97            Self::Oz15 => 2.10,
98            Self::Oz2 => 2.80,
99            Self::Oz25 => 3.50,
100            Self::Oz3 => 4.20,
101        }
102    }
103}
104
105/// Etch factor affecting conductor cross-section geometry.
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
107pub enum EtchFactor {
108    /// Rectangular cross-section (no etch compensation).
109    None,
110    /// 1:1 etch — trapezoid with top = W - 2T.
111    OneToOne,
112    /// 2:1 etch — trapezoid with top = W - T.
113    TwoToOne,
114}
115
116impl EtchFactor {
117    /// Cross-sectional area in square mils given width W and thickness T (both in mils).
118    pub fn cross_section_sq_mils(self, width: f64, thickness: f64) -> f64 {
119        match self {
120            Self::None => width * thickness,
121            Self::OneToOne => (width + (width - 2.0 * thickness)) * thickness / 2.0,
122            Self::TwoToOne => (width + (width - thickness)) * thickness / 2.0,
123        }
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn copper_weight_values() {
133        assert!((CopperWeight::Oz1.thickness_mils() - 1.40).abs() < 1e-10);
134        assert!((CopperWeight::Oz1.thickness_mm() - 0.035).abs() < 1e-10);
135    }
136
137    #[test]
138    fn etch_factor_rectangular() {
139        // 10 mil wide, 1.4 mil thick, no etch → 14 sq mils
140        let area = EtchFactor::None.cross_section_sq_mils(10.0, 1.4);
141        assert!((area - 14.0).abs() < 1e-10);
142    }
143
144    #[test]
145    fn parse_copper_weight() {
146        assert_eq!(CopperWeight::from_str_oz("1oz").unwrap(), CopperWeight::Oz1);
147        assert_eq!(CopperWeight::from_str_oz("0.5oz").unwrap(), CopperWeight::Oz05);
148        assert_eq!(CopperWeight::from_str_oz("2.5").unwrap(), CopperWeight::Oz25);
149    }
150}