Modelled threads (Thread with modeled: true): the ISO 68-1 profile cut along a helix, M3 to M20, on shafts and in bores; running out past a free end of the face, stopping square at a set length. The groove is a watertight screw-sweep mesh (kernel::sweep_helix_mesh) cut by kernel::faceted_boolean (BSP on the faceted body, tolerance ladder checked by A−B + A∩B = A); the polygon rebuild welds vertices across grid cells, splits pinched loops, finds T-junctions through a grid, and takes the group's plane for a loop a hair off flat
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AS5Tn3xieewUSg3uxrMVdL
This commit is contained in:
1 parent
1f619e49cc
commit
141a5fea8a
11 files changed
+421
-34
No files matched your search
@@ -0,0 +1,69 @@
|
||||
//! Modelled threads: the ISO metric profile cut along a helix, M3 to M20, outside and inside.
|
||||
|
||||
use std::f64::consts::PI;
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::Session;
|
||||
|
||||
fn run(s: &mut Session, id: &str, p: Value) -> Value {
|
||||
match s.execute(id, &p) {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("{id} {p}: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Volume from the body's mesh: exact for the faceted threaded body, and quicker than a full
|
||||
/// measurement of its thousands of faces.
|
||||
fn volume(s: &mut Session, b: &str) -> f64 {
|
||||
let st = s.model.state();
|
||||
st.body(b).map(|mb| mb.body.tessellate(1e-3).unwrap().measure().volume).unwrap_or(0.0)
|
||||
}
|
||||
|
||||
const SIZES: [(f64, f64); 9] = [(3.0, 0.5), (4.0, 0.7), (5.0, 0.8), (6.0, 1.0), (8.0, 1.25), (10.0, 1.5), (12.0, 1.75), (16.0, 2.0), (20.0, 2.5)];
|
||||
|
||||
/// ∫ 2π r · width(r) dr over [a, b]: the volume one turn of the groove takes out (a screw
|
||||
/// motion of a section in an axial plane sweeps 2π∫r dA per turn).
|
||||
fn per_turn(a: f64, b: f64, width: impl Fn(f64) -> f64) -> f64 {
|
||||
let n = 400;
|
||||
(0..n).map(|i| a + (b - a) * (i as f64 + 0.5) / n as f64).map(|r| 2.0 * PI * r * width(r) * (b - a) / n as f64).sum()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modelled_external_threads_m3_to_m20() {
|
||||
let h = |p: f64| p * 3f64.sqrt() / 2.0;
|
||||
let slope = (PI / 6.0).tan();
|
||||
for (d, p) in SIZES {
|
||||
let mut s = Session::default();
|
||||
let len = 4.0 * p;
|
||||
run(&mut s, "solid.cylinder", json!({"radius": d / 2.0, "height": len + 2.0, "body_name": "Shaft"}));
|
||||
let before = volume(&mut s, "Shaft");
|
||||
let th = run(&mut s, "solid.thread", json!({"face": [d / 2.0, 0, 1], "length": len, "modeled": true}));
|
||||
assert!(th["thread"].is_null(), "a modelled thread is real geometry, not an annotation: {th}");
|
||||
let r_root = d / 2.0 - 5.0 * h(p) / 8.0;
|
||||
let want = per_turn(r_root, d / 2.0, |r| p / 4.0 + 2.0 * (r - r_root) * slope) * len / p;
|
||||
let got = before - volume(&mut s, "Shaft");
|
||||
assert!((got - want).abs() < want * 0.03, "M{d}: removed {got}, ISO profile {want}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modelled_internal_threads_m3_to_m20() {
|
||||
let h = |p: f64| p * 3f64.sqrt() / 2.0;
|
||||
let slope = (PI / 6.0).tan();
|
||||
for (d, p) in SIZES {
|
||||
let mut s = Session::default();
|
||||
let len = 4.0 * p;
|
||||
let d1 = d - 2.0 * 5.0 * h(p) / 8.0;
|
||||
run(&mut s, "solid.box", json!({"corner": [-d, -d, 0], "length": 2.0 * d, "width": 2.0 * d, "height": len, "body_name": "Nut"}));
|
||||
run(&mut s, "solid.cylinder", json!({"base": [0, 0, -1], "radius": d1 / 2.0, "height": len + 2.0, "body_name": "Bore"}));
|
||||
run(&mut s, "solid.combine", json!({"target": "Nut", "tools": ["Bore"], "operation": "cut"}));
|
||||
let before = volume(&mut s, "Nut");
|
||||
let th = run(&mut s, "solid.thread", json!({"face": [d1 / 2.0, 0, len / 2.0], "modeled": true}));
|
||||
assert!(th["thread"].is_null(), "{th}");
|
||||
// Bolt-shaped space: P/8 flat at the major diameter, widening towards the bore.
|
||||
let want = per_turn(d1 / 2.0, d / 2.0, |r| p / 8.0 + 2.0 * (d / 2.0 - r) * slope) * len / p;
|
||||
let got = before - volume(&mut s, "Nut");
|
||||
assert!((got - want).abs() < want * 0.03, "M{d}: removed {got}, ISO profile {want}");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user