feat(render): draw a proxy entity's cached preview instead of nothing

An entity from an application we have no reader for — e.g. an Autodesk Raster
Design embedded raster image — now arrives as `Unknown` (acadrust 769741d) and
rendered as nothing at all, so the drawing silently lost it with no hint that
anything was there.

Such entities almost always ship proxy graphics: the vector preview their author
cached for precisely this case, and what AutoCAD itself falls back to when the
object enabler is absent. Decode it and draw it, so the entity holds its real
place and size.

The record grammar has no reference implementation to copy — LibreDWG keeps the
blob raw ("see par 29") and ACadSharp stores the bytes without parsing them — so
rather than infer a general decoder from one sample, `proxy_graphics` accepts a
single polyline record and only when the blob matches it byte-for-byte: sizes
must agree, the points must consume every remaining byte, and coordinates must
be finite. Anything else returns None and draws nothing, exactly as before, so
a file this does not model cannot regress or sprout invented geometry.

Verified on a Raster Design drawing: its 140-byte preview decodes with 0 bytes
left over to the image frame (1192.149 x 877.824, closed), matching what ODA
reports for the same entity. Unit-tested against that real blob.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-17 12:53:42 +03:00
1 parent 3bd5453c3b
commit f720e40ba1
4 files changed
+134 -1

No files matched your search

+1
View File
@@ -22,6 +22,7 @@ pub mod dgn_linestyle;
pub mod truck_tess;
pub mod tessellate;
pub(crate) mod tess;
pub mod proxy_graphics;
pub mod tess_util;
pub mod solid3d_tess;
pub mod spline_tess;
+111
View File
@@ -0,0 +1,111 @@
//! Proxy entity graphics — the cached vector preview an application stores
//! alongside a custom entity so viewers without its object enabler can still
//! draw something. AutoCAD falls back to exactly this; so do we.
//!
//! No reference decoder exists to copy: LibreDWG keeps the blob raw and points
//! at the spec ("see par 29"), ACadSharp likewise stores `ProxyGraphics` bytes
//! and never parses them. The full record grammar is therefore unverified here,
//! and guessing at it from a sample is how you end up misreading every file.
//!
//! So this decodes exactly one shape — a single polyline record — and only when
//! the blob matches it byte-for-byte. Anything else returns `None` and draws
//! nothing, which is what happened before this existed: no regression, no
//! invented geometry.
/// A closed/open polyline lifted from a proxy-graphics blob (world coords).
pub struct ProxyPolyline {
pub points: Vec<[f64; 3]>,
}
/// Record type for a polyline entry. The only one accepted.
const ENTRY_POLYLINE: u32 = 6;
/// Layout accepted (little-endian), validated in full before anything is
/// emitted:
///
/// ```text
/// u32 total_size == blob.len()
/// u32 entry_count == 1
/// u32 payload_size == blob.len() - 8
/// u32 entry_type == 6 (polyline)
/// u32 point_count
/// [f64; 3] * point_count
/// ```
///
/// Verified against an Autodesk Raster Design embedded raster image, whose
/// 140-byte preview decodes to its image frame with 0 bytes left over.
pub fn decode_polyline(blob: &[u8]) -> Option<ProxyPolyline> {
let u32_at = |o: usize| -> Option<u32> {
blob.get(o..o + 4)
.map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
};
if blob.len() < 20 || u32_at(0)? as usize != blob.len() || u32_at(4)? != 1 {
return None;
}
if u32_at(8)? as usize != blob.len() - 8 || u32_at(12)? != ENTRY_POLYLINE {
return None;
}
let n = u32_at(16)? as usize;
// The points must account for every remaining byte — a short read would
// mean the record carries something this decoder does not model.
if n < 2 || 20usize.checked_add(n.checked_mul(24)?)? != blob.len() {
return None;
}
let mut points = Vec::with_capacity(n);
for i in 0..n {
let o = 20 + i * 24;
let f = |k: usize| -> f64 {
let b = &blob[o + k * 8..o + k * 8 + 8];
f64::from_le_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]])
};
let (x, y, z) = (f(0), f(1), f(2));
if !x.is_finite() || !y.is_finite() || !z.is_finite() {
return None;
}
points.push([x, y, z]);
}
Some(ProxyPolyline { points })
}
#[cfg(test)]
mod tests {
use super::*;
/// The real 140-byte preview of an Autodesk Raster Design embedded raster
/// image: its frame, 1192.149 x 877.824, closed back to the origin.
#[test]
fn decodes_the_raster_design_image_frame() {
let hex = "8C00000001000000840000000600000005000000\
000000000000000000000000000000000000000000000000\
DEF97E6A1C422E3D3DDF4F8D976E8B400000000000000000\
6BBC749398A092403DDF4F8D976E8B400000000000000000\
6BBC7493 98A09240 0000000000000000 0000000000000000\
000000000000000000000000000000000000000000000000";
let blob: Vec<u8> = hex
.split_whitespace()
.collect::<String>()
.as_bytes()
.chunks(2)
.map(|c| u8::from_str_radix(std::str::from_utf8(c).unwrap(), 16).unwrap())
.collect();
assert_eq!(blob.len(), 140);
let p = decode_polyline(&blob).expect("frame decodes");
assert_eq!(p.points.len(), 5);
assert!((p.points[2][0] - 1192.1490).abs() < 1e-3);
assert!((p.points[2][1] - 877.8240).abs() < 1e-3);
// Closed: last point returns to the first.
assert_eq!(p.points[0], p.points[4]);
}
#[test]
fn rejects_anything_it_does_not_model() {
assert!(decode_polyline(&[]).is_none());
assert!(decode_polyline(&[0u8; 140]).is_none()); // size field wrong
let mut b = vec![0u8; 140];
b[0] = 140; // right size, wrong everything else
assert!(decode_polyline(&b).is_none());
}
}
+21
View File
@@ -228,6 +228,27 @@ pub(crate) fn tessellate_entity(
let pattern_length = pattern_length * pslt_factor;
let pattern = pattern.map(|v| v * pslt_factor);
// ── Proxy entity: draw its cached preview ───────────────────────────────
//
// An entity from an application we have no reader for (e.g. an Autodesk
// Raster Design embedded raster image) arrives as `Unknown`. Its own data is
// a private format we cannot decode — but it usually ships a proxy-graphics
// blob, the vector preview its author cached for exactly this case. AutoCAD
// draws that when the object enabler is missing; draw it too, so the entity
// occupies its real place instead of silently disappearing.
if let EntityType::Unknown(_) = e {
if let Some(blob) = e.common().graphic_data.as_ref() {
if let Some(p) = convert::proxy_graphics::decode_polyline(blob) {
let (pts, pts_low) = convert::tessellate::points_to_ds(p.points);
let mut w = WireModel::solid(h.value().to_string(), pts, entity_color, sel);
w.points_low = pts_low;
w.line_weight_px = line_weight_px;
w.aci = aci;
return vec![w];
}
}
}
// ── Dimension baked-block fast path ─────────────────────────────────────
//
// AutoCAD bakes each dimension's final geometry (extension lines, dim
+1 -1
View File
@@ -46,7 +46,7 @@ fn split_ds_xyz(x: f64, y: f64, z: f64) -> ([f32; 3], [f32; 3]) {
/// Split each absolute f64 source point into double-single (high, low) f32
/// buffers in one pass — the relative-to-eye residual the GPU/CPU reconstruct
/// to f64 precision at UTM-scale coordinates.
fn points_to_ds(
pub(crate) fn points_to_ds(
src: impl IntoIterator<Item = [f64; 3]>,
) -> (Vec<[f32; 3]>, Vec<[f32; 3]>) {
let it = src.into_iter();