Replace GFootprintList with KIWAY-based filter

The symbol chooser's footprint dropdown now fetches filtered results from
pcbnew via KIWAY instead of requiring direct access to FOOTPRINT_LIST.
Filter criteria (pin count, footprint patterns) are serialized as JSON,
sent to pcbnew, and matching footprints are returned as a JSON array.

The filtering uses preloaded footprints from the adapter rather than
re-reading from disk, which eliminates the main performance bottleneck
when selecting symbols.
This commit is contained in:
Seth Hillbrand
2026-01-23 08:42:58 -08:00
parent 764cfdd3ba
commit 55036f9921
15 changed files with 329 additions and 317 deletions
-34
View File
@@ -31,13 +31,8 @@
#include <footprint_info.h>
#include <dialogs/html_message_box.h>
#include <string_utils.h>
#include <kiface_ids.h>
#include <kiway.h>
#include <lib_id.h>
#include <thread>
#include <utility>
#include <wx/tokenzr.h>
#include <kiface_base.h>
FOOTPRINT_INFO* FOOTPRINT_LIST::GetFootprintInfo( const wxString& aLibNickname,
const wxString& aFootprintName )
@@ -152,32 +147,3 @@ wxString FOOTPRINT_LIST::GetErrorMessages()
return messages;
}
static FOOTPRINT_LIST* get_instance_from_id( KIWAY& aKiway, int aId )
{
void* ptr = nullptr;
try
{
ptr = Kiface().IfaceOrAddress( aId );
if( !ptr )
{
KIFACE* kiface = aKiway.KiFACE( KIWAY::FACE_PCB );
ptr = kiface->IfaceOrAddress( aId );
}
return static_cast<FOOTPRINT_LIST*>( ptr );
}
catch( ... )
{
return nullptr;
}
}
FOOTPRINT_LIST* FOOTPRINT_LIST::GetInstance( KIWAY& aKiway )
{
return get_instance_from_id( aKiway, KIFACE_FOOTPRINT_LIST );
}
+81 -55
View File
@@ -19,29 +19,23 @@
#include <eda_draw_frame.h>
#include <kiway.h>
#include <kiway_player.h>
#include <project.h>
#include <project_pcb.h>
#include <kiface_ids.h>
#include <widgets/footprint_choice.h>
#include <widgets/footprint_select_widget.h>
#include <widgets/wx_progress_reporters.h>
#include <progress_reporter.h>
#include <footprint_info_impl.h>
#include <nlohmann/json.hpp>
#include <wx/wupdlock.h>
wxDEFINE_EVENT( EVT_FOOTPRINT_SELECTED, wxCommandEvent );
FOOTPRINT_SELECT_WIDGET::FOOTPRINT_SELECT_WIDGET( EDA_DRAW_FRAME* aFrame, wxWindow* aParent,
FOOTPRINT_LIST* aFpList, bool aUpdate,
int aMaxItems ) :
wxPanel( aParent ),
m_update( aUpdate ),
m_max_items( aMaxItems ),
m_fp_list( aFpList ),
m_frame( aFrame )
wxPanel( aParent ),
m_max_items( aMaxItems ),
m_pin_count( 0 ),
m_zero_filter( true ),
m_kiway( nullptr )
{
m_zero_filter = true;
m_sizer = new wxBoxSizer( wxVERTICAL );
m_fp_sel_ctrl = new FOOTPRINT_CHOICE( this, wxID_ANY );
m_sizer->Add( m_fp_sel_ctrl, 1, wxEXPAND, 5 );
@@ -56,26 +50,7 @@ FOOTPRINT_SELECT_WIDGET::FOOTPRINT_SELECT_WIDGET( EDA_DRAW_FRAME* aFrame, wxWind
void FOOTPRINT_SELECT_WIDGET::Load( KIWAY& aKiway, PROJECT& aProject )
{
m_fp_list = FOOTPRINT_LIST::GetInstance( aKiway );
wxCHECK_MSG( m_fp_list, /* void */, "Failed to get the footprint list from the KiWay" );
if( m_fp_list->GetCount() == 0 )
{
WX_PROGRESS_REPORTER progressReporter( m_frame, _( "Load Footprint Libraries" ), 1,
PR_CAN_ABORT );
// If the fp-info-cache is empty (or, more likely, hasn't been created in a new
// project yet), load footprints the hard way.
FOOTPRINT_LIBRARY_ADAPTER* footprints = aProject.FootprintLibAdapter( aKiway );
FOOTPRINT_LIST_IMPL& fpList = static_cast<FOOTPRINT_LIST_IMPL&>( *m_fp_list );
fpList.ReadFootprintFiles( footprints, nullptr, &progressReporter );
}
m_fp_filter.SetList( *m_fp_list );
if( m_update )
UpdateList();
m_kiway = &aKiway;
}
@@ -98,7 +73,8 @@ void FOOTPRINT_SELECT_WIDGET::OnComboBox( wxCommandEvent& aEvent )
void FOOTPRINT_SELECT_WIDGET::ClearFilters()
{
m_fp_filter.ClearFilters();
m_pin_count = 0;
m_filters.Clear();
m_default_footprint.Clear();
m_zero_filter = false;
}
@@ -106,7 +82,7 @@ void FOOTPRINT_SELECT_WIDGET::ClearFilters()
void FOOTPRINT_SELECT_WIDGET::FilterByPinCount( int aPinCount )
{
m_fp_filter.FilterByPinCount( aPinCount );
m_pin_count = aPinCount;
}
@@ -114,7 +90,7 @@ void FOOTPRINT_SELECT_WIDGET::FilterByFootprintFilters( wxArrayString const& aFi
bool aZeroFilters )
{
m_zero_filter = ( aZeroFilters && aFilters.size() == 0 );
m_fp_filter.FilterByFootprintFilters( aFilters );
m_filters = aFilters;
}
@@ -126,34 +102,84 @@ void FOOTPRINT_SELECT_WIDGET::SetDefaultFootprint( wxString const& aFp )
bool FOOTPRINT_SELECT_WIDGET::UpdateList()
{
int n_items = 0;
if( !m_fp_list )
if( !m_kiway )
return false;
wxWindowUpdateLocker lock( m_fp_sel_ctrl );
m_fp_sel_ctrl->Clear();
// Be careful adding items! "Default" must occupy POS_DEFAULT,
// "Other" must occupy POS_OTHER, and the separator must occupy POS_SEPARATOR.
m_fp_sel_ctrl->Append( m_default_footprint.IsEmpty() ?
_( "No default footprint" ) :
wxS( "[" ) + _( "Default" ) + wxS( "] " ) + m_default_footprint,
new wxStringClientData( m_default_footprint ) );
// Add the default footprint entry at the top
wxString defaultLabel = m_default_footprint.IsEmpty()
? _( "No default footprint" )
: wxS( "[" ) + _( "Default" ) + wxS( "] " ) + m_default_footprint;
if( !m_zero_filter )
m_fp_sel_ctrl->Append( defaultLabel, new wxStringClientData( m_default_footprint ) );
// If zero_filter is set and we have no filters, show no footprints
if( m_zero_filter && m_filters.IsEmpty() && m_pin_count == 0 )
{
for( FOOTPRINT_INFO& fpinfo : m_fp_filter )
SelectDefault();
return true;
}
// Build JSON request for pcbnew
using json = nlohmann::json;
json request;
request["pin_count"] = m_pin_count;
request["zero_filters"] = m_zero_filter;
request["max_results"] = m_max_items;
json filtersArray = json::array();
for( const wxString& filter : m_filters )
filtersArray.push_back( filter.ToStdString() );
request["filters"] = filtersArray;
// Get the filter function from pcbnew via KIWAY
try
{
KIFACE* kiface = m_kiway->KiFACE( KIWAY::FACE_PCB );
if( !kiface )
{
wxString display_name( fpinfo.GetLibNickname() + wxS( ":" ) +
fpinfo.GetFootprintName() );
m_fp_sel_ctrl->Append( display_name, new wxStringClientData( display_name ) );
++n_items;
if( n_items >= m_max_items )
break;
SelectDefault();
return true;
}
void* funcPtr = kiface->IfaceOrAddress( KIFACE_FILTER_FOOTPRINTS );
if( !funcPtr )
{
SelectDefault();
return true;
}
// Call the filter function
using FilterFunc = wxString ( * )( const wxString& );
FilterFunc filterFootprints = reinterpret_cast<FilterFunc>( funcPtr );
wxString requestStr = wxString::FromUTF8( request.dump() );
wxString responseStr = filterFootprints( requestStr );
// Parse the response
json response = json::parse( responseStr.ToStdString() );
if( response.is_array() )
{
for( const auto& item : response )
{
if( item.is_string() )
{
wxString fpName = wxString::FromUTF8( item.get<std::string>() );
m_fp_sel_ctrl->Append( fpName, new wxStringClientData( fpName ) );
}
}
}
}
catch( const std::exception& e )
{
// JSON parsing or other error - just show default
}
SelectDefault();
+3 -1
View File
@@ -52,6 +52,7 @@
#include <settings/settings_manager.h>
#include <settings/cvpcb_settings.h>
#include <display_footprints_frame.h>
#include <footprint_info_impl.h>
#include <kiplatform/ui.h>
#include <listboxes.h>
#include <tools/cvpcb_actions.h>
@@ -81,7 +82,7 @@ CVPCB_MAINFRAME::CVPCB_MAINFRAME( KIWAY* aKiway, wxWindow* aParent ) :
m_cannotClose = false;
m_skipComponentSelect = false;
m_filteringOptions = FOOTPRINTS_LISTBOX::UNFILTERED_FP_LIST;
m_FootprintsList = FOOTPRINT_LIST::GetInstance( Kiway() );
m_FootprintsList = new FOOTPRINT_LIST_IMPL();
m_initialized = false;
m_aboutTitle = _( "Assign Footprints" );
@@ -241,6 +242,7 @@ CVPCB_MAINFRAME::~CVPCB_MAINFRAME()
delete m_actions;
delete m_toolManager;
delete m_toolDispatcher;
delete m_FootprintsList;
m_auimgr.UnInit();
}
+4 -12
View File
@@ -384,21 +384,13 @@ wxPanel* PANEL_SYMBOL_CHOOSER::constructRightPanel( wxWindow* aParent )
if( m_show_footprints )
{
FOOTPRINT_LIST* fp_list = FOOTPRINT_LIST::GetInstance( m_frame->Kiway() );
sizer->Add( m_symbol_preview, 11, wxEXPAND | wxALL, 5 );
if ( fp_list )
{
if( m_allow_field_edits )
m_fp_sel_ctrl = new FOOTPRINT_SELECT_WIDGET( m_frame, panel, fp_list, true );
m_fp_sel_ctrl = new FOOTPRINT_SELECT_WIDGET( m_frame, panel );
sizer->Add( m_fp_sel_ctrl, 0, wxEXPAND | wxLEFT | wxRIGHT, 5 );
m_fp_preview = new FOOTPRINT_PREVIEW_WIDGET( panel, m_frame->Kiway() );
m_fp_preview->SetUserUnits( m_frame->GetUserUnits() );
}
if( m_fp_sel_ctrl )
sizer->Add( m_fp_sel_ctrl, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5 );
m_fp_preview = new FOOTPRINT_PREVIEW_WIDGET( panel, m_frame->Kiway() );
m_fp_preview->SetUserUnits( m_frame->GetUserUnits() );
if( m_fp_preview )
sizer->Add( m_fp_preview, 10, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5 );
-12
View File
@@ -169,9 +169,6 @@ public:
{
}
virtual void WriteCacheToFile( const wxString& aFilePath ) {};
virtual void ReadCacheFromFile( const wxString& aFilePath ){};
/**
* @return the number of items stored in list
*/
@@ -256,15 +253,6 @@ public:
FOOTPRINT_LIBRARY_ADAPTER* GetAdapter() const { return m_adapter; }
/**
* Factory function to return a #FOOTPRINT_LIST via Kiway.
*
* This is not guaranteed to succeed and will return null if the kiface is not available.
*
* @param aKiway active kiway instance.
*/
static FOOTPRINT_LIST* GetInstance( KIWAY& aKiway );
protected:
FOOTPRINT_LIBRARY_ADAPTER* m_adapter; ///< no ownership
+5 -6
View File
@@ -31,14 +31,13 @@ enum KIFACE_ADDR_ID : int
{
KIFACE_ID_INVALID,
/**
* Return a pointer to the global instance of FOOTPRINT_LIST from pcbnew.
* Type is FOOTPRINT_LIST*
* Caller does NOT own.
*/
KIFACE_FOOTPRINT_LIST,
KIFACE_FOOTPRINT_LIBRARY_ADAPTER,
/// Function pointer type: wxString (*)(const wxString& aFilterJson)
/// Input JSON: {"pin_count": N, "filters": ["pattern1", ...], "zero_filters": bool, "max_results": N}
/// Output JSON: ["lib:footprint1", "lib:footprint2", ...]
KIFACE_FILTER_FOOTPRINTS,
KIFACE_LOAD_SCHEMATIC,
KIFACE_NETLIST_SCHEMATIC,
KIFACE_SCRIPTING_LEGACY,
+21 -37
View File
@@ -20,15 +20,14 @@
#ifndef FOOTPRINT_SELECT_WIDGET_H
#define FOOTPRINT_SELECT_WIDGET_H
#include <footprint_filter.h>
#include <footprint_info.h>
#include <vector>
#include <wx/arrstr.h>
#include <wx/panel.h>
class KIWAY;
class PROJECT;
class FOOTPRINT_CHOICE;
class wxWindow;
class EDA_DRAW_FRAME;
/**
* This event is fired when a footprint is selected. The string data of the
@@ -42,36 +41,21 @@ public:
/**
* Construct a footprint selector widget.
*
* This requires references to an external footprint loader, and an external
* unique_ptr-to-FOOTPRINT_LIST. The latter will be populated with a
* FOOTPRINT_LIST instance the first time Load() is called.
*
* The reason for this is that footprint loading tends to be very expensive,
* especially when using online libraries. The caller is expected to keep
* these objects around (e.g. they may be statics on the dialog this
* FOOTPRINT_SELECT_WIDGET is created in) so footprints do not have to be
* loaded more than once.
*
* @param aFrame - parent frame for context
* @param aParent - parent window
* @param aFpList - FOOTPRINT_LIST container
* @param aUpdate - whether to call UpdateList() automatically when finished loading
* @param aMaxItems - maximum number of filter items to display, in addition to
* Default and Other
* @param aMaxItems - maximum number of filter items to display
*/
FOOTPRINT_SELECT_WIDGET( EDA_DRAW_FRAME* aFrame, wxWindow* aParent, FOOTPRINT_LIST* aFpList,
bool aUpdate = true, int aMaxItems = 400 );
FOOTPRINT_SELECT_WIDGET( EDA_DRAW_FRAME* aFrame, wxWindow* aParent, int aMaxItems = 400 );
virtual ~FOOTPRINT_SELECT_WIDGET()
{
}
/**
* Start loading. This function returns immediately; footprints will
* continue to load in the background.
* Initialize the widget with a KIWAY for cross-module communication.
*
* @param aKiway - active kiway instance. This is cached for use when "Other"
* is selected.
* @param aProject - current project
* @param aKiway - active kiway instance used to fetch filtered footprints from pcbnew
* @param aProject - current project (unused, kept for API compatibility)
*/
void Load( KIWAY& aKiway, PROJECT& aProject );
@@ -104,11 +88,10 @@ public:
void SetDefaultFootprint( const wxString& aFp );
/**
* Update the contents of the list to match the filters. Has no effect if
* the footprint list has not been loaded yet. The "default" footprint will be
* selected.
* Update the contents of the list by fetching filtered footprints from pcbnew.
* The "default" footprint will be selected.
*
* @return true if the footprint list has been loaded (and the list was updated)
* @return true if the list was updated successfully
*/
bool UpdateList();
@@ -123,17 +106,18 @@ public:
virtual bool Enable( bool aEnable = true ) override;
private:
FOOTPRINT_CHOICE* m_fp_sel_ctrl;
wxSizer* m_sizer;
FOOTPRINT_CHOICE* m_fp_sel_ctrl;
wxSizer* m_sizer;
bool m_update;
int m_max_items;
wxString m_default_footprint;
int m_max_items;
wxString m_default_footprint;
FOOTPRINT_LIST* m_fp_list;
FOOTPRINT_FILTER m_fp_filter;
bool m_zero_filter;
EDA_DRAW_FRAME* m_frame;
// Filter parameters
int m_pin_count;
wxArrayString m_filters;
bool m_zero_filter;
KIWAY* m_kiway;
void OnComboBox( wxCommandEvent& aEvent );
};
-12
View File
@@ -297,15 +297,3 @@ FOOTPRINT_LIST_IMPL::FOOTPRINT_LIST_IMPL() :
m_cancelled( false )
{
}
void FOOTPRINT_LIST_IMPL::WriteCacheToFile( const wxString& aFilePath )
{
// Cache disabled; no longer needed with async library loading
}
void FOOTPRINT_LIST_IMPL::ReadCacheFromFile( const wxString& aFilePath )
{
// Cache disabled; no longer needed with async library loading
}
-5
View File
@@ -87,9 +87,6 @@ public:
FOOTPRINT_LIST_IMPL();
virtual ~FOOTPRINT_LIST_IMPL() {};
void WriteCacheToFile( const wxString& aFilePath ) override;
void ReadCacheFromFile( const wxString& aFilePath ) override;
bool ReadFootprintFiles( FOOTPRINT_LIBRARY_ADAPTER* aAdapter, const wxString* aNickname = nullptr,
PROGRESS_REPORTER* aProgressReporter = nullptr ) override;
@@ -127,7 +124,5 @@ private:
std::mutex m_loadInProgress;
};
extern FOOTPRINT_LIST_IMPL GFootprintList; // KIFACE scope.
#endif // FOOTPRINT_INFO_IMPL_H
+48 -84
View File
@@ -38,6 +38,10 @@ std::map<wxString, LIB_DATA> FOOTPRINT_LIBRARY_ADAPTER::GlobalLibraries;
std::shared_mutex FOOTPRINT_LIBRARY_ADAPTER::GlobalLibraryMutex;
std::map<wxString, std::vector<std::unique_ptr<FOOTPRINT>>> FOOTPRINT_LIBRARY_ADAPTER::PreloadedFootprints;
std::shared_mutex FOOTPRINT_LIBRARY_ADAPTER::PreloadedFootprintsMutex;
FOOTPRINT_LIBRARY_ADAPTER::FOOTPRINT_LIBRARY_ADAPTER( LIBRARY_MANAGER& aManager ) :
LIBRARY_MANAGER_ADAPTER( aManager )
@@ -53,9 +57,45 @@ wxString FOOTPRINT_LIBRARY_ADAPTER::GlobalPathEnvVariableName()
void FOOTPRINT_LIBRARY_ADAPTER::enumerateLibrary( LIB_DATA* aLib )
{
wxArrayString dummyList;
wxArrayString namesAS;
std::map<std::string, UTF8> options = aLib->row->GetOptionsMap();
pcbplugin( aLib )->FootprintEnumerate( dummyList, getUri( aLib->row ), false, &options );
PCB_IO* plugin = pcbplugin( aLib );
wxString uri = getUri( aLib->row );
wxString nickname = aLib->row->Nickname();
plugin->FootprintEnumerate( namesAS, uri, false, &options );
std::vector<std::unique_ptr<FOOTPRINT>> footprints;
footprints.reserve( namesAS.size() );
for( const wxString& footprintName : namesAS )
{
FOOTPRINT* footprint = nullptr;
try
{
footprint = plugin->FootprintLoad( uri, footprintName, false, &options );
}
catch( IO_ERROR& e )
{
wxLogTrace( traceLibraries, "FP: Exception loading footprint %s from %s: %s",
footprintName, nickname, e.What() );
continue;
}
if( !footprint )
continue;
LIB_ID id = footprint->GetFPID();
id.SetLibNickname( nickname );
footprint->SetFPID( id );
footprints.emplace_back( footprint );
}
{
std::unique_lock lock( PreloadedFootprintsMutex );
PreloadedFootprints[nickname] = std::move( footprints );
}
}
@@ -100,97 +140,21 @@ std::vector<FOOTPRINT*> FOOTPRINT_LIBRARY_ADAPTER::GetFootprints( const wxString
{
std::vector<FOOTPRINT*> footprints;
std::optional<const LIB_DATA*> maybeLib = fetchIfLoaded( aNickname );
std::shared_lock lock( PreloadedFootprintsMutex );
auto it = PreloadedFootprints.find( aNickname );
if( !maybeLib )
if( it == PreloadedFootprints.end() )
return footprints;
const LIB_DATA* lib = *maybeLib;
std::map<std::string, UTF8> options = lib->row->GetOptionsMap();
wxArrayString namesAS;
footprints.reserve( it->second.size() );
try
{
pcbplugin( lib )->FootprintEnumerate( namesAS, getUri( lib->row ), true, &options );
}
catch( IO_ERROR& e )
{
wxLogTrace( traceLibraries, "FP: Exception enumerating library %s: %s", lib->row->Nickname(), e.What() );
}
for( const wxString& footprintName : namesAS )
{
FOOTPRINT* footprint = nullptr;
try
{
footprint = pcbplugin( lib )->FootprintLoad( getUri( lib->row ),footprintName, false, &options );
}
catch( IO_ERROR& e )
{
wxLogTrace( traceLibraries, "FP: Exception loading footprint from %s: %s", lib->row->Nickname(), e.What() );
continue;
}
wxCHECK2( footprint, continue );
LIB_ID id = footprint->GetFPID();
id.SetLibNickname( lib->row->Nickname() );
footprint->SetFPID( id );
footprints.emplace_back( footprint );
}
for( const auto& fp : it->second )
footprints.push_back( fp.get() );
return footprints;
}
std::vector<const FOOTPRINT*> FOOTPRINT_LIBRARY_ADAPTER::GetCachedFootprints( const wxString& aNickname,
bool aBestEfforts )
{
std::vector<const FOOTPRINT*> footprints;
std::optional<const LIB_DATA*> maybeLib = fetchIfLoaded( aNickname );
if( !maybeLib )
return footprints;
const LIB_DATA* lib = *maybeLib;
std::map<std::string, UTF8> options = lib->row->GetOptionsMap();
wxArrayString namesAS;
try
{
pcbplugin( lib )->FootprintEnumerate( namesAS, getUri( lib->row ), true, &options );
}
catch( IO_ERROR& e )
{
wxLogTrace( traceLibraries, "FP: Exception enumerating library %s: %s",
lib->row->Nickname(), e.What() );
}
for( const wxString& footprintName : namesAS )
{
const FOOTPRINT* footprint = nullptr;
try
{
footprint = pcbplugin( lib )->GetEnumeratedFootprint( getUri( lib->row ), footprintName,
&options );
}
catch( IO_ERROR& e )
{
wxLogTrace( traceLibraries, "FP: Exception getting cached footprint from %s: %s",
lib->row->Nickname(), e.What() );
continue;
}
if( footprint )
footprints.emplace_back( footprint );
}
return footprints;
}
std::vector<wxString> FOOTPRINT_LIBRARY_ADAPTER::GetFootprintNames( const wxString& aNickname, bool aBestEfforts )
{
+5 -13
View File
@@ -58,19 +58,6 @@ public:
*/
std::vector<FOOTPRINT*> GetFootprints( const wxString& aNickname, bool aBestEfforts = false );
/**
* Retrieves const pointers to cached footprints without cloning. This is much faster than
* GetFootprints() for read-only access (e.g., populating tree views with metadata).
*
* The returned pointers are owned by the library plugin cache and remain valid as long as
* the library is loaded. Callers must NOT delete or modify the returned footprints.
*
* @param aNickname is the library to query
* @param aBestEfforts if true, don't throw on errors, just return a smaller or empty list.
* @return const pointers to cached footprints
*/
std::vector<const FOOTPRINT*> GetCachedFootprints( const wxString& aNickname,
bool aBestEfforts = false );
/**
* Retrieves a list of footprint names contained in a given loaded library
@@ -191,6 +178,11 @@ private:
static std::map<wxString, LIB_DATA> GlobalLibraries;
static std::shared_mutex GlobalLibraryMutex;
/// Storage for preloaded footprints, indexed by library nickname.
/// These are cloned during library enumeration so GetFootprints() returns instantly.
static std::map<wxString, std::vector<std::unique_ptr<FOOTPRINT>>> PreloadedFootprints;
static std::shared_mutex PreloadedFootprintsMutex;
};
#endif //FOOTPRINT_LIBRARY_ADAPTER_H
+14 -18
View File
@@ -485,22 +485,16 @@ void FOOTPRINT_VIEWER_FRAME::ReCreateFootprintList()
if( !getCurNickname() )
setCurFootprintName( wxEmptyString );
auto fp_info_list = FOOTPRINT_LIST::GetInstance( Kiway() );
FOOTPRINT_LIBRARY_ADAPTER* adapter = PROJECT_PCB::FootprintLibAdapter( &Prj() );
wxString nickname = getCurNickname();
fp_info_list->ReadFootprintFiles( PROJECT_PCB::FootprintLibAdapter( &Prj() ), !nickname ? nullptr : &nickname );
if( !nickname )
return;
if( fp_info_list->GetErrorCount() )
{
if( KISTATUSBAR* statusBar = dynamic_cast<KISTATUSBAR*>( GetStatusBar() ) )
statusBar->SetLoadWarningMessages( fp_info_list->GetErrorMessages() );
std::vector<FOOTPRINT*> footprints = adapter->GetFootprints( nickname, true );
// For footprint libraries that support one footprint per file, there may have been
// valid footprints read so show the footprints that loaded properly.
if( fp_info_list->GetList().empty() )
return;
}
if( footprints.empty() )
return;
std::set<wxString> excludes;
@@ -513,24 +507,26 @@ void FOOTPRINT_VIEWER_FRAME::ReCreateFootprintList()
const wxString filterTerm = tokenizer.GetNextToken().Lower();
EDA_COMBINED_MATCHER matcher( filterTerm, CTX_LIBITEM );
for( const std::unique_ptr<FOOTPRINT_INFO>& footprint : fp_info_list->GetList() )
for( FOOTPRINT* footprint : footprints )
{
std::vector<SEARCH_TERM> searchTerms = footprint->GetSearchTerms();
int matched = matcher.ScoreTerms( searchTerms );
if( filterTerm.IsNumber() && wxAtoi( filterTerm ) == (int)footprint->GetPadCount() )
if( filterTerm.IsNumber() && wxAtoi( filterTerm ) == (int)footprint->GetPadCount( DO_NOT_INCLUDE_NPTH ) )
matched++;
if( !matched )
excludes.insert( footprint->GetFootprintName() );
excludes.insert( footprint->GetFPID().GetLibItemName() );
}
}
}
for( const std::unique_ptr<FOOTPRINT_INFO>& footprint : fp_info_list->GetList() )
for( FOOTPRINT* footprint : footprints )
{
if( !excludes.count( footprint->GetFootprintName() ) )
m_fpList->Append( footprint->GetFootprintName() );
wxString fpName = footprint->GetFPID().GetLibItemName();
if( !excludes.count( fpName ) )
m_fpList->Append( fpName );
}
int index = wxNOT_FOUND;
+3 -5
View File
@@ -60,14 +60,12 @@ void FP_TREE_MODEL_ADAPTER::AddLibraries( EDA_BASE_FRAME* aParent )
bool pinned = alg::contains( cfg->m_Session.pinned_fp_libs, libName )
|| alg::contains( project.m_PinnedFootprintLibs, libName );
// Use cached footprints (no cloning) for faster tree population. The const_cast is safe
// because DoAddLibrary only reads metadata from the footprints, it doesn't modify them.
std::vector<const FOOTPRINT*> footprints = m_libs->GetCachedFootprints( libName, true );
std::vector<FOOTPRINT*> footprints = m_libs->GetFootprints( libName, true );
std::vector<LIB_TREE_ITEM*> treeItems;
treeItems.reserve( footprints.size() );
for( const FOOTPRINT* fp : footprints )
treeItems.push_back( const_cast<FOOTPRINT*>( fp ) );
for( FOOTPRINT* fp : footprints )
treeItems.push_back( fp );
DoAddLibrary( libName, *m_libs->GetLibraryDescription( libName ), treeItems, pinned, true );
}
+9 -13
View File
@@ -120,13 +120,12 @@ void FP_TREE_SYNCHRONIZING_ADAPTER::Sync( FOOTPRINT_LIBRARY_ADAPTER* aLibs )
bool pinned = alg::contains( cfg->m_Session.pinned_fp_libs, libName )
|| alg::contains( project.m_PinnedFootprintLibs, libName );
// Use cached footprints (no cloning) for faster tree population
std::vector<const FOOTPRINT*> footprints = m_libs->GetCachedFootprints( libName, true );
std::vector<FOOTPRINT*> footprints = m_libs->GetFootprints( libName, true );
std::vector<LIB_TREE_ITEM*> treeItems;
treeItems.reserve( footprints.size() );
for( const FOOTPRINT* fp : footprints )
treeItems.push_back( const_cast<FOOTPRINT*>( fp ) );
for( FOOTPRINT* fp : footprints )
treeItems.push_back( fp );
DoAddLibrary( libName, *optDesc, treeItems, pinned, true );
@@ -148,13 +147,12 @@ int FP_TREE_SYNCHRONIZING_ADAPTER::GetLibrariesCount() const
void FP_TREE_SYNCHRONIZING_ADAPTER::updateLibrary( LIB_TREE_NODE_LIBRARY& aLibNode )
{
// Use cached footprints (no cloning) for faster updates
std::vector<const FOOTPRINT*> footprints = m_libs->GetCachedFootprints( aLibNode.m_Name, true );
std::vector<FOOTPRINT*> footprints = m_libs->GetFootprints( aLibNode.m_Name, true );
// Build a map of footprint names for quick lookup
std::map<wxString, const FOOTPRINT*> fpMap;
std::map<wxString, FOOTPRINT*> fpMap;
for( const FOOTPRINT* fp : footprints )
for( FOOTPRINT* fp : footprints )
fpMap[fp->GetFPID().GetLibItemName()] = fp;
// Remove items that no longer exist
@@ -164,9 +162,7 @@ void FP_TREE_SYNCHRONIZING_ADAPTER::updateLibrary( LIB_TREE_NODE_LIBRARY& aLibNo
if( fpIt != fpMap.end() )
{
// const_cast is safe because Update() only reads metadata from the footprint
static_cast<LIB_TREE_NODE_ITEM*>( nodeIt->get() )->Update(
const_cast<FOOTPRINT*>( fpIt->second ) );
static_cast<LIB_TREE_NODE_ITEM*>( nodeIt->get() )->Update( fpIt->second );
fpMap.erase( fpIt );
++nodeIt;
}
@@ -176,9 +172,9 @@ void FP_TREE_SYNCHRONIZING_ADAPTER::updateLibrary( LIB_TREE_NODE_LIBRARY& aLibNo
}
}
// Add new items (const_cast is safe because AddItem only reads metadata)
// Add new items
for( auto& [name, fp] : fpMap )
aLibNode.AddItem( const_cast<FOOTPRINT*>( fp ) );
aLibNode.AddItem( fp );
aLibNode.AssignIntrinsicRanks( m_shownColumns );
m_libMap.insert( aLibNode.m_Name );
+136 -10
View File
@@ -48,6 +48,8 @@
#include <footprint_wizard_frame.h>
#include <footprint_preview_panel.h>
#include <footprint_info_impl.h>
#include <footprint.h>
#include <nlohmann/json.hpp>
#include <dialogs/dialog_configure_paths.h>
#include <dialogs/panel_grid_settings.h>
#include <panel_display_options.h>
@@ -85,6 +87,133 @@
extern "C" PyObject* PyInit__pcbnew( void );
/**
* Filter footprints based on criteria passed as JSON.
*
* Input JSON format:
* {"pin_count": N, "filters": ["pattern1", ...], "zero_filters": bool, "max_results": N}
*
* Output JSON format:
* ["lib:footprint1", "lib:footprint2", ...]
*
* @param aFilterJson JSON string with filter parameters
* @return JSON string with array of matching footprint LIB_IDs
*/
static wxString filterFootprints( const wxString& aFilterJson )
{
using json = nlohmann::json;
try
{
json input = json::parse( aFilterJson.ToStdString() );
int pinCount = input.value( "pin_count", 0 );
bool zeroFilters = input.value( "zero_filters", true );
int maxResults = input.value( "max_results", 400 );
wxArrayString filters;
if( input.contains( "filters" ) && input["filters"].is_array() )
{
for( const auto& f : input["filters"] )
{
if( f.is_string() )
filters.Add( wxString::FromUTF8( f.get<std::string>() ) );
}
}
bool hasFilters = ( pinCount > 0 || !filters.IsEmpty() );
if( zeroFilters && !hasFilters )
return wxS( "[]" );
PROJECT* project = nullptr;
if( wxTheApp )
{
wxWindow* focus = wxWindow::FindFocus();
wxWindow* top = focus ? wxGetTopLevelParent( focus ) : wxTheApp->GetTopWindow();
if( top )
{
if( KIWAY_HOLDER* holder = dynamic_cast<KIWAY_HOLDER*>( top ) )
project = &holder->Prj();
}
}
if( !project )
project = &Pgm().GetSettingsManager().Prj();
FOOTPRINT_LIBRARY_ADAPTER* adapter = PROJECT_PCB::FootprintLibAdapter( project );
if( !adapter )
return wxS( "[]" );
adapter->AsyncLoad();
adapter->BlockUntilLoaded();
// Iterate through preloaded footprints directly instead of re-reading from disk
json output = json::array();
int count = 0;
for( const wxString& nickname : adapter->GetLibraryNames() )
{
std::vector<FOOTPRINT*> footprints = adapter->GetFootprints( nickname, true );
for( FOOTPRINT* fp : footprints )
{
if( !fp )
continue;
// Pin count filter
if( pinCount > 0 )
{
int fpPadCount = fp->GetUniquePadCount( DO_NOT_INCLUDE_NPTH );
if( fpPadCount != pinCount )
continue;
}
// Footprint filter patterns
if( !filters.IsEmpty() )
{
wxString fpName = fp->GetFPID().GetLibItemName();
bool matches = false;
for( const wxString& filter : filters )
{
if( fpName.Matches( filter ) )
{
matches = true;
break;
}
}
if( !matches )
continue;
}
wxString libId = fp->GetFPID().Format();
output.push_back( libId.ToStdString() );
if( ++count >= maxResults )
break;
}
if( count >= maxResults )
break;
}
return wxString::FromUTF8( output.dump() );
}
catch( const std::exception& e )
{
return wxS( "[]" );
}
}
namespace PCB {
static struct IFACE : public KIFACE_BASE, public UNITS_PROVIDER
@@ -367,10 +496,6 @@ static struct IFACE : public KIFACE_BASE, public UNITS_PROVIDER
{
switch( aDataId )
{
// Return a pointer to the global instance of the footprint list.
case KIFACE_FOOTPRINT_LIST:
return (void*) &GFootprintList;
case KIFACE_FOOTPRINT_LIBRARY_ADAPTER:
{
// This is the mechanism by which FOOTPRINT_SELECT_WIDGET can get access to the adapter
@@ -395,6 +520,13 @@ static struct IFACE : public KIFACE_BASE, public UNITS_PROVIDER
return PROJECT_PCB::FootprintLibAdapter( project );
}
case KIFACE_FILTER_FOOTPRINTS:
{
// Return function pointer for filtering footprints
// Signature: wxString (*)(const wxString& aFilterJson)
return reinterpret_cast<void*>( &filterFootprints );
}
case KIFACE_SCRIPTING_LEGACY:
return reinterpret_cast<void*>( PyInit__pcbnew );
@@ -446,12 +578,6 @@ KIFACE_API KIFACE* KIFACE_GETTER( int* aKIFACEversion, int aKiwayVersion, PGM_BA
}
/// The global footprint info table. This is performance-intensive to build so we
/// keep a hash-stamped global version. Any deviation from the request vs. stored
/// hash will result in it being rebuilt.
FOOTPRINT_LIST_IMPL GFootprintList;
bool IFACE::OnKifaceStart( PGM_BASE* aProgram, int aCtlBits, KIWAY* aKiway )
{
// This is process-level-initialization, not project-level-initialization of the DSO.