forked from 1b-agent/opencadstudio-upstream
Merge remote-tracking branch 'origin/main' into codex/import-formats
# Conflicts: # Cargo.lock
This commit is contained in:
commit
d8959a4635
43 files changed
+189
-760
No files matched your search
+6
-26
@@ -762,7 +762,7 @@ impl OpenCADStudio {
|
||||
Err(e) => return err(format!("open: {e}")),
|
||||
};
|
||||
match crate::io::load_bytes_finalized(&path_buf, bytes) {
|
||||
Ok((doc, dropped)) => {
|
||||
Ok(doc) => {
|
||||
let i = self.active_tab;
|
||||
self.tabs[i].scene.clear();
|
||||
self.tabs[i].scene.document = doc;
|
||||
@@ -784,13 +784,7 @@ impl OpenCADStudio {
|
||||
// The opened document settled (API v9): the post-op
|
||||
// boundary announces `DocumentOpened` once for this load.
|
||||
self.tabs[i].note_document_loaded();
|
||||
let mut summary = self.entity_summary();
|
||||
if dropped > 0 {
|
||||
if let Some(obj) = summary.as_object_mut() {
|
||||
obj.insert("purged".to_string(), json!(dropped));
|
||||
}
|
||||
}
|
||||
summary
|
||||
self.entity_summary()
|
||||
}
|
||||
Err(e) => err(format!("open: {e}")),
|
||||
}
|
||||
@@ -1903,8 +1897,7 @@ mod tests {
|
||||
fingerprint: None,
|
||||
});
|
||||
let bytes = std::fs::read(&path).expect("read the fixture back");
|
||||
let (document, _dropped) =
|
||||
crate::io::load_bytes_finalized(&path, bytes).expect("reparse the fixture");
|
||||
let document = crate::io::load_bytes_finalized(&path, bytes).expect("reparse the fixture");
|
||||
let caches = crate::scene::build_derived_caches_with_progress(&document, &|_| {}, None);
|
||||
let _ = plugin_dispatch_log();
|
||||
let _ = app.update(crate::app::Message::FileOpened(
|
||||
@@ -3839,7 +3832,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn open_finalizes_and_purges_like_the_ui_open_path() {
|
||||
fn open_finalizes_like_the_ui_open_path() {
|
||||
let mut app = OpenCADStudio::new_for_test();
|
||||
let stale = codec::Handle::from(9999);
|
||||
app.tabs[app.active_tab].scene.solid_models.insert(
|
||||
@@ -3857,26 +3850,13 @@ mod tests {
|
||||
good.center = codec::types::Vector3::new(5.0, 5.0, 0.0);
|
||||
good.radius = 2.0;
|
||||
doc.add_entity(codec::EntityType::Circle(good)).unwrap();
|
||||
let mut corrupt = codec::entities::Circle::new();
|
||||
corrupt.center = codec::types::Vector3::new(1.0, 1.0, 0.0);
|
||||
// An absurd radius is rejected; a zero radius is valid.
|
||||
corrupt.radius = 1.0e11;
|
||||
doc.add_entity(codec::EntityType::Circle(corrupt)).unwrap();
|
||||
let bytes = crate::io::save_to_bytes(&doc, "dxf", doc.version)
|
||||
.expect("save a document containing a corrupt entity");
|
||||
let bytes = crate::io::save_to_bytes(&doc, "dxf", doc.version).expect("save the fixture");
|
||||
std::fs::write(&path, bytes).unwrap();
|
||||
|
||||
let p = path.to_string_lossy().replace('\\', "\\\\");
|
||||
let result = app.automation_op(&format!(r#"{{"op":"open","path":"{p}"}}"#));
|
||||
assert_eq!(result["ok"], true, "{}", result["error"]);
|
||||
assert_eq!(
|
||||
result["total"], 1,
|
||||
"the corrupt circle must not survive the open"
|
||||
);
|
||||
assert_eq!(
|
||||
result["purged"], 1,
|
||||
"the purge count must be reported, matching the UI open path's diagnostics"
|
||||
);
|
||||
assert_eq!(result["total"], 1);
|
||||
|
||||
let i = app.active_tab;
|
||||
assert!(!app.tabs[i].scene.solid_models.contains_key(&stale));
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1009,10 +1009,9 @@ impl OpenCADStudio {
|
||||
"new" => {
|
||||
if let Some(template) = req["template"].as_str() {
|
||||
match self.apply_template(template) {
|
||||
Ok(purged) => {
|
||||
Ok(()) => {
|
||||
self.set_control_result(json!({
|
||||
"template": template,
|
||||
"purged": purged,
|
||||
"total": self.tabs[i].scene.document.entities().count(),
|
||||
}));
|
||||
Task::none()
|
||||
|
||||
@@ -434,7 +434,7 @@ impl OpenCADStudio {
|
||||
/// `new` with `"template"` — the web build reads no files from a path, so
|
||||
/// a template is refused there rather than silently ignored.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub(in crate::app) fn apply_template(&mut self, _template: &str) -> Result<usize, Value> {
|
||||
pub(in crate::app) fn apply_template(&mut self, _template: &str) -> Result<(), Value> {
|
||||
Err(failure(
|
||||
"invalid_request",
|
||||
"starting from a template needs the desktop build",
|
||||
@@ -445,7 +445,7 @@ impl OpenCADStudio {
|
||||
/// file. Tables and styles come from the template; the document keeps no
|
||||
/// source path.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(in crate::app) fn apply_template(&mut self, template: &str) -> Result<usize, Value> {
|
||||
pub(in crate::app) fn apply_template(&mut self, template: &str) -> Result<(), Value> {
|
||||
let path = std::path::PathBuf::from(template);
|
||||
let bytes = self
|
||||
.read_drawing(&path)
|
||||
@@ -461,7 +461,7 @@ impl OpenCADStudio {
|
||||
} else {
|
||||
path.clone()
|
||||
};
|
||||
let (mut document, purged) = crate::io::load_bytes_finalized(&load_path, bytes)
|
||||
let mut document = crate::io::load_bytes_finalized(&load_path, bytes)
|
||||
.map_err(|e| failure("template_failed", e))?;
|
||||
document.source_path = Some(path.to_string_lossy().into_owned());
|
||||
let i = self.active_tab;
|
||||
@@ -482,7 +482,7 @@ impl OpenCADStudio {
|
||||
// A whole document just became resident in the tab (API v9): the
|
||||
// boundary announces `DocumentOpened` once.
|
||||
self.tabs[i].note_document_loaded();
|
||||
Ok(purged)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `file_identity` — stable per-document GUID (the drawing's SPMDOCUNIQUE value,
|
||||
|
||||
@@ -886,7 +886,7 @@ impl OpenCADStudio {
|
||||
.filter(|br| dwg_picked.iter().any(|(key, _)| *key == 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,
|
||||
@@ -1093,7 +1093,7 @@ impl OpenCADStudio {
|
||||
self.tabs[i].xref_unloaded.remove(key);
|
||||
self.tabs[i].xref_stat_cache.remove(key);
|
||||
}
|
||||
let (infos, _dropped) =
|
||||
let infos =
|
||||
crate::io::xref::resolve_xrefs(&mut self.tabs[i].scene.document, &base_dir);
|
||||
let fresh = crate::io::xref::collect_entries_with_prev(
|
||||
&self.tabs[i].scene.document,
|
||||
|
||||
+2
-25
@@ -2051,34 +2051,13 @@ impl OpenCADStudio {
|
||||
.xrefs
|
||||
.iter()
|
||||
.any(|item| item.status == crate::io::xref::XrefStatus::Failed);
|
||||
let document_repaired = parser_errors_recovered
|
||||
|| reference_recovered
|
||||
|| caches.corrupt_dropped > 0
|
||||
|| caches.xref_dropped > 0;
|
||||
let document_repaired = parser_errors_recovered || reference_recovered;
|
||||
let recovery_needed = document_repaired || reference_failed;
|
||||
let total_ms = open_started
|
||||
.map(|started| started.elapsed().as_millis() as u32)
|
||||
.unwrap_or(0);
|
||||
self.command_line
|
||||
.push_output(crate::tf!("Opened \"{name}\" — {entity_count} entities").as_ref());
|
||||
if caches.corrupt_dropped > 0 {
|
||||
self.command_line.push_error(
|
||||
crate::tf!(
|
||||
"Warning: {} corrupt entities dropped (parser junk — bad normals / counts)",
|
||||
caches.corrupt_dropped
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
if caches.xref_dropped > 0 {
|
||||
self.command_line.push_error(
|
||||
crate::tf!(
|
||||
"Warning: {} corrupt xref entities dropped",
|
||||
caches.xref_dropped
|
||||
)
|
||||
.as_ref(),
|
||||
);
|
||||
}
|
||||
for info in &caches.xrefs {
|
||||
match info.status {
|
||||
crate::io::xref::XrefStatus::Loaded => {
|
||||
@@ -2178,9 +2157,7 @@ impl OpenCADStudio {
|
||||
size_bytes,
|
||||
caches.source_sha256.clone(),
|
||||
caches.read_stats.clone(),
|
||||
entity_count.saturating_add(caches.corrupt_dropped),
|
||||
caches.corrupt_dropped,
|
||||
caches.xref_dropped,
|
||||
entity_count,
|
||||
&caches.xrefs,
|
||||
&doc.notifications,
|
||||
document_repaired,
|
||||
|
||||
Reference in new issue
Block a user