Files
cadcraft/crates/render/tests/point_style.rs
Valentin KleinandClaude Opus 5.5 4c056c54bd Points draw with the point style (PDMODE/PDSIZE) (fixes #261) (#265)
POINT objects were always a one-pixel dot. They now draw the figure PDMODE
picks (nothing, plus, cross, tick, with an optional circle and/or square)
at the size PDSIZE gives: drawing units when positive, a percentage of the
view height when negative, 5 % when 0. The canvas passes its view height;
exports and plots use the plotted window or the extents.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:33:43 -04:00

70 lines
3.1 KiB
Rust

//! POINT display: point style (PDMODE) and size (PDSIZE) (issue #261).
use cadcraft_doc::{Common, Drawing, EntityKind, Line, Point, Space};
use cadcraft_geom::{Bounds2, Vec3};
use cadcraft_render::{DisplayList, Kind, Options, build};
/// A point at (5,5) and a vertical line 100 units long (the extents are 100 high).
fn drawing(pdmode: i64, pdsize: f64) -> Drawing {
let mut d = Drawing::new_imperial();
d.header.set_i64("PDMODE", pdmode);
d.header.set_f64("PDSIZE", pdsize);
d.add(&Space::Model, Common::default(), EntityKind::Point(Point { p: Vec3::new(5.0, 5.0, 0.0), angle: 0.0 })).unwrap();
d.add(&Space::Model, Common::default(), EntityKind::Line(Line { a: Vec3::new(0.0, 0.0, 0.0), b: Vec3::new(0.0, 100.0, 0.0) })).unwrap();
d
}
/// Dots and the bounds of the point's line work (the line along x = 0 is left out).
fn point_parts(l: &DisplayList) -> (usize, usize, Bounds2) {
let dots = l.prims.iter().filter(|p| p.kind == Kind::Point).count();
let lines: Vec<_> = l.prims.iter().filter(|p| p.kind == Kind::Polyline && l.points(p).iter().any(|q| q.x > 1.0)).collect();
let b = Bounds2::from_points(lines.iter().flat_map(|p| l.points(p).iter().copied()));
(dots, lines.len(), b)
}
fn close(b: &Bounds2, x0: f64, y0: f64, x1: f64, y1: f64) -> bool {
[b.min.x - x0, b.min.y - y0, b.max.x - x1, b.max.y - y1].iter().all(|v| v.abs() < 1e-3)
}
#[test]
fn cross_in_circle_with_absolute_size() {
let l = build(&drawing(35, 1.0), &Space::Model, &Options::default());
let (dots, lines, b) = point_parts(&l);
assert_eq!((dots, lines), (0, 3), "an X (two lines) and a circle, no dot");
assert!(close(&b, 4.5, 4.5, 5.5, 5.5), "{b:?}");
}
#[test]
fn dot_and_nothing() {
let (dots, lines, _) = point_parts(&build(&drawing(0, 1.0), &Space::Model, &Options::default()));
assert_eq!((dots, lines), (1, 0));
let (dots, lines, _) = point_parts(&build(&drawing(1, 1.0), &Space::Model, &Options::default()));
assert_eq!((dots, lines), (0, 0));
// 96 = square and circle around a dot.
let (dots, lines, b) = point_parts(&build(&drawing(96, 2.0), &Space::Model, &Options::default()));
assert_eq!((dots, lines), (1, 2));
assert!(close(&b, 4.0, 4.0, 6.0, 6.0), "{b:?}");
}
#[test]
fn relative_sizes_follow_the_view_height() {
// PDSIZE -10: 10 % of a 50-unit view = 5 across.
let opts = Options { view_height: 50.0, ..Options::default() };
let (_, lines, b) = point_parts(&build(&drawing(2, -10.0), &Space::Model, &opts));
assert_eq!(lines, 2);
assert!(close(&b, 2.5, 2.5, 7.5, 7.5), "{b:?}");
// PDSIZE 0 without a view height: 5 % of the 100-unit extents = 5 across.
let (_, _, b) = point_parts(&build(&drawing(3, 0.0), &Space::Model, &Options::default()));
assert!(close(&b, 2.5, 2.5, 7.5, 7.5), "{b:?}");
}
#[test]
fn hostile_point_settings_do_not_panic() {
for mode in [i64::MIN, -1, 5, 31, 127, i64::MAX] {
for size in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, -1e300, 1e300, 0.0, -5.0] {
let l = build(&drawing(mode, size), &Space::Model, &Options::default());
assert!(l.verts.iter().all(|v| v.is_finite()), "PDMODE {mode} PDSIZE {size}");
}
}
}