diff --git a/src/app/automation.rs b/src/app/automation.rs index 8012361d7..9ca7e8c73 100644 --- a/src/app/automation.rs +++ b/src/app/automation.rs @@ -1417,7 +1417,8 @@ mod tests { doc.add_entity(acadrust::EntityType::Circle(good)).unwrap(); let mut corrupt = acadrust::entities::Circle::new(); corrupt.center = acadrust::types::Vector3::new(1.0, 1.0, 0.0); - corrupt.radius = 0.0; // io::is_entity_corrupt rejects a zero-radius circle + // An absurd radius is rejected; a zero radius is valid. + corrupt.radius = 1.0e11; doc.add_entity(acadrust::EntityType::Circle(corrupt)) .unwrap(); let bytes = crate::io::save_to_bytes(&doc, "dxf", doc.version) diff --git a/src/io/mod.rs b/src/io/mod.rs index 1b88c2bd6..55ae70b1f 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -2023,6 +2023,8 @@ fn sync_current_styles_on_save(doc: &mut CadDocument) { // `purge_corrupt_entities` scans the document and removes any entity that // fails a cheap sanity check, returning the number dropped so the caller can // surface it to the UI / log. +// +// Keep valid degenerate geometry: dropping it would trigger strict-open recovery. fn finite_unit_normal(n: &acadrust::types::Vector3) -> bool { let (x, y, z) = (n.x, n.y, n.z); @@ -2080,20 +2082,15 @@ pub(crate) fn is_entity_corrupt(e: &EntityType) -> bool { || p.vertices.iter().any(|v| !finite_vec3(&v.location)) } E::Line(l) => !finite_vec3(&l.start) || !finite_vec3(&l.end), + // Zero-radius curves are valid and have bounded kernel tessellation. E::Circle(c) => { - !finite_vec3(&c.center) - || !finite_coord(c.radius) - // Reject zero- or near-zero circles: they tessellate into a - // degenerate curve the tessellator cannot sample. - || c.radius.abs() < 1.0e-10 - || c.radius.abs() > 1.0e10 + !finite_vec3(&c.center) || !finite_coord(c.radius) || c.radius.abs() > 1.0e10 } E::Arc(a) => { !finite_vec3(&a.center) || !finite_coord(a.radius) || !a.start_angle.is_finite() || !a.end_angle.is_finite() - || a.radius.abs() < 1.0e-10 || a.radius.abs() > 1.0e10 || !finite_unit_normal(&a.normal) } @@ -2343,7 +2340,7 @@ mod layer_roundtrip_tests { #[cfg(test)] mod corrupt_guard_tests { use super::*; - use acadrust::entities::{Arc, EntityType, Spline}; + use acadrust::entities::{Arc, Circle, EntityType, Spline}; use acadrust::types::Vector3; // Small but finite arcs are valid records. Kernel tessellation is bounded, @@ -2383,6 +2380,32 @@ mod corrupt_guard_tests { assert!(!is_entity_corrupt(&EntityType::Arc(a))); } + #[test] + fn keeps_zero_radius_circle_and_arc() { + let mut c = Circle::new(); + c.center = Vector3::new(206.2, 150.7, 0.0); + c.radius = 0.0; + assert!(!is_entity_corrupt(&EntityType::Circle(c))); + + let mut a = Arc::new(); + a.center = Vector3::new(223.5, 174.5, 0.0); + a.radius = 0.0; + a.start_angle = 0.0; + a.end_angle = 0.0; + a.normal = Vector3::new(0.0, 0.0, 1.0); + assert!(!is_entity_corrupt(&EntityType::Arc(a))); + } + + #[test] + fn drops_absurd_radius_circle() { + let mut c = Circle::new(); + c.radius = 1.0e11; + assert!(is_entity_corrupt(&EntityType::Circle(c))); + let mut c = Circle::new(); + c.radius = f64::NAN; + assert!(is_entity_corrupt(&EntityType::Circle(c))); + } + // Parser desync emits 100_000-control-point splines; building a kernel // NURBS from one and tessellating it OOMs. The control-point cap rejects it. #[test]