cad-editor/src/modules/annotate/aligned_dim.rs

169 lines
5.8 KiB
Rust
Raw Normal View History

// 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 {
2026-05-12 10:36:15 +03:00
fn name(&self) -> &'static str {
"DIMALIGNED"
}
fn prompt(&self) -> String {
match self.step {
2026-05-12 10:36:15 +03:00
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)))
}
}
}
2026-05-12 10:36:15 +03:00
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![
2026-05-12 10:36:15 +03:00
[p1.x, p1.y, p1.z],
[d1.x, d1.y, d1.z],
[f32::NAN, 0.0, 0.0],
2026-05-12 10:36:15 +03:00
[p2.x, p2.y, p2.z],
[d2.x, d2.y, d2.z],
[f32::NAN, 0.0, 0.0],
2026-05-12 10:36:15 +03:00
[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,
2026-05-12 10:36:15 +03:00
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