OLE2FRAME: draw its frame outline; a DWG save no longer lists it as left out (#2422)

The frame is the rectangle from the entity's two corner points (groups 10/20 and
11/21). A DWG write and read-back keeps the OLE data (ole_frame.rs), so the DWG
note leaves the type out of notSaved.
This commit is contained in:
SBaksa
2026-10-31 14:27:48 +11:00
parent dc1442ac89
commit 4caf26ee60
6 changed files with 85 additions and 1 deletions
+7 -1
View File
@@ -720,6 +720,10 @@ pub fn not_saved(d: &Drawing) -> std::collections::BTreeMap<String, usize> {
/// store section, which is written back with the drawing (`Foreign::data_store`).
const MODELER_TYPES: [&str; 3] = ["3DSOLID", "REGION", "BODY"];
/// Types the DWG bridge writes back whole although CADCraft does not model them: a DWG save and
/// read-back keeps their data (`ole_frame.rs`, #2422), so they are not "left out".
const DWG_KEPT_TYPES: [&str; 1] = ["OLE2FRAME"];
/// [`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.
pub fn not_saved_as(d: &Drawing, version: Version) -> std::collections::BTreeMap<String, usize> {
@@ -728,7 +732,9 @@ pub fn not_saved_as(d: &Drawing, version: Version) -> std::collections::BTreeMap
let stores = std::iter::once(&d.model).chain(d.layouts.iter().map(|l| &l.entities)).chain(d.blocks.values().map(|b| &b.entities));
for e in stores.flat_map(|s| s.iter()) {
if let cadcraft_doc::EntityKind::Unknown(u) = &e.kind {
if MODELER_TYPES.iter().any(|t| u.dxf_type.eq_ignore_ascii_case(t)) && stored.contains(&e.handle) {
if DWG_KEPT_TYPES.iter().any(|t| u.dxf_type.eq_ignore_ascii_case(t))
|| (MODELER_TYPES.iter().any(|t| u.dxf_type.eq_ignore_ascii_case(t)) && stored.contains(&e.handle))
{
continue;
}
let n = out.entry(u.dxf_type.clone()).or_insert(0);
@@ -82,3 +82,16 @@ fn solids_whose_geometry_the_data_store_keeps_are_not_reported_from_2013() {
// Before 2013 a file has no data store: every solid loses its geometry.
assert_eq!(not_saved_as(&d, Version::R2010), [("3DSOLID".to_string(), 2), ("REGION".to_string(), 1)].into_iter().collect());
}
/// An OLE2FRAME is written back whole by a DWG save, so the DWG note doesn't list it; other
/// unmodelled types are still listed (#2422).
#[test]
fn a_dwg_save_does_not_list_an_ole_frame() {
use cadcraft_engine::cmd::file::{Version, not_saved_as};
let mut d = Drawing::new_imperial();
d.add(&Space::Model, Common::default(), unknown("OLE2FRAME")).unwrap();
d.add(&Space::Model, Common::default(), unknown("MLINE")).unwrap();
for v in [Version::R2000, Version::R2018] {
assert_eq!(not_saved_as(&d, v), [("MLINE".to_string(), 1)].into_iter().collect(), "{v:?}");
}
}
+1
View File
@@ -44,6 +44,7 @@ mod mtext_columns_roundtrip;
mod mtext_embedded_object;
mod mtext_line_spacing;
mod multiline_attributes;
mod ole_frame;
mod paperview_marker;
mod passthrough_image_roundtrip;
mod pdf_followups;
+36
View File
@@ -0,0 +1,36 @@
//! An OLE2FRAME is kept as an unknown entity; a DWG save carries it with its data (#2422).
use cadcraft_doc::EntityKind;
use cadcraft_io::{SaveOptions, Version, read, read_dxf, write_with};
/// A 2000 DXF with one OLE2FRAME: corners, the binary data and the end marker (DXF Reference).
fn dxf() -> String {
String::from(
"0\nSECTION\n2\nHEADER\n9\n$ACADVER\n1\nAC1015\n0\nENDSEC\n0\nSECTION\n2\nENTITIES\n\
0\nOLE2FRAME\n5\n2B\n100\nAcDbEntity\n8\n0\n100\nAcDbOle2Frame\n70\n2\n3\nOLE Object\n\
10\n30.14\n20\n-18.99\n30\n0\n11\n35.27\n21\n-22.39\n31\n0\n71\n2\n72\n0\n90\n8\n310\n0102030405060708\n1\nOLE\n\
0\nENDSEC\n0\nEOF\n",
)
}
fn ole(d: &cadcraft_doc::Drawing) -> Option<cadcraft_doc::Unknown> {
d.model.iter().find_map(|e| match &e.kind {
EntityKind::Unknown(u) if u.dxf_type == "OLE2FRAME" => Some(u.clone()),
_ => None,
})
}
#[test]
fn a_dwg_keeps_an_ole_frame_with_its_data() {
let d = read_dxf(dxf().as_bytes()).unwrap();
assert!(ole(&d).is_some());
for v in [Version::R2000, Version::R2010, Version::R2018] {
let dwg = write_with(&d, "x.dwg", &SaveOptions { version: Some(v), binary: false }).unwrap();
let back = read(&dwg, "x.dwg").unwrap();
let u = ole(&back).unwrap_or_else(|| panic!("{v:?}: the OLE2FRAME is gone"));
let data: String = u.tags.iter().filter(|t| t.code == 310).map(|t| t.value.to_ascii_uppercase()).collect();
assert_eq!(data, "0102030405060708", "{v:?}: data");
// Only the data is compared: with this stand-in blob the DWG round trip returns default
// corners (1,1)/(0,0), which a real object might not do; not verified without a real file.
}
}
+14
View File
@@ -234,6 +234,12 @@ fn entity_parts(b: &mut Builder, ctx: &Ctx, e: &Entity) {
b.polyline(ctx, rgb, lw, l);
}
}
// An OLE object's content is not drawn; its frame, from its two corner points, is.
EntityKind::Unknown(u) if u.dxf_type.eq_ignore_ascii_case("OLE2FRAME") => {
if let Some(q) = ole_frame(&u.tags) {
b.polyline(ctx, rgb, lw, &q);
}
}
kind => {
for prim in kind.prims() {
match prim {
@@ -379,3 +385,11 @@ fn leader_scale(d: &Drawing, st: Option<&cadcraft_doc::DimStyle>) -> f64 {
let dimscale = d.header.f64("DIMSCALE", 1.0);
st.map(|s| s.effective_scale(dimscale)).unwrap_or_else(|| cadcraft_doc::DimStyle::default().effective_scale(dimscale))
}
/// The closed outline of an OLE2FRAME from its upper-left (groups 10, 20) and lower-right
/// (groups 11, 21) corners (DXF Reference, OLE2FRAME); `None` when either is missing or not finite.
fn ole_frame(tags: &[cadcraft_doc::RawTag]) -> Option<[Vec2; 5]> {
let get = |code: i32| tags.iter().find(|t| t.code == code).and_then(|t| t.value.trim().parse::<f64>().ok()).filter(|v| v.is_finite());
let (a, b) = (Vec2::new(get(10)?, get(20)?), Vec2::new(get(11)?, get(21)?));
Some([a, Vec2::new(b.x, a.y), b, Vec2::new(a.x, b.y), a])
}
+14
View File
@@ -1122,3 +1122,17 @@ fn raster_hatch_pattern_matches_one_by_one() {
assert!(render(&l, &view, &o).unwrap().data() == render_one_by_one(&l, &view, &o).data());
}
}
/// An OLE2FRAME draws its frame, the rectangle from its two corner points (#2422), and nothing
/// when a corner is missing.
#[test]
fn ole_frame_draws_its_outline() {
let tag = |code: i32, value: &str| cadcraft_doc::RawTag { code, value: value.into() };
let frame = |tags: Vec<cadcraft_doc::RawTag>| EntityKind::Unknown(cadcraft_doc::Unknown { dxf_type: "OLE2FRAME".into(), tags });
let mut d = Drawing::new_imperial();
d.add(&Space::Model, Common::default(), frame(vec![tag(10, "30"), tag(20, "-19"), tag(11, "35"), tag(21, "-22")])).unwrap();
d.add(&Space::Model, Common::default(), frame(vec![tag(10, "1"), tag(20, "2")])).unwrap();
let l = build(&d, &Space::Model, &Options { tolerance: 0.01, ..Default::default() });
assert_eq!(l.prims.len(), 1, "only the frame with both corners is drawn");
assert_eq!(l.prims[0].len, 5, "a closed rectangle: five points");
}