Guard constraint solving and DWG export against degenerate geometry

Retaining a line's length added a P2PDistance constraint, whose gradient
divides by the current distance. For a zero-length line the solver got a NaN
Jacobian and its SVD fallback never returned, freezing the editor (e.g.
ECONSTRAINT on a line drawn by clicking one point twice). Hold coincident
endpoints together with per-axis differences instead.

Control-point splines read without knots are given a degree their control
points can carry (at most n - 1) and a clamped knot vector when a drawing
loads. Generating knots for fewer points than the degree underflowed during
DWG write: a panic in debug builds, an unbounded allocation in release, and
reachable from autosave. A lone control point with no knots or fit points is
purged as corrupt.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Christopher OverbeckandClaude Opus 5 committed 2026-09-17 10:33:18 -05:00
1 parent a3d65e2fc8
commit 1ac9309f85
3 files changed
+110 -6

No files matched your search

+19
View File
@@ -2346,3 +2346,22 @@ mod region_tests {
}
}
}
#[cfg(test)]
mod degenerate_constraint_tests {
use crate::app::OpenCADStudio;
/// Retaining a zero-length line's size used to hand the solver a NaN
/// Jacobian, and its SVD fallback never returned.
#[test]
fn equal_constraint_with_a_zero_length_line_returns() {
let mut app = OpenCADStudio::new_for_test();
app.automation_op(r#"{"op":"new"}"#);
app.automation_op(r#"{"op":"run","cmd":"LINE 0,0 0,0 "}"#);
app.automation_op(r#"{"op":"run","cmd":"LINE 5,5 20,7 "}"#);
app.automation_op(r#"{"op":"select","type":"Line"}"#);
app.automation_op(r#"{"op":"run","cmd":"ECONSTRAINT"}"#);
let lines = app.automation_op(r#"{"op":"query","type":"Line","detail":"geometry"}"#);
assert_eq!(lines["ok"], true, "{lines}");
}
}