/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2019 CERN * Copyright The 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 3 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, see . */ #include "import_proj.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include IMPORT_PROJ_HELPER::IMPORT_PROJ_HELPER( KICAD_MANAGER_FRAME* aFrame, const std::vector& aSchFileExtensions, const std::vector& aPcbFileExtensions ) : m_frame( aFrame ), m_schExtenstions( aSchFileExtensions ), m_pcbExtenstions( aPcbFileExtensions ) { } void IMPORT_PROJ_HELPER::FindEmptyTargetDir() { // Append a new directory with the same name of the project file // Keep iterating until we find an empty directory wxString newDir = m_TargetProj.GetName(); int attempt = 0; m_TargetProj.AppendDir( newDir ); while( m_TargetProj.DirExists() ) { m_TargetProj.RemoveLastDir(); wxString suffix = wxString::Format( "_%d", ++attempt ); m_TargetProj.AppendDir( newDir + suffix ); } } class SCOPED_FILE_REMOVER { wxString m_file; public: SCOPED_FILE_REMOVER( const wxString& aFile ) : m_file( aFile ) {} ~SCOPED_FILE_REMOVER() { wxRemoveFile( m_file ); } }; void IMPORT_PROJ_HELPER::ImportIndividualFile( KICAD_T aFT, int aImportedFileType ) { FRAME_T frame_type; wxString appImportFile; std::vector neededExts; switch( aFT ) { case SCHEMATIC_T: neededExts = m_schExtenstions; frame_type = FRAME_SCH; break; case PCB_T: neededExts = m_pcbExtenstions; frame_type = FRAME_PCB_EDITOR; break; default: return; } std::vector copiedFiles; for( wxString ext : neededExts ) { if( ext == wxS( "INPUT" ) ) ext = m_InputFile.GetExt(); wxFileName candidate = m_InputFile; candidate.SetExt( ext ); if( !candidate.FileExists() ) continue; wxFileName targetFile( m_TargetProj.GetPath(), candidate.GetName(), candidate.GetExt() ); if( !targetFile.FileExists() ) { bool copied = wxCopyFile( candidate.GetFullPath(), targetFile.GetFullPath(), false ); if( copied ) { // Will be auto-removed copiedFiles.emplace_back( targetFile.GetFullPath() ); } } // Pick the first file to pass to application if( appImportFile.empty() && targetFile.FileExists() ) appImportFile = targetFile.GetFullPath(); } if( appImportFile.empty() ) return; doImport( appImportFile, frame_type, aImportedFileType ); } void IMPORT_PROJ_HELPER::doImport( const wxString& aFile, FRAME_T aFrameType, int aImportedFileType ) { if( KIWAY_PLAYER* frame = m_frame->Kiway().Player( aFrameType, true ) ) { std::stringstream ss; ss << aImportedFileType << '\n' << TO_UTF8( aFile ); for( const auto& [key, value] : m_properties ) ss << '\n' << key << '\n' << value.wx_str(); std::string packet = ss.str(); frame->Kiway().ExpressMail( aFrameType, MAIL_IMPORT_FILE, packet, m_frame ); if( !frame->IsShownOnScreen() ) frame->Show( true ); // On Windows, Raise() does not bring the window on screen, when iconized if( frame->IsIconized() ) frame->Iconize( false ); frame->Raise(); } } void IMPORT_PROJ_HELPER::EasyEDAProProjectHandler() { wxFileName fname = m_InputFile; if( fname.GetExt() == wxS( "epro" ) || fname.GetExt() == wxS( "zip" ) ) { nlohmann::json project = EASYEDAPRO::ReadProjectOrDeviceFile( fname.GetFullPath() ); std::map prjSchematics = project.at( "schematics" ); std::map prjBoards = project.at( "boards" ); std::map prjPcbNames = project.at( "pcbs" ); std::vector toImport = EASYEDAPRO::ProjectToSelectorDialog( project, false, false ); if( toImport.size() > 1 ) toImport = DIALOG_IMPORT_CHOOSE_PROJECT::RunModal( m_frame, toImport ); if( toImport.size() == 1 ) { const IMPORT_PROJECT_DESC& desc = toImport[0]; m_properties["pcb_id"] = desc.PCBId; m_properties["sch_id"] = desc.SchematicId; } else { m_properties["pcb_id"] = ""; m_properties["sch_id"] = ""; } } } void IMPORT_PROJ_HELPER::addLocalLibraries( const std::set& aNames, FRAME_T aFrameType ) { KIWAY_PLAYER* frame = m_frame->Kiway().Player( aFrameType, true ); std::stringstream ss; // First line is the source project directory so the handler can distinguish // project-local libraries from external fixed-path references. ss << TO_UTF8( m_InputFile.GetPath() ) << '\n'; for( const wxString& name : aNames ) { wxFileName fname( name ); fname.MakeAbsolute( m_InputFile.GetPath() ); ss << TO_UTF8( fname.GetFullPath() ) << '\n'; } std::string packet = ss.str(); frame->Kiway().ExpressMail( aFrameType, MAIL_ADD_LOCAL_LIB, packet, m_frame ); } void IMPORT_PROJ_HELPER::AltiumProjectHandler() { wxFileConfig config( wxEmptyString, wxEmptyString, wxEmptyString, m_InputFile.GetFullPath(), wxCONFIG_USE_NO_ESCAPE_CHARACTERS ); wxString groupname; long groupid; std::set sch_file; std::set sch_libs; std::set pcb_libs; wxString pcb_file; int pcb_file_type = PCB_IO_MGR::ALTIUM_DESIGNER; auto matchPcbExt = []( const wxString& aExt ) -> int { if( !aExt.CmpNoCase( wxS( "PcbDoc" ) ) ) return PCB_IO_MGR::ALTIUM_DESIGNER; if( !aExt.CmpNoCase( wxS( "CSPcbDoc" ) ) ) return PCB_IO_MGR::ALTIUM_CIRCUIT_STUDIO; if( !aExt.CmpNoCase( wxS( "CMPcbDoc" ) ) ) return PCB_IO_MGR::ALTIUM_CIRCUIT_MAKER; if( !aExt.CmpNoCase( wxS( "SWPcbDoc" ) ) ) return PCB_IO_MGR::SOLIDWORKS_PCB; return -1; }; for( bool more = config.GetFirstGroup( groupname, groupid ); more; more = config.GetNextGroup( groupname, groupid ) ) { if( !groupname.StartsWith( wxS( "Document" ) ) ) continue; wxString number = groupname.Mid( 8 ); long docNumber; if( !number.ToLong( &docNumber ) ) continue; wxString path = config.Read( groupname + wxS( "/DocumentPath" ), wxEmptyString ); if( path.empty() ) continue; wxFileName fname( path, wxPATH_WIN ); if( !fname.IsAbsolute() ) fname.MakeAbsolute( m_InputFile.GetPath() ); int pcbType = matchPcbExt( fname.GetExt() ); if( pcbType >= 0 && pcb_file.empty() ) { pcb_file = fname.GetFullPath(); pcb_file_type = pcbType; } if( !fname.GetExt().CmpNoCase( "SCHDOC" ) ) sch_file.insert( fname.GetFullPath() ); if( !fname.GetExt().CmpNoCase( "PCBLIB" ) ) pcb_libs.insert( fname.GetFullPath() ); if( !fname.GetExt().CmpNoCase( "SCHLIB" ) ) sch_libs.insert( fname.GetFullPath() ); } addLocalLibraries( sch_libs, FRAME_SCH ); addLocalLibraries( pcb_libs, FRAME_PCB_EDITOR ); m_properties["project_file"] = m_InputFile.GetFullPath(); int ii = 0; for( auto& path : sch_file ) { std::string key = "sch" + std::to_string( ii++ ); m_properties[key] = path.ToStdString(); } if( !sch_file.empty() ) doImport( "", FRAME_SCH, SCH_IO_MGR::SCH_ALTIUM ); if( !pcb_file.empty() ) doImport( pcb_file, FRAME_PCB_EDITOR, pcb_file_type ); } void IMPORT_PROJ_HELPER::ImportPadsFiles() { m_properties.clear(); PADS_COMMON::RELATED_FILES related = PADS_COMMON::FindRelatedPadsFiles( m_InputFile.GetFullPath() ); if( !related.HasPcb() && !related.HasSchematic() ) return; std::vector copiedFiles; auto copyAndImport = [&]( const wxString& sourceFile, FRAME_T frameType, int fileType ) { if( sourceFile.IsEmpty() ) return; wxFileName srcFn( sourceFile ); wxFileName targetFile( m_TargetProj.GetPath(), srcFn.GetName(), srcFn.GetExt() ); if( !targetFile.FileExists() ) { bool copied = wxCopyFile( srcFn.GetFullPath(), targetFile.GetFullPath(), false ); if( copied ) copiedFiles.emplace_back( targetFile.GetFullPath() ); } if( targetFile.FileExists() ) doImport( targetFile.GetFullPath(), frameType, fileType ); }; copyAndImport( related.schematicFile, FRAME_SCH, SCH_IO_MGR::SCH_PADS ); copyAndImport( related.pcbFile, FRAME_PCB_EDITOR, PCB_IO_MGR::PADS ); } void IMPORT_PROJ_HELPER::GedaProjectHandler() { wxFileName prjFile = m_InputFile; if( prjFile.GetExt().CmpNoCase( "prj" ) != 0 ) return; wxTextFile file; if( !file.Open( prjFile.GetFullPath() ) ) { wxLogWarning( _( "Could not open gEDA / Lepton EDA project file '%s'." ), prjFile.GetFullPath() ); return; } std::vector schFiles; wxString outputName; wxString elementsDir; for( size_t i = 0; i < file.GetLineCount(); ++i ) { wxString line = file.GetLine( i ); line.Trim( true ); line.Trim( false ); if( line.IsEmpty() ) continue; if( line.StartsWith( wxT( "#" ) ) || line.StartsWith( wxT( ";" ) ) ) continue; int commentPos = line.Find( wxT( "#" ) ); if( commentPos != wxNOT_FOUND ) { line = line.Left( commentPos ); line.Trim( true ); line.Trim( false ); } if( line.IsEmpty() ) continue; wxStringTokenizer tok( line ); if( !tok.HasMoreTokens() ) continue; wxString keyword = tok.GetNextToken(); if( keyword.CmpNoCase( "schematics" ) == 0 ) { while( tok.HasMoreTokens() ) { wxString schToken = tok.GetNextToken(); wxFileName schFile( schToken ); if( !schFile.IsAbsolute() ) schFile.MakeAbsolute( prjFile.GetPath() ); schFiles.push_back( schFile ); } } else if( keyword.CmpNoCase( "output-name" ) == 0 ) { wxString rest = line.Mid( keyword.length() ); rest.Trim( true ); rest.Trim( false ); outputName = rest; } else if( keyword.CmpNoCase( "elements-dir" ) == 0 ) { wxString rest = line.Mid( keyword.length() ); rest.Trim( true ); rest.Trim( false ); elementsDir = rest; } } if( !elementsDir.IsEmpty() ) { wxFileName elementsPath( elementsDir ); if( !elementsPath.IsAbsolute() ) elementsPath.MakeAbsolute( prjFile.GetPath() ); m_properties["elements_dir"] = elementsPath.GetFullPath().ToStdString(); } if( schFiles.empty() ) { wxFileName candidate = prjFile; candidate.SetExt( wxS( "sch" ) ); if( candidate.FileExists() ) schFiles.push_back( candidate ); } auto promptForMissingFile = [&]( const wxString& aTitle, const wxString& aWildcard, wxFileName& aFile ) -> bool { wxString defaultDir = aFile.GetPath(); if( defaultDir.IsEmpty() ) defaultDir = prjFile.GetPath(); wxFileDialog dlg( m_frame, aTitle, defaultDir, wxEmptyString, aWildcard, wxFD_OPEN | wxFD_FILE_MUST_EXIST ); if( dlg.ShowModal() == wxID_OK ) { aFile.Assign( dlg.GetPath() ); return true; } aFile.Clear(); return false; }; std::vector resolvedSchFiles; const wxString schWildcard = _( "gEDA / Lepton EDA schematic files" ) + wxS( " (*.sch)|*.sch" ); for( wxFileName schFile : schFiles ) { if( !schFile.FileExists() ) { if( !promptForMissingFile( _( "Locate gEDA / Lepton EDA Schematic" ), schWildcard, schFile ) ) continue; } resolvedSchFiles.push_back( schFile ); } if( schFiles.empty() && resolvedSchFiles.empty() ) { wxFileName schFile; if( promptForMissingFile( _( "Locate gEDA / Lepton EDA Schematic" ), schWildcard, schFile ) ) resolvedSchFiles.push_back( schFile ); } if( resolvedSchFiles.size() > 1 ) { // Pass additional schematic files as a semicolon-delimited property so the // importer can create sub-sheets for each additional page within a single // KiCad project hierarchy. wxString additionalFiles; for( size_t i = 1; i < resolvedSchFiles.size(); i++ ) { if( !additionalFiles.IsEmpty() ) additionalFiles += wxT( ";" ); additionalFiles += resolvedSchFiles[i].GetFullPath(); } m_properties["additional_schematics"] = additionalFiles.ToStdString(); } // Auto-discover subdirectories containing .sym files and pass them as // additional search paths for the importer's symbol resolution. auto discoverSymDirs = [&]( const wxString& aBaseDir, wxString& aSymPaths ) { wxDir dir( aBaseDir ); if( !dir.IsOpened() ) return; wxString subdir; bool cont = dir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS ); while( cont ) { wxString subdirPath = aBaseDir + wxFileName::GetPathSeparator() + subdir; wxDir childDir( subdirPath ); if( childDir.IsOpened() && childDir.HasFiles( wxT( "*.sym" ) ) ) { if( !aSymPaths.IsEmpty() ) aSymPaths += wxT( "\n" ); aSymPaths += subdirPath; } cont = dir.GetNext( &subdir ); } }; wxString symPaths; discoverSymDirs( prjFile.GetPath(), symPaths ); if( !resolvedSchFiles.empty() ) { wxString schDir = resolvedSchFiles[0].GetPath(); if( schDir != prjFile.GetPath() ) discoverSymDirs( schDir, symPaths ); } if( !symPaths.IsEmpty() ) m_properties["sym_search_paths"] = symPaths.ToStdString(); if( !resolvedSchFiles.empty() ) doImport( resolvedSchFiles[0].GetFullPath(), FRAME_SCH, SCH_IO_MGR::SCH_GEDA ); wxFileName layoutDir( prjFile.GetPath(), wxEmptyString ); layoutDir.RemoveLastDir(); layoutDir.AppendDir( wxS( "layout" ) ); wxFileName pcbFile; if( !outputName.IsEmpty() ) { wxFileName candidate( layoutDir.GetPath(), outputName, wxS( "pcb" ) ); if( candidate.FileExists() ) pcbFile = candidate; } if( !pcbFile.FileExists() ) { wxFileName candidate( prjFile.GetPath(), prjFile.GetName(), wxS( "pcb" ) ); if( candidate.FileExists() ) pcbFile = candidate; } if( !pcbFile.FileExists() ) { wxFileName candidate( layoutDir.GetPath(), prjFile.GetName(), wxS( "pcb" ) ); if( candidate.FileExists() ) pcbFile = candidate; } const wxString pcbWildcard = _( "gEDA / Lepton EDA PCB files" ) + wxS( " (*.pcb)|*.pcb" ); if( !pcbFile.FileExists() ) promptForMissingFile( _( "Locate gEDA / Lepton EDA PCB" ), pcbWildcard, pcbFile ); if( pcbFile.FileExists() ) doImport( pcbFile.GetFullPath(), FRAME_PCB_EDITOR, PCB_IO_MGR::GEDA_PCB ); } void IMPORT_PROJ_HELPER::ImportFiles( int aImportedSchFileType, int aImportedPcbFileType ) { m_properties.clear(); if( aImportedSchFileType == SCH_IO_MGR::SCH_EASYEDAPRO || aImportedPcbFileType == PCB_IO_MGR::EASYEDAPRO ) { EasyEDAProProjectHandler(); } else if( aImportedSchFileType == SCH_IO_MGR::SCH_ALTIUM || aImportedPcbFileType == PCB_IO_MGR::ALTIUM_DESIGNER ) { AltiumProjectHandler(); return; } else if( aImportedSchFileType == SCH_IO_MGR::SCH_GEDA ) { if( m_InputFile.GetExt().CmpNoCase( "prj" ) == 0 ) { GedaProjectHandler(); } else if( m_InputFile.GetExt().CmpNoCase( "pcb" ) == 0 ) { ImportIndividualFile( PCB_T, aImportedPcbFileType ); } else { // When importing a bare .sch file, pass the original source directory // so the importer can find symbols in subdirectories relative to the // gEDA / Lepton EDA schematic, even though the file may be copied to a new location // for the KiCad project. wxString sourceDir = m_InputFile.GetPath(); wxString symPaths = sourceDir; auto discoverSymDirs = [&]( const wxString& aBaseDir ) { wxDir dir( aBaseDir ); if( !dir.IsOpened() ) return; wxString subdir; bool cont = dir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS ); while( cont ) { wxString subdirPath = aBaseDir + wxFileName::GetPathSeparator() + subdir; wxDir childDir( subdirPath ); if( childDir.IsOpened() && childDir.HasFiles( wxT( "*.sym" ) ) ) symPaths += wxT( "\n" ) + subdirPath; cont = dir.GetNext( &subdir ); } }; discoverSymDirs( sourceDir ); m_properties["sym_search_paths"] = symPaths.ToStdString(); doImport( m_InputFile.GetFullPath(), FRAME_SCH, SCH_IO_MGR::SCH_GEDA ); ImportIndividualFile( PCB_T, aImportedPcbFileType ); } return; } ImportIndividualFile( SCHEMATIC_T, aImportedSchFileType ); ImportIndividualFile( PCB_T, aImportedPcbFileType ); }