// DIMDIAMETER command — diameter dimension for circles and arcs. use acadrust::entities::{Dimension, DimensionDiameter}; 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_diameter.svg")); pub fn tool() -> ToolDef { ToolDef { id: "DIMDIAMETER", label: "Diameter", icon: ICON, event: ModuleEvent::Command("DIMDIAMETER".to_string()), } } enum Step { CenterPoint, ArcPoint(Vec3), TextPoint { center: Vec3, arc_pt: Vec3 }, } pub struct DiameterDimensionCommand { step: Step, /// Optional text that replaces the measured value (None = measurement). text_override: Option, /// True while the next typed line is captured as the text override. awaiting_text: bool, /// Explicit text rotation in radians (None = follow the UCS/style). text_angle: Option, /// True while the next typed value is captured as the text angle. awaiting_angle: bool, } impl DiameterDimensionCommand { pub fn new() -> Self { Self { step: Step::CenterPoint, text_override: None, awaiting_text: false, text_angle: None, awaiting_angle: false, } } } impl CadCommand for DiameterDimensionCommand { fn name(&self) -> &'static str { "DIMDIAMETER" } fn prompt(&self) -> String { if self.awaiting_text { return "DIMDIAMETER Enter dimension text (blank = measured value):".into(); } if self.awaiting_angle { return "DIMDIAMETER Specify text angle (degrees):".into(); } match self.step { Step::CenterPoint => "DIMDIAMETER Specify center point:".into(), Step::ArcPoint(_) => "DIMDIAMETER Specify point on circle:".into(), Step::TextPoint { .. } => { "DIMDIAMETER Specify dimension line location [Text/Angle]:".into() } } } fn on_point(&mut self, pt: DVec3) -> CmdResult { let pt = pt.as_vec3(); match self.step { Step::CenterPoint => { self.step = Step::ArcPoint(pt); CmdResult::NeedPoint } Step::ArcPoint(center) => { self.step = Step::TextPoint { center, arc_pt: pt }; CmdResult::NeedPoint } Step::TextPoint { center, arc_pt } => { let mut dim = DimensionDiameter::new(v3(center), v3(arc_pt)); 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.base.actual_measurement = dim.measurement(); dim.base.user_text = self.text_override.clone(); // An explicit text angle overrides the default rotation. if let Some(a) = self.text_angle { dim.base.text_rotation = a; } CmdResult::CommitAndExit(EntityType::Dimension(Dimension::Diameter(dim))) } } } fn on_enter(&mut self) -> CmdResult { // A bare Enter while entering override text accepts the measured value. if self.awaiting_text { self.awaiting_text = false; return CmdResult::NeedPoint; } if self.awaiting_angle { self.awaiting_angle = false; return CmdResult::NeedPoint; } CmdResult::Cancel } fn wants_text_input(&self) -> bool { true } fn point_step_accepts_keywords(&self) -> bool { // While entering the override text or angle it is a value, not a point step. !self.awaiting_text && !self.awaiting_angle } fn wants_text_with_spaces(&self) -> bool { // The override text may contain spaces. self.awaiting_text } fn on_text_input(&mut self, text: &str) -> Option { if self.awaiting_text { let t = text.trim(); // Blank (or the "<>" placeholder) keeps the measured value. self.text_override = if t.is_empty() || t == "<>" { None } else { Some(t.to_string()) }; self.awaiting_text = false; return Some(CmdResult::NeedPoint); } if self.awaiting_angle { let t = text.trim(); // Blank clears any explicit angle (follow the default again). self.text_angle = if t.is_empty() { None } else { t.parse::().ok().map(f64::to_radians) }; self.awaiting_angle = false; return Some(CmdResult::NeedPoint); } match text.trim().to_uppercase().as_str() { "T" | "TEXT" | "M" | "MTEXT" => { self.awaiting_text = true; Some(CmdResult::NeedPoint) } "A" | "ANGLE" => { self.awaiting_angle = true; Some(CmdResult::NeedPoint) } _ => None, } } fn on_mouse_move(&mut self, pt: DVec3) -> Option { let pt = pt.as_vec3(); match self.step { Step::CenterPoint => None, Step::ArcPoint(center) => Some(preview_line(center, pt)), Step::TextPoint { center, arc_pt } => { let far = center + (center - arc_pt); // opposite point on circle Some(preview_line(far, pt)) } } } } fn v3(p: Vec3) -> Vector3 { Vector3::new(p.x as f64, p.y as f64, p.z as f64) } fn preview_line(a: Vec3, b: Vec3) -> WireModel { WireModel { name: "dimdia_preview".into(), points: vec![[a.x, a.y, a.z], [b.x, b.y, b.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: &["DDI", "DIMDIAMETER"] }); // DiameterDimensionCommand