diff --git a/src/app/commands.rs b/src/app/commands.rs index 2aa7bb30f..996dc1484 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)); } + "REVCLOUD" => { + use crate::modules::home::draw::revcloud::RevCloudCommand; + let cmd = RevCloudCommand::new(); + self.command_line.push_info(&cmd.prompt()); + self.tabs[i].active_cmd = Some(Box::new(cmd)); + } + "DONUT"|"DO" => { use crate::modules::home::draw::donut::DonutCommand; let cmd = DonutCommand::new(); diff --git a/src/modules/home/draw/mod.rs b/src/modules/home/draw/mod.rs index 6dbd2b60e..f72dd0938 100644 --- a/src/modules/home/draw/mod.rs +++ b/src/modules/home/draw/mod.rs @@ -7,5 +7,6 @@ pub mod line; pub mod point; pub mod polyline; pub mod ray; +pub mod revcloud; pub mod shapes; pub mod spline; diff --git a/src/modules/home/draw/revcloud.rs b/src/modules/home/draw/revcloud.rs new file mode 100644 index 000000000..89ed69a4b --- /dev/null +++ b/src/modules/home/draw/revcloud.rs @@ -0,0 +1,107 @@ +// REVCLOUD command — draw a revision cloud (arc-bumped closed polyline). +// +// Workflow: pick polygon corners (like PLINE), press Enter to close. +// Each segment of the polygon is subdivided into arc bumps (bulge = 0.5). +// Minimum arc length = `arc_length` parameter. + +use acadrust::entities::LwPolyline; +use acadrust::types::Vector2; +use acadrust::{EntityType, entities::LwVertex}; +use glam::Vec3; + +use crate::command::{CadCommand, CmdResult}; +use crate::scene::wire_model::WireModel; + +const DEFAULT_ARC_LEN: f64 = 1.0; // default arc bump length + +pub struct RevCloudCommand { + points: Vec, + arc_length: f64, +} + +impl RevCloudCommand { + pub fn new() -> Self { + Self { + points: vec![], + arc_length: DEFAULT_ARC_LEN, + } + } +} + +impl CadCommand for RevCloudCommand { + fn name(&self) -> &'static str { "REVCLOUD" } + + fn prompt(&self) -> String { + if self.points.is_empty() { + format!("REVCLOUD Specify start point (arc length = {:.2}):", self.arc_length) + } else { + format!("REVCLOUD Specify next point ({} pts, Enter to close):", self.points.len()) + } + } + + fn on_point(&mut self, pt: Vec3) -> CmdResult { + self.points.push(pt); + CmdResult::NeedPoint + } + + fn on_enter(&mut self) -> CmdResult { + if self.points.len() < 3 { + return CmdResult::Cancel; + } + let entity = make_revcloud(&self.points, self.arc_length); + CmdResult::CommitAndExit(entity) + } + + fn on_mouse_move(&mut self, pt: Vec3) -> Option { + if self.points.is_empty() { return None; } + let mut preview_pts: Vec<[f32; 3]> = + self.points.iter().map(|p| [p.x, p.y, p.z]).collect(); + preview_pts.push([pt.x, pt.y, pt.z]); + preview_pts.push([self.points[0].x, self.points[0].y, self.points[0].z]); + Some(WireModel { + name: "revcloud_preview".into(), + points: preview_pts, + color: WireModel::CYAN, + selected: false, + pattern_length: 0.0, + pattern: [0.0; 8], + line_weight_px: 1.0, + snap_pts: vec![], + tangent_geoms: vec![], + key_vertices: vec![], + }) + } +} + +fn make_revcloud(pts: &[Vec3], arc_len: f64) -> EntityType { + let n = pts.len(); + let mut vertices: Vec = Vec::new(); + + // For each edge, subdivide into arc bumps (bulge ≈ 0.5) + let bump_bulge = 0.5f64; // tan(included_angle/4) ≈ 0.5 → ~53° arc + + for i in 0..n { + let p0 = pts[i]; + let p1 = pts[(i + 1) % n]; + let seg_len = ((p1.x - p0.x).powi(2) + (p1.z - p0.z).powi(2)).sqrt() as f64; + if seg_len < 1e-6 { continue; } + + // How many full arcs fit? + let num_arcs = ((seg_len / arc_len).round() as usize).max(1); + let step_x = (p1.x - p0.x) as f64 / num_arcs as f64; + let step_z = (p1.z - p0.z) as f64 / num_arcs as f64; + + for j in 0..num_arcs { + let x = p0.x as f64 + step_x * j as f64; + let y = p0.z as f64 + step_z * j as f64; // DXF Y + let mut v = LwVertex::new(Vector2::new(x, y)); + v.bulge = bump_bulge; + vertices.push(v); + } + } + + let mut p = LwPolyline::new(); + p.is_closed = true; + p.vertices = vertices; + EntityType::LwPolyline(p) +}