forked from 1b-agent/opencadstudio-upstream
feat: expose Spline CV in the Draw expansion
This commit is contained in:
1 parent
b19992cbcb
commit
306d998ebf
4 files changed
+103
-39
No files matched your search
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" role="img" aria-label="Spline CV" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 18L6 4L18 4L21 18" stroke="#B4B6B9" stroke-dasharray="2 2"/><path d="M3 18C6 0 18 0 21 18" stroke="#B4B6B9" stroke-width="1.6"/><path d="M2 17h2v2H2zM5 3h2v2H5zM17 3h2v2h-2zM20 17h2v2h-2z" fill="#6DB7ED" stroke="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 395 B |
@@ -872,9 +872,13 @@ impl OpenCADStudio {
|
||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||
}
|
||||
|
||||
"SPLINE" => {
|
||||
"SPLINE" | "SPLINECV" => {
|
||||
use crate::modules::draw::draw::spline::SplineCommand;
|
||||
let new_cmd = SplineCommand::new();
|
||||
let new_cmd = if cmd == "SPLINECV" {
|
||||
SplineCommand::control_vertices()
|
||||
} else {
|
||||
SplineCommand::new()
|
||||
};
|
||||
self.command_line.push_info(&new_cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||
}
|
||||
|
||||
@@ -24,49 +24,75 @@ pub fn tool() -> ToolDef {
|
||||
|
||||
pub struct SplineCommand {
|
||||
pts: Vec<DVec3>,
|
||||
control_vertices: bool,
|
||||
choosing_method: bool,
|
||||
}
|
||||
|
||||
impl SplineCommand {
|
||||
pub fn new() -> Self {
|
||||
Self { pts: Vec::new() }
|
||||
Self { pts: Vec::new(), control_vertices: false, choosing_method: false }
|
||||
}
|
||||
|
||||
pub fn control_vertices() -> Self {
|
||||
Self { control_vertices: true, ..Self::new() }
|
||||
}
|
||||
|
||||
fn build(&self, closed: bool) -> Option<EntityType> {
|
||||
if self.pts.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
let fit_points = self
|
||||
.pts
|
||||
.iter()
|
||||
.map(|p| Vector3::new(p.x, p.y, p.z))
|
||||
.collect();
|
||||
let mut spline = Spline {
|
||||
degree: 3,
|
||||
fit_points,
|
||||
..Default::default()
|
||||
};
|
||||
spline.flags.closed = closed;
|
||||
Some(EntityType::Spline(spline))
|
||||
Some(EntityType::Spline(make_spline(&self.pts, closed, self.control_vertices)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Preview through the same fit-point representation that `build()` commits.
|
||||
fn sample_curve(pts: &[DVec3], closed: bool) -> Vec<[f32; 3]> {
|
||||
/// Store the chosen construction method directly in the persistent spline.
|
||||
fn make_spline(pts: &[DVec3], closed: bool, control_vertices: bool) -> Spline {
|
||||
let mut points: Vec<Vector3> = pts
|
||||
.iter()
|
||||
.map(|p| Vector3::new(p.x, p.y, p.z))
|
||||
.collect();
|
||||
let mut spline = if control_vertices {
|
||||
if closed && pts.first() != pts.last() {
|
||||
points.push(points[0]);
|
||||
}
|
||||
let count = points.len();
|
||||
let degree = 3.min(count.saturating_sub(1));
|
||||
let spans = count - degree;
|
||||
// Open uniform knot vector: degree + 1 equal knots at each endpoint.
|
||||
let knots = (0..count + degree + 1)
|
||||
.map(|index| {
|
||||
if index <= degree { 0.0 }
|
||||
else if index >= count { 1.0 }
|
||||
else { (index - degree) as f64 / spans as f64 }
|
||||
})
|
||||
.collect();
|
||||
Spline {
|
||||
degree: degree as i32,
|
||||
control_points: points,
|
||||
knots,
|
||||
weights: vec![1.0; count],
|
||||
..Default::default()
|
||||
}
|
||||
} else {
|
||||
Spline {
|
||||
degree: 3,
|
||||
fit_points: points,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
spline.flags.closed = closed;
|
||||
spline
|
||||
}
|
||||
|
||||
/// Preview the exact representation that `build()` commits.
|
||||
fn sample_curve(pts: &[DVec3], closed: bool, control_vertices: bool) -> Vec<[f32; 3]> {
|
||||
if pts.len() < 2 {
|
||||
return pts
|
||||
.iter()
|
||||
.map(|p| [p.x as f32, p.y as f32, p.z as f32])
|
||||
.collect();
|
||||
}
|
||||
let mut spline = Spline {
|
||||
degree: 3,
|
||||
fit_points: pts
|
||||
.iter()
|
||||
.map(|p| Vector3::new(p.x, p.y, p.z))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
};
|
||||
spline.flags.closed = closed;
|
||||
let spline = make_spline(pts, closed, control_vertices);
|
||||
crate::entities::curve::spline_curve(&spline)
|
||||
.map(|curve| crate::entities::curve::curve_points(&curve))
|
||||
.unwrap_or_else(|| crate::entities::spline::measurement_polyline(&spline))
|
||||
@@ -77,11 +103,15 @@ fn sample_curve(pts: &[DVec3], closed: bool) -> Vec<[f32; 3]> {
|
||||
|
||||
impl CadCommand for SplineCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
"SPLINE"
|
||||
if self.control_vertices { "SPLINECV" } else { "SPLINE" }
|
||||
}
|
||||
|
||||
fn prompt(&self) -> String {
|
||||
if self.pts.is_empty() {
|
||||
if self.choosing_method {
|
||||
"SPLINE Choose creation method [Fit/Control vertices]:".into()
|
||||
} else if self.pts.is_empty() && self.control_vertices {
|
||||
t!("SPLINE Specify first control point:").into_owned()
|
||||
} else if self.pts.is_empty() {
|
||||
t!("SPLINE Specify first point:").into_owned()
|
||||
} else {
|
||||
let n = self.pts.len();
|
||||
@@ -91,8 +121,11 @@ impl CadCommand for SplineCommand {
|
||||
|
||||
fn options(&self) -> Vec<crate::command::CmdOption> {
|
||||
use crate::command::CmdOption;
|
||||
if self.choosing_method {
|
||||
return vec![CmdOption::new("Fit", "FIT"), CmdOption::new("Control vertices", "CV")];
|
||||
}
|
||||
if self.pts.is_empty() {
|
||||
return Vec::new();
|
||||
return vec![CmdOption::new(t!("Method").as_ref(), "M")];
|
||||
}
|
||||
let mut opts = vec![CmdOption::new(t!("Close").as_ref(), "C")];
|
||||
// Undo only makes sense once a control point exists.
|
||||
@@ -102,11 +135,18 @@ impl CadCommand for SplineCommand {
|
||||
}
|
||||
|
||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||
if self.choosing_method || !pt.is_finite() {
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
self.pts.push(pt);
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
if self.choosing_method {
|
||||
self.choosing_method = false;
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
match self.build(false) {
|
||||
Some(e) => CmdResult::CommitAndExit(e),
|
||||
None => CmdResult::Cancel,
|
||||
@@ -114,26 +154,37 @@ impl CadCommand for SplineCommand {
|
||||
}
|
||||
|
||||
fn enter_accepts_default_start(&self) -> bool {
|
||||
self.pts.is_empty()
|
||||
self.pts.is_empty() && !self.choosing_method
|
||||
}
|
||||
|
||||
fn on_escape(&mut self) -> CmdResult {
|
||||
match self.build(false) {
|
||||
Some(e) => CmdResult::CommitAndExit(e),
|
||||
None => CmdResult::Cancel,
|
||||
}
|
||||
CmdResult::Cancel
|
||||
}
|
||||
|
||||
fn wants_text_input(&self) -> bool {
|
||||
!self.pts.is_empty()
|
||||
true
|
||||
}
|
||||
|
||||
fn point_step_accepts_keywords(&self) -> bool {
|
||||
!self.pts.is_empty()
|
||||
true
|
||||
}
|
||||
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
match text.trim().to_uppercase().as_str() {
|
||||
"M" | "METHOD" if self.pts.is_empty() => {
|
||||
self.choosing_method = true;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
"F" | "FIT" if self.pts.is_empty() => {
|
||||
self.control_vertices = false;
|
||||
self.choosing_method = false;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
"CV" | "CONTROL" | "CONTROLVERTICES" if self.pts.is_empty() => {
|
||||
self.control_vertices = true;
|
||||
self.choosing_method = false;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
"C" | "CLOSE" => match self.build(true) {
|
||||
Some(e) => Some(CmdResult::CommitAndExit(e)),
|
||||
None => Some(CmdResult::NeedPoint),
|
||||
@@ -150,12 +201,12 @@ impl CadCommand for SplineCommand {
|
||||
if self.pts.is_empty() {
|
||||
return None;
|
||||
}
|
||||
// Preview the committed fit-point representation.
|
||||
// Preview the committed construction method.
|
||||
let mut ctrl = self.pts.clone();
|
||||
ctrl.push(pt);
|
||||
Some(WireModel::solid(
|
||||
"rubber_band".into(),
|
||||
sample_curve(&ctrl, false),
|
||||
sample_curve(&ctrl, false, self.control_vertices),
|
||||
WireModel::CYAN,
|
||||
false,
|
||||
))
|
||||
@@ -164,4 +215,4 @@ impl CadCommand for SplineCommand {
|
||||
|
||||
|
||||
// ── Autocomplete registry ─────────────────────────────────
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["SPLINE"] }); // SplineCommand
|
||||
inventory::submit!(crate::command::CommandRegistration { names: &["SPLINE", "SPLINECV"] }); // SplineCommand
|
||||
@@ -0,0 +1,6 @@
|
||||
Tool {
|
||||
command: "SPLINECV",
|
||||
label: "Spline CV",
|
||||
icon: include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/icons/spline_cv.svg")),
|
||||
options: &[],
|
||||
}
|
||||
Reference in new issue
Block a user