Pcbnew: fix footprint selection bug.

Do not remove footprint objects from list of selected objects when there
are no drawable objects other than the reference and value text.

Increase the minimum size rectangle for footprints from 0.025mm to 1mm
when the footprint has no drawing objects so that it is easier to select
the footprint in this case.

Add some pretty colors to the footprint bounding box and hull outline
so that it's possible to tell the difference between selection areas.

Fixes https://gitlab.com/kicad/code/kicad/-/issues/8379
This commit is contained in:
Wayne Stambaugh 2021-06-23 18:58:58 -04:00
parent 50eea4f487
commit 27e9750507
3 changed files with 91 additions and 60 deletions

View File

@ -679,9 +679,10 @@ const EDA_RECT FOOTPRINT::GetBoundingBox( bool aIncludeText, bool aIncludeInvisi
for( FP_ZONE* zone : m_fp_zones )
area.Merge( zone->GetBoundingBox() );
// Groups do not contribute to the rect, only their members
bool noDrawItems = ( m_drawings.empty() && m_pads.empty() && m_fp_zones.empty() );
if( aIncludeText )
// Groups do not contribute to the rect, only their members
if( aIncludeText || noDrawItems )
{
for( BOARD_ITEM* item : m_drawings )
{
@ -710,16 +711,18 @@ const EDA_RECT FOOTPRINT::GetBoundingBox( bool aIncludeText, bool aIncludeInvisi
}
if( ( m_value->IsVisible() && valueLayerIsVisible ) || aIncludeInvisibleText )
if( ( m_value->IsVisible() && valueLayerIsVisible )
|| aIncludeInvisibleText || noDrawItems )
area.Merge( m_value->GetBoundingBox() );
if( ( m_reference->IsVisible() && refLayerIsVisible ) || aIncludeInvisibleText )
if( ( m_reference->IsVisible() && refLayerIsVisible )
|| aIncludeInvisibleText || noDrawItems )
area.Merge( m_reference->GetBoundingBox() );
}
if( board )
{
if( aIncludeText && aIncludeInvisibleText )
if( ( aIncludeText && aIncludeInvisibleText ) || noDrawItems )
{
m_boundingBoxCacheTimeStamp = board->GetTimeStamp();
m_cachedBoundingBox = area;
@ -790,9 +793,10 @@ SHAPE_POLY_SET FOOTPRINT::GetBoundingHull() const
if( rawPolys.OutlineCount() == 0 )
{
// generate a small dummy rectangular outline around the anchor
const int halfsize = Millimeter2iu( 0.02 );
const int halfsize = Millimeter2iu( 1.0 );
rawPolys.NewOutline();
// add a square:
rawPolys.Append( GetPosition().x - halfsize, GetPosition().y - halfsize );
rawPolys.Append( GetPosition().x + halfsize, GetPosition().y - halfsize );
@ -924,13 +928,19 @@ bool FOOTPRINT::HitTest( const EDA_RECT& aRect, bool aContained, int aAccuracy )
arect.Inflate( aAccuracy );
if( aContained )
{
return arect.Contains( GetBoundingBox( false, false ) );
}
else
{
// If the rect does not intersect the bounding box, skip any tests
if( !aRect.Intersects( GetBoundingBox( false, false ) ) )
return false;
// The empty footprint dummy rectangle intersects the selection area.
if( m_pads.empty() && m_fp_zones.empty() && m_drawings.empty() )
return GetBoundingBox( true, false ).Intersects( arect );
// Determine if any elements in the FOOTPRINT intersect the rect
for( PAD* pad : m_pads )
{
@ -2087,14 +2097,18 @@ static struct FOOTPRINT_DESC
propMgr.AddProperty( new PROPERTY<FOOTPRINT, int>( _HKI( "Solderpaste Margin Override" ),
&FOOTPRINT::SetLocalSolderPasteMargin, &FOOTPRINT::GetLocalSolderPasteMargin,
PROPERTY_DISPLAY::DISTANCE ) );
propMgr.AddProperty( new PROPERTY<FOOTPRINT, double>( _HKI( "Solderpaste Margin Ratio Override" ),
&FOOTPRINT::SetLocalSolderPasteMarginRatio, &FOOTPRINT::GetLocalSolderPasteMarginRatio ) );
propMgr.AddProperty( new PROPERTY<FOOTPRINT,
double>( _HKI( "Solderpaste Margin Ratio Override" ),
&FOOTPRINT::SetLocalSolderPasteMarginRatio,
&FOOTPRINT::GetLocalSolderPasteMarginRatio ) );
propMgr.AddProperty( new PROPERTY<FOOTPRINT, int>( _HKI( "Thermal Relief Width" ),
&FOOTPRINT::SetThermalWidth, &FOOTPRINT::GetThermalWidth,
PROPERTY_DISPLAY::DISTANCE ) );
&FOOTPRINT::SetThermalWidth,
&FOOTPRINT::GetThermalWidth,
PROPERTY_DISPLAY::DISTANCE ) );
propMgr.AddProperty( new PROPERTY<FOOTPRINT, int>( _HKI( "Thermal Relief Gap" ),
&FOOTPRINT::SetThermalGap, &FOOTPRINT::GetThermalGap,
PROPERTY_DISPLAY::DISTANCE ) );
&FOOTPRINT::SetThermalGap,
&FOOTPRINT::GetThermalGap,
PROPERTY_DISPLAY::DISTANCE ) );
// TODO zone connection, FPID?
}
} _FOOTPRINT_DESC;

View File

@ -2,6 +2,8 @@
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2013-2019 CERN
* Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
*
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* @author Maciej Suminski <maciej.suminski@cern.ch>
*
@ -458,18 +460,6 @@ bool PCB_PAINTER::Draw( const VIEW_ITEM* aItem, int aLayer )
if( !item )
return false;
if( ADVANCED_CFG::GetCfg().m_DrawBoundingBoxes )
{
// Show bounding boxes of painted objects for debugging.
EDA_RECT box = item->GetBoundingBox();
m_gal->SetIsFill( false );
m_gal->SetIsStroke( true );
m_gal->SetStrokeColor( item->IsSelected() ? COLOR4D( 1.0, 0.2, 0.2, 1 ) :
COLOR4D( 0.2, 0.2, 0.2, 1 ) );
m_gal->SetLineWidth( Mils2iu( 3 ) );
m_gal->DrawRectangle( box.GetOrigin(), box.GetEnd() );
}
// the "cast" applied in here clarifies which overloaded draw() is called
switch( item->Type() )
{
@ -538,6 +528,40 @@ bool PCB_PAINTER::Draw( const VIEW_ITEM* aItem, int aLayer )
return false;
}
// Draw bounding boxes after drawing objects so they can be seen.
if( ADVANCED_CFG::GetCfg().m_DrawBoundingBoxes )
{
// Show bounding boxes of painted objects for debugging.
EDA_RECT box = item->GetBoundingBox();
m_gal->SetIsFill( false );
m_gal->SetIsStroke( true );
if( item->Type() == PCB_FOOTPRINT_T )
m_gal->SetStrokeColor( item->IsSelected() ? COLOR4D( 1.0, 0.2, 0.2, 1 ) :
COLOR4D( MAGENTA ) );
else
m_gal->SetStrokeColor( item->IsSelected() ? COLOR4D( 1.0, 0.2, 0.2, 1 ) :
COLOR4D( 0.2, 0.2, 0.2, 1 ) );
m_gal->SetLineWidth( 1.5 / m_gal->GetWorldScale() );
m_gal->DrawRectangle( box.GetOrigin(), box.GetEnd() );
if( item->Type() == PCB_FOOTPRINT_T )
{
m_gal->SetStrokeColor( item->IsSelected() ? COLOR4D( 1.0, 0.2, 0.2, 1 ) :
COLOR4D( CYAN ) );
const FOOTPRINT* fp = static_cast<const FOOTPRINT*>( item );
if( fp )
{
SHAPE_POLY_SET convex = fp->GetBoundingHull();
m_gal->DrawPolyline( convex.COutline( 0 ) );
}
}
}
return true;
}
@ -716,9 +740,11 @@ void PCB_PAINTER::draw( const PCB_VIA* aVia, int aLayer )
VECTOR2D textpos( 0.0, 0.0 );
wxString netname = UnescapeString( aVia->GetShortNetname() );
// calculate the size of net name text:
double tsize = 1.5 * size / netname.Length();
tsize = std::min( tsize, size );
// Use a smaller text size to handle interline, pen size..
tsize *= 0.7;
VECTOR2D namesize( tsize, tsize );
@ -914,6 +940,7 @@ void PCB_PAINTER::draw( const PAD* aPad, int aLayer )
// calculate the size of net name text:
double tsize = 1.5 * padsize.x / netname.Length();
tsize = std::min( tsize, size );
// Use a smaller text size to handle interline, pen size..
tsize *= 0.7;
VECTOR2D namesize( tsize, tsize );
@ -929,6 +956,7 @@ void PCB_PAINTER::draw( const PAD* aPad, int aLayer )
textpos.y = -textpos.y;
double tsize = 1.5 * padsize.x / padName.Length();
tsize = std::min( tsize, size );
// Use a smaller text size to handle interline, pen size..
tsize *= 0.7;
tsize = std::min( tsize, size );
@ -1122,7 +1150,6 @@ void PCB_PAINTER::draw( const PAD* aPad, int aLayer )
else if( effectiveMargin.x > 0 )
{
// A positive margin produces a larger rect, but with rounded corners
m_gal->DrawRectangle( r->GetPosition(), r->GetPosition() + r->GetSize() );
// Use segments to produce the margin with rounded corners
@ -1190,7 +1217,6 @@ void PCB_PAINTER::draw( const PAD* aPad, int aLayer )
else
{
// This is expensive. Avoid if possible.
SHAPE_POLY_SET polySet;
aPad->TransformShapeWithClearanceToPolygon( polySet, ToLAYER_ID( aLayer ), margin.x,
bds.m_MaxError, ERROR_INSIDE );
@ -1543,19 +1569,6 @@ void PCB_PAINTER::draw( const FOOTPRINT* aFootprint, int aLayer )
VECTOR2D center = aFootprint->GetPosition();
m_gal->DrawLine( center - VECTOR2D( anchorSize, 0 ), center + VECTOR2D( anchorSize, 0 ) );
m_gal->DrawLine( center - VECTOR2D( 0, anchorSize ), center + VECTOR2D( 0, anchorSize ) );
#if 0 // For debug purpose only: draw the footing bounding box
double bboxThickness = 1.0 / m_gal->GetWorldScale();
m_gal->SetLineWidth( bboxThickness );
EDA_RECT rect = aFootprint->GetBoundingBox();
m_gal->DrawRectangle( VECTOR2D( rect.GetOrigin() ), VECTOR2D( rect.GetEnd() ) );
double bboxThickness = 3.0 / m_gal->GetWorldScale();
m_gal->SetLineWidth( bboxThickness );
SHAPE_POLY_SET convex = aFootprint->GetBoundingHull();
m_gal->DrawPolyline( convex.COutline( 0 ) );
#endif
}
}
@ -1626,7 +1639,7 @@ void PCB_PAINTER::draw( const PCB_GROUP* aGroup, int aLayer )
void PCB_PAINTER::draw( const ZONE* aZone, int aLayer )
{
/**
/*
* aLayer will be the virtual zone layer (LAYER_ZONE_START, ... in GAL_LAYER_ID)
* This is used for draw ordering in the GAL.
* The color for the zone comes from the associated copper layer ( aLayer - LAYER_ZONE_START )
@ -1752,6 +1765,7 @@ void PCB_PAINTER::draw( const PCB_DIMENSION_BASE* aDimension, int aLayer )
break;
}
}
// Draw text
const PCB_TEXT& text = aDimension->Text();
VECTOR2D position( text.GetTextPos().x, text.GetTextPos().y );

View File

@ -2,9 +2,9 @@
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2013-2017 CERN
* Copyright (C) 2018-2021 KiCad Developers, see AUTHORS.txt for contributors.
* @author Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
* @author Maciej Suminski <maciej.suminski@cern.ch>
* Copyright (C) 2018-2020 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -76,6 +76,7 @@ public:
Add( PCB_ACTIONS::selectConnection );
Add( PCB_ACTIONS::selectNet );
// This could be enabled if we have better logic for picking the target net with the mouse
// Add( PCB_ACTIONS::deselectNet );
Add( PCB_ACTIONS::selectSameSheet );
@ -90,7 +91,7 @@ private:
/**
* Private implementation of firewalled private data
* Private implementation of firewalled private data.
*/
class PCB_SELECTION_TOOL::PRIV
{
@ -307,9 +308,9 @@ int PCB_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
{
evt->SetPassEvent();
}
// Single click? Select single object
else if( evt->IsClick( BUT_LEFT ) )
{
// Single click? Select single object
if( m_highlight_modifier && brd_editor )
m_toolMgr->RunAction( PCB_ACTIONS::highlightNet, true );
else
@ -419,7 +420,8 @@ int PCB_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
// Yes -> run the move tool and wait till it finishes
PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( m_selection.GetItem( 0 ) );
// If there is only item in the selection and it's a track, then we need to route it
// If there is only item in the selection and it's a track, then we need
// to route it.
bool doRouting = ( track && ( 1 == m_selection.GetSize() ) );
if( doRouting && trackDragAction == TRACK_DRAG_ACTION::DRAG )
@ -802,9 +804,9 @@ bool PCB_SELECTION_TOOL::selectMultiple()
int width = area.GetEnd().x - area.GetOrigin().x;
/* Selection mode depends on direction of drag-selection:
* Left > Right : Select objects that are fully enclosed by selection
* Right > Left : Select objects that are crossed by selection
*/
* Left > Right : Select objects that are fully enclosed by selection
* Right > Left : Select objects that are crossed by selection
*/
bool windowSelection = width >= 0 ? true : false;
if( view->IsMirroredX() )
@ -1230,7 +1232,8 @@ void PCB_SELECTION_TOOL::selectConnectedTracks( BOARD_CONNECTED_ITEM& aStartItem
expand = true;
}
if( viaMap.count( pt ) && !viaMap[ pt ]->IsSelected() && aStopCondition != STOP_AT_JUNCTION )
if( viaMap.count( pt ) && !viaMap[ pt ]->IsSelected()
&& aStopCondition != STOP_AT_JUNCTION )
select( viaMap[ pt ] );
activePts.erase( activePts.begin() + i );
@ -1333,7 +1336,8 @@ void PCB_SELECTION_TOOL::selectAllItemsOnSheet( wxString& aSheetPath )
for( int netCode : netcodeList )
{
for( BOARD_CONNECTED_ITEM* mitem : board()->GetConnectivity()->GetNetItems( netCode, padType ) )
for( BOARD_CONNECTED_ITEM* mitem : board()->GetConnectivity()->GetNetItems( netCode,
padType ) )
{
if( mitem->Type() == PCB_PAD_T && !alg::contains( footprintList, mitem->GetParent() ) )
{
@ -1360,7 +1364,8 @@ void PCB_SELECTION_TOOL::selectAllItemsOnSheet( wxString& aSheetPath )
for( int netCode : netcodeList )
{
for( BOARD_CONNECTED_ITEM* item : board()->GetConnectivity()->GetNetItems( netCode, trackViaType ) )
for( BOARD_CONNECTED_ITEM* item : board()->GetConnectivity()->GetNetItems( netCode,
trackViaType ) )
localConnectionList.push_back( item );
}
@ -1496,9 +1501,7 @@ int PCB_SELECTION_TOOL::find( const TOOL_EVENT& aEvent )
/**
* Function itemIsIncludedByFilter()
*
* Determine if an item is included by the filter specified
* Determine if an item is included by the filter specified.
*
* @return true if aItem should be selected by this filter (i..e not filtered out)
*/
@ -1942,9 +1945,15 @@ bool PCB_SELECTION_TOOL::Selectable( const BOARD_ITEM* aItem, bool checkVisibili
return false;
// Allow selection of footprints if some part of the footprint is visible.
const FOOTPRINT* footprint = static_cast<const FOOTPRINT*>( aItem );
// If the footprint has no items except the reference and value fields, include the
// footprint in the selections.
if( footprint->GraphicalItems().empty()
&& footprint->Pads().empty()
&& footprint->Zones().empty() )
return true;
for( const BOARD_ITEM* item : footprint->GraphicalItems() )
{
if( Selectable( item, true ) )
@ -2391,7 +2400,6 @@ void PCB_SELECTION_TOOL::GuessSelectionCandidates( GENERAL_COLLECTOR& aCollector
}
// Prefer exact hits to sloppy ones
constexpr int MAX_SLOP = 5;
int pixel = (int) aCollector.GetGuide()->OnePixelInIU();
@ -2411,7 +2419,6 @@ void PCB_SELECTION_TOOL::GuessSelectionCandidates( GENERAL_COLLECTOR& aCollector
}
// Prune sloppier items
if( minSlop < INT_MAX )
{
for( std::pair<BOARD_ITEM*, int> pair : itemsBySloppiness )
@ -2423,7 +2430,6 @@ void PCB_SELECTION_TOOL::GuessSelectionCandidates( GENERAL_COLLECTOR& aCollector
// 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.5;
std::vector<std::pair<BOARD_ITEM*, double>> itemsByArea;
@ -2480,7 +2486,6 @@ void PCB_SELECTION_TOOL::GuessSelectionCandidates( GENERAL_COLLECTOR& aCollector
// 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.70;
for( int i = 0; i < aCollector.GetCount(); ++i )
@ -2493,7 +2498,6 @@ void PCB_SELECTION_TOOL::GuessSelectionCandidates( GENERAL_COLLECTOR& aCollector
}
// Hopefully we've now got what the user wanted.
if( (unsigned) aCollector.GetCount() > rejected.size() ) // do not remove everything
{
for( BOARD_ITEM* item : rejected )
@ -2531,7 +2535,6 @@ void PCB_SELECTION_TOOL::FilterCollectorForHierarchy( GENERAL_COLLECTOR& aCollec
// Set TEMP_SELECTED on all parents which are included in the GENERAL_COLLECTOR. This
// algorithm is O3n, whereas checking for the parent inclusion could potentially be On^2.
for( int j = 0; j < aCollector.GetCount(); j++ )
{
if( aCollector[j]->GetParent() )