DIMALIGNED's drag preview never matched where the dimension landed. The preview offset the dimension line in the XZ plane while the committed entity's renderer offsets in the XY plane, and the commit path passed the straight-line p2→cursor *distance* to `set_offset`, which re-applies it along the perpendicular — wrong distance, always on the +perp side. Both now store the cursor as the definition point and project it onto the XY-plane perpendicular, matching the renderer and DIMLINEAR. The same XZ-vs-XY plane confusion affected other annotate commands: - DIMORDINATE worked entirely in the XZ plane (dropping the Y coordinate) and left `definition_point` at the origin, so the leader kinked through (0,0,0). Reworked onto the XY plane with a proper orthogonal elbow and a live preview that matches the placed entity. - DIMTEDIT / DIMJOGLINE cursor markers were built in the XZ plane, so in the top-down view they collapsed to a flat line instead of a box / zigzag. Rebuilt in the XY plane. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
169 lines
5.8 KiB
Rust
169 lines
5.8 KiB
Rust
// DIMALIGNED command — aligned dimension (measures true distance between two points).
|
||
|
||
use acadrust::entities::{Dimension, DimensionAligned};
|
||
use acadrust::types::Vector3;
|
||
use acadrust::EntityType;
|
||
use glam::{DVec3, Vec3};
|
||
|
||
use crate::command::{CadCommand, CmdResult};
|
||
use crate::modules::{IconKind, ModuleEvent, ToolDef};
|
||
use crate::scene::model::wire_model::WireModel;
|
||
|
||
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/dim_aligned.svg"));
|
||
|
||
pub fn tool() -> ToolDef {
|
||
ToolDef {
|
||
id: "DIMALIGNED",
|
||
label: "Aligned",
|
||
icon: ICON,
|
||
event: ModuleEvent::Command("DIMALIGNED".to_string()),
|
||
}
|
||
}
|
||
|
||
enum Step {
|
||
First,
|
||
Second(Vec3),
|
||
DimLine { p1: Vec3, p2: Vec3 },
|
||
}
|
||
|
||
pub struct AlignedDimensionCommand {
|
||
step: Step,
|
||
}
|
||
|
||
impl AlignedDimensionCommand {
|
||
pub fn new() -> Self {
|
||
Self { step: Step::First }
|
||
}
|
||
}
|
||
|
||
impl CadCommand for AlignedDimensionCommand {
|
||
fn name(&self) -> &'static str {
|
||
"DIMALIGNED"
|
||
}
|
||
|
||
fn prompt(&self) -> String {
|
||
match self.step {
|
||
Step::First => "DIMALIGNED Specify first extension line origin:".into(),
|
||
Step::Second(_) => "DIMALIGNED Specify second extension line origin:".into(),
|
||
Step::DimLine { .. } => "DIMALIGNED Specify dimension line location:".into(),
|
||
}
|
||
}
|
||
|
||
fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3();
|
||
match self.step {
|
||
Step::First => {
|
||
self.step = Step::Second(pt);
|
||
CmdResult::NeedPoint
|
||
}
|
||
Step::Second(p1) => {
|
||
self.step = Step::DimLine { p1, p2: pt };
|
||
CmdResult::NeedPoint
|
||
}
|
||
Step::DimLine { p1, p2 } => {
|
||
let mut dim = DimensionAligned::new(v3(p1), v3(p2));
|
||
// 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);
|
||
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)))
|
||
}
|
||
}
|
||
}
|
||
|
||
fn on_enter(&mut self) -> CmdResult {
|
||
CmdResult::Cancel
|
||
}
|
||
|
||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
|
||
let (p1, p2) = match self.step {
|
||
Step::First => return None,
|
||
Step::Second(p1) => (p1, pt),
|
||
Step::DimLine { p1, p2 } => {
|
||
return Some(preview_aligned(p1, p2, pt));
|
||
}
|
||
};
|
||
Some(WireModel {
|
||
name: "dimaligned_preview".into(),
|
||
points: vec![[p1.x, p1.y, p1.z], [p2.x, p2.y, p2.z]],
|
||
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(),
|
||
})
|
||
}
|
||
}
|
||
|
||
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 (d1, d2) = dim_line_endpoints(p1, p2, dim_pt);
|
||
WireModel {
|
||
name: "dimaligned_preview".into(),
|
||
points: vec![
|
||
[p1.x, p1.y, p1.z],
|
||
[d1.x, d1.y, d1.z],
|
||
[f32::NAN, 0.0, 0.0],
|
||
[p2.x, p2.y, p2.z],
|
||
[d2.x, d2.y, d2.z],
|
||
[f32::NAN, 0.0, 0.0],
|
||
[d1.x, d1.y, d1.z],
|
||
[d2.x, d2.y, d2.z],
|
||
],
|
||
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: &["DAL", "DIMALIGNED"] }); // AlignedDimensionCommand
|