Skip to main content

pcb_toolkit/
materials.rs

1//! PCB substrate material database.
2//!
3//! Material data extracted from Saturn PCB Toolkit v8.44 binary.
4//! See `docs/notes/15-materials-data.md` and `NOTES.md` for extraction details.
5
6use serde::{Deserialize, Serialize};
7
8/// A PCB substrate material with dielectric properties.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Material {
11    pub name: &'static str,
12    /// Relative permittivity (dielectric constant).
13    pub er: f64,
14    /// Glass transition temperature (°C), if known.
15    pub tg: Option<f64>,
16    /// Surface roughness correction factor (0.98 for FR-4 variants, 1.0 for smooth).
17    pub roughness_factor: f64,
18}
19
20/// Built-in material database.
21///
22/// First 23 materials have Er values extracted from binary at offset 0x4b9ae8.
23/// Roughness factor from ComboBox1Change decompilation: 0.98 for FR-4 family, 1.0 for others.
24pub static MATERIALS: &[Material] = &[
25    Material { name: "FR-4 STD", er: 4.6, tg: Some(130.0), roughness_factor: 0.98 },
26    Material { name: "FR-5", er: 4.3, tg: None, roughness_factor: 0.98 },
27    Material { name: "FR406", er: 3.8, tg: None, roughness_factor: 0.98 },
28    Material { name: "FR408", er: 3.9, tg: None, roughness_factor: 0.98 },
29    Material { name: "Getek ML200C", er: 4.2, tg: None, roughness_factor: 0.98 },
30    Material { name: "Getek ML200D", er: 3.78, tg: None, roughness_factor: 0.98 },
31    Material { name: "Getek ML200M", er: 2.94, tg: None, roughness_factor: 1.0 },
32    Material { name: "Getek RG200D", er: 3.0, tg: None, roughness_factor: 1.0 },
33    Material { name: "Isola P95", er: 6.15, tg: None, roughness_factor: 1.0 },
34    Material { name: "Isola P96", er: 10.2, tg: None, roughness_factor: 1.0 },
35    Material { name: "Isola P26N", er: 3.38, tg: None, roughness_factor: 1.0 },
36    Material { name: "RO2800", er: 3.66, tg: None, roughness_factor: 1.0 },
37    Material { name: "RO3003", er: 2.5, tg: None, roughness_factor: 1.0 },
38    Material { name: "RO3006", er: 2.35, tg: None, roughness_factor: 1.0 },
39    Material { name: "RO3010", er: 2.2, tg: None, roughness_factor: 1.0 },
40    Material { name: "RO4003", er: 2.1, tg: None, roughness_factor: 1.0 },
41    Material { name: "RO4350", er: 4.25, tg: None, roughness_factor: 1.0 },
42    Material { name: "RT5500", er: 4.5, tg: None, roughness_factor: 1.0 },
43    Material { name: "RT5870", er: 4.1, tg: None, roughness_factor: 1.0 },
44    Material { name: "RT5880", er: 3.7, tg: None, roughness_factor: 1.0 },
45    Material { name: "RT6002", er: 3.4, tg: None, roughness_factor: 1.0 },
46    Material { name: "RT6006", er: 4.15, tg: None, roughness_factor: 1.0 },
47    Material { name: "RT6010", er: 4.38, tg: None, roughness_factor: 1.0 },
48    // Materials 24-44: Er values not yet extracted from binary (need ComboBox1Change decompilation).
49    // For now, users must supply custom Er for these.
50    Material { name: "Teflon PTFE", er: 2.1, tg: None, roughness_factor: 1.0 },
51    Material { name: "Air", er: 1.0, tg: None, roughness_factor: 1.0 },
52];
53
54/// Look up a material by name (case-insensitive).
55pub fn lookup(name: &str) -> Option<&'static Material> {
56    MATERIALS.iter().find(|m| m.name.eq_ignore_ascii_case(name))
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62
63    #[test]
64    fn lookup_fr4() {
65        let m = lookup("FR-4 STD").unwrap();
66        assert!((m.er - 4.6).abs() < 1e-10);
67        assert!((m.roughness_factor - 0.98).abs() < 1e-10);
68    }
69
70    #[test]
71    fn lookup_case_insensitive() {
72        assert!(lookup("fr-4 std").is_some());
73        assert!(lookup("AIR").is_some());
74    }
75
76    #[test]
77    fn lookup_unknown() {
78        assert!(lookup("unobtanium").is_none());
79    }
80}