Refine the footprint selection logic
- Footprints cannot be selected if they do not have items visible on the screen - Clicking on empty space in a footprint will select it if the space is contained in the visible bounding box - Clicking on a selected footprint a second time will deselect it - Clicking on a footprint that has pads selected will still select the footprint This avoids having the full footprint bounding box considered when selecting footprints (useful b/c footprint bbox is often much larger and unexpected). Also allows non-standard footprints (e.g. silk or fab only footprints) to be logically selected if the layers on which they have elements are visible. Fixes https://gitlab.com/kicad/code/kicad/-/issues/15284
This commit is contained in:
parent
8762859c6d
commit
a90c9d7c93
|
@ -1099,6 +1099,51 @@ const BOX2I FOOTPRINT::GetBoundingBox( bool aIncludeText, bool aIncludeInvisible
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const BOX2I FOOTPRINT::GetLayerBoundingBox( LSET aLayers ) const
|
||||||
|
{
|
||||||
|
std::vector<PCB_TEXT*> texts;
|
||||||
|
const BOARD* board = GetBoard();
|
||||||
|
bool isFPEdit = board && board->IsFootprintHolder();
|
||||||
|
|
||||||
|
// Start with an uninitialized bounding box
|
||||||
|
BOX2I bbox;
|
||||||
|
|
||||||
|
for( BOARD_ITEM* item : m_drawings )
|
||||||
|
{
|
||||||
|
if( m_privateLayers.test( item->GetLayer() ) && !isFPEdit )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if( ( aLayers & item->GetLayerSet() ).none() )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// We want the bitmap bounding box just in the footprint editor
|
||||||
|
// so it will start with the correct initial zoom
|
||||||
|
if( item->Type() == PCB_BITMAP_T && !isFPEdit )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bbox.Merge( item->GetBoundingBox() );
|
||||||
|
}
|
||||||
|
|
||||||
|
for( PAD* pad : m_pads )
|
||||||
|
{
|
||||||
|
if( ( aLayers & pad->GetLayerSet() ).none() )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bbox.Merge( pad->GetBoundingBox() );
|
||||||
|
}
|
||||||
|
|
||||||
|
for( ZONE* zone : m_zones )
|
||||||
|
{
|
||||||
|
if( ( aLayers & zone->GetLayerSet() ).none() )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
bbox.Merge( zone->GetBoundingBox() );
|
||||||
|
}
|
||||||
|
|
||||||
|
return bbox;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
SHAPE_POLY_SET FOOTPRINT::GetBoundingHull() const
|
SHAPE_POLY_SET FOOTPRINT::GetBoundingHull() const
|
||||||
{
|
{
|
||||||
const BOARD* board = GetBoard();
|
const BOARD* board = GetBoard();
|
||||||
|
|
|
@ -172,6 +172,11 @@ public:
|
||||||
const BOX2I GetBoundingBox() const override;
|
const BOX2I GetBoundingBox() const override;
|
||||||
const BOX2I GetBoundingBox( bool aIncludeText, bool aIncludeInvisibleText ) const;
|
const BOX2I GetBoundingBox( bool aIncludeText, bool aIncludeInvisibleText ) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the bounding box of the footprint on a given set of layers
|
||||||
|
*/
|
||||||
|
const BOX2I GetLayerBoundingBox( LSET aLayers ) const;
|
||||||
|
|
||||||
VECTOR2I GetCenter() const override
|
VECTOR2I GetCenter() const override
|
||||||
{
|
{
|
||||||
return GetBoundingBox( false, false ).GetCenter();
|
return GetBoundingBox( false, false ).GetCenter();
|
||||||
|
|
|
@ -3241,6 +3241,47 @@ void PCB_SELECTION_TOOL::FilterCollectorForMarkers( GENERAL_COLLECTOR& aCollecto
|
||||||
void PCB_SELECTION_TOOL::FilterCollectorForFootprints( GENERAL_COLLECTOR& aCollector, const VECTOR2I& aWhere ) const
|
void PCB_SELECTION_TOOL::FilterCollectorForFootprints( GENERAL_COLLECTOR& aCollector, const VECTOR2I& aWhere ) const
|
||||||
{
|
{
|
||||||
const RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
|
const RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
|
||||||
|
BOX2D viewport = getView()->GetViewport();
|
||||||
|
BOX2I extents( { KiROUND( viewport.GetPosition().x ), KiROUND( viewport.GetPosition().y ) },
|
||||||
|
{ KiROUND( viewport.GetSize().x ), KiROUND( viewport.GetSize().y ) } );
|
||||||
|
|
||||||
|
bool need_direct_hit = false;
|
||||||
|
FOOTPRINT* single_fp = nullptr;
|
||||||
|
|
||||||
|
// If the designer is not modifying the existing selection AND we already have
|
||||||
|
// a selection, then we only want to select items that are directly under the cursor.
|
||||||
|
// This prevents us from being unable to clear the selection when zoomed into a footprint
|
||||||
|
if( !m_additive && !m_subtractive && !m_exclusive_or && m_selection.GetSize() > 0 )
|
||||||
|
{
|
||||||
|
need_direct_hit = true;
|
||||||
|
|
||||||
|
for( EDA_ITEM* item : m_selection )
|
||||||
|
{
|
||||||
|
FOOTPRINT* fp = nullptr;
|
||||||
|
|
||||||
|
if( item->Type() != PCB_FOOTPRINT_T )
|
||||||
|
fp = static_cast<BOARD_ITEM*>( item )->GetParentFootprint();
|
||||||
|
|
||||||
|
// If the selection contains items that are not footprints, then don't restrict
|
||||||
|
// whether we deselect the item or not.
|
||||||
|
if( !fp )
|
||||||
|
{
|
||||||
|
single_fp = nullptr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if( !single_fp )
|
||||||
|
{
|
||||||
|
single_fp = fp;
|
||||||
|
}
|
||||||
|
// If the selection contains items from multiple footprints, then don't restrict
|
||||||
|
// whether we deselect the item or not.
|
||||||
|
else if( single_fp != fp )
|
||||||
|
{
|
||||||
|
single_fp = nullptr;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto visibleLayers =
|
auto visibleLayers =
|
||||||
[&]()
|
[&]()
|
||||||
|
@ -3278,24 +3319,58 @@ void PCB_SELECTION_TOOL::FilterCollectorForFootprints( GENERAL_COLLECTOR& aColle
|
||||||
// Iterate from the back so we don't have to worry about removals.
|
// Iterate from the back so we don't have to worry about removals.
|
||||||
for( int i = aCollector.GetCount() - 1; i >= 0; --i )
|
for( int i = aCollector.GetCount() - 1; i >= 0; --i )
|
||||||
{
|
{
|
||||||
bool has_hit = false;
|
|
||||||
BOARD_ITEM* item = aCollector[i];
|
BOARD_ITEM* item = aCollector[i];
|
||||||
FOOTPRINT* fp = dyn_cast<FOOTPRINT*>( item );
|
FOOTPRINT* fp = dyn_cast<FOOTPRINT*>( item );
|
||||||
|
|
||||||
if( !fp )
|
if( !fp )
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
BOX2I bbox = fp->GetLayerBoundingBox( layers );
|
||||||
|
|
||||||
|
// If the point clicked is not inside the visible bounding box, we can also remove it.
|
||||||
|
if( !bbox.Contains( aWhere) )
|
||||||
|
aCollector.Remove( item );
|
||||||
|
|
||||||
|
bool has_hit = false;
|
||||||
|
|
||||||
for( PCB_LAYER_ID layer : layers.Seq() )
|
for( PCB_LAYER_ID layer : layers.Seq() )
|
||||||
{
|
{
|
||||||
if( fp->HitTestOnLayer( aWhere, layer ) )
|
if( fp->HitTestOnLayer( extents, false, layer ) )
|
||||||
{
|
{
|
||||||
has_hit = true;
|
has_hit = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the point is outside of the visible bounding box, we can remove it.
|
||||||
if( !has_hit )
|
if( !has_hit )
|
||||||
|
{
|
||||||
aCollector.Remove( item );
|
aCollector.Remove( item );
|
||||||
|
}
|
||||||
|
// Do not require a direct hit on this fp if the existing selection only contains
|
||||||
|
// this fp's items. This allows you to have a selection of pads from a single
|
||||||
|
// footprint and still click in the center of the footprint to select it.
|
||||||
|
else if( single_fp )
|
||||||
|
{
|
||||||
|
if( fp == single_fp )
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if( need_direct_hit )
|
||||||
|
{
|
||||||
|
has_hit = false;
|
||||||
|
|
||||||
|
for( PCB_LAYER_ID layer : layers.Seq() )
|
||||||
|
{
|
||||||
|
if( fp->HitTestOnLayer( aWhere, layer ) )
|
||||||
|
{
|
||||||
|
has_hit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !has_hit )
|
||||||
|
aCollector.Remove( item );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue