From bd57957df7d5ea83f9a75cc86738cf10d4886044 Mon Sep 17 00:00:00 2001 From: Seth Hillbrand Date: Tue, 1 Aug 2023 10:37:19 -0700 Subject: [PATCH] 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 f3d65b51567af733a0a9ce453a6199aa64e5092c) --- pcbnew/footprint.cpp | 86 +++++++++++++++++++++++++++-- pcbnew/footprint.h | 16 +++++- pcbnew/pcb_expr_functions.cpp | 2 +- pcbnew/tools/pcb_selection_tool.cpp | 69 ++++++++++++++++++++++- pcbnew/tools/pcb_selection_tool.h | 5 ++ 5 files changed, 165 insertions(+), 13 deletions(-) diff --git a/pcbnew/footprint.cpp b/pcbnew/footprint.cpp index d75ae7cc50..37b8984ee4 100644 --- a/pcbnew/footprint.cpp +++ b/pcbnew/footprint.cpp @@ -1103,23 +1103,97 @@ void FOOTPRINT::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vectorIsOnLayer( aLayer ) ) + return true; + } // No pads? Check if this entire footprint exists on the given layer for( FP_ZONE* zone : m_fp_zones ) { - if( !zone->IsOnLayer( aLayer ) ) - return false; + if( zone->IsOnLayer( aLayer ) ) + 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 ) { - 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 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 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; } diff --git a/pcbnew/footprint.h b/pcbnew/footprint.h index b16ab71306..d5860dd2a1 100644 --- a/pcbnew/footprint.h +++ b/pcbnew/footprint.h @@ -329,9 +329,7 @@ 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 ) const override; @@ -518,6 +516,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. */ diff --git a/pcbnew/pcb_expr_functions.cpp b/pcbnew/pcb_expr_functions.cpp index 3d1d5f3d64..eb3fbd624c 100644 --- a/pcbnew/pcb_expr_functions.cpp +++ b/pcbnew/pcb_expr_functions.cpp @@ -117,7 +117,7 @@ static void existsOnLayerFunc( LIBEVAL::CONTEXT* aCtx, void *self ) { anyMatch = true; - if( item->IsOnLayer( ToLAYER_ID( entry.GetValue()))) + if( item->IsOnLayer( ToLAYER_ID( entry.GetValue() ) ) ) return 1.0; } } diff --git a/pcbnew/tools/pcb_selection_tool.cpp b/pcbnew/tools/pcb_selection_tool.cpp index 80ec950b10..ebdffeb4ed 100644 --- a/pcbnew/tools/pcb_selection_tool.cpp +++ b/pcbnew/tools/pcb_selection_tool.cpp @@ -744,6 +744,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 ) { @@ -965,6 +967,7 @@ bool PCB_SELECTION_TOOL::selectMultiple() { BOARD_ITEM* item = static_cast( candidate.first ); + if( item && Selectable( item ) && item->HitTest( selectionRect, !greedySelection ) && ( 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 ) { BOARD_ITEM* item = aCollector[i]; - KICAD_T type = item->Type(); - if( ( type == PCB_TEXT_T || type == PCB_TEXTBOX_T || type == PCB_SHAPE_T ) - && silkLayers[item->GetLayer()] ) + if( item->IsType( { PCB_TEXT_T, PCB_TEXTBOX_T, PCB_SHAPE_T, PCB_FOOTPRINT_T } ) + && item->IsOnLayer( activeLayer ) ) { 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 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( 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 ) { diff --git a/pcbnew/tools/pcb_selection_tool.h b/pcbnew/tools/pcb_selection_tool.h index 61238ea7ec..ea42f4cdf7 100644 --- a/pcbnew/tools/pcb_selection_tool.h +++ b/pcbnew/tools/pcb_selection_tool.h @@ -212,6 +212,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 {