/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2023 Alex Shvartzkop * Copyright (C) 2023 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 "pcb_easyedapro_plugin.h" #include "pcb_easyedapro_parser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct EASYEDAPRO_PLUGIN::PRJ_DATA { std::map> m_Footprints; std::map m_Blobs; std::map> m_Poured; }; EASYEDAPRO_PLUGIN::EASYEDAPRO_PLUGIN() { m_board = nullptr; m_props = nullptr; } EASYEDAPRO_PLUGIN::~EASYEDAPRO_PLUGIN() { if( m_projectData ) delete m_projectData; } bool EASYEDAPRO_PLUGIN::CanReadBoard( 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; } BOARD* EASYEDAPRO_PLUGIN::LoadBoard( const wxString& aFileName, BOARD* aAppendToMe, const STRING_UTF8_MAP* aProperties, PROJECT* aProject, PROGRESS_REPORTER* aProgressReporter ) { m_props = aProperties; m_board = aAppendToMe ? aAppendToMe : new BOARD(); // Give the filename to the board if it's new if( !aAppendToMe ) m_board->SetFileName( aFileName ); if( aProgressReporter ) { aProgressReporter->Report( wxString::Format( _( "Loading %s..." ), aFileName ) ); if( !aProgressReporter->KeepRefreshing() ) THROW_IO_ERROR( _( "Open cancelled by user." ) ); } PCB_EASYEDAPRO_PARSER parser( nullptr, nullptr ); wxFileName fname( aFileName ); if( fname.GetExt() == wxS( "epro" ) || fname.GetExt() == wxS( "zip" ) ) { nlohmann::json project = EASYEDAPRO::ReadProjectFile( aFileName ); std::map prjSchematics = project.at( "schematics" ); std::map prjBoards = project.at( "boards" ); std::map prjPcbNames = project.at( "pcbs" ); wxString pcbToLoad; if( prjBoards.size() > 0 ) { EASYEDAPRO::PRJ_BOARD boardToLoad = prjBoards.begin()->second; pcbToLoad = boardToLoad.pcb; } else if( prjPcbNames.size() > 0 ) { pcbToLoad = prjPcbNames.begin()->first; } if( prjPcbNames.empty() ) return nullptr; LoadAllDataFromProject( aFileName, project ); if( !m_projectData ) return nullptr; auto cb = [&]( const wxString& name, const wxString& pcbUuid, wxInputStream& zip ) -> bool { if( !name.EndsWith( wxS( ".epcb" ) ) ) EASY_IT_CONTINUE; if( pcbUuid != pcbToLoad ) EASY_IT_CONTINUE; std::vector lines = EASYEDAPRO::ParseJsonLines( zip, name ); wxString boardKey = pcbUuid + wxS( "_0" ); wxScopedCharBuffer cb = boardKey.ToUTF8(); wxString boardPouredKey = wxBase64Encode( cb.data(), cb.length() ); const std::multimap& boardPoured = get_def( m_projectData->m_Poured, boardPouredKey ); parser.ParseBoard( m_board, project, m_projectData->m_Footprints, m_projectData->m_Blobs, boardPoured, lines, EASYEDAPRO::ShortenLibName( fname.GetName() ) ); EASY_IT_BREAK; }; EASYEDAPRO::IterateZipFiles( aFileName, cb ); } return m_board; } long long EASYEDAPRO_PLUGIN::GetLibraryTimestamp( const wxString& aLibraryPath ) const { return 0; } void EASYEDAPRO_PLUGIN::FootprintEnumerate( wxArrayString& aFootprintNames, const wxString& aLibraryPath, bool aBestEfforts, const STRING_UTF8_MAP* aProperties ) { wxFileName fname( aLibraryPath ); if( fname.GetExt() == wxS( "efoo" ) ) { 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( 7 ) == "Footprint" ) { aFootprintNames.Add( js.at( 8 ).get() ); } } } else if( fname.GetExt() == wxS( "epro" ) || fname.GetExt() == wxS( "zip" ) ) { nlohmann::json project = EASYEDAPRO::ReadProjectFile( aLibraryPath ); std::map footprintMap = project.at( "footprints" ); for( auto& [key, value] : footprintMap ) aFootprintNames.Add( value.at( "title" ) ); } } void EASYEDAPRO_PLUGIN::LoadAllDataFromProject( const wxString& aProjectPath, const nlohmann::json& aProject ) { if( m_projectData ) delete m_projectData; m_projectData = new PRJ_DATA(); PCB_EASYEDAPRO_PARSER parser( nullptr, nullptr ); wxFileName fname( aProjectPath ); wxString fpLibName = EASYEDAPRO::ShortenLibName( fname.GetName() ); std::map> result; auto cb = [&]( const wxString& name, const wxString& baseName, wxInputStream& zip ) -> bool { if( !name.EndsWith( wxS( ".efoo" ) ) && !name.EndsWith( wxS( ".eblob" ) ) && !name.EndsWith( wxS( ".ecop" ) ) ) { EASY_IT_CONTINUE; } std::vector lines = EASYEDAPRO::ParseJsonLines( zip, name ); if( name.EndsWith( wxS( ".efoo" ) ) ) { nlohmann::json fpData = aProject.at( "footprints" ).at( baseName ); wxString fpTitle = fpData.at( "title" ); FOOTPRINT* footprint = parser.ParseFootprint( aProject, baseName, lines ); if( !footprint ) EASY_IT_CONTINUE; LIB_ID fpID = EASYEDAPRO::ToKiCadLibID( fpLibName, fpTitle ); footprint->SetFPID( fpID ); m_projectData->m_Footprints.emplace( baseName, footprint ); } 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; } } } else if( name.EndsWith( wxS( ".ecop" ) ) && EASYEDAPRO::IMPORT_POURED ) { for( const nlohmann::json& line : lines ) { if( line.at( 0 ) == "POURED" ) { if( !line.at( 2 ).is_string() ) continue; // Unknown type of POURED EASYEDAPRO::POURED poured = line; m_projectData->m_Poured[baseName].emplace( poured.parentId, poured ); } } } EASY_IT_CONTINUE; }; EASYEDAPRO::IterateZipFiles( aProjectPath, cb ); } FOOTPRINT* EASYEDAPRO_PLUGIN::FootprintLoad( const wxString& aLibraryPath, const wxString& aFootprintName, bool aKeepUUID, const STRING_UTF8_MAP* aProperties ) { PCB_EASYEDAPRO_PARSER parser( nullptr, nullptr ); FOOTPRINT* footprint = nullptr; wxFileName libFname( aLibraryPath ); if( libFname.GetExt() == wxS( "efoo" ) ) { wxFFileInputStream ffis( aLibraryPath ); wxTextInputStream txt( ffis, wxS( " " ), wxConvUTF8 ); std::vector lines = EASYEDAPRO::ParseJsonLines( ffis, aLibraryPath ); for( const nlohmann::json& js : lines ) { if( js.at( 0 ) == "ATTR" ) { EASYEDAPRO::PCB_ATTR attr = js; if( attr.key == wxS( "Footprint" ) && attr.value != aFootprintName ) return nullptr; } } footprint = parser.ParseFootprint( nlohmann::json(), wxEmptyString, lines ); if( !footprint ) { THROW_IO_ERROR( wxString::Format( _( "Cannot load footprint '%s' from '%s'" ), aFootprintName, aLibraryPath ) ); } LIB_ID fpID = EASYEDAPRO::ToKiCadLibID( wxEmptyString, aFootprintName ); footprint->SetFPID( fpID ); footprint->Reference().SetVisible( true ); footprint->Value().SetText( aFootprintName ); footprint->Value().SetVisible( true ); footprint->AutoPositionFields(); } else if( libFname.GetExt() == wxS( "epro" ) || libFname.GetExt() == wxS( "zip" ) ) { nlohmann::json project = EASYEDAPRO::ReadProjectFile( aLibraryPath ); wxString fpUuid; std::map footprintMap = project.at( "footprints" ); for( auto& [uuid, data] : footprintMap ) { wxString title = data.at( "title" ); if( title == aFootprintName ) { fpUuid = uuid; break; } } if( !fpUuid ) { THROW_IO_ERROR( wxString::Format( _( "Footprint '%s' not found in project '%s'" ), aFootprintName, aLibraryPath ) ); } auto cb = [&]( const wxString& name, const wxString& baseName, wxInputStream& zip ) -> bool { if( !name.EndsWith( wxS( ".efoo" ) ) ) EASY_IT_CONTINUE; if( baseName != fpUuid ) EASY_IT_CONTINUE; std::vector lines = EASYEDAPRO::ParseJsonLines( zip, name ); footprint = parser.ParseFootprint( project, fpUuid, lines ); if( !footprint ) { THROW_IO_ERROR( wxString::Format( _( "Cannot load footprint '%s' from '%s'" ), aFootprintName, aLibraryPath ) ); } LIB_ID fpID = EASYEDAPRO::ToKiCadLibID( wxEmptyString, aFootprintName ); footprint->SetFPID( fpID ); footprint->Reference().SetVisible( true ); footprint->Value().SetText( aFootprintName ); footprint->Value().SetVisible( true ); footprint->AutoPositionFields(); EASY_IT_BREAK; }; EASYEDAPRO::IterateZipFiles( aLibraryPath, cb ); } return footprint; } std::vector EASYEDAPRO_PLUGIN::GetImportedCachedLibraryFootprints() { std::vector result; if( !m_projectData ) return result; for( auto& [fpUuid, footprint] : m_projectData->m_Footprints ) { result.push_back( static_cast( footprint->Clone() ) ); } return result; }