From e8125c8702df6f9497f120aa09270c4c9f332614 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Thu, 17 Sep 2026 17:19:09 +0300 Subject: [PATCH] fix: preserve selected smooth curve geometry --- src/app/commands/draw.rs | 2 +- src/entities/spline.rs | 26 +++++++++ src/modules/parametric/mod.rs | 2 +- src/modules/parametric/smooth.rs | 34 ++++++++++- src/scene/parametric_solve.rs | 97 ++++++++++++++++++++++++++++++++ 5 files changed, 157 insertions(+), 4 deletions(-) diff --git a/src/app/commands/draw.rs b/src/app/commands/draw.rs index fe3518075..c52c96b1e 100644 --- a/src/app/commands/draw.rs +++ b/src/app/commands/draw.rs @@ -1206,7 +1206,7 @@ impl OpenCADStudio { } } - "GCSMOOTH" | "SMOOTHCONSTRAINT" => { + "GCSMOOTH" => { use crate::modules::parametric::SmoothConstraintCommand; let handles = self.tabs[i].scene.selected_handles_in_order(); if handles.is_empty() { diff --git a/src/entities/spline.rs b/src/entities/spline.rs index 9cdf5c68b..efbbc7e35 100644 --- a/src/entities/spline.rs +++ b/src/entities/spline.rs @@ -1390,4 +1390,30 @@ mod tests { acadrust::types::Vector3::new(0.0, 2.5, 0.0) ); } + + #[test] + fn showing_derived_fit_points_does_not_change_the_curve() { + let mut spline = Spline::default(); + spline.degree = 3; + spline.control_points = vec![ + acadrust::types::Vector3::new(0.0, 0.0, 0.0), + acadrust::types::Vector3::new(1.0, 3.0, 0.0), + acadrust::types::Vector3::new(4.0, 3.0, 0.0), + acadrust::types::Vector3::new(5.0, 0.0, 0.0), + ]; + spline.knots = cadkernel::space::clamped_uniform_knots(3, 4); + let before = nurbs3(&spline).unwrap().point_at(0.37); + + assert!(prepare_fit_point_view(&mut spline)); + spline.cv_frame_visible = false; + + assert!(shows_fit_points(&spline)); + assert!(!uses_fit_method(&spline)); + let after = nurbs3(&spline).unwrap().point_at(0.37); + assert!( + cadkernel::space::Vec3::from(before) + .distance(cadkernel::space::Vec3::from(after)) + < 1.0e-12 + ); + } } diff --git a/src/modules/parametric/mod.rs b/src/modules/parametric/mod.rs index 4b5d4e65e..7562bb0e6 100644 --- a/src/modules/parametric/mod.rs +++ b/src/modules/parametric/mod.rs @@ -138,7 +138,7 @@ impl CadModule for ParametricModule { inventory::submit!(crate::command::CommandRegistration { names: &[ - "AUTOCONSTRAIN", "CONSTRAINTSETTINGS", "GCSMOOTH", "SMOOTHCONSTRAINT", "GCSHOW", "GCHIDE", "GCRESET", + "AUTOCONSTRAIN", "CONSTRAINTSETTINGS", "GCSMOOTH", "GCSHOW", "GCHIDE", "GCRESET", "GCSHOWALL", "GCHIDEALL", "DCSHOW", "DCHIDE", "DCSHOWALL", "DCHIDEALL", "DCCONVERT", "DELCONSTRAINT", ] diff --git a/src/modules/parametric/smooth.rs b/src/modules/parametric/smooth.rs index dc81a158f..e5a9c0dd3 100644 --- a/src/modules/parametric/smooth.rs +++ b/src/modules/parametric/smooth.rs @@ -74,8 +74,10 @@ impl SmoothConstraintCommand { }) } EntityType::LwPolyline(_) | EntityType::Polyline2D(_) => { - let (source, _, _) = crate::scene::centerline::picked_source(entity, handle, point)?; - let segment = usize::try_from(source.segment_index).ok()?; + let planar = crate::entities::curve::entity_curve(entity)?; + let local = planar.plane.project(point.to_array())?; + let segments = planar.curve.segments(); + let (segment, _) = cadkernel::geom2d::nearest_of(segments.iter(), local)?; let points = crate::scene::dimension_assoc::source_points(entity); let closed = match entity { EntityType::LwPolyline(polyline) => polyline.is_closed, @@ -229,3 +231,31 @@ impl CadCommand for SmoothConstraintCommand { CmdResult::Cancel } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn polyline_target_keeps_the_picked_arc_segment_and_endpoint() { + let handle = Handle::new(7); + let mut polyline = acadrust::entities::LwPolyline::new(); + polyline.vertices = vec![ + acadrust::entities::LwVertex::from_coords(0.0, 0.0), + acadrust::entities::LwVertex::with_bulge( + acadrust::types::Vector2::new(5.0, 0.0), + 1.0, + ), + acadrust::entities::LwVertex::from_coords(10.0, 0.0), + ]; + + let target = SmoothConstraintCommand::target_reference( + &EntityType::LwPolyline(polyline), + handle, + DVec3::new(10.0, 0.0, 0.0), + ) + .expect("arc endpoint"); + assert_eq!(target.curve, ParametricRef::segment(handle, 1)); + assert_eq!(target.endpoint, ParametricRef::point(handle, 2)); + } +} diff --git a/src/scene/parametric_solve.rs b/src/scene/parametric_solve.rs index ffbc8c8f8..13d554111 100644 --- a/src/scene/parametric_solve.rs +++ b/src/scene/parametric_solve.rs @@ -3977,4 +3977,101 @@ mod tests { assert!(jet.tangent[1].abs() < 1e-9, "jet={jet:?}"); assert!(cadkernel::space::Vec3::from(jet.curvature).length() < 5e-3); } + + #[test] + fn smooth_constraint_uses_the_selected_polyline_arc_curvature() { + let mut scene = Scene::new(); + let mut target = acadrust::entities::LwPolyline::new(); + target.vertices = vec![ + acadrust::entities::LwVertex::with_bulge( + acadrust::types::Vector2::new(0.0, 0.0), + 1.0, + ), + acadrust::entities::LwVertex::from_coords(10.0, 0.0), + ]; + let target = scene.add_entity(EntityType::LwPolyline(target)); + let mut spline = acadrust::entities::Spline::new(); + spline.degree = 3; + spline.control_points = vec![ + Vector3::new(2.0, 1.0, 0.0), + Vector3::new(3.0, 2.0, 0.0), + Vector3::new(4.0, 3.0, 0.0), + Vector3::new(5.0, 3.0, 0.0), + ]; + spline.knots = cadkernel::space::clamped_uniform_knots(3, 4); + let spline = scene.add_entity(EntityType::Spline(spline)); + scene + .parametric_constraint_set_mut(ParametricScope::ModelSpace) + .add( + ConstraintKind::Smooth, + vec![ + ParametricRef::point(spline, 0), + ParametricRef::point(target, 0), + ParametricRef::segment(target, 0), + ], + None, + ); + + scene.bump_entities(&[(target, super::ChangeKind::Modified)]); + + let EntityType::Spline(spline) = scene.document.get_entity(spline).unwrap() else { + panic!("expected spline"); + }; + let curve = crate::entities::spline::nurbs3(spline).unwrap(); + let jet = + cadkernel::space::CurveJet::from_nurbs(&curve, cadkernel::space::SplineEnd::Start) + .unwrap(); + assert!(cadkernel::space::Vec3::from(jet.point).length() < 1.0e-9); + assert!(jet.tangent[0].abs() < 1.0e-9, "jet={jet:?}"); + assert!( + (cadkernel::space::Vec3::from(jet.curvature).length() - 0.2).abs() < 2.0e-4, + "jet={jet:?}" + ); + } + + #[test] + fn smooth_constraint_accepts_a_stale_normal_on_an_arbitrary_plane() { + let mut scene = Scene::new(); + let target = scene.add_entity(EntityType::Line( + acadrust::entities::Line::from_points( + Vector3::new(0.0, 0.0, 1.0), + Vector3::new(5.0, 0.0, 1.0), + ), + )); + let mut source = acadrust::entities::Spline::new(); + source.degree = 3; + source.normal = Vector3::UNIT_Z; + source.control_points = vec![ + Vector3::new(1.0, 0.0, 0.0), + Vector3::new(2.0, 0.0, 2.0), + Vector3::new(4.0, 0.0, 2.0), + Vector3::new(5.0, 0.0, 3.0), + ]; + source.knots = cadkernel::space::clamped_uniform_knots(3, 4); + let source = scene.add_entity(EntityType::Spline(source)); + let refs = vec![ + ParametricRef::point(source, 0), + ParametricRef::point(target, 0), + ]; + assert!(scene + .validate_parametric_constraint(ConstraintKind::Smooth, &refs, None) + .is_ok()); + scene + .parametric_constraint_set_mut(ParametricScope::ModelSpace) + .add(ConstraintKind::Smooth, refs, None); + + scene.bump_entities(&[(target, super::ChangeKind::Modified)]); + + let EntityType::Spline(source) = scene.document.get_entity(source).unwrap() else { + panic!("expected spline"); + }; + let curve = crate::entities::spline::nurbs3(source).unwrap(); + let jet = + cadkernel::space::CurveJet::from_nurbs(&curve, cadkernel::space::SplineEnd::Start) + .unwrap(); + assert!(cadkernel::space::Vec3::from(jet.point) + .distance(cadkernel::space::Vec3::new(0.0, 0.0, 1.0)) + < 1.0e-9); + assert!(jet.tangent[1].abs() < 1.0e-9 && jet.tangent[2].abs() < 1.0e-9); + } }