Revise method of selecting footprints in contrast
When working in high contrast mode, we want to be able to select a
footprint with only elements in, e.g. silk and fab layers.
The previous design for footprint IsOnLayer had one behavior of every
other element and a different behavior for footprints. This leads to
multiple bugs as new features use the overloaded IsOnLayer expecting it
to report if the element exists on a layer or not.
For footprints, we need a different routine to determine whether or not
to select the footprint when clicking on it. IsOnLayer will report if
the footprint has any elements on a specific layer but we don't want to
use the bbox for a hittest because large footprints with through hole
pads will exist on every layer and have an enormous bbox. Instead, we
filter footprints based on the hittest of each element. This behaves in
a more logical fashion, allowing you to select a footprint by clicking
on a visible element of that footprint.
Fixes https://gitlab.com/kicad/code/kicad/-/issues/15284
(cherry picked from commit f3d65b5156
)
This commit is contained in:
parent
093db600ff
commit
bd57957df7
|
@ -1103,23 +1103,97 @@ void FOOTPRINT::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_I
|
||||||
bool FOOTPRINT::IsOnLayer( PCB_LAYER_ID aLayer ) const
|
bool FOOTPRINT::IsOnLayer( PCB_LAYER_ID aLayer ) const
|
||||||
{
|
{
|
||||||
// If we have any pads, fall back on normal checking
|
// If we have any pads, fall back on normal checking
|
||||||
if( !m_pads.empty() )
|
for( PAD* pad : m_pads )
|
||||||
return m_layer == aLayer;
|
{
|
||||||
|
if( pad->IsOnLayer( aLayer ) )
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// No pads? Check if this entire footprint exists on the given layer
|
// No pads? Check if this entire footprint exists on the given layer
|
||||||
for( FP_ZONE* zone : m_fp_zones )
|
for( FP_ZONE* zone : m_fp_zones )
|
||||||
{
|
{
|
||||||
if( !zone->IsOnLayer( aLayer ) )
|
if( zone->IsOnLayer( aLayer ) )
|
||||||
return false;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( m_reference->IsOnLayer( aLayer ) || m_value->IsOnLayer( aLayer ) )
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for( BOARD_ITEM* item : m_drawings )
|
||||||
|
{
|
||||||
|
if( item->IsOnLayer( aLayer ) )
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool FOOTPRINT::HitTestOnLayer( const VECTOR2I& aPosition, PCB_LAYER_ID aLayer, int aAccuracy ) const
|
||||||
|
{
|
||||||
|
for( PAD* pad : m_pads )
|
||||||
|
{
|
||||||
|
if( pad->IsOnLayer( aLayer ) && pad->HitTest( aPosition, aAccuracy ) )
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for( ZONE* zone : m_fp_zones )
|
||||||
|
{
|
||||||
|
if( zone->IsOnLayer( aLayer ) && zone->HitTest( aPosition, aAccuracy ) )
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for( BOARD_ITEM* item : m_drawings )
|
for( BOARD_ITEM* item : m_drawings )
|
||||||
{
|
{
|
||||||
if( !item->IsOnLayer( aLayer ) )
|
if( item->Type() != PCB_TEXT_T && item->IsOnLayer( aLayer )
|
||||||
|
&& item->HitTest( aPosition, aAccuracy ) )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool FOOTPRINT::HitTestOnLayer( const BOX2I& aRect, bool aContained, PCB_LAYER_ID aLayer, int aAccuracy ) const
|
||||||
|
{
|
||||||
|
std::vector<BOARD_ITEM*> items;
|
||||||
|
|
||||||
|
for( PAD* pad : m_pads )
|
||||||
|
{
|
||||||
|
if( pad->IsOnLayer( aLayer ) )
|
||||||
|
items.push_back( pad );
|
||||||
|
}
|
||||||
|
|
||||||
|
for( FP_ZONE* zone : m_fp_zones )
|
||||||
|
{
|
||||||
|
if( zone->IsOnLayer( aLayer ) )
|
||||||
|
items.push_back( zone );
|
||||||
|
}
|
||||||
|
|
||||||
|
for( BOARD_ITEM* item : m_drawings )
|
||||||
|
{
|
||||||
|
if( item->Type() != PCB_TEXT_T && item->IsOnLayer( aLayer ) )
|
||||||
|
items.push_back( item );
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we require the elements to be contained in the rect and any of them are not,
|
||||||
|
// we can return false;
|
||||||
|
// Conversely, if we just require any of the elements to have a hit, we can return true
|
||||||
|
// when the first one is found.
|
||||||
|
for( BOARD_ITEM* item : items )
|
||||||
|
{
|
||||||
|
if( !aContained && item->HitTest( aRect, aContained, aAccuracy ) )
|
||||||
|
return true;
|
||||||
|
else if( aContained && !item->HitTest( aRect, aContained, aAccuracy ) )
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
// If we didn't exit in the loop, that means that we did not return false for aContained or
|
||||||
|
// we did not return true for !aContained. So we can just return the bool with a test of
|
||||||
|
// whether there were any elements or not.
|
||||||
|
return !items.empty() && aContained;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -329,9 +329,7 @@ public:
|
||||||
bool IsFlipped() const { return GetLayer() == B_Cu; }
|
bool IsFlipped() const { return GetLayer() == B_Cu; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A special IsOnLayer for footprints: return true if the footprint contains only items on the
|
* @copydoc BOARD_ITEM::IsOnLayer
|
||||||
* given layer, even if that layer is not one of the valid footprint layers F_Cu and B_Cu.
|
|
||||||
* This allows selection of "graphic" footprints that contain only silkscreen, for example.
|
|
||||||
*/
|
*/
|
||||||
bool IsOnLayer( PCB_LAYER_ID aLayer ) const override;
|
bool IsOnLayer( PCB_LAYER_ID aLayer ) const override;
|
||||||
|
|
||||||
|
@ -518,6 +516,18 @@ public:
|
||||||
|
|
||||||
bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override;
|
bool HitTest( const BOX2I& aRect, bool aContained, int aAccuracy = 0 ) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if the point hits one or more of the footprint elements on a given layer.
|
||||||
|
*
|
||||||
|
* @param aPosition is the point to test
|
||||||
|
* @param aAccuracy is the hit test accuracy
|
||||||
|
* @param aLayer is the layer to test
|
||||||
|
* @return true if aPosition hits a footprint element on aLayer
|
||||||
|
*/
|
||||||
|
bool HitTestOnLayer( const VECTOR2I& aPosition, PCB_LAYER_ID aLayer, int aAccuracy = 0 ) const;
|
||||||
|
|
||||||
|
bool HitTestOnLayer( const BOX2I& aRect, bool aContained, PCB_LAYER_ID aLayer, int aAccuracy = 0 ) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return reference designator text.
|
* @return reference designator text.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -117,7 +117,7 @@ static void existsOnLayerFunc( LIBEVAL::CONTEXT* aCtx, void *self )
|
||||||
{
|
{
|
||||||
anyMatch = true;
|
anyMatch = true;
|
||||||
|
|
||||||
if( item->IsOnLayer( ToLAYER_ID( entry.GetValue())))
|
if( item->IsOnLayer( ToLAYER_ID( entry.GetValue() ) ) )
|
||||||
return 1.0;
|
return 1.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -744,6 +744,8 @@ bool PCB_SELECTION_TOOL::selectPoint( const VECTOR2I& aWhere, bool aOnDrag,
|
||||||
// Apply the stateful filter
|
// Apply the stateful filter
|
||||||
FilterCollectedItems( collector, false );
|
FilterCollectedItems( collector, false );
|
||||||
|
|
||||||
|
FilterCollectorForFootprints( collector, aWhere );
|
||||||
|
|
||||||
// For subtracting, we only want items that are selected
|
// For subtracting, we only want items that are selected
|
||||||
if( m_subtractive )
|
if( m_subtractive )
|
||||||
{
|
{
|
||||||
|
@ -965,6 +967,7 @@ bool PCB_SELECTION_TOOL::selectMultiple()
|
||||||
{
|
{
|
||||||
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( candidate.first );
|
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( candidate.first );
|
||||||
|
|
||||||
|
|
||||||
if( item && Selectable( item ) && item->HitTest( selectionRect, !greedySelection )
|
if( item && Selectable( item ) && item->HitTest( selectionRect, !greedySelection )
|
||||||
&& ( greedySelection || !group_items.count( item ) ) )
|
&& ( greedySelection || !group_items.count( item ) ) )
|
||||||
{
|
{
|
||||||
|
@ -2938,10 +2941,9 @@ void PCB_SELECTION_TOOL::GuessSelectionCandidates( GENERAL_COLLECTOR& aCollector
|
||||||
for( int i = 0; i < aCollector.GetCount(); ++i )
|
for( int i = 0; i < aCollector.GetCount(); ++i )
|
||||||
{
|
{
|
||||||
BOARD_ITEM* item = aCollector[i];
|
BOARD_ITEM* item = aCollector[i];
|
||||||
KICAD_T type = item->Type();
|
|
||||||
|
|
||||||
if( ( type == PCB_TEXT_T || type == PCB_TEXTBOX_T || type == PCB_SHAPE_T )
|
if( item->IsType( { PCB_TEXT_T, PCB_TEXTBOX_T, PCB_SHAPE_T, PCB_FOOTPRINT_T } )
|
||||||
&& silkLayers[item->GetLayer()] )
|
&& item->IsOnLayer( activeLayer ) )
|
||||||
{
|
{
|
||||||
preferred.insert( item );
|
preferred.insert( item );
|
||||||
}
|
}
|
||||||
|
@ -3198,6 +3200,67 @@ void PCB_SELECTION_TOOL::FilterCollectorForMarkers( GENERAL_COLLECTOR& aCollecto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PCB_SELECTION_TOOL::FilterCollectorForFootprints( GENERAL_COLLECTOR& aCollector, const VECTOR2I& aWhere ) const
|
||||||
|
{
|
||||||
|
const RENDER_SETTINGS* settings = getView()->GetPainter()->GetSettings();
|
||||||
|
|
||||||
|
auto visibleLayers =
|
||||||
|
[&]()
|
||||||
|
{
|
||||||
|
if( m_isFootprintEditor )
|
||||||
|
{
|
||||||
|
LSET set;
|
||||||
|
|
||||||
|
for( PCB_LAYER_ID layer : LSET::AllLayersMask().Seq() )
|
||||||
|
set.set( layer, view()->IsLayerVisible( layer ) );
|
||||||
|
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return board()->GetVisibleLayers();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
LSET layers = visibleLayers();
|
||||||
|
|
||||||
|
if( settings->GetHighContrast() )
|
||||||
|
{
|
||||||
|
layers.reset();
|
||||||
|
|
||||||
|
const std::set<int> activeLayers = settings->GetHighContrastLayers();
|
||||||
|
|
||||||
|
for( int layer : activeLayers )
|
||||||
|
{
|
||||||
|
if( layer >= 0 && layer < PCB_LAYER_ID_COUNT )
|
||||||
|
layers.set( layer );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate from the back so we don't have to worry about removals.
|
||||||
|
for( int i = aCollector.GetCount() - 1; i >= 0; --i )
|
||||||
|
{
|
||||||
|
bool has_hit = false;
|
||||||
|
BOARD_ITEM* item = aCollector[i];
|
||||||
|
FOOTPRINT* fp = dyn_cast<FOOTPRINT*>( item );
|
||||||
|
|
||||||
|
if( !fp )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for( PCB_LAYER_ID layer : layers.Seq() )
|
||||||
|
{
|
||||||
|
if( fp->HitTestOnLayer( aWhere, layer ) )
|
||||||
|
{
|
||||||
|
has_hit = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !has_hit )
|
||||||
|
aCollector.Remove( item );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int PCB_SELECTION_TOOL::updateSelection( const TOOL_EVENT& aEvent )
|
int PCB_SELECTION_TOOL::updateSelection( const TOOL_EVENT& aEvent )
|
||||||
{
|
{
|
||||||
|
|
|
@ -212,6 +212,11 @@ public:
|
||||||
*/
|
*/
|
||||||
void FilterCollectedItems( GENERAL_COLLECTOR& aCollector, bool aMultiSelect );
|
void FilterCollectedItems( GENERAL_COLLECTOR& aCollector, bool aMultiSelect );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drop footprints that are not directly selected
|
||||||
|
*/
|
||||||
|
void FilterCollectorForFootprints( GENERAL_COLLECTOR& aCollector, const VECTOR2I& aWhere ) const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
KIGFX::PCB_VIEW* view() const
|
KIGFX::PCB_VIEW* view() const
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue