Feat: REVCLOUD command — revision cloud (arc-bumped closed polyline)

Pick polygon corners, Enter to close. Each edge is subdivided into arc
bumps (bulge = 0.5 ≈ 53° arcs). Result is a closed LwPolyline.
Arc bump length defaults to 1.0 drawing units.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Sonnet 4.6 committed 2026-04-02 23:51:17 +03:00
1 parent 8fca75fade
commit c9719bacdf
3 files changed
+115

No files matched your search

+1
View File
@@ -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;
+107
View File
@@ -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<Vec3>,
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<WireModel> {
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<LwVertex> = 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)
}