Files
cadcraft/crates/engine/src/cmd/modify2/ncopy.rs
T
2ez4gcxandClaude Opus 5.5 990ff2ce9f Say "or" once in prompts that offer options (#2563)
* Say "or" once in prompts that offer options

Twelve prompt messages ended in "or", and the command line adds "or"
before a prompt's options, so they read "or or": HELIX height, XLINE angle
and offset, BLEND, four LENGTHEN prompts, two NCOPY prompts, PEDIT and
table breaks. They now read as AutoCAD's do.

The interface catalogs are keyed by these messages: each key loses its
"or" and each translation its trailing word for "or"; where the shorter
key already had a translation, the longer row is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Say "or" once in HELIX's radius prompts; rename the Vietnamese keys

HELIX's base and top radius prompts ended in "or" before their Diameter
option too, so they read "Specify base radius or or [Diameter]". They
now read "Specify base radius or [Diameter] <1.0000>:", and their keys
lose "or" in all 17 catalogs, as the other twelve did.

The Vietnamese catalog, merged from main, gets the same renames as the
other catalogs; where the shorter key already had a translation, the
longer row goes. The unused "Specify rotation angle or" row goes from
every catalog too: RECTANG has asked "Specify rotation angle" since
#2554.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 14:36:37 -07:00

155 lines
5.8 KiB
Rust

use super::{
Accept, Common, EntityKind, Handle, Input, Interactive, MAX_BLOCK_DEPTH, MAX_GEN, Mat3, Prompt, Result, Session, Step, Value, Vec2, bad, curves,
json, other, point_param, push_entity, targets,
};
// =====================================================================================
// NCOPY
// =====================================================================================
/// The leaf objects of a block reference in world coordinates (nested references expanded).
fn nested_leaves(d: &cadcraft_doc::Drawing, ins: &cadcraft_doc::Insert, m: &Mat3, depth: usize, out: &mut Vec<(Common, EntityKind)>) {
if depth >= MAX_BLOCK_DEPTH || out.len() >= MAX_GEN {
return;
}
let Some(b) = d.block(&ins.block) else { return };
let mm = m.then_before(ins.transform(b.base.xy()));
for e in b.entities.iter() {
match &e.kind {
EntityKind::Insert(inner) => nested_leaves(d, inner, &mm, depth + 1, out),
EntityKind::AttDef(_) => {}
k => {
let mut k = k.clone();
k.transform(&mm);
out.push((e.common.clone(), k));
}
}
}
}
fn leaves_of(s: &Session, h: Handle) -> Result<Vec<(Common, EntityKind)>> {
let e = curves::entity(s, h)?;
let EntityKind::Insert(ins) = &e.kind else { return Err(other("Select a block reference.")) };
let mut out = Vec::new();
nested_leaves(s.doc()?, ins, &Mat3::IDENTITY, 0, &mut out);
Ok(out)
}
fn nearest_leaf(leaves: Vec<(Common, EntityKind)>, p: Vec2) -> Option<(Common, EntityKind)> {
leaves.into_iter().min_by(|a, b| curves::kind_distance(&a.1, p).total_cmp(&curves::kind_distance(&b.1, p)))
}
fn place_copies(s: &mut Session, items: &[(Common, EntityKind)], delta: Vec2) -> Result<Vec<Handle>> {
let space = s.space();
let m = Mat3::translate(delta);
let mut out = Vec::new();
for (c, k) in items {
let mut k = k.clone();
k.transform(&m);
out.push(push_entity(s, &space, c.clone(), k)?);
}
Ok(out)
}
pub(super) fn run_ncopy(s: &mut Session, p: &Value) -> Result<Value> {
let h = targets(s, p)?.first().copied().ok_or_else(|| bad("ncopy", "`handle` (block reference) is required"))?;
let leaves = leaves_of(s, h)?;
let items: Vec<(Common, EntityKind)> = match point_param(p, "pick") {
Some(q) => nearest_leaf(leaves, q).into_iter().collect(),
None => leaves,
};
if items.is_empty() {
return Err(other("The block reference has no nested objects."));
}
let delta = match (point_param(p, "delta"), point_param(p, "from"), point_param(p, "to")) {
(Some(d), _, _) => d,
(None, Some(a), Some(b)) => b - a,
_ => Vec2::ZERO,
};
let hs = place_copies(s, &items, delta)?;
Ok(json!({ "handles": hs.iter().map(|h| h.hex()).collect::<Vec<_>>() }))
}
#[derive(Default)]
pub(super) struct NcopyM {
items: Vec<(Common, EntityKind)>,
selecting_done: bool,
base: Option<Vec2>,
}
impl Interactive for NcopyM {
fn name(&self) -> &'static str {
"NCOPY"
}
fn begin(&mut self, s: &mut Session) -> Result<Step> {
s.set_selection(Vec::new());
Ok(Step::Continue)
}
fn prompt(&self, _s: &Session) -> Prompt {
match (self.selecting_done, self.base) {
(false, _) => Prompt::new("Select nested objects to copy", Accept::POINT).kw(&["Settings"]),
(true, None) => Prompt::new("Specify base point", Accept::POINT).kw(&["Displacement", "Multiple"]).default("Displacement"),
(true, Some(b)) => Prompt::new("Specify second point or <use first point as displacement>", Accept::POINT).base(b),
}
}
fn input(&mut self, s: &mut Session, i: Input) -> Result<Step> {
if !self.selecting_done {
return match i {
Input::Point(p) => {
let h = curves::pick_at(s, p).ok_or_else(|| other("*Invalid selection*"))?;
let leaf = nearest_leaf(leaves_of(s, h)?, p).ok_or_else(|| other("Nothing nested there."))?;
s.echo(format!("Nested object: {}", leaf.1.type_name()));
if self.items.len() < MAX_GEN {
self.items.push(leaf);
}
Ok(Step::Continue)
}
Input::Keyword(_) => {
s.echo("Insert mode: Copy (nested objects are copied as independent objects).");
Ok(Step::Continue)
}
Input::Enter => {
if self.items.is_empty() {
return Ok(Step::Cancel);
}
self.selecting_done = true;
Ok(Step::Continue)
}
_ => Ok(Step::Continue),
};
}
match (self.base, i) {
(None, Input::Point(p)) => {
self.base = Some(p);
Ok(Step::Continue)
}
(None, Input::Enter | Input::Keyword(_)) => {
place_copies(s, &self.items, Vec2::ZERO)?;
Ok(Step::Done)
}
(Some(b), Input::Point(p)) => {
place_copies(s, &self.items, p - b)?;
Ok(Step::Done)
}
(Some(b), Input::Enter) => {
place_copies(s, &self.items, b)?;
Ok(Step::Done)
}
_ => Ok(Step::Continue),
}
}
fn preview(&self, _s: &Session, c: Vec2) -> Vec<EntityKind> {
let Some(b) = self.base else { return Vec::new() };
let m = Mat3::translate(c - b);
self.items
.iter()
.take(500)
.map(|(_, k)| {
let mut k = k.clone();
k.transform(&m);
k
})
.collect()
}
}