forked from 1b-agent/opencadstudio-upstream
fix(io): keep zero-radius circles and arcs on open
Problem The open guard rejects circles and arcs with a radius below 1e-10. A normal open fails when a drawing contains one. The user gets the recovery prompt. AutoCAD and nanoCAD open these drawings. The check came fromace99541. Its comment says the tessellator cannot sample a degenerate curve. That was true of truck's parameter_division. Commit8c7ebdc9removed truck. Commit96be8214made the kernel sample circles and arcs by sweep angle with a floored segment count. The reason for the check no longer exists. Change - Remove the near-zero radius check for circles and arcs. - Keep the checks for non-finite values and radii above 1e10. - Update the guard comments. The purge rejects parser desync. It does not reject degenerate geometry. Be generous in what you accept and strict in what you produce. - Add tests: a zero radius passes the guard, an absurd radius does not. - Change the automation purge test sample from radius 0 to radius 1e11. Behavior - Zero-radius circles and arcs load. They render as dots. - Tessellation, hit testing, snapping, and hatch boundaries accept a zero radius. - The DXF writer does not change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
7aff9fa046
commit
8e14b10104
2 files changed
+49
-9
No files matched your search
@@ -1414,7 +1414,9 @@ mod tests {
|
|||||||
doc.add_entity(acadrust::EntityType::Circle(good)).unwrap();
|
doc.add_entity(acadrust::EntityType::Circle(good)).unwrap();
|
||||||
let mut corrupt = acadrust::entities::Circle::new();
|
let mut corrupt = acadrust::entities::Circle::new();
|
||||||
corrupt.center = acadrust::types::Vector3::new(1.0, 1.0, 0.0);
|
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
|
// Past any drawing's extent: io::is_entity_corrupt treats it as parser
|
||||||
|
// desync. (A zero radius is legal geometry and survives the purge.)
|
||||||
|
corrupt.radius = 1.0e11;
|
||||||
doc.add_entity(acadrust::EntityType::Circle(corrupt)).unwrap();
|
doc.add_entity(acadrust::EntityType::Circle(corrupt)).unwrap();
|
||||||
let bytes = crate::io::save_to_bytes(&doc, "dxf", doc.version)
|
let bytes = crate::io::save_to_bytes(&doc, "dxf", doc.version)
|
||||||
.expect("save a document containing a corrupt entity");
|
.expect("save a document containing a corrupt entity");
|
||||||
|
|||||||
+46
-8
@@ -2022,6 +2022,12 @@ fn sync_current_styles_on_save(doc: &mut CadDocument) {
|
|||||||
// `purge_corrupt_entities` scans the document and removes any entity that
|
// `purge_corrupt_entities` scans the document and removes any entity that
|
||||||
// fails a cheap sanity check, returning the number dropped so the caller can
|
// fails a cheap sanity check, returning the number dropped so the caller can
|
||||||
// surface it to the UI / log.
|
// surface it to the UI / log.
|
||||||
|
//
|
||||||
|
// The checks target what a desynchronised parser produces, not what a CAD
|
||||||
|
// package might legitimately write. Degenerate-but-legal geometry — a
|
||||||
|
// zero-radius circle, a zero-sweep arc, a zero-length line — is kept, since
|
||||||
|
// the strict open path treats every drop as a corrupt file and sends the
|
||||||
|
// user through recovery for it.
|
||||||
|
|
||||||
fn finite_unit_normal(n: &acadrust::types::Vector3) -> bool {
|
fn finite_unit_normal(n: &acadrust::types::Vector3) -> bool {
|
||||||
let (x, y, z) = (n.x, n.y, n.z);
|
let (x, y, z) = (n.x, n.y, n.z);
|
||||||
@@ -2079,20 +2085,23 @@ pub(crate) fn is_entity_corrupt(e: &EntityType) -> bool {
|
|||||||
|| p.vertices.iter().any(|v| !finite_vec3(&v.location))
|
|| p.vertices.iter().any(|v| !finite_vec3(&v.location))
|
||||||
}
|
}
|
||||||
E::Line(l) => !finite_vec3(&l.start) || !finite_vec3(&l.end),
|
E::Line(l) => !finite_vec3(&l.start) || !finite_vec3(&l.end),
|
||||||
|
// A zero or near-zero radius is legal, if useless, geometry: AutoCAD
|
||||||
|
// and nanoCAD write and open such circles and arcs, and real
|
||||||
|
// drawings carry them (collapsed dimension arcs, point markers).
|
||||||
|
// Every consumer here copes with it — the kernel tessellates by
|
||||||
|
// sweep angle and floors the segment count, the circle shader draws
|
||||||
|
// the distance to the radius, and the tangent snap solver returns
|
||||||
|
// nothing when the cursor sits inside the radius — so only the
|
||||||
|
// desync signatures are rejected: non-finite values and radii past
|
||||||
|
// any drawing's extent.
|
||||||
E::Circle(c) => {
|
E::Circle(c) => {
|
||||||
!finite_vec3(&c.center)
|
!finite_vec3(&c.center) || !finite_coord(c.radius) || c.radius.abs() > 1.0e10
|
||||||
|| !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
|
|
||||||
}
|
}
|
||||||
E::Arc(a) => {
|
E::Arc(a) => {
|
||||||
!finite_vec3(&a.center)
|
!finite_vec3(&a.center)
|
||||||
|| !finite_coord(a.radius)
|
|| !finite_coord(a.radius)
|
||||||
|| !a.start_angle.is_finite()
|
|| !a.start_angle.is_finite()
|
||||||
|| !a.end_angle.is_finite()
|
|| !a.end_angle.is_finite()
|
||||||
|| a.radius.abs() < 1.0e-10
|
|
||||||
|| a.radius.abs() > 1.0e10
|
|| a.radius.abs() > 1.0e10
|
||||||
|| !finite_unit_normal(&a.normal)
|
|| !finite_unit_normal(&a.normal)
|
||||||
}
|
}
|
||||||
@@ -2342,7 +2351,7 @@ mod layer_roundtrip_tests {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod corrupt_guard_tests {
|
mod corrupt_guard_tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use acadrust::entities::{Arc, EntityType, Spline};
|
use acadrust::entities::{Arc, Circle, EntityType, Spline};
|
||||||
use acadrust::types::Vector3;
|
use acadrust::types::Vector3;
|
||||||
|
|
||||||
// Small but finite arcs are valid records. Kernel tessellation is bounded,
|
// Small but finite arcs are valid records. Kernel tessellation is bounded,
|
||||||
@@ -2382,6 +2391,35 @@ mod corrupt_guard_tests {
|
|||||||
assert!(!is_entity_corrupt(&EntityType::Arc(a)));
|
assert!(!is_entity_corrupt(&EntityType::Arc(a)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zero-radius circles and arcs are legal DXF that AutoCAD and nanoCAD
|
||||||
|
// open without complaint. A strict open that drops them fails the whole
|
||||||
|
// file, so they must pass the guard; only absurd radii are desync.
|
||||||
|
#[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
|
// Parser desync emits 100_000-control-point splines; building a kernel
|
||||||
// NURBS from one and tessellating it OOMs. The control-point cap rejects it.
|
// NURBS from one and tessellating it OOMs. The control-point cap rejects it.
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in new issue
Block a user