diff --git a/src/app/properties.rs b/src/app/properties.rs index 76936d69..b1a3cbbf 100644 --- a/src/app/properties.rs +++ b/src/app/properties.rs @@ -244,7 +244,10 @@ impl H7CAD { /// Rebuild the cached selected_grips from the current entity selection. pub(super) fn refresh_selected_grips(&mut self) { let i = self.active_tab; - let wo = self.tabs[i].scene.world_offset; + let is_paper = self.tabs[i].scene.current_layout != "Model"; + // Paper-space entity coordinates are NOT offset by world_offset (same rule + // as wire tessellation in wires_for_block). Only subtract in model space. + let wo = if is_paper { [0.0f64; 3] } else { self.tabs[i].scene.world_offset }; let (new_handle, new_grips) = { let selected = self.tabs[i].scene.selected_entities(); if selected.len() == 1 { diff --git a/src/app/update.rs b/src/app/update.rs index 11f9653e..01680531 100644 --- a/src/app/update.rs +++ b/src/app/update.rs @@ -1,7 +1,7 @@ use super::{H7CAD, Message, POLY_START_DELAY_MS}; use super::helpers::{parse_coord, angle_close, ortho_constrain, polar_constrain, ucs_to_wcs, ucs_z_axis}; use crate::scene::{self, Scene, VIEWCUBE_DRAW_PX, VIEWCUBE_PAD, VIEWCUBE_PX}; -use crate::scene::grip::{find_hit_grip, GripEdit}; +use crate::scene::grip::{find_hit_grip, find_hit_grip_paper, GripEdit}; use crate::scene::object::GripApply; use crate::modules::ModuleEvent; use crate::ui::PropertiesPanel; @@ -1362,8 +1362,20 @@ impl H7CAD { if self.tabs[i].active_cmd.is_none() && !self.tabs[i].selected_grips.is_empty() { if let Some(handle) = self.tabs[i].selected_handle { - let vp_mat = self.tabs[i].scene.camera.borrow().view_proj(bounds); - let grip_hit = find_hit_grip(p, &self.tabs[i].selected_grips, vp_mat, bounds); + let is_paper = self.tabs[i].scene.current_layout != "Model"; + let grip_hit = if is_paper { + let cam = self.tabs[i].scene.camera.borrow(); + let aspect = if vh > 0.0 { vw / vh } else { 1.0 }; + let half_h = cam.ortho_size(); + let half_w = half_h * aspect; + let tx = cam.target.x; + let ty = cam.target.y; + drop(cam); + find_hit_grip_paper(p, &self.tabs[i].selected_grips, tx, ty, half_w, half_h, bounds) + } else { + let vp_mat = self.tabs[i].scene.camera.borrow().view_proj(bounds); + find_hit_grip(p, &self.tabs[i].selected_grips, vp_mat, bounds) + }; if let Some((grip_id, is_translate, world)) = grip_hit { self.tabs[i].active_grip = Some(GripEdit { handle, diff --git a/src/app/view.rs b/src/app/view.rs index 4fd33d66..9c703e8a 100644 --- a/src/app/view.rs +++ b/src/app/view.rs @@ -3,7 +3,7 @@ use super::document::DocumentTab; use super::history::history_dropdown_labels; use super::helpers::grid_plane_from_camera; use crate::scene::{VIEWCUBE_DRAW_PX, VIEWCUBE_PAD}; -use crate::scene::grip::grips_to_screen; +use crate::scene::grip::{grips_to_screen, grips_to_screen_paper}; use crate::scene::paper_canvas::PaperCanvas; use crate::scene::viewport_pane::{PaperViewportPane, ViewportPane}; use crate::ui::overlay; @@ -138,9 +138,21 @@ impl H7CAD { let bounds = iced::Rectangle { x: 0.0, y: 0.0, width: vw, height: vh, }; - let vp_mat = tab.scene.camera.borrow().view_proj(bounds); let sel_h = tab.selected_handle; - grips_to_screen(&tab.selected_grips, vp_mat, bounds) + let screen_grips = if is_paper { + let cam = tab.scene.camera.borrow(); + let aspect = if vh > 0.0 { vw / vh } else { 1.0 }; + let half_h = cam.ortho_size(); + let half_w = half_h * aspect; + let tx = cam.target.x; + let ty = cam.target.y; + drop(cam); + grips_to_screen_paper(&tab.selected_grips, tx, ty, half_w, half_h, bounds) + } else { + let vp_mat = tab.scene.camera.borrow().view_proj(bounds); + grips_to_screen(&tab.selected_grips, vp_mat, bounds) + }; + screen_grips .into_iter() .filter(|(_, screen, _, _)| { screen.x.is_finite() diff --git a/src/entities/mtext.rs b/src/entities/mtext.rs index 76131b70..1eba2e0b 100644 --- a/src/entities/mtext.rs +++ b/src/entities/mtext.rs @@ -103,15 +103,23 @@ fn to_truck(t: &MText, document: &acadrust::CadDocument) -> TruckEntity { // distance, which is 5/3 × text_height (≈ 1.667). factor = 1.0 → single // spacing, factor = 2.0 → double spacing, etc. let line_h = t.height as f32 * ls_factor * (5.0 / 3.0) * font.line_spacing; - let total_h = line_h * n_lines; + let h = t.height as f32; + // CXF glyphs sit on the baseline (y=0) and extend UP by `h` (cap height). + // Block top = line-0 baseline + h + // Block bottom = last-line baseline = line-0 baseline − (n_lines−1)·line_h + // + // v_offset is the Y of line-0 baseline relative to the insertion point, so: + // Top attachment → block top at insertion → v_offset = −h + // Bottom attachment → block bottom at insertion → v_offset = (n_lines−1)·line_h + // Middle attachment → block center at insertion → midpoint of the two above let v_offset = match t.attachment_point { - AttachmentPoint::TopLeft | AttachmentPoint::TopCenter | AttachmentPoint::TopRight => 0.0, + AttachmentPoint::TopLeft | AttachmentPoint::TopCenter | AttachmentPoint::TopRight => -h, AttachmentPoint::MiddleLeft | AttachmentPoint::MiddleCenter - | AttachmentPoint::MiddleRight => -total_h * 0.5, + | AttachmentPoint::MiddleRight => ((n_lines - 1.0) * line_h - h) * 0.5, AttachmentPoint::BottomLeft | AttachmentPoint::BottomCenter - | AttachmentPoint::BottomRight => -total_h, + | AttachmentPoint::BottomRight => (n_lines - 1.0) * line_h, }; let h_anchor = match t.attachment_point { AttachmentPoint::TopCenter diff --git a/src/scene/grip.rs b/src/scene/grip.rs index 8c1ca8f0..37b29345 100644 --- a/src/scene/grip.rs +++ b/src/scene/grip.rs @@ -50,6 +50,53 @@ pub fn grips_to_screen( .collect() } +/// Paper-space variant: project grips using the 2-D linear `to_px` transform. +/// Parameters match the `to_px` closure in `paper_canvas.rs`. +pub fn grips_to_screen_paper( + grips: &[GripDef], + tx: f32, ty: f32, + half_w: f32, half_h: f32, + bounds: Rectangle, +) -> Vec<(usize, Point, bool, GripShape)> { + grips + .iter() + .map(|g| { + let screen = Point::new( + (g.world.x - tx + half_w) / (2.0 * half_w) * bounds.width, + (ty + half_h - g.world.y) / (2.0 * half_h) * bounds.height, + ); + (g.id, screen, g.is_midpoint, g.shape) + }) + .collect() +} + +/// Paper-space hit-test variant (mirrors `find_hit_grip` but uses 2-D projection). +pub fn find_hit_grip_paper( + cursor: Point, + grips: &[GripDef], + tx: f32, ty: f32, + half_w: f32, half_h: f32, + bounds: Rectangle, +) -> Option<(usize, bool, Vec3)> { + let mut best_dist = GRIP_THRESHOLD_PX; + let mut best: Option<(usize, bool, Vec3)> = None; + + for g in grips { + let screen = Point::new( + (g.world.x - tx + half_w) / (2.0 * half_w) * bounds.width, + (ty + half_h - g.world.y) / (2.0 * half_h) * bounds.height, + ); + let dx = screen.x - cursor.x; + let dy = screen.y - cursor.y; + let d = (dx * dx + dy * dy).sqrt(); + if d < best_dist { + best_dist = d; + best = Some((g.id, g.is_midpoint, g.world)); + } + } + best +} + /// Find the closest grip within `GRIP_THRESHOLD_PX` pixels of `cursor`. /// Returns `(grip_id, is_translate, world_pos)` if found, else `None`. pub fn find_hit_grip(