Files
cadcraft/crates/engine/src/cmd/modify2/ncopy.rs
T

172 lines
6.3 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
// =====================================================================================
/// Block entities one NCOPY may visit in all, leaves or not: references to shared, empty or
/// repeated sub-blocks multiply the visits (not the leaves) with each level of nesting.
const MAX_VISITS: usize = 1_000_000;
/// The leaf objects of a block reference in world coordinates (nested references expanded). An
/// error when the nesting needs more than [`MAX_VISITS`] visits.
fn nested_leaves(
d: &cadcraft_doc::Drawing,
ins: &cadcraft_doc::Insert,
m: &Mat3,
depth: usize,
visits: &mut usize,
out: &mut Vec<(Common, EntityKind)>,
) -> Result<()> {
if depth >= MAX_BLOCK_DEPTH || out.len() >= MAX_GEN {
return Ok(());
}
let Some(b) = d.block(&ins.block) else { return Ok(()) };
let mm = m.then_before(ins.transform(b.base.xy()));
for e in b.entities.iter() {
*visits += 1;
if *visits > MAX_VISITS {
return Err(other("The block reference nests too many objects to copy."));
}
match &e.kind {
EntityKind::Insert(inner) => nested_leaves(d, inner, &mm, depth + 1, visits, out)?,
EntityKind::AttDef(_) => {}
k => {
let mut k = k.clone();
k.transform(&mm);
out.push((e.common.clone(), k));
}
}
}
Ok(())
}
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 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()
}
}