Use the codec audit and repair in place of OCS's corruption guard

Opening a drawing no longer drops entities with its own sanity checks or
rewrites knotless splines; recovery runs the codec's repair and records
its actions and unresolved issues in the drawing's notifications. Xref
merges keep every entity. The dropped-entity counts, their command-line
warnings, recovery report rows and the automation "purged" field go with
the guard.

AUDIT now reports the codec's audit (counts plus the first 50 issues) and
asks whether to fix; Yes applies the codec repair as one undo step.

Cargo: codec 325fa98, kernel dfca391, graph 5d4e713.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 5.5 committed 2026-10-16 12:02:39 +03:00
1 parent 3c9404510f
commit 04b2c07a38
43 files changed
+192 -763

No files matched your search

+2 -2
View File
@@ -1128,7 +1128,7 @@ impl OpenCADStudio {
.filter(|br| reload_keys.contains(&br.handle.value()))
.map(|br| br.handle)
.collect();
let (infos, _dropped) = crate::io::xref::resolve_xrefs_for_keys(
let infos = crate::io::xref::resolve_xrefs_for_keys(
&mut self.tabs[i].scene.document,
base_dir,
&handles,
@@ -1674,7 +1674,7 @@ impl OpenCADStudio {
self.tabs[i].xref_unloaded.remove(key);
self.tabs[i].xref_stat_cache.remove(key);
}
let (infos, _dropped) = crate::io::xref::resolve_xrefs(
let infos = crate::io::xref::resolve_xrefs(
&mut self.tabs[i].scene.document,
&base_dir,
);
+49 -40
View File
@@ -3635,53 +3635,62 @@ impl OpenCADStudio {
.push_error(crate::tf!("COMPARE: {error}").as_ref()),
}
}
// AUDIT runs the codec's audit; `AUDIT Yes` also applies its
// repair, undoable as one step.
"AUDIT" => {
use std::collections::BTreeSet;
let mut undefined_layers: BTreeSet<String> = BTreeSet::new();
let mut undefined_blocks: BTreeSet<String> = BTreeSet::new();
let mut total = 0usize;
{
use crate::command::KeywordCommand;
let c = KeywordCommand::new(
"AUDIT",
"AUDIT Fix any errors detected? [Yes / No] <No>:",
vec![("Yes", "YES", None), ("No", "NO", None)],
)
.with_default("NO");
self.command_line.push_info(&c.prompt());
self.tabs[i].active_cmd = Some(Box::new(c));
}
cmd if cmd.starts_with("AUDIT ") => {
let fix = cmd[6..].trim().to_uppercase().starts_with('Y');
let report = self.tabs[i].scene.document.audit();
let total = {
let doc = &self.tabs[i].scene.document;
for e in doc.entities() {
total += 1;
let layer = &e.common().layer;
if !layer.is_empty() && doc.layers.get(layer).is_none() {
undefined_layers.insert(layer.clone());
}
if let codec::EntityType::Insert(ins) = e {
if doc.block_records.get(&ins.block_name).is_none() {
undefined_blocks.insert(ins.block_name.clone());
}
}
}
}
doc.entities().count() + doc.objects.len()
};
self.command_line
.push_output(crate::tf!("AUDIT: scanned {total} object(s).").as_ref());
if undefined_layers.is_empty() && undefined_blocks.is_empty() {
if report.is_clean() {
self.command_line
.push_output(crate::t!("AUDIT: no issues found.").as_ref());
} else {
if !undefined_layers.is_empty() {
self.command_line.push_error(
crate::tf!(
"AUDIT: reference(s) to undefined layer(s): {}",
undefined_layers.into_iter().collect::<Vec<_>>().join(", ")
)
.as_ref(),
);
}
if !undefined_blocks.is_empty() {
self.command_line.push_error(
crate::tf!(
"AUDIT: reference(s) to undefined block(s): {}",
undefined_blocks.into_iter().collect::<Vec<_>>().join(", ")
)
.as_ref(),
);
}
self.command_line.push_info(
crate::t!("AUDIT: report only — no automatic repair performed.").as_ref(),
return Some(Task::none());
}
self.command_line.push_error(
crate::tf!(
"AUDIT: {} error(s), {} warning(s) found.",
report.error_count(),
report.warning_count()
)
.as_ref(),
);
// ponytail: first 50 issues only; the counts above cover the rest.
for issue in report.issues.iter().take(50) {
self.command_line
.push_info(&format!(" {:?}: {}", issue.code, issue.message));
}
if fix {
self.push_undo_snapshot(i, "AUDIT");
let repair = self.tabs[i].scene.document.repair();
self.command_line.push_output(
crate::tf!(
"AUDIT: {} repair(s) applied, {} issue(s) left unresolved.",
repair.actions.len(),
repair.unresolved.len()
)
.as_ref(),
);
if !repair.actions.is_empty() {
self.tabs[i].dirty = true;
self.tabs[i].scene.rebuild_derived_caches();
self.refresh_layer_panel();
}
}
}