29#include <boost/test/unit_test.hpp>
56#include <wx/filename.h>
57#include <wx/wfstream.h>
58#include <wx/zipstrm.h>
67 +
"pcbnew/plugins/easyedapro/ProProject_LS2K0300Core_2025-11-14.epro2" );
71static void writeZipEntry( wxZipOutputStream& aZip,
const wxString& aName,
const std::string& aContent )
73 aZip.PutNextEntry( aName );
74 aZip.Write( aContent.data(), aContent.size() );
79static wxString makeTempElibz2Path(
const wxString& aPrefix )
81 wxString tempPath = wxFileName::CreateTempFileName( aPrefix );
82 wxRemoveFile( tempPath );
84 wxFileName fn( tempPath );
85 fn.SetExt( wxS(
"elibz2" ) );
87 return fn.GetFullPath();
91static void writeV3LibraryArchive(
const wxString& aPath,
const std::string& aSymbolIndex,
92 const std::string& aFootprintIndex,
const std::string& aElibu )
94 wxFFileOutputStream out( aPath );
97 wxZipOutputStream
zip( out, -1, wxConvUTF8 );
99 if( !aSymbolIndex.empty() )
100 writeZipEntry(
zip, wxS(
"symbol2.json" ), aSymbolIndex );
102 if( !aFootprintIndex.empty() )
103 writeZipEntry(
zip, wxS(
"footprint2.json" ), aFootprintIndex );
105 writeZipEntry(
zip, wxS(
"library.elibu" ), aElibu );
116 wxString archivePath = makeTempElibz2Path( wxS(
"easyedapro_v3_mixed" ) );
118 const std::string symbolIndex = R
"({
121 "sym_uuid": { "uuid": "sym_uuid", "display_title": "SymbolName" }
127 const std::string footprintIndex = R
"({
131 "fp_uuid": { "uuid": "fp_uuid", "display_title": "FootprintName" }
136 const std::string elibu =
"{\"type\":\"DOCHEAD\"}||{\"docType\":\"SYMBOL\",\"uuid\":\"sym_uuid\"}|\n"
137 "{\"type\":\"DOCHEAD\"}||{\"docType\":\"FOOTPRINT\",\"uuid\":\"fp_uuid\"}|\n";
139 writeV3LibraryArchive( archivePath, symbolIndex, footprintIndex, elibu );
145 std::map<wxString, wxString> footprints =
148 BOOST_CHECK_EQUAL( symbols.at( wxS(
"SymbolName" ) ), wxString( wxS(
"sym_uuid" ) ) );
149 BOOST_CHECK_EQUAL( footprints.at( wxS(
"FootprintName" ) ), wxString( wxS(
"fp_uuid" ) ) );
151 BOOST_CHECK( wxRemoveFile( archivePath ) );
157 wxString archivePath = makeTempElibz2Path( wxS(
"easyedapro_v3_dupes" ) );
159 const std::string symbolIndex = R
"({
162 "aaaaaaaa0001": { "uuid": "aaaaaaaa0001", "display_title": "DUP" },
163 "aaaaaaaa0002": { "uuid": "aaaaaaaa0002", "display_title": "DUP" },
164 "aaaaaaaa0003": { "uuid": "aaaaaaaa0003", "display_title": "DUP" }
170 const std::string elibu =
"{\"type\":\"DOCHEAD\"}||{\"docType\":\"SYMBOL\",\"uuid\":\"aaaaaaaa0001\"}|\n"
171 "{\"type\":\"DOCHEAD\"}||{\"docType\":\"SYMBOL\",\"uuid\":\"aaaaaaaa0002\"}|\n"
172 "{\"type\":\"DOCHEAD\"}||{\"docType\":\"SYMBOL\",\"uuid\":\"aaaaaaaa0003\"}|\n";
174 writeV3LibraryArchive( archivePath, symbolIndex, std::string(), elibu );
181 BOOST_REQUIRE_EQUAL( symbols.size(), 3 );
182 BOOST_CHECK( symbols.contains( wxS(
"DUP" ) ) );
183 BOOST_CHECK( symbols.contains( wxS(
"DUP_2" ) ) );
184 BOOST_CHECK( symbols.contains( wxS(
"DUP_3" ) ) );
186 BOOST_CHECK( wxRemoveFile( archivePath ) );
193 BOOST_REQUIRE_NO_THROW(
v3.Load() );
201 bool hasAtLeastOneSheet =
false;
203 for(
const auto& [schUuid, sch] :
project.at(
"schematics" ).items() )
205 if( sch.contains(
"sheets" ) && sch.at(
"sheets" ).is_array()
206 && !sch.at(
"sheets" ).empty() )
208 hasAtLeastOneSheet =
true;
213 BOOST_CHECK( hasAtLeastOneSheet );
220 BOOST_REQUIRE_NO_THROW(
v3.Load() );
222 const auto& pages =
v3.GetRawDocs( wxS(
"SCH_PAGE" ) );
225 bool hasTextValue =
false;
226 bool hasLineStartEnd =
false;
227 bool hasWireNetAttr =
false;
229 for(
const auto& [pageUuid, pageDoc] : pages )
233 if( row.
type == wxS(
"TEXT" ) && row.
inner.contains(
"value" ) )
236 if( row.
type == wxS(
"LINE" )
237 && row.
inner.contains(
"startX" ) && row.
inner.contains(
"startY" )
238 && row.
inner.contains(
"endX" ) && row.
inner.contains(
"endY" ) )
240 hasLineStartEnd =
true;
243 if( row.
type == wxS(
"ATTR" )
246 hasWireNetAttr =
true;
251 BOOST_CHECK( hasTextValue );
252 BOOST_CHECK( hasLineStartEnd );
253 BOOST_CHECK( hasWireNetAttr );
260 BOOST_REQUIRE_NO_THROW(
v3.Load() );
262 const auto& symbols =
v3.GetRawDocs( wxS(
"SYMBOL" ) );
265 bool hasBlobObject =
false;
267 for(
const auto& [symbolUuid, symbolDoc] : symbols )
271 if( row.
type != wxS(
"OBJ" ) )
276 if( content.StartsWith( wxS(
"blob:" ) ) )
278 hasBlobObject =
true;
287 BOOST_CHECK( hasBlobObject );
294 BOOST_REQUIRE_NO_THROW(
v3.Load() );
297 std::map<wxString, EASYEDAPRO::PRJ_DEVICE> devices =
project.at(
"devices" );
301 for(
const auto& [devUuid, dev] : devices )
303 if( dev.title == aTitle )
318 wxString ls2kSymbolUuid = ls2kDevice->
attributes.at( wxS(
"Symbol" ) );
326 std::array<int, 6> unitPinCount = {};
330 int unit =
pin->GetUnit();
332 if( unit >= 1 && unit <= 5 )
333 unitPinCount[unit]++;
336 for(
int unit = 1; unit <= 5; ++unit )
337 BOOST_CHECK_MESSAGE( unitPinCount[unit] > 0,
338 "LS2K0300 should have pins in each unit" );
340 wxString sgmSymbolUuid = sgmDevice->
attributes.at( wxS(
"Symbol" ) );
347 bool hasLeftPins =
false;
348 bool hasRightPins =
false;
352 if(
pin->GetPosition().x < 0 )
358 else if(
pin->GetPosition().x > 0 )
366 BOOST_CHECK( hasLeftPins );
367 BOOST_CHECK( hasRightPins );
374 wxS(
"easyedapro_v3_test" ) );
376 wxFileName archiveFile( tempProject.
DirStr(),
"sample",
"epro2" );
387 BOOST_REQUIRE_NO_THROW( rootSheet =
388 plugin->LoadSchematicFile( archiveFile.GetFullPath(),
389 &schematic,
nullptr,
nullptr ) );
417 if( !
text->GetText().IsEmpty() )
425 if( !label->
GetText().IsEmpty() )
430 BOOST_CHECK( wireCount > 0 );
431 BOOST_CHECK( textCount > 0 );
432 BOOST_CHECK( labelCount > 0 );
439 wxS(
"easyedapro_v3_test" ) );
441 wxFileName archiveFile( tempProject.
DirStr(),
"sample",
"epro2" );
448 BOOST_REQUIRE_NO_THROW(
v3.Load() );
457 std::vector<NET_ATTR> candidates;
458 std::map<wxString, int> valueCounts;
460 for(
const auto& [pageUuid, pageDoc] :
v3.GetRawDocs( wxS(
"SCH_PAGE" ) ) )
464 if( row.
type != wxS(
"ATTR" ) )
476 if( value.IsEmpty() )
479 valueCounts[value]++;
484 if( align.StartsWith( wxS(
"RIGHT" ) ) )
485 candidates.push_back( { value, align, rotation } );
491 NET_ATTR selected = candidates.front();
493 for(
const NET_ATTR& candidate : candidates )
495 if( valueCounts[candidate.value] == 1 )
497 selected = candidate;
504 if( selected.align.StartsWith( wxS(
"RIGHT" ) ) )
506 else if( selected.align.StartsWith( wxS(
"LEFT" ) ) )
509 int rot = selected.rotation % 360;
516 for(
int i = 0; i < rot; i += 90 )
517 expectedSpinStyle = expectedSpinStyle.
RotateCCW();
528 BOOST_REQUIRE_NO_THROW( rootSheet =
529 plugin->LoadSchematicFile( archiveFile.GetFullPath(),
530 &schematic,
nullptr,
nullptr ) );
536 bool hasExpectedSpinStyle =
false;
549 if( label->
GetText() != selected.value )
554 if(
static_cast<int>( label->
GetSpinStyle() ) ==
static_cast<int>( expectedSpinStyle ) )
555 hasExpectedSpinStyle =
true;
560 BOOST_CHECK_MESSAGE( hasExpectedSpinStyle,
561 wxString::Format(
"Label '%s' spin style mismatch", selected.value ) );
568 wxS(
"easyedapro_v3_test" ) );
570 wxFileName archiveFile( tempProject.
DirStr(),
"sample",
"epro2" );
576 BOOST_REQUIRE_NO_THROW(
v3.Load() );
578 std::set<wxString> netportSymbolUuids;
580 for(
const auto& [uuid, symDoc] :
v3.GetRawDocs( wxS(
"SYMBOL" ) ) )
584 if( row.
type == wxS(
"META" )
587 netportSymbolUuids.insert( uuid );
595 std::set<wxString> netportComponentIds;
597 for(
const auto& [pageUuid, pageDoc] :
v3.GetRawDocs( wxS(
"SCH_PAGE" ) ) )
601 if( row.
type != wxS(
"ATTR" ) )
609 if( netportSymbolUuids.count( symbolUuid ) )
616 std::set<wxString> expectedNetportNames;
618 for(
const auto& [pageUuid, pageDoc] :
v3.GetRawDocs( wxS(
"SCH_PAGE" ) ) )
622 if( row.
type != wxS(
"ATTR" ) )
627 if( !netportComponentIds.count( parentId ) )
634 row.
inner.value(
"value", nlohmann::json() ) );
636 if( !
name.IsEmpty() )
637 expectedNetportNames.insert(
name );
651 BOOST_REQUIRE_NO_THROW( rootSheet =
652 plugin->LoadSchematicFile( archiveFile.GetFullPath(),
653 &schematic,
nullptr,
nullptr ) );
658 int netportSymbolCount = 0;
659 int globalLabelCount = 0;
660 bool hasInputLabel =
false;
661 bool hasOutputLabel =
false;
662 bool hasBidiLabel =
false;
664 std::set<wxString> importedGlobalLabelNames;
676 wxString ref = symbol->
GetRef( &sheetPath,
false );
678 if( ref.StartsWith( wxS(
"#NET" ) ) )
679 netportSymbolCount++;
686 if( label->
GetText().IsEmpty() )
690 importedGlobalLabelNames.insert( label->
GetText() );
703 BOOST_CHECK( globalLabelCount > 0 );
706 BOOST_CHECK( hasInputLabel );
707 BOOST_CHECK( hasOutputLabel );
708 BOOST_CHECK( hasBidiLabel );
711 for(
const wxString&
expected : expectedNetportNames )
713 BOOST_CHECK_MESSAGE( importedGlobalLabelNames.count(
expected ) > 0,
714 wxString::Format(
"Missing global label '%s'",
expected ) );
722 wxS(
"easyedapro_v3_test" ) );
724 wxFileName archiveFile( tempProject.
DirStr(),
"sample",
"epro2" );
735 BOOST_REQUIRE_NO_THROW( rootSheet =
736 plugin->LoadSchematicFile( archiveFile.GetFullPath(),
737 &schematic,
nullptr,
nullptr ) );
756 wxString ref = symbol->
GetRef( &sheetPath,
false );
758 if( ref == wxS(
"U6" ) )
760 else if( ref == wxS(
"U7" ) )
762 else if( ref == wxS(
"U1" ) )
772 "U7 should remain above U6 after EasyEDA Pro v3 import" );
777 BOOST_CHECK( !u6Footprint.IsEmpty() );
778 BOOST_CHECK( u6Footprint.Contains( wxS(
"SOT-563-6" ) ) );
780 BOOST_CHECK( !u1Footprint.IsEmpty() );
781 BOOST_CHECK( u1Footprint.Contains( wxS(
"BGA-286_17x17_12.0x12.0mm" ) ) );
788 wxS(
"easyedapro_v3_test" ) );
790 wxFileName archiveFile( tempProject.
DirStr(),
"sample",
"epro2" );
801 BOOST_REQUIRE_NO_THROW( rootSheet =
802 plugin->LoadSchematicFile( archiveFile.GetFullPath(),
803 &schematic,
nullptr,
nullptr ) );
809 int plus5vShownCount = 0;
810 int plus5vOffsetCount = 0;
811 int plus5vUprightCount = 0;
812 int plus5vUprightAtSymbolXCount = 0;
813 int powerValueOnPinBodySideCheckedCount = 0;
814 int powerValueOnWrongPinSideCount = 0;
815 int otherPowerValueCount = 0;
816 int otherPowerShownCount = 0;
839 if( valueField->
IsVisible() && !valueText.IsEmpty() )
845 std::vector<SCH_PIN*> pins = symbol->
GetPins( &sheetPath );
851 switch( pins[0]->PinDrawOrient( symbol->
GetTransform() ) )
862 long long dot =
static_cast<long long>(
delta.x ) * dir.
x
863 +
static_cast<long long>(
delta.y ) * dir.
y;
865 powerValueOnPinBodySideCheckedCount++;
868 powerValueOnWrongPinSideCount++;
874 if( valueText == wxS(
"+5V" ) )
886 plus5vUprightCount++;
889 plus5vUprightAtSymbolXCount++;
895 else if( valueText.StartsWith( wxS(
"+" ) ) )
897 otherPowerValueCount++;
900 otherPowerShownCount++;
905 BOOST_CHECK( plus5vCount > 0 );
907 BOOST_CHECK( plus5vOffsetCount > 0 );
908 BOOST_CHECK( plus5vUprightCount > 0 );
911 BOOST_CHECK( powerValueOnPinBodySideCheckedCount > 0 );
914 BOOST_CHECK( otherPowerValueCount > 0 );
926 wxS(
"easyedapro_v3_test" ) );
928 wxFileName archiveFile( tempProject.
DirStr(),
"sample",
"epro2" );
935 SCH_SHEET* rootSheet = plugin->LoadSchematicFile( archiveFile.GetFullPath(), &schematic );
942 (void) item, rootSymbols++;
945 BOOST_CHECK( rootSymbols > 0 );
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
PROJECT & Project() const
wxString DirStr() const
Get the path to the temporary directory as a wxString.
std::vector< SCH_PIN * > GetPins() const override
int GetUnitCount() const override
Holds all the data relating to one schematic.
SCH_SHEET_LIST Hierarchy() const
Return the full schematic flattened hierarchical sheet list.
void SetProject(PROJECT *aPrj)
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, RESOLUTION_CONTEXT aContext, const wxString &aVariantName=wxEmptyString, int aDepth=0) 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
const wxString GetFootprintFieldText(const SCH_SHEET_PATH *aPath, RESOLUTION_CONTEXT aContext, const wxString &aVariantName=wxEmptyString) 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
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.
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_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I