mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 18:23:15 +02:00
wxSingleInstanceChecker is meant for running programs, not file locking. This implements an RAII class for file locking that stores the lock files next to the file being locked, allowing it to be easily found and removed. Also includes the ability to override the lock, with information about the original owner Fixes https://gitlab.com/kicad/code/kicad/-/issues/14734
1301 lines
42 KiB
C++
1301 lines
42 KiB
C++
/*
|
|
* 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 <stambaughw@gmail.com>
|
|
* 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 <confirm.h>
|
|
#include <core/arraydim.h>
|
|
#include <gestfich.h>
|
|
#include <pcb_edit_frame.h>
|
|
#include <board_design_settings.h>
|
|
#include <3d_viewer/eda_3d_viewer_frame.h>
|
|
#include <widgets/msgpanel.h>
|
|
#include <fp_lib_table.h>
|
|
#include <kiface_base.h>
|
|
#include <macros.h>
|
|
#include <trace_helpers.h>
|
|
#include <lockfile.h>
|
|
#include <wx/snglinst.h>
|
|
#include <netlist_reader/pcb_netlist.h>
|
|
#include <pcbnew_id.h>
|
|
#include <io_mgr.h>
|
|
#include <wildcards_and_files_ext.h>
|
|
#include <tool/tool_manager.h>
|
|
#include <board.h>
|
|
#include <wx/checkbox.h>
|
|
#include <wx/stdpaths.h>
|
|
#include <ratsnest/ratsnest_data.h>
|
|
#include <kiplatform/app.h>
|
|
#include <widgets/appearance_controls.h>
|
|
#include <widgets/wx_infobar.h>
|
|
#include <widgets/wx_progress_reporters.h>
|
|
#include <settings/settings_manager.h>
|
|
#include <paths.h>
|
|
#include <project/project_file.h>
|
|
#include <project/project_local_settings.h>
|
|
#include <project/net_settings.h>
|
|
#include <plugins/cadstar/cadstar_pcb_archive_plugin.h>
|
|
#include <plugins/kicad/pcb_plugin.h>
|
|
#include <dialogs/dialog_imported_layers.h>
|
|
#include <string>
|
|
#include <tools/pcb_actions.h>
|
|
#include "footprint_info_impl.h"
|
|
#include "board_commit.h"
|
|
#include "zone_filler.h"
|
|
#include <wx_filename.h> // For ::ResolvePossibleSymlinks()
|
|
|
|
#include <wx/wupdlock.h>
|
|
#include <wx/filedlg.h>
|
|
#include <wx/wfstream.h>
|
|
#include <wx/txtstrm.h>
|
|
|
|
#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<std::string> 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;
|
|
|
|
*aFileName = dlg.GetPath();
|
|
*aFileName = EnsureFileExtension( *aFileName, KiCadPcbFileExtension );
|
|
|
|
#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<LEGACYFILEDLG_SAVE_PROJECT*>( 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<wxString>( 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<wxString>( 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<wxString>( 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<wxString>( 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<wxString>( 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<PCB_SHAPE*>( 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<wxString>& 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<LOCKFILE> lock = std::make_unique<LOCKFILE>( fullFileName );
|
|
|
|
if( !lock->Locked() )
|
|
{
|
|
msg.Printf( _( "PCB '%s' is already open by '%s' at '%s'." ), wx_filename.GetFullName(),
|
|
lock->GetUsername(), lock->GetHostname() );
|
|
|
|
if( !OverrideLock( 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;
|
|
|
|
// EAGLE_PLUGIN can use this info to center the BOARD, but it does not yet.
|
|
props["page_width"] = std::to_string( GetPageSizeIU().x );
|
|
props["page_height"] = std::to_string( GetPageSizeIU().y );
|
|
|
|
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<FOOTPRINT*> 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<ZONE*> 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
|
|
|
|
// Re-fill any zones which had their legacy fills deleted on open
|
|
|
|
for( ZONE* zone : GetBoard()->Zones() )
|
|
{
|
|
if( zone->GetFlags() & CANDIDATE )
|
|
{
|
|
toFill.push_back( zone );
|
|
zone->ClearTempFlags();
|
|
}
|
|
}
|
|
|
|
if( toFill.size() )
|
|
{
|
|
BOARD_COMMIT commit( this );
|
|
ZONE_FILLER filler( GetBoard(), &commit );
|
|
|
|
progressReporter.Report( _( "Converting zone fills" ) );
|
|
filler.SetProgressReporter( &progressReporter );
|
|
|
|
if( filler.Fill( toFill ) )
|
|
commit.Push( _( "Convert Zone(s)" ), SKIP_CONNECTIVITY );
|
|
|
|
rebuildConnectivity();
|
|
}
|
|
|
|
// 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() && 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;
|
|
}
|
|
|
|
// 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( EnsureFileExtension( aFileName, 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<wxString>( 1, aFileName ),
|
|
KICTL_EAGLE_BRD | KICTL_IMPORT_LIB );
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|