From e1f76ea2ab5411f30d4f37aa0ec1bf0fa9280d2b Mon Sep 17 00:00:00 2001 From: Jeff Young Date: Sun, 27 Dec 2020 01:00:41 +0000 Subject: [PATCH] Gracefully handle dots in the outline path. Fixes https://gitlab.com/kicad/code/kicad/issues/6854 --- .../convert_drawsegment_list_to_polygon.cpp | 133 ++++++++---------- 1 file changed, 59 insertions(+), 74 deletions(-) diff --git a/pcbnew/convert_drawsegment_list_to_polygon.cpp b/pcbnew/convert_drawsegment_list_to_polygon.cpp index 18f44216e8..44c4c6aecf 100644 --- a/pcbnew/convert_drawsegment_list_to_polygon.cpp +++ b/pcbnew/convert_drawsegment_list_to_polygon.cpp @@ -47,25 +47,6 @@ */ const wxChar* traceBoardOutline = wxT( "KICAD_BOARD_OUTLINE" ); -/** - * Function close_ness - * is a non-exact distance (also called Manhattan distance) used to approximate - * the distance between two points. - * The distance is very in-exact, but can be helpful when used - * to pick between alternative neighboring points. - * @param aLeft is the first point - * @param aRight is the second point - * @return unsigned - a measure of proximity that the caller knows about, in BIU, - * but remember it is only an approximation. - */ - -static unsigned close_ness( const wxPoint& aLeft, const wxPoint& aRight ) -{ - // Don't need an accurate distance calculation, just something - // approximating it, for relative ordering. - return unsigned( std::abs( aLeft.x - aRight.x ) + abs( aLeft.y - aRight.y ) ); -} - /** * Function close_enough * is a local and tunable method of qualifying the proximity of two points. @@ -75,27 +56,23 @@ static unsigned close_ness( const wxPoint& aLeft, const wxPoint& aRight ) * @param aLimit is a measure of proximity that the caller knows about. * @return bool - true if the two points are close enough, else false. */ -inline bool close_enough( const wxPoint& aLeft, const wxPoint& aRight, unsigned aLimit ) +bool close_enough( VECTOR2I aLeft, VECTOR2I aRight, unsigned aLimit ) { - // We don't use an accurate distance calculation, just something - // approximating it, since aLimit is non-exact anyway except when zero. - return close_ness( aLeft, aRight ) <= aLimit; + return ( aLeft - aRight ).SquaredEuclideanNorm() <= SEG::Square( aLimit ); } /** - * Function close_st + * Function closer_to_first * Local method which qualifies whether the start or end point of a segment is closest to a point. * - * @param aReference is the reference point + * @param aRef is the reference point * @param aFirst is the first point * @param aSecond is the second point - * @return bool - true if the the first point is closest to the reference, otherwise false. + * @return bool - true if the first point is closest to the reference, otherwise false. */ -inline bool close_st( const wxPoint& aReference, const wxPoint& aFirst, const wxPoint& aSecond ) +bool closer_to_first( VECTOR2I aRef, VECTOR2I aFirst, VECTOR2I aSecond ) { - // We don't use an accurate distance calculation, just something - // approximating to find the closest to the reference. - return close_ness( aReference, aFirst ) <= close_ness( aReference, aSecond ); + return ( aRef - aFirst ).SquaredEuclideanNorm() < ( aRef - aSecond ).SquaredEuclideanNorm(); } @@ -111,70 +88,78 @@ inline bool close_st( const wxPoint& aReference, const wxPoint& aFirst, const wx static PCB_SHAPE* findNext( PCB_SHAPE* aShape, const wxPoint& aPoint, const std::vector& aList, unsigned aLimit ) { - unsigned min_d = INT_MAX; - int ndx_min = 0; - - // find the point closest to aPoint and perhaps exactly matching aPoint. - for( size_t i = 0; i < aList.size(); ++i ) + // Look for an unused, exact hit + for( PCB_SHAPE* graphic : aList ) { - PCB_SHAPE* graphic = aList[i]; - - if( graphic == aShape ) + if( graphic == aShape || ( graphic->GetFlags() & SKIP_STRUCT ) != 0 ) continue; - unsigned d; - switch( graphic->GetShape() ) { case S_ARC: if( aPoint == graphic->GetArcStart() || aPoint == graphic->GetArcEnd() ) - { return graphic; - } - d = close_ness( aPoint, graphic->GetArcStart() ); - if( d < min_d ) - { - min_d = d; - ndx_min = i; - } - - d = close_ness( aPoint, graphic->GetArcEnd() ); - if( d < min_d ) - { - min_d = d; - ndx_min = i; - } break; default: if( aPoint == graphic->GetStart() || aPoint == graphic->GetEnd() ) - { return graphic; + } + } + + // Search again for anything that's close, even something already used. (The latter is + // important for error reporting.) + VECTOR2I pt( aPoint ); + SEG::ecoord closest_dist_sq = SEG::Square( aLimit ); + PCB_SHAPE* closest_graphic = nullptr; + SEG::ecoord d_sq; + + for( PCB_SHAPE* graphic : aList ) + { + if( graphic == aShape ) + continue; + + switch( graphic->GetShape() ) + { + case S_ARC: + d_sq = ( pt - graphic->GetArcStart() ).SquaredEuclideanNorm(); + + if( d_sq < closest_dist_sq ) + { + closest_dist_sq = d_sq; + closest_graphic = graphic; } - d = close_ness( aPoint, graphic->GetStart() ); - if( d < min_d ) + d_sq = ( pt - graphic->GetArcEnd() ).SquaredEuclideanNorm(); + + if( d_sq < closest_dist_sq ) { - min_d = d; - ndx_min = i; + closest_dist_sq = d_sq; + closest_graphic = graphic; + } + break; + + default: + d_sq = ( pt - graphic->GetStart() ).SquaredEuclideanNorm(); + + if( d_sq < closest_dist_sq ) + { + closest_dist_sq = d_sq; + closest_graphic = graphic; } - d = close_ness( aPoint, graphic->GetEnd() ); - if( d < min_d ) + d_sq = ( pt - graphic->GetEnd() ).SquaredEuclideanNorm(); + + if( d_sq < closest_dist_sq ) { - min_d = d; - ndx_min = i; + closest_dist_sq = d_sq; + closest_graphic = graphic; } } } - if( min_d <= aLimit ) - { - return aList[ndx_min]; - } - - return NULL; + return closest_graphic; // Note: will be nullptr if nothing within aLimit } @@ -439,7 +424,7 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& // Use the line segment end point furthest away from prevPt as we assume // the other end to be ON prevPt or very close to it. - if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) ) + if( closer_to_first( prevPt, graphic->GetStart(), graphic->GetEnd()) ) nextPt = graphic->GetEnd(); else nextPt = graphic->GetStart(); @@ -502,7 +487,7 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& // prevPt as we assume the other end to be ON prevPt or // very close to it. - if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) ) + if( closer_to_first( prevPt, graphic->GetStart(), graphic->GetEnd()) ) { nextPt = graphic->GetEnd(); } @@ -697,7 +682,7 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& // prevPt as we assume the other end to be ON prevPt or // very close to it. - if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) ) + if( closer_to_first( prevPt, graphic->GetStart(), graphic->GetEnd()) ) nextPt = graphic->GetEnd(); else nextPt = graphic->GetStart(); @@ -758,7 +743,7 @@ bool ConvertOutlineToPolygon( std::vector& aSegList, SHAPE_POLY_SET& // prevPt as we assume the other end to be ON prevPt or // very close to it. - if( close_st( prevPt, graphic->GetStart(), graphic->GetEnd() ) ) + if( closer_to_first( prevPt, graphic->GetStart(), graphic->GetEnd()) ) { nextPt = graphic->GetEnd(); }