Tapped holes: Rc and NPT take the tap maker's tabulated drill

The Rc and NPT drills were rounded to a drill series from a third-party reproduction of DIN 345
and a fractional/letter inch series cited from memory. They now come from the same tap maker's
catalogue the G and Rp drills do: Yamawa YEU23, printed p.709 (Rc, the maximum straight bored
hole with non-full threads, Rc 1/16 to 4) and p.710 (NPT, the maximum straight bored hole, 1/16
to 3). A size its tables lack (Rc 5 and 6, NPT 3 1/2 to 12) is refused as a tapped hole, naming
the range. The dialog's tooltip keeps the standard's minor diameter where the thread ends.
Rc 1/2 drills 17.6 mm (minor 17.807), 1/2 NPT 17.60 mm (minor 17.473).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKMoxpM7Vuv8Rb6E3K2
This commit is contained in:
jyviko
2026-10-27 11:15:38 -04:00
committed by Irakli Kourtis
parent b6c3b32a98
commit e7994d48cc
7 changed files with 132 additions and 173 deletions
+7 -7
View File
@@ -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이며, 드릴은 탭 제조사의 권장값입니다"
}
}
+1 -1
View File
@@ -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;
+100 -132
View File
@@ -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<String>,
}
/// Metric twist drill diameters (mm), DIN 345:2006 (taper shank twist drills, the series up to
/// 100 mm), as reproduced at <https://www.fasten.it/en/Norms/Norm/DIN%20345>; 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).
/// <https://www.yamawa.eu/files/Yamawa_European_Catalogue_YEU23.pdf>
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<TaperDrill> {
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<f64>, 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<crate::pipe_thread_sizes::PipeSize> {
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<TaperDrill> {
let sizes = match self.family {
TappedThreadFamily::Rc => crate::pipe_thread_sizes::iso_7_1().collect::<Vec<_>>(),
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<String> {
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<TappedThread> {
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<TappedThread> {
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<TappedThread> {
}
/// 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<TappedThread> {
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:?}");
+4 -2
View File
@@ -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<Value> {
{
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") {
+5 -5
View File
@@ -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"));
}
+3 -7
View File
@@ -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));
+12 -19
View File
@@ -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 <https://www.fasten.it/en/Norms/Norm/DIN%20345>: 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;