mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 10:35:58 +00:00
Tapped holes: a preference keeps metric's major-less-pitch drill
New metric tapped holes drill the tap maker's drill (M8 6.8 mm). The Design preference "Metric tapped holes drill the major diameter less the pitch" (`metric_tap_drill_major_less_pitch`, off by default) gives them the drill every metric size took before (M8 6.75 mm): the Hole dialog shows and sends it, and `solid.hole` given a thread and no `diameter` drills it, through the session as `auto_project` is (kept when a design is opened or started, and across tabs). Unified, pipe and taper sizes keep their tabulated drills; a given diameter is unchanged. Saved designs keep the drill they stored either way. Labels in en, ar, he, ja, zh, de and ko. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LUXeKMoxpM7Vuv8Rb6E3K2
This commit is contained in:
@@ -1,35 +1,49 @@
|
||||
{
|
||||
"en": {
|
||||
"prefs.holes": "Holes",
|
||||
"prefs.metric_tap_drill_major_less_pitch": "Metric tapped holes drill the major diameter less the pitch (M8: 6.75 mm), not the tap maker's drill",
|
||||
"thread.drill_range": "The tap maker's drill is {drill} mm. The internal thread's standard minor diameter is {minor} mm. A drill from {low} to {high} mm is accepted.",
|
||||
"thread.drill_range_custom": "The drill is the major diameter less the pitch, {drill} mm. The internal thread's standard minor diameter is {minor} mm. A drill from {low} to {high} mm is accepted.",
|
||||
"thread.drill_range_taper": "The tap maker's drill is {drill} mm. The standard's minor diameter where the thread ends is {minor} mm. A drill from {low} to {high} mm is accepted."
|
||||
},
|
||||
"ar": {
|
||||
"prefs.holes": "الثقوب",
|
||||
"prefs.metric_tap_drill_major_less_pitch": "تُثقب الثقوب الملولبة المترية بالقطر الأكبر ناقص الخطوة (M8: 6.75 مم) بدل مثقاب صانع اللقمة",
|
||||
"thread.drill_range": "مثقاب صانع اللقمة {drill} مم. القطر الأصغر القياسي للسن الداخلي {minor} مم. يُقبل مثقاب من {low} إلى {high} مم.",
|
||||
"thread.drill_range_custom": "المثقاب هو القطر الأكبر ناقص الخطوة، {drill} مم. القطر الأصغر القياسي للسن الداخلي {minor} مم. يُقبل مثقاب من {low} إلى {high} مم.",
|
||||
"thread.drill_range_taper": "مثقاب صانع اللقمة {drill} مم. القطر الأصغر القياسي عند نهاية السن {minor} مم. يُقبل مثقاب من {low} إلى {high} مم."
|
||||
},
|
||||
"he": {
|
||||
"prefs.holes": "חורים",
|
||||
"prefs.metric_tap_drill_major_less_pitch": "חורים מתוברגים מטריים נקדחים בקוטר הגדול פחות הפסיעה (M8: 6.75 מ\"מ) ולא במקדח של יצרן המברז",
|
||||
"thread.drill_range": "מקדח יצרן המברז הוא {drill} מ\"מ. הקוטר הקטן התקני של התבריג הפנימי הוא {minor} מ\"מ. מתקבל מקדח מ-{low} עד {high} מ\"מ.",
|
||||
"thread.drill_range_custom": "המקדח הוא הקוטר הגדול פחות הפסיעה, {drill} מ\"מ. הקוטר הקטן התקני של התבריג הפנימי הוא {minor} מ\"מ. מתקבל מקדח מ-{low} עד {high} מ\"מ.",
|
||||
"thread.drill_range_taper": "מקדח יצרן המברז הוא {drill} מ\"מ. הקוטר הקטן התקני בסוף התבריג הוא {minor} מ\"מ. מתקבל מקדח מ-{low} עד {high} מ\"מ."
|
||||
},
|
||||
"ja": {
|
||||
"prefs.holes": "穴",
|
||||
"prefs.metric_tap_drill_major_less_pitch": "メートルねじ穴の下穴を、タップメーカーの推奨値ではなく外径からピッチを引いた値にする(M8: 6.75 mm)",
|
||||
"thread.drill_range": "タップメーカーの下穴ドリルは {drill} mm です。めねじの規格の内径は {minor} mm です。{low}~{high} mm のドリルを使用できます。",
|
||||
"thread.drill_range_custom": "下穴ドリルは外径からピッチを引いた {drill} mm です。めねじの規格の内径は {minor} mm です。{low}~{high} mm のドリルを使用できます。",
|
||||
"thread.drill_range_taper": "タップメーカーの下穴ドリルは {drill} mm です。ねじ終端での規格の内径は {minor} mm です。{low}~{high} mm のドリルを使用できます。"
|
||||
},
|
||||
"zh": {
|
||||
"prefs.holes": "孔",
|
||||
"prefs.metric_tap_drill_major_less_pitch": "公制螺纹孔的底孔取大径减螺距(M8:6.75 mm),而非丝锥制造商的钻头",
|
||||
"thread.drill_range": "丝锥制造商的钻头为 {drill} mm。内螺纹的标准小径为 {minor} mm。可使用 {low} 至 {high} mm 的钻头。",
|
||||
"thread.drill_range_custom": "钻头为大径减螺距,{drill} mm。内螺纹的标准小径为 {minor} mm。可使用 {low} 至 {high} mm 的钻头。",
|
||||
"thread.drill_range_taper": "丝锥制造商的钻头为 {drill} mm。螺纹终端处的标准小径为 {minor} mm。可使用 {low} 至 {high} mm 的钻头。"
|
||||
},
|
||||
"de": {
|
||||
"prefs.holes": "Bohrungen",
|
||||
"prefs.metric_tap_drill_major_less_pitch": "Metrische Gewindebohrungen mit Nenndurchmesser minus Steigung bohren (M8: 6,75 mm), nicht mit dem Bohrer des Gewindebohrerherstellers",
|
||||
"thread.drill_range": "Der Bohrer des Gewindebohrerherstellers ist {drill} mm. Der genormte Kerndurchmesser des Innengewindes ist {minor} mm. Ein Bohrer von {low} bis {high} mm wird angenommen.",
|
||||
"thread.drill_range_custom": "Der Bohrer ist der Nenndurchmesser minus Steigung, {drill} mm. Der genormte Kerndurchmesser des Innengewindes ist {minor} mm. Ein Bohrer von {low} bis {high} mm wird angenommen.",
|
||||
"thread.drill_range_taper": "Der Bohrer des Gewindebohrerherstellers ist {drill} mm. Der genormte Kerndurchmesser am Gewindeende ist {minor} mm. Ein Bohrer von {low} bis {high} mm wird angenommen."
|
||||
},
|
||||
"ko": {
|
||||
"prefs.holes": "구멍",
|
||||
"prefs.metric_tap_drill_major_less_pitch": "미터 탭 구멍을 탭 제조사의 드릴 대신 바깥지름에서 피치를 뺀 값으로 뚫기 (M8: 6.75 mm)",
|
||||
"thread.drill_range": "탭 제조사의 드릴은 {drill} mm입니다. 암나사의 규격 골지름은 {minor} mm입니다. {low}~{high} mm 드릴을 사용할 수 있습니다.",
|
||||
"thread.drill_range_custom": "드릴은 바깥지름에서 피치를 뺀 {drill} mm입니다. 암나사의 규격 골지름은 {minor} mm입니다. {low}~{high} mm 드릴을 사용할 수 있습니다.",
|
||||
"thread.drill_range_taper": "탭 제조사의 드릴은 {drill} mm입니다. 나사 끝에서의 규격 골지름은 {minor} mm입니다. {low}~{high} mm 드릴을 사용할 수 있습니다."
|
||||
|
||||
@@ -485,6 +485,16 @@ impl TappedThread {
|
||||
(self.minor_diameter().min(self.tap_drill), self.tap_drill * (1.0 + DRILL_ALLOWANCE))
|
||||
}
|
||||
|
||||
/// The drill a new hole of this size takes without a `diameter`: the tap maker's, or for an
|
||||
/// ISO metric size with `major_less_pitch` (the preference that keeps the drill every metric
|
||||
/// size took before) the major diameter less the pitch, to the micrometre.
|
||||
pub fn default_drill(&self, major_less_pitch: bool) -> f64 {
|
||||
match (self.family, major_less_pitch) {
|
||||
(TappedThreadFamily::Metric, true) => ((self.major_diameter - self.pitch) * 1000.0).round() / 1000.0,
|
||||
_ => self.tap_drill,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a hole of `diameter` mm can be tapped to this size; the error names the range.
|
||||
pub fn check_drill(&self, diameter: f64) -> Result<(), String> {
|
||||
let (lo, hi) = self.drill_range();
|
||||
|
||||
@@ -2545,7 +2545,7 @@ fn hole(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
(None, Some(t), None) => {
|
||||
let size = solidcraft_doc::parse_tapped_thread(t).ok_or_else(|| bad(cmd, "thread"))?;
|
||||
Expr::new(format!("{} mm", size.tap_drill))
|
||||
Expr::new(format!("{} mm", size.default_drill(s.metric_tap_drill_major_less_pitch)))
|
||||
}
|
||||
(None, None, None) => return Err(bad(cmd, "needs `diameter`, `thread` (a tapped hole) or `clearance` (a bolt size)")),
|
||||
};
|
||||
|
||||
@@ -368,6 +368,9 @@ pub struct Session {
|
||||
pub(crate) followed: Option<cmd::inspect::Followed>,
|
||||
/// Project a face's edges into a sketch created on it (the default; `sketch.auto_project`).
|
||||
pub auto_project: bool,
|
||||
/// A new metric tapped hole given no `diameter` drills the major diameter less the pitch,
|
||||
/// as before the tap maker's drills (a user preference); false: the tap maker's drill.
|
||||
pub metric_tap_drill_major_less_pitch: bool,
|
||||
/// How calls that break a command's parameter contract are treated (off by default). It
|
||||
/// belongs to the session, not the design, so it survives opening and starting designs.
|
||||
pub param_check: schema::ParamCheck,
|
||||
@@ -569,6 +572,7 @@ impl Session {
|
||||
document_id: cmd::inspect::new_document_id(),
|
||||
followed: None,
|
||||
auto_project: true,
|
||||
metric_tap_drill_major_less_pitch: false,
|
||||
param_check: schema::ParamCheck::Off,
|
||||
easyprint_upload: None,
|
||||
live_links: None,
|
||||
@@ -613,6 +617,7 @@ impl Session {
|
||||
/// checkpoint keeps, not the replaced session's file path (docs/mcp.md).
|
||||
pub fn replace_design(&mut self, fresh: Session) {
|
||||
let auto_project = self.auto_project;
|
||||
let metric_tap_drill_major_less_pitch = self.metric_tap_drill_major_less_pitch;
|
||||
let param_check = self.param_check;
|
||||
let easyprint_upload = self.easyprint_upload.take();
|
||||
let render_viewport = self.render_viewport.clone();
|
||||
@@ -626,6 +631,7 @@ impl Session {
|
||||
*self = fresh;
|
||||
self.crash_journal = crash_journal;
|
||||
self.auto_project = auto_project;
|
||||
self.metric_tap_drill_major_less_pitch = metric_tap_drill_major_less_pitch;
|
||||
self.param_check = param_check;
|
||||
self.easyprint_upload = easyprint_upload;
|
||||
self.render_viewport = render_viewport;
|
||||
@@ -1338,6 +1344,7 @@ impl Session {
|
||||
document_id: self.document_id.clone(),
|
||||
followed: None,
|
||||
auto_project: self.auto_project,
|
||||
metric_tap_drill_major_less_pitch: self.metric_tap_drill_major_less_pitch,
|
||||
param_check: self.param_check,
|
||||
easyprint_upload: self.easyprint_upload.clone(),
|
||||
live_links: None,
|
||||
|
||||
@@ -566,7 +566,14 @@ pub(super) fn rows(app: &mut SolveApp, ctx: &egui::Context, d: &mut Dialog, ui:
|
||||
opts.tapped = tap == 1;
|
||||
});
|
||||
if opts.tapped {
|
||||
enter |= crate::dialogs_thread::hole_rows(ui, &app.session.doc, &mut opts.thread, &mut opts.custom_thread, &mut opts.drill);
|
||||
enter |= crate::dialogs_thread::hole_rows(
|
||||
ui,
|
||||
&app.session.doc,
|
||||
&mut opts.thread,
|
||||
&mut opts.custom_thread,
|
||||
&mut opts.drill,
|
||||
app.preferences.metric_tap_drill_major_less_pitch,
|
||||
);
|
||||
crate::dialogs_thread::hole_hand_row(ui, &mut opts.hand);
|
||||
ui_kit::checkbox_grid(ui, &mut [("Full thread depth", &mut opts.thread_full_depth), ("Modeled", &mut opts.modeled)], 1);
|
||||
// A modelled thread's inner end, towards the bottom, as the thread dialog's.
|
||||
|
||||
@@ -74,6 +74,7 @@ pub(crate) fn hole_rows(
|
||||
designation: &mut String,
|
||||
custom: &mut Option<HoleThreadDraft>,
|
||||
drill: &mut Option<String>,
|
||||
major_less_pitch: bool,
|
||||
) -> bool {
|
||||
let before = designation.clone();
|
||||
let designation_id = ui.make_persistent_id("hole_thread_designation");
|
||||
@@ -169,12 +170,13 @@ pub(crate) fn hole_rows(
|
||||
form_row(ui, "Tap drill diameter", |ui| {
|
||||
match parse_tapped_thread(designation).filter(|_| custom.as_ref().is_none_or(|draft| draft.error.is_none())) {
|
||||
Some(size) => {
|
||||
let default = format!("{} mm", size.tap_drill);
|
||||
let default = format!("{} mm", size.default_drill(major_less_pitch));
|
||||
let (lo, hi) = size.drill_range();
|
||||
let mm = |v: f64| format!("{:.3}", v);
|
||||
let args = [("drill", mm(size.tap_drill)), ("minor", mm(size.minor_diameter())), ("low", mm(lo)), ("high", mm(hi))];
|
||||
let args =
|
||||
[("drill", mm(size.default_drill(major_less_pitch))), ("minor", mm(size.minor_diameter())), ("low", mm(lo)), ("high", mm(hi))];
|
||||
let args: Vec<(&str, &str)> = args.iter().map(|(k, v)| (*k, v.as_str())).collect();
|
||||
let id = match (size.taper_drill().is_some(), size.drill_is_tabulated()) {
|
||||
let id = match (size.taper_drill().is_some(), size.drill_is_tabulated() && size.default_drill(major_less_pitch) == size.tap_drill) {
|
||||
(true, _) => "thread.drill_range_taper",
|
||||
(false, true) => "thread.drill_range",
|
||||
(false, false) => "thread.drill_range_custom",
|
||||
@@ -982,7 +984,7 @@ mod contribution_tests {
|
||||
let doc = Document::default();
|
||||
let _ = ctx.run_ui(Default::default(), |ui| {
|
||||
egui::Grid::new("hole_catalogue").show(ui, |ui| {
|
||||
hole_rows(ui, &doc, &mut designation, &mut custom, &mut None);
|
||||
hole_rows(ui, &doc, &mut designation, &mut custom, &mut None, false);
|
||||
});
|
||||
});
|
||||
assert_eq!(designation, original);
|
||||
@@ -1131,7 +1133,7 @@ mod contribution_tests {
|
||||
let doc = Document::default();
|
||||
let _ = context.run_ui(Default::default(), |ui| {
|
||||
egui::Grid::new("authored_hole").show(ui, |ui| {
|
||||
hole_rows(ui, &doc, &mut hole, &mut custom, &mut None);
|
||||
hole_rows(ui, &doc, &mut hole, &mut custom, &mut None, false);
|
||||
});
|
||||
});
|
||||
assert_eq!(hole, original);
|
||||
|
||||
@@ -158,6 +158,7 @@ fn swap_in(app: &mut SolveApp, mut slot: DocSlot) -> DocSlot {
|
||||
put_view(app, slot.view);
|
||||
// Preferences the session holds go with the user, not the tab (#680).
|
||||
app.session.auto_project = out.session.auto_project;
|
||||
app.session.metric_tap_drill_major_less_pitch = out.session.metric_tap_drill_major_less_pitch;
|
||||
// Views cache by revision: the incoming design must not look unchanged.
|
||||
let before = app.session.revision;
|
||||
app.session.invalidate_view(out_rev);
|
||||
|
||||
@@ -974,6 +974,8 @@ impl SolveApp {
|
||||
if matches!(id, "file.save_mesh" | "file.send_to_slicer") {
|
||||
solidcraft_engine::slicers::set_chosen_programs(&self.preferences.custom_slicers);
|
||||
}
|
||||
// The preference the hole command reads, current even before the next frame applies it.
|
||||
self.session.metric_tap_drill_major_less_pitch = self.preferences.metric_tap_drill_major_less_pitch;
|
||||
let t0 = now_ms();
|
||||
let r = self.session.execute(id, ¶ms).map_err(|e| e.to_string());
|
||||
self.big_operation |= now_ms() - t0 >= recovery_ui::BIG_OPERATION_MS;
|
||||
|
||||
@@ -617,6 +617,10 @@ preference_settings! {
|
||||
/// Up/Down and the wheel over a focused value field step its plain number (`value_step`,
|
||||
/// #2320). Off by default: the keys move the text cursor and the wheel scrolls, as before.
|
||||
step_values: bool = false, PAGE_DESIGN => check_id("ui.step_values");
|
||||
/// New metric tapped holes drill the major diameter less the pitch (M8: 6.75 mm), as before
|
||||
/// the tap maker's drills; off by default: the tap maker's drill (M8: 6.8 mm). Saved designs
|
||||
/// keep the drill they stored either way.
|
||||
metric_tap_drill_major_less_pitch: bool = false, PAGE_DESIGN => check_id("prefs.metric_tap_drill_major_less_pitch");
|
||||
/// Reopen the saved designs that were open when the app was last closed (#5542).
|
||||
reopen_designs: bool = true, PAGE_GENERAL;
|
||||
/// Those designs' files, in tab order, as the last quit left them.
|
||||
@@ -1102,6 +1106,8 @@ fn defaults(app: &mut SolveApp, page: usize) {
|
||||
/// the interface scale.
|
||||
pub fn apply(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
apply_network(&app.preferences);
|
||||
// The hole command reads the metric tapped drill rule from the session.
|
||||
app.session.metric_tap_drill_major_less_pitch = app.preferences.metric_tap_drill_major_less_pitch;
|
||||
// The profiler records while the preference is on (switched when it changes; at start-up only
|
||||
// when on, so SOLIDCRAFT_PROFILE=1 is not undone by the default).
|
||||
let profile = app.preferences.profile_operations;
|
||||
@@ -2422,6 +2428,46 @@ mod tests {
|
||||
assert!(!solidcraft_engine::profile::enabled());
|
||||
}
|
||||
|
||||
/// New metric tapped holes drill the tap maker's drill (M8: 6.8 mm); the preference keeps
|
||||
/// the major diameter less the pitch they took before (6.75 mm). It reaches the hole command
|
||||
/// whether set through the control channel or the Preferences page, and a hole given its
|
||||
/// diameter keeps it either way.
|
||||
#[test]
|
||||
fn the_metric_tap_drill_preference_keeps_major_less_pitch() {
|
||||
use serde_json::json;
|
||||
let plate = || {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.run("solid.box", json!({"length":40,"width":20,"height":10,"body_name":"Plate"})).unwrap();
|
||||
h
|
||||
};
|
||||
let drilled = |h: &mut crate::scenario::Harness, params: serde_json::Value| {
|
||||
let id = h.app.run("solid.hole", params).unwrap()["feature"].as_u64().unwrap();
|
||||
match &h.app.session.doc.feature(id).unwrap().kind {
|
||||
solidcraft_engine::doc::FeatureKind::Hole { diameter, .. } => diameter.to_string(),
|
||||
k => panic!("{k:?}"),
|
||||
}
|
||||
};
|
||||
let m8 = json!({"position":[10,10,10],"thread":"M8"});
|
||||
let mut h = plate();
|
||||
h.frames(1);
|
||||
assert!(!h.app.preferences.metric_tap_drill_major_less_pitch, "off by default");
|
||||
assert_eq!(drilled(&mut h, m8.clone()), "6.8 mm");
|
||||
let mut h = plate();
|
||||
h.call("ui.prefs", json!({"metric_tap_drill_major_less_pitch": true}));
|
||||
h.frames(2);
|
||||
assert!(h.app.session.metric_tap_drill_major_less_pitch);
|
||||
assert_eq!(drilled(&mut h, m8.clone()), "6.75 mm");
|
||||
// Only metric changes: a Unified size keeps its tabulated drill.
|
||||
assert_eq!(drilled(&mut h, json!({"position":[25,10,10],"thread":"1/4-20 UNC"})), "5.1054 mm");
|
||||
let mut given = m8;
|
||||
given["position"] = json!([33, 10, 10]);
|
||||
given["diameter"] = json!("6.9 mm");
|
||||
assert_eq!(drilled(&mut h, given), "6.9 mm");
|
||||
// A design opened or started keeps the user's choice.
|
||||
h.app.run("file.new", json!({})).unwrap();
|
||||
assert!(h.app.session.metric_tap_drill_major_less_pitch);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_ghost_position_defaults_round_trips_and_reads_tolerantly() {
|
||||
use super::SpaceMouseGhost::{LeftViewport as Current, LeftWindow as Left, RightViewport as Right};
|
||||
|
||||
@@ -36,6 +36,10 @@ pub(super) fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let step = setting::step_values(app, ui).on_hover_text(tip);
|
||||
crate::scenario::publish_handle("prefs:step_values", step.rect.center());
|
||||
});
|
||||
ui_kit::settings_section(ui, "holes", app.tr("prefs.holes"), |ui| {
|
||||
let check = setting::metric_tap_drill_major_less_pitch(app, ui);
|
||||
crate::scenario::publish_handle("prefs:metric_tap_drill_major_less_pitch", check.rect.center());
|
||||
});
|
||||
ui_kit::settings_section(ui, "orientation", app.tr("prefs.orientation"), |ui| {
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for (id, label, y_up) in [("z", "Z up", false), ("y", "Y up", true)] {
|
||||
|
||||
@@ -27,6 +27,10 @@ cutting taps, printed p.704 (M, coarse) and pp.704 to 706 (MF, fine); M8 drills
|
||||
the pitch, to 0.001 mm (M8.5x1: 7.5 mm). This replaces the major-minus-pitch
|
||||
estimate every metric size took before (M8 6.75 mm). A saved design keeps the
|
||||
diameter it stored, so it builds as before; editing such a hole keeps its drill.
|
||||
The Design preference "Metric tapped holes drill the major diameter less the
|
||||
pitch" (`metric_tap_drill_major_less_pitch`, off by default) gives new metric
|
||||
holes that older drill again, in the Hole dialog and for the `solid.hole` command
|
||||
given no `diameter`.
|
||||
Custom metric dimensions remain editable, and Custom stays selected even when its
|
||||
current dimensions match a catalogue entry.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user