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 ) );
192 wxString archivePath = makeTempElibz2Path( wxS(
"easyedapro_v3_tombstone" ) );
194 const std::string footprintIndex = R
"({
204 const std::string elibu =
205 "{\"type\":\"DOCHEAD\"}||{\"docType\":\"FOOTPRINT\",\"uuid\":\"fp_tombstone\"}|\n"
206 "{\"type\":\"POLY\",\"id\":\"p1\",\"ticket\":1}||{\"path\":[[0,0],[10,10]],\"width\":1}|\n"
207 "{\"type\":\"POLY\",\"id\":\"p1\",\"ticket\":2}|||\n"
208 "{\"type\":\"POLY\",\"id\":\"p2\",\"ticket\":1}|||\n"
209 "{\"type\":\"POLY\",\"id\":\"p2\",\"ticket\":2}||{\"path\":[[0,0],[10,10]],\"width\":1}|\n"
210 "{\"type\":\"FILL\",\"id\":\"p3\",\"ticket\":1}||{\"path\":[[0,0],[10,10]]}|\n";
212 writeV3LibraryArchive( archivePath, std::string(), footprintIndex, elibu );
222 BOOST_CHECK( !row.
inner.is_null() );
225 BOOST_CHECK( !doc->
rowById.contains( wxS(
"p1" ) ) );
239 BOOST_CHECK( wxRemoveFile( archivePath ) );
246 BOOST_REQUIRE_NO_THROW(
v3.Load() );
254 bool hasAtLeastOneSheet =
false;
256 for(
const auto& [schUuid, sch] :
project.at(
"schematics" ).items() )
258 if( sch.contains(
"sheets" ) && sch.at(
"sheets" ).is_array()
259 && !sch.at(
"sheets" ).empty() )
261 hasAtLeastOneSheet =
true;
266 BOOST_CHECK( hasAtLeastOneSheet );
273 BOOST_REQUIRE_NO_THROW(
v3.Load() );
275 const auto& pages =
v3.GetRawDocs( wxS(
"SCH_PAGE" ) );
278 bool hasTextValue =
false;
279 bool hasLineStartEnd =
false;
280 bool hasWireNetAttr =
false;
282 for(
const auto& [pageUuid, pageDoc] : pages )
286 if( row.
type == wxS(
"TEXT" ) && row.
inner.contains(
"value" ) )
289 if( row.
type == wxS(
"LINE" )
290 && row.
inner.contains(
"startX" ) && row.
inner.contains(
"startY" )
291 && row.
inner.contains(
"endX" ) && row.
inner.contains(
"endY" ) )
293 hasLineStartEnd =
true;
296 if( row.
type == wxS(
"ATTR" )
299 hasWireNetAttr =
true;
304 BOOST_CHECK( hasTextValue );
305 BOOST_CHECK( hasLineStartEnd );
306 BOOST_CHECK( hasWireNetAttr );
313 BOOST_REQUIRE_NO_THROW(
v3.Load() );
315 const auto& symbols =
v3.GetRawDocs( wxS(
"SYMBOL" ) );
318 bool hasBlobObject =
false;
320 for(
const auto& [symbolUuid, symbolDoc] : symbols )
324 if( row.
type != wxS(
"OBJ" ) )
329 if( content.StartsWith( wxS(
"blob:" ) ) )
331 hasBlobObject =
true;
340 BOOST_CHECK( hasBlobObject );
347 BOOST_REQUIRE_NO_THROW(
v3.Load() );
350 std::map<wxString, EASYEDAPRO::PRJ_DEVICE> devices =
project.at(
"devices" );
354 for(
const auto& [devUuid, dev] : devices )
356 if( dev.title == aTitle )
371 wxString ls2kSymbolUuid = ls2kDevice->
attributes.at( wxS(
"Symbol" ) );
379 std::array<int, 6> unitPinCount = {};
383 int unit =
pin->GetUnit();
385 if( unit >= 1 && unit <= 5 )
386 unitPinCount[unit]++;
389 for(
int unit = 1; unit <= 5; ++unit )
390 BOOST_CHECK_MESSAGE( unitPinCount[unit] > 0,
"LS2K0300 should have pins in each unit" );
392 wxString sgmSymbolUuid = sgmDevice->
attributes.at( wxS(
"Symbol" ) );
399 bool hasLeftPins =
false;
400 bool hasRightPins =
false;
404 if(
pin->GetPosition().x < 0 )
410 else if(
pin->GetPosition().x > 0 )
418 BOOST_CHECK( hasLeftPins );
419 BOOST_CHECK( hasRightPins );
426 wxS(
"easyedapro_v3_test" ) );
428 wxFileName archiveFile( tempProject.
DirStr(),
"sample",
"epro2" );
439 BOOST_REQUIRE_NO_THROW( rootSheet = plugin->LoadSchematicFile( archiveFile.GetFullPath(),
440 &schematic,
nullptr,
nullptr ) );
468 if( !
text->GetText().IsEmpty() )
476 if( !label->
GetText().IsEmpty() )
481 BOOST_CHECK( wireCount > 0 );
482 BOOST_CHECK( textCount > 0 );
483 BOOST_CHECK( labelCount > 0 );
490 wxS(
"easyedapro_v3_test" ) );
492 wxFileName archiveFile( tempProject.
DirStr(),
"sample",
"epro2" );
499 BOOST_REQUIRE_NO_THROW(
v3.Load() );
509 std::map<wxString, int> valueCounts;
511 for(
const auto& [pageUuid, pageDoc] :
v3.GetRawDocs( wxS(
"SCH_PAGE" ) ) )
515 if( row.
type != wxS(
"ATTR" ) )
527 if( value.IsEmpty() )
530 valueCounts[value]++;
535 if( align.StartsWith( wxS(
"RIGHT" ) ) )
536 candidates.push_back( { value, align, rotation } );
546 if( valueCounts[candidate.value] == 1 )
548 selected = candidate;
555 if( selected.align.StartsWith( wxS(
"RIGHT" ) ) )
557 else if( selected.align.StartsWith( wxS(
"LEFT" ) ) )
560 int rot = selected.rotation % 360;
567 for(
int i = 0; i < rot; i += 90 )
568 expectedSpinStyle = expectedSpinStyle.
RotateCCW();
579 BOOST_REQUIRE_NO_THROW( rootSheet =
580 plugin->LoadSchematicFile( archiveFile.GetFullPath(),
581 &schematic,
nullptr,
nullptr ) );
587 bool hasExpectedSpinStyle =
false;
600 if( label->
GetText() != selected.value )
605 if(
static_cast<int>( label->
GetSpinStyle() ) ==
static_cast<int>( expectedSpinStyle ) )
606 hasExpectedSpinStyle =
true;
611 BOOST_CHECK_MESSAGE( hasExpectedSpinStyle,
612 wxString::Format(
"Label '%s' spin style mismatch", selected.value ) );
619 wxS(
"easyedapro_v3_test" ) );
621 wxFileName archiveFile( tempProject.
DirStr(),
"sample",
"epro2" );
627 BOOST_REQUIRE_NO_THROW(
v3.Load() );
629 std::set<wxString> netportSymbolUuids;
631 for(
const auto& [uuid, symDoc] :
v3.GetRawDocs( wxS(
"SYMBOL" ) ) )
635 if( row.
type == wxS(
"META" )
638 netportSymbolUuids.insert( uuid );
646 std::set<wxString> netportComponentIds;
648 for(
const auto& [pageUuid, pageDoc] :
v3.GetRawDocs( wxS(
"SCH_PAGE" ) ) )
652 if( row.
type != wxS(
"ATTR" ) )
660 if( netportSymbolUuids.count( symbolUuid ) )
667 std::set<wxString> expectedNetportNames;
669 for(
const auto& [pageUuid, pageDoc] :
v3.GetRawDocs( wxS(
"SCH_PAGE" ) ) )
673 if( row.
type != wxS(
"ATTR" ) )
678 if( !netportComponentIds.count( parentId ) )
685 row.
inner.value(
"value", nlohmann::json() ) );
687 if( !
name.IsEmpty() )
688 expectedNetportNames.insert(
name );
702 BOOST_REQUIRE_NO_THROW( rootSheet =
703 plugin->LoadSchematicFile( archiveFile.GetFullPath(),
704 &schematic,
nullptr,
nullptr ) );
709 int netportSymbolCount = 0;
710 int globalLabelCount = 0;
711 bool hasInputLabel =
false;
712 bool hasOutputLabel =
false;
713 bool hasBidiLabel =
false;
715 std::set<wxString> importedGlobalLabelNames;
727 wxString ref = symbol->
GetRef( &sheetPath,
false );
729 if( ref.StartsWith( wxS(
"#NET" ) ) )
730 netportSymbolCount++;
737 if( label->
GetText().IsEmpty() )
741 importedGlobalLabelNames.insert( label->
GetText() );
754 BOOST_CHECK( globalLabelCount > 0 );
757 BOOST_CHECK( hasInputLabel );
758 BOOST_CHECK( hasOutputLabel );
759 BOOST_CHECK( hasBidiLabel );
762 for(
const wxString&
expected : expectedNetportNames )
764 BOOST_CHECK_MESSAGE( importedGlobalLabelNames.count(
expected ) > 0,
765 wxString::Format(
"Missing global label '%s'",
expected ) );
773 wxS(
"easyedapro_v3_test" ) );
775 wxFileName archiveFile( tempProject.
DirStr(),
"sample",
"epro2" );
786 BOOST_REQUIRE_NO_THROW( rootSheet =
787 plugin->LoadSchematicFile( archiveFile.GetFullPath(),
788 &schematic,
nullptr,
nullptr ) );
807 wxString ref = symbol->
GetRef( &sheetPath,
false );
809 if( ref == wxS(
"U6" ) )
811 else if( ref == wxS(
"U7" ) )
813 else if( ref == wxS(
"U1" ) )
823 "U7 should remain above U6 after EasyEDA Pro v3 import" );
828 BOOST_CHECK( !u6Footprint.IsEmpty() );
829 BOOST_CHECK( u6Footprint.Contains( wxS(
"SOT-563-6" ) ) );
831 BOOST_CHECK( !u1Footprint.IsEmpty() );
832 BOOST_CHECK( u1Footprint.Contains( wxS(
"BGA-286_17x17_12.0x12.0mm" ) ) );
839 wxS(
"easyedapro_v3_test" ) );
841 wxFileName archiveFile( tempProject.
DirStr(),
"sample",
"epro2" );
852 BOOST_REQUIRE_NO_THROW( rootSheet =
853 plugin->LoadSchematicFile( archiveFile.GetFullPath(),
854 &schematic,
nullptr,
nullptr ) );
860 int plus5vShownCount = 0;
861 int plus5vOffsetCount = 0;
862 int plus5vUprightCount = 0;
863 int plus5vUprightAtSymbolXCount = 0;
864 int powerValueOnPinBodySideCheckedCount = 0;
865 int powerValueOnWrongPinSideCount = 0;
866 int otherPowerValueCount = 0;
867 int otherPowerShownCount = 0;
890 if( valueField->
IsVisible() && !valueText.IsEmpty() )
896 std::vector<SCH_PIN*> pins = symbol->
GetPins( &sheetPath );
902 switch( pins[0]->PinDrawOrient( symbol->
GetTransform() ) )
913 long long dot =
static_cast<long long>(
delta.x ) * dir.
x
914 +
static_cast<long long>(
delta.y ) * dir.
y;
916 powerValueOnPinBodySideCheckedCount++;
919 powerValueOnWrongPinSideCount++;
925 if( valueText == wxS(
"+5V" ) )
937 plus5vUprightCount++;
940 plus5vUprightAtSymbolXCount++;
946 else if( valueText.StartsWith( wxS(
"+" ) ) )
948 otherPowerValueCount++;
951 otherPowerShownCount++;
956 BOOST_CHECK( plus5vCount > 0 );
958 BOOST_CHECK( plus5vOffsetCount > 0 );
959 BOOST_CHECK( plus5vUprightCount > 0 );
962 BOOST_CHECK( powerValueOnPinBodySideCheckedCount > 0 );
965 BOOST_CHECK( otherPowerValueCount > 0 );
977 wxS(
"easyedapro_v3_test" ) );
979 wxFileName archiveFile( tempProject.
DirStr(),
"sample",
"epro2" );
986 SCH_SHEET* rootSheet = plugin->LoadSchematicFile( archiveFile.GetFullPath(), &schematic );
993 (void) item, rootSymbols++;
996 BOOST_CHECK( rootSymbols > 0 );
const wxString & GetPageNumber() const
Parses EasyEDA Pro v3 .epro2 archives.
const V3_DOC_RAW * FindRawDoc(const wxString &aDocType, const wxString &aUuid) const
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.
int GetUnitCount() const override
std::vector< SCH_PIN * > GetGraphicalPins(int aUnit=0, int aBodyStyle=0) const override
Graphical pins: Return schematic pin objects as drawn (unexpanded), filtered by unit/body.
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.
Spin style for labels of all kinds on schematics.
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.
std::map< wxString, size_t > rowById
std::vector< V3_ROW > rows
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)
static std::vector< int > candidates(const SEGMENT_INDEX &aIndex, const SEG &aQuery, int aPadding)
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I