1191 lines
39 KiB
C++
1191 lines
39 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 <string>
|
|
|
|
#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 <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 <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 <pgm_base.h>
|
|
#include <project/project_file.h>
|
|
#include <project_pcb.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 <tools/pcb_actions.h>
|
|
#include "footprint_info_impl.h"
|
|
#include <board_commit.h>
|
|
#include <zone_filler.h>
|
|
#include <wx_filename.h> // For ::ResolvePossibleSymlinks()
|
|
|
|
#include <kiplatform/io.h>
|
|
|
|
#include <wx/stdpaths.h>
|
|
#include <wx/filedlg.h>
|
|
#include <wx/txtstrm.h>
|
|
|
|
#include "widgets/filedlg_hook_save_project.h"
|
|
|
|
//#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, wxString* aFileName, int aCtl = 0 )
|
|
{
|
|
std::vector<PLUGIN_FILE_DESC> descriptions;
|
|
|
|
for( const auto& plugin : IO_MGR::PLUGIN_REGISTRY::Instance()->AllPlugins() )
|
|
{
|
|
bool isKiCad = plugin.m_type == IO_MGR::KICAD_SEXP || plugin.m_type == IO_MGR::LEGACY;
|
|
|
|
if( ( aCtl & KICTL_KICAD_ONLY ) && !isKiCad )
|
|
continue;
|
|
|
|
if( ( aCtl & KICTL_NONKICAD_ONLY ) && isKiCad )
|
|
continue;
|
|
|
|
// release the PLUGIN even if an exception is thrown.
|
|
PLUGIN::RELEASER pi( plugin.m_createFunc() );
|
|
wxCHECK( pi, false );
|
|
|
|
const PLUGIN_FILE_DESC& desc = pi->GetBoardFileDesc();
|
|
|
|
if( desc.m_FileExtensions.empty() )
|
|
continue;
|
|
|
|
descriptions.emplace_back( desc );
|
|
}
|
|
|
|
wxString fileFiltersStr;
|
|
std::vector<std::string> allExtensions;
|
|
std::set<wxString> allWildcardsSet;
|
|
|
|
for( const PLUGIN_FILE_DESC& desc : descriptions )
|
|
{
|
|
if( !fileFiltersStr.IsEmpty() )
|
|
fileFiltersStr += wxChar( '|' );
|
|
|
|
fileFiltersStr += desc.FileFilter();
|
|
|
|
for( const std::string& ext : desc.m_FileExtensions )
|
|
{
|
|
allExtensions.emplace_back( ext );
|
|
allWildcardsSet.insert( wxT( "*." ) + formatWildcardExt( ext ) + wxT( ";" ) );
|
|
}
|
|
}
|
|
|
|
wxString allWildcardsStr;
|
|
|
|
for( const wxString& wildcard : allWildcardsSet )
|
|
allWildcardsStr << wildcard;
|
|
|
|
if( aCtl & KICTL_KICAD_ONLY )
|
|
{
|
|
fileFiltersStr = _( "All KiCad Board Files" ) + AddFileExtListToFilter( allExtensions );
|
|
}
|
|
else
|
|
{
|
|
fileFiltersStr = _( "All supported formats" ) + wxT( "|" ) + allWildcardsStr + wxT( "|" )
|
|
+ fileFiltersStr;
|
|
}
|
|
|
|
wxFileName fileName( *aFileName );
|
|
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,
|
|
( aCtl & KICTL_KICAD_ONLY ) ? _( "Open Board File" )
|
|
: _( "Import Non KiCad Board File" ),
|
|
path, name, fileFiltersStr, wxFD_OPEN | wxFD_FILE_MUST_EXIST );
|
|
|
|
if( dlg.ShowModal() == wxID_OK )
|
|
{
|
|
*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
|
|
FILEDLG_HOOK_SAVE_PROJECT newProjectHook;
|
|
|
|
if( Kiface().IsSingle() && aParent->Prj().IsNullProject() )
|
|
dlg.SetCustomizeHook( newProjectHook );
|
|
|
|
if( dlg.ShowModal() != wxID_OK )
|
|
return false;
|
|
|
|
*aFileName = dlg.GetPath();
|
|
*aFileName = EnsureFileExtension( *aFileName, KiCadPcbFileExtension );
|
|
|
|
if( newProjectHook.IsAttachedToDialog() )
|
|
*aCreateProject = newProjectHook.GetCreateNewProject();
|
|
else if( !aParent->Prj().IsNullProject() )
|
|
*aCreateProject = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void PCB_EDIT_FRAME::OnFileHistory( wxCommandEvent& event )
|
|
{
|
|
wxString fn = GetFileFromHistory( event.GetId(), _( "Printed circuit board" ) );
|
|
|
|
if( !!fn )
|
|
{
|
|
if( !wxFileName::IsFileReadable( fn ) )
|
|
{
|
|
if( !AskLoadBoardFileName( this, &fn, KICTL_KICAD_ONLY ) )
|
|
return;
|
|
}
|
|
|
|
OpenProjectFiles( std::vector<wxString>( 1, fn ), KICTL_KICAD_ONLY );
|
|
}
|
|
}
|
|
|
|
|
|
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 = KICTL_KICAD_ONLY;
|
|
wxString fileName = Prj().AbsolutePath( GetBoard()->GetFileName() );
|
|
|
|
return AskLoadBoardFileName( this, &fileName, open_ctl )
|
|
&& OpenProjectFiles( std::vector<wxString>( 1, fileName ), open_ctl );
|
|
}
|
|
|
|
case ID_IMPORT_NON_KICAD_BOARD:
|
|
{
|
|
int open_ctl = KICTL_NONKICAD_ONLY;
|
|
wxString fileName; // = Prj().AbsolutePath( GetBoard()->GetFileName() );
|
|
|
|
return AskLoadBoardFileName( this, &fileName, open_ctl )
|
|
&& 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;
|
|
}
|
|
}
|
|
|
|
|
|
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->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 = IO_MGR::FindPluginTypeFromBoardPath( fullFileName, aCtl );
|
|
|
|
if( pluginType == IO_MGR::FILE_TYPE_NONE )
|
|
return false;
|
|
|
|
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
|
|
{
|
|
if( pi == nullptr )
|
|
{
|
|
// There was no plugin found, e.g. due to invalid file extension, file header,...
|
|
THROW_IO_ERROR( _( "File format is not supported" ) );
|
|
}
|
|
|
|
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->LoadBoard( 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);
|
|
|
|
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 = PROJECT_PCB::PcbFootprintLibs( &Prj() );
|
|
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
|
|
{
|
|
PROJECT_PCB::PcbFootprintLibs( &Prj() )->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
|
|
|
|
// 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->SaveBoard( 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( 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->SaveBoard( 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:
|
|
case IO_MGR::EASYEDA:
|
|
case IO_MGR::EASYEDAPRO:
|
|
return OpenProjectFiles( std::vector<wxString>( 1, aFileName ),
|
|
KICTL_NONKICAD_ONLY | KICTL_IMPORT_LIB );
|
|
|
|
default: break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|