Feat: Phase 4.4 — WIPEOUT command (WO)

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 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-04-02 23:54:07 +03:00
commit e4a7970c3e
3 changed files with 168 additions and 0 deletions

View file

@ -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();

View file

@ -10,3 +10,4 @@ pub mod ray;
pub mod revcloud;
pub mod shapes;
pub mod spline;
pub mod wipeout;

View file

@ -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<Vec3>,
poly_pts: Vec<Vec3>,
}
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<WireModel> {
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<Vector2> = pts.iter()
.map(|p| Vector2::new(p.x as f64, p.z as f64))
.collect();
EntityType::Wipeout(Wipeout::polygonal(&verts, z))
}