mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-25 03:16:50 -06:00
* 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
187 lines
7.4 KiB
Rust
187 lines
7.4 KiB
Rust
//! Dimensions take viewport layer colours, leader arrows their style's DIMSCALE, attributes
|
|
//! their own properties (issue #313).
|
|
|
|
use std::sync::Arc;
|
|
|
|
use cadcraft_color::{Color, Rgb};
|
|
use cadcraft_doc::{
|
|
Attrib, Block, Common, DimKind, DimStyle, Dimension, Drawing, Entity, EntityKind, HAlign, Handle, Insert, Leader, Line, Lineweight, Space, Text,
|
|
VAlign, Viewport,
|
|
};
|
|
use cadcraft_geom::{Bounds2, Vec2, Vec3};
|
|
use cadcraft_render::{Kind, Options, VIEWPORT_CONTENT, build};
|
|
|
|
fn rgb(i: u8) -> Rgb {
|
|
Color::Index(i).resolve(Color::Index(7), Color::Index(7))
|
|
}
|
|
|
|
fn layer(d: &mut Drawing, name: &str, color: u8, lw: Lineweight) {
|
|
d.ensure_layer(name);
|
|
let l = d.layer_mut(name).unwrap();
|
|
l.color = Color::Index(color);
|
|
l.lineweight = lw;
|
|
}
|
|
|
|
fn on(layer: &str) -> Common {
|
|
Common { layer: layer.into(), ..Default::default() }
|
|
}
|
|
|
|
#[test]
|
|
fn dimensions_take_the_viewport_layer_colour() {
|
|
let mut d = Drawing::new_imperial();
|
|
layer(&mut d, "A", 1, Lineweight::ByLayer);
|
|
let dim = Dimension {
|
|
kind: DimKind::Linear { rotation: 0.0 },
|
|
defpt: Vec3::new(0.0, 2.0, 0.0),
|
|
text_mid: Vec3::ZERO,
|
|
p13: Vec3::ZERO,
|
|
p14: Vec3::new(10.0, 0.0, 0.0),
|
|
p15: Vec3::ZERO,
|
|
p16: Vec3::ZERO,
|
|
text: String::new(),
|
|
style: "Standard".into(),
|
|
measurement: 0.0,
|
|
text_rotation: 0.0,
|
|
user_text_pos: false,
|
|
block: None,
|
|
overrides: Default::default(),
|
|
assoc: Vec::new(),
|
|
marks: Default::default(),
|
|
};
|
|
d.add(&Space::Model, on("A"), EntityKind::Dimension(dim)).unwrap();
|
|
d.add(&Space::Model, on("A"), EntityKind::Line(Line { a: Vec3::new(0.0, -1.0, 0.0), b: Vec3::new(10.0, -1.0, 0.0) })).unwrap();
|
|
let vp = Viewport {
|
|
center: Vec3::new(10.0, 10.0, 0.0),
|
|
width: 20.0,
|
|
height: 10.0,
|
|
view_center: Vec2::new(5.0, 1.0),
|
|
view_height: 10.0,
|
|
id: 2,
|
|
locked: false,
|
|
frozen_layers: Vec::new(),
|
|
layer_colors: vec![("a".into(), Color::Index(3))],
|
|
clip: None,
|
|
on: true,
|
|
};
|
|
d.add(&Space::Paper("Layout1".into()), Common::default(), EntityKind::Viewport(vp)).unwrap();
|
|
let l = build(&d, &Space::Paper("Layout1".into()), &Options::default());
|
|
let seen: Vec<Rgb> = l.prims.iter().filter(|p| p.handle == VIEWPORT_CONTENT).map(|p| p.color).collect();
|
|
assert!(seen.iter().filter(|c| **c == rgb(3)).count() > 3, "dimension lines, arrows and text: {seen:?}");
|
|
assert!(seen.iter().all(|c| *c == rgb(3)), "everything on layer A is green in the viewport: {seen:?}");
|
|
// Model space keeps the layer's own colour.
|
|
let m = build(&d, &Space::Model, &Options::default());
|
|
assert!(m.prims.iter().all(|p| p.color == rgb(1)));
|
|
}
|
|
|
|
/// Length of the leader's arrowhead along the leader (x).
|
|
fn arrow_length(d: &Drawing) -> f64 {
|
|
let l = build(d, &Space::Model, &Options::default());
|
|
let tris: Vec<Vec2> = l.prims.iter().filter(|p| p.kind == Kind::Tris).flat_map(|p| l.points(p).to_vec()).collect();
|
|
Bounds2::from_points(tris).width()
|
|
}
|
|
|
|
#[test]
|
|
fn leader_arrows_use_their_style_dimscale() {
|
|
let mut d = Drawing::new_imperial();
|
|
d.dim_styles.push(DimStyle { name: "BIG".into(), scale: 10.0, arrow_size: 0.18, ..DimStyle::default() });
|
|
let leader = Leader { vertices: vec![Vec3::ZERO, Vec3::new(5.0, 0.0, 0.0)], arrow: true, spline: false, style: "BIG".into() };
|
|
d.add(&Space::Model, Common::default(), EntityKind::Leader(leader)).unwrap();
|
|
d.header.set_f64("DIMSCALE", 1.0);
|
|
assert!((arrow_length(&d) - 1.8).abs() < 1e-6, "{}", arrow_length(&d));
|
|
// A style DIMSCALE of 0 falls back to the drawing's.
|
|
d.dim_styles.iter_mut().filter(|s| s.name == "BIG").for_each(|s| s.scale = 0.0);
|
|
d.header.set_f64("DIMSCALE", 2.0);
|
|
assert!((arrow_length(&d) - 0.36).abs() < 1e-6, "{}", arrow_length(&d));
|
|
// Both 0: never a zero-size arrow.
|
|
d.header.set_f64("DIMSCALE", 0.0);
|
|
assert!((arrow_length(&d) - 0.18).abs() < 1e-6, "{}", arrow_length(&d));
|
|
}
|
|
|
|
fn text(x: f64, value: &str) -> Text {
|
|
Text {
|
|
insert: Vec3::new(x, 0.0, 0.0),
|
|
align_pt: None,
|
|
height: 1.0,
|
|
value: value.into(),
|
|
rotation: 0.0,
|
|
width_factor: 1.0,
|
|
oblique: 0.0,
|
|
style: "Standard".into(),
|
|
halign: HAlign::Left,
|
|
valign: VAlign::Baseline,
|
|
}
|
|
}
|
|
|
|
fn attdef(handle: u64, tag: &str, x: f64, common: Common) -> Entity {
|
|
let a = Attrib {
|
|
tag: tag.into(),
|
|
text: text(x, tag),
|
|
invisible: false,
|
|
constant: false,
|
|
prompt: String::new(),
|
|
props: Default::default(),
|
|
multiline: None,
|
|
modes: Default::default(),
|
|
};
|
|
Entity { common, ..Entity::new(Handle(handle), EntityKind::AttDef(a)) }
|
|
}
|
|
|
|
/// Colour and lineweight of the attribute text drawn between x0 and x0 + 5 (none = not drawn).
|
|
fn attrib_at(d: &Drawing, x0: f64) -> Option<(Rgb, f32)> {
|
|
let l = build(d, &Space::Model, &Options { lineweights: true, ..Options::default() });
|
|
let mut it = l.prims.iter().filter(|p| l.points(p).iter().all(|q| q.x >= x0 - 0.5 && q.x < x0 + 5.0)).map(|p| (p.color, p.lw));
|
|
let first = it.next()?;
|
|
assert!(it.all(|x| x == first), "one colour and lineweight per attribute");
|
|
Some(first)
|
|
}
|
|
|
|
#[test]
|
|
fn attributes_draw_with_their_own_properties() {
|
|
let mut d = Drawing::new_imperial();
|
|
layer(&mut d, "TXT", 2, Lineweight::Mm100(50));
|
|
layer(&mut d, "INS", 5, Lineweight::Mm100(18));
|
|
let mut blk = Block::new("TAGS");
|
|
// ByLayer on its own layer; ByBlock; ByLayer on layer 0 (= the reference's layer); explicit.
|
|
blk.entities.push(attdef(0x10, "BYLAYER", 0.0, on("TXT")));
|
|
blk.entities.push(attdef(0x11, "BYBLOCK", 10.0, Common { color: Color::ByBlock, lineweight: Lineweight::ByBlock, ..on("0") }));
|
|
blk.entities.push(attdef(0x12, "LAYER0", 20.0, on("0")));
|
|
blk.entities.push(attdef(0x13, "OWN", 30.0, Common { color: Color::Index(6), lineweight: Lineweight::Mm100(70), ..on("TXT") }));
|
|
d.blocks.insert(blk.name.clone(), Arc::new(blk));
|
|
let attribs = [("bylayer", 0.0), ("ByBlock", 10.0), ("LAYER0", 20.0), ("OWN", 30.0), ("NODEF", 40.0)]
|
|
.map(|(tag, x)| Attrib {
|
|
tag: tag.into(),
|
|
text: text(x, "X"),
|
|
invisible: false,
|
|
constant: false,
|
|
prompt: String::new(),
|
|
props: Default::default(),
|
|
multiline: None,
|
|
modes: Default::default(),
|
|
})
|
|
.to_vec();
|
|
let ins = Insert {
|
|
block: "TAGS".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 common = Common { color: Color::Index(1), lineweight: Lineweight::Mm100(35), ..on("INS") };
|
|
d.add(&Space::Model, common, EntityKind::Insert(ins)).unwrap();
|
|
|
|
assert_eq!(attrib_at(&d, 0.0), Some((rgb(2), 0.5)), "ByLayer: the attribute's layer");
|
|
assert_eq!(attrib_at(&d, 10.0), Some((rgb(1), 0.35)), "ByBlock: the block reference's");
|
|
assert_eq!(attrib_at(&d, 20.0), Some((rgb(5), 0.18)), "layer 0: the block reference's layer");
|
|
assert_eq!(attrib_at(&d, 30.0), Some((rgb(6), 0.7)), "explicit");
|
|
assert_eq!(attrib_at(&d, 40.0), Some((rgb(1), 0.35)), "no definition: the block reference's");
|
|
|
|
// An attribute on a layer that is off is hidden; the others stay.
|
|
d.layer_mut("TXT").unwrap().on = false;
|
|
assert_eq!(attrib_at(&d, 0.0), None);
|
|
assert_eq!(attrib_at(&d, 10.0), Some((rgb(1), 0.35)));
|
|
}
|