/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2004-2015 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2011 Wayne Stambaugh * Copyright (C) 2016-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 #include #include #include #include #include #include <3d_viewer/eda_3d_viewer_frame.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 #include #include #include #include #include "footprint_info_impl.h" #include "board_commit.h" #include "zone_filler.h" #include // For ::ResolvePossibleSymlinks() #include #include #include #include #include #if wxCHECK_VERSION( 3, 1, 7 ) #include "widgets/filedlg_hook_save_project.h" #else #include "widgets/legacyfiledlg_save_project.h" #endif //#define USE_INSTRUMENTATION 1 #define USE_INSTRUMENTATION 0 /** * Show a wxFileDialog asking for a #BOARD filename to open. * * @param aParent is a wxFrame passed to wxFileDialog. * @param aCtl is where to put the OpenProjectFiles() control bits. * @param aFileName on entry is a probable choice, on return is the chosen filename. * @param aKicadFilesOnly true to list KiCad pcb files plugins only, false to list import plugins. * @return true if chosen, else false if user aborted. */ bool AskLoadBoardFileName( PCB_EDIT_FRAME* aParent, int* aCtl, wxString* aFileName, bool aKicadFilesOnly ) { // This is a subset of all PLUGINs which are trusted to be able to // load a BOARD. User may occasionally use the wrong plugin to load a // *.brd file (since both legacy and eagle use *.brd extension), // but eventually *.kicad_pcb will be more common than legacy *.brd files. // clang-format off static const struct { const wxString& filter; IO_MGR::PCB_FILE_T pluginType; } loaders[] = { // Current Kicad board files. { PcbFileWildcard(), IO_MGR::KICAD_SEXP }, // Old Kicad board files. { LegacyPcbFileWildcard(), IO_MGR::LEGACY }, // Import Altium Circuit Maker board files. { AltiumCircuitMakerPcbFileWildcard(), IO_MGR::ALTIUM_CIRCUIT_MAKER }, // Import Altium Circuit Studio board files. { AltiumCircuitStudioPcbFileWildcard(), IO_MGR::ALTIUM_CIRCUIT_STUDIO }, // Import Altium Designer board files. { AltiumDesignerPcbFileWildcard(), IO_MGR::ALTIUM_DESIGNER }, // Import Cadstar PCB Archive board files. { CadstarPcbArchiveFileWildcard(), IO_MGR::CADSTAR_PCB_ARCHIVE }, // Import Eagle board files. { EaglePcbFileWildcard(), IO_MGR::EAGLE }, // Import PCAD board files. { PCadPcbFileWildcard(), IO_MGR::PCAD }, // Import Fabmaster board files. { FabmasterPcbFileWildcard(), IO_MGR::FABMASTER }, }; // clang-format on wxFileName fileName( *aFileName ); wxString fileFilters; if( aKicadFilesOnly ) { std::vector fileExtensions; for( unsigned ii = 0; ii < 2; ++ii ) { if( !fileFilters.IsEmpty() ) fileFilters += wxChar( '|' ); fileFilters += wxGetTranslation( loaders[ii].filter ); PLUGIN::RELEASER plugin( IO_MGR::PluginFind( loaders[ii].pluginType ) ); wxCHECK( plugin, false ); fileExtensions.push_back( plugin->GetFileExtension().ToStdString() ); } fileFilters = _( "All KiCad Board Files" ) + AddFileExtListToFilter( fileExtensions ) + wxT( "|" ) + fileFilters; } else { wxString allWildcards; for( unsigned ii = 2; ii < arrayDim( loaders ); ++ii ) { if( !fileFilters.IsEmpty() ) fileFilters += wxChar( '|' ); fileFilters += wxGetTranslation( loaders[ii].filter ); PLUGIN::RELEASER plugin( IO_MGR::PluginFind( loaders[ii].pluginType ) ); wxCHECK( plugin, false ); allWildcards += wxT( "*." ) + formatWildcardExt( plugin->GetFileExtension() ) + wxT( ";" ); } fileFilters = _( "All supported formats" ) + wxT( "|" ) + allWildcards + wxT( "|" ) + fileFilters; } wxString path; wxString name; if( fileName.FileExists() ) { path = fileName.GetPath(); name = fileName.GetFullName(); } else { path = aParent->GetMruPath(); if( path.IsEmpty() ) path = PATHS::GetDefaultUserProjectsPath(); // leave name empty } wxFileDialog dlg( aParent, aKicadFilesOnly ? _( "Open Board File" ) : _( "Import Non KiCad Board File" ), path, name, fileFilters, wxFD_OPEN | wxFD_FILE_MUST_EXIST ); if( dlg.ShowModal() == wxID_OK ) { // For import option, if Eagle (*.brd files), tell OpenProjectFiles() to use Eagle plugin. // It's the only special case because of the duplicate use of the *.brd file extension. // Other cases are clear because of unique file extensions. *aCtl = aKicadFilesOnly ? 0 : KICTL_EAGLE_BRD; *aFileName = dlg.GetPath(); aParent->SetMruPath( wxFileName( dlg.GetPath() ).GetPath() ); return true; } else { return false; } } /** * Put up a wxFileDialog asking for a BOARD filename to save. * * @param aParent is a wxFrame passed to wxFileDialog. * @param aFileName on entry is a probable choice, on return is the chosen full filename * (includes path). * @param aCreateProject will be filled with the state of the Create Project? checkbox if relevant. * @return true if chosen, else false if user aborted. */ bool AskSaveBoardFileName( PCB_EDIT_FRAME* aParent, wxString* aFileName, bool* aCreateProject ) { wxString wildcard = PcbFileWildcard(); wxFileName fn = *aFileName; fn.SetExt( KiCadPcbFileExtension ); wxFileDialog dlg( aParent, _( "Save Board File As" ), fn.GetPath(), fn.GetFullName(), wildcard, wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); // Add a "Create a project" checkbox in standalone mode and one isn't loaded #if wxCHECK_VERSION( 3, 1, 7 ) FILEDLG_HOOK_SAVE_PROJECT newProjectHook; if( Kiface().IsSingle() && aParent->Prj().IsNullProject() ) dlg.SetCustomizeHook( newProjectHook ); #else if( Kiface().IsSingle() && aParent->Prj().IsNullProject() ) dlg.SetExtraControlCreator( &LEGACYFILEDLG_SAVE_PROJECT::Create ); #endif if( dlg.ShowModal() != wxID_OK ) return false; fn = dlg.GetPath(); // always enforce filename extension, user may not have entered it. fn.SetExt( KiCadPcbFileExtension ); *aFileName = fn.GetFullPath(); #if wxCHECK_VERSION( 3, 1, 7 ) if( newProjectHook.IsAttachedToDialog() ) *aCreateProject = newProjectHook.GetCreateNewProject(); else if( !aParent->Prj().IsNullProject() ) *aCreateProject = true; #else if( wxWindow* ec = dlg.GetExtraControl() ) *aCreateProject = static_cast( ec )->GetValue(); else if( !aParent->Prj().IsNullProject() ) *aCreateProject = true; #endif return true; } void PCB_EDIT_FRAME::OnFileHistory( wxCommandEvent& event ) { wxString fn = GetFileFromHistory( event.GetId(), _( "Printed circuit board" ) ); if( !!fn ) { int open_ctl = 0; if( !wxFileName::IsFileReadable( fn ) ) { if( !AskLoadBoardFileName( this, &open_ctl, &fn, true ) ) return; } OpenProjectFiles( std::vector( 1, fn ), open_ctl ); } } void PCB_EDIT_FRAME::OnClearFileHistory( wxCommandEvent& aEvent ) { ClearFileHistory(); } void PCB_EDIT_FRAME::Files_io( wxCommandEvent& event ) { int id = event.GetId(); Files_io_from_id( id ); } bool PCB_EDIT_FRAME::Files_io_from_id( int id ) { wxString msg; switch( id ) { case ID_LOAD_FILE: { int open_ctl = 0; wxString fileName = Prj().AbsolutePath( GetBoard()->GetFileName() ); return AskLoadBoardFileName( this, &open_ctl, &fileName, true ) && OpenProjectFiles( std::vector( 1, fileName ), open_ctl ); } case ID_IMPORT_NON_KICAD_BOARD: { int open_ctl = 1; wxString fileName; // = Prj().AbsolutePath( GetBoard()->GetFileName() ); return AskLoadBoardFileName( this, &open_ctl, &fileName, false ) && OpenProjectFiles( std::vector( 1, fileName ), open_ctl ); } case ID_MENU_RECOVER_BOARD_AUTOSAVE: { wxFileName currfn = Prj().AbsolutePath( GetBoard()->GetFileName() ); wxFileName fn = currfn; wxString rec_name = GetAutoSaveFilePrefix() + fn.GetName(); fn.SetName( rec_name ); if( !fn.FileExists() ) { msg.Printf( _( "Recovery file '%s' not found." ), fn.GetFullPath() ); DisplayInfoMessage( this, msg ); return false; } msg.Printf( _( "OK to load recovery file '%s'?" ), fn.GetFullPath() ); if( !IsOK( this, msg ) ) return false; GetScreen()->SetContentModified( false ); // do not prompt the user for changes if( OpenProjectFiles( std::vector( 1, fn.GetFullPath() ) ) ) { // Re-set the name since name or extension was changed GetBoard()->SetFileName( currfn.GetFullPath() ); UpdateTitle(); return true; } return false; } case ID_REVERT_BOARD: { wxFileName fn = Prj().AbsolutePath( GetBoard()->GetFileName() ); msg.Printf( _( "Revert '%s' to last version saved?" ), fn.GetFullPath() ); if( !IsOK( this, msg ) ) return false; GetScreen()->SetContentModified( false ); // do not prompt the user for changes ReleaseFile(); return OpenProjectFiles( std::vector( 1, fn.GetFullPath() ) ); } case ID_NEW_BOARD: { if( IsContentModified() ) { wxFileName fileName = GetBoard()->GetFileName(); wxString saveMsg = _( "Current board will be closed, save changes to '%s' before " "continuing?" ); if( !HandleUnsavedChanges( this, wxString::Format( saveMsg, fileName.GetFullName() ), [&]()->bool { return Files_io_from_id( ID_SAVE_BOARD ); } ) ) { return false; } } else if( !GetBoard()->IsEmpty() ) { if( !IsOK( this, _( "Current Board will be closed. Continue?" ) ) ) return false; } SaveProjectLocalSettings(); GetBoard()->ClearProject(); SETTINGS_MANAGER* mgr = GetSettingsManager(); mgr->UnloadProject( &mgr->Prj() ); if( !Clear_Pcb( false ) ) return false; LoadProjectSettings(); onBoardLoaded(); OnModify(); return true; } case ID_SAVE_BOARD: if( !GetBoard()->GetFileName().IsEmpty() ) { if( SavePcbFile( Prj().AbsolutePath( GetBoard()->GetFileName() ) ) ) { m_autoSaveRequired = false; return true; } return false; } KI_FALLTHROUGH; case ID_COPY_BOARD_AS: case ID_SAVE_BOARD_AS: { bool addToHistory = ( id == ID_SAVE_BOARD_AS ); wxString orig_name; wxFileName::SplitPath( GetBoard()->GetFileName(), nullptr, nullptr, &orig_name, nullptr ); if( orig_name.IsEmpty() ) orig_name = NAMELESS_PROJECT; wxFileName savePath( Prj().GetProjectFullName() ); if( !savePath.IsOk() || !savePath.IsDirWritable() ) { savePath = GetMruPath(); if( !savePath.IsOk() || !savePath.IsDirWritable() ) savePath = PATHS::GetDefaultUserProjectsPath(); } wxFileName fn( savePath.GetPath(), orig_name, KiCadPcbFileExtension ); wxString filename = fn.GetFullPath(); bool createProject = false; bool success = false; if( AskSaveBoardFileName( this, &filename, &createProject ) ) { if( id == ID_COPY_BOARD_AS ) { success = SavePcbCopy( filename, createProject ); } else { success = SavePcbFile( filename, addToHistory, createProject ); if( success ) m_autoSaveRequired = false; } } return success; } default: return false; } } // The KIWAY_PLAYER::OpenProjectFiles() API knows nothing about plugins, so // determine how to load the BOARD here, with minor assistance from KICTL_EAGLE_BRD // bit flag. IO_MGR::PCB_FILE_T plugin_type( const wxString& aFileName, int aCtl ) { IO_MGR::PCB_FILE_T pluginType; wxFileName fn = aFileName; // Note: file extensions are expected to be in lower case. // This is not always true, especially when importing files, so the string // comparisons are case insensitive to try to find the suitable plugin. if( fn.GetExt().CmpNoCase( IO_MGR::GetFileExtension( IO_MGR::LEGACY ) ) == 0 ) { wxFileInputStream input( aFileName ); bool is_legacy = true; if(input.IsOk() && !input.Eof() ) { wxTextInputStream text( input ); wxString line = text.ReadLine(); if( !line.StartsWith( wxT( "PCBNEW" ) ) ) is_legacy = false; } // both legacy and eagle share a common file extension. pluginType = ( aCtl & KICTL_EAGLE_BRD ) || !is_legacy ? IO_MGR::EAGLE : IO_MGR::LEGACY; } else if( fn.GetExt().CmpNoCase( IO_MGR::GetFileExtension( IO_MGR::PCAD ) ) == 0 ) { pluginType = IO_MGR::PCAD; } else if( fn.GetExt().CmpNoCase( IO_MGR::GetFileExtension( IO_MGR::ALTIUM_DESIGNER ) ) == 0 ) { pluginType = IO_MGR::ALTIUM_DESIGNER; } else if( fn.GetExt().CmpNoCase( IO_MGR::GetFileExtension( IO_MGR::ALTIUM_CIRCUIT_STUDIO ) ) == 0 ) { pluginType = IO_MGR::ALTIUM_CIRCUIT_STUDIO; } else if( fn.GetExt().CmpNoCase( IO_MGR::GetFileExtension( IO_MGR::ALTIUM_CIRCUIT_MAKER ) ) == 0 ) { pluginType = IO_MGR::ALTIUM_CIRCUIT_MAKER; } else if( fn.GetExt().CmpNoCase( IO_MGR::GetFileExtension( IO_MGR::CADSTAR_PCB_ARCHIVE ) ) == 0 ) { pluginType = IO_MGR::CADSTAR_PCB_ARCHIVE; } else if( fn.GetExt().CmpNoCase( IO_MGR::GetFileExtension( IO_MGR::FABMASTER ) ) == 0 ) { pluginType = IO_MGR::FABMASTER; } else { pluginType = IO_MGR::KICAD_SEXP; } return pluginType; } int PCB_EDIT_FRAME::inferLegacyEdgeClearance( BOARD* aBoard ) { PCB_LAYER_COLLECTOR collector; collector.SetLayerId( Edge_Cuts ); collector.Collect( aBoard, GENERAL_COLLECTOR::AllBoardItems ); int edgeWidth = -1; bool mixed = false; for( int i = 0; i < collector.GetCount(); i++ ) { if( collector[i]->Type() == PCB_SHAPE_T ) { int itemWidth = static_cast( collector[i] )->GetWidth(); if( edgeWidth != -1 && edgeWidth != itemWidth ) { mixed = true; edgeWidth = std::max( edgeWidth, itemWidth ); } else { edgeWidth = itemWidth; } } } if( mixed ) { // If they had different widths then we can't ensure that fills will be the same. DisplayInfoMessage( this, _( "If the zones on this board are refilled the Copper Edge Clearance " "setting will be used (see Board Setup > Design Rules > Constraints).\n" "This may result in different fills from previous KiCad versions which " "used the line thicknesses of the board boundary on the Edge Cuts " "layer." ) ); } return std::max( 0, edgeWidth / 2 ); } bool PCB_EDIT_FRAME::OpenProjectFiles( const std::vector& aFileSet, int aCtl ) { // This is for python: if( aFileSet.size() != 1 ) { UTF8 msg = StrPrintf( "Pcbnew:%s() takes a single filename", __func__ ); DisplayError( this, msg ); return false; } wxString fullFileName( aFileSet[0] ); wxFileName wx_filename( fullFileName ); wxString msg; if( Kiface().IsSingle() ) KIPLATFORM::APP::RegisterApplicationRestart( fullFileName ); // We insist on caller sending us an absolute path, if it does not, we say it's a bug. wxASSERT_MSG( wx_filename.IsAbsolute(), wxT( "Path is not absolute!" ) ); std::unique_ptr lock = std::make_unique( fullFileName ); if( !lock->Valid() && lock->IsLockedByMe() ) { // If we cannot acquire the lock but we appear to be the one who // locked it, check to see if there is another KiCad instance running. // If there is not, then we can override the lock. This could happen if // KiCad crashed or was interrupted if( !Pgm().SingleInstance()->IsAnotherRunning() ) lock->OverrideLock(); } if( !lock->Valid() ) { msg.Printf( _( "PCB '%s' is already open by '%s' at '%s'." ), wx_filename.GetFullName(), lock->GetUsername(), lock->GetHostname() ); if( !AskOverrideLock( this, msg ) ) return false; lock->OverrideLock(); } if( IsContentModified() ) { if( !HandleUnsavedChanges( this, _( "The current PCB has been modified. Save changes?" ), [&]() -> bool { return SavePcbFile( GetBoard()->GetFileName() ); } ) ) { return false; } } wxFileName pro = fullFileName; pro.SetExt( ProjectFileExtension ); bool is_new = !wxFileName::IsFileReadable( fullFileName ); // If its a non-existent schematic and caller thinks it exists if( is_new && !( aCtl & KICTL_CREATE ) ) { // notify user that fullFileName does not exist, ask if user wants to create it. msg.Printf( _( "PCB '%s' does not exist. Do you wish to create it?" ), fullFileName ); if( !IsOK( this, msg ) ) return false; } // Get rid of any existing warnings about the old board GetInfoBar()->Dismiss(); WX_PROGRESS_REPORTER progressReporter( this, is_new ? _( "Creating PCB" ) : _( "Loading PCB" ), 1 ); // No save prompt (we already prompted above), and only reset to a new blank board if new Clear_Pcb( false, !is_new ); IO_MGR::PCB_FILE_T pluginType = plugin_type( fullFileName, aCtl ); bool converted = pluginType != IO_MGR::LEGACY && pluginType != IO_MGR::KICAD_SEXP; // Loading a project should only be done under carefully considered circumstances. // The calling code should know not to ask me here to change projects unless // it knows what consequences that will have on other KIFACEs running and using // this same PROJECT. It can be very harmful if that calling code is stupid. SETTINGS_MANAGER* mgr = GetSettingsManager(); if( pro.GetFullPath() != mgr->Prj().GetProjectFullName() ) { // calls SaveProject SaveProjectLocalSettings(); GetBoard()->ClearProject(); mgr->UnloadProject( &mgr->Prj() ); mgr->LoadProject( pro.GetFullPath() ); // Do not allow saving a project if one doesn't exist. This normally happens if we are // standalone and opening a board that has been moved from its project folder. // For converted projects, we don't want to set the read-only flag because we want a project // to be saved for the new file in case things like netclasses got migrated. Prj().SetReadOnly( !pro.Exists() && !converted ); } // Clear the cache footprint list which may be project specific GFootprintList.Clear(); if( is_new ) { // Link the existing blank board to the new project GetBoard()->SetProject( &Prj() ); GetBoard()->SetFileName( fullFileName ); OnModify(); } else { BOARD* loadedBoard = nullptr; // it will be set to non-NULL if loaded OK PLUGIN::RELEASER pi( IO_MGR::PluginFind( pluginType ) ); LAYER_REMAPPABLE_PLUGIN* layerRemappablePlugin = dynamic_cast< LAYER_REMAPPABLE_PLUGIN* >( (PLUGIN*) pi ); if( layerRemappablePlugin ) { layerRemappablePlugin->RegisterLayerMappingCallback( std::bind( DIALOG_IMPORTED_LAYERS::GetMapModal, this, std::placeholders::_1 ) ); } // This will rename the file if there is an autosave and the user want to recover CheckForAutoSaveFile( fullFileName ); bool failedLoad = false; try { STRING_UTF8_MAP props; char xbuf[30]; char ybuf[30]; // EAGLE_PLUGIN can use this info to center the BOARD, but it does not yet. snprintf( xbuf, sizeof(xbuf), "%d", GetPageSizeIU().x ); snprintf( ybuf, sizeof(ybuf), "%d", GetPageSizeIU().y ); props["page_width"] = xbuf; props["page_height"] = ybuf; pi->SetQueryUserCallback( [&]( wxString aTitle, int aIcon, wxString aMessage, wxString aAction ) -> bool { KIDIALOG dlg( nullptr, aMessage, aTitle, wxOK | wxCANCEL | aIcon ); if( !aAction.IsEmpty() ) dlg.SetOKLabel( aAction ); dlg.DoNotShowCheckbox( aMessage, 0 ); return dlg.ShowModal() == wxID_OK; } ); #if USE_INSTRUMENTATION // measure the time to load a BOARD. unsigned startTime = GetRunningMicroSecs(); #endif loadedBoard = pi->Load( fullFileName, nullptr, &props, &Prj(), &progressReporter ); #if USE_INSTRUMENTATION unsigned stopTime = GetRunningMicroSecs(); printf( "PLUGIN::Load(): %u usecs\n", stopTime - startTime ); #endif } catch( const FUTURE_FORMAT_ERROR& ffe ) { msg.Printf( _( "Error loading PCB '%s'." ), fullFileName ); progressReporter.Hide(); DisplayErrorMessage( this, msg, ffe.Problem() ); failedLoad = true; } catch( const IO_ERROR& ioe ) { if( ioe.Problem() != wxT( "CANCEL" ) ) { msg.Printf( _( "Error loading PCB '%s'." ), fullFileName ); progressReporter.Hide(); DisplayErrorMessage( this, msg, ioe.What() ); } failedLoad = true; } catch( const std::bad_alloc& ) { msg.Printf( _( "Memory exhausted loading PCB '%s'" ), fullFileName ); progressReporter.Hide(); DisplayErrorMessage( this, msg, wxEmptyString ); failedLoad = true; } if( failedLoad ) { // We didn't create a new blank board above, so do that now Clear_Pcb( false ); return false; } // This fixes a focus issue after the progress reporter is done on GTK. It shouldn't // cause any issues on macOS and Windows. If it does, it will have to be conditionally // compiled. Raise(); // Skip (possibly expensive) connectivity build here; we build it below after load SetBoard( loadedBoard, false, &progressReporter ); if( GFootprintList.GetCount() == 0 ) GFootprintList.ReadCacheFromFile( Prj().GetProjectPath() + wxT( "fp-info-cache" ) ); if( loadedBoard->m_LegacyDesignSettingsLoaded ) { Prj().SetReadOnly( false ); // Before we had a copper edge clearance setting, the edge line widths could be used // as a kludge to control them. So if there's no setting then infer it from the // edge widths. if( !loadedBoard->m_LegacyCopperEdgeClearanceLoaded ) { int edgeClearance = inferLegacyEdgeClearance( loadedBoard ); loadedBoard->GetDesignSettings().m_CopperEdgeClearance = edgeClearance; } // On save; design settings will be removed from the board loadedBoard->SetModified(); } // Move legacy view settings to local project settings if( !loadedBoard->m_LegacyVisibleLayers.test( Rescue ) ) { Prj().GetLocalSettings().m_VisibleLayers = loadedBoard->m_LegacyVisibleLayers; loadedBoard->SetModified(); } if( !loadedBoard->m_LegacyVisibleItems.test( GAL_LAYER_INDEX( GAL_LAYER_ID_BITMASK_END ) ) ) { Prj().GetLocalSettings().m_VisibleItems = loadedBoard->m_LegacyVisibleItems; loadedBoard->SetModified(); } // we should not ask PLUGINs to do these items: loadedBoard->BuildListOfNets(); ResolveDRCExclusions( true ); m_toolManager->RunAction( PCB_ACTIONS::repairBoard, true, true); if( loadedBoard->IsModified() ) OnModify(); else GetScreen()->SetContentModified( false ); if( ( pluginType == IO_MGR::LEGACY ) || ( pluginType == IO_MGR::KICAD_SEXP && loadedBoard->GetFileFormatVersionAtLoad() < SEXPR_BOARD_FILE_VERSION && loadedBoard->GetGenerator().Lower() != wxT( "gerbview" ) ) ) { m_infoBar->RemoveAllButtons(); m_infoBar->AddCloseButton(); m_infoBar->ShowMessage( _( "This file was created by an older version of KiCad. " "It will be converted to the new format when saved." ), wxICON_WARNING, WX_INFOBAR::MESSAGE_TYPE::OUTDATED_SAVE ); } // Import footprints into a project-specific library //================================================== // TODO: This should be refactored out of here into somewhere specific to the Project Import // E.g. KICAD_MANAGER_FRAME::ImportNonKiCadProject if( aCtl & KICTL_IMPORT_LIB ) { wxFileName loadedBoardFn( fullFileName ); wxString libNickName = loadedBoardFn.GetName(); // Extract a footprint library from the design and add it to the fp-lib-table // The footprints are saved in a new .pretty library. // If this library already exists, all previous footprints will be deleted std::vector loadedFootprints = pi->GetImportedCachedLibraryFootprints(); wxString newLibPath = CreateNewProjectLibrary( libNickName ); // Only create the new library if CreateNewLibrary succeeded (note that this fails if // the library already exists and the user aborts after seeing the warning message // which prompts the user to continue with overwrite or abort) if( newLibPath.Length() > 0 ) { PLUGIN::RELEASER piSexpr( IO_MGR::PluginFind( IO_MGR::KICAD_SEXP ) ); for( FOOTPRINT* footprint : loadedFootprints ) { try { if( !footprint->GetFPID().GetLibItemName().empty() ) // Handle old boards. { footprint->SetReference( "REF**" ); piSexpr->FootprintSave( newLibPath, footprint ); delete footprint; } } catch( const IO_ERROR& ioe ) { wxLogError( _( "Error saving footprint %s to project specific library." ) + wxS( "\n%s" ), footprint->GetFPID().GetUniStringLibItemName(), ioe.What() ); } } FP_LIB_TABLE* prjlibtable = Prj().PcbFootprintLibs(); const wxString& project_env = PROJECT_VAR_NAME; wxString rel_path, env_path; wxASSERT_MSG( wxGetEnv( project_env, &env_path ), wxT( "There is no project variable?" ) ); wxString result( newLibPath ); if( result.Replace( env_path, wxT( "$(" ) + project_env + wxT( ")" ) ) ) rel_path = result; FP_LIB_TABLE_ROW* row = new FP_LIB_TABLE_ROW( libNickName, rel_path, wxT( "KiCad" ), wxEmptyString ); prjlibtable->InsertRow( row ); wxString tblName = Prj().FootprintLibTblName(); try { Prj().PcbFootprintLibs()->Save( tblName ); } catch( const IO_ERROR& ioe ) { wxLogError( _( "Error saving project specific footprint library table." ) + wxS( "\n%s" ), ioe.What() ); } // Update footprint LIB_IDs to point to the just imported library for( FOOTPRINT* footprint : GetBoard()->Footprints() ) { LIB_ID libId = footprint->GetFPID(); if( libId.GetLibItemName().empty() ) continue; libId.SetLibNickname( libNickName ); footprint->SetFPID( libId ); } } } } { wxFileName fn = fullFileName; if( converted ) fn.SetExt( PcbFileExtension ); wxString fname = fn.GetFullPath(); fname.Replace( WIN_STRING_DIR_SEP, UNIX_STRING_DIR_SEP ); GetBoard()->SetFileName( fname ); } // Lock the file newly opened: m_file_checker.reset( lock.release() ); if( !converted ) UpdateFileHistory( GetBoard()->GetFileName() ); std::vector toFill; // Rebuild list of nets (full ratsnest rebuild) GetBoard()->BuildConnectivity( &progressReporter ); // Load project settings after setting up board; some of them depend on the nets list LoadProjectSettings(); // Syncs the UI (appearance panel, etc) with the loaded board and project onBoardLoaded(); // Refresh the 3D view, if any EDA_3D_VIEWER_FRAME* draw3DFrame = Get3DViewerFrame(); if( draw3DFrame ) draw3DFrame->NewDisplay(); #if 0 && defined(DEBUG) // Output the board object tree to stdout, but please run from command prompt: GetBoard()->Show( 0, std::cout ); #endif // from EDA_APPL which was first loaded BOARD only: { /* For an obscure reason the focus is lost after loading a board file * when starting up the process. * (seems due to the recreation of the layer manager after loading the file) * Give focus to main window and Drawpanel * must be done for these 2 windows (for an obscure reason ...) * Linux specific * This is more a workaround than a fix. */ SetFocus(); GetCanvas()->SetFocus(); } return true; } bool PCB_EDIT_FRAME::SavePcbFile( const wxString& aFileName, bool addToHistory, bool aChangeProject ) { // please, keep it simple. prompting goes elsewhere. wxFileName pcbFileName = aFileName; if( pcbFileName.GetExt() == LegacyPcbFileExtension ) pcbFileName.SetExt( KiCadPcbFileExtension ); // Write through symlinks, don't replace them WX_FILENAME::ResolvePossibleSymlinks( pcbFileName ); if( !IsWritable( pcbFileName ) ) { wxString msg = wxString::Format( _( "Insufficient permissions to write file '%s'." ), pcbFileName.GetFullPath() ); DisplayError( this, msg ); return false; } // TODO: these will break if we ever go multi-board wxFileName projectFile( pcbFileName ); wxFileName rulesFile( pcbFileName ); wxString msg; projectFile.SetExt( ProjectFileExtension ); rulesFile.SetExt( DesignRulesFileExtension ); if( projectFile.FileExists() ) { GetSettingsManager()->SaveProject(); } else if( aChangeProject ) { Prj().SetReadOnly( false ); GetSettingsManager()->SaveProjectAs( projectFile.GetFullPath() ); } wxFileName currentRules( GetDesignRulesPath() ); if( currentRules.FileExists() && !rulesFile.FileExists() && aChangeProject ) KiCopyFile( currentRules.GetFullPath(), rulesFile.GetFullPath(), msg ); if( !msg.IsEmpty() ) { DisplayError( this, wxString::Format( _( "Error saving custom rules file '%s'." ), rulesFile.GetFullPath() ) ); } if( projectFile.FileExists() ) { // Save various DRC parameters, such as violation severities (which may have been // edited via the DRC dialog as well as the Board Setup dialog), DRC exclusions, etc. saveProjectSettings(); GetBoard()->SynchronizeProperties(); GetBoard()->SynchronizeNetsAndNetClasses( false ); } wxString tempFile = wxFileName::CreateTempFileName( wxS( "pcbnew" ) ); wxString upperTxt; wxString lowerTxt; try { PLUGIN::RELEASER pi( IO_MGR::PluginFind( IO_MGR::KICAD_SEXP ) ); pi->Save( tempFile, GetBoard(), nullptr ); } catch( const IO_ERROR& ioe ) { DisplayError( this, wxString::Format( _( "Error saving board file '%s'.\n%s" ), pcbFileName.GetFullPath(), ioe.What() ) ); lowerTxt.Printf( _( "Failed to create temporary file '%s'." ), tempFile ); SetMsgPanel( upperTxt, lowerTxt ); // In case we started a file but didn't fully write it, clean up wxRemoveFile( tempFile ); return false; } // Preserve the permissions of the current file KIPLATFORM::IO::DuplicatePermissions( pcbFileName.GetFullPath(), tempFile ); // If save succeeded, replace the original with what we just wrote if( !wxRenameFile( tempFile, pcbFileName.GetFullPath() ) ) { DisplayError( this, wxString::Format( _( "Error saving board file '%s'.\n" "Failed to rename temporary file '%s." ), pcbFileName.GetFullPath(), tempFile ) ); lowerTxt.Printf( _( "Failed to rename temporary file '%s'." ), tempFile ); SetMsgPanel( upperTxt, lowerTxt ); return false; } if( !Kiface().IsSingle() ) { WX_STRING_REPORTER backupReporter( &upperTxt ); if( GetSettingsManager()->TriggerBackupIfNeeded( backupReporter ) ) upperTxt.clear(); } GetBoard()->SetFileName( pcbFileName.GetFullPath() ); // Update the lock in case it was a Save As LockFile( pcbFileName.GetFullPath() ); // Put the saved file in File History if requested if( addToHistory ) UpdateFileHistory( GetBoard()->GetFileName() ); // Delete auto save file on successful save. wxFileName autoSaveFileName = pcbFileName; autoSaveFileName.SetName( GetAutoSaveFilePrefix() + pcbFileName.GetName() ); if( autoSaveFileName.FileExists() ) wxRemoveFile( autoSaveFileName.GetFullPath() ); lowerTxt.Printf( _( "File '%s' saved." ), pcbFileName.GetFullPath() ); SetStatusText( lowerTxt, 0 ); // Get rid of the old version conversion warning, or any other dismissable warning :) if( m_infoBar->GetMessageType() == WX_INFOBAR::MESSAGE_TYPE::OUTDATED_SAVE ) m_infoBar->Dismiss(); if( m_infoBar->IsShown() && m_infoBar->HasCloseButton() ) m_infoBar->Dismiss(); GetScreen()->SetContentModified( false ); UpdateTitle(); return true; } bool PCB_EDIT_FRAME::SavePcbCopy( const wxString& aFileName, bool aCreateProject ) { wxFileName pcbFileName = aFileName; // Ensure the file ext is the right ext: pcbFileName.SetExt( KiCadPcbFileExtension ); if( !IsWritable( pcbFileName ) ) { DisplayError( this, wxString::Format( _( "Insufficient permissions to write file '%s'." ), pcbFileName.GetFullPath() ) ); return false; } // Save various DRC parameters, such as violation severities (which may have been // edited via the DRC dialog as well as the Board Setup dialog), DRC exclusions, etc. SaveProjectLocalSettings(); GetBoard()->SynchronizeNetsAndNetClasses( false ); try { PLUGIN::RELEASER pi( IO_MGR::PluginFind( IO_MGR::KICAD_SEXP ) ); wxASSERT( pcbFileName.IsAbsolute() ); pi->Save( pcbFileName.GetFullPath(), GetBoard(), nullptr ); } catch( const IO_ERROR& ioe ) { DisplayError( this, wxString::Format( _( "Error saving board file '%s'.\n%s" ), pcbFileName.GetFullPath(), ioe.What() ) ); return false; } wxFileName projectFile( pcbFileName ); wxFileName rulesFile( pcbFileName ); wxString msg; projectFile.SetExt( ProjectFileExtension ); rulesFile.SetExt( DesignRulesFileExtension ); if( aCreateProject && !projectFile.FileExists() ) GetSettingsManager()->SaveProjectCopy( projectFile.GetFullPath() ); wxFileName currentRules( GetDesignRulesPath() ); if( aCreateProject && currentRules.FileExists() && !rulesFile.FileExists() ) KiCopyFile( currentRules.GetFullPath(), rulesFile.GetFullPath(), msg ); if( !msg.IsEmpty() ) { DisplayError( this, wxString::Format( _( "Error saving custom rules file '%s'." ), rulesFile.GetFullPath() ) ); } DisplayInfoMessage( this, wxString::Format( _( "Board copied to:\n%s" ), pcbFileName.GetFullPath() ) ); return true; } bool PCB_EDIT_FRAME::doAutoSave() { wxFileName tmpFileName; // Don't run autosave if content has not been modified if( !IsContentModified() ) return true; wxString title = GetTitle(); // Save frame title, that can be modified by the save process if( GetBoard()->GetFileName().IsEmpty() ) { tmpFileName = wxFileName( PATHS::GetDefaultUserProjectsPath(), NAMELESS_PROJECT, KiCadPcbFileExtension ); GetBoard()->SetFileName( tmpFileName.GetFullPath() ); } else { tmpFileName = Prj().AbsolutePath( GetBoard()->GetFileName() ); } wxFileName autoSaveFileName = tmpFileName; // Auto save file name is the board file name prepended with autosaveFilePrefix string. autoSaveFileName.SetName( GetAutoSaveFilePrefix() + autoSaveFileName.GetName() ); if( !autoSaveFileName.IsOk() ) return false; // If the board file path is not writable, try writing to a platform specific temp file // path. If that path isn't writable, give up. if( !autoSaveFileName.IsDirWritable() ) { autoSaveFileName.SetPath( wxFileName::GetTempDir() ); if( !autoSaveFileName.IsOk() || !autoSaveFileName.IsDirWritable() ) return false; } wxLogTrace( traceAutoSave, wxT( "Creating auto save file <" ) + autoSaveFileName.GetFullPath() + wxT( ">" ) ); if( SavePcbFile( autoSaveFileName.GetFullPath(), false, false ) ) { GetScreen()->SetContentModified(); GetBoard()->SetFileName( tmpFileName.GetFullPath() ); UpdateTitle(); m_autoSaveRequired = false; m_autoSavePending = false; if( !Kiface().IsSingle() && GetSettingsManager()->GetCommonSettings()->m_Backup.backup_on_autosave ) { GetSettingsManager()->TriggerBackupIfNeeded( NULL_REPORTER::GetInstance() ); } SetTitle( title ); // Restore initial frame title return true; } GetBoard()->SetFileName( tmpFileName.GetFullPath() ); SetTitle( title ); // Restore initial frame title return false; } bool PCB_EDIT_FRAME::importFile( const wxString& aFileName, int aFileType ) { switch( (IO_MGR::PCB_FILE_T) aFileType ) { case IO_MGR::CADSTAR_PCB_ARCHIVE: case IO_MGR::EAGLE: return OpenProjectFiles( std::vector( 1, aFileName ), KICTL_EAGLE_BRD | KICTL_IMPORT_LIB ); default: return false; } return false; }