From 57710eec2b31adedd07ffe4c11cfbb8b63a309bb Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Sat, 11 Jul 2026 21:26:40 +0300 Subject: [PATCH] fix(precision): keep typed coordinates exact in f64 (#311) Typed values were quantized to the f32 grid before reaching the entity (e.g. a length of 2000.8 committed as 2000.8020), worsening with magnitude. The interaction point layer was all glam::Vec3 (f32). Two-layer fix: 1. Coordinate backbone -> f64. last_point, last_cursor_world, dyn_anchor, dyn_ref become DVec3; UcsXform transforms, parse_coord, the ucs helper fns, dyn_resolve_point, the OTRACK typed-distance paths, command-line coordinate entry and the coordinate readout all compute in f64. Screen/GPU/alignment stays f32 (snap engine, render geometry, projection, UCS-icon/viewcube drawing) with casts only at those boundaries. 2. Per-command committed storage -> DVec3. on_point already received a DVec3, but many command impls narrowed it back to f32 (let pt = pt.as_vec3(); Vec fields) and re-widened at build, quantizing the committed coordinate. Migrated PLINE, SPLINE, MLINE, LENGTHEN, INSERT, 3D primitives, every dimension/leader/mleader, ray, revcloud, wipeout, attdef, table, tolerance, array center, mview, plot_window, plus the QDIM_PLACE handler and qdim_collect_points. Preview, hit-test and rubber-band paths keep f32 at the boundary. Hatch manual boundaries are left for a follow-up: HatchModel.boundary is structurally Arc> and needs a model-level widen. Co-Authored-By: Claude Opus 4.8 --- src/app/command_driver.rs | 4 +- src/app/commands/dim.rs | 38 +++++----- src/app/commands/layerprops.rs | 10 +-- src/app/document.rs | 14 ++-- src/app/helpers.rs | 100 +++++++++++++------------- src/app/mod.rs | 2 +- src/app/update/command.rs | 9 ++- src/app/update/dynamic.rs | 63 ++++++++-------- src/app/update/viewport.rs | 66 +++++++++-------- src/app/view/controls.rs | 6 +- src/app/view/mod.rs | 14 ++-- src/modules/annotate/aligned_dim.rs | 28 +++++--- src/modules/annotate/angular_dim.rs | 36 ++++++---- src/modules/annotate/diameter_dim.rs | 19 ++--- src/modules/annotate/dim_baseline.rs | 40 +++++++---- src/modules/annotate/dim_continue.rs | 38 ++++++---- src/modules/annotate/dimjogline.rs | 2 +- src/modules/annotate/dimtedit.rs | 5 +- src/modules/annotate/leader_cmd.rs | 32 ++++----- src/modules/annotate/linear_dim.rs | 41 ++++++----- src/modules/annotate/mleader_cmd.rs | 19 ++--- src/modules/annotate/mleader_edit.rs | 18 ++--- src/modules/annotate/ordinate_dim.rs | 25 ++++--- src/modules/annotate/qdim.rs | 10 +-- src/modules/annotate/radius_dim.rs | 23 +++--- src/modules/annotate/table_cmd.rs | 4 +- src/modules/annotate/tolerance_cmd.rs | 4 +- src/modules/draw/draw/attdef.rs | 4 +- src/modules/draw/draw/mline.rs | 19 +++-- src/modules/draw/draw/polyline.rs | 50 +++++++------ src/modules/draw/draw/raster_image.rs | 16 ++--- src/modules/draw/draw/ray.rs | 28 ++++---- src/modules/draw/draw/revcloud.rs | 33 +++++---- src/modules/draw/draw/spline.rs | 19 ++--- src/modules/draw/draw/wipeout.rs | 38 +++++----- src/modules/draw/modify/align.rs | 26 +++---- src/modules/draw/modify/array.rs | 91 +++++++++++------------ src/modules/draw/modify/break_cmd.rs | 96 ++++++++++++------------- src/modules/draw/modify/lengthen.rs | 37 +++++----- src/modules/draw/modify/stretch.rs | 34 +++++---- src/modules/draw/modify/torient.rs | 12 ++-- src/modules/insert/insert_block.rs | 7 +- src/modules/insert/solid3d_cmds.rs | 16 ++--- src/modules/layout/mview.rs | 29 ++++---- src/modules/model/primitive_cmd.rs | 62 ++++++++-------- src/modules/view/plot_window.rs | 10 +-- src/ui/statusbar/mod.rs | 8 +-- 47 files changed, 699 insertions(+), 606 deletions(-) diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index 39ef387da..1b5ad38ed 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -137,7 +137,7 @@ impl OpenCADStudio { }; self.last_point = Some(wcs); self.push_ucs_to_cmd(i); - let _ = self.feed_command(StepInput::Point(wcs.as_dvec3())); + let _ = self.feed_command(StepInput::Point(wcs)); } else { let _ = self.feed_command(StepInput::Text(token.to_string())); } @@ -1156,7 +1156,7 @@ impl OpenCADStudio { .scene .document .get_entity(handle) - .and_then(|e| break_entity(e, p1.as_vec3(), p2.as_vec3())); + .and_then(|e| break_entity(e, p1, p2)); match replacement { Some(frags) => { let label = self.history_label_from_active_cmd(i, "BREAK"); diff --git a/src/app/commands/dim.rs b/src/app/commands/dim.rs index 50bf761ea..d83c32053 100644 --- a/src/app/commands/dim.rs +++ b/src/app/commands/dim.rs @@ -468,7 +468,7 @@ impl OpenCADStudio { // dimension-line point and the gathered geometry now selected. Build // a continuous chain of linear dimensions across the endpoints. cmd if cmd.starts_with("QDIM_PLACE ") => { - let nums: Vec = cmd + let nums: Vec = cmd .split_whitespace() .skip(1) .filter_map(|s| s.parse().ok()) @@ -476,14 +476,14 @@ impl OpenCADStudio { if nums.len() < 3 { return Some(Task::none()); } - let place = glam::Vec3::new(nums[0], nums[1], nums[2]); + let place = glam::DVec3::new(nums[0], nums[1], nums[2]); let handles: Vec = self.tabs[i] .scene .selected_entities() .iter() .map(|(h, _)| *h) .collect(); - let mut pts: Vec = Vec::new(); + let mut pts: Vec = Vec::new(); for h in &handles { if let Some(e) = self.tabs[i].scene.document.get_entity(*h) { qdim_collect_points(e, &mut pts); @@ -498,10 +498,10 @@ impl OpenCADStudio { // span dimensions horizontally (ordered by X), else vertically. let (minx, maxx) = pts .iter() - .fold((f32::MAX, f32::MIN), |(a, b), p| (a.min(p.x), b.max(p.x))); + .fold((f64::MAX, f64::MIN), |(a, b), p| (a.min(p.x), b.max(p.x))); let (miny, maxy) = pts .iter() - .fold((f32::MAX, f32::MIN), |(a, b), p| (a.min(p.y), b.max(p.y))); + .fold((f64::MAX, f64::MIN), |(a, b), p| (a.min(p.y), b.max(p.y))); let horizontal = (maxx - minx) >= (maxy - miny); if horizontal { pts.sort_by(|a, b| a.x.total_cmp(&b.x)); @@ -516,8 +516,8 @@ impl OpenCADStudio { return Some(Task::none()); } self.push_undo_snapshot(i, "QDIM"); - let v = |p: glam::Vec3| { - acadrust::types::Vector3::new(p.x as f64, p.y as f64, p.z as f64) + let v = |p: glam::DVec3| { + acadrust::types::Vector3::new(p.x, p.y, p.z) }; let mut made = 0usize; for w in pts.windows(2) { @@ -530,9 +530,9 @@ impl OpenCADStudio { }; // Dim line passes through the picked perpendicular position. let def = if horizontal { - glam::Vec3::new((p1.x + p2.x) * 0.5, place.y, 0.0) + glam::DVec3::new((p1.x + p2.x) * 0.5, place.y, 0.0) } else { - glam::Vec3::new(place.x, (p1.y + p2.y) * 0.5, 0.0) + glam::DVec3::new(place.x, (p1.y + p2.y) * 0.5, 0.0) }; dim.definition_point = v(def); dim.base.definition_point = v(def); @@ -1124,9 +1124,9 @@ fn find_last_linear_dim( /// Collect candidate dimension endpoints from an entity for QDIM — line ends, /// polyline vertices, arc endpoints — in world space. -fn qdim_collect_points(e: &acadrust::EntityType, out: &mut Vec) { +fn qdim_collect_points(e: &acadrust::EntityType, out: &mut Vec) { use acadrust::EntityType as ET; - let p = |v: &acadrust::types::Vector3| glam::Vec3::new(v.x as f32, v.y as f32, v.z as f32); + let p = |v: &acadrust::types::Vector3| glam::DVec3::new(v.x, v.y, v.z); match e { ET::Line(l) => { out.push(p(&l.start)); @@ -1134,10 +1134,10 @@ fn qdim_collect_points(e: &acadrust::EntityType, out: &mut Vec) { } ET::LwPolyline(pl) => { for v in &pl.vertices { - out.push(glam::Vec3::new( - v.location.x as f32, - v.location.y as f32, - pl.elevation as f32, + out.push(glam::DVec3::new( + v.location.x, + v.location.y, + pl.elevation, )); } } @@ -1148,10 +1148,10 @@ fn qdim_collect_points(e: &acadrust::EntityType, out: &mut Vec) { } ET::Arc(a) => { for &ang in &[a.start_angle, a.end_angle] { - out.push(glam::Vec3::new( - (a.center.x + a.radius * ang.cos()) as f32, - (a.center.y + a.radius * ang.sin()) as f32, - a.center.z as f32, + out.push(glam::DVec3::new( + a.center.x + a.radius * ang.cos(), + a.center.y + a.radius * ang.sin(), + a.center.z, )); } } diff --git a/src/app/commands/layerprops.rs b/src/app/commands/layerprops.rs index 7fb1abd00..ba2b7c5e2 100644 --- a/src/app/commands/layerprops.rs +++ b/src/app/commands/layerprops.rs @@ -282,13 +282,9 @@ impl OpenCADStudio { let current = self.tabs[i].active_ucs.as_ref(); let origin = current .map(|u| { - glam::Vec3::new( - u.origin.x as f32, - u.origin.y as f32, - u.origin.z as f32, - ) + glam::DVec3::new(u.origin.x, u.origin.y, u.origin.z) }) - .unwrap_or(glam::Vec3::ZERO); + .unwrap_or(glam::DVec3::ZERO); let mut new_ucs = ucs_rotated_z(origin, rad); // If already had axes, compose rotation on top if let Some(ref ucs) = self.tabs[i].active_ucs { @@ -302,7 +298,7 @@ impl OpenCADStudio { ucs.y_axis.y as f32, ucs.y_axis.z as f32, ); - let z_ax = ucs_z_axis(ucs); + let z_ax = ucs_z_axis(ucs).as_vec3(); let rot = glam::Quat::from_axis_angle(z_ax, rad); let nx = rot * old_x; let ny = rot * old_y; diff --git a/src/app/document.rs b/src/app/document.rs index 466270dac..9f01a6a60 100644 --- a/src/app/document.rs +++ b/src/app/document.rs @@ -169,7 +169,7 @@ pub(super) struct DocumentTab { pub(super) wireframe: bool, pub(super) render_mode: acadrust::entities::ViewportRenderMode, pub(super) visual_style: String, - pub(super) last_cursor_world: glam::Vec3, + pub(super) last_cursor_world: glam::DVec3, pub(super) last_cursor_screen: iced::Point, /// Base point (`App::last_point`) projected to viewport pixels, refreshed /// on cursor move. Lets the dynamic-input overlay place the distance label @@ -185,10 +185,10 @@ pub(super) struct DocumentTab { pub(super) dyn_guide: crate::command::DynGuide, /// World-space anchor the current step's values are measured from. `None` /// falls back to `App::last_point`. - pub(super) dyn_anchor: Option, + pub(super) dyn_anchor: Option, /// Far end of a reference line through `dyn_anchor` (for the `Perp` guide: /// the base edge / major axis the offset is measured square to). - pub(super) dyn_ref: Option, + pub(super) dyn_ref: Option, /// `dyn_ref` projected to viewport pixels. pub(super) dyn_ref_screen: Option, /// Index of the field that TAB has focused (the one keystrokes edit). @@ -360,9 +360,9 @@ impl DocumentTab { let (o, x, y, z) = self.ucs_xform().axes(); let origin = glam::Vec3::new(o.x as f32, o.y as f32, o.z as f32); glam::Mat4::from_cols( - x.extend(0.0), - y.extend(0.0), - z.extend(0.0), + x.as_vec3().extend(0.0), + y.as_vec3().extend(0.0), + z.as_vec3().extend(0.0), origin.extend(1.0), ) } @@ -421,7 +421,7 @@ impl DocumentTab { wireframe: false, render_mode: acadrust::entities::ViewportRenderMode::Wireframe2D, visual_style: "Wireframe 2D".into(), - last_cursor_world: glam::Vec3::ZERO, + last_cursor_world: glam::DVec3::ZERO, last_cursor_screen: iced::Point::ORIGIN, last_point_screen: None, dyn_fields: Vec::new(), diff --git a/src/app/helpers.rs b/src/app/helpers.rs index 1be547dda..7cca42db9 100644 --- a/src/app/helpers.rs +++ b/src/app/helpers.rs @@ -20,7 +20,7 @@ pub(super) enum CoordKind { /// "x,y,z" → Vec3(x, y, z) /// A leading `@` marks the value relative to the last point; a leading /// `#` forces absolute. Separators: comma or semicolon. -pub(super) fn parse_coord(text: &str) -> Option<(glam::Vec3, CoordKind)> { +pub(super) fn parse_coord(text: &str) -> Option<(glam::DVec3, CoordKind)> { let trimmed = text.trim(); let (kind, rest) = if let Some(r) = trimmed.strip_prefix('@') { (CoordKind::Relative, r) @@ -29,14 +29,14 @@ pub(super) fn parse_coord(text: &str) -> Option<(glam::Vec3, CoordKind)> { } else { (CoordKind::Default, trimmed) }; - let parts: Vec = rest + let parts: Vec = rest .split(|c| c == ',' || c == ';') .map(|s| s.trim()) .filter_map(|s| s.parse().ok()) .collect(); match parts.as_slice() { - [x, y] => Some((glam::Vec3::new(*x, *y, 0.0), kind)), - [x, y, z] => Some((glam::Vec3::new(*x, *y, *z), kind)), + [x, y] => Some((glam::DVec3::new(*x, *y, 0.0), kind)), + [x, y, z] => Some((glam::DVec3::new(*x, *y, *z), kind)), _ => None, } } @@ -54,29 +54,28 @@ pub(super) fn parse_coord(text: &str) -> Option<(glam::Vec3, CoordKind)> { /// is just the transpose (the dot products in `to_ucs`); no matrix inversion. #[derive(Clone, Copy)] pub(super) struct UcsXform { - origin: glam::Vec3, - x: glam::Vec3, - y: glam::Vec3, - z: glam::Vec3, + origin: glam::DVec3, + x: glam::DVec3, + y: glam::DVec3, + z: glam::DVec3, } impl UcsXform { /// Plain WCS — no active UCS. pub(super) fn identity() -> Self { Self { - origin: glam::Vec3::ZERO, - x: glam::Vec3::X, - y: glam::Vec3::Y, - z: glam::Vec3::Z, + origin: glam::DVec3::ZERO, + x: glam::DVec3::X, + y: glam::DVec3::Y, + z: glam::DVec3::Z, } } pub(super) fn from_ucs(ucs: &Ucs) -> Self { - let v = - |a: acadrust::types::Vector3| glam::Vec3::new(a.x as f32, a.y as f32, a.z as f32); - let x = v(ucs.x_axis).normalize_or(glam::Vec3::X); - let y = v(ucs.y_axis).normalize_or(glam::Vec3::Y); - let z = x.cross(y).normalize_or(glam::Vec3::Z); + let v = |a: acadrust::types::Vector3| glam::DVec3::new(a.x, a.y, a.z); + let x = v(ucs.x_axis).normalize_or(glam::DVec3::X); + let y = v(ucs.y_axis).normalize_or(glam::DVec3::Y); + let z = x.cross(y).normalize_or(glam::DVec3::Z); Self { origin: v(ucs.origin), x, y, z } } @@ -86,45 +85,46 @@ impl UcsXform { /// True when this is plain WCS — lets callers skip the conversion. pub(super) fn is_identity(&self) -> bool { - self.origin == glam::Vec3::ZERO - && self.x == glam::Vec3::X - && self.y == glam::Vec3::Y - && self.z == glam::Vec3::Z + self.origin == glam::DVec3::ZERO + && self.x == glam::DVec3::X + && self.y == glam::DVec3::Y + && self.z == glam::DVec3::Z } /// UCS point → WCS. - pub(super) fn to_wcs(&self, p: glam::Vec3) -> glam::Vec3 { + pub(super) fn to_wcs(&self, p: glam::DVec3) -> glam::DVec3 { self.origin + self.x * p.x + self.y * p.y + self.z * p.z } /// WCS point → UCS. - pub(super) fn to_ucs(&self, p: glam::Vec3) -> glam::Vec3 { + pub(super) fn to_ucs(&self, p: glam::DVec3) -> glam::DVec3 { let d = p - self.origin; - glam::Vec3::new(d.dot(self.x), d.dot(self.y), d.dot(self.z)) + glam::DVec3::new(d.dot(self.x), d.dot(self.y), d.dot(self.z)) } /// UCS direction → WCS (rotation only, no origin shift). - pub(super) fn vec_to_wcs(&self, v: glam::Vec3) -> glam::Vec3 { + pub(super) fn vec_to_wcs(&self, v: glam::DVec3) -> glam::DVec3 { self.x * v.x + self.y * v.y + self.z * v.z } /// WCS direction → UCS (rotation only, no origin shift). - pub(super) fn vec_to_ucs(&self, v: glam::Vec3) -> glam::Vec3 { - glam::Vec3::new(v.dot(self.x), v.dot(self.y), v.dot(self.z)) + pub(super) fn vec_to_ucs(&self, v: glam::DVec3) -> glam::DVec3 { + glam::DVec3::new(v.dot(self.x), v.dot(self.y), v.dot(self.z)) } /// `(origin, x, y, z)` axes in WCS — for drawing the UCS icon. - pub(super) fn axes(&self) -> (glam::Vec3, glam::Vec3, glam::Vec3, glam::Vec3) { + pub(super) fn axes(&self) -> (glam::DVec3, glam::DVec3, glam::DVec3, glam::DVec3) { (self.origin, self.x, self.y, self.z) } /// UCS→world rotation matrix (columns = UCS axes). For consumers that take - /// a `Mat4` rotation directly (ViewCube, OTRACK ray directions). + /// a `Mat4` rotation directly (ViewCube, OTRACK ray directions). The GPU / + /// screen layer is f32, so the axes downcast here at the boundary. pub(super) fn rotation_mat(&self) -> glam::Mat4 { glam::Mat4::from_cols( - self.x.extend(0.0), - self.y.extend(0.0), - self.z.extend(0.0), + self.x.as_vec3().extend(0.0), + self.y.as_vec3().extend(0.0), + self.z.as_vec3().extend(0.0), glam::Vec4::W, ) } @@ -135,26 +135,26 @@ impl UcsXform { /// Rotate a UCS-local offset into WCS without applying the origin /// translation — used for relative coordinate entry, where only the /// axis orientation matters, not the UCS origin. -pub(super) fn ucs_rotate_vec(offset: glam::Vec3, ucs: &Ucs) -> glam::Vec3 { +pub(super) fn ucs_rotate_vec(offset: glam::DVec3, ucs: &Ucs) -> glam::DVec3 { UcsXform::from_ucs(ucs).vec_to_wcs(offset) } /// Convert a point from UCS local coordinates to WCS. -pub(super) fn ucs_to_wcs(pt: glam::Vec3, ucs: &Ucs) -> glam::Vec3 { +pub(super) fn ucs_to_wcs(pt: glam::DVec3, ucs: &Ucs) -> glam::DVec3 { UcsXform::from_ucs(ucs).to_wcs(pt) } /// Return the normalised Z axis of a UCS (cross product of X and Y axes). -pub(super) fn ucs_z_axis(ucs: &Ucs) -> glam::Vec3 { +pub(super) fn ucs_z_axis(ucs: &Ucs) -> glam::DVec3 { UcsXform::from_ucs(ucs).axes().3 } /// Build a UCS with `origin` and axes rotated by `angle_z_rad` around the Z axis. -pub(super) fn ucs_rotated_z(origin: glam::Vec3, angle_z: f32) -> Ucs { +pub(super) fn ucs_rotated_z(origin: glam::DVec3, angle_z: f32) -> Ucs { let cos = angle_z.cos() as f64; let sin = angle_z.sin() as f64; let mut ucs = Ucs::new("*ACTIVE*"); - ucs.origin = acadrust::types::Vector3::new(origin.x as f64, origin.y as f64, origin.z as f64); + ucs.origin = acadrust::types::Vector3::new(origin.x, origin.y, origin.z); ucs.x_axis = acadrust::types::Vector3::new(cos, sin, 0.0); ucs.y_axis = acadrust::types::Vector3::new(-sin, cos, 0.0); ucs @@ -181,15 +181,15 @@ pub(super) fn grid_plane_from_camera(pitch: f32, yaw: f32) -> GridPlane { /// Constrain `pt` to the nearest 90° direction from `base`, in the active UCS /// plane — ortho follows the user's coordinate system, not world axes. `xf` is /// identity for plain WCS, so the world-XY behaviour is unchanged there. -pub(super) fn ortho_constrain(pt: glam::Vec3, base: glam::Vec3, xf: &UcsXform) -> glam::Vec3 { +pub(super) fn ortho_constrain(pt: glam::DVec3, base: glam::DVec3, xf: &UcsXform) -> glam::DVec3 { let p = xf.to_ucs(pt); let b = xf.to_ucs(base); let dx = (p.x - b.x).abs(); let dy = (p.y - b.y).abs(); let c = if dx >= dy { - glam::Vec3::new(p.x, b.y, p.z) + glam::DVec3::new(p.x, b.y, p.z) } else { - glam::Vec3::new(b.x, p.y, p.z) + glam::DVec3::new(b.x, p.y, p.z) }; xf.to_wcs(c) } @@ -197,11 +197,11 @@ pub(super) fn ortho_constrain(pt: glam::Vec3, base: glam::Vec3, xf: &UcsXform) - /// Constrain `pt` to the nearest polar angle multiple from `base`, measured in /// the active UCS plane (identity `xf` = world XY, Z-up). pub(super) fn polar_constrain( - pt: glam::Vec3, - base: glam::Vec3, + pt: glam::DVec3, + base: glam::DVec3, step_deg: f32, xf: &UcsXform, -) -> glam::Vec3 { +) -> glam::DVec3 { let p = xf.to_ucs(pt); let b = xf.to_ucs(base); let dx = p.x - b.x; @@ -210,10 +210,10 @@ pub(super) fn polar_constrain( if dist < 1e-6 { return pt; } - let step = step_deg.to_radians(); + let step = (step_deg as f64).to_radians(); let angle = dy.atan2(dx); let snapped = (angle / step).round() * step; - xf.to_wcs(glam::Vec3::new( + xf.to_wcs(glam::DVec3::new( b.x + dist * snapped.cos(), b.y + dist * snapped.sin(), p.z, @@ -224,18 +224,18 @@ pub(super) fn polar_constrain( /// screen pixels of the nearest polar ray; otherwise the cursor is left free /// so POLAR behaves as if off when pointing away from every angle (issue #70). pub(super) fn polar_constrain_near( - pt: glam::Vec3, - base: glam::Vec3, + pt: glam::DVec3, + base: glam::DVec3, step_deg: f32, view_rot: glam::Mat4, eye: glam::DVec3, bounds: iced::Rectangle, tol_px: f32, xf: &UcsXform, -) -> glam::Vec3 { +) -> glam::DVec3 { let snapped = polar_constrain(pt, base, step_deg, xf); - let to_screen = |w: glam::Vec3| { - let ndc = view_rot.project_point3((w.as_dvec3() - eye).as_vec3()); + let to_screen = |w: glam::DVec3| { + let ndc = view_rot.project_point3((w - eye).as_vec3()); ( (ndc.x + 1.0) * 0.5 * bounds.width, (1.0 - ndc.y) * 0.5 * bounds.height, diff --git a/src/app/mod.rs b/src/app/mod.rs index 2619c158c..87384bc7c 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -416,7 +416,7 @@ pub(super) struct OpenCADStudio { /// Whether the layout/paper-space tabs are shown at the bottom (LAYOUTTAB). show_layout_tabs: bool, /// Last point committed by a drawing command — used as ortho/polar base. - last_point: Option, + last_point: Option, /// Endpoint + unit exit-tangent of the most recently drawn line/arc, so /// `ARC_CONT` (Arc → Continue) can start tangentially from where drawing /// ended. `None` once a non-line/arc entity is committed. diff --git a/src/app/update/command.rs b/src/app/update/command.rs index 9da6c422f..87c852549 100644 --- a/src/app/update/command.rs +++ b/src/app/update/command.rs @@ -283,13 +283,16 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task { // (issue #69). if let Some((base, dir)) = self.otrack_active { if let Some(dist) = crate::app::expr_eval::eval_number(text.trim()) { - let pt = base + dir * dist as f32; + // Typed distance along the tracking ray: keep the + // magnitude exact in f64 (ray origin/dir are the + // screen-space snapper's f32). + let pt = base.as_dvec3() + dir.as_dvec3() * dist; self.last_point = Some(pt); self.dyn_user_reshaped = false; self.sync_dyn_fields(); self.reset_tracking_after_point(); self.push_ucs_to_cmd(i); - let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt.as_dvec3())); + let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt)); if let Some(r) = result { let task = self.apply_cmd_result(r); self.refresh_active_cmd_preview(i); @@ -331,7 +334,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task { self.sync_dyn_fields(); self.reset_tracking_after_point(); self.push_ucs_to_cmd(i); - let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(wcs_pt.as_dvec3())); + let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(wcs_pt)); if let Some(r) = result { let task = self.apply_cmd_result(r); // The rubber-band preview that the command diff --git a/src/app/update/dynamic.rs b/src/app/update/dynamic.rs index abc157aed..6f1919381 100644 --- a/src/app/update/dynamic.rs +++ b/src/app/update/dynamic.rs @@ -171,9 +171,9 @@ impl OpenCADStudio { self.tabs[i].dyn_guide = spec.guide; self.tabs[i].dyn_anchor = match spec.anchor { crate::command::DynAnchor::LastPoint => self.last_point, - crate::command::DynAnchor::Point(p) => Some(p.as_vec3()), + crate::command::DynAnchor::Point(p) => Some(p), }; - self.tabs[i].dyn_ref = spec.ref_point.map(|v| v.as_vec3()); + self.tabs[i].dyn_ref = spec.ref_point; } /// Track cursor dwell over a selected entity's grip. Sets @@ -212,13 +212,11 @@ impl OpenCADStudio { /// `None` for any other entity kind. Uses `last_point` (the final pick) to /// pick the endpoint the pen actually finished on. pub(in crate::app) fn update_cont_anchor(&mut self, entity: &acadrust::EntityType) { - let last = self - .last_point - .map(|v| glam::DVec3::new(v.x as f64, v.y as f64, v.z as f64)); + let last = self.last_point; self.cont_anchor = crate::modules::draw::draw::arc::continue_anchor(entity, last); } - pub(in crate::app) fn dyn_resolve_point(&self) -> Option { + pub(in crate::app) fn dyn_resolve_point(&self) -> Option { use crate::app::document::DynComponent; let i = self.active_tab; let fields = &self.tabs[i].dyn_fields; @@ -229,7 +227,7 @@ impl OpenCADStudio { let base = self.tabs[i] .dyn_anchor .or(self.last_point) - .unwrap_or(glam::Vec3::ZERO); + .unwrap_or(glam::DVec3::ZERO); // Buffer value parsed as f32 (de-scaled by the role so a typed diameter // becomes a radius), or the supplied geometric live value. Width/Height // are shown unsigned, so a typed value normally takes the sign of the @@ -241,13 +239,13 @@ impl OpenCADStudio { // `dyn_user_reshaped`, so it cleanly distinguishes comma-cartesian entry // from Tab-separated dimension entry (#269). let comma_cartesian = self.dyn_user_reshaped; - let val = |idx: usize, live: f32| -> f32 { + let val = |idx: usize, live: f64| -> f64 { match fields[idx] .buffer .as_ref() .map(|s| s.trim().replace(',', ".")) - .and_then(|s| crate::app::expr_eval::eval_number(&s).map(|v| v as f32)) - .map(|v| v / fields[idx].role.value_scale()) + .and_then(|s| crate::app::expr_eval::eval_number(&s)) + .map(|v| v / fields[idx].role.value_scale() as f64) { Some(v) => { if matches!( @@ -277,12 +275,12 @@ impl OpenCADStudio { // A typed angle is shown unsigned (0..180); give it the sign of the // cursor's current side so an entry made below the X axis sweeps // downward to match the arc instead of mirroring up. Untyped → live. - let angle_rad = |idx: usize| -> f32 { + let angle_rad = |idx: usize| -> f64 { match fields[idx] .buffer .as_ref() .map(|s| s.trim().replace(',', ".")) - .and_then(|s| crate::app::expr_eval::eval_number(&s).map(|v| v as f32)) + .and_then(|s| crate::app::expr_eval::eval_number(&s)) { Some(mag) => mag.abs().to_radians().copysign(dy), None => live_a, @@ -298,13 +296,13 @@ impl OpenCADStudio { (self.tabs[i].dyn_ref, comps.as_slice()) { let axis = (ref_pt - base).normalize_or_zero(); - let perp = glam::Vec3::new(-axis.y, axis.x, 0.0); + let perp = glam::DVec3::new(-axis.y, axis.x, 0.0); let signed = (w - base).dot(perp); let typed = fields[0] .buffer .as_ref() .map(|s| s.trim().replace(',', ".")) - .and_then(|s| crate::app::expr_eval::eval_number(&s).map(|v| v as f32)); + .and_then(|s| crate::app::expr_eval::eval_number(&s)); let h = match typed { Some(v) => v.abs().copysign(signed), None => signed, @@ -316,28 +314,28 @@ impl OpenCADStudio { // position relative to base; typed values are relative deltas. let has_base = self.last_point.is_some(); // Relative result: base + the typed UCS-frame offset mapped to world. - let rel = |off_ucs: glam::Vec3| base + xf.vec_to_wcs(off_ucs); + let rel = |off_ucs: glam::DVec3| base + xf.vec_to_wcs(off_ucs); match comps.as_slice() { [DynComponent::X, DynComponent::Y] if has_base => { - Some(rel(glam::Vec3::new(val(0, dx), val(1, dy), 0.0))) + Some(rel(glam::DVec3::new(val(0, dx), val(1, dy), 0.0))) } [DynComponent::X, DynComponent::Y] => { - Some(glam::Vec3::new(val(0, w.x), val(1, w.y), base.z)) + Some(glam::DVec3::new(val(0, w.x), val(1, w.y), base.z)) } [DynComponent::X, DynComponent::Y, DynComponent::Z] if has_base => { - Some(rel(glam::Vec3::new(val(0, dx), val(1, dy), val(2, dz)))) + Some(rel(glam::DVec3::new(val(0, dx), val(1, dy), val(2, dz)))) } [DynComponent::X, DynComponent::Y, DynComponent::Z] => { - Some(glam::Vec3::new(val(0, w.x), val(1, w.y), val(2, base.z))) + Some(glam::DVec3::new(val(0, w.x), val(1, w.y), val(2, base.z))) } [DynComponent::Distance, DynComponent::Angle] => { let d = val(0, live_d); let a = angle_rad(1); - Some(rel(glam::Vec3::new(d * a.cos(), d * a.sin(), 0.0))) + Some(rel(glam::DVec3::new(d * a.cos(), d * a.sin(), 0.0))) } [DynComponent::Distance] => { // Keep the cursor's direction (in UCS), override the magnitude. - let dir = glam::Vec3::new(dx, dy, 0.0).normalize_or(glam::Vec3::X); + let dir = glam::DVec3::new(dx, dy, 0.0).normalize_or(glam::DVec3::X); Some(rel(dir * val(0, live_d))) } [DynComponent::Angle] => { @@ -346,7 +344,7 @@ impl OpenCADStudio { // magnitude — keep it literal. Only the polar Distance+Angle // pair uses the cursor-signed `angle_rad`. let a = val(0, live_a.to_degrees()).to_radians(); - Some(rel(glam::Vec3::new(live_d * a.cos(), live_d * a.sin(), 0.0))) + Some(rel(glam::DVec3::new(live_d * a.cos(), live_d * a.sin(), 0.0))) } _ => None, } @@ -433,7 +431,7 @@ impl OpenCADStudio { let anchor = self.tabs[i] .dyn_anchor .or(self.last_point) - .unwrap_or(glam::Vec3::ZERO); + .unwrap_or(glam::DVec3::ZERO); let cur = self.tabs[i].last_cursor_world; let a_cur = (cur.y - anchor.y).atan2(cur.x - anchor.x); let a_ref = self.tabs[i] @@ -441,11 +439,11 @@ impl OpenCADStudio { .map(|r| (r.y - anchor.y).atan2(r.x - anchor.x)) .unwrap_or(0.0); let mut d = a_cur - a_ref; - while d > std::f32::consts::PI { - d -= std::f32::consts::TAU; + while d > std::f64::consts::PI { + d -= std::f64::consts::TAU; } - while d <= -std::f32::consts::PI { - d += std::f32::consts::TAU; + while d <= -std::f64::consts::PI { + d += std::f64::consts::TAU; } if d < 0.0 { format!("-{t}") @@ -479,7 +477,10 @@ impl OpenCADStudio { .find_map(|f| f.buffer.clone()) { if let Some(dist) = crate::app::expr_eval::eval_number(text.trim()) { - let pt = base + dir * dist as f32; + // Distance is typed, so resolve in f64: the tracking ray + // origin/dir come from the screen-space snapper (f32) but + // the magnitude the user entered stays exact. + let pt = base.as_dvec3() + dir.as_dvec3() * dist; self.last_point = Some(pt); for f in self.tabs[i].dyn_fields.iter_mut() { f.buffer = None; @@ -489,7 +490,7 @@ impl OpenCADStudio { self.sync_dyn_fields(); self.reset_tracking_after_point(); self.push_ucs_to_cmd(i); - let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt.as_dvec3())); + let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt)); let task = result.map(|r| self.apply_cmd_result(r))?; self.refresh_active_cmd_preview(i); return Some(task); @@ -548,7 +549,7 @@ impl OpenCADStudio { self.sync_dyn_fields(); self.reset_tracking_after_point(); self.push_ucs_to_cmd(i); - let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt.as_dvec3())); + let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt)); for f in self.tabs[i].dyn_fields.iter_mut() { f.buffer = None; } @@ -578,7 +579,7 @@ impl OpenCADStudio { let previews = self.tabs[i] .active_cmd .as_mut() - .map(|c| c.on_preview_wires(cur.as_dvec3())) + .map(|c| c.on_preview_wires(cur)) .unwrap_or_default(); self.tabs[i].scene.set_preview_wires(previews); } diff --git a/src/app/update/viewport.rs b/src/app/update/viewport.rs index 38a80c745..70cb82f32 100644 --- a/src/app/update/viewport.rs +++ b/src/app/update/viewport.rs @@ -190,7 +190,7 @@ impl OpenCADStudio { ) }); let pick = |cam: &crate::scene::view::camera::Camera| match plane { - Some((normal, origin)) => cam.pick_on_plane(p, bounds, normal, origin), + Some((normal, origin)) => cam.pick_on_plane(p, bounds, normal.as_vec3(), origin), None => cam.pick_on_target_plane(p, bounds), }; match edit_cam { @@ -592,7 +592,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { sel.orbit_pivot = self.tabs[i] .scene .orbit_pivot() - .or(Some(self.tabs[i].last_cursor_world.as_dvec3())); + .or(Some(self.tabs[i].last_cursor_world)); } let pivot = sel.orbit_pivot; drop(sel); @@ -768,13 +768,13 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { } if snap_hit.is_none() { - let base = grip.origin_world.as_vec3(); + let base = grip.origin_world; let ucs_xf = self.tabs[i].ucs_xform(); if self.ortho_mode { - snapped = ortho_constrain(snapped.as_vec3(), base, &ucs_xf).as_dvec3(); + snapped = ortho_constrain(snapped, base, &ucs_xf); } else if self.polar_mode { snapped = polar_constrain_near( - snapped.as_vec3(), + snapped, base, self.polar_increment_deg, view_rot, @@ -782,8 +782,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { bounds, self.snapper.osnap_radius_px, &ucs_xf, - ) - .as_dvec3(); + ); } } @@ -835,7 +834,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { height: vp_size.1, }; let world = self.cursor_model_point(i, &edit_cam, p, bounds); - self.tabs[i].last_cursor_world = world.as_vec3(); + self.tabs[i].last_cursor_world = world; } // Rollover highlight: when idle (no active command, no @@ -936,7 +935,10 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { ) } else { let (go, gr) = self.tabs[i].ucs_grid_basis(); - self.snapper.from_point = self.last_point; + // The snapper is a screen-space (f32) engine; the f64 + // base only matters for typed-input precision, so hand it + // the downcast point here. + self.snapper.from_point = self.last_point.map(|p| p.as_vec3()); self.snapper .snap(snap_cursor, p, &all_wires[..], view_rot, eye, bounds, go, gr) }; @@ -980,7 +982,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { eye, bounds, step, - self.last_point, + self.last_point.map(|p| p.as_vec3()), self.ortho_mode && !is_window_corner, ucs, ) @@ -997,7 +999,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { if self.tabs[i].snap_result.is_none() && otrack_hit.is_none() { if let Some(par) = self.snapper.parallel_snap( cursor_world.as_vec3(), - self.last_point, + self.last_point.map(|p| p.as_vec3()), view_rot, eye, bounds, @@ -1005,7 +1007,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { if let (Some(base), Some((dir, _))) = (self.last_point, self.snapper.parallel_ref) { - self.otrack_active = Some((base, dir)); + self.otrack_active = Some((base.as_vec3(), dir)); } self.tabs[i].snap_result = Some(par); } @@ -1034,11 +1036,10 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { if let Some(base) = self.last_point { let ucs_xf = self.tabs[i].ucs_xform(); if self.ortho_mode { - pt = ortho_constrain(pt.as_vec3(), base, &ucs_xf) - .as_dvec3(); + pt = ortho_constrain(pt, base, &ucs_xf); } else if self.polar_mode { pt = polar_constrain_near( - pt.as_vec3(), + pt, base, self.polar_increment_deg, view_rot, @@ -1046,8 +1047,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { bounds, self.snapper.osnap_radius_px, &ucs_xf, - ) - .as_dvec3(); + ); } } } @@ -1060,7 +1060,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { } pt }; - self.tabs[i].last_cursor_world = effective.as_vec3(); + self.tabs[i].last_cursor_world = effective; self.tabs[i].last_cursor_screen = p_full; // Project the step anchor (an explicit `dyn_anchor` or the // last point) so the dynamic-input overlay can place its @@ -1069,8 +1069,8 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { // origin (`tile_b`, the viewport rect inside a viewport) so // the DYN guide/labels share the canvas frame with // `last_cursor_screen` (= p_full, canvas space). - let proj = |bp: glam::Vec3| { - let ndc = view_rot.project_point3((bp.as_dvec3() - eye).as_vec3()); + let proj = |bp: glam::DVec3| { + let ndc = view_rot.project_point3((bp - eye).as_vec3()); iced::Point::new( (ndc.x + 1.0) * 0.5 * bounds.width + tile_b.x, (1.0 - ndc.y) * 0.5 * bounds.height + tile_b.y, @@ -1096,7 +1096,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { .and_then(|c| c.window_first_corner()); self.tabs[i].scene.selection.borrow_mut().preview_box = if is_window_corner { - window_first.map(|c1| (proj(c1.as_vec3()), p_full, true)) + window_first.map(|c1| (proj(c1), p_full, true)) } else { None }; @@ -1217,6 +1217,8 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { // the snapped angle direction, extending across the drawing. if self.polar_mode && !needs_entity { if let Some(base) = self.last_point { + // Screen-space dotted guide (render geometry is f32). + let base = base.as_vec3(); let effective = effective.as_vec3(); let dx = effective.x - base.x; let dy = effective.y - base.y; @@ -1367,7 +1369,12 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { } else { None }; - crate::ui::overlay::ucs_icon_hit(cam.view_proj_rte(bounds), bounds, (ux, uy, uz), os) + crate::ui::overlay::ucs_icon_hit( + cam.view_proj_rte(bounds), + bounds, + (ux.as_vec3(), uy.as_vec3(), uz.as_vec3()), + os, + ) } /// Apply one frame of a UCS-icon grip drag: map the cursor onto the active @@ -1896,7 +1903,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { .snap_tangent_only(snap_cursor.as_vec3(), p, &all_wires[..], view_rot, eye, bounds) } else { let (go, gr) = self.tabs[i].ucs_grid_basis(); - self.snapper.from_point = self.last_point; + self.snapper.from_point = self.last_point.map(|p| p.as_vec3()); self.snapper.snap(snap_cursor, p, &all_wires[..], view_rot, eye, bounds, go, gr) }; // Snap runs in model space; the result is already model. @@ -1916,7 +1923,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { }; let ucs = self.tabs[i].scene.viewcube_ucs_mat(); self.snapper - .otrack_snap(raw.as_vec3(), view_rot, eye, bounds, step, self.last_point, self.ortho_mode && !is_window_corner, ucs) + .otrack_snap(raw.as_vec3(), view_rot, eye, bounds, step, self.last_point.map(|p| p.as_vec3()), self.ortho_mode && !is_window_corner, ucs) } else { None }; @@ -1934,10 +1941,10 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { if let Some(base) = self.last_point { let ucs_xf = self.tabs[i].ucs_xform(); if self.ortho_mode { - pt = ortho_constrain(pt.as_vec3(), base, &ucs_xf).as_dvec3(); + pt = ortho_constrain(pt, base, &ucs_xf); } else if self.polar_mode { pt = polar_constrain_near( - pt.as_vec3(), + pt, base, self.polar_increment_deg, view_rot, @@ -1945,8 +1952,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { bounds, self.snapper.osnap_radius_px, &ucs_xf, - ) - .as_dvec3(); + ); } } } @@ -2128,7 +2134,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { self.tabs[i].dyn_active = 0; } } - self.last_point = Some(world_pt.as_vec3()); + self.last_point = Some(world_pt); self.dyn_user_reshaped = false; self.sync_dyn_fields(); self.reset_tracking_after_point(); @@ -2147,7 +2153,7 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task { .as_ref() .and_then(|c| c.resolved_anchor()) { - self.last_point = Some(a.as_vec3()); + self.last_point = Some(a); } handled } else { diff --git a/src/app/view/controls.rs b/src/app/view/controls.rs index 92c73cd56..3aec2fbf0 100644 --- a/src/app/view/controls.rs +++ b/src/app/view/controls.rs @@ -191,15 +191,15 @@ pub(super) fn viewport_controls<'a>( /// world position (and the base point for polar quantities). pub(super) fn dyn_component_value( f: &DynFieldEntry, - w: glam::Vec3, - base: Option, + w: glam::DVec3, + base: Option, xf: &super::super::helpers::UcsXform, comma_cartesian: bool, ) -> String { if let Some(b) = &f.buffer { return b.clone(); } - let b = base.unwrap_or(glam::Vec3::ZERO); + let b = base.unwrap_or(glam::DVec3::ZERO); // Relative deltas and the polar angle read in the active UCS plane. The // delta is offset-invariant, so only the axis rotation matters (identity // xf reproduces the world-frame deltas). diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index 9970440cc..ef91f3e68 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -200,7 +200,7 @@ impl OpenCADStudio { let (vw, vh) = tab.scene.selection.borrow().vp_size; let model_basis = { let (o, ux, uy, uz) = tab.ucs_xform().axes(); - (o.as_dvec3(), (ux, uy, uz)) + (o, (ux.as_vec3(), uy.as_vec3(), uz.as_vec3())) }; let grid: Vec = tab .scene @@ -212,7 +212,7 @@ impl OpenCADStudio { Some(u) => { let (o, ux, uy, uz) = super::helpers::UcsXform::from_ucs(&u).axes(); - (o.as_dvec3(), (ux, uy, uz)) + (o, (ux.as_vec3(), uy.as_vec3(), uz.as_vec3())) } None => ( glam::DVec3::ZERO, @@ -345,7 +345,7 @@ impl OpenCADStudio { vec![crate::ui::overlay::UcsIconParams { view_proj: vp_cam.view_proj_rte(full), bounds: full, - axes: (ux, uy, uz), + axes: (ux.as_vec3(), uy.as_vec3(), uz.as_vec3()), origin_screen, hover: self.ucs_icon_hover, selected: self.ucs_icon_selected, @@ -378,7 +378,7 @@ impl OpenCADStudio { crate::ui::overlay::UcsIconParams { view_proj: cam.view_proj_rte(b), bounds: b, - axes: (ux, uy, uz), + axes: (ux.as_vec3(), uy.as_vec3(), uz.as_vec3()), origin_screen, hover: i == active && self.ucs_icon_hover, selected: i == active && self.ucs_icon_selected, @@ -440,7 +440,7 @@ impl OpenCADStudio { match (otrack_proj, self.otrack_active) { (Some((view_rot, eye, ob)), Some((base, _dir))) => { let b = ost_project(base, view_rot, eye, ob); - let a = ost_project(tab.last_cursor_world, view_rot, eye, ob); + let a = ost_project(tab.last_cursor_world.as_vec3(), view_rot, eye, ob); (b.x.is_finite() && a.x.is_finite()).then_some((b, a)) } _ => None, @@ -573,7 +573,7 @@ impl OpenCADStudio { // A command may drive a typed scalar by mouse (e.g. a // perpendicular distance to a picked object); show that live // value in the box until the user types over it. - let live = tab.active_cmd.as_ref().and_then(|c| c.dyn_live_value(w.as_dvec3())); + let live = tab.active_cmd.as_ref().and_then(|c| c.dyn_live_value(w)); let boxes: Vec = tab .dyn_fields .iter() @@ -1304,7 +1304,7 @@ impl OpenCADStudio { // drawing coordinates (paper space carries no offset), then // report it in the active UCS — the readout follows the // user's coordinate system, not raw WCS (no-op without UCS). - let to_readout = |p: glam::Vec3| { + let to_readout = |p: glam::DVec3| { // The readout follows the active pane's UCS — model space // or inside a floating viewport (no-op without a UCS). if tab.editing_model_space() { diff --git a/src/modules/annotate/aligned_dim.rs b/src/modules/annotate/aligned_dim.rs index 1cee532e0..ae813a356 100644 --- a/src/modules/annotate/aligned_dim.rs +++ b/src/modules/annotate/aligned_dim.rs @@ -3,7 +3,7 @@ use acadrust::entities::{Dimension, DimensionAligned}; use acadrust::types::Vector3; use acadrust::EntityType; -use glam::{DVec3, Vec3}; +use glam::DVec3; use crate::command::{CadCommand, CmdResult}; use crate::modules::{IconKind, ModuleEvent, ToolDef}; @@ -22,8 +22,8 @@ pub fn tool() -> ToolDef { enum Step { First, - Second(Vec3), - DimLine { p1: Vec3, p2: Vec3 }, + Second(DVec3), + DimLine { p1: DVec3, p2: DVec3 }, } pub struct AlignedDimensionCommand { @@ -73,7 +73,7 @@ impl CadCommand for AlignedDimensionCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { Step::First => { self.step = Step::Second(pt); @@ -174,7 +174,7 @@ impl CadCommand for AlignedDimensionCommand { } } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); + fn on_mouse_move(&mut self, pt: DVec3) -> Option { let (p1, p2) = match self.step { Step::First => return None, Step::Second(p1) => (p1, pt), @@ -182,6 +182,9 @@ impl CadCommand for AlignedDimensionCommand { return Some(preview_aligned(p1, p2, pt)); } }; + // Preview WireModel points are screen/GPU-side: downcast to f32. + let p1 = p1.as_vec3(); + let p2 = p2.as_vec3(); Some(WireModel { text_verts: Vec::new(), name: "dimaligned_preview".into(), @@ -205,8 +208,8 @@ impl CadCommand for AlignedDimensionCommand { } } -fn v3(p: Vec3) -> Vector3 { - Vector3::new(p.x as f64, p.y as f64, p.z as f64) +fn v3(p: DVec3) -> Vector3 { + Vector3::new(p.x, p.y, p.z) } /// Dimension-line endpoints: the baseline `p1`–`p2` shifted to pass through @@ -214,16 +217,21 @@ fn v3(p: Vec3) -> Vector3 { /// it matches the committed entity's renderer (and DIMLINEAR). The old /// preview used an XZ-plane perpendicular, drawing the offset in the wrong /// spatial direction. (#150) -fn dim_line_endpoints(p1: Vec3, p2: Vec3, dim_pt: Vec3) -> (Vec3, Vec3) { +fn dim_line_endpoints(p1: DVec3, p2: DVec3, dim_pt: DVec3) -> (DVec3, DVec3) { let axis = (p2 - p1).normalize_or_zero(); - let perp = Vec3::new(-axis.y, axis.x, 0.0); + let perp = DVec3::new(-axis.y, axis.x, 0.0); let offset = (dim_pt - p1).dot(perp); (p1 + perp * offset, p2 + perp * offset) } -fn preview_aligned(p1: Vec3, p2: Vec3, dim_pt: Vec3) -> WireModel { +fn preview_aligned(p1: DVec3, p2: DVec3, dim_pt: DVec3) -> WireModel { // Show ext lines + dim line. let (d1, d2) = dim_line_endpoints(p1, p2, dim_pt); + // Preview WireModel points are screen/GPU-side: downcast to f32. + let p1 = p1.as_vec3(); + let p2 = p2.as_vec3(); + let d1 = d1.as_vec3(); + let d2 = d2.as_vec3(); WireModel { text_verts: Vec::new(), name: "dimaligned_preview".into(), diff --git a/src/modules/annotate/angular_dim.rs b/src/modules/annotate/angular_dim.rs index fc1ba7678..9f35f5eeb 100644 --- a/src/modules/annotate/angular_dim.rs +++ b/src/modules/annotate/angular_dim.rs @@ -20,15 +20,15 @@ pub fn tool() -> ToolDef { enum Step { Vertex, - FirstRay(Vec3), + FirstRay(DVec3), SecondRay { - vertex: Vec3, - first: Vec3, + vertex: DVec3, + first: DVec3, }, ArcPoint { - vertex: Vec3, - first: Vec3, - second: Vec3, + vertex: DVec3, + first: DVec3, + second: DVec3, }, } @@ -56,7 +56,7 @@ impl CadCommand for AngularDimensionCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { Step::Vertex => { self.step = Step::FirstRay(pt); @@ -98,28 +98,34 @@ impl CadCommand for AngularDimensionCommand { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); + fn on_mouse_move(&mut self, pt: DVec3) -> Option { + let pt = pt.as_vec3(); match self.step { Step::Vertex => None, - Step::FirstRay(vertex) => Some(preview_wire(vec![vertex, pt])), + Step::FirstRay(vertex) => Some(preview_wire(vec![vertex.as_vec3(), pt])), Step::SecondRay { vertex, first } => Some(preview_wire(vec![ - vertex, - first, + vertex.as_vec3(), + first.as_vec3(), Vec3::new(f32::NAN, f32::NAN, f32::NAN), - vertex, + vertex.as_vec3(), pt, ])), Step::ArcPoint { vertex, first, second, - } => Some(preview_wire(angular_preview(vertex, first, second, pt))), + } => Some(preview_wire(angular_preview( + vertex.as_vec3(), + first.as_vec3(), + second.as_vec3(), + pt, + ))), } } } -fn v3(pt: Vec3) -> Vector3 { - Vector3::new(pt.x as f64, pt.y as f64, pt.z as f64) +fn v3(pt: DVec3) -> Vector3 { + Vector3::new(pt.x, pt.y, pt.z) } fn preview_wire(points: Vec) -> WireModel { diff --git a/src/modules/annotate/diameter_dim.rs b/src/modules/annotate/diameter_dim.rs index cb1f4995e..5e86d9396 100644 --- a/src/modules/annotate/diameter_dim.rs +++ b/src/modules/annotate/diameter_dim.rs @@ -22,8 +22,8 @@ pub fn tool() -> ToolDef { enum Step { CenterPoint, - ArcPoint(Vec3), - TextPoint { center: Vec3, arc_pt: Vec3 }, + ArcPoint(DVec3), + TextPoint { center: DVec3, arc_pt: DVec3 }, } pub struct DiameterDimensionCommand { @@ -71,7 +71,7 @@ impl CadCommand for DiameterDimensionCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { Step::CenterPoint => { self.step = Step::ArcPoint(pt); @@ -86,7 +86,7 @@ impl CadCommand for DiameterDimensionCommand { dim.base.definition_point = v3(arc_pt); dim.base.text_middle_point = v3(pt); dim.base.insertion_point = v3(pt); - dim.leader_length = arc_pt.distance(pt) as f64; + dim.leader_length = arc_pt.distance(pt); dim.base.actual_measurement = dim.measurement(); dim.base.user_text = self.text_override.clone(); // An explicit text angle overrides the default rotation. @@ -161,20 +161,21 @@ impl CadCommand for DiameterDimensionCommand { } } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); + fn on_mouse_move(&mut self, pt: DVec3) -> Option { + let pt = pt.as_vec3(); match self.step { Step::CenterPoint => None, - Step::ArcPoint(center) => Some(preview_line(center, pt)), + Step::ArcPoint(center) => Some(preview_line(center.as_vec3(), pt)), Step::TextPoint { center, arc_pt } => { let far = center + (center - arc_pt); // opposite point on circle - Some(preview_line(far, pt)) + Some(preview_line(far.as_vec3(), pt)) } } } } -fn v3(p: Vec3) -> Vector3 { - Vector3::new(p.x as f64, p.y as f64, p.z as f64) +fn v3(p: DVec3) -> Vector3 { + Vector3::new(p.x, p.y, p.z) } fn preview_line(a: Vec3, b: Vec3) -> WireModel { diff --git a/src/modules/annotate/dim_baseline.rs b/src/modules/annotate/dim_baseline.rs index 32e71734e..9607f7ceb 100644 --- a/src/modules/annotate/dim_baseline.rs +++ b/src/modules/annotate/dim_baseline.rs @@ -27,22 +27,22 @@ pub fn tool() -> ToolDef { } /// Fallback stacking increment (world units) when no DimStyle is available. -const DEFAULT_DIMDLI: f32 = 1.5; +const DEFAULT_DIMDLI: f64 = 1.5; pub struct DimBaselineCommand { /// Fixed first-extension-line origin (never changes). - base_p1: Vec3, + base_p1: DVec3, /// Direction along the measurement direction (0.0 = horizontal, PI/2 = vertical). rotation: f64, /// Text reading rotation inherited from the base dim (keeps a UCS-aligned /// chain's text consistent with the originating DIMLINEAR). 0 = natural. text_rotation: f64, /// Unit vector perpendicular to the dimension axis, pointing toward the dim line side. - perp: Vec3, + perp: DVec3, /// Perpendicular distance of the NEXT dimension line from the extension-line axis. - next_offset: f32, + next_offset: f64, /// Stacking increment from the active DimStyle (DIMDLI). - dimdli: f32, + dimdli: f64, /// True once we have a base dimension loaded. ready: bool, } @@ -51,10 +51,10 @@ impl DimBaselineCommand { /// No base dim found — cancel immediately. pub fn new() -> Self { Self { - base_p1: Vec3::ZERO, + base_p1: DVec3::ZERO, rotation: 0.0, text_rotation: 0.0, - perp: Vec3::Y, + perp: DVec3::Y, next_offset: DEFAULT_DIMDLI, dimdli: DEFAULT_DIMDLI, ready: false, @@ -76,11 +76,16 @@ impl DimBaselineCommand { text_rotation: f64, dimdli: f32, ) -> Self { + // The base dim's points enter as f32 from the caller; promote to f64 so + // all committed-coordinate math downstream stays exact. + let p1 = p1.as_dvec3(); + let definition_point = definition_point.as_dvec3(); // Measurement axis = the base dim's rotation angle (any angle, incl. a // UCS-aligned one), not a world horizontal/vertical. - let axis = Vec3::new(rotation.cos() as f32, rotation.sin() as f32, 0.0); - let perp = Vec3::new(-axis.y, axis.x, 0.0); + let axis = DVec3::new(rotation.cos(), rotation.sin(), 0.0); + let perp = DVec3::new(-axis.y, axis.x, 0.0); let base_offset = (definition_point - p1).dot(perp); + let dimdli = dimdli as f64; let dimdli = if dimdli.abs() < 1e-6 { DEFAULT_DIMDLI } else { @@ -110,7 +115,7 @@ impl DimBaselineCommand { /// base origin and `p2` each projected INDEPENDENTLY onto the dim line (at /// the current stacking offset). Projecting both keeps the line straight /// even when the two origins aren't level — a shared offset tilts it. (#181) - fn dim_line_pts(&self, p2: Vec3) -> (Vec3, Vec3) { + fn dim_line_pts(&self, p2: DVec3) -> (DVec3, DVec3) { let target = self.base_p1.dot(self.perp) + self.next_offset; let d1 = self.base_p1 + self.perp * (target - self.base_p1.dot(self.perp)); let d2 = p2 + self.perp * (target - p2.dot(self.perp)); @@ -131,7 +136,7 @@ impl CadCommand for DimBaselineCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { if !self.ready { return CmdResult::Cancel; } @@ -162,12 +167,17 @@ impl CadCommand for DimBaselineCommand { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); + fn on_mouse_move(&mut self, pt: DVec3) -> Option { if !self.ready { return None; } - let p1 = self.base_p1; + // dim_line_pts stays in f64; downcast only here, into the preview's + // [f32;3] GPU buffer — correct pixel-space, never touches committed geometry. let (dim_line_pt, dim_line_pt2) = self.dim_line_pts(pt); + let p1 = self.base_p1.as_vec3(); + let dim_line_pt = dim_line_pt.as_vec3(); + let dim_line_pt2 = dim_line_pt2.as_vec3(); + let pt = pt.as_vec3(); Some(WireModel { text_verts: Vec::new(), name: "dimbase_preview".into(), @@ -200,8 +210,8 @@ impl CadCommand for DimBaselineCommand { } } -fn v3(p: Vec3) -> Vector3 { - Vector3::new(p.x as f64, p.y as f64, p.z as f64) +fn v3(p: DVec3) -> Vector3 { + Vector3::new(p.x, p.y, p.z) } diff --git a/src/modules/annotate/dim_continue.rs b/src/modules/annotate/dim_continue.rs index 0914b6ced..5cd5bf50b 100644 --- a/src/modules/annotate/dim_continue.rs +++ b/src/modules/annotate/dim_continue.rs @@ -28,7 +28,7 @@ pub fn tool() -> ToolDef { pub struct DimContinueCommand { /// Fixed first-extension-line origin for the current step (moves each iteration). - chain_p1: Vec3, + chain_p1: DVec3, /// Direction along the dimension axis (0.0 = horizontal, PI/2 = vertical). rotation: f64, /// Text reading rotation inherited from the base dim so a UCS-aligned chain @@ -39,9 +39,9 @@ pub struct DimContinueCommand { /// coordinate so the whole chain stays collinear — even when the extension /// origins sit at different perpendicular positions (extension lines of /// different lengths). (#151) - dim_line_perp: f32, + dim_line_perp: f64, /// Direction of "up" perpendicular to the dim axis (points toward the dim line). - perp: Vec3, + perp: DVec3, /// True once we have a base dimension loaded. ready: bool, } @@ -50,11 +50,11 @@ impl DimContinueCommand { /// No base dim found — will show an error prompt and cancel immediately. pub fn new() -> Self { Self { - chain_p1: Vec3::ZERO, + chain_p1: DVec3::ZERO, rotation: 0.0, text_rotation: 0.0, dim_line_perp: 0.0, - perp: Vec3::Y, + perp: DVec3::Y, ready: false, } } @@ -75,11 +75,16 @@ impl DimContinueCommand { rotation: f64, text_rotation: f64, ) -> Self { + // Widen the base-dim points to f64 so the committed chain math runs in + // full precision. (The base points arrive as f32 from the entity, but + // every derived committed coordinate must stay f64 from here on.) + let p2 = p2.as_dvec3(); + let definition_point = definition_point.as_dvec3(); // Axis unit vector along the measurement direction — the base dim's // rotation angle (any angle, incl. a UCS-aligned one), not a world H/V. - let axis = Vec3::new(rotation.cos() as f32, rotation.sin() as f32, 0.0); + let axis = DVec3::new(rotation.cos(), rotation.sin(), 0.0); // Perpendicular unit vector toward the dim line. - let perp = Vec3::new(-axis.y, axis.x, 0.0); + let perp = DVec3::new(-axis.y, axis.x, 0.0); let dim_line_perp = definition_point.dot(perp); Self { chain_p1: p2, @@ -105,7 +110,7 @@ impl CadCommand for DimContinueCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { if !self.ready { return CmdResult::Cancel; } @@ -141,13 +146,18 @@ impl CadCommand for DimContinueCommand { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); + fn on_mouse_move(&mut self, pt: DVec3) -> Option { if !self.ready { return None; } - let p1 = self.chain_p1; - let dim_line_pt = p1 + self.perp * (self.dim_line_perp - p1.dot(self.perp)); - let dim_line_pt2 = pt + self.perp * (self.dim_line_perp - pt.dot(self.perp)); + // Preview / rubber-band geometry feeds an f32 GPU buffer, so drop to f32 + // at this screen-only boundary. + let pt = pt.as_vec3(); + let p1 = self.chain_p1.as_vec3(); + let perp = self.perp.as_vec3(); + let dim_line_perp = self.dim_line_perp as f32; + let dim_line_pt = p1 + perp * (dim_line_perp - p1.dot(perp)); + let dim_line_pt2 = pt + perp * (dim_line_perp - pt.dot(perp)); Some(WireModel { text_verts: Vec::new(), name: "dimcont_preview".into(), @@ -180,8 +190,8 @@ impl CadCommand for DimContinueCommand { } } -fn v3(p: Vec3) -> Vector3 { - Vector3::new(p.x as f64, p.y as f64, p.z as f64) +fn v3(p: DVec3) -> Vector3 { + Vector3::new(p.x, p.y, p.z) } diff --git a/src/modules/annotate/dimjogline.rs b/src/modules/annotate/dimjogline.rs index dd6a77957..4540e24f7 100644 --- a/src/modules/annotate/dimjogline.rs +++ b/src/modules/annotate/dimjogline.rs @@ -63,7 +63,7 @@ impl CadCommand for DimJogLineCommand { CmdResult::NeedPoint } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { if let Step::PickJogPos { handle } = &self.step { let h = *handle; // Emit sentinel for commands.rs to store the jog position diff --git a/src/modules/annotate/dimtedit.rs b/src/modules/annotate/dimtedit.rs index 098848d6a..d7ffcaa67 100644 --- a/src/modules/annotate/dimtedit.rs +++ b/src/modules/annotate/dimtedit.rs @@ -69,13 +69,12 @@ impl CadCommand for DimTeditCommand { CmdResult::NeedPoint } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { if let Step::PickTextPos { handle, entity } = &mut self.step { let h = *handle; if let Some(mut ent) = entity.take() { if let EntityType::Dimension(ref mut d) = ent { - let new_pt = - acadrust::types::Vector3::new(pt.x as f64, pt.y as f64, pt.z as f64); + let new_pt = acadrust::types::Vector3::new(pt.x, pt.y, pt.z); d.base_mut().text_middle_point = new_pt; d.base_mut().insertion_point = new_pt; // Pin the text to this location, else the renderer recomputes diff --git a/src/modules/annotate/leader_cmd.rs b/src/modules/annotate/leader_cmd.rs index 74160a1b7..69eb43572 100644 --- a/src/modules/annotate/leader_cmd.rs +++ b/src/modules/annotate/leader_cmd.rs @@ -31,7 +31,7 @@ pub fn tool() -> ToolDef { } pub struct LeaderCommand { - verts: Vec, + verts: Vec, ucs: Mat4, } @@ -61,7 +61,7 @@ impl CadCommand for LeaderCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { self.verts.push(pt); CmdResult::NeedPoint } @@ -85,24 +85,24 @@ impl CadCommand for LeaderCommand { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); + fn on_mouse_move(&mut self, pt: DVec3) -> Option { if self.verts.is_empty() { return None; } - let mut pts = self.verts.clone(); - pts.push(pt); + let mut pts: Vec = self.verts.iter().map(|p| p.as_vec3()).collect(); + pts.push(pt.as_vec3()); Some(preview_wire(&pts)) } } // ── Helpers ───────────────────────────────────────────────────────────────── -fn v3(p: Vec3) -> Vector3 { - Vector3::new(p.x as f64, p.y as f64, p.z as f64) +fn dv3(p: DVec3) -> Vector3 { + Vector3::new(p.x, p.y, p.z) } -fn build_leader(verts: &[Vec3], ucs: Mat4) -> Leader { - let mut l = Leader::from_vertices(verts.iter().map(|p| v3(*p)).collect()); +fn build_leader(verts: &[DVec3], ucs: Mat4) -> Leader { + let mut l = Leader::from_vertices(verts.iter().map(|p| dv3(*p)).collect()); l.creation_type = LeaderCreationType::WithText; l.hookline_enabled = true; // The hookline / text read along the UCS X axis (DXF "horizontal direction @@ -116,17 +116,17 @@ fn build_leader(verts: &[Vec3], ucs: Mat4) -> Leader { /// keeps the text reading away from the leader (text to the right of a /// left-pointing landing, to the left of a right-pointing one). fn annotation_anchor( - verts: &[Vec3], + verts: &[DVec3], text_height: f64, ucs: Mat4, -) -> (Vec3, AttachmentPoint) { +) -> (DVec3, AttachmentPoint) { let last = *verts.last().unwrap(); let prev = verts[verts.len() - 2]; // Side + landing run along the UCS X axis (identity = world). - let ux = ucs.transform_vector3(Vec3::X).normalize_or(Vec3::X); + let ux = ucs.transform_vector3(Vec3::X).normalize_or(Vec3::X).as_dvec3(); let to_right = (last - prev).dot(ux) >= 0.0; - let sign = if to_right { 1.0_f32 } else { -1.0_f32 }; - let anchor = last + ux * (sign * text_height as f32 * 1.5); + let sign = if to_right { 1.0_f64 } else { -1.0_f64 }; + let anchor = last + ux * (sign * text_height * 1.5); let attach = if to_right { AttachmentPoint::MiddleLeft } else { @@ -137,14 +137,14 @@ fn annotation_anchor( fn build_mtext( text: &str, - pos: Vec3, + pos: DVec3, height: f64, attach: AttachmentPoint, ucs: Mat4, ) -> MText { let mut m = MText::new(); m.value = text.to_string(); - m.insertion_point = v3(pos); + m.insertion_point = dv3(pos); m.height = height; m.attachment_point = attach; // Text reads along the UCS X axis. diff --git a/src/modules/annotate/linear_dim.rs b/src/modules/annotate/linear_dim.rs index a67aa4ff6..8f4a2c5c4 100644 --- a/src/modules/annotate/linear_dim.rs +++ b/src/modules/annotate/linear_dim.rs @@ -10,10 +10,10 @@ use glam::{DVec3, Mat4, Vec3}; /// World-space measurement axis for a linear dimension between `first` and /// `second`, chosen as the UCS X or Y axis (whichever the span is closer to). /// `ucs` is the UCS→wire affine; identity gives the world X/Y behaviour. -fn measure_axis(first: Vec3, second: Vec3, ucs: Mat4) -> Vec3 { - let ux = ucs.transform_vector3(Vec3::X).normalize_or(Vec3::X); - let uy = ucs.transform_vector3(Vec3::Y).normalize_or(Vec3::Y); - let du = ucs.inverse().transform_vector3(second - first); +fn measure_axis(first: DVec3, second: DVec3, ucs: Mat4) -> DVec3 { + let ux = ucs.transform_vector3(Vec3::X).normalize_or(Vec3::X).as_dvec3(); + let uy = ucs.transform_vector3(Vec3::Y).normalize_or(Vec3::Y).as_dvec3(); + let du = ucs.inverse().transform_vector3((second - first).as_vec3()); if du.x.abs() >= du.y.abs() { ux } else { @@ -34,8 +34,8 @@ pub fn tool() -> ToolDef { enum Step { FirstPoint, - SecondPoint(Vec3), - DimensionLine { first: Vec3, second: Vec3 }, + SecondPoint(DVec3), + DimensionLine { first: DVec3, second: DVec3 }, } pub struct LinearDimensionCommand { @@ -91,7 +91,7 @@ impl CadCommand for LinearDimensionCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { Step::FirstPoint => { self.step = Step::SecondPoint(pt); @@ -106,7 +106,7 @@ impl CadCommand for LinearDimensionCommand { // Measure along the UCS axis the span is closest to; the DXF // rotation is that axis's angle in world space. let axis = measure_axis(first, second, self.ucs); - dim.rotation = (axis.y as f64).atan2(axis.x as f64); + dim.rotation = axis.y.atan2(axis.x); // Bake the UCS X-axis angle as the text rotation so the // measurement text reads horizontally in the UCS (i.e. square on // screen) regardless of the style's force-horizontal flags, @@ -200,7 +200,7 @@ impl CadCommand for LinearDimensionCommand { } } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); + fn on_mouse_move(&mut self, pt: DVec3) -> Option { match self.step { Step::FirstPoint => None, Step::SecondPoint(first) => Some(preview_wire(vec![first, pt])), @@ -212,15 +212,18 @@ impl CadCommand for LinearDimensionCommand { } } -fn v3(pt: Vec3) -> Vector3 { - Vector3::new(pt.x as f64, pt.y as f64, pt.z as f64) +fn v3(pt: DVec3) -> Vector3 { + Vector3::new(pt.x, pt.y, pt.z) } -fn preview_wire(points: Vec) -> WireModel { +fn preview_wire(points: Vec) -> WireModel { WireModel { text_verts: Vec::new(), name: "dimlinear_preview".to_string(), - points: points.into_iter().map(|p| [p.x, p.y, p.z]).collect(), + points: points + .into_iter() + .map(|p| [p.x as f32, p.y as f32, p.z as f32]) + .collect(), points_low: Vec::new(), color: WireModel::CYAN, selected: false, @@ -244,23 +247,23 @@ fn preview_wire(points: Vec) -> WireModel { /// single shared offset only lands both on the line when they are level, and /// tilts the dimension line when they are not (e.g. measuring across sloped /// points). See #181. -fn dim_line_endpoints(first: Vec3, second: Vec3, def: Vec3, axis: Vec3) -> (Vec3, Vec3) { - let perp = Vec3::new(-axis.y, axis.x, 0.0); +fn dim_line_endpoints(first: DVec3, second: DVec3, def: DVec3, axis: DVec3) -> (DVec3, DVec3) { + let perp = DVec3::new(-axis.y, axis.x, 0.0); let dperp = def.dot(perp); let d1 = first + perp * (dperp - first.dot(perp)); let d2 = second + perp * (dperp - second.dot(perp)); (d1, d2) } -fn linear_dimension_preview(first: Vec3, second: Vec3, def: Vec3, axis: Vec3) -> Vec { +fn linear_dimension_preview(first: DVec3, second: DVec3, def: DVec3, axis: DVec3) -> Vec { let (d1, d2) = dim_line_endpoints(first, second, def, axis); - let nan = Vec3::new(f32::NAN, f32::NAN, f32::NAN); + let nan = DVec3::new(f64::NAN, f64::NAN, f64::NAN); vec![first, d1, nan, second, d2, nan, d1, d2] } -fn linear_text_pos(first: Vec3, second: Vec3, def: Vec3, axis: Vec3) -> Vec3 { +fn linear_text_pos(first: DVec3, second: DVec3, def: DVec3, axis: DVec3) -> DVec3 { let (d1, d2) = dim_line_endpoints(first, second, def, axis); - let perp = Vec3::new(-axis.y, axis.x, 0.0); + let perp = DVec3::new(-axis.y, axis.x, 0.0); (d1 + d2) * 0.5 + perp * 0.15 } diff --git a/src/modules/annotate/mleader_cmd.rs b/src/modules/annotate/mleader_cmd.rs index a3ffcace7..0ebe2e8dd 100644 --- a/src/modules/annotate/mleader_cmd.rs +++ b/src/modules/annotate/mleader_cmd.rs @@ -26,7 +26,7 @@ pub fn tool() -> ToolDef { } pub struct MLeaderCommand { - verts: Vec, + verts: Vec, ucs: Mat4, } @@ -56,7 +56,7 @@ impl CadCommand for MLeaderCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { self.verts.push(pt); CmdResult::NeedPoint } @@ -75,23 +75,24 @@ impl CadCommand for MLeaderCommand { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); + fn on_mouse_move(&mut self, pt: DVec3) -> Option { if self.verts.is_empty() { return None; } - let mut pts = self.verts.clone(); - pts.push(pt); + // Preview / rubber-band is GPU screen-space: downcast to f32 here. + let mut pts: Vec = self.verts.iter().map(|p| p.as_vec3()).collect(); + pts.push(pt.as_vec3()); Some(preview_wire(&pts)) } } // ── Helpers ───────────────────────────────────────────────────────────────── -fn v3(p: Vec3) -> Vector3 { - Vector3::new(p.x as f64, p.y as f64, p.z as f64) +fn v3(p: DVec3) -> Vector3 { + Vector3::new(p.x, p.y, p.z) } -fn build_mleader(text: &str, verts: &[Vec3], ucs: Mat4) -> MultiLeader { +fn build_mleader(text: &str, verts: &[DVec3], ucs: Mat4) -> MultiLeader { // Last vertex = content/text location; remaining = leader line points let (leader_pts, content_pt) = verts.split_at(verts.len() - 1); let content_pt = content_pt[0]; @@ -113,7 +114,7 @@ fn build_mleader(text: &str, verts: &[Vec3], ucs: Mat4) -> MultiLeader { let ux = ucs.transform_vector3(Vec3::X).normalize_or(Vec3::X); // Which side of the leader the text sits on, measured along the UCS X axis. let last_leader = leader_pts.last().copied().unwrap_or(content_pt); - let to_right = (content_pt - last_leader).dot(ux) >= 0.0; + let to_right = (content_pt - last_leader).dot(ux.as_dvec3()) >= 0.0; let sign = if to_right { 1.0 } else { -1.0 }; let landing = ux * (sign as f32); diff --git a/src/modules/annotate/mleader_edit.rs b/src/modules/annotate/mleader_edit.rs index 3034109f2..cccc0df1f 100644 --- a/src/modules/annotate/mleader_edit.rs +++ b/src/modules/annotate/mleader_edit.rs @@ -37,7 +37,7 @@ enum AddStep { CollectPoints { handle: Handle, entity: EntityType, - pts: Vec, + pts: Vec, }, } @@ -84,7 +84,7 @@ impl CadCommand for MLeaderAddCommand { CmdResult::NeedPoint } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { match &mut self.step { AddStep::PickArrowhead { handle, entity } => { if let Some(ent) = entity.take() { @@ -121,7 +121,7 @@ impl CadCommand for MLeaderAddCommand { // Add a new leader root with the collected points let points: Vec = pts .iter() - .map(|p| Vector3::new(p.x as f64, p.y as f64, p.z as f64)) + .map(|p| Vector3::new(p.x, p.y, p.z)) .collect(); let root = ml.context.add_leader_root(); root.create_line(points); @@ -132,14 +132,16 @@ impl CadCommand for MLeaderAddCommand { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); + fn on_mouse_move(&mut self, pt: DVec3) -> Option { let existing_pts = match &self.step { AddStep::CollectPoints { pts, .. } => pts.clone(), _ => return None, }; let mut all_pts = existing_pts; all_pts.push(pt); - Some(preview_wire(&all_pts)) + Some(preview_wire( + &all_pts.iter().map(|p| p.as_vec3()).collect::>(), + )) } fn inject_picked_entity(&mut self, entity: EntityType) { @@ -275,7 +277,7 @@ pub fn tool_align() -> ToolDef { enum AlignStep { Gathering, PickDirection { handles: Vec }, - PickEndDir { handles: Vec, from: Vec3 }, + PickEndDir { handles: Vec, from: DVec3 }, } pub struct MLeaderAlignCommand { @@ -321,7 +323,7 @@ impl CadCommand for MLeaderAlignCommand { CmdResult::NeedPoint } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { match &mut self.step { AlignStep::PickDirection { handles } => { let h = handles.clone(); @@ -423,7 +425,7 @@ impl CadCommand for MLeaderCollectCommand { CmdResult::NeedPoint } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { if let CollectStep::PickLocation { handles } = &self.step { let h = handles.clone(); // Build a new combined multileader at the collection point, delete originals. diff --git a/src/modules/annotate/ordinate_dim.rs b/src/modules/annotate/ordinate_dim.rs index 9ac937850..e982c5b8d 100644 --- a/src/modules/annotate/ordinate_dim.rs +++ b/src/modules/annotate/ordinate_dim.rs @@ -28,7 +28,7 @@ pub fn tool() -> ToolDef { enum Step { FeaturePoint, - LeaderEndpoint { feature: Vec3 }, + LeaderEndpoint { feature: DVec3 }, } pub struct OrdinateDimCommand { @@ -55,7 +55,7 @@ impl CadCommand for OrdinateDimCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { Step::FeaturePoint => { self.step = Step::LeaderEndpoint { feature: pt }; @@ -80,25 +80,30 @@ impl CadCommand for OrdinateDimCommand { fn on_enter(&mut self) -> CmdResult { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); + fn on_mouse_move(&mut self, pt: DVec3) -> Option { let feature = match self.step { Step::LeaderEndpoint { feature } => feature, _ => return None, }; let is_x = is_x_type(feature, pt); let elbow = ordinate_elbow(feature, pt, is_x); - Some(preview_wire(vec![feature, elbow, pt])) + // Screen-only rubber band: downcast to f32 at the preview boundary. + Some(preview_wire(vec![ + feature.as_vec3(), + elbow.as_vec3(), + pt.as_vec3(), + ])) } } -fn v3(p: Vec3) -> Vector3 { - Vector3::new(p.x as f64, p.y as f64, p.z as f64) +fn v3(p: DVec3) -> Vector3 { + Vector3::new(p.x, p.y, p.z) } /// X-datum (labels the feature's X coordinate) when the leader runs more /// vertically than horizontally; Y-datum otherwise. Mirrors the placement /// decision so the preview and the committed entity agree. -fn is_x_type(feature: Vec3, leader: Vec3) -> bool { +fn is_x_type(feature: DVec3, leader: DVec3) -> bool { let dx = (leader.x - feature.x).abs(); let dy = (leader.y - feature.y).abs(); dy >= dx @@ -106,11 +111,11 @@ fn is_x_type(feature: Vec3, leader: Vec3) -> bool { /// Orthogonal elbow of the ordinate leader: an X-datum runs along Y from the /// feature then jogs across in X; a Y-datum runs along X then jogs in Y. -fn ordinate_elbow(feature: Vec3, leader: Vec3, is_x: bool) -> Vec3 { +fn ordinate_elbow(feature: DVec3, leader: DVec3, is_x: bool) -> DVec3 { if is_x { - Vec3::new(feature.x, leader.y, feature.z) + DVec3::new(feature.x, leader.y, feature.z) } else { - Vec3::new(leader.x, feature.y, feature.z) + DVec3::new(leader.x, feature.y, feature.z) } } diff --git a/src/modules/annotate/qdim.rs b/src/modules/annotate/qdim.rs index 316752fcc..432ad4c70 100644 --- a/src/modules/annotate/qdim.rs +++ b/src/modules/annotate/qdim.rs @@ -9,7 +9,7 @@ use acadrust::entities::{Dimension, DimensionLinear}; use acadrust::types::Vector3; use acadrust::{EntityType, Handle}; -use glam::{DVec3, Vec3}; +use glam::DVec3; use crate::command::{CadCommand, CmdResult}; use crate::modules::{IconKind, ModuleEvent, ToolDef}; @@ -67,9 +67,11 @@ impl CadCommand for QdimCommand { CmdResult::NeedPoint } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { if let Step::PlaceLine { handles } = &self.step { CmdResult::Relaunch( + // Keep the picked placement point in full f64 precision so the + // committed dimension coordinates are not quantized to f32. format!("QDIM_PLACE {:.6} {:.6} {:.6}", pt.x, pt.y, pt.z), handles.clone(), ) @@ -112,8 +114,8 @@ impl CadCommand for QdimCommand { /// Build a linear dimension between two points at the given dimension-line position. #[allow(dead_code)] -pub fn make_linear_dim(p1: Vec3, p2: Vec3, dim_pt: Vec3) -> EntityType { - let v = |p: Vec3| Vector3::new(p.x as f64, p.y as f64, p.z as f64); +pub fn make_linear_dim(p1: DVec3, p2: DVec3, dim_pt: DVec3) -> EntityType { + let v = |p: DVec3| Vector3::new(p.x, p.y, p.z); let mut dim = DimensionLinear::new(v(p1), v(p2)); // Determine axis: horizontal if Δx > Δz, else vertical let dx = (p2.x - p1.x).abs(); diff --git a/src/modules/annotate/radius_dim.rs b/src/modules/annotate/radius_dim.rs index 25ba8efe1..86937ec5b 100644 --- a/src/modules/annotate/radius_dim.rs +++ b/src/modules/annotate/radius_dim.rs @@ -20,8 +20,8 @@ pub fn tool() -> ToolDef { enum Step { CenterPoint, - RadiusPoint(Vec3), - TextPoint { center: Vec3, point: Vec3 }, + RadiusPoint(DVec3), + TextPoint { center: DVec3, point: DVec3 }, } pub struct RadiusDimensionCommand { @@ -69,7 +69,7 @@ impl CadCommand for RadiusDimensionCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { Step::CenterPoint => { self.step = Step::RadiusPoint(pt); @@ -84,7 +84,7 @@ impl CadCommand for RadiusDimensionCommand { dim.base.definition_point = v3(point); dim.base.text_middle_point = v3(pt); dim.base.insertion_point = v3(pt); - dim.leader_length = point.distance(pt) as f64; + dim.leader_length = point.distance(pt); dim.base.actual_measurement = dim.measurement(); dim.base.user_text = self.text_override.clone(); // An explicit text angle overrides the default rotation. @@ -163,23 +163,24 @@ impl CadCommand for RadiusDimensionCommand { } } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); + fn on_mouse_move(&mut self, pt: DVec3) -> Option { + let pt = pt.as_vec3(); match self.step { Step::CenterPoint => None, - Step::RadiusPoint(center) => Some(preview_wire(vec![center, pt])), + Step::RadiusPoint(center) => Some(preview_wire(vec![center.as_vec3(), pt])), Step::TextPoint { center, point } => Some(preview_wire(vec![ - center, - point, + center.as_vec3(), + point.as_vec3(), Vec3::new(f32::NAN, f32::NAN, f32::NAN), - point, + point.as_vec3(), pt, ])), } } } -fn v3(pt: Vec3) -> Vector3 { - Vector3::new(pt.x as f64, pt.y as f64, pt.z as f64) +fn v3(pt: DVec3) -> Vector3 { + Vector3::new(pt.x, pt.y, pt.z) } fn preview_wire(points: Vec) -> WireModel { diff --git a/src/modules/annotate/table_cmd.rs b/src/modules/annotate/table_cmd.rs index 4afc93503..30b72d99d 100644 --- a/src/modules/annotate/table_cmd.rs +++ b/src/modules/annotate/table_cmd.rs @@ -116,9 +116,9 @@ impl CadCommand for TableCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { if let Step::Insertion { cols, rows } = self.step { - let ins = Vector3::new(pt.x as f64, pt.y as f64, pt.z as f64); + let ins = Vector3::new(pt.x, pt.y, pt.z); let table = TableBuilder::new(rows, cols) .at(ins) .row_height(ROW_HEIGHT) diff --git a/src/modules/annotate/tolerance_cmd.rs b/src/modules/annotate/tolerance_cmd.rs index 280f848b8..8a8edb5cd 100644 --- a/src/modules/annotate/tolerance_cmd.rs +++ b/src/modules/annotate/tolerance_cmd.rs @@ -64,9 +64,9 @@ impl CadCommand for ToleranceCommand { Some(CmdResult::NeedPoint) } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { if let Step::Insertion { text } = &self.step { - let ins = Vector3::new(pt.x as f64, pt.y as f64, pt.z as f64); + let ins = Vector3::new(pt.x, pt.y, pt.z); let tol = Tolerance::with_text(ins, text.clone()); CmdResult::CommitAndExit(EntityType::Tolerance(tol)) } else { diff --git a/src/modules/draw/draw/attdef.rs b/src/modules/draw/draw/attdef.rs index 9d778777c..fc032b327 100644 --- a/src/modules/draw/draw/attdef.rs +++ b/src/modules/draw/draw/attdef.rs @@ -106,7 +106,7 @@ impl CadCommand for AttdefCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { if let Step::Insertion { tag, prompt, @@ -117,7 +117,7 @@ impl CadCommand for AttdefCommand { tag: tag.clone(), prompt: prompt.clone(), default_value: default.clone(), - insertion_point: Vector3::new(pt.x as f64, pt.y as f64, pt.z as f64), + insertion_point: Vector3::new(pt.x, pt.y, pt.z), height: self.height, ..Default::default() }; diff --git a/src/modules/draw/draw/mline.rs b/src/modules/draw/draw/mline.rs index 2a114021f..5e5757311 100644 --- a/src/modules/draw/draw/mline.rs +++ b/src/modules/draw/draw/mline.rs @@ -8,13 +8,13 @@ use acadrust::entities::MLine; use acadrust::types::Vector3; use acadrust::EntityType; -use glam::{DVec3, Vec3}; +use glam::DVec3; use crate::command::{CadCommand, CmdResult}; use crate::scene::model::wire_model::WireModel; pub struct MlineCommand { - points: Vec, + points: Vec, scale: f64, waiting_scale: bool, style_name: String, @@ -111,7 +111,7 @@ impl CadCommand for MlineCommand { None } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { self.points.push(pt); CmdResult::NeedPoint } @@ -124,11 +124,16 @@ impl CadCommand for MlineCommand { CmdResult::CommitAndExit(entity) } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); + fn on_mouse_move(&mut self, pt: DVec3) -> Option { + let pt = pt.as_vec3(); if self.points.is_empty() { return None; } - let mut pts: Vec<[f32; 3]> = self.points.iter().map(|p| [p.x, p.y, p.z]).collect(); + let mut pts: Vec<[f32; 3]> = self + .points + .iter() + .map(|p| [p.x as f32, p.y as f32, p.z as f32]) + .collect(); pts.push([pt.x, pt.y, pt.z]); Some(WireModel { text_verts: Vec::new(), @@ -153,10 +158,10 @@ impl CadCommand for MlineCommand { } } -fn build_mline(pts: &[Vec3], scale: f64, closed: bool, style_name: &str) -> EntityType { +fn build_mline(pts: &[DVec3], scale: f64, closed: bool, style_name: &str) -> EntityType { let verts: Vec = pts .iter() - .map(|p| Vector3::new(p.x as f64, p.y as f64, p.z as f64)) + .map(|p| Vector3::new(p.x, p.y, p.z)) .collect(); let mut mline = if closed { MLine::closed_from_points(&verts) diff --git a/src/modules/draw/draw/polyline.rs b/src/modules/draw/draw/polyline.rs index 32a5557a5..8157871fb 100644 --- a/src/modules/draw/draw/polyline.rs +++ b/src/modules/draw/draw/polyline.rs @@ -12,7 +12,7 @@ use acadrust::entities::LwVertex; use acadrust::types::Vector2; use acadrust::{EntityType, Handle, LwPolyline}; -use glam::{DVec3, Vec2, Vec3}; +use glam::{DVec2, DVec3, Vec2, Vec3}; use crate::command::{CadCommand, CmdResult}; use crate::modules::{IconKind, ModuleEvent, ToolDef}; @@ -40,7 +40,7 @@ enum SegMode { // ── Command implementation ───────────────────────────────────────────────── pub struct PlineCommand { - vertices: Vec, + vertices: Vec, /// Bulge for segment i → i+1 (one entry per vertex; last entry unused on commit). bulges: Vec, mode: SegMode, @@ -90,7 +90,7 @@ impl PlineCommand { .iter() .enumerate() .map(|(i, v)| { - let mut lv = LwVertex::new(Vector2::new(v.x as f64, v.y as f64)); + let mut lv = LwVertex::new(Vector2::new(v.x, v.y)); lv.bulge = self.bulges.get(i).copied().unwrap_or(0.0); lv }) @@ -108,14 +108,14 @@ impl PlineCommand { /// Compute the bulge for the arc from `a` to `b` that is tangent to `tangent` at `a`. /// Returns 0.0 if the points are coincident or the tangent is parallel to the chord. -fn compute_bulge(a: Vec2, tangent: Vec2, b: Vec2) -> f64 { +fn compute_bulge(a: DVec2, tangent: DVec2, b: DVec2) -> f64 { let d = b - a; let len_sq = d.length_squared(); if len_sq < 1e-10 { return 0.0; } // Perpendicular to tangent (CCW) — this is the direction to the arc center. - let perp = Vec2::new(-tangent.y, tangent.x); + let perp = DVec2::new(-tangent.y, tangent.x); let dot = d.dot(perp); if dot.abs() < 1e-10 { // Tangent is perpendicular to chord → straight line (bulge = 0). @@ -133,15 +133,15 @@ fn compute_bulge(a: Vec2, tangent: Vec2, b: Vec2) -> f64 { if t > 0.0 { // CCW arc: ensure arc_angle is in (0, 2π]. if arc_angle <= 0.0 { - arc_angle += std::f32::consts::TAU; + arc_angle += std::f64::consts::TAU; } } else { // CW arc: ensure arc_angle is in [-2π, 0). if arc_angle >= 0.0 { - arc_angle -= std::f32::consts::TAU; + arc_angle -= std::f64::consts::TAU; } } - (arc_angle as f64 / 4.0).tan() + (arc_angle / 4.0).tan() } /// Update `tangent` after an arc segment described by `bulge` from `a` to `b`. @@ -240,26 +240,28 @@ impl CadCommand for PlineCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { if !self.vertices.is_empty() { let last = *self.vertices.last().unwrap(); let last_idx = self.vertices.len() - 1; let bulge = match self.mode { SegMode::Line => { - let d = Vec2::new(pt.x - last.x, pt.y - last.y); + let d = DVec2::new(pt.x - last.x, pt.y - last.y); if d.length_squared() > 1e-10 { - self.last_tangent = Some(d.normalize()); + // Direction only — f32 is sufficient for tangent continuity. + self.last_tangent = Some(d.normalize().as_vec2()); } 0.0 } SegMode::Arc => { - let a = Vec2::new(last.x, last.y); - let b = Vec2::new(pt.x, pt.y); - let tangent = self.last_tangent.unwrap_or_else(|| { + let a = DVec2::new(last.x, last.y); + let b = DVec2::new(pt.x, pt.y); + let tangent = self + .last_tangent + .map(|t| t.as_dvec2()) // No previous tangent: default to pointing right (arbitrary). - Vec2::new(1.0, 0.0) - }); + .unwrap_or(DVec2::new(1.0, 0.0)); let bulge = compute_bulge(a, tangent, b); update_tangent_after_arc(&mut self.last_tangent, bulge); bulge @@ -318,11 +320,12 @@ impl CadCommand for PlineCommand { } } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); + fn on_mouse_move(&mut self, pt: DVec3) -> Option { + let pt = pt.as_vec3(); // The committed vertices already render as a real document entity, so // the preview is just the pending segment from the last vertex to the - // cursor. (#119) - let last = *self.vertices.last()?; + // cursor. (#119) Downcast to f32 for the pixel-space rubber band. + let last = self.vertices.last()?.as_vec3(); let mut pts: Vec<[f32; 3]> = Vec::new(); match self.mode { @@ -331,9 +334,12 @@ impl CadCommand for PlineCommand { pts.push([pt.x, pt.y, pt.z]); } SegMode::Arc => { - let a = Vec2::new(last.x, last.y); - let b = Vec2::new(pt.x, pt.y); - let tangent = self.last_tangent.unwrap_or(Vec2::new(1.0, 0.0)); + let a = DVec2::new(last.x as f64, last.y as f64); + let b = DVec2::new(pt.x as f64, pt.y as f64); + let tangent = self + .last_tangent + .map(|t| t.as_dvec2()) + .unwrap_or(DVec2::new(1.0, 0.0)); let bulge = compute_bulge(a, tangent, b); let arc_pts = arc_sample_points(last, bulge, pt, 16); pts.extend_from_slice(&arc_pts); diff --git a/src/modules/draw/draw/raster_image.rs b/src/modules/draw/draw/raster_image.rs index 8766e155d..2d7d25f72 100644 --- a/src/modules/draw/draw/raster_image.rs +++ b/src/modules/draw/draw/raster_image.rs @@ -9,7 +9,7 @@ use acadrust::entities::RasterImage; use acadrust::types::Vector3; use acadrust::EntityType; -use glam::{DVec3, Vec3}; +use glam::DVec3; use crate::command::{CadCommand, CmdResult}; use crate::scene::model::wire_model::WireModel; @@ -18,7 +18,7 @@ pub struct ImageCommand { file_path: String, pixel_width: u32, pixel_height: u32, - origin: Option, + origin: Option, } impl ImageCommand { @@ -39,11 +39,11 @@ impl ImageCommand { } } - fn make_entity(&self, origin: Vec3, width_pt: Vec3) -> EntityType { - let world_width = ((width_pt.x - origin.x) as f64).abs().max(0.001); + fn make_entity(&self, origin: DVec3, width_pt: DVec3) -> EntityType { + let world_width = (width_pt.x - origin.x).abs().max(0.001); let world_height = world_width / self.aspect(); - let ins = Vector3::new(origin.x as f64, origin.y as f64, origin.z as f64); + let ins = Vector3::new(origin.x, origin.y, origin.z); let mut img = RasterImage::with_size( &self.file_path, @@ -75,7 +75,7 @@ impl CadCommand for ImageCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { if let Some(origin) = self.origin { let entity = self.make_entity(origin, pt); CmdResult::CommitAndExit(entity) @@ -89,7 +89,7 @@ impl CadCommand for ImageCommand { // If origin is set, place with a default width of 1 unit * pixel count / 100 if let Some(origin) = self.origin { let default_w = (self.pixel_width as f64 / 100.0).max(1.0); - let width_pt = Vec3::new(origin.x + default_w as f32, origin.y, origin.z); + let width_pt = DVec3::new(origin.x + default_w, origin.y, origin.z); let entity = self.make_entity(origin, width_pt); CmdResult::CommitAndExit(entity) } else { @@ -98,7 +98,7 @@ impl CadCommand for ImageCommand { } fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); - let origin = self.origin?; + let origin = self.origin?.as_vec3(); let world_width = (pt.x - origin.x).abs().max(0.001); let world_height = world_width / self.aspect() as f32; diff --git a/src/modules/draw/draw/ray.rs b/src/modules/draw/draw/ray.rs index 213d34b43..1e57b2f0d 100644 --- a/src/modules/draw/draw/ray.rs +++ b/src/modules/draw/draw/ray.rs @@ -10,14 +10,14 @@ use acadrust::EntityType; use crate::command::{CadCommand, CmdResult}; use crate::scene::model::wire_model::WireModel; -use glam::{DVec3, Vec3}; +use glam::DVec3; const DISPLAY_EXTENT: f32 = 1_000_000.0; // ── RAY ─────────────────────────────────────────────────────────────────── pub struct RayCommand { - base: Option, + base: Option, } impl RayCommand { @@ -48,7 +48,7 @@ impl CadCommand for RayCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { if let Some(base) = self.base { let dir = pt - base; let len = dir.length(); @@ -57,8 +57,8 @@ impl CadCommand for RayCommand { } let dir_n = dir / len; let ray = RayEnt::new( - Vector3::new(base.x as f64, base.y as f64, base.z as f64), - Vector3::new(dir_n.x as f64, dir_n.y as f64, dir_n.z as f64), + Vector3::new(base.x, base.y, base.z), + Vector3::new(dir_n.x, dir_n.y, dir_n.z), ); // Stay active: new base = same base (can keep clicking through points) // Actually AutoCAD prompts for new start after each ray — reset base. @@ -78,8 +78,9 @@ impl CadCommand for RayCommand { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); - let base = self.base?; + fn on_mouse_move(&mut self, pt: DVec3) -> Option { + let pt = pt.as_vec3(); + let base = self.base?.as_vec3(); let dir = (pt - base).normalize_or_zero(); let far = base + dir * DISPLAY_EXTENT; Some(WireModel { @@ -108,7 +109,7 @@ impl CadCommand for RayCommand { // ── XLINE ───────────────────────────────────────────────────────────────── pub struct XLineCommand { - base: Option, + base: Option, } impl XLineCommand { @@ -139,7 +140,7 @@ impl CadCommand for XLineCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { if let Some(base) = self.base { let dir = pt - base; let len = dir.length(); @@ -148,8 +149,8 @@ impl CadCommand for XLineCommand { } let dir_n = dir / len; let xline = XLineEnt::new( - Vector3::new(base.x as f64, base.y as f64, base.z as f64), - Vector3::new(dir_n.x as f64, dir_n.y as f64, dir_n.z as f64), + Vector3::new(base.x, base.y, base.z), + Vector3::new(dir_n.x, dir_n.y, dir_n.z), ); self.base = None; CmdResult::CommitEntity(EntityType::XLine(xline)) @@ -167,8 +168,9 @@ impl CadCommand for XLineCommand { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); - let base = self.base?; + fn on_mouse_move(&mut self, pt: DVec3) -> Option { + let pt = pt.as_vec3(); + let base = self.base?.as_vec3(); let dir = (pt - base).normalize_or_zero(); let far_pos = base + dir * DISPLAY_EXTENT; let far_neg = base - dir * DISPLAY_EXTENT; diff --git a/src/modules/draw/draw/revcloud.rs b/src/modules/draw/draw/revcloud.rs index a769f5e7b..156ad81a5 100644 --- a/src/modules/draw/draw/revcloud.rs +++ b/src/modules/draw/draw/revcloud.rs @@ -7,7 +7,7 @@ use acadrust::entities::LwPolyline; use acadrust::types::Vector2; use acadrust::{entities::LwVertex, EntityType}; -use glam::{DVec3, Vec3}; +use glam::DVec3; use crate::command::{CadCommand, CmdResult}; use crate::modules::{IconKind, ModuleEvent, ToolDef}; @@ -27,7 +27,7 @@ pub fn tool() -> ToolDef { const DEFAULT_ARC_LEN: f64 = 1.0; // default arc bump length pub struct RevCloudCommand { - points: Vec, + points: Vec, arc_length: f64, } @@ -59,7 +59,7 @@ impl CadCommand for RevCloudCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { self.points.push(pt); CmdResult::NeedPoint } @@ -72,13 +72,22 @@ impl CadCommand for RevCloudCommand { CmdResult::CommitAndExit(entity) } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); + fn on_mouse_move(&mut self, pt: DVec3) -> Option { + let pt = pt.as_vec3(); if self.points.is_empty() { return None; } - let mut preview_pts: Vec<[f32; 3]> = self.points.iter().map(|p| [p.x, p.y, p.z]).collect(); + let mut preview_pts: Vec<[f32; 3]> = self + .points + .iter() + .map(|p| [p.x as f32, p.y as f32, p.z as f32]) + .collect(); preview_pts.push([pt.x, pt.y, pt.z]); - preview_pts.push([self.points[0].x, self.points[0].y, self.points[0].z]); + preview_pts.push([ + self.points[0].x as f32, + self.points[0].y as f32, + self.points[0].z as f32, + ]); Some(WireModel { text_verts: Vec::new(), name: "revcloud_preview".into(), @@ -102,7 +111,7 @@ impl CadCommand for RevCloudCommand { } } -fn make_revcloud(pts: &[Vec3], arc_len: f64) -> EntityType { +fn make_revcloud(pts: &[DVec3], arc_len: f64) -> EntityType { let n = pts.len(); let mut vertices: Vec = Vec::new(); @@ -112,19 +121,19 @@ fn make_revcloud(pts: &[Vec3], arc_len: f64) -> EntityType { for i in 0..n { let p0 = pts[i]; let p1 = pts[(i + 1) % n]; - let seg_len = ((p1.x - p0.x).powi(2) + (p1.y - p0.y).powi(2)).sqrt() as f64; + let seg_len = ((p1.x - p0.x).powi(2) + (p1.y - p0.y).powi(2)).sqrt(); if seg_len < 1e-6 { continue; } // How many full arcs fit? let num_arcs = ((seg_len / arc_len).round() as usize).max(1); - let step_x = (p1.x - p0.x) as f64 / num_arcs as f64; - let step_y = (p1.y - p0.y) as f64 / num_arcs as f64; + let step_x = (p1.x - p0.x) / num_arcs as f64; + let step_y = (p1.y - p0.y) / num_arcs as f64; for j in 0..num_arcs { - let x = p0.x as f64 + step_x * j as f64; - let y = p0.y as f64 + step_y * j as f64; // DXF Y + let x = p0.x + step_x * j as f64; + let y = p0.y + step_y * j as f64; // DXF Y let mut v = LwVertex::new(Vector2::new(x, y)); v.bulge = bump_bulge; vertices.push(v); diff --git a/src/modules/draw/draw/spline.rs b/src/modules/draw/draw/spline.rs index 7390cb51c..43f9ad88c 100644 --- a/src/modules/draw/draw/spline.rs +++ b/src/modules/draw/draw/spline.rs @@ -9,7 +9,7 @@ use acadrust::{EntityType, Spline}; use crate::command::{CadCommand, CmdResult}; use crate::modules::{IconKind, ModuleEvent, ToolDef}; use crate::scene::model::wire_model::WireModel; -use glam::{DVec3, Vec3}; +use glam::DVec3; use truck_modeling::base::{BoundedCurve, ParametricCurve}; use truck_modeling::{BSplineCurve, KnotVec, Point3}; @@ -24,7 +24,7 @@ pub fn tool() -> ToolDef { } pub struct SplineCommand { - pts: Vec, + pts: Vec, } impl SplineCommand { @@ -39,7 +39,7 @@ impl SplineCommand { let control_points = self .pts .iter() - .map(|p| Vector3::new(p.x as f64, p.y as f64, p.z as f64)) + .map(|p| Vector3::new(p.x, p.y, p.z)) .collect(); let n = self.pts.len(); // Uniform open knot vector for degree-3 clamped B-spline @@ -77,15 +77,18 @@ fn uniform_knots(n: usize, d: usize) -> Vec { /// built with the same control points + knot vector `build()` commits so the /// preview traces the exact final curve — not the straight control polygon. /// Fewer than 2 points has no curve; the points are returned as-is. -fn sample_curve(pts: &[Vec3]) -> Vec<[f32; 3]> { +fn sample_curve(pts: &[DVec3]) -> Vec<[f32; 3]> { let n = pts.len(); if n < 2 { - return pts.iter().map(|p| [p.x, p.y, p.z]).collect(); + return pts + .iter() + .map(|p| [p.x as f32, p.y as f32, p.z as f32]) + .collect(); } let degree = 3_usize.min(n - 1); let ctrl: Vec = pts .iter() - .map(|p| Point3::new(p.x as f64, p.y as f64, p.z as f64)) + .map(|p| Point3::new(p.x, p.y, p.z)) .collect(); let curve = BSplineCurve::new(KnotVec::from(uniform_knots(n, degree)), ctrl); let (t0, t1) = curve.range_tuple(); @@ -125,7 +128,7 @@ impl CadCommand for SplineCommand { opts } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { self.pts.push(pt); CmdResult::NeedPoint } @@ -170,7 +173,7 @@ impl CadCommand for SplineCommand { } } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); + fn on_mouse_move(&mut self, pt: DVec3) -> Option { if self.pts.is_empty() { return None; } diff --git a/src/modules/draw/draw/wipeout.rs b/src/modules/draw/draw/wipeout.rs index 9fbc5eace..bc300e094 100644 --- a/src/modules/draw/draw/wipeout.rs +++ b/src/modules/draw/draw/wipeout.rs @@ -7,7 +7,7 @@ use acadrust::entities::Wipeout; use acadrust::types::Vector3; use acadrust::EntityType; -use glam::{DVec3, Vec3}; +use glam::DVec3; use crate::command::{CadCommand, CmdResult}; use crate::modules::{IconKind, ModuleEvent, ToolDef}; @@ -26,8 +26,8 @@ pub fn tool() -> ToolDef { pub struct WipeoutCommand { mode: WipeoutMode, - first: Option, - poly_pts: Vec, + first: Option, + poly_pts: Vec, } enum WipeoutMode { @@ -79,7 +79,7 @@ impl CadCommand for WipeoutCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { match &self.mode { WipeoutMode::Rectangular => { if let Some(p1) = self.first { @@ -110,7 +110,7 @@ impl CadCommand for WipeoutCommand { fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); match &self.mode { WipeoutMode::Rectangular => { - let p1 = self.first?; + let p1 = self.first?.as_vec3(); let min = p1.min(pt); let max = p1.max(pt); Some(WireModel { @@ -144,10 +144,17 @@ impl CadCommand for WipeoutCommand { if self.poly_pts.is_empty() { return None; } - let mut pts: Vec<[f32; 3]> = - self.poly_pts.iter().map(|p| [p.x, p.y, p.z]).collect(); + let mut pts: Vec<[f32; 3]> = self + .poly_pts + .iter() + .map(|p| [p.x as f32, p.y as f32, p.z as f32]) + .collect(); pts.push([pt.x, pt.y, pt.z]); - pts.push([self.poly_pts[0].x, self.poly_pts[0].y, self.poly_pts[0].z]); + pts.push([ + self.poly_pts[0].x as f32, + self.poly_pts[0].y as f32, + self.poly_pts[0].z as f32, + ]); Some(WireModel { text_verts: Vec::new(), name: "wipeout_poly_preview".into(), @@ -173,20 +180,17 @@ impl CadCommand for WipeoutCommand { } } -fn make_rect_wipeout(p1: Vec3, p2: Vec3) -> EntityType { +fn make_rect_wipeout(p1: DVec3, p2: DVec3) -> EntityType { // Drawing plane is world XY (z = elevation). - let c1 = Vector3::new(p1.x.min(p2.x) as f64, p1.y.min(p2.y) as f64, p1.z as f64); - let c2 = Vector3::new(p1.x.max(p2.x) as f64, p1.y.max(p2.y) as f64, p1.z as f64); + let c1 = Vector3::new(p1.x.min(p2.x), p1.y.min(p2.y), p1.z); + let c2 = Vector3::new(p1.x.max(p2.x), p1.y.max(p2.y), p1.z); EntityType::Wipeout(Wipeout::from_corners(c1, c2)) } -fn make_poly_wipeout(pts: &[Vec3]) -> EntityType { +fn make_poly_wipeout(pts: &[DVec3]) -> EntityType { use acadrust::types::Vector2; - let z = pts[0].z as f64; - let verts: Vec = pts - .iter() - .map(|p| Vector2::new(p.x as f64, p.y as f64)) - .collect(); + let z = pts[0].z; + let verts: Vec = pts.iter().map(|p| Vector2::new(p.x, p.y)).collect(); EntityType::Wipeout(Wipeout::polygonal(&verts, z)) } diff --git a/src/modules/draw/modify/align.rs b/src/modules/draw/modify/align.rs index f94befb1f..389f8f7ce 100644 --- a/src/modules/draw/modify/align.rs +++ b/src/modules/draw/modify/align.rs @@ -10,17 +10,17 @@ // With 2 pairs: translate + rotate (+ optional uniform scale to fit) use acadrust::Handle; -use glam::{DVec3, Vec3}; +use glam::DVec3; use crate::command::{CadCommand, CmdResult, EntityTransform}; pub struct AlignCommand { state: AlignState, handles: Vec, - src1: Option, - dst1: Option, - src2: Option, - dst2: Option, + src1: Option, + dst1: Option, + src2: Option, + dst2: Option, } #[derive(PartialEq)] @@ -74,7 +74,7 @@ impl CadCommand for AlignCommand { CmdResult::NeedPoint } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.state { AlignState::Gathering => CmdResult::NeedPoint, AlignState::Src1 => { @@ -117,7 +117,7 @@ impl CadCommand for AlignCommand { let delta = d - s; CmdResult::TransformSelected( self.handles.clone(), - EntityTransform::Translate(delta.as_dvec3()), + EntityTransform::Translate(delta), ) } _ => CmdResult::Cancel, @@ -163,7 +163,7 @@ impl AlignCommand { let delta = d1 - s1; return CmdResult::TransformSelected( self.handles.clone(), - EntityTransform::Translate(delta.as_dvec3()), + EntityTransform::Translate(delta), ); } @@ -182,10 +182,12 @@ impl AlignCommand { // Compose via AlignTransform CmdResult CmdResult::AlignSelected { handles: self.handles.clone(), - src1: s1.as_dvec3(), - dst1: d1.as_dvec3(), - angle_rad: angle, - scale: scale_factor, + src1: s1, + dst1: d1, + // Reference points (src1/dst1) stay f64 exact; the derived + // similarity scalars feed the f32 EntityTransform, so downcast here. + angle_rad: angle as f32, + scale: scale_factor as f32, } } } diff --git a/src/modules/draw/modify/array.rs b/src/modules/draw/modify/array.rs index 9bea9927d..09ec63140 100644 --- a/src/modules/draw/modify/array.rs +++ b/src/modules/draw/modify/array.rs @@ -14,7 +14,7 @@ // 1. Center point → 2. Item count (text) → 3. Total angle in degrees (text) use acadrust::Handle; -use glam::{DVec3, Mat4, Vec3}; +use glam::{DVec3, Mat4}; use crate::command::{CadCommand, CmdResult, EntityTransform}; use crate::modules::draw::defaults; @@ -246,8 +246,8 @@ impl CadCommand for ArrayRectCommand { enum PolarStep { Center, - Count { center: Vec3 }, - Angle { center: Vec3, count: u32 }, + Count { center: DVec3 }, + Angle { center: DVec3, count: u32 }, } pub struct ArrayPolarCommand { @@ -331,7 +331,6 @@ impl CadCommand for ArrayPolarCommand { v }; let step_rad = total_deg.to_radians() / count as f32; - let center = center.as_dvec3(); let transforms = (1..count) .map(|n| EntityTransform::Rotate { center, @@ -348,8 +347,12 @@ impl CadCommand for ArrayPolarCommand { let pt = pt.as_vec3(); let (center, count, total_deg) = match &self.step { PolarStep::Center => (pt, self.default_count, self.default_angle), - PolarStep::Count { center } => (*center, self.default_count, self.default_angle), - PolarStep::Angle { center, count } => (*center, *count, self.default_angle), + PolarStep::Count { center } => { + (center.as_vec3(), self.default_count, self.default_angle) + } + PolarStep::Angle { center, count } => { + (center.as_vec3(), *count, self.default_angle) + } }; let step_rad = total_deg.to_radians() / count as f32; let mut out: Vec = (1..count) @@ -373,7 +376,6 @@ impl CadCommand for ArrayPolarCommand { } fn on_point(&mut self, pt: DVec3) -> CmdResult { - let pt = pt.as_vec3(); if let PolarStep::Center = self.step { self.step = PolarStep::Count { center: pt }; } @@ -398,8 +400,8 @@ impl CadCommand for ArrayPolarCommand { // → Returns BatchCopy with Translate transforms derived from path samples. use acadrust::EntityType; -use std::f32::consts::PI as FPI; -use std::f32::consts::TAU as FTAU; +use std::f64::consts::PI as FPI; +use std::f64::consts::TAU as FTAU; // ── Path geometry helpers ────────────────────────────────────────────────── @@ -408,7 +410,7 @@ use std::f32::consts::TAU as FTAU; /// sweep) and every point is mapped OCS→WCS via the polyline's normal. The /// previous ad-hoc bulge math placed the arc centre on the wrong side of the /// chord and ignored the extrusion, so arc-segment paths came out mirrored. -fn lw_dense_pts(p: &acadrust::entities::LwPolyline) -> Vec { +fn lw_dense_pts(p: &acadrust::entities::LwPolyline) -> Vec { use crate::entities::common::BulgeArc; use crate::scene::view::transform::ocs_point_to_wcs; @@ -418,14 +420,14 @@ fn lw_dense_pts(p: &acadrust::entities::LwPolyline) -> Vec { } let normal = (p.normal.x, p.normal.y, p.normal.z); let elev = p.elevation; - let to_wcs = |x: f64, y: f64| -> Vec3 { + let to_wcs = |x: f64, y: f64| -> DVec3 { let (wx, wy, wz) = ocs_point_to_wcs((x, y, elev), normal); - Vec3::new(wx as f32, wy as f32, wz as f32) + DVec3::new(wx, wy, wz) }; let n = verts.len(); let segs = if p.is_closed { n } else { n.saturating_sub(1) }; - let mut out: Vec = vec![]; + let mut out: Vec = vec![]; for i in 0..segs { let v0 = &verts[i]; @@ -456,15 +458,15 @@ fn lw_dense_pts(p: &acadrust::entities::LwPolyline) -> Vec { } /// Walk `pts` (ordered) and return `count` points at equal arc-length spacing. -fn subsample_equidistant(pts: &[Vec3], count: usize) -> Vec { +fn subsample_equidistant(pts: &[DVec3], count: usize) -> Vec { if count == 0 { return vec![]; } if pts.len() < 2 { - return vec![pts.first().copied().unwrap_or(Vec3::ZERO); count]; + return vec![pts.first().copied().unwrap_or(DVec3::ZERO); count]; } - let mut cum = vec![0.0f32; pts.len()]; + let mut cum = vec![0.0f64; pts.len()]; for i in 1..pts.len() { cum[i] = cum[i - 1] + pts[i].distance(pts[i - 1]); } @@ -477,7 +479,7 @@ fn subsample_equidistant(pts: &[Vec3], count: usize) -> Vec { let mut seg = 0usize; for i in 0..count { let target = if count > 1 { - total * i as f32 / (count - 1) as f32 + total * i as f64 / (count - 1) as f64 } else { 0.0 }; @@ -512,7 +514,7 @@ pub struct ArrayPathCommand { /// path end nearest this point, so clicking near either end picks the /// travel direction (a stored arc is always CCW regardless of how it was /// drawn, so without this the array could run opposite to expectation). - pick_pt: Vec3, + pick_pt: DVec3, } impl ArrayPathCommand { @@ -527,13 +529,13 @@ impl ArrayPathCommand { all_entities, step: PathStep::SelectPath, default_count: defaults::get_array_path_count() as u32, - pick_pt: Vec3::ZERO, + pick_pt: DVec3::ZERO, } } /// Sample the path and orient the points so index 0 is the end nearest the /// pick point, giving the user control over the array's travel direction. - fn oriented_samples(&self, entity: &EntityType, count: usize) -> Vec { + fn oriented_samples(&self, entity: &EntityType, count: usize) -> Vec { let mut pts = Self::sample_path(entity, count); if pts.len() >= 2 { let d_first = self.pick_pt.distance_squared(pts[0]); @@ -546,16 +548,16 @@ impl ArrayPathCommand { } /// Sample `count` evenly-spaced points along `entity`. - fn sample_path(entity: &EntityType, count: usize) -> Vec { + fn sample_path(entity: &EntityType, count: usize) -> Vec { if count == 0 { return vec![]; } match entity { EntityType::Line(l) => { - let p0 = Vec3::new(l.start.x as f32, l.start.y as f32, 0.0); - let p1 = Vec3::new(l.end.x as f32, l.end.y as f32, 0.0); - let d = (count - 1).max(1) as f32; - (0..count).map(|i| p0.lerp(p1, i as f32 / d)).collect() + let p0 = DVec3::new(l.start.x, l.start.y, 0.0); + let p1 = DVec3::new(l.end.x, l.end.y, 0.0); + let d = (count - 1).max(1) as f64; + (0..count).map(|i| p0.lerp(p1, i as f64 / d)).collect() } EntityType::Arc(a) => { // Sample in WCS via the arc's OCS basis so flipped-normal arcs @@ -576,10 +578,10 @@ impl ArrayPathCommand { .map(|i| { let ang = sa + span * (i as f64 / d); let (c, s) = (ang.cos(), ang.sin()); - Vec3::new( - (cx + r * c * ax.0 + r * s * ay.0) as f32, - (cy + r * c * ax.1 + r * s * ay.1) as f32, - (cz + r * c * ax.2 + r * s * ay.2) as f32, + DVec3::new( + cx + r * c * ax.0 + r * s * ay.0, + cy + r * c * ax.1 + r * s * ay.1, + cz + r * c * ax.2 + r * s * ay.2, ) }) .collect() @@ -595,25 +597,25 @@ impl ArrayPathCommand { .map(|i| { let ang = i as f64 / count as f64 * std::f64::consts::TAU; let (cs, sn) = (ang.cos(), ang.sin()); - Vec3::new( - (cx + r * cs * ax.0 + r * sn * ay.0) as f32, - (cy + r * cs * ax.1 + r * sn * ay.1) as f32, - (cz + r * cs * ax.2 + r * sn * ay.2) as f32, + DVec3::new( + cx + r * cs * ax.0 + r * sn * ay.0, + cy + r * cs * ax.1 + r * sn * ay.1, + cz + r * cs * ax.2 + r * sn * ay.2, ) }) .collect() } EntityType::LwPolyline(p) => subsample_equidistant(&lw_dense_pts(p), count), - _ => vec![Vec3::ZERO; count], + _ => vec![DVec3::ZERO; count], } } /// Continuous path-tangent angle (XY) at each sample, via central /// differences and unwrapped so the sequence stays smooth across an arc /// that turns more than ±π in total. - fn tangents(pts: &[Vec3]) -> Vec { + fn tangents(pts: &[DVec3]) -> Vec { let n = pts.len(); - let mut a = vec![0.0f32; n]; + let mut a = vec![0.0f64; n]; for i in 0..n { let prev = if i > 0 { pts[i - 1] } else { pts[i] }; let next = if i + 1 < n { pts[i + 1] } else { pts[i] }; @@ -645,7 +647,7 @@ impl ArrayPathCommand { /// orientation; each copy rotates by the tangent change at its sample). /// On a straight path the tangent never changes, so every copy is a pure /// `Translate` — identical to a non-aligned array. - fn build_transforms(pts: &[Vec3]) -> Vec { + fn build_transforms(pts: &[DVec3]) -> Vec { if pts.len() < 2 { return vec![]; } @@ -658,14 +660,14 @@ impl ArrayPathCommand { .map(|(i, &p)| { let dth = tans[i] - t0; if dth.abs() < 1e-5 { - EntityTransform::Translate((p - p0).as_dvec3()) + EntityTransform::Translate(p - p0) } else { // The aligned copy is the rigid motion x' = p + R(x - p0), // which is a pure rotation by `dth` about its fixed point. let center = Self::rigid_center(p0, p, dth); EntityTransform::Rotate { - center: center.as_dvec3(), - angle_rad: dth, + center, + angle_rad: dth as f32, } } }) @@ -674,7 +676,7 @@ impl ArrayPathCommand { /// Fixed point of the rigid motion `x' = pi + R(dth)·(x - p0)`; rotating an /// entity by `dth` about this point reproduces that motion exactly. - fn rigid_center(p0: Vec3, pi: Vec3, dth: f32) -> Vec3 { + fn rigid_center(p0: DVec3, pi: DVec3, dth: f64) -> DVec3 { let (s, c) = dth.sin_cos(); // b = pi - R·p0 let bx = pi.x - (c * p0.x - s * p0.y); @@ -682,7 +684,7 @@ impl ArrayPathCommand { // c = (I - R)^{-1} · b, with (I-R)^{-1} = 1/(2(1-c)) [[1-c, -s],[s, 1-c]] let one_c = 1.0 - c; let det = 2.0 * one_c; - Vec3::new( + DVec3::new( (one_c * bx - s * by) / det, (s * bx + one_c * by) / det, p0.z, @@ -724,7 +726,6 @@ impl CadCommand for ArrayPathCommand { } fn on_entity_pick(&mut self, handle: Handle, pt: DVec3) -> CmdResult { - let pt = pt.as_vec3(); if handle.is_null() || self.handles.contains(&handle) { return CmdResult::NeedPoint; } @@ -758,7 +759,9 @@ impl CadCommand for ArrayPathCommand { if pts.len() >= 2 { return vec![WireModel::solid( "arraypath_hover".into(), - pts.iter().map(|p| [p.x, p.y, p.z]).collect(), + pts.iter() + .map(|p| [p.x as f32, p.y as f32, p.z as f32]) + .collect(), WireModel::CYAN, false, )]; diff --git a/src/modules/draw/modify/break_cmd.rs b/src/modules/draw/modify/break_cmd.rs index ccb57444b..067606f7e 100644 --- a/src/modules/draw/modify/break_cmd.rs +++ b/src/modules/draw/modify/break_cmd.rs @@ -15,7 +15,7 @@ use acadrust::entities::{ }; use acadrust::types::Vector3; use acadrust::{EntityType, Handle}; -use glam::{DVec3, Vec3}; +use glam::DVec3; use truck_modeling::base::{BoundedCurve, Cut}; use crate::command::{CadCommand, CmdResult}; @@ -38,7 +38,7 @@ pub fn tool() -> ToolDef { /// Break `entity` between world-space points `p1` and `p2`. /// Returns the replacement entities (empty vec means "erase, no replacement"). -pub fn break_entity(entity: &EntityType, p1: Vec3, p2: Vec3) -> Option> { +pub fn break_entity(entity: &EntityType, p1: DVec3, p2: DVec3) -> Option> { match entity { EntityType::Line(line) => Some(break_line(line, p1, p2)), EntityType::Arc(arc) => Some(break_arc(arc, p1, p2)), @@ -50,13 +50,9 @@ pub fn break_entity(entity: &EntityType, p1: Vec3, p2: Vec3) -> Option Vec { - let s = Vec3::new( - line.start.x as f32, - line.start.y as f32, - line.start.z as f32, - ); - let e = Vec3::new(line.end.x as f32, line.end.y as f32, line.end.z as f32); +fn break_line(line: &LineEnt, p1: DVec3, p2: DVec3) -> Vec { + let s = DVec3::new(line.start.x, line.start.y, line.start.z); + let e = DVec3::new(line.end.x, line.end.y, line.end.z); let dir = e - s; let len2 = dir.length_squared(); if len2 < 1e-12 { @@ -94,17 +90,17 @@ fn break_line(line: &LineEnt, p1: Vec3, p2: Vec3) -> Vec { result } -fn break_arc(arc: &ArcEnt, p1: Vec3, p2: Vec3) -> Vec { - let cx = arc.center.x as f32; - let cy = arc.center.y as f32; - let r = arc.radius as f32; +fn break_arc(arc: &ArcEnt, p1: DVec3, p2: DVec3) -> Vec { + let cx = arc.center.x; + let cy = arc.center.y; + let r = arc.radius; // Project p1 and p2 onto the arc (world XY plane) let a1 = angle_on_arc(cx, cy, p1); let a2 = angle_on_arc(cx, cy, p2); - let start = arc.start_angle as f32; - let end = arc.end_angle as f32; + let start = arc.start_angle; + let end = arc.end_angle; // Normalize: clamp a1 to arc range, then remove CCW from a1 to a2 let a1_on = clamp_to_arc(a1, start, end); @@ -120,14 +116,14 @@ fn break_arc(arc: &ArcEnt, p1: Vec3, p2: Vec3) -> Vec { let _ = r; // radius unchanged let mut result = arc.clone(); result.common.handle = Handle::NULL; - result.start_angle = a2_on as f64; - result.end_angle = a1_on as f64; + result.start_angle = a2_on; + result.end_angle = a1_on; vec![EntityType::Arc(result)] } -fn break_circle(circle: &acadrust::entities::Circle, p1: Vec3, p2: Vec3) -> Vec { - let cx = circle.center.x as f32; - let cy = circle.center.y as f32; +fn break_circle(circle: &acadrust::entities::Circle, p1: DVec3, p2: DVec3) -> Vec { + let cx = circle.center.x; + let cy = circle.center.y; let a1 = angle_on_arc(cx, cy, p1); let a2 = angle_on_arc(cx, cy, p2); @@ -143,12 +139,12 @@ fn break_circle(circle: &acadrust::entities::Circle, p1: Vec3, p2: Vec3) -> Vec< arc.center = circle.center.clone(); arc.radius = circle.radius; arc.normal = circle.normal.clone(); - arc.start_angle = a2 as f64; - arc.end_angle = a1 as f64; + arc.start_angle = a2; + arc.end_angle = a1; vec![EntityType::Arc(arc)] } -fn break_lwpolyline(p: &LwPolyline, p1: Vec3, p2: Vec3) -> Vec { +fn break_lwpolyline(p: &LwPolyline, p1: DVec3, p2: DVec3) -> Vec { // For LwPolyline, find the nearest vertex indices for p1 and p2, // then split into two polylines at those vertices. let n = p.vertices.len(); @@ -191,7 +187,7 @@ fn break_lwpolyline(p: &LwPolyline, p1: Vec3, p2: Vec3) -> Vec { } } -fn break_ellipse(ell: &EllipseEnt, p1: Vec3, p2: Vec3) -> Vec { +fn break_ellipse(ell: &EllipseEnt, p1: DVec3, p2: DVec3) -> Vec { // Compute the eccentric-anomaly parameter of a world point relative to ellipse. // World = DXF (XY drawing plane). let cx = ell.center.x; @@ -205,9 +201,9 @@ fn break_ellipse(ell: &EllipseEnt, p1: Vec3, p2: Vec3) -> Vec { let ny = ell.major_axis.y / a; // Project a point onto the ellipse parameter (eccentric anomaly) - let param_of = |pt: Vec3| -> f64 { - let rx = pt.x as f64 - cx; - let ry = pt.y as f64 - cy; + let param_of = |pt: DVec3| -> f64 { + let rx = pt.x - cx; + let ry = pt.y - cy; let xl = rx * nx + ry * ny; let yl = -rx * ny + ry * nx; yl.atan2(xl) // atan2(yl/b*b, xl/a*a) simplifies to atan2(yl,xl) for ordering @@ -257,19 +253,19 @@ fn break_ellipse(ell: &EllipseEnt, p1: Vec3, p2: Vec3) -> Vec { // ── Small utilities ──────────────────────────────────────────────────────── /// Returns the angle (radians, 0-2π) of `pt` viewed from (cx, cy) in the world XY plane. -fn angle_on_arc(cx: f32, cy: f32, pt: Vec3) -> f32 { +fn angle_on_arc(cx: f64, cy: f64, pt: DVec3) -> f64 { let dx = pt.x - cx; let dy = pt.y - cy; - dy.atan2(dx).rem_euclid(std::f32::consts::TAU) + dy.atan2(dx).rem_euclid(std::f64::consts::TAU) } /// Clamp angle `a` to within the arc's angular range (CCW from `start` to `end`). -fn clamp_to_arc(a: f32, start: f32, end: f32) -> f32 { - let span = (end - start).rem_euclid(std::f32::consts::TAU); - let rel = (a - start).rem_euclid(std::f32::consts::TAU); +fn clamp_to_arc(a: f64, start: f64, end: f64) -> f64 { + let span = (end - start).rem_euclid(std::f64::consts::TAU); + let rel = (a - start).rem_euclid(std::f64::consts::TAU); if rel <= span { a - } else if rel < span + (std::f32::consts::TAU - span) / 2.0 { + } else if rel < span + (std::f64::consts::TAU - span) / 2.0 { end } else { start @@ -277,35 +273,35 @@ fn clamp_to_arc(a: f32, start: f32, end: f32) -> f32 { } /// Find the index of the polyline vertex closest to `pt`. -fn nearest_pline_param(p: &LwPolyline, pt: Vec3) -> usize { +fn nearest_pline_param(p: &LwPolyline, pt: DVec3) -> usize { p.vertices .iter() .enumerate() .min_by_key(|(_, v)| { - let dx = v.location.x as f32 - pt.x; - let dy = v.location.y as f32 - pt.y; + let dx = v.location.x - pt.x; + let dy = v.location.y - pt.y; ((dx * dx + dy * dy) * 1e6) as i64 }) .map(|(i, _)| i) .unwrap_or(0) } -fn world_to_dxf(v: Vec3) -> Vec3 { +fn world_to_dxf(v: DVec3) -> DVec3 { // World = DXF (identity). - Vec3::new(v.x, v.y, v.z) + DVec3::new(v.x, v.y, v.z) } -fn vec3_to_v3(v: Vec3) -> Vector3 { - Vector3::new(v.x as f64, v.y as f64, v.z as f64) +fn vec3_to_v3(v: DVec3) -> Vector3 { + Vector3::new(v.x, v.y, v.z) } -fn break_spline(spl: &SplineEnt, p1: Vec3, p2: Vec3) -> Vec { +fn break_spline(spl: &SplineEnt, p1: DVec3, p2: DVec3) -> Vec { // Find the two nearest parameters to p1 and p2 (DXF XY: world x, z). - let t1 = match spline_nearest_t(spl, p1.x as f64, p1.y as f64) { + let t1 = match spline_nearest_t(spl, p1.x, p1.y) { Some(t) => t, None => return vec![EntityType::Spline(spl.clone())], }; - let t2 = match spline_nearest_t(spl, p2.x as f64, p2.y as f64) { + let t2 = match spline_nearest_t(spl, p2.x, p2.y) { Some(t) => t, None => return vec![EntityType::Spline(spl.clone())], }; @@ -351,7 +347,7 @@ fn break_spline(spl: &SplineEnt, p1: Vec3, p2: Vec3) -> Vec { /// BreakEntity CmdResult variant added below. pub struct BreakInteractiveCommand { target: Option, - p1: Option, + p1: Option, } impl BreakInteractiveCommand { @@ -382,7 +378,7 @@ impl CadCommand for BreakInteractiveCommand { self.target.is_none() } - fn on_entity_pick(&mut self, handle: Handle, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_entity_pick(&mut self, handle: Handle, pt: DVec3) -> CmdResult { if handle.is_null() { return CmdResult::NeedPoint; } @@ -391,7 +387,7 @@ impl CadCommand for BreakInteractiveCommand { CmdResult::NeedPoint } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { let handle = match self.target { Some(h) => h, None => return CmdResult::Cancel, @@ -403,7 +399,7 @@ impl CadCommand for BreakInteractiveCommand { return CmdResult::NeedPoint; } }; - CmdResult::BreakEntity { handle, p1: p1.as_dvec3(), p2: pt.as_dvec3() } + CmdResult::BreakEntity { handle, p1, p2: pt } } fn on_enter(&mut self) -> CmdResult { @@ -452,12 +448,12 @@ impl CadCommand for BreakAtPointCommand { CmdResult::NeedPoint } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.target { Some(handle) => CmdResult::BreakEntity { handle, - p1: pt.as_dvec3(), - p2: pt.as_dvec3(), + p1: pt, + p2: pt, }, None => CmdResult::Cancel, } diff --git a/src/modules/draw/modify/lengthen.rs b/src/modules/draw/modify/lengthen.rs index 971f9e63c..c2091b266 100644 --- a/src/modules/draw/modify/lengthen.rs +++ b/src/modules/draw/modify/lengthen.rs @@ -159,11 +159,16 @@ pub fn lengthen_entity(entity: &EntityType, pick_pt: Vec3, mode: &LenMode) -> Op } fn lengthen_line(line: &LineEnt, pick_pt: Vec3, mode: &LenMode) -> Option { - let s = Vec3::new(line.start.x as f32, line.start.y as f32, 0.0); - let e = Vec3::new(line.end.x as f32, line.end.y as f32, 0.0); - let p = Vec3::new(pick_pt.x, pick_pt.y, 0.0); + // Keep the line's own coordinates on the f64 grid; only pick_pt (screen-only, + // nearest-end selection) widens for the distance compare. + let sx = line.start.x; + let sy = line.start.y; + let ex = line.end.x; + let ey = line.end.y; - let current_len = (e - s).length() as f64; + let dx = ex - sx; + let dy = ey - sy; + let current_len = (dx * dx + dy * dy).sqrt(); if current_len < 1e-10 { return None; } @@ -173,23 +178,28 @@ fn lengthen_line(line: &LineEnt, pick_pt: Vec3, mode: &LenMode) -> Option f64 { } } -fn xz_to_v3(v: Vec3, z: f64) -> Vector3 { - // v is (world_x, world_z, 0) → DXF (x, world_z, z) - Vector3::new(v.x as f64, v.y as f64, z) -} - fn lengthen_lwpoly(poly: &LwPolyline, pick_pt: Vec3, mode: &LenMode) -> Option { let n = poly.vertices.len(); if n < 2 { diff --git a/src/modules/draw/modify/stretch.rs b/src/modules/draw/modify/stretch.rs index a2a80a6b2..53c5d52f1 100644 --- a/src/modules/draw/modify/stretch.rs +++ b/src/modules/draw/modify/stretch.rs @@ -16,7 +16,7 @@ // All others : move the whole entity if any point is inside. use acadrust::Handle; -use glam::{DVec3, Vec3}; +use glam::DVec3; use crate::command::{CadCommand, CmdResult}; use crate::modules::{IconKind, ModuleEvent, ToolDef}; @@ -39,14 +39,14 @@ enum Step { /// Waiting for the first crossing-window corner. WindowCorner1, /// Waiting for the second corner; `c1` is the first corner. - WindowCorner2(Vec3), + WindowCorner2(DVec3), /// Crossing window defined; waiting for base point. - Base { win_min: Vec3, win_max: Vec3 }, + Base { win_min: DVec3, win_max: DVec3 }, /// Waiting for target point. Target { - win_min: Vec3, - win_max: Vec3, - base: Vec3, + win_min: DVec3, + win_max: DVec3, + base: DVec3, }, } @@ -86,7 +86,7 @@ impl CadCommand for StretchCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { match &self.step { Step::WindowCorner1 => { self.step = Step::WindowCorner2(pt); @@ -115,9 +115,9 @@ impl CadCommand for StretchCommand { let delta = pt - *base; CmdResult::StretchEntities { handles: self.handles.clone(), - win_min: win_min.as_dvec3(), - win_max: win_max.as_dvec3(), - delta: delta.as_dvec3(), + win_min: *win_min, + win_max: *win_max, + delta, } } } @@ -140,12 +140,12 @@ impl CadCommand for StretchCommand { // Expose the first corner so the host draws a filled crossing marquee to // the cursor, matching a normal box selection instead of a bare outline. match &self.step { - Step::WindowCorner2(c1) => Some(c1.as_dvec3()), + Step::WindowCorner2(c1) => Some(*c1), _ => None, } } - fn on_preview_wires(&mut self, pt: DVec3) -> Vec { let pt = pt.as_vec3(); + fn on_preview_wires(&mut self, pt: DVec3) -> Vec { match &self.step { // The crossing-window rectangle is drawn as a filled selection // marquee by the host (via window_first_corner) so it matches a @@ -158,15 +158,19 @@ impl CadCommand for StretchCommand { } => { let delta = pt - *base; // Live ghost: vertices inside the crossing window follow the - // cursor, the rest stay anchored. + // cursor, the rest stay anchored. This is the preview/GPU path, + // so downcast to f32 only at the WireModel boundary. let mut out: Vec = self .wire_models .iter() - .map(|w| w.stretched(*win_min, *win_max, delta)) + .map(|w| w.stretched((*win_min).as_vec3(), (*win_max).as_vec3(), delta.as_vec3())) .collect(); out.push(WireModel::solid( "rubber_band".into(), - vec![[base.x, base.y, base.z], [pt.x, pt.y, pt.z]], + vec![ + [base.x as f32, base.y as f32, base.z as f32], + [pt.x as f32, pt.y as f32, pt.z as f32], + ], WireModel::CYAN, false, )); diff --git a/src/modules/draw/modify/torient.rs b/src/modules/draw/modify/torient.rs index 87b07c99f..71691bc93 100644 --- a/src/modules/draw/modify/torient.rs +++ b/src/modules/draw/modify/torient.rs @@ -6,7 +6,7 @@ // Step 2: If first point picked, pick second point to define angle vector. use acadrust::Handle; -use glam::{DVec3, Vec3}; +use glam::DVec3; use crate::command::{CadCommand, CmdResult, DynField}; use crate::scene::model::wire_model::WireModel; @@ -16,7 +16,7 @@ use acadrust::EntityType; enum Step { AngleOrFirstPoint, - SecondPoint { first_point: Vec3 }, + SecondPoint { first_point: DVec3 }, } pub struct TorientCommand { @@ -122,7 +122,7 @@ impl CadCommand for TorientCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { Step::AngleOrFirstPoint => { self.step = Step::SecondPoint { first_point: pt }; @@ -131,7 +131,7 @@ impl CadCommand for TorientCommand { Step::SecondPoint { first_point } => { let dx = pt.x - first_point.x; let dy = pt.y - first_point.y; - let angle = dy.atan2(dx) as f64; + let angle = dy.atan2(dx); self.commit_angle(Some(angle)) } } @@ -157,8 +157,10 @@ impl CadCommand for TorientCommand { } } - fn on_preview_wires(&mut self, pt: DVec3) -> Vec { let pt = pt.as_vec3(); + fn on_preview_wires(&mut self, pt: DVec3) -> Vec { + let pt = pt.as_vec3(); if let Step::SecondPoint { first_point } = self.step { + let first_point = first_point.as_vec3(); vec![WireModel::solid( "rubber_band".into(), vec![[first_point.x, first_point.y, first_point.z], [pt.x, pt.y, pt.z]], diff --git a/src/modules/insert/insert_block.rs b/src/modules/insert/insert_block.rs index f8941eb1b..d0f8d6d71 100644 --- a/src/modules/insert/insert_block.rs +++ b/src/modules/insert/insert_block.rs @@ -135,14 +135,11 @@ impl CadCommand for InsertBlockCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { match &self.step { Step::Name => CmdResult::NeedPoint, Step::Point { name } => { - let mut ins = Insert::new( - name.clone(), - Vector3::new(pt.x as f64, pt.y as f64, pt.z as f64), - ); + let mut ins = Insert::new(name.clone(), Vector3::new(pt.x, pt.y, pt.z)); ins.set_x_scale(self.x_scale); ins.set_y_scale(self.y_scale); ins.rotation = self.rotation_rad; diff --git a/src/modules/insert/solid3d_cmds.rs b/src/modules/insert/solid3d_cmds.rs index b1a816104..9ed1f6124 100644 --- a/src/modules/insert/solid3d_cmds.rs +++ b/src/modules/insert/solid3d_cmds.rs @@ -7,7 +7,7 @@ // live in the Model tab (`modules::model::primitive_cmd`). use acadrust::{entities::Solid3D, EntityType}; -use glam::{DVec3, Vec3}; +use glam::DVec3; use crate::command::{CadCommand, CmdResult}; @@ -95,8 +95,8 @@ impl CadCommand for ExtrudeCommand { pub struct RevolveCommand { step: RevolveStep, target_handle: acadrust::Handle, - axis_start: Vec3, - axis_end: Vec3, + axis_start: DVec3, + axis_end: DVec3, color: [f32; 4], } @@ -113,8 +113,8 @@ impl RevolveCommand { Self { step: RevolveStep::Pick, target_handle: acadrust::Handle::NULL, - axis_start: Vec3::ZERO, - axis_end: Vec3::new(0.0, 0.0, 1.0), + axis_start: DVec3::ZERO, + axis_end: DVec3::new(0.0, 0.0, 1.0), color, } } @@ -143,7 +143,7 @@ impl CadCommand for RevolveCommand { self.step = RevolveStep::AxisStart; CmdResult::NeedPoint } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { RevolveStep::AxisStart => { self.axis_start = pt; @@ -186,8 +186,8 @@ impl RevolveCommand { fn make_revolve(&self, angle_deg: f32) -> CmdResult { CmdResult::RevolveEntity { handle: self.target_handle, - axis_start: self.axis_start.as_dvec3(), - axis_end: self.axis_end.as_dvec3(), + axis_start: self.axis_start, + axis_end: self.axis_end, angle_deg, color: self.color, } diff --git a/src/modules/layout/mview.rs b/src/modules/layout/mview.rs index b5b0f805c..41c89fa40 100644 --- a/src/modules/layout/mview.rs +++ b/src/modules/layout/mview.rs @@ -11,7 +11,7 @@ use acadrust::EntityType; use crate::command::{CadCommand, CmdResult}; use crate::modules::{IconKind, ModuleEvent, ToolDef}; use crate::scene::model::wire_model::WireModel; -use glam::{DVec3, Vec3}; +use glam::DVec3; // ── Ribbon definition ───────────────────────────────────────────────────── @@ -27,7 +27,7 @@ pub fn tool() -> ToolDef { // ── Command ─────────────────────────────────────────────────────────────── pub struct MviewCommand { - corner1: Option, + corner1: Option, } impl MviewCommand { @@ -49,16 +49,16 @@ impl CadCommand for MviewCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { if let Some(c1) = self.corner1 { - let w = (pt.x - c1.x).abs() as f64; - let h = (pt.y - c1.y).abs() as f64; + let w = (pt.x - c1.x).abs(); + let h = (pt.y - c1.y).abs(); if w < 1.0 || h < 1.0 { return CmdResult::Cancel; } - let cx = ((c1.x + pt.x) / 2.0) as f64; - let cy = ((c1.y + pt.y) / 2.0) as f64; - let cz = c1.z as f64; + let cx = (c1.x + pt.x) / 2.0; + let cy = (c1.y + pt.y) / 2.0; + let cz = c1.z; let mut vp = Viewport::new(); vp.center = Vector3::new(cx, cy, cz); @@ -77,17 +77,18 @@ impl CadCommand for MviewCommand { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); + fn on_mouse_move(&mut self, pt: DVec3) -> Option { + let pt = pt.as_vec3(); let c1 = self.corner1?; Some(WireModel { text_verts: Vec::new(), name: "mview_preview".to_string(), points: vec![ - [c1.x, c1.y, c1.z], - [pt.x, c1.y, c1.z], - [pt.x, pt.y, c1.z], - [c1.x, pt.y, c1.z], - [c1.x, c1.y, c1.z], + [c1.x as f32, c1.y as f32, c1.z as f32], + [pt.x, c1.y as f32, c1.z as f32], + [pt.x, pt.y, c1.z as f32], + [c1.x as f32, pt.y, c1.z as f32], + [c1.x as f32, c1.y as f32, c1.z as f32], ], points_low: Vec::new(), color: WireModel::CYAN, diff --git a/src/modules/model/primitive_cmd.rs b/src/modules/model/primitive_cmd.rs index 922156c00..86f0400f7 100644 --- a/src/modules/model/primitive_cmd.rs +++ b/src/modules/model/primitive_cmd.rs @@ -9,7 +9,7 @@ use acadrust::entities::Solid3D; use acadrust::{primitives, EntityType}; -use glam::{DVec3, Vec3}; +use glam::DVec3; use truck_modeling::Solid; use crate::command::{CadCommand, CmdResult}; @@ -63,7 +63,7 @@ impl Shape { pub struct PrimitiveCommand { shape: Shape, /// Footprint points collected so far (local/world XY, z = 0). - pts: Vec, + pts: Vec, /// True once the footprint is set and we are collecting the height. height_step: bool, } @@ -90,9 +90,9 @@ impl PrimitiveCommand { match self.shape { Shape::Box | Shape::Wedge => { let d = self.pts[1] - self.pts[0]; - (d.x.abs().max(d.y.abs())) as f64 + d.x.abs().max(d.y.abs()) } - _ => (self.pts[1] - self.pts[0]).length() as f64, + _ => (self.pts[1] - self.pts[0]).length(), } .max(1.0) } @@ -103,23 +103,19 @@ impl PrimitiveCommand { let (doc, solid) = match self.shape { Shape::Box | Shape::Wedge => { let (a, b) = (self.pts[0], self.pts[1]); - let length = (b.x - a.x).abs() as f64; - let width = (b.y - a.y).abs() as f64; + let length = (b.x - a.x).abs(); + let width = (b.y - a.y).abs(); if length < 1e-6 || width < 1e-6 || height < 1e-6 { return None; } if self.shape == Shape::Box { - let center = [ - (a.x + b.x) as f64 / 2.0, - (a.y + b.y) as f64 / 2.0, - height / 2.0, - ]; + let center = [(a.x + b.x) / 2.0, (a.y + b.y) / 2.0, height / 2.0]; ( primitives::build_box(center, length, width, height), solid_model::box_solid(center, length, width, height), ) } else { - let origin = [a.x.min(b.x) as f64, a.y.min(b.y) as f64, 0.0]; + let origin = [a.x.min(b.x), a.y.min(b.y), 0.0]; ( primitives::build_wedge(origin, length, width, height), solid_model::wedge_solid(origin, length, width, height), @@ -128,11 +124,11 @@ impl PrimitiveCommand { } Shape::Cylinder | Shape::Cone => { let c = self.pts[0]; - let r = (self.pts[1] - c).length() as f64; + let r = (self.pts[1] - c).length(); if r < 1e-6 || height < 1e-6 { return None; } - let center = [c.x as f64, c.y as f64, 0.0]; + let center = [c.x, c.y, 0.0]; if self.shape == Shape::Cylinder { ( primitives::build_cylinder(center, r, height), @@ -147,11 +143,11 @@ impl PrimitiveCommand { } Shape::Sphere => { let c = self.pts[0]; - let r = (self.pts[1] - c).length() as f64; + let r = (self.pts[1] - c).length(); if r < 1e-6 { return None; } - let center = [c.x as f64, c.y as f64, 0.0]; + let center = [c.x, c.y, 0.0]; ( primitives::build_sphere(center, r), solid_model::sphere_solid(center, r), @@ -159,12 +155,12 @@ impl PrimitiveCommand { } Shape::Torus => { let c = self.pts[0]; - let major = (self.pts[1] - c).length() as f64; - let minor = (self.pts[2] - self.pts[1]).length() as f64; + let major = (self.pts[1] - c).length(); + let minor = (self.pts[2] - self.pts[1]).length(); if major < 1e-6 || minor < 1e-6 { return None; } - let center = [c.x as f64, c.y as f64, 0.0]; + let center = [c.x, c.y, 0.0]; ( primitives::build_torus(center, major, minor), solid_model::torus_solid(center, major, minor), @@ -209,11 +205,11 @@ impl CadCommand for PrimitiveCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { if self.height_step { // A click in the ground plane has no Z; use its distance from the // footprint centre as the height magnitude. - let h = (pt - self.pts[0]).length() as f64; + let h = (pt - self.pts[0]).length(); return self.commit(h.max(1e-6)); } self.pts.push(pt); @@ -253,7 +249,7 @@ impl CadCommand for PrimitiveCommand { Some(self.commit(h)) } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); + fn on_mouse_move(&mut self, pt: DVec3) -> Option { if self.height_step || self.pts.is_empty() { return None; } @@ -265,7 +261,7 @@ impl CadCommand for PrimitiveCommand { // ── Footprint preview ─────────────────────────────────────────────────────── -fn footprint_wire(shape: Shape, pts: &[Vec3]) -> WireModel { +fn footprint_wire(shape: Shape, pts: &[DVec3]) -> WireModel { let mut points: Vec<[f32; 3]> = Vec::new(); if shape.radial() { let c = pts[0]; @@ -280,21 +276,25 @@ fn footprint_wire(shape: Shape, pts: &[Vec3]) -> WireModel { } else { let (a, b) = (pts[0], pts[1]); points.extend_from_slice(&[ - [a.x, a.y, 0.0], - [b.x, a.y, 0.0], - [b.x, b.y, 0.0], - [a.x, b.y, 0.0], - [a.x, a.y, 0.0], + [a.x as f32, a.y as f32, 0.0], + [b.x as f32, a.y as f32, 0.0], + [b.x as f32, b.y as f32, 0.0], + [a.x as f32, b.y as f32, 0.0], + [a.x as f32, a.y as f32, 0.0], ]); } wire("primitive_preview", points) } -fn circle_points(out: &mut Vec<[f32; 3]>, c: Vec3, r: f32) { +fn circle_points(out: &mut Vec<[f32; 3]>, c: DVec3, r: f64) { const SEG: usize = 48; for i in 0..=SEG { - let t = i as f32 / SEG as f32 * std::f32::consts::TAU; - out.push([c.x + r * t.cos(), c.y + r * t.sin(), 0.0]); + let t = i as f64 / SEG as f64 * std::f64::consts::TAU; + out.push([ + (c.x + r * t.cos()) as f32, + (c.y + r * t.sin()) as f32, + 0.0, + ]); } } diff --git a/src/modules/view/plot_window.rs b/src/modules/view/plot_window.rs index 2a6c90f33..6ed9ab723 100644 --- a/src/modules/view/plot_window.rs +++ b/src/modules/view/plot_window.rs @@ -5,10 +5,10 @@ use crate::command::{CadCommand, CmdResult}; use crate::scene::model::wire_model::WireModel; -use glam::{DVec3, Vec3}; +use glam::DVec3; pub struct PlotWindowCommand { - p1: Option, + p1: Option, } impl PlotWindowCommand { @@ -30,13 +30,13 @@ impl CadCommand for PlotWindowCommand { } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); + fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.p1 { None => { self.p1 = Some(pt); CmdResult::NeedPoint } - Some(p1) => CmdResult::SetPlotWindow { p1: p1.as_dvec3(), p2: pt.as_dvec3() }, + Some(p1) => CmdResult::SetPlotWindow { p1, p2: pt }, } } @@ -45,7 +45,7 @@ impl CadCommand for PlotWindowCommand { } fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); - let p1 = self.p1?; + let p1 = self.p1?.as_vec3(); // Draw the selection rectangle. Some(WireModel { text_verts: Vec::new(), diff --git a/src/ui/statusbar/mod.rs b/src/ui/statusbar/mod.rs index 02d258c90..ddeadcf6e 100644 --- a/src/ui/statusbar/mod.rs +++ b/src/ui/statusbar/mod.rs @@ -78,12 +78,12 @@ impl StatusBar { // LWDISPLAY header flag — controls lineweight visibility in the viewport. lineweight_display: bool, // Live cursor position in model coordinates, for the coordinate readout. - cursor_world: glam::Vec3, + cursor_world: glam::DVec3, // $COORDS readout mode: 0 = static (updates only on a pick), 1 = live // absolute, 2 = polar (distance, + last_point: Option, // True while a command is prompting for a point (enables the polar readout). picking: bool, // True while clean-screen mode hides the ribbon and side panels. @@ -385,8 +385,8 @@ impl StatusBar { // ── Coordinate readout ──────────────────────────────────────────────────── -fn format_coords(cursor: glam::Vec3, last: Option, mode: i16, picking: bool) -> String { - let abs = |p: glam::Vec3| format!("{:.4}, {:.4}, {:.4}", p.x, p.y, p.z); +fn format_coords(cursor: glam::DVec3, last: Option, mode: i16, picking: bool) -> String { + let abs = |p: glam::DVec3| format!("{:.4}, {:.4}, {:.4}", p.x, p.y, p.z); match mode { // Static: show the last picked point; the readout freezes between picks. 0 => abs(last.unwrap_or(cursor)),