Revert #1682 (three-axis milling) (#1715)

Thanks @bherbruck.
This commit is contained in:
bherbruck authored and GitHub committed 2026-10-16 09:02:19 -04:00
1 parent 1ded8fdd27
commit 2c020dbcbc
85 files changed
+43 -14487

No files matched your search

-6
View File
@@ -1612,9 +1612,3 @@ mod display_mesh_tests;
#[cfg(test)]
#[path = "regularization_protection_tests.rs"]
mod regularization_protection_tests;
#[path = "body_verification.rs"]
mod verification;
#[cfg(test)]
#[path = "body_verification_tests.rs"]
mod verification_tests;
-58
View File
@@ -1,58 +0,0 @@
//! Manufacturing mesh path: no display LOD, tolerance floor or missing-face fallback.
use super::*;
impl Body {
/// Mesh at a fixed physical chord tolerance. Refinement/output exhaustion is an error.
pub fn tessellate_for_verification(&self, tol: f64, triangle_limit: usize) -> Result<Mesh> {
if !tol.is_finite() || tol <= 0. || triangle_limit == 0 || triangle_limit > 2_000_000 {
return Err(KernelError::Invalid("verification tessellation needs positive finite tolerance and 1–2000000 triangles".into()));
}
if let Some(mesh) = &self.mesh {
if mesh.triangles.len() > triangle_limit {
return Err(KernelError::Failed("verification tessellation exceeds triangle budget".into()));
}
return Ok(mesh.to_mesh());
}
guard("verification tessellation", || {
let mut out = Mesh::default();
let mut face_index = 0_u32;
for shell in self.solid.boundaries() {
let mesh = truck_meshalgo::tessellation::robust_triangulation_checked(shell, tol)
.ok_or_else(|| KernelError::Failed("verification tessellation cannot meet tolerance within refinement budget".into()))?;
if mesh.face_iter().count() != shell.face_iter().count() {
return Err(KernelError::Failed("verification tessellation has missing faces".into()));
}
for face in mesh.face_iter() {
let pm =
face.surface().ok_or_else(|| KernelError::Failed(format!("verification tessellation could not mesh face {face_index}")))?;
let count = pm.faces().triangle_iter().count();
if count == 0 || count > triangle_limit.saturating_sub(out.triangles.len()) {
return Err(KernelError::Failed("verification tessellation has empty face or exceeds triangle budget".into()));
}
let flip = !face.orientation();
for tri in pm.faces().triangle_iter() {
let mut indices = [0; 3];
for (slot, vertex) in indices.iter_mut().zip(tri) {
let p = pm.positions().get(vertex.pos).ok_or_else(|| KernelError::Failed("invalid verification vertex".into()))?;
let p = Vec3::new(p.x, p.y, p.z);
if ![p.x, p.y, p.z].into_iter().all(f64::is_finite) {
return Err(KernelError::Failed("non-finite verification vertex".into()));
}
*slot = u32::try_from(out.positions.len()).map_err(|_| KernelError::Failed("verification vertex limit".into()))?;
out.positions.push(p);
let n = vertex.nor.and_then(|i| pm.normals().get(i)).map(|n| Vec3::new(n.x, n.y, n.z)).unwrap_or(Vec3::Z);
out.normals.push(if flip { -n } else { n });
}
out.triangles.push(if flip { [indices[0], indices[2], indices[1]] } else { indices });
out.tri_face.push(face_index);
}
face_index += 1;
}
}
if face_index == 0 {
return Err(KernelError::Failed("verification tessellation has no faces".into()));
}
Ok(out)
})
}
}
@@ -1,94 +0,0 @@
//! Independent analytic checks for the fixed-tolerance verification mesh.
#![allow(clippy::unwrap_used)]
use super::*;
// Run the original candidate-index differential tests in ordinary workspace CI;
// the vendored crate's standalone test targets are intentionally disabled.
#[path = "../../../vendor/truck-meshalgo/src/tessellation/boundary_index.rs"]
mod boundary_index;
fn radial_error(mesh: &Mesh, radius: f64, center: Vec3) -> f64 {
let mut error = 0_f64;
for indices in &mesh.triangles {
let points = mesh.tri(indices).unwrap();
// The cylinder side spans both cap heights. Check every side edge,
// including triangle diagonals, against the analytic radial distance.
if points.iter().any(|p| p.z < center.z + 1.) && points.iter().any(|p| p.z > center.z + 5.) {
for i in 0..3 {
let midpoint = (points[i] + points[(i + 1) % 3]) * 0.5 - center;
error = error.max((radius - midpoint.x.hypot(midpoint.y)).abs());
}
}
}
error
}
#[test]
fn verification_tessellation_keeps_physical_tolerance_for_large_curved_bodies() {
let tolerance = 0.00125;
let radius = 5000.;
let center = Vec3::new(20_000., -10_000., 3.);
let body = crate::cylinder(center, Vec3::Z, radius, 6.).unwrap();
let display = body.tessellate(tolerance).unwrap();
assert!(radial_error(&display, radius, center) > tolerance * 4.);
let mesh = body.tessellate_for_verification(tolerance, 200_000).unwrap();
assert!(mesh.triangles.len() > display.triangles.len());
let volume = mesh.measure().volume;
assert!(volume >= std::f64::consts::PI * (radius - tolerance).powi(2) * 6.);
assert!(volume <= std::f64::consts::PI * (radius + tolerance).powi(2) * 6.);
assert!(radial_error(&mesh, radius, center) <= tolerance, "{}", radial_error(&mesh, radius, center));
assert_eq!(mesh.tri_face.iter().copied().collect::<std::collections::BTreeSet<_>>().len(), body.face_count());
}
#[test]
fn verification_tessellation_rejects_bad_settings_and_excessive_output() {
let body = crate::box_solid(Vec3::ZERO, Vec3::new(10., 10., 6.)).unwrap();
for tolerance in [0., -1., f64::NAN, f64::INFINITY] {
assert!(body.tessellate_for_verification(tolerance, 100).is_err());
}
for limit in [0, 2_000_001] {
assert!(body.tessellate_for_verification(0.00125, limit).is_err());
}
assert!(body.tessellate_for_verification(0.00125, 11).unwrap_err().to_string().contains("triangle budget"));
let mesh = body.tessellate_for_verification(0.00125, 12).unwrap();
assert_eq!(mesh.triangles.len(), 12);
let imported = crate::mesh_body(&mesh.positions, &mesh.triangles).unwrap();
assert!(imported.tessellate_for_verification(0.00125, 11).is_err());
assert_eq!(imported.tessellate_for_verification(0.00125, 12).unwrap().triangles.len(), 12);
}
#[test]
fn verification_tessellation_does_not_accept_refinement_budget_fallback() {
let body = crate::cylinder(Vec3::ZERO, Vec3::Z, 5000., 6.).unwrap();
let error = body.tessellate_for_verification(1e-10, 200_000).unwrap_err();
assert!(error.to_string().contains("refinement budget"), "{error}");
// Curved in both parameters: the surface grid exhausts its budget before
// any circular boundary needs the edge's 20,000-point limit.
let sphere = crate::sphere(Vec3::ZERO, 10.).unwrap();
let error = sphere.tessellate_for_verification(1e-5, 200_000).unwrap_err();
assert!(error.to_string().contains("refinement budget"), "{error}");
}
#[test]
fn verification_tessellation_preserves_concave_material_and_multiple_holes() {
use solidcraft_geom::{Loop2, Plane, Region2, Vec2};
let polygon = |points: &[(f64, f64)]| Loop2::polygon(&points.iter().map(|&(x, y)| Vec2::new(x, y)).collect::<Vec<_>>());
let region = Region2 {
outer: polygon(&[(0., 0.), (20., 0.), (20., 10.), (10., 10.), (10., 20.), (0., 20.)]),
holes: vec![polygon(&[(2., 2.), (2., 4.), (4., 4.), (4., 2.)]), polygon(&[(12., 2.), (12., 4.), (14., 4.), (14., 2.)])],
};
let bodies = crate::extrude(&Plane::XY, &[region], 0., 6.).unwrap();
assert_eq!(bodies.len(), 1);
let mesh = bodies.first().unwrap().tessellate_for_verification(0.00125, 200_000).unwrap();
assert!((mesh.measure().volume - 1752.).abs() < 1e-6);
for (point, material) in [
(Vec3::new(1., 1., 3.), true),
(Vec3::new(19., 9., 3.), true),
(Vec3::new(9., 19., 3.), true),
(Vec3::new(3., 3., 3.), false),
(Vec3::new(13., 3., 3.), false),
(Vec3::new(15., 15., 3.), false),
] {
assert_eq!(mesh.contains(point), material, "{point:?}");
}
}
+1 -1
View File
@@ -172,7 +172,7 @@ pub use step_in::{
};
pub use step_out::{ExportBody, ExportProduct, StepHeader, step_export_bodies, step_export_products};
pub use surface_extend::{ExtendLimit, ExtendMode, ExtendOptions, extend_surface};
pub use surface_kind::{EdgeCircle, FaceSurface, edge_circle, face_surface, hole_axis};
pub use surface_kind::{EdgeCircle, FaceSurface, edge_circle, face_surface};
pub use surface_offset::offset_surface;
pub use surface_ruled::{Ruled, RuledCorners, RuledMode, ruled_surface};
pub use surface_untrim::{UntrimHoles, untrim};
-15
View File
@@ -165,21 +165,6 @@ impl FaceSurface {
}
}
/// An inward cylindrical face's axis and radius. The caller still verifies the
/// machined result against the complete target, including trimmed face bounds.
pub fn hole_axis(b: &Body, face: usize, tolerance: f64) -> crate::Result<(Vec3, Vec3, f64)> {
if !tolerance.is_finite() || tolerance <= 0. {
return Err(crate::KernelError::Invalid("invalid hole tolerance".into()));
}
crate::guard("hole face axis", || {
let f = b.solid.face_iter().nth(face).ok_or_else(|| crate::KernelError::Invalid("hole face no longer exists".into()))?;
match surf_of(f, tolerance) {
Some(Surf::Cylinder { o, a, r, convex: false }) => Ok((o, a, r)),
_ => Err(crate::KernelError::Invalid("select an inward cylindrical hole face".into())),
}
})
}
#[cfg(test)]
mod tests {
use super::*;