Convert Mesh says how big a mesh is, the limit, and what converts (#797)
A mesh over the conversion budget is refused with its triangle (or vertex) count, the limit, and that only constant-section parts convert. Refs #346 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9cabc3532d
commit
9bb18842e9
1 file changed
+30
-3
@@ -18,6 +18,14 @@ fn invalid(message: &str) -> KernelError {
|
||||
KernelError::Invalid(format!("mesh conversion: {message}"))
|
||||
}
|
||||
|
||||
/// A mesh over the triangle budget: how big it is, the limit, and that only constant-section
|
||||
/// parts convert, so a smaller freeform mesh would still be refused (#346).
|
||||
fn over_budget(triangles: usize) -> KernelError {
|
||||
invalid(&format!(
|
||||
"the mesh has {triangles} triangles, more than the {MAX_TRIANGLES} conversion can take; only closed constant-section parts (one profile extruded straight) can be converted, so a freeform mesh is refused at any size"
|
||||
))
|
||||
}
|
||||
|
||||
/// Reconstruct a constant-section mesh as analytic planes and, where certified
|
||||
/// within `tolerance` mm, circular cylinders. The source mesh is not modified.
|
||||
/// Unsupported shapes are refused instead of silently returning a faceted solid.
|
||||
@@ -30,7 +38,7 @@ pub fn convert_prismatic_mesh(body: &Body, tolerance: f64) -> Result<Body> {
|
||||
let welded = weld(input, tolerance)?;
|
||||
let mesh = welded.mesh.as_ref().ok_or_else(|| invalid("empty mesh"))?;
|
||||
if mesh.triangles.len() > MAX_TRIANGLES {
|
||||
return Err(invalid("mesh exceeds the 50000-triangle conversion budget"));
|
||||
return Err(over_budget(mesh.triangles.len()));
|
||||
}
|
||||
let mut edges: BTreeMap<(u32, u32), (usize, i32)> = BTreeMap::new();
|
||||
let mut axes: BTreeMap<(i64, i64, i64), (Vec3, f64)> = BTreeMap::new();
|
||||
@@ -69,8 +77,11 @@ pub fn convert_prismatic_mesh(body: &Body, tolerance: f64) -> Result<Body> {
|
||||
}
|
||||
|
||||
fn weld(mesh: &crate::meshbody::TriMesh, tolerance: f64) -> Result<Body> {
|
||||
if mesh.triangles.len() > MAX_TRIANGLES || mesh.positions.len() > MAX_TRIANGLES * 3 {
|
||||
return Err(invalid("mesh exceeds the conversion budget"));
|
||||
if mesh.triangles.len() > MAX_TRIANGLES {
|
||||
return Err(over_budget(mesh.triangles.len()));
|
||||
}
|
||||
if mesh.positions.len() > MAX_TRIANGLES * 3 {
|
||||
return Err(invalid(&format!("the mesh has {} vertices, more than the {} conversion can take", mesh.positions.len(), MAX_TRIANGLES * 3)));
|
||||
}
|
||||
let bounds = mesh.positions.iter().fold(solidcraft_geom::Aabb3::EMPTY, |mut b, p| {
|
||||
b.add(*p);
|
||||
@@ -464,6 +475,22 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_mesh_over_the_budget_says_how_big_it_is_and_what_converts() {
|
||||
// #346: a freeform mesh just over the limit (a strip of 50001 triangles) is refused with
|
||||
// its size, the limit, and that only constant-section parts convert at any size.
|
||||
let n = MAX_TRIANGLES + 1;
|
||||
let positions: Vec<Vec3> = (0..n + 2).map(|k| Vec3::new((k / 2) as f64, (k % 2) as f64, 0.)).collect();
|
||||
let triangles: Vec<[u32; 3]> = (0..n as u32).map(|i| [i, i + 1, i + 2]).collect();
|
||||
let body = crate::mesh_body(&positions, &triangles).unwrap();
|
||||
let err = convert_prismatic_mesh(&body, 0.05).unwrap_err().to_string();
|
||||
assert!(err.contains("50001 triangles") && err.contains("50000"), "{err}");
|
||||
assert!(err.contains("constant-section") && err.contains("freeform"), "{err}");
|
||||
let mesh = crate::meshbody::TriMesh { positions: vec![Vec3::ZERO; MAX_TRIANGLES * 3 + 1], triangles: vec![] };
|
||||
let err = weld(&mesh, 0.05).unwrap_err().to_string();
|
||||
assert!(err.contains("150001 vertices") && err.contains("150000"), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fitting_cannot_open_a_hole_through_a_nearby_wall() {
|
||||
let outer = Loop2::polygon(&[Vec2::new(-1., -1.), Vec2::new(1., -1.), Vec2::new(1., 1.), Vec2::new(-1., 1.)]);
|
||||
|
||||
Reference in new issue
Block a user