From 8b9049c60455c4491266d2fa6f11f6b3feeb114e Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Tue, 6 Oct 2026 09:29:22 +0300 Subject: [PATCH] fix(view): keep named views of earlier versions at their place, in f64 A VIEW record's centre is an offset from its target in view coordinates. Views saved by earlier versions stored the target itself as the centre; treating that as an offset put them (and VIEW plots) at about twice the coordinates. One shared `named_view_center` now resolves the centre in f64 (DVec3/DQuat), taking a centre equal to a non-zero target as the old convention; VIEW RESTORE, the view plot window and Place on Sheet use it. Co-Authored-By: Claude Opus 5.5 --- src/app/commands/sheet_set.rs | 5 ++--- src/app/update/file.rs | 4 ++-- src/scene/camera_ops.rs | 23 +++++++++++++++++++---- src/scene/mod.rs | 1 + 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/app/commands/sheet_set.rs b/src/app/commands/sheet_set.rs index f7a32ac1d..9061fff77 100644 --- a/src/app/commands/sheet_set.rs +++ b/src/app/commands/sheet_set.rs @@ -247,14 +247,13 @@ struct ModelView { } /// Named view `view` of a model drawing, or its extents. -// ponytail: plan views only (centre = target + view centre); a twisted or 3D -// view would need the full DCS transform. fn model_view(doc: &codec::CadDocument, view: Option<&str>) -> Option { use codec::types::Vector3; if let Some(name) = view { let v = doc.views.iter().find(|v| !v.paper_space && v.name.eq_ignore_ascii_case(name))?; + let c = crate::scene::named_view_center(v); return Some(ModelView { - center: Vector3::new(v.target.x + v.center.x, v.target.y + v.center.y, v.target.z), + center: Vector3::new(c.x, c.y, c.z), width: v.width.abs().max(1e-6), height: v.height.abs().max(1e-6), target: v.target.clone(), diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 973a7bed5..dd67b3dfe 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -5519,8 +5519,8 @@ impl OpenCADStudio { let half_w = view.width.abs() * 0.5; let half_h = view.height.abs() * 0.5; (half_w > 1e-9 && half_h > 1e-9).then_some(())?; - // The view centre is relative to its target (plan views). - let (cx, cy) = (view.target.x + view.center.x, view.target.y + view.center.y); + let center = crate::scene::named_view_center(view); + let (cx, cy) = (center.x, center.y); self.area_plot_job((cx - half_w, cy - half_h, cx + half_w, cy + half_h)) } diff --git a/src/scene/camera_ops.rs b/src/scene/camera_ops.rs index b216219be..679947034 100644 --- a/src/scene/camera_ops.rs +++ b/src/scene/camera_ops.rs @@ -166,6 +166,24 @@ fn is_active_vport_name(name: &str) -> bool { name.eq_ignore_ascii_case("*Active") } +/// The world point a named view is centred on. The VIEW record's centre is +/// in view (DCS) coordinates, an offset from its target in the view plane. +/// Views saved by earlier versions of this application stored the target +/// itself as the centre; that centre (equal to a non-zero target) is taken +/// as the target, not as an offset from it. +pub fn named_view_center(view: &codec::tables::View) -> glam::DVec3 { + use glam::{DQuat, DVec3}; + let target = DVec3::new(view.target.x, view.target.y, view.target.z); + let (cx, cy) = (view.center.x, view.center.y); + let tol = 1e-9 * target.length().max(1.0); + let legacy = (target.x != 0.0 || target.y != 0.0) && (cx - target.x).abs() <= tol && (cy - target.y).abs() <= tol; + if legacy { + return target; + } + let eye = DVec3::new(view.direction.x, view.direction.y, view.direction.z).try_normalize().unwrap_or(DVec3::Z); + target + DQuat::from_rotation_arc(DVec3::Z, eye) * DVec3::new(cx, cy, 0.0) +} + impl Scene { /// A geometry mutation makes every fitted Model camera AABB stale. Clear /// both the live camera and inactive tile snapshots immediately so no view @@ -189,7 +207,6 @@ impl Scene { use glam::Vec3; let cam = &mut *self.camera.borrow_mut(); // The stored direction points from the target toward the eye. - cam.target = glam::DVec3::new(view.target.x, view.target.y, view.target.z); let eye_dir = Vec3::new( view.direction.x as f32, view.direction.y as f32, @@ -202,9 +219,7 @@ impl Scene { }; // Build rotation: canonical eye is +Z, rotate to eye_dir. cam.rotation = glam::Quat::from_rotation_arc(Vec3::Z, eye_dir); - // The view centre is an offset in view coordinates from the target. - let offset = cam.rotation * Vec3::new(view.center.x as f32, view.center.y as f32, 0.0); - cam.target += glam::DVec3::new(offset.x as f64, offset.y as f64, offset.z as f64); + cam.target = named_view_center(view); // Sync yaw/pitch from new rotation (for ViewCube). let pitch = eye_dir.z.clamp(-1.0, 1.0).asin(); let yaw = eye_dir.x.atan2(eye_dir.y); diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 67335f4bb..aac51adc1 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -37,6 +37,7 @@ pub use page_setup::{apply_default_page_setup, document_page_setups, rotated_mar mod dwg_native_constraints; mod entity; pub use entity::CreateBlockOptions; +pub use camera_ops::named_view_center; #[cfg(test)] mod hatch_boundary; mod group_layer;