Feat: DONUT command (DO) — filled circular ring as LwPolyline
Creates LwPolyline with 2 vertices at (cx ± r_avg, cy), bulge=1.0, start_width/end_width = (outer - inner) / 2, is_closed = true. Workflow: inner diameter → outer diameter → click center points (Enter to exit). Inner diameter 0 → filled solid circle. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9a1eeab53d
commit
8fca75fade
3 changed files with 134 additions and 1 deletions
|
|
@ -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 | \
|
||||
|
|
|
|||
125
src/modules/home/draw/donut.rs
Normal file
125
src/modules/home/draw/donut.rs
Normal file
|
|
@ -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<CmdResult> {
|
||||
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)
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
pub mod arc;
|
||||
pub mod circle;
|
||||
pub mod donut;
|
||||
pub mod ellipse;
|
||||
pub mod hatch;
|
||||
pub mod line;
|
||||
|
|
|
|||
Loading…
Reference in a new issue