/*
* 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 );
}