forked from 1b-agent/opencadstudio-upstream
Merge upstream main; follow the boxed MText codec type and the updated palette branch
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
commit
8f20f0aef6
119 files changed
+560
-1020
No files matched your search
+13
-33
@@ -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(
|
||||
@@ -2129,7 +2122,7 @@ mod tests {
|
||||
dimension.definition_point = Vector3::new(0.0, 5.0, 0.0);
|
||||
dimension.base.text_middle_point = Vector3::new(5.0, 5.0, 0.0);
|
||||
document
|
||||
.add_entity(EntityType::Dimension(Dimension::Linear(dimension)))
|
||||
.add_entity(EntityType::Dimension(Box::new(Dimension::Linear(dimension))))
|
||||
.unwrap();
|
||||
crate::modules::draw::modify::explode::bake_dimension_blocks(&mut document);
|
||||
|
||||
@@ -2152,7 +2145,7 @@ mod tests {
|
||||
dimension.base.text_middle_point = Vector3::new(5.0, 5.0, 0.0);
|
||||
app.tabs[app.active_tab]
|
||||
.scene
|
||||
.add_entity(EntityType::Dimension(Dimension::Linear(dimension)));
|
||||
.add_entity(EntityType::Dimension(Box::new(Dimension::Linear(dimension))));
|
||||
let path = std::env::temp_dir().join(format!(
|
||||
"ocs_verified_dimension_{}_{}.dwg",
|
||||
std::process::id(),
|
||||
@@ -2190,7 +2183,7 @@ mod tests {
|
||||
let i = app.active_tab;
|
||||
app.tabs[i]
|
||||
.scene
|
||||
.add_entity(EntityType::Dimension(Dimension::Linear(dimension)));
|
||||
.add_entity(EntityType::Dimension(Box::new(Dimension::Linear(dimension))));
|
||||
let path = std::env::temp_dir().join(format!(
|
||||
"ocs_verified_dimension_follow_up_{}_{}.dwg",
|
||||
std::process::id(),
|
||||
@@ -2240,7 +2233,7 @@ mod tests {
|
||||
app.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.add_entity(EntityType::Dimension(Dimension::Linear(d)))
|
||||
.add_entity(EntityType::Dimension(Box::new(Dimension::Linear(d))))
|
||||
.unwrap();
|
||||
let path = std::env::temp_dir().join(format!(
|
||||
"ocs_verified_dim_{}_{}.dwg",
|
||||
@@ -2330,7 +2323,7 @@ mod tests {
|
||||
viewport.width = 100.0;
|
||||
viewport.height = 50.0;
|
||||
viewport.turn_on();
|
||||
scene.add_entity(codec::EntityType::Viewport(viewport));
|
||||
scene.add_entity(codec::EntityType::Viewport(Box::new(viewport)));
|
||||
for entity in scene.document.entities_mut() {
|
||||
if let codec::EntityType::Viewport(viewport) = entity {
|
||||
viewport.status.grid_on = true;
|
||||
@@ -2774,7 +2767,7 @@ mod tests {
|
||||
codec::MText::with_value("\\A1;10.5000", codec::types::Vector3::new(10.0, 20.0, 0.0));
|
||||
app.tabs[i]
|
||||
.scene
|
||||
.add_entity(codec::EntityType::MText(mtext));
|
||||
.add_entity(codec::EntityType::MText(Box::new(mtext)));
|
||||
|
||||
let q = app.automation_op(
|
||||
r#"{"op":"query","type":"MTEXT","detail":"full","fields":["value","text","height","bounds"]}"#,
|
||||
@@ -3642,7 +3635,7 @@ mod tests {
|
||||
let mut dst_mtext = MText::new();
|
||||
dst_mtext.value = "DSTM".into();
|
||||
dst_mtext.height = 2.0;
|
||||
let dst_mtext_h = app.tabs[i].scene.add_entity(EntityType::MText(dst_mtext));
|
||||
let dst_mtext_h = app.tabs[i].scene.add_entity(EntityType::MText(Box::new(dst_mtext)));
|
||||
|
||||
// Drive the interactive command exactly as the viewport does:
|
||||
// phase 1 source pick, phase 2 destination selection.
|
||||
@@ -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));
|
||||
|
||||
@@ -1405,7 +1405,7 @@ mod paste_entities_kernel_tests {
|
||||
fn remaps_leader_annotation_link_to_pasted_handles() {
|
||||
let mut seed = codec::CadDocument::new();
|
||||
let note = seed
|
||||
.add_entity(codec::EntityType::MText(codec::entities::MText::new()))
|
||||
.add_entity(codec::EntityType::MText(Box::new(codec::entities::MText::new())))
|
||||
.unwrap();
|
||||
let mut leader = codec::entities::Leader::default();
|
||||
leader.annotation_handle = note;
|
||||
|
||||
@@ -36,10 +36,12 @@ impl OpenCADStudio {
|
||||
) {
|
||||
let i = self.active_tab;
|
||||
let valid = matches!(
|
||||
self.tabs[i].scene.document.get_entity(dimension),
|
||||
Some(codec::EntityType::Dimension(
|
||||
codec::entities::Dimension::Linear(_) | codec::entities::Dimension::Aligned(_)
|
||||
))
|
||||
self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity(dimension)
|
||||
.and_then(crate::entities::dimension::as_dimension),
|
||||
Some(codec::entities::Dimension::Linear(_) | codec::entities::Dimension::Aligned(_))
|
||||
);
|
||||
if !valid {
|
||||
self.command_line.push_error(
|
||||
@@ -415,8 +417,12 @@ fn apply_dimspace(
|
||||
requested_spacing: Option<f64>,
|
||||
) -> bool {
|
||||
use codec::entities::Dimension;
|
||||
let (axis, normal, definition, auto_spacing) = match scene.document.get_entity(base_h) {
|
||||
Some(codec::EntityType::Dimension(dimension @ Dimension::Linear(d))) => {
|
||||
let base = scene
|
||||
.document
|
||||
.get_entity(base_h)
|
||||
.and_then(crate::entities::dimension::as_dimension);
|
||||
let (axis, normal, definition, auto_spacing) = match base {
|
||||
Some(dimension @ Dimension::Linear(d)) => {
|
||||
let spacing = dimension_auto_spacing(
|
||||
&scene.document,
|
||||
dimension,
|
||||
@@ -433,7 +439,7 @@ fn apply_dimspace(
|
||||
spacing,
|
||||
)
|
||||
}
|
||||
Some(codec::EntityType::Dimension(dimension @ Dimension::Aligned(d))) => {
|
||||
Some(dimension @ Dimension::Aligned(d)) => {
|
||||
let spacing = dimension_auto_spacing(
|
||||
&scene.document,
|
||||
dimension,
|
||||
@@ -471,7 +477,7 @@ fn apply_dimspace(
|
||||
kernel::space::move_to_dimension_spacing([p.x, p.y, p.z], frame, target);
|
||||
*p = codec::types::Vector3::new(point[0], point[1], point[2]);
|
||||
};
|
||||
match dim {
|
||||
match &mut **dim {
|
||||
Dimension::Linear(d) => {
|
||||
slide(&mut d.definition_point);
|
||||
d.base.definition_point = d.definition_point;
|
||||
|
||||
@@ -39,10 +39,8 @@ impl OpenCADStudio {
|
||||
let delta_safe = self.delta_add_safe(i, &entity);
|
||||
let pending = self.begin_undo(i, label, 1, delta_safe);
|
||||
let is_associative_dimension = matches!(
|
||||
entity,
|
||||
codec::EntityType::Dimension(
|
||||
codec::entities::Dimension::Linear(_) | codec::entities::Dimension::Aligned(_)
|
||||
)
|
||||
crate::entities::dimension::as_dimension(&entity),
|
||||
Some(codec::entities::Dimension::Linear(_) | codec::entities::Dimension::Aligned(_))
|
||||
);
|
||||
let association_enabled = self.tabs[i].scene.document.header.dimension_associativity == 2;
|
||||
let committed = if preserve_commit_layer {
|
||||
@@ -257,10 +255,8 @@ impl OpenCADStudio {
|
||||
};
|
||||
let pending = self.begin_undo(i, label, 1, delta_safe);
|
||||
let is_associative_dimension = matches!(
|
||||
entity,
|
||||
codec::EntityType::Dimension(
|
||||
codec::entities::Dimension::Linear(_) | codec::entities::Dimension::Aligned(_)
|
||||
)
|
||||
crate::entities::dimension::as_dimension(&entity),
|
||||
Some(codec::entities::Dimension::Linear(_) | codec::entities::Dimension::Aligned(_))
|
||||
);
|
||||
let association_enabled = self.tabs[i].scene.document.header.dimension_associativity == 2;
|
||||
let committed = if preserve_commit_layer {
|
||||
@@ -324,8 +320,8 @@ impl OpenCADStudio {
|
||||
let label = self.history_label_from_active_cmd(i, "DIMENSION");
|
||||
let association_mode = self.tabs[i].scene.document.header.dimension_associativity;
|
||||
let single_source_dimension = matches!(
|
||||
&entity,
|
||||
codec::EntityType::Dimension(codec::entities::Dimension::Ordinate(_))
|
||||
crate::entities::dimension::as_dimension(&entity),
|
||||
Some(codec::entities::Dimension::Ordinate(_))
|
||||
);
|
||||
// A dimension placed on the sheet but measuring model geometry
|
||||
// through a viewport carries the compensation as a negative
|
||||
|
||||
@@ -160,7 +160,7 @@ impl OpenCADStudio {
|
||||
Operation::Rotate(degrees) => {
|
||||
dimension.base_mut().text_rotation = degrees.to_radians();
|
||||
}
|
||||
Operation::Oblique(degrees) => match dimension {
|
||||
Operation::Oblique(degrees) => match &mut **dimension {
|
||||
codec::entities::Dimension::Linear(value) => {
|
||||
value.ext_line_rotation = degrees.to_radians();
|
||||
}
|
||||
|
||||
@@ -463,7 +463,7 @@ impl OpenCADStudio {
|
||||
false
|
||||
}
|
||||
};
|
||||
match dim {
|
||||
match &mut **dim {
|
||||
Dimension::Linear(d) => {
|
||||
stretched |= mv(&mut d.first_point);
|
||||
stretched |= mv(&mut d.second_point);
|
||||
|
||||
@@ -21,7 +21,7 @@ impl OpenCADStudio {
|
||||
});
|
||||
let label = self.history_label_from_active_cmd(i, "MVIEW");
|
||||
let pending = self.begin_undo(i, label, 1, true);
|
||||
let handle = self.commit_entity_handle(codec::EntityType::Viewport(viewport));
|
||||
let handle = self.commit_entity_handle(codec::EntityType::Viewport(Box::new(viewport)));
|
||||
if let (Some(handle), Some(saved)) = (handle, saved_view) {
|
||||
if let Some(codec::EntityType::Viewport(viewport)) =
|
||||
self.tabs[i].scene.document.get_entity_mut(handle)
|
||||
@@ -244,7 +244,8 @@ impl OpenCADStudio {
|
||||
viewport.id = 2;
|
||||
viewport.clip_boundary_handle = clip_handle;
|
||||
viewport.status.non_rectangular_clipping = !clip_handle.is_null();
|
||||
let viewport_handle = self.commit_entity_handle(codec::EntityType::Viewport(viewport));
|
||||
let viewport_handle =
|
||||
self.commit_entity_handle(codec::EntityType::Viewport(Box::new(viewport)));
|
||||
if let Some(viewport_handle) = viewport_handle {
|
||||
if !created_boundary {
|
||||
let before = self.tabs[i]
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -294,7 +294,7 @@ impl OpenCADStudio {
|
||||
} else {
|
||||
line.layer
|
||||
};
|
||||
scene.add_entity(codec::EntityType::MText(mtext));
|
||||
scene.add_entity(codec::EntityType::MText(Box::new(mtext)));
|
||||
erase.extend(line.handles);
|
||||
}
|
||||
erase.sort();
|
||||
|
||||
@@ -2389,7 +2389,7 @@ impl OpenCADStudio {
|
||||
Vector3::new(mv.center.x - mv.target.x, mv.center.y - mv.target.y, 0.0);
|
||||
vp.view_height = mv.height;
|
||||
vp.custom_scale = scale;
|
||||
scene.add_entity(codec::EntityType::Viewport(vp));
|
||||
scene.add_entity(codec::EntityType::Viewport(Box::new(vp)));
|
||||
// A paper-space named view of the same name frames it.
|
||||
let mut named = codec::tables::View::new(title.as_str());
|
||||
named.handle = scene.document.allocate_handle();
|
||||
|
||||
@@ -3671,53 +3671,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -126,10 +126,10 @@ mod tests {
|
||||
let i = app.active_tab;
|
||||
let first = app.tabs[i]
|
||||
.scene
|
||||
.add_entity(codec::EntityType::Viewport(codec::entities::Viewport::new()));
|
||||
.add_entity(codec::EntityType::Viewport(Box::new(codec::entities::Viewport::new())));
|
||||
let second = app.tabs[i]
|
||||
.scene
|
||||
.add_entity(codec::EntityType::Viewport(codec::entities::Viewport::new()));
|
||||
.add_entity(codec::EntityType::Viewport(Box::new(codec::entities::Viewport::new())));
|
||||
app.tabs[i].scene.active_viewport = Some(first);
|
||||
let _ = app.run_command_line("UCSFOLLOW 1");
|
||||
assert!(app.tabs[i].scene.ucs_follow());
|
||||
|
||||
@@ -1000,7 +1000,10 @@ impl OpenCADStudio {
|
||||
match self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.add_entity_to_layout(codec::EntityType::Viewport(vp), &layout_name)
|
||||
.add_entity_to_layout(
|
||||
codec::EntityType::Viewport(Box::new(vp)),
|
||||
&layout_name,
|
||||
)
|
||||
{
|
||||
Ok(handle) => {
|
||||
self.tabs[i].scene.auto_fit_viewport(handle);
|
||||
@@ -2207,7 +2210,7 @@ mod tests {
|
||||
locked_hatch.common.layer = "LOCKED_LAYER".into();
|
||||
let h_locked_hatch = app.tabs[i]
|
||||
.scene
|
||||
.add_entity_clone(EntityType::Hatch(locked_hatch));
|
||||
.add_entity_clone(EntityType::Hatch(Box::new(locked_hatch)));
|
||||
|
||||
let h_line = app.tabs[i]
|
||||
.scene
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -352,7 +352,7 @@ impl OpenCADStudio {
|
||||
|
||||
fn is_angular_dimension(entity: &EntityType) -> bool {
|
||||
matches!(
|
||||
entity,
|
||||
EntityType::Dimension(Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_))
|
||||
crate::entities::dimension::as_dimension(entity),
|
||||
Some(Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_))
|
||||
)
|
||||
}
|
||||
@@ -841,7 +841,7 @@ impl super::OpenCADStudio {
|
||||
// catastrophic cancellation. Insertion is a pure translation, so zeroing
|
||||
// it keeps the layout's shape, wrapping and alignment identical.
|
||||
mt.insertion_point = Vector3::new(0.0, 0.0, 0.0);
|
||||
let entity = EntityType::MText(mt.clone());
|
||||
let entity = EntityType::MText(Box::new(mt.clone()));
|
||||
let anno = 1.0;
|
||||
let bg = self.tabs[i].scene.bg_color;
|
||||
let wires: Vec<WireModel> = tessellate::tessellate(
|
||||
@@ -1753,7 +1753,8 @@ impl super::OpenCADStudio {
|
||||
));
|
||||
mt.insertion_point = codec::types::Vector3::new(position.x, position.y, position.z);
|
||||
self.push_undo_snapshot(i, "MTEXT");
|
||||
let handle = self.commit_entity_handle(plane.place_entity(EntityType::MText(mt)));
|
||||
let handle =
|
||||
self.commit_entity_handle(plane.place_entity(EntityType::MText(Box::new(mt))));
|
||||
if annotative {
|
||||
let scale = self.tabs[i].scene.creation_annotation_scale_handle();
|
||||
if let (Some(handle), Some(scale)) = (handle, scale) {
|
||||
@@ -1869,7 +1870,8 @@ impl super::OpenCADStudio {
|
||||
));
|
||||
mt.insertion_point = codec::types::Vector3::new(position.x, position.y, position.z);
|
||||
self.push_undo_snapshot(i, "MTEXT");
|
||||
let handle = self.commit_entity_handle(plane.place_entity(EntityType::MText(mt)));
|
||||
let handle =
|
||||
self.commit_entity_handle(plane.place_entity(EntityType::MText(Box::new(mt))));
|
||||
self.tabs[i].dirty = true;
|
||||
if annotative {
|
||||
let scale = self.tabs[i].scene.creation_annotation_scale_handle();
|
||||
@@ -1885,7 +1887,7 @@ impl super::OpenCADStudio {
|
||||
if let (Some(h), Some(ed)) = (handle, self.mtext_editor.as_mut()) {
|
||||
ed.editing = Some(h);
|
||||
if let Some(EntityType::MText(mtext)) = self.tabs[i].scene.document.get_entity(h) {
|
||||
ed.original = Some(mtext.clone());
|
||||
ed.original = Some((**mtext).clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,7 +497,7 @@ mod tests {
|
||||
vp.width = 200.;
|
||||
vp.height = 100.;
|
||||
vp.view_height = 50.;
|
||||
let handle = scene.add_entity(codec::EntityType::Viewport(vp));
|
||||
let handle = scene.add_entity(codec::EntityType::Viewport(Box::new(vp)));
|
||||
scene.active_viewport = Some(handle);
|
||||
let mut camera = scene.navigation_camera();
|
||||
camera.target = DVec3::new(120., 30., 8.);
|
||||
|
||||
@@ -943,7 +943,7 @@ impl<'a> HostSession<'a> {
|
||||
frame.common.layer = layer;
|
||||
}
|
||||
self.push_undo("Embed picture");
|
||||
let handle = self.add_entity(EntityType::Ole2Frame(frame));
|
||||
let handle = self.add_entity(EntityType::Ole2Frame(Box::new(frame)));
|
||||
if handle.is_null() {
|
||||
return Err("the picture frame could not be added".into());
|
||||
}
|
||||
@@ -6708,7 +6708,7 @@ mod tests {
|
||||
let EntityType::Dimension(dimension) = &mut after else {
|
||||
unreachable!()
|
||||
};
|
||||
if let codec::entities::Dimension::Linear(value) = dimension {
|
||||
if let codec::entities::Dimension::Linear(value) = &mut **dimension {
|
||||
value.second_point.x = 30.0;
|
||||
}
|
||||
ocs_plugin_api::entity_coverage::validate_entity_mutation(&before, &after).unwrap();
|
||||
@@ -7414,7 +7414,7 @@ mod tests {
|
||||
.unwrap();
|
||||
let mut viewport = codec::entities::Viewport::new();
|
||||
viewport.common.owner_handle = owner;
|
||||
let viewport_handle = host.add_entity(EntityType::Viewport(viewport));
|
||||
let viewport_handle = host.add_entity(EntityType::Viewport(Box::new(viewport)));
|
||||
let scale_handle = host.document_mut().allocate_handle();
|
||||
let mut scale = codec::objects::Scale::new("1:1", 1.0, 1.0);
|
||||
scale.handle = scale_handle;
|
||||
@@ -10861,7 +10861,7 @@ mod tests {
|
||||
bare.height = 30.0;
|
||||
let bare = host
|
||||
.document_mut()
|
||||
.add_entity(EntityType::Viewport(bare))
|
||||
.add_entity(EntityType::Viewport(Box::new(bare)))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
host.document().get_entity(bare).map(|e| match e {
|
||||
|
||||
+37
-33
@@ -1489,12 +1489,14 @@ impl OpenCADStudio {
|
||||
if let Some((anno_field, insert_after)) = anno {
|
||||
let is_anno = crate::scene::annotative::is_annotative(doc, entity)
|
||||
|| match entity {
|
||||
codec::EntityType::Dimension(codec::entities::Dimension::Arc(
|
||||
dimension,
|
||||
)) => crate::scene::annotative::dim_style_is_annotative(
|
||||
doc,
|
||||
&dimension.base.style_name,
|
||||
),
|
||||
codec::EntityType::Dimension(dimension)
|
||||
if matches!(**dimension, codec::entities::Dimension::Arc(_)) =>
|
||||
{
|
||||
crate::scene::annotative::dim_style_is_annotative(
|
||||
doc,
|
||||
&dimension.base().style_name,
|
||||
)
|
||||
}
|
||||
codec::EntityType::Tolerance(_) => {
|
||||
crate::scene::annotative::annotation_style_is_annotative(doc, entity)
|
||||
}
|
||||
@@ -1530,7 +1532,9 @@ impl OpenCADStudio {
|
||||
"annotative",
|
||||
yes_no_choice(t.is_annotative || is_anno),
|
||||
),
|
||||
codec::EntityType::Dimension(codec::entities::Dimension::Arc(_)) => {
|
||||
codec::EntityType::Dimension(dimension)
|
||||
if matches!(**dimension, codec::entities::Dimension::Arc(_)) =>
|
||||
{
|
||||
set_row(
|
||||
&mut sections,
|
||||
"annotative",
|
||||
@@ -1683,10 +1687,14 @@ impl OpenCADStudio {
|
||||
format!("{} ({})", t!("Surface"), t!("Extrusion"))
|
||||
}
|
||||
// The reference names an angular dimension by its kind.
|
||||
codec::EntityType::Dimension(codec::entities::Dimension::Angular2Ln(_)) => {
|
||||
codec::EntityType::Dimension(dimension)
|
||||
if matches!(**dimension, codec::entities::Dimension::Angular2Ln(_)) =>
|
||||
{
|
||||
t!("Angular Dimension").into_owned()
|
||||
}
|
||||
codec::EntityType::Dimension(codec::entities::Dimension::Angular3Pt(_)) => {
|
||||
codec::EntityType::Dimension(dimension)
|
||||
if matches!(**dimension, codec::entities::Dimension::Angular3Pt(_)) =>
|
||||
{
|
||||
t!("3 Point Angular Dimension").into_owned()
|
||||
}
|
||||
codec::EntityType::Underlay(underlay) => match underlay.underlay_type {
|
||||
@@ -1719,22 +1727,14 @@ impl OpenCADStudio {
|
||||
title
|
||||
} else {
|
||||
use codec::entities::Dimension;
|
||||
match entity {
|
||||
codec::EntityType::Dimension(Dimension::Aligned(_)) => {
|
||||
t!("Aligned Dimensional Constraint")
|
||||
}
|
||||
codec::EntityType::Dimension(Dimension::Angular2Ln(_)) => {
|
||||
t!("Angular Dimension (Dynamic)")
|
||||
}
|
||||
codec::EntityType::Dimension(Dimension::Angular3Pt(_)) => {
|
||||
match crate::entities::dimension::as_dimension(entity) {
|
||||
Some(Dimension::Aligned(_)) => t!("Aligned Dimensional Constraint"),
|
||||
Some(Dimension::Angular2Ln(_)) => t!("Angular Dimension (Dynamic)"),
|
||||
Some(Dimension::Angular3Pt(_)) => {
|
||||
t!("3 Point Angular Dimension (Dynamic)")
|
||||
}
|
||||
codec::EntityType::Dimension(Dimension::Radius(_)) => {
|
||||
t!("Radius Dimensional Constraint")
|
||||
}
|
||||
codec::EntityType::Dimension(Dimension::Diameter(_)) => {
|
||||
t!("Diameter Dimensional Constraint")
|
||||
}
|
||||
Some(Dimension::Radius(_)) => t!("Radius Dimensional Constraint"),
|
||||
Some(Dimension::Diameter(_)) => t!("Diameter Dimensional Constraint"),
|
||||
_ => t!("Linear Dimensional Constraint"),
|
||||
}
|
||||
.into_owned()
|
||||
@@ -2743,7 +2743,7 @@ handles={handles_ms:.1} panel={:.1} ribbon={ribbon_ms:.1} tail={:.1} selected={}
|
||||
// scale are available.
|
||||
if let codec::EntityType::Dimension(dim) = contextual.as_ref() {
|
||||
if matches!(
|
||||
dim,
|
||||
&**dim,
|
||||
codec::entities::Dimension::Linear(_)
|
||||
| codec::entities::Dimension::Aligned(_)
|
||||
) && !dim.base().text_user_positioned
|
||||
@@ -2780,13 +2780,17 @@ handles={handles_ms:.1} panel={:.1} ribbon={ribbon_ms:.1} tail={:.1} selected={}
|
||||
}
|
||||
}
|
||||
}
|
||||
let dynamic_angle = match contextual.as_ref() {
|
||||
codec::EntityType::Dimension(
|
||||
dim @ (codec::entities::Dimension::Angular2Ln(_)
|
||||
| codec::entities::Dimension::Angular3Pt(_)),
|
||||
) if self.tabs[i].scene.is_dynamic_dimension(handle) => Some(dim),
|
||||
_ => None,
|
||||
};
|
||||
let contextual_dimension =
|
||||
crate::entities::dimension::as_dimension(contextual.as_ref());
|
||||
let dynamic_angle = contextual_dimension
|
||||
.filter(|dim| {
|
||||
matches!(
|
||||
dim,
|
||||
codec::entities::Dimension::Angular2Ln(_)
|
||||
| codec::entities::Dimension::Angular3Pt(_)
|
||||
)
|
||||
})
|
||||
.filter(|_| self.tabs[i].scene.is_dynamic_dimension(handle));
|
||||
if let Some(dim) = dynamic_angle {
|
||||
// A dynamic angle shows a triangle at each end of its arc
|
||||
// pointing away from it, and squares at the arc point and
|
||||
@@ -3060,8 +3064,8 @@ handles={handles_ms:.1} panel={:.1} ribbon={ribbon_ms:.1} tail={:.1} selected={}
|
||||
) -> Option<Handle> {
|
||||
let i = self.active_tab;
|
||||
let tracks_dimension_chain = matches!(
|
||||
&entity,
|
||||
codec::EntityType::Dimension(
|
||||
crate::entities::dimension::as_dimension(&entity),
|
||||
Some(
|
||||
codec::entities::Dimension::Linear(_)
|
||||
| codec::entities::Dimension::Aligned(_)
|
||||
| codec::entities::Dimension::Angular2Ln(_)
|
||||
|
||||
@@ -224,7 +224,7 @@ fn fixture_dimension_reference(app: &mut OpenCADStudio) -> String {
|
||||
dimension.base.style_name = "BO_ARROWSTYLE".to_string();
|
||||
app.tabs[i]
|
||||
.scene
|
||||
.add_entity(EntityType::Dimension(Dimension::Aligned(dimension)));
|
||||
.add_entity(EntityType::Dimension(Box::new(Dimension::Aligned(dimension))));
|
||||
name.to_string()
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ fn fixture_dimension_name_reference(app: &mut OpenCADStudio) -> String {
|
||||
dimension.base.style_name = "BO_ARROWNAMESTYLE".to_string();
|
||||
app.tabs[i]
|
||||
.scene
|
||||
.add_entity(EntityType::Dimension(Dimension::Aligned(dimension)));
|
||||
.add_entity(EntityType::Dimension(Box::new(Dimension::Aligned(dimension))));
|
||||
name.to_string()
|
||||
}
|
||||
|
||||
|
||||
@@ -2913,9 +2913,12 @@ impl OpenCADStudio {
|
||||
&value,
|
||||
);
|
||||
if applied && field == "dim_text_inside" {
|
||||
if let Some(codec::EntityType::Dimension(
|
||||
codec::entities::Dimension::LargeRadial(dimension),
|
||||
)) = app.tabs[i].scene.document.get_entity_mut(handle)
|
||||
let radial = app.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity_mut(handle)
|
||||
.and_then(crate::entities::dimension::as_dimension_mut);
|
||||
if let Some(codec::entities::Dimension::LargeRadial(dimension)) = radial
|
||||
{
|
||||
dimension.base.text_user_positioned = false;
|
||||
}
|
||||
@@ -4182,9 +4185,13 @@ impl OpenCADStudio {
|
||||
&val,
|
||||
);
|
||||
if applied && field == "dim_text_inside" {
|
||||
if let Some(codec::EntityType::Dimension(
|
||||
codec::entities::Dimension::LargeRadial(dimension),
|
||||
)) = self.tabs[i].scene.document.get_entity_mut(handle)
|
||||
if let Some(codec::entities::Dimension::LargeRadial(
|
||||
dimension,
|
||||
)) = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.get_entity_mut(handle)
|
||||
.and_then(crate::entities::dimension::as_dimension_mut)
|
||||
{
|
||||
dimension.base.text_user_positioned = false;
|
||||
}
|
||||
|
||||
@@ -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
@@ -2053,34 +2053,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 => {
|
||||
@@ -2180,9 +2159,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,
|
||||
|
||||
@@ -1084,7 +1084,7 @@ mod tests {
|
||||
.document
|
||||
.entities()
|
||||
.filter_map(|entity| match entity {
|
||||
EntityType::Hatch(hatch) => Some(hatch),
|
||||
EntityType::Hatch(hatch) => Some(&**hatch),
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
|
||||
@@ -28,7 +28,7 @@ fn fixture() -> (OpenCADStudio, Handle, ViewportFrame) {
|
||||
viewport.custom_scale = 0.1;
|
||||
viewport.view_direction = Vector3::UNIT_Z;
|
||||
viewport.turn_on();
|
||||
let viewport = scene.add_entity(EntityType::Viewport(viewport));
|
||||
let viewport = scene.add_entity(EntityType::Viewport(Box::new(viewport)));
|
||||
let frame = scene.viewport_frame(viewport).unwrap();
|
||||
scene.document.header.dimension_associativity = 2;
|
||||
let i = app.active_tab;
|
||||
@@ -323,7 +323,7 @@ fn viewport_dimension_linear_aligned_and_radial_object_picks() {
|
||||
assert_eq!(
|
||||
preview_text(
|
||||
&app,
|
||||
&EntityType::Dimension(dimension(&app.tabs[i].scene.document).clone())
|
||||
&EntityType::Dimension(Box::new(dimension(&app.tabs[i].scene.document).clone()))
|
||||
),
|
||||
value,
|
||||
"{name}"
|
||||
@@ -361,7 +361,7 @@ fn viewport_dimension_angular_preserves_angle_without_length_factor() {
|
||||
let _ = app.feed_command(StepInput::Point(place));
|
||||
let doc = &app.tabs[app.active_tab].scene.document;
|
||||
assert_eq!(
|
||||
preview_text(&app, &EntityType::Dimension(dimension(doc).clone())),
|
||||
preview_text(&app, &EntityType::Dimension(Box::new(dimension(doc).clone()))),
|
||||
value
|
||||
);
|
||||
assert!((dimension(doc).measurement() - 90.0).abs() < 1e-5);
|
||||
@@ -1461,9 +1461,9 @@ fn viewport_dimension_invalid_paths_leave_no_partial_association() {
|
||||
Vector3::new(55.0, 60.0, 0.0),
|
||||
)));
|
||||
for (source, path) in [(model, vec![Handle::new(u64::MAX)]), (leaf, vec![root])] {
|
||||
let dim = scene.add_entity(EntityType::Dimension(Dimension::Aligned(
|
||||
let dim = scene.add_entity(EntityType::Dimension(Box::new(Dimension::Aligned(
|
||||
DimensionAligned::new(Vector3::new(50.0, 50.0, 0.0), Vector3::new(55.0, 50.0, 0.0)),
|
||||
)));
|
||||
))));
|
||||
let invalid = AcceptedSnap {
|
||||
paper_point: DVec3::new(50.0, 50.0, 0.0),
|
||||
model_point: DVec3::ZERO,
|
||||
|
||||
@@ -3248,7 +3248,7 @@ pub(crate) fn materialize_large_radial_text_position(
|
||||
let Some(EntityType::Dimension(dimension)) = document.get_entity(handle) else {
|
||||
return;
|
||||
};
|
||||
if !matches!(dimension, Dimension::LargeRadial(_)) || dimension.base().text_user_positioned {
|
||||
if !matches!(&**dimension, Dimension::LargeRadial(_)) || dimension.base().text_user_positioned {
|
||||
return;
|
||||
}
|
||||
let source = document.dim_styles.iter().find(|style| {
|
||||
@@ -3262,8 +3262,8 @@ pub(crate) fn materialize_large_radial_text_position(
|
||||
.map(|style| resolved_dimension_style(style, dimension, document))
|
||||
.map(|style| styled_dimension_text_position(dimension, &style, 1.0))
|
||||
.unwrap_or_else(|| dimension_text_pos_f64(dimension, None, 2.5, 1.0));
|
||||
if let Some(EntityType::Dimension(Dimension::LargeRadial(radial))) =
|
||||
document.get_entity_mut(handle)
|
||||
if let Some(Dimension::LargeRadial(radial)) =
|
||||
document.get_entity_mut(handle).and_then(as_dimension_mut)
|
||||
{
|
||||
radial.base.text_middle_point = position;
|
||||
}
|
||||
@@ -6491,7 +6491,7 @@ fn dimension_text_entity(
|
||||
}
|
||||
mtext.normal = base.normal;
|
||||
mtext.common = base.common.clone();
|
||||
return Some(EntityType::MText(mtext));
|
||||
return Some(EntityType::MText(Box::new(mtext)));
|
||||
}
|
||||
|
||||
let value = if style.is_some_and(|style| style.dimtxtdirection) {
|
||||
@@ -7150,7 +7150,7 @@ fn dimension_tolerance_entity(
|
||||
mtext.style = primary_style;
|
||||
mtext.attachment_point = codec::entities::AttachmentPoint::MiddleCenter;
|
||||
mtext.common = primary_common;
|
||||
return Some(EntityType::MText(mtext));
|
||||
return Some(EntityType::MText(Box::new(mtext)));
|
||||
}
|
||||
|
||||
let mut t = Text::with_value(tol, pos)
|
||||
@@ -9559,3 +9559,22 @@ mod layout_parity_tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The dimension an entity is, if it is one. The variant holds it boxed, so
|
||||
/// a pattern cannot reach into it; match on this instead.
|
||||
pub(crate) fn as_dimension(entity: &codec::EntityType) -> Option<&codec::entities::Dimension> {
|
||||
match entity {
|
||||
codec::EntityType::Dimension(dimension) => Some(dimension),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// [`as_dimension`], mutably.
|
||||
pub(crate) fn as_dimension_mut(
|
||||
entity: &mut codec::EntityType,
|
||||
) -> Option<&mut codec::entities::Dimension> {
|
||||
match entity {
|
||||
codec::EntityType::Dimension(dimension) => Some(dimension),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -620,7 +620,7 @@ fn dynamic_data_text(data: &DynamicBlockData) -> String {
|
||||
"{}; dependencies {}; parameters {}; distance multiplier {:.6}; angle {:.6}",
|
||||
value.action.element.name,
|
||||
value.action.dependencies.len(),
|
||||
value.action.parameter_ids.len(),
|
||||
value.action.action_ids.len(),
|
||||
value.offsets.distance_multiplier,
|
||||
value.offsets.angle_offset
|
||||
),
|
||||
@@ -628,14 +628,14 @@ fn dynamic_data_text(data: &DynamicBlockData) -> String {
|
||||
"{}; dependencies {}; parameters {}; connections {}",
|
||||
value.action.element.name,
|
||||
value.action.dependencies.len(),
|
||||
value.action.parameter_ids.len(),
|
||||
value.action.action_ids.len(),
|
||||
value.connections.len()
|
||||
),
|
||||
DynamicBlockData::RotateAction(value) | DynamicBlockData::ScaleAction(value) => format!(
|
||||
"{}; dependencies {}; parameters {}; base {:.6},{:.6},{:.6}; dependent {}",
|
||||
value.action.action.element.name,
|
||||
value.action.action.dependencies.len(),
|
||||
value.action.action.parameter_ids.len(),
|
||||
value.action.action.action_ids.len(),
|
||||
value.action.base_point.x,
|
||||
value.action.base_point.y,
|
||||
value.action.base_point.z,
|
||||
|
||||
@@ -676,7 +676,9 @@ pub fn adapt_mtext_paragraphs(
|
||||
ATab::Left(_) => TabKind::Left,
|
||||
ATab::Center(_) => TabKind::Center,
|
||||
ATab::Right(_) => TabKind::Right,
|
||||
ATab::Decimal(..) => TabKind::Decimal,
|
||||
ATab::Decimal(..) | ATab::DecimalWithCharacter { .. } => {
|
||||
TabKind::Decimal
|
||||
}
|
||||
};
|
||||
TabStop {
|
||||
position: ts.position() as f32 * entity_height,
|
||||
|
||||
@@ -158,6 +158,16 @@ pub trait TextContent {
|
||||
fn replace_text(&mut self, search: &str, rep: &str);
|
||||
}
|
||||
|
||||
/// Entity variants holding their entity boxed (MTEXT) dispatch through here.
|
||||
impl<T: TextContent + ?Sized> TextContent for Box<T> {
|
||||
fn text_content(&self) -> Option<String> {
|
||||
(**self).text_content()
|
||||
}
|
||||
fn replace_text(&mut self, search: &str, rep: &str) {
|
||||
(**self).replace_text(search, rep)
|
||||
}
|
||||
}
|
||||
|
||||
/// Human-readable variant name used by Quick Select / Select Similar
|
||||
/// filtering. Stable across releases — the strings here are the canonical
|
||||
/// type identifiers throughout the UI.
|
||||
|
||||
@@ -638,7 +638,7 @@ pub(crate) fn build_entity(
|
||||
if let Some(style) = text_style_field(spec, document)? {
|
||||
mtext.style = style;
|
||||
}
|
||||
EntityType::MText(mtext)
|
||||
EntityType::MText(Box::new(mtext))
|
||||
}
|
||||
"INSERT" => {
|
||||
let block = spec["block"].as_str().unwrap_or("");
|
||||
@@ -724,7 +724,7 @@ pub(crate) fn build_entity(
|
||||
let mut path = BoundaryPath::external();
|
||||
path.add_edge(BoundaryEdge::Polyline(PolylineEdge::new(vertices, true)));
|
||||
hatch.paths.push(path);
|
||||
EntityType::Hatch(hatch)
|
||||
EntityType::Hatch(Box::new(hatch))
|
||||
}
|
||||
other => {
|
||||
return Err(failure(
|
||||
|
||||
@@ -472,6 +472,7 @@ fn complex_elements(
|
||||
scale: decimal(*scale),
|
||||
rotation: decimal(*rot_deg).to_radians(),
|
||||
absolute_rotation: false,
|
||||
source_flags: 0,
|
||||
offset: [decimal(*x), decimal(*y)],
|
||||
}
|
||||
}
|
||||
@@ -492,6 +493,7 @@ fn complex_elements(
|
||||
scale: decimal(*scale),
|
||||
rotation: decimal(*rot_deg).to_radians(),
|
||||
absolute_rotation: false,
|
||||
source_flags: 0,
|
||||
offset: [decimal(*x), decimal(*y)],
|
||||
},
|
||||
};
|
||||
|
||||
+34
-408
@@ -130,7 +130,7 @@ fn recovery_fingerprint_needed(caches: &DerivedCaches) -> bool {
|
||||
crate::io::xref::XrefStatus::Recovered | crate::io::xref::XrefStatus::Failed
|
||||
)
|
||||
});
|
||||
parser_issue || caches.corrupt_dropped > 0 || caches.xref_dropped > 0 || reference_issue
|
||||
parser_issue || reference_issue
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -439,17 +439,10 @@ async fn open_path_with_phase_attempt(
|
||||
let parse_ms = t_parse.elapsed().as_millis() as u32;
|
||||
progress2.set(crate::app::OPEN_PHASE_PARSING, 5800, 1000, 1000);
|
||||
let t_purge = Instant::now();
|
||||
let dropped = purge_corrupt_entities(&mut doc);
|
||||
let purge_ms = t_purge.elapsed().as_millis() as u32;
|
||||
if matches!(attempt, OpenAttempt::Strict) && dropped > 0 {
|
||||
return Err(OpenAttemptFailure {
|
||||
message: format!(
|
||||
"normal read found {dropped} structurally invalid drawing records"
|
||||
),
|
||||
read_stats: Some(read_stats),
|
||||
recoverable: true,
|
||||
});
|
||||
if matches!(attempt, OpenAttempt::Recovery(..)) {
|
||||
repair_drawing(&mut doc);
|
||||
}
|
||||
let purge_ms = t_purge.elapsed().as_millis() as u32;
|
||||
// Before the geometry is prepared below: repairing the
|
||||
// drawing's stripped catalog linetypes after it would leave
|
||||
// the first view drawn without their shapes. None is added:
|
||||
@@ -457,7 +450,7 @@ async fn open_path_with_phase_attempt(
|
||||
crate::io::linetypes::repair_document(&mut doc);
|
||||
progress2.set(crate::app::OPEN_PHASE_XREF, 6000, 0, 1);
|
||||
let t_xref = Instant::now();
|
||||
let (xref_infos, xref_dropped) = if let Some(base_dir) = path2.parent() {
|
||||
let xref_infos = if let Some(base_dir) = path2.parent() {
|
||||
let xref_progress = {
|
||||
let progress = Arc::clone(&progress2);
|
||||
let callback: Arc<dyn Fn(usize, usize) + Send + Sync> =
|
||||
@@ -479,7 +472,7 @@ async fn open_path_with_phase_attempt(
|
||||
Some(xref_progress),
|
||||
)
|
||||
} else {
|
||||
(Vec::new(), 0)
|
||||
Vec::new()
|
||||
};
|
||||
let xref_ms = t_xref.elapsed().as_millis() as u32;
|
||||
progress2.set(crate::app::OPEN_PHASE_CACHING, 7400, 0, 10000);
|
||||
@@ -506,9 +499,7 @@ async fn open_path_with_phase_attempt(
|
||||
// Filled in after `prepare_open_geometry` below.
|
||||
finalize_ms: 0,
|
||||
};
|
||||
caches.corrupt_dropped = dropped;
|
||||
caches.read_stats = Some(read_stats);
|
||||
caches.xref_dropped = xref_dropped;
|
||||
caches.xrefs = xref_infos;
|
||||
if recovery_fingerprint_needed(&caches) {
|
||||
caches.source_sha256 =
|
||||
@@ -800,25 +791,17 @@ async fn load_web_bytes(
|
||||
fix_current_style_names(&mut doc);
|
||||
crate::entities::field::local_header_dates(&mut doc);
|
||||
progress.set(crate::app::OPEN_PHASE_CACHING, 7000, 0, 1);
|
||||
let dropped = purge_corrupt_entities(&mut doc);
|
||||
if !recovery_mode && dropped > 0 {
|
||||
return Err(OpenLoadError {
|
||||
message: format!("normal read found {dropped} structurally invalid drawing records"),
|
||||
source_sha256: None,
|
||||
read_stats: Some(outcome.stats),
|
||||
recovery_available: true,
|
||||
});
|
||||
if recovery_mode {
|
||||
repair_drawing(&mut doc);
|
||||
}
|
||||
// XREFs inside the opened drawing resolve from the in-session bytes
|
||||
// registry (see `resolve_web_xrefs`) before the caches snapshot the
|
||||
// document, so merged xref geometry is part of the caches — the same
|
||||
// phase order the native open uses.
|
||||
let (xref_infos, xref_dropped) = resolve_web_xrefs(&mut doc, progress.clone()).await;
|
||||
let xref_infos = resolve_web_xrefs(&mut doc, progress.clone()).await;
|
||||
let mut caches = crate::scene::build_derived_caches(&doc);
|
||||
caches.corrupt_dropped = dropped;
|
||||
caches.read_stats = Some(outcome.stats);
|
||||
caches.xrefs = xref_infos;
|
||||
caches.xref_dropped = xref_dropped;
|
||||
if source_sha256.is_none() && recovery_fingerprint_needed(&caches) {
|
||||
source_sha256 = web_worker::sha256_document(bytes).await.ok();
|
||||
}
|
||||
@@ -891,7 +874,7 @@ pub async fn parse_drawing_bytes_web(name: &str, bytes: &[u8]) -> Result<CadDocu
|
||||
async fn resolve_web_xrefs(
|
||||
doc: &mut CadDocument,
|
||||
progress: Arc<OpenProgressState>,
|
||||
) -> (Vec<crate::io::xref::XrefInfo>, usize) {
|
||||
) -> Vec<crate::io::xref::XrefInfo> {
|
||||
let pending: Vec<String> = doc
|
||||
.block_records
|
||||
.iter()
|
||||
@@ -995,7 +978,7 @@ pub fn load_bytes(name: &str, bytes: Vec<u8>) -> Result<CadDocument, String> {
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn load_bytes_finalized(path: &Path, bytes: Vec<u8>) -> Result<(CadDocument, usize), String> {
|
||||
pub fn load_bytes_finalized(path: &Path, bytes: Vec<u8>) -> Result<CadDocument, String> {
|
||||
let name = path
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy().into_owned())
|
||||
@@ -1006,8 +989,7 @@ pub fn load_bytes_finalized(path: &Path, bytes: Vec<u8>) -> Result<(CadDocument,
|
||||
doc.source_path = Some(path.to_string_lossy().into_owned());
|
||||
// Opening is a field evaluation event (FIELDEVAL bit 1).
|
||||
crate::entities::field::update_fields(&mut doc, 1, None);
|
||||
let dropped = purge_corrupt_entities(&mut doc);
|
||||
Ok((doc, dropped))
|
||||
Ok(doc)
|
||||
}
|
||||
|
||||
/// Load a DWG or DXF file directly from a path (auto-detect by extension).
|
||||
@@ -1254,7 +1236,6 @@ fn finalize_loaded_outcome(
|
||||
let doc = &mut outcome.document;
|
||||
normalize_block_origins(doc);
|
||||
materialize_missing_block_markers(doc);
|
||||
normalize_knotless_splines(doc);
|
||||
if outcome.stats.source_format == Some(codec::SourceFormat::Dxf) {
|
||||
fix_dxf_dimension_rotations(doc);
|
||||
fix_dxf_layout_plot_settings(doc);
|
||||
@@ -2679,174 +2660,24 @@ fn sync_current_styles_on_save(doc: &mut CadDocument) {
|
||||
set_sketch_settings(doc, sketch_type, increment, tolerance);
|
||||
}
|
||||
|
||||
// ── Corrupt-entity guard ──────────────────────────────────────────────────
|
||||
//
|
||||
// opencadcodec's DWG parser occasionally desynchronises on certain files and
|
||||
// produces entities with garbage fields: non-unit normals (components in
|
||||
// 1e200+), nonsensical vertex counts (e.g. 100000), or infinite/NaN
|
||||
// coordinates. Tessellating such entities triggers huge allocations and
|
||||
// numerical blow-ups in the wire pipeline.
|
||||
//
|
||||
// `purge_corrupt_entities` scans the document and removes any entity that
|
||||
// fails a cheap sanity check, returning the number dropped so the caller can
|
||||
// surface it to the UI / log.
|
||||
//
|
||||
// Keep valid degenerate geometry: dropping it would trigger strict-open recovery.
|
||||
|
||||
fn finite_unit_normal(n: &codec::types::Vector3) -> bool {
|
||||
let (x, y, z) = (n.x, n.y, n.z);
|
||||
if !x.is_finite() || !y.is_finite() || !z.is_finite() {
|
||||
return false;
|
||||
/// Recovery runs the codec's repair and reports what it did, and what it
|
||||
/// left, through the drawing's notifications.
|
||||
pub(crate) fn repair_drawing(doc: &mut CadDocument) {
|
||||
let repair = doc.repair();
|
||||
for action in &repair.actions {
|
||||
doc.notifications.notify(
|
||||
codec::notification::NotificationType::Warning,
|
||||
format!("Recovery repair: {action:?}"),
|
||||
);
|
||||
}
|
||||
let mag2 = x * x + y * y + z * z;
|
||||
// Accept anything within ~10% of unit length. Real files sometimes
|
||||
// store slightly denormalised normals from rounding.
|
||||
(mag2 - 1.0).abs() < 0.21
|
||||
}
|
||||
|
||||
fn finite_coord(v: f64) -> bool {
|
||||
v.is_finite() && v.abs() < 1.0e12
|
||||
}
|
||||
|
||||
fn finite_vec3(v: &codec::types::Vector3) -> bool {
|
||||
finite_coord(v.x) && finite_coord(v.y) && finite_coord(v.z)
|
||||
}
|
||||
|
||||
/// Returns true if the entity looks like parser garbage and should be dropped.
|
||||
pub(crate) fn is_entity_corrupt(e: &EntityType) -> bool {
|
||||
use codec::entities::EntityType as E;
|
||||
// Reject polylines at or above this vertex count. Even valid drawings
|
||||
// rarely use this many — and parser desync produces exactly-100_000-vertex
|
||||
// junk records.
|
||||
const MAX_VERTS: usize = 100_000;
|
||||
match e {
|
||||
E::LwPolyline(p) => {
|
||||
!finite_unit_normal(&p.normal)
|
||||
|| p.vertices.len() >= MAX_VERTS
|
||||
|| !finite_coord(p.elevation)
|
||||
|| p.elevation.abs() > 1.0e10
|
||||
|| !finite_coord(p.thickness)
|
||||
|| p.thickness.abs() > 1.0e10
|
||||
|| p.vertices
|
||||
.iter()
|
||||
.any(|v| !finite_coord(v.location.x) || !finite_coord(v.location.y))
|
||||
}
|
||||
E::Polyline2D(p) => {
|
||||
!finite_unit_normal(&p.normal)
|
||||
|| p.vertices.len() >= MAX_VERTS
|
||||
|| !finite_coord(p.elevation)
|
||||
|| p.elevation.abs() > 1.0e10
|
||||
|| !finite_coord(p.thickness)
|
||||
|| p.thickness.abs() > 1.0e10
|
||||
|| p.vertices.iter().any(|v| !finite_vec3(&v.location))
|
||||
}
|
||||
E::Polyline3D(p) => {
|
||||
p.vertices.len() >= MAX_VERTS || p.vertices.iter().any(|v| !finite_vec3(&v.position))
|
||||
}
|
||||
E::Polyline(p) => {
|
||||
p.vertices.len() >= MAX_VERTS || p.vertices.iter().any(|v| !finite_vec3(&v.location))
|
||||
}
|
||||
E::Line(l) => !finite_vec3(&l.start) || !finite_vec3(&l.end),
|
||||
// Zero-radius curves are valid and have bounded kernel tessellation.
|
||||
E::Circle(c) => {
|
||||
!finite_vec3(&c.center) || !finite_coord(c.radius) || c.radius.abs() > 1.0e10
|
||||
}
|
||||
E::Arc(a) => {
|
||||
!finite_vec3(&a.center)
|
||||
|| !finite_coord(a.radius)
|
||||
|| !a.start_angle.is_finite()
|
||||
|| !a.end_angle.is_finite()
|
||||
|| a.radius.abs() > 1.0e10
|
||||
|| !finite_unit_normal(&a.normal)
|
||||
}
|
||||
E::Ellipse(e) => {
|
||||
!finite_vec3(&e.center)
|
||||
|| !finite_vec3(&e.major_axis)
|
||||
|| !e.start_parameter.is_finite()
|
||||
|| !e.end_parameter.is_finite()
|
||||
|| (e.end_parameter - e.start_parameter).abs() < 1.0e-9
|
||||
|| {
|
||||
let m2 = e.major_axis.x * e.major_axis.x
|
||||
+ e.major_axis.y * e.major_axis.y
|
||||
+ e.major_axis.z * e.major_axis.z;
|
||||
!m2.is_finite() || m2 < 1.0e-20 || m2 > 1.0e20
|
||||
}
|
||||
|| !e.minor_axis_ratio.is_finite()
|
||||
|| e.minor_axis_ratio.abs() < 1.0e-10
|
||||
}
|
||||
E::Spline(s) => {
|
||||
// Parser desync emits exactly-100_000-control-point splines with a
|
||||
// garbage knot vector. Building a NURBS from one and
|
||||
// tessellating it runs `parameter_division` into an unbounded
|
||||
// allocation — single-threaded, 32 GB+ — long before the drawing
|
||||
// finishes loading. Reject the desync signature plus any spline
|
||||
// the kernel can't build: non-finite control points, or a knot vector
|
||||
// that's non-finite, non-monotonic, or the wrong length
|
||||
// (the kernel requires `knots.len() == ctrl.len() + degree + 1`).
|
||||
let n = s.control_points.len();
|
||||
let degree_bad = s.degree < 1;
|
||||
let deg = s.degree.max(0) as usize;
|
||||
let compact_periodic_knots = s.flags.periodic && s.knots.len() == n.saturating_add(1);
|
||||
let knots_bad = !s.knots.is_empty()
|
||||
&& (s.knots.iter().any(|k| !k.is_finite())
|
||||
|| s.knots.windows(2).any(|w| w[1] < w[0])
|
||||
|| (!compact_periodic_knots && s.knots.len() != n + deg + 1));
|
||||
// A single control point with no knots and no fit points cannot
|
||||
// form a curve, and no knot vector exists for it.
|
||||
let lone_point = n == 1 && s.knots.is_empty() && s.fit_points.is_empty();
|
||||
n >= MAX_VERTS
|
||||
|| degree_bad
|
||||
|| lone_point
|
||||
|| s.control_points.iter().any(|p| !finite_vec3(p))
|
||||
|| knots_bad
|
||||
}
|
||||
_ => false,
|
||||
for issue in &repair.unresolved {
|
||||
doc.notifications.notify(
|
||||
codec::notification::NotificationType::Warning,
|
||||
format!("Recovery left unresolved: {}", issue.message),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Give control-point splines stored without knots a degree their control
|
||||
/// points can carry, and the matching clamped knot vector.
|
||||
///
|
||||
/// A degree-p curve needs at least p + 1 control points. Knot generation
|
||||
/// (here, and in the DWG writer for knot-less splines) subtracts in `usize`
|
||||
/// and underflows when there are fewer; in release builds the wrapped count
|
||||
/// drives an unbounded allocation. Lowering the degree keeps the geometry the
|
||||
/// points describe: two points are a straight segment at any degree.
|
||||
fn normalize_knotless_splines(doc: &mut CadDocument) {
|
||||
for entity in doc.entities_mut() {
|
||||
let EntityType::Spline(spline) = entity else {
|
||||
continue;
|
||||
};
|
||||
let n = spline.control_points.len();
|
||||
if !spline.knots.is_empty() || n < 2 || spline.degree < 1 {
|
||||
continue;
|
||||
}
|
||||
let degree = (spline.degree as usize).min(n - 1);
|
||||
spline.degree = degree as i32;
|
||||
spline.knots = codec::entities::Spline::generate_clamped_knots(degree, n);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn purge_corrupt_entities(doc: &mut CadDocument) -> usize {
|
||||
use crate::par::prelude::*;
|
||||
normalize_knotless_splines(doc);
|
||||
// Detection is pure and read-only; the per-vertex finite/extent checks on
|
||||
// large polylines dominate, so fan the scan out across cores. Gather
|
||||
// entity references in one pass, test in parallel, then remove serially
|
||||
// (`remove_entity` needs `&mut doc`).
|
||||
let entities: Vec<&EntityType> = doc.entities().collect();
|
||||
let bad: Vec<codec::Handle> = entities
|
||||
.par_iter()
|
||||
.filter(|e| is_entity_corrupt(e))
|
||||
.map(|e| e.common().handle)
|
||||
.collect();
|
||||
let n = bad.len();
|
||||
for h in bad {
|
||||
doc.remove_entity(h);
|
||||
}
|
||||
n
|
||||
}
|
||||
|
||||
/// opencadcodec's ViewportStatusFlags::from_bits() maps bit 0 → is_on and bit 15 → locked,
|
||||
/// but the real DXF/DWG spec uses bit 15 (0x8000) → viewport on and bit 14 (0x4000) → locked.
|
||||
/// Files from AutoCAD and other tools always set bit 15 for active viewports, leaving bit 0
|
||||
@@ -2969,7 +2800,7 @@ mod dimension_arrow_save_tests {
|
||||
dimension.definition_point = Vector3::new(0.0, 20.0, 0.0);
|
||||
dimension.base.definition_point = dimension.definition_point;
|
||||
dimension.base.text_middle_point = Vector3::new(50.0, 20.0, 0.0);
|
||||
doc.add_entity(EntityType::Dimension(Dimension::Linear(dimension)))
|
||||
doc.add_entity(EntityType::Dimension(Box::new(Dimension::Linear(dimension))))
|
||||
.unwrap();
|
||||
doc
|
||||
}
|
||||
@@ -3161,214 +2992,6 @@ mod layer_roundtrip_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod corrupt_guard_tests {
|
||||
use super::*;
|
||||
use codec::entities::{Arc, Circle, EntityType, Spline};
|
||||
use codec::types::Vector3;
|
||||
|
||||
fn knotless_spline(degree: i32, points: usize) -> Spline {
|
||||
let mut spline = Spline::new();
|
||||
spline.degree = degree;
|
||||
spline.control_points = (0..points)
|
||||
.map(|index| Vector3::new(index as f64, index as f64, 0.0))
|
||||
.collect();
|
||||
spline
|
||||
}
|
||||
|
||||
// Degree 3 over two control points with no knot vector: generating knots
|
||||
// for it underflows, and writing it to DWG used to panic (debug) or
|
||||
// allocate without bound (release).
|
||||
#[test]
|
||||
fn knotless_spline_with_too_few_points_is_lowered_and_saves_as_dwg() {
|
||||
let mut doc = CadDocument::new();
|
||||
doc.add_entity(EntityType::Spline(knotless_spline(3, 2)))
|
||||
.unwrap();
|
||||
doc.add_entity(EntityType::Spline(knotless_spline(3, 1)))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
purge_corrupt_entities(&mut doc),
|
||||
1,
|
||||
"the lone point is dropped"
|
||||
);
|
||||
let spline = doc
|
||||
.entities()
|
||||
.find_map(|entity| match entity {
|
||||
EntityType::Spline(spline) => Some(spline.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.expect("the two-point spline is kept");
|
||||
assert_eq!(spline.degree, 1);
|
||||
assert_eq!(spline.knots, vec![0.0, 0.0, 1.0, 1.0]);
|
||||
|
||||
let path =
|
||||
std::env::temp_dir().join(format!("ocs_knotless_spline_{}.dwg", std::process::id()));
|
||||
save_as_version(&doc, &path, codec::DxfVersion::AC1032).expect("save");
|
||||
let _ = std::fs::remove_file(&path);
|
||||
}
|
||||
|
||||
// `load_file` (headless export, xrefs, block import) does not purge, so
|
||||
// the spline repair has to happen when any load is finalized.
|
||||
#[test]
|
||||
fn knotless_spline_is_repaired_by_every_load_path() {
|
||||
const DXF: &str =
|
||||
"0\nSECTION\n2\nENTITIES\n0\nSPLINE\n8\n0\n70\n0\n71\n3\n72\n0\n73\n2\n74\n0\n\
|
||||
10\n0\n20\n0\n30\n0\n10\n1\n20\n1\n30\n0\n0\nENDSEC\n0\nEOF\n";
|
||||
let dir = std::env::temp_dir();
|
||||
let source = dir.join(format!("ocs_knotless_load_{}.dxf", std::process::id()));
|
||||
let target = dir.join(format!("ocs_knotless_load_{}.dwg", std::process::id()));
|
||||
std::fs::write(&source, DXF).unwrap();
|
||||
let doc = load_file(&source).expect("load");
|
||||
let _ = std::fs::remove_file(&source);
|
||||
let spline = doc
|
||||
.entities()
|
||||
.find_map(|entity| match entity {
|
||||
EntityType::Spline(spline) => Some(spline),
|
||||
_ => None,
|
||||
})
|
||||
.expect("spline");
|
||||
assert_eq!(spline.degree, 1);
|
||||
assert_eq!(spline.knots.len(), 4);
|
||||
save_as_version(&doc, &target, codec::DxfVersion::AC1032).expect("save");
|
||||
let _ = std::fs::remove_file(&target);
|
||||
}
|
||||
|
||||
// Small but finite arcs are valid records. Kernel tessellation is bounded,
|
||||
// so opening must retain them instead of treating their size as corruption.
|
||||
#[test]
|
||||
fn keeps_small_finite_arc() {
|
||||
let mut a = Arc::new();
|
||||
a.center = Vector3::new(2880.84, 891.83, 0.0);
|
||||
a.radius = 0.0038974142851181423;
|
||||
a.start_angle = 1.0401656235942365;
|
||||
a.end_angle = 1.0401671831670538;
|
||||
a.normal = Vector3::new(0.0, 0.0, 1.0);
|
||||
assert!(!is_entity_corrupt(&EntityType::Arc(a)));
|
||||
}
|
||||
|
||||
// A nearly straight arc still carries valid source geometry.
|
||||
#[test]
|
||||
fn keeps_nearly_straight_arc() {
|
||||
let mut a = Arc::new();
|
||||
a.center = Vector3::new(551435.3071786845, 4051623.7156955916, 0.0);
|
||||
a.radius = 35.0;
|
||||
a.start_angle = 5.823361856481176;
|
||||
a.end_angle = 5.823362506017916;
|
||||
a.normal = Vector3::new(0.0, 0.0, 1.0);
|
||||
assert!(!is_entity_corrupt(&EntityType::Arc(a)));
|
||||
}
|
||||
|
||||
// A large-radius small-sweep arc is still a visible curve and must survive:
|
||||
// radius 1e6 × sweep 1e-4 ≈ 100 units of arc.
|
||||
#[test]
|
||||
fn keeps_large_radius_small_sweep_arc() {
|
||||
let mut a = Arc::new();
|
||||
a.radius = 1.0e6;
|
||||
a.start_angle = 0.0;
|
||||
a.end_angle = 1.0e-4;
|
||||
a.normal = Vector3::new(0.0, 0.0, 1.0);
|
||||
assert!(!is_entity_corrupt(&EntityType::Arc(a)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_zero_radius_circle_and_arc() {
|
||||
let mut c = Circle::new();
|
||||
c.center = Vector3::new(206.2, 150.7, 0.0);
|
||||
c.radius = 0.0;
|
||||
assert!(!is_entity_corrupt(&EntityType::Circle(c)));
|
||||
|
||||
let mut a = Arc::new();
|
||||
a.center = Vector3::new(223.5, 174.5, 0.0);
|
||||
a.radius = 0.0;
|
||||
a.start_angle = 0.0;
|
||||
a.end_angle = 0.0;
|
||||
a.normal = Vector3::new(0.0, 0.0, 1.0);
|
||||
assert!(!is_entity_corrupt(&EntityType::Arc(a)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drops_absurd_radius_circle() {
|
||||
let mut c = Circle::new();
|
||||
c.radius = 1.0e11;
|
||||
assert!(is_entity_corrupt(&EntityType::Circle(c)));
|
||||
let mut c = Circle::new();
|
||||
c.radius = f64::NAN;
|
||||
assert!(is_entity_corrupt(&EntityType::Circle(c)));
|
||||
}
|
||||
|
||||
// Parser desync emits 100_000-control-point splines; building a kernel
|
||||
// NURBS from one and tessellating it OOMs. The control-point cap rejects it.
|
||||
#[test]
|
||||
fn rejects_desync_spline() {
|
||||
let pts = vec![Vector3::new(0.0, 0.0, 0.0); 100_000];
|
||||
let s = Spline::from_control_points(3, pts);
|
||||
assert!(is_entity_corrupt(&EntityType::Spline(s)));
|
||||
}
|
||||
|
||||
// A collapsed spline is still a valid source record. Kernel subdivision
|
||||
// has a fixed depth bound and can process it safely.
|
||||
#[test]
|
||||
fn keeps_degenerate_point_spline() {
|
||||
let pts = vec![Vector3::new(1e-12, -1e-12, 0.0); 9];
|
||||
let s = Spline::from_control_points(3, pts);
|
||||
assert!(!is_entity_corrupt(&EntityType::Spline(s)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_compact_periodic_spline_knots() {
|
||||
let pts = (0..12)
|
||||
.map(|index| Vector3::new(index as f64, (index % 3) as f64, 0.0))
|
||||
.collect();
|
||||
let mut s = Spline::from_control_points(2, pts);
|
||||
s.flags.closed = true;
|
||||
s.flags.periodic = true;
|
||||
s.knots = (0..13).map(|value| value as f64).collect();
|
||||
assert!(!is_entity_corrupt(&EntityType::Spline(s)));
|
||||
}
|
||||
|
||||
// A normal cubic spline (4 control points, valid clamped knots) survives.
|
||||
#[test]
|
||||
fn keeps_valid_spline() {
|
||||
let pts = vec![
|
||||
Vector3::new(0.0, 0.0, 0.0),
|
||||
Vector3::new(1.0, 1.0, 0.0),
|
||||
Vector3::new(2.0, -1.0, 0.0),
|
||||
Vector3::new(3.0, 0.0, 0.0),
|
||||
];
|
||||
let s = Spline::from_control_points(3, pts);
|
||||
assert!(!is_entity_corrupt(&EntityType::Spline(s)));
|
||||
}
|
||||
|
||||
// A corrupt or adversarial MINSERT row/column pair (u16, so its unchecked
|
||||
// product can reach into the billions) must be rejected before it can
|
||||
// drive the render graph's per-instance allocation and expansion.
|
||||
#[test]
|
||||
fn preserves_large_minsert_data_while_rendering_is_bounded() {
|
||||
let mut i = codec::entities::Insert::new("BLOCK", Vector3::ZERO);
|
||||
i.row_count = u16::MAX;
|
||||
i.column_count = u16::MAX;
|
||||
assert!(!is_entity_corrupt(&EntityType::Insert(i)));
|
||||
}
|
||||
|
||||
// An ordinary array insert, well under the budget, is valid source data.
|
||||
#[test]
|
||||
fn keeps_a_reasonable_minsert() {
|
||||
let mut i = codec::entities::Insert::new("BLOCK", Vector3::ZERO);
|
||||
i.row_count = 10;
|
||||
i.column_count = 10;
|
||||
i.row_spacing = 5.0;
|
||||
i.column_spacing = 5.0;
|
||||
assert!(!is_entity_corrupt(&EntityType::Insert(i)));
|
||||
}
|
||||
|
||||
// A plain (non-array) INSERT is never treated as a MINSERT-count problem.
|
||||
#[test]
|
||||
fn keeps_a_plain_insert() {
|
||||
let i = codec::entities::Insert::new("BLOCK", Vector3::ZERO);
|
||||
assert!(!is_entity_corrupt(&EntityType::Insert(i)));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod pre_r2000_layer_plot_tests {
|
||||
use super::*;
|
||||
@@ -3545,11 +3168,12 @@ mod prepare_save_tests {
|
||||
dim.base.definition_point = Vector3::ZERO;
|
||||
dim.base.common.handle = doc.allocate_handle();
|
||||
let handle = dim.base.common.handle;
|
||||
doc.add_entity(EntityType::Dimension(Dimension::Linear(dim)))
|
||||
doc.add_entity(EntityType::Dimension(Box::new(Dimension::Linear(dim))))
|
||||
.unwrap();
|
||||
|
||||
let prepared = prepare_document_for_save(doc.clone(), doc.version);
|
||||
let EntityType::Dimension(Dimension::Linear(linear)) = prepared.get_entity(handle).unwrap()
|
||||
let Some(Dimension::Linear(linear)) =
|
||||
crate::entities::dimension::as_dimension(prepared.get_entity(handle).unwrap())
|
||||
else {
|
||||
panic!("dimension missing from prepared clone");
|
||||
};
|
||||
@@ -3565,7 +3189,9 @@ mod prepare_save_tests {
|
||||
assert_eq!(linear.base.definition_point, linear.definition_point);
|
||||
|
||||
// The live document is untouched: no block name, stale group-10.
|
||||
let EntityType::Dimension(Dimension::Linear(live)) = doc.get_entity(handle).unwrap() else {
|
||||
let Some(Dimension::Linear(live)) =
|
||||
crate::entities::dimension::as_dimension(doc.get_entity(handle).unwrap())
|
||||
else {
|
||||
panic!("dimension missing from live document");
|
||||
};
|
||||
assert!(live.base.block_name.is_empty());
|
||||
@@ -3582,7 +3208,7 @@ mod prepare_save_tests {
|
||||
let mut dim =
|
||||
DimensionLinear::new(Vector3::new(0.0, 0.0, 0.0), Vector3::new(10.0, 0.0, 0.0));
|
||||
dim.base.common.handle = doc.allocate_handle();
|
||||
doc.add_entity(EntityType::Dimension(Dimension::Linear(dim)))
|
||||
doc.add_entity(EntityType::Dimension(Box::new(Dimension::Linear(dim))))
|
||||
.unwrap();
|
||||
for ext in ["dwg", "dxf"] {
|
||||
let inline = save_to_bytes(&doc, ext, codec::DxfVersion::AC1032).expect("inline");
|
||||
|
||||
+1
-1
@@ -168,7 +168,7 @@ pub fn add_embedded_image(
|
||||
) -> Result<codec::Handle, String> {
|
||||
let (upper_left, lower_right) = corners_from_placement(origin, width, image.aspect());
|
||||
let ole = build_embedded_ole(image, upper_left, lower_right);
|
||||
doc.add_entity(codec::EntityType::Ole2Frame(ole))
|
||||
doc.add_entity(codec::EntityType::Ole2Frame(Box::new(ole)))
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@ pub struct RecoveryReport {
|
||||
pub source_sha256: Option<String>,
|
||||
pub read_stats: Option<codec::ReadStats>,
|
||||
pub entities_scanned: usize,
|
||||
pub entities_removed: usize,
|
||||
pub referenced_entities_removed: usize,
|
||||
pub parser_errors_recovered: usize,
|
||||
pub diagnostics: Vec<(String, String)>,
|
||||
pub references_checked: usize,
|
||||
@@ -54,8 +52,6 @@ impl RecoveryReport {
|
||||
source_sha256: Option<String>,
|
||||
read_stats: Option<codec::ReadStats>,
|
||||
entities_scanned: usize,
|
||||
entities_removed: usize,
|
||||
referenced_entities_removed: usize,
|
||||
references: &[XrefInfo],
|
||||
notifications: &codec::notification::NotificationCollection,
|
||||
save_as_required: bool,
|
||||
@@ -163,8 +159,6 @@ impl RecoveryReport {
|
||||
source_sha256,
|
||||
read_stats,
|
||||
entities_scanned,
|
||||
entities_removed,
|
||||
referenced_entities_removed,
|
||||
parser_errors_recovered,
|
||||
diagnostics,
|
||||
references_checked: references.len(),
|
||||
@@ -211,8 +205,6 @@ impl RecoveryReport {
|
||||
.unwrap_or(0),
|
||||
read_stats,
|
||||
entities_scanned: 0,
|
||||
entities_removed: 0,
|
||||
referenced_entities_removed: 0,
|
||||
diagnostics: Vec::new(),
|
||||
references_checked: 0,
|
||||
references_loaded: 0,
|
||||
@@ -233,17 +225,11 @@ impl RecoveryReport {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn removed_total(&self) -> usize {
|
||||
self.entities_removed
|
||||
.saturating_add(self.referenced_entities_removed)
|
||||
}
|
||||
|
||||
pub fn issues_found(&self) -> usize {
|
||||
self.parser_errors_recovered
|
||||
.saturating_add(self.references_recovered)
|
||||
.saturating_add(self.references_failed)
|
||||
.saturating_add(self.references_missing)
|
||||
.saturating_add(self.removed_total())
|
||||
.saturating_add(usize::from(self.error.is_some()))
|
||||
}
|
||||
|
||||
@@ -315,11 +301,6 @@ impl RecoveryReport {
|
||||
));
|
||||
}
|
||||
lines.push(format!("Entities scanned: {}", self.entities_scanned));
|
||||
lines.push(format!("Entities removed: {}", self.entities_removed));
|
||||
lines.push(format!(
|
||||
"Referenced entities removed: {}",
|
||||
self.referenced_entities_removed
|
||||
));
|
||||
lines.push(format!(
|
||||
"Parser errors recovered: {}",
|
||||
self.parser_errors_recovered
|
||||
|
||||
+17
-45
@@ -277,12 +277,8 @@ fn read_outcome_from_bytes(
|
||||
/// `base_dir`, and populate each xref block with entities from the
|
||||
/// referenced file.
|
||||
///
|
||||
/// Returns a list of [`XrefInfo`] describing each xref block found, plus the
|
||||
/// number of corrupt xref entities dropped during merge. Purging happens
|
||||
/// inline as each xref's entities are merged — xref content is parser output
|
||||
/// just like the host doc, so it gets the same corrupt-entity guard. Folding
|
||||
/// it in here avoids a second full-document `entities()` walk after resolve.
|
||||
pub fn resolve_xrefs(doc: &mut CadDocument, base_dir: &Path) -> (Vec<XrefInfo>, usize) {
|
||||
/// Returns a list of [`XrefInfo`] describing each xref block found.
|
||||
pub fn resolve_xrefs(doc: &mut CadDocument, base_dir: &Path) -> Vec<XrefInfo> {
|
||||
resolve_xrefs_with_filter(doc, base_dir, None, None)
|
||||
}
|
||||
|
||||
@@ -295,7 +291,7 @@ pub fn resolve_xrefs_for_keys(
|
||||
doc: &mut CadDocument,
|
||||
base_dir: &Path,
|
||||
keys: &HashSet<Handle>,
|
||||
) -> (Vec<XrefInfo>, usize) {
|
||||
) -> Vec<XrefInfo> {
|
||||
resolve_xrefs_with_filter(doc, base_dir, None, Some(keys))
|
||||
}
|
||||
|
||||
@@ -308,7 +304,7 @@ pub fn resolve_xrefs_with_progress(
|
||||
doc: &mut CadDocument,
|
||||
base_dir: &Path,
|
||||
progress: Option<std::sync::Arc<dyn Fn(usize, usize) + Send + Sync>>,
|
||||
) -> (Vec<XrefInfo>, usize) {
|
||||
) -> Vec<XrefInfo> {
|
||||
resolve_xrefs_with_filter(doc, base_dir, progress, None)
|
||||
}
|
||||
|
||||
@@ -317,7 +313,7 @@ fn resolve_xrefs_with_filter(
|
||||
base_dir: &Path,
|
||||
progress: Option<std::sync::Arc<dyn Fn(usize, usize) + Send + Sync>>,
|
||||
only: Option<&HashSet<Handle>>,
|
||||
) -> (Vec<XrefInfo>, usize) {
|
||||
) -> Vec<XrefInfo> {
|
||||
// Auto-resolve every xref — frustum + LOD culling keep GPU cost bounded.
|
||||
let xref_entries: Vec<(String, String, Handle)> = doc
|
||||
.block_records
|
||||
@@ -432,7 +428,6 @@ fn resolve_xrefs_with_filter(
|
||||
// Phase 2 — merge each parsed xref into the host document, in the original
|
||||
// block order (par_iter preserves it), so handle allocation is deterministic.
|
||||
let mut result = Vec::with_capacity(parsed.len());
|
||||
let mut dropped = 0usize;
|
||||
#[allow(unused_mut, unused_variables)]
|
||||
for (
|
||||
merge_index,
|
||||
@@ -468,19 +463,7 @@ fn resolve_xrefs_with_filter(
|
||||
.iter()
|
||||
.map(|diagnostic| diagnostic.message.clone()),
|
||||
);
|
||||
let invalid = super::purge_corrupt_entities(&mut outcome.document);
|
||||
if invalid > 0 {
|
||||
diagnostics.push(format!(
|
||||
"normal read found {invalid} structurally invalid reference records"
|
||||
));
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if source_sha256.is_none() {
|
||||
source_sha256 = resolved.as_ref().and_then(|path| {
|
||||
super::stable_sha256_file(path, initial_fingerprint.as_ref())
|
||||
});
|
||||
}
|
||||
}
|
||||
if recovered || invalid > 0 {
|
||||
if recovered {
|
||||
(XrefStatus::Failed, diagnostics, Some(outcome.stats))
|
||||
} else {
|
||||
// Reload is replacement, never append. Keep the prior merge
|
||||
@@ -502,7 +485,7 @@ fn resolve_xrefs_with_filter(
|
||||
let _ = unload_reference(doc, br_handle.value());
|
||||
remove_pipe_symbols(doc, &block_name);
|
||||
ensure_block_entities(doc, &block_name);
|
||||
dropped += merge_xref_into_block(doc, &block_name, br_handle, outcome.document);
|
||||
merge_xref_into_block(doc, &block_name, br_handle, outcome.document);
|
||||
if visretain {
|
||||
restore_dependent_layers(doc, &block_name, kept_layers);
|
||||
}
|
||||
@@ -533,7 +516,7 @@ fn resolve_xrefs_with_filter(
|
||||
}
|
||||
}
|
||||
|
||||
(result, dropped)
|
||||
result
|
||||
}
|
||||
|
||||
/// Underlay files named by a relative (or moved) path: read them from where
|
||||
@@ -1218,13 +1201,12 @@ fn copy_xref_sortents(
|
||||
/// — which doesn't know about the xref's layers — and silently falls
|
||||
/// back to WHITE / 1 px line weight. AutoCAD's BIND command uses the
|
||||
/// same naming scheme.
|
||||
/// Returns the number of corrupt entities skipped during the merge.
|
||||
fn merge_xref_into_block(
|
||||
doc: &mut CadDocument,
|
||||
xref_block_name: &str,
|
||||
br_handle: Handle,
|
||||
xref_doc: CadDocument,
|
||||
) -> usize {
|
||||
) {
|
||||
let prefix = xref_block_name;
|
||||
|
||||
// Carry the xref's INSUNITS onto the host BlockRecord. Used at INSERT
|
||||
@@ -1264,7 +1246,7 @@ fn merge_xref_into_block(
|
||||
// ── Entities (shared helper) ────────────────────────────────────────
|
||||
// The unremapped-handle count is ignored on the load path (BIND reports
|
||||
// it per bind).
|
||||
let (dropped, _, entity_handle_map) = merge_source_entities(doc, &xref_doc, br_handle, &maps);
|
||||
let (_, entity_handle_map) = merge_source_entities(doc, &xref_doc, br_handle, &maps);
|
||||
|
||||
// ── Draw-order overrides (shared helper) ──────────────────────────────
|
||||
copy_xref_sortents(
|
||||
@@ -1274,7 +1256,6 @@ fn merge_xref_into_block(
|
||||
&maps.br_handles,
|
||||
&entity_handle_map,
|
||||
);
|
||||
dropped
|
||||
}
|
||||
|
||||
/// Source entities in display order: each block record's `entity_handles`
|
||||
@@ -1308,26 +1289,18 @@ fn ordered_source_entities(xref_doc: &CadDocument) -> Vec<(Handle, EntityType)>
|
||||
|
||||
/// Merge one source doc's entities into `target`, routing model-space content
|
||||
/// into `target_br` and nested-block content into its imported counterpart
|
||||
/// (`maps.br_handles`). Returns corrupt-drop count, unremapped-handle count,
|
||||
/// and the old→new entity handle map for the sortents copy.
|
||||
/// (`maps.br_handles`). Returns the unremapped-handle count and the old→new
|
||||
/// entity handle map for the sortents copy.
|
||||
fn merge_source_entities(
|
||||
target: &mut CadDocument,
|
||||
source: &CadDocument,
|
||||
target_br: Handle,
|
||||
maps: &XrefSymbolMaps,
|
||||
) -> (usize, usize, HashMap<Handle, Handle>) {
|
||||
) -> (usize, HashMap<Handle, Handle>) {
|
||||
let source_ms_handle = source.header.model_space_block_handle;
|
||||
let mut entity_handle_map: HashMap<Handle, Handle> = HashMap::default();
|
||||
let mut dropped = 0usize;
|
||||
let mut unremapped = 0usize;
|
||||
for (old_h, mut entity) in ordered_source_entities(source) {
|
||||
// Drop parser-garbage entities (bad normals / vertex counts / inf
|
||||
// coords) before they enter the host doc — they trigger huge
|
||||
// allocations and unbounded recursion in the wire pipeline.
|
||||
if super::is_entity_corrupt(&entity) {
|
||||
dropped += 1;
|
||||
continue;
|
||||
}
|
||||
// Remap every symbol reference so the host's resolver hits the copies
|
||||
// just imported (load path ignores the unremapped count; BIND reports
|
||||
// it per bind).
|
||||
@@ -1351,7 +1324,7 @@ fn merge_source_entities(
|
||||
entity_handle_map.insert(old_h, new_h);
|
||||
}
|
||||
}
|
||||
(dropped, unremapped, entity_handle_map)
|
||||
(unremapped, entity_handle_map)
|
||||
}
|
||||
|
||||
fn is_sentinel_linetype(name: &str) -> bool {
|
||||
@@ -2170,8 +2143,7 @@ fn merge_bound_doc(
|
||||
taken,
|
||||
};
|
||||
let maps = import_xref_symbols(target, source, &mut naming);
|
||||
let (_, unremapped, entity_handle_map) =
|
||||
merge_source_entities(target, source, target_br, &maps);
|
||||
let (unremapped, entity_handle_map) = merge_source_entities(target, source, target_br, &maps);
|
||||
copy_xref_sortents(
|
||||
target,
|
||||
source,
|
||||
@@ -2970,7 +2942,7 @@ mod tests {
|
||||
// 3. Targeted reload: back to Loaded.
|
||||
let mut handles = HashSet::default();
|
||||
handles.insert(doc.block_records.get("MID").unwrap().handle);
|
||||
let (infos, _) = resolve_xrefs_for_keys(&mut doc, &dir, &handles);
|
||||
let infos = resolve_xrefs_for_keys(&mut doc, &dir, &handles);
|
||||
assert!(infos.iter().any(|i| i.name == "MID"));
|
||||
let listed = collect_entries_with_prev(&doc, &dir, &empty, &no_prev);
|
||||
assert!(listed
|
||||
@@ -3336,7 +3308,7 @@ mod tests {
|
||||
// Hatch (pattern names have no table — preserved verbatim).
|
||||
let mut hatch = codec::entities::Hatch::new();
|
||||
hatch.pattern.name = "ANSI31".to_string();
|
||||
doc.add_entity(codec::EntityType::Hatch(hatch)).unwrap();
|
||||
doc.add_entity(codec::EntityType::Hatch(Box::new(hatch))).unwrap();
|
||||
// Dim-style reference via a tolerance entity.
|
||||
doc.dim_styles
|
||||
.add(codec::tables::DimStyle::new("ARCH"))
|
||||
|
||||
@@ -696,10 +696,10 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
||||
"ATTSYNC block name to sync:" => Some(("insert", "attsync-block-name-to-sync")),
|
||||
"ATTSYNC: no block named \"{arg}\"." => Some(("insert", "attsync-no-block-named-arg")),
|
||||
"ATTSYNC: synchronised {synced} insert(s) of \"{block}\" against {} attribute definition(s)." => Some(("insert", "attsync-synchronised-synced-insert-s-of-block")),
|
||||
"AUDIT Fix any errors detected? [Yes / No] <No>:" => Some(("styles", "audit-fix-any-errors-detected")),
|
||||
"AUDIT: no issues found." => Some(("styles", "audit-no-issues-found")),
|
||||
"AUDIT: reference(s) to undefined block(s): {}" => Some(("styles", "audit-reference-s-to-undefined-block-s")),
|
||||
"AUDIT: reference(s) to undefined layer(s): {}" => Some(("styles", "audit-reference-s-to-undefined-layer-s")),
|
||||
"AUDIT: report only — no automatic repair performed." => Some(("styles", "audit-report-only-no-automatic-repair-performe")),
|
||||
"AUDIT: {} error(s), {} warning(s) found." => Some(("styles", "audit-error-s-warning-s-found")),
|
||||
"AUDIT: {} repair(s) applied, {} issue(s) left unresolved." => Some(("styles", "audit-repair-s-applied-issue-s-left")),
|
||||
"AUDIT: scanned {total} object(s)." => Some(("styles", "audit-scanned-total-object-s")),
|
||||
"About" => Some(("manage", "about")),
|
||||
"Above" => Some(("common", "above")),
|
||||
@@ -3806,8 +3806,6 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
||||
"WIPEOUT Rectangular Specify first corner:" => Some(("draw", "wipeout-rectangular-specify-first-corner")),
|
||||
"WIPEOUT Rectangular Specify opposite corner:" => Some(("draw", "wipeout-rectangular-specify-opposite-corner")),
|
||||
"Warning" => Some(("errors", "warning")),
|
||||
"Warning: {} corrupt entities dropped (parser junk — bad normals / counts)" => Some(("file", "warning-corrupt-entities-dropped-parser-junk-b")),
|
||||
"Warning: {} corrupt xref entities dropped" => Some(("file", "warning-corrupt-xref-entities-dropped")),
|
||||
"Watertight" => Some(("properties", "watertight")),
|
||||
"Web" => Some(("common", "web")),
|
||||
"Web File" => Some(("properties", "web-file")),
|
||||
|
||||
@@ -95,7 +95,7 @@ pub(crate) fn aligned_dimension_entity(
|
||||
crate::entities::dimension::reset_automatic_text_position(&mut dim.base);
|
||||
dim.base.actual_measurement = dim.measurement();
|
||||
crate::entities::dimension::set_dimension_text_override(&mut dim.base, text_override);
|
||||
EntityType::Dimension(Dimension::Aligned(dim))
|
||||
EntityType::Dimension(Box::new(Dimension::Aligned(dim)))
|
||||
}
|
||||
|
||||
impl CadCommand for AlignedDimensionCommand {
|
||||
|
||||
@@ -232,7 +232,7 @@ impl AngularDimensionCommand {
|
||||
}
|
||||
|
||||
fn commit(&self, dimension: Dimension) -> CmdResult {
|
||||
let entity = self.plane.place_entity(EntityType::Dimension(dimension));
|
||||
let entity = self.plane.place_entity(EntityType::Dimension(Box::new(dimension)));
|
||||
CmdResult::CommitDimension {
|
||||
entity,
|
||||
association: DimensionAssociationInput::Explicit(self.source_refs.clone()),
|
||||
@@ -1242,7 +1242,7 @@ pub(crate) fn angular_two_line_entity(
|
||||
dim.base.insertion_point = dim.dimension_arc;
|
||||
dim.base.actual_measurement = dim.measurement_degrees();
|
||||
crate::entities::dimension::set_dimension_text_override(&mut dim.base, text);
|
||||
Some(EntityType::Dimension(Dimension::Angular2Ln(dim)))
|
||||
Some(EntityType::Dimension(Box::new(Dimension::Angular2Ln(dim))))
|
||||
}
|
||||
|
||||
/// A three-point angular dimension (vertex, two points, arc point), for a
|
||||
@@ -1261,7 +1261,7 @@ pub(crate) fn angular_three_point_entity(
|
||||
dim.base.insertion_point = dim.definition_point;
|
||||
dim.base.actual_measurement = dim.measurement_degrees();
|
||||
crate::entities::dimension::set_dimension_text_override(&mut dim.base, text);
|
||||
Some(EntityType::Dimension(Dimension::Angular3Pt(dim)))
|
||||
Some(EntityType::Dimension(Box::new(Dimension::Angular3Pt(dim))))
|
||||
}
|
||||
|
||||
fn v3(point: DVec3) -> Vector3 {
|
||||
|
||||
@@ -258,7 +258,7 @@ impl ArcLengthDimensionCommand {
|
||||
|handle| DimensionAssociationInput::Explicit(selection.association_sources(handle)),
|
||||
);
|
||||
CmdResult::CommitDimension {
|
||||
entity: plane.place_entity(EntityType::Dimension(Dimension::Arc(dimension))),
|
||||
entity: plane.place_entity(EntityType::Dimension(Box::new(Dimension::Arc(dimension)))),
|
||||
association,
|
||||
preserve_base_style: false,
|
||||
continue_command: false,
|
||||
|
||||
@@ -83,7 +83,7 @@ impl DiameterDimensionCommand {
|
||||
if let Some(angle) = self.text_angle {
|
||||
dim.base.text_rotation = angle;
|
||||
}
|
||||
plane.place_entity(EntityType::Dimension(Dimension::Diameter(dim)))
|
||||
plane.place_entity(EntityType::Dimension(Box::new(Dimension::Diameter(dim))))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ pub(crate) fn diameter_constraint_entity(
|
||||
dim.leader_length = chord.distance(location).max(radius * 0.5);
|
||||
dim.base.actual_measurement = dim.measurement();
|
||||
crate::entities::dimension::set_dimension_text_override(&mut dim.base, text);
|
||||
Some(EntityType::Dimension(Dimension::Diameter(dim)))
|
||||
Some(EntityType::Dimension(Box::new(Dimension::Diameter(dim))))
|
||||
}
|
||||
|
||||
impl CadCommand for DiameterDimensionCommand {
|
||||
|
||||
@@ -151,7 +151,7 @@ impl DimBaselineCommand {
|
||||
let increment = self.effective_increment(&style.style_name);
|
||||
let plane = plane_from_normal(dimension.base().definition_point, style.normal);
|
||||
let pick = pick.and_then(|point| plane.project(point.to_array()).map(Vec2::from));
|
||||
let kind = match dimension {
|
||||
let kind = match &**dimension {
|
||||
Dimension::Linear(source) => {
|
||||
let first = project(&plane, source.first_point);
|
||||
let second = project(&plane, source.second_point);
|
||||
@@ -441,7 +441,7 @@ impl CadCommand for DimBaselineCommand {
|
||||
self.committed += 1;
|
||||
self.advance(1.0);
|
||||
CmdResult::CommitDimension {
|
||||
entity: EntityType::Dimension(dimension),
|
||||
entity: EntityType::Dimension(Box::new(dimension)),
|
||||
association: DimensionAssociationInput::Infer(None),
|
||||
preserve_base_style: self.preserve_base_style,
|
||||
continue_command: true,
|
||||
@@ -489,7 +489,7 @@ impl CadCommand for DimBaselineCommand {
|
||||
fn dimension_preview(&self, cursor: DVec3) -> Option<Vec<DimensionPreview>> {
|
||||
self.build_dimension(cursor).map(|dimension| {
|
||||
vec![DimensionPreview {
|
||||
entity: EntityType::Dimension(dimension),
|
||||
entity: EntityType::Dimension(Box::new(dimension)),
|
||||
preserve_base_style: self.preserve_base_style,
|
||||
}]
|
||||
})
|
||||
|
||||
@@ -117,7 +117,7 @@ impl DimContinueCommand {
|
||||
let style = SourceStyle::from_base(dimension.base());
|
||||
let plane = plane_from_normal(dimension.base().definition_point, style.normal);
|
||||
let pick = pick_world.and_then(|point| plane.project(point.to_array()).map(Vec2::from));
|
||||
let kind = match dimension {
|
||||
let kind = match &**dimension {
|
||||
Dimension::Linear(source) => {
|
||||
let rotation = source.rotation;
|
||||
let perpendicular = Vec2::new(-rotation.sin(), rotation.cos());
|
||||
@@ -383,7 +383,7 @@ impl CadCommand for DimContinueCommand {
|
||||
}
|
||||
self.advance(point);
|
||||
CmdResult::CommitDimension {
|
||||
entity: EntityType::Dimension(dimension),
|
||||
entity: EntityType::Dimension(Box::new(dimension)),
|
||||
association: DimensionAssociationInput::Infer(None),
|
||||
preserve_base_style: self.preserve_base_style,
|
||||
continue_command: true,
|
||||
@@ -433,7 +433,7 @@ impl CadCommand for DimContinueCommand {
|
||||
fn dimension_preview(&self, cursor: DVec3) -> Option<Vec<DimensionPreview>> {
|
||||
self.build_dimension(cursor).map(|dimension| {
|
||||
vec![DimensionPreview {
|
||||
entity: EntityType::Dimension(dimension),
|
||||
entity: EntityType::Dimension(Box::new(dimension)),
|
||||
preserve_base_style: self.preserve_base_style,
|
||||
}]
|
||||
})
|
||||
|
||||
@@ -145,13 +145,17 @@ impl CadCommand for DimJogLineCommand {
|
||||
}
|
||||
|
||||
fn on_entity_pick(&mut self, handle: Handle, _point: DVec3) -> CmdResult {
|
||||
let Some(EntityType::Dimension(dimension @ (Dimension::Linear(_) | Dimension::Aligned(_)))) =
|
||||
self.picked_entity.take()
|
||||
else {
|
||||
let Some(EntityType::Dimension(dimension)) = self.picked_entity.take() else {
|
||||
return CmdResult::ReportError(
|
||||
t!("DIMJOGLINE: select a linear or aligned dimension.").into_owned(),
|
||||
);
|
||||
};
|
||||
let dimension = *dimension;
|
||||
if !matches!(dimension, Dimension::Linear(_) | Dimension::Aligned(_)) {
|
||||
return CmdResult::ReportError(
|
||||
t!("DIMJOGLINE: select a linear or aligned dimension.").into_owned(),
|
||||
);
|
||||
}
|
||||
if handle.is_null() {
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
|
||||
@@ -104,8 +104,8 @@ impl CadCommand for DimSpaceCommand {
|
||||
for selected in entities {
|
||||
if selected.handle != *base
|
||||
&& matches!(
|
||||
selected.entity,
|
||||
EntityType::Dimension(
|
||||
crate::entities::dimension::as_dimension(&selected.entity),
|
||||
Some(
|
||||
codec::entities::Dimension::Linear(_)
|
||||
| codec::entities::Dimension::Aligned(_)
|
||||
)
|
||||
@@ -124,11 +124,14 @@ impl CadCommand for DimSpaceCommand {
|
||||
match &mut self.step {
|
||||
Step::PickBase => {
|
||||
if !matches!(
|
||||
self.picked_entity.take(),
|
||||
Some(EntityType::Dimension(
|
||||
self.picked_entity
|
||||
.take()
|
||||
.as_ref()
|
||||
.and_then(crate::entities::dimension::as_dimension),
|
||||
Some(
|
||||
codec::entities::Dimension::Linear(_)
|
||||
| codec::entities::Dimension::Aligned(_)
|
||||
))
|
||||
)
|
||||
) {
|
||||
return CmdResult::ReportError(
|
||||
t!("DIMSPACE: select a linear or aligned dimension.").into_owned(),
|
||||
|
||||
@@ -158,7 +158,7 @@ impl CadCommand for FieldPlaceCommand {
|
||||
mtext.attachment_point = self.attachment;
|
||||
// No defined width: the field text never wraps.
|
||||
mtext.rectangle_width = 0.0;
|
||||
CmdResult::CommitAndExit(self.plane.place_entity(EntityType::MText(mtext)))
|
||||
CmdResult::CommitAndExit(self.plane.place_entity(EntityType::MText(Box::new(mtext))))
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
|
||||
@@ -138,7 +138,9 @@ impl JoggedRadiusDimensionCommand {
|
||||
);
|
||||
|
||||
CmdResult::CommitDimension {
|
||||
entity: plane.place_entity(EntityType::Dimension(Dimension::LargeRadial(dimension))),
|
||||
entity: plane.place_entity(EntityType::Dimension(Box::new(Dimension::LargeRadial(
|
||||
dimension,
|
||||
)))),
|
||||
association,
|
||||
preserve_base_style: false,
|
||||
continue_command: false,
|
||||
|
||||
@@ -151,7 +151,7 @@ impl LeaderCommand {
|
||||
CmdResult::CommitManyAndEditText {
|
||||
entities: vec![
|
||||
self.plane.place_entity(EntityType::Leader(leader)),
|
||||
self.plane.place_entity(EntityType::MText(mtext)),
|
||||
self.plane.place_entity(EntityType::MText(Box::new(mtext))),
|
||||
],
|
||||
edit_index: 1,
|
||||
open_editor,
|
||||
|
||||
@@ -141,7 +141,7 @@ pub(crate) fn linear_dimension_entity(
|
||||
crate::entities::dimension::reset_automatic_text_position(&mut dim.base);
|
||||
dim.base.actual_measurement = dim.measurement();
|
||||
crate::entities::dimension::set_dimension_text_override(&mut dim.base, text_override);
|
||||
EntityType::Dimension(Dimension::Linear(dim))
|
||||
EntityType::Dimension(Box::new(Dimension::Linear(dim)))
|
||||
}
|
||||
|
||||
impl CadCommand for LinearDimensionCommand {
|
||||
|
||||
@@ -648,7 +648,7 @@ impl CadCommand for MLeaderCommand {
|
||||
text.insertion_point.z,
|
||||
));
|
||||
self.text = text.value.clone();
|
||||
self.selected_mtext = Some(text);
|
||||
self.selected_mtext = Some(*text);
|
||||
self.content_type = LeaderContentType::MText;
|
||||
self.order = CreationOrder::ContentFirst;
|
||||
self.step = Step::PickPoints;
|
||||
|
||||
@@ -80,7 +80,7 @@ impl OrdinateDimCommand {
|
||||
}
|
||||
dim.refresh_measurement();
|
||||
self.plane
|
||||
.place_entity(EntityType::Dimension(Dimension::Ordinate(dim)))
|
||||
.place_entity(EntityType::Dimension(Box::new(Dimension::Ordinate(dim))))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -341,7 +341,7 @@ impl QdimCommand {
|
||||
]);
|
||||
(
|
||||
self.plane
|
||||
.place_entity(EntityType::Dimension(Dimension::Linear(dimension))),
|
||||
.place_entity(EntityType::Dimension(Box::new(Dimension::Linear(dimension)))),
|
||||
association,
|
||||
)
|
||||
}
|
||||
@@ -366,7 +366,7 @@ impl QdimCommand {
|
||||
dimension.refresh_measurement();
|
||||
(
|
||||
self.plane
|
||||
.place_entity(EntityType::Dimension(Dimension::Ordinate(dimension))),
|
||||
.place_entity(EntityType::Dimension(Box::new(Dimension::Ordinate(dimension)))),
|
||||
DimensionAssociationInput::Explicit(vec![point.sources.first().copied()]),
|
||||
)
|
||||
}
|
||||
@@ -405,7 +405,7 @@ impl QdimCommand {
|
||||
dimension.base.text_user_positioned = true;
|
||||
dimension.leader_length = chord.distance(text);
|
||||
dimension.base.actual_measurement = dimension.measurement();
|
||||
EntityType::Dimension(Dimension::Radius(dimension))
|
||||
EntityType::Dimension(Box::new(Dimension::Radius(dimension)))
|
||||
} else {
|
||||
let opposite = center - direction * radius;
|
||||
let mut dimension = DimensionDiameter::new(v3(chord), v3(opposite));
|
||||
@@ -415,7 +415,7 @@ impl QdimCommand {
|
||||
dimension.base.text_user_positioned = true;
|
||||
dimension.leader_length = chord.distance(text);
|
||||
dimension.base.actual_measurement = dimension.measurement();
|
||||
EntityType::Dimension(Dimension::Diameter(dimension))
|
||||
EntityType::Dimension(Box::new(Dimension::Diameter(dimension)))
|
||||
};
|
||||
(self.plane.place_entity(entity), association)
|
||||
})
|
||||
@@ -680,7 +680,7 @@ fn preview_entity(index: usize, entity: &EntityType) -> Vec<WireModel> {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut lines: Vec<Vec<[f64; 3]>> = Vec::new();
|
||||
match dimension {
|
||||
match &**dimension {
|
||||
Dimension::Linear(value) => {
|
||||
let first = d3(value.first_point);
|
||||
let second = d3(value.second_point);
|
||||
|
||||
@@ -93,7 +93,7 @@ pub(crate) fn radial_dimension_entity(
|
||||
if let Some(angle) = text_angle {
|
||||
dim.base.text_rotation = angle;
|
||||
}
|
||||
plane.place_entity(EntityType::Dimension(Dimension::Radius(dim)))
|
||||
plane.place_entity(EntityType::Dimension(Box::new(Dimension::Radius(dim))))
|
||||
}
|
||||
|
||||
/// A radius dimension for a dimensional constraint: the leader leaves the
|
||||
@@ -111,7 +111,7 @@ pub(crate) fn radius_constraint_entity(
|
||||
dim.leader_length = chord.distance(location).max(radius * 0.5);
|
||||
dim.base.actual_measurement = dim.measurement();
|
||||
crate::entities::dimension::set_dimension_text_override(&mut dim.base, text);
|
||||
Some(EntityType::Dimension(Dimension::Radius(dim)))
|
||||
Some(EntityType::Dimension(Box::new(Dimension::Radius(dim))))
|
||||
}
|
||||
|
||||
/// The unit direction a radial constraint's dimension points in, from the
|
||||
|
||||
@@ -222,7 +222,7 @@ fn separate(p: &Piece, doc: &CadDocument) -> Vec<EntityType> {
|
||||
let at = p.point(p.rect[0] + x * (p.rect[1] - p.rect[0]), p.rect[3]);
|
||||
let mut m = mtext_from(p.entity, p.content.clone(), at, p.height, p.rotation);
|
||||
m.attachment_point = attachment;
|
||||
vec![EntityType::MText(m)]
|
||||
vec![EntityType::MText(Box::new(m))]
|
||||
}
|
||||
EntityType::MText(m) => {
|
||||
// The drawn words, grouped into lines by their baseline.
|
||||
@@ -261,7 +261,8 @@ fn separate(p: &Piece, doc: &CadDocument) -> Vec<EntityType> {
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
let at = p.point(line[0].0 - origin_along, p.rect[3] - k as f64 * step);
|
||||
EntityType::MText(mtext_from(p.entity, value, at, line[0].1.height, m.rotation))
|
||||
let height = line[0].1.height;
|
||||
EntityType::MText(Box::new(mtext_from(p.entity, value, at, height, m.rotation)))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -352,7 +353,7 @@ pub fn plan(
|
||||
}
|
||||
Some(Txt2MtxtPlan {
|
||||
erase: pieces.iter().map(|p| p.handle).collect(),
|
||||
add: vec![EntityType::MText(m)],
|
||||
add: vec![EntityType::MText(Box::new(m))],
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ impl CadCommand for PasteSpecCommand {
|
||||
mtext.style = self.style.clone();
|
||||
mtext.attachment_point = AttachmentPoint::TopLeft;
|
||||
mtext.rectangle_width = 0.0;
|
||||
CmdResult::CommitAndExit(self.plane.place_entity(EntityType::MText(mtext)))
|
||||
CmdResult::CommitAndExit(self.plane.place_entity(EntityType::MText(Box::new(mtext))))
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
|
||||
@@ -925,7 +925,7 @@ mod tests {
|
||||
}));
|
||||
}
|
||||
hatch.paths.push(path);
|
||||
let entity = EntityType::Hatch(hatch);
|
||||
let entity = EntityType::Hatch(Box::new(hatch));
|
||||
HatcheditCommand::with_handle(Handle::new(7), "ANSI31".into(), 1.0, 0.0, false)
|
||||
.with_appearance(Some(&entity), Color::Index(6), Transparency::T_40)
|
||||
.with_origin([9.0, 8.0])
|
||||
@@ -1096,7 +1096,7 @@ mod tests {
|
||||
counter_clockwise: true,
|
||||
}));
|
||||
hatch.paths.push(path);
|
||||
let entity = EntityType::Hatch(hatch);
|
||||
let entity = EntityType::Hatch(Box::new(hatch));
|
||||
let mut command =
|
||||
HatcheditCommand::with_handle(Handle::new(7), "ANSI31".into(), 1.0, 0.0, false)
|
||||
.with_appearance(Some(&entity), Color::ByLayer, Transparency::ByLayer);
|
||||
|
||||
@@ -104,7 +104,7 @@ impl ImageCommand {
|
||||
let (upper_left, lower_right) =
|
||||
corners_from_placement(ins, world_width, self.aspect());
|
||||
let ole = build_embedded_ole(image, upper_left, lower_right);
|
||||
self.plane.place_entity(EntityType::Ole2Frame(ole))
|
||||
self.plane.place_entity(EntityType::Ole2Frame(Box::new(ole)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1439,7 +1439,7 @@ fn sync_dimension_base_points_and_collect_pending(doc: &mut CadDocument) -> Vec<
|
||||
let EntityType::Dimension(dimension) = entity else {
|
||||
return None;
|
||||
};
|
||||
let definition_point = match dimension {
|
||||
let definition_point = match &**dimension {
|
||||
Dimension::Linear(value) => Some(value.definition_point),
|
||||
Dimension::Aligned(value) => Some(value.definition_point),
|
||||
Dimension::Radius(value) => Some(value.definition_point),
|
||||
@@ -1493,7 +1493,7 @@ pub fn bake_dimension_blocks(doc: &mut CadDocument) {
|
||||
let dimensions: Vec<(Handle, Dimension)> = pending
|
||||
.into_iter()
|
||||
.filter_map(|handle| match doc.get_entity(handle) {
|
||||
Some(EntityType::Dimension(d)) => Some((handle, d.clone())),
|
||||
Some(EntityType::Dimension(d)) => Some((handle, (**d).clone())),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
@@ -1654,7 +1654,7 @@ mod tests {
|
||||
d.base.text_middle_point = Vector3::new(5.0, 5.0, 0.0);
|
||||
// block_name is left empty — exactly what OCS-created dimensions carry.
|
||||
let handle = doc
|
||||
.add_entity(EntityType::Dimension(Dimension::Linear(d)))
|
||||
.add_entity(EntityType::Dimension(Box::new(Dimension::Linear(d))))
|
||||
.unwrap();
|
||||
|
||||
bake_dimension_blocks(&mut doc);
|
||||
@@ -1693,7 +1693,7 @@ mod tests {
|
||||
// Exactly what OCS-created dimensions carry: insertion == text anchor.
|
||||
d.base.insertion_point = d.base.text_middle_point;
|
||||
let handle = doc
|
||||
.add_entity(EntityType::Dimension(Dimension::Linear(d)))
|
||||
.add_entity(EntityType::Dimension(Box::new(Dimension::Linear(d))))
|
||||
.unwrap();
|
||||
|
||||
bake_dimension_blocks(&mut doc);
|
||||
@@ -1720,7 +1720,7 @@ mod tests {
|
||||
d.definition_point = Vector3::new(0.0, 5.0, 0.0);
|
||||
d.base.text_middle_point = Vector3::new(5.0, 5.0, 0.0);
|
||||
let handle = doc
|
||||
.add_entity(EntityType::Dimension(Dimension::Linear(d)))
|
||||
.add_entity(EntityType::Dimension(Box::new(Dimension::Linear(d))))
|
||||
.unwrap();
|
||||
|
||||
bake_dimension_blocks(&mut doc);
|
||||
@@ -1798,7 +1798,7 @@ mod tests {
|
||||
d.rotation = 0.0;
|
||||
d.definition_point = Vector3::new(0.0, 8.0, 0.0);
|
||||
let handle = doc
|
||||
.add_entity(EntityType::Dimension(Dimension::Linear(d)))
|
||||
.add_entity(EntityType::Dimension(Box::new(Dimension::Linear(d))))
|
||||
.unwrap();
|
||||
bake_dimension_blocks(&mut doc);
|
||||
let name = match doc.get_entity(handle) {
|
||||
@@ -1827,7 +1827,7 @@ mod tests {
|
||||
let mut d = DimensionLinear::new(Vector3::new(0.0, 0.0, 0.0), Vector3::new(10.0, 0.0, 0.0));
|
||||
d.definition_point = Vector3::new(0.0, 5.0, 0.0);
|
||||
let handle = doc
|
||||
.add_entity(EntityType::Dimension(Dimension::Linear(d)))
|
||||
.add_entity(EntityType::Dimension(Box::new(Dimension::Linear(d))))
|
||||
.unwrap();
|
||||
bake_dimension_blocks(&mut doc);
|
||||
let name = match doc.get_entity(handle) {
|
||||
@@ -1875,7 +1875,7 @@ mod tests {
|
||||
.collect();
|
||||
assert!(!expected.is_empty(), "the dimension explodes into no lines");
|
||||
|
||||
let mut turned = EntityType::Dimension(flat);
|
||||
let mut turned = EntityType::Dimension(Box::new(flat));
|
||||
crate::scene::view::dispatch::apply_transform(
|
||||
&mut turned,
|
||||
&EntityTransform::Rotate {
|
||||
@@ -1911,7 +1911,7 @@ mod tests {
|
||||
);
|
||||
d.definition_point = Vector3::new(5.0, 5.0, 0.0); // arc location
|
||||
let handle = doc
|
||||
.add_entity(EntityType::Dimension(Dimension::Angular3Pt(d)))
|
||||
.add_entity(EntityType::Dimension(Box::new(Dimension::Angular3Pt(d))))
|
||||
.unwrap();
|
||||
bake_dimension_blocks(&mut doc);
|
||||
let name = match doc.get_entity(handle) {
|
||||
@@ -1940,7 +1940,7 @@ mod tests {
|
||||
let mut d = DimensionDiameter::new(edge, far);
|
||||
d.base.text_middle_point = Vector3::new(3.0, 9.0, 0.0);
|
||||
let handle = doc
|
||||
.add_entity(EntityType::Dimension(Dimension::Diameter(d)))
|
||||
.add_entity(EntityType::Dimension(Box::new(Dimension::Diameter(d))))
|
||||
.unwrap();
|
||||
bake_dimension_blocks(&mut doc);
|
||||
let name = match doc.get_entity(handle) {
|
||||
@@ -1971,7 +1971,7 @@ mod tests {
|
||||
d.rotation = std::f64::consts::FRAC_PI_2; // 90°, vertical dimension line
|
||||
d.definition_point = Vector3::new(8.0, 0.0, 0.0);
|
||||
let handle = doc
|
||||
.add_entity(EntityType::Dimension(Dimension::Linear(d)))
|
||||
.add_entity(EntityType::Dimension(Box::new(Dimension::Linear(d))))
|
||||
.unwrap();
|
||||
bake_dimension_blocks(&mut doc);
|
||||
let name = match doc.get_entity(handle) {
|
||||
@@ -2064,7 +2064,7 @@ mod tests {
|
||||
DimensionLinear::new(Vector3::new(0.0, 0.0, 0.0), Vector3::new(10.0, 0.0, 0.0));
|
||||
d.definition_point = Vector3::new(0.0, 5.0, 0.0);
|
||||
d.base.text_middle_point = Vector3::new(5.0, 5.0, 0.0);
|
||||
doc.add_entity(EntityType::Dimension(Dimension::Linear(d)))
|
||||
doc.add_entity(EntityType::Dimension(Box::new(Dimension::Linear(d))))
|
||||
.unwrap();
|
||||
|
||||
let path =
|
||||
@@ -2171,7 +2171,7 @@ mod tests {
|
||||
d.base.text_middle_point = Vector3::new(5.0, 5.0, 0.0);
|
||||
d.base.common.xdictionary_handle = Some(dict_handle);
|
||||
let dim_handle = doc
|
||||
.add_entity(EntityType::Dimension(Dimension::Linear(d)))
|
||||
.add_entity(EntityType::Dimension(Box::new(Dimension::Linear(d))))
|
||||
.unwrap();
|
||||
|
||||
bake_dimension_blocks(&mut doc);
|
||||
|
||||
@@ -462,7 +462,7 @@ fn mtext_of_text(geom: &DgnGeom, color: u16) -> Option<EntityType> {
|
||||
m.rectangle_width = 0.0;
|
||||
m.drawing_direction = DrawingDirection::ByStyle;
|
||||
m.style = "Standard".to_string();
|
||||
Some(EntityType::MText(m))
|
||||
Some(EntityType::MText(Box::new(m)))
|
||||
}
|
||||
|
||||
/// A text node as one MTEXT, a paragraph a line, attached at the node's
|
||||
@@ -515,7 +515,7 @@ fn mtext_of_node(
|
||||
m.rectangle_width = 0.0;
|
||||
m.drawing_direction = DrawingDirection::ByStyle;
|
||||
m.style = style;
|
||||
Some(EntityType::MText(m))
|
||||
Some(EntityType::MText(Box::new(m)))
|
||||
}
|
||||
|
||||
/// An arc, circle or ellipse in its own plane.
|
||||
|
||||
@@ -1482,7 +1482,7 @@ pub fn convert(
|
||||
let layer = naming.layer(pdf_layer, Kind::Text);
|
||||
mtext.common.color = color_on(&layer, color_of(text.color));
|
||||
mtext.common.layer = layer;
|
||||
result.entities.push(EntityType::MText(mtext));
|
||||
result.entities.push(EntityType::MText(Box::new(mtext)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1712,7 +1712,7 @@ fn fill_entities(
|
||||
return Vec::new();
|
||||
}
|
||||
hatch.elevation = z;
|
||||
vec![EntityType::Hatch(hatch)]
|
||||
vec![EntityType::Hatch(Box::new(hatch))]
|
||||
}
|
||||
|
||||
inventory::submit!(crate::command::CommandRegistration {
|
||||
|
||||
@@ -144,7 +144,7 @@ impl MvsetupCommand {
|
||||
if self.arrangement == Arrangement::Engineering {
|
||||
viewport.view_direction = directions[(row * 2 + column) as usize].clone();
|
||||
}
|
||||
viewports.push(EntityType::Viewport(viewport));
|
||||
viewports.push(EntityType::Viewport(Box::new(viewport)));
|
||||
}
|
||||
}
|
||||
CmdResult::CommitEntitiesAndExit(viewports)
|
||||
|
||||
@@ -322,7 +322,7 @@ pub fn supports_annotation_context(entity: &EntityType) -> bool {
|
||||
| EntityType::Tolerance(_)
|
||||
| EntityType::Hatch(_) => true,
|
||||
EntityType::Dimension(dimension) => {
|
||||
!matches!(dimension, codec::entities::Dimension::Arc(_))
|
||||
!matches!(&**dimension, codec::entities::Dimension::Arc(_))
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
|
||||
@@ -788,7 +788,7 @@ mod tests {
|
||||
dimension.base.block_name = "SVC_TARGET".to_string();
|
||||
scene
|
||||
.document
|
||||
.add_entity(EntityType::Dimension(Dimension::Aligned(dimension)))
|
||||
.add_entity(EntityType::Dimension(Box::new(Dimension::Aligned(dimension))))
|
||||
.unwrap();
|
||||
|
||||
// A table's picture block and a block-valued cell, by handle.
|
||||
@@ -894,7 +894,7 @@ mod tests {
|
||||
dimension.base.block_name = "SVC_RENAMED".to_string();
|
||||
scene
|
||||
.document
|
||||
.add_entity(EntityType::Dimension(Dimension::Aligned(dimension)))
|
||||
.add_entity(EntityType::Dimension(Box::new(Dimension::Aligned(dimension))))
|
||||
.unwrap();
|
||||
let mut style = DimStyle::new("SVC_STYLE");
|
||||
style.dimblk = target;
|
||||
|
||||
Vendored
+1
-1
@@ -747,7 +747,7 @@ fn tessellate_sub_local(
|
||||
// Sanity guard: skip sub-entities whose primary dimension is so large
|
||||
// that adaptive tessellation will explode into hundreds of millions
|
||||
// of points. These are typically corrupt-radius primitives that slipped
|
||||
// past purge_corrupt_entities (finite but absurd values).
|
||||
// past the codec's unusable-geometry audit (finite but absurd values).
|
||||
if is_unreasonable_extent(sub) {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
@@ -479,7 +479,7 @@ impl Scene {
|
||||
self.previous_view = Some(ViewSnapshot::Floating {
|
||||
layout,
|
||||
handle,
|
||||
viewport: Box::new(viewport.clone()),
|
||||
viewport: viewport.clone(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -538,7 +538,7 @@ impl Scene {
|
||||
});
|
||||
return false;
|
||||
}
|
||||
let current = Box::new(current.clone());
|
||||
let current = current.clone();
|
||||
if let Some(EntityType::Viewport(destination)) =
|
||||
self.document.get_entity_mut(handle)
|
||||
{
|
||||
|
||||
@@ -869,7 +869,7 @@ pub(crate) fn constraint_from_associative_dimension(
|
||||
Some(ParametricRef::whole(*reference.xrefs.first()?))
|
||||
};
|
||||
let measurement = DrivingValue::Literal(dimension.measurement());
|
||||
match dimension {
|
||||
match &**dimension {
|
||||
Dimension::Aligned(_) => Some((
|
||||
ConstraintKind::Distance,
|
||||
vec![point(0)?, point(1)?],
|
||||
@@ -921,7 +921,7 @@ pub(crate) fn radial_extension_points(
|
||||
let EntityType::Dimension(dimension_entity) = document.get_entity(dimension)? else {
|
||||
return None;
|
||||
};
|
||||
let target_point = match dimension_entity {
|
||||
let target_point = match &**dimension_entity {
|
||||
Dimension::Radius(radius) => radius.definition_point,
|
||||
Dimension::Diameter(diameter) => diameter.angle_vertex,
|
||||
Dimension::LargeRadial(radial) => radial.chord_point,
|
||||
@@ -1009,7 +1009,7 @@ impl Scene {
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let radial_data = match dimension_entity {
|
||||
let radial_data = match &**dimension_entity {
|
||||
Dimension::Radius(radius) => Some((
|
||||
radius.angle_vertex,
|
||||
radius.measurement(),
|
||||
@@ -1223,7 +1223,7 @@ impl Scene {
|
||||
};
|
||||
// Radial references identify a circle/arc and angle, rather than a vertex.
|
||||
let radial_dimension = matches!(
|
||||
dimension_entity,
|
||||
&**dimension_entity,
|
||||
Dimension::Radius(_) | Dimension::Diameter(_) | Dimension::LargeRadial(_)
|
||||
);
|
||||
if radial_dimension {
|
||||
@@ -1383,7 +1383,7 @@ impl Scene {
|
||||
let Some(EntityType::Dimension(entity)) = self.document.get_entity(dimension) else {
|
||||
return Vec::new();
|
||||
};
|
||||
match entity {
|
||||
match &**entity {
|
||||
// A radial dimension's single reference describes the point where
|
||||
// the leader meets the circle.
|
||||
Dimension::Radius(radius) => vec![radius.definition_point],
|
||||
@@ -1458,17 +1458,15 @@ impl Scene {
|
||||
let measurement_supported = !association.trans_space
|
||||
|| self.document.get_entity(dimension).is_some_and(|entity| {
|
||||
matches!(
|
||||
entity,
|
||||
EntityType::Dimension(Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_))
|
||||
crate::entities::dimension::as_dimension(entity),
|
||||
Some(Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_))
|
||||
) || persisted_measurement_scale(&self.document, entity).is_some()
|
||||
});
|
||||
let measurement_supported = measurement_supported
|
||||
&& self.document.get_entity(dimension).is_some_and(|entity| {
|
||||
if !matches!(
|
||||
entity,
|
||||
EntityType::Dimension(
|
||||
Dimension::Radius(_) | Dimension::Diameter(_) | Dimension::LargeRadial(_)
|
||||
)
|
||||
crate::entities::dimension::as_dimension(entity),
|
||||
Some(Dimension::Radius(_) | Dimension::Diameter(_) | Dimension::LargeRadial(_))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
@@ -1652,7 +1650,7 @@ impl Scene {
|
||||
let Some(EntityType::Dimension(entity)) = self.document.get_entity(dimension) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let radial_data = match entity {
|
||||
let radial_data = match &**entity {
|
||||
Dimension::Radius(radius) => Some((
|
||||
radius.angle_vertex,
|
||||
radius.measurement(),
|
||||
@@ -1850,8 +1848,8 @@ impl Scene {
|
||||
continue;
|
||||
};
|
||||
let angular = matches!(
|
||||
&before,
|
||||
EntityType::Dimension(Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_))
|
||||
crate::entities::dimension::as_dimension(&before),
|
||||
Some(Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_))
|
||||
);
|
||||
let measurement_scale = if let Some(frame) = map.frame.filter(|_| !angular) {
|
||||
let Some(mut scale) = persisted_measurement_scale(&self.document, &before) else {
|
||||
@@ -1874,7 +1872,7 @@ impl Scene {
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
match dimension {
|
||||
match &mut **dimension {
|
||||
Dimension::Linear(linear) => {
|
||||
// The sheet annotation the user placed — the dimension
|
||||
// line's standoff and, when they dragged it, the text — is
|
||||
@@ -2104,7 +2102,7 @@ impl Scene {
|
||||
arc.base.actual_measurement = arc.measurement();
|
||||
}
|
||||
}
|
||||
let measurement = match &*dimension {
|
||||
let measurement = match &**dimension {
|
||||
Dimension::Linear(linear) => linear_measurement(linear),
|
||||
_ => dimension.measurement(),
|
||||
};
|
||||
@@ -2155,8 +2153,12 @@ impl Scene {
|
||||
refreshed
|
||||
}
|
||||
pub(crate) fn sync_diameter_association_angle(&mut self, dimension: Handle) {
|
||||
let chord = match self.document.get_entity(dimension) {
|
||||
Some(EntityType::Dimension(Dimension::Diameter(diameter))) => diameter.angle_vertex,
|
||||
let chord = match self
|
||||
.document
|
||||
.get_entity(dimension)
|
||||
.and_then(crate::entities::dimension::as_dimension)
|
||||
{
|
||||
Some(Dimension::Diameter(diameter)) => diameter.angle_vertex,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
@@ -2247,9 +2249,9 @@ mod tests {
|
||||
Vector3::ZERO,
|
||||
5.0,
|
||||
)));
|
||||
let dimension = scene.add_entity(EntityType::Dimension(Dimension::Diameter(
|
||||
let dimension = scene.add_entity(EntityType::Dimension(Box::new(Dimension::Diameter(
|
||||
DimensionDiameter::new(Vector3::new(5.0, 0.0, 0.0), Vector3::new(-5.0, 0.0, 0.0)),
|
||||
)));
|
||||
))));
|
||||
scene.attach_dimension_association(dimension, vec![Some(circle)]);
|
||||
let association = scene
|
||||
.document
|
||||
@@ -2270,8 +2272,10 @@ mod tests {
|
||||
.expect("diameter association");
|
||||
|
||||
scene.begin_undo_recording();
|
||||
let Some(EntityType::Dimension(Dimension::Diameter(diameter))) =
|
||||
scene.document.get_entity_mut(dimension)
|
||||
let Some(Dimension::Diameter(diameter)) = scene
|
||||
.document
|
||||
.get_entity_mut(dimension)
|
||||
.and_then(crate::entities::dimension::as_dimension_mut)
|
||||
else {
|
||||
panic!("diameter dimension");
|
||||
};
|
||||
|
||||
+1
-1
@@ -3206,7 +3206,7 @@ impl Scene {
|
||||
if let Some(origin) = model.pattern_origin {
|
||||
dxf.record_pattern_origin(Vector2::new(origin[0], origin[1]));
|
||||
}
|
||||
let mut entity = EntityType::Hatch(dxf);
|
||||
let mut entity = EntityType::Hatch(Box::new(dxf));
|
||||
|
||||
if let Some(layer) = layer {
|
||||
entity.as_entity_mut().set_layer(layer.to_string());
|
||||
|
||||
+3
-3
@@ -64,10 +64,10 @@ impl Scene {
|
||||
if let Some(EntityType::Viewport(sheet)) = self.document.get_entity(from.viewport).cloned()
|
||||
{
|
||||
if let Some(EntityType::Viewport(target)) = self.document.get_entity_mut(to.viewport) {
|
||||
*target = codec::entities::Viewport {
|
||||
*target = Box::new(codec::entities::Viewport {
|
||||
common: target.common.clone(),
|
||||
..sheet
|
||||
};
|
||||
..*sheet
|
||||
});
|
||||
}
|
||||
}
|
||||
self.unopened_layouts.remove(name);
|
||||
|
||||
+5
-13
@@ -1221,12 +1221,6 @@ pub struct DerivedCaches {
|
||||
/// Prepared during file open so entity selection/deselection never scans
|
||||
/// the complete object store on the UI thread.
|
||||
pub object_data: crate::entities::object_data::ObjectDataCache,
|
||||
/// Number of entities removed by the corrupt-entity guard during load.
|
||||
/// Reported back to the UI so the user knows when a file had parser-junk
|
||||
/// entities silently dropped.
|
||||
pub corrupt_dropped: usize,
|
||||
/// Corrupt entities dropped while resolving referenced drawings.
|
||||
pub xref_dropped: usize,
|
||||
/// XREF resolution results produced by the loader worker. Keeping these in
|
||||
/// the open bundle prevents parsing and merging references on the UI thread.
|
||||
pub xrefs: Vec<crate::io::xref::XrefInfo>,
|
||||
@@ -1523,8 +1517,6 @@ fn build_derived_caches_impl(
|
||||
meshes,
|
||||
block_meshes,
|
||||
object_data,
|
||||
corrupt_dropped: 0,
|
||||
xref_dropped: 0,
|
||||
xrefs: Vec::new(),
|
||||
prepared_geometry: None,
|
||||
timings: OpenTimings::default(),
|
||||
@@ -4956,7 +4948,7 @@ impl Scene {
|
||||
fit_sheet_viewport(&mut vp, min_lim, max_lim);
|
||||
if let Ok(handle) = self
|
||||
.document
|
||||
.add_entity_to_layout(EntityType::Viewport(vp), layout_name)
|
||||
.add_entity_to_layout(EntityType::Viewport(Box::new(vp)), layout_name)
|
||||
{
|
||||
if let Some(ObjectType::Layout(l)) = self.document.objects.get_mut(&layout_handle) {
|
||||
l.viewports.retain(|candidate| *candidate != handle);
|
||||
@@ -5027,7 +5019,7 @@ impl Scene {
|
||||
viewport.status = codec::entities::ViewportStatusFlags::default_on();
|
||||
if let Ok(handle) = self
|
||||
.document
|
||||
.add_entity_to_layout(EntityType::Viewport(viewport), layout_name)
|
||||
.add_entity_to_layout(EntityType::Viewport(Box::new(viewport)), layout_name)
|
||||
{
|
||||
// The reference zooms the new viewport to the model's extents
|
||||
// with a margin of under one percent.
|
||||
@@ -13802,7 +13794,7 @@ mod journal_tests {
|
||||
let mut annotative = MText::new();
|
||||
annotative.value = "annotative".to_string();
|
||||
annotative.is_annotative = true;
|
||||
let annotative = s.add_entity(EntityType::MText(annotative));
|
||||
let annotative = s.add_entity(EntityType::MText(Box::new(annotative)));
|
||||
|
||||
let before_points = s.geometry_epoch;
|
||||
s.invalidate_point_dependencies();
|
||||
@@ -14117,7 +14109,7 @@ mod journal_tests {
|
||||
scene.document.layers.add_or_replace(Layer::new("Shadows"));
|
||||
let mut hatch = Hatch::new();
|
||||
hatch.common.layer = "Shadows".to_string();
|
||||
let handle = scene.add_entity(EntityType::Hatch(hatch));
|
||||
let handle = scene.add_entity(EntityType::Hatch(Box::new(hatch)));
|
||||
let model = scene.model_space_block_handle();
|
||||
assert_eq!(
|
||||
scene
|
||||
@@ -15447,7 +15439,7 @@ mod layout_cache_tests {
|
||||
vp.height = 100.0;
|
||||
let handle = scene
|
||||
.document
|
||||
.add_entity_to_layout(codec::EntityType::Viewport(vp), "P1")
|
||||
.add_entity_to_layout(codec::EntityType::Viewport(Box::new(vp)), "P1")
|
||||
.unwrap();
|
||||
assert!(scene.set_viewport_scale_named_for(handle, "1:50").is_some());
|
||||
assert!(scene.active_viewport.is_none());
|
||||
|
||||
+2
-2
@@ -285,7 +285,7 @@ mod viewport_zoom_tests {
|
||||
viewport.width = 100.0;
|
||||
viewport.height = 100.0;
|
||||
viewport.view_height = 200.0;
|
||||
let handle = scene.add_entity(EntityType::Viewport(viewport));
|
||||
let handle = scene.add_entity(EntityType::Viewport(Box::new(viewport)));
|
||||
scene.active_viewport = Some(handle);
|
||||
|
||||
let view_height = |scene: &Scene| match scene.document.get_entity(handle) {
|
||||
@@ -337,7 +337,7 @@ mod clip_tests {
|
||||
viewport.height = 20.0;
|
||||
viewport.clip_boundary_handle = clip;
|
||||
viewport.status.non_rectangular_clipping = true;
|
||||
let viewport = scene.add_entity(EntityType::Viewport(viewport));
|
||||
let viewport = scene.add_entity(EntityType::Viewport(Box::new(viewport)));
|
||||
for (x, y, inside) in [
|
||||
(2.0, 2.0, true),
|
||||
(8.0, 2.0, true),
|
||||
|
||||
@@ -696,7 +696,7 @@ fn dynamic_dimension_follow_points(
|
||||
target.attachment_point = base.attachment_point;
|
||||
target.line_spacing_style = base.line_spacing_style;
|
||||
target.line_spacing_factor = base.line_spacing_factor;
|
||||
*dimension = rebuilt;
|
||||
*dimension = *rebuilt;
|
||||
true
|
||||
}
|
||||
|
||||
@@ -794,7 +794,7 @@ fn dynamic_dimension_follow_radial(
|
||||
let Some(codec::EntityType::Dimension(mut rebuilt)) = rebuilt else {
|
||||
return false;
|
||||
};
|
||||
let unchanged = match (&*dimension, &rebuilt) {
|
||||
let unchanged = match (&*dimension, &*rebuilt) {
|
||||
(Dimension::Radius(old), Dimension::Radius(new)) => {
|
||||
same(old.angle_vertex, new.angle_vertex)
|
||||
&& same(old.definition_point, new.definition_point)
|
||||
@@ -808,7 +808,7 @@ fn dynamic_dimension_follow_radial(
|
||||
if unchanged {
|
||||
return false;
|
||||
}
|
||||
match &mut rebuilt {
|
||||
match &mut *rebuilt {
|
||||
Dimension::Radius(new) => new.leader_length = leader,
|
||||
Dimension::Diameter(new) => new.leader_length = leader,
|
||||
_ => {}
|
||||
@@ -821,7 +821,7 @@ fn dynamic_dimension_follow_radial(
|
||||
target.attachment_point = base.attachment_point;
|
||||
target.line_spacing_style = base.line_spacing_style;
|
||||
target.line_spacing_factor = base.line_spacing_factor;
|
||||
*dimension = rebuilt;
|
||||
*dimension = *rebuilt;
|
||||
true
|
||||
}
|
||||
|
||||
@@ -881,7 +881,7 @@ fn dynamic_dimension_follow_angle(
|
||||
target.attachment_point = base.attachment_point;
|
||||
target.line_spacing_style = base.line_spacing_style;
|
||||
target.line_spacing_factor = base.line_spacing_factor;
|
||||
*dimension = rebuilt;
|
||||
*dimension = *rebuilt;
|
||||
true
|
||||
}
|
||||
|
||||
@@ -2264,10 +2264,14 @@ impl super::Scene {
|
||||
});
|
||||
let measured = points.filter(|_| !angular).map(|(first, second)| {
|
||||
let delta = second - first;
|
||||
let axis = match self.document.get_entity(*dimension) {
|
||||
Some(codec::EntityType::Dimension(codec::entities::Dimension::Linear(
|
||||
linear,
|
||||
))) => Some((linear.rotation.cos(), linear.rotation.sin())),
|
||||
let axis = match self
|
||||
.document
|
||||
.get_entity(*dimension)
|
||||
.and_then(crate::entities::dimension::as_dimension)
|
||||
{
|
||||
Some(codec::entities::Dimension::Linear(linear)) => {
|
||||
Some((linear.rotation.cos(), linear.rotation.sin()))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
match (constraint.kind, axis) {
|
||||
@@ -2605,7 +2609,7 @@ impl super::Scene {
|
||||
// line, which breaks around the text, so the text is centred on
|
||||
// it rather than lifted above it.
|
||||
let radial = matches!(
|
||||
dimension,
|
||||
&**dimension,
|
||||
codec::entities::Dimension::Radius(_) | codec::entities::Dimension::Diameter(_)
|
||||
);
|
||||
let centred = !radial || ov::int(xdata, ov::DIMTAD) == Some(0);
|
||||
@@ -2615,7 +2619,7 @@ impl super::Scene {
|
||||
// The reference draws dynamic distance text horizontally; an
|
||||
// angle's text keeps its style's alignment.
|
||||
let angular = matches!(
|
||||
dimension,
|
||||
&**dimension,
|
||||
codec::entities::Dimension::Angular2Ln(_)
|
||||
| codec::entities::Dimension::Angular3Pt(_)
|
||||
);
|
||||
|
||||
@@ -16,7 +16,7 @@ impl Scene {
|
||||
let viewports: Vec<&Viewport> = viewport_handles
|
||||
.iter()
|
||||
.filter_map(|handle| match self.document.get_entity(*handle) {
|
||||
Some(EntityType::Viewport(vp)) => Some(vp),
|
||||
Some(EntityType::Viewport(vp)) => Some(&**vp),
|
||||
_ => None,
|
||||
})
|
||||
.filter(|vp| {
|
||||
@@ -446,7 +446,7 @@ impl Scene {
|
||||
Some(EntityType::Viewport(viewport))
|
||||
if viewport.common.owner_handle == paper_block && viewport.is_on() =>
|
||||
{
|
||||
Some(viewport)
|
||||
Some(&**viewport)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
|
||||
@@ -152,7 +152,7 @@ impl ObjectKind {
|
||||
EntityType::Text(e::Text::with_value("Text", origin).with_height(2.5))
|
||||
}
|
||||
ObjectKind::MText => {
|
||||
EntityType::MText(e::MText::with_value("MText", origin).with_height(2.5))
|
||||
EntityType::MText(Box::new(e::MText::with_value("MText", origin).with_height(2.5)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,10 +62,6 @@ pub fn view_window<'a>(
|
||||
crate::tr!("recovery", "issues-found"),
|
||||
report.issues_found().to_string(),
|
||||
),
|
||||
metric(
|
||||
crate::tr!("recovery", "entities-removed"),
|
||||
report.removed_total().to_string(),
|
||||
),
|
||||
metric(
|
||||
crate::tr!("recovery", "references-checked"),
|
||||
report.references_checked.to_string(),
|
||||
@@ -75,16 +71,6 @@ pub fn view_window<'a>(
|
||||
.width(Fill);
|
||||
|
||||
let mut details = column![].spacing(6);
|
||||
if report.referenced_entities_removed > 0 {
|
||||
details = details.push(
|
||||
text(format!(
|
||||
"{}: {}",
|
||||
crate::tr!("recovery", "referenced-entities-removed"),
|
||||
report.referenced_entities_removed
|
||||
))
|
||||
.size(11),
|
||||
);
|
||||
}
|
||||
if report.references_missing > 0 || report.references_failed > 0 {
|
||||
details = details.push(
|
||||
text(format!(
|
||||
|
||||
Reference in new issue
Block a user