From eac19348d5ef05912fd3e21c70621025077842c3 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Mon, 28 Sep 2026 14:08:36 +0300 Subject: [PATCH] refactor(draw): TRIM geometry helpers come from the kernel; drop a probe test #1511 added an orthogonal ellipse projection and a nearest-end arc_parameter to draw/modify/geom.rs, the latter shadowing the kernel function of the same name. Both now live in opencadkernel 237827e and are re-exported here, so every kernel caller gets them too. test_coincident_line_and_arc_grip_move only printed values and asserted nothing; removed. Pins opencadkernel 237827e, opencadgraph fe36957. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 6 +- Cargo.toml | 6 +- src/app/update/viewport.rs | 64 -------------------- src/modules/draw/modify/geom.rs | 101 +------------------------------- 4 files changed, 7 insertions(+), 170 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db628fe75..e0ef979bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4225,7 +4225,7 @@ dependencies = [ [[package]] name = "opencadgraph" version = "0.1.0" -source = "git+https://github.com/HakanSeven12/opencadgraph.git?rev=7ef5ae4#7ef5ae4a283ee0f5e0c08fde71d3c35e3455c418" +source = "git+https://github.com/HakanSeven12/opencadgraph.git?rev=fe36957#fe3695774499c0faca16e93b2c85d1751fe13475" dependencies = [ "opencadkernel", "serde_json", @@ -4234,7 +4234,7 @@ dependencies = [ [[package]] name = "opencadkernel" version = "0.1.0" -source = "git+https://github.com/HakanSeven12/opencadkernel.git?rev=1e02630#1e02630af0cc0e79a27fc0c4d4e874a84fb168ad" +source = "git+https://github.com/HakanSeven12/opencadkernel.git?rev=237827e#237827ef22b013487cf157dcb35730c52ae0c763" dependencies = [ "cavalier_contours", "opencadcodec", @@ -4245,7 +4245,7 @@ dependencies = [ [[package]] name = "opencadkernel-constraints" version = "0.1.0" -source = "git+https://github.com/HakanSeven12/opencadkernel.git?rev=1e02630#1e02630af0cc0e79a27fc0c4d4e874a84fb168ad" +source = "git+https://github.com/HakanSeven12/opencadkernel.git?rev=237827e#237827ef22b013487cf157dcb35730c52ae0c763" dependencies = [ "nalgebra", ] diff --git a/Cargo.toml b/Cargo.toml index 80c0ecd21..a9d31c7fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,9 +34,9 @@ clap = { version = "4", features = ["derive", "string"] } env_logger = "0.11" log = "0.4" codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "ad16215", features = ["serde"] } -kernel = { package = "opencadkernel", git = "https://github.com/HakanSeven12/opencadkernel.git", rev = "1e02630", features = ["acis", "offset"] } -kernel-constraints = { package = "opencadkernel-constraints", git = "https://github.com/HakanSeven12/opencadkernel.git", rev = "1e02630" } -graph = { package = "opencadgraph", git = "https://github.com/HakanSeven12/opencadgraph.git", rev = "7ef5ae4" } +kernel = { package = "opencadkernel", git = "https://github.com/HakanSeven12/opencadkernel.git", rev = "237827e", features = ["acis", "offset"] } +kernel-constraints = { package = "opencadkernel-constraints", git = "https://github.com/HakanSeven12/opencadkernel.git", rev = "237827e" } +graph = { package = "opencadgraph", git = "https://github.com/HakanSeven12/opencadgraph.git", rev = "fe36957" } dwg-thumbnailer = { path = "crates/dwg-thumbnailer" } flate2 = "1" image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] } diff --git a/src/app/update/viewport.rs b/src/app/update/viewport.rs index f189ea0b0..50a31730d 100644 --- a/src/app/update/viewport.rs +++ b/src/app/update/viewport.rs @@ -7009,70 +7009,6 @@ mod selection_preview_tests { ); } - #[test] - fn test_coincident_line_and_arc_grip_move() { - use codec::{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); - - let pt = glam::DVec3::new(-2.8146711761917635, 12.22681563180158, 0.0); - - // Line ending at pt - let mut line = codec::entities::Line::new(); - line.start = Vector3::new(-20.0, 12.22681563180158, 0.0); - line.end = Vector3::new(pt.x, pt.y, pt.z); - let line_h = app.tabs[i].scene.add_entity(EntityType::Line(line)); - - // Arc starting at pt - let mut arc = codec::entities::Arc::new(); - arc.center = Vector3::new(0.0, 0.0, 0.0); - arc.radius = (pt.x * pt.x + pt.y * pt.y).sqrt(); - let sa = (pt.y).atan2(pt.x); - arc.start_angle = sa; - arc.end_angle = sa + 1.0; - let arc_h = app.tabs[i].scene.add_entity(EntityType::Arc(arc)); - - app.tabs[i].scene.selected.insert(line_h); - app.tabs[i].scene.selected.insert(arc_h); - app.refresh_selected_grips(); - - println!("selected grips count: {}", app.tabs[i].selected_grips.len()); - for (g_idx, g) in app.tabs[i].selected_grips.iter().enumerate() { - let h = app.tabs[i].selected_grip_handles[g_idx]; - println!("grip idx {g_idx}: handle {:?}, id {}, world {:?}", h, g.id, g.world); - } - - let bounds = iced::Rectangle::with_size(iced::Size::new(800.0, 600.0)); - let cursor = app.tabs[i].scene.camera.borrow().project(pt, bounds).unwrap(); - app.tabs[i].scene.selection.borrow_mut().last_move_pos = - Some(iced::Point::new(cursor.x, cursor.y)); - let _ = app.on_viewport_left_press(); - - let active = app.tabs[i].active_grip.as_ref().expect("grip must engage"); - println!("active grip targets len: {}", active.targets.len()); - for t in &active.targets { - println!("target: handle {:?}, grip_id {}, last_world {:?}", t.handle, t.grip_id, t.last_world); - } - - // Drag cursor by (5.0, 5.0) - let new_target = pt + glam::DVec3::new(5.0, 5.0, 0.0); - let new_cursor = app.tabs[i].scene.camera.borrow().project(new_target, bounds).unwrap(); - let _ = app.on_viewport_move(iced::Point::new(new_cursor.x, new_cursor.y)); - - let EntityType::Line(moved_line) = app.tabs[i].scene.document.get_entity(line_h).unwrap() else { panic!() }; - let EntityType::Arc(moved_arc) = app.tabs[i].scene.document.get_entity(arc_h).unwrap() else { panic!() }; - - println!("moved line end: {:?}", moved_line.end); - let arc_start_moved = glam::DVec3::new( - moved_arc.center.x + moved_arc.radius * moved_arc.start_angle.cos(), - moved_arc.center.y + moved_arc.radius * moved_arc.start_angle.sin(), - 0.0, - ); - println!("moved arc start: {:?}", arc_start_moved); - } - #[test] fn constraint_glyph_tooltip_appears_after_hover_dwell() { use crate::scene::parametric_constraints::{ConstraintKind, ParametricRef, ParametricScope}; diff --git a/src/modules/draw/modify/geom.rs b/src/modules/draw/modify/geom.rs index f5bb524b9..cd7a78721 100644 --- a/src/modules/draw/modify/geom.rs +++ b/src/modules/draw/modify/geom.rs @@ -15,106 +15,7 @@ use kernel::geom2d::{self, Ellipse}; /// Re-exported unchanged: these already speak in plain `f64`, so there is no /// call-shape difference for this module to absorb. -pub use kernel::geom2d::{lerp, normalize_angle}; - -/// Where `angle` sits along the arc from `start` to `end`, as `0.0..=1.0`. -/// -/// Unlike `kernel::geom2d::arc_parameter` which clamps any angle outside the arc to 1.0, -/// this clamps angles in the gap outside the arc to 0.0 or 1.0 depending on which endpoint -/// they are closer to. This prevents cursor jitter near `start` from snapping to 1.0. -pub fn arc_parameter(angle: f64, start: f64, end: f64) -> f64 { - let span = kernel::geom2d::arc_span(start, end); - if span < 1e-12 { - return 0.0; - } - let norm_angle = kernel::geom2d::normalize_angle(angle); - let norm_start = kernel::geom2d::normalize_angle(start); - let delta = (norm_angle - norm_start).rem_euclid(std::f64::consts::TAU); - if delta <= span { - (delta / span).clamp(0.0, 1.0) - } else { - let gap = std::f64::consts::TAU - span; - let past_end = delta - span; - if past_end <= gap * 0.5 { - 1.0 - } else { - 0.0 - } - } -} - -/// Finds the closest parametric parameter `t` on an origin-centered ellipse -/// `(x/a)^2 + (y/b)^2 = 1` for an arbitrary query point `(u, v)` in local coordinates, -/// using David Eberly's exact orthogonal projection algorithm. -/// -/// Unlike radial projection `(v/b).atan2(u/a)` which deviates by up to 20-40 degrees -/// when the query point is slightly off the ellipse perimeter (e.g. within screen pick box), -/// this finds the true orthogonal projection on the ellipse regardless of zoom level. -pub fn ellipse_closest_parameter(a: f64, b: f64, u: f64, v: f64) -> f64 { - if a < 1e-12 || b < 1e-12 { - return 0.0; - } - if (a - b).abs() < 1e-9 { - return v.atan2(u); - } - if a < b { - let t_swapped = ellipse_closest_parameter(b, a, v, u); - return std::f64::consts::FRAC_PI_2 - t_swapped; - } - - let sign_x = if u >= 0.0 { 1.0 } else { -1.0 }; - let sign_y = if v >= 0.0 { 1.0 } else { -1.0 }; - let u_abs = u.abs(); - let v_abs = v.abs(); - - if v_abs < 1e-12 { - let x = if u_abs < (a * a - b * b) / a { - a * a * u_abs / (a * a - b * b) - } else { - a - }; - let y = b * (1.0 - (x / a).powi(2)).max(0.0).sqrt(); - return (sign_y * y / b).atan2(sign_x * x / a); - } - - if u_abs < 1e-12 { - return (sign_y * 1.0).atan2(0.0); - } - - let a2 = a * a; - let b2 = b * b; - let mut t0 = -b2 + b * v_abs; - let mut t1 = -b2 + (a * u_abs).hypot(b * v_abs); - let mut t = t0; - - for _ in 0..25 { - let t_a = t + a2; - let t_b = t + b2; - if t_a.abs() < 1e-12 || t_b.abs() < 1e-12 { - break; - } - let f = (a * u_abs / t_a).powi(2) + (b * v_abs / t_b).powi(2) - 1.0; - if f.abs() < 1e-12 { - break; - } - let df = -2.0 * (a2 * u_abs * u_abs / (t_a.powi(3)) + b2 * v_abs * v_abs / (t_b.powi(3))); - let t_next = t - f / df; - if t_next > t0 && t_next < t1 { - t = t_next; - } else { - if f > 0.0 { - t0 = t; - } else { - t1 = t; - } - t = 0.5 * (t0 + t1); - } - } - - let x = a2 * u_abs / (t + a2); - let y = b2 * v_abs / (t + b2); - (sign_y * y / b).atan2(sign_x * x / a) -} +pub use kernel::geom2d::{arc_parameter, ellipse_closest_parameter, lerp, normalize_angle}; /// Preview geometry keeps the density the commands have always used. const SEGMENTS_PER_RADIAN: f64 = geom2d::DEFAULT_SEGMENTS_PER_RADIAN;