mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-24 02:46:51 -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
126 lines
5.7 KiB
Rust
126 lines
5.7 KiB
Rust
//! Picking (and the hover highlight, which picks the same way) finds dimensions and multileaders
|
|
//! anywhere on their words, as plain text is found anywhere inside its box.
|
|
|
|
use cadcraft_engine::Session;
|
|
use cadcraft_engine::doc::{EntityKind, Handle, Space};
|
|
use cadcraft_engine::geom::{Bounds2, Vec2};
|
|
use cadcraft_engine::render;
|
|
use cadcraft_engine::select::pick;
|
|
use serde_json::json;
|
|
|
|
/// The last entity of `kind` and the box (centre, half size along the text, angle) of its words.
|
|
fn text_of(s: &Session, kind: &str) -> (Handle, Vec2, Vec2, f64) {
|
|
let d = s.doc().unwrap();
|
|
let e = d.model.iter().filter(|e| format!("{:?}", e.kind).starts_with(kind)).last().unwrap();
|
|
let (pts, angle): (Vec<Vec2>, f64) = match &e.kind {
|
|
EntityKind::Dimension(dm) => {
|
|
let g = render::dimension_in(d, dm);
|
|
(g.text.iter().flatten().chain(g.text_glyphs.iter().flatten().flatten()).copied().collect(), g.text_angle)
|
|
}
|
|
EntityKind::MLeader(m) => {
|
|
let t = m.text.as_ref().unwrap();
|
|
(render::layout_mtext_entity(d, t).strokes.into_iter().flatten().collect(), t.rotation)
|
|
}
|
|
_ => unreachable!(),
|
|
};
|
|
let b = Bounds2::from_points(pts.iter().map(|q| q.rotate(-angle)));
|
|
(e.handle, b.center().rotate(angle), Vec2::new(b.width() / 2.0, b.height() / 2.0), angle)
|
|
}
|
|
|
|
/// A point `(u, v)` half-sizes from the text box's centre, along and across the text.
|
|
fn at(c: Vec2, half: Vec2, angle: f64, u: f64, v: f64) -> Vec2 {
|
|
c + Vec2::new(half.x * u, half.y * v).rotate(angle)
|
|
}
|
|
|
|
#[test]
|
|
fn dimension_and_multileader_text_is_picked_anywhere_on_its_words() {
|
|
let mut s = Session::new();
|
|
s.execute("dimlinear", &json!({"p1": [0, 0], "p2": [100, 0], "at": [50, 30], "text": "TWO WORDS HERE"})).unwrap();
|
|
let (h, c, half, angle) = text_of(&s, "Dimension");
|
|
let tiny = half.y * 0.01;
|
|
let d = s.doc().unwrap();
|
|
// Between letters, at the ends and the corners of the words: all the dimension.
|
|
for (u, v) in [(0.0, 0.0), (0.33, 0.1), (-0.9, -0.8), (0.9, 0.8), (0.5, -0.5)] {
|
|
assert_eq!(pick(d, &Space::Model, at(c, half, angle, u, v), tiny), Some(h), "({u}, {v})");
|
|
}
|
|
assert_eq!(pick(d, &Space::Model, at(c, half, angle, 0.0, 6.0), tiny), None, "well above the words: nothing");
|
|
|
|
s.execute("mleader", &json!({"points": [[200, 0], [220, 20]], "text": "LEADER NOTE"})).unwrap();
|
|
let (h, c, half, angle) = text_of(&s, "MLeader");
|
|
let d = s.doc().unwrap();
|
|
for (u, v) in [(0.0, 0.0), (0.4, 0.2), (-0.8, -0.6)] {
|
|
assert_eq!(pick(d, &Space::Model, at(c, half, angle, u, v), half.y * 0.01), Some(h), "leader text ({u}, {v})");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn slanted_dimension_text_is_picked_inside_its_turned_box_only() {
|
|
let mut s = Session::new();
|
|
// Text along the dimension line (not kept horizontal), as in most surveying styles.
|
|
for name in ["DIMTIH", "DIMTOH"] {
|
|
s.execute("setvar", &json!({"name": name, "value": 0})).unwrap();
|
|
}
|
|
s.execute("dimaligned", &json!({"p1": [0, 0], "p2": [100, 100], "at": [40, 60], "text": "SLANTED LABEL"})).unwrap();
|
|
let (h, c, half, angle) = text_of(&s, "Dimension");
|
|
assert!((angle - std::f64::consts::FRAC_PI_4).abs() < 1e-6, "aligned with the 45° line: {angle}");
|
|
let d = s.doc().unwrap();
|
|
let tiny = half.y * 0.01;
|
|
assert_eq!(pick(d, &Space::Model, at(c, half, angle, 0.6, 0.0), tiny), Some(h));
|
|
// Inside the axis-aligned box around the turned words, but off them (and off the lines).
|
|
let off = at(c, half, angle, 0.3, 4.0);
|
|
let aabb = Bounds2::from_points([
|
|
at(c, half, angle, -1.0, -1.0),
|
|
at(c, half, angle, 1.0, -1.0),
|
|
at(c, half, angle, 1.0, 1.0),
|
|
at(c, half, angle, -1.0, 1.0),
|
|
]);
|
|
assert!(aabb.contains(off), "the probe sits in the loose box");
|
|
assert_ne!(pick(d, &Space::Model, off, tiny), Some(h), "but not on the words");
|
|
}
|
|
|
|
#[test]
|
|
fn a_dimension_drawn_from_its_block_is_picked_on_the_block_text() {
|
|
// As AutoCAD saves dimensions: the geometry in an anonymous block, here with the text moved
|
|
// well away from where the dimension's own geometry would set it.
|
|
let mut s = Session::new();
|
|
s.execute("dimlinear", &json!({"p1": [0, 0], "p2": [100, 0], "at": [50, 30]})).unwrap();
|
|
s.execute("mtext", &json!({"at": [400, 400], "text": "MOVED TEXT", "height": 5})).unwrap();
|
|
let d = s.doc_mut().unwrap();
|
|
let text = d.model.iter().find(|e| matches!(e.kind, EntityKind::MText(_))).unwrap().as_ref().clone();
|
|
let dim = d.model.iter().find(|e| matches!(e.kind, EntityKind::Dimension(_))).unwrap().handle;
|
|
d.model.remove(text.handle);
|
|
let mut entities = cadcraft_engine::doc::EntityStore::new();
|
|
entities.push(text);
|
|
let block = cadcraft_engine::doc::Block {
|
|
xref_overlay: false,
|
|
xref_status: Default::default(),
|
|
name: "*D1".into(),
|
|
annotative: false,
|
|
base: Default::default(),
|
|
entities,
|
|
description: String::new(),
|
|
anonymous: true,
|
|
xref_path: None,
|
|
explodable: true,
|
|
units: 0,
|
|
array: None,
|
|
};
|
|
d.blocks.insert("*D1".into(), std::sync::Arc::new(block));
|
|
d.model.modify(dim, |e| {
|
|
if let EntityKind::Dimension(dm) = &mut e.kind {
|
|
dm.block = Some("*D1".into());
|
|
}
|
|
});
|
|
let d = s.doc().unwrap();
|
|
let m = render::layout_mtext_entity(
|
|
d,
|
|
match &d.block("*D1").unwrap().entities.iter().next().unwrap().kind {
|
|
EntityKind::MText(t) => t,
|
|
_ => unreachable!(),
|
|
},
|
|
);
|
|
let mid = m.bounds.center();
|
|
assert_eq!(pick(d, &Space::Model, mid, 0.01), Some(dim), "on the block's text");
|
|
assert_eq!(pick(d, &Space::Model, Vec2::new(50.0, 33.0), 0.01), None, "not where the dimension would have set it");
|
|
}
|