fix: correct ViewCubeSnap and MSPACE orbit to write viewport entity
- Add orbit_active_viewport(), snap_active_viewport_to_angles(), active_view_rotation_mat(), active_viewport_yaw_pitch() helpers so navigation gestures modify the DXF viewport entity's view_direction instead of the paper-space camera. - Fix "already there → flip opposite" check: the previous code used rot * Vec4::W (always (0,0,0,1) for a rotation matrix); now reads (yaw, pitch) directly from camera_for_viewport() via the new helper. - MSPACE right-drag orbit early-returns after writing the viewport entity, preventing the paper-space camera from also being modified. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7e0e892bcc
commit
f176f7c80a
2 changed files with 101 additions and 9 deletions
|
|
@ -1062,9 +1062,19 @@ impl H7CAD {
|
||||||
if sel.right_dragging {
|
if sel.right_dragging {
|
||||||
if let Some(last) = sel.right_last_pos {
|
if let Some(last) = sel.right_last_pos {
|
||||||
let (dx, dy) = (p.x - last.x, p.y - last.y);
|
let (dx, dy) = (p.x - last.x, p.y - last.y);
|
||||||
self.tabs[i].scene.camera.borrow_mut().orbit(dx, dy);
|
if self.tabs[i].scene.active_viewport.is_some() {
|
||||||
}
|
// Update position before dropping the borrow.
|
||||||
sel.right_last_pos = Some(p);
|
sel.right_last_pos = Some(p);
|
||||||
|
drop(sel);
|
||||||
|
self.tabs[i].scene.orbit_active_viewport(dx, dy);
|
||||||
|
return Task::none();
|
||||||
|
} else {
|
||||||
|
self.tabs[i].scene.camera.borrow_mut().orbit(dx, dy);
|
||||||
|
sel.right_last_pos = Some(p);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sel.right_last_pos = Some(p);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1803,10 +1813,10 @@ impl H7CAD {
|
||||||
|
|
||||||
Message::ViewportClick => {
|
Message::ViewportClick => {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let cam = self.tabs[i].scene.camera.borrow();
|
let rot = self.tabs[i].scene.active_view_rotation_mat();
|
||||||
let (vw, vh) = self.tabs[i].scene.selection.borrow().vp_size;
|
let (vw, vh) = self.tabs[i].scene.selection.borrow().vp_size;
|
||||||
if let Some(region) = scene::hit_test(
|
if let Some(region) = scene::hit_test(
|
||||||
self.cursor_pos.x, self.cursor_pos.y, vw, vh, cam.view_rotation_mat(), VIEWCUBE_PX,
|
self.cursor_pos.x, self.cursor_pos.y, vw, vh, rot, VIEWCUBE_PX,
|
||||||
) {
|
) {
|
||||||
return Task::done(Message::ViewCubeSnap(region));
|
return Task::done(Message::ViewCubeSnap(region));
|
||||||
}
|
}
|
||||||
|
|
@ -1821,15 +1831,31 @@ impl H7CAD {
|
||||||
Message::ViewCubeSnap(region) => {
|
Message::ViewCubeSnap(region) => {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let mut region = region;
|
let mut region = region;
|
||||||
{
|
let (yaw, pitch) = {
|
||||||
let mut cam = self.tabs[i].scene.camera.borrow_mut();
|
|
||||||
let (target_yaw, target_pitch) = region.snap_angles();
|
let (target_yaw, target_pitch) = region.snap_angles();
|
||||||
if angle_close(cam.yaw, target_yaw, 0.01)
|
// Check current orientation to detect "already there → flip to opposite".
|
||||||
&& angle_close(cam.pitch, target_pitch, 0.01)
|
let already_there = if let Some((cur_yaw, cur_pitch)) =
|
||||||
|
self.tabs[i].scene.active_viewport_yaw_pitch()
|
||||||
{
|
{
|
||||||
|
angle_close(cur_yaw, target_yaw, 0.01)
|
||||||
|
&& angle_close(cur_pitch, target_pitch, 0.01)
|
||||||
|
} else if self.tabs[i].scene.active_viewport.is_some() {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
let cam = self.tabs[i].scene.camera.borrow();
|
||||||
|
angle_close(cam.yaw, target_yaw, 0.01)
|
||||||
|
&& angle_close(cam.pitch, target_pitch, 0.01)
|
||||||
|
};
|
||||||
|
if already_there {
|
||||||
region = region.opposite();
|
region = region.opposite();
|
||||||
}
|
}
|
||||||
let (yaw, pitch) = region.snap_angles();
|
region.snap_angles()
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.tabs[i].scene.active_viewport.is_some() {
|
||||||
|
self.tabs[i].scene.snap_active_viewport_to_angles(yaw, pitch);
|
||||||
|
} else {
|
||||||
|
let mut cam = self.tabs[i].scene.camera.borrow_mut();
|
||||||
cam.snap_to_angles(yaw, pitch);
|
cam.snap_to_angles(yaw, pitch);
|
||||||
}
|
}
|
||||||
self.tabs[i].scene.camera_generation += 1;
|
self.tabs[i].scene.camera_generation += 1;
|
||||||
|
|
|
||||||
|
|
@ -976,6 +976,72 @@ impl Scene {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Orbit the active viewport's view direction by the given screen-pixel delta.
|
||||||
|
/// No-op when there is no active viewport or it is locked.
|
||||||
|
pub fn orbit_active_viewport(&mut self, delta_x: f32, delta_y: f32) {
|
||||||
|
let vp_handle = match self.active_viewport {
|
||||||
|
Some(h) => h,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
let mut cam = match self.camera_for_viewport(vp_handle) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
cam.orbit(delta_x, delta_y);
|
||||||
|
// Write the new view direction back to the viewport entity.
|
||||||
|
let eye = cam.rotation * glam::Vec3::Z;
|
||||||
|
if let Some(acadrust::EntityType::Viewport(vp)) =
|
||||||
|
self.document.get_entity_mut(vp_handle)
|
||||||
|
{
|
||||||
|
if vp.status.locked {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vp.view_direction.x = eye.x as f64;
|
||||||
|
vp.view_direction.y = eye.y as f64;
|
||||||
|
vp.view_direction.z = eye.z as f64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Snap the active viewport's view direction to a canonical yaw/pitch.
|
||||||
|
/// No-op when there is no active viewport or it is locked.
|
||||||
|
pub fn snap_active_viewport_to_angles(&mut self, yaw: f32, pitch: f32) {
|
||||||
|
let vp_handle = match self.active_viewport {
|
||||||
|
Some(h) => h,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
let cos_p = pitch.cos();
|
||||||
|
let eye = glam::Vec3::new(cos_p * yaw.sin(), cos_p * yaw.cos(), pitch.sin());
|
||||||
|
if let Some(acadrust::EntityType::Viewport(vp)) =
|
||||||
|
self.document.get_entity_mut(vp_handle)
|
||||||
|
{
|
||||||
|
if vp.status.locked {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
vp.view_direction.x = eye.x as f64;
|
||||||
|
vp.view_direction.y = eye.y as f64;
|
||||||
|
vp.view_direction.z = eye.z as f64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// View-rotation matrix for the active viewport (MSPACE), or the
|
||||||
|
/// paper-space camera's matrix when not in MSPACE.
|
||||||
|
/// Used by ViewCube hit-testing so clicks map to the correct camera.
|
||||||
|
pub fn active_view_rotation_mat(&self) -> glam::Mat4 {
|
||||||
|
if let Some(h) = self.active_viewport {
|
||||||
|
if let Some(cam) = self.camera_for_viewport(h) {
|
||||||
|
return cam.view_rotation_mat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.camera.borrow().view_rotation_mat()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return (yaw, pitch) of the active viewport's camera, or None if PSPACE.
|
||||||
|
pub fn active_viewport_yaw_pitch(&self) -> Option<(f32, f32)> {
|
||||||
|
let h = self.active_viewport?;
|
||||||
|
let cam = self.camera_for_viewport(h)?;
|
||||||
|
Some((cam.yaw, cam.pitch))
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the handle of the user viewport whose bounding rectangle contains
|
/// Return the handle of the user viewport whose bounding rectangle contains
|
||||||
/// the given paper-space point, or `None` if no viewport matches.
|
/// the given paper-space point, or `None` if no viewport matches.
|
||||||
pub fn viewport_at_paper_point(&self, px: f32, py: f32) -> Option<Handle> {
|
pub fn viewport_at_paper_point(&self, px: f32, py: f32) -> Option<Handle> {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue