From 6260723937165b544a3524498e0ca0049a0c69fe Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Thu, 17 Sep 2026 13:50:15 +0300 Subject: [PATCH] Revert "Match Smooth connected-endpoint workflow" This reverts commit dfa75f45b53b5ecd37ba0c9adad7443c343b0adb. --- src/app/command_driver.rs | 30 ----- src/app/commands/draw.rs | 51 +++++---- src/modules/parametric/smooth.rs | 138 +++++++++++++++++++++-- src/scene/parametric_constraints.rs | 163 +--------------------------- 4 files changed, 161 insertions(+), 221 deletions(-) diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index 10de341fe..d8e1a035b 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -849,36 +849,6 @@ impl OpenCADStudio { /// command, so `was_active` is false and their selection is preserved. pub(super) fn apply_cmd_result(&mut self, mut result: CmdResult) -> Task { let i = self.active_tab; - if let CmdResult::AddParametricConstraint { - kind, - refs, - .. - } = &mut result - { - if *kind == crate::scene::parametric_constraints::ConstraintKind::Smooth - && refs.len() == 1 - { - let source = refs[0]; - let scope = self.tabs[i].current_parametric_scope(); - let Some(resolved) = self.tabs[i] - .scene - .smooth_refs_from_connected_spline(scope, source.entity, source.marker) - else { - self.tabs[i].snap_result = None; - self.command_line - .push_error("No valid constraint point found."); - if let Some(prompt) = self.tabs[i] - .active_cmd - .as_ref() - .map(|command| command.prompt()) - { - self.command_line.push_info(&prompt); - } - return Task::none(); - }; - *refs = resolved; - } - } if let CmdResult::ReturnPoint(pt) = result { self.tabs[i].scene.clear_preview_wire(); if let Some(mut suspended) = self.tabs[i].suspended_cmd.take() { diff --git a/src/app/commands/draw.rs b/src/app/commands/draw.rs index 1d3981574..2289dbf09 100644 --- a/src/app/commands/draw.rs +++ b/src/app/commands/draw.rs @@ -1213,32 +1213,41 @@ impl OpenCADStudio { let command = SmoothConstraintCommand::new(); self.command_line.push_info(&command.prompt()); self.tabs[i].active_cmd = Some(Box::new(command)); + } else if handles.len() != 2 { + self.tabs[i].scene.deselect_all(); + self.command_line.push_error( + "Smooth requires an open spline first and a target curve second.", + ); + let command = SmoothConstraintCommand::new(); + self.command_line.push_info(&command.prompt()); + self.tabs[i].active_cmd = Some(Box::new(command)); } else { - let scope = self.tabs[i].current_parametric_scope(); - let refs = (handles.len() == 1) - .then(|| { - self.tabs[i] - .scene - .smooth_refs_from_connected_spline(scope, handles[0], None) - }) - .flatten(); - if let Some(refs) = refs { - use crate::command::CmdResult; - use crate::scene::parametric_constraints::ConstraintKind; - return Some(self.apply_cmd_result(CmdResult::AddParametricConstraint { - kind: ConstraintKind::Smooth, - refs, - driving_param: None, - label: "Smooth constraint", - })); - } else { + let first = self.tabs[i].scene.document.get_entity(handles[0]); + let second = self.tabs[i].scene.document.get_entity(handles[1]); + let refs = first.zip(second).and_then(|(first, second)| { + SmoothConstraintCommand::preselected_refs( + (first, handles[0]), + (second, handles[1]), + ) + }); + let Some(refs) = refs else { self.tabs[i].scene.deselect_all(); - self.command_line - .push_error("No valid constraint point found."); + self.command_line.push_error( + "Smooth requires an open spline first and a line, arc, polyline segment or open spline second.", + ); let command = SmoothConstraintCommand::new(); self.command_line.push_info(&command.prompt()); self.tabs[i].active_cmd = Some(Box::new(command)); - } + return None; + }; + use crate::command::CmdResult; + use crate::scene::parametric_constraints::ConstraintKind; + return Some(self.apply_cmd_result(CmdResult::AddParametricConstraint { + kind: ConstraintKind::Smooth, + refs, + driving_param: None, + label: "Smooth constraint", + })); } } diff --git a/src/modules/parametric/smooth.rs b/src/modules/parametric/smooth.rs index 3df5b0966..dc81a158f 100644 --- a/src/modules/parametric/smooth.rs +++ b/src/modules/parametric/smooth.rs @@ -5,13 +5,21 @@ use crate::command::{CadCommand, CmdResult}; use crate::scene::parametric_constraints::{ConstraintKind, ParametricRef}; use crate::t; +#[derive(Clone, Copy)] +struct SmoothTarget { + endpoint: ParametricRef, + curve: ParametricRef, +} + pub struct SmoothConstraintCommand { + source: Option, picked_entity: Option, } impl SmoothConstraintCommand { pub fn new() -> Self { Self { + source: None, picked_entity: None, } } @@ -45,8 +53,106 @@ impl SmoothConstraintCommand { Some(ParametricRef::point(handle, marker)) } - fn invalid_selection() -> CmdResult { - CmdResult::ReportError(t!("No valid constraint point found.").into_owned()) + fn target_reference(entity: &EntityType, handle: Handle, point: DVec3) -> Option { + match entity { + EntityType::Line(_) | EntityType::Arc(_) => { + let points = crate::scene::dimension_assoc::source_points(entity); + let endpoints = [*points.first()?, *points.last()?]; + let marker = Self::nearest_marker(&endpoints, point)?; + Some(SmoothTarget { + endpoint: ParametricRef::point(handle, marker), + curve: ParametricRef::whole(handle), + }) + } + EntityType::Spline(spline) if !spline.flags.closed && !spline.flags.periodic => { + let points = crate::scene::dimension_assoc::source_points(entity); + let endpoints = [*points.first()?, *points.last()?]; + let marker = Self::nearest_marker(&endpoints, point)?; + Some(SmoothTarget { + endpoint: ParametricRef::point(handle, marker), + curve: ParametricRef::whole(handle), + }) + } + EntityType::LwPolyline(_) | EntityType::Polyline2D(_) => { + let (source, _, _) = crate::scene::centerline::picked_source(entity, handle, point)?; + let segment = usize::try_from(source.segment_index).ok()?; + let points = crate::scene::dimension_assoc::source_points(entity); + let closed = match entity { + EntityType::LwPolyline(polyline) => polyline.is_closed, + EntityType::Polyline2D(polyline) => polyline.is_closed(), + _ => false, + }; + let end = if segment + 1 < points.len() { + segment + 1 + } else if closed { + 0 + } else { + return None; + }; + let candidates = [points.get(segment).copied()?, points.get(end).copied()?]; + let side = Self::nearest_marker(&candidates, point)? as usize; + let vertex = if side == 0 { segment } else { end }; + Some(SmoothTarget { + endpoint: ParametricRef::point(handle, vertex as i32), + curve: ParametricRef::segment(handle, segment), + }) + } + _ => None, + } + } + + pub fn preselected_refs( + first: (&EntityType, Handle), + second: (&EntityType, Handle), + ) -> Option> { + let source_points = crate::scene::dimension_assoc::source_points(first.0); + let source_ends = [*source_points.first()?, *source_points.last()?]; + let source_point = source_ends + .iter() + .copied() + .min_by(|left, right| { + let target_points = crate::scene::dimension_assoc::source_points(second.0); + let distance = |source: acadrust::types::Vector3| { + target_points + .iter() + .map(|target| (source - *target).length_squared()) + .fold(f64::INFINITY, f64::min) + }; + distance(*left).total_cmp(&distance(*right)) + })?; + let source = Self::source_reference(first.0, first.1, Self::point(source_point))?; + + let target_points = crate::scene::dimension_assoc::source_points(second.0); + let target_point = target_points + .iter() + .copied() + .min_by(|left, right| { + (Self::point(*left) - Self::point(source_point)) + .length_squared() + .total_cmp( + &(Self::point(*right) - Self::point(source_point)).length_squared(), + ) + })?; + let target = Self::target_reference(second.0, second.1, Self::point(target_point))?; + let mut refs = vec![source, target.endpoint]; + if target.curve.marker.is_some() { + refs.push(target.curve); + } + Some(refs) + } + + fn invalid_source() -> CmdResult { + CmdResult::ReportError( + t!("Invalid first selection for Smooth. Select an endpoint of an open spline.") + .into_owned(), + ) + } + + fn invalid_target() -> CmdResult { + CmdResult::ReportError( + t!("Invalid second selection for Smooth. Select an endpoint of a line, arc, polyline segment or open spline.") + .into_owned(), + ) } } @@ -56,7 +162,11 @@ impl CadCommand for SmoothConstraintCommand { } fn prompt(&self) -> String { - t!("GCSMOOTH Select first spline curve:").into_owned() + if self.source.is_some() { + t!("GCSMOOTH Select second curve endpoint:").into_owned() + } else { + t!("GCSMOOTH Select first spline curve endpoint:").into_owned() + } } fn needs_entity_pick(&self) -> bool { @@ -82,14 +192,26 @@ impl CadCommand for SmoothConstraintCommand { let Some(entity) = self.picked_entity.take() else { return CmdResult::NeedPoint; }; - let Some(source) = Self::source_reference(&entity, handle, point) else { - return Self::invalid_selection(); + let Some(source) = self.source else { + let Some(source) = Self::source_reference(&entity, handle, point) else { + return Self::invalid_source(); + }; + self.source = Some(source); + return CmdResult::NeedPoint; }; + let Some(target) = Self::target_reference(&entity, handle, point) else { + return Self::invalid_target(); + }; + if source.entity == target.endpoint.entity { + return Self::invalid_target(); + } + let mut refs = vec![source, target.endpoint]; + if target.curve.marker.is_some() { + refs.push(target.curve); + } CmdResult::AddParametricConstraint { kind: ConstraintKind::Smooth, - // The host resolves the target from an existing Coincident - // relation. GCSMOOTH itself accepts only the spline selection. - refs: vec![source], + refs, driving_param: None, label: "Smooth constraint", } diff --git a/src/scene/parametric_constraints.rs b/src/scene/parametric_constraints.rs index 637d52c5b..3d0473217 100644 --- a/src/scene/parametric_constraints.rs +++ b/src/scene/parametric_constraints.rs @@ -1382,56 +1382,6 @@ impl super::Scene { ), }) .collect(); - if settings - .enabled - .contains(&crate::app::settings::AutoConstraintKind::Coincident) - { - for refs in self.inferred_coincident_constraints_with_tolerance( - scope, - handles, - settings.distance_tolerance, - ) { - let duplicate = mapped.iter().any(|(kind, current)| { - *kind == ConstraintKind::Coincident - && (current == &refs - || (current.len() == 2 - && refs.len() == 2 - && current[0] == refs[1] - && current[1] == refs[0])) - }); - if !duplicate { - mapped.push((ConstraintKind::Coincident, refs)); - } - } - let priority = |kind: ConstraintKind| { - let auto_kind = match kind { - ConstraintKind::Coincident => { - crate::app::settings::AutoConstraintKind::Coincident - } - ConstraintKind::Colinear => crate::app::settings::AutoConstraintKind::Collinear, - ConstraintKind::Parallel => crate::app::settings::AutoConstraintKind::Parallel, - ConstraintKind::Perpendicular => { - crate::app::settings::AutoConstraintKind::Perpendicular - } - ConstraintKind::Tangent => crate::app::settings::AutoConstraintKind::Tangent, - ConstraintKind::Concentric => { - crate::app::settings::AutoConstraintKind::Concentric - } - ConstraintKind::Horizontal => { - crate::app::settings::AutoConstraintKind::Horizontal - } - ConstraintKind::Vertical => crate::app::settings::AutoConstraintKind::Vertical, - ConstraintKind::Equal => crate::app::settings::AutoConstraintKind::Equal, - _ => return usize::MAX, - }; - settings - .priority - .iter() - .position(|candidate| *candidate == auto_kind) - .unwrap_or(usize::MAX) - }; - mapped.sort_by_key(|(kind, _)| priority(*kind)); - } if let Some(existing) = self.parametric_constraint_set(scope) { mapped.retain(|(kind, refs)| { !existing.constraints.iter().any(|constraint| { @@ -1459,19 +1409,6 @@ impl super::Scene { &self, scope: ParametricScope, handles: &[Handle], - ) -> Vec> { - self.inferred_coincident_constraints_with_tolerance( - scope, - handles, - COINCIDENT_EPSILON_SQ.sqrt(), - ) - } - - fn inferred_coincident_constraints_with_tolerance( - &self, - scope: ParametricScope, - handles: &[Handle], - distance_tolerance: f64, ) -> Vec> { let owner = scope.owner_handle(&self.document); let mut sources = Vec::new(); @@ -1500,7 +1437,7 @@ impl super::Scene { for second in first + 1..sources.len() { if sources[first].0.entity == sources[second].0.entity || (sources[first].1 - sources[second].1).length_squared() - > distance_tolerance.max(0.0).powi(2) + > COINCIDENT_EPSILON_SQ { continue; } @@ -1526,104 +1463,6 @@ impl super::Scene { inferred } - /// Resolve the target of GCSMOOTH through an existing Coincident - /// relation. The command selects only the spline; the connected endpoint - /// identifies both the target entity and the target point. - pub(crate) fn smooth_refs_from_connected_spline( - &self, - scope: ParametricScope, - spline_handle: Handle, - preferred_marker: Option, - ) -> Option> { - let spline = self.document.get_entity(spline_handle)?; - let acadrust::EntityType::Spline(spline) = spline else { - return None; - }; - if spline.flags.closed || spline.flags.periodic { - return None; - } - let markers: Vec = match preferred_marker { - Some(marker @ (0 | 1)) => vec![marker], - _ => vec![0, 1], - }; - let set = self.parametric_constraint_set(scope)?; - for marker in markers { - let source = ParametricRef::point(spline_handle, marker); - for constraint in set.constraints.iter().filter(|constraint| { - constraint.enabled - && constraint.kind == ConstraintKind::Coincident - && constraint.refs.len() == 2 - }) { - let target = if constraint.refs[0] == source { - constraint.refs[1] - } else if constraint.refs[1] == source { - constraint.refs[0] - } else { - continue; - }; - if target.entity == spline_handle || target.marker.is_none() { - continue; - } - let Some(target_entity) = self.document.get_entity(target.entity) else { - continue; - }; - let target_curve = match target_entity { - acadrust::EntityType::Line(_) - | acadrust::EntityType::Arc(_) - | acadrust::EntityType::Spline(_) => ParametricRef::whole(target.entity), - acadrust::EntityType::LwPolyline(polyline) => { - let Some(vertex) = target.marker.and_then(|marker| usize::try_from(marker).ok()) - else { - continue; - }; - let count = polyline.vertices.len(); - if count < 2 || vertex >= count { - continue; - } - let segment = if vertex == 0 { - 0 - } else if vertex + 1 == count && !polyline.is_closed { - vertex - 1 - } else { - vertex.saturating_sub(1) - }; - ParametricRef::segment(target.entity, segment) - } - acadrust::EntityType::Polyline2D(polyline) => { - let Some(vertex) = target.marker.and_then(|marker| usize::try_from(marker).ok()) - else { - continue; - }; - let count = polyline.vertices.len(); - if count < 2 || vertex >= count { - continue; - } - let segment = if vertex == 0 { - 0 - } else if vertex + 1 == count && !polyline.is_closed() { - vertex - 1 - } else { - vertex.saturating_sub(1) - }; - ParametricRef::segment(target.entity, segment) - } - _ => continue, - }; - let mut refs = vec![source, target]; - if target_curve.marker.is_some() { - refs.push(target_curve); - } - if self - .validate_parametric_constraint(ConstraintKind::Smooth, &refs, None) - .is_ok() - { - return Some(refs); - } - } - } - None - } - /// The constraint set for `scope`, if one has been created. pub fn parametric_constraint_set( &self,