Files
OneCAD/crates/engine/src/tests/flush_join_loft.rs
T

100 lines
4.8 KiB
Rust

//! A flange joined flush with the end of a bent, tapered lofted half tube (#1391).
//!
//! The tube is a half annulus with a 6 mm wall, lofted through three sections along a bend of radius 300 mm and 20 degrees, its radius
//! growing from 60 to 75 mm. The flange is a thicker half annulus (3 mm into the wall, 25 mm beyond it), 8 mm thick, extruded back from
//! the tube's end plane and joined. Its front face lies in that plane, coplanar with the tube's end face, and the neighbours of that
//! face are faces of the loft, which curve and lean: the faces cannot be pushed apart, and the join failed with "Union: no result".
//! (With a 40 degree bend the retry with the tool displaced joins it; with this bend that retry fails too.)
use std::f64::consts::PI;
use super::*;
/// Radius of the bend of the tube's axis, and the turn of it (radians).
const BEND: f64 = 300.0;
const TURN: f64 = 20.0 * PI / 180.0;
/// Inner radius of the tube at its start and at its end, and the wall.
const R0: f64 = 60.0;
const R1: f64 = 75.0;
const WALL: f64 = 6.0;
/// How far the flange reaches into the tube's wall, how far beyond its outer face, and how thick it is.
const LAND_IN: f64 = 3.0;
const LAND_OUT: f64 = 25.0;
const THICK: f64 = 8.0;
/// Sketch coordinates are given to nine decimals, as a design's own would be.
fn r9(x: f64) -> f64 {
(x * 1e9).round() / 1e9
}
/// The plane of the section at the bend angle `a`, moved `over` along the axis (the sketch's x is up, its y across the tube).
fn plane(a: f64, over: f64) -> Value {
let (tx, ty) = (a.cos(), a.sin());
json!({
"origin": [r9(BEND * a.sin() + over * tx), r9(BEND * (1.0 - a.cos()) + over * ty), 0],
"x_dir": [0, 0, 1],
"y_dir": [r9(ty), r9(-tx), 0],
})
}
/// A half annulus (the half above z = 0) on `plane`, between the radii `r_in` and `r_out`.
fn half_annulus(s: &mut Session, name: &str, plane: Value, r_in: f64, r_out: f64) {
let (r_in, r_out) = (r9(r_in), r9(r_out));
run(s, "sketch.create", json!({"plane": plane, "name": name}));
run(s, "sketch.arc.three_point", json!({"start": [0, -r_out], "end": [0, r_out], "through": [r_out, 0]}));
run(s, "sketch.line", json!({"points": [[0, r_out], [0, r_in]]}));
run(s, "sketch.arc.three_point", json!({"start": [0, r_in], "end": [0, -r_in], "through": [r_in, 0]}));
run(s, "sketch.line", json!({"points": [[0, -r_in], [0, -r_out]]}));
run(s, "sketch.finish", json!({}));
}
/// The tube: a loft through three sections.
fn tube(s: &mut Session) {
for i in 0..3 {
let t = i as f64 / 2.0;
let r_in = R0 + (R1 - R0) * t;
half_annulus(s, &format!("S{i}"), plane(TURN * t, 0.0), r_in, r_in + WALL);
}
let sections: Vec<Value> = (0..3).map(|i| json!({"sketch": format!("S{i}")})).collect();
run(s, "solid.loft", json!({"sections": sections, "operation": "new", "name": "TubeLoft", "body_name": "Tube"}));
}
/// The flange, joined to the tube. Its front face is `over` mm past the tube's end plane (0: in it).
fn flange(s: &mut Session, over: f64) {
half_annulus(s, "Flange", plane(TURN, over), R1 + LAND_IN, R1 + WALL + LAND_OUT);
let join = json!({"sketch": "Flange", "distance": THICK + over, "direction": "negative", "operation": "join", "targets": ["Tube"], "name": "JoinFlange"});
run(s, "solid.extrude", join);
}
#[test]
fn a_flange_flush_with_the_end_of_a_bent_tapered_tube_joins() {
// The flange's face, the half annulus, has this area.
let (r_in, r_out) = (R1 + LAND_IN, R1 + WALL + LAND_OUT);
let footprint = PI / 2.0 * (r_out * r_out - r_in * r_in);
// The reference: with the flange 2 mm past the end plane nothing is coplanar with the tube's end face (that join is made by another
// route), and what it adds beyond that plane is the footprint, 2 mm deep.
let mut s = Session::default();
tube(&mut s);
flange(&mut s, 2.0);
let reference = volume(&mut s) - 2.0 * footprint;
let mut s = Session::default();
tube(&mut s);
flange(&mut s, 0.0);
let v = volume(&mut s);
assert!(rel(v, reference) < 3e-4, "{v} against {reference}");
// One valid body in one piece, and nothing of it beyond the end plane (to a micrometre: the curved faces end on it within the
// tolerance of their edges): the face the flange and the tube share is one flat face.
let state = s.model.state();
assert_eq!(state.bodies.len(), 1);
let body = &state.bodies[0].body;
assert!(body.validity().is_empty(), "{:?}", body.validity());
assert_eq!(body.lumps().unwrap().len(), 1);
let (origin, normal) = (Vec3::new(BEND * TURN.sin(), BEND * (1.0 - TURN.cos()), 0.0), Vec3::new(TURN.cos(), TURN.sin(), 0.0));
let mesh = body.tessellate(0.05).unwrap();
let beyond = mesh.positions.iter().map(|p| (*p - origin).dot(normal)).fold(f64::MIN, f64::max);
assert!(beyond < 1e-3, "the join reaches {beyond} mm past the end plane");
}