From 4e7fa189aa6f8f0d28b221b7059b16fc61258578 Mon Sep 17 00:00:00 2001 From: Seth Hillbrand Date: Mon, 25 Aug 2025 11:26:02 -0700 Subject: [PATCH] Refactor gfx import cleanup Break up monolithic function into responsibilities. Adjust cleanup to correctly modify each graphical pairing. Fix drc test to properly report gap distances that are relevant to outlines Fixes https://gitlab.com/kicad/code/kicad/-/issues/13090 --- pcbnew/convert_shape_list_to_polygon.cpp | 882 ++++++++++-------- pcbnew/drc/drc_test_provider_misc.cpp | 39 +- pcbnew/fix_board_shape.cpp | 172 +++- pcbnew/fix_board_shape.h | 6 +- pcbnew/graphics_cleaner.cpp | 6 +- .../pcb_io/easyeda/pcb_io_easyeda_parser.cpp | 12 +- .../easyedapro/pcb_io_easyedapro_parser.cpp | 14 +- pcbnew/tools/drawing_tool.cpp | 11 +- pcbnew/tools/edit_tool.cpp | 22 +- 9 files changed, 630 insertions(+), 534 deletions(-) diff --git a/pcbnew/convert_shape_list_to_polygon.cpp b/pcbnew/convert_shape_list_to_polygon.cpp index 7d4b13a41e..28cc61d21d 100644 --- a/pcbnew/convert_shape_list_to_polygon.cpp +++ b/pcbnew/convert_shape_list_to_polygon.cpp @@ -173,36 +173,374 @@ using KDTree = nanoflann::KDTreeSingleIndexAdaptor; +static void processClosedShape( PCB_SHAPE* aShape, SHAPE_LINE_CHAIN& aContour, + std::map, PCB_SHAPE*>& aShapeOwners, + int aErrorMax, bool aAllowUseArcsInPolygons ) +{ + switch( aShape->GetShape() ) + { + case SHAPE_T::POLY: + { + VECTOR2I prevPt; + bool firstPt = true; + + for( auto it = aShape->GetPolyShape().CIterate(); it; it++ ) + { + VECTOR2I pt = *it; + aContour.Append( pt ); + + if( firstPt ) + firstPt = false; + else + aShapeOwners[ std::make_pair( prevPt, pt ) ] = aShape; + + prevPt = pt; + } + aContour.SetClosed( true ); + break; + } + case SHAPE_T::CIRCLE: + { + VECTOR2I center = aShape->GetCenter(); + int radius = aShape->GetRadius(); + VECTOR2I start = center; + start.x += radius; + + SHAPE_ARC arc360( center, start, ANGLE_360, 0 ); + aContour.Append( arc360, aErrorMax ); + aContour.SetClosed( true ); + + for( int ii = 1; ii < aContour.PointCount(); ++ii ) + { + aShapeOwners[ std::make_pair( aContour.CPoint( ii-1 ), + aContour.CPoint( ii ) ) ] = aShape; + } + + if( !aAllowUseArcsInPolygons ) + aContour.ClearArcs(); + break; + } + case SHAPE_T::RECTANGLE: + { + std::vector pts = aShape->GetRectCorners(); + VECTOR2I prevPt; + bool firstPt = true; + + for( const VECTOR2I& pt : pts ) + { + aContour.Append( pt ); + + if( firstPt ) + firstPt = false; + else + aShapeOwners[ std::make_pair( prevPt, pt ) ] = aShape; + + prevPt = pt; + } + aContour.SetClosed( true ); + break; + } + default: + break; + } +} + +static void processShapeSegment( PCB_SHAPE* aShape, SHAPE_LINE_CHAIN& aContour, + VECTOR2I& aPrevPt, + std::map, PCB_SHAPE*>& aShapeOwners, + int aErrorMax, int aChainingEpsilon, bool aAllowUseArcsInPolygons ) +{ + switch( aShape->GetShape() ) + { + case SHAPE_T::SEGMENT: + { + VECTOR2I nextPt; + + if( closer_to_first( aPrevPt, aShape->GetStart(), aShape->GetEnd() ) ) + nextPt = aShape->GetEnd(); + else + nextPt = aShape->GetStart(); + + aContour.Append( nextPt ); + aShapeOwners[ std::make_pair( aPrevPt, nextPt ) ] = aShape; + aPrevPt = nextPt; + break; + } + case SHAPE_T::ARC: + { + VECTOR2I pstart = aShape->GetStart(); + VECTOR2I pmid = aShape->GetArcMid(); + VECTOR2I pend = aShape->GetEnd(); + + if( !close_enough( aPrevPt, pstart, aChainingEpsilon ) ) + { + wxASSERT( close_enough( aPrevPt, aShape->GetEnd(), aChainingEpsilon ) ); + std::swap( pstart, pend ); + } + + pstart = aPrevPt; + SHAPE_ARC sarc( pstart, pmid, pend, 0 ); + SHAPE_LINE_CHAIN arcChain; + arcChain.Append( sarc, aErrorMax ); + + if( !aAllowUseArcsInPolygons ) + arcChain.ClearArcs(); + + for( int ii = 1; ii < arcChain.PointCount(); ++ii ) + { + aShapeOwners[ std::make_pair( arcChain.CPoint( ii - 1 ), + arcChain.CPoint( ii ) ) ] = aShape; + } + + aContour.Append( arcChain ); + aPrevPt = pend; + break; + } + case SHAPE_T::BEZIER: + { + VECTOR2I nextPt; + bool reverse = false; + + if( closer_to_first( aPrevPt, aShape->GetStart(), aShape->GetEnd() ) ) + { + nextPt = aShape->GetEnd(); + } + else + { + nextPt = aShape->GetStart(); + reverse = true; + } + + aShape->RebuildBezierToSegmentsPointsList( aErrorMax ); + + if( reverse ) + { + for( int jj = aShape->GetBezierPoints().size() - 1; jj >= 0; jj-- ) + { + const VECTOR2I& pt = aShape->GetBezierPoints()[jj]; + + if( aPrevPt == pt ) + continue; + + aContour.Append( pt ); + aShapeOwners[ std::make_pair( aPrevPt, pt ) ] = aShape; + aPrevPt = pt; + } + } + else + { + for( const VECTOR2I& pt : aShape->GetBezierPoints() ) + { + if( aPrevPt == pt ) + continue; + + aContour.Append( pt ); + aShapeOwners[ std::make_pair( aPrevPt, pt ) ] = aShape; + aPrevPt = pt; + } + } + + aPrevPt = nextPt; + break; + } + default: + break; + } +} + +static std::map> buildContourHierarchy( const std::vector& aContours ) +{ + std::map> contourToParentIndexesMap; + + for( size_t ii = 0; ii < aContours.size(); ++ii ) + { + VECTOR2I firstPt = aContours[ii].GetPoint( 0 ); + std::vector parents; + + for( size_t jj = 0; jj < aContours.size(); ++jj ) + { + if( jj == ii ) + continue; + + const SHAPE_LINE_CHAIN& parentCandidate = aContours[jj]; + + if( parentCandidate.PointInside( firstPt, 0, true ) ) + parents.push_back( jj ); + } + + contourToParentIndexesMap[ii] = std::move( parents ); + } + + return contourToParentIndexesMap; +} + +static bool addOutlinesToPolygon( const std::vector& aContours, + const std::map>& aContourHierarchy, + SHAPE_POLY_SET& aPolygons, bool aAllowDisjoint, + OUTLINE_ERROR_HANDLER* aErrorHandler, + const std::function& aFetchOwner, + std::map& aContourToOutlineIdxMap ) +{ + for( const auto& [ contourIndex, parentIndexes ] : aContourHierarchy ) + { + if( parentIndexes.size() % 2 == 0 ) + { + // Even number of parents; top-level outline + if( !aAllowDisjoint && !aPolygons.IsEmpty() ) + { + if( aErrorHandler ) + { + BOARD_ITEM* a = aFetchOwner( aPolygons.Outline( 0 ).GetSegment( 0 ) ); + BOARD_ITEM* b = aFetchOwner( aContours[ contourIndex ].GetSegment( 0 ) ); + + if( a && b ) + { + (*aErrorHandler)( _( "(multiple board outlines not supported)" ), a, b, + aContours[ contourIndex ].GetPoint( 0 ) ); + return false; + } + } + } + + aPolygons.AddOutline( aContours[ contourIndex ] ); + aContourToOutlineIdxMap[ contourIndex ] = aPolygons.OutlineCount() - 1; + } + } + return true; +} + +static void addHolesToPolygon( const std::vector& aContours, + const std::map>& aContourHierarchy, + const std::map& aContourToOutlineIdxMap, + SHAPE_POLY_SET& aPolygons ) +{ + for( const auto& [ contourIndex, parentIndexes ] : aContourHierarchy ) + { + if( parentIndexes.size() % 2 == 1 ) + { + // Odd number of parents; we're a hole in the parent which has one fewer parents + const SHAPE_LINE_CHAIN& hole = aContours[ contourIndex ]; + + for( int parentContourIdx : parentIndexes ) + { + if( aContourHierarchy.at( parentContourIdx ).size() == parentIndexes.size() - 1 ) + { + int outlineIdx = aContourToOutlineIdxMap.at( parentContourIdx ); + aPolygons.AddHole( hole, outlineIdx ); + break; + } + } + } + } +} + +static bool checkSelfIntersections( SHAPE_POLY_SET& aPolygons, + OUTLINE_ERROR_HANDLER* aErrorHandler, + const std::function& aFetchOwner ) +{ + bool selfIntersecting = false; + std::vector segments; + size_t total = 0; + + for( int ii = 0; ii < aPolygons.OutlineCount(); ++ii ) + { + const SHAPE_LINE_CHAIN& contour = aPolygons.Outline( ii ); + total += contour.SegmentCount(); + + for( int jj = 0; jj < aPolygons.HoleCount( ii ); ++jj ) + { + const SHAPE_LINE_CHAIN& hole = aPolygons.Hole( ii, jj ); + total += hole.SegmentCount(); + } + } + + segments.reserve( total ); + + for( auto seg = aPolygons.IterateSegmentsWithHoles(); seg; seg++ ) + { + SEG segment = *seg; + + if( LexicographicalCompare( segment.A, segment.B ) > 0 ) + std::swap( segment.A, segment.B ); + + segments.push_back( segment ); + } + + std::sort( segments.begin(), segments.end(), + []( const SEG& a, const SEG& b ) + { + if( a.A != b.A ) + return LexicographicalCompare( a.A, b.A ) < 0; + return LexicographicalCompare( a.B, b.B ) < 0; + } ); + + for( size_t i = 0; i < segments.size(); ++i ) + { + const SEG& seg1 = segments[i]; + + for( size_t j = i + 1; j < segments.size(); ++j ) + { + const SEG& seg2 = segments[j]; + + if( seg2.A > seg1.B ) + break; + + if( seg1 == seg2 || ( seg1.A == seg2.B && seg1.B == seg2.A ) ) + { + if( aErrorHandler ) + { + BOARD_ITEM* a = aFetchOwner( seg1 ); + BOARD_ITEM* b = aFetchOwner( seg2 ); + (*aErrorHandler)( _( "(self-intersecting)" ), a, b, seg1.A ); + } + selfIntersecting = true; + } + else if( OPT_VECTOR2I pt = seg1.Intersect( seg2, true ) ) + { + if( aErrorHandler ) + { + BOARD_ITEM* a = aFetchOwner( seg1 ); + BOARD_ITEM* b = aFetchOwner( seg2 ); + (*aErrorHandler)( _( "(self-intersecting)" ), a, b, *pt ); + } + selfIntersecting = true; + } + } + } + + return !selfIntersecting; +} + // Helper function to find next shape using KD-tree static PCB_SHAPE* findNext( PCB_SHAPE* aShape, const VECTOR2I& aPoint, const KDTree& kdTree, - const PCB_SHAPE_ENDPOINTS_ADAPTOR& adaptor, unsigned aLimit ) + const PCB_SHAPE_ENDPOINTS_ADAPTOR& adaptor, double aChainingEpsilon ) { const double query_pt[2] = { static_cast( aPoint.x ), static_cast( aPoint.y ) }; - // Search for points within a very small radius for exact matches - std::vector> matches; - double search_radius_sq = static_cast( aLimit * aLimit ); - nanoflann::RadiusResultSet radiusResultSet( search_radius_sq, matches ); + uint32_t indices[2]; + double distances[2]; + kdTree.knnSearch( query_pt, 2, indices, distances ); - kdTree.findNeighbors( radiusResultSet, query_pt ); - - if( matches.empty() ) + if( distances[0] == std::numeric_limits::max() ) return nullptr; // Find the closest valid candidate PCB_SHAPE* closest_graphic = nullptr; - double closest_dist_sq = search_radius_sq; + double closest_dist_sq = aChainingEpsilon * aChainingEpsilon; - for( const auto& match : matches ) + for( size_t i = 0; i < 2; ++i ) { - PCB_SHAPE* candidate = adaptor.endpoints[match.first].second; + if( distances[i] == std::numeric_limits::max() ) + continue; + + PCB_SHAPE* candidate = adaptor.endpoints[indices[i]].second; if( candidate == aShape ) continue; - if( match.second < closest_dist_sq ) + if( distances[i] < closest_dist_sq ) { - closest_dist_sq = match.second; + closest_dist_sq = distances[i]; closest_graphic = candidate; } } @@ -225,10 +563,9 @@ bool doConvertOutlineToPolygon( std::vector& aShapeList, SHAPE_POLY_ // Pre-build KD-tree PCB_SHAPE_ENDPOINTS_ADAPTOR adaptor( aShapeList ); - KDTree kdTree( 2, adaptor, nanoflann::KDTreeSingleIndexAdaptorParams( 10 ) ); + KDTree kdTree( 2, adaptor ); - // Keep a list of where the various shapes came from so after doing our combined-polygon - // tests we can still report errors against the individual graphic items. + // Keep a list of where the various shapes came from std::map, PCB_SHAPE*> shapeOwners; auto fetchOwner = @@ -238,92 +575,34 @@ bool doConvertOutlineToPolygon( std::vector& aShapeList, SHAPE_POLY_ return it == shapeOwners.end() ? nullptr : it->second; }; - PCB_SHAPE* prevGraphic = nullptr; - VECTOR2I prevPt; - std::set> reportedGaps; - std::vector contours; contours.reserve( startCandidates.size() ); for( PCB_SHAPE* shape : startCandidates ) shape->ClearFlags( SKIP_STRUCT ); + // Process each shape to build contours while( startCandidates.size() ) { - graphic = (PCB_SHAPE*) *startCandidates.begin(); + graphic = *startCandidates.begin(); graphic->SetFlags( SKIP_STRUCT ); aCleaner.insert( graphic ); startCandidates.erase( startCandidates.begin() ); contours.emplace_back(); - SHAPE_LINE_CHAIN& currContour = contours.back(); currContour.SetWidth( graphic->GetWidth() ); - bool firstPt = true; - // Circles, rects and polygons are closed shapes unto themselves (and do not combine - // with other shapes), so process them separately. - if( graphic->GetShape() == SHAPE_T::POLY ) + // Handle closed shapes (circles, rects, polygons) + if( graphic->GetShape() == SHAPE_T::POLY || graphic->GetShape() == SHAPE_T::CIRCLE + || graphic->GetShape() == SHAPE_T::RECTANGLE ) { - for( auto it = graphic->GetPolyShape().CIterate(); it; it++ ) - { - VECTOR2I pt = *it; - - currContour.Append( pt ); - - if( firstPt ) - firstPt = false; - else - shapeOwners[ std::make_pair( prevPt, pt ) ] = graphic; - - prevPt = pt; - } - - currContour.SetClosed( true ); - } - else if( graphic->GetShape() == SHAPE_T::CIRCLE ) - { - VECTOR2I center = graphic->GetCenter(); - int radius = graphic->GetRadius(); - VECTOR2I start = center; - start.x += radius; - - // Add 360 deg Arc in currContour - SHAPE_ARC arc360( center, start, ANGLE_360, 0 ); - currContour.Append( arc360, aErrorMax ); - currContour.SetClosed( true ); - - // set shapeOwners for currContour points created by appending the arc360: - for( int ii = 1; ii < currContour.PointCount(); ++ii ) - { - shapeOwners[ std::make_pair( currContour.CPoint( ii-1 ), - currContour.CPoint( ii ) ) ] = graphic; - } - - if( !aAllowUseArcsInPolygons ) - currContour.ClearArcs(); - } - else if( graphic->GetShape() == SHAPE_T::RECTANGLE ) - { - std::vector pts = graphic->GetRectCorners(); - - for( const VECTOR2I& pt : pts ) - { - currContour.Append( pt ); - - if( firstPt ) - firstPt = false; - else - shapeOwners[ std::make_pair( prevPt, pt ) ] = graphic; - - prevPt = pt; - } - - currContour.SetClosed( true ); + processClosedShape( graphic, currContour, shapeOwners, aErrorMax, aAllowUseArcsInPolygons ); } else { + // Build chains for open shapes std::deque chain; chain.push_back( graphic ); @@ -331,69 +610,68 @@ bool doConvertOutlineToPolygon( std::vector& aShapeList, SHAPE_POLY_ VECTOR2I frontPt = graphic->GetStart(); VECTOR2I backPt = graphic->GetEnd(); - auto extendChain = - [&]( bool forward ) + auto extendChain = [&]( bool forward ) + { + PCB_SHAPE* curr = forward ? chain.back() : chain.front(); + VECTOR2I prev = forward ? backPt : frontPt; + + for( ;; ) + { + PCB_SHAPE* next = findNext( curr, prev, kdTree, adaptor, aChainingEpsilon ); + + if( next && !( next->GetFlags() & SKIP_STRUCT ) ) { - PCB_SHAPE* curr = forward ? chain.back() : chain.front(); - VECTOR2I prev = forward ? backPt : frontPt; + next->SetFlags( SKIP_STRUCT ); + aCleaner.insert( next ); + startCandidates.erase( next ); - for(;;) + if( forward ) + chain.push_back( next ); + else + chain.push_front( next ); + + if( closer_to_first( prev, next->GetStart(), next->GetEnd() ) ) + prev = next->GetEnd(); + else + prev = next->GetStart(); + + curr = next; + continue; + } + + if( next ) + { + PCB_SHAPE* chainEnd = forward ? chain.front() : chain.back(); + VECTOR2I chainPt = forward ? frontPt : backPt; + + if( next == chainEnd && close_enough( prev, chainPt, aChainingEpsilon ) ) { - PCB_SHAPE* next = - findNext( curr, prev, kdTree, adaptor, aChainingEpsilon ); - - if( next && !( next->GetFlags() & SKIP_STRUCT ) ) - { - next->SetFlags( SKIP_STRUCT ); - aCleaner.insert( next ); - startCandidates.erase( next ); - - if( forward ) - chain.push_back( next ); - else - chain.push_front( next ); - - if( closer_to_first( prev, next->GetStart(), next->GetEnd() ) ) - prev = next->GetEnd(); - else - prev = next->GetStart(); - - curr = next; - continue; - } - - if( next ) - { - PCB_SHAPE* chainEnd = forward ? chain.front() : chain.back(); - VECTOR2I chainPt = forward ? frontPt : backPt; - - if( next == chainEnd - && close_enough( prev, chainPt, aChainingEpsilon ) ) - { - closed = true; - } - else - { - if( aErrorHandler ) - (*aErrorHandler)( _( "(self-intersecting)" ), curr, next, prev ); - selfIntersecting = true; - } - } - - if( forward ) - backPt = prev; - else - frontPt = prev; - - break; + closed = true; } - }; + else + { + if( aErrorHandler ) + ( *aErrorHandler )( _( "(self-intersecting)" ), curr, next, prev ); + + selfIntersecting = true; + } + } + + if( forward ) + backPt = prev; + else + frontPt = prev; + + break; + } + }; extendChain( true ); if( !closed ) extendChain( false ); + // Process the chain to build the contour PCB_SHAPE* first = chain.front(); VECTOR2I startPt; @@ -413,132 +691,24 @@ bool doConvertOutlineToPolygon( std::vector& aShapeList, SHAPE_POLY_ } currContour.Append( startPt ); - prevPt = startPt; + VECTOR2I prevPt = startPt; - for( PCB_SHAPE* graphic : chain ) + for( PCB_SHAPE* shapeInChain : chain ) { - switch( graphic->GetShape() ) - { - case SHAPE_T::SEGMENT: - { - VECTOR2I nextPt; - - // 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( closer_to_first( prevPt, graphic->GetStart(), graphic->GetEnd() ) ) - nextPt = graphic->GetEnd(); - else - nextPt = graphic->GetStart(); - - currContour.Append( nextPt ); - shapeOwners[ std::make_pair( prevPt, nextPt ) ] = graphic; - prevPt = nextPt; - } - break; - - case SHAPE_T::ARC: - { - VECTOR2I pstart = graphic->GetStart(); - VECTOR2I pmid = graphic->GetArcMid(); - VECTOR2I pend = graphic->GetEnd(); - - if( !close_enough( prevPt, pstart, aChainingEpsilon ) ) - { - wxASSERT( close_enough( prevPt, graphic->GetEnd(), aChainingEpsilon ) ); - - 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( !aAllowUseArcsInPolygons ) - arcChain.ClearArcs(); - - for( int ii = 1; ii < arcChain.PointCount(); ++ii ) - { - shapeOwners[ std::make_pair( arcChain.CPoint( ii - 1 ), - arcChain.CPoint( ii ) ) ] = graphic; - } - - currContour.Append( arcChain ); - prevPt = pend; - } - break; - - case SHAPE_T::BEZIER: - { - // We do not support Bezier curves in polygons, so approximate with a series - // of short lines and put those line segments into the !same! PATH. - VECTOR2I nextPt; - bool reverse = false; - - if( closer_to_first( prevPt, graphic->GetStart(), graphic->GetEnd() ) ) - { - nextPt = graphic->GetEnd(); - } - else - { - nextPt = graphic->GetStart(); - reverse = true; - } - - graphic->RebuildBezierToSegmentsPointsList( aErrorMax ); - - if( reverse ) - { - for( int jj = graphic->GetBezierPoints().size() - 1; jj >= 0; jj-- ) - { - const VECTOR2I& pt = graphic->GetBezierPoints()[jj]; - - if( prevPt == pt ) - continue; - - currContour.Append( pt ); - shapeOwners[ std::make_pair( prevPt, pt ) ] = graphic; - prevPt = pt; - } - } - else - { - for( const VECTOR2I& pt : graphic->GetBezierPoints() ) - { - if( prevPt == pt ) - continue; - - currContour.Append( pt ); - shapeOwners[ std::make_pair( prevPt, pt ) ] = graphic; - prevPt = pt; - } - } - - prevPt = nextPt; - } - break; - - default: - UNIMPLEMENTED_FOR( graphic->SHAPE_T_asString() ); - return false; - } + processShapeSegment( shapeInChain, currContour, prevPt, shapeOwners, + aErrorMax, aChainingEpsilon, aAllowUseArcsInPolygons ); } - if( close_enough( currContour.CPoint( 0 ), currContour.CLastPoint(), - aChainingEpsilon ) ) + // Handle contour closure + if( close_enough( currContour.CPoint( 0 ), currContour.CLastPoint(), aChainingEpsilon ) ) { - if( currContour.CPoint( 0 ) != currContour.CLastPoint() - && currContour.PointCount() > 2 ) + if( currContour.CPoint( 0 ) != currContour.CLastPoint() && currContour.PointCount() > 2 ) { PCB_SHAPE* owner = fetchOwner( currContour.CSegment( -1 ) ); if( currContour.IsArcEnd( currContour.PointCount() - 1 ) ) { - SHAPE_ARC arc = - currContour.Arc( currContour.ArcIndex( currContour.PointCount() - 1 ) ); + SHAPE_ARC arc = currContour.Arc( currContour.ArcIndex( currContour.PointCount() - 1 ) ); SHAPE_ARC sarc( arc.GetP0(), arc.GetArcMid(), currContour.CPoint( 0 ), 0 ); @@ -549,10 +719,7 @@ bool doConvertOutlineToPolygon( std::vector& aShapeList, SHAPE_POLY_ arcChain.ClearArcs(); for( int ii = 1; ii < arcChain.PointCount(); ++ii ) - { - shapeOwners[ std::make_pair( arcChain.CPoint( ii - 1 ), - arcChain.CPoint( ii ) ) ] = owner; - } + shapeOwners[std::make_pair( arcChain.CPoint( ii - 1 ), arcChain.CPoint( ii ) )] = owner; currContour.RemoveShape( currContour.PointCount() - 1 ); currContour.Append( arcChain ); @@ -570,32 +737,56 @@ bool doConvertOutlineToPolygon( std::vector& aShapeList, SHAPE_POLY_ } else { - auto report_gap = - [&]( PCB_SHAPE* a, PCB_SHAPE* b, const VECTOR2I& pt ) - { - if( !aErrorHandler ) - return; + auto report_gap = [&]( const VECTOR2I& pt ) + { + if( !aErrorHandler ) + return; - auto key = std::minmax( a, b ); + const double query_pt[2] = { static_cast( pt.x ), static_cast( pt.y ) }; + uint32_t indices[2]; + double dists[2]; - if( reportedGaps.insert( key ).second ) - (*aErrorHandler)( _( "(not a closed shape)" ), a, b, pt ); - }; + // Find the two closest items to the given point using kdtree + kdTree.knnSearch( query_pt, 2, indices, dists ); - report_gap( chain.front(), chain.back(), currContour.CPoint( 0 ) ); - report_gap( chain.back(), chain.front(), currContour.CLastPoint() ); + PCB_SHAPE* shapeA = adaptor.endpoints[indices[0]].second; + PCB_SHAPE* shapeB = adaptor.endpoints[indices[1]].second; + + // Avoid reporting the same pair twice + auto key = std::minmax( shapeA, shapeB ); + + if( !reportedGaps.insert( key ).second ) + return; + + // Find the nearest points between the two shapes and calculate midpoint + std::shared_ptr effectiveShapeA = shapeA->GetEffectiveShape(); + std::shared_ptr effectiveShapeB = shapeB->GetEffectiveShape(); + VECTOR2I ptA, ptB; + VECTOR2I midpoint = pt; // fallback to original point + + if( effectiveShapeA && effectiveShapeB + && effectiveShapeA->NearestPoints( effectiveShapeB.get(), ptA, ptB ) ) + { + midpoint = ( ptA + ptB ) / 2; + } + + ( *aErrorHandler )( _( "(not a closed shape)" ), shapeA, shapeB, midpoint ); + }; + + report_gap( currContour.CPoint( 0 ) ); + report_gap( currContour.CLastPoint() ); } } } + // Ensure all contours are closed for( const SHAPE_LINE_CHAIN& contour : contours ) { if( !contour.IsClosed() ) return false; } - std::map> contourToParentIndexesMap; - + // Generate bounding boxes for hierarchy calculations for( size_t ii = 0; ii < contours.size(); ++ii ) { SHAPE_LINE_CHAIN& contour = contours[ii]; @@ -604,159 +795,22 @@ bool doConvertOutlineToPolygon( std::vector& aShapeList, SHAPE_POLY_ contour.GenerateBBoxCache(); } - for( size_t ii = 0; ii < contours.size(); ++ii ) - { - VECTOR2I firstPt = contours[ii].GetPoint( 0 ); - std::vector parents; + // Build contour hierarchy + auto contourHierarchy = buildContourHierarchy( contours ); - for( size_t jj = 0; jj < contours.size(); ++jj ) - { - if( jj == ii ) - continue; - - const SHAPE_LINE_CHAIN& parentCandidate = contours[jj]; - - if( parentCandidate.PointInside( firstPt, 0, true ) ) - parents.push_back( jj ); - } - - contourToParentIndexesMap[ii] = std::move( parents ); - } - - // Next add those that are top-level outlines to the SHAPE_POLY_SET + // Add outlines to polygon set std::map contourToOutlineIdxMap; - - for( const auto& [ contourIndex, parentIndexes ] : contourToParentIndexesMap ) + if( !addOutlinesToPolygon( contours, contourHierarchy, aPolygons, aAllowDisjoint, + aErrorHandler, fetchOwner, contourToOutlineIdxMap ) ) { - if( parentIndexes.size() %2 == 0 ) - { - // Even number of parents; top-level outline - if( !aAllowDisjoint && !aPolygons.IsEmpty() ) - { - if( aErrorHandler ) - { - BOARD_ITEM* a = fetchOwner( aPolygons.Outline( 0 ).GetSegment( 0 ) ); - BOARD_ITEM* b = fetchOwner( contours[ contourIndex ].GetSegment( 0 ) ); - - if( a && b ) - { - (*aErrorHandler)( _( "(multiple board outlines not supported)" ), a, b, - contours[ contourIndex ].GetPoint( 0 ) ); - - return false; - } - } - } - - aPolygons.AddOutline( contours[ contourIndex ] ); - contourToOutlineIdxMap[ contourIndex ] = aPolygons.OutlineCount() - 1; - } + return false; } - // And finally add the holes - for( const auto& [ contourIndex, parentIndexes ] : contourToParentIndexesMap ) - { - if( parentIndexes.size() %2 == 1 ) - { - // Odd number of parents; we're a hole in the parent which has one fewer parents - // than we have. - const SHAPE_LINE_CHAIN& hole = contours[ contourIndex ]; + // Add holes to polygon set + addHolesToPolygon( contours, contourHierarchy, contourToOutlineIdxMap, aPolygons ); - for( int parentContourIdx : parentIndexes ) - { - if( contourToParentIndexesMap[ parentContourIdx ].size() == parentIndexes.size() - 1 ) - { - int outlineIdx = contourToOutlineIdxMap[ parentContourIdx ]; - aPolygons.AddHole( hole, outlineIdx ); - break; - } - } - } - } - - // All of the silliness that follows is to work around the segment iterator while checking - // for collisions. - // TODO: Implement proper segment and point iterators that follow std - std::vector segments; - size_t total = 0; - - for( int ii = 0; ii < aPolygons.OutlineCount(); ++ii ) - { - const SHAPE_LINE_CHAIN& contour = aPolygons.Outline( ii ); - total += contour.SegmentCount(); - - for( int jj = 0; jj < aPolygons.HoleCount( ii ); ++jj ) - { - const SHAPE_LINE_CHAIN& hole = aPolygons.Hole( ii, jj ); - total += hole.SegmentCount(); - } - } - - segments.reserve( total ); - - for( auto seg = aPolygons.IterateSegmentsWithHoles(); seg; seg++ ) - { - SEG segment = *seg; - - // Ensure segments are always ordered A < B - if( LexicographicalCompare( segment.A, segment.B ) > 0 ) - { - std::swap( segment.A, segment.B ); - } - - segments.push_back( segment ); - } - - std::sort( segments.begin(), segments.end(), - []( const SEG& a, const SEG& b ) - { - if( a.A != b.A ) - return LexicographicalCompare( a.A, b.A ) < 0; - - return LexicographicalCompare( a.B, b.B ) < 0; - } ); - - for( size_t i = 0; i < segments.size(); ++i ) - { - const SEG& seg1 = segments[i]; - - for( size_t j = i + 1; j < segments.size(); ++j ) - { - const SEG& seg2 = segments[j]; - - if( seg2.A > seg1.B ) - { - // No more segments to check, as they are sorted by A and B. - break; - } - - // Check for exact overlapping segments. - if( seg1 == seg2 || ( seg1.A == seg2.B && seg1.B == seg2.A ) ) - { - if( aErrorHandler ) - { - BOARD_ITEM* a = fetchOwner( seg1 ); - BOARD_ITEM* b = fetchOwner( seg2 ); - (*aErrorHandler)( _( "(self-intersecting)" ), a, b, seg1.A ); - } - - selfIntersecting = true; - } - else if( OPT_VECTOR2I pt = seg1.Intersect( seg2, true ) ) - { - if( aErrorHandler ) - { - BOARD_ITEM* a = fetchOwner( seg1 ); - BOARD_ITEM* b = fetchOwner( seg2 ); - (*aErrorHandler)( _( "(self-intersecting)" ), a, b, *pt ); - } - - selfIntersecting = true; - } - } - } - - return !selfIntersecting; + // Check for self-intersections + return checkSelfIntersections( aPolygons, aErrorHandler, fetchOwner ); } diff --git a/pcbnew/drc/drc_test_provider_misc.cpp b/pcbnew/drc/drc_test_provider_misc.cpp index c70236cb31..e293fae7f9 100644 --- a/pcbnew/drc/drc_test_provider_misc.cpp +++ b/pcbnew/drc/drc_test_provider_misc.cpp @@ -147,21 +147,15 @@ void DRC_TEST_PROVIDER_MISC::testOutline() if( !itemA ) // If we only have a single item, make sure it's A std::swap( itemA, itemB ); - VECTOR2I markerPos = pt; - int gap = 0; + VECTOR2I markerPos = pt; + int gap = 0; + PCB_SHAPE* shapeA = nullptr; + PCB_SHAPE* shapeB = nullptr; - if( itemA && itemB ) + if( itemA && itemB && itemA->Type() == PCB_SHAPE_T && itemB->Type() == PCB_SHAPE_T ) { - std::shared_ptr shapeA = itemA->GetEffectiveShape(); - std::shared_ptr shapeB = itemB->GetEffectiveShape(); - VECTOR2I ptA; - VECTOR2I ptB; - - if( shapeA && shapeB && shapeA->NearestPoints( shapeB.get(), ptA, ptB ) ) - { - gap = ( ptA - ptB ).EuclideanNorm(); - markerPos = ( ptA + ptB ) / 2; - } + shapeA = static_cast( itemA ); + shapeB = static_cast( itemB ); } else { @@ -174,9 +168,24 @@ void DRC_TEST_PROVIDER_MISC::testOutline() itemB = shapeB; } - std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_INVALID_OUTLINE ); + if( shapeA && shapeB ) + { + VECTOR2I pts0[2] = { shapeA->GetStart(), shapeA->GetEnd() }; + VECTOR2I pts1[2] = { shapeB->GetStart(), shapeB->GetEnd() }; - wxString msg2; + SEG::ecoord d[4]; + d[0] = ( pts0[0] - pts1[0] ).SquaredEuclideanNorm(); + d[1] = ( pts0[0] - pts1[1] ).SquaredEuclideanNorm(); + d[2] = ( pts0[1] - pts1[0] ).SquaredEuclideanNorm(); + d[3] = ( pts0[1] - pts1[1] ).SquaredEuclideanNorm(); + + int idx = std::min_element( d, d + 4 ) - d; + gap = std::sqrt( d[idx] ); + markerPos = ( pts0[idx / 2] + pts1[idx % 2] ) / 2; + } + + std::shared_ptr drcItem = DRC_ITEM::Create( DRCE_INVALID_OUTLINE ); + wxString msg2; if( itemA && itemB ) msg2.Printf( _( "%s (gap %s)" ), msg, MessageTextFromValue( gap ) ); diff --git a/pcbnew/fix_board_shape.cpp b/pcbnew/fix_board_shape.cpp index ba2641ecb9..b15213379f 100644 --- a/pcbnew/fix_board_shape.cpp +++ b/pcbnew/fix_board_shape.cpp @@ -26,79 +26,110 @@ #include #include +#include #include #include +#include + + +struct PCB_SHAPE_ENDPOINTS_ADAPTOR +{ + std::vector> endpoints; + + PCB_SHAPE_ENDPOINTS_ADAPTOR( const std::vector& shapes ) + { + endpoints.reserve( shapes.size() * 2 ); + + for( PCB_SHAPE* shape : shapes ) + { + endpoints.emplace_back( shape->GetStart(), shape ); + endpoints.emplace_back( shape->GetEnd(), shape ); + } + } + + // Required by nanoflann + size_t kdtree_get_point_count() const { return endpoints.size(); } + + // Returns the dim'th component of the idx'th point + double kdtree_get_pt( const size_t idx, const size_t dim ) const + { + if( dim == 0 ) + return static_cast( endpoints[idx].first.x ); + else + return static_cast( endpoints[idx].first.y ); + } + + template + bool kdtree_get_bbox( BBOX& ) const + { + return false; + } +}; + +using KDTree = nanoflann::KDTreeSingleIndexAdaptor, + PCB_SHAPE_ENDPOINTS_ADAPTOR, + 2 /* dim */ >; + /** * Searches for a PCB_SHAPE matching a given end point or start point in a list. * @param aShape The starting shape. * @param aPoint The starting or ending point to search for. - * @param aList The list to remove from. - * @param aLimit is the distance from \a aPoint that still constitutes a valid find. + * @param kdTree The KD-tree for efficient nearest neighbor search. + * @param adaptor The adaptor containing the endpoints data. + * @param aChainingEpsilon is the distance from \a aPoint that still constitutes a valid find. * @return PCB_SHAPE* - The first PCB_SHAPE that has a start or end point matching * aPoint, otherwise NULL if none. */ -static PCB_SHAPE* findNext( PCB_SHAPE* aShape, const VECTOR2I& aPoint, - const std::vector& aList, unsigned aLimit ) +static PCB_SHAPE* findNext( PCB_SHAPE* aShape, const VECTOR2I& aPoint, const KDTree& kdTree, + const PCB_SHAPE_ENDPOINTS_ADAPTOR& adaptor, double aChainingEpsilon ) { - // Look for an unused, exact hit - for( PCB_SHAPE* graphic : aList ) + const double query_pt[2] = { static_cast( aPoint.x ), static_cast( aPoint.y ) }; + + uint32_t indices[2]; + double distances[2]; + kdTree.knnSearch( query_pt, 2, indices, distances ); + + if( distances[0] == std::numeric_limits::max() ) + return nullptr; + + // Find the closest valid candidate + PCB_SHAPE* closest_graphic = nullptr; + double closest_dist_sq = aChainingEpsilon * aChainingEpsilon; + + for( size_t i = 0; i < 2; ++i ) { - if( graphic == aShape || ( graphic->GetFlags() & SKIP_STRUCT ) != 0 ) + if( distances[i] == std::numeric_limits::max() ) continue; - if( aPoint == graphic->GetStart() || aPoint == graphic->GetEnd() ) - return graphic; - } + PCB_SHAPE* candidate = adaptor.endpoints[indices[i]].second; - // Search again for anything that's close. - 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 || ( graphic->GetFlags() & SKIP_STRUCT ) != 0 ) + if( candidate == aShape ) continue; - d_sq = ( pt - graphic->GetStart() ).SquaredEuclideanNorm(); + if( candidate->GetFlags() & SKIP_STRUCT ) + continue; - if( d_sq < closest_dist_sq ) + if( distances[i] < closest_dist_sq ) { - closest_dist_sq = d_sq; - closest_graphic = graphic; - } - - d_sq = ( pt - graphic->GetEnd() ).SquaredEuclideanNorm(); - - if( d_sq < closest_dist_sq ) - { - closest_dist_sq = d_sq; - closest_graphic = graphic; + closest_dist_sq = distances[i]; + closest_graphic = candidate; } } - return closest_graphic; // Note: will be nullptr if nothing within aLimit + return closest_graphic; } -void ConnectBoardShapes( std::vector& aShapeList, - std::vector>& aNewShapes, int aChainingEpsilon ) +void ConnectBoardShapes( std::vector& aShapeList, int aChainingEpsilon ) { if( aShapeList.size() == 0 ) return; - (void) aNewShapes; - -#if 0 - // Not used, but not removed, just in case - auto close_enough = []( const VECTOR2I& aLeft, const VECTOR2I& aRight, unsigned aLimit ) -> bool - { - return ( aLeft - aRight ).SquaredEuclideanNorm() <= SEG::Square( aLimit ); - }; -#endif + // Pre-build KD-tree + PCB_SHAPE_ENDPOINTS_ADAPTOR adaptor( aShapeList ); + KDTree kdTree( 2, adaptor ); auto closer_to_first = []( const VECTOR2I& aRef, const VECTOR2I& aFirst, const VECTOR2I& aSecond ) -> bool @@ -288,6 +319,55 @@ void ConnectBoardShapes( std::vector& aShapeList, success = true; } + else if( shape0 == SHAPE_T::BEZIER && shape1 == SHAPE_T::BEZIER ) + { + PCB_SHAPE* bez0 = aPrevShape; + PCB_SHAPE* bez1 = aShape; + + VECTOR2I pts0[2] = { bez0->GetStart(), bez0->GetEnd() }; + VECTOR2I pts1[2] = { bez1->GetStart(), bez1->GetEnd() }; + + SEG::ecoord d[4]; + d[0] = ( pts0[0] - pts1[0] ).SquaredEuclideanNorm(); + d[1] = ( pts0[0] - pts1[1] ).SquaredEuclideanNorm(); + d[2] = ( pts0[1] - pts1[0] ).SquaredEuclideanNorm(); + d[3] = ( pts0[1] - pts1[1] ).SquaredEuclideanNorm(); + + int idx = std::min_element( d, d + 4 ) - d; + int i0 = idx / 2; + int i1 = idx % 2; + VECTOR2I middle = ( pts0[i0] + pts1[i1] ) / 2; + + // Adjust first bezier curve + if( i0 == 0 ) + { + VECTOR2I delta = middle - bez0->GetStart(); + bez0->SetStart( middle ); + bez0->SetBezierC1( bez0->GetBezierC1() + delta ); + } + else + { + VECTOR2I delta = middle - bez0->GetEnd(); + bez0->SetEnd( middle ); + bez0->SetBezierC2( bez0->GetBezierC2() + delta ); + } + + // Adjust second bezier curve + if( i1 == 0 ) + { + VECTOR2I delta = middle - bez1->GetStart(); + bez1->SetStart( middle ); + bez1->SetBezierC1( bez1->GetBezierC1() + delta ); + } + else + { + VECTOR2I delta = middle - bez1->GetEnd(); + bez1->SetEnd( middle ); + bez1->SetBezierC2( bez1->GetBezierC2() + delta ); + } + + success = true; + } return success; }; @@ -317,7 +397,7 @@ void ConnectBoardShapes( std::vector& aShapeList, { // Get next closest segment. PCB_SHAPE* nextGraphic = - findNext( curr_graphic, prevPt, aShapeList, aChainingEpsilon ); + findNext( curr_graphic, prevPt, kdTree, adaptor, aChainingEpsilon ); if( !nextGraphic ) break; @@ -336,8 +416,8 @@ void ConnectBoardShapes( std::vector& aShapeList, const VECTOR2I ptEnd = graphic->GetEnd(); const VECTOR2I ptStart = graphic->GetStart(); - PCB_SHAPE* grAtEnd = findNext( graphic, ptEnd, aShapeList, aChainingEpsilon ); - PCB_SHAPE* grAtStart = findNext( graphic, ptStart, aShapeList, aChainingEpsilon ); + PCB_SHAPE* grAtEnd = findNext( graphic, ptEnd, kdTree, adaptor, aChainingEpsilon ); + PCB_SHAPE* grAtStart = findNext( graphic, ptStart, kdTree, adaptor, aChainingEpsilon ); bool beginFromEndPt = true; diff --git a/pcbnew/fix_board_shape.h b/pcbnew/fix_board_shape.h index 6851bb916e..d374de446e 100644 --- a/pcbnew/fix_board_shape.h +++ b/pcbnew/fix_board_shape.h @@ -32,10 +32,8 @@ /** * Connects shapes to each other, making continious contours (adjacent shapes will have a common vertex) * aChainingEpsilon is the max distance between vertices of different shapes to connect. - * Modifies original shapes, or creates new line segments and stores them in aNewShapes. + * Modifies original shapes */ -void ConnectBoardShapes( std::vector& aShapeList, - std::vector>& aNewShapes, - int aChainingEpsilon ); +void ConnectBoardShapes( std::vector& aShapeList, int aChainingEpsilon ); #endif // FIX_BOARD_SHAPE_H_ \ No newline at end of file diff --git a/pcbnew/graphics_cleaner.cpp b/pcbnew/graphics_cleaner.cpp index e56ee5e261..3aecdd7a9f 100644 --- a/pcbnew/graphics_cleaner.cpp +++ b/pcbnew/graphics_cleaner.cpp @@ -214,7 +214,6 @@ void GRAPHICS_CLEANER::fixBoardOutlines() return; std::vector shapeList; - std::vector> newShapes; for( BOARD_ITEM* item : m_drawings ) { @@ -229,12 +228,9 @@ void GRAPHICS_CLEANER::fixBoardOutlines() m_commit.Modify( shape ); } - ConnectBoardShapes( shapeList, newShapes, m_outlinesTolerance ); + ConnectBoardShapes( shapeList, m_outlinesTolerance ); std::vector items_to_select; - - for( std::unique_ptr& ptr : newShapes ) - m_commit.Add( ptr.release() ); } diff --git a/pcbnew/pcb_io/easyeda/pcb_io_easyeda_parser.cpp b/pcbnew/pcb_io/easyeda/pcb_io_easyeda_parser.cpp index 271e2a9ad3..8926a9904e 100644 --- a/pcbnew/pcb_io/easyeda/pcb_io_easyeda_parser.cpp +++ b/pcbnew/pcb_io/easyeda/pcb_io_easyeda_parser.cpp @@ -1101,7 +1101,6 @@ FOOTPRINT* PCB_IO_EASYEDA_PARSER::ParseFootprint( // Heal board outlines std::vector shapes; - std::vector> newShapes; for( BOARD_ITEM* item : footprint->GraphicalItems() ) { @@ -1112,10 +1111,7 @@ FOOTPRINT* PCB_IO_EASYEDA_PARSER::ParseFootprint( shapes.push_back( static_cast( item ) ); } - ConnectBoardShapes( shapes, newShapes, SHAPE_JOIN_DISTANCE ); - - for( std::unique_ptr& ptr : newShapes ) - footprint->Add( ptr.release(), ADD_MODE::APPEND ); + ConnectBoardShapes( shapes, SHAPE_JOIN_DISTANCE ); // EasyEDA footprints don't have courtyard, so build a box ourselves if( !footprint->IsOnLayer( F_CrtYd ) ) @@ -1177,7 +1173,6 @@ void PCB_IO_EASYEDA_PARSER::ParseBoard( BOARD* aBoard, const VECTOR2D& aOrigin, // Heal board outlines std::vector shapes; - std::vector> newShapes; for( BOARD_ITEM* item : aBoard->Drawings() ) { @@ -1188,8 +1183,5 @@ void PCB_IO_EASYEDA_PARSER::ParseBoard( BOARD* aBoard, const VECTOR2D& aOrigin, shapes.push_back( static_cast( item ) ); } - ConnectBoardShapes( shapes, newShapes, SHAPE_JOIN_DISTANCE ); - - for( std::unique_ptr& ptr : newShapes ) - aBoard->Add( ptr.release(), ADD_MODE::APPEND ); + ConnectBoardShapes( shapes, SHAPE_JOIN_DISTANCE ); } diff --git a/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro_parser.cpp b/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro_parser.cpp index 2a3b45324d..d158ce2b38 100644 --- a/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro_parser.cpp +++ b/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro_parser.cpp @@ -929,7 +929,6 @@ FOOTPRINT* PCB_IO_EASYEDAPRO_PARSER::ParseFootprint( const nlohmann::json& // Heal board outlines std::vector edgeShapes; - std::vector> newShapes; for( BOARD_ITEM* item : footprint->GraphicalItems() ) { @@ -937,10 +936,7 @@ FOOTPRINT* PCB_IO_EASYEDAPRO_PARSER::ParseFootprint( const nlohmann::json& edgeShapes.push_back( static_cast( item ) ); } - ConnectBoardShapes( edgeShapes, newShapes, SHAPE_JOIN_DISTANCE ); - - for( std::unique_ptr& ptr : newShapes ) - footprint->Add( ptr.release(), ADD_MODE::APPEND ); + ConnectBoardShapes( edgeShapes, SHAPE_JOIN_DISTANCE ); // EasyEDA footprints don't have courtyard, so build a box ourselves if( !footprint->IsOnLayer( F_CrtYd ) ) @@ -1859,8 +1855,7 @@ void PCB_IO_EASYEDAPRO_PARSER::ParseBoard( } // Heal board outlines - std::vector shapes; - std::vector> newShapes; + std::vector shapes; for( BOARD_ITEM* item : aBoard->Drawings() ) { @@ -1871,10 +1866,7 @@ void PCB_IO_EASYEDAPRO_PARSER::ParseBoard( shapes.push_back( static_cast( item ) ); } - ConnectBoardShapes( shapes, newShapes, SHAPE_JOIN_DISTANCE ); - - for( std::unique_ptr& ptr : newShapes ) - aBoard->Add( ptr.release(), ADD_MODE::APPEND ); + ConnectBoardShapes( shapes, SHAPE_JOIN_DISTANCE ); // Center the board BOX2I outlineBbox = aBoard->ComputeBoundingBox( true ); diff --git a/pcbnew/tools/drawing_tool.cpp b/pcbnew/tools/drawing_tool.cpp index 6d037b21a0..824d5240fa 100644 --- a/pcbnew/tools/drawing_tool.cpp +++ b/pcbnew/tools/drawing_tool.cpp @@ -1850,8 +1850,7 @@ int DRAWING_TOOL::PlaceImportedGraphics( const TOOL_EVENT& aEvent ) if( dlg.ShouldFixDiscontinuities() ) { - std::vector shapeList; - std::vector> newShapes; + std::vector shapeList; for( const std::unique_ptr& ptr : list ) { @@ -1859,13 +1858,7 @@ int DRAWING_TOOL::PlaceImportedGraphics( const TOOL_EVENT& aEvent ) shapeList.push_back( shape ); } - ConnectBoardShapes( shapeList, newShapes, dlg.GetTolerance() ); - - for( std::unique_ptr& ptr : newShapes ) - { - ptr->SetParent( m_frame->GetBoard() ); - list.push_back( std::move( ptr ) ); - } + ConnectBoardShapes( shapeList, dlg.GetTolerance() ); } for( std::unique_ptr& ptr : list ) diff --git a/pcbnew/tools/edit_tool.cpp b/pcbnew/tools/edit_tool.cpp index 3f06d2cb0d..14456a4551 100644 --- a/pcbnew/tools/edit_tool.cpp +++ b/pcbnew/tools/edit_tool.cpp @@ -1804,8 +1804,7 @@ int EDIT_TOOL::HealShapes( const TOOL_EVENT& aEvent ) BOARD_COMMIT commit{ this }; - std::vector shapeList; - std::vector> newShapes; + std::vector shapeList; for( EDA_ITEM* item : selection ) { @@ -1816,27 +1815,10 @@ int EDIT_TOOL::HealShapes( const TOOL_EVENT& aEvent ) } } - ConnectBoardShapes( shapeList, newShapes, s_toleranceValue ); - - std::vector items_to_select; - - for( std::unique_ptr& ptr : newShapes ) - { - PCB_SHAPE* shape = ptr.release(); - - commit.Add( shape ); - items_to_select.push_back( shape ); - } + ConnectBoardShapes( shapeList, s_toleranceValue ); commit.Push( _( "Heal Shapes" ) ); - // Select added items - for( PCB_SHAPE* item : items_to_select ) - m_selectionTool->AddItemToSel( item, true ); - - if( items_to_select.size() > 0 ) - m_toolMgr->ProcessEvent( EVENTS::SelectedEvent ); - // Notify other tools of the changes m_toolMgr->ProcessEvent( EVENTS::SelectedItemsModified );