fix(trim): skip polyline segments with no plan-view shape

`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 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TQzYZsXBwPZ5HwBaGitEUV
This commit is contained in:
Claude committed 2026-09-17 16:17:58 +00:00
1 parent bec382a99b
commit ac9a6ce91a
1 file changed
+56
+56
View File
@@ -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 {