mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-25 19:36:49 -06:00
ELLIPSE: after Rotation the next value is the rotation around the major axis (ratio = cos angle, 0 to 89.4 degrees; a picked point gives the angle from the centre), instead of the other-axis distance. At the elliptical arc's start/end prompts, Parameter reads the values as ellipse parameters and Angle switches back to true angles. BREAK: First point asks for a new first break point; the selection pick then only selects the object. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
74 lines
2.6 KiB
Rust
74 lines
2.6 KiB
Rust
//! Prompt keywords that change how the next value is read: ELLIPSE Rotation and Parameter, BREAK
|
|
//! First point.
|
|
|
|
use cadcraft_engine::Session;
|
|
use cadcraft_engine::doc::EntityKind;
|
|
|
|
fn ellipses(s: &Session) -> Vec<cadcraft_engine::doc::Ellipse> {
|
|
s.doc().unwrap().model.iter().filter_map(|e| if let EntityKind::Ellipse(el) = &e.kind { Some(el.clone()) } else { None }).collect()
|
|
}
|
|
|
|
fn lines(s: &Session) -> Vec<((f64, f64), (f64, f64))> {
|
|
let mut v: Vec<_> = s
|
|
.doc()
|
|
.unwrap()
|
|
.model
|
|
.iter()
|
|
.filter_map(|e| if let EntityKind::Line(l) = &e.kind { Some(((l.a.x, l.a.y), (l.b.x, l.b.y))) } else { None })
|
|
.collect();
|
|
v.sort_by(|a, b| a.0.0.total_cmp(&b.0.0));
|
|
v
|
|
}
|
|
|
|
fn near(a: f64, b: f64) -> bool {
|
|
(a - b).abs() < 1e-6
|
|
}
|
|
|
|
#[test]
|
|
fn ellipse_rotation_sets_the_axis_ratio() {
|
|
let mut s = Session::new();
|
|
s.cmdline("ELLIPSE 0,0 10,0 R 60").unwrap();
|
|
assert!(s.running.is_none());
|
|
let e = ellipses(&s);
|
|
assert_eq!(e.len(), 1);
|
|
let e = &e[0];
|
|
assert!(near(e.center.x, 5.0) && near(e.center.y, 0.0), "center {:?}", e.center);
|
|
assert!(near(e.major.x.abs(), 5.0) && near(e.major.y, 0.0), "major {:?}", e.major);
|
|
assert!(near(e.ratio, 0.5), "ratio {}", e.ratio);
|
|
// Center mode, via a script.
|
|
let mut s = Session::new();
|
|
s.script("ELLIPSE C 0,0 0,8 R 0\n").unwrap();
|
|
let e = ellipses(&s);
|
|
assert_eq!(e.len(), 1);
|
|
assert!(near(e[0].ratio, 1.0) && near(e[0].major.y, 8.0), "{:?}", e[0]);
|
|
// Out of range: re-prompts instead of drawing a degenerate ellipse.
|
|
let mut s = Session::new();
|
|
s.cmdline("ELLIPSE 0,0 10,0 R 90").unwrap();
|
|
assert!(s.running.is_some());
|
|
assert!(ellipses(&s).is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn elliptical_arc_parameter_values() {
|
|
let mut s = Session::new();
|
|
// A 10 x 5 ellipse: the angle 45° is the parameter atan2(sin45/5, cos45/10) ≠ 45°; with
|
|
// Parameter the values are taken as given.
|
|
s.cmdline("ELLIPSE A 0,0 20,0 5 P 45 A 180").unwrap();
|
|
assert!(s.running.is_none());
|
|
let e = ellipses(&s);
|
|
assert_eq!(e.len(), 1);
|
|
assert!(near(e[0].start, 45f64.to_radians()), "start {}", e[0].start);
|
|
// `A` switches the end value back to an angle; 180° is parameter π either way.
|
|
assert!(near(e[0].end, std::f64::consts::PI), "end {}", e[0].end);
|
|
}
|
|
|
|
#[test]
|
|
fn break_first_point_option() {
|
|
let mut s = Session::new();
|
|
s.cmdline("LINE 0,0 10,0").unwrap();
|
|
s.cmdline("").unwrap();
|
|
s.cmdline("BREAK 5,0 F 2,0 6,0").unwrap();
|
|
assert!(s.running.is_none());
|
|
assert_eq!(lines(&s), vec![((0.0, 0.0), (2.0, 0.0)), ((6.0, 0.0), (10.0, 0.0))]);
|
|
}
|