Tests: edges are read on their NURBS forms, a chamfer's chain is its caller's, the cap test's hatch

- An SMLib circle edge's `interval` is on its NURBS form (`Edge::parameters`), and an analytic
  face's `domain` on the form's (u, v). edges_are_on_smlibs_curves evaluated `curve` at the
  form's interval and took the domain for angles; it checks the side's whole turn and height
  on the form and a rim's turn by `curve_interval`. The tangent hole test's edge middles were
  read the same way (the copy seemed to join faces on opposite sides of the line).
- kernel::chamfer blends the edges it is given; a feature's pick is extended by
  kernel::tangent_chain first (doc eval). The chamfer tests do the same:
  one_chamfer_pick_follows_the_tangent_chain_at_any_part_size removes its 54.66 mm³ again.
- Since the section hatch per part (#8529) a cut is tinted with the hatch colour its body's
  name picks (Body1: steel); the GPU cap test turns the part hatch off, as the CPU one does
  (2885f101b on devgreen-engine-builds).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
jyviko
2026-10-28 22:44:48 -04:00
committed by Irakli Kourtis
parent 2523d13cf0
commit b71e5ae6f4
3 changed files with 22 additions and 7 deletions
+2
View File
@@ -160,6 +160,8 @@ fn pictures_show_the_section_and_its_cap() {
let mut cam = view::home_camera(&s).unwrap();
cam.fov = fov;
let mut sc = view::scene(&s, &cam).unwrap();
// The cap in the cap colour: a part's hatch colour is the one its name picks (#8529).
sc.part_hatch = None;
// A marker inside the body, behind the cut face.
let red = Rgb(255, 0, 0);
sc.lines.push(SceneLine {
+5 -3
View File
@@ -4,7 +4,7 @@
//! against the round wall instead of extending that short seam outside the solid.
use solidcraft_geom::{Loop2, Plane, Region2, Seg2, Vec2, Vec3};
use solidcraft_kernel::{Body, BoolOp, boolean, chamfer, cylinder, extrude};
use solidcraft_kernel::{Body, BoolOp, boolean, chamfer, cylinder, extrude, tangent_chain};
use std::f64::consts::PI;
fn holder() -> Body {
@@ -54,7 +54,9 @@ fn expected_removed(distance: f64) -> f64 {
#[test]
fn chamfer_the_top_lip_at_a_tangent_wall_junction() {
let body = holder();
let made = chamfer(&body, &[Vec3::new(10.0, 10.0, 60.0)], 2.0).unwrap();
// The pick's tangent chain, as a chamfer feature selects it (the kernel blends the edges it
// is given).
let made = chamfer(&body, &tangent_chain(&body, &[Vec3::new(10.0, 10.0, 60.0)]).unwrap(), 2.0).unwrap();
assert!(made.validity().is_empty(), "{:?}", made.validity());
assert_eq!(made.crossed_face_loops().unwrap(), 0);
let history = made.history().expect("the chamfer retains exact face provenance");
@@ -115,7 +117,7 @@ fn plate_with_stadium(plate: f64) -> Body {
fn one_chamfer_pick_follows_the_tangent_chain_at_any_part_size() {
for plate in [140.0, 145.0, 250.0] {
let body = plate_with_stadium(plate);
let made = chamfer(&body, &[Vec3::new(0.0, 5.0, 20.0)], 1.0).unwrap();
let made = chamfer(&body, &tangent_chain(&body, &[Vec3::new(0.0, 5.0, 20.0)]).unwrap(), 1.0).unwrap();
assert!(made.validity().is_empty(), "{:?}", made.validity());
// Two straight edges (c^2/2 * 40 each) and both end arcs (Pappus, c^2/2 * 2 pi (R - c/3)).
let expected = 40.0 + 0.5 * 2.0 * PI * (5.0 - 1.0 / 3.0);
+15 -4
View File
@@ -82,10 +82,19 @@ mod native_tests {
fn edges_are_on_smlibs_curves() {
let cyl = Brep::make_cylinder([0.0; 3], 5.0, 10.0).unwrap().to_native().unwrap();
let side = cyl.faces.iter().find(|f| matches!(f.surface, b::Surface::Cylinder { .. })).unwrap();
assert!((side.domain[0][1] - side.domain[0][0] - TAU).abs() < 1e-12);
assert_eq!(side.domain[1], [0.0, 10.0]);
// The side is on SMLib's NURBS form of the cylinder (its domain is the form's): the whole
// turn, so its domain's two u ends meet, from the base to the top.
let form = b::eval::SurfaceEval::new(side.parameters()).unwrap();
let [[u0, u1], [v0, v1]] = side.domain;
for v in [v0, v1] {
let (a, z) = (form.point([u0, v]).unwrap(), form.point([u1, v]).unwrap());
assert!(a.dist(z) < 1e-9, "{a:?} {z:?}");
}
let heights = [form.point([u0, v0]).unwrap().z, form.point([u0, v1]).unwrap().z];
assert!(heights.iter().any(|h| h.abs() < 1e-9) && heights.iter().any(|h| (h - 10.0).abs() < 1e-9), "{heights:?}");
for e in &cyl.edges {
let eval = b::eval::CurveEval::new(&e.curve).unwrap();
// Evaluated on the curve its interval is on (a circle's NURBS form).
let eval = b::eval::CurveEval::new(e.parameters()).unwrap();
let [a, z] = e.interval;
// Ends on the edge's vertices.
for (t, v) in [(a, e.vertices[0]), (z, e.vertices[1])] {
@@ -94,6 +103,7 @@ mod native_tests {
assert!((p - Vec3::new(q[0], q[1], q[2])).len() < 1e-9, "{:?} at {t}", e.curve);
}
if let b::Curve::Circle { radius, .. } = e.curve {
let [a, z] = e.curve_interval().unwrap();
assert!((radius - 5.0).abs() < 1e-12 && (z - a - TAU).abs() < 1e-9);
}
}
@@ -126,7 +136,8 @@ mod native_tests {
p[0].abs() < 1e-9 && (p[1] - 14.5).abs() < 1e-9
})
};
let middle = |e: &b::Edge| b::eval::CurveEval::new(&e.curve).unwrap().point(0.5 * (e.interval[0] + e.interval[1])).unwrap();
// On the curve the interval is on (a circle's NURBS form).
let middle = |e: &b::Edge| b::eval::CurveEval::new(e.parameters()).unwrap().point(0.5 * (e.interval[0] + e.interval[1])).unwrap();
let copies: Vec<&b::Edge> = record.edges.iter().filter(|e| on_line(e)).collect();
assert_eq!(copies.len(), 2);
for copy in copies {