From 5bbe01b570fbb68b6b1ff3e68ac029d7af7b8307 Mon Sep 17 00:00:00 2001
From: Alex Shvartzkop <dudesuchamazing@gmail.com>
Date: Mon, 25 Dec 2023 07:00:11 +0300
Subject: [PATCH] EasyEDA Pro: support .elibz format (device libraries)

---
 .../io/easyedapro/easyedapro_import_utils.cpp | 11 +++--
 .../io/easyedapro/easyedapro_import_utils.h   |  2 +-
 common/io/easyedapro/easyedapro_parser.cpp    | 24 +++++++---
 .../sch_io/easyedapro/sch_io_easyedapro.cpp   | 48 ++++++++++++-------
 .../sch_io/easyedapro/sch_io_easyedapro.h     |  5 +-
 kicad/import_proj.cpp                         |  2 +-
 .../pcb_io/easyedapro/pcb_io_easyedapro.cpp   | 30 +++++++++---
 pcbnew/pcb_io/easyedapro/pcb_io_easyedapro.h  |  6 ++-
 8 files changed, 89 insertions(+), 39 deletions(-)

diff --git a/common/io/easyedapro/easyedapro_import_utils.cpp b/common/io/easyedapro/easyedapro_import_utils.cpp
index 3d4b8e6b85..8bb13e6a87 100644
--- a/common/io/easyedapro/easyedapro_import_utils.cpp
+++ b/common/io/easyedapro/easyedapro_import_utils.cpp
@@ -140,7 +140,7 @@ EASYEDAPRO::ProjectToSelectorDialog( const nlohmann::json& aProject, bool aPcbOn
 }
 
 
-nlohmann::json EASYEDAPRO::ReadProjectFile( const wxString& aZipFileName )
+nlohmann::json EASYEDAPRO::ReadProjectOrDeviceFile( const wxString& aZipFileName )
 {
     std::shared_ptr<wxZipEntry> entry;
     wxFFileInputStream          in( aZipFileName );
@@ -150,7 +150,7 @@ nlohmann::json EASYEDAPRO::ReadProjectFile( const wxString& aZipFileName )
     {
         wxString name = entry->GetName();
 
-        if( name == wxS( "project.json" ) )
+        if( name == wxS( "project.json" ) || name == wxS( "device.json" ) )
         {
             wxMemoryOutputStream memos;
             memos << zip;
@@ -161,9 +161,10 @@ nlohmann::json EASYEDAPRO::ReadProjectFile( const wxString& aZipFileName )
         }
     }
 
-    THROW_IO_ERROR( wxString::Format( _( "'%s' does not appear to be a valid EasyEDA (JLCEDA) Pro "
-                                         "project file. Cannot find project.json." ),
-                                      aZipFileName ) );
+    THROW_IO_ERROR( wxString::Format(
+            _( "'%s' does not appear to be a valid EasyEDA (JLCEDA) Pro "
+               "project or library file. Cannot find project.json or device.json." ),
+            aZipFileName ) );
 }
 
 
diff --git a/common/io/easyedapro/easyedapro_import_utils.h b/common/io/easyedapro/easyedapro_import_utils.h
index f61adfa5bd..d4b6477a8c 100644
--- a/common/io/easyedapro/easyedapro_import_utils.h
+++ b/common/io/easyedapro/easyedapro_import_utils.h
@@ -46,7 +46,7 @@ std::vector<IMPORT_PROJECT_DESC> ProjectToSelectorDialog( const nlohmann::json&
                                                           bool                  aPcbOnly = false,
                                                           bool                  aSchOnly = false );
 
-nlohmann::json ReadProjectFile( const wxString& aZipFileName );
+nlohmann::json ReadProjectOrDeviceFile( const wxString& aZipFileName );
 
 void IterateZipFiles(
         const wxString&                                                         aFileName,
diff --git a/common/io/easyedapro/easyedapro_parser.cpp b/common/io/easyedapro/easyedapro_parser.cpp
index 126daf1bd2..b1f0f18785 100644
--- a/common/io/easyedapro/easyedapro_parser.cpp
+++ b/common/io/easyedapro/easyedapro_parser.cpp
@@ -195,10 +195,14 @@ void EASYEDAPRO::from_json( const nlohmann::json& j, EASYEDAPRO::PRJ_SYMBOL& d )
     if( j.at( "source" ).is_string() )
         d.source = j.at( "source" ).get<wxString>();
 
-    if( j.at( "desc" ).is_string() )
+    if( j.contains( "desc" ) )
         d.desc = j.at( "desc" ).get<wxString>();
+    else if( j.contains( "description" ) )
+        d.desc = j.at( "description" ).get<wxString>();
 
-    if( j.at( "title" ).is_string() )
+    if( j.contains( "display_title" ) )
+        d.title = j.at( "display_title" ).get<wxString>();
+    else if( j.contains( "title" ) )
         d.title = j.at( "title" ).get<wxString>();
 
     if( j.at( "version" ).is_string() )
@@ -219,10 +223,14 @@ void EASYEDAPRO::from_json( const nlohmann::json& j, EASYEDAPRO::PRJ_FOOTPRINT&
     if( j.at( "source" ).is_string() )
         d.source = j.at( "source" ).get<wxString>();
 
-    if( j.at( "desc" ).is_string() )
+    if( j.contains( "desc" ) )
         d.desc = j.at( "desc" ).get<wxString>();
+    else if( j.contains( "description" ) )
+        d.desc = j.at( "description" ).get<wxString>();
 
-    if( j.at( "title" ).is_string() )
+    if( j.contains( "display_title" ) )
+        d.title = j.at( "display_title" ).get<wxString>();
+    else if( j.contains( "title" ) )
         d.title = j.at( "title" ).get<wxString>();
 
     if( j.at( "version" ).is_string() )
@@ -243,10 +251,14 @@ void EASYEDAPRO::from_json( const nlohmann::json& j, EASYEDAPRO::PRJ_DEVICE& d )
     if( j.at( "source" ).is_string() )
         d.source = j.at( "source" ).get<wxString>();
 
-    if( j.at( "description" ).is_string() )
+    if( j.contains( "desc" ) )
+        d.description = j.at( "desc" ).get<wxString>();
+    else if( j.contains( "description" ) )
         d.description = j.at( "description" ).get<wxString>();
 
-    if( j.at( "title" ).is_string() )
+    if( j.contains( "display_title" ) )
+        d.title = j.at( "display_title" ).get<wxString>();
+    else if( j.contains( "title" ) )
         d.title = j.at( "title" ).get<wxString>();
 
     if( j.at( "version" ).is_string() )
diff --git a/eeschema/sch_io/easyedapro/sch_io_easyedapro.cpp b/eeschema/sch_io/easyedapro/sch_io_easyedapro.cpp
index 8a96634821..3e5aa69ed4 100644
--- a/eeschema/sch_io/easyedapro/sch_io_easyedapro.cpp
+++ b/eeschema/sch_io/easyedapro/sch_io_easyedapro.cpp
@@ -116,7 +116,7 @@ static LIB_SYMBOL* loadSymbol( nlohmann::json project, const wxString& aLibraryP
     SCH_EASYEDAPRO_PARSER parser( nullptr, nullptr );
     LIB_SYMBOL*           symbol = nullptr;
     wxFileName            libFname( aLibraryPath );
-    wxString              symLibName = EASYEDAPRO::ShortenLibName( libFname.GetName() );
+    wxString              symLibName = LIB_ID::FixIllegalChars( libFname.GetName(), true );
 
     /*if( libFname.GetExt() == wxS( "esym" ) )
     {
@@ -146,7 +146,8 @@ static LIB_SYMBOL* loadSymbol( nlohmann::json project, const wxString& aLibraryP
         }
     }
     else */
-    if( libFname.GetExt() == wxS( "epro" ) || libFname.GetExt() == wxS( "zip" ) )
+    if( libFname.GetExt() == wxS( "elibz" ) || libFname.GetExt() == wxS( "epro" )
+        || libFname.GetExt() == wxS( "zip" ) )
     {
         std::map<wxString, EASYEDAPRO::PRJ_SYMBOL>    prjSymbols = project.at( "symbols" );
         std::map<wxString, EASYEDAPRO::PRJ_FOOTPRINT> prjFootprints = project.at( "footprints" );
@@ -206,7 +207,7 @@ static LIB_SYMBOL* loadSymbol( nlohmann::json project, const wxString& aLibraryP
             LIB_ID libID = EASYEDAPRO::ToKiCadLibID( symLibName, aAliasName );
             symInfo.libSymbol->SetLibId( libID );
             symInfo.libSymbol->SetName( aAliasName );
-            symInfo.libSymbol->GetFootprintField().SetText( symLibName + wxS( "/" ) + fpTitle );
+            symInfo.libSymbol->GetFootprintField().SetText( symLibName + wxS( ":" ) + fpTitle );
 
             symbol = symInfo.libSymbol.release();
 
@@ -244,13 +245,23 @@ void SCH_IO_EASYEDAPRO::EnumerateSymbolLib( wxArrayString&         aSymbolNameLi
             }
         }
     }
-    else if( fname.GetExt() == wxS( "epro" ) || fname.GetExt() == wxS( "zip" ) )
+    else if( fname.GetExt() == wxS( "elibz" ) || fname.GetExt() == wxS( "epro" )
+             || fname.GetExt() == wxS( "zip" ) )
     {
-        nlohmann::json                     project = EASYEDAPRO::ReadProjectFile( aLibraryPath );
+        nlohmann::json project = EASYEDAPRO::ReadProjectOrDeviceFile( aLibraryPath );
         std::map<wxString, nlohmann::json> symbolMap = project.at( "symbols" );
 
         for( auto& [key, value] : symbolMap )
-            aSymbolNameList.Add( value.at( "title" ) );
+        {
+            wxString title;
+
+            if( value.contains( "display_title" ) )
+                title = value.at( "display_title" ).get<wxString>();
+            else
+                title = value.at( "title" ).get<wxString>();
+
+            aSymbolNameList.Add( title );
+        }
     }
 }
 
@@ -265,8 +276,11 @@ void SCH_IO_EASYEDAPRO::EnumerateSymbolLib( std::vector<LIB_SYMBOL*>& aSymbolLis
 
     EnumerateSymbolLib( symbolNameList, aLibraryPath, aProperties );
 
-    if( libFname.GetExt() == wxS( "epro" ) || libFname.GetExt() == wxS( "zip" ) )
-        project = EASYEDAPRO::ReadProjectFile( aLibraryPath );
+    if( libFname.GetExt() == wxS( "elibz" ) || libFname.GetExt() == wxS( "epro" )
+        || libFname.GetExt() == wxS( "zip" ) )
+    {
+        project = EASYEDAPRO::ReadProjectOrDeviceFile( aLibraryPath );
+    }
 
     for( const wxString& symbolName : symbolNameList )
     {
@@ -292,7 +306,7 @@ void SCH_IO_EASYEDAPRO::LoadAllDataFromProject( const wxString& aProjectPath )
     if( fname.GetExt() != wxS( "epro" ) && fname.GetExt() != wxS( "zip" ) )
         return;
 
-    nlohmann::json project = EASYEDAPRO::ReadProjectFile( aProjectPath );
+    nlohmann::json project = EASYEDAPRO::ReadProjectOrDeviceFile( aProjectPath );
 
     std::map<wxString, EASYEDAPRO::PRJ_SYMBOL>    prjSymbols = project.at( "symbols" );
     std::map<wxString, EASYEDAPRO::PRJ_FOOTPRINT> prjFootprints = project.at( "footprints" );
@@ -335,7 +349,7 @@ void SCH_IO_EASYEDAPRO::LoadAllDataFromProject( const wxString& aProjectPath )
             LIB_ID libID = EASYEDAPRO::ToKiCadLibID( symLibName, symData.title );
             symInfo.libSymbol->SetLibId( libID );
             symInfo.libSymbol->SetName( symData.title );
-            symInfo.libSymbol->GetFootprintField().SetText( symLibName + wxS( "/" ) + fpTitle );
+            symInfo.libSymbol->GetFootprintField().SetText( symLibName + wxS( ":" ) + fpTitle );
 
             m_projectData->m_Symbols.emplace( baseName, std::move( symInfo ) );
         }
@@ -357,15 +371,17 @@ void SCH_IO_EASYEDAPRO::LoadAllDataFromProject( const wxString& aProjectPath )
 }
 
 
-LIB_SYMBOL* SCH_IO_EASYEDAPRO::LoadSymbol( const wxString&        aLibraryPath,
-                                               const wxString&        aAliasName,
-                                               const STRING_UTF8_MAP* aProperties )
+LIB_SYMBOL* SCH_IO_EASYEDAPRO::LoadSymbol( const wxString& aLibraryPath, const wxString& aAliasName,
+                                           const STRING_UTF8_MAP* aProperties )
 {
     wxFileName     libFname( aLibraryPath );
     nlohmann::json project;
 
-    if( libFname.GetExt() == wxS( "epro" ) || libFname.GetExt() == wxS( "zip" ) )
-        project = EASYEDAPRO::ReadProjectFile( aLibraryPath );
+    if( libFname.GetExt() == wxS( "elibz" ) || libFname.GetExt() == wxS( "epro" )
+        || libFname.GetExt() == wxS( "zip" ) )
+    {
+        project = EASYEDAPRO::ReadProjectOrDeviceFile( aLibraryPath );
+    }
 
     return loadSymbol( project, aLibraryPath, aAliasName, aProperties );
 }
@@ -413,7 +429,7 @@ SCH_SHEET* SCH_IO_EASYEDAPRO::LoadSchematicFile( const wxString& aFileName,
     if( fname.GetExt() != wxS( "epro" ) && fname.GetExt() != wxS( "zip" ) )
         return rootSheet;
 
-    nlohmann::json project = EASYEDAPRO::ReadProjectFile( aFileName );
+    nlohmann::json project = EASYEDAPRO::ReadProjectOrDeviceFile( aFileName );
 
     std::map<wxString, EASYEDAPRO::PRJ_SCHEMATIC> prjSchematics = project.at( "schematics" );
 
diff --git a/eeschema/sch_io/easyedapro/sch_io_easyedapro.h b/eeschema/sch_io/easyedapro/sch_io_easyedapro.h
index dd5f786392..0513ac09b0 100644
--- a/eeschema/sch_io/easyedapro/sch_io_easyedapro.h
+++ b/eeschema/sch_io/easyedapro/sch_io_easyedapro.h
@@ -55,7 +55,10 @@ public:
         return PLUGIN_FILE_DESC( _HKI( "EasyEDA (JLCEDA) Pro files" ), { "epro", "zip" } );
     }
 
-    const PLUGIN_FILE_DESC GetLibraryFileDesc() const override { return GetSchematicFileDesc(); }
+    const PLUGIN_FILE_DESC GetLibraryFileDesc() const override
+    {
+        return PLUGIN_FILE_DESC( _HKI( "EasyEDA (JLCEDA) Pro files" ), { "elibz", "epro", "zip" } );
+    }
 
     bool CanReadSchematicFile( const wxString& aFileName ) const override;
 
diff --git a/kicad/import_proj.cpp b/kicad/import_proj.cpp
index 5fe7c7b91c..b960bfa6b1 100644
--- a/kicad/import_proj.cpp
+++ b/kicad/import_proj.cpp
@@ -174,7 +174,7 @@ void IMPORT_PROJ_HELPER::EasyEDAProProjectHandler()
 
     if( fname.GetExt() == wxS( "epro" ) || fname.GetExt() == wxS( "zip" ) )
     {
-        nlohmann::json project = EASYEDAPRO::ReadProjectFile( fname.GetFullPath() );
+        nlohmann::json project = EASYEDAPRO::ReadProjectOrDeviceFile( fname.GetFullPath() );
 
         std::map<wxString, EASYEDAPRO::PRJ_SCHEMATIC> prjSchematics = project.at( "schematics" );
         std::map<wxString, EASYEDAPRO::PRJ_BOARD>     prjBoards = project.at( "boards" );
diff --git a/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro.cpp b/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro.cpp
index 885245a4e0..b70db93719 100644
--- a/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro.cpp
+++ b/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro.cpp
@@ -125,7 +125,7 @@ BOARD* PCB_IO_EASYEDAPRO::LoadBoard( const wxString& aFileName, BOARD* aAppendTo
 
     if( fname.GetExt() == wxS( "epro" ) || fname.GetExt() == wxS( "zip" ) )
     {
-        nlohmann::json project = EASYEDAPRO::ReadProjectFile( aFileName );
+        nlohmann::json project = EASYEDAPRO::ReadProjectOrDeviceFile( aFileName );
 
         wxString pcbToLoad;
 
@@ -220,13 +220,23 @@ void PCB_IO_EASYEDAPRO::FootprintEnumerate( wxArrayString&  aFootprintNames,
             }
         }
     }
-    else if( fname.GetExt() == wxS( "epro" ) || fname.GetExt() == wxS( "zip" ) )
+    else if( fname.GetExt() == wxS( "elibz" ) || fname.GetExt() == wxS( "epro" )
+             || fname.GetExt() == wxS( "zip" ) )
     {
-        nlohmann::json                     project = EASYEDAPRO::ReadProjectFile( aLibraryPath );
+        nlohmann::json project = EASYEDAPRO::ReadProjectOrDeviceFile( aLibraryPath );
         std::map<wxString, nlohmann::json> footprintMap = project.at( "footprints" );
 
         for( auto& [key, value] : footprintMap )
-            aFootprintNames.Add( value.at( "title" ) );
+        {
+            wxString title;
+
+            if( value.contains( "display_title" ) )
+                title = value.at( "display_title" ).get<wxString>();
+            else
+                title = value.at( "title" ).get<wxString>();
+
+            aFootprintNames.Add( title );
+        }
     }
 }
 
@@ -344,16 +354,22 @@ FOOTPRINT* PCB_IO_EASYEDAPRO::FootprintLoad( const wxString& aLibraryPath,
         footprint->Value().SetVisible( true );
         footprint->AutoPositionFields();
     }
-    else if( libFname.GetExt() == wxS( "epro" ) || libFname.GetExt() == wxS( "zip" ) )
+    else if( libFname.GetExt() == wxS( "elibz" ) || libFname.GetExt() == wxS( "epro" )
+             || libFname.GetExt() == wxS( "zip" ) )
     {
-        nlohmann::json project = EASYEDAPRO::ReadProjectFile( aLibraryPath );
+        nlohmann::json project = EASYEDAPRO::ReadProjectOrDeviceFile( aLibraryPath );
 
         wxString fpUuid;
 
         std::map<wxString, nlohmann::json> footprintMap = project.at( "footprints" );
         for( auto& [uuid, data] : footprintMap )
         {
-            wxString title = data.at( "title" );
+            wxString title;
+
+            if( data.contains( "display_title" ) )
+                title = data.at( "display_title" ).get<wxString>();
+            else
+                title = data.at( "title" ).get<wxString>();
 
             if( title == aFootprintName )
             {
diff --git a/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro.h b/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro.h
index 9b0d10ed1b..3fb78d2bcf 100644
--- a/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro.h
+++ b/pcbnew/pcb_io/easyedapro/pcb_io_easyedapro.h
@@ -47,12 +47,14 @@ public:
 
     PLUGIN_FILE_DESC GetFootprintLibDesc() const override
     {
-        return PLUGIN_FILE_DESC( _HKI( "EasyEDA (JLCEDA) Pro project" ), { "epro", "zip" } );
+        return PLUGIN_FILE_DESC( _HKI( "EasyEDA (JLCEDA) Pro project" ),
+                                 { "elibz", "epro", "zip" } );
     }
 
     PLUGIN_FILE_DESC GetFootprintFileDesc() const override
     {
-        return PLUGIN_FILE_DESC( _HKI( "EasyEDA (JLCEDA) Pro files" ), { "efoo", "epro", "zip" } );
+        return PLUGIN_FILE_DESC( _HKI( "EasyEDA (JLCEDA) Pro files" ),
+                                 { "elibz", "efoo", "epro", "zip" } );
     }
 
     bool CanReadBoard( const wxString& aFileName ) const override;