Files
cadcraft/crates/engine/tests/dim_vars.rs

283 lines
12 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Dimension style variables beyond the basics: fit (DIMATFIT, DIMTIX, DIMSOXD, DIMTOFL),
//! suppressed dimension line halves (DIMSD1/2), fixed-length extension lines (DIMFXL/DIMFXLON),
//! DIMTVP, DIMTMOVE, DIMTOLJ, DIMAZIN, DIMARCSYM, DIMLDRBLK and the dimension and extension
//! line linetypes and lineweights. They are SETVAR overrides, DIMOVERRIDE values and style fields.
use cadcraft_engine::Session;
use cadcraft_engine::doc::{DimStyle, Dimension, EntityKind, Linetype};
use cadcraft_engine::geom::Vec2;
use cadcraft_engine::render::{DimGeometry, LineRole};
use serde_json::{Value, json};
fn set(s: &mut Session, name: &str, value: Value) {
s.execute("setvar", &json!({ "name": name, "value": value })).unwrap();
}
fn last_dim(s: &Session) -> (Dimension, DimGeometry) {
let d = s.doc().unwrap();
let dm = d
.model
.iter()
.filter_map(|e| match &e.kind {
EntityKind::Dimension(dm) => Some(dm.clone()),
_ => None,
})
.last()
.unwrap();
let g = cadcraft_engine::render::dimension_in(d, &dm);
(dm, g)
}
fn dim_lines(g: &DimGeometry) -> Vec<Vec<Vec2>> {
g.lines_of(LineRole::Dim).cloned().collect()
}
/// Whether a dimension line on y = `y` covers `x`.
fn covers(g: &DimGeometry, y: f64, x: f64) -> bool {
dim_lines(g)
.iter()
.any(|l| l.windows(2).any(|w| (w[0].y - y).abs() < 1e-9 && (w[1].y - y).abs() < 1e-9 && w[0].x.min(w[1].x) < x && x < w[0].x.max(w[1].x)))
}
fn linear(s: &mut Session, x2: f64) {
s.execute("dimlinear", &json!({ "p1": [0, 0], "p2": [x2, 0], "at": [x2 / 2.0, 3] })).unwrap();
}
#[test]
fn new_variables_are_style_overrides_and_read_back() {
let mut s = Session::new();
for (name, v) in [
("DIMTOFL", json!(1)),
("DIMATFIT", json!(1)),
("DIMTIX", json!(1)),
("DIMSOXD", json!(1)),
("DIMTMOVE", json!(2)),
("DIMTVP", json!(1.5)),
("DIMTOLJ", json!(0)),
("DIMAZIN", json!(2)),
("DIMARCSYM", json!(1)),
("DIMLWD", json!(50)),
("DIMLWE", json!("ByLayer")),
("DIMFXLON", json!(1)),
("DIMFXL", json!(0.5)),
("DIMJOGANG", json!(60)),
("DIMSD1", json!(1)),
] {
set(&mut s, name, v);
}
let o = s.doc().unwrap().dim_overrides();
assert_eq!(o.get("forceLineInside"), Some(&json!(true)));
assert_eq!(o.get("fit"), Some(&json!(1)));
assert_eq!(o.get("textMove"), Some(&json!(2)));
assert_eq!(o.get("tolJust"), Some(&json!(0)));
assert_eq!(o.get("dimLineweight"), Some(&json!(50)));
assert_eq!(o.get("extLineweight"), Some(&json!(-1)));
assert_eq!(o.get("jogAngle"), Some(&json!(60.0)));
assert_eq!(o.get("suppressDim1"), Some(&json!(true)));
// A new dimension takes the overrides.
linear(&mut s, 10.0);
let (dm, _) = last_dim(&s);
let st = DimStyle::default().with_overrides(&dm.overrides);
assert!(st.force_line_inside && st.text_inside && st.suppress_outside_arrows && st.fixed_ext && st.suppress_dim1);
assert_eq!((st.fit, st.text_move, st.arc_symbol, st.angular_zero_suppression), (1, 2, 1, 2));
// Out-of-range values are refused and leave the style alone.
let mut st = DimStyle::default();
let bad = st.apply_fields(&serde_json::Map::from_iter([
("DIMATFIT".to_string(), json!(7)),
("DIMTMOVE".to_string(), json!(3)),
("DIMLWD".to_string(), json!(500)),
("DIMJOGANG".to_string(), json!(120)),
("DIMFXL".to_string(), json!(-1)),
]));
assert_eq!(bad.len(), 5, "{bad:?}");
assert_eq!(st, DimStyle::default());
// DIMOVERRIDE takes them per dimension.
let h = s.doc().unwrap().model.last().unwrap().handle.hex();
s.execute("dimoverride", &json!({ "handles": [h], "DIMTOFL": 0, "DIMLTYPE": "DASHED" })).unwrap();
let (dm, _) = last_dim(&s);
let st = DimStyle::default().with_overrides(&dm.overrides);
assert!(!st.force_line_inside && st.text_inside);
assert_eq!(st.dim_linetype, "DASHED");
}
#[test]
fn fit_moves_text_or_arrows_outside() {
// 0.5 between the extension lines: the text "0.5000" doesn't fit, the arrows (2 × 0.18) do.
let mut s = Session::new();
linear(&mut s, 0.5);
let (_, g) = last_dim(&s);
assert!(g.text_pos.x > 0.5, "best fit: text outside, {:?}", g.text_pos);
assert!(covers(&g, 3.0, 0.25), "arrows inside: the line runs between them");
// DIMATFIT 0: both outside; no line between the extension lines without DIMTOFL.
set(&mut s, "DIMATFIT", json!(0));
linear(&mut s, 0.5);
let (_, g) = last_dim(&s);
assert!(g.text_pos.x > 0.5);
assert!(!covers(&g, 3.0, 0.25));
assert!(covers(&g, 3.0, -0.2) && covers(&g, 3.0, 0.7), "stubs outside both extension lines");
// DIMTOFL draws it anyway.
set(&mut s, "DIMTOFL", json!(1));
linear(&mut s, 0.5);
let (_, g) = last_dim(&s);
assert!(covers(&g, 3.0, 0.25));
// DIMTIX keeps the text between the extension lines; DIMSOXD drops the outside arrows.
set(&mut s, "DIMTOFL", json!(0));
set(&mut s, "DIMTIX", json!(1));
linear(&mut s, 0.5);
let (_, g) = last_dim(&s);
assert!((g.text_pos.x - 0.25).abs() < 1e-9, "{:?}", g.text_pos);
assert_eq!(g.fills.len(), 2, "arrows outside");
set(&mut s, "DIMSOXD", json!(1));
linear(&mut s, 0.5);
let (_, g) = last_dim(&s);
assert!(g.fills.is_empty(), "outside arrows suppressed");
// DIMATFIT 1 (arrows first): text that fits alone stays inside, the arrows go out.
let mut s = Session::new();
set(&mut s, "DIMATFIT", json!(1));
set(&mut s, "DIMDEC", json!(0));
linear(&mut s, 0.5);
let (_, g) = last_dim(&s);
assert!((g.text_pos.x - 0.25).abs() < 1e-9, "{:?}", g.text_pos);
assert!(g.fills.iter().flatten().any(|p| p.x < 0.0) && g.fills.iter().flatten().any(|p| p.x > 0.5));
}
#[test]
fn suppressed_halves_fixed_extension_lines_and_vertical_position() {
let mut s = Session::new();
set(&mut s, "DIMTAD", json!(1));
set(&mut s, "DIMSD1", json!(1));
linear(&mut s, 10.0);
let (_, g) = last_dim(&s);
assert!(!covers(&g, 3.0, 2.0) && covers(&g, 3.0, 8.0));
assert_eq!(g.fills.len(), 1, "only the second arrowhead");
// Fixed-length extension lines: 0.5 from the dimension line toward the origin.
let mut s = Session::new();
set(&mut s, "DIMFXLON", json!(1));
set(&mut s, "DIMFXL", json!(0.5));
linear(&mut s, 10.0);
let (_, g) = last_dim(&s);
for l in g.lines_of(LineRole::Ext) {
let lo = l.iter().map(|p| p.y).fold(f64::INFINITY, f64::min);
assert!((lo - 2.5).abs() < 1e-9, "{l:?}");
}
// DIMTVP lifts centred text by text heights.
let mut s = Session::new();
set(&mut s, "DIMTVP", json!(1.0));
linear(&mut s, 10.0);
let (_, g) = last_dim(&s);
assert!((g.text_pos.y - (3.0 + 0.18)).abs() < 1e-9, "{:?}", g.text_pos);
assert!(covers(&g, 3.0, 5.0), "text off the line: the line isn't broken");
}
#[test]
fn text_move_settings() {
// DIMTMOVE 1: a leader joins text moved off the line; the line stays put.
let mut s = Session::new();
set(&mut s, "DIMTMOVE", json!(1));
linear(&mut s, 10.0);
let h = s.doc().unwrap().model.last().unwrap().handle.hex();
s.execute("dimtedit", &json!({ "handles": [h], "at": [5, 8] })).unwrap();
let (dm, g) = last_dim(&s);
assert!((dm.defpt.y - 3.0).abs() < 1e-9);
assert!(dim_lines(&g).iter().any(|l| l.first().is_some_and(|p| p.dist(Vec2::new(5.0, 3.0)) < 1e-9) && l.last().is_some_and(|p| p.y > 7.0)));
// DIMTMOVE 0: the line follows (DIMTAD 1 keeps it below the text).
let mut s = Session::new();
set(&mut s, "DIMTAD", json!(1));
linear(&mut s, 10.0);
let h = s.doc().unwrap().model.last().unwrap().handle.hex();
s.execute("dimtedit", &json!({ "handles": [h], "at": [5, 8] })).unwrap();
let (dm, _) = last_dim(&s);
assert!((dm.defpt.y - (8.0 - 0.09 - 0.09)).abs() < 1e-9, "{:?}", dm.defpt);
// Moving the text grip does the same.
let grips = s.doc().unwrap().model.last().unwrap().kind.grips();
let i = grips.iter().position(|p| p.dist(dm.text_mid.xy()) < 1e-9).unwrap();
s.execute("grip.stretch", &json!({ "handle": h, "index": i, "to": [5, 12] })).unwrap();
let (dm, _) = last_dim(&s);
assert!((dm.defpt.y - (12.0 - 0.18)).abs() < 1e-9, "{:?}", dm.defpt);
}
#[test]
fn tolerance_justification_angular_zeros_and_arc_symbol() {
let mut s = Session::new();
set(&mut s, "DIMTOL", json!(1));
set(&mut s, "DIMTP", json!(0.1));
set(&mut s, "DIMTM", json!(0.2));
linear(&mut s, 10.0);
let (_, middle) = last_dim(&s);
assert!(!middle.mtext.contains("\\A"));
set(&mut s, "DIMTOLJ", json!(0));
linear(&mut s, 10.0);
let (_, bottom) = last_dim(&s);
assert!(bottom.mtext.contains("\\A0;"), "{}", bottom.mtext);
// Bottom alignment: the lower tolerance sits on the baseline, higher than when centred.
let low = |g: &DimGeometry| g.text.iter().flatten().map(|p| p.y).fold(f64::INFINITY, f64::min);
assert!(low(&bottom) > low(&middle) + 1e-6);
// DIMAZIN 2 drops trailing zeros of angles.
let mut s = Session::new();
set(&mut s, "DIMADEC", json!(2));
s.execute("dimangular", &json!({ "vertex": [0, 0], "p1": [10, 0], "p2": [0, 10], "at": [3, 3] })).unwrap();
assert_eq!(last_dim(&s).1.value, "90.00°");
set(&mut s, "DIMAZIN", json!(2));
s.execute("dimangular", &json!({ "vertex": [0, 0], "p1": [10, 0], "p2": [0, 10], "at": [3, 3] })).unwrap();
assert_eq!(last_dim(&s).1.value, "90°");
// DIMARCSYM: before the text, over it, or none.
let mut s = Session::new();
s.execute("arc", &json!({ "center": [0, 0], "radius": 5, "start": 0, "end": 90 })).unwrap();
let a = s.doc().unwrap().model.last().unwrap().handle.hex();
s.execute("dimarc", &json!({ "handle": a, "at": [5, 5] })).unwrap();
let (_, g0) = last_dim(&s);
assert!(g0.value.starts_with('⌒'), "{}", g0.value);
set(&mut s, "DIMARCSYM", json!(2));
s.execute("dimarc", &json!({ "handle": a, "at": [5, 5] })).unwrap();
let (_, g2) = last_dim(&s);
assert!(!g2.value.contains('⌒'));
set(&mut s, "DIMARCSYM", json!(1));
s.execute("dimarc", &json!({ "handle": a, "at": [5, 5] })).unwrap();
let (_, g1) = last_dim(&s);
assert!(!g1.value.contains('⌒'));
assert_eq!(dim_lines(&g1).len(), dim_lines(&g2).len() + 1, "the symbol drawn over the text");
}
#[test]
fn linetypes_and_lineweights_of_dimension_and_extension_lines() {
let mut s = Session::new();
s.doc_mut().unwrap().linetypes.push(Linetype::simple("DASHED", "", &[0.5, -0.25]));
linear(&mut s, 10.0);
let opts = cadcraft_engine::render::Options { lineweights: true, ..Default::default() };
let count = |s: &Session| cadcraft_engine::render::build(s.doc().unwrap(), &cadcraft_engine::doc::Space::Model, &opts).prims.len();
let plain = count(&s);
let h = s.doc().unwrap().model.last().unwrap().handle.hex();
s.execute("dimoverride", &json!({ "handles": [h], "DIMLTEX2": "DASHED" })).unwrap();
let dashed_ext2 = count(&s);
assert!(dashed_ext2 > plain, "extension line 2 dashed: {plain} → {dashed_ext2}");
s.execute("dimoverride", &json!({ "handles": [h], "DIMLTYPE": "DASHED" })).unwrap();
assert!(count(&s) > dashed_ext2 + 4, "dimension line dashed too");
// Lineweights: 0.50 mm dimension lines, 0.13 mm extension lines.
s.execute("dimoverride", &json!({ "handles": [h], "clear": true })).unwrap();
s.execute("dimoverride", &json!({ "handles": [h], "DIMLWD": 50, "DIMLWE": 13 })).unwrap();
let dl = cadcraft_engine::render::build(s.doc().unwrap(), &cadcraft_engine::doc::Space::Model, &opts);
let lws: Vec<f32> = dl.prims.iter().filter(|p| p.kind == cadcraft_engine::render::Kind::Polyline).map(|p| p.lw).collect();
assert!(lws.contains(&0.5) && lws.contains(&0.13), "{lws:?}");
}
#[test]
fn leaders_use_the_leader_arrow() {
let mut s = Session::new();
for st in &mut s.doc_mut().unwrap().dim_styles {
st.arrow_block = "_DOT".into();
}
s.execute("leader", &json!({ "points": [[0, 0], [5, 5]], "text": "A" })).unwrap();
let tris = |s: &Session| {
let dl = cadcraft_engine::render::build(s.doc().unwrap(), &cadcraft_engine::doc::Space::Model, &Default::default());
dl.prims.iter().filter(|p| p.kind == cadcraft_engine::render::Kind::Tris).map(|p| p.len).sum::<u32>()
};
// DIMLDRBLK "" is a closed filled arrow (one triangle) whatever DIMBLK is.
assert_eq!(tris(&s), 3);
for st in &mut s.doc_mut().unwrap().dim_styles {
st.leader_arrow_block = "_DOT".into();
}
assert_eq!(tris(&s), 72, "a dot: 24 triangles");
}