30#include <wx/filename.h>
31#include <wx/stdpaths.h>
64wxString stageProject(
const std::filesystem::path& aProjectRoot,
const wxString& aStem )
66 wxString sep = wxFileName::GetPathSeparator();
67 wxString projectPath = wxString::FromUTF8( aProjectRoot.string() ) + sep + aStem + wxT(
".kicad_pro" );
71 Pgm().GetSettingsManager().
Prj().GetProjectDirectory() );
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::SCOPED_TEMP_DIR> m_sourceDir;
87IMPORTED_BOARD importSampleBoard(
PROJECT& aProject,
const std::string& aTag )
89 IMPORTED_BOARD sample;
91 const wxString archiveName = wxS(
"ProProject_LS2K0300Core_2025-11-14.epro2" );
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" );
99 sample.m_sourceDir = std::make_unique<KI_TEST::SCOPED_TEMP_DIR>( wxString::FromUTF8( aTag ) );
101 wxFileName importFn( sample.m_sourceDir->
PathStr(), archiveName );
102 BOOST_REQUIRE( wxCopyFile( srcFn.GetFullPath(), importFn.GetFullPath() ) );
104 std::map<std::string, UTF8> properties;
105 properties[
"pcb_id"] =
"eb9fbfba682940f7a002816e66fbb3d7";
107 sample.m_plugin = std::make_unique<PCB_IO_EASYEDAPRO_V3>();
108 sample.m_board = std::make_unique<BOARD>();
110 sample.m_plugin->LoadAndAppendBoard( importFn.GetFullPath(), *sample.m_board, &properties, &aProject );
112 BOOST_REQUIRE_GT( sample.m_board->
Footprints().size(), 0 );
114 for(
FOOTPRINT* fp : sample.m_plugin->GetImportedCachedLibraryFootprints() )
115 sample.m_definitions.emplace_back( fp );
117 BOOST_REQUIRE_GT( sample.m_definitions.size(), 0 );
125 const wxString& aNickname,
const wxString& aDirStem,
130 wxString libPath = libDir.GetFullPath();
135 pi->CreateLibrary( libPath );
139 copy->SetReference( wxS(
"REF**" ) );
140 pi->FootprintSave( libPath,
copy.get() );
147 row.
SetURI( wxS(
"${KIPRJMOD}/" ) + libDir.GetFullName() );
157struct COLLISION_CANDIDATE
165COLLISION_CANDIDATE findCandidate( IMPORTED_BOARD& aSample )
167 COLLISION_CANDIDATE candidate;
171 wxString nick = fp->GetFPID().GetUniStringLibNickname();
172 wxString
name = fp->GetFPID().GetUniStringLibItemName();
174 if( nick.IsEmpty() ||
name.IsEmpty() )
177 for(
const std::unique_ptr<FOOTPRINT>& def : aSample.m_definitions )
179 if( def->GetFPID().GetUniStringLibItemName() !=
name || def->Pads().empty() )
182 candidate.m_footprint = fp;
183 candidate.m_nickname = nick;
184 candidate.m_name =
name;
203 wxString projectPath = stageProject( projectDir.
Path(), wxS(
"eagle_fpreconcile" ) );
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" );
213 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
217 BOOST_REQUIRE_GT( board->Footprints().size(), 0 );
220 std::vector<std::unique_ptr<FOOTPRINT>> defs;
223 defs.emplace_back( fp );
228 const wxString cacheNick = wxS(
"eagle_test-import-fps" );
232 reconciler.
Reconcile( board.get(), std::move( defs ), cacheNick, {} );
236 BOOST_CHECK_GT(
result.m_savedToCache, 0 );
238 wxFileName prettyDir(
project.GetProjectPath(), cacheNick,
240 BOOST_CHECK_MESSAGE( wxDir::Exists( prettyDir.GetFullPath() ),
241 "Generated .pretty was not published" );
246 BOOST_CHECK( projectTable->
HasRow( cacheNick ) );
251 for(
FOOTPRINT* fp : board->Footprints() )
253 wxString
name = fp->GetFPID().GetUniStringLibItemName();
258 wxString nick = fp->GetFPID().GetUniStringLibNickname();
260 BOOST_CHECK_MESSAGE( !nick.IsEmpty(),
261 wxString::Format(
"Board footprint '%s' left with empty nickname",
264 wxString::Format(
"FPID '%s:%s' does not resolve after reconciliation",
269 BOOST_CHECK_GT( resolved, 0 );
279 stageProject( projectDir.
Path(), wxS(
"altium_fpreconcile" ) );
282 std::string dataPath =
286 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
290 BOOST_REQUIRE_GT( board->Footprints().size(), 0 );
293 std::vector<std::unique_ptr<FOOTPRINT>> defs;
296 defs.emplace_back( fp );
301 const wxString cacheNick = wxS(
"hifive-import-fps" );
305 reconciler.
Reconcile( board.get(), std::move( defs ), cacheNick, {} );
312 for(
FOOTPRINT* fp : board->Footprints() )
314 wxString
name = fp->GetFPID().GetUniStringLibItemName();
319 wxString nick = fp->GetFPID().GetUniStringLibNickname();
322 wxString::Format(
"FPID '%s:%s' does not resolve after reconciliation",
327 BOOST_CHECK_GT( resolved, 0 );
336 stageProject( projectDir.
Path(), wxS(
"altium_fpparity" ) );
340 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
343 +
"plugins/altium/eDP_adapter_dvt1_source/eDP_adapter_dvt1.PcbDoc",
346 std::vector<std::unique_ptr<FOOTPRINT>> defs;
349 defs.emplace_back( fp );
355 reconciler.
Reconcile( board.get(), std::move( defs ), wxS(
"parity-fps" ), {} );
360 for(
FOOTPRINT* fp : board->Footprints() )
362 std::unique_ptr<FOOTPRINT> cached( adapter->
LoadFootprint( fp->GetFPID().GetUniStringLibNickname(),
363 fp->GetFPID().GetUniStringLibItemName(),
true ) );
375 BOOST_CHECK_GT( checked, 0 );
386 stageProject( projectDir.
Path(), wxS(
"allegro_fpparity" ) );
391 +
"plugins/allegro/boards/ProiectBoard/ProiectBoard.brd",
399 reconciler.
Reconcile( board.get(), {}, wxS(
"parity-fps" ), {} );
401 BOOST_REQUIRE_GT( board->Footprints().size(), 0 );
405 for(
FOOTPRINT* fp : board->Footprints() )
407 std::unique_ptr<FOOTPRINT> cached( adapter->
LoadFootprint( fp->GetFPID().GetUniStringLibNickname(),
408 fp->GetFPID().GetUniStringLibItemName(),
true ) );
426 stageProject( projectDir.
Path(), wxS(
"altium_fpreconcile_missing" ) );
429 std::string dataPath =
433 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
437 BOOST_REQUIRE_GT( board->Footprints().size(), 0 );
439 std::vector<std::unique_ptr<FOOTPRINT>> defs;
442 defs.emplace_back( fp );
448 std::optional<LIBRARY_TABLE*>
table =
459 const wxString cacheNick = wxS(
"hifive-missing-import-fps" );
464 BOOST_CHECK_NO_THROW(
result = reconciler.
Reconcile( board.get(), std::move( defs ), cacheNick,
465 { wxS(
"other_source" ) } ) );
477 stageProject( projectDir.
Path(), wxS(
"backside_fpparity" ) );
490 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
493 +
"plugins/altium/eDP_adapter_dvt1_source/eDP_adapter_dvt1.PcbDoc",
496 std::vector<std::unique_ptr<FOOTPRINT>> defs;
499 defs.emplace_back( fp );
503 for(
FOOTPRINT* fp : board->Footprints() )
505 if( fp->GetLayer() ==
F_Cu && fp->Pads().size() > 2 )
512 BOOST_REQUIRE_GT( flipped, 0 );
518 reconciler.
Reconcile( board.get(), std::move( defs ), wxS(
"parity-fps" ), {} );
522 wxString boardPath =
project.GetProjectPath() + wxS(
"backside.kicad_pcb" );
524 std::unique_ptr<BOARD> reloaded( kicad.
LoadBoard( boardPath,
nullptr, &
project ) );
530 for(
FOOTPRINT* fp : reloaded->Footprints() )
532 std::unique_ptr<FOOTPRINT> cached( adapter->
LoadFootprint( fp->GetFPID().GetUniStringLibNickname(),
533 fp->GetFPID().GetUniStringLibItemName(),
true ) );
545 BOOST_CHECK_GT( checked, 0 );
556 stageProject( projectDir.
Path(), wxS(
"backside_custompad" ) );
572 footprint->
SetFPID(
LIB_ID( wxS(
"" ), wxS(
"AsymmetricPad" ) ) );
573 board.
Add( footprint );
576 pad->SetNumber( wxS(
"1" ) );
582 pad->AddPrimitivePoly(
F_Cu, std::vector<VECTOR2I>{ { 0, 0 }, { 900000, 0 }, { 900000, 300000 }, { 0, 300000 } },
592 publishLibrary(
project, *adapter, wxS(
"backside-custom" ), wxS(
"backside-custom" ), *footprint );
593 footprint->
SetFPID(
LIB_ID( wxS(
"backside-custom" ), wxS(
"AsymmetricPad" ) ) );
595 std::unique_ptr<FOOTPRINT> cached( adapter->
LoadFootprint( wxS(
"backside-custom" ), wxS(
"AsymmetricPad" ),
true ) );
607 stageProject( projectDir.
Path(), wxS(
"cached_custompadstack" ) );
614 footprint->
SetFPID(
LIB_ID( wxS(
"" ), wxS(
"CustomStack" ) ) );
615 board.
Add( footprint );
618 pad->SetNumber( wxS(
"1" ) );
629 publishLibrary(
project, *adapter, wxS(
"custom-stack" ), wxS(
"custom-stack" ), *footprint );
631 std::unique_ptr<FOOTPRINT> cached( adapter->
LoadFootprint( wxS(
"custom-stack" ), wxS(
"CustomStack" ),
true ) );
633 BOOST_REQUIRE_EQUAL( cached->Pads().size(), 1 );
635 BOOST_CHECK_LE( cached->Pads().front()->Padstack().UniqueLayers().size(), 2 );
644 stageProject( projectDir.
Path(), wxS(
"easyedapro_v3_fpreconcile" ) );
647 IMPORTED_BOARD sample = importSampleBoard(
project,
"easyedapro_v3_fpreconcile_src" );
648 BOARD* board = sample.m_board.get();
651 wxFileName srcDir( sample.m_sourceDir->
PathStr(), wxEmptyString );
652 wxFileName strayLib( srcDir.GetPath(),
655 BOOST_CHECK_MESSAGE( !wxDir::Exists( strayLib.GetFullPath() ),
656 "LoadBoard wrote a library into the source directory" );
661 const wxString cacheNick = wxS(
"ls2k0300-import-fps" );
665 reconciler.
Reconcile( board, std::move( sample.m_definitions ), cacheNick, {} );
669 BOOST_CHECK_GT(
result.m_savedToCache, 0 );
671 wxFileName prettyDir(
project.GetProjectPath(), cacheNick,
673 BOOST_CHECK_MESSAGE( wxDir::Exists( prettyDir.GetFullPath() ),
674 "Generated .pretty was not published into the project" );
678 BOOST_CHECK( projectTable->
HasRow( cacheNick ) );
684 wxString
name = fp->GetFPID().GetUniStringLibItemName();
689 wxString nick = fp->GetFPID().GetUniStringLibNickname();
692 wxString::Format(
"FPID '%s:%s' does not resolve after reconciliation",
697 BOOST_CHECK_GT( resolved, 0 );
707 stageProject( projectDir.
Path(), wxS(
"fpreconcile_collide" ) );
710 IMPORTED_BOARD sample = importSampleBoard(
project,
"fpreconcile_collide_src" );
711 COLLISION_CANDIDATE candidate = findCandidate( sample );
719 impostor.
SetFPID(
LIB_ID( candidate.m_nickname, candidate.m_name ) );
721 publishLibrary(
project, *adapter, candidate.m_nickname, wxS(
"impostor" ), impostor );
723 const wxString cacheNick = wxS(
"collide-import-fps" );
727 sample.m_board.get(), std::move( sample.m_definitions ), cacheNick, {} );
732 BOOST_CHECK_EQUAL( candidate.m_footprint->GetFPID().GetUniStringLibNickname(), cacheNick );
734 std::unique_ptr<FOOTPRINT> linked( adapter->
LoadFootprint( cacheNick, candidate.m_name,
737 BOOST_CHECK_GT( linked->Pads().size(), 0 );
746 stageProject( projectDir.
Path(), wxS(
"fpreconcile_namecollide" ) );
749 std::string dataPath =
753 std::unique_ptr<BOARD>
source = std::make_unique<BOARD>();
763 if( fp->Pads().empty() )
768 else if( fp->Pads().size() != firstSource->
Pads().size() )
778 const wxString sharedName = wxS(
"SHARED_FP" );
779 const size_t firstPads = firstSource->
Pads().size();
780 const size_t secondPads = secondSource->
Pads().size();
782 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
785 std::vector<std::unique_ptr<FOOTPRINT>> defs;
788 auto place = [&](
const FOOTPRINT* aSource,
const wxString& aNickname )
794 std::unique_ptr<FOOTPRINT> def(
static_cast<FOOTPRINT*
>( aSource->
Clone() ) );
795 def->SetFPID(
LIB_ID( aNickname, sharedName ) );
796 defs.push_back( std::move( def ) );
801 FOOTPRINT* firstPlaced = place( firstSource, wxS(
"libAlpha" ) );
802 FOOTPRINT* secondPlaced = place( secondSource, wxS(
"libBeta" ) );
807 const wxString cacheNick = wxS(
"namecollide-import-fps" );
812 reconciler.
Reconcile( board.get(), std::move( defs ), cacheNick, {} );
823 "Footprints from two source libraries share one cache item name" );
826 std::unique_ptr<FOOTPRINT> firstLinked(
828 std::unique_ptr<FOOTPRINT> secondLinked(
841 BOOST_CHECK_MESSAGE(
reporter.GetMessages().Contains(
842 wxString::Format( wxS(
"renamed to '%s'" ), renamed ) ),
843 "Cache rename was not reported" );
852 stageProject( projectDir.
Path(), wxS(
"fpreconcile_row" ) );
855 IMPORTED_BOARD sample = importSampleBoard(
project,
"fpreconcile_row_src" );
860 const wxString cacheNick = wxS(
"row-import-fps" );
865 publishLibrary(
project, *adapter, cacheNick, wxS(
"user-owned" ), owned );
873 const wxString uriBefore = row->
URI();
878 sample.m_board.get(), std::move( sample.m_definitions ), cacheNick, {} );
880 BOOST_CHECK(
result.m_cacheNickname.IsEmpty() );
883 wxFileName prettyDir(
project.GetProjectPath(), cacheNick,
885 BOOST_CHECK_MESSAGE( !wxDir::Exists( prettyDir.GetFullPath() ),
886 "Published a cache over a nickname the user already owns" );
895 stageProject( projectDir.
Path(), wxS(
"eagle_netlist_roundtrip" ) );
899 brdFn.AppendDir( wxS(
"io" ) );
900 brdFn.AppendDir( wxS(
"eagle" ) );
901 brdFn.SetFullName( wxS(
"eagle-import-testfile.brd" ) );
905 std::unique_ptr<BOARD> imported = std::make_unique<BOARD>();
906 imported->SetProject( &
project );
910 std::vector<std::unique_ptr<FOOTPRINT>> defs;
913 defs.emplace_back( fp );
918 const wxString cacheNick = wxS(
"eagle_test-import-fps" );
920 reconciler.
Reconcile( imported.get(), std::move( defs ), cacheNick, {} );
924 std::set<wxString> seen;
925 int expectedComponents = 0;
927 for(
FOOTPRINT* fp : imported->Footprints() )
929 LIB_ID fpid = fp->GetFPID();
934 wxString ref = wxString::Format( wxS(
"U%d" ), ++expectedComponents );
939 BOOST_REQUIRE_GT( expectedComponents, 0 );
942 std::unique_ptr<BOARD> target = std::make_unique<BOARD>();
943 target->SetProject( &
project );
946 toolMgr.
SetEnvironment( target.get(),
nullptr,
nullptr,
nullptr,
nullptr );
960 BOOST_CHECK_MESSAGE( !
reporter.GetMessages().Lower().Contains( wxS(
"not found" ) ),
961 "Netlist updater reported a footprint not found after reconciliation" );
962 BOOST_CHECK_EQUAL(
static_cast<int>( target->Footprints().size() ), expectedComponents );
Update the BOARD with a new netlist.
void SetReporter(REPORTER *aReporter)
bool UpdateNetlist(NETLIST &aNetlist)
Update the board's components according to the new netlist.
void SetWillDrag(bool aWillDrag)
void SetIsDryRun(bool aEnabled)
Enable dry run mode (just report, no changes to PCB).
void SetDeleteUnusedFootprints(bool aEnabled)
int GetErrorCount() const
void SetReplaceFootprints(bool aEnabled)
Information pertinent to a Pcbnew printed circuit board.
void Add(BOARD_ITEM *aItem, ADD_MODE aMode=ADD_MODE::INSERT, bool aSkipConnectivity=false) override
Removes an item from the container.
const FOOTPRINTS & Footprints() const
void SetProject(PROJECT *aProject, bool aReferenceOnly=false)
Link a board to a given project.
void InitSettings(APP_SETTINGS_BASE *aSettings)
wxString PathStr() const
Get the path to the temporary directory as a wxString.
const std::filesystem::path & Path() const
Get the path to the temporary directory as a std::filesystem::path.
std::optional< LIBRARY_TABLE * > ProjectTable() const
Retrieves the project library table for this adapter type, or nullopt if one doesn't exist.
std::optional< LIBRARY_TABLE * > Table(LIBRARY_TABLE_TYPE aType, LIBRARY_TABLE_SCOPE aScope)
Retrieves a given table; creating a new empty project table if a valid project is loaded and the give...
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.
wxString GetUniStringLibId() const
const wxString GetUniStringLibItemName() const
Get strings for display messages in dialogs.
const wxString GetUniStringLibNickname() const
Store information read from a netlist along with the flags used to update the NETLIST in the BOARD.
@ CUSTOM
Shapes can be defined on arbitrary layers.
static LSET UnplatedHoleMask()
layer set for a mechanical unplated through hole pad
static LSET SMDMask()
layer set for a SMD pad on Front layer
FLIP_DIRECTION m_FlipDirection
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 PCB_IO API or a portion o...
std::vector< FOOTPRINT * > GetImportedCachedLibraryFootprints() override
Return a container with the cached library footprints generated in the last call to Load.
A PCB_IO derivation for saving and loading Pcbnew s-expression formatted files.
void SaveBoard(const wxString &aFileName, BOARD &aBoard, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Write aBoard to a storage file in a format that this PCB_IO implementation knows about or it can be u...
@ KICAD_SEXP
S-expression Pcbnew file format.
static PCB_IO * FindPlugin(PCB_FILE_T aFileType)
Return a PCB_IO which the caller can use to import, export, save, or load design documents.
static const wxString ShowType(PCB_FILE_T aFileType)
Return a brief name for a plugin given aFileType enum.
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.
std::unique_ptr< BOARD > LoadBoard(const wxString &aFileName, const std::map< std::string, UTF8 > *aProperties=nullptr, PROJECT *aProject=nullptr)
Load information from some input file format that this PCB_IO implementation knows about into new BOA...
virtual SETTINGS_MANAGER & GetSettingsManager() const
virtual LIBRARY_MANAGER & GetLibraryManager() const
static FOOTPRINT_LIBRARY_ADAPTER * FootprintLibAdapter(PROJECT *aProject)
Container for project specific data.
virtual const wxString GetProjectPath() const
Return the full path of the project.
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.
A wrapper for reporting to a wxString object.
KIFACE_BASE & Kiface()
Global KIFACE_BASE "get" accessor.
static constexpr EDA_ANGLE ANGLE_90
static const std::string KiCadFootprintLibPathExtension
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
@ LEFT_RIGHT
Flip left to right (around the Y axis)
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.
@ NPTH
like PAD_PTH, but not plated mechanical use only, no connection allowed
@ SMD
Smd pad, appears on the solder paste layer (default)
PGM_BASE & Pgm()
The global program "get" accessor.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
IbisParser parser & reporter
wxString result
Test unit parsing edge cases and error handling.
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I