Paste New copies an import's bodies under the names its features use when the names are numbered (#1117)

Where an Import's stored labels pair one for one with its numbered built bodies (X, X (2), X (3)), component.paste_new stores the copy under the bodies' names, so the copied features after it find their bodies. No format change.
This commit is contained in:
NOWN-DEV authored and GitHub committed 2026-10-14 13:24:27 -04:00
1 parent 6ebfcd16b6
commit f79dfe41b6
2 files changed
+201 -4

No files matched your search

+35 -4
View File
@@ -94,6 +94,24 @@ fn unique(base: &str, used: &mut BTreeSet<String>) -> String {
name
}
/// Whether `built`, the bodies a feature made, are the ones its `stored` names give, one for one. A
/// name is built as stored, or as `stored (n)` when a body of that name exists already: the solids
/// of a STEP file that share a label are stored as `X`, `X`, `X` and built as `X`, `X (2)`, `X (3)`,
/// and a second import of a file builds `Aa (2)` for the stored `Aa`.
fn named_by(stored: &[String], built: &[String]) -> bool {
!stored.is_empty()
&& stored.len() == built.len()
&& stored.iter().zip(built).all(|(stored, built)| {
let base = stored.trim();
!base.is_empty()
&& (built == base
|| built
.strip_prefix(base)
.and_then(|rest| rest.strip_prefix(" (")?.strip_suffix(')'))
.is_some_and(|n| !n.is_empty() && n.bytes().all(|b| b.is_ascii_digit())))
})
}
fn graph(doc: &Document, root: u64) -> Result<Vec<u64>> {
let definitions: BTreeSet<_> = doc.components.iter().map(|c| c.id).collect();
let mut children: BTreeMap<u64, Vec<u64>> = BTreeMap::new();
@@ -271,17 +289,26 @@ pub(super) fn paste(
}
// Collect historical outputs too: a later Combine may have consumed a named tool body.
let mut outputs: BTreeMap<u64, Vec<String>> = BTreeMap::new();
// The names the bodies of a feature have in the model, where they are its stored names, numbered.
let mut shown: BTreeMap<u64, Vec<String>> = BTreeMap::new();
for f in &feats {
if let Some(result) = s.model.result(f.id) {
let created: Vec<_> = result.state.bodies.iter().filter(|b| b.feature == f.id).map(|b| b.name.clone()).collect();
for name in &created {
bodies.entry(name.clone()).or_insert_with(|| unique(name, &mut used));
}
if named_by(&f.body_names, &created) {
shown.insert(f.id, created.clone());
}
outputs.insert(f.id, created);
}
for name in &f.body_names {
if !name.is_empty() {
bodies.entry(name.clone()).or_insert_with(|| unique(name, &mut used));
// Later features name a body as the model shows it. Where that differs from the stored name,
// the stored name is only a label (the first body of another import may be called so).
if !shown.contains_key(&f.id) {
for name in &f.body_names {
if !name.is_empty() {
bodies.entry(name.clone()).or_insert_with(|| unique(name, &mut used));
}
}
}
if bodies.len() > MAX_WORK {
@@ -379,7 +406,11 @@ pub(super) fn paste(
f.id = *features.get(&old.id).ok_or_else(|| bad(CMD, "missing feature mapping"))?;
f.name = names.get(&old.name).cloned().ok_or_else(|| bad(CMD, "missing feature name"))?;
f.component = *components.get(&old.component).ok_or_else(|| bad(CMD, "missing component mapping"))?;
if f.body_names.is_empty() {
if let Some(names) = shown.get(&old.id) {
// Stored as `X`, `X`, `X` and built as `X`, `X (2)`, `X (3)`: the copy is stored under the
// names of the bodies, so that it builds the names the copied features after it use.
f.body_names = names.clone();
} else if f.body_names.is_empty() {
f.body_names = outputs.get(&old.id).cloned().unwrap_or_default();
}
f.remap_clone(&features, &names, &bodies, &parameters)?;