mirror of
https://github.com/storytold/cadcraft.git
synced 2026-11-08 11:29:55 -06:00
A proxy whose class the file doesn't name is reported as not saved
From 2013 the DWG keeps another program's proxy entity with the class its group 91 names, so the loss report skipped every ACAD_PROXY_ENTITY. Saving drops a proxy whose group 91 names no class of the file's CLASSES, though, so that one is now counted. The group 91 helpers move to cadcraft_doc::foreign so the engine can ask Foreign::proxy_class.
This commit is contained in:
@@ -82,6 +82,12 @@ pub struct Foreign {
|
||||
}
|
||||
|
||||
impl Foreign {
|
||||
/// The class of the file's CLASSES a proxy's group 91 names (see [`class_number`]), if any.
|
||||
/// Saving as DWG keeps a proxy with that class and leaves out one whose class it can't name.
|
||||
pub fn proxy_class(&self, tags: &[RawTag]) -> Option<&str> {
|
||||
class_index(class_number(tags.iter().map(|r| (r.code, r.value.as_str())))).and_then(|i| self.class_order.get(i)).map(String::as_str)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.objects.is_empty()
|
||||
&& self.dictionary.is_empty()
|
||||
@@ -98,3 +104,29 @@ impl Foreign {
|
||||
self.data_store.iter().filter(|t| t.code == 320).filter_map(|t| Handle::parse_hex(t.value.trim())).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// Where a proxy's class number is: its first group 91 outside the `102 {NAME` … `102 }` groups
|
||||
/// (an application's can hold a 91 of its own) and before any xdata.
|
||||
pub fn class_group<S: AsRef<str>>(groups: impl IntoIterator<Item = (i32, S)>) -> Option<usize> {
|
||||
let mut in_group = false;
|
||||
for (i, (code, value)) in groups.into_iter().enumerate() {
|
||||
match code {
|
||||
1000.. => return None,
|
||||
102 => in_group = value.as_ref().trim().starts_with('{'),
|
||||
91 if !in_group => return Some(i),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// A proxy's class number (see [`class_group`]).
|
||||
pub fn class_number<S: AsRef<str>>(groups: impl IntoIterator<Item = (i32, S)> + Clone) -> Option<i64> {
|
||||
let i = class_group(groups.clone())?;
|
||||
groups.into_iter().nth(i).and_then(|(_, v)| v.as_ref().trim().parse().ok())
|
||||
}
|
||||
|
||||
/// The place in CLASSES a class number names (500 is the first class).
|
||||
pub fn class_index(number: Option<i64>) -> Option<usize> {
|
||||
usize::try_from(number?.checked_sub(500)?).ok()
|
||||
}
|
||||
|
||||
@@ -733,8 +733,8 @@ const MODELER_TYPES: [&str; 3] = ["3DSOLID", "REGION", "BODY"];
|
||||
|
||||
/// [`not_saved`] for a file saved as `version`. From 2013 on, a 3D solid, region or body the data
|
||||
/// store has a record for keeps its geometry and is not counted, and neither is another program's
|
||||
/// proxy entity: the DWG writes it with its class (before 2013 it has no class to name, and the
|
||||
/// DWG leaves it out).
|
||||
/// proxy entity whose class the file names: the DWG writes it with that class. Before 2013, or
|
||||
/// when its group 91 names no class of the file, the DWG leaves it out and it is counted.
|
||||
pub fn not_saved_as(d: &Drawing, version: Version) -> std::collections::BTreeMap<String, usize> {
|
||||
let mut out: std::collections::BTreeMap<String, usize> = std::collections::BTreeMap::new();
|
||||
let stored = if version >= Version::R2013 { d.foreign.data_store_entities() } else { Default::default() };
|
||||
@@ -744,7 +744,7 @@ pub fn not_saved_as(d: &Drawing, version: Version) -> std::collections::BTreeMap
|
||||
if MODELER_TYPES.iter().any(|t| u.dxf_type.eq_ignore_ascii_case(t)) && stored.contains(&e.handle) {
|
||||
continue;
|
||||
}
|
||||
if version >= Version::R2013 && u.dxf_type.eq_ignore_ascii_case("ACAD_PROXY_ENTITY") {
|
||||
if version >= Version::R2013 && u.dxf_type.eq_ignore_ascii_case("ACAD_PROXY_ENTITY") && d.foreign.proxy_class(&u.tags).is_some() {
|
||||
continue;
|
||||
}
|
||||
let n = out.entry(u.dxf_type.clone()).or_insert(0);
|
||||
|
||||
@@ -83,18 +83,35 @@ fn solids_whose_geometry_the_data_store_keeps_are_not_reported_from_2013() {
|
||||
assert_eq!(not_saved_as(&d, Version::R2010), [("3DSOLID".to_string(), 2), ("REGION".to_string(), 1)].into_iter().collect());
|
||||
}
|
||||
|
||||
/// Another program's proxy entity reaches a 2013 or later DWG with its class; before 2013 the DWG
|
||||
/// leaves it out and says so.
|
||||
/// Another program's proxy entity reaches a 2013 or later DWG with the class its group 91 names;
|
||||
/// before 2013, or when the file has no such class, the DWG leaves it out and says so.
|
||||
#[test]
|
||||
fn proxy_entities_are_reported_only_before_2013() {
|
||||
use cadcraft_engine::cmd::file::{Version, not_saved_as};
|
||||
use cadcraft_engine::doc::foreign::Foreign;
|
||||
let proxy = |class: i64| {
|
||||
EntityKind::Unknown(Unknown {
|
||||
dxf_type: "ACAD_PROXY_ENTITY".into(),
|
||||
tags: vec![
|
||||
RawTag { code: 8, value: "0".into() },
|
||||
RawTag { code: 90, value: "498".into() },
|
||||
RawTag { code: 91, value: class.to_string() },
|
||||
],
|
||||
})
|
||||
};
|
||||
let mut d = Drawing::new_imperial();
|
||||
d.add(&Space::Model, Common::default(), unknown("ACAD_PROXY_ENTITY")).unwrap();
|
||||
d.add(&Space::Model, Common::default(), proxy(500)).unwrap();
|
||||
d.add(&Space::Model, Common::default(), unknown("MLINE")).unwrap();
|
||||
d.foreign = std::sync::Arc::new(Foreign { class_order: vec!["MYAPP_THING".into()], ..Foreign::default() });
|
||||
for v in [Version::R2013, Version::R2018] {
|
||||
assert_eq!(not_saved_as(&d, v), [("MLINE".to_string(), 1)].into_iter().collect(), "{v:?}");
|
||||
}
|
||||
for v in [Version::R2004, Version::R2010] {
|
||||
assert_eq!(not_saved_as(&d, v), [("ACAD_PROXY_ENTITY".to_string(), 1), ("MLINE".to_string(), 1)].into_iter().collect(), "{v:?}");
|
||||
}
|
||||
// A class number the file's CLASSES don't have, or none at all: the DWG can't name the class,
|
||||
// leaves the proxy out, and says so from 2013 on too.
|
||||
d.add(&Space::Model, Common::default(), proxy(501)).unwrap();
|
||||
d.add(&Space::Model, Common::default(), unknown("ACAD_PROXY_ENTITY")).unwrap();
|
||||
assert_eq!(not_saved_as(&d, Version::R2018), [("ACAD_PROXY_ENTITY".to_string(), 2), ("MLINE".to_string(), 1)].into_iter().collect());
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ use std::borrow::Cow;
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
use cadcraft_doc::foreign::{Foreign, ForeignExtra, ForeignKey, ForeignObject};
|
||||
use cadcraft_doc::foreign::{Foreign, ForeignExtra, ForeignKey, ForeignObject, class_group, class_index, class_number};
|
||||
use cadcraft_doc::{Drawing, Entity, EntityKind, Handle, RawTag, Unknown};
|
||||
use cadcraft_dxf::{Section, Tag};
|
||||
|
||||
@@ -559,32 +559,6 @@ fn is_proxy(kind: &str) -> bool {
|
||||
matches!(up(kind).as_str(), "ACAD_PROXY_OBJECT" | "ACAD_PROXY_ENTITY")
|
||||
}
|
||||
|
||||
/// Where a proxy's class number is: its first group 91 outside the `102 {NAME` … `102 }` groups
|
||||
/// (an application's can hold a 91 of its own) and before any xdata.
|
||||
fn class_group<S: AsRef<str>>(groups: impl IntoIterator<Item = (i32, S)>) -> Option<usize> {
|
||||
let mut in_group = false;
|
||||
for (i, (code, value)) in groups.into_iter().enumerate() {
|
||||
match code {
|
||||
1000.. => return None,
|
||||
102 => in_group = value.as_ref().trim().starts_with('{'),
|
||||
91 if !in_group => return Some(i),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// A proxy's class number (see [`class_group`]).
|
||||
fn class_number<S: AsRef<str>>(groups: impl IntoIterator<Item = (i32, S)> + Clone) -> Option<i64> {
|
||||
let i = class_group(groups.clone())?;
|
||||
groups.into_iter().nth(i).and_then(|(_, v)| v.as_ref().trim().parse().ok())
|
||||
}
|
||||
|
||||
/// The place in CLASSES a class number names (500 is the first class).
|
||||
fn class_index(number: Option<i64>) -> Option<usize> {
|
||||
usize::try_from(number?.checked_sub(500)?).ok()
|
||||
}
|
||||
|
||||
/// Set a proxy's class number (see [`class_group`]).
|
||||
fn set_class_number(tags: &mut [Tag], n: i64) {
|
||||
if let Some(t) = class_group(tags.iter().map(|t| (t.code, t.str()))).and_then(|i| tags.get_mut(i)) {
|
||||
|
||||
Reference in New Issue
Block a user