Files
OneCAD-Draft/examples/inspect_polyline.rs
T
HakanandClaude Opus 4.7 b2588e1790 feat(io): drop parser-garbage entities at load time
acadrust's DWG reader occasionally desynchronises on certain files
(observed on render.dwg: 1519 of 1763 LwPolylines arrived with non-unit
normals in the 1e200+ range and 100k phantom vertices). Tessellating
those entities allocates gigabytes and produces NaN-laden wires,
freezing the UI.

Add a pre-tessellation guard that scans freshly loaded documents and
drops any entity failing a cheap sanity check: non-unit normal,
non-finite or absurd coordinate, or vertex count above 100k. Count of
dropped entities is threaded through DerivedCaches and surfaced as a
warning in the command line so the user knows the file was patched up
on the way in.

Same file in DXF form parses cleanly (0 corrupt), so this is strictly
a DWG-path workaround until the upstream parser bug is fixed.

The new examples/inspect_polyline.rs is a one-off diagnostic that dumps
LwPolyline/Polyline2D/Polyline3D fields plus a corrupt-entity count;
useful for narrowing down the acadrust bug.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 10:00:16 +03:00

175 lines
6.5 KiB
Rust

// One-off inspector for diagnosing a DWG: dumps every LwPolyline/Polyline2D/
// Polyline3D with its elevation, normal vector, and per-vertex coordinates.
// Run with: cargo run --example inspect_polyline -- <path-to-dwg>
use acadrust::entities::EntityType;
use acadrust::io::dwg::DwgReader;
use acadrust::DxfReader;
use std::env;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = env::args().nth(1).ok_or("usage: inspect_polyline <file>")?;
let mut doc = if path.to_lowercase().ends_with(".dxf") {
DxfReader::from_file(&path)?.read()?
} else {
DwgReader::from_file(&path)?.read()?
};
// Cheap inline corruption check — mirrors the rules in src/io/mod.rs so the
// example reports the same drop count without needing a library crate.
let total = doc.entities().count();
let bad = doc
.entities()
.filter(|e| {
use acadrust::entities::EntityType as E;
const MAX: usize = 100_000;
let bad_normal = |n: &acadrust::types::Vector3| {
!n.x.is_finite()
|| !n.y.is_finite()
|| !n.z.is_finite()
|| ((n.x * n.x + n.y * n.y + n.z * n.z) - 1.0).abs() >= 0.21
};
match e {
E::LwPolyline(p) => {
bad_normal(&p.normal)
|| p.vertices.len() > MAX
|| !p.elevation.is_finite()
|| p.elevation.abs() > 1e10
}
E::Polyline2D(p) => {
bad_normal(&p.normal)
|| p.vertices.len() > MAX
|| !p.elevation.is_finite()
|| p.elevation.abs() > 1e10
}
_ => false,
}
})
.count();
println!("Entities: total={} corrupt={}", total, bad);
println!();
let _ = &mut doc; // silence unused-mut warning if we don't mutate below
let h = &doc.header;
println!(
"Header extents: min=({:.3},{:.3},{:.3}) max=({:.3},{:.3},{:.3})",
h.model_space_extents_min.x,
h.model_space_extents_min.y,
h.model_space_extents_min.z,
h.model_space_extents_max.x,
h.model_space_extents_max.y,
h.model_space_extents_max.z,
);
println!();
let mut lwp = 0usize;
let mut p2d = 0usize;
let mut p3d = 0usize;
let mut pl = 0usize;
for (i, e) in doc.entities().enumerate() {
match e {
EntityType::LwPolyline(p) => {
lwp += 1;
if p.common.layer != "PLN_Sev" {
continue;
}
let nx_abs = p.normal.x.abs();
let ny_abs = p.normal.y.abs();
let corrupt = !nx_abs.is_finite()
|| nx_abs > 1.0
|| ny_abs > 1.0
|| p.vertices.len() > 1000;
if corrupt {
println!(
"[{}] CORRUPT handle={:?} verts={} elev={:.3e} normal=({:.2e},{:.2e},{:.2e})",
i,
p.common.handle,
p.vertices.len(),
p.elevation,
p.normal.x,
p.normal.y,
p.normal.z,
);
} else {
println!(
"[{}] OK handle={:?} verts={} elev={:.3} normal=({:.4},{:.4},{:.4})",
i,
p.common.handle,
p.vertices.len(),
p.elevation,
p.normal.x,
p.normal.y,
p.normal.z,
);
for (vi, v) in p.vertices.iter().take(3).enumerate() {
println!(" v{}: ({:.3}, {:.3}) bulge={}", vi, v.location.x, v.location.y, v.bulge);
}
}
}
EntityType::Polyline2D(p) => {
p2d += 1;
if p2d <= 5 {
println!(
"[{}] Polyline2D handle={:?} layer={:?}",
i, p.common.handle, p.common.layer
);
println!(
" elevation={} thickness={} normal=({},{},{})",
p.elevation, p.thickness, p.normal.x, p.normal.y, p.normal.z
);
println!(" closed={} verts={}", p.is_closed(), p.vertices.len());
for (vi, v) in p.vertices.iter().enumerate() {
println!(
" v{}: ({}, {}, {}) bulge={}",
vi, v.location.x, v.location.y, v.location.z, v.bulge
);
}
println!();
}
}
EntityType::Polyline3D(p) => {
p3d += 1;
if p3d <= 5 {
println!(
"[{}] Polyline3D handle={:?} layer={:?}",
i, p.common.handle, p.common.layer
);
println!(" closed={} verts={}", p.is_closed(), p.vertices.len());
for (vi, v) in p.vertices.iter().enumerate() {
println!(
" v{}: ({}, {}, {})",
vi, v.position.x, v.position.y, v.position.z
);
}
println!();
}
}
EntityType::Polyline(p) => {
pl += 1;
if pl <= 5 {
println!(
"[{}] Polyline (heavy 3D) handle={:?} layer={:?}",
i, p.common.handle, p.common.layer
);
println!(" closed={} verts={}", p.flags.is_closed(), p.vertices.len());
for (vi, v) in p.vertices.iter().enumerate() {
println!(
" v{}: ({}, {}, {})",
vi, v.location.x, v.location.y, v.location.z
);
}
println!();
}
}
_ => {}
}
}
println!(
"Totals: LwPolyline={} Polyline2D={} Polyline3D={} Polyline={}",
lwp, p2d, p3d, pl
);
Ok(())
}