diff --git a/common/eda_text.cpp b/common/eda_text.cpp index c628b18542..8795497fe6 100644 --- a/common/eda_text.cpp +++ b/common/eda_text.cpp @@ -612,9 +612,9 @@ std::vector EDA_TEXT::TransformToSegmentList() const } else { - GRText( NULL, GetTextPos(), color, GetText(), GetDrawRotation(), size, GetHorizJustify(), - GetVertJustify(), penWidth, IsItalic(), forceBold, addTextSegmToBuffer, - &cornerBuffer ); + GRText( NULL, GetTextPos(), color, GetShownText(), GetDrawRotation(), size, + GetHorizJustify(), GetVertJustify(), penWidth, IsItalic(), forceBold, + addTextSegmToBuffer, &cornerBuffer ); } return cornerBuffer; diff --git a/pcbnew/board_items_to_polygon_shape_transform.cpp b/pcbnew/board_items_to_polygon_shape_transform.cpp index dd0d8d7441..cac4866240 100644 --- a/pcbnew/board_items_to_polygon_shape_transform.cpp +++ b/pcbnew/board_items_to_polygon_shape_transform.cpp @@ -231,7 +231,7 @@ void FOOTPRINT::TransformFPShapesWithClearanceToPolygon( SHAPE_POLY_SET& aCorner { std::vector texts; // List of FP_TEXT to convert - for( auto item : GraphicalItems() ) + for( BOARD_ITEM* item : GraphicalItems() ) { if( item->Type() == PCB_FP_TEXT_T && aIncludeText ) { @@ -262,28 +262,35 @@ void FOOTPRINT::TransformFPShapesWithClearanceToPolygon( SHAPE_POLY_SET& aCorner texts.push_back( &Value() ); } - prms.m_cornerBuffer = &aCornerBuffer; - - for( FP_TEXT* textmod : texts ) + for( FP_TEXT* text : texts ) { - bool forceBold = true; - int penWidth = 0; // force max width for bold text - - prms.m_textWidth = textmod->GetEffectiveTextPenWidth() + ( 2 * aClearance ); - prms.m_error = aError; - wxSize size = textmod->GetTextSize(); - - if( textmod->IsMirrored() ) - size.x = -size.x; - - GRText( NULL, textmod->GetTextPos(), BLACK, textmod->GetShownText(), - textmod->GetDrawRotation(), size, textmod->GetHorizJustify(), - textmod->GetVertJustify(), penWidth, textmod->IsItalic(), forceBold, - addTextSegmToPoly, &prms ); + text->TransformTextShapeWithClearanceToPolygon( aCornerBuffer, aLayer, aClearance, + aError, aErrorLoc ); } } +void FP_TEXT::TransformTextShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, + enum PCB_LAYER_ID aLayer, + int aClearance, int aError, + ERROR_LOC aErrorLoc ) const +{ + bool forceBold = true; + int penWidth = 0; // force max width for bold text + + prms.m_cornerBuffer = &aCornerBuffer; + prms.m_textWidth = GetEffectiveTextPenWidth() + ( 2 * aClearance ); + prms.m_error = aError; + wxSize size = GetTextSize(); + + if( IsMirrored() ) + size.x = -size.x; + + GRText( NULL, GetTextPos(), BLACK, GetShownText(), GetDrawRotation(), size, GetHorizJustify(), + GetVertJustify(), penWidth, IsItalic(), forceBold, addTextSegmToPoly, &prms ); +} + + void ZONE::TransformSolidAreasShapesToPolygon( PCB_LAYER_ID aLayer, SHAPE_POLY_SET& aCornerBuffer, int aError ) const { diff --git a/pcbnew/collectors.h b/pcbnew/collectors.h index 35f427682d..5535cba3eb 100644 --- a/pcbnew/collectors.h +++ b/pcbnew/collectors.h @@ -335,7 +335,7 @@ public: */ void SetGuide( const COLLECTORS_GUIDE* aGuide ) { m_Guide = aGuide; } - const COLLECTORS_GUIDE* GetGuide() { return m_Guide; } + const COLLECTORS_GUIDE* GetGuide() const { return m_Guide; } /** * @return int - The number if items which met the primary search criteria diff --git a/pcbnew/footprint.cpp b/pcbnew/footprint.cpp index 1c06b04aac..225f74306e 100644 --- a/pcbnew/footprint.cpp +++ b/pcbnew/footprint.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -1626,45 +1627,76 @@ static double polygonArea( SHAPE_POLY_SET& aPolySet ) return area; } -// a helper function to add a rectangular polygon aRect to aPolySet -static void addRect( SHAPE_POLY_SET& aPolySet, wxRect aRect ) -{ - aPolySet.NewOutline(); - aPolySet.Append( aRect.GetX(), aRect.GetY() ); - aPolySet.Append( aRect.GetX()+aRect.width, aRect.GetY() ); - aPolySet.Append( aRect.GetX()+aRect.width, aRect.GetY()+aRect.height ); - aPolySet.Append( aRect.GetX(), aRect.GetY()+aRect.height ); +double FOOTPRINT::GetCoverageArea( const BOARD_ITEM* aItem, const GENERAL_COLLECTOR& aCollector ) +{ + int textMargin = KiROUND( 5 * aCollector.GetGuide()->OnePixelInIU() ); + SHAPE_POLY_SET poly; + + if( aItem->Type() == PCB_FOOTPRINT_T ) + { + const FOOTPRINT* footprint = static_cast( aItem ); + + return footprint->GetFootprintRect().GetArea(); + } + else if( aItem->Type() == PCB_FP_TEXT_T ) + { + const FP_TEXT* text = static_cast( aItem ); + + text->TransformTextShapeWithClearanceToPolygon( poly, UNDEFINED_LAYER, textMargin, + ARC_LOW_DEF, ERROR_OUTSIDE ); + } + else + { + aItem->TransformShapeWithClearanceToPolygon( poly, UNDEFINED_LAYER, 0, + ARC_LOW_DEF, ERROR_OUTSIDE ); + } + + poly.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + poly.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + return polygonArea( poly ); } + double FOOTPRINT::CoverageRatio( const GENERAL_COLLECTOR& aCollector ) const { - double fpArea = GetFootprintRect().GetArea(); + int textMargin = KiROUND( 5 * aCollector.GetGuide()->OnePixelInIU() ); + + SHAPE_POLY_SET footprintRegion( GetBoundingPoly() ); SHAPE_POLY_SET coveredRegion; - addRect( coveredRegion, GetFootprintRect() ); - // build list of holes (covered areas not available for selection) - SHAPE_POLY_SET holes; + TransformPadsWithClearanceToPolygon( coveredRegion, UNDEFINED_LAYER, 0, ARC_LOW_DEF, + ERROR_OUTSIDE ); - for( PAD* pad : m_pads ) - addRect( holes, pad->GetBoundingBox() ); - - addRect( holes, m_reference->GetBoundingBox() ); - addRect( holes, m_value->GetBoundingBox() ); + TransformFPShapesWithClearanceToPolygon( coveredRegion, UNDEFINED_LAYER, textMargin, + ARC_LOW_DEF, ERROR_OUTSIDE, + true, /* include text */ + false /* include shapes */ ); for( int i = 0; i < aCollector.GetCount(); ++i ) { - BOARD_ITEM* item = aCollector[i]; + const BOARD_ITEM* item = aCollector[i]; switch( item->Type() ) { - case PCB_TEXT_T: case PCB_FP_TEXT_T: + case PCB_FP_SHAPE_T: + if( item->GetParent() != this ) + { + item->TransformShapeWithClearanceToPolygon( coveredRegion, UNDEFINED_LAYER, 1, + ARC_LOW_DEF, ERROR_OUTSIDE ); + } + break; + + case PCB_TEXT_T: + case PCB_SHAPE_T: case PCB_TRACE_T: case PCB_ARC_T: case PCB_VIA_T: - addRect( holes, item->GetBoundingBox() ); + item->TransformShapeWithClearanceToPolygon( coveredRegion, UNDEFINED_LAYER, 1, + ARC_LOW_DEF, ERROR_OUTSIDE ); break; + default: break; } @@ -1674,7 +1706,8 @@ double FOOTPRINT::CoverageRatio( const GENERAL_COLLECTOR& aCollector ) const try { - uncoveredRegion.BooleanSubtract( coveredRegion, holes, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); + uncoveredRegion.BooleanSubtract( footprintRegion, coveredRegion, + SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); uncoveredRegion.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); uncoveredRegion.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE ); } @@ -1684,9 +1717,10 @@ double FOOTPRINT::CoverageRatio( const GENERAL_COLLECTOR& aCollector ) const return 1.0; } + double footprintRegionArea = polygonArea( footprintRegion ); double uncoveredRegionArea = polygonArea( uncoveredRegion ); - double coveredArea = fpArea - uncoveredRegionArea; - double ratio = ( coveredArea / fpArea ); + double coveredArea = footprintRegionArea - uncoveredRegionArea; + double ratio = ( coveredArea / footprintRegionArea ); return std::min( ratio, 1.0 ); } diff --git a/pcbnew/footprint.h b/pcbnew/footprint.h index be4c69ec15..f63aac7f05 100644 --- a/pcbnew/footprint.h +++ b/pcbnew/footprint.h @@ -663,6 +663,8 @@ public: */ double CoverageRatio( const GENERAL_COLLECTOR& aCollector ) const; + static double GetCoverageArea( const BOARD_ITEM* aItem, const GENERAL_COLLECTOR& aCollector ); + /// Return the initial comments block or NULL if none, without transfer of ownership. const wxArrayString* GetInitialComments() const { return m_initial_comments; } diff --git a/pcbnew/fp_text.h b/pcbnew/fp_text.h index 7e7689fe66..47e2c021e2 100644 --- a/pcbnew/fp_text.h +++ b/pcbnew/fp_text.h @@ -198,6 +198,10 @@ public: return TextHitTest( aRect, aContained, aAccuracy ); } + void TransformTextShapeWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer, + PCB_LAYER_ID aLayer, int aClearanceValue, + int aError, ERROR_LOC aErrorLoc ) const; + // @copydoc BOARD_ITEM::GetEffectiveShape virtual std::shared_ptr GetEffectiveShape( PCB_LAYER_ID aLayer = UNDEFINED_LAYER ) const override; diff --git a/pcbnew/tools/selection_tool.cpp b/pcbnew/tools/selection_tool.cpp index eb93826e71..0fd909a24f 100644 --- a/pcbnew/tools/selection_tool.cpp +++ b/pcbnew/tools/selection_tool.cpp @@ -33,6 +33,7 @@ using namespace std::placeholders; #include #include #include +#include #include #include #include @@ -46,7 +47,6 @@ using namespace std::placeholders; #include #include #include -//#include #include #include #include @@ -161,7 +161,7 @@ bool SELECTION_TOOL::Init() // "Cancel" goes at the top of the context menu when a tool is active menu.AddItem( ACTIONS::cancelInteractive, activeToolCondition, 1 ); - menu.AddItem( PCB_ACTIONS::groupLeave, inGroupCondition, 1); + menu.AddItem( PCB_ACTIONS::groupLeave, inGroupCondition, 1 ); menu.AddSeparator( 1 ); if( frame ) @@ -2153,81 +2153,6 @@ bool SELECTION_TOOL::selectionContains( const VECTOR2I& aPoint ) const } -static EDA_RECT getRect( const BOARD_ITEM* aItem ) -{ - if( aItem->Type() == PCB_FOOTPRINT_T ) - return static_cast( aItem )->GetFootprintRect(); - - return aItem->GetBoundingBox(); -} - - -static double calcArea( const BOARD_ITEM* aItem ) -{ - if( aItem->Type() == PCB_TRACE_T ) - { - const TRACK* t = static_cast( aItem ); - return ( t->GetWidth() + t->GetLength() ) * t->GetWidth(); - } - - return getRect( aItem ).GetArea(); -} - - -/*static double calcMinArea( GENERAL_COLLECTOR& aCollector, KICAD_T aType ) -{ - double best = std::numeric_limits::max(); - - if( !aCollector.GetCount() ) - return 0.0; - - for( int i = 0; i < aCollector.GetCount(); i++ ) - { - BOARD_ITEM* item = aCollector[i]; - if( item->Type() == aType ) - best = std::min( best, calcArea( item ) ); - } - - return best; -}*/ - - -static double calcMaxArea( GENERAL_COLLECTOR& aCollector, KICAD_T aType ) -{ - double best = 0.0; - - for( int i = 0; i < aCollector.GetCount(); i++ ) - { - BOARD_ITEM* item = aCollector[i]; - if( item->Type() == aType ) - best = std::max( best, calcArea( item ) ); - } - - return best; -} - - -static inline double calcCommonArea( const BOARD_ITEM* aItem, const BOARD_ITEM* aOther ) -{ - if( !aItem || !aOther ) - return 0; - - return getRect( aItem ).Common( getRect( aOther ) ).GetArea(); -} - - -double calcRatio( double a, double b ) -{ - if( a == 0.0 && b == 0.0 ) - return 1.0; - - if( b == 0.0 ) - return std::numeric_limits::max(); - - return a / b; -} - - // The general idea here is that if the user clicks directly on a small item inside a larger // one, then they want the small item. The quintessential case of this is clicking on a pad // within a footprint, but we also apply it for text within a footprint, footprints within @@ -2248,24 +2173,6 @@ void SELECTION_TOOL::GuessSelectionCandidates( GENERAL_COLLECTOR& aCollector, std::set rejected; wxPoint where( aWhere.x, aWhere.y ); - // footprints which are below this percentage of the largest footprint will be considered - // for selection; all others will not - constexpr double footprintToFootprintMinRatio = 0.20; - // pads which are below this percentage of their parent's area will exclude their parent - constexpr double padToFootprintMinRatio = 0.45; - // footprints containing items with items-to-footprint area ratio higher than this will be - // forced to stay on the list - constexpr double footprintMaxCoverRatio = 0.90; - constexpr double viaToPadMinRatio = 0.50; - constexpr double trackViaLengthRatio = 2.0; - constexpr double trackTrackLengthRatio = 0.3; - constexpr double textToFeatureMinRatio = 0.2; - constexpr double textToFootprintMinRatio = 0.4; - // If the common area of two compared items is above the following threshold, they cannot - // be rejected (it means they overlap and it might be hard to pick one by selecting - // its unique area). - constexpr double commonAreaRatio = 0.6; - PCB_LAYER_ID activeLayer = (PCB_LAYER_ID) view()->GetTopLayer(); LSET silkLayers( 2, B_SilkS, F_SilkS ); @@ -2289,288 +2196,152 @@ void SELECTION_TOOL::GuessSelectionCandidates( GENERAL_COLLECTOR& aCollector, for( BOARD_ITEM* item : preferred ) aCollector.Append( item ); + return; } } - // Zone edges are very specific; zone fills much less so. - if( aCollector.CountType( PCB_ZONE_T ) > 0 ) - { - for( int i = aCollector.GetCount() - 1; i >= 0; i-- ) - { - if( aCollector[i]->Type() == PCB_ZONE_T ) - { - ZONE* zone = static_cast( aCollector[i] ); + // Prefer exact hits to sloppy ones - if( zone->HitTestForEdge( where, 5 * aCollector.GetGuide()->OnePixelInIU() ) ) - preferred.insert( zone ); - else - rejected.insert( zone ); - } - } + constexpr int maxSlop = 5; - if( preferred.size() > 0 ) - { - aCollector.Empty(); + BOX2D viewportD = getView()->GetViewport(); + BOX2I viewport( VECTOR2I( viewportD.GetPosition() ), VECTOR2I( viewportD.GetSize() ) ); + int pixel = (int) aCollector.GetGuide()->OnePixelInIU(); + int minActual = INT_MAX; + SEG loc( where, where ); - for( BOARD_ITEM* item : preferred ) - aCollector.Append( item ); - return; - } - } - - if( aCollector.CountType( PCB_FP_TEXT_T ) > 0 ) - { - for( int i = 0; i < aCollector.GetCount(); ++i ) - { - if( FP_TEXT* txt = dyn_cast( aCollector[i] ) ) - { - double textArea = calcArea( txt ); - - for( int j = 0; j < aCollector.GetCount(); ++j ) - { - if( i == j ) - continue; - - BOARD_ITEM* item = aCollector[j]; - double itemArea = calcArea( item ); - double areaRatio = calcRatio( textArea, itemArea ); - double commonArea = calcCommonArea( txt, item ); - double itemCommonRatio = calcRatio( commonArea, itemArea ); - double txtCommonRatio = calcRatio( commonArea, textArea ); - - if( item->Type() == PCB_FOOTPRINT_T ) - { - // when text area is small compared to an overlapping footprint, - // then it's a clear sign the text is the selection target - if( areaRatio < textToFootprintMinRatio && itemCommonRatio < commonAreaRatio ) - rejected.insert( item ); - } - - switch( item->Type() ) - { - case PCB_TRACE_T: - case PCB_ARC_T: - case PCB_PAD_T: - case PCB_SHAPE_T: - case PCB_VIA_T: - case PCB_FOOTPRINT_T: - if( areaRatio > textToFeatureMinRatio && txtCommonRatio < commonAreaRatio ) - rejected.insert( txt ); - break; - default: - break; - } - } - } - } - } - - if( aCollector.CountType( PCB_FP_SHAPE_T ) + aCollector.CountType( PCB_SHAPE_T ) > 1 ) - { - // Prefer exact hits to sloppy ones - int accuracy = KiROUND( 5 * aCollector.GetGuide()->OnePixelInIU() ); - bool found = false; - - for( int dist = 0; dist < accuracy; ++dist ) - { - for( int i = 0; i < aCollector.GetCount(); ++i ) - { - if( PCB_SHAPE* shape = dynamic_cast( aCollector[i] ) ) - { - if( shape->HitTest( where, dist ) ) - { - found = true; - break; - } - } - } - - if( found ) - { - // throw out everything that is more sloppy than what we found - for( int i = 0; i < aCollector.GetCount(); ++i ) - { - if( PCB_SHAPE* shape = dynamic_cast( aCollector[i] ) ) - { - if( !shape->HitTest( where, dist ) ) - rejected.insert( shape ); - } - } - - // we're done now - break; - } - } - } - - if( aCollector.CountType( PCB_PAD_T ) > 0 ) - { - for( int i = 0; i < aCollector.GetCount(); ++i ) - { - if( PAD* pad = dyn_cast( aCollector[i] ) ) - { - FOOTPRINT* parent = pad->GetParent(); - double ratio = calcRatio( calcArea( pad ), calcArea( parent ) ); - - // when pad area is small compared to the parent footprint, - // then it is a clear sign the pad is the selection target - if( ratio < padToFootprintMinRatio ) - rejected.insert( pad->GetParent() ); - } - } - } - - bool hasNonModules = false; + std::map itemsBySloppiness; for( int i = 0; i < aCollector.GetCount(); ++i ) { - if( aCollector[i]->Type() != PCB_FOOTPRINT_T ) + BOARD_ITEM* item = aCollector[i]; + int actualSlop = INT_MAX; + + switch( item->Type() ) { - hasNonModules = true; + case PCB_TEXT_T: + { + PCB_TEXT* text = static_cast( item ); + text->GetEffectiveTextShape()->Collide( loc, maxSlop * pixel, &actualSlop ); + } + break; + + case PCB_FP_TEXT_T: + { + FP_TEXT* text = static_cast( item ); + text->GetEffectiveTextShape()->Collide( loc, maxSlop * pixel, &actualSlop ); + } + break; + + case PCB_ZONE_T: + { + ZONE* zone = static_cast( item ); + + // Zone borders are very specific + if( zone->HitTestForEdge( where, maxSlop * pixel / 2 ) ) + actualSlop = 0; + else if( zone->HitTestForEdge( where, maxSlop * pixel ) ) + actualSlop = maxSlop * pixel / 2; + else + item->GetEffectiveShape()->Collide( loc, maxSlop * pixel, &actualSlop ); + } + break; + + case PCB_FOOTPRINT_T: + { + FOOTPRINT* footprint = static_cast( item ); + + footprint->GetBoundingPoly().Collide( loc, maxSlop * pixel, &actualSlop ); + + // Consider footprints larger than the viewport only as a last resort + if( item->ViewBBox().GetHeight() > viewport.GetHeight() + || item->ViewBBox().GetWidth() > viewport.GetWidth() ) + { + actualSlop = INT_MAX / 2; + } + } + break; + + default: + item->GetEffectiveShape()->Collide( loc, maxSlop * pixel, &actualSlop ); break; } + + itemsBySloppiness[ item ] = actualSlop; + + if( actualSlop < minActual ) + minActual = actualSlop; } - if( aCollector.CountType( PCB_FOOTPRINT_T ) > 0 ) + // Prune sloppier items + + for( std::pair pair : itemsBySloppiness ) { - double maxArea = calcMaxArea( aCollector, PCB_FOOTPRINT_T ); - BOX2D viewportD = getView()->GetViewport(); - BOX2I viewport( VECTOR2I( viewportD.GetPosition() ), VECTOR2I( viewportD.GetSize() ) ); - double maxCoverRatio = footprintMaxCoverRatio; - - // FOOTPRINT::CoverageRatio() doesn't take zone handles & borders into account so just - // use a more aggressive cutoff point if zones are involved. - if( aCollector.CountType( PCB_ZONE_T ) ) - maxCoverRatio /= 2; - - for( int i = 0; i < aCollector.GetCount(); ++i ) - { - if( FOOTPRINT* footprint = dyn_cast( aCollector[i] ) ) - { - // filter out components larger than the viewport - if( footprint->ViewBBox().GetHeight() > viewport.GetHeight() - || footprint->ViewBBox().GetWidth() > viewport.GetWidth() ) - { - rejected.insert( footprint ); - } - // footprints completely covered with other features have no other - // means of selection, so must be kept - else if( footprint->CoverageRatio( aCollector ) > maxCoverRatio ) - { - rejected.erase( footprint ); - } - // if a footprint is much smaller than the largest overlapping - // footprint then it should be considered for selection - else if( calcRatio( calcArea( footprint ), maxArea ) <= footprintToFootprintMinRatio ) - { - continue; - } - // reject ALL OTHER footprints if there's still something else left - // to select - else if( hasNonModules ) - { - rejected.insert( footprint ); - } - } - } + if( pair.second > minActual + pixel ) + aCollector.Transfer( pair.first ); } - if( aCollector.CountType( PCB_VIA_T ) > 0 ) + // If the user clicked on a small item within a much larger one then it's pretty clear + // they're trying to select the smaller one. + + constexpr double sizeRatio = 1.3; + + std::vector> itemsByArea; + + for( int i = 0; i < aCollector.GetCount(); ++i ) { - for( int i = 0; i < aCollector.GetCount(); ++i ) + BOARD_ITEM* item = aCollector[i]; + double area; + + if( item->Type() == PCB_ZONE_T + && static_cast( item )->HitTestForEdge( where, maxSlop * pixel / 2 ) ) { - if( VIA* via = dyn_cast( aCollector[i] ) ) - { - double viaArea = calcArea( via ); - - for( int j = 0; j < aCollector.GetCount(); ++j ) - { - if( i == j ) - continue; - - BOARD_ITEM* item = aCollector[j]; - double areaRatio = calcRatio( viaArea, calcArea( item ) ); - - if( item->Type() == PCB_FOOTPRINT_T && areaRatio < padToFootprintMinRatio ) - rejected.insert( item ); - - if( item->Type() == PCB_PAD_T && areaRatio < viaToPadMinRatio ) - rejected.insert( item ); - - if( TRACK* track = dyn_cast( item ) ) - { - if( track->GetNetCode() != via->GetNetCode() ) - continue; - - double lenRatio = (double) ( track->GetLength() + track->GetWidth() ) / - (double) via->GetWidth(); - - if( lenRatio > trackViaLengthRatio ) - rejected.insert( track ); - } - } - } + // Zone borders are very specific, so make them "small" + area = maxSlop * pixel * pixel; } + else + { + area = FOOTPRINT::GetCoverageArea( item, aCollector ); + } + + itemsByArea.emplace_back( item, area ); } - int nTracks = aCollector.CountType( PCB_TRACE_T ); + std::sort( itemsByArea.begin(), itemsByArea.end(), + []( const std::pair& lhs, + const std::pair& rhs ) -> bool + { + return lhs.second < rhs.second; + } ); - if( nTracks > 0 ) + bool rejecting = false; + + for( int i = 1; i < (int) itemsByArea.size(); ++i ) { - double maxLength = 0.0; - double minLength = std::numeric_limits::max(); - double maxArea = 0.0; - const TRACK* maxTrack = nullptr; + if( itemsByArea[i].second > itemsByArea[i-1].second * sizeRatio ) + rejecting = true; - for( int i = 0; i < aCollector.GetCount(); ++i ) + if( rejecting ) + rejected.insert( itemsByArea[i].first ); + } + + // Special case: if a footprint is completely covered with other features then there's no + // way to select it -- so we need to leave it in the list for user disambiguation. + + constexpr double maxCoverRatio = 0.75; + + for( int i = 0; i < aCollector.GetCount(); ++i ) + { + if( FOOTPRINT* footprint = dynamic_cast( aCollector[i] ) ) { - if( TRACK* track = dyn_cast( aCollector[i] ) ) - { - maxLength = std::max( track->GetLength(), maxLength ); - maxLength = std::max( (double) track->GetWidth(), maxLength ); - - minLength = std::min( std::max( track->GetLength(), (double) track->GetWidth() ), minLength ); - - double area = track->GetLength() * track->GetWidth(); - - if( area > maxArea ) - { - maxArea = area; - maxTrack = track; - } - } - } - - if( maxLength > 0.0 && minLength / maxLength < trackTrackLengthRatio && nTracks > 1 ) - { - for( int i = 0; i < aCollector.GetCount(); ++i ) - { - if( TRACK* track = dyn_cast( aCollector[i] ) ) - { - double ratio = std::max( (double) track->GetWidth(), track->GetLength() ) / maxLength; - - if( ratio > trackTrackLengthRatio ) - rejected.insert( track ); - } - } - } - - for( int j = 0; j < aCollector.GetCount(); ++j ) - { - if( FOOTPRINT* footprint = dyn_cast( aCollector[j] ) ) - { - double ratio = calcRatio( maxArea, footprint->GetFootprintRect().GetArea() ); - - if( ratio < padToFootprintMinRatio - && calcCommonArea( maxTrack, footprint ) < commonAreaRatio ) - { - rejected.insert( footprint ); - } - } + if( footprint->CoverageRatio( aCollector ) > maxCoverRatio ) + rejected.erase( footprint ); } } + // Hopefully we've now got what the user wanted. + if( (unsigned) aCollector.GetCount() > rejected.size() ) // do not remove everything { for( BOARD_ITEM* item : rejected )