cad-editor/examples/inspect_viewports.rs

126 lines
3.9 KiB
Rust
Raw Normal View History

fix(scene): viewport auto-fit fallback + f64-precision projection Three changes to make paper-space viewports render correctly on UTM-scale drawings whose viewports were saved with default view_target=(0, 0, 0) and a view_center pointing at empty WCS: 1. **Auto-fit fallback.** When the saved view's WCS rect doesn't overlap the IQR-bounded content cluster (`world_offset ± local_extent_max`), override `view_center` with `world_offset` and recompute `view_height` to fit the cluster into the paper viewport. Matches AutoCAD's silent auto-fit-on-open for stale / uninitialized viewports; without it those viewports rendered blank. 2. **f64 projection inner loop.** The previous f32 path computed `(wire_offset_rel - target_offset_rel).dot(view_right) - view_center` by subtracting values at ~5e6 magnitude (f32 ULP ~0.5 m) to land on a small paper offset. The cancellation produced centimetre-scale jitter on paper output even when the model was clean. Reconstruct the wire WCS coord, subtract the display center, and dot-project in f64; cast to f32 only at the final paper position where magnitudes are bounded by the viewport rect. 3. **examples/inspect_viewports.rs** — diagnostic that dumps every viewport's saved view fields (view_target, view_center, view_height vs paper height, custom_scale, status flags) plus drawing-level insertion_units and model-space extents. Used to verify that acadrust's DWG reader reads view_height correctly; the `view_height == vp.height` patterns observed on real files turn out to be genuine "default 1:1" file content rather than a reader bug (a round-trip test through DwgWriter / DwgReader survives all per-viewport values). Also: switch the `acadrust` patch from `HakanSeven12/acadrust@feat/ expose-blockrecord-is-loaded` (a stale branch with the abandoned is_loaded experiment) to `hakanaktt/acadrust@main`. Upstream main now carries the layout paper-dimensions and mirrored-arc fixes that this branch used to add — plus PR #20's DXF reading fixes that the old branch was missing. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 19:32:36 +03:00
// Dumps every paper-space viewport's view fields from a DWG/DXF.
// cargo run --release --example inspect_viewports -- <path>
use acadrust::entities::EntityType;
use acadrust::io::dwg::DwgReader;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let path = std::env::args().nth(1).expect("usage: inspect_viewports <file>");
let doc = DwgReader::from_file(&path)?.read()?;
println!("file: {}", path);
println!("total entities: {}", doc.entities().count());
let unit_name = match doc.header.insertion_units {
0 => "Unitless",
1 => "Inches",
2 => "Feet",
3 => "Miles",
4 => "Millimeters",
5 => "Centimeters",
6 => "Meters",
7 => "Kilometers",
_ => "Other",
};
println!(
"header.insertion_units = {} ({})",
doc.header.insertion_units, unit_name
);
println!(
"header.model_space_extents min=({:.3}, {:.3}, {:.3}) max=({:.3}, {:.3}, {:.3})",
doc.header.model_space_extents_min.x,
doc.header.model_space_extents_min.y,
doc.header.model_space_extents_min.z,
doc.header.model_space_extents_max.x,
doc.header.model_space_extents_max.y,
doc.header.model_space_extents_max.z,
);
println!();
let viewports: Vec<_> = doc
.entities()
.filter_map(|e| {
if let EntityType::Viewport(v) = e {
Some(v.clone())
} else {
None
}
})
.collect();
println!("viewports: {}\n", viewports.len());
for (i, vp) in viewports.iter().enumerate() {
let scale_from_view_height = if vp.view_height.abs() > 1e-9 {
vp.height / vp.view_height
} else {
f64::NAN
};
println!(
"[{}] id={} handle={:?}",
i, vp.id, vp.common.handle
);
println!(
" paper center=({:.3}, {:.3}) size={:.3} × {:.3}",
vp.center.x, vp.center.y, vp.width, vp.height
);
println!(
" view_target=({:.3}, {:.3}, {:.3})",
vp.view_target.x, vp.view_target.y, vp.view_target.z
);
println!(
" view_direction=({:.3}, {:.3}, {:.3})",
vp.view_direction.x, vp.view_direction.y, vp.view_direction.z
);
println!(
" view_center=({:.3}, {:.3})",
vp.view_center.x, vp.view_center.y
);
println!(
" view_height={:.6} == vp.height? {} ⇒ height/view_height = {:.6}",
vp.view_height,
(vp.view_height - vp.height).abs() < 1e-6,
scale_from_view_height
);
println!(
" custom_scale={:.6} twist={:.4} lens={:.2}",
vp.custom_scale, vp.twist_angle, vp.lens_length
);
println!(
" status: on={}, locked={}, perspective={}",
vp.status.is_on, vp.status.locked, vp.status.perspective
);
println!();
}
// Summary stats
let n_with_view_eq_height = viewports
.iter()
.filter(|v| (v.view_height - v.height).abs() < 1e-6)
.count();
let n_with_target_zero = viewports
.iter()
.filter(|v| {
v.view_target.x.abs() < 1e-9
&& v.view_target.y.abs() < 1e-9
&& v.view_target.z.abs() < 1e-9
})
.count();
let unique_view_heights: std::collections::BTreeSet<u64> = viewports
.iter()
.map(|v| v.view_height.to_bits())
.collect();
println!("─────── summary ───────");
println!(
" view_height == vp.height : {}/{}",
n_with_view_eq_height,
viewports.len()
);
println!(
" view_target == (0, 0, 0) : {}/{}",
n_with_target_zero,
viewports.len()
);
println!(
" unique view_height values : {}",
unique_view_heights.len()
);
Ok(())
}