@@ -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