forked from 1b-agent/opencadstudio-upstream
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 <[email protected]>
127 lines
3.9 KiB
Rust
127 lines
3.9 KiB
Rust
// 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(())
|
||
}
|