The next MLEADER started again with "arrowhead first", Maxpoints 2 and a landing line. As in AutoCAD, the order (arrowhead, landing or content first) and the Options values (Maxpoints, leader landing, content type, first and second angle) are now kept in Session::last_used for the rest of the session and are where the next MLEADER starts. JSON calls keep their documented defaults. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
8db51c2766
commit
de8ed87a6e
|
|
@ -9,7 +9,7 @@ use serde_json::{Value, json};
|
|||
use super::helpers::v3;
|
||||
use super::machines::SelectRun;
|
||||
use super::*;
|
||||
use crate::{Accept, EngineError, Input, Interactive, Prompt, Result, Session, Step};
|
||||
use crate::{Accept, EngineError, Input, Interactive, LeaderOrder, MLeaderOptions, Prompt, Result, Session, Step};
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![
|
||||
|
|
@ -69,7 +69,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
|||
.menu(&["Dimension", "Multileader"])
|
||||
.alias(&["mld"])
|
||||
.params("{points: [[arrow], ..., [landing]], text}")
|
||||
.interactive(|_| Ok(Box::new(MLeaderM::default()))),
|
||||
.interactive(|s| Ok(Box::new(MLeaderM::new(s)))),
|
||||
CommandSpec::new("leader", "Leader", run_leader).alias(&["lead"]).params("{points: [[x,y]...], text?}"),
|
||||
CommandSpec::new("dimstyle.update", "Update", run_update)
|
||||
.menu(&["Dimension", "Update"])
|
||||
|
|
@ -1455,15 +1455,6 @@ impl Interactive for ChainM {
|
|||
}
|
||||
}
|
||||
|
||||
/// Which end of the multileader MLEADER places first.
|
||||
#[derive(Clone, Copy, Default, PartialEq)]
|
||||
enum LeaderOrder {
|
||||
#[default]
|
||||
Arrowhead,
|
||||
Landing,
|
||||
Content,
|
||||
}
|
||||
|
||||
/// The MLEADER Options prompt being answered.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum LeaderOpt {
|
||||
|
|
@ -1495,23 +1486,28 @@ struct MLeaderM {
|
|||
content: bool,
|
||||
}
|
||||
|
||||
impl Default for MLeaderM {
|
||||
fn default() -> Self {
|
||||
impl MLeaderM {
|
||||
/// Starts with the order and Options chosen the last time (as AutoCAD does).
|
||||
fn new(s: &Session) -> Self {
|
||||
let o = s.last_used.mleader;
|
||||
let max_points = o.max_points.clamp(2, MAX_LEADER_POINTS);
|
||||
MLeaderM {
|
||||
order: LeaderOrder::Arrowhead,
|
||||
order: o.order,
|
||||
pts: Vec::new(),
|
||||
leader_done: false,
|
||||
text: None,
|
||||
opt: None,
|
||||
max_points: 2,
|
||||
angles: [0.0; 2],
|
||||
landing: true,
|
||||
content: true,
|
||||
max_points,
|
||||
angles: o.angles,
|
||||
landing: o.landing,
|
||||
content: o.content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MLeaderM {
|
||||
/// Keep the order and Options for the next MLEADER.
|
||||
fn remember(&self, s: &mut Session) {
|
||||
s.last_used.mleader =
|
||||
MLeaderOptions { order: self.order, max_points: self.max_points, angles: self.angles, landing: self.landing, content: self.content };
|
||||
}
|
||||
/// The leader from arrowhead to landing, with `extra` placed next.
|
||||
fn leader(&self, extra: Option<Vec2>) -> Vec<Vec2> {
|
||||
let mut pts = self.pts.clone();
|
||||
|
|
@ -1649,6 +1645,7 @@ impl Interactive for MLeaderM {
|
|||
fn input(&mut self, s: &mut Session, i: Input) -> Result<Step> {
|
||||
if let Some(o) = self.opt {
|
||||
self.opt = self.option(s, o, i);
|
||||
self.remember(s);
|
||||
return Ok(Step::Continue);
|
||||
}
|
||||
if self.wants_text() {
|
||||
|
|
@ -1663,12 +1660,15 @@ impl Interactive for MLeaderM {
|
|||
self.leader_done = true;
|
||||
}
|
||||
}
|
||||
Input::Keyword(k) if self.pts.is_empty() => match k.as_str() {
|
||||
"leader Landing first" => self.order = LeaderOrder::Landing,
|
||||
"leader arrowHead first" => self.order = LeaderOrder::Arrowhead,
|
||||
"Content first" => self.order = LeaderOrder::Content,
|
||||
_ => self.opt = Some(LeaderOpt::Menu),
|
||||
},
|
||||
Input::Keyword(k) if self.pts.is_empty() => {
|
||||
match k.as_str() {
|
||||
"leader Landing first" => self.order = LeaderOrder::Landing,
|
||||
"leader arrowHead first" => self.order = LeaderOrder::Arrowhead,
|
||||
"Content first" => self.order = LeaderOrder::Content,
|
||||
_ => self.opt = Some(LeaderOpt::Menu),
|
||||
}
|
||||
self.remember(s);
|
||||
}
|
||||
Input::Enter if self.pts.is_empty() => self.opt = Some(LeaderOpt::Menu),
|
||||
// Enter at "next point" ends the leader at the last point.
|
||||
Input::Enter if self.pts.len() >= 2 => self.leader_done = true,
|
||||
|
|
|
|||
|
|
@ -346,11 +346,42 @@ pub struct LastUsed {
|
|||
pub rotate_angle: f64,
|
||||
/// SCALE factor.
|
||||
pub scale_factor: f64,
|
||||
/// MLEADER placement order and Options.
|
||||
pub mleader: MLeaderOptions,
|
||||
}
|
||||
|
||||
impl Default for LastUsed {
|
||||
fn default() -> Self {
|
||||
LastUsed { hatch_select: false, polygon_circumscribed: false, rotate_angle: 0.0, scale_factor: 1.0 }
|
||||
LastUsed { hatch_select: false, polygon_circumscribed: false, rotate_angle: 0.0, scale_factor: 1.0, mleader: MLeaderOptions::default() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Which part of a multileader MLEADER places first.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub enum LeaderOrder {
|
||||
#[default]
|
||||
Arrowhead,
|
||||
Landing,
|
||||
Content,
|
||||
}
|
||||
|
||||
/// The MLEADER placement order and the values set at its Options prompt.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct MLeaderOptions {
|
||||
pub order: LeaderOrder,
|
||||
/// Maximum points of the leader line (2..=64).
|
||||
pub max_points: usize,
|
||||
/// First and second angle constraints (radians; 0 = free).
|
||||
pub angles: [f64; 2],
|
||||
/// Draw the landing line (dogleg).
|
||||
pub landing: bool,
|
||||
/// Ask for multiline text content (Content type None turns it off).
|
||||
pub content: bool,
|
||||
}
|
||||
|
||||
impl Default for MLeaderOptions {
|
||||
fn default() -> Self {
|
||||
MLeaderOptions { order: LeaderOrder::Arrowhead, max_points: 2, angles: [0.0; 2], landing: true, content: true }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -123,7 +123,9 @@ fn mleader_options_take_effect() {
|
|||
assert_eq!(m.dogleg, 0.0);
|
||||
assert_eq!(m.text.unwrap().contents, "txt");
|
||||
|
||||
// First angle constraint snaps the leader to 45° steps.
|
||||
// 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();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
//! MLEADER starts with the placement order and Options chosen the last time (#247 follow-up).
|
||||
|
||||
use cadcraft_engine::Session;
|
||||
use cadcraft_engine::doc::{EntityKind, MLeader};
|
||||
use cadcraft_engine::geom::Vec2;
|
||||
use serde_json::json;
|
||||
|
||||
fn last_mleader(s: &Session) -> MLeader {
|
||||
match &s.doc().unwrap().model.last().unwrap().kind {
|
||||
EntityKind::MLeader(m) => m.clone(),
|
||||
other => panic!("not a multileader: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn near(a: Vec2, b: Vec2) -> bool {
|
||||
a.dist(b) < 1e-6
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mleader_remembers_order_and_options() {
|
||||
let mut s = Session::new();
|
||||
// Landing first, then Options: Maxpoints 3, no landing line.
|
||||
s.script("mleader l o m 3 a n x 10,5 5,5 0,0 one\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.dogleg, 0.0);
|
||||
|
||||
// The next MLEADER asks for the landing first and keeps both options.
|
||||
s.cmdline("mleader").unwrap();
|
||||
assert!(s.prompt_text().contains("landing location"), "{}", s.prompt_text());
|
||||
s.script("20,5 15,5 10,0 two\n").unwrap();
|
||||
let m = last_mleader(&s);
|
||||
assert_eq!(m.leaders[0].len(), 2, "three points (Maxpoints 3): {m:?}");
|
||||
assert!(near(m.leaders[0][0].xy(), Vec2::new(10.0, 0.0)) && near(m.landing.xy(), Vec2::new(20.0, 5.0)), "{m:?}");
|
||||
assert_eq!(m.dogleg, 0.0);
|
||||
assert_eq!(m.text.unwrap().contents, "two");
|
||||
|
||||
// Switching back to arrowhead first is remembered too, also when the command is cancelled.
|
||||
s.script("mleader h\n").unwrap();
|
||||
s.cancel();
|
||||
s.cmdline("mleader").unwrap();
|
||||
assert!(s.prompt_text().contains("arrowhead location"), "{}", s.prompt_text());
|
||||
s.cancel();
|
||||
|
||||
// JSON calls keep their documented defaults (landing line drawn).
|
||||
s.execute("mleader", &json!({"points": [[0, 0], [5, 5]], "text": "json"})).unwrap();
|
||||
assert!(last_mleader(&s).dogleg > 0.0);
|
||||
}
|
||||
Loading…
Reference in New Issue