Files
cadcraft/crates/engine/tests/it/angle_prompts.rs
aiongg 9cf115e808 CI: one test binary per crate, nextest, line tables only (#1989)
* Build: line tables only in dev, no debug info for dependencies

* Tests: one integration-test binary per crate

Each crate's tests/*.rs becomes a module of tests/it/main.rs. Files that
install process-wide state (the file io hooks, user aliases, SAVETIME and
SAVEFILEPATH, user linetype and pattern files, registered fonts) stay their
own test binaries.

* xtask ci: nextest, tests without examples, one wasm check call
2026-10-23 13:34:26 -04:00

103 lines
4.0 KiB
Rust

//! Angle prompts read typed angles with the drawing's settings (#353): AUNITS for bare numbers,
//! rotations turn clockwise when ANGDIR is 1, directions are measured from ANGBASE.
use std::f64::consts::{FRAC_PI_2, FRAC_PI_4, PI};
use cadcraft_engine::Session;
use cadcraft_engine::doc::{DimKind, EntityKind};
use cadcraft_engine::geom::Vec2;
use serde_json::json;
const QUARTER: &str = "1.5707963267948966";
const EIGHTH: &str = "0.7853981633974483";
/// Radians, clockwise angles, 0 = north.
fn session() -> Session {
let mut s = Session::new();
for (name, value) in [("AUNITS", json!(3)), ("ANGDIR", json!(1)), ("ANGBASE", json!(90))] {
s.execute("setvar", &json!({ "name": name, "value": value })).unwrap();
}
s
}
fn last(s: &Session) -> EntityKind {
s.doc().unwrap().model.last().unwrap().kind.clone()
}
/// `a - b` turned into (-π, π].
fn diff(a: f64, b: f64) -> f64 {
let d = (a - b).rem_euclid(2.0 * PI);
if d > PI { d - 2.0 * PI } else { d }
}
#[test]
fn rotate_angle_is_a_rotation_in_aunits() {
let mut s = session();
s.execute("line", &json!({ "points": [[0, 0], [10, 0]] })).unwrap();
s.script(&format!("ROTATE ALL\n\n0,0 {QUARTER}\n")).unwrap();
assert!(s.running.is_none(), "{}", s.prompt_text());
// A quarter turn clockwise (ANGDIR 1), not 1.57 degrees counterclockwise.
match last(&s) {
EntityKind::Line(l) => assert!(l.b.xy().near(Vec2::new(0.0, -10.0), 1e-9), "{:?}", l.b),
k => panic!("expected a line, got {k:?}"),
}
}
#[test]
fn hatch_angle_is_a_rotation_in_aunits() {
let mut s = session();
s.script(&format!("HATCH A {QUARTER}\n\n")).unwrap();
assert!(s.running.is_none(), "{}", s.prompt_text());
let a = s.doc().unwrap().header.f64("HPANG", 0.0);
assert!(diff(a, -FRAC_PI_2).abs() < 1e-9, "HPANG {a}");
}
#[test]
fn dimension_line_and_text_angles_are_directions() {
let mut s = session();
// Rotated 0 points at ANGBASE (north); the text angle a quarter turn clockwise from north is east.
s.script(&format!("DIMLINEAR 0,0 10,0 R 0 A {QUARTER} 5,5\n")).unwrap();
assert!(s.running.is_none(), "{}", s.prompt_text());
match last(&s) {
EntityKind::Dimension(d) => {
let DimKind::Linear { rotation } = d.kind else { panic!("expected a linear dimension, got {:?}", d.kind) };
assert!(diff(rotation, FRAC_PI_2).abs() < 1e-9, "rotation {rotation}");
assert!(diff(d.text_rotation, 0.0).abs() < 1e-9, "text rotation {}", d.text_rotation);
}
k => panic!("expected a dimension, got {k:?}"),
}
}
#[test]
fn chamfer_and_mleader_angles_are_amounts_in_aunits() {
let mut s = session();
for l in ["0,0 12,0", "10,-2 10,12"] {
s.cmdline(&format!("LINE {l}")).unwrap();
s.cmdline("").unwrap();
}
// Length 2 on the first line at an eighth turn from it, whatever ANGBASE and ANGDIR say.
s.script(&format!("CHAMFER A 2 {EIGHTH} 5,0 10,5\n")).unwrap();
assert!(s.running.is_none(), "{}", s.prompt_text());
let d = s.doc().unwrap().header.f64("CHAMFERD", 0.0);
assert!((d - FRAC_PI_4).abs() < 1e-9, "CHAMFERD {d}");
let chamfer = s.doc().unwrap().model.iter().any(|e| match &e.kind {
EntityKind::Line(l) => {
let (a, b) = (l.a.xy(), l.b.xy());
(a.near(Vec2::new(8.0, 0.0), 1e-6) && b.near(Vec2::new(10.0, 2.0), 1e-6))
|| (b.near(Vec2::new(8.0, 0.0), 1e-6) && a.near(Vec2::new(10.0, 2.0), 1e-6))
}
_ => false,
});
assert!(chamfer, "{:?}", s.doc().unwrap().model.iter().map(|e| &e.kind).collect::<Vec<_>>());
// The first angle constraint snaps the leader to eighth-turn steps.
s.script(&format!("MLEADER O F {EIGHTH} X 0,0 10,8 note\n")).unwrap();
match last(&s) {
EntityKind::MLeader(m) => {
let r = Vec2::new(10.0, 8.0).len();
assert!(m.landing.xy().near(Vec2::new(r, r) / 2f64.sqrt(), 1e-6), "{:?}", m.landing);
}
k => panic!("expected a multileader, got {k:?}"),
}
}