29#include <boost/test/unit_test.hpp>
55#include <wx/filename.h>
56#include <wx/wfstream.h>
57#include <wx/zipstrm.h>
66 +
"pcbnew/plugins/easyedapro/ProProject_LS2K0300Core_2025-11-14.epro2" );
70static void writeZipEntry( wxZipOutputStream& aZip,
const wxString& aName,
const std::string& aContent )
72 aZip.PutNextEntry( aName );
73 aZip.Write( aContent.data(), aContent.size() );
78static wxString makeTempElibz2Path(
const wxString& aPrefix )
80 wxString tempPath = wxFileName::CreateTempFileName( aPrefix );
81 wxRemoveFile( tempPath );
83 wxFileName fn( tempPath );
84 fn.SetExt( wxS(
"elibz2" ) );
86 return fn.GetFullPath();
90static void writeV3LibraryArchive(
const wxString& aPath,
const std::string& aSymbolIndex,
91 const std::string& aFootprintIndex,
const std::string& aElibu )
93 wxFFileOutputStream out( aPath );
96 wxZipOutputStream
zip( out, -1, wxConvUTF8 );
98 if( !aSymbolIndex.empty() )
99 writeZipEntry(
zip, wxS(
"symbol2.json" ), aSymbolIndex );
101 if( !aFootprintIndex.empty() )
102 writeZipEntry(
zip, wxS(
"footprint2.json" ), aFootprintIndex );
104 writeZipEntry(
zip, wxS(
"library.elibu" ), aElibu );
115 wxString archivePath = makeTempElibz2Path( wxS(
"easyedapro_v3_mixed" ) );
117 const std::string symbolIndex = R
"({
120 "sym_uuid": { "uuid": "sym_uuid", "display_title": "SymbolName" }
126 const std::string footprintIndex = R
"({
130 "fp_uuid": { "uuid": "fp_uuid", "display_title": "FootprintName" }
135 const std::string elibu =
"{\"type\":\"DOCHEAD\"}||{\"docType\":\"SYMBOL\",\"uuid\":\"sym_uuid\"}|\n"
136 "{\"type\":\"DOCHEAD\"}||{\"docType\":\"FOOTPRINT\",\"uuid\":\"fp_uuid\"}|\n";
138 writeV3LibraryArchive( archivePath, symbolIndex, footprintIndex, elibu );
144 std::map<wxString, wxString> footprints =
147 BOOST_CHECK_EQUAL( symbols.at( wxS(
"SymbolName" ) ), wxString( wxS(
"sym_uuid" ) ) );
148 BOOST_CHECK_EQUAL( footprints.at( wxS(
"FootprintName" ) ), wxString( wxS(
"fp_uuid" ) ) );
150 BOOST_CHECK( wxRemoveFile( archivePath ) );
156 wxString archivePath = makeTempElibz2Path( wxS(
"easyedapro_v3_dupes" ) );
158 const std::string symbolIndex = R
"({
161 "aaaaaaaa0001": { "uuid": "aaaaaaaa0001", "display_title": "DUP" },
162 "aaaaaaaa0002": { "uuid": "aaaaaaaa0002", "display_title": "DUP" },
163 "aaaaaaaa0003": { "uuid": "aaaaaaaa0003", "display_title": "DUP" }
169 const std::string elibu =
"{\"type\":\"DOCHEAD\"}||{\"docType\":\"SYMBOL\",\"uuid\":\"aaaaaaaa0001\"}|\n"
170 "{\"type\":\"DOCHEAD\"}||{\"docType\":\"SYMBOL\",\"uuid\":\"aaaaaaaa0002\"}|\n"
171 "{\"type\":\"DOCHEAD\"}||{\"docType\":\"SYMBOL\",\"uuid\":\"aaaaaaaa0003\"}|\n";
173 writeV3LibraryArchive( archivePath, symbolIndex, std::string(), elibu );
180 BOOST_REQUIRE_EQUAL( symbols.size(), 3 );
181 BOOST_CHECK( symbols.contains( wxS(
"DUP" ) ) );
182 BOOST_CHECK( symbols.contains( wxS(
"DUP_2" ) ) );
183 BOOST_CHECK( symbols.contains( wxS(
"DUP_3" ) ) );
185 BOOST_CHECK( wxRemoveFile( archivePath ) );
192 BOOST_REQUIRE_NO_THROW(
v3.Load() );
200 bool hasAtLeastOneSheet =
false;
202 for(
const auto& [schUuid, sch] :
project.at(
"schematics" ).items() )
204 if( sch.contains(
"sheets" ) && sch.at(
"sheets" ).is_array()
205 && !sch.at(
"sheets" ).empty() )
207 hasAtLeastOneSheet =
true;
212 BOOST_CHECK( hasAtLeastOneSheet );
219 BOOST_REQUIRE_NO_THROW(
v3.Load() );
221 const auto& pages =
v3.GetRawDocs( wxS(
"SCH_PAGE" ) );
224 bool hasTextValue =
false;
225 bool hasLineStartEnd =
false;
226 bool hasWireNetAttr =
false;
228 for(
const auto& [pageUuid, pageDoc] : pages )
232 if( row.
type == wxS(
"TEXT" ) && row.
inner.contains(
"value" ) )
235 if( row.
type == wxS(
"LINE" )
236 && row.
inner.contains(
"startX" ) && row.
inner.contains(
"startY" )
237 && row.
inner.contains(
"endX" ) && row.
inner.contains(
"endY" ) )
239 hasLineStartEnd =
true;
242 if( row.
type == wxS(
"ATTR" )
245 hasWireNetAttr =
true;
250 BOOST_CHECK( hasTextValue );
251 BOOST_CHECK( hasLineStartEnd );
252 BOOST_CHECK( hasWireNetAttr );
259 BOOST_REQUIRE_NO_THROW(
v3.Load() );
261 const auto& symbols =
v3.GetRawDocs( wxS(
"SYMBOL" ) );
264 bool hasBlobObject =
false;
266 for(
const auto& [symbolUuid, symbolDoc] : symbols )
270 if( row.
type != wxS(
"OBJ" ) )
275 if( content.StartsWith( wxS(
"blob:" ) ) )
277 hasBlobObject =
true;
286 BOOST_CHECK( hasBlobObject );
293 BOOST_REQUIRE_NO_THROW(
v3.Load() );
296 std::map<wxString, EASYEDAPRO::PRJ_DEVICE> devices =
project.at(
"devices" );
300 for(
const auto& [devUuid, dev] : devices )
302 if( dev.title == aTitle )
317 wxString ls2kSymbolUuid = ls2kDevice->
attributes.at( wxS(
"Symbol" ) );
325 std::array<int, 6> unitPinCount = {};
329 int unit =
pin->GetUnit();
331 if( unit >= 1 && unit <= 5 )
332 unitPinCount[unit]++;
335 for(
int unit = 1; unit <= 5; ++unit )
337 "LS2K0300 should have pins in each unit" );
339 wxString sgmSymbolUuid = sgmDevice->
attributes.at( wxS(
"Symbol" ) );
346 bool hasLeftPins =
false;
347 bool hasRightPins =
false;
351 if(
pin->GetPosition().x < 0 )
357 else if(
pin->GetPosition().x > 0 )
365 BOOST_CHECK( hasLeftPins );
366 BOOST_CHECK( hasRightPins );
372 wxString tempDir = wxFileName::CreateTempFileName(
"easyedapro_v3_import" );
378 wxFileName projectFile( tempDir,
"easyedapro_v3_test",
"kicad_pro" );
379 wxFileName archiveFile( tempDir,
"sample",
"epro2" );
386 schematic.SetProject( &
Pgm().GetSettingsManager().
Prj() );
392 BOOST_REQUIRE_NO_THROW( rootSheet =
393 plugin->LoadSchematicFile( archiveFile.GetFullPath(),
394 &schematic,
nullptr,
nullptr ) );
397 schematic.RefreshHierarchy();
422 if( !
text->GetText().IsEmpty() )
430 if( !label->
GetText().IsEmpty() )
435 BOOST_CHECK( wireCount > 0 );
436 BOOST_CHECK( textCount > 0 );
437 BOOST_CHECK( labelCount > 0 );
439 BOOST_CHECK( wxFileName::Rmdir( tempDir, wxPATH_RMDIR_RECURSIVE ) );
445 wxString tempDir = wxFileName::CreateTempFileName(
"easyedapro_v3_import" );
451 wxFileName projectFile( tempDir,
"easyedapro_v3_test",
"kicad_pro" );
452 wxFileName archiveFile( tempDir,
"sample",
"epro2" );
459 BOOST_REQUIRE_NO_THROW(
v3.Load() );
468 std::vector<NET_ATTR> candidates;
469 std::map<wxString, int> valueCounts;
471 for(
const auto& [pageUuid, pageDoc] :
v3.GetRawDocs( wxS(
"SCH_PAGE" ) ) )
475 if( row.
type != wxS(
"ATTR" ) )
487 if( value.IsEmpty() )
490 valueCounts[value]++;
495 if( align.StartsWith( wxS(
"RIGHT" ) ) )
496 candidates.push_back( { value, align, rotation } );
502 NET_ATTR selected = candidates.front();
504 for(
const NET_ATTR& candidate : candidates )
506 if( valueCounts[candidate.value] == 1 )
508 selected = candidate;
515 if( selected.align.StartsWith( wxS(
"RIGHT" ) ) )
517 else if( selected.align.StartsWith( wxS(
"LEFT" ) ) )
520 int rot = selected.rotation % 360;
527 for(
int i = 0; i < rot; i += 90 )
528 expectedSpinStyle = expectedSpinStyle.
RotateCCW();
535 schematic.SetProject( &
Pgm().GetSettingsManager().
Prj() );
541 BOOST_REQUIRE_NO_THROW( rootSheet =
542 plugin->LoadSchematicFile( archiveFile.GetFullPath(),
543 &schematic,
nullptr,
nullptr ) );
546 schematic.RefreshHierarchy();
549 bool hasExpectedSpinStyle =
false;
562 if( label->
GetText() != selected.value )
567 if(
static_cast<int>( label->
GetSpinStyle() ) ==
static_cast<int>( expectedSpinStyle ) )
568 hasExpectedSpinStyle =
true;
574 wxString::Format(
"Label '%s' spin style mismatch", selected.value ) );
576 BOOST_CHECK( wxFileName::Rmdir( tempDir, wxPATH_RMDIR_RECURSIVE ) );
582 wxString tempDir = wxFileName::CreateTempFileName(
"easyedapro_v3_import" );
588 wxFileName projectFile( tempDir,
"easyedapro_v3_test",
"kicad_pro" );
589 wxFileName archiveFile( tempDir,
"sample",
"epro2" );
595 BOOST_REQUIRE_NO_THROW(
v3.Load() );
597 std::set<wxString> netportSymbolUuids;
599 for(
const auto& [uuid, symDoc] :
v3.GetRawDocs( wxS(
"SYMBOL" ) ) )
603 if( row.
type == wxS(
"META" )
606 netportSymbolUuids.insert( uuid );
614 std::set<wxString> netportComponentIds;
616 for(
const auto& [pageUuid, pageDoc] :
v3.GetRawDocs( wxS(
"SCH_PAGE" ) ) )
620 if( row.
type != wxS(
"ATTR" ) )
628 if( netportSymbolUuids.count( symbolUuid ) )
635 std::set<wxString> expectedNetportNames;
637 for(
const auto& [pageUuid, pageDoc] :
v3.GetRawDocs( wxS(
"SCH_PAGE" ) ) )
641 if( row.
type != wxS(
"ATTR" ) )
646 if( !netportComponentIds.count( parentId ) )
653 row.
inner.value(
"value", nlohmann::json() ) );
655 if( !
name.IsEmpty() )
656 expectedNetportNames.insert(
name );
666 schematic.SetProject( &
Pgm().GetSettingsManager().
Prj() );
672 BOOST_REQUIRE_NO_THROW( rootSheet =
673 plugin->LoadSchematicFile( archiveFile.GetFullPath(),
674 &schematic,
nullptr,
nullptr ) );
677 schematic.RefreshHierarchy();
679 int netportSymbolCount = 0;
680 int globalLabelCount = 0;
681 bool hasInputLabel =
false;
682 bool hasOutputLabel =
false;
683 bool hasBidiLabel =
false;
685 std::set<wxString> importedGlobalLabelNames;
697 wxString ref = symbol->
GetRef( &sheetPath,
false );
699 if( ref.StartsWith( wxS(
"#NET" ) ) )
700 netportSymbolCount++;
707 if( label->
GetText().IsEmpty() )
711 importedGlobalLabelNames.insert( label->
GetText() );
724 BOOST_CHECK( globalLabelCount > 0 );
727 BOOST_CHECK( hasInputLabel );
728 BOOST_CHECK( hasOutputLabel );
729 BOOST_CHECK( hasBidiLabel );
732 for(
const wxString&
expected : expectedNetportNames )
735 wxString::Format(
"Missing global label '%s'",
expected ) );
738 BOOST_CHECK( wxFileName::Rmdir( tempDir, wxPATH_RMDIR_RECURSIVE ) );
744 wxString tempDir = wxFileName::CreateTempFileName(
"easyedapro_v3_import" );
750 wxFileName projectFile( tempDir,
"easyedapro_v3_test",
"kicad_pro" );
751 wxFileName archiveFile( tempDir,
"sample",
"epro2" );
758 schematic.SetProject( &
Pgm().GetSettingsManager().
Prj() );
764 BOOST_REQUIRE_NO_THROW( rootSheet =
765 plugin->LoadSchematicFile( archiveFile.GetFullPath(),
766 &schematic,
nullptr,
nullptr ) );
769 schematic.RefreshHierarchy();
785 wxString ref = symbol->
GetRef( &sheetPath,
false );
787 if( ref == wxS(
"U6" ) )
789 else if( ref == wxS(
"U7" ) )
791 else if( ref == wxS(
"U1" ) )
801 "U7 should remain above U6 after EasyEDA Pro v3 import" );
806 BOOST_CHECK( !u6Footprint.IsEmpty() );
807 BOOST_CHECK( u6Footprint.Contains( wxS(
"SOT-563-6" ) ) );
809 BOOST_CHECK( !u1Footprint.IsEmpty() );
810 BOOST_CHECK( u1Footprint.Contains( wxS(
"BGA-286_17x17_12.0x12.0mm" ) ) );
812 BOOST_CHECK( wxFileName::Rmdir( tempDir, wxPATH_RMDIR_RECURSIVE ) );
818 wxString tempDir = wxFileName::CreateTempFileName(
"easyedapro_v3_import" );
824 wxFileName projectFile( tempDir,
"easyedapro_v3_test",
"kicad_pro" );
825 wxFileName archiveFile( tempDir,
"sample",
"epro2" );
832 schematic.SetProject( &
Pgm().GetSettingsManager().
Prj() );
838 BOOST_REQUIRE_NO_THROW( rootSheet =
839 plugin->LoadSchematicFile( archiveFile.GetFullPath(),
840 &schematic,
nullptr,
nullptr ) );
843 schematic.RefreshHierarchy();
846 int plus5vShownCount = 0;
847 int plus5vOffsetCount = 0;
848 int plus5vUprightCount = 0;
849 int plus5vUprightAtSymbolXCount = 0;
850 int powerValueOnPinBodySideCheckedCount = 0;
851 int powerValueOnWrongPinSideCount = 0;
852 int otherPowerValueCount = 0;
853 int otherPowerShownCount = 0;
874 wxString valueText = valueField->
GetShownText( &sheetPath,
false );
876 if( valueField->
IsVisible() && !valueText.IsEmpty() )
882 std::vector<SCH_PIN*> pins = symbol->
GetPins( &sheetPath );
888 switch( pins[0]->PinDrawOrient( symbol->
GetTransform() ) )
899 long long dot =
static_cast<long long>(
delta.x ) * dir.
x
900 +
static_cast<long long>(
delta.y ) * dir.
y;
902 powerValueOnPinBodySideCheckedCount++;
905 powerValueOnWrongPinSideCount++;
911 if( valueText == wxS(
"+5V" ) )
923 plus5vUprightCount++;
926 plus5vUprightAtSymbolXCount++;
932 else if( valueText.StartsWith( wxS(
"+" ) ) )
934 otherPowerValueCount++;
937 otherPowerShownCount++;
942 BOOST_CHECK( plus5vCount > 0 );
944 BOOST_CHECK( plus5vOffsetCount > 0 );
945 BOOST_CHECK( plus5vUprightCount > 0 );
948 BOOST_CHECK( powerValueOnPinBodySideCheckedCount > 0 );
951 BOOST_CHECK( otherPowerValueCount > 0 );
954 BOOST_CHECK( wxFileName::Rmdir( tempDir, wxPATH_RMDIR_RECURSIVE ) );
964 wxString tempDir = wxFileName::CreateTempFileName(
"easyedapro_v3_import" );
968 wxFileName projectFile( tempDir,
"easyedapro_v3_test",
"kicad_pro" );
969 wxFileName archiveFile( tempDir,
"sample",
"epro2" );
975 schematic.SetProject( &
Pgm().GetSettingsManager().
Prj() );
978 SCH_SHEET* rootSheet = plugin->LoadSchematicFile( archiveFile.GetFullPath(), &schematic );
980 schematic.RefreshHierarchy();
985 (void) item, rootSymbols++;
988 BOOST_CHECK( rootSymbols > 0 );
991 BOOST_CHECK( wxFileName::Rmdir( tempDir, wxPATH_RMDIR_RECURSIVE ) );
const wxString & GetPageNumber() const
Parses EasyEDA Pro v3 .epro2 archives.
void LoadLibrary()
Load all v3 docs from a library archive.
virtual const wxString & GetText() const
Return the string associated with the text object.
virtual bool IsVisible() const
GR_TEXT_H_ALIGN_T GetHorizJustify() const
GR_TEXT_V_ALIGN_T GetVertJustify() const
EE_TYPE OfType(KICAD_T aType) const
std::vector< SCH_PIN * > GetPins() const override
int GetUnitCount() const override
virtual SETTINGS_MANAGER & GetSettingsManager() const
Holds all the data relating to one schematic.
EASYEDAPRO::SYM_INFO ParseSymbol(const EASYEDAPRO::V3_DOC_RAW &aDoc, const std::map< wxString, wxString > &aDeviceAttributes, const std::map< wxString, EASYEDAPRO::BLOB > &aBlobMap={})
VECTOR2I GetPosition() const override
wxString GetShownText(const SCH_SHEET_PATH *aPath, bool aAllowExtraText, int aDepth=0, const wxString &aVariantName=wxEmptyString) const
Base class for any item which can be embedded within the SCHEMATIC container class,...
SCH_LAYER_ID GetLayer() const
Return the layer this item is on.
SPIN_STYLE GetSpinStyle() const
LABEL_FLAG_SHAPE GetShape() const
Segment description base class to describe items which have 2 end points (track, wire,...
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
SCH_SCREEN * GetScreen() const
std::vector< const SCH_PIN * > GetPins(const SCH_SHEET_PATH *aSheet) const
Retrieve a list of the SCH_PINs for the given sheet path.
bool IsGlobalPower() const override
const wxString GetFootprintFieldText(bool aResolve, const SCH_SHEET_PATH *aPath, bool aAllowExtraText, const wxString &aVariantName=wxEmptyString) const
VECTOR2I GetPosition() const override
int GetOrientation() const override
Get the display symbol orientation.
const wxString GetRef(const SCH_SHEET_PATH *aSheet, bool aIncludeUnit=false) const override
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this symbol.
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
const TRANSFORM & GetTransform() const
BOOST_AUTO_TEST_CASE(LibraryIndexMergeKeepsSymbolAndFootprintNames)
std::unique_ptr< T > IO_RELEASER
Helper to hold and release an IO_BASE object when exceptions are thrown.
nlohmann::json BuildV3ProjectIndexFromRawDocs(const V3_DOC_PARSER &aParser, bool aIncludeLibraryMetadata=true)
Build a minimal legacy-style project index from parsed v3 raw documents.
bool V3GetBool(const nlohmann::json &aObj, const char *aKey, bool aDefault)
int AlignToFontV(const wxString &aAlign)
Map an EasyEDA "HORIZ_VERT" alignment token to a KiCad justify code (0 top/left, 1 center,...
wxString V3JsonToString(const nlohmann::json &aValue, const wxString &aDefault)
int V3GetInt(const nlohmann::json &aObj, const char *aKey, int aDefault)
int AlignToFontH(const wxString &aAlign)
std::map< wxString, wxString > BuildV3LibraryItemMap(const V3_DOC_PARSER &aParser, const char *aIndexKey, const wxString &aDocType)
wxString V3GetString(const nlohmann::json &aObj, const char *aKey, const wxString &aDefault)
std::string GetTestDataRootDir()
PGM_BASE & Pgm()
The global program "get" accessor.
@ PIN_UP
The pin extends upwards from the connection point: Probably on the bottom side of the symbol.
@ PIN_RIGHT
The pin extends rightwards from the connection point.
@ PIN_LEFT
The pin extends leftwards from the connection point: Probably on the right side of the symbol.
@ PIN_DOWN
The pin extends downwards from the connection: Probably on the top side of the symbol.
std::map< wxString, wxString > attributes
std::unique_ptr< LIB_SYMBOL > libSymbol
Raw parsed document from an EasyEDA Pro v3 .epru stream.
One parsed row from an EasyEDA Pro v3 .epru document stream.
@ VALUE
Field Value of part, i.e. "3.3K".
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
static wxString getEasyEdaProV3ArchivePath()
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
VECTOR3I expected(15, 30, 45)
BOOST_CHECK_MESSAGE(totalMismatches==0, std::to_string(totalMismatches)+" board(s) with strategy disagreements")
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I