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
24#include <qa_utils/file_utils.h>
27
28#include <wx/dir.h>
29#include <wx/filefn.h>
30#include <wx/filename.h>
31#include <wx/stdpaths.h>
32
33#include <board.h>
34#include <footprint.h>
35#include <lib_id.h>
36#include <pgm_base.h>
37#include <project.h>
38#include <project_pcb.h>
39#include <reporter.h>
50#include <tool/tool_manager.h>
51
53
54
55namespace
56{
58wxString stageProject( const wxString& aStem )
59{
60 wxString sep = wxFileName::GetPathSeparator();
61 wxString dir = wxStandardPaths::Get().GetTempDir() + sep + aStem + wxT( "-fpreconcile-qa" );
62
63 if( wxDirExists( dir ) )
64 wxFileName::Rmdir( dir, wxPATH_RMDIR_RECURSIVE );
65
66 wxFileName::Mkdir( dir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
67
68 wxString projectPath = dir + sep + aStem + wxT( ".kicad_pro" );
69
70 Pgm().GetSettingsManager().LoadProject( projectPath );
72 Pgm().GetSettingsManager().Prj().GetProjectDirectory() );
73
75}
76
77
79struct IMPORTED_BOARD
80{
81 std::unique_ptr<PCB_IO_EASYEDAPRO_V3> m_plugin;
82 std::unique_ptr<BOARD> m_board;
83 std::vector<std::unique_ptr<FOOTPRINT>> m_definitions;
84 std::unique_ptr<KI_TEST::SCOPED_TEMP_DIR> m_sourceDir;
85};
86
87
88IMPORTED_BOARD importSampleBoard( PROJECT& aProject, const std::string& aTag )
89{
90 IMPORTED_BOARD sample;
91
92 const wxString archiveName = wxS( "ProProject_LS2K0300Core_2025-11-14.epro2" );
93
94 wxFileName srcFn( wxString::FromUTF8( KI_TEST::GetPcbnewTestDataDir() ) );
95 srcFn.AppendDir( wxS( "plugins" ) );
96 srcFn.AppendDir( wxS( "easyedapro" ) );
97 srcFn.SetFullName( archiveName );
98 BOOST_REQUIRE_MESSAGE( srcFn.FileExists(), "Missing EasyEDA Pro v3 board fixture" );
99
100 sample.m_sourceDir = std::make_unique<KI_TEST::SCOPED_TEMP_DIR>( wxString::FromUTF8( aTag ) );
101
102 wxFileName importFn( sample.m_sourceDir->PathStr(), 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( sample.m_sourceDir->PathStr(), wxEmptyString );
342 wxFileName strayLib( srcDir.GetPath(),
343 EASYEDAPRO::ShortenLibName( wxS( "ProProject_LS2K0300Core_2025-11-14" ) ),
345 BOOST_CHECK_MESSAGE( !wxDir::Exists( strayLib.GetFullPath() ),
346 "LoadBoard wrote a library into the source directory" );
347
349 BOOST_REQUIRE( adapter );
350
351 const wxString cacheNick = wxS( "ls2k0300-import-fps" );
352 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath() );
353
355 reconciler.Reconcile( board, std::move( sample.m_definitions ), cacheNick, {} );
356
357 // the cache lands in the project directory, not next to the source archive
358 BOOST_CHECK_EQUAL( result.m_cacheNickname, cacheNick );
359 BOOST_CHECK_GT( result.m_savedToCache, 0 );
360
361 wxFileName prettyDir( project.GetProjectPath(), cacheNick,
363 BOOST_CHECK_MESSAGE( wxDir::Exists( prettyDir.GetFullPath() ),
364 "Generated .pretty was not published into the project" );
365
366 LIBRARY_TABLE* projectTable = adapter->ProjectTable().value_or( nullptr );
367 BOOST_REQUIRE( projectTable );
368 BOOST_CHECK( projectTable->HasRow( cacheNick ) );
369
370 int resolved = 0;
371
372 for( FOOTPRINT* fp : board->Footprints() )
373 {
374 wxString name = fp->GetFPID().GetUniStringLibItemName();
375
376 if( name.IsEmpty() )
377 continue;
378
379 wxString nick = fp->GetFPID().GetUniStringLibNickname();
380
381 BOOST_CHECK_MESSAGE( adapter->FootprintExists( nick, name ),
382 wxString::Format( "FPID '%s:%s' does not resolve after reconciliation",
383 nick, name ) );
384 resolved++;
385 }
386
387 BOOST_CHECK_GT( resolved, 0 );
388 BOOST_CHECK_EQUAL( result.m_unresolved, 0 );
389}
390
391
392// An unrelated library that happens to carry the nickname the importer emitted must not swallow
393// the imported definition; without the provenance check the footprint relinks to the wrong part
394BOOST_AUTO_TEST_CASE( CollidingNicknameDoesNotStealTheLink )
395{
396 stageProject( wxS( "fpreconcile_collide" ) );
398
399 IMPORTED_BOARD sample = importSampleBoard( project, "fpreconcile_collide_src" );
400 COLLISION_CANDIDATE candidate = findCandidate( sample );
401 BOOST_REQUIRE( candidate.m_footprint );
402
404 BOOST_REQUIRE( adapter );
405
406 // a padless namesake registered under the importer's nickname: same name, different part
407 FOOTPRINT impostor( nullptr );
408 impostor.SetFPID( LIB_ID( candidate.m_nickname, candidate.m_name ) );
409 BOOST_REQUIRE( impostor.Pads().empty() );
410 publishLibrary( project, *adapter, candidate.m_nickname, wxS( "impostor" ), impostor );
411
412 const wxString cacheNick = wxS( "collide-import-fps" );
413 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath() );
414
416 sample.m_board.get(), std::move( sample.m_definitions ), cacheNick, {} );
417
418 BOOST_CHECK_EQUAL( result.m_cacheNickname, cacheNick );
419
420 // the imported definition is kept in the cache rather than dropped for the namesake
421 BOOST_CHECK_EQUAL( candidate.m_footprint->GetFPID().GetUniStringLibNickname(), cacheNick );
422
423 std::unique_ptr<FOOTPRINT> linked( adapter->LoadFootprint( cacheNick, candidate.m_name,
424 true ) );
425 BOOST_REQUIRE( linked );
426 BOOST_CHECK_GT( linked->Pads().size(), 0 );
427}
428
429
430// Two source libraries supplying different footprints under one bare name must both survive the
431// cache; keying the cache by the bare name alone dropped the second and relinked its instance
432BOOST_AUTO_TEST_CASE( SameNameFromDifferentLibrariesKeepsBothDefinitions )
433{
434 stageProject( wxS( "fpreconcile_namecollide" ) );
436
437 std::string dataPath =
438 KI_TEST::GetPcbnewTestDataDir() + "plugins/altium/HiFive/HiFive1.B01.PcbDoc";
439
441 std::unique_ptr<BOARD> source = std::make_unique<BOARD>();
442 source->SetProject( &project );
443 plugin.LoadAndAppendBoard( dataPath, *source, nullptr, &project );
444
445 // two real imported footprints that a pad count tells apart
446 FOOTPRINT* firstSource = nullptr;
447 FOOTPRINT* secondSource = nullptr;
448
449 for( FOOTPRINT* fp : source->Footprints() )
450 {
451 if( fp->Pads().empty() )
452 continue;
453
454 if( !firstSource )
455 firstSource = fp;
456 else if( fp->Pads().size() != firstSource->Pads().size() )
457 secondSource = fp;
458
459 if( secondSource )
460 break;
461 }
462
463 BOOST_REQUIRE( firstSource );
464 BOOST_REQUIRE( secondSource );
465
466 const wxString sharedName = wxS( "SHARED_FP" );
467 const size_t firstPads = firstSource->Pads().size();
468 const size_t secondPads = secondSource->Pads().size();
469
470 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
471 board->SetProject( &project );
472
473 std::vector<std::unique_ptr<FOOTPRINT>> defs;
474
475 // the same bare name under two source libraries, both placed and defined
476 auto place = [&]( const FOOTPRINT* aSource, const wxString& aNickname )
477 {
478 FOOTPRINT* placed = static_cast<FOOTPRINT*>( aSource->Clone() );
479 placed->SetFPID( LIB_ID( aNickname, sharedName ) );
480 board->Add( placed, ADD_MODE::APPEND );
481
482 std::unique_ptr<FOOTPRINT> def( static_cast<FOOTPRINT*>( aSource->Clone() ) );
483 def->SetFPID( LIB_ID( aNickname, sharedName ) );
484 defs.push_back( std::move( def ) );
485
486 return placed;
487 };
488
489 FOOTPRINT* firstPlaced = place( firstSource, wxS( "libAlpha" ) );
490 FOOTPRINT* secondPlaced = place( secondSource, wxS( "libBeta" ) );
491
493 BOOST_REQUIRE( adapter );
494
495 const wxString cacheNick = wxS( "namecollide-import-fps" );
497 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath(), reporter );
498
500 reconciler.Reconcile( board.get(), std::move( defs ), cacheNick, {} );
501
502 BOOST_CHECK_EQUAL( result.m_cacheNickname, cacheNick );
503 BOOST_CHECK_EQUAL( result.m_savedToCache, 2 );
504
505 LIB_ID firstId = firstPlaced->GetFPID();
506 LIB_ID secondId = secondPlaced->GetFPID();
507
508 BOOST_CHECK_EQUAL( firstId.GetUniStringLibNickname(), cacheNick );
509 BOOST_CHECK_EQUAL( secondId.GetUniStringLibNickname(), cacheNick );
510 BOOST_CHECK_MESSAGE( firstId.GetUniStringLibItemName() != secondId.GetUniStringLibItemName(),
511 "Footprints from two source libraries share one cache item name" );
512
513 // each instance still resolves to the footprint it was imported as
514 std::unique_ptr<FOOTPRINT> firstLinked(
515 adapter->LoadFootprint( cacheNick, firstId.GetUniStringLibItemName(), true ) );
516 std::unique_ptr<FOOTPRINT> secondLinked(
517 adapter->LoadFootprint( cacheNick, secondId.GetUniStringLibItemName(), true ) );
518
519 BOOST_REQUIRE( firstLinked );
520 BOOST_REQUIRE( secondLinked );
521 BOOST_CHECK_EQUAL( firstLinked->Pads().size(), firstPads );
522 BOOST_CHECK_EQUAL( secondLinked->Pads().size(), secondPads );
523
524 // the user is told which footprint the cache renamed
525 const wxString renamed = firstId.GetUniStringLibItemName() == sharedName
526 ? secondId.GetUniStringLibItemName()
527 : firstId.GetUniStringLibItemName();
528
529 BOOST_CHECK_MESSAGE( reporter.GetMessages().Contains(
530 wxString::Format( wxS( "renamed to '%s'" ), renamed ) ),
531 "Cache rename was not reported" );
532}
533
534
535// A project row already owning the cache nickname is a user library even when no .pretty sits at
536// the generated path, so publishing must not rewrite its URI
537BOOST_AUTO_TEST_CASE( ExistingUserRowIsNotRepurposed )
538{
539 stageProject( wxS( "fpreconcile_row" ) );
541
542 IMPORTED_BOARD sample = importSampleBoard( project, "fpreconcile_row_src" );
543
545 BOOST_REQUIRE( adapter );
546
547 const wxString cacheNick = wxS( "row-import-fps" );
548
549 // the user's row owns the nickname but points at a library of its own
550 FOOTPRINT owned( nullptr );
551 owned.SetFPID( LIB_ID( cacheNick, wxS( "UserPart" ) ) );
552 publishLibrary( project, *adapter, cacheNick, wxS( "user-owned" ), owned );
553
554 LIBRARY_TABLE* table = adapter->ProjectTable().value_or( nullptr );
556
557 LIBRARY_TABLE_ROW* row = table->Row( cacheNick ).value_or( nullptr );
558 BOOST_REQUIRE( row );
559
560 const wxString uriBefore = row->URI();
561
562 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath() );
563
565 sample.m_board.get(), std::move( sample.m_definitions ), cacheNick, {} );
566
567 BOOST_CHECK( result.m_cacheNickname.IsEmpty() );
568 BOOST_CHECK_EQUAL( row->URI(), uriBefore );
569
570 wxFileName prettyDir( project.GetProjectPath(), cacheNick,
572 BOOST_CHECK_MESSAGE( !wxDir::Exists( prettyDir.GetFullPath() ),
573 "Published a cache over a nickname the user already owns" );
574}
575
576
577// regression gate for a netlist of reconciled FPIDs applying via BOARD_NETLIST_UPDATER with 0 errors
578// and no not-found, over the same adapter path Update PCB from Schematic uses
579BOOST_AUTO_TEST_CASE( ReconciledFootprintsResolveViaNetlistUpdater )
580{
581 stageProject( wxS( "eagle_netlist_roundtrip" ) );
583
584 wxFileName brdFn( KI_TEST::GetEeschemaTestDataDir() );
585 brdFn.AppendDir( wxS( "io" ) );
586 brdFn.AppendDir( wxS( "eagle" ) );
587 brdFn.SetFullName( wxS( "eagle-import-testfile.brd" ) );
588 BOOST_REQUIRE( brdFn.FileExists() );
589
590 PCB_IO_EAGLE plugin;
591 std::unique_ptr<BOARD> imported = std::make_unique<BOARD>();
592 imported->SetProject( &project );
593 plugin.LoadAndAppendBoard( brdFn.GetFullPath(), *imported, nullptr, &project );
594
595 std::vector<FOOTPRINT*> raw = plugin.GetImportedCachedLibraryFootprints();
596 std::vector<std::unique_ptr<FOOTPRINT>> defs;
597
598 for( FOOTPRINT* fp : raw )
599 defs.emplace_back( fp );
600
602 BOOST_REQUIRE( adapter );
603
604 const wxString cacheNick = wxS( "eagle_test-import-fps" );
605 FOOTPRINT_IMPORT_RECONCILER reconciler( *adapter, project.GetProjectPath() );
606 reconciler.Reconcile( imported.get(), std::move( defs ), cacheNick, {} );
607
608 // netlist of distinct reconciled FPIDs, one fresh component each
610 std::set<wxString> seen;
611 int expectedComponents = 0;
612
613 for( FOOTPRINT* fp : imported->Footprints() )
614 {
615 LIB_ID fpid = fp->GetFPID();
616
617 if( fpid.GetUniStringLibItemName().IsEmpty() || !seen.insert( fpid.GetUniStringLibId() ).second )
618 continue;
619
620 wxString ref = wxString::Format( wxS( "U%d" ), ++expectedComponents );
621 netlist.AddComponent(
622 new COMPONENT( fpid, ref, ref, KIID_PATH(), std::vector<KIID>{ KIID() } ) );
623 }
624
625 BOOST_REQUIRE_GT( expectedComponents, 0 );
626
627 // updater must load each footprint from the reconciled lib onto a fresh board
628 std::unique_ptr<BOARD> target = std::make_unique<BOARD>();
629 target->SetProject( &project );
630
631 TOOL_MANAGER toolMgr;
632 toolMgr.SetEnvironment( target.get(), nullptr, nullptr, nullptr, nullptr );
633 toolMgr.RegisterTool( new KI_TEST::DUMMY_TOOL() );
634
636 BOARD_NETLIST_UPDATER updater( &toolMgr, target.get() );
637 updater.SetReporter( &reporter );
638 updater.SetReplaceFootprints( false );
639 updater.SetDeleteUnusedFootprints( false );
640
641 BOOST_REQUIRE( updater.UpdateNetlist( netlist ) );
642
643 BOOST_CHECK_EQUAL( updater.GetErrorCount(), 0 );
644 BOOST_CHECK_MESSAGE( !reporter.GetMessages().Lower().Contains( wxS( "not found" ) ),
645 "Netlist updater reported a footprint not found after reconciliation" );
646 BOOST_CHECK_EQUAL( static_cast<int>( target->Footprints().size() ), expectedComponents );
647}
648
649
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:474
EDA_ITEM * Clone() const override
Invoke a function on all children.
std::deque< PAD * > & Pads()
Definition footprint.h:404
const LIB_ID & GetFPID() const
Definition footprint.h:473
Definition kiid.h:46
wxString PathStr() const
Get the path to the temporary directory as a wxString.
Definition file_utils.h:62
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:727
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")