Skip to content

Commit 1db1ea0

Browse files
HakanSeven12claude
andcommitted
fix(fillet): polyline re-fillet replaces the old arc, open ends close, arc segments are not filleted as chords
Fixes #1422 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 0bfd5dc commit 1db1ea0

1 file changed

Lines changed: 80 additions & 31 deletions

File tree

‎src/modules/draw/modify/fillet.rs‎

Lines changed: 80 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,14 @@ fn compute_fillet_entities(
817817
radius: f64,
818818
) -> Option<(EntityType, EntityType, Option<EntityType>)> {
819819
let z = e1.elevation();
820+
// A polyline arc segment would be filleted as its straight chord.
821+
let picks_arc_segment = |entity: &FilletEntity| {
822+
matches!(entity, FilletEntity::LwPoly { poly, seg_idx, .. }
823+
if poly.vertices.get(*seg_idx).is_some_and(|vertex| vertex.bulge.abs() > 1e-12))
824+
};
825+
if picks_arc_segment(e1) || picks_arc_segment(e2) {
826+
return None;
827+
}
820828

821829
match (e1, e2) {
822830
// ── Line × Line ───────────────────────────────────────────────────
@@ -848,24 +856,39 @@ fn compute_fillet_entities(
848856
..
849857
},
850858
) if h1 == h2 => {
851-
// Adjacent segments share a corner vertex — fillet that corner.
852-
let (low, high) = if *s1 < *s2 { (*s1, *s2) } else { (*s2, *s1) };
853859
let n = p1.vertices.len();
854-
// The wrap-around corner of a closed polyline joins the last
855-
// segment (n-1) and the first (0); their shared vertex is v0.
856-
let wrap = p1.is_closed && low == 0 && high == n.saturating_sub(1);
857-
// Segments must be consecutive, or the wrap-around pair above.
858-
if !(high == low + 1 || wrap) {
859-
return None;
860+
let closed = p1.is_closed;
861+
let seg_count = if closed { n } else { n.saturating_sub(1) };
862+
let next = |seg: usize| if closed { (seg + 1) % n } else { seg + 1 };
863+
let straight = |seg: usize| seg < seg_count && p1.vertices[seg].bulge.abs() < 1e-12;
864+
// `before` runs into the corner and `after` leaves it. Between them
865+
// sits nothing (a sharp corner), an old fillet arc being replaced,
866+
// or — on an open polyline — the gap between its two ends, which
867+
// the fillet closes. Arc segments themselves are never filleted:
868+
// treating one as its chord bent the result across the shape.
869+
enum Between {
870+
Corner,
871+
Arc(usize),
872+
OpenEnds,
860873
}
861-
// `before_seg` ends at the shared corner vertex, `after_seg` starts
862-
// there. For the wrap corner that is seg n-1 → v0 → seg 0; for a
863-
// normal corner it is seg low → v[high] → seg high.
864-
let (before_seg, after_seg, corner_idx) = if wrap {
865-
(high, low, 0)
866-
} else {
867-
(low, high, high)
874+
let order = |a: usize, b: usize| {
875+
if next(a) == b {
876+
Some((a, b, Between::Corner))
877+
} else if next(a) < seg_count
878+
&& next(next(a)) == b
879+
&& !straight(next(a))
880+
{
881+
Some((a, b, Between::Arc(next(a))))
882+
} else if !closed && seg_count >= 2 && a == seg_count - 1 && b == 0 {
883+
Some((a, b, Between::OpenEnds))
884+
} else {
885+
None
886+
}
868887
};
888+
let (before_seg, after_seg, between) = order(*s1, *s2).or_else(|| order(*s2, *s1))?;
889+
if !straight(before_seg) || !straight(after_seg) {
890+
return None;
891+
}
869892
let l1 = lwpoly_seg_as_line(p1, before_seg);
870893
let l2 = lwpoly_seg_as_line(p1, after_seg);
871894
// Re-map click to whichever segment each was picked on.
@@ -874,23 +897,49 @@ fn compute_fillet_entities(
874897
} else {
875898
(click2, click1)
876899
};
877-
match compute_fillet(&l1, c1, &l2, c2, radius)? {
878-
(EntityType::Line(tl1), EntityType::Line(tl2), maybe_arc) => {
879-
let t1 = [tl1.end.x, tl1.end.y]; // trimmed end of seg before corner
880-
let t2 = [tl2.start.x, tl2.start.y]; // trimmed start of seg after corner
881-
let bulge = if let Some(EntityType::Arc(ref fa)) = maybe_arc {
882-
// center from arc entity
883-
compute_bulge(t1, t2, [fa.center.x, fa.center.y])
884-
} else {
885-
0.0 // r=0, sharp corner
886-
};
887-
let new_poly = lwpoly_replace_corner(p1, corner_idx, t1, t2, bulge);
888-
let et = EntityType::LwPolyline(new_poly);
889-
// Return same rebuilt poly for both slots; caller uses only h1.
890-
Some((et.clone(), et, None))
900+
let (EntityType::Line(tl1), EntityType::Line(tl2), maybe_arc) =
901+
compute_fillet(&l1, c1, &l2, c2, radius)?
902+
else {
903+
return None;
904+
};
905+
let t1 = [tl1.end.x, tl1.end.y]; // trimmed end of seg before corner
906+
let t2 = [tl2.start.x, tl2.start.y]; // trimmed start of seg after corner
907+
let bulge = match maybe_arc {
908+
Some(EntityType::Arc(ref fa)) => compute_bulge(t1, t2, [fa.center.x, fa.center.y]),
909+
_ => 0.0, // r=0, sharp corner
910+
};
911+
let new_poly = match between {
912+
Between::Corner => lwpoly_replace_corner(p1, after_seg, t1, t2, bulge),
913+
// The old arc runs from its own vertex to the `after` vertex:
914+
// move both onto the new tangent points.
915+
Between::Arc(arc_seg) => {
916+
let mut poly = p1.clone();
917+
poly.common.handle = Handle::NULL;
918+
poly.vertices[arc_seg].location.x = t1[0];
919+
poly.vertices[arc_seg].location.y = t1[1];
920+
poly.vertices[arc_seg].bulge = bulge;
921+
poly.vertices[after_seg].location.x = t2[0];
922+
poly.vertices[after_seg].location.y = t2[1];
923+
poly
891924
}
892-
_ => None,
893-
}
925+
// The last vertex becomes the start of the fillet and the
926+
// closing segment is the fillet itself.
927+
Between::OpenEnds => {
928+
let mut poly = p1.clone();
929+
poly.common.handle = Handle::NULL;
930+
let last = n - 1;
931+
poly.vertices[last].location.x = t1[0];
932+
poly.vertices[last].location.y = t1[1];
933+
poly.vertices[last].bulge = bulge;
934+
poly.vertices[0].location.x = t2[0];
935+
poly.vertices[0].location.y = t2[1];
936+
poly.is_closed = true;
937+
poly
938+
}
939+
};
940+
let et = EntityType::LwPolyline(new_poly);
941+
// Return same rebuilt poly for both slots; caller uses only h1.
942+
Some((et.clone(), et, None))
894943
}
895944
// ── LwPoly × LwPoly (different entities) ──────────────────────────
896945
(

0 commit comments

Comments
 (0)