From f9c3546551fc578cfa6934adc29cff51a96fa3ba Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Tue, 18 Aug 2026 20:12:30 +0300 Subject: [PATCH] fix(trim): keep retained geometry continuous Use kernel trim spans so unrelated boundary crossings do not split survivors. --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/modules/draw/modify/trim.rs | 89 +++++++++++++++++---------------- 3 files changed, 49 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8114161f2..829cec057 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -878,7 +878,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cadkernel" version = "0.1.0" -source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=b8ec104#b8ec104e86060fe7d5c698870915b5388569649b" +source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=ff950ce#ff950ce4146c9fb94a5478e6704ada01264dbf9f" dependencies = [ "acadrust", "cavalier_contours", diff --git a/Cargo.toml b/Cargo.toml index d726fa92b..41d691137 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ rfd = "0.17" clap = { version = "4", features = ["derive"] } env_logger = "0.11" acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "931c4ab", features = ["serde"] } -cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "b8ec104", features = ["acis", "offset"] } +cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "ff950ce", features = ["acis", "offset"] } dwg-thumbnailer = { path = "crates/dwg-thumbnailer" } flate2 = "1" image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] } diff --git a/src/modules/draw/modify/trim.rs b/src/modules/draw/modify/trim.rs index bb41968a4..20e5abd8d 100644 --- a/src/modules/draw/modify/trim.rs +++ b/src/modules/draw/modify/trim.rs @@ -29,8 +29,8 @@ use acadrust::{EntityType, Handle}; use glam::DVec3; use cadkernel::geom2d::nurbs::clamped_uniform_knots; use cadkernel::geom2d::{ - intersect as kernel_intersect, Arc as KernelArc, Circle as KernelCircle, Curve, - Extent as KernelExtent, + intersect as kernel_intersect, trim_spans as kernel_trim_spans, Arc as KernelArc, + Circle as KernelCircle, Curve, Extent as KernelExtent, Ellipse as KernelEllipse, EllipseArc as KernelEllipseArc, Line as KernelLine, NurbsCurve, Ray as KernelRay, Tolerance as KernelTolerance, XLine as KernelXLine, }; @@ -485,7 +485,24 @@ fn trim_ellipse(orig: &EllipseEnt, ts: &[f64], t_click: f64) -> Vec return vec![EntityType::Ellipse(e)]; } - trim_intervals(ts, t_click) + let major_radius = orig.major_axis.x.hypot(orig.major_axis.y); + if major_radius < 1e-9 { + return vec![]; + } + let curve = Curve::Ellipse(KernelEllipseArc { + ellipse: KernelEllipse { + centre: [orig.center.x, orig.center.y], + major_radius, + minor_radius: major_radius * orig.minor_axis_ratio, + major_axis: [ + orig.major_axis.x / major_radius, + orig.major_axis.y / major_radius, + ], + }, + start_parameter: t0, + end_parameter: t1, + }); + trim_intervals(&curve, ts, t_click) .into_iter() .filter_map(|(ta, tb)| { if (tb - ta).abs() < 1e-6 { @@ -545,11 +562,13 @@ fn extend_ellipse(orig: &EllipseEnt, t_click: f64, geos: &[Geo]) -> Option Vec { - let Some((t0, t1)) = spline_range(spl) else { + let Some(curve) = spline_to_nurbs(spl) else { return vec![]; }; + let (t0, t1) = curve.domain(); + let curve = Curve::Nurbs(curve); - trim_intervals(ts, t_click) + trim_intervals(&curve, ts, t_click) .into_iter() .filter_map(|(ta, tb)| { let t_lo = t0 + ta * (t1 - t0); @@ -642,39 +661,16 @@ fn extend_spline(spl: &SplineEnt, t_click: f64, geos: &[Geo]) -> Option Vec<(f64, f64)> { - cut_intervals(ts, t_click, KernelExtent::Bounded) -} - -/// The stretches a target keeps after the one holding the click is removed. -/// -/// The outer bounds come from how far the target's parameter runs, so a ray -/// keeps an interval that reaches infinity rather than one that stops at an -/// invented far end. Those infinities are what tell the caller a surviving -/// piece is still unbounded. -fn cut_intervals(ts: &[f64], t_click: f64, extent: KernelExtent) -> Vec<(f64, f64)> { - let (first, last) = match extent { - KernelExtent::Bounded => (0.0, 1.0), - KernelExtent::Forward => (0.0, f64::INFINITY), - KernelExtent::Infinite => (f64::NEG_INFINITY, f64::INFINITY), - }; - let mut bounds = vec![first]; - bounds.extend_from_slice(ts); - bounds.push(last); - bounds.dedup_by(|a, b| (*a - *b).abs() < 1e-6); - - let remove = bounds - .windows(2) - .position(|w| t_click >= w[0] - 1e-6 && t_click <= w[1] + 1e-6); - - bounds - .windows(2) - .enumerate() - .filter(|(idx, _)| Some(*idx) != remove) - .filter(|(_, w)| (w[1] - w[0]) > 1e-6) - .map(|(_, w)| (w[0], w[1])) - .collect() +fn trim_intervals(curve: &Curve, ts: &[f64], t_click: f64) -> Vec<(f64, f64)> { + kernel_trim_spans( + curve, + ts, + t_click, + KernelTolerance::new(CUT_TOLERANCE), + ) + .into_iter() + .map(|span| (span[0], span[1])) + .collect() } /// Trim a Line entity. Returns the surviving line segments. @@ -682,7 +678,8 @@ fn trim_line(orig: &LineEnt, ts: &[f64], t_click: f64) -> Vec { let p1 = [orig.start.x, orig.start.y]; let p2 = [orig.end.x, orig.end.y]; let z = orig.start.z; - trim_intervals(ts, t_click) + let curve = Curve::Line(KernelLine { start: p1, end: p2 }); + trim_intervals(&curve, ts, t_click) .into_iter() .filter_map(|(ta, tb)| { let a = lerp2(p1, p2, ta); @@ -768,8 +765,14 @@ fn trim_arc(orig: &ArcEnt, ts: &[f64], t_click: f64) -> Vec { } }; let angle_at = |t: f64| norm(a0) + span * t; + let curve = Curve::Arc(KernelArc { + centre: [orig.center.x, orig.center.y], + radius: orig.radius, + start_angle: a0, + end_angle: a1, + }); - trim_intervals(ts, t_click) + trim_intervals(&curve, ts, t_click) .into_iter() .filter_map(|(ta, tb)| { if (tb - ta).abs() < 1e-6 { @@ -1113,7 +1116,8 @@ fn trim_ray(orig: &RayEnt, ts: &[f64], t_click: f64) -> Vec { let dz = orig.direction.z; let pt = |t: f64| [bx + t * dx, by + t * dy, bz + t * dz]; - cut_intervals(ts, t_click, KernelExtent::Forward) + let curve = ray_curve(orig); + trim_intervals(&curve, ts, t_click) .into_iter() .filter_map(|(ta, tb)| { let pa = pt(ta); @@ -1159,7 +1163,8 @@ fn trim_xline(orig: &XLineEnt, ts: &[f64], t_click: f64) -> Vec { let dz = orig.direction.z; let pt = |t: f64| [bx + t * dx, by + t * dy, bz + t * dz]; - cut_intervals(ts, t_click, KernelExtent::Infinite) + let curve = xline_curve(orig); + trim_intervals(&curve, ts, t_click) .into_iter() .filter_map(|(ta, tb)| { let pa = pt(ta);