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
This commit is contained in:
parent
25e391719e
commit
f3d65b5156
|
@ -259,7 +259,7 @@ public:
|
|||
* @param aLayer The layer to test for.
|
||||
* @return true if on given layer, else false.
|
||||
*/
|
||||
virtual bool IsOnLayer( PCB_LAYER_ID aLayer, bool aIncludeCourtyards = false ) const
|
||||
virtual bool IsOnLayer( PCB_LAYER_ID aLayer ) const
|
||||
{
|
||||
return m_layer == aLayer;
|
||||
}
|
||||
|
|
|
@ -152,7 +152,7 @@ public:
|
|||
PCB_GROUP* DeepDuplicate() const;
|
||||
|
||||
/// @copydoc BOARD_ITEM::IsOnLayer
|
||||
bool IsOnLayer( PCB_LAYER_ID aLayer, bool aIncludeCourtyards = false ) const override;
|
||||
bool IsOnLayer( PCB_LAYER_ID aLayer ) const override;
|
||||
|
||||
/// @copydoc EDA_ITEM::HitTest
|
||||
bool HitTest( const VECTOR2I& aPosition, int aAccuracy = 0 ) const override;
|
||||
|
|
|
@ -146,7 +146,7 @@ void PCB_BASE_EDIT_FRAME::ShowGraphicItemPropertiesDialog( PCB_SHAPE* aShape )
|
|||
|
||||
if( dlg.ShowQuasiModal() == wxID_OK )
|
||||
{
|
||||
if( aShape->IsOnLayer( GetActiveLayer(), true ) )
|
||||
if( aShape->IsOnLayer( GetActiveLayer() ) )
|
||||
{
|
||||
DRAWING_TOOL* drawingTool = m_toolManager->GetTool<DRAWING_TOOL>();
|
||||
drawingTool->SetStroke( aShape->GetStroke(), GetActiveLayer() );
|
||||
|
|
|
@ -1255,37 +1255,102 @@ void FOOTPRINT::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_I
|
|||
}
|
||||
|
||||
|
||||
bool FOOTPRINT::IsOnLayer( PCB_LAYER_ID aLayer, bool aIncludeCourtyards ) const
|
||||
bool FOOTPRINT::IsOnLayer( PCB_LAYER_ID aLayer ) const
|
||||
{
|
||||
static const LSET courtyardLayers( 2, B_CrtYd, F_CrtYd );
|
||||
|
||||
if( aIncludeCourtyards && courtyardLayers[aLayer] )
|
||||
return !GetCourtyard( aLayer ).IsEmpty();
|
||||
|
||||
// If we have any pads, fall back on normal checking
|
||||
if( !m_pads.empty() )
|
||||
return m_layer == aLayer;
|
||||
for( PAD* pad : m_pads )
|
||||
{
|
||||
if( pad->IsOnLayer( aLayer ) )
|
||||
return true;
|
||||
}
|
||||
|
||||
// No pads? Check if this entire footprint exists on the given layer
|
||||
for( ZONE* zone : m_zones )
|
||||
{
|
||||
if( !zone->IsOnLayer( aLayer ) )
|
||||
return false;
|
||||
if( zone->IsOnLayer( aLayer ) )
|
||||
return true;
|
||||
}
|
||||
|
||||
for( PCB_FIELD* field : m_fields )
|
||||
{
|
||||
if( !field->IsOnLayer( aLayer ) )
|
||||
return false;
|
||||
if( field->IsOnLayer( aLayer ) )
|
||||
return true;
|
||||
}
|
||||
|
||||
for( BOARD_ITEM* item : m_drawings )
|
||||
{
|
||||
if( !item->IsOnLayer( aLayer ) )
|
||||
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_zones )
|
||||
{
|
||||
if( zone->IsOnLayer( aLayer ) && zone->HitTest( aPosition, aAccuracy ) )
|
||||
return true;
|
||||
}
|
||||
|
||||
for( BOARD_ITEM* item : m_drawings )
|
||||
{
|
||||
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( ZONE* zone : m_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 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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -343,11 +343,9 @@ public:
|
|||
bool IsFlipped() const { return GetLayer() == B_Cu; }
|
||||
|
||||
/**
|
||||
* A special IsOnLayer for footprints: return true if the footprint contains only items on the
|
||||
* 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.
|
||||
* @copydoc BOARD_ITEM::IsOnLayer
|
||||
*/
|
||||
bool IsOnLayer( PCB_LAYER_ID aLayer, bool aIncludeCourtyards = false ) const override;
|
||||
bool IsOnLayer( PCB_LAYER_ID aLayer ) const override;
|
||||
|
||||
// m_footprintStatus bits:
|
||||
#define FP_is_LOCKED 0x01 ///< footprint LOCKED: no autoplace allowed
|
||||
|
@ -532,6 +530,18 @@ public:
|
|||
|
||||
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.
|
||||
*/
|
||||
|
|
|
@ -618,7 +618,7 @@ public:
|
|||
|
||||
void GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList ) override;
|
||||
|
||||
bool IsOnLayer( PCB_LAYER_ID aLayer, bool aIncludeCourtyards = false ) const override
|
||||
bool IsOnLayer( PCB_LAYER_ID aLayer ) const override
|
||||
{
|
||||
return m_layerMask[aLayer];
|
||||
}
|
||||
|
|
|
@ -1390,7 +1390,7 @@ void PCB_EDIT_FRAME::SetActiveLayer( PCB_LAYER_ID aLayer )
|
|||
// as they won't be found in the view layer's itemset for re-painting.
|
||||
if( GetDisplayOptions().m_ContrastModeDisplay == HIGH_CONTRAST_MODE::HIDDEN )
|
||||
{
|
||||
if( item->IsOnLayer( oldLayer, true ) || item->IsOnLayer( aLayer, true ) )
|
||||
if( item->IsOnLayer( oldLayer ) || item->IsOnLayer( aLayer ) )
|
||||
return KIGFX::ALL;
|
||||
}
|
||||
|
||||
|
|
|
@ -117,7 +117,7 @@ static void existsOnLayerFunc( LIBEVAL::CONTEXT* aCtx, void *self )
|
|||
{
|
||||
anyMatch = true;
|
||||
|
||||
if( item->IsOnLayer( ToLAYER_ID( entry.GetValue() ), true ) )
|
||||
if( item->IsOnLayer( ToLAYER_ID( entry.GetValue() ) ) )
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -321,12 +321,12 @@ LSET PCB_GROUP::GetLayerSet() const
|
|||
}
|
||||
|
||||
|
||||
bool PCB_GROUP::IsOnLayer( PCB_LAYER_ID aLayer, bool aIncludeCourtyards ) const
|
||||
bool PCB_GROUP::IsOnLayer( PCB_LAYER_ID aLayer ) const
|
||||
{
|
||||
// A group is on a layer if any item is on the layer
|
||||
for( BOARD_ITEM* item : m_items )
|
||||
{
|
||||
if( item->IsOnLayer( aLayer, aIncludeCourtyards ) )
|
||||
if( item->IsOnLayer( aLayer ) )
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -476,7 +476,7 @@ int PCB_VIA::GetSolderMaskExpansion() const
|
|||
}
|
||||
|
||||
|
||||
bool PCB_VIA::IsOnLayer( PCB_LAYER_ID aLayer, bool aIncludeCourtyards ) const
|
||||
bool PCB_VIA::IsOnLayer( PCB_LAYER_ID aLayer ) const
|
||||
{
|
||||
#if 0
|
||||
// Nice and simple, but raises its ugly head in performance profiles....
|
||||
|
|
|
@ -404,7 +404,7 @@ public:
|
|||
bool IsTented() const override;
|
||||
int GetSolderMaskExpansion() const;
|
||||
|
||||
bool IsOnLayer( PCB_LAYER_ID aLayer, bool aIncludeCourtyards = false ) const override;
|
||||
bool IsOnLayer( PCB_LAYER_ID aLayer ) const override;
|
||||
|
||||
virtual LSET GetLayerSet() const override;
|
||||
|
||||
|
|
|
@ -739,6 +739,8 @@ bool PCB_SELECTION_TOOL::selectPoint( const VECTOR2I& aWhere, bool aOnDrag,
|
|||
// Apply the stateful filter
|
||||
FilterCollectedItems( collector, false );
|
||||
|
||||
FilterCollectorForFootprints( collector, aWhere );
|
||||
|
||||
// For subtracting, we only want items that are selected
|
||||
if( m_subtractive )
|
||||
{
|
||||
|
@ -960,6 +962,7 @@ bool PCB_SELECTION_TOOL::selectMultiple()
|
|||
{
|
||||
BOARD_ITEM* item = static_cast<BOARD_ITEM*>( candidate.first );
|
||||
|
||||
|
||||
if( item && Selectable( item ) && item->HitTest( selectionRect, !greedySelection )
|
||||
&& ( greedySelection || !group_items.count( item ) ) )
|
||||
{
|
||||
|
@ -2359,7 +2362,7 @@ bool PCB_SELECTION_TOOL::Selectable( const BOARD_ITEM* aItem, bool checkVisibili
|
|||
for( int layer : activeLayers )
|
||||
{
|
||||
// NOTE: Only checking the regular layers (not GAL meta-layers)
|
||||
if( layer < PCB_LAYER_ID_COUNT && aItem->IsOnLayer( ToLAYER_ID( layer ), true ) )
|
||||
if( layer < PCB_LAYER_ID_COUNT && aItem->IsOnLayer( ToLAYER_ID( layer ) ) )
|
||||
{
|
||||
onActiveLayer = true;
|
||||
break;
|
||||
|
@ -2878,11 +2881,9 @@ void PCB_SELECTION_TOOL::GuessSelectionCandidates( GENERAL_COLLECTOR& aCollector
|
|||
for( int i = 0; i < aCollector.GetCount(); ++i )
|
||||
{
|
||||
BOARD_ITEM* item = aCollector[i];
|
||||
KICAD_T type = item->Type();
|
||||
|
||||
if( ( type == PCB_FIELD_T || type == PCB_TEXT_T || type == PCB_TEXTBOX_T
|
||||
|| type == PCB_SHAPE_T )
|
||||
&& silkLayers[item->GetLayer()] )
|
||||
if( item->IsType( { PCB_FIELD_T, PCB_TEXT_T, PCB_TEXTBOX_T, PCB_SHAPE_T, PCB_FOOTPRINT_T } )
|
||||
&& item->IsOnLayer( activeLayer ) )
|
||||
{
|
||||
preferred.insert( item );
|
||||
}
|
||||
|
@ -3149,6 +3150,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 )
|
||||
{
|
||||
|
|
|
@ -211,6 +211,11 @@ public:
|
|||
*/
|
||||
void FilterCollectedItems( GENERAL_COLLECTOR& aCollector, bool aMultiSelect );
|
||||
|
||||
/**
|
||||
* Drop footprints that are not directly selected
|
||||
*/
|
||||
void FilterCollectorForFootprints( GENERAL_COLLECTOR& aCollector, const VECTOR2I& aWhere ) const;
|
||||
|
||||
protected:
|
||||
KIGFX::PCB_VIEW* view() const
|
||||
{
|
||||
|
|
|
@ -342,7 +342,7 @@ double ZONE::ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const
|
|||
}
|
||||
|
||||
|
||||
bool ZONE::IsOnLayer( PCB_LAYER_ID aLayer, bool aIncludeCourtyards ) const
|
||||
bool ZONE::IsOnLayer( PCB_LAYER_ID aLayer ) const
|
||||
{
|
||||
return m_layerSet.test( aLayer );
|
||||
}
|
||||
|
|
|
@ -171,7 +171,7 @@ public:
|
|||
// Return the first layer in GUI sequence.
|
||||
PCB_LAYER_ID GetFirstLayer() const;
|
||||
|
||||
virtual bool IsOnLayer( PCB_LAYER_ID, bool aIncludeCourtyards = false ) const override;
|
||||
virtual bool IsOnLayer( PCB_LAYER_ID ) const override;
|
||||
|
||||
virtual void ViewGetLayers( int aLayers[], int& aCount ) const override;
|
||||
|
||||
|
|
Loading…
Reference in New Issue