feat(compare): COMPARE overlays what differs from another drawing

COMPARE picks a drawing (or takes its path) and matches its objects with
this drawing's by what they draw: type, layer and tessellated geometry,
so an unchanged copy matches whatever its handle. Objects only here are
outlined in green, objects only in the other drawing in red, over the
current drawing, and Next / Previous zoom from one difference to the
next with a count in the prompt. A moved or edited object shows as one
of each. eXit (or Esc) clears the overlay.

Refs #970

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-13 21:31:46 +03:00
1 parent f0ac89629b
commit 58b2f22c09
30 files changed
+372

No files matched your search

+5
View File
@@ -885,6 +885,11 @@ impl OpenCADStudio {
CmdResult::ZoomToWindow { p1, p2 } => {
self.handle_zoom_to_window(p1, p2);
}
CmdResult::Show { p1, p2 } => {
let i = self.active_tab;
self.tabs[i].scene.remember_current_view();
self.tabs[i].scene.zoom_to_window(p1.as_vec3(), p2.as_vec3());
}
CmdResult::CommitAndZoom { entity, p1, p2 } => {
self.handle_commit_entities_and_exit(vec![entity], false, false);
self.handle_zoom_to_window(p1, p2);
+36
View File
@@ -3335,6 +3335,42 @@ impl OpenCADStudio {
// Read-only scan: flags entities on undefined layers and block
// references to undefined block definitions. Reports only; it does
// not auto-repair (so it can never make the drawing worse).
// COMPARE — overlay what differs from another drawing. Bare picks
// the drawing; with a path it compares at once.
"COMPARE" => {
return Some(Task::perform(
async {
crate::sys::file_dialog()
.set_title(crate::t!("Compare with drawing").as_ref())
.add_filter(crate::t!("Drawing").as_ref(), &["dwg", "dxf"])
.pick_file()
.await
.map(|handle| crate::sys::handle_path(&handle))
},
|path| match path {
Some(path) => Message::Command(format!("COMPARE {}", path.display())),
None => Message::Noop,
},
));
}
cmd if cmd.starts_with("COMPARE ") => {
let path = std::path::PathBuf::from(cmd.trim_start_matches("COMPARE").trim());
match crate::io::load_file(&path) {
Ok(other) => {
let comparison =
crate::scene::compare::compare(&self.tabs[i].scene, other);
// The overlay shows at once, before the cursor moves.
self.tabs[i].scene.set_preview_wires(comparison.overlay.clone());
let command =
crate::modules::manage::compare::CompareCommand::new(comparison);
self.command_line.push_info(&command.prompt());
self.tabs[i].active_cmd = Some(Box::new(command));
}
Err(error) => self
.command_line
.push_error(crate::tf!("COMPARE: {error}").as_ref()),
}
}
"AUDIT" => {
use std::collections::BTreeSet;
let mut undefined_layers: BTreeSet<String> = BTreeSet::new();