mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-26 03:46:53 -06:00
584 lines
23 KiB
Rust
584 lines
23 KiB
Rust
use crate::*;
|
|
use cadcraft_geom::{Bounds2, Mat3, PolyVertex, Vec2, Vec3};
|
|
|
|
fn line(a: (f64, f64), b: (f64, f64)) -> EntityKind {
|
|
EntityKind::Line(Line { a: Vec3::new(a.0, a.1, 0.0), b: Vec3::new(b.0, b.1, 0.0) })
|
|
}
|
|
|
|
#[test]
|
|
fn new_drawing_has_standard_tables() {
|
|
let d = Drawing::new_imperial();
|
|
assert!(d.layer("0").is_some());
|
|
assert!(d.linetype("continuous").is_some());
|
|
assert!(d.text_style("Standard").is_some());
|
|
assert_eq!(d.header.f64("TEXTSIZE", 0.0), 0.2);
|
|
let m = Drawing::new_metric();
|
|
assert!(m.dim_style("ISO-25").is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn new_metric_drawing_has_metric_multileader_and_table_styles() {
|
|
let m = Drawing::new_metric();
|
|
let iso = m.dim_style("ISO-25").unwrap();
|
|
let ml = m.mleader_styles.iter().find(|s| s.name == "Standard").unwrap();
|
|
assert_eq!((ml.text_height, ml.arrow_size), (iso.text_height, iso.arrow_size));
|
|
assert_eq!(ml.dogleg, 5.0);
|
|
let ts = m.table_styles.iter().find(|s| s.name == "Standard").unwrap();
|
|
assert_eq!(ts.text_height, iso.text_height);
|
|
assert_eq!(iso.text_height, 2.5);
|
|
|
|
let i = Drawing::new_imperial();
|
|
assert_eq!(i.mleader_styles, vec![MLeaderStyle::default()]);
|
|
assert_eq!(i.table_styles, vec![TableStyle::default()]);
|
|
assert_eq!((i.mleader_styles[0].text_height, i.table_styles[0].text_height), (0.18, 0.18));
|
|
}
|
|
|
|
#[test]
|
|
fn add_and_extents() {
|
|
let mut d = Drawing::new_imperial();
|
|
d.add(&Space::Model, Common::default(), line((0.0, 0.0), (10.0, 5.0))).unwrap();
|
|
d.add(&Space::Model, Common::default(), EntityKind::Circle(Circle { center: Vec3::new(20.0, 0.0, 0.0), radius: 2.0 })).unwrap();
|
|
let b = d.extents(&Space::Model);
|
|
assert_eq!(b.min, Vec2::new(0.0, -2.0));
|
|
assert_eq!(b.max, Vec2::new(22.0, 5.0));
|
|
}
|
|
|
|
#[test]
|
|
fn hidden_layers_excluded_from_extents() {
|
|
let mut d = Drawing::new_imperial();
|
|
let c = Common { layer: "Hidden".into(), ..Default::default() };
|
|
d.add(&Space::Model, c, line((100.0, 100.0), (200.0, 200.0))).unwrap();
|
|
d.add(&Space::Model, Common::default(), line((0.0, 0.0), (1.0, 1.0))).unwrap();
|
|
d.layer_mut("Hidden").unwrap().on = false;
|
|
assert_eq!(d.extents(&Space::Model).max, Vec2::new(1.0, 1.0));
|
|
}
|
|
|
|
#[test]
|
|
fn rays_and_xlines_do_not_stretch_extents() {
|
|
// ZOOM Extents ignores infinite lines; real drawings carry stray rays millions of units away.
|
|
let mut d = Drawing::new_imperial();
|
|
d.add(&Space::Model, Common::default(), line((0.0, 0.0), (1.0, 1.0))).unwrap();
|
|
let far = Vec3::new(-1_977_989.0, -1_952_638.0, 0.0);
|
|
d.add(&Space::Model, Common::default(), EntityKind::Ray(RayLine { base: far, dir: Vec3::new(-0.7, -0.7, 0.0) })).unwrap();
|
|
d.add(&Space::Model, Common::default(), EntityKind::XLine(RayLine { base: far, dir: Vec3::new(1.0, 0.0, 0.0) })).unwrap();
|
|
let b = d.extents(&Space::Model);
|
|
assert_eq!((b.min, b.max), (Vec2::ZERO, Vec2::new(1.0, 1.0)));
|
|
}
|
|
|
|
#[test]
|
|
fn inserts_of_missing_or_empty_blocks_do_not_stretch_extents() {
|
|
// An unloaded xref or a purged definition draws nothing, so its insertion point is not an extent.
|
|
let mut d = Drawing::new_imperial();
|
|
d.add(&Space::Model, Common::default(), line((0.0, 0.0), (1.0, 1.0))).unwrap();
|
|
let ins = |block: &str, x: f64, y: f64| {
|
|
EntityKind::Insert(Insert {
|
|
block: block.into(),
|
|
insert: Vec3::new(x, y, 0.0),
|
|
scale: Vec3::new(1.0, 1.0, 1.0),
|
|
rotation: 0.0,
|
|
attribs: vec![],
|
|
cols: 1,
|
|
rows: 1,
|
|
col_spacing: 0.0,
|
|
row_spacing: 0.0,
|
|
})
|
|
};
|
|
d.blocks.insert("Empty".into(), std::sync::Arc::new(Block::new("Empty")));
|
|
d.add(&Space::Model, Common::default(), ins("Missing", -125_319.0, -79_990.0)).unwrap();
|
|
d.add(&Space::Model, Common::default(), ins("Empty", 55_652.0, -3_448_576.0)).unwrap();
|
|
let b = d.extents(&Space::Model);
|
|
assert_eq!((b.min, b.max), (Vec2::ZERO, Vec2::new(1.0, 1.0)));
|
|
// A block with geometry still counts.
|
|
let mut full = Block::new("Full");
|
|
full.entities.push(Entity::new(Handle(1), line((0.0, 0.0), (2.0, 2.0))));
|
|
d.blocks.insert("Full".into(), std::sync::Arc::new(full));
|
|
d.add(&Space::Model, Common::default(), ins("Full", 10.0, 10.0)).unwrap();
|
|
assert_eq!(d.extents(&Space::Model).max, Vec2::new(12.0, 12.0));
|
|
}
|
|
|
|
#[test]
|
|
fn visible_attributes_of_a_missing_block_still_count() {
|
|
// The renderer draws the attributes of a reference whose definition is missing, so they are
|
|
// extents and selection targets, as with an empty definition. The insertion point still isn't.
|
|
let attrib = |invisible: bool| Attrib {
|
|
tag: "TAG".into(),
|
|
text: Text {
|
|
insert: Vec3::new(1000.0, 1000.0, 0.0),
|
|
align_pt: None,
|
|
height: 10.0,
|
|
value: "hello".into(),
|
|
rotation: 0.0,
|
|
width_factor: 1.0,
|
|
oblique: 0.0,
|
|
style: "Standard".into(),
|
|
halign: HAlign::Left,
|
|
valign: VAlign::Baseline,
|
|
},
|
|
invisible,
|
|
constant: false,
|
|
prompt: String::new(),
|
|
props: Default::default(),
|
|
multiline: None,
|
|
modes: Default::default(),
|
|
};
|
|
let ins = |block: &str, attribs: Vec<Attrib>| {
|
|
Entity::new(
|
|
Handle(1),
|
|
EntityKind::Insert(Insert {
|
|
block: block.into(),
|
|
insert: Vec3::ZERO,
|
|
scale: Vec3::new(1.0, 1.0, 1.0),
|
|
rotation: 0.0,
|
|
attribs,
|
|
cols: 1,
|
|
rows: 1,
|
|
col_spacing: 0.0,
|
|
row_spacing: 0.0,
|
|
}),
|
|
)
|
|
};
|
|
let mut d = Drawing::new_imperial();
|
|
d.blocks.insert("Empty".into(), std::sync::Arc::new(Block::new("Empty")));
|
|
let missing = entity_bounds(&d, &ins("Missing", vec![attrib(false)]), 0);
|
|
let empty = entity_bounds(&d, &ins("Empty", vec![attrib(false)]), 0);
|
|
assert!(!missing.is_empty());
|
|
assert_eq!((missing.min, missing.max), (empty.min, empty.max));
|
|
assert_eq!(missing.min, Vec2::new(1000.0, 1000.0));
|
|
// Without a visible attribute there is nothing to measure.
|
|
assert!(entity_bounds(&d, &ins("Missing", vec![]), 0).is_empty());
|
|
assert!(entity_bounds(&d, &ins("Missing", vec![attrib(true)]), 0).is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn transform_arc_mirror_keeps_shape() {
|
|
let mut k = EntityKind::Arc(Arc { center: Vec3::ZERO, radius: 1.0, start: 0.0, end: std::f64::consts::FRAC_PI_2 });
|
|
k.transform(&Mat3::mirror(Vec2::ZERO, Vec2::Y));
|
|
if let EntityKind::Arc(a) = k {
|
|
let g = cadcraft_geom::Arc::new(a.center.xy(), a.radius, a.start, a.end);
|
|
assert!(g.mid_point().near(Vec2::from_angle(3.0 * std::f64::consts::FRAC_PI_4), 1e-9));
|
|
} else {
|
|
panic!()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn insert_bounds_with_cyclic_block_terminate() {
|
|
let mut d = Drawing::new_imperial();
|
|
let mut b = Block::new("A");
|
|
b.entities.push(Entity::new(
|
|
Handle(1),
|
|
EntityKind::Insert(Insert {
|
|
block: "A".into(),
|
|
insert: Vec3::ZERO,
|
|
scale: Vec3::new(1.0, 1.0, 1.0),
|
|
rotation: 0.0,
|
|
attribs: vec![],
|
|
cols: 1,
|
|
rows: 1,
|
|
col_spacing: 0.0,
|
|
row_spacing: 0.0,
|
|
}),
|
|
));
|
|
b.entities.push(Entity::new(Handle(2), line((0.0, 0.0), (1.0, 1.0))));
|
|
d.blocks.insert("A".into(), std::sync::Arc::new(b));
|
|
let h = d
|
|
.add(
|
|
&Space::Model,
|
|
Common::default(),
|
|
EntityKind::Insert(Insert {
|
|
block: "A".into(),
|
|
insert: Vec3::new(5.0, 5.0, 0.0),
|
|
scale: Vec3::new(2.0, 2.0, 1.0),
|
|
rotation: 0.0,
|
|
attribs: vec![],
|
|
cols: 1,
|
|
rows: 1,
|
|
col_spacing: 0.0,
|
|
row_spacing: 0.0,
|
|
}),
|
|
)
|
|
.unwrap();
|
|
let e = d.entity(h).unwrap().clone();
|
|
let bb = entity_bounds(&d, &e, 0);
|
|
assert_eq!(bb.max, Vec2::new(7.0, 7.0));
|
|
}
|
|
|
|
#[test]
|
|
fn entity_serde_roundtrip() {
|
|
let e = Entity::new(Handle(7), line((1.0, 2.0), (3.0, 4.0)));
|
|
let s = serde_json::to_string(&e).unwrap();
|
|
let back: Entity = serde_json::from_str(&s).unwrap();
|
|
assert_eq!(e, back);
|
|
}
|
|
|
|
#[test]
|
|
fn paper_space_entities() {
|
|
let mut d = Drawing::new_imperial();
|
|
let h = d.add(&Space::Paper("Layout1".into()), Common::default(), line((0.0, 0.0), (1.0, 0.0))).unwrap();
|
|
assert_eq!(d.space_of(h), Some(Space::Paper("Layout1".into())));
|
|
assert!(d.remove_entity(h).is_some());
|
|
assert_eq!(d.entity_count(), 0);
|
|
}
|
|
|
|
fn wide_pline(verts: &[(f64, f64, f64, f64, f64)], closed: bool, const_width: f64) -> Bounds2 {
|
|
// (x, y, start width, end width, bulge) per vertex.
|
|
let mut d = Drawing::new_metric();
|
|
let vertices = verts.iter().map(|&(x, y, s, e, b)| PolyVertex { p: Vec2::new(x, y), bulge: b, start_width: s, end_width: e }).collect();
|
|
let kind = EntityKind::LwPolyline(LwPolyline { vertices, closed, const_width, elevation: 0.0, plinegen: false });
|
|
d.add(&Space::Model, Common::default(), kind).unwrap();
|
|
d.extents(&Space::Model)
|
|
}
|
|
|
|
fn near_bounds(b: Bounds2, min: (f64, f64), max: (f64, f64)) -> bool {
|
|
let close = |a: f64, b: f64| (a - b).abs() < 1e-9;
|
|
close(b.min.x, min.0) && close(b.min.y, min.1) && close(b.max.x, max.0) && close(b.max.y, max.1)
|
|
}
|
|
|
|
/// #90: a polyline's extents enclose its drawn band, whether the width is constant or per segment.
|
|
#[test]
|
|
fn wide_polyline_extents_follow_the_band() {
|
|
// The issue's cases: constant width, the same band from per-segment widths, a taper,
|
|
// two segments of different widths, and a width on the open end vertex (which draws nothing).
|
|
let b = wide_pline(&[(0.0, 0.0, 0.0, 0.0, 0.0), (10.0, 0.0, 0.0, 0.0, 0.0)], false, 2.0);
|
|
assert!(near_bounds(b, (0.0, -1.0), (10.0, 1.0)), "{b:?}");
|
|
let b = wide_pline(&[(0.0, 0.0, 2.0, 2.0, 0.0), (10.0, 0.0, 2.0, 2.0, 0.0)], false, 0.0);
|
|
assert!(near_bounds(b, (0.0, -1.0), (10.0, 1.0)), "{b:?}");
|
|
let b = wide_pline(&[(0.0, 0.0, 0.0, 4.0, 0.0), (10.0, 0.0, 4.0, 0.0, 0.0)], false, 0.0);
|
|
assert!(near_bounds(b, (0.0, -2.0), (10.0, 2.0)), "{b:?}");
|
|
let b = wide_pline(&[(0.0, 0.0, 2.0, 2.0, 0.0), (10.0, 0.0, 6.0, 6.0, 0.0), (20.0, 0.0, 0.0, 0.0, 0.0)], false, 0.0);
|
|
assert!(near_bounds(b, (0.0, -3.0), (20.0, 3.0)), "{b:?}");
|
|
let b = wide_pline(&[(0.0, 0.0, 0.0, 0.0, 0.0), (10.0, 0.0, 6.0, 6.0, 0.0)], false, 0.0);
|
|
assert!(near_bounds(b, (0.0, 0.0), (10.0, 0.0)), "{b:?}");
|
|
// A diagonal band: its exact corners, not the centre line padded by the half-width.
|
|
let h = std::f64::consts::FRAC_1_SQRT_2;
|
|
let b = wide_pline(&[(0.0, 0.0, 0.0, 0.0, 0.0), (10.0, 10.0, 0.0, 0.0, 0.0)], false, 2.0);
|
|
assert!(near_bounds(b, (-h, -h), (10.0 + h, 10.0 + h)), "{b:?}");
|
|
// A closed polyline's closing segment counts (here the only wide one).
|
|
let sq = [(0.0, 0.0, 0.0, 0.0, 0.0), (10.0, 0.0, 0.0, 0.0, 0.0), (10.0, 10.0, 0.0, 0.0, 0.0), (0.0, 10.0, 2.0, 2.0, 0.0)];
|
|
assert!(near_bounds(wide_pline(&sq, true, 0.0), (-1.0, 0.0), (10.0, 10.0)));
|
|
assert!(near_bounds(wide_pline(&sq, false, 0.0), (0.0, 0.0), (10.0, 10.0)), "open: the end vertex starts no segment");
|
|
// Non-finite or negative widths count as zero.
|
|
let b = wide_pline(&[(0.0, 0.0, f64::NAN, f64::INFINITY, 0.0), (10.0, 0.0, 0.0, 0.0, 0.0)], false, 0.0);
|
|
assert!(near_bounds(b, (0.0, 0.0), (10.0, 0.0)), "{b:?}");
|
|
let b = wide_pline(&[(0.0, 0.0, -4.0, -4.0, 0.0), (10.0, 0.0, 0.0, 0.0, 0.0)], false, 0.0);
|
|
assert!(near_bounds(b, (0.0, 0.0), (10.0, 0.0)), "{b:?}");
|
|
}
|
|
|
|
/// A wide arc segment reaches its outer edge (radius plus half the width), never less, and only
|
|
/// by the sampling tolerance more.
|
|
#[test]
|
|
fn wide_arc_segment_extents_reach_the_outer_edge() {
|
|
// A half circle below the axis (bulge 1, counter-clockwise from (0,0) to (10,0)), radius 5.
|
|
let exact_min = (-1.0, -6.0);
|
|
let exact_max = (11.0, 0.0);
|
|
for b in [
|
|
wide_pline(&[(0.0, 0.0, 0.0, 0.0, 1.0), (10.0, 0.0, 0.0, 0.0, 0.0)], false, 2.0),
|
|
wide_pline(&[(0.0, 0.0, 2.0, 2.0, 1.0), (10.0, 0.0, 0.0, 0.0, 0.0)], false, 0.0),
|
|
] {
|
|
assert!(b.min.x <= exact_min.0 && b.min.y <= exact_min.1 && b.max.x >= exact_max.0 && b.max.y >= exact_max.1, "never smaller: {b:?}");
|
|
assert!(
|
|
b.min.x > exact_min.0 - 1e-3 && b.min.y > exact_min.1 - 1e-3 && b.max.x < exact_max.0 + 1e-3 && b.max.y < exact_max.1 + 1e-3,
|
|
"tight: {b:?}"
|
|
);
|
|
}
|
|
// A taper along the arc (0 to 4 wide): compared with a brute-force sampling of both band
|
|
// edges, since the extremes of a tapering curve lie between the obvious points.
|
|
let b = wide_pline(&[(0.0, 0.0, 0.0, 4.0, 1.0), (10.0, 0.0, 0.0, 0.0, 0.0)], false, 0.0);
|
|
let mut exact = Bounds2::EMPTY;
|
|
for k in 0..=100_000 {
|
|
let t = f64::from(k) / 100_000.0;
|
|
// Counter-clockwise from angle pi (the start, (0,0)) to 2 pi (the end, (10,0)).
|
|
let a = std::f64::consts::PI * (1.0 + t);
|
|
let dir = Vec2::new(a.cos(), a.sin());
|
|
let h = 2.0 * t;
|
|
exact.add(Vec2::new(5.0, 0.0) + dir * (5.0 + h));
|
|
exact.add(Vec2::new(5.0, 0.0) + dir * (5.0 - h));
|
|
}
|
|
for (got, want, outward) in [(b.min.x, exact.min.x, -1.0), (b.min.y, exact.min.y, -1.0), (b.max.x, exact.max.x, 1.0), (b.max.y, exact.max.y, 1.0)]
|
|
{
|
|
let over = (got - want) * outward;
|
|
assert!(over > -1e-6 && over < 1e-3, "got {b:?}, exact {exact:?}");
|
|
}
|
|
}
|
|
|
|
fn plain_insert(block: &str) -> EntityKind {
|
|
EntityKind::Insert(Insert {
|
|
block: block.into(),
|
|
insert: Vec3::ZERO,
|
|
scale: Vec3::new(1.0, 1.0, 1.0),
|
|
rotation: 0.0,
|
|
attribs: vec![],
|
|
cols: 1,
|
|
rows: 1,
|
|
col_spacing: 0.0,
|
|
row_spacing: 0.0,
|
|
})
|
|
}
|
|
|
|
fn on_layer(layer: &str, kind: EntityKind, h: u64) -> Entity {
|
|
Entity { handle: Handle(h), common: Common { layer: layer.into(), ..Default::default() }, kind }
|
|
}
|
|
|
|
/// Model space with one insert of a block holding a near line (0,0)-(1,1) plus `extra`.
|
|
fn drawing_with_block(extra: Entity) -> Drawing {
|
|
let mut d = Drawing::new_imperial();
|
|
let mut blk = Block::new("B");
|
|
blk.entities.push(Entity::new(Handle(1), line((0.0, 0.0), (1.0, 1.0))));
|
|
blk.entities.push(extra);
|
|
d.blocks.insert("B".into(), std::sync::Arc::new(blk));
|
|
d.add(&Space::Model, Common::default(), plain_insert("B")).unwrap();
|
|
d
|
|
}
|
|
|
|
fn far_line(layer: &str) -> Entity {
|
|
on_layer(layer, line((5000.0, 5000.0), (6000.0, 6000.0)), 2)
|
|
}
|
|
|
|
fn extents_pair(d: &Drawing) -> (Vec2, Vec2) {
|
|
let b = d.extents(&Space::Model);
|
|
(b.min, b.max)
|
|
}
|
|
|
|
/// #706: block A reaches B once through a chain of 14 blocks (B at depth 15, its line's block E at
|
|
/// 16, past the limit) and once directly (B at depth 1). `children` picks which of the two A holds.
|
|
fn deep_and_shallow(children: &[&str]) -> Drawing {
|
|
let mut d = Drawing::new_imperial();
|
|
let mut add = |name: &str, kids: Vec<EntityKind>| {
|
|
let mut blk = Block::new(name);
|
|
for (i, k) in kids.into_iter().enumerate() {
|
|
blk.entities.push(Entity::new(Handle(i as u64 + 1), k));
|
|
}
|
|
d.blocks.insert(name.into(), std::sync::Arc::new(blk));
|
|
};
|
|
add("E", vec![line((1000.0, 1000.0), (2000.0, 1000.0))]);
|
|
add("B", vec![plain_insert("E")]);
|
|
for i in 1..=14 {
|
|
let next = if i == 14 { "B".to_string() } else { format!("C{}", i + 1) };
|
|
add(&format!("C{i}"), vec![plain_insert(&next)]);
|
|
}
|
|
add("A", children.iter().map(|c| plain_insert(c)).collect());
|
|
d.add(&Space::Model, Common::default(), plain_insert("A")).unwrap();
|
|
d
|
|
}
|
|
|
|
#[test]
|
|
fn deep_block_visit_does_not_hide_a_shallow_reference() {
|
|
let line = (Vec2::new(1000.0, 1000.0), Vec2::new(2000.0, 1000.0));
|
|
// Control: past the limit, the deep path alone contributes nothing.
|
|
assert!(deep_and_shallow(&["C1"]).extents(&Space::Model).is_empty());
|
|
assert_eq!(extents_pair(&deep_and_shallow(&["B"])), line);
|
|
assert_eq!(extents_pair(&deep_and_shallow(&["B", "C1"])), line);
|
|
assert_eq!(extents_pair(&deep_and_shallow(&["C1", "B"])), line);
|
|
}
|
|
|
|
#[test]
|
|
fn block_entity_with_invisible_flag_is_ignored_in_insert_extents() {
|
|
let mut far = far_line("0");
|
|
far.common.visible = false;
|
|
let d = drawing_with_block(far);
|
|
assert_eq!(extents_pair(&d), (Vec2::ZERO, Vec2::new(1.0, 1.0)));
|
|
// Control: the same entity visible does count.
|
|
let d = drawing_with_block(far_line("0"));
|
|
assert_eq!(d.extents(&Space::Model).max, Vec2::new(6000.0, 6000.0));
|
|
}
|
|
|
|
#[test]
|
|
fn block_entity_on_off_or_frozen_layer_is_ignored() {
|
|
for frozen in [false, true] {
|
|
let mut d = drawing_with_block(far_line("Hidden"));
|
|
assert!(d.layer("Hidden").is_none(), "block contents do not create layers");
|
|
let mut layer = Layer::new("Hidden");
|
|
if frozen {
|
|
layer.frozen = true;
|
|
} else {
|
|
layer.on = false;
|
|
}
|
|
d.layers.push(layer);
|
|
assert_eq!(extents_pair(&d), (Vec2::ZERO, Vec2::new(1.0, 1.0)), "frozen = {frozen}");
|
|
// Turned back on, it counts again.
|
|
let l = d.layer_mut("Hidden").unwrap();
|
|
l.on = true;
|
|
l.frozen = false;
|
|
assert_eq!(d.extents(&Space::Model).max, Vec2::new(6000.0, 6000.0));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn block_entity_on_layer_zero_counts_even_if_layer_zero_is_off_or_missing() {
|
|
// Layer "0" takes the insert's layer, which the caller has already checked; so it counts here.
|
|
// The insert itself sits on layer "Vis".
|
|
let mut d = Drawing::new_imperial();
|
|
d.add(&Space::Model, Common { layer: "Vis".into(), ..Default::default() }, plain_insert("B")).unwrap();
|
|
let mut blk = Block::new("B");
|
|
blk.entities.push(far_line("0"));
|
|
d.blocks.insert("B".into(), std::sync::Arc::new(blk));
|
|
// Layer 0 switched off.
|
|
d.layer_mut("0").unwrap().on = false;
|
|
assert_eq!(d.extents(&Space::Model).max, Vec2::new(6000.0, 6000.0));
|
|
// Layer 0 frozen.
|
|
let l = d.layer_mut("0").unwrap();
|
|
l.on = true;
|
|
l.frozen = true;
|
|
assert_eq!(d.extents(&Space::Model).max, Vec2::new(6000.0, 6000.0));
|
|
// Layer 0 not defined at all.
|
|
d.layers.retain(|l| l.name != "0");
|
|
assert!(d.layer("0").is_none());
|
|
assert_eq!(d.extents(&Space::Model).max, Vec2::new(6000.0, 6000.0));
|
|
}
|
|
|
|
#[test]
|
|
fn block_entity_on_undefined_layer_counts() {
|
|
// An unknown layer name is not hidden (is_none_or).
|
|
let d = drawing_with_block(far_line("Nowhere"));
|
|
assert!(d.layer("Nowhere").is_none());
|
|
assert_eq!(d.extents(&Space::Model).max, Vec2::new(6000.0, 6000.0));
|
|
}
|
|
|
|
#[test]
|
|
fn attdef_in_block_is_ignored_in_insert_extents() {
|
|
let attdef = Entity::new(
|
|
Handle(2),
|
|
EntityKind::AttDef(Attrib {
|
|
tag: "TAG".into(),
|
|
text: Text {
|
|
insert: Vec3::new(5000.0, 5000.0, 0.0),
|
|
align_pt: None,
|
|
height: 1.0,
|
|
value: "x".into(),
|
|
rotation: 0.0,
|
|
width_factor: 1.0,
|
|
oblique: 0.0,
|
|
style: "Standard".into(),
|
|
halign: HAlign::Left,
|
|
valign: VAlign::Baseline,
|
|
},
|
|
invisible: false,
|
|
constant: false,
|
|
prompt: String::new(),
|
|
props: Default::default(),
|
|
multiline: None,
|
|
modes: Default::default(),
|
|
}),
|
|
);
|
|
let d = drawing_with_block(attdef);
|
|
assert_eq!(extents_pair(&d), (Vec2::ZERO, Vec2::new(1.0, 1.0)));
|
|
}
|
|
|
|
fn text_kind(value: &str) -> EntityKind {
|
|
EntityKind::Text(Text {
|
|
insert: Vec3::new(10.0, 10.0, 0.0),
|
|
align_pt: None,
|
|
height: 2.0,
|
|
value: value.into(),
|
|
rotation: 0.0,
|
|
width_factor: 1.0,
|
|
oblique: 0.0,
|
|
style: "Standard".into(),
|
|
halign: HAlign::Left,
|
|
valign: VAlign::Baseline,
|
|
})
|
|
}
|
|
|
|
fn mtext_kind(contents: &str) -> EntityKind {
|
|
EntityKind::MText(MText {
|
|
insert: Vec3::new(10.0, 10.0, 0.0),
|
|
height: 2.0,
|
|
width: 0.0,
|
|
attach: 1,
|
|
rotation: 0.0,
|
|
style: "Standard".into(),
|
|
contents: contents.into(),
|
|
line_spacing: 1.0,
|
|
line_spacing_exact: false,
|
|
columns: None,
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn blank_text_and_mtext_have_no_bounds() {
|
|
let d = Drawing::new_imperial();
|
|
for s in ["", " ", " ", "\t \n", "\\~", " \\~ \\~", "\u{a0}"] {
|
|
for kind in [text_kind(s), mtext_kind(s)] {
|
|
let b = entity_bounds(&d, &Entity::new(Handle(1), kind), 0);
|
|
assert!(b.is_empty(), "{s:?} should have empty bounds");
|
|
}
|
|
}
|
|
// Blank texts do not stretch the drawing extents either.
|
|
let mut d = Drawing::new_imperial();
|
|
d.add(&Space::Model, Common::default(), line((0.0, 0.0), (1.0, 1.0))).unwrap();
|
|
d.add(&Space::Model, Common::default(), text_kind(" \\~")).unwrap();
|
|
d.add(&Space::Model, Common::default(), mtext_kind("")).unwrap();
|
|
assert_eq!(extents_pair(&d), (Vec2::ZERO, Vec2::new(1.0, 1.0)));
|
|
}
|
|
|
|
#[test]
|
|
fn non_blank_text_and_mtext_have_bounds() {
|
|
let d = Drawing::new_imperial();
|
|
for kind in [text_kind("Hi"), text_kind(" a "), mtext_kind("Hi"), mtext_kind("a\\Pb"), mtext_kind("\\~x")] {
|
|
let b = entity_bounds(&d, &Entity::new(Handle(1), kind.clone()), 0);
|
|
assert!(!b.is_empty(), "{kind:?} should have bounds");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn invisible_dimblock_entity_is_ignored_in_dimension_extents() {
|
|
// Dynamic blocks keep dimensional constraints on layer *ADSK_CONSTRAINTS (group 60 = 1).
|
|
let dim = |d: &mut Drawing, far: Entity| {
|
|
let mut blk = Block::new("*D1");
|
|
blk.entities.push(Entity::new(Handle(1), line((0.0, 0.0), (1.0, 0.0))));
|
|
blk.entities.push(far);
|
|
d.blocks.insert("*D1".into(), std::sync::Arc::new(blk));
|
|
d.add(
|
|
&Space::Model,
|
|
Common::default(),
|
|
EntityKind::Dimension(Dimension {
|
|
kind: DimKind::Linear { rotation: 0.0 },
|
|
defpt: Vec3::new(1.0, 1.0, 0.0),
|
|
text_mid: Vec3::new(0.5, 1.0, 0.0),
|
|
p13: Vec3::ZERO,
|
|
p14: Vec3::new(1.0, 0.0, 0.0),
|
|
p15: Vec3::ZERO,
|
|
p16: Vec3::ZERO,
|
|
text: String::new(),
|
|
style: "Standard".into(),
|
|
measurement: 1.0,
|
|
text_rotation: 0.0,
|
|
user_text_pos: false,
|
|
block: Some("*D1".into()),
|
|
overrides: Default::default(),
|
|
assoc: vec![],
|
|
marks: Default::default(),
|
|
}),
|
|
)
|
|
.unwrap();
|
|
};
|
|
// Invisible flag.
|
|
let mut d = Drawing::new_imperial();
|
|
let mut hidden = on_layer("*ADSK_CONSTRAINTS", line((5000.0, 5000.0), (6000.0, 6000.0)), 2);
|
|
hidden.common.visible = false;
|
|
dim(&mut d, hidden);
|
|
assert!(d.extents(&Space::Model).max.x < 100.0);
|
|
// Hidden by its layer state.
|
|
let mut d = Drawing::new_imperial();
|
|
let mut l = Layer::new("*ADSK_CONSTRAINTS");
|
|
l.on = false;
|
|
d.layers.push(l);
|
|
dim(&mut d, on_layer("*ADSK_CONSTRAINTS", line((5000.0, 5000.0), (6000.0, 6000.0)), 2));
|
|
assert!(d.extents(&Space::Model).max.x < 100.0);
|
|
// Control: a visible far entity does count.
|
|
let mut d = Drawing::new_imperial();
|
|
dim(&mut d, far_line("0"));
|
|
assert_eq!(d.extents(&Space::Model).max, Vec2::new(6000.0, 6000.0));
|
|
}
|
|
|
|
#[test]
|
|
fn space_names_its_tab() {
|
|
assert!(!Space::Model.is_paper());
|
|
assert_eq!(Space::Model.layout(), None);
|
|
assert_eq!(Space::Model.tab_name(), "Model");
|
|
let p = Space::Paper("Sheet A".into());
|
|
assert!(p.is_paper());
|
|
assert_eq!(p.layout(), Some("Sheet A"));
|
|
assert_eq!(p.tab_name(), "Sheet A");
|
|
}
|