From e83c8a28a599e4f5bc3019ae1f4cedd66914dda8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 17 Sep 2026 16:17:58 +0000 Subject: [PATCH] fix(trim): skip polyline segments with no plan-view shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `sample_entity_xy` walks a polyline's exploded segments and drops the vertex each segment shares with the previous one. A segment whose curve plane is upright — any non-horizontal run of a 3D polyline — has no XY curve, so `entity_curve_xy` declines it and the segment samples to nothing. With nothing accumulated yet, the seam test compared the two empty ends as equal and then sliced the empty sample from index 1: panicked at src/modules/draw/modify/trim.rs:3982:42: range start index 1 out of range for slice of length 0 On the web build that panic unwinds through winit's event-loop runner and leaves its state borrowed, which is the "RefCell already borrowed" follow-on in the same report. Skip an empty sample instead, which leaves the seam handling to the segments that do have a plan-view shape. Fixes HakanSeven12/OpenCADStudio#1318 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TQzYZsXBwPZ5HwBaGitEUV (cherry picked from commit ac9a6ce91ad7ad13adf79ba5cb494fdcf0bd4071) --- src/modules/draw/modify/trim.rs | 56 +++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/modules/draw/modify/trim.rs b/src/modules/draw/modify/trim.rs index b18573986..31f29e3cd 100644 --- a/src/modules/draw/modify/trim.rs +++ b/src/modules/draw/modify/trim.rs @@ -3810,6 +3810,55 @@ mod tests { other => panic!("expected LwPolyline, got {other:?}"), } } + + /// #1318 repro: a 3D polyline whose first segment is not horizontal has no + /// plan-view shape, so it samples to nothing. The seam test then compared + /// two empty ends as equal and sliced the empty sample from index 1. + #[test] + fn sampling_skips_segments_with_no_plan_shape() { + use acadrust::entities::{Polyline3D, Vertex3DPolyline}; + + let mut pl = Polyline3D::new(); + pl.vertices = vec![ + // Vertical: an upright curve plane, dropped by `entity_curve_xy`. + Vertex3DPolyline::from_xyz(0.0, 0.0, 0.0), + Vertex3DPolyline::from_xyz(0.0, 0.0, 10.0), + // Horizontal, so this one does sample. + Vertex3DPolyline::from_xyz(10.0, 0.0, 10.0), + ]; + + let pts = sample_entity_xy(&EntityType::Polyline3D(pl)); + assert!( + !pts.is_empty(), + "the horizontal segment still has to be sampled" + ); + assert!( + pts.iter().all(|p| p.iter().all(|c| c.is_finite())), + "sampled points stay finite: {pts:?}" + ); + } + + /// The same skip must not swallow a leading segment that does sample, and + /// must keep dropping the duplicated seam vertex between two of them. + #[test] + fn sampling_still_joins_segments_at_their_seam() { + use acadrust::entities::{Polyline3D, Vertex3DPolyline}; + + let mut pl = Polyline3D::new(); + pl.vertices = vec![ + Vertex3DPolyline::from_xyz(0.0, 0.0, 0.0), + Vertex3DPolyline::from_xyz(10.0, 0.0, 0.0), + Vertex3DPolyline::from_xyz(10.0, 10.0, 0.0), + ]; + + let pts = sample_entity_xy(&EntityType::Polyline3D(pl)); + let seam = [10.0, 0.0]; + assert_eq!( + pts.iter().filter(|p| **p == seam).count(), + 1, + "the shared vertex appears once, not twice: {pts:?}" + ); + } } // ══════════════════════════════════════════════════════════════════════════ @@ -3978,6 +4027,13 @@ fn sample_entity_xy(e: &EntityType) -> Vec<[f64; 2]> { let mut pts: Vec<[f64; 2]> = Vec::new(); for seg in crate::modules::draw::modify::explode::explode_polyline_segments(e) { let sp = sample_entity_xy(&seg); + // A segment with no plan-view shape samples to nothing — an + // edge-on plane has no XY curve, so `entity_curve_xy` declines it. + // Skipping it here also keeps the seam test below from comparing + // two `None`s and then slicing an empty sample (#1318). + if sp.is_empty() { + continue; + } if pts.last() == sp.first() { pts.extend_from_slice(&sp[1..]); } else {