From 09cd50ea627627654fd9a947d05d40d09e74fb3e Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Fri, 2 Oct 2026 10:06:10 +0300 Subject: [PATCH] fix(trim): polyline vertices are not cutting edges; self-cuts only as an edge #1567 made every vertex a trim limit, so a polyline no edge crosses lost the clicked segment and a trimmed piece stopped at the next corner instead of running on to the next cutting edge. Vertices are no cuts again, a polyline with no cut is left alone, and its own crossings cut it only when it is among the cutting edges. The crossing window's unused pick corner is dropped. Co-Authored-By: Claude Opus 5.5 --- src/modules/draw/modify/trim.rs | 53 +++++++++++---------------------- 1 file changed, 18 insertions(+), 35 deletions(-) diff --git a/src/modules/draw/modify/trim.rs b/src/modules/draw/modify/trim.rs index ecb0e3eb8..130151dad 100644 --- a/src/modules/draw/modify/trim.rs +++ b/src/modules/draw/modify/trim.rs @@ -1170,10 +1170,11 @@ fn lwpoly_segment_curve( /// polyline intersect. /// /// Segment neighbours are deliberately excluded: their common vertex is a -/// normal polyline vertex, not a self-intersection. -fn lwpoly_self_cut_params(poly: &LwPolyline) -> Vec { +/// normal polyline vertex, not a self-intersection. A polyline cuts itself +/// only when it is one of the cutting edges. +fn lwpoly_self_cut_params(poly: &LwPolyline, geos: &[Geo]) -> Vec { let n = poly.vertices.len(); - if n < 3 { + if n < 3 || !geos.iter().any(|geo| geo.handle() == poly.common.handle) { return Vec::new(); } @@ -1286,7 +1287,6 @@ fn trim_lwpolyline(poly: &LwPolyline, cx: f64, cy: f64, geos: &[Geo]) -> Option< poly.vertices[i % n].bulge }; - // Boundary cuts as global params (segment index + local u). // External boundary cuts as global params (segment index + local u). let mut cuts: Vec = Vec::new(); @@ -1307,25 +1307,16 @@ fn trim_lwpolyline(poly: &LwPolyline, cx: f64, cy: f64, geos: &[Geo]) -> Option< } // A polyline can also cut itself. These are intersections between - // non-adjacent segments belonging to this same LwPolyline. - cuts.extend(lwpoly_self_cut_params(poly)); - - // Every polyline vertex is also a valid trim boundary. This allows an entire - // segment between two vertices to be removed even when no external entity - // crosses that particular segment. - if closed { - for i in 0..seg_count { - cuts.push(i as f64); - } - } else { - // 0 and `total` are added explicitly below as the open polyline ends. - for i in 1..seg_count { - cuts.push(i as f64); - } - } + // non-adjacent segments belonging to this same LwPolyline. Its vertices + // are not cuts: the trimmed piece runs on past corners to the next + // cutting edge, and a polyline no edge crosses is left alone. + cuts.extend(lwpoly_self_cut_params(poly, geos)); cuts.sort_by(f64::total_cmp); cuts.dedup_by(|a, b| (*a - *b).abs() < 1e-6); + if cuts.is_empty() { + return None; + } // Click param: nearest point on the polyline. let mut best = (f64::INFINITY, 0.0_f64); @@ -1430,7 +1421,11 @@ fn trim_lwpolyline(poly: &LwPolyline, cx: f64, cy: f64, geos: &[Geo]) -> Option< } } - Some(out) + if out.is_empty() { + None + } else { + Some(out) + } } // ── Extend helpers ──────────────────────────────────────────────────────── @@ -1764,9 +1759,6 @@ enum TrimMode { struct CrossingWindow { min: [f64; 2], max: [f64; 2], - /// The first corner is the trim-side hint, matching the side from which - /// the crossing window was dragged. - pick: [f64; 2], } fn segment_window_range( @@ -1828,7 +1820,7 @@ fn crossing_trim_lwpolyline( // Compute the polyline's own non-adjacent segment intersections once. // These global parameters will later be mapped back into each source segment. - let self_cuts = lwpoly_self_cut_params(poly); + let self_cuts = lwpoly_self_cut_params(poly, geos); for i in 0..seg_count { let a = vertex_xy(i); @@ -1859,7 +1851,6 @@ fn crossing_trim_lwpolyline( continue; }; // External intersections on this segment. - // External intersections on this segment. let mut cuts = polyline_seg_ts(a, b, bulge, handle, geos); // Also include self-intersections belonging to this source segment. @@ -2923,11 +2914,7 @@ impl CadCommand for TrimCommand { if !matches!(self.mode, TrimMode::Pick) || fence.len() < 2 { return None; } - let window = window.map(|(min, max)| CrossingWindow { - min, - max, - pick: fence[0], - }); + let window = window.map(|(min, max)| CrossingWindow { min, max }); let replacements = fence_pass( &self.all_entities, &self.geos, @@ -3307,7 +3294,6 @@ impl CadCommand for TrimCommand { let window = CrossingWindow { min: [p1[0].min(p2[0]), p1[1].min(p2[1])], max: [p1[0].max(p2[0]), p1[1].max(p2[1])], - pick: p1, }; self.fence_run(&rect, Some(window)) } @@ -3339,7 +3325,6 @@ impl CadCommand for TrimCommand { let window = CrossingWindow { min: [p1[0].min(p2[0]), p1[1].min(p2[1])], max: [p1[0].max(p2[0]), p1[1].max(p2[1])], - pick: p1, }; out.extend(fence_result_preview( &self.all_entities, @@ -3773,7 +3758,6 @@ impl CadCommand for ExtendCommand { let window = CrossingWindow { min: [p1[0].min(p2[0]), p1[1].min(p2[1])], max: [p1[0].max(p2[0]), p1[1].max(p2[1])], - pick: p1, }; self.fence_run(&rect, Some(window)) } @@ -3805,7 +3789,6 @@ impl CadCommand for ExtendCommand { let window = CrossingWindow { min: [p1[0].min(p2[0]), p1[1].min(p2[1])], max: [p1[0].max(p2[0]), p1[1].max(p2[1])], - pick: p1, }; out.extend(fence_result_preview( &self.all_entities,