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<Vec3> 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<Vec<[f32;2]>> and needs a model-level widen.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-11 21:26:40 +03:00
1 parent 8ed10ceb2a
commit 57710eec2b
47 files changed
+699 -606

No files matched your search

+18 -10
View File
@@ -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<WireModel> { let pt = pt.as_vec3();
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
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(),
+21 -15
View File
@@ -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<WireModel> { let pt = pt.as_vec3();
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
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<Vec3>) -> WireModel {
+10 -9
View File
@@ -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<WireModel> { let pt = pt.as_vec3();
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
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 {
+25 -15
View File
@@ -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<WireModel> { let pt = pt.as_vec3();
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
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)
}
+24 -14
View File
@@ -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<WireModel> { let pt = pt.as_vec3();
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
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)
}
+1 -1
View File
@@ -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
+2 -3
View File
@@ -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
+16 -16
View File
@@ -31,7 +31,7 @@ pub fn tool() -> ToolDef {
}
pub struct LeaderCommand {
verts: Vec<Vec3>,
verts: Vec<DVec3>,
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<WireModel> { let pt = pt.as_vec3();
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
if self.verts.is_empty() {
return None;
}
let mut pts = self.verts.clone();
pts.push(pt);
let mut pts: Vec<Vec3> = 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.
+22 -19
View File
@@ -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<WireModel> { let pt = pt.as_vec3();
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
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<Vec3>) -> WireModel {
fn preview_wire(points: Vec<DVec3>) -> 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<Vec3>) -> 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<Vec3> {
fn linear_dimension_preview(first: DVec3, second: DVec3, def: DVec3, axis: DVec3) -> Vec<DVec3> {
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
}
+10 -9
View File
@@ -26,7 +26,7 @@ pub fn tool() -> ToolDef {
}
pub struct MLeaderCommand {
verts: Vec<Vec3>,
verts: Vec<DVec3>,
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<WireModel> { let pt = pt.as_vec3();
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
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<Vec3> = 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);
+10 -8
View File
@@ -37,7 +37,7 @@ enum AddStep {
CollectPoints {
handle: Handle,
entity: EntityType,
pts: Vec<Vec3>,
pts: Vec<DVec3>,
},
}
@@ -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<Vector3> = 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<WireModel> { let pt = pt.as_vec3();
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
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::<Vec<_>>(),
))
}
fn inject_picked_entity(&mut self, entity: EntityType) {
@@ -275,7 +277,7 @@ pub fn tool_align() -> ToolDef {
enum AlignStep {
Gathering,
PickDirection { handles: Vec<Handle> },
PickEndDir { handles: Vec<Handle>, from: Vec3 },
PickEndDir { handles: Vec<Handle>, 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.
+15 -10
View File
@@ -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<WireModel> { let pt = pt.as_vec3();
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
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)
}
}
+6 -4
View File
@@ -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();
+12 -11
View File
@@ -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<WireModel> { let pt = pt.as_vec3();
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
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<Vec3>) -> WireModel {
+2 -2
View File
@@ -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)
+2 -2
View File
@@ -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 {