/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2023 Alex Shvartzkop * Copyright (C) 2023-2024 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include "sch_easyedapro_parser.h" #include "sch_io_easyedapro.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct SCH_IO_EASYEDAPRO::PRJ_DATA { std::map m_Symbols; std::map m_Blobs; }; SCH_IO_EASYEDAPRO::SCH_IO_EASYEDAPRO() : SCH_IO( wxS( "EasyEDA Pro (JLCEDA) Schematic" ) ) { m_reporter = &WXLOG_REPORTER::GetInstance(); } SCH_IO_EASYEDAPRO::~SCH_IO_EASYEDAPRO() { if( m_projectData ) delete m_projectData; } bool SCH_IO_EASYEDAPRO::CanReadSchematicFile( const wxString& aFileName ) const { if( aFileName.Lower().EndsWith( wxS( ".epro" ) ) ) { return true; } else if( aFileName.Lower().EndsWith( wxS( ".zip" ) ) ) { std::shared_ptr entry; wxFFileInputStream in( aFileName ); wxZipInputStream zip( in ); if( !zip.IsOk() ) return false; while( entry.reset( zip.GetNextEntry() ), entry.get() != NULL ) { wxString name = entry->GetName(); if( name == wxS( "project.json" ) ) return true; } return false; } return false; } int SCH_IO_EASYEDAPRO::GetModifyHash() const { return 0; } static LIB_SYMBOL* loadSymbol( nlohmann::json project, const wxString& aLibraryPath, const wxString& aAliasName, const STRING_UTF8_MAP* aProperties ) { SCH_EASYEDAPRO_PARSER parser( nullptr, nullptr ); LIB_SYMBOL* symbol = nullptr; wxFileName libFname( aLibraryPath ); wxString symLibName = LIB_ID::FixIllegalChars( libFname.GetName(), true ); /*if( libFname.GetExt() == wxS( "esym" ) ) { wxFFileInputStream ffis( aLibraryPath ); wxTextInputStream txt( ffis, wxS( " " ), wxConvUTF8 ); bool loadThis = false; std::vector lines; while( ffis.CanRead() ) { nlohmann::json js = nlohmann::json::parse( txt.ReadLine() ); lines.emplace_back( js ); if( js.at( 0 ) == "ATTR" && js.at( 3 ) == "Symbol" ) { if( js.at( 4 ).get() == aAliasName ) { loadThis = true; } } } if( loadThis ) { EASYEDAPRO::SYM_INFO symInfo = parser.ParseSymbol( lines ); return symInfo.libSymbol.release(); } } else */ if( libFname.GetExt() == wxS( "elibz" ) || libFname.GetExt() == wxS( "epro" ) || libFname.GetExt() == wxS( "zip" ) ) { std::map prjSymbols = project.at( "symbols" ); std::map prjFootprints = project.at( "footprints" ); std::map prjDevices = project.at( "devices" ); auto prjSymIt = std::find_if( prjSymbols.begin(), prjSymbols.end(), [&]( const auto& pair ) { return pair.second.title == aAliasName; } ); if( prjSymIt == prjSymbols.end() ) return nullptr; wxString prjSymUuid = prjSymIt->first; wxString description; wxString customTags; std::map deviceAttributes; wxString fpTitle; for( auto& [key, device] : prjDevices ) { auto val = get_opt( device.attributes, "Symbol" ); if( val && *val == prjSymUuid ) { description = device.description; deviceAttributes = device.attributes; if( device.custom_tags.is_string() ) customTags = device.custom_tags.get(); if( auto fpUuid = get_opt( device.attributes, "Footprint" ) ) { if( auto prjFp = get_opt( prjFootprints, *fpUuid ) ) { fpTitle = prjFp->title; break; } } } } auto cb = [&]( const wxString& name, const wxString& symUuid, wxInputStream& zip ) -> bool { if( !name.EndsWith( wxS( ".esym" ) ) ) EASY_IT_CONTINUE; if( symUuid != prjSymUuid ) EASY_IT_CONTINUE; wxTextInputStream txt( zip, wxS( " " ), wxConvUTF8 ); std::vector lines; while( zip.CanRead() ) { nlohmann::json js = nlohmann::json::parse( txt.ReadLine() ); lines.emplace_back( js ); } EASYEDAPRO::SYM_INFO symInfo = parser.ParseSymbol( lines, deviceAttributes ); if( !symInfo.libSymbol ) EASY_IT_CONTINUE; LIB_ID libID = EASYEDAPRO::ToKiCadLibID( symLibName, aAliasName ); symInfo.libSymbol->SetLibId( libID ); symInfo.libSymbol->SetName( aAliasName ); symInfo.libSymbol->GetFootprintField().SetText( symLibName + wxS( ":" ) + fpTitle ); wxString keywords = customTags; keywords.Replace( wxS( ":" ), wxS( " " ), true ); symInfo.libSymbol->SetKeyWords( keywords ); description.Replace( wxS( "\u2103" ), wxS( "\u00B0C" ), true ); // ℃ -> °C symInfo.libSymbol->SetDescription( description ); symbol = symInfo.libSymbol.release(); EASY_IT_BREAK; }; EASYEDAPRO::IterateZipFiles( aLibraryPath, cb ); } return symbol; } void SCH_IO_EASYEDAPRO::EnumerateSymbolLib( wxArrayString& aSymbolNameList, const wxString& aLibraryPath, const STRING_UTF8_MAP* aProperties ) { wxFileName fname( aLibraryPath ); if( fname.GetExt() == wxS( "esym" ) ) { wxFFileInputStream ffis( aLibraryPath ); wxTextInputStream txt( ffis, wxS( " " ), wxConvUTF8 ); while( ffis.CanRead() ) { wxString line = txt.ReadLine(); if( !line.Contains( wxS( "ATTR" ) ) ) continue; // Don't bother parsing nlohmann::json js = nlohmann::json::parse( line ); if( js.at( 0 ) == "ATTR" && js.at( 3 ) == "Symbol" ) { aSymbolNameList.Add( js.at( 4 ).get() ); } } } else if( fname.GetExt() == wxS( "elibz" ) || fname.GetExt() == wxS( "epro" ) || fname.GetExt() == wxS( "zip" ) ) { nlohmann::json project = EASYEDAPRO::ReadProjectOrDeviceFile( aLibraryPath ); std::map symbolMap = project.at( "symbols" ); for( auto& [key, value] : symbolMap ) { wxString title; if( value.contains( "display_title" ) ) title = value.at( "display_title" ).get(); else title = value.at( "title" ).get(); aSymbolNameList.Add( title ); } } } void SCH_IO_EASYEDAPRO::EnumerateSymbolLib( std::vector& aSymbolList, const wxString& aLibraryPath, const STRING_UTF8_MAP* aProperties ) { wxFileName libFname( aLibraryPath ); wxArrayString symbolNameList; nlohmann::json project; EnumerateSymbolLib( symbolNameList, aLibraryPath, aProperties ); if( libFname.GetExt() == wxS( "elibz" ) || libFname.GetExt() == wxS( "epro" ) || libFname.GetExt() == wxS( "zip" ) ) { project = EASYEDAPRO::ReadProjectOrDeviceFile( aLibraryPath ); } for( const wxString& symbolName : symbolNameList ) { LIB_SYMBOL* sym = loadSymbol( project, aLibraryPath, symbolName, aProperties ); if( sym ) aSymbolList.push_back( sym ); } } void SCH_IO_EASYEDAPRO::LoadAllDataFromProject( const wxString& aProjectPath ) { if( m_projectData ) delete m_projectData; m_projectData = new PRJ_DATA; SCH_EASYEDAPRO_PARSER parser( nullptr, nullptr ); wxFileName fname( aProjectPath ); wxString symLibName = EASYEDAPRO::ShortenLibName( fname.GetName() ); if( fname.GetExt() != wxS( "epro" ) && fname.GetExt() != wxS( "zip" ) ) return; nlohmann::json project = EASYEDAPRO::ReadProjectOrDeviceFile( aProjectPath ); std::map prjSymbols = project.at( "symbols" ); std::map prjFootprints = project.at( "footprints" ); std::map prjDevices = project.at( "devices" ); auto cb = [&]( const wxString& name, const wxString& baseName, wxInputStream& zip ) -> bool { if( !name.EndsWith( wxS( ".esym" ) ) && !name.EndsWith( wxS( ".eblob" ) ) ) EASY_IT_CONTINUE; std::vector lines = EASYEDAPRO::ParseJsonLines( zip, name ); if( name.EndsWith( wxS( ".esym" ) ) ) { wxString description; wxString customTags; std::map deviceAttributes; wxString fpTitle; for( auto& [key, device] : prjDevices ) { auto val = get_opt( device.attributes, "Symbol" ); if( val && *val == baseName ) { description = device.description; deviceAttributes = device.attributes; if( device.custom_tags.is_string() ) customTags = device.custom_tags.get(); if( auto fpUuid = get_opt( device.attributes, "Footprint" ) ) { if( auto prjFp = get_opt( prjFootprints, *fpUuid ) ) { fpTitle = prjFp->title; break; } } } } EASYEDAPRO::PRJ_SYMBOL symData = prjSymbols.at( baseName ); EASYEDAPRO::SYM_INFO symInfo = parser.ParseSymbol( lines, deviceAttributes ); if( !symInfo.libSymbol ) EASY_IT_CONTINUE; LIB_ID libID = EASYEDAPRO::ToKiCadLibID( symLibName, symData.title ); symInfo.libSymbol->SetLibId( libID ); symInfo.libSymbol->SetName( symData.title ); symInfo.libSymbol->GetFootprintField().SetText( symLibName + wxS( ":" ) + fpTitle ); wxString keywords = customTags; keywords.Replace( wxS( ":" ), wxS( " " ), true ); symInfo.libSymbol->SetKeyWords( keywords ); description.Replace( wxS( "\u2103" ), wxS( "\u00B0C" ), true ); // ℃ -> °C symInfo.libSymbol->SetDescription( description ); m_projectData->m_Symbols.emplace( baseName, std::move( symInfo ) ); } else if( name.EndsWith( wxS( ".eblob" ) ) ) { for( const nlohmann::json& line : lines ) { if( line.at( 0 ) == "BLOB" ) { EASYEDAPRO::BLOB blob = line; m_projectData->m_Blobs[blob.objectId] = blob; } } } EASY_IT_CONTINUE; }; EASYEDAPRO::IterateZipFiles( aProjectPath, cb ); } 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( "elibz" ) || libFname.GetExt() == wxS( "epro" ) || libFname.GetExt() == wxS( "zip" ) ) { project = EASYEDAPRO::ReadProjectOrDeviceFile( aLibraryPath ); } return loadSymbol( project, aLibraryPath, aAliasName, aProperties ); } SCH_SHEET* SCH_IO_EASYEDAPRO::LoadSchematicFile( const wxString& aFileName, SCHEMATIC* aSchematic, SCH_SHEET* aAppendToMe, const STRING_UTF8_MAP* aProperties ) { wxCHECK( !aFileName.IsEmpty() && aSchematic, nullptr ); SCH_SHEET* rootSheet = nullptr; if( aAppendToMe ) { wxCHECK_MSG( aSchematic->IsValid(), nullptr, wxS( "Can't append to a schematic with no root!" ) ); rootSheet = &aSchematic->Root(); } else { rootSheet = new SCH_SHEET( aSchematic ); rootSheet->SetFileName( aFileName ); aSchematic->SetRoot( rootSheet ); } if( !rootSheet->GetScreen() ) { SCH_SCREEN* screen = new SCH_SCREEN( aSchematic ); screen->SetFileName( aFileName ); rootSheet->SetScreen( screen ); } SYMBOL_LIB_TABLE* libTable = PROJECT_SCH::SchSymbolLibTable( &aSchematic->Prj() ); wxCHECK_MSG( libTable, nullptr, wxS( "Could not load symbol lib table." ) ); SCH_EASYEDAPRO_PARSER parser( nullptr, nullptr ); wxFileName fname( aFileName ); wxString libName = EASYEDAPRO::ShortenLibName( fname.GetName() ); wxFileName libFileName( fname.GetPath(), libName, FILEEXT::KiCadSymbolLibFileExtension ); if( fname.GetExt() != wxS( "epro" ) && fname.GetExt() != wxS( "zip" ) ) return rootSheet; nlohmann::json project = EASYEDAPRO::ReadProjectOrDeviceFile( aFileName ); std::map prjSchematics = project.at( "schematics" ); wxString schematicToLoad; if( aProperties && aProperties->Exists( "sch_id" ) ) { schematicToLoad = wxString::FromUTF8( aProperties->at( "sch_id" ) ); } else { if( prjSchematics.size() == 1 ) { schematicToLoad = prjSchematics.begin()->first; } else { std::vector chosen = m_choose_project_handler( EASYEDAPRO::ProjectToSelectorDialog( project, false, true ) ); if( chosen.size() > 0 ) schematicToLoad = chosen[0].SchematicId; } } if( schematicToLoad.empty() ) return nullptr; wxString rootBaseName = EscapeString( prjSchematics[schematicToLoad].name, CTX_FILENAME ); wxFileName rootFname( aFileName ); rootFname.SetFullName( rootBaseName + wxS( "." ) + wxString::FromUTF8( FILEEXT::KiCadSchematicFileExtension ) ); rootSheet->SetName( prjSchematics[schematicToLoad].name ); rootSheet->SetFileName( rootFname.GetFullPath() ); rootSheet->GetScreen()->SetFileName( rootFname.GetFullPath() ); const std::vector& prjSchematicSheets = prjSchematics[schematicToLoad].sheets; LoadAllDataFromProject( aFileName ); if( !m_projectData ) return nullptr; const int schSheetsCount = prjSchematicSheets.size(); auto cbs = [&]( const wxString& name, const wxString& baseName, wxInputStream& zip ) -> bool { if( !name.EndsWith( wxS( ".esch" ) ) ) EASY_IT_CONTINUE; wxArrayString nameParts = wxSplit( name, '\\', '\0' ); if( nameParts.size() == 1 ) nameParts = wxSplit( name, '/', '\0' ); if( nameParts.size() < 3 ) EASY_IT_CONTINUE; wxString schematicUuid = nameParts[1]; wxString sheetFileName = nameParts[2]; wxString sheetId = sheetFileName.BeforeLast( '.' ); int sheetId_i; sheetId.ToInt( &sheetId_i ); if( schematicUuid != schematicToLoad ) EASY_IT_CONTINUE; auto prjSheetIt = std::find_if( prjSchematicSheets.begin(), prjSchematicSheets.end(), [&]( const EASYEDAPRO::PRJ_SHEET& s ) { return s.id == sheetId_i; } ); if( prjSheetIt == prjSchematicSheets.end() ) EASY_IT_CONTINUE; std::vector lines = EASYEDAPRO::ParseJsonLines( zip, name ); if( schSheetsCount > 1 ) { wxString sheetBaseName = sheetId + wxS( "_" ) + EscapeString( prjSheetIt->name, CTX_FILENAME ); wxFileName sheetFname( aFileName ); sheetFname.SetFullName( sheetBaseName + wxS( "." ) + wxString::FromUTF8( FILEEXT::KiCadSchematicFileExtension ) ); wxFileName relSheetPath( sheetFname ); relSheetPath.MakeRelativeTo( rootFname.GetPath() ); std::unique_ptr subSheet = std::make_unique( aSchematic ); subSheet->SetFileName( relSheetPath.GetFullPath() ); subSheet->SetName( prjSheetIt->name ); SCH_SCREEN* screen = new SCH_SCREEN( aSchematic ); screen->SetFileName( sheetFname.GetFullPath() ); screen->SetPageNumber( sheetId ); subSheet->SetScreen( screen ); VECTOR2I pos; pos.x = schIUScale.MilsToIU( 200 ); pos.y = schIUScale.MilsToIU( 200 ) + ( subSheet->GetSize().y + schIUScale.MilsToIU( 200 ) ) * ( sheetId_i - 1 ); subSheet->SetPosition( pos ); SCH_SHEET_PATH sheetPath; sheetPath.push_back( rootSheet ); sheetPath.push_back( subSheet.get() ); sheetPath.SetPageNumber( sheetId ); aSchematic->SetCurrentSheet( sheetPath ); parser.ParseSchematic( aSchematic, subSheet.get(), project, m_projectData->m_Symbols, m_projectData->m_Blobs, lines, libName ); rootSheet->GetScreen()->Append( subSheet.release() ); } else { parser.ParseSchematic( aSchematic, rootSheet, project, m_projectData->m_Symbols, m_projectData->m_Blobs, lines, libName ); } EASY_IT_CONTINUE; }; EASYEDAPRO::IterateZipFiles( aFileName, cbs ); IO_RELEASER sch_plugin( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_KICAD ) ); if( !libTable->HasLibrary( libName ) ) { // Create a new empty symbol library. sch_plugin->CreateLibrary( libFileName.GetFullPath() ); wxString libTableUri = wxS( "${KIPRJMOD}/" ) + libFileName.GetFullName(); // Add the new library to the project symbol library table. libTable->InsertRow( new SYMBOL_LIB_TABLE_ROW( libName, libTableUri, wxS( "KiCad" ) ) ); // Save project symbol library table. wxFileName fn( aSchematic->Prj().GetProjectPath(), SYMBOL_LIB_TABLE::GetSymbolLibTableFileName() ); // So output formatter goes out of scope and closes the file before reloading. { FILE_OUTPUTFORMATTER formatter( fn.GetFullPath() ); libTable->Format( &formatter, 0 ); } // Relaod the symbol library table. aSchematic->Prj().SetElem( PROJECT::ELEM_SYMBOL_LIB_TABLE, NULL ); PROJECT_SCH::SchSymbolLibTable( &aSchematic->Prj() ); } // set properties to prevent save file on every symbol save STRING_UTF8_MAP properties; properties.emplace( SCH_IO_KICAD_SEXPR::PropBuffering, wxEmptyString ); for( auto& [symbolUuid, symInfo] : m_projectData->m_Symbols ) sch_plugin->SaveSymbol( libFileName.GetFullPath(), symInfo.libSymbol.release(), &properties ); sch_plugin->SaveLibrary( libFileName.GetFullPath() ); aSchematic->CurrentSheet().UpdateAllScreenReferences(); aSchematic->FixupJunctions(); return rootSheet; }