diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index d56df0dad..01e70881d 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -946,55 +946,6 @@ impl OpenCADStudio { TableCellEditStart::Started } - /// Adds inferred Coincident constraints after a line is committed. - /// Holding Shift suppresses inference. No match means no undo entry. - fn infer_and_add_coincident_constraints(&mut self, i: usize, new_handle: Handle, entity: &acadrust::EntityType) { - if self.shift_down { - return; - } - let scope = self.tabs[i].current_sketch_scope(); - let pairs = crate::scene::sketch_constraints::infer_coincident_refs( - &self.tabs[i].scene.document, - scope, - new_handle, - entity, - ); - if pairs.is_empty() { - return; - } - let constraints_before = self - .tabs[i] - .scene - .sketch_constraint_set(scope) - .cloned() - .unwrap_or_else(|| crate::scene::sketch_constraints::SketchConstraintSet::new(scope)); - let mut touched: Vec = Vec::new(); - for (new_ref, existing_ref) in &pairs { - touched.push(new_ref.entity); - touched.push(existing_ref.entity); - } - touched.sort(); - touched.dedup(); - let label = "Coincident (inferred)"; - let pending = self.begin_undo(i, label, touched.len(), true); - self.tabs[i] - .scene - .record_undo_sketch_constraints_before(scope, constraints_before); - for (new_ref, existing_ref) in pairs { - self.tabs[i].scene.sketch_constraint_set_mut(scope).add( - crate::scene::sketch_constraints::ConstraintKind::Coincident, - vec![new_ref, existing_ref], - None, - ); - } - let changes: Vec<(Handle, crate::scene::ChangeKind)> = - touched.into_iter().map(|h| (h, crate::scene::ChangeKind::Modified)).collect(); - self.tabs[i].scene.bump_entities(&changes); - if let Some(pd) = pending { - self.commit_undo_delta(i, pd); - } - } - /// Removes and re-solves the first conflicting constraint in the active scope. pub(super) fn resolve_one_sketch_conflict(&mut self) { let i = self.active_tab; @@ -1655,20 +1606,6 @@ impl OpenCADStudio { if let Some(pd) = pending { self.commit_undo_delta(i, pd); } - // Stage 8/ยง6.2: live inference, scoped to `LINE` (the design - // doc's own example) rather than every `CommitEntity` source โ€” - // FILLET/OFFSET/MIRROR/etc. also route Lines through this same - // dispatch and shouldn't silently acquire constraints a user - // never asked for. - if let Some(handle) = committed { - let is_line_command = - self.tabs[i].active_cmd.as_ref().is_some_and(|c| c.name() == "LINE"); - if is_line_command { - if let Some(new_entity) = self.tabs[i].scene.document.get_entity(handle).cloned() { - self.infer_and_add_coincident_constraints(i, handle, &new_entity); - } - } - } } CmdResult::CommitEntities(mut entities) => { let locked_source = entities.iter().find_map(|entity| { @@ -6992,13 +6929,12 @@ mod sketch_constraint_undo_tests { ); } - /// A line endpoint landing on an existing point infers Coincident unless - /// Shift is held. + /// Drawing commands must not create sketch constraints implicitly. #[test] - fn drawing_a_line_onto_an_existing_endpoint_infers_a_coincident_constraint() { + fn drawing_a_line_onto_an_existing_endpoint_does_not_add_a_constraint() { let mut app = OpenCADStudio::new_for_test(); let _ = app.automation_op(r#"{"op":"new"}"#); - let existing = add_line(&mut app, 0.0, 0.0, 5.0, 0.0); + let _existing = add_line(&mut app, 0.0, 0.0, 5.0, 0.0); let new_line = acadrust::EntityType::Line(acadrust::entities::Line::from_points( acadrust::types::Vector3::new(5.0, 0.0, 0.0), // exactly on `existing`'s end point @@ -7007,30 +6943,6 @@ mod sketch_constraint_undo_tests { app.tabs[app.active_tab].active_cmd = Some(Box::new(crate::modules::draw::draw::line::LineCommand::new())); let _ = app.apply_cmd_result(CmdResult::CommitEntity(new_line)); - let set = app.tabs[app.active_tab].scene.sketch_constraint_set(SketchScope::ModelSpace).expect("a constraint set should exist"); - assert_eq!(set.constraints.len(), 1, "an inferred Coincident constraint should have been added"); - let c = &set.constraints[0]; - assert_eq!(c.kind, ConstraintKind::Coincident); - let touches_existing = c.refs.iter().any(|r| r.entity == existing); - assert!(touches_existing, "the inferred constraint should reference the pre-existing line"); - } - - /// Same setup as above, but with Shift held: inference must be - /// suppressed entirely. - #[test] - fn shift_suppresses_the_coincident_inference() { - let mut app = OpenCADStudio::new_for_test(); - let _ = app.automation_op(r#"{"op":"new"}"#); - let _existing = add_line(&mut app, 0.0, 0.0, 5.0, 0.0); - - let new_line = acadrust::EntityType::Line(acadrust::entities::Line::from_points( - acadrust::types::Vector3::new(5.0, 0.0, 0.0), - acadrust::types::Vector3::new(10.0, 0.0, 0.0), - )); - app.tabs[app.active_tab].active_cmd = Some(Box::new(crate::modules::draw::draw::line::LineCommand::new())); - app.shift_down = true; - let _ = app.apply_cmd_result(CmdResult::CommitEntity(new_line)); - assert_eq!( app.tabs[app.active_tab] .scene @@ -7038,7 +6950,7 @@ mod sketch_constraint_undo_tests { .map(|s| s.constraints.len()) .unwrap_or(0), 0, - "Shift should have suppressed the inference" + "a drawing command must not add an implicit constraint" ); } diff --git a/src/scene/sketch_constraints.rs b/src/scene/sketch_constraints.rs index b4a723187..af0a7dba3 100644 --- a/src/scene/sketch_constraints.rs +++ b/src/scene/sketch_constraints.rs @@ -299,8 +299,7 @@ pub(crate) fn resolve_point(entity: &acadrust::EntityType, marker: i32) -> Optio } /// Below this squared distance (1e-6 world units), two points count as -/// already coincident for [`infer_coincident_refs`]/[`nearest_sketch_point`]'s -/// purposes. +/// already coincident for [`nearest_sketch_point`]'s purposes. const COINCIDENT_EPSILON_SQ: f64 = 1.0e-12; /// Finds the addressable entity point nearest a snapped world position. @@ -343,51 +342,6 @@ pub(crate) fn nearest_sketch_point( best.map(|(_, r)| r) } -/// For each point on `new_entity`, finds an existing entity in `scope` (other -/// than `new_handle` itself) whose corresponding point already coincides, -/// and returns a `(new, existing)` `SketchRef` pair per match โ€” the caller -/// adds each as a `Coincident` constraint. -/// -/// Matching uses a tight world-space tolerance because this runs after the -/// snapped entity has been committed. -pub(crate) fn infer_coincident_refs( - document: &acadrust::CadDocument, - scope: SketchScope, - new_handle: Handle, - new_entity: &acadrust::EntityType, -) -> Vec<(SketchRef, SketchRef)> { - let owner = scope.owner_handle(document); - let new_points = super::dimension_assoc::source_points(new_entity); - if new_points.is_empty() { - return Vec::new(); - } - let mut pairs = Vec::new(); - for (new_marker, new_point) in new_points.iter().enumerate() { - for candidate in document.entities() { - let common = candidate.common(); - if common.handle == new_handle || common.owner_handle != owner { - continue; - } - for (marker, point) in super::dimension_assoc::source_points(candidate) - .iter() - .enumerate() - { - let dx = point.x - new_point.x; - let dy = point.y - new_point.y; - let dz = point.z - new_point.z; - if dx * dx + dy * dy + dz * dz <= COINCIDENT_EPSILON_SQ { - pairs.push(( - SketchRef::point(new_handle, new_marker as i32), - SketchRef::point(common.handle, marker as i32), - )); - break; - } - } - } - } - pairs -} - impl ConstraintKind { /// The short symbol a constraint glyph shows โ€” matches the existing /// ribbon icons (`crate::modules::parametric::{tools,value}`) for