diff --git a/src/modules/annotate/aligned_dim.rs b/src/modules/annotate/aligned_dim.rs index 3ab9c9c51..d8611aaf8 100644 --- a/src/modules/annotate/aligned_dim.rs +++ b/src/modules/annotate/aligned_dim.rs @@ -61,17 +61,19 @@ impl CadCommand for AlignedDimensionCommand { } Step::DimLine { p1, p2 } => { let mut dim = DimensionAligned::new(v3(p1), v3(p2)); - // Set offset: distance from p2 to the dim line location - let dx = pt.x as f64 - p2.x as f64; - let dy = pt.z as f64 - p2.z as f64; - let offset = (dx * dx + dy * dy).sqrt(); - dim.set_offset(offset); + // The dimension line runs through the cursor: store it as the + // definition point and let the renderer project it onto the + // line perpendicular (same as the preview and DIMLINEAR). + // + // The old path called `set_offset` with the straight-line + // p2→cursor *distance*, which `set_offset` then re-applies along + // the line perpendicular — placing the line at the wrong + // perpendicular distance and always on the +perp side, so it + // never matched the preview the user was dragging. (#150) + dim.definition_point = v3(pt); dim.base.definition_point = v3(pt); - dim.base.text_middle_point = v3(Vec3::new( - (p1.x + p2.x) * 0.5, - (p1.y + p2.y) * 0.5, - (p1.z + p2.z) * 0.5, - )); + let (d1, d2) = dim_line_endpoints(p1, p2, pt); + dim.base.text_middle_point = v3((d1 + d2) * 0.5); dim.base.insertion_point = dim.base.text_middle_point; dim.base.actual_measurement = dim.measurement(); CmdResult::CommitAndExit(EntityType::Dimension(Dimension::Aligned(dim))) @@ -117,13 +119,21 @@ fn v3(p: Vec3) -> Vector3 { Vector3::new(p.x as f64, p.y as f64, p.z as f64) } +/// Dimension-line endpoints: the baseline `p1`–`p2` shifted to pass through +/// the cursor's perpendicular projection. Uses the XY-plane perpendicular so +/// 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) { + let axis = (p2 - p1).normalize_or_zero(); + let perp = Vec3::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 { - // Show ext lines + dim line - let dir = (p2 - p1).normalize_or_zero(); - let perp = Vec3::new(-dir.z, dir.y, dir.x).normalize_or_zero(); - let offset = (dim_pt - p2).dot(perp); - let d1 = p1 + perp * offset; - let d2 = p2 + perp * offset; + // Show ext lines + dim line. + let (d1, d2) = dim_line_endpoints(p1, p2, dim_pt); WireModel { name: "dimaligned_preview".into(), points: vec![ diff --git a/src/modules/annotate/dimjogline.rs b/src/modules/annotate/dimjogline.rs index efbb85071..354694d69 100644 --- a/src/modules/annotate/dimjogline.rs +++ b/src/modules/annotate/dimjogline.rs @@ -86,10 +86,13 @@ impl CadCommand for DimJogLineCommand { let d = 0.3_f32; Some(WireModel { name: "dimjog_preview".into(), + // Jog zigzag in the XY drawing plane (Z is elevation, ~0). The old + // marker varied Z, so in the top-down view it collapsed to a flat + // line instead of a zigzag. (#150) points: vec![ [pt.x - d, pt.y, pt.z], - [pt.x - d * 0.3, pt.y, pt.z + d], - [pt.x + d * 0.3, pt.y, pt.z - d], + [pt.x - d * 0.3, pt.y + d, pt.z], + [pt.x + d * 0.3, pt.y - d, pt.z], [pt.x + d, pt.y, pt.z], ], points_low: Vec::new(), diff --git a/src/modules/annotate/dimtedit.rs b/src/modules/annotate/dimtedit.rs index 2d7bc003c..f4c105472 100644 --- a/src/modules/annotate/dimtedit.rs +++ b/src/modules/annotate/dimtedit.rs @@ -99,12 +99,15 @@ impl CadCommand for DimTeditCommand { let d = 0.2_f32; Some(WireModel { name: "dimtedit_preview".into(), + // Marker box in the XY drawing plane (Z is elevation, ~0). The old + // box varied Z, so in the top-down view it collapsed to a flat line + // instead of a square. (#150) points: vec![ - [pt.x - d, pt.y, pt.z - d], - [pt.x + d, pt.y, pt.z - d], - [pt.x + d, pt.y, pt.z + d], - [pt.x - d, pt.y, pt.z + d], - [pt.x - d, pt.y, pt.z - d], + [pt.x - d, pt.y - d, pt.z], + [pt.x + d, pt.y - d, pt.z], + [pt.x + d, pt.y + d, pt.z], + [pt.x - d, pt.y + d, pt.z], + [pt.x - d, pt.y - d, pt.z], ], points_low: Vec::new(), color: WireModel::CYAN, diff --git a/src/modules/annotate/ordinate_dim.rs b/src/modules/annotate/ordinate_dim.rs index bbe83deac..0e7a8b294 100644 --- a/src/modules/annotate/ordinate_dim.rs +++ b/src/modules/annotate/ordinate_dim.rs @@ -62,14 +62,16 @@ impl CadCommand for OrdinateDimCommand { CmdResult::NeedPoint } Step::LeaderEndpoint { feature } => { - let dx = (pt.x - feature.x).abs(); - let dy = (pt.z - feature.z).abs(); - // If leader is more vertical (Y-screen = Z-world moves more) → X ordinate. - // If leader is more horizontal → Y ordinate. - let is_x = dy >= dx; - let feat_v3 = v3(feature); - let lead_v3 = v3(pt); - let dim = DimensionOrdinate::new(feat_v3, lead_v3, is_x); + let is_x = is_x_type(feature, pt); + let elbow = ordinate_elbow(feature, pt, is_x); + let mut dim = DimensionOrdinate::new(v3(feature), v3(pt), is_x); + // The leader is an orthogonal L from the feature to the + // endpoint; store its elbow as the definition point. Without it + // the renderer draws feature → (0,0,0) → endpoint, kinking the + // leader through the world origin. The old code also worked in + // the wrong (XZ) plane, dropping the Y coordinate. (#150) + dim.definition_point = v3(elbow); + dim.base.definition_point = v3(elbow); CmdResult::CommitAndExit(EntityType::Dimension(Dimension::Ordinate(dim))) } } @@ -78,13 +80,60 @@ impl CadCommand for OrdinateDimCommand { fn on_enter(&mut self) -> CmdResult { CmdResult::Cancel } - fn on_preview_wires(&mut self, _pt: DVec3) -> Vec { - vec![] + fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); + 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])) } } fn v3(p: Vec3) -> Vector3 { - Vector3::new(p.x as f64, 0.0, p.z as f64) + Vector3::new(p.x as f64, p.y as f64, p.z as f64) +} + +/// 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 { + let dx = (leader.x - feature.x).abs(); + let dy = (leader.y - feature.y).abs(); + dy >= dx +} + +/// 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 { + if is_x { + Vec3::new(feature.x, leader.y, feature.z) + } else { + Vec3::new(leader.x, feature.y, feature.z) + } +} + +fn preview_wire(points: Vec) -> WireModel { + WireModel { + name: "dimordinate_preview".into(), + points: points.into_iter().map(|p| [p.x, p.y, p.z]).collect(), + 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(), + } }