KiCad PCB EDA Suite
Loading...
Searching...
No Matches
eeschema/test_easyedapro_v3_import.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, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
28
29#include <boost/test/unit_test.hpp>
30
31#include <array>
32#include <set>
33
35
38
40
41#include <pgm_base.h>
42#include <schematic.h>
43#include <sch_io/sch_io.h>
44#include <sch_io/sch_io_mgr.h>
45#include <sch_label.h>
46#include <sch_line.h>
47#include <sch_pin.h>
48#include <sch_screen.h>
49#include <sch_symbol.h>
50#include <sch_text.h>
51
53
54#include <wx/filefn.h>
55#include <wx/filename.h>
56#include <wx/wfstream.h>
57#include <wx/zipstrm.h>
58
59
60namespace
61{
62
63static wxString getEasyEdaProV3ArchivePath()
64{
65 return wxString::FromUTF8( KI_TEST::GetTestDataRootDir()
66 + "pcbnew/plugins/easyedapro/ProProject_LS2K0300Core_2025-11-14.epro2" );
67}
68
69
70static void writeZipEntry( wxZipOutputStream& aZip, const wxString& aName, const std::string& aContent )
71{
72 aZip.PutNextEntry( aName );
73 aZip.Write( aContent.data(), aContent.size() );
74 aZip.CloseEntry();
75}
76
77
78static wxString makeTempElibz2Path( const wxString& aPrefix )
79{
80 wxString tempPath = wxFileName::CreateTempFileName( aPrefix );
81 wxRemoveFile( tempPath );
82
83 wxFileName fn( tempPath );
84 fn.SetExt( wxS( "elibz2" ) );
85
86 return fn.GetFullPath();
87}
88
89
90static void writeV3LibraryArchive( const wxString& aPath, const std::string& aSymbolIndex,
91 const std::string& aFootprintIndex, const std::string& aElibu )
92{
93 wxFFileOutputStream out( aPath );
94 BOOST_REQUIRE( out.IsOk() );
95
96 wxZipOutputStream zip( out, -1, wxConvUTF8 );
97
98 if( !aSymbolIndex.empty() )
99 writeZipEntry( zip, wxS( "symbol2.json" ), aSymbolIndex );
100
101 if( !aFootprintIndex.empty() )
102 writeZipEntry( zip, wxS( "footprint2.json" ), aFootprintIndex );
103
104 writeZipEntry( zip, wxS( "library.elibu" ), aElibu );
105}
106
107} // namespace
108
109
110BOOST_AUTO_TEST_SUITE( EasyEdaProV3Import )
111
112
113BOOST_AUTO_TEST_CASE( LibraryIndexMergeKeepsSymbolAndFootprintNames )
114{
115 wxString archivePath = makeTempElibz2Path( wxS( "easyedapro_v3_mixed" ) );
116
117 const std::string symbolIndex = R"({
118 "devices": {},
119 "symbols": {
120 "sym_uuid": { "uuid": "sym_uuid", "display_title": "SymbolName" }
121 },
122 "footprints": {},
123 "panelLibs": {}
124 })";
125
126 const std::string footprintIndex = R"({
127 "devices": {},
128 "symbols": {},
129 "footprints": {
130 "fp_uuid": { "uuid": "fp_uuid", "display_title": "FootprintName" }
131 },
132 "panelLibs": {}
133 })";
134
135 const std::string elibu = "{\"type\":\"DOCHEAD\"}||{\"docType\":\"SYMBOL\",\"uuid\":\"sym_uuid\"}|\n"
136 "{\"type\":\"DOCHEAD\"}||{\"docType\":\"FOOTPRINT\",\"uuid\":\"fp_uuid\"}|\n";
137
138 writeV3LibraryArchive( archivePath, symbolIndex, footprintIndex, elibu );
139
140 EASYEDAPRO::V3_DOC_PARSER parser( archivePath );
141 BOOST_REQUIRE_NO_THROW( parser.LoadLibrary() );
142
143 std::map<wxString, wxString> symbols = EASYEDAPRO::BuildV3LibraryItemMap( parser, "symbols", wxS( "SYMBOL" ) );
144 std::map<wxString, wxString> footprints =
145 EASYEDAPRO::BuildV3LibraryItemMap( parser, "footprints", wxS( "FOOTPRINT" ) );
146
147 BOOST_CHECK_EQUAL( symbols.at( wxS( "SymbolName" ) ), wxString( wxS( "sym_uuid" ) ) );
148 BOOST_CHECK_EQUAL( footprints.at( wxS( "FootprintName" ) ), wxString( wxS( "fp_uuid" ) ) );
149
150 BOOST_CHECK( wxRemoveFile( archivePath ) );
151}
152
153
154BOOST_AUTO_TEST_CASE( LibraryItemDuplicateNamesGetNumericSuffix )
155{
156 wxString archivePath = makeTempElibz2Path( wxS( "easyedapro_v3_dupes" ) );
157
158 const std::string symbolIndex = R"({
159 "devices": {},
160 "symbols": {
161 "aaaaaaaa0001": { "uuid": "aaaaaaaa0001", "display_title": "DUP" },
162 "aaaaaaaa0002": { "uuid": "aaaaaaaa0002", "display_title": "DUP" },
163 "aaaaaaaa0003": { "uuid": "aaaaaaaa0003", "display_title": "DUP" }
164 },
165 "footprints": {},
166 "panelLibs": {}
167 })";
168
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";
172
173 writeV3LibraryArchive( archivePath, symbolIndex, std::string(), elibu );
174
175 EASYEDAPRO::V3_DOC_PARSER parser( archivePath );
176 BOOST_REQUIRE_NO_THROW( parser.LoadLibrary() );
177
178 std::map<wxString, wxString> symbols = EASYEDAPRO::BuildV3LibraryItemMap( parser, "symbols", wxS( "SYMBOL" ) );
179
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" ) ) );
184
185 BOOST_CHECK( wxRemoveFile( archivePath ) );
186}
187
188
189BOOST_AUTO_TEST_CASE( BuildProjectIndexHasSchematicSheets )
190{
192 BOOST_REQUIRE_NO_THROW( v3.Load() );
193
195
196 BOOST_REQUIRE( project.contains( "schematics" ) );
197 BOOST_REQUIRE( project.at( "schematics" ).is_object() );
198 BOOST_REQUIRE( !project.at( "schematics" ).empty() );
199
200 bool hasAtLeastOneSheet = false;
201
202 for( const auto& [schUuid, sch] : project.at( "schematics" ).items() )
203 {
204 if( sch.contains( "sheets" ) && sch.at( "sheets" ).is_array()
205 && !sch.at( "sheets" ).empty() )
206 {
207 hasAtLeastOneSheet = true;
208 break;
209 }
210 }
211
212 BOOST_CHECK( hasAtLeastOneSheet );
213}
214
215
216BOOST_AUTO_TEST_CASE( RawSchematicPageContainsTextWireAndNetAttr )
217{
219 BOOST_REQUIRE_NO_THROW( v3.Load() );
220
221 const auto& pages = v3.GetRawDocs( wxS( "SCH_PAGE" ) );
222 BOOST_REQUIRE( !pages.empty() );
223
224 bool hasTextValue = false;
225 bool hasLineStartEnd = false;
226 bool hasWireNetAttr = false;
227
228 for( const auto& [pageUuid, pageDoc] : pages )
229 {
230 for( const EASYEDAPRO::V3_ROW& row : pageDoc.rows )
231 {
232 if( row.type == wxS( "TEXT" ) && row.inner.contains( "value" ) )
233 hasTextValue = true;
234
235 if( row.type == wxS( "LINE" )
236 && row.inner.contains( "startX" ) && row.inner.contains( "startY" )
237 && row.inner.contains( "endX" ) && row.inner.contains( "endY" ) )
238 {
239 hasLineStartEnd = true;
240 }
241
242 if( row.type == wxS( "ATTR" )
243 && EASYEDAPRO::V3GetString( row.inner, "key" ) == wxS( "NET" ) )
244 {
245 hasWireNetAttr = true;
246 }
247 }
248 }
249
250 BOOST_CHECK( hasTextValue );
251 BOOST_CHECK( hasLineStartEnd );
252 BOOST_CHECK( hasWireNetAttr );
253}
254
255
256BOOST_AUTO_TEST_CASE( RawSymbolContainsBlobObjectReference )
257{
259 BOOST_REQUIRE_NO_THROW( v3.Load() );
260
261 const auto& symbols = v3.GetRawDocs( wxS( "SYMBOL" ) );
262 BOOST_REQUIRE( !symbols.empty() );
263
264 bool hasBlobObject = false;
265
266 for( const auto& [symbolUuid, symbolDoc] : symbols )
267 {
268 for( const EASYEDAPRO::V3_ROW& row : symbolDoc.rows )
269 {
270 if( row.type != wxS( "OBJ" ) )
271 continue;
272
273 wxString content = EASYEDAPRO::V3GetString( row.inner, "content" );
274
275 if( content.StartsWith( wxS( "blob:" ) ) )
276 {
277 hasBlobObject = true;
278 break;
279 }
280 }
281
282 if( hasBlobObject )
283 break;
284 }
285
286 BOOST_CHECK( hasBlobObject );
287}
288
289
290BOOST_AUTO_TEST_CASE( RawSymbolPartUnitsAndPinOrientationAreStable )
291{
293 BOOST_REQUIRE_NO_THROW( v3.Load() );
294
296 std::map<wxString, EASYEDAPRO::PRJ_DEVICE> devices = project.at( "devices" );
297
298 auto findDevice = [&]( const wxString& aTitle ) -> const EASYEDAPRO::PRJ_DEVICE*
299 {
300 for( const auto& [devUuid, dev] : devices )
301 {
302 if( dev.title == aTitle )
303 return &dev;
304 }
305
306 return nullptr;
307 };
308
309 const EASYEDAPRO::PRJ_DEVICE* ls2kDevice = findDevice( wxS( "LS2K0300" ) );
310 BOOST_REQUIRE( ls2kDevice );
311
312 const EASYEDAPRO::PRJ_DEVICE* sgmDevice = findDevice( wxS( "SGM61032BXKB6G/TR" ) );
313 BOOST_REQUIRE( sgmDevice );
314
315 SCH_EASYEDAPRO_V3_PARSER parser( nullptr, nullptr );
316
317 wxString ls2kSymbolUuid = ls2kDevice->attributes.at( wxS( "Symbol" ) );
318 const EASYEDAPRO::V3_DOC_RAW* ls2kRaw = v3.FindRawDoc( wxS( "SYMBOL" ), ls2kSymbolUuid );
319 BOOST_REQUIRE( ls2kRaw );
320
321 EASYEDAPRO::SYM_INFO ls2kInfo = parser.ParseSymbol( *ls2kRaw, ls2kDevice->attributes );
322 BOOST_REQUIRE( ls2kInfo.libSymbol );
323 BOOST_CHECK_EQUAL( ls2kInfo.libSymbol->GetUnitCount(), 5 );
324
325 std::array<int, 6> unitPinCount = {};
326
327 for( SCH_PIN* pin : ls2kInfo.libSymbol->GetPins() )
328 {
329 int unit = pin->GetUnit();
330
331 if( unit >= 1 && unit <= 5 )
332 unitPinCount[unit]++;
333 }
334
335 for( int unit = 1; unit <= 5; ++unit )
336 BOOST_CHECK_MESSAGE( unitPinCount[unit] > 0,
337 "LS2K0300 should have pins in each unit" );
338
339 wxString sgmSymbolUuid = sgmDevice->attributes.at( wxS( "Symbol" ) );
340 const EASYEDAPRO::V3_DOC_RAW* sgmRaw = v3.FindRawDoc( wxS( "SYMBOL" ), sgmSymbolUuid );
341 BOOST_REQUIRE( sgmRaw );
342
343 EASYEDAPRO::SYM_INFO sgmInfo = parser.ParseSymbol( *sgmRaw, sgmDevice->attributes );
344 BOOST_REQUIRE( sgmInfo.libSymbol );
345
346 bool hasLeftPins = false;
347 bool hasRightPins = false;
348
349 for( SCH_PIN* pin : sgmInfo.libSymbol->GetPins() )
350 {
351 if( pin->GetPosition().x < 0 )
352 {
353 hasLeftPins = true;
354 BOOST_CHECK_EQUAL( static_cast<int>( pin->GetOrientation() ),
355 static_cast<int>( PIN_ORIENTATION::PIN_RIGHT ) );
356 }
357 else if( pin->GetPosition().x > 0 )
358 {
359 hasRightPins = true;
360 BOOST_CHECK_EQUAL( static_cast<int>( pin->GetOrientation() ),
361 static_cast<int>( PIN_ORIENTATION::PIN_LEFT ) );
362 }
363 }
364
365 BOOST_CHECK( hasLeftPins );
366 BOOST_CHECK( hasRightPins );
367}
368
369
370BOOST_AUTO_TEST_CASE( PluginLoadProducesWireTextAndLabel )
371{
372 wxString tempDir = wxFileName::CreateTempFileName( "easyedapro_v3_import" );
373
374 BOOST_REQUIRE( wxFileExists( tempDir ) );
375 BOOST_REQUIRE( wxRemoveFile( tempDir ) );
376 BOOST_REQUIRE( wxMkdir( tempDir ) );
377
378 wxFileName projectFile( tempDir, "easyedapro_v3_test", "kicad_pro" );
379 wxFileName archiveFile( tempDir, "sample", "epro2" );
380
381 BOOST_REQUIRE( wxCopyFile( getEasyEdaProV3ArchivePath(), archiveFile.GetFullPath() ) );
382
383 Pgm().GetSettingsManager().LoadProject( projectFile.GetFullPath().ToStdString() );
384
385 SCHEMATIC schematic( nullptr );
386 schematic.SetProject( &Pgm().GetSettingsManager().Prj() );
387
388 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_EASYEDAPRO_V3 ) );
389 BOOST_REQUIRE( plugin );
390
391 SCH_SHEET* rootSheet = nullptr;
392 BOOST_REQUIRE_NO_THROW( rootSheet =
393 plugin->LoadSchematicFile( archiveFile.GetFullPath(),
394 &schematic, nullptr, nullptr ) );
395 BOOST_REQUIRE( rootSheet );
396
397 schematic.RefreshHierarchy();
398
399 int wireCount = 0;
400 int textCount = 0;
401 int labelCount = 0;
402
403 for( const SCH_SHEET_PATH& sheetPath : schematic.Hierarchy() )
404 {
405 SCH_SCREEN* screen = sheetPath.LastScreen();
406
407 if( !screen )
408 continue;
409
410 for( SCH_ITEM* item : screen->Items().OfType( SCH_LINE_T ) )
411 {
412 SCH_LINE* line = static_cast<SCH_LINE*>( item );
413
414 if( line->GetLayer() == LAYER_WIRE )
415 wireCount++;
416 }
417
418 for( SCH_ITEM* item : screen->Items().OfType( SCH_TEXT_T ) )
419 {
420 SCH_TEXT* text = static_cast<SCH_TEXT*>( item );
421
422 if( !text->GetText().IsEmpty() )
423 textCount++;
424 }
425
426 for( SCH_ITEM* item : screen->Items().OfType( SCH_LABEL_T ) )
427 {
428 SCH_LABEL* label = static_cast<SCH_LABEL*>( item );
429
430 if( !label->GetText().IsEmpty() )
431 labelCount++;
432 }
433 }
434
435 BOOST_CHECK( wireCount > 0 );
436 BOOST_CHECK( textCount > 0 );
437 BOOST_CHECK( labelCount > 0 );
438
439 BOOST_CHECK( wxFileName::Rmdir( tempDir, wxPATH_RMDIR_RECURSIVE ) );
440}
441
442
443BOOST_AUTO_TEST_CASE( PluginLoadPreservesWireNetLabelAlignment )
444{
445 wxString tempDir = wxFileName::CreateTempFileName( "easyedapro_v3_import" );
446
447 BOOST_REQUIRE( wxFileExists( tempDir ) );
448 BOOST_REQUIRE( wxRemoveFile( tempDir ) );
449 BOOST_REQUIRE( wxMkdir( tempDir ) );
450
451 wxFileName projectFile( tempDir, "easyedapro_v3_test", "kicad_pro" );
452 wxFileName archiveFile( tempDir, "sample", "epro2" );
453
454 BOOST_REQUIRE( wxCopyFile( getEasyEdaProV3ArchivePath(), archiveFile.GetFullPath() ) );
455
456 // Pick a stable, unique NET label from raw docs where align indicates the text is anchored
457 // on the right side of the connection point.
458 EASYEDAPRO::V3_DOC_PARSER v3( archiveFile.GetFullPath() );
459 BOOST_REQUIRE_NO_THROW( v3.Load() );
460
461 struct NET_ATTR
462 {
463 wxString value;
464 wxString align;
465 int rotation;
466 };
467
468 std::vector<NET_ATTR> candidates;
469 std::map<wxString, int> valueCounts;
470
471 for( const auto& [pageUuid, pageDoc] : v3.GetRawDocs( wxS( "SCH_PAGE" ) ) )
472 {
473 for( const EASYEDAPRO::V3_ROW& row : pageDoc.rows )
474 {
475 if( row.type != wxS( "ATTR" ) )
476 continue;
477
478 if( EASYEDAPRO::V3GetString( row.inner, "key" ) != wxS( "NET" ) )
479 continue;
480
481 if( !EASYEDAPRO::V3GetBool( row.inner, "valueVisible" ) )
482 continue;
483
484 wxString value =
485 EASYEDAPRO::V3JsonToString( row.inner.value( "value", nlohmann::json() ) );
486
487 if( value.IsEmpty() )
488 continue;
489
490 valueCounts[value]++;
491
492 wxString align = EASYEDAPRO::V3GetString( row.inner, "align", wxS( "LEFT_BOTTOM" ) );
493 int rotation = EASYEDAPRO::V3GetInt( row.inner, "rotation", 0 );
494
495 if( align.StartsWith( wxS( "RIGHT" ) ) )
496 candidates.push_back( { value, align, rotation } );
497 }
498 }
499
500 BOOST_REQUIRE( !candidates.empty() );
501
502 NET_ATTR selected = candidates.front();
503
504 for( const NET_ATTR& candidate : candidates )
505 {
506 if( valueCounts[candidate.value] == 1 )
507 {
508 selected = candidate;
509 break;
510 }
511 }
512
513 SPIN_STYLE expectedSpinStyle = SPIN_STYLE::RIGHT;
514
515 if( selected.align.StartsWith( wxS( "RIGHT" ) ) )
516 expectedSpinStyle = SPIN_STYLE::LEFT;
517 else if( selected.align.StartsWith( wxS( "LEFT" ) ) )
518 expectedSpinStyle = SPIN_STYLE::RIGHT;
519
520 int rot = selected.rotation % 360;
521
522 if( rot < 0 )
523 rot += 360;
524
525 if( rot % 90 == 0 )
526 {
527 for( int i = 0; i < rot; i += 90 )
528 expectedSpinStyle = expectedSpinStyle.RotateCCW();
529 }
530
531 // Load via the plugin and validate the resulting schematic keeps label spin style.
532 Pgm().GetSettingsManager().LoadProject( projectFile.GetFullPath().ToStdString() );
533
534 SCHEMATIC schematic( nullptr );
535 schematic.SetProject( &Pgm().GetSettingsManager().Prj() );
536
537 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_EASYEDAPRO_V3 ) );
538 BOOST_REQUIRE( plugin );
539
540 SCH_SHEET* rootSheet = nullptr;
541 BOOST_REQUIRE_NO_THROW( rootSheet =
542 plugin->LoadSchematicFile( archiveFile.GetFullPath(),
543 &schematic, nullptr, nullptr ) );
544 BOOST_REQUIRE( rootSheet );
545
546 schematic.RefreshHierarchy();
547
548 int foundCount = 0;
549 bool hasExpectedSpinStyle = false;
550
551 for( const SCH_SHEET_PATH& sheetPath : schematic.Hierarchy() )
552 {
553 SCH_SCREEN* screen = sheetPath.LastScreen();
554
555 if( !screen )
556 continue;
557
558 for( SCH_ITEM* item : screen->Items().OfType( SCH_LABEL_T ) )
559 {
560 SCH_LABEL* label = static_cast<SCH_LABEL*>( item );
561
562 if( label->GetText() != selected.value )
563 continue;
564
565 foundCount++;
566
567 if( static_cast<int>( label->GetSpinStyle() ) == static_cast<int>( expectedSpinStyle ) )
568 hasExpectedSpinStyle = true;
569 }
570 }
571
572 BOOST_CHECK_EQUAL( foundCount, 1 );
573 BOOST_CHECK_MESSAGE( hasExpectedSpinStyle,
574 wxString::Format( "Label '%s' spin style mismatch", selected.value ) );
575
576 BOOST_CHECK( wxFileName::Rmdir( tempDir, wxPATH_RMDIR_RECURSIVE ) );
577}
578
579
580BOOST_AUTO_TEST_CASE( PluginLoadConvertsNetportsToGlobalLabels )
581{
582 wxString tempDir = wxFileName::CreateTempFileName( "easyedapro_v3_import" );
583
584 BOOST_REQUIRE( wxFileExists( tempDir ) );
585 BOOST_REQUIRE( wxRemoveFile( tempDir ) );
586 BOOST_REQUIRE( wxMkdir( tempDir ) );
587
588 wxFileName projectFile( tempDir, "easyedapro_v3_test", "kicad_pro" );
589 wxFileName archiveFile( tempDir, "sample", "epro2" );
590
591 BOOST_REQUIRE( wxCopyFile( getEasyEdaProV3ArchivePath(), archiveFile.GetFullPath() ) );
592
593 // Extract expected netport instance names from the raw v3 docs.
594 EASYEDAPRO::V3_DOC_PARSER v3( archiveFile.GetFullPath() );
595 BOOST_REQUIRE_NO_THROW( v3.Load() );
596
597 std::set<wxString> netportSymbolUuids;
598
599 for( const auto& [uuid, symDoc] : v3.GetRawDocs( wxS( "SYMBOL" ) ) )
600 {
601 for( const EASYEDAPRO::V3_ROW& row : symDoc.rows )
602 {
603 if( row.type == wxS( "META" )
604 && EASYEDAPRO::V3GetInt( row.inner, "docType", 0 ) == 19 )
605 {
606 netportSymbolUuids.insert( uuid );
607 break;
608 }
609 }
610 }
611
612 BOOST_REQUIRE( !netportSymbolUuids.empty() );
613
614 std::set<wxString> netportComponentIds;
615
616 for( const auto& [pageUuid, pageDoc] : v3.GetRawDocs( wxS( "SCH_PAGE" ) ) )
617 {
618 for( const EASYEDAPRO::V3_ROW& row : pageDoc.rows )
619 {
620 if( row.type != wxS( "ATTR" ) )
621 continue;
622
623 if( EASYEDAPRO::V3GetString( row.inner, "key" ) != wxS( "Symbol" ) )
624 continue;
625
626 wxString symbolUuid = EASYEDAPRO::V3GetString( row.inner, "value" );
627
628 if( netportSymbolUuids.count( symbolUuid ) )
629 netportComponentIds.insert( EASYEDAPRO::V3GetString( row.inner, "parentId" ) );
630 }
631 }
632
633 BOOST_REQUIRE( !netportComponentIds.empty() );
634
635 std::set<wxString> expectedNetportNames;
636
637 for( const auto& [pageUuid, pageDoc] : v3.GetRawDocs( wxS( "SCH_PAGE" ) ) )
638 {
639 for( const EASYEDAPRO::V3_ROW& row : pageDoc.rows )
640 {
641 if( row.type != wxS( "ATTR" ) )
642 continue;
643
644 wxString parentId = EASYEDAPRO::V3GetString( row.inner, "parentId" );
645
646 if( !netportComponentIds.count( parentId ) )
647 continue;
648
649 if( EASYEDAPRO::V3GetString( row.inner, "key" ) != wxS( "Name" ) )
650 continue;
651
653 row.inner.value( "value", nlohmann::json() ) );
654
655 if( !name.IsEmpty() )
656 expectedNetportNames.insert( name );
657 }
658 }
659
660 BOOST_REQUIRE( !expectedNetportNames.empty() );
661
662 // Load via the plugin and validate the resulting schematic contains global labels (not symbols).
663 Pgm().GetSettingsManager().LoadProject( projectFile.GetFullPath().ToStdString() );
664
665 SCHEMATIC schematic( nullptr );
666 schematic.SetProject( &Pgm().GetSettingsManager().Prj() );
667
668 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_EASYEDAPRO_V3 ) );
669 BOOST_REQUIRE( plugin );
670
671 SCH_SHEET* rootSheet = nullptr;
672 BOOST_REQUIRE_NO_THROW( rootSheet =
673 plugin->LoadSchematicFile( archiveFile.GetFullPath(),
674 &schematic, nullptr, nullptr ) );
675 BOOST_REQUIRE( rootSheet );
676
677 schematic.RefreshHierarchy();
678
679 int netportSymbolCount = 0;
680 int globalLabelCount = 0;
681 bool hasInputLabel = false;
682 bool hasOutputLabel = false;
683 bool hasBidiLabel = false;
684
685 std::set<wxString> importedGlobalLabelNames;
686
687 for( const SCH_SHEET_PATH& sheetPath : schematic.Hierarchy() )
688 {
689 SCH_SCREEN* screen = sheetPath.LastScreen();
690
691 if( !screen )
692 continue;
693
694 for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
695 {
696 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
697 wxString ref = symbol->GetRef( &sheetPath, false );
698
699 if( ref.StartsWith( wxS( "#NET" ) ) )
700 netportSymbolCount++;
701 }
702
703 for( SCH_ITEM* item : screen->Items().OfType( SCH_GLOBAL_LABEL_T ) )
704 {
705 SCH_GLOBALLABEL* label = static_cast<SCH_GLOBALLABEL*>( item );
706
707 if( label->GetText().IsEmpty() )
708 continue;
709
710 globalLabelCount++;
711 importedGlobalLabelNames.insert( label->GetText() );
712
713 switch( label->GetShape() )
714 {
715 case LABEL_FLAG_SHAPE::L_INPUT: hasInputLabel = true; break;
716 case LABEL_FLAG_SHAPE::L_OUTPUT: hasOutputLabel = true; break;
717 case LABEL_FLAG_SHAPE::L_BIDI: hasBidiLabel = true; break;
718 default: break;
719 }
720 }
721 }
722
723 BOOST_CHECK_EQUAL( netportSymbolCount, 0 );
724 BOOST_CHECK( globalLabelCount > 0 );
725
726 // The test project contains Netport-IN/OUT/BI symbols; ensure we preserve their direction.
727 BOOST_CHECK( hasInputLabel );
728 BOOST_CHECK( hasOutputLabel );
729 BOOST_CHECK( hasBidiLabel );
730
731 // All netport instance names should import as global labels.
732 for( const wxString& expected : expectedNetportNames )
733 {
734 BOOST_CHECK_MESSAGE( importedGlobalLabelNames.count( expected ) > 0,
735 wxString::Format( "Missing global label '%s'", expected ) );
736 }
737
738 BOOST_CHECK( wxFileName::Rmdir( tempDir, wxPATH_RMDIR_RECURSIVE ) );
739}
740
741
742BOOST_AUTO_TEST_CASE( PluginLoadKeepsSchematicVerticalOrderAndFootprints )
743{
744 wxString tempDir = wxFileName::CreateTempFileName( "easyedapro_v3_import" );
745
746 BOOST_REQUIRE( wxFileExists( tempDir ) );
747 BOOST_REQUIRE( wxRemoveFile( tempDir ) );
748 BOOST_REQUIRE( wxMkdir( tempDir ) );
749
750 wxFileName projectFile( tempDir, "easyedapro_v3_test", "kicad_pro" );
751 wxFileName archiveFile( tempDir, "sample", "epro2" );
752
753 BOOST_REQUIRE( wxCopyFile( getEasyEdaProV3ArchivePath(), archiveFile.GetFullPath() ) );
754
755 Pgm().GetSettingsManager().LoadProject( projectFile.GetFullPath().ToStdString() );
756
757 SCHEMATIC schematic( nullptr );
758 schematic.SetProject( &Pgm().GetSettingsManager().Prj() );
759
760 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_EASYEDAPRO_V3 ) );
761 BOOST_REQUIRE( plugin );
762
763 SCH_SHEET* rootSheet = nullptr;
764 BOOST_REQUIRE_NO_THROW( rootSheet =
765 plugin->LoadSchematicFile( archiveFile.GetFullPath(),
766 &schematic, nullptr, nullptr ) );
767 BOOST_REQUIRE( rootSheet );
768
769 schematic.RefreshHierarchy();
770
771 SCH_SYMBOL* u6 = nullptr;
772 SCH_SYMBOL* u7 = nullptr;
773 SCH_SYMBOL* u1 = nullptr;
774
775 for( const SCH_SHEET_PATH& sheetPath : schematic.Hierarchy() )
776 {
777 SCH_SCREEN* screen = sheetPath.LastScreen();
778
779 if( !screen )
780 continue;
781
782 for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
783 {
784 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
785 wxString ref = symbol->GetRef( &sheetPath, false );
786
787 if( ref == wxS( "U6" ) )
788 u6 = symbol;
789 else if( ref == wxS( "U7" ) )
790 u7 = symbol;
791 else if( ref == wxS( "U1" ) )
792 u1 = symbol;
793 }
794 }
795
796 BOOST_REQUIRE( u6 );
797 BOOST_REQUIRE( u7 );
798 BOOST_REQUIRE( u1 );
799
801 "U7 should remain above U6 after EasyEDA Pro v3 import" );
802
803 wxString u6Footprint = u6->GetFootprintFieldText( false, nullptr, false );
804 wxString u1Footprint = u1->GetFootprintFieldText( false, nullptr, false );
805
806 BOOST_CHECK( !u6Footprint.IsEmpty() );
807 BOOST_CHECK( u6Footprint.Contains( wxS( "SOT-563-6" ) ) );
808
809 BOOST_CHECK( !u1Footprint.IsEmpty() );
810 BOOST_CHECK( u1Footprint.Contains( wxS( "BGA-286_17x17_12.0x12.0mm" ) ) );
811
812 BOOST_CHECK( wxFileName::Rmdir( tempDir, wxPATH_RMDIR_RECURSIVE ) );
813}
814
815
816BOOST_AUTO_TEST_CASE( PluginLoadPowerSymbolsKeepValueAndVisibility )
817{
818 wxString tempDir = wxFileName::CreateTempFileName( "easyedapro_v3_import" );
819
820 BOOST_REQUIRE( wxFileExists( tempDir ) );
821 BOOST_REQUIRE( wxRemoveFile( tempDir ) );
822 BOOST_REQUIRE( wxMkdir( tempDir ) );
823
824 wxFileName projectFile( tempDir, "easyedapro_v3_test", "kicad_pro" );
825 wxFileName archiveFile( tempDir, "sample", "epro2" );
826
827 BOOST_REQUIRE( wxCopyFile( getEasyEdaProV3ArchivePath(), archiveFile.GetFullPath() ) );
828
829 Pgm().GetSettingsManager().LoadProject( projectFile.GetFullPath().ToStdString() );
830
831 SCHEMATIC schematic( nullptr );
832 schematic.SetProject( &Pgm().GetSettingsManager().Prj() );
833
834 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_EASYEDAPRO_V3 ) );
835 BOOST_REQUIRE( plugin );
836
837 SCH_SHEET* rootSheet = nullptr;
838 BOOST_REQUIRE_NO_THROW( rootSheet =
839 plugin->LoadSchematicFile( archiveFile.GetFullPath(),
840 &schematic, nullptr, nullptr ) );
841 BOOST_REQUIRE( rootSheet );
842
843 schematic.RefreshHierarchy();
844
845 int plus5vCount = 0;
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;
854
855 for( const SCH_SHEET_PATH& sheetPath : schematic.Hierarchy() )
856 {
857 SCH_SCREEN* screen = sheetPath.LastScreen();
858
859 if( !screen )
860 continue;
861
862 for( SCH_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
863 {
864 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
865
866 if( !symbol->IsGlobalPower() )
867 continue;
868
869 SCH_FIELD* valueField = symbol->GetField( FIELD_T::VALUE );
870
871 if( !valueField )
872 continue;
873
874 wxString valueText = valueField->GetShownText( &sheetPath, false );
875
876 if( valueField->IsVisible() && !valueText.IsEmpty() )
877 {
878 VECTOR2I delta = valueField->GetPosition() - symbol->GetPosition();
879
880 if( delta != VECTOR2I() )
881 {
882 std::vector<SCH_PIN*> pins = symbol->GetPins( &sheetPath );
883
884 if( !pins.empty() )
885 {
886 VECTOR2I dir;
887
888 switch( pins[0]->PinDrawOrient( symbol->GetTransform() ) )
889 {
890 case PIN_ORIENTATION::PIN_LEFT: dir = VECTOR2I( -1, 0 ); break;
891 case PIN_ORIENTATION::PIN_RIGHT: dir = VECTOR2I( 1, 0 ); break;
892 case PIN_ORIENTATION::PIN_UP: dir = VECTOR2I( 0, -1 ); break;
893 case PIN_ORIENTATION::PIN_DOWN: dir = VECTOR2I( 0, 1 ); break;
894 default: dir = VECTOR2I(); break;
895 }
896
897 if( dir != VECTOR2I() )
898 {
899 long long dot = static_cast<long long>( delta.x ) * dir.x
900 + static_cast<long long>( delta.y ) * dir.y;
901
902 powerValueOnPinBodySideCheckedCount++;
903
904 if( dot < 0 )
905 powerValueOnWrongPinSideCount++;
906 }
907 }
908 }
909 }
910
911 if( valueText == wxS( "+5V" ) )
912 {
913 plus5vCount++;
914
915 if( valueField->IsVisible() )
916 plus5vShownCount++;
917
918 if( valueField->GetPosition().y != symbol->GetPosition().y )
919 plus5vOffsetCount++;
920
921 if( symbol->GetOrientation() == SYM_ORIENT_0 )
922 {
923 plus5vUprightCount++;
924
925 if( valueField->GetPosition().x == symbol->GetPosition().x )
926 plus5vUprightAtSymbolXCount++;
927
930 }
931 }
932 else if( valueText.StartsWith( wxS( "+" ) ) )
933 {
934 otherPowerValueCount++;
935
936 if( valueField->IsVisible() )
937 otherPowerShownCount++;
938 }
939 }
940 }
941
942 BOOST_CHECK( plus5vCount > 0 );
943 BOOST_CHECK_EQUAL( plus5vShownCount, plus5vCount );
944 BOOST_CHECK( plus5vOffsetCount > 0 );
945 BOOST_CHECK( plus5vUprightCount > 0 );
946 BOOST_CHECK_EQUAL( plus5vUprightAtSymbolXCount, plus5vUprightCount );
947
948 BOOST_CHECK( powerValueOnPinBodySideCheckedCount > 0 );
949 BOOST_CHECK_EQUAL( powerValueOnWrongPinSideCount, 0 );
950
951 BOOST_CHECK( otherPowerValueCount > 0 );
952 BOOST_CHECK_EQUAL( otherPowerShownCount, otherPowerValueCount );
953
954 BOOST_CHECK( wxFileName::Rmdir( tempDir, wxPATH_RMDIR_RECURSIVE ) );
955}
956
957
962BOOST_AUTO_TEST_CASE( PluginLoadFillsRootSheetWithFirstPage )
963{
964 wxString tempDir = wxFileName::CreateTempFileName( "easyedapro_v3_import" );
965 BOOST_REQUIRE( wxRemoveFile( tempDir ) );
966 BOOST_REQUIRE( wxMkdir( tempDir ) );
967
968 wxFileName projectFile( tempDir, "easyedapro_v3_test", "kicad_pro" );
969 wxFileName archiveFile( tempDir, "sample", "epro2" );
970 BOOST_REQUIRE( wxCopyFile( getEasyEdaProV3ArchivePath(), archiveFile.GetFullPath() ) );
971
972 Pgm().GetSettingsManager().LoadProject( projectFile.GetFullPath().ToStdString() );
973
974 SCHEMATIC schematic( nullptr );
975 schematic.SetProject( &Pgm().GetSettingsManager().Prj() );
976
977 IO_RELEASER<SCH_IO> plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_EASYEDAPRO_V3 ) );
978 SCH_SHEET* rootSheet = plugin->LoadSchematicFile( archiveFile.GetFullPath(), &schematic );
979 BOOST_REQUIRE( rootSheet );
980 schematic.RefreshHierarchy();
981
982 int rootSymbols = 0;
983
984 for( SCH_ITEM* item : rootSheet->GetScreen()->Items().OfType( SCH_SYMBOL_T ) )
985 (void) item, rootSymbols++;
986
987 // The root screen must carry real schematic content, not just sub-sheet links.
988 BOOST_CHECK( rootSymbols > 0 );
989 BOOST_CHECK( !rootSheet->GetScreen()->GetPageNumber().IsEmpty() );
990
991 BOOST_CHECK( wxFileName::Rmdir( tempDir, wxPATH_RMDIR_RECURSIVE ) );
992}
993
994
1000BOOST_AUTO_TEST_CASE( AlignTokenMapsToJustify )
1001{
1002 BOOST_CHECK_EQUAL( EASYEDAPRO::AlignToFontV( wxS( "CENTER_TOP" ) ), 0 );
1003 BOOST_CHECK_EQUAL( EASYEDAPRO::AlignToFontV( wxS( "CENTER_MIDDLE" ) ), 1 );
1004 BOOST_CHECK_EQUAL( EASYEDAPRO::AlignToFontV( wxS( "CENTER_BOTTOM" ) ), 2 );
1005 BOOST_CHECK_EQUAL( EASYEDAPRO::AlignToFontV( wxS( "LEFT_BOTTOM" ) ), 2 );
1006 BOOST_CHECK_EQUAL( EASYEDAPRO::AlignToFontV( wxS( "RIGHT_MIDDLE" ) ), 1 );
1007
1008 BOOST_CHECK_EQUAL( EASYEDAPRO::AlignToFontH( wxS( "CENTER_BOTTOM" ) ), 1 );
1009 BOOST_CHECK_EQUAL( EASYEDAPRO::AlignToFontH( wxS( "LEFT_BOTTOM" ) ), 0 );
1010 BOOST_CHECK_EQUAL( EASYEDAPRO::AlignToFontH( wxS( "RIGHT_TOP" ) ), 2 );
1011}
1012
1013
const char * name
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.
Definition eda_text.h:110
virtual bool IsVisible() const
Definition eda_text.h:208
GR_TEXT_H_ALIGN_T GetHorizJustify() const
Definition eda_text.h:221
GR_TEXT_V_ALIGN_T GetVertJustify() const
Definition eda_text.h:224
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:221
std::vector< SCH_PIN * > GetPins() const override
int GetUnitCount() const override
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:124
Holds all the data relating to one schematic.
Definition schematic.h:90
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,...
Definition sch_item.h:162
SCH_LAYER_ID GetLayer() const
Return the layer this item is on.
Definition sch_item.h:338
SPIN_STYLE GetSpinStyle() const
LABEL_FLAG_SHAPE GetShape() const
Definition sch_label.h:178
Segment description base class to describe items which have 2 end points (track, wire,...
Definition sch_line.h:38
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:115
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.
Definition sch_sheet.h:44
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:139
Schematic symbol object.
Definition sch_symbol.h:69
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
Definition sch_symbol.h:914
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.
SPIN_STYLE RotateCCW()
const TRANSFORM & GetTransform() const
Definition symbol.h:243
BOOST_AUTO_TEST_CASE(LibraryIndexMergeKeepsSymbolAndFootprintNames)
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:728
@ LAYER_WIRE
Definition layer_ids.h:458
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.
see class PGM_BASE
@ PIN_UP
The pin extends upwards from the connection point: Probably on the bottom side of the symbol.
Definition pin_type.h:123
@ PIN_RIGHT
The pin extends rightwards from the connection point.
Definition pin_type.h:107
@ PIN_LEFT
The pin extends leftwards from the connection point: Probably on the right side of the symbol.
Definition pin_type.h:114
@ PIN_DOWN
The pin extends downwards from the connection: Probably on the top side of the symbol.
Definition pin_type.h:131
@ L_BIDI
Definition sch_label.h:100
@ L_OUTPUT
Definition sch_label.h:99
@ L_INPUT
Definition sch_label.h:98
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.
@ SYM_ORIENT_0
Definition symbol.h:35
@ 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()
KIBIS_PIN * pin
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")
VECTOR2I v3(-2, 1)
int delta
@ GR_TEXT_H_ALIGN_CENTER
@ GR_TEXT_V_ALIGN_CENTER
@ SCH_LINE_T
Definition typeinfo.h:160
@ SCH_SYMBOL_T
Definition typeinfo.h:169
@ SCH_LABEL_T
Definition typeinfo.h:164
@ SCH_TEXT_T
Definition typeinfo.h:148
@ SCH_GLOBAL_LABEL_T
Definition typeinfo.h:165
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683