diff --git a/assets/locales/pending/pipe-threads.json b/assets/locales/pending/pipe-threads.json index c95eb7b9b4..1d4862775c 100644 --- a/assets/locales/pending/pipe-threads.json +++ b/assets/locales/pending/pipe-threads.json @@ -4,48 +4,48 @@ "thread.form_bspt": "Pipe taper R, Rc (ISO 7-1)", "thread.form_npt": "Pipe taper NPT (ASME B1.20.1)", "thread.form_rp": "Pipe Rp (ISO 7-1)", - "thread.taper_drill_exact": "The standard's minor diameter where the thread ends is {exact} mm; this is the stocked drill under it" + "thread.taper_drill_exact": "The standard's minor diameter where the thread ends is {exact} mm; the drill is the tap maker's recommendation" }, "ar": { "thread.form_bspp": "سن أنابيب G (ISO 228-1)", "thread.form_bspt": "سن أنابيب مخروطي R، Rc (ISO 7-1)", "thread.form_npt": "سن أنابيب مخروطي NPT (ASME B1.20.1)", "thread.form_rp": "سن أنابيب داخلي متوازٍ Rp (ISO 7-1)", - "thread.taper_drill_exact": "القطر الأصغر القياسي عند نهاية السن {exact} مم؛ هذا هو المثقاب المتوفر الأصغر منه مباشرة" + "thread.taper_drill_exact": "القطر الأصغر القياسي عند نهاية السن {exact} مم؛ المثقاب هو ما يوصي به صانع اللقمة" }, "he": { "thread.form_bspp": "תבריג צנרת G (ISO 228-1)", "thread.form_bspt": "תבריג צנרת קוני R, Rc (ISO 7-1)", "thread.form_npt": "תבריג צנרת קוני NPT (ASME B1.20.1)", "thread.form_rp": "תבריג צנרת פנימי מקביל Rp (ISO 7-1)", - "thread.taper_drill_exact": "הקוטר הקטן התקני בסוף התבריג הוא {exact} מ\"מ; זהו המקדח המלאי שמתחתיו" + "thread.taper_drill_exact": "הקוטר הקטן התקני בסוף התבריג הוא {exact} מ\"מ; המקדח הוא המלצת יצרן המברז" }, "ja": { "thread.form_bspp": "管用平行ねじ G(ISO 228-1)", "thread.form_bspt": "管用テーパねじ R、Rc(ISO 7-1)", "thread.form_npt": "管用テーパねじ NPT(ASME B1.20.1)", "thread.form_rp": "管用平行めねじ Rp(ISO 7-1)", - "thread.taper_drill_exact": "ねじ終端での規格の谷径は {exact} mm です。これはそれ以下で最も近い市販ドリルです" + "thread.taper_drill_exact": "ねじ終端での規格の谷径は {exact} mm です。下穴ドリルはタップメーカーの推奨値です" }, "zh": { "thread.form_bspp": "管螺纹 G(ISO 228-1)", "thread.form_bspt": "圆锥管螺纹 R、Rc(ISO 7-1)", "thread.form_npt": "圆锥管螺纹 NPT(ASME B1.20.1)", "thread.form_rp": "圆柱内管螺纹 Rp(ISO 7-1)", - "thread.taper_drill_exact": "螺纹终端处的标准小径为 {exact} mm;这是不大于它的常备钻头" + "thread.taper_drill_exact": "螺纹终端处的标准小径为 {exact} mm;钻头为丝锥制造商的推荐值" }, "de": { "thread.form_bspp": "Rohrgewinde G (ISO 228-1)", "thread.form_bspt": "Kegeliges Rohrgewinde R, Rc (ISO 7-1)", "thread.form_npt": "Kegeliges Rohrgewinde NPT (ASME B1.20.1)", "thread.form_rp": "Zylindrisches Innenrohrgewinde Rp (ISO 7-1)", - "thread.taper_drill_exact": "Der genormte Kerndurchmesser am Gewindeende ist {exact} mm; dies ist der nächstkleinere Lagerbohrer" + "thread.taper_drill_exact": "Der genormte Kerndurchmesser am Gewindeende ist {exact} mm; der Bohrer ist die Empfehlung des Gewindebohrerherstellers" }, "ko": { "thread.form_bspp": "관용 평행나사 G (ISO 228-1)", "thread.form_bspt": "관용 테이퍼나사 R, Rc (ISO 7-1)", "thread.form_npt": "관용 테이퍼나사 NPT (ASME B1.20.1)", "thread.form_rp": "관용 평행 암나사 Rp (ISO 7-1)", - "thread.taper_drill_exact": "나사 끝에서의 규격 골지름은 {exact} mm이며, 이는 그 이하의 가장 가까운 상용 드릴입니다" + "thread.taper_drill_exact": "나사 끝에서의 규격 골지름은 {exact} mm이며, 드릴은 탭 제조사의 권장값입니다" } } diff --git a/crates/doc/src/lib.rs b/crates/doc/src/lib.rs index a6de45e77f..994d1840e9 100644 --- a/crates/doc/src/lib.rs +++ b/crates/doc/src/lib.rs @@ -53,7 +53,7 @@ mod points; mod profile_refs; pub mod progress; mod thread_catalog; -pub use thread_catalog::{TappedThread, TappedThreadFamily, parse_tapped_thread, tapped_thread_sizes}; +pub use thread_catalog::{TaperDrill, TappedThread, TappedThreadFamily, parse_tapped_thread, tapped_thread_sizes, untabulated_taper_tap}; mod walk; pub use profile_refs::{capture_profile_selection, profile_fallback_notes, profile_selection_indices, profile_selection_indices_lenient}; pub mod plastic; diff --git a/crates/doc/src/thread_catalog.rs b/crates/doc/src/thread_catalog.rs index eec847aadc..5906b8a7a1 100644 --- a/crates/doc/src/thread_catalog.rs +++ b/crates/doc/src/thread_catalog.rs @@ -46,138 +46,108 @@ fn pipe_size(size: crate::pipe_thread_catalog::PipeThread) -> TappedThread { } } -/// The drill of an internal taper thread (Rc, NPT): `exact` is the standard's internal minor -/// diameter where the thread ends (mm), the reference plane's minor at the face (d - 2h for ISO -/// 7-1, E1 - h for NPT) less the 1 in 16 taper over the thread's length (ISO 7-1's useful thread, -/// NPT's effective thread L2), so a tap cuts full form over the whole length. The hole is -/// drilled with the largest stocked drill not larger than that (`name`, `diameter`): metric for -/// Rc, inch for NPT. Above the drill series it is bored to `exact` (no `name`). +/// The drill of an internal taper thread (Rc, NPT). `diameter` is the tap maker's tabulated +/// drill for a straight (not reamed) hole; `exact` is the standard's internal minor diameter where +/// the thread ends (mm), the reference plane's minor at the face (d - 2h for ISO 7-1, E1 - h for +/// NPT) less the 1 in 16 taper over the thread's length (ISO 7-1's useful thread, NPT's effective +/// thread L2), which the dialog shows beside it. #[derive(Clone, Debug, PartialEq)] pub struct TaperDrill { pub exact: f64, pub diameter: f64, - pub name: Option, } -/// Metric twist drill diameters (mm), DIN 345:2006 (taper shank twist drills, the series up to -/// 100 mm), as reproduced at ; three digits that -/// reproduction drops are restored from their neighbours (15.25, 34.5, 54). -const DIN_345: &[f64] = &[ - 3.0, 3.2, 3.5, 3.8, 4.0, 4.2, 4.5, 4.8, 5.0, 5.2, 5.5, 5.8, 6.0, 6.2, 6.5, 6.8, 7.0, 7.2, 7.5, 7.8, 8.0, 8.2, 8.5, 8.8, 9.0, 9.2, 9.5, 9.8, 10.0, - 10.2, 10.5, 10.8, 11.0, 11.2, 11.5, 11.8, 12.0, 12.2, 12.5, 12.8, 13.0, 13.2, 13.5, 13.8, 14.0, 14.25, 14.5, 14.75, 15.0, 15.25, 15.5, 15.75, - 16.0, 16.25, 16.5, 16.75, 17.0, 17.25, 17.5, 17.75, 18.0, 18.25, 18.5, 18.75, 19.0, 19.25, 19.5, 19.75, 20.0, 20.25, 20.5, 20.75, 21.0, 21.25, - 21.5, 21.75, 22.0, 22.25, 22.5, 22.75, 23.0, 23.25, 23.5, 23.75, 24.0, 24.25, 24.5, 24.75, 25.0, 25.25, 25.5, 25.75, 26.0, 26.25, 26.5, 26.75, - 27.25, 27.5, 27.75, 28.0, 28.25, 28.5, 28.75, 29.0, 29.25, 29.5, 29.75, 30.0, 30.25, 30.5, 30.75, 31.0, 31.25, 31.5, 31.75, 32.0, 32.5, 33.0, - 33.5, 34.5, 35.0, 35.5, 36.0, 36.5, 37.0, 37.5, 38.0, 38.5, 39.0, 39.5, 40.0, 40.5, 41.0, 41.5, 42.0, 42.5, 43.0, 43.5, 44.0, 44.5, 45.0, 45.5, - 46.0, 46.5, 47.0, 47.5, 48.0, 48.5, 49.0, 49.5, 50.0, 50.5, 51.0, 52.0, 53.0, 54.0, 55.0, 56.0, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0, 64.0, - 65.0, 66.0, 67.0, 68.0, 69.0, 70.0, 71.0, 72.0, 73.0, 74.0, 76.0, 77.0, 78.0, 79.0, 80.0, 81.0, 82.0, 83.0, 84.0, 85.0, 86.0, 87.0, 88.0, 89.0, - 90.0, 91.0, 92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0, 99.0, 100.0, +/// Rc (ISO 7-1, "PT") drills: Yamawa YEU23, printed p.709 ("Taper Internal Pipe Threads - +/// Rc(BSPT)-PT"), the maximum size of a straight bored hole when the threads have non-full +/// threads (the same values as the Rc tap's bored hole A on printed p.121), for the sizes of +/// ISO 7-1's table it lists (1/16 to 4). +/// +const RC_DRILLS: &[(&str, f64)] = &[ + ("1/16", 6.1), + ("1/8", 8.1), + ("1/4", 10.7), + ("3/8", 14.2), + ("1/2", 17.6), + ("3/4", 23.0), + ("1", 29.0), + ("1 1/4", 37.6), + ("1 1/2", 43.5), + ("2", 55.0), + ("2 1/2", 70.3), + ("3", 82.8), + ("4", 107.6), ]; -/// Letter drill sizes (in), ASME B94.11M (twist drills). -const LETTER_DRILLS: &[(char, f64)] = &[ - ('A', 0.234), - ('B', 0.238), - ('C', 0.242), - ('D', 0.246), - ('E', 0.250), - ('F', 0.257), - ('G', 0.261), - ('H', 0.266), - ('I', 0.272), - ('J', 0.277), - ('K', 0.281), - ('L', 0.290), - ('M', 0.295), - ('N', 0.302), - ('O', 0.316), - ('P', 0.323), - ('Q', 0.332), - ('R', 0.339), - ('S', 0.348), - ('T', 0.358), - ('U', 0.368), - ('V', 0.377), - ('W', 0.386), - ('X', 0.397), - ('Y', 0.404), - ('Z', 0.413), +/// NPT drills: Yamawa YEU23, printed p.710 ("American Taper Pipe Threads (NPT)", referring to +/// ASME B1.20.1), the maximum size of a straight bored hole, 1/16 to 3. +const NPT_DRILLS: &[(&str, f64)] = &[ + ("1/16", 6.05), + ("1/8", 8.39), + ("1/4", 10.85), + ("3/8", 14.27), + ("1/2", 17.60), + ("3/4", 22.91), + ("1", 28.78), + ("1 1/4", 37.50), + ("1 1/2", 43.57), + ("2", 55.58), + ("2 1/2", 66.28), + ("3", 82.05), ]; -/// Fractional drill sizes by 64ths of an inch up to this many 64ths (1 1/2 in), ASME B94.11M. -const FRACTIONAL_64THS: u32 = 96; - -/// A fraction of an inch in lowest terms: `11/16 in`, `1 1/4 in`. -fn inch_fraction(n64: u32) -> String { - let (whole, mut num, mut den) = (n64 / 64, n64 % 64, 64); - while num > 0 && num % 2 == 0 { - num /= 2; - den /= 2; - } - match (whole, num) { - (0, _) => format!("{num}/{den} in"), - (w, 0) => format!("{w} in"), - (w, _) => format!("{w} {num}/{den} in"), - } +/// The drill of an Rc or NPT size the tap maker tabulates; none for the others. +fn taper_drill(family: TappedThreadFamily, size: &crate::pipe_thread_sizes::PipeSize) -> Option { + let (table, h) = match family { + TappedThreadFamily::Npt => (NPT_DRILLS, crate::pipe_thread_sizes::NPT_HEIGHT * size.pitch()), + _ => (RC_DRILLS, 0.640327 * size.pitch()), + }; + let diameter = table.iter().find(|(n, _)| *n == size.nominal)?.1; + Some(TaperDrill { exact: size.major - 2.0 * h - size.length / 16.0, diameter }) } -/// The largest stocked drill not larger than `exact` mm: metric (DIN 345) or inch (letter and -/// fractional drills, ASME B94.11M). None past the series. -fn stocked_drill(exact: f64, inch: bool) -> Option<(f64, String)> { - let fits = |d: f64| d <= exact + 1e-9; - if !inch { - let max = DIN_345.iter().copied().fold(0.0, f64::max); - if exact > max + 1.0 { - return None; - } - return DIN_345 - .iter() - .copied() - .filter(|d| fits(*d)) - .fold(None, |a: Option, d| Some(a.map_or(d, |a| a.max(d)))) - .map(|d| (d, format!("{d} mm"))); - } - if exact > f64::from(FRACTIONAL_64THS) / 64.0 * 25.4 + 25.4 / 64.0 { - return None; - } - let letters = LETTER_DRILLS.iter().map(|(c, d)| (d * 25.4, format!("{c} ({d:.3} in)"))); - let fractions = (1..=FRACTIONAL_64THS).map(|n| (f64::from(n) / 64.0 * 25.4, inch_fraction(n))); - letters.chain(fractions).filter(|(d, _)| fits(*d)).max_by(|a, b| a.0.total_cmp(&b.0)) -} - -/// The drill of an Rc or NPT size ([`TaperDrill`]). -fn taper_drill(family: TappedThreadFamily, size: &crate::pipe_thread_sizes::PipeSize) -> TaperDrill { - let p = size.pitch(); - let h = if family == TappedThreadFamily::Npt { crate::pipe_thread_sizes::NPT_HEIGHT * p } else { 0.640327 * p }; - let exact = size.major - 2.0 * h - size.length / 16.0; - match stocked_drill(exact, family == TappedThreadFamily::Npt) { - Some((diameter, name)) => TaperDrill { exact, diameter, name: Some(name) }, - None => TaperDrill { exact, diameter: (exact * 1000.0).floor() / 1000.0, name: None }, +/// The pipe sizes of a taper family. +fn taper_sizes(family: TappedThreadFamily) -> Vec { + match family { + TappedThreadFamily::Rc => crate::pipe_thread_sizes::iso_7_1().collect(), + TappedThreadFamily::Npt => crate::pipe_thread_sizes::npt().collect(), + _ => Vec::new(), } } impl TappedThread { - /// An Rc or NPT size's drill: the standard's exact value and the stocked drill it is. + /// An Rc or NPT size's drill: the tap maker's and the standard's exact value. pub fn taper_drill(&self) -> Option { - let sizes = match self.family { - TappedThreadFamily::Rc => crate::pipe_thread_sizes::iso_7_1().collect::>(), - TappedThreadFamily::Npt => crate::pipe_thread_sizes::npt().collect(), - _ => return None, - }; - sizes.iter().find(|z| (z.major - self.major_diameter).abs() < 1e-9).map(|z| taper_drill(self.family, z)) + taper_sizes(self.family).iter().find(|z| (z.major - self.major_diameter).abs() < 1e-9).and_then(|z| taper_drill(self.family, z)) } } +/// Why an Rc or NPT designation is no tapped hole's: a size of its standard the tap maker's table +/// has no drill for. None for any other text. +pub fn untabulated_taper_tap(text: &str) -> Option { + use crate::thread_forms::ThreadForm; + let (family, form, listed) = match ThreadForm::of_designation(text) { + Some(ThreadForm::Bspt) if ThreadForm::pipe_internal(text) => (TappedThreadFamily::Rc, ThreadForm::Bspt, "Rc 1/16 to Rc 4"), + Some(ThreadForm::Npt) => (TappedThreadFamily::Npt, ThreadForm::Npt, "1/16 to 3 NPT"), + _ => return None, + }; + let size = form.parse(text, 1).ok()?; + let z = taper_sizes(family).into_iter().find(|z| (z.major - size.major).abs() < 1e-9)?; + taper_drill(family, &z).is_none().then(|| { + format!("no tap drill is tabulated for `{}` (Yamawa's table, which the tapped-hole drills come from, lists {listed}): it is not offered as a tapped hole", text.trim()) + }) +} + /// An internal taper thread (Rc, NPT) of the size tables a modelled thread cuts -/// ([`crate::pipe_thread_sizes`]), drilled with [`TaperDrill`]'s stocked drill. -fn taper_size(family: TappedThreadFamily, size: crate::pipe_thread_sizes::PipeSize) -> TappedThread { +/// ([`crate::pipe_thread_sizes`]), drilled with the tap maker's drill ([`TaperDrill`]); none for +/// a size without one. +fn taper_size(family: TappedThreadFamily, size: crate::pipe_thread_sizes::PipeSize) -> Option { use crate::thread_forms::ThreadForm; let p = size.pitch(); let form = if family == TappedThreadFamily::Npt { ThreadForm::Npt } else { ThreadForm::Bspt }; let designation = form.format(size.major, p, p); let designation = if form == ThreadForm::Bspt { designation.replacen("R ", "Rc ", 1) } else { designation }; - let tap_drill = taper_drill(family, &size).diameter; - TappedThread { family, designation, major_diameter: size.major, pitch: p, tap_drill } + let tap_drill = taper_drill(family, &size)?.diameter; + Some(TappedThread { family, designation, major_diameter: size.major, pitch: p, tap_drill }) } // Diameter/pitch pairs verified against Guhring, plus Yamawa for M12x1.25. @@ -249,8 +219,7 @@ pub fn tapped_thread_sizes(family: TappedThreadFamily) -> Vec { return crate::pipe_thread_catalog::pipe_thread_sizes().map(pipe_size).filter(|s| s.family == family).collect(); } match family { - TappedThreadFamily::Rc => return crate::pipe_thread_sizes::iso_7_1().map(|z| taper_size(family, z)).collect(), - TappedThreadFamily::Npt => return crate::pipe_thread_sizes::npt().map(|z| taper_size(family, z)).collect(), + TappedThreadFamily::Rc | TappedThreadFamily::Npt => return taper_sizes(family).into_iter().filter_map(|z| taper_size(family, z)).collect(), _ => {} } @@ -274,8 +243,8 @@ pub fn tapped_thread_sizes(family: TappedThreadFamily) -> Vec { } /// Explicit metric custom sizes remain supported. Unified sizes must be in the verified -/// catalogue and include their UNC/UNF suffix. G/Rp use explicit process drills, Rc/NPT the drill -/// derived from their standards ([`taper_size`]). +/// catalogue and include their UNC/UNF suffix. G/Rp use explicit process drills, Rc/NPT the tap +/// maker's tabulated drills ([`TaperDrill`]); an Rc or NPT size without one is none. pub fn parse_tapped_thread(text: &str) -> Option { if text.len() > 128 { return None; @@ -319,36 +288,35 @@ mod tests { } } - /// Rc and NPT holes: the size tables' diameters, and the stocked drill just under the internal - /// minor where the thread ends (Rc 1/2: d1 18.631 at the face less 13.2 mm of useful thread at - /// 1 in 16, 17.807, drilled 17.75 mm; 1/2 NPT: E1 - h = 0.77843 - 0.05714 in at the face less - /// L2 = 0.5337 in at 1 in 16, 0.6879 in, drilled 11/16 in), and no external R. + /// Rc and NPT holes: the size tables' diameters, the tap maker's drill (Yamawa YEU23 pp.709 + /// and 710) and the standard's minor where the thread ends (Rc 1/2: d1 18.631 at the face less + /// 13.2 mm of useful thread at 1 in 16; 1/2 NPT: E1 - h = 0.77843 - 0.05714 in at the face less + /// L2 = 0.5337 in at 1 in 16), a size without a tabulated drill refused, and no external R. #[test] - fn internal_taper_threads_drill_to_their_minor_where_the_thread_ends() { + fn internal_taper_threads_take_the_tap_makers_drill() { let rc = parse_tapped_thread("Rc1/2").unwrap(); - assert_eq!((rc.family, rc.designation.as_str(), rc.major_diameter), (TappedThreadFamily::Rc, "Rc 1/2", 20.955)); + assert_eq!((rc.family, rc.designation.as_str(), rc.major_diameter, rc.tap_drill), (TappedThreadFamily::Rc, "Rc 1/2", 20.955, 17.6)); let d = rc.taper_drill().unwrap(); assert!((d.exact - (18.631 - 13.2 / 16.0)).abs() < 2e-3, "{d:?}"); - assert_eq!((rc.tap_drill, d.name.as_deref()), (17.75, Some("17.75 mm"))); let npt = parse_tapped_thread("1/2 NPT").unwrap(); - assert_eq!((npt.family, npt.designation.as_str()), (TappedThreadFamily::Npt, "1/2-14 NPT")); + assert_eq!((npt.family, npt.designation.as_str(), npt.tap_drill), (TappedThreadFamily::Npt, "1/2-14 NPT", 17.6)); let d = npt.taper_drill().unwrap(); assert!((d.exact - (0.77843 - 0.8 / 14.0 - 0.5337 / 16.0) * 25.4).abs() < 2e-3, "{d:?}"); - assert_eq!((npt.tap_drill, d.name.as_deref()), (11.0 / 16.0 * 25.4, Some("11/16 in"))); - // A letter drill where it is the closer stocked size (1/8 NPT), and a bore past the series. - let small = parse_tapped_thread("1/8 NPT").unwrap().taper_drill().unwrap(); - assert!(small.diameter <= small.exact && small.name.is_some(), "{small:?}"); - let large = parse_tapped_thread("Rc 6").unwrap().taper_drill().unwrap(); - assert!(large.name.is_none() && (large.diameter - large.exact).abs() < 1e-3, "{large:?}"); - for family in [TappedThreadFamily::Rc, TappedThreadFamily::Npt] { - for s in tapped_thread_sizes(family) { - let d = s.taper_drill().unwrap(); - assert!(d.diameter <= d.exact + 1e-9 && d.exact - d.diameter < 0.02 * d.exact + 0.4, "{}: {d:?}", s.designation); - } - } assert_eq!(rc.profile_degrees(), 55.0); assert_eq!(npt.profile_degrees(), 60.0); assert!(parse_tapped_thread("R 1/2").is_none(), "an external taper thread is no hole's"); + for untabulated in ["Rc 5", "Rc 6", "4-8 NPT", "12 NPT"] { + assert!(parse_tapped_thread(untabulated).is_none(), "{untabulated}"); + assert!(untabulated_taper_tap(untabulated).is_some_and(|e| e.contains("no tap drill is tabulated")), "{untabulated}"); + } + assert!(untabulated_taper_tap("Rc 1/2").is_none() && untabulated_taper_tap("M8").is_none()); + // Each tabulated drill lies within a pitch of the standard's minor where the thread ends. + for family in [TappedThreadFamily::Rc, TappedThreadFamily::Npt] { + for s in tapped_thread_sizes(family) { + let d = s.taper_drill().unwrap(); + assert!((d.diameter - d.exact).abs() < s.pitch, "{}: {d:?}", s.designation); + } + } } #[test] @@ -367,8 +335,8 @@ mod tests { let count = match family { TappedThreadFamily::Metric => sizes.len(), TappedThreadFamily::G | TappedThreadFamily::Rp => 6, - TappedThreadFamily::Rc => 15, - TappedThreadFamily::Npt => crate::pipe_thread_sizes::npt().count(), + TappedThreadFamily::Rc => RC_DRILLS.len(), + TappedThreadFamily::Npt => NPT_DRILLS.len(), _ => 14, }; assert_eq!(sizes.len(), count, "{family:?}"); diff --git a/crates/engine/src/cmd/features.rs b/crates/engine/src/cmd/features.rs index c3dab105f5..144ce2f893 100644 --- a/crates/engine/src/cmd/features.rs +++ b/crates/engine/src/cmd/features.rs @@ -69,7 +69,7 @@ pub static COMMANDS: &[CommandSpec] = &[ .at("SOLID", "CREATE") .icon("hole") .key("H") - .params("position: [x,y,z] on a face (numbers, or length expressions the hole follows) | sketch + points: [sketch point ids] (one hole each, perpendicular to the sketch, into the material on whichever side of the sketch plane it is) | sketch alone (every sketch point: standalone points and circle centres); flip?: bool (sketch points: drill to the other side); single?: bool (with one sketch point: a Single hole that follows the point, drilled into the face there); direction?: [x,y,z] (default: into the face, against its outward normal; one that leaves the face is mirrored into it; ignored with sketch points unless single); diameter | thread (a tapped hole: metric major minus pitch or tabulated UNC/UNF, G or Rp cut-tap drill, or the Rc/NPT drill to the internal minor diameter where the thread ends) | clearance: \"M6\" with fit?: close|normal|loose (ISO 273); depth? (default through all) | to?: [x,y,z] (To Object: drill to that face or point); references?: [{edge: [x,y,z] on a straight edge of the face, distance: expr}] (at most two; the centre goes that far from each edge's line, in the face's plane, and follows the edges; with a numeric position only); type?: simple|drilled|counterbore|countersink; tip_angle?; cb_diameter?, cb_depth?; cs_diameter?, cs_angle?; thread?: \"M6\", \"1/4-20 UNC\", \"G 1/4\", \"Rp 1/4\", \"Rc 1/4\" or \"1/4-18 NPT\" (cosmetic unless modeled); hand?: right|left (the tapped thread's hand; default right; needs thread); thread_depth?: positive length independent of drill depth (default: full useful cylindrical wall; must fit that wall); modeled?: bool (needs thread: the tapped thread cut into the wall, the groove between its teeth, as solid.thread's modelled one; default false: listed only); ease_out?: expr|standard|short (modelled tapped threads: the inner end, towards the bottom, fades out over this length, ISO 3508's runout, standard 2.5 P, short 1.25 P; without it the groove stops square at thread_depth, or clear of a blind hole's bottom); targets?: [body] (the bodies to drill; holes at the points of a sketch on a body's face default to that body, found along the holes' direction on it alone; explicit targets or participants override, [\"*\"] every body)"), + .params("position: [x,y,z] on a face (numbers, or length expressions the hole follows) | sketch + points: [sketch point ids] (one hole each, perpendicular to the sketch, into the material on whichever side of the sketch plane it is) | sketch alone (every sketch point: standalone points and circle centres); flip?: bool (sketch points: drill to the other side); single?: bool (with one sketch point: a Single hole that follows the point, drilled into the face there); direction?: [x,y,z] (default: into the face, against its outward normal; one that leaves the face is mirrored into it; ignored with sketch points unless single); diameter | thread (a tapped hole: metric major minus pitch or tabulated UNC/UNF, G or Rp cut-tap drill, or the Rc/NPT tap maker's tabulated drill, Rc 1/16 to 4 and NPT 1/16 to 3) | clearance: \"M6\" with fit?: close|normal|loose (ISO 273); depth? (default through all) | to?: [x,y,z] (To Object: drill to that face or point); references?: [{edge: [x,y,z] on a straight edge of the face, distance: expr}] (at most two; the centre goes that far from each edge's line, in the face's plane, and follows the edges; with a numeric position only); type?: simple|drilled|counterbore|countersink; tip_angle?; cb_diameter?, cb_depth?; cs_diameter?, cs_angle?; thread?: \"M6\", \"1/4-20 UNC\", \"G 1/4\", \"Rp 1/4\", \"Rc 1/4\" or \"1/4-18 NPT\" (cosmetic unless modeled); hand?: right|left (the tapped thread's hand; default right; needs thread); thread_depth?: positive length independent of drill depth (default: full useful cylindrical wall; must fit that wall); modeled?: bool (needs thread: the tapped thread cut into the wall, the groove between its teeth, as solid.thread's modelled one; default false: listed only); ease_out?: expr|standard|short (modelled tapped threads: the inner end, towards the bottom, fades out over this length, ISO 3508's runout, standard 2.5 P, short 1.25 P; without it the groove stops square at thread_depth, or clear of a blind hole's bottom); targets?: [body] (the bodies to drill; holes at the points of a sketch on a body's face default to that body, found along the holes' direction on it alone; explicit targets or participants override, [\"*\"] every body)"), CommandSpec::new("solid.pipe", "Pipe", pipe) .at("SOLID", "CREATE") .icon("pipe") @@ -2434,7 +2434,9 @@ fn hole(s: &mut Session, p: &Value) -> Result { { return Err(bad( cmd, - fine_only_hint(t).unwrap_or_else(|| format!("unknown tapped thread `{t}` (ISO metric or a catalogued UNC/UNF, G, Rp, Rc or NPT size)")), + fine_only_hint(t) + .or_else(|| solidcraft_doc::untabulated_taper_tap(t)) + .unwrap_or_else(|| format!("unknown tapped thread `{t}` (ISO metric or a catalogued UNC/UNF, G, Rp, Rc or NPT size)")), )); } let thread_depth = match p.get("thread_depth") { diff --git a/crates/engine/src/thread_tests.rs b/crates/engine/src/thread_tests.rs index d375298501..10710c0d47 100644 --- a/crates/engine/src/thread_tests.rs +++ b/crates/engine/src/thread_tests.rs @@ -1216,8 +1216,8 @@ fn a_taper_pipe_thread_starts_at_a_free_end_and_is_named_for_its_side() { /// A tapped hole's thread cut into its wall (`modeled`), as the thread feature cuts one: an M8 /// blind hole whose groove stops clear of the drill point's bottom, or fades out before it /// (`ease_out`, ISO 3508), which leaves more of the wall than the full form there; an Rc 1/2 -/// through a plate, drilled with the stocked drill under its minor where the thread ends and cut -/// to its taper; each a valid body that reopens the same. +/// through a plate, drilled with the tap maker's drill and cut to its taper; each a valid body +/// that reopens the same. An Rc size with no tabulated drill is refused. #[test] fn a_modelled_tapped_hole_stops_clear_of_its_bottom_or_fades_out_before_it() { let blind = |extra: Value| { @@ -1243,9 +1243,8 @@ fn a_modelled_tapped_hole_stops_clear_of_its_bottom_or_fades_out_before_it() { run(&mut s, "solid.box", json!({"length":40,"width":40,"height":12,"body_name":"Plate"})); let out = run(&mut s, "solid.hole", json!({"position":[20,20,12],"direction":[0,0,-1],"thread":"Rc 1/2"})); let drilled = exact_volume(&s, "Plate"); - // d1 at the face less 13.2 mm of useful thread at 1 in 16 is 17.807 mm: the stocked drill - // under it is 17.75 mm (DIN 345). - let drill: f64 = 17.75; + // The tap maker's drill for a straight Rc 1/2 hole (Yamawa YEU23 p.709). + let drill: f64 = 17.6; assert!((drilled - (40.0 * 40.0 * 12.0 - PI * (drill / 2.0).powi(2) * 12.0)).abs() < 1e-3, "{drilled}"); let id = out["feature"].as_u64().unwrap(); let redefine = json!({"position":[20,20,12],"direction":[0,0,-1],"thread":"Rc 1/2","modeled":true}); @@ -1256,4 +1255,5 @@ fn a_modelled_tapped_hole_stops_clear_of_its_bottom_or_fades_out_before_it() { let err = |p: Value| Session::default().execute("solid.hole", &p).err().map(|e| e.to_string()).unwrap_or_default(); assert!(err(json!({"position":[0,0,0],"diameter":5,"modeled":true})).contains("`modeled` needs `thread`")); assert!(err(json!({"position":[0,0,0],"thread":"M8","ease_out":"standard"})).contains("modelled thread")); + assert!(err(json!({"position":[0,0,0],"thread":"Rc 5"})).contains("no tap drill is tabulated")); } diff --git a/crates/ui-egui/src/dialogs_thread.rs b/crates/ui-egui/src/dialogs_thread.rs index d161a4f7fd..e7aa2bc1a2 100644 --- a/crates/ui-egui/src/dialogs_thread.rs +++ b/crates/ui-egui/src/dialogs_thread.rs @@ -155,16 +155,12 @@ pub(crate) fn hole_rows(ui: &mut egui::Ui, doc: &Document, designation: &mut Str } form_row(ui, "Tap drill diameter", |ui| { match parse_tapped_thread(designation).filter(|_| custom.as_ref().is_none_or(|draft| draft.error.is_none())) { - // An Rc or NPT hole is drilled with the stocked drill under the standard's minor - // diameter where the thread ends; that exact value is the tooltip. + // An Rc or NPT hole takes the tap maker's drill; the standard's minor diameter where + // the thread ends is the tooltip. Some(size) => match size.taper_drill() { Some(d) => { - let shown = d - .name - .filter(|n| !n.ends_with(" mm")) - .map_or_else(|| format!("{} mm", size.tap_drill), |n| format!("{} mm ({n})", size.tap_drill)); let exact = format!("{:.3}", d.exact); - ui.label(shown).on_hover_text(crate::i18n::format("thread.taper_drill_exact", &[("exact", &exact)])); + ui.label(format!("{} mm", size.tap_drill)).on_hover_text(crate::i18n::format("thread.taper_drill_exact", &[("exact", &exact)])); } None => { ui.label(format!("{} mm", size.tap_drill)); diff --git a/docs/thread-catalogue.md b/docs/thread-catalogue.md index e1de44ad35..641215a10f 100644 --- a/docs/thread-catalogue.md +++ b/docs/thread-catalogue.md @@ -36,18 +36,16 @@ thread classes and sealing behaviour are outside this catalogue. ## Taper Rc and NPT threads -An Rc or NPT hole is drilled with the largest stocked drill not larger than the -internal thread's minor diameter where the thread ends: the reference plane's -minor (d - 2h for ISO 7-1, E1 - h for NPT) at the face, less the 1 in 16 taper -over the thread's length (ISO 7-1's useful thread, NPT's effective thread L2), so -that the tap cuts full form over the whole length. Rc takes a metric drill of the -DIN 345 series (to 100 mm), NPT a letter or fractional inch drill (by 64ths to -1 1/2 in, ASME B94.11M); past the series the hole is bored to the exact value. The -Hole dialog shows the drill and, as its tooltip, the exact value: Rc 1/2 is -17.807 mm, drilled 17.75 mm; 1/2 NPT is 17.473 mm (0.6879 in), drilled 11/16 in -(17.4625 mm). This is derived from the standards, not a tap maker's -recommendation (which often allows for reaming); an explicit `diameter` -overrides it. Listed, the hole +An Rc or NPT hole takes the tap maker's drill for a straight (not reamed) hole: +Yamawa YEU23's "maximum size of straight bored hole", for Rc with non-full +threads (printed p.709, Rc 1/16 to 4) and for NPT (printed p.710, 1/16 to 3). +A size of ISO 7-1 or ASME B1.20.1 outside those (Rc 5 and 6, NPT 3 1/2 to 12) is +refused as a tapped hole. The Hole dialog shows the drill and, as its tooltip, +the standard's minor diameter where the thread ends: the reference plane's minor +(d - 2h for ISO 7-1, E1 - h for NPT) at the face less the 1 in 16 taper over the +thread's length (ISO 7-1's useful thread, NPT's effective thread L2). Rc 1/2: +drill 17.6 mm, minor 17.807 mm; 1/2 NPT: drill 17.60 mm, minor 17.473 mm. An +explicit `diameter` overrides the drill. Listed, the hole is that cylindrical drill; modelled, the thread's taper is cut into it (the tap's minor cone and the helical groove). @@ -78,16 +76,11 @@ cylindrical pilot plus the designation; modelled, the helical groove is cut too. ## Primary sources -- DIN 345:2006 (taper shank twist drills), the metric drill diameters to 100 mm as - reproduced at : the stocked drills - of Rc holes. Three digits that reproduction drops (15.25, 34.5, 54) are restored - from their neighbours. -- ASME B94.11M (twist drills): the letter drill sizes A to Z and fractional sizes - by 64ths of an inch, the stocked drills of NPT holes. - [Yamawa, YEU23 European catalogue](https://www.yamawa.eu/files/Yamawa_European_Catalogue_YEU23.pdf), printed page 668: basic G/Rp dimensions; page 707: separate G and Rp drill and - recommended bore columns. These supply the finite parallel-pipe entries above. + recommended bore columns; page 709: Rc drills (maximum straight bored hole); + page 710: NPT drills. These supply the finite parallel-pipe entries above. - [Guhring, Tap Drill Sizes](https://guhring.com/media/catalogs/lgbkp3ieki4.pdf), PDF page 2: UNC/UNF pitch and practical CUT TAPS approximately75% drills;