1use geop_core_math::geop_error::{GeopError, GeopResult};
19
20use crate::iso::TRIANGLE_HEIGHT;
21
22pub const TAPER: f64 = 1.0 / 32.0;
25
26const INCH: f64 = 25.4;
28
29#[derive(Clone, Copy, Debug, PartialEq)]
31pub struct PipeSize {
32 pub name: &'static str,
34 pub threads_per_inch: f64,
35 pub outside: f64,
37 pub e0: f64,
39 pub l1: f64,
41 pub l2: f64,
43}
44
45impl PipeSize {
46 pub fn pitch(&self) -> f64 {
47 INCH / self.threads_per_inch
48 }
49
50 pub fn height(&self) -> f64 {
52 0.8 * self.pitch()
53 }
54
55 pub fn e1(&self) -> f64 {
58 self.e0 + 2.0 * TAPER * self.l1
59 }
60
61 pub fn diameters(&self, internal: bool) -> (f64, f64) {
65 let pitch_diameter = if internal { self.e1() } else { self.e0 };
66 (
67 pitch_diameter + self.height(),
68 pitch_diameter - self.height(),
69 )
70 }
71
72 pub fn crest(&self) -> f64 {
75 (TRIANGLE_HEIGHT * self.pitch() - self.height()) / 2.0
76 }
77
78 pub fn designation(&self) -> String {
80 format!("{}-{} NPT", self.name, self.threads_per_inch)
81 }
82}
83
84const fn size(
85 name: &'static str,
86 threads_per_inch: f64,
87 outside: f64,
88 e0: f64,
89 l1: f64,
90 l2: f64,
91) -> PipeSize {
92 PipeSize {
93 name,
94 threads_per_inch,
95 outside: outside * INCH,
96 e0: e0 * INCH,
97 l1: l1 * INCH,
98 l2: l2 * INCH,
99 }
100}
101
102pub const SIZES: [PipeSize; 10] = [
104 size("1/16", 27.0, 0.3125, 0.27118, 0.160, 0.2611),
105 size("1/8", 27.0, 0.405, 0.36351, 0.1615, 0.2639),
106 size("1/4", 18.0, 0.540, 0.47739, 0.2278, 0.4018),
107 size("3/8", 18.0, 0.675, 0.61201, 0.240, 0.4078),
108 size("1/2", 14.0, 0.840, 0.75843, 0.320, 0.5337),
109 size("3/4", 14.0, 1.050, 0.96768, 0.339, 0.5457),
110 size("1", 11.5, 1.315, 1.21363, 0.400, 0.6828),
111 size("1-1/4", 11.5, 1.660, 1.55713, 0.420, 0.7068),
112 size("1-1/2", 11.5, 1.900, 1.79609, 0.420, 0.7235),
113 size("2", 11.5, 2.375, 2.26902, 0.436, 0.7565),
114];
115
116pub fn npt(name: &str) -> GeopResult<&'static PipeSize> {
118 SIZES.iter().find(|s| s.name == name).ok_or_else(|| {
119 let known: Vec<&str> = SIZES.iter().map(|s| s.name).collect();
120 GeopError::new(format!(
121 "{name:?} is no NPT pipe size; there are {}",
122 known.join(", ")
123 ))
124 })
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
135 fn the_table_is_consistent() {
136 for s in &SIZES {
137 let (major, minor) = s.diameters(false);
138 assert!(minor < s.e0 && s.e0 < major, "{s:?}");
139 assert!(major < s.outside, "{s:?}");
140 assert!(s.l1 < s.l2, "{s:?}");
141 assert!(s.diameters(true).0 > major, "{s:?}");
142 assert!(s.crest() > 0.0, "{s:?}");
143 }
144 for w in SIZES.windows(2) {
145 assert!(w[0].outside < w[1].outside, "{w:?}");
146 }
147 let half = npt("1/2").unwrap();
148 assert_eq!(half.designation(), "1/2-14 NPT");
149 assert!((half.e1() / INCH - 0.77843).abs() < 1e-9);
151 assert!(npt("5/8").is_err());
152 }
153}