From 02f4ca0cf7caff62e98758cb91e02f0fa3765bdc Mon Sep 17 00:00:00 2001 From: Karim Jerbi Date: Tue, 22 Sep 2026 20:40:15 +0100 Subject: [PATCH] fix(grips): XLINE/RAY direction grip uses stretch semantics, not move MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The direction handle was built with center_grip (is_midpoint), so drags arrived as Translate, which the direction arm ignores — the grip engaged but silently did nothing. Build it with square_grip (same marker) so the drag arrives as Absolute and repoints the line. Tests: - xline_direction_grip_drag_changes_direction: press, drag, direction is +X --- src/app/update/viewport.rs | 42 ++++++++++++++++++++++++++++++++++++++ src/entities/ray.rs | 12 +++++++---- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/app/update/viewport.rs b/src/app/update/viewport.rs index 2d013adcd..2ea1d6471 100644 --- a/src/app/update/viewport.rs +++ b/src/app/update/viewport.rs @@ -6772,6 +6772,48 @@ mod selection_preview_tests { } } + #[test] + fn xline_direction_grip_drag_changes_direction() { + use acadrust::{types::Vector3, EntityType}; + let mut app = OpenCADStudio::new_for_test(); + app.automation_op(r#"{"op":"new"}"#); + let i = app.active_tab; + app.tabs[i].scene.selection.borrow_mut().vp_size = (800.0, 600.0); + // Base away from the model origin so the UCS icon cannot swallow + // the press (it sits at the origin in a fresh drawing). + let handle = app.tabs[i].scene.add_entity(EntityType::XLine( + acadrust::entities::XLine::new( + Vector3::new(100.0, 50.0, 0.0), + Vector3::new(0.0, 1.0, 0.0), + ), + )); + app.tabs[i].scene.selected.insert(handle); + app.refresh_selected_grips(); + assert_eq!(app.tabs[i].selected_grips.len(), 2); + // Press on grip 1 (direction handle) and drag it to point along +X. + let bounds = iced::Rectangle::with_size(iced::Size::new(800.0, 600.0)); + let g1world = app.tabs[i].selected_grips[1].world; + let g1cursor = app.tabs[i].scene.camera.borrow().project(g1world, bounds).unwrap(); + app.tabs[i].scene.selection.borrow_mut().last_move_pos = + Some(iced::Point::new(g1cursor.x, g1cursor.y)); + let _ = app.on_viewport_left_press(); + assert_eq!( + app.tabs[i].active_grip.as_ref().map(|g| g.grip_id), + Some(1), + "press on the direction grip must engage grip 1" + ); + let dir_target = glam::DVec3::new(200.0, 50.0, 0.0); + let dir_cursor = app.tabs[i].scene.camera.borrow().project(dir_target, bounds).unwrap(); + let _ = app.on_viewport_move(iced::Point::new(dir_cursor.x, dir_cursor.y)); + let EntityType::XLine(moved) = app.tabs[i].scene.document.get_entity(handle).unwrap() + else { panic!("expected xline") }; + assert!( + (moved.direction.x - 1.0).abs() < 1e-6 && moved.direction.y.abs() < 1e-6, + "direction grip drag must point the xline along +X, got {:?}", + moved.direction + ); + } + #[test] fn constraint_glyph_tooltip_appears_after_hover_dwell() { use crate::scene::parametric_constraints::{ConstraintKind, ParametricRef, ParametricScope}; diff --git a/src/entities/ray.rs b/src/entities/ray.rs index 04aeb5bb1..598210259 100644 --- a/src/entities/ray.rs +++ b/src/entities/ray.rs @@ -3,7 +3,7 @@ use crate::t; use crate::command::EntityTransform; use crate::entities::common::{ - center_grip, edit_prop as edit, format_length, ro_prop as ro, square_grip, + edit_prop as edit, format_length, ro_prop as ro, square_grip, }; use crate::entities::curve::{point_along, unit_direction}; use crate::entities::traits::{Grippable, PropertyEditable, Transformable, RenderConvertible}; @@ -48,11 +48,14 @@ impl Grippable for Ray { let bp = &self.base_point; let dir = &self.direction; // Grip 0: base point (movable) - // Grip 1: a point along the direction (changes direction) + // Grip 1: a point along the direction (changes direction). A stretch + // grip, not a move handle: `center_grip` would flag it `is_midpoint` + // and the drag would arrive as `Translate`, which the direction arm + // ignores — the grip would silently do nothing. let guide_dist = 10.0_f64; vec![ square_grip(0, glam::DVec3::new(bp.x, bp.y, bp.z)), - center_grip( + square_grip( 1, glam::DVec3::new( bp.x + dir.x * guide_dist, @@ -191,7 +194,8 @@ impl Grippable for XLine { let guide_dist = 10.0_f64; vec![ square_grip(0, glam::DVec3::new(bp.x, bp.y, bp.z)), - center_grip( + // Direction handle: a stretch grip, not a move handle (see Ray). + square_grip( 1, glam::DVec3::new( bp.x + dir.x * guide_dist,