mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-04 19:15:57 +00:00
Tests: a face's flat area may differ from its surface's by the sails its boundary chords bridge
a_modelled_thread_in_a_bore_meshes_inside_its_block held each face's triangle area to within 5% of its surface's area. At the 0.1 mm tolerance, face 18 failed that: 1.777 mm^2 of triangles over 1.522 mm^2 of surface. Face 18 is the groove's crest band where it runs out through the block's end, a wedge at most 0.44 mm wide. Its boundary sides are the helix edge's chords, 1.37 mm long and sagging 0.062 mm (inside the tolerance). A flat triangle on such a chord bridges the sail between the chord and the edge instead of following it, and the band is no wider than the sag. The excess, 0.255 mm^2, is less than the sails along the face's boundary (sum of 2/3 L sag = 0.62 mm^2). Every triangle is within the tolerance: the chord check passes. No triangulation of the inside changes this; the boundary nodes are the shared edge nodes. The check now allows 5% plus the boundary's sails at the tolerance, 2/3 L tol per boundary side. A zigzag that multiplies a face's area, which this test exists to catch, is still far outside that. On the run-out root band the bound allows about 40% at 0.1 mm and 5% plus 4 mm^2 at 0.01 mm, against the former 6x. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -470,11 +470,16 @@ fn iso_groove(p: f64, d: f64, wall: f64, internal: bool) -> [(f64, f64); 4] {
|
||||
}
|
||||
|
||||
/// Each face's triangles, in 3D, cover as much area as its surface over the triangles' own
|
||||
/// parameters, within 5% (a coarse tolerance on a small curved face, a few percent): nothing
|
||||
/// zigzags across the face, which multiplies its area. The surface's area by the midpoint rule
|
||||
/// on sixteen pieces of each parameter triangle.
|
||||
/// parameters, within 5% (a coarse tolerance on a small curved face, a few percent) and the
|
||||
/// sails along its boundary: nothing zigzags across the face, which multiplies its area. The
|
||||
/// surface's area by the midpoint rule on sixteen pieces of each parameter triangle. A face's
|
||||
/// boundary sides are its edges' chords, each up to the chord tolerance `tol` off its edge, and
|
||||
/// between a chord of length L and its edge lies a sail of surface of at most 2/3 L tol (a
|
||||
/// parabolic segment) that flat triangles bridge rather than follow: a face no wider than its
|
||||
/// chords sag (the thread's crest band where it runs out through the block's end, 0.06 mm sag
|
||||
/// at 0.1) takes up to that much more or less area however it is triangulated inside.
|
||||
#[cfg(feature = "smlib")]
|
||||
fn assert_faces_lie_flat(name: &str, plan: &solidcraft_geom::tess::TessPlan) {
|
||||
fn assert_faces_lie_flat(name: &str, plan: &solidcraft_geom::tess::TessPlan, tol: f64) {
|
||||
for fp in &plan.faces {
|
||||
let Some(s) = fp.surface.and_then(|i| plan.surfaces.get(i as usize)) else { continue };
|
||||
let at = |i: u32| {
|
||||
@@ -504,7 +509,21 @@ fn assert_faces_lie_flat(name: &str, plan: &solidcraft_geom::tess::TessPlan) {
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!((flat - surface).abs() <= 0.05 * surface, "{name}: face {} has {flat} mm² of triangles over {surface} mm² of surface", fp.face);
|
||||
// The boundary: the sides of exactly one triangle.
|
||||
let mut sides: HashMap<(u32, u32), u32> = HashMap::new();
|
||||
for t in &fp.triangles {
|
||||
for k in 0..3 {
|
||||
let (a, b) = (t[k], t[(k + 1) % 3]);
|
||||
*sides.entry((a.min(b), a.max(b))).or_default() += 1;
|
||||
}
|
||||
}
|
||||
let boundary: f64 = sides.iter().filter(|(_, n)| **n == 1).map(|((a, b), _)| at(*a).dist(at(*b))).sum();
|
||||
let sails = 2.0 / 3.0 * boundary * tol;
|
||||
assert!(
|
||||
(flat - surface).abs() <= 0.05 * surface + sails,
|
||||
"{name}: face {} has {flat} mm² of triangles over {surface} mm² of surface (its boundary's sails {sails})",
|
||||
fp.face
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,7 +553,7 @@ fn a_modelled_thread_in_a_bore_meshes_inside_its_block() {
|
||||
let m = closed_display_mesh("thread in a bore", &body, tol);
|
||||
let chord = max_chord(&body, &m);
|
||||
assert!(chord <= tol * 1.05, "thread in a bore at {tol}: chord {chord}");
|
||||
assert_faces_lie_flat("thread in a bore", &ok(body.mesh_plan(&crate::MeshTolerance { chord: tol, angle: Some(crate::DISPLAY_ANGLE) })));
|
||||
assert_faces_lie_flat("thread in a bore", &ok(body.mesh_plan(&crate::MeshTolerance { chord: tol, angle: Some(crate::DISPLAY_ANGLE) })), tol);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,7 +579,7 @@ fn a_thread_root_band_running_out_through_an_end_face_lies_on_its_surface() {
|
||||
let chord = max_chord(&body, &m);
|
||||
assert!(chord <= tol * 1.05, "thread run out at {tol}: chord {chord}");
|
||||
assert!(m.triangles.len() < 60_000, "thread run out at {tol}: {} triangles", m.triangles.len());
|
||||
assert_faces_lie_flat("thread run out", &ok(body.mesh_plan(&crate::MeshTolerance { chord: tol, angle: Some(crate::DISPLAY_ANGLE) })));
|
||||
assert_faces_lie_flat("thread run out", &ok(body.mesh_plan(&crate::MeshTolerance { chord: tol, angle: Some(crate::DISPLAY_ANGLE) })), tol);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user