forked from 1b-agent/opencadstudio-upstream
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 <[email protected]>
This commit is contained in:
1 parent
66ac8c4ec0
commit
eac19348d5
4 files changed
+7
-170
No files matched your search
Generated
+3
-3
@@ -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",
|
||||
]
|
||||
|
||||
+3
-3
@@ -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"] }
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user