From e021cf4575087b4df1b22bd2595f1089bc18f99f Mon Sep 17 00:00:00 2001
From: Alex Shvartzkop <dudesuchamazing@gmail.com>
Date: Tue, 11 Feb 2025 05:06:55 +0300
Subject: [PATCH] Connect first/last shapes within tolerance when building
 board outline.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/19901
Also see https://gitlab.com/kicad/code/kicad/-/issues/18125
---
 pcbnew/convert_shape_list_to_polygon.cpp | 49 +++++++++++++++++++++---
 1 file changed, 44 insertions(+), 5 deletions(-)

diff --git a/pcbnew/convert_shape_list_to_polygon.cpp b/pcbnew/convert_shape_list_to_polygon.cpp
index 0d239e4f05..a8032da2e2 100644
--- a/pcbnew/convert_shape_list_to_polygon.cpp
+++ b/pcbnew/convert_shape_list_to_polygon.cpp
@@ -354,16 +354,14 @@ bool doConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aShapeList, SHAPE_POLY_
                         std::swap( pstart, pend );
                     }
 
+                    // Snap the arc start point to avoid potential self-intersections
+                    pstart = prevPt;
+
                     SHAPE_ARC sarc( pstart, pmid, pend, 0 );
 
                     SHAPE_LINE_CHAIN arcChain;
                     arcChain.Append( sarc, aErrorMax );
 
-                    // if this arc is after another object, pop off the first point
-                    // the previous point from the last object should be already close enough as part of chaining
-                    if( prevGraphic != nullptr )
-                        arcChain.Remove( 0 );
-
                     if( !aAllowUseArcsInPolygons )
                         arcChain.ClearArcs();
 
@@ -454,6 +452,47 @@ bool doConvertOutlineToPolygon( std::vector<PCB_SHAPE*>& aShapeList, SHAPE_POLY_
                 // Finished, or ran into trouble...
                 if( close_enough( startPt, prevPt, aChainingEpsilon ) )
                 {
+                    if( startPt != prevPt && currContour.PointCount() > 2 )
+                    {
+                        // Snap the last shape's endpoint to the outline startpoint
+                        PCB_SHAPE* owner = fetchOwner( currContour.CSegment( -1 ) );
+
+                        if( currContour.IsArcEnd( currContour.PointCount() - 1 ) )
+                        {
+                            SHAPE_ARC arc = currContour.Arc(
+                                    currContour.ArcIndex( currContour.PointCount() - 1 ) );
+
+                            // Snap the arc endpoint
+                            SHAPE_ARC sarc( arc.GetP0(), arc.GetArcMid(), startPt, 0 );
+
+                            SHAPE_LINE_CHAIN arcChain;
+                            arcChain.Append( sarc, aErrorMax );
+
+                            if( !aAllowUseArcsInPolygons )
+                                arcChain.ClearArcs();
+
+                            // Set shapeOwners for arcChain points created by appending the sarc:
+                            for( int ii = 1; ii < arcChain.PointCount(); ++ii )
+                            {
+                                shapeOwners[std::make_pair( arcChain.CPoint( ii - 1 ),
+                                                            arcChain.CPoint( ii ) )] = owner;
+                            }
+
+                            currContour.RemoveShape( currContour.PointCount() - 1 );
+                            currContour.Append( arcChain );
+                        }
+                        else
+                        {
+                            // Snap the segment endpoint
+                            currContour.SetPoint( -1, startPt );
+
+                            shapeOwners[std::make_pair( currContour.CPoint( -2 ),
+                                                        currContour.CPoint( -1 ) )] = owner;
+                        }
+
+                        prevPt = startPt;
+                    }
+
                     currContour.SetClosed( true );
                     break;
                 }