AREA: refuse an open elliptical arc instead of measuring the whole ellipse (#487) (#601)

Squash of PR #601 by @MarekSpodBiedry.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
MarekSpodBiedryandClaude Opus 5.5 authored and GitHub committed 2026-10-11 14:04:40 -04:00
1 parent 5cb54c3969
commit 41c3c83fb9
4 files changed
+26 -2

No files matched your search

+4
View File
@@ -212,6 +212,10 @@ fn object_area(s: &Session, h: Handle) -> Result<(f64, f64)> {
(g.area().abs(), g.len())
}
EntityKind::Ellipse(el) => {
let arc = cadcraft_geom::Ellipse { center: el.center.xy(), major: el.major.xy(), ratio: el.ratio, start: el.start, end: el.end };
if !arc.is_full() {
return Err(bad("area", "object has no area"));
}
let a = el.major.xy().len();
let b = a * el.ratio;
(cadcraft_geom::PI * a * b, cadcraft_geom::PI * (3.0 * (a + b) - ((3.0 * a + b) * (a + 3.0 * b)).sqrt()))
+18
View File
@@ -1,6 +1,7 @@
//! AREA's Object / Add area / Subtract area and DIST's Multiple points, typed as at the command line.
use cadcraft_engine::Session;
use serde_json::json;
fn run(lines: &[&str]) -> (Session, Vec<String>) {
let mut s = Session::new();
@@ -48,3 +49,20 @@ fn dist_multiple_points_sums_the_segments() {
assert!(has(&log, "Distance = 11.0000"), "{log:?}");
assert!(s.running.is_none(), "{}", s.prompt_text());
}
#[test]
fn area_of_an_open_elliptical_arc_is_refused_and_a_full_ellipse_is_measured() {
for end in [90, 45] {
let mut s = Session::new();
let r = s.execute("ellipse", &json!({"center": [0, 0], "major": [4, 0], "ratio": 0.5, "start": 0, "end": end})).unwrap();
assert!(s.execute("area", &json!({"handle": r["handle"]})).is_err());
}
let mut s = Session::new();
let r = s.execute("ellipse", &json!({"center": [0, 0], "major": [4, 0], "ratio": 0.5})).unwrap();
let a = s.execute("area", &json!({"handle": r["handle"]})).unwrap();
assert!((a["area"].as_f64().unwrap() - 8.0 * std::f64::consts::PI).abs() < 1e-9, "{a}");
// Object selection of the quarter ellipse says it has no area.
let (_, log) = run(&["ellipse a c 0,0 4,0 2 0 90", "area", "o", "4,0"]);
assert!(has(&log, "no area"), "{log:?}");
assert!(!has(&log, "Area = "), "{log:?}");
}
+3 -1
View File
@@ -20,10 +20,12 @@ fn frame(app: &mut CadApp, ctx: &egui::Context) {
let mut raw = egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(egui::Pos2::ZERO, egui::vec2(1280.0, 800.0))), ..Default::default() };
app.raw_input_hook(&mut raw);
cadcraft_ui_egui::cmdline::capture_tab(ctx, &mut raw);
let _ = ctx.run_ui(raw, |ui| {
let mut out = ctx.run_ui(raw, |ui| {
app.logic(ui.ctx());
app.ui(ui);
});
// No renderer here: drop the texture updates deliberately.
out.textures_delta.clear();
assert!(!ctx.egui_wants_keyboard_input(), "a text field must not hold focus");
}
+1 -1
View File
@@ -97,7 +97,7 @@ Where line and arc are supported the results are exact.
| Closest point: line, arc, circle, polyline | exact | exact |
| Closest point: ellipse, spline | exact to tolerance | **sampled** (`curve.rs:264`, `curves.rs:637`) |
| Tangent points, common tangents | every curve | circles and arcs only (`curve.rs:170-183`) |
| AREA: circle, ellipse | exact | exact area (ellipse perimeter by Ramanujan, not exact) |
| AREA: circle, ellipse | exact | exact area (ellipse perimeter by Ramanujan, not exact); an open elliptical arc has no area, like an arc |
| AREA: polyline with arcs | exact (bulge segments) | **tessellated at 1e-4** (`curve.rs:491`) |
| MASSPROP (regions: area, perimeter, centroid, moments) | exact on regions and solids | shoelace over a tessellated circle, polyline or first hatch loop (`utility.rs:310-352`); no regions |
| LIST/DIST/ID precision | full double | full double |