Tests: the fit and taper tests use geometry that still doesn't fit

Under the blend-overflow decision (237f0ae74) a convex round running over a face's far
edge (a cliff) is built and cut from the part, so the truck-era fixtures of the reduce
and taper fits (a 2 mm fin, a plate narrowing to 2 mm, a wedge, a 10 mm cube's edge at
R20) no longer refuse their radius and the fits have nothing to react to. Each test now
uses a case SMLib still refuses, and checks the cliff case builds:

- engine fillet_fit_reduce_keeps_the_radius_where_it_fits: a 14 x 6 x 10 post on a
  40 x 30 x 20 block, its four verticals at 3.5 (two rounds would cross on its 6 mm ends)
  reduced to 3 on 4 of 5 edges, the block's corner kept at 3.5: 24840 - (4 - pi) 90 -
  (1 - pi/4) 3.5^2 20. New fillet_fit_reduce_keeps_a_round_over_a_cliff: the fin's R4
  builds, nothing reduced, at the kernel's fillet_max_each answer.
- engine fillet_fit_taper_follows_the_room_along_the_edge, spline_fillet_takes_over_a_taper,
  kernel taper_follows_the_room_along_the_edge, ui-egui fillet_spline_handle_tests: the
  narrowing plate stands against a wall behind its slanted side (z -15 to 30), so its top
  and bottom end in concave edges: R3 is refused and tapers 3 -> 1.8 as before. Without
  the wall, R3 is cut over the 2 mm top's far edge and kept constant: at x the section
  loses (1 - pi/4) 9, or 3 w - (F(3) - F(3 - w)) where the top is w < 3 wide, 6522.7745566.
- The wedge closing to a point: on its own R16 is cut over its cliffs (the section
  integral, 4195.578821, SMLib within 4e-8) and kept; its room's taper is checked as a
  spline fillet. Against a wall (new fillet_fit_taper_closes_to_a_point) the taper must
  close to 0, which SMLib fails today (status 1001).
- kernel taper_mesh_tests: the collapsed taper is built from taper() as a spline fillet.
- ui-egui the_feature_menu_copies_a_warning_message: the 10 mm cube's four verticals at
  6 are reduced to 5 (a full round), which the warning says.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jyviko
2026-10-29 22:37:29 -04:00
committed by Irakli Kourtis
parent deaf8e6793
commit c47c1d26a6
5 changed files with 178 additions and 69 deletions
+99 -34
View File
@@ -4007,42 +4007,85 @@ fn fillet_fit_reduce_uses_the_largest_radius_that_fits() {
assert!(s.execute("solid.fillet", &json!({"edges": mids, "radius": 1, "fit": "taper", "type": "chord"})).is_err());
}
/// A plate 15 high whose top narrows from 20 to `tip` mm along 40 mm (a wedge at 0), and, with
/// `wall`, a wall standing behind its slanted side from z = -15 to 30, which the plate's top and
/// bottom end on in concave edges: a blend of its front edges can't run past them.
fn tapering_plate(tip: f64, wall: bool) -> Session {
let mut s = Session::default();
run(&mut s, "sketch.create", json!({"plane": "XY"}));
let pts = if tip > 0.0 { json!([[0, 0], [40, 0], [40, tip], [0, 20]]) } else { json!([[0, 0], [40, 0], [0, 20]]) };
run(&mut s, "sketch.line", json!({"points": pts, "closed": true}));
run(&mut s, "solid.extrude", json!({"distance": 15}));
if wall {
run(&mut s, "sketch.create", json!({"plane": {"origin": [0, 0, -15], "x_dir": [1, 0, 0], "y_dir": [0, 1, 0]}}));
run(&mut s, "sketch.line", json!({"points": [[40, tip], [0, 20], [0, 30], [40, 30]], "closed": true}));
run(&mut s, "solid.extrude", json!({"distance": 45, "operation": "join"}));
assert_eq!(s.model.state().bodies.len(), 1);
}
s
}
/// [`tapering_plate`]'s volume: the plate, and the wall's 20 (40 − tip) section along 45.
fn tapering_plate_volume(tip: f64, wall: bool) -> f64 {
(40.0 * (20.0 + tip) / 2.0) * 15.0 + if wall { 20.0 * (40.0 - tip) * 45.0 } else { 0.0 }
}
#[test]
fn fillet_fit_taper_follows_the_room_along_the_edge() {
// #293: the top front edge of a plate whose top narrows from 20 to `tip` mm along 40 mm.
// At a 2 mm tip it can't take 3 mm; taper keeps 3 where it fits and comes down to 0.9 of the
// 2 mm there. With an 8 mm tip 3 fits and stays constant; a plate closing to a point
// tapers to 0. The warning says what was done.
// #293: the top front edge of a plate whose top narrows from 20 to `tip` mm along 40 mm,
// against a wall. At a 2 mm tip it can't take 3 mm; taper keeps 3 where it fits and comes
// down to 0.9 of the 2 mm there. With an 8 mm tip 3 fits and stays constant. The warning
// says what was done.
let (k, edge) = (1.0 - PI / 4.0, json!([[20, 0, 15]]));
let plate = |tip: f64| {
let mut s = Session::default();
run(&mut s, "sketch.create", json!({"plane": "XY"}));
let pts = if tip > 0.0 { json!([[0, 0], [40, 0], [40, tip], [0, 20]]) } else { json!([[0, 0], [40, 0], [0, 20]]) };
run(&mut s, "sketch.line", json!({"points": pts, "closed": true}));
run(&mut s, "solid.extrude", json!({"distance": 15}));
s
};
let warning = |s: &Session, out: &Value| out["feature"].as_u64().and_then(|id| s.model.result(id)).and_then(|r| r.warning.clone());
let mut s = plate(2.0);
let mut s = tapering_plate(2.0, true);
assert!(s.execute("solid.fillet", &json!({"edges": edge, "radius": 3})).is_err());
let out = run(&mut s, "solid.fillet", json!({"edges": edge, "radius": 3, "fit": "taper"}));
let w = warning(&s, &out).unwrap_or_default();
assert!(w.contains("tapers to the room on 1 of 1 edges, down to 1.8 mm"), "{w}");
// Between the material a constant 1.8 and a constant 3 would remove.
let (v, full) = (volume(&mut s), 40.0 * 22.0 / 2.0 * 15.0);
let (v, full) = (volume(&mut s), tapering_plate_volume(2.0, true));
assert!(v < full - k * 1.8 * 1.8 * 40.0 && v > full - k * 9.0 * 40.0, "{v}");
let json = serde_json::to_string(&s.doc.features).unwrap();
assert_eq!(json.matches("\"fit\":\"taper\"").count(), 1, "{json}");
let mut s = plate(8.0);
let mut s = tapering_plate(8.0, true);
let out = run(&mut s, "solid.fillet", json!({"edges": edge, "radius": 3, "fit": "taper"}));
assert_eq!(warning(&s, &out), None);
assert!(rel(volume(&mut s), 40.0 * 28.0 / 2.0 * 15.0 - k * 9.0 * 40.0) < 2e-4);
assert!(rel(volume(&mut s), tapering_plate_volume(8.0, true) - k * 9.0 * 40.0) < 2e-4);
let mut s = plate(0.0);
let out = run(&mut s, "solid.fillet", json!({"edges": edge, "radius": 16, "fit": "taper"}));
let w = warning(&s, &out).unwrap_or_default();
// Without the wall the 2 mm top's far edge is a cliff: R3 runs over it and is cut from the
// plate as if the top went on (the blend-overflow decision, 237f0ae74), so it fits and the
// taper leaves it constant. At x the plate's section is [0, w] × [0, 15], w = 20 − 0.45 x:
// the round takes its whole (1 − π/4)·9 where w >= 3, and where w < 3 the part of the
// 3 × 3 corner outside the circle about (3, 3) with s <= w, 3 w − (F(3) − F(3 − w)),
// F(v) = (v √(9 − v²) + 9 asin(v/3)) / 2.
let mut s = tapering_plate(2.0, false);
let out = run(&mut s, "solid.fillet", json!({"edges": edge, "radius": 3, "fit": "taper"}));
assert_eq!(warning(&s, &out), None);
let f = |v: f64| 0.5 * (v * (9.0 - v * v).max(0.0).sqrt() + 9.0 * (v / 3.0).asin());
let n = 20000;
let taken: f64 = (0..n)
.map(|i| {
let w = 20.0 - 0.45 * 40.0 * (i as f64 + 0.5) / n as f64;
if w >= 3.0 { 9.0 * k } else { 3.0 * w - (f(3.0) - f(3.0 - w)) }
})
.sum::<f64>()
* 40.0
/ n as f64;
let want = tapering_plate_volume(2.0, false) - taken;
assert!(rel(volume(&mut s), want) < 1e-6, "{} vs {want}", volume(&mut s));
}
#[test]
fn fillet_fit_taper_closes_to_a_point() {
// #293: a wedge against a wall, its top closing to a point at x = 40: no constant radius
// fits there, and taper comes down to 0 at the point.
let mut s = tapering_plate(0.0, true);
assert!(s.execute("solid.fillet", &json!({"edges": [[20, 0, 15]], "radius": 16})).is_err());
let out = run(&mut s, "solid.fillet", json!({"edges": [[20, 0, 15]], "radius": 16, "fit": "taper"}));
let w = out["feature"].as_u64().and_then(|id| s.model.result(id)).and_then(|r| r.warning.clone()).unwrap_or_default();
assert!(w.contains("on 1 of 1 edges, down to 0 mm"), "{w}");
}
@@ -4050,10 +4093,7 @@ fn fillet_fit_taper_follows_the_room_along_the_edge() {
fn spline_fillet_takes_over_a_taper() {
// A tapered fillet keeps each edge's radius spline; given back as a `spline` fillet it makes
// the same part, and its control radii are then the user's to change.
let mut s = Session::default();
run(&mut s, "sketch.create", json!({"plane": "XY"}));
run(&mut s, "sketch.line", json!({"points": [[0, 0], [40, 0], [40, 2], [0, 20]], "closed": true}));
run(&mut s, "solid.extrude", json!({"distance": 15}));
let mut s = tapering_plate(2.0, true);
let edge = json!([[20, 0, 15]]);
let out = run(&mut s, "solid.fillet", json!({"edges": edge, "radius": 3, "fit": "taper"}));
let id = out["feature"].as_u64().unwrap();
@@ -4079,29 +4119,54 @@ fn spline_fillet_takes_over_a_taper() {
#[test]
fn fillet_fit_reduce_keeps_the_radius_where_it_fits() {
// A 2 mm fin on a block: its top edge can't take 4, a corner of the block far from it can.
// Only the fin's edge is reduced; the reply gives each edge's radius.
// A 14 x 6 x 10 post on a block: its four vertical edges can't take 3.5 (two rounds on its
// 6 mm ends would cross), a corner of the block far from it can. Only the post's edges are
// reduced, to 3 (a full round); the reply gives each edge's radius.
let mut s = Session::default();
run(&mut s, "solid.box", json!({"length": 40, "width": 30, "height": 20}));
run(&mut s, "solid.box", json!({"length": 2, "width": 30, "height": 10, "corner": [19, 0, 20], "operation": "join"}));
run(&mut s, "solid.box", json!({"length": 14, "width": 6, "height": 10, "corner": [2, 2, 20], "operation": "join"}));
assert_eq!(s.model.state().bodies.len(), 1);
let edges = json!([[21, 15, 30], [40, 0, 10]]);
let out = run(&mut s, "solid.fillet", json!({"edges": edges, "radius": 4, "fit": "reduce"}));
let fin = out["radius_applied"].as_f64().unwrap();
assert!(fin < 4.0, "{out}");
let post = [[2.0, 2.0, 25.0], [16.0, 2.0, 25.0], [2.0, 8.0, 25.0], [16.0, 8.0, 25.0]];
let edges = json!([post[0], post[1], post[2], post[3], [40, 0, 10]]);
let out = run(&mut s, "solid.fillet", json!({"edges": edges, "radius": 3.5, "fit": "reduce"}));
assert_eq!(out["radius_applied"].as_f64(), Some(3.0), "{out}");
let each: Vec<(Vec<f64>, f64)> = out["radii_applied"]
.as_array()
.unwrap()
.iter()
.map(|x| (x["edge"].as_array().unwrap().iter().filter_map(Value::as_f64).collect(), x["radius"].as_f64().unwrap()))
.collect();
assert_eq!(each, [(vec![21.0, 15.0, 30.0], fin), (vec![40.0, 0.0, 10.0], 4.0)], "{out}");
let removed = (1.0 - PI / 4.0) * (fin * fin * 30.0 + 16.0 * 20.0);
let want = 40.0 * 30.0 * 20.0 + 2.0 * 30.0 * 10.0 - removed;
let mut want_each: Vec<(Vec<f64>, f64)> = post.iter().map(|p| (p.to_vec(), 3.0)).collect();
want_each.push((vec![40.0, 0.0, 10.0], 3.5));
assert_eq!(each, want_each, "{out}");
// The post's full round takes (4 − π)·9 of its section along its 10 mm height, the block's
// corner (1 − π/4)·3.5² along 20.
let removed = (4.0 - PI) * 9.0 * 10.0 + (1.0 - PI / 4.0) * 3.5 * 3.5 * 20.0;
let want = 40.0 * 30.0 * 20.0 + 14.0 * 6.0 * 10.0 - removed;
assert!(rel(volume(&mut s), want) < 2e-4, "{} vs {want}", volume(&mut s));
let id = out["feature"].as_u64().unwrap();
let warning = s.model.result(id).and_then(|r| r.warning.clone()).unwrap_or_default();
assert!(warning.contains("on 1 of 2 edges"), "{warning}");
assert!(warning.contains("on 4 of 5 edges"), "{warning}");
}
/// A 2 mm fin's top edge at R4 runs over the fin's top, past its far edge (a cliff): it is cut
/// as if the top went on (the blend-overflow decision), so with `fit: reduce` nothing is reduced.
/// The fin's section loses the 4 x 4 square at its edge less the quarter disc about (17, 26)
/// inside the fin (x >= 19), as `fillet_max_each_reduces_only_where_the_radius_does_not_fit`
/// in the kernel finds it.
#[test]
fn fillet_fit_reduce_keeps_a_round_over_a_cliff() {
let mut s = Session::default();
run(&mut s, "solid.box", json!({"length": 40, "width": 30, "height": 20}));
run(&mut s, "solid.box", json!({"length": 2, "width": 30, "height": 10, "corner": [19, 0, 20], "operation": "join"}));
let out = run(&mut s, "solid.fillet", json!({"edges": [[21, 15, 30], [40, 0, 10]], "radius": 4, "fit": "reduce"}));
assert_eq!(out["radius_applied"].as_f64(), None, "{out}");
let id = out["feature"].as_u64().unwrap();
assert_eq!(s.model.result(id).and_then(|r| r.warning.clone()), None);
let disc = |a: f64| 0.5 * (a * (16.0 - a * a).max(0.0).sqrt() + 16.0 * (a / 4.0).asin());
let removed = (2.0 * 4.0 - (disc(4.0) - disc(2.0))) * 30.0 + (1.0 - PI / 4.0) * 16.0 * 20.0;
let want = 40.0 * 30.0 * 20.0 + 2.0 * 30.0 * 10.0 - removed;
assert!(rel(volume(&mut s), want) < 1e-6, "{} vs {want}", volume(&mut s));
}
#[test]
+55 -23
View File
@@ -642,11 +642,26 @@ mod tests {
assert!(fillet_variable(&b, at, start, 4.0, 1e-9).is_err());
}
/// A prism of the polygon `pts` (in xy) from z = 0 to `h`.
fn prism(pts: &[(f64, f64)], h: f64) -> Body {
/// A prism of the polygon `pts` (in xy) from z = `z0` to `z1`.
fn prism_between(pts: &[(f64, f64)], z0: f64, z1: f64) -> Body {
use solidcraft_geom::{Loop2, Plane, Region2, Vec2};
let outer = Loop2::polygon(&pts.iter().map(|(x, y)| Vec2::new(*x, *y)).collect::<Vec<_>>());
crate::extrude(&Plane::XY, &[Region2 { outer, holes: vec![] }], 0.0, h).unwrap().pop().unwrap()
crate::extrude(&Plane::XY, &[Region2 { outer, holes: vec![] }], z0, z1).unwrap().pop().unwrap()
}
/// A prism of the polygon `pts` (in xy) from z = 0 to `h`.
fn prism(pts: &[(f64, f64)], h: f64) -> Body {
prism_between(pts, 0.0, h)
}
/// A plate 15 high whose top narrows from 20 to 2 mm along 40 mm (the trapezoid (0, 0),
/// (40, 0), (40, 2), (0, 20)), against a wall standing behind its slanted side from z = -15
/// to 30: the plate's top and bottom end on the wall in concave edges, so a blend of its
/// front edges can't run past them (no cliff to cut over). 6600 + 20 · 38 · 45.
fn ledge() -> Body {
let plate = prism(&[(0.0, 0.0), (40.0, 0.0), (40.0, 2.0), (0.0, 20.0)], 15.0);
let wall = prism_between(&[(40.0, 2.0), (0.0, 20.0), (0.0, 30.0), (40.0, 30.0)], -15.0, 30.0);
crate::boolean(&plate, &wall, crate::BoolOp::Union).unwrap().unwrap()
}
/// The material a right-angled blend with the radius `law` removes along an edge of length L:
@@ -663,17 +678,48 @@ mod tests {
}
/// A taper on the top front edge of a wedge, whose top face narrows to a point, and of a
/// trapezoid, whose top face narrows to 2 mm: the radius asked for where it fits, below
/// 0.9 of the room everywhere, down to zero at the wedge's tip and to 1.8 mm at the
/// trapezoid's narrow end; the volume integrates the spline.
/// plate against a wall, whose top face narrows to 2 mm: the radius asked for where it fits,
/// below 0.9 of the room everywhere, down to zero at the wedge's tip and to 1.8 mm at the
/// plate's narrow end; the volume integrates the spline. On the free-standing wedge R16 runs
/// over the top's far edge and the front's bottom edge (cliffs), so it is built as if those
/// faces went on and cut from the wedge (the blend-overflow decision, 237f0ae74), and a
/// tapered fillet keeps it; against the wall the plate's R3 has no room at the narrow end
/// and tapers.
#[test]
fn taper_follows_the_room_along_the_edge() {
let (at, start) = (Vec3::new(20.0, 0.0, 15.0), Vec3::new(0.0, 0.0, 15.0));
let plate = ledge();
let full = 6600.0 + 20.0 * 38.0 * 45.0;
assert!(matches!(crate::fillet(&plate, &[at], 3.0), Err(KernelError::TooLarge(_))));
let (f, tapers) = fillet_tapered(&plate, &[at], 3.0).unwrap();
let t = tapers.into_iter().flatten().next().unwrap();
let law = if t.start.dist(start) < 1e-9 { t.spline } else { t.spline.reversed() };
assert!((law.eval(0.0) - 3.0).abs() < 1e-9 && (law.eval(1.0) - 1.8).abs() < 1e-9);
let v = fine_volume(&f);
assert!(rel(v, full - removed(&law, 40.0)) < 1e-4, "{v} vs {}", full - removed(&law, 40.0));
// A radius that fits stays constant.
let (g, tapers) = fillet_tapered(&plate, &[at], 1.0).unwrap();
assert_eq!(tapers, vec![None]);
assert!(rel(measure(&g).unwrap().volume, measure(&crate::fillet(&plate, &[at], 1.0).unwrap()).unwrap().volume) < 1e-12);
let wedge = prism(&[(0.0, 0.0), (40.0, 0.0), (0.0, 20.0)], 15.0);
assert!(matches!(crate::fillet(&wedge, &[at], 16.0), Err(KernelError::TooLarge(_))));
// At x the section is the rectangle [0, w] × [0, 15], w = 20 (1 − x/40), less the part
// of the 16 × 16 square at the edge outside the circle about (16, 16): integrated along x.
let cut = |w: f64| {
let (r, s, t) = (16.0f64, 16.0f64.min(w), 15.0);
let f = |v: f64| 0.5 * (v * (r * r - v * v).max(0.0).sqrt() + r * r * (v / r).clamp(-1.0, 1.0).asin());
let s1 = (r - (r * r - (r - t) * (r - t)).sqrt()).min(s);
t * s1 + r * (s - s1) - (f(r - s1) - f(r - s))
};
let n = 20000;
let removed16: f64 = (0..n).map(|i| cut(20.0 * (1.0 - (i as f64 + 0.5) / n as f64))).sum::<f64>() * 40.0 / n as f64;
let (f, tapers) = fillet_tapered(&wedge, &[at], 16.0).unwrap();
assert_eq!(tapers, vec![None]);
let v = measure(&f).unwrap().volume;
assert!(rel(v, 6000.0 - removed16) < 1e-6, "{v} vs {}", 6000.0 - removed16);
// The taper the room gives: the top face is 20 (1 − u) wide, the front 15 high.
let t = taper(&wedge, at, 16.0).unwrap();
let law = if t.start.dist(start) < 1e-9 { t.spline.clone() } else { t.spline.reversed() };
// The room: the top face is 20 (1 − u) wide, the front 15 high.
for i in 0..=100 {
let u = i as f64 / 100.0;
let r = law.eval(u);
@@ -681,25 +727,11 @@ mod tests {
}
assert_eq!(law.eval(1.0), 0.0);
assert!((law.eval(0.0) - 13.5).abs() < 1e-9);
let (f, tapers) = fillet_tapered(&wedge, &[at], 16.0).unwrap();
assert_eq!(tapers, vec![Some(t)]);
let f = fillet_variable_spline(&wedge, at, t.start, &t.spline).unwrap();
let v = fine_volume(&f);
assert!(rel(v, 6000.0 - removed(&law, 40.0)) < 1e-4, "{v} vs {}", 6000.0 - removed(&law, 40.0));
assert_eq!(f.unmeshed_faces(), 0);
assert!(faces_and_vertices(&f).0.contains(&3));
let trap = prism(&[(0.0, 0.0), (40.0, 0.0), (40.0, 2.0), (0.0, 20.0)], 15.0);
assert!(matches!(crate::fillet(&trap, &[at], 3.0), Err(KernelError::TooLarge(_))));
let (f, tapers) = fillet_tapered(&trap, &[at], 3.0).unwrap();
let t = tapers.into_iter().flatten().next().unwrap();
let law = if t.start.dist(start) < 1e-9 { t.spline } else { t.spline.reversed() };
assert!((law.eval(0.0) - 3.0).abs() < 1e-9 && (law.eval(1.0) - 1.8).abs() < 1e-9);
let v = fine_volume(&f);
assert!(rel(v, 6600.0 - removed(&law, 40.0)) < 1e-4, "{v} vs {}", 6600.0 - removed(&law, 40.0));
// A radius that fits stays constant.
let (g, tapers) = fillet_tapered(&trap, &[at], 1.0).unwrap();
assert_eq!(tapers, vec![None]);
assert!(rel(measure(&g).unwrap().volume, measure(&crate::fillet(&trap, &[at], 1.0).unwrap()).unwrap().volume) < 1e-12);
}
/// A radius spline given directly: a cubic with its control radii pulling the radius up in
+5 -3
View File
@@ -5,9 +5,11 @@ fn wedge() -> (Body, RadiusSpline) {
let outer = Loop2::polygon(&[Vec2::new(0., 0.), Vec2::new(40., 0.), Vec2::new(0., 20.)]);
let body = crate::extrude(&Plane::XY, &[Region2 { outer, holes: vec![] }], 0., 15.).unwrap().pop().unwrap();
let at = Vec3::new(20., 0., 15.);
let (filleted, tapers) = fillet_tapered(&body, &[at], 16.).unwrap();
let taper = tapers.into_iter().flatten().next().unwrap();
(filleted, taper.spline)
// The room's taper, closing to zero at the wedge's tip, given as a spline fillet: R16 itself
// is built over the wedge's cliffs (the blend-overflow decision), so a tapered fillet no
// longer tapers it.
let t = taper(&body, at, 16.).unwrap();
(fillet_variable_spline(&body, at, t.start, &t.spline).unwrap(), t.spline)
}
fn expected(law: &RadiusSpline) -> f64 {
@@ -2,13 +2,22 @@ use crate::scenario::Harness;
use serde_json::{Value, json};
use solidcraft_engine::doc::{FeatureKind, FilletStyle, kernel};
/// A plate whose top narrows from 20 to 2 mm along 40 mm, its top front edge rounded at 3 mm
/// with Taper to Fit.
fn fixture() -> Harness {
let mut h = Harness::new();
/// A plate whose top narrows from 20 to 2 mm along 40 mm, against a wall behind its slanted
/// side from z = -15 to 30 (so a blend of its front edges has no cliff to run over and is cut
/// short by the wall: the blend-overflow decision, 237f0ae74).
fn plate(h: &mut Harness) {
h.app.run("sketch.create", json!({"plane": "XY"})).unwrap();
h.app.run("sketch.line", json!({"points": [[0, 0], [40, 0], [40, 2], [0, 20]], "closed": true})).unwrap();
h.app.run("solid.extrude", json!({"distance": 15, "body_name": "Plate"})).unwrap();
h.app.run("sketch.create", json!({"plane": {"origin": [0, 0, -15], "x_dir": [1, 0, 0], "y_dir": [0, 1, 0]}})).unwrap();
h.app.run("sketch.line", json!({"points": [[40, 2], [0, 20], [0, 30], [40, 30]], "closed": true})).unwrap();
h.app.run("solid.extrude", json!({"distance": 45, "operation": "join"})).unwrap();
}
/// [`plate`], its top front edge rounded at 3 mm with Taper to Fit.
fn fixture() -> Harness {
let mut h = Harness::new();
plate(&mut h);
h.app.run("solid.fillet", json!({"edges": [[20, 0, 15]], "radius": 3, "fit": "taper", "name": "Round"})).unwrap();
h.frames(4);
h
@@ -174,9 +183,7 @@ fn double_clicks_add_and_remove_control_radii() {
#[test]
fn a_taper_over_several_edges_keeps_the_radius_arrow() {
let mut h = Harness::new();
h.app.run("sketch.create", json!({"plane": "XY"})).unwrap();
h.app.run("sketch.line", json!({"points": [[0, 0], [40, 0], [40, 2], [0, 20]], "closed": true})).unwrap();
h.app.run("solid.extrude", json!({"distance": 15, "body_name": "Plate"})).unwrap();
plate(&mut h);
h.app.run("solid.fillet", json!({"edges": [[20, 0, 15], [20, 0, 0]], "radius": 3, "fit": "taper", "name": "Round"})).unwrap();
h.frames(4);
open(&mut h);
+5 -2
View File
@@ -1154,8 +1154,11 @@ mod tests {
fn the_feature_menu_copies_a_warning_message() {
for left in [false, true] {
let mut h = two_boxes(left);
// Too large for the box: reduced to what fits, which the feature's warning says.
h.app.run("solid.fillet", json!({"edges": [[5, 0, 10]], "radius": 20, "fit": "reduce", "body": "One"})).unwrap();
// Too large for the box: R6 on its four vertical edges would cross on the 10 mm faces,
// so it is reduced to what fits (a full round, 5), which the feature's warning says.
h.app
.run("solid.fillet", json!({"edges": [[0, 0, 5], [10, 0, 5], [10, 10, 5], [0, 10, 5]], "radius": 6, "fit": "reduce", "body": "One"}))
.unwrap();
h.frames(4);
let id = h.app.session.doc.features.last().map(|f| f.id).unwrap();
let name = h.app.session.doc.features.last().map(|f| f.name.clone()).unwrap();