Xref attach: placement preview, relative paths on the first save, no-match messages

The reference follows the cursor while its insertion point, scale or
rotation is asked (preview-only PScale/PX/PY/PZ/PRotate included); a
relative path chosen before the drawing had a file becomes relative on the
first save, and relative paths start from "./" at or below the drawing's
folder; -XREF name options report "No matching xref names found.".

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
ramox81andClaude Opus 5.5 committed 2026-09-23 15:34:06 +03:00
1 parent 6bd55bc32e
commit 498a97df0f
8 files changed
+204 -7

No files matched your search

+55 -1
View File
@@ -125,7 +125,8 @@ impl OpenCADStudio {
return;
}
}
if let Some(existing) = existing_reference(&self.tabs[i].scene, &name) {
let existing = existing_reference(&self.tabs[i].scene, &name);
if let Some(existing) = existing.as_ref() {
self.command_line
.push_output(&format!("Xref \"{}\" has already been defined.", existing));
self.command_line
@@ -155,10 +156,60 @@ impl OpenCADStudio {
self.commit_xref_attach(i, request, entity);
return;
}
let (wires, source_units) = self.xref_preview(i, existing.as_deref(), &full);
let host_units = self.tabs[i].scene.document.header.insertion_units;
let unit = crate::app::properties::insert_unit_scale(host_units, source_units).unwrap_or(1.0);
let command = command.with_preview(wires, unit);
self.command_line.push_info(&command.prompt());
self.tabs[i].active_cmd = Some(Box::new(command));
}
/// The reference's geometry for the placement ghost, in its own units:
/// an existing definition's content, or the file's model space.
fn xref_preview(
&self,
i: usize,
existing: Option<&str>,
path: &str,
) -> (Vec<crate::scene::model::wire_model::WireModel>, i16) {
// ponytail: a huge reference skips the ghost; cap by entity count.
const MAX_PREVIEW_ENTITIES: usize = 20_000;
let scene = &self.tabs[i].scene;
if let Some(name) = existing {
let units = scene
.document
.block_records
.get(name)
.map(|br| br.units)
.unwrap_or(0);
return (scene.block_preview_wires(name), units);
}
#[cfg(not(target_arch = "wasm32"))]
if let Ok(source) = crate::io::load_file(std::path::Path::new(path)) {
let owner = source.header.model_space_block_handle;
let entities: Vec<acadrust::EntityType> = source
.entities()
.filter(|e| e.common().owner_handle == owner)
.filter(|e| {
!matches!(
e,
acadrust::EntityType::Block(_) | acadrust::EntityType::BlockEnd(_)
)
})
.take(MAX_PREVIEW_ENTITIES + 1)
.cloned()
.collect();
if entities.len() <= MAX_PREVIEW_ENTITIES {
return (
scene.wires_for_entities(&entities),
source.header.insertion_units,
);
}
}
let _ = path;
(Vec::new(), 0)
}
/// Creates (or reuses) the definition and commits its INSERT — one undo
/// step for both, so undo leaves no definition behind.
pub(in crate::app) fn commit_xref_attach(
@@ -170,6 +221,9 @@ impl OpenCADStudio {
let pending = self.begin_undo(i, "XATTACH", 1, false);
let host = self.tabs[i].current_path.clone();
let name = prepare_xref_definition(&mut self.tabs[i].scene, &request, host.as_deref());
if host.is_none() && request.path_type == Pathtype::Relative {
self.tabs[i].xref_relative_on_save.insert(name.clone());
}
if let acadrust::EntityType::Insert(insert) = &mut entity {
insert.block_name = name;
}
+4
View File
@@ -231,6 +231,9 @@ pub(super) struct DocumentTab {
/// Session set of unloaded reference keys (Task 8b). Owns the set that
/// `collect_entries` takes as `unloaded`, so CLI and palette agree.
pub(super) xref_unloaded: crate::io::xref_model::UnloadSet,
/// References attached with a relative path while the drawing had no
/// file yet: stored full until the first save, then made relative.
pub(super) xref_relative_on_save: rustc_hash::FxHashSet<String>,
/// Load-time mtimes per reference key (Task 8b). Written on every
/// palette refresh; `Stale` is detectable from the second refresh on.
pub(super) xref_stat_cache: crate::io::xref_model::RefStatCache,
@@ -650,6 +653,7 @@ impl DocumentTab {
active_mleader_style: "Standard".to_string(),
last_synced_camera_gen: 0,
xref_unloaded: crate::io::xref_model::UnloadSet::default(),
xref_relative_on_save: rustc_hash::FxHashSet::default(),
xref_stat_cache: crate::io::xref_model::RefStatCache::default(),
xref_missing: 0,
is_start: false,
+17
View File
@@ -2304,6 +2304,15 @@ impl OpenCADStudio {
});
let clone_started = iced::time::Instant::now();
let mut snapshot = self.tabs[i].scene.document_for_save();
// First save: references attached with a relative path type while
// the drawing had no file become relative to it.
if self.tabs[i].current_path.is_none() && purpose != crate::app::SavePurpose::Autosave {
crate::io::xref::make_relative(
&mut snapshot,
&self.tabs[i].xref_relative_on_save,
&path,
);
}
// Save-As across folders: rebase relative reference paths onto the
// new base dir inside the snapshot only (live strings are untouched).
if purpose == crate::app::SavePurpose::SaveAs {
@@ -2606,6 +2615,14 @@ impl OpenCADStudio {
}
if outcome.set_current_path {
let old_path = self.tabs[i].current_path.clone();
if old_path.is_none() {
let pending = std::mem::take(&mut self.tabs[i].xref_relative_on_save);
crate::io::xref::make_relative(
&mut self.tabs[i].scene.document,
&pending,
&outcome.path,
);
}
self.tabs[i].current_path = Some(outcome.path.clone());
self.tabs[i].scene.document.version = outcome.version;
if outcome.purpose == crate::app::SavePurpose::SaveAs {