Files
OneCAD-Draft/src/modules/draw/inquiry/dist.rs
T
Kevin GriffinandClaude Opus 4.8 21192011be fix(dist): measure in f64 so snapped endpoints stay precise (#193)
DistCommand stored the first point as Vec3 and ran on_point through
`let pt = pt.as_vec3();`, downcasting the f64 picked point to f32 before
computing distance/deltas. At survey-scale coordinates (eastings ~5e5)
f32's 24-bit mantissa gives ~0.03–0.06 unit resolution, so a snapped
endpoint measured slightly off — matching the report in #193.

Store `first` as DVec3 and keep the math in f64; cast to f32 only for
the visual preview wire vertices.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 09:52:36 -07:00

93 lines
2.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// DIST command — measure distance and angle between two picked points.
use glam::DVec3;
use crate::command::{CadCommand, CmdResult};
use crate::scene::model::wire_model::WireModel;
pub struct DistCommand {
// Keep the picked point in full f64 precision. Downcasting to f32 here
// loses ~0.03–0.06 units at survey-scale coordinates (e.g. eastings near
// 5e5), which made snapped-endpoint measurements read off by that much.
first: Option<DVec3>,
}
impl DistCommand {
pub fn new() -> Self {
Self { first: None }
}
}
impl CadCommand for DistCommand {
fn name(&self) -> &'static str {
"DIST"
}
fn prompt(&self) -> String {
if self.first.is_none() {
"DIST Specify first point:".into()
} else {
"DIST Specify second point:".into()
}
}
fn on_point(&mut self, pt: DVec3) -> CmdResult {
if let Some(p1) = self.first {
let delta = pt - p1;
let dist = delta.length();
let dx = delta.x;
let dy = delta.y; // drawing plane is world XY
let dz = delta.z; // elevation
// Angle in XY plane — degrees from +X
let angle_xy = dy.atan2(dx).to_degrees();
// Angle from XY plane toward Z (elevation angle)
let dist_xy = dx.hypot(dy);
let angle_z = dz.atan2(dist_xy).to_degrees();
let msg = format!(
"Distance = {dist:.4}, Angle in XY Plane = {angle_xy:.4}°, Angle from XY Plane = {angle_z:.4}°\n Delta X = {dx:.4}, Delta Y = {dy:.4}, Delta Z = {dz:.4}",
);
CmdResult::Measurement(msg)
} else {
self.first = Some(pt);
CmdResult::NeedPoint
}
}
fn on_enter(&mut self) -> CmdResult {
CmdResult::Cancel
}
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
let p1 = self.first?;
// The preview wire is purely visual, so f32 vertices are fine here.
Some(WireModel {
name: "dist_preview".into(),
points: vec![
[p1.x as f32, p1.y as f32, p1.z as f32],
[pt.x as f32, pt.y as f32, pt.z as f32],
],
points_low: Vec::new(),
color: WireModel::CYAN,
selected: false,
pattern_length: 0.0,
pattern: [0.0; 8],
line_weight_px: 1.0,
snap_pts: vec![],
tangent_geoms: vec![],
aci: 0,
key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
vp_scissor: None,
fill_tris: vec![],
fill_tris_low: Vec::new(),
})
}
}
// ── Autocomplete registry ─────────────────────────────────
inventory::submit!(crate::command::CommandRegistration { names: &["DI", "DIST"] }); // DistCommand