KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_footprint_import_reconciler.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <memory>
21#include <set>
22#include <vector>
23
26
27#include <wx/dir.h>
28#include <wx/filefn.h>
29#include <wx/filename.h>
30#include <wx/stdpaths.h>
31
32#include <board.h>
33#include <footprint.h>
34#include <lib_id.h>
35#include <pgm_base.h>
36#include <project.h>
37#include <project_pcb.h>
38#include <reporter.h>
49#include <tool/tool_manager.h>
50
52
53
54namespace
55{
57wxString stageProject( const wxString& aStem )
58{
59 wxString sep = wxFileName::GetPathSeparator();
60 wxString dir = wxStandardPaths::Get().GetTempDir() + sep + aStem + wxT( "-fpreconcile-qa" );
61
62 if( wxDirExists( dir ) )
63 wxFileName::Rmdir( dir, wxPATH_RMDIR_RECURSIVE );
64
65 wxFileName::Mkdir( dir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
66
67 wxString projectPath = dir + sep + aStem + wxT( ".kicad_pro" );
68
69 Pgm().GetSettingsManager().LoadProject( projectPath );
71 Pgm().GetSettingsManager().Prj().GetProjectDirectory() );
72
74}
75
76
78struct IMPORTED_BOARD
79{
80 std::unique_ptr<PCB_IO_EASYEDAPRO_V3> m_plugin;
81 std::unique_ptr<BOARD> m_board;
82 std::vector<std::unique_ptr<FOOTPRINT>> m_definitions;
83 std::unique_ptr<KI_TEST::TEMPORARY_DIRECTORY> m_sourceDir;
84};
85
86
87IMPORTED_BOARD importSampleBoard( PROJECT& aProject, const std::string& aTag )
88{
89 IMPORTED_BOARD sample;
90
91 const wxString archiveName = wxS( "ProProject_LS2K0300Core_2025-11-14.epro2" );
92
93 wxFileName srcFn( wxString::FromUTF8( KI_TEST::GetPcbnewTestDataDir() ) );
94 srcFn.AppendDir( wxS( "plugins" ) );
95 srcFn.AppendDir( wxS( "easyedapro" ) );
96 srcFn.SetFullName( archiveName );
97 BOOST_REQUIRE_MESSAGE( srcFn.FileExists(), "Missing EasyEDA Pro v3 board fixture" );
98
99 sample.m_sourceDir = std::make_unique<KI_TEST::TEMPORARY_DIRECTORY>( aTag, "" );
100
101 wxFileName importFn( wxString::FromUTF8( sample.m_sourceDir->GetPath().string() ),
102 archiveName );
103 BOOST_REQUIRE( wxCopyFile( srcFn.GetFullPath(), importFn.GetFullPath() ) );
104
105 std::map<std::string, UTF8> properties;
106 properties["pcb_id"] = "eb9fbfba682940f7a002816e66fbb3d7";
107
108 sample.m_plugin = std::make_unique<PCB_IO_EASYEDAPRO_V3>();
109 sample.m_board = std::make_unique<BOARD>();
110 sample.m_board->SetProject( &aProject );
111 sample.m_plugin->LoadAndAppendBoard( importFn.GetFullPath(), *sample.m_board, &properties, &aProject );
112
113 BOOST_REQUIRE_GT( sample.m_board->Footprints().size(), 0 );
114
115 for( FOOTPRINT* fp : sample.m_plugin->GetImportedCachedLibraryFootprints() )
116 sample.m_definitions.emplace_back( fp );
117
118 BOOST_REQUIRE_GT( sample.m_definitions.size(), 0 );
119
120 return sample;
121}
122
123
125void publishLibrary( PROJECT& aProject, FOOTPRINT_LIBRARY_ADAPTER& aAdapter,
126 const wxString& aNickname, const wxString& aDirStem,
127 const FOOTPRINT& aFootprint )
128{
129 wxFileName libDir( aProject.GetProjectPath(), aDirStem,
131 wxString libPath = libDir.GetFullPath();
132
134 BOOST_REQUIRE( pi );
135
136 pi->CreateLibrary( libPath );
137
138 std::unique_ptr<FOOTPRINT> copy( static_cast<FOOTPRINT*>( aFootprint.Clone() ) );
139 copy->SetFPID( LIB_ID( aNickname, aFootprint.GetFPID().GetUniStringLibItemName() ) );
140 copy->SetReference( wxS( "REF**" ) );
141 pi->FootprintSave( libPath, copy.get() );
142
143 LIBRARY_TABLE* table = aAdapter.ProjectTable().value_or( nullptr );
145
146 LIBRARY_TABLE_ROW& row = table->InsertRow();
147 row.SetNickname( aNickname );
148 row.SetURI( wxS( "${KIPRJMOD}/" ) + libDir.GetFullName() );
149 row.SetType( wxS( "KiCad" ) );
151
152 BOOST_REQUIRE( table->Save().has_value() );
153 aAdapter.LoadOne( aNickname );
154}
155
156
158struct COLLISION_CANDIDATE
159{
160 FOOTPRINT* m_footprint = nullptr;
161 wxString m_nickname;
162 wxString m_name;
163};
164
165
166COLLISION_CANDIDATE findCandidate( IMPORTED_BOARD& aSample )
167{
168 COLLISION_CANDIDATE candidate;
169
170 for( FOOTPRINT* fp : aSample.m_board->Footprints() )
171 {
172 wxString nick = fp->GetFPID().GetUniStringLibNickname();
173 wxString name = fp->GetFPID().GetUniStringLibItemName();
174
175 if( nick.IsEmpty() || name.IsEmpty() )
176 continue;
177
178 for( const std::unique_ptr<FOOTPRINT>& def : aSample.m_definitions )
179 {
180 if( def->GetFPID().GetUniStringLibItemName() != name || def->Pads().empty() )
181 continue;
182
183 candidate.m_footprint = fp;
184 candidate.m_nickname = nick;
185 candidate.m_name = name;
186
187 return candidate;
188 }
189 }
190
191 return candidate;
192}
193} // namespace
194
195
196BOOST_AUTO_TEST_SUITE( FootprintImportReconciler )
197
198
199// Eagle board reconciles to a generated cache: .pretty published, row added, every FPID resolves
200// fails on revert, since Eagle FPIDs keep empty nicknames
201BOOST_AUTO_TEST_CASE( EagleBoardResolvesToGeneratedCache )
202{
203 wxString projectPath = stageProject( wxS( "eagle_fpreconcile" ) );
205
206 wxFileName brdFn( KI_TEST::GetEeschemaTestDataDir() );
207 brdFn.AppendDir( wxS( "io" ) );
208 brdFn.AppendDir( wxS( "eagle" ) );
209 brdFn.SetFullName( wxS( "eagle-import-testfile.brd" ) );
210 BOOST_REQUIRE_MESSAGE( brdFn.FileExists(), "Missing Eagle board fixture" );
211
212 PCB_IO_EAGLE plugin;
213 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
214 board->SetProject( &project );
215 plugin.LoadAndAppendBoard( brdFn.GetFullPath(), *board, nullptr, &project );
216
217 BOOST_REQUIRE_GT( board->Footprints().size(), 0 );
218
219 std::vector<FOOTPRINT*> raw = plugin.GetImportedCachedLibraryFootprints();
220 std::vector<std::unique_ptr<FOOTPRINT>> defs;
221
222 for( FOOTPRINT* fp : raw )
223 defs.emplace_back( fp );
224
226 BOOST_REQUIRE( adapter );
227
228 const wxString cacheNick = wxS( "eagle_test-import-fps" );
229 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath() );
230
232 reconciler.Reconcile( board.get(), std::move( defs ), cacheNick, {} );
233
234 // cache library published
235 BOOST_CHECK_EQUAL( result.m_cacheNickname, cacheNick );
236 BOOST_CHECK_GT( result.m_savedToCache, 0 );
237
238 wxFileName prettyDir( project.GetProjectPath(), cacheNick,
240 BOOST_CHECK_MESSAGE( wxDir::Exists( prettyDir.GetFullPath() ),
241 "Generated .pretty was not published" );
242
243 // project fp-lib table gained the row
244 LIBRARY_TABLE* projectTable = adapter->ProjectTable().value_or( nullptr );
245 BOOST_REQUIRE( projectTable );
246 BOOST_CHECK( projectTable->HasRow( cacheNick ) );
247
248 // every board FPID resolves via the adapter
249 int resolved = 0;
250
251 for( FOOTPRINT* fp : board->Footprints() )
252 {
253 wxString name = fp->GetFPID().GetUniStringLibItemName();
254
255 if( name.IsEmpty() )
256 continue;
257
258 wxString nick = fp->GetFPID().GetUniStringLibNickname();
259
260 BOOST_CHECK_MESSAGE( !nick.IsEmpty(),
261 wxString::Format( "Board footprint '%s' left with empty nickname",
262 name ) );
263 BOOST_CHECK_MESSAGE( adapter->FootprintExists( nick, name ),
264 wxString::Format( "FPID '%s:%s' does not resolve after reconciliation",
265 nick, name ) );
266 resolved++;
267 }
268
269 BOOST_CHECK_GT( resolved, 0 );
270 BOOST_CHECK_EQUAL( result.m_unresolved, 0 );
271}
272
273
274// Altium footprints carry a source-.PcbLib nick not registered here, so all fall back to the cache
275// and resolve through the adapter
276BOOST_AUTO_TEST_CASE( AltiumBoardResolvesToGeneratedCache )
277{
278 stageProject( wxS( "altium_fpreconcile" ) );
280
281 std::string dataPath =
282 KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/HiFive/HiFive1.B01.PcbDoc";
283
285 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
286 board->SetProject( &project );
287 plugin.LoadAndAppendBoard( dataPath, *board, nullptr, &project );
288
289 BOOST_REQUIRE_GT( board->Footprints().size(), 0 );
290
291 std::vector<FOOTPRINT*> raw = plugin.GetImportedCachedLibraryFootprints();
292 std::vector<std::unique_ptr<FOOTPRINT>> defs;
293
294 for( FOOTPRINT* fp : raw )
295 defs.emplace_back( fp );
296
298 BOOST_REQUIRE( adapter );
299
300 const wxString cacheNick = wxS( "hifive-import-fps" );
301 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath() );
302
304 reconciler.Reconcile( board.get(), std::move( defs ), cacheNick, {} );
305
306 BOOST_CHECK_EQUAL( result.m_cacheNickname, cacheNick );
307 BOOST_CHECK_EQUAL( result.m_unresolved, 0 );
308
309 int resolved = 0;
310
311 for( FOOTPRINT* fp : board->Footprints() )
312 {
313 wxString name = fp->GetFPID().GetUniStringLibItemName();
314
315 if( name.IsEmpty() )
316 continue;
317
318 wxString nick = fp->GetFPID().GetUniStringLibNickname();
319
320 BOOST_CHECK_MESSAGE( adapter->FootprintExists( nick, name ),
321 wxString::Format( "FPID '%s:%s' does not resolve after reconciliation",
322 nick, name ) );
323 resolved++;
324 }
325
326 BOOST_CHECK_GT( resolved, 0 );
327}
328
329
330// EasyEDA Pro v3 hands definitions over the standard hook, leaving the source directory untouched
331// fails on revert, since LoadBoard then publishes its own .pretty beside the archive
332BOOST_AUTO_TEST_CASE( EasyEdaProV3BoardResolvesToGeneratedCache )
333{
334 stageProject( wxS( "easyedapro_v3_fpreconcile" ) );
336
337 IMPORTED_BOARD sample = importSampleBoard( project, "easyedapro_v3_fpreconcile_src" );
338 BOARD* board = sample.m_board.get();
339
340 // the importer must not have published anything of its own beside the archive
341 wxFileName srcDir( wxString::FromUTF8( sample.m_sourceDir->GetPath().string() ),
342 wxEmptyString );
343 wxFileName strayLib( srcDir.GetPath(),
344 EASYEDAPRO::ShortenLibName( wxS( "ProProject_LS2K0300Core_2025-11-14" ) ),
346 BOOST_CHECK_MESSAGE( !wxDir::Exists( strayLib.GetFullPath() ),
347 "LoadBoard wrote a library into the source directory" );
348
350 BOOST_REQUIRE( adapter );
351
352 const wxString cacheNick = wxS( "ls2k0300-import-fps" );
353 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath() );
354
356 reconciler.Reconcile( board, std::move( sample.m_definitions ), cacheNick, {} );
357
358 // the cache lands in the project directory, not next to the source archive
359 BOOST_CHECK_EQUAL( result.m_cacheNickname, cacheNick );
360 BOOST_CHECK_GT( result.m_savedToCache, 0 );
361
362 wxFileName prettyDir( project.GetProjectPath(), cacheNick,
364 BOOST_CHECK_MESSAGE( wxDir::Exists( prettyDir.GetFullPath() ),
365 "Generated .pretty was not published into the project" );
366
367 LIBRARY_TABLE* projectTable = adapter->ProjectTable().value_or( nullptr );
368 BOOST_REQUIRE( projectTable );
369 BOOST_CHECK( projectTable->HasRow( cacheNick ) );
370
371 int resolved = 0;
372
373 for( FOOTPRINT* fp : board->Footprints() )
374 {
375 wxString name = fp->GetFPID().GetUniStringLibItemName();
376
377 if( name.IsEmpty() )
378 continue;
379
380 wxString nick = fp->GetFPID().GetUniStringLibNickname();
381
382 BOOST_CHECK_MESSAGE( adapter->FootprintExists( nick, name ),
383 wxString::Format( "FPID '%s:%s' does not resolve after reconciliation",
384 nick, name ) );
385 resolved++;
386 }
387
388 BOOST_CHECK_GT( resolved, 0 );
389 BOOST_CHECK_EQUAL( result.m_unresolved, 0 );
390}
391
392
393// An unrelated library that happens to carry the nickname the importer emitted must not swallow
394// the imported definition; without the provenance check the footprint relinks to the wrong part
395BOOST_AUTO_TEST_CASE( CollidingNicknameDoesNotStealTheLink )
396{
397 stageProject( wxS( "fpreconcile_collide" ) );
399
400 IMPORTED_BOARD sample = importSampleBoard( project, "fpreconcile_collide_src" );
401 COLLISION_CANDIDATE candidate = findCandidate( sample );
402 BOOST_REQUIRE( candidate.m_footprint );
403
405 BOOST_REQUIRE( adapter );
406
407 // a padless namesake registered under the importer's nickname: same name, different part
408 FOOTPRINT impostor( nullptr );
409 impostor.SetFPID( LIB_ID( candidate.m_nickname, candidate.m_name ) );
410 BOOST_REQUIRE( impostor.Pads().empty() );
411 publishLibrary( project, *adapter, candidate.m_nickname, wxS( "impostor" ), impostor );
412
413 const wxString cacheNick = wxS( "collide-import-fps" );
414 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath() );
415
417 sample.m_board.get(), std::move( sample.m_definitions ), cacheNick, {} );
418
419 BOOST_CHECK_EQUAL( result.m_cacheNickname, cacheNick );
420
421 // the imported definition is kept in the cache rather than dropped for the namesake
422 BOOST_CHECK_EQUAL( candidate.m_footprint->GetFPID().GetUniStringLibNickname(), cacheNick );
423
424 std::unique_ptr<FOOTPRINT> linked( adapter->LoadFootprint( cacheNick, candidate.m_name,
425 true ) );
426 BOOST_REQUIRE( linked );
427 BOOST_CHECK_GT( linked->Pads().size(), 0 );
428}
429
430
431// Two source libraries supplying different footprints under one bare name must both survive the
432// cache; keying the cache by the bare name alone dropped the second and relinked its instance
433BOOST_AUTO_TEST_CASE( SameNameFromDifferentLibrariesKeepsBothDefinitions )
434{
435 stageProject( wxS( "fpreconcile_namecollide" ) );
437
438 std::string dataPath =
439 KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/HiFive/HiFive1.B01.PcbDoc";
440
442 std::unique_ptr<BOARD> source = std::make_unique<BOARD>();
443 source->SetProject( &project );
444 plugin.LoadAndAppendBoard( dataPath, *source, nullptr, &project );
445
446 // two real imported footprints that a pad count tells apart
447 FOOTPRINT* firstSource = nullptr;
448 FOOTPRINT* secondSource = nullptr;
449
450 for( FOOTPRINT* fp : source->Footprints() )
451 {
452 if( fp->Pads().empty() )
453 continue;
454
455 if( !firstSource )
456 firstSource = fp;
457 else if( fp->Pads().size() != firstSource->Pads().size() )
458 secondSource = fp;
459
460 if( secondSource )
461 break;
462 }
463
464 BOOST_REQUIRE( firstSource );
465 BOOST_REQUIRE( secondSource );
466
467 const wxString sharedName = wxS( "SHARED_FP" );
468 const size_t firstPads = firstSource->Pads().size();
469 const size_t secondPads = secondSource->Pads().size();
470
471 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
472 board->SetProject( &project );
473
474 std::vector<std::unique_ptr<FOOTPRINT>> defs;
475
476 // the same bare name under two source libraries, both placed and defined
477 auto place = [&]( const FOOTPRINT* aSource, const wxString& aNickname )
478 {
479 FOOTPRINT* placed = static_cast<FOOTPRINT*>( aSource->Clone() );
480 placed->SetFPID( LIB_ID( aNickname, sharedName ) );
481 board->Add( placed, ADD_MODE::APPEND );
482
483 std::unique_ptr<FOOTPRINT> def( static_cast<FOOTPRINT*>( aSource->Clone() ) );
484 def->SetFPID( LIB_ID( aNickname, sharedName ) );
485 defs.push_back( std::move( def ) );
486
487 return placed;
488 };
489
490 FOOTPRINT* firstPlaced = place( firstSource, wxS( "libAlpha" ) );
491 FOOTPRINT* secondPlaced = place( secondSource, wxS( "libBeta" ) );
492
494 BOOST_REQUIRE( adapter );
495
496 const wxString cacheNick = wxS( "namecollide-import-fps" );
498 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath(), reporter );
499
501 reconciler.Reconcile( board.get(), std::move( defs ), cacheNick, {} );
502
503 BOOST_CHECK_EQUAL( result.m_cacheNickname, cacheNick );
504 BOOST_CHECK_EQUAL( result.m_savedToCache, 2 );
505
506 LIB_ID firstId = firstPlaced->GetFPID();
507 LIB_ID secondId = secondPlaced->GetFPID();
508
509 BOOST_CHECK_EQUAL( firstId.GetUniStringLibNickname(), cacheNick );
510 BOOST_CHECK_EQUAL( secondId.GetUniStringLibNickname(), cacheNick );
511 BOOST_CHECK_MESSAGE( firstId.GetUniStringLibItemName() != secondId.GetUniStringLibItemName(),
512 "Footprints from two source libraries share one cache item name" );
513
514 // each instance still resolves to the footprint it was imported as
515 std::unique_ptr<FOOTPRINT> firstLinked(
516 adapter->LoadFootprint( cacheNick, firstId.GetUniStringLibItemName(), true ) );
517 std::unique_ptr<FOOTPRINT> secondLinked(
518 adapter->LoadFootprint( cacheNick, secondId.GetUniStringLibItemName(), true ) );
519
520 BOOST_REQUIRE( firstLinked );
521 BOOST_REQUIRE( secondLinked );
522 BOOST_CHECK_EQUAL( firstLinked->Pads().size(), firstPads );
523 BOOST_CHECK_EQUAL( secondLinked->Pads().size(), secondPads );
524
525 // the user is told which footprint the cache renamed
526 const wxString renamed = firstId.GetUniStringLibItemName() == sharedName
527 ? secondId.GetUniStringLibItemName()
528 : firstId.GetUniStringLibItemName();
529
530 BOOST_CHECK_MESSAGE( reporter.GetMessages().Contains(
531 wxString::Format( wxS( "renamed to '%s'" ), renamed ) ),
532 "Cache rename was not reported" );
533}
534
535
536// A project row already owning the cache nickname is a user library even when no .pretty sits at
537// the generated path, so publishing must not rewrite its URI
538BOOST_AUTO_TEST_CASE( ExistingUserRowIsNotRepurposed )
539{
540 stageProject( wxS( "fpreconcile_row" ) );
542
543 IMPORTED_BOARD sample = importSampleBoard( project, "fpreconcile_row_src" );
544
546 BOOST_REQUIRE( adapter );
547
548 const wxString cacheNick = wxS( "row-import-fps" );
549
550 // the user's row owns the nickname but points at a library of its own
551 FOOTPRINT owned( nullptr );
552 owned.SetFPID( LIB_ID( cacheNick, wxS( "UserPart" ) ) );
553 publishLibrary( project, *adapter, cacheNick, wxS( "user-owned" ), owned );
554
555 LIBRARY_TABLE* table = adapter->ProjectTable().value_or( nullptr );
557
558 LIBRARY_TABLE_ROW* row = table->Row( cacheNick ).value_or( nullptr );
559 BOOST_REQUIRE( row );
560
561 const wxString uriBefore = row->URI();
562
563 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath() );
564
566 sample.m_board.get(), std::move( sample.m_definitions ), cacheNick, {} );
567
568 BOOST_CHECK( result.m_cacheNickname.IsEmpty() );
569 BOOST_CHECK_EQUAL( row->URI(), uriBefore );
570
571 wxFileName prettyDir( project.GetProjectPath(), cacheNick,
573 BOOST_CHECK_MESSAGE( !wxDir::Exists( prettyDir.GetFullPath() ),
574 "Published a cache over a nickname the user already owns" );
575}
576
577
578// regression gate for a netlist of reconciled FPIDs applying via BOARD_NETLIST_UPDATER with 0 errors
579// and no not-found, over the same adapter path Update PCB from Schematic uses
580BOOST_AUTO_TEST_CASE( ReconciledFootprintsResolveViaNetlistUpdater )
581{
582 stageProject( wxS( "eagle_netlist_roundtrip" ) );
584
585 wxFileName brdFn( KI_TEST::GetEeschemaTestDataDir() );
586 brdFn.AppendDir( wxS( "io" ) );
587 brdFn.AppendDir( wxS( "eagle" ) );
588 brdFn.SetFullName( wxS( "eagle-import-testfile.brd" ) );
589 BOOST_REQUIRE( brdFn.FileExists() );
590
591 PCB_IO_EAGLE plugin;
592 std::unique_ptr<BOARD> imported = std::make_unique<BOARD>();
593 imported->SetProject( &project );
594 plugin.LoadAndAppendBoard( brdFn.GetFullPath(), *imported, nullptr, &project );
595
596 std::vector<FOOTPRINT*> raw = plugin.GetImportedCachedLibraryFootprints();
597 std::vector<std::unique_ptr<FOOTPRINT>> defs;
598
599 for( FOOTPRINT* fp : raw )
600 defs.emplace_back( fp );
601
603 BOOST_REQUIRE( adapter );
604
605 const wxString cacheNick = wxS( "eagle_test-import-fps" );
606 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath() );
607 reconciler.Reconcile( imported.get(), std::move( defs ), cacheNick, {} );
608
609 // netlist of distinct reconciled FPIDs, one fresh component each
611 std::set<wxString> seen;
612 int expectedComponents = 0;
613
614 for( FOOTPRINT* fp : imported->Footprints() )
615 {
616 LIB_ID fpid = fp->GetFPID();
617
618 if( fpid.GetUniStringLibItemName().IsEmpty() || !seen.insert( fpid.GetUniStringLibId() ).second )
619 continue;
620
621 wxString ref = wxString::Format( wxS( "U%d" ), ++expectedComponents );
622 netlist.AddComponent(
623 new COMPONENT( fpid, ref, ref, KIID_PATH(), std::vector<KIID>{ KIID() } ) );
624 }
625
626 BOOST_REQUIRE_GT( expectedComponents, 0 );
627
628 // updater must load each footprint from the reconciled lib onto a fresh board
629 std::unique_ptr<BOARD> target = std::make_unique<BOARD>();
630 target->SetProject( &project );
631
632 TOOL_MANAGER toolMgr;
633 toolMgr.SetEnvironment( target.get(), nullptr, nullptr, nullptr, nullptr );
634 toolMgr.RegisterTool( new KI_TEST::DUMMY_TOOL() );
635
637 BOARD_NETLIST_UPDATER updater( &toolMgr, target.get() );
638 updater.SetReporter( &reporter );
639 updater.SetReplaceFootprints( false );
640 updater.SetDeleteUnusedFootprints( false );
641
642 BOOST_REQUIRE( updater.UpdateNetlist( netlist ) );
643
644 BOOST_CHECK_EQUAL( updater.GetErrorCount(), 0 );
645 BOOST_CHECK_MESSAGE( !reporter.GetMessages().Lower().Contains( wxS( "not found" ) ),
646 "Netlist updater reported a footprint not found after reconciliation" );
647 BOOST_CHECK_EQUAL( static_cast<int>( target->Footprints().size() ), expectedComponents );
648}
649
650
const char * name
Update the BOARD with a new netlist.
void SetReporter(REPORTER *aReporter)
Enable dry run mode (just report, no changes to PCB).
bool UpdateNetlist(NETLIST &aNetlist)
Update the board's components according to the new netlist.
void SetDeleteUnusedFootprints(bool aEnabled)
void SetReplaceFootprints(bool aEnabled)
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:409
const FOOTPRINTS & Footprints() const
Definition board.h:463
void SetProject(PROJECT *aProject, bool aReferenceOnly=false)
Link a board to a given project.
Definition board.cpp:374
Frame-independent, non-interactive service that reconciles the footprint-library references of a fres...
FOOTPRINT_IMPORT_RECONCILE_RESULT Reconcile(BOARD *aBoard, std::vector< std::unique_ptr< FOOTPRINT > > aDefinitions, const wxString &aCacheNickname, const std::vector< wxString > &aSourceLibNicknames)
Reconcile aBoard against the importer definitions and the provenance source libraries.
An interface to the global shared library manager that is schematic-specific and linked to one projec...
FOOTPRINT * LoadFootprint(const wxString &aNickname, const wxString &aName, bool aKeepUUID)
Load a FOOTPRINT having aName from the library given by aNickname.
std::optional< LIB_STATUS > LoadOne(LIB_DATA *aLib) override
Loads or reloads the given library, if it exists.
bool FootprintExists(const wxString &aNickname, const wxString &aName)
void SetFPID(const LIB_ID &aFPID)
Definition footprint.h:466
EDA_ITEM * Clone() const override
Invoke a function on all children.
std::deque< PAD * > & Pads()
Definition footprint.h:396
const LIB_ID & GetFPID() const
Definition footprint.h:465
Definition kiid.h:46
const std::filesystem::path & GetPath() const
std::optional< LIBRARY_TABLE * > ProjectTable() const
Retrieves the project library table for this adapter type, or nullopt if one doesn't exist.
void LoadProjectTables(std::initializer_list< LIBRARY_TABLE_TYPE > aTablesToLoad={})
(Re)loads the project library tables in the given list, or all tables if no list is given
void SetNickname(const wxString &aNickname)
void SetType(const wxString &aType)
void SetURI(const wxString &aUri)
void SetScope(LIBRARY_TABLE_SCOPE aScope)
const wxString & URI() const
bool HasRow(const wxString &aNickname) const
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
wxString GetUniStringLibId() const
Definition lib_id.h:144
const wxString GetUniStringLibItemName() const
Get strings for display messages in dialogs.
Definition lib_id.h:108
const wxString GetUniStringLibNickname() const
Definition lib_id.h:84
Store information read from a netlist along with the flags used to update the NETLIST in the BOARD.
std::vector< FOOTPRINT * > GetImportedCachedLibraryFootprints() override
Return a container with the cached library footprints generated in the last call to Load.
Works with Eagle 6.x XML board files and footprints to implement the Pcbnew #PLUGIN API or a portion ...
std::vector< FOOTPRINT * > GetImportedCachedLibraryFootprints() override
Return a container with the cached library footprints generated in the last call to Load.
@ KICAD_SEXP
S-expression Pcbnew file format.
Definition pcb_io_mgr.h:54
static PCB_IO * FindPlugin(PCB_FILE_T aFileType)
Return a #PLUGIN which the caller can use to import, export, save, or load design documents.
void LoadAndAppendBoard(const wxString &aFileName, BOARD &aAppendToMe, const std::map< std::string, UTF8 > *aProperties=nullptr, PROJECT *aProject=nullptr)
Same as LoadBoard(), but appends the loaded board to an existing board, which must already exist.
Definition pcb_io.cpp:85
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:123
virtual LIBRARY_MANAGER & GetLibraryManager() const
Definition pgm_base.h:125
static FOOTPRINT_LIBRARY_ADAPTER * FootprintLibAdapter(PROJECT *aProject)
Container for project specific data.
Definition project.h:63
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition project.cpp:183
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
Master controller class:
void RegisterTool(TOOL_BASE *aTool)
Add a tool to the manager set and sets it up.
void SetEnvironment(EDA_ITEM *aModel, KIGFX::VIEW *aView, KIGFX::VIEW_CONTROLS *aViewControls, APP_SETTINGS_BASE *aSettings, TOOLS_HOLDER *aFrame)
Set the work environment (model, view, view controls and the parent window).
A wrapper for reporting to a wxString object.
Definition reporter.h:242
static const std::string KiCadFootprintLibPathExtension
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
Definition io_mgr.h:33
PROJECT & Prj()
Definition kicad.cpp:724
wxString ShortenLibName(wxString aProjectName)
std::string GetPcbnewTestDataDir()
Utility which returns a path to the data directory where the test board files are stored.
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
Outcome of a post-import footprint-library reconciliation pass.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_CASE(EagleBoardResolvesToGeneratedCache)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
std::string netlist
IbisParser parser & reporter
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")