KiCad PCB EDA Suite
Loading...
Searching...
No Matches
easyedapro_import_utils.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 (C) 2023 Alex Shvartzkop <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
22#include "easyedapro_parser.h"
24
26
27#include <algorithm>
28#include <core/map_helpers.h>
29#include <ki_exception.h>
30#include <string_utils.h>
31#include <vector>
32#include <json_common.h>
34
35#include <wx/log.h>
36#include <wx/stream.h>
37#include <wx/zipstrm.h>
38#include <wx/wfstream.h>
39#include <wx/mstream.h>
40#include <wx/txtstrm.h>
41#include <trace_helpers.h>
42
43
44namespace
45{
46
47// clang-format off
48static const std::vector<wxString> c_deviceAttributesWhitelist = { wxS( "Value" ),
49 wxS( "Datasheet" ),
50 wxS( "Manufacturer Part" ),
51 wxS( "Manufacturer" ),
52 wxS( "BOM_Manufacturer Part" ),
53 wxS( "BOM_Manufacturer" ),
54 wxS( "Supplier Part" ),
55 wxS( "Supplier" ),
56 wxS( "BOM_Supplier Part" ),
57 wxS( "BOM_Supplier" ),
58 wxS( "LCSC Part Name" ) };
59// clang-format on
60
61
62static std::string ToStdString( const wxString& aStr )
63{
64 return std::string( aStr.ToUTF8() );
65}
66
67
68static wxString ToWxString( const std::string& aStr )
69{
70 return wxString::FromUTF8( aStr.c_str() );
71}
72
73
74static nlohmann::json EmptyV3ProjectIndex()
75{
76 nlohmann::json project = nlohmann::json::object();
77 project["schematics"] = nlohmann::json::object();
78 project["boards"] = nlohmann::json::object();
79 project["pcbs"] = nlohmann::json::object();
80 project["symbols"] = nlohmann::json::object();
81 project["footprints"] = nlohmann::json::object();
82 project["devices"] = nlohmann::json::object();
83
84 return project;
85}
86
87
88static const nlohmann::json* FindMetaRow( const EASYEDAPRO::V3_DOC_RAW& aDoc )
89{
90 for( const EASYEDAPRO::V3_ROW& row : aDoc.rows )
91 {
92 if( row.type == wxS( "META" ) )
93 return &row.inner;
94 }
95
96 return nullptr;
97}
98
99
100static wxString MetaGetString( const EASYEDAPRO::V3_DOC_RAW& aDoc, const char* aKey,
101 const wxString& aDefault = wxEmptyString )
102{
103 if( const nlohmann::json* meta = FindMetaRow( aDoc ) )
104 return EASYEDAPRO::V3GetString( *meta, aKey, aDefault );
105
106 return aDefault;
107}
108
109
110static int MetaGetInt( const EASYEDAPRO::V3_DOC_RAW& aDoc, const char* aKey, int aDefault = 0 )
111{
112 if( const nlohmann::json* meta = FindMetaRow( aDoc ) )
113 return EASYEDAPRO::V3GetInt( *meta, aKey, aDefault );
114
115 return aDefault;
116}
117
118
119static nlohmann::json MetaGetValue( const EASYEDAPRO::V3_DOC_RAW& aDoc, const char* aKey,
120 const nlohmann::json& aDefault )
121{
122 if( const nlohmann::json* meta = FindMetaRow( aDoc ) )
123 {
124 auto it = meta->find( aKey );
125
126 if( it != meta->end() )
127 return *it;
128 }
129
130 return aDefault;
131}
132
133} // namespace
134
135
136wxString EASYEDAPRO::ShortenLibName( wxString aProjectName )
137{
138 wxString shortenedName = aProjectName;
139 shortenedName.Replace( wxS( "ProProject_" ), wxS( "" ) );
140 shortenedName.Replace( wxS( "ProDocument_" ), wxS( "" ) );
141 shortenedName = shortenedName.substr( 0, 10 );
142
143 return LIB_ID::FixIllegalChars( shortenedName + wxS( "-easyedapro" ), true );
144}
145
146
147LIB_ID EASYEDAPRO::ToKiCadLibID( const wxString& aLibName, const wxString& aLibReference )
148{
149 wxString libName = LIB_ID::FixIllegalChars( aLibName, true );
150 wxString libReference = EscapeString( aLibReference, CTX_LIBID );
151
152 wxString key = !libName.empty() ? ( libName + ':' + libReference ) : libReference;
153
154 LIB_ID libId;
155 libId.Parse( key, true );
156
157 return libId;
158}
159
160
161std::vector<IMPORT_PROJECT_DESC>
162EASYEDAPRO::ProjectToSelectorDialog( const nlohmann::json& aProject, bool aPcbOnly, bool aSchOnly )
163{
164 std::vector<IMPORT_PROJECT_DESC> result;
165
166 std::map<wxString, EASYEDAPRO::PRJ_SCHEMATIC> prjSchematics = aProject.at( "schematics" );
167 std::map<wxString, EASYEDAPRO::PRJ_BOARD> prjBoards = aProject.at( "boards" );
168
169 std::map<wxString, wxString> prjPcbNames;
170 std::map<wxString, nlohmann::json> prjPcbs = aProject.at( "pcbs" );
171
172 for( const auto& [pcbUuid, pcbJsonEntry] : prjPcbs )
173 {
174 if( pcbJsonEntry.is_string() )
175 prjPcbNames.emplace( pcbUuid, pcbJsonEntry );
176 else if( pcbJsonEntry.is_object() )
177 prjPcbNames.emplace( pcbUuid, pcbJsonEntry.at( "title" ) );
178 }
179
180 for( const auto& [prjName, board] : prjBoards )
181 {
183 desc.ComboName = desc.ComboId = prjName;
184 desc.PCBId = board.pcb;
185 desc.SchematicId = board.schematic;
186
187 auto pcbNameIt = prjPcbNames.find( desc.PCBId );
188 if( pcbNameIt != prjPcbNames.end() )
189 {
190 desc.PCBName = pcbNameIt->second;
191
192 if( desc.PCBName.empty() )
193 desc.PCBName = pcbNameIt->first;
194
195 prjPcbNames.erase( pcbNameIt );
196 }
197
198 auto schIt = prjSchematics.find( desc.SchematicId );
199 if( schIt != prjSchematics.end() )
200 {
201 desc.SchematicName = schIt->second.name;
202
203 if( desc.SchematicName.empty() )
204 desc.SchematicName = schIt->first;
205
206 prjSchematics.erase( schIt );
207 }
208
209 result.emplace_back( desc );
210 }
211
212 if( !aSchOnly )
213 {
214 for( const auto& [pcbId, pcbName] : prjPcbNames )
215 {
217 desc.PCBId = pcbId;
218 desc.PCBName = pcbName;
219
220 if( desc.PCBName.empty() )
221 desc.PCBName = pcbId;
222
223 result.emplace_back( desc );
224 }
225 }
226
227 if( !aPcbOnly )
228 {
229 for( const auto& [schId, schData] : prjSchematics )
230 {
232 desc.SchematicId = schId;
233 desc.SchematicName = schData.name;
234
235 if( desc.SchematicName.empty() )
236 desc.SchematicName = schId;
237
238 result.emplace_back( desc );
239 }
240 }
241
242 return result;
243}
244
245
246nlohmann::json EASYEDAPRO::FindJsonFile( const wxString& aZipFileName,
247 const std::set<wxString>& aFileNames )
248{
249 std::shared_ptr<wxZipEntry> entry;
250 wxFFileInputStream in( aZipFileName );
251 wxZipInputStream zip( in );
252
253 while( entry.reset( zip.GetNextEntry() ), entry.get() )
254 {
255 wxString name = entry->GetName();
256
257 try
258 {
259 if( aFileNames.find( name ) != aFileNames.end() )
260 {
261 wxMemoryOutputStream memos;
262 memos << zip;
263 wxStreamBuffer* buf = memos.GetOutputStreamBuffer();
264
265 wxString str = wxString::FromUTF8( (char*) buf->GetBufferStart(), buf->GetBufferSize() );
266
267 return nlohmann::json::parse( str );
268 }
269 }
270 catch( nlohmann::json::exception& e )
271 {
272 THROW_IO_ERRORF( _( "JSON error reading '%s': %s" ), name, e.what() );
273 }
274 catch( std::exception& e )
275 {
276 THROW_IO_ERRORF( _( "Error reading '%s': %s" ), name, e.what() );
277 }
278 }
279
280 return nlohmann::json{};
281}
282
283
284nlohmann::json EASYEDAPRO::ReadProjectOrDeviceFile( const wxString& aZipFileName )
285{
286 static const std::set<wxString> c_files = { wxS( "project.json" ), wxS( "device.json" ),
287 wxS( "footprint.json" ), wxS( "symbol.json" ) };
288
289 nlohmann::json j = FindJsonFile( aZipFileName, c_files );
290
291 if( !j.is_null() )
292 return j;
293
294 THROW_IO_ERRORF( _( "'%s' does not appear to be a valid EasyEDA (JLCEDA) Pro project or library file. "
295 "Cannot find project.json or device.json." ), aZipFileName );
296}
297
298
300 const wxString& aFileName,
301 std::function<bool( const wxString&, const wxString&, wxInputStream& )> aCallback )
302{
303 std::shared_ptr<wxZipEntry> entry;
304 wxFFileInputStream in( aFileName );
305 wxZipInputStream zip( in );
306
307 if( !zip.IsOk() )
308 THROW_IO_ERRORF( _( "Cannot read ZIP archive '%s'" ), aFileName );
309
310 while( entry.reset( zip.GetNextEntry() ), entry.get() )
311 {
312 wxString name = entry->GetName();
313 wxString baseName = name.AfterLast( '\\' ).AfterLast( '/' ).BeforeFirst( '.' );
314
315 try
316 {
317 if( aCallback( name, baseName, zip ) )
318 break;
319 }
320 catch( nlohmann::json::exception& e )
321 {
322 THROW_IO_ERRORF( _( "JSON error reading '%s': %s" ), name, e.what() );
323 }
324 catch( std::exception& e )
325 {
326 THROW_IO_ERRORF( _( "Error reading '%s': %s" ), name, e.what() );
327 }
328 }
329}
330
331
332std::vector<nlohmann::json> EASYEDAPRO::ParseJsonLines( wxInputStream& aInput, const wxString& aSource )
333{
334 wxTextInputStream txt( aInput, wxS( " " ), wxConvUTF8 );
335
336 int currentLine = 1;
337
338 std::vector<nlohmann::json> lines;
339 while( aInput.CanRead() )
340 {
341 try
342 {
343 wxString line = txt.ReadLine();
344
345 if( !line.IsEmpty() )
346 {
347 nlohmann::json js = nlohmann::json::parse( line );
348 lines.emplace_back( js );
349 }
350 else
351 {
352 lines.emplace_back( nlohmann::json() );
353 }
354 }
355 catch( nlohmann::json::exception& e )
356 {
357 wxLogTrace( traceEasyEdaIo, wxT( "Cannot parse JSON line %d in '%s': %s" ),
358 currentLine, aSource, e.what() );
359 }
360
361 currentLine++;
362 }
363
364 return lines;
365}
366
367
368std::vector<std::vector<nlohmann::json>>
369EASYEDAPRO::ParseJsonLinesWithSeparation( wxInputStream& aInput, const wxString& aSource )
370{
371 wxTextInputStream txt( aInput, wxS( " " ), wxConvUTF8 );
372
373 int currentLine = 1;
374
375 std::vector<std::vector<nlohmann::json>> lineBlocks;
376 lineBlocks.emplace_back();
377
378 while( aInput.CanRead() )
379 {
380 try
381 {
382 wxString line = txt.ReadLine();
383
384 if( !line.IsEmpty() )
385 {
386 nlohmann::json js = nlohmann::json::parse( line );
387 lineBlocks.back().emplace_back( js );
388 }
389 else
390 {
391 lineBlocks.emplace_back();
392 }
393 }
394 catch( nlohmann::json::exception& e )
395 {
396 wxLogTrace( traceEasyEdaIo, wxT( "Cannot parse JSON line %d in '%s': %s" ),
397 currentLine, aSource, e.what() );
398 }
399
400 currentLine++;
401 }
402
403 return lineBlocks;
404}
405
406
407std::map<wxString, wxString>
408EASYEDAPRO::AnyMapToStringMap( const std::map<wxString, nlohmann::json>& aInput )
409{
410 std::map<wxString, wxString> stringMap;
411
412 for( auto& [key, value] : aInput )
413 {
414 if( value.is_string() )
415 stringMap[key] = value.get<wxString>();
416 else if( value.is_number() )
417 stringMap[key] = wxString::FromCDouble( value.get<double>() );
418 }
419
420 return stringMap;
421}
422
423
424nlohmann::json EASYEDAPRO::BuildV3ProjectIndexFromRawDocs( const V3_DOC_PARSER& aParser, bool aIncludeLibraryMetadata )
425{
426 nlohmann::json project = EmptyV3ProjectIndex();
427
428 struct SHEET_INFO
429 {
430 wxString uuid;
431 wxString name;
432 int zIndex = 0;
433 int order = 0;
434 };
435
436 std::map<wxString, std::vector<SHEET_INFO>> sheetsBySch;
437 int pageOrder = 0;
438
439 for( const auto& [uuid, pageDoc] : aParser.GetRawDocs( wxS( "SCH_PAGE" ) ) )
440 {
441 wxString schematic = MetaGetString( pageDoc, "schematic" );
442
443 if( schematic.empty() )
444 schematic = V3GetString( pageDoc.head, "schematic" );
445
446 if( schematic.empty() )
447 continue;
448
449 SHEET_INFO info;
450 info.uuid = uuid;
451 info.name = MetaGetString( pageDoc, "title", uuid );
452 info.zIndex = MetaGetInt( pageDoc, "zIndex", 0 );
453 info.order = pageOrder++;
454
455 sheetsBySch[schematic].push_back( std::move( info ) );
456 }
457
458 std::map<wxString, wxString> schematicsByBoard;
459
460 for( const auto& [uuid, schDoc] : aParser.GetRawDocs( wxS( "SCH" ) ) )
461 {
462 nlohmann::json sch = nlohmann::json::object();
463 sch["name"] = ToStdString( MetaGetString( schDoc, "title", uuid ) );
464 sch["sheets"] = nlohmann::json::array();
465
466 auto pagesIt = sheetsBySch.find( uuid );
467
468 if( pagesIt != sheetsBySch.end() )
469 {
470 auto& pages = pagesIt->second;
471
472 std::sort( pages.begin(), pages.end(),
473 []( const SHEET_INFO& aLeft, const SHEET_INFO& aRight )
474 {
475 if( aLeft.zIndex != aRight.zIndex )
476 return aLeft.zIndex < aRight.zIndex;
477
478 return aLeft.order < aRight.order;
479 } );
480
481 int sheetId = 1;
482
483 for( const SHEET_INFO& page : pages )
484 {
485 sch["sheets"].push_back( nlohmann::json::object( { { "id", sheetId++ },
486 { "name", ToStdString( page.name ) },
487 { "uuid", ToStdString( page.uuid ) } } ) );
488 }
489 }
490
491 project["schematics"][ToStdString( uuid )] = std::move( sch );
492
493 wxString board = MetaGetString( schDoc, "board" );
494
495 if( !board.empty() )
496 schematicsByBoard[board] = uuid;
497 }
498
499 std::map<wxString, wxString> boardTitles;
500
501 for( const auto& [uuid, boardDoc] : aParser.GetRawDocs( wxS( "BOARD" ) ) )
502 boardTitles[uuid] = MetaGetString( boardDoc, "title", uuid );
503
504 std::map<wxString, wxString> pcbsByBoard;
505
506 for( const auto& [uuid, pcbDoc] : aParser.GetRawDocs( wxS( "PCB" ) ) )
507 {
508 nlohmann::json pcb = nlohmann::json::object();
509 pcb["title"] = ToStdString( MetaGetString( pcbDoc, "title", uuid ) );
510
511 project["pcbs"][ToStdString( uuid )] = std::move( pcb );
512
513 wxString board = MetaGetString( pcbDoc, "board" );
514
515 if( !board.empty() )
516 pcbsByBoard[board] = uuid;
517 }
518
519 std::set<wxString> allBoardRefs;
520
521 for( const auto& [boardRef, schUuid] : schematicsByBoard )
522 allBoardRefs.insert( boardRef );
523
524 for( const auto& [boardRef, pcbUuid] : pcbsByBoard )
525 allBoardRefs.insert( boardRef );
526
527 for( const wxString& boardRef : allBoardRefs )
528 {
529 wxString boardName = boardRef;
530
531 if( auto it = boardTitles.find( boardRef ); it != boardTitles.end() )
532 boardName = it->second;
533
534 if( boardName.empty() )
535 boardName = boardRef;
536
537 nlohmann::json board = nlohmann::json::object();
538 auto schIt = schematicsByBoard.find( boardRef );
539 auto pcbIt = pcbsByBoard.find( boardRef );
540
541 board["schematic"] = schIt != schematicsByBoard.end() ? ToStdString( schIt->second ) : "";
542 board["pcb"] = pcbIt != pcbsByBoard.end() ? ToStdString( pcbIt->second ) : "";
543
544 project["boards"][ToStdString( boardName )] = std::move( board );
545 }
546
547 if( project["boards"].empty() && project["pcbs"].is_object() && project["pcbs"].size() == 1
548 && project["schematics"].is_object() && project["schematics"].size() == 1 )
549 {
550 auto pcbIt = project["pcbs"].begin();
551 auto schIt = project["schematics"].begin();
552
553 wxString pcbId = wxString::FromUTF8( pcbIt.key() );
554 wxString schId = wxString::FromUTF8( schIt.key() );
555 wxString boardName = wxString::FromUTF8( pcbIt.value().value( "title", pcbIt.key() ) );
556
557 if( boardName.empty() )
558 boardName = schId;
559
560 nlohmann::json board = nlohmann::json::object();
561 board["schematic"] = ToStdString( schId );
562 board["pcb"] = ToStdString( pcbId );
563
564 project["boards"][ToStdString( boardName )] = std::move( board );
565 }
566
567 if( !aIncludeLibraryMetadata )
568 return project;
569
570 for( const auto& [uuid, symDoc] : aParser.GetRawDocs( wxS( "SYMBOL" ) ) )
571 {
572 nlohmann::json sym = nlohmann::json::object();
573 sym["source"] = ToStdString( MetaGetString( symDoc, "source" ) );
574 sym["description"] = ToStdString( MetaGetString( symDoc, "description" ) );
575 sym["title"] = ToStdString( MetaGetString( symDoc, "title", uuid ) );
576 sym["display_title"] = sym["title"];
577 sym["version"] = "3";
578 sym["type"] = MetaGetInt( symDoc, "docType", static_cast<int>( SYMBOL_TYPE::NORMAL ) );
579 sym["tags"] = MetaGetValue( symDoc, "tags", nlohmann::json::object() );
580
581 project["symbols"][ToStdString( uuid )] = std::move( sym );
582 }
583
584 for( const auto& [uuid, fpDoc] : aParser.GetRawDocs( wxS( "FOOTPRINT" ) ) )
585 {
586 nlohmann::json fp = nlohmann::json::object();
587 fp["source"] = ToStdString( MetaGetString( fpDoc, "source" ) );
588 fp["description"] = ToStdString( MetaGetString( fpDoc, "description" ) );
589 fp["title"] = ToStdString( MetaGetString( fpDoc, "title", uuid ) );
590 fp["display_title"] = fp["title"];
591 fp["version"] = "3";
592 fp["type"] = static_cast<int>( FOOTPRINT_TYPE::NORMAL );
593 fp["tags"] = MetaGetValue( fpDoc, "tags", nlohmann::json::object() );
594
595 project["footprints"][ToStdString( uuid )] = std::move( fp );
596 }
597
598 for( const auto& [uuid, deviceDoc] : aParser.GetRawDocs( wxS( "DEVICE" ) ) )
599 {
600 nlohmann::json dev = nlohmann::json::object();
601 dev["source"] = ToStdString( MetaGetString( deviceDoc, "source" ) );
602 dev["description"] = ToStdString( MetaGetString( deviceDoc, "description" ) );
603 dev["title"] = ToStdString( MetaGetString( deviceDoc, "title", uuid ) );
604 dev["version"] = "3";
605 dev["tags"] = MetaGetValue( deviceDoc, "tags", nlohmann::json::object() );
606 dev["attributes"] = MetaGetValue( deviceDoc, "attributes", nlohmann::json::object() );
607
608 project["devices"][ToStdString( uuid )] = std::move( dev );
609 }
610
611 return project;
612}
613
614
615std::map<wxString, EASYEDAPRO::BLOB> EASYEDAPRO::BuildV3BlobMap( const V3_DOC_PARSER& aParser )
616{
617 std::map<wxString, BLOB> blobs;
618
619 for( const auto& [blobDocUuid, rawDoc] : aParser.GetRawDocs( wxS( "BLOB" ) ) )
620 {
621 for( const V3_ROW& row : rawDoc.rows )
622 {
623 if( row.type != wxS( "BLOB" ) )
624 continue;
625
626 try
627 {
628 BLOB blob;
629 blob.objectId = V3GetString( row.outer, "id" );
630 blob.url = V3GetString( row.inner, "content" );
631 blobs[blob.objectId] = blob;
632 }
633 catch( nlohmann::json::exception& e )
634 {
635 wxLogTrace( traceEasyEdaIo, wxT( "EasyEDA Pro v3 blob in '%s' was skipped due to parse error: %s" ),
636 blobDocUuid, e.what() );
637 }
638 }
639 }
640
641 return blobs;
642}
643
644
645wxString EASYEDAPRO::GetV3LibraryItemTitle( const nlohmann::json& aMetadata, const wxString& aUuid )
646{
647 wxString title = EASYEDAPRO::V3GetString( aMetadata, "display_title" );
648
649 if( title.empty() )
650 title = EASYEDAPRO::V3GetString( aMetadata, "title" );
651
652 if( title.empty() )
653 title = aUuid;
654
655 return title;
656}
657
658
659wxString EASYEDAPRO::KeywordsFromV3Tags( const nlohmann::json& aTags )
660{
661 if( !aTags.is_object() )
662 return {};
663
664 wxString keywords;
665
666 auto appendTagName = [&]( const char* aKey )
667 {
668 if( !aTags.contains( aKey ) || !aTags.at( aKey ).is_object() )
669 return;
670
671 wxString name = V3GetString( aTags.at( aKey ), "name" );
672
673 if( name.empty() )
674 return;
675
676 if( !keywords.empty() )
677 keywords += wxS( " " );
678
679 keywords += name;
680 };
681
682 appendTagName( "parent_tag" );
683 appendTagName( "child_tag" );
684
685 return keywords;
686}
687
688
689wxString EASYEDAPRO::ResolveDeviceFieldVariables( const wxString& aInput,
690 const std::map<wxString, wxString>& aDeviceAttributes )
691{
692 wxString inputText = aInput;
693 wxString resolvedText;
694 int variableCount = 0;
695
696 // Resolve variables: ={Variable1}text{Variable2}
697 do
698 {
699 if( !inputText.StartsWith( wxS( "={" ) ) )
700 return inputText;
701
702 resolvedText.Clear();
703 variableCount = 0;
704
705 for( size_t i = 1; i < inputText.size(); )
706 {
707 wxUniChar c = inputText[i++];
708
709 if( c == '{' )
710 {
711 wxString varName;
712 bool endFound = false;
713
714 while( i < inputText.size() )
715 {
716 c = inputText[i++];
717
718 if( c == '}' )
719 {
720 endFound = true;
721 break;
722 }
723
724 varName << c;
725 }
726
727 if( !endFound )
728 return inputText;
729
730 wxString varValue =
731 get_def( aDeviceAttributes, varName, wxString::Format( wxS( "{%s!}" ), varName ) );
732
733 resolvedText << varValue;
734 variableCount++;
735 }
736 else
737 {
738 resolvedText << c;
739 }
740 }
741
742 inputText = resolvedText;
743 } while( variableCount > 0 );
744
745 return resolvedText;
746}
747
748
749wxString EASYEDAPRO::NormalizeEasyEDAText( wxString aText )
750{
751 // ℃ -> °C
752 aText.Replace( wxS( "\u2103" ), wxS( "\u00B0C" ), true );
753 return aText;
754}
755
756
757wxString EASYEDAPRO::MakeUniqueLibName( std::set<wxString>& aUsedNames, const wxString& aName,
758 const wxString& aFallback )
759{
760 wxString uniqueBase = aName.empty() ? aFallback : aName;
761 wxString uniqueName = uniqueBase;
762 int suffix = 2;
763
764 while( aUsedNames.contains( uniqueName ) )
765 uniqueName = uniqueBase + wxString::Format( wxS( "_%d" ), suffix++ );
766
767 aUsedNames.insert( uniqueName );
768 return uniqueName;
769}
770
771
772std::map<wxString, wxString> EASYEDAPRO::BuildV3DeviceLibNames( nlohmann::json& aProject,
773 const std::map<wxString, PRJ_DEVICE>& aDevices,
774 std::set<wxString>* aUsedNames )
775{
776 std::map<wxString, wxString> deviceLibNames;
777 std::set<wxString> localUsedNames;
778 std::set<wxString>& usedLibNames = aUsedNames ? *aUsedNames : localUsedNames;
779 nlohmann::json deviceLibNamesJson = nlohmann::json::object();
780
781 for( const auto& [devUuid, device] : aDevices )
782 {
783 wxString libNameForDevice = MakeUniqueLibName( usedLibNames, device.title, devUuid );
784 deviceLibNames[devUuid] = libNameForDevice;
785 deviceLibNamesJson[std::string( devUuid.ToUTF8() )] = std::string( libNameForDevice.ToUTF8() );
786 }
787
788 aProject["device_lib_names"] = std::move( deviceLibNamesJson );
789 return deviceLibNames;
790}
791
792
793wxString EASYEDAPRO::LookupV3DeviceLibName( const nlohmann::json& aProject, const wxString& aDeviceUuid )
794{
795 if( aDeviceUuid.empty() || !aProject.contains( "device_lib_names" )
796 || !aProject.at( "device_lib_names" ).is_object() )
797 {
798 return {};
799 }
800
801 std::string deviceIdUtf8 = std::string( aDeviceUuid.ToUTF8() );
802 const auto& names = aProject.at( "device_lib_names" );
803
804 if( names.contains( deviceIdUtf8 ) && names.at( deviceIdUtf8 ).is_string() )
805 return names.at( deviceIdUtf8 ).get<wxString>();
806
807 return {};
808}
809
810
812 const wxString& aDeviceUuid )
813{
814 V3_DEVICE_DATA data;
815
816 if( aDeviceUuid.empty() || !aProject.contains( "devices" ) || !aProject.at( "devices" ).is_object() )
817 return data;
818
819 std::string deviceIdUtf8 = std::string( aDeviceUuid.ToUTF8() );
820
821 if( !aProject.at( "devices" ).contains( deviceIdUtf8 ) )
822 return data;
823
824 const nlohmann::json& dev = aProject.at( "devices" ).at( deviceIdUtf8 );
825
826 data.found = true;
827 data.description = V3GetString( dev, "description" );
828
829 if( dev.contains( "attributes" ) && dev.at( "attributes" ).is_object() )
830 {
831 for( const auto& [key, value] : dev.at( "attributes" ).items() )
832 data.attributes[wxString::FromUTF8( key )] = V3JsonToString( value );
833 }
834
835 return data;
836}
837
838
839wxString EASYEDAPRO::ResolveV3DeviceValueText( const std::map<wxString, wxString>& aDeviceAttributes )
840{
841 wxString valueText = get_def( aDeviceAttributes, wxS( "Value" ), wxEmptyString );
842
843 if( valueText.empty() )
844 valueText = get_def( aDeviceAttributes, wxS( "Name" ), wxEmptyString );
845
846 return NormalizeEasyEDAText( ResolveDeviceFieldVariables( valueText, aDeviceAttributes ) );
847}
848
849
851 const std::map<wxString, wxString>& aDeviceAttributes, bool aIncludeValue,
852 const std::function<void( const wxString& aKey, const wxString& aValue )>& aCallback )
853{
854 for( const wxString& attrKey : c_deviceAttributesWhitelist )
855 {
856 if( !aIncludeValue && attrKey == wxS( "Value" ) )
857 continue;
858
859 auto valOpt = get_opt( aDeviceAttributes, attrKey );
860
861 if( !valOpt || valOpt->empty() )
862 continue;
863
864 aCallback( attrKey,
865 NormalizeEasyEDAText( ResolveDeviceFieldVariables( *valOpt, aDeviceAttributes ) ) );
866 }
867}
868
869
870template <typename Map>
871static wxString MakeUniqueV3LibraryName( const Map& aItems, const wxString& aName, const wxString& aFallback )
872{
873 wxString uniqueBase = aName.empty() ? aFallback : aName;
874 wxString uniqueName = uniqueBase;
875
876 int suffix = 2;
877 while( aItems.contains( uniqueName ) )
878 uniqueName = uniqueBase + wxString::Format( wxS( "_%d" ), suffix++ );
879
880 return uniqueName;
881}
882
883
884std::map<wxString, EASYEDAPRO::V3_SYMBOL_LIB_ITEM> EASYEDAPRO::BuildV3SymbolLibraryMap( const V3_DOC_PARSER& aParser )
885{
886 std::map<wxString, V3_SYMBOL_LIB_ITEM> items;
887 const nlohmann::json& index = aParser.GetLibraryIndex();
888
889 const bool hasDevices = index.is_object() && index.contains( "devices" ) && index.at( "devices" ).is_object()
890 && !index.at( "devices" ).empty();
891
892 if( hasDevices )
893 {
894 try
895 {
896 for( const auto& [devUuidKey, deviceJson] : index.at( "devices" ).items() )
897 {
898 if( !deviceJson.is_object() )
899 continue;
900
901 PRJ_DEVICE device = deviceJson;
902 auto symbolIt = device.attributes.find( wxS( "Symbol" ) );
903
904 if( symbolIt == device.attributes.end() || symbolIt->second.empty() )
905 continue;
906
907 if( !aParser.FindRawDoc( wxS( "SYMBOL" ), symbolIt->second ) )
908 continue;
909
910 wxString deviceUuid = V3GetString( deviceJson, "uuid", ToWxString( devUuidKey ) );
911 wxString name = device.title.empty() ? GetV3LibraryItemTitle( deviceJson, deviceUuid ) : device.title;
913 item.symbolUuid = symbolIt->second;
914 item.device = std::move( device );
915 item.hasDevice = true;
916
917 items.emplace( MakeUniqueV3LibraryName( items, name, deviceUuid ), std::move( item ) );
918 }
919 }
920 catch( nlohmann::json::exception& e )
921 {
922 wxLogTrace( traceEasyEdaIo, wxT( "Failed to parse EasyEDA Pro v3 device metadata: %s" ), e.what() );
923 return {};
924 }
925
926 return items;
927 }
928
929 for( const auto& [name, symbolUuid] : BuildV3LibraryItemMap( aParser, "symbols", wxS( "SYMBOL" ) ) )
930 {
932 item.symbolUuid = symbolUuid;
933 items.emplace( name, std::move( item ) );
934 }
935
936 return items;
937}
938
939
940std::map<wxString, wxString> EASYEDAPRO::BuildV3LibraryItemMap( const V3_DOC_PARSER& aParser, const char* aIndexKey,
941 const wxString& aDocType )
942{
943 std::map<wxString, wxString> titlesByUuid;
944 const nlohmann::json& index = aParser.GetLibraryIndex();
945
946 if( index.is_object() && index.contains( aIndexKey ) && index.at( aIndexKey ).is_object() )
947 {
948 for( const auto& [uuidKey, metadata] : index.at( aIndexKey ).items() )
949 {
950 wxString uuid = V3GetString( metadata, "uuid", ToWxString( uuidKey ) );
951 titlesByUuid[uuid] = GetV3LibraryItemTitle( metadata, uuid );
952 }
953 }
954
955 std::map<wxString, wxString> nameToUuid;
956
957 for( const auto& [uuid, rawDoc] : aParser.GetRawDocs( aDocType ) )
958 {
959 (void) rawDoc;
960
961 wxString name = uuid;
962
963 if( auto it = titlesByUuid.find( uuid ); it != titlesByUuid.end() )
964 name = it->second;
965
966 nameToUuid[MakeUniqueV3LibraryName( nameToUuid, name, uuid )] = uuid;
967 }
968
969 return nameToUuid;
970}
int index
const char * name
Parses EasyEDA Pro v3 .epro2 archives.
const V3_DOC_RAW * FindRawDoc(const wxString &aDocType, const wxString &aUuid) const
const std::map< wxString, V3_DOC_RAW > & GetRawDocs(const wxString &aDocType) const
const nlohmann::json & GetLibraryIndex() const
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
int Parse(const UTF8 &aId, bool aFix=false)
Parse LIB_ID with the information from aId.
Definition lib_id.cpp:65
static UTF8 FixIllegalChars(const UTF8 &aLibItemName, bool aLib)
Replace illegal LIB_ID item name characters with underscores '_'.
Definition lib_id.cpp:205
static bool empty(const wxTextEntryBase *aCtrl)
static wxString MakeUniqueV3LibraryName(const Map &aItems, const wxString &aName, const wxString &aFallback)
static std::string ToStdString(const wxString &aStr)
#define _(s)
const wxChar *const traceEasyEdaIo
#define THROW_IO_ERRORF(msg,...)
wxString get_def(const std::map< wxString, wxString > &aMap, const char *aKey, const char *aDefval="")
Definition map_helpers.h:60
std::optional< V > get_opt(const std::map< wxString, V > &aMap, const wxString &aKey)
Definition map_helpers.h:30
nlohmann::json BuildV3ProjectIndexFromRawDocs(const V3_DOC_PARSER &aParser, bool aIncludeLibraryMetadata=true)
Build a minimal legacy-style project index from parsed v3 raw documents.
LIB_ID ToKiCadLibID(const wxString &aLibName, const wxString &aLibReference)
wxString MakeUniqueLibName(std::set< wxString > &aUsedNames, const wxString &aName, const wxString &aFallback)
Allocate a unique library item name, recording it in aUsedNames.
wxString KeywordsFromV3Tags(const nlohmann::json &aTags)
Build KiCad keywords from a v3 tags object (parent_tag / child_tag name fields).
wxString GetV3LibraryItemTitle(const nlohmann::json &aMetadata, const wxString &aUuid)
void ForEachImportedDeviceField(const std::map< wxString, wxString > &aDeviceAttributes, bool aIncludeValue, const std::function< void(const wxString &aKey, const wxString &aValue)> &aCallback)
Invoke aCallback for each non-empty whitelisted Device field (resolved + normalized).
wxString ResolveDeviceFieldVariables(const wxString &aInput, const std::map< wxString, wxString > &aDeviceAttributes)
Resolve EasyEDA ={Var} / ={A}text{B} field expressions against device attributes.
void IterateZipFiles(const wxString &aFileName, std::function< bool(const wxString &, const wxString &, wxInputStream &)> aCallback)
std::vector< nlohmann::json > ParseJsonLines(wxInputStream &aInput, const wxString &aSource)
wxString NormalizeEasyEDAText(wxString aText)
Replace EasyEDA temperature glyph (℃) with °C.
V3_DEVICE_DATA GetV3DeviceData(const nlohmann::json &aProject, const wxString &aDeviceUuid)
std::vector< std::vector< nlohmann::json > > ParseJsonLinesWithSeparation(wxInputStream &aInput, const wxString &aSource)
Multiple document types (e.g.
wxString LookupV3DeviceLibName(const nlohmann::json &aProject, const wxString &aDeviceUuid)
nlohmann::json FindJsonFile(const wxString &aZipFileName, const std::set< wxString > &aFileNames)
std::vector< IMPORT_PROJECT_DESC > ProjectToSelectorDialog(const nlohmann::json &aProject, bool aPcbOnly=false, bool aSchOnly=false)
nlohmann::json ReadProjectOrDeviceFile(const wxString &aZipFileName)
wxString ShortenLibName(wxString aProjectName)
std::map< wxString, wxString > AnyMapToStringMap(const std::map< wxString, nlohmann::json > &aInput)
wxString V3JsonToString(const nlohmann::json &aValue, const wxString &aDefault)
wxString ResolveV3DeviceValueText(const std::map< wxString, wxString > &aDeviceAttributes)
Preferred Value text: Value attribute, else Name, with variables resolved.
int V3GetInt(const nlohmann::json &aObj, const char *aKey, int aDefault)
std::map< wxString, V3_SYMBOL_LIB_ITEM > BuildV3SymbolLibraryMap(const V3_DOC_PARSER &aParser)
std::map< wxString, wxString > BuildV3LibraryItemMap(const V3_DOC_PARSER &aParser, const char *aIndexKey, const wxString &aDocType)
std::map< wxString, BLOB > BuildV3BlobMap(const V3_DOC_PARSER &aParser)
wxString V3GetString(const nlohmann::json &aObj, const char *aKey, const wxString &aDefault)
std::map< wxString, wxString > BuildV3DeviceLibNames(nlohmann::json &aProject, const std::map< wxString, PRJ_DEVICE > &aDevices, std::set< wxString > *aUsedNames=nullptr)
Stable project-lib item names keyed by Device UUID (one entry per Device).
wxString EscapeString(const wxString &aSource, ESCAPE_CONTEXT aContext)
The Escape/Unescape routines use HTML-entity-reference-style encoding to handle characters which are:...
@ CTX_LIBID
std::map< wxString, wxString > attributes
std::map< wxString, wxString > attributes
Raw parsed document from an EasyEDA Pro v3 .epru stream.
std::vector< V3_ROW > rows
One parsed row from an EasyEDA Pro v3 .epru document stream.
Build the schematic-library name map for a v3 .elibz2.
Describes how non-KiCad boards and schematics should be imported as KiCad projects.
wxString result
Test unit parsing edge cases and error handling.
wxLogTrace helper definitions.