Tapered join on a face: sew a whole face, keep the run-on inside the body (#1534) (#1763)

Thanks @HiroKenEz.
This commit is contained in:
HiroKenEz authored and GitHub committed 2026-10-16 13:26:15 -04:00
1 parent 31b2ddc0cf
commit 19be7d27e7
2 files changed
+113 -2

No files matched your search

@@ -782,6 +782,60 @@ fn a_tapered_text_joins_a_block_on_its_face_or_hanging_over_its_edge() {
}
}
/// A tapered foot extruded 3 mm down from a part's bottom face and joined (#1534). Run on into
/// the part with their slope, sides that narrow the foot away from the face widened it past the
/// face: a box got a lip round its walls (0.16 mm wide, and nothing said), a cylinder's join found
/// no result, and sides that widen the foot met the face's edge there. A foot on the whole face is
/// sewn on along it; one drawn just inside the face's edge starts on it. Either way: the part plus
/// the frustum, and nothing beside the part's walls.
#[test]
fn a_tapered_foot_under_a_part_joins_as_drawn() {
// (round, the foot's half width: the whole face, or just inside its edge)
for (round, half) in [(false, 15.0), (true, 15.0), (false, 14.9)] {
for taper in [-20.0f64, 20.0] {
let t = taper.to_radians().tan();
let mut s = Session::default();
run(&mut s, "sketch.create", json!({"plane":"XY"}));
if round {
run(&mut s, "sketch.circle.center", json!({"center":[0,0],"radius":15}));
} else {
run(&mut s, "sketch.rectangle.two_point", json!({"p0":[-15,-15],"p1":[15,15]}));
}
run(&mut s, "sketch.finish", json!({}));
run(&mut s, "solid.extrude", json!({"distance":10,"body_name":"B"}));
let taper_deg = format!("{taper} deg");
if half < 15.0 {
run(&mut s, "sketch.create", json!({"plane":"XY"}));
run(&mut s, "sketch.rectangle.two_point", json!({"p0":[-half,-half],"p1":[half,half]}));
run(&mut s, "sketch.finish", json!({}));
run(&mut s, "solid.extrude", json!({"distance":3,"direction":"negative","operation":"join","taper":taper_deg}));
} else {
let foot = json!({"face":[5,1,0],"face_body":"B","distance":3,"direction":"positive","operation":"join","taper":taper_deg});
run(&mut s, "solid.extrude", foot);
}
let (part, frustum) = if round {
let r = 15.0 + 3.0 * t;
(std::f64::consts::PI * 225.0 * 10.0, std::f64::consts::PI * (225.0 + 15.0 * r + r * r))
} else {
let side = 2.0 * half + 6.0 * t;
(9000.0, 4.0 * half * half + 2.0 * half * side + side * side)
};
let v = volume(&mut s);
// The cylinder is measured on its mesh; a foot that starts on the face stands straight
// for a hair first.
let tol = if round || half < 15.0 { 2e-4 } else { 1e-9 } * (part + frustum);
assert!((v - (part + frustum)).abs() < tol, "round {round}, half {half}, taper {taper}: {v} for {}", part + frustum);
assert_eq!(s.world_state().bodies.len(), 1);
let state = s.model.state();
let mesh = state.body("B").unwrap().mesh();
// Beside the part, above the foot's start: nothing.
let out = |x: f64, y: f64| if round { x.hypot(y) } else { x.abs().max(y.abs()) } > 15.0 + 1e-6;
let beside = mesh.positions.iter().filter(|p| p.z > 1e-6 && out(p.x, p.y)).count();
assert_eq!(beside, 0, "round {round}, half {half}, taper {taper}: {:?}", mesh.bounds());
}
}
}
/// A rounded part as drawn in the app: a rectangle and a quarter circle tangent to its top
/// line, extruded together. The arc (about (62.17, -21), radius 21) ends square at the front
/// face and in a knife-edge tip where it runs into the back face.