diff --git a/crates/engine/src/cmd/inquiry.rs b/crates/engine/src/cmd/inquiry.rs index f0500923..e21e92f5 100644 --- a/crates/engine/src/cmd/inquiry.rs +++ b/crates/engine/src/cmd/inquiry.rs @@ -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())) diff --git a/crates/engine/tests/inquiry_keywords.rs b/crates/engine/tests/inquiry_keywords.rs index 4e69e14e..49e0560a 100644 --- a/crates/engine/tests/inquiry_keywords.rs +++ b/crates/engine/tests/inquiry_keywords.rs @@ -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) { 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:?}"); +} diff --git a/crates/ui-egui/tests/hidden_command_line.rs b/crates/ui-egui/tests/hidden_command_line.rs index cf525956..58427f62 100644 --- a/crates/ui-egui/tests/hidden_command_line.rs +++ b/crates/ui-egui/tests/hidden_command_line.rs @@ -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"); } diff --git a/docs/geometry-parity.md b/docs/geometry-parity.md index c29d552d..f8dae3a5 100644 --- a/docs/geometry-parity.md +++ b/docs/geometry-parity.md @@ -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 |