mirror of
https://github.com/storytold/cadcraft.git
synced 2026-10-25 11:26:46 -06:00
MLEADER's Options answered "not available yet" for Leader type sPline and None and for Content type Block. Spline leaders now draw as smooth curves through their points (DXF LEADER path type 72 = 1); None places only the content at one picked point; Block asks for a block name and how it attaches (Center extents or Insertion point) and inserts it at the end of the landing. MLeader gains `spline` and `block` (serde defaults); DXF stays LEADER + MTEXT/INSERT. PURGE, RENAME, copy/paste, FLATTEN, grips and extents follow the block content. The choices are remembered like the other MLEADER options. docs/gaps.md: top-10 row 5, F1 and F10 narrowed. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
141 lines
5.0 KiB
Rust
141 lines
5.0 KiB
Rust
//! Options listed at the dimension and multileader prompts take effect (#243).
|
|
|
|
use cadcraft_engine::Session;
|
|
use cadcraft_engine::doc::{DimKind, Dimension, EntityKind, MLeader};
|
|
use cadcraft_engine::geom::Vec2;
|
|
|
|
fn last(s: &Session) -> EntityKind {
|
|
s.doc().unwrap().model.last().unwrap().kind.clone()
|
|
}
|
|
|
|
fn last_dim(s: &Session) -> Dimension {
|
|
match last(s) {
|
|
EntityKind::Dimension(d) => d,
|
|
other => panic!("not a dimension: {other:?}"),
|
|
}
|
|
}
|
|
|
|
fn last_mleader(s: &Session) -> MLeader {
|
|
match last(s) {
|
|
EntityKind::MLeader(m) => m,
|
|
other => panic!("not a multileader: {other:?}"),
|
|
}
|
|
}
|
|
|
|
fn value(s: &Session, d: &Dimension) -> String {
|
|
cadcraft_engine::render::dimension_in(s.doc().unwrap(), d).value
|
|
}
|
|
|
|
fn near(a: Vec2, b: Vec2) -> bool {
|
|
a.dist(b) < 1e-6
|
|
}
|
|
|
|
#[test]
|
|
fn dimlinear_angle_rotates_the_text_and_text_replaces_it() {
|
|
let mut s = Session::new();
|
|
s.script("dimlinear 0,0 10,0 a 30 t\nX=<>\n5,-3\n").unwrap();
|
|
let d = last_dim(&s);
|
|
assert_eq!(d.kind, DimKind::Linear { rotation: 0.0 }, "Angle must not rotate the dimension line");
|
|
assert!((d.text_rotation - 30f64.to_radians()).abs() < 1e-9, "{}", d.text_rotation);
|
|
assert_eq!(d.text, "X=<>");
|
|
assert!(s.running.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn dimlinear_vertical_forces_the_orientation() {
|
|
let mut s = Session::new();
|
|
s.script("dimlinear 0,0 10,5 v").unwrap();
|
|
let p = s.prompt_text();
|
|
assert!(p.contains("[Mtext/Text/Angle]"), "{p}");
|
|
s.script("5,-3\n").unwrap();
|
|
assert_eq!(last_dim(&s).kind, DimKind::Linear { rotation: std::f64::consts::FRAC_PI_2 });
|
|
}
|
|
|
|
#[test]
|
|
fn dimordinate_datum_and_text_options() {
|
|
let mut s = Session::new();
|
|
// The leader endpoint alone would make a Y datum; Xdatum forces X, and Text is a text option.
|
|
s.script("dimordinate 0,0 x t\nA <>\n10,2\n").unwrap();
|
|
let d = last_dim(&s);
|
|
assert_eq!(d.kind, DimKind::Ordinate { x_type: true });
|
|
assert_eq!(d.text, "A <>");
|
|
s.script("dimordinate 0,0 y a 90 2,10\n").unwrap();
|
|
let d = last_dim(&s);
|
|
assert_eq!(d.kind, DimKind::Ordinate { x_type: false });
|
|
assert!((d.text_rotation - std::f64::consts::FRAC_PI_2).abs() < 1e-9);
|
|
}
|
|
|
|
#[test]
|
|
fn dimradius_text_options() {
|
|
let mut s = Session::new();
|
|
s.script("circle 0,0 5\ndimradius 5,0 t\nR=<>\na 45 8,8\n").unwrap();
|
|
let d = last_dim(&s);
|
|
assert_eq!(d.kind, DimKind::Radius);
|
|
assert_eq!(d.text, "R=<>");
|
|
assert!((d.text_rotation - 45f64.to_radians()).abs() < 1e-9);
|
|
}
|
|
|
|
#[test]
|
|
fn dimangular_quadrant_locks_the_measured_angle() {
|
|
let mut s = Session::new();
|
|
// Placed at (-3,-4) the arc would measure the 270° side; the quadrant keeps the 90° one.
|
|
s.script("dimangular\n\n0,0 10,0 0,10 q 5,5 -3,-4\n").unwrap();
|
|
let d = last_dim(&s);
|
|
assert_eq!(value(&s, &d), "90°");
|
|
assert!(near(d.defpt.xy(), Vec2::new(5.0, 5.0).normalized() * 5.0), "{:?}", d.defpt);
|
|
}
|
|
|
|
#[test]
|
|
fn dimarc_partial_measures_part_of_the_arc() {
|
|
let mut s = Session::new();
|
|
s.script("arc c 0,0 -10,0 10,0\n").unwrap();
|
|
assert!(matches!(last(&s), EntityKind::Arc(_)), "{:?}", last(&s));
|
|
s.script("dimarc 0,-10 p 10,0 0,-10 5,-12\n").unwrap();
|
|
let d = last_dim(&s);
|
|
assert_eq!(d.kind, DimKind::ArcLength);
|
|
assert!(near(d.p13.xy(), Vec2::new(0.0, -10.0)) && near(d.p14.xy(), Vec2::new(10.0, 0.0)), "{d:?}");
|
|
assert_eq!(value(&s, &d), "⌒15.7080");
|
|
}
|
|
|
|
#[test]
|
|
fn mleader_landing_first_and_content_first() {
|
|
let mut s = Session::new();
|
|
s.script("mleader l 10,5 0,0 note\n").unwrap();
|
|
let m = last_mleader(&s);
|
|
assert_eq!(m.leaders.len(), 1);
|
|
assert!(near(m.leaders[0][0].xy(), Vec2::ZERO) && near(m.landing.xy(), Vec2::new(10.0, 5.0)), "{m:?}");
|
|
assert_eq!(m.text.unwrap().contents, "note");
|
|
|
|
s.script("mleader c 10,5 hello\n0,0\n").unwrap();
|
|
let m = last_mleader(&s);
|
|
assert!(near(m.leaders[0][0].xy(), Vec2::ZERO) && near(m.landing.xy(), Vec2::new(10.0, 5.0)), "{m:?}");
|
|
assert_eq!(m.text.unwrap().contents, "hello");
|
|
}
|
|
|
|
#[test]
|
|
fn mleader_options_take_effect() {
|
|
let mut s = Session::new();
|
|
// Enter opens Options; Maxpoints 3 and no landing line.
|
|
s.script("mleader\n\nm 3 a n x 0,0 5,5 10,5 txt\n").unwrap();
|
|
let m = last_mleader(&s);
|
|
assert_eq!(m.leaders[0].len(), 2, "{m:?}");
|
|
assert!(near(m.leaders[0][1].xy(), Vec2::new(5.0, 5.0)) && near(m.landing.xy(), Vec2::new(10.0, 5.0)));
|
|
assert_eq!(m.dogleg, 0.0);
|
|
assert_eq!(m.text.unwrap().contents, "txt");
|
|
|
|
// First angle constraint snaps the leader to 45° steps (a new session: MLEADER would keep the
|
|
// Maxpoints and landing chosen above).
|
|
let mut s = Session::new();
|
|
s.script("mleader o f 45 x 0,0 10,8 note\n").unwrap();
|
|
let m = last_mleader(&s);
|
|
let r = Vec2::new(10.0, 8.0).len();
|
|
assert!(near(m.landing.xy(), Vec2::new(r, r) / 2f64.sqrt()), "{:?}", m.landing);
|
|
|
|
// Leader type sPline keeps the following input and draws a spline leader.
|
|
s.script("mleader o l p x 0,0 4,4 hi\n").unwrap();
|
|
let m = last_mleader(&s);
|
|
assert!(m.spline);
|
|
assert_eq!(m.text.unwrap().contents, "hi");
|
|
assert!(s.running.is_none());
|
|
}
|