From b84d387f43d3ca93cd12be9aa26c1221641aea95 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Fri, 11 Sep 2026 10:24:58 +0300 Subject: [PATCH 1/2] Match SPLINE CV command behavior --- src/modules/draw/draw/spline.rs | 57 +++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/src/modules/draw/draw/spline.rs b/src/modules/draw/draw/spline.rs index 8ace3cb2d..7b05899ad 100644 --- a/src/modules/draw/draw/spline.rs +++ b/src/modules/draw/draw/spline.rs @@ -26,6 +26,8 @@ pub struct SplineCommand { pts: Vec, control_vertices: bool, choosing_method: bool, + choosing_degree: bool, + degree: usize, } impl SplineCommand { @@ -34,6 +36,8 @@ impl SplineCommand { pts: Vec::new(), control_vertices: false, choosing_method: false, + choosing_degree: false, + degree: 3, } } @@ -48,11 +52,17 @@ impl SplineCommand { if self.pts.len() < 2 { return None; } - Some(EntityType::Spline(make_spline( - &self.pts, - closed, - self.control_vertices, - ))) + let mut spline = make_spline(&self.pts, closed, self.control_vertices); + if self.control_vertices { + let count = spline.control_points.len(); + let degree = self.degree.min(count - 1); + spline.degree = degree as i32; + spline.knots = (0..count + degree + 1).map(|index| { + if index <= degree { 0.0 } else if index >= count { 1.0 } + else { (index - degree) as f64 / (count - degree) as f64 } + }).collect(); + } + Some(EntityType::Spline(spline)) } } @@ -97,6 +107,7 @@ fn make_spline(pts: &[DVec3], closed: bool, control_vertices: bool) -> Spline { } /// Preview the exact representation that `build()` commits. +#[cfg(test)] fn sample_curve(pts: &[DVec3], closed: bool, control_vertices: bool) -> Vec<[f32; 3]> { if pts.len() < 2 { return pts @@ -123,7 +134,9 @@ impl CadCommand for SplineCommand { } fn prompt(&self) -> String { - if self.choosing_method { + if self.choosing_degree { + format!("SPLINE Enter degree of spline <{}>:", self.degree) + } else 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() @@ -143,8 +156,11 @@ impl CadCommand for SplineCommand { CmdOption::new("Control vertices", "CV"), ]; } + if self.choosing_degree { return vec![]; } if self.pts.is_empty() { - return vec![CmdOption::new(t!("Method").as_ref(), "M")]; + let mut options = vec![CmdOption::new(t!("Method").as_ref(), "M")]; + if self.control_vertices { options.push(CmdOption::new("Degree", "D")); } + return options; } let mut opts = vec![CmdOption::new(t!("Close").as_ref(), "C")]; // Undo only makes sense once a control point exists. @@ -154,7 +170,7 @@ impl CadCommand for SplineCommand { } fn on_point(&mut self, pt: DVec3) -> CmdResult { - if self.choosing_method || !pt.is_finite() { + if self.choosing_method || self.choosing_degree || !pt.is_finite() { return CmdResult::NeedPoint; } self.pts.push(pt); @@ -162,7 +178,8 @@ impl CadCommand for SplineCommand { } fn on_enter(&mut self) -> CmdResult { - if self.choosing_method { + if self.choosing_method || self.choosing_degree { + self.choosing_degree = false; self.choosing_method = false; return CmdResult::NeedPoint; } @@ -189,7 +206,16 @@ impl CadCommand for SplineCommand { } fn on_text_input(&mut self, text: &str) -> Option { + if self.choosing_degree { + self.degree = text.trim().parse::().ok().filter(|d| (1..=10).contains(d))?; + self.choosing_degree = false; + return Some(CmdResult::NeedPoint); + } match text.trim().to_uppercase().as_str() { + "D" | "DEGREE" if self.pts.is_empty() && self.control_vertices => { + self.choosing_degree = true; + Some(CmdResult::NeedPoint) + } "M" | "METHOD" if self.pts.is_empty() => { self.choosing_method = true; Some(CmdResult::NeedPoint) @@ -217,15 +243,20 @@ impl CadCommand for SplineCommand { } fn on_mouse_move(&mut self, pt: DVec3) -> Option { - if self.pts.is_empty() { + if self.pts.is_empty() || self.choosing_method || self.choosing_degree { return None; } // Preview the committed construction method. - let mut ctrl = self.pts.clone(); - ctrl.push(pt); + self.pts.push(pt); + let entity = self.build(false); + self.pts.pop(); + let Some(EntityType::Spline(spline)) = entity else { return None; }; + let points = crate::entities::curve::spline_curve(&spline) + .map(|curve| crate::entities::curve::curve_points(&curve)) + .unwrap_or_else(|| crate::entities::spline::measurement_polyline(&spline)); Some(WireModel::solid( "rubber_band".into(), - sample_curve(&ctrl, false, self.control_vertices), + points.into_iter().map(|p| [p[0] as f32, p[1] as f32, p[2] as f32]).collect(), WireModel::CYAN, false, )) From 7a9ffef5cf71f4c12925a1509d9e0c7c9fb39b07 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Fri, 11 Sep 2026 10:43:58 +0300 Subject: [PATCH 2/2] Preserve periodic control-vertex spline seams --- src/modules/draw/draw/spline.rs | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/modules/draw/draw/spline.rs b/src/modules/draw/draw/spline.rs index 7b05899ad..aa8597da6 100644 --- a/src/modules/draw/draw/spline.rs +++ b/src/modules/draw/draw/spline.rs @@ -52,20 +52,29 @@ impl SplineCommand { if self.pts.len() < 2 { return None; } - let mut spline = make_spline(&self.pts, closed, self.control_vertices); - if self.control_vertices { - let count = spline.control_points.len(); - let degree = self.degree.min(count - 1); - spline.degree = degree as i32; - spline.knots = (0..count + degree + 1).map(|index| { - if index <= degree { 0.0 } else if index >= count { 1.0 } - else { (index - degree) as f64 / (count - degree) as f64 } - }).collect(); - } + let spline = if self.control_vertices { + let points: Vec<_> = self.pts.iter().map(|point| point.to_array()).collect(); + control_spline(&points, self.degree.min(points.len() - 1), closed)? + } else { make_spline(&self.pts, closed, false) }; Some(EntityType::Spline(spline)) } } +fn control_spline(points: &[[f64; 3]], degree: usize, closed: bool) -> Option { + let curve = cadkernel::space::NurbsCurve3::from_control_polygon(degree, points, closed)?; + let mut spline = Spline { + degree: curve.degree() as i32, + control_points: curve.control_points().iter().map(|p| Vector3::new(p[0], p[1], p[2])).collect(), + knots: curve.knots().to_vec(), + weights: curve.weights().to_vec(), + ..Default::default() + }; + spline.flags.closed = closed; + spline.flags.periodic = closed; + spline.flags.planar = crate::entities::curve::spline_is_planar(&spline); + Some(spline) +} + /// 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 = pts.iter().map(|p| Vector3::new(p.x, p.y, p.z)).collect();