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