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:
parent
1572d0c38f
commit
e4a7970c3e
3 changed files with 168 additions and 0 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -10,3 +10,4 @@ pub mod ray;
|
|||
pub mod revcloud;
|
||||
pub mod shapes;
|
||||
pub mod spline;
|
||||
pub mod wipeout;
|
||||
|
|
|
|||
155
src/modules/home/draw/wipeout.rs
Normal file
155
src/modules/home/draw/wipeout.rs
Normal 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))
|
||||
}
|
||||
Loading…
Reference in a new issue