Rewrite PCBNew selection disambiguation based on shapes.

The new shape architecture gives us the opportunity to make text
selection much more intuitive by actually looking at the glyph
shapes.  Before text would be selected when you clicked in the
descenders area (which was usually blank given uppercase letters
and digits).

Fixes https://gitlab.com/kicad/code/kicad/issues/6525
This commit is contained in:
Jeff Young 2020-11-27 18:04:30 +00:00
parent 0293f880ec
commit 896ad4a749
7 changed files with 210 additions and 392 deletions

View File

@ -612,9 +612,9 @@ std::vector<wxPoint> 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;

View File

@ -231,7 +231,7 @@ void FOOTPRINT::TransformFPShapesWithClearanceToPolygon( SHAPE_POLY_SET& aCorner
{
std::vector<FP_TEXT*> 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
{

View File

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

View File

@ -33,6 +33,7 @@
#include <pcb_edit_frame.h>
#include <board.h>
#include <fp_shape.h>
#include <pcb_text.h>
#include <footprint.h>
#include <view/view.h>
#include <geometry/shape_null.h>
@ -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<const FOOTPRINT*>( aItem );
return footprint->GetFootprintRect().GetArea();
}
else if( aItem->Type() == PCB_FP_TEXT_T )
{
const FP_TEXT* text = static_cast<const FP_TEXT*>( 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 );
}

View File

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

View File

@ -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<SHAPE> GetEffectiveShape( PCB_LAYER_ID aLayer = UNDEFINED_LAYER ) const override;

View File

@ -33,6 +33,7 @@ using namespace std::placeholders;
#include <track.h>
#include <footprint.h>
#include <pcb_shape.h>
#include <pcb_text.h>
#include <zone.h>
#include <collectors.h>
#include <confirm.h>
@ -46,7 +47,6 @@ using namespace std::placeholders;
#include <pcbnew_settings.h>
#include <tool/tool_event.h>
#include <tool/tool_manager.h>
//#include <router/router_tool.h>
#include <connectivity/connectivity_data.h>
#include <footprint_viewer_frame.h>
#include <id.h>
@ -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<const FOOTPRINT*>( aItem )->GetFootprintRect();
return aItem->GetBoundingBox();
}
static double calcArea( const BOARD_ITEM* aItem )
{
if( aItem->Type() == PCB_TRACE_T )
{
const TRACK* t = static_cast<const TRACK*>( 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<double>::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<double>::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<BOARD_ITEM*> 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<ZONE*>( 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<FP_TEXT*>( 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<PCB_SHAPE*>( 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<PCB_SHAPE*>( 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<PAD*>( 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<BOARD_ITEM*, int> 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<PCB_TEXT*>( item );
text->GetEffectiveTextShape()->Collide( loc, maxSlop * pixel, &actualSlop );
}
break;
case PCB_FP_TEXT_T:
{
FP_TEXT* text = static_cast<FP_TEXT*>( item );
text->GetEffectiveTextShape()->Collide( loc, maxSlop * pixel, &actualSlop );
}
break;
case PCB_ZONE_T:
{
ZONE* zone = static_cast<ZONE*>( 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<FOOTPRINT*>( 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<BOARD_ITEM*, int> 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<FOOTPRINT*>( 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<std::pair<BOARD_ITEM*, double>> 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<ZONE*>( item )->HitTestForEdge( where, maxSlop * pixel / 2 ) )
{
if( VIA* via = dyn_cast<VIA*>( 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<TRACK*>( 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<BOARD_ITEM*, double>& lhs,
const std::pair<BOARD_ITEM*, double>& 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<double>::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<FOOTPRINT*>( aCollector[i] ) )
{
if( TRACK* track = dyn_cast<TRACK*>( 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<TRACK*>( 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<FOOTPRINT*>( 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 )