diff --git a/src/app/commands.rs b/src/app/commands.rs index 5a321154..2aa7bb30 100644 --- a/src/app/commands.rs +++ b/src/app/commands.rs @@ -332,6 +332,13 @@ impl H7CAD { self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } + "DONUT"|"DO" => { + use crate::modules::home::draw::donut::DonutCommand; + let cmd = DonutCommand::new(); + self.command_line.push_info(&cmd.prompt()); + self.tabs[i].active_cmd = Some(Box::new(cmd)); + } + "CIRCLE"|"C" => { use crate::modules::home::draw::circle::CircleCommand; let new_cmd = CircleCommand::new(); @@ -1037,7 +1044,7 @@ impl H7CAD { "HELP"|"?" => { self.command_line.push_output( - "Draw: LINE CIRCLE ARC PLINE RECT POLY POINT ELLIPSE SPLINE RAY XLINE HATCH | \ + "Draw: LINE CIRCLE ARC PLINE RECT POLY POINT ELLIPSE SPLINE RAY XLINE HATCH DONUT | \ Modify: MOVE COPY ROTATE SCALE MIRROR ERASE OFFSET EXTEND FILLET CHAMFER STRETCH EXPLODE TRIM BREAK JOIN LENGTHEN ALIGN | \ Array: ARRAY ARRAYRECT ARRAYPOLAR ARRAYPATH | \ Text: TEXT MTEXT LEADER MLEADER | \ diff --git a/src/modules/home/draw/donut.rs b/src/modules/home/draw/donut.rs new file mode 100644 index 00000000..3c63499e --- /dev/null +++ b/src/modules/home/draw/donut.rs @@ -0,0 +1,125 @@ +// DONUT command — create a filled circular ring (thick LwPolyline). +// +// A donut is an LwPolyline with: +// - 2 vertices at (cx ± r_avg, 0), both with bulge = 1.0 (two 180° CCW arcs) +// - start_width = end_width = (outer - inner) / 2 +// - is_closed = true +// +// Workflow: +// 1. Type inner diameter (or 0 for a filled circle) +// 2. Type outer diameter +// 3. Click center point(s); Enter to finish + +use acadrust::entities::{LwPolyline, LwVertex}; +use acadrust::EntityType; +use glam::Vec3; + +use crate::command::{CadCommand, CmdResult}; + +pub struct DonutCommand { + state: DonutState, + inner_r: f64, + outer_r: f64, +} + +enum DonutState { + AskInner, + AskOuter, + PlaceCenter, +} + +impl DonutCommand { + pub fn new() -> Self { + Self { + state: DonutState::AskInner, + inner_r: 0.0, + outer_r: 1.0, + } + } +} + +impl CadCommand for DonutCommand { + fn name(&self) -> &'static str { "DONUT" } + + fn prompt(&self) -> String { + match &self.state { + DonutState::AskInner => "DONUT Specify inside diameter <0>:".into(), + DonutState::AskOuter => "DONUT Specify outside diameter:".into(), + DonutState::PlaceCenter => "DONUT Specify center of donut (Enter to exit):".into(), + } + } + + fn wants_text_input(&self) -> bool { + matches!(self.state, DonutState::AskInner | DonutState::AskOuter) + } + + fn on_text_input(&mut self, text: &str) -> Option { + let val: f64 = text.trim().replace(',', ".").parse().ok().filter(|&v: &f64| v >= 0.0)?; + match &self.state { + DonutState::AskInner => { + self.inner_r = val / 2.0; + self.state = DonutState::AskOuter; + Some(CmdResult::NeedPoint) + } + DonutState::AskOuter => { + if val <= 0.0 { return None; } + self.outer_r = val / 2.0; + if self.inner_r > self.outer_r { + std::mem::swap(&mut self.inner_r, &mut self.outer_r); + } + self.state = DonutState::PlaceCenter; + Some(CmdResult::NeedPoint) + } + _ => None, + } + } + + fn on_point(&mut self, pt: Vec3) -> CmdResult { + match &self.state { + DonutState::PlaceCenter => { + let entity = make_donut(pt.x as f64, pt.z as f64, self.inner_r, self.outer_r); + // Keep command active so user can place more donuts. + CmdResult::CommitEntity(entity) + } + _ => CmdResult::NeedPoint, + } + } + + fn on_enter(&mut self) -> CmdResult { + match &self.state { + DonutState::AskInner => { + // Accept default 0 for inner diameter + self.inner_r = 0.0; + self.state = DonutState::AskOuter; + CmdResult::NeedPoint + } + DonutState::PlaceCenter => CmdResult::Cancel, + _ => CmdResult::Cancel, + } + } +} + +fn make_donut(cx: f64, cy: f64, inner_r: f64, outer_r: f64) -> EntityType { + use acadrust::types::Vector2; + let r_avg = (inner_r + outer_r) / 2.0; + let width = outer_r - inner_r; + + let mut p = LwPolyline::new(); + p.is_closed = true; + p.constant_width = width; + + // Vertex at (cx - r, cy) with bulge 1.0 (180° CCW arc to next vertex) + let mut v0 = LwVertex::new(Vector2::new(cx - r_avg, cy)); + v0.bulge = 1.0; + v0.start_width = width; + v0.end_width = width; + + // Vertex at (cx + r, cy) with bulge 1.0 (second 180° arc back to v0) + let mut v1 = LwVertex::new(Vector2::new(cx + r_avg, cy)); + v1.bulge = 1.0; + v1.start_width = width; + v1.end_width = width; + + p.vertices = vec![v0, v1]; + EntityType::LwPolyline(p) +} diff --git a/src/modules/home/draw/mod.rs b/src/modules/home/draw/mod.rs index e4ae8827..6dbd2b60 100644 --- a/src/modules/home/draw/mod.rs +++ b/src/modules/home/draw/mod.rs @@ -1,5 +1,6 @@ pub mod arc; pub mod circle; +pub mod donut; pub mod ellipse; pub mod hatch; pub mod line;