From e4a7970c3e23be54f19c2c19b3caad1cbe8b0e12 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Thu, 2 Apr 2026 23:54:07 +0300 Subject: [PATCH] =?UTF-8?q?Feat:=20Phase=204.4=20=E2=80=94=20WIPEOUT=20com?= =?UTF-8?q?mand=20(WO)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creates rectangular (2-corner pick) or polygonal (multi-pick, Enter to close) wipeout masking entities. Uses acadrust Wipeout::from_corners / ::polygonal. Options: WIPEOUT → rectangular; WIPEOUT P → polygonal. Co-Authored-By: Claude Sonnet 4.6 --- src/app/commands.rs | 12 +++ src/modules/home/draw/mod.rs | 1 + src/modules/home/draw/wipeout.rs | 155 +++++++++++++++++++++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 src/modules/home/draw/wipeout.rs diff --git a/src/app/commands.rs b/src/app/commands.rs index 996dc1484..7620fadd4 100644 --- a/src/app/commands.rs +++ b/src/app/commands.rs @@ -332,6 +332,18 @@ impl H7CAD { self.tabs[i].active_cmd = Some(Box::new(new_cmd)); } + cmd if cmd == "WIPEOUT" || cmd == "WO" || cmd.starts_with("WIPEOUT ") => { + use crate::modules::home::draw::wipeout::WipeoutCommand; + let args = cmd.split_once(' ').map(|(_, r)| r.trim().to_uppercase()).unwrap_or_default(); + let wo_cmd = if args == "P" || args == "POLYGONAL" { + WipeoutCommand::new_polygonal() + } else { + WipeoutCommand::new_rectangular() + }; + self.command_line.push_info(&wo_cmd.prompt()); + self.tabs[i].active_cmd = Some(Box::new(wo_cmd)); + } + "REVCLOUD" => { use crate::modules::home::draw::revcloud::RevCloudCommand; let cmd = RevCloudCommand::new(); diff --git a/src/modules/home/draw/mod.rs b/src/modules/home/draw/mod.rs index f72dd0938..2a174081b 100644 --- a/src/modules/home/draw/mod.rs +++ b/src/modules/home/draw/mod.rs @@ -10,3 +10,4 @@ pub mod ray; pub mod revcloud; pub mod shapes; pub mod spline; +pub mod wipeout; diff --git a/src/modules/home/draw/wipeout.rs b/src/modules/home/draw/wipeout.rs new file mode 100644 index 000000000..a38c78d64 --- /dev/null +++ b/src/modules/home/draw/wipeout.rs @@ -0,0 +1,155 @@ +// WIPEOUT command — create a rectangular or polygonal masking area. +// +// Modes: +// WIPEOUT (default): two-corner rectangular wipeout +// WIPEOUT P: polygonal wipeout (pick corners, Enter to close) + +use acadrust::entities::Wipeout; +use acadrust::types::Vector3; +use acadrust::EntityType; +use glam::Vec3; + +use crate::command::{CadCommand, CmdResult}; +use crate::scene::wire_model::WireModel; + +pub struct WipeoutCommand { + mode: WipeoutMode, + first: Option, + poly_pts: Vec, +} + +enum WipeoutMode { + Rectangular, + Polygonal, +} + +impl WipeoutCommand { + pub fn new_rectangular() -> Self { + Self { mode: WipeoutMode::Rectangular, first: None, poly_pts: vec![] } + } + pub fn new_polygonal() -> Self { + Self { mode: WipeoutMode::Polygonal, first: None, poly_pts: vec![] } + } +} + +impl CadCommand for WipeoutCommand { + fn name(&self) -> &'static str { "WIPEOUT" } + + fn prompt(&self) -> String { + match &self.mode { + WipeoutMode::Rectangular => { + if self.first.is_none() { + "WIPEOUT Specify first corner:".into() + } else { + "WIPEOUT Specify opposite corner:".into() + } + } + WipeoutMode::Polygonal => { + if self.poly_pts.is_empty() { + "WIPEOUT (Polygon) Specify first point:".into() + } else { + format!("WIPEOUT (Polygon) Specify next point ({} pts, Enter to close):", self.poly_pts.len()) + } + } + } + } + + fn on_point(&mut self, pt: Vec3) -> CmdResult { + match &self.mode { + WipeoutMode::Rectangular => { + if let Some(p1) = self.first { + let entity = make_rect_wipeout(p1, pt); + CmdResult::CommitAndExit(entity) + } else { + self.first = Some(pt); + CmdResult::NeedPoint + } + } + WipeoutMode::Polygonal => { + self.poly_pts.push(pt); + CmdResult::NeedPoint + } + } + } + + fn on_enter(&mut self) -> CmdResult { + match &self.mode { + WipeoutMode::Polygonal if self.poly_pts.len() >= 3 => { + let entity = make_poly_wipeout(&self.poly_pts); + CmdResult::CommitAndExit(entity) + } + _ => CmdResult::Cancel, + } + } + + fn on_mouse_move(&mut self, pt: Vec3) -> Option { + match &self.mode { + WipeoutMode::Rectangular => { + let p1 = self.first?; + let min = p1.min(pt); + let max = p1.max(pt); + Some(WireModel { + name: "wipeout_preview".into(), + points: vec![ + [min.x, min.y, min.z], + [max.x, min.y, min.z], + [max.x, max.y, max.z], + [min.x, max.y, max.z], + [min.x, min.y, min.z], + ], + 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![], + }) + } + WipeoutMode::Polygonal => { + if self.poly_pts.is_empty() { return None; } + let mut pts: Vec<[f32; 3]> = + self.poly_pts.iter().map(|p| [p.x, p.y, p.z]).collect(); + pts.push([pt.x, pt.y, pt.z]); + pts.push([self.poly_pts[0].x, self.poly_pts[0].y, self.poly_pts[0].z]); + Some(WireModel { + name: "wipeout_poly_preview".into(), + points: 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_rect_wipeout(p1: Vec3, p2: Vec3) -> EntityType { + // Y-up world: X→DXF X, Z→DXF Y, Y→DXF Z + let c1 = Vector3::new( + p1.x.min(p2.x) as f64, + p1.z.min(p2.z) as f64, + p1.y as f64, + ); + let c2 = Vector3::new( + p1.x.max(p2.x) as f64, + p1.z.max(p2.z) as f64, + p1.y as f64, + ); + EntityType::Wipeout(Wipeout::from_corners(c1, c2)) +} + +fn make_poly_wipeout(pts: &[Vec3]) -> EntityType { + use acadrust::types::Vector2; + let z = pts[0].y as f64; + let verts: Vec = pts.iter() + .map(|p| Vector2::new(p.x as f64, p.z as f64)) + .collect(); + EntityType::Wipeout(Wipeout::polygonal(&verts, z)) +}