mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-25 03:16:50 -06:00
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>
70 lines
3.1 KiB
Rust
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}");
|
|
}
|
|
}
|
|
}
|