1049 lines
33 KiB
C++
1049 lines
33 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-2018 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 <fctsys.h>
|
|
#include <confirm.h>
|
|
#include <kicad_string.h>
|
|
#include <gestfich.h>
|
|
#include <pcb_edit_frame.h>
|
|
#include <macros.h>
|
|
#include <3d_viewer/eda_3d_viewer.h>
|
|
#include <richio.h>
|
|
#include <filter_reader.h>
|
|
#include <pgm_base.h>
|
|
#include <msgpanel.h>
|
|
#include <fp_lib_table.h>
|
|
#include <ratsnest_data.h>
|
|
#include <kiway.h>
|
|
#include <kiway_player.h>
|
|
#include <trace_helpers.h>
|
|
#include <lockfile.cpp>
|
|
#include <netlist_reader/pcb_netlist.h>
|
|
#include <pcbnew.h>
|
|
#include <pcbnew_id.h>
|
|
#include <io_mgr.h>
|
|
#include <wildcards_and_files_ext.h>
|
|
|
|
#include <class_board.h>
|
|
#include <build_version.h> // LEGACY_BOARD_FILE_VERSION
|
|
|
|
#include <wx/stdpaths.h>
|
|
#include <pcb_layer_widget.h>
|
|
#include <wx/wupdlock.h>
|
|
|
|
|
|
//#define USE_INSTRUMENTATION 1
|
|
#define USE_INSTRUMENTATION 0
|
|
|
|
|
|
/**
|
|
* Function AskLoadBoardFileName
|
|
* puts up 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 kiacad pcb files plugins only, false to list import plugins.
|
|
*
|
|
* @return bool - true if chosen, else false if user aborted.
|
|
*/
|
|
bool AskLoadBoardFileName( wxWindow* 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.
|
|
static const struct
|
|
{
|
|
const wxString& filter;
|
|
IO_MGR::PCB_FILE_T pluginType;
|
|
} loaders[] =
|
|
{
|
|
{ PcbFileWildcard(), IO_MGR::KICAD_SEXP }, // Current Kicad board files
|
|
{ LegacyPcbFileWildcard(), IO_MGR::LEGACY }, // Old Kicad board files
|
|
{ EaglePcbFileWildcard(), IO_MGR::EAGLE }, // Import board files
|
|
{ PCadPcbFileWildcard(), IO_MGR::PCAD }, // Import board files
|
|
};
|
|
|
|
wxFileName fileName( *aFileName );
|
|
wxString fileFilters;
|
|
|
|
if( aKicadFilesOnly )
|
|
{
|
|
for( unsigned ii = 0; ii < 2; ++ii )
|
|
{
|
|
if( !fileFilters.IsEmpty() )
|
|
fileFilters += wxChar( '|' );
|
|
|
|
fileFilters += wxGetTranslation( loaders[ii].filter );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( unsigned ii = 2; ii < arrayDim( loaders ); ++ii )
|
|
{
|
|
if( !fileFilters.IsEmpty() )
|
|
fileFilters += wxChar( '|' );
|
|
|
|
fileFilters += wxGetTranslation( loaders[ii].filter );
|
|
}
|
|
}
|
|
|
|
wxString path;
|
|
wxString name;
|
|
|
|
if( fileName.FileExists() )
|
|
{
|
|
path = fileName.GetPath();
|
|
name = fileName.GetFullName();
|
|
}
|
|
else
|
|
{
|
|
path = wxStandardPaths::Get().GetDocumentsDir();
|
|
// 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();
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Function AskSaveBoardFileName
|
|
* puts 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).
|
|
*
|
|
* @return bool - true if chosen, else false if user aborted.
|
|
*/
|
|
bool AskSaveBoardFileName( wxWindow* aParent, wxString* aFileName )
|
|
{
|
|
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
|
|
);
|
|
|
|
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();
|
|
|
|
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::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_READ_BOARD_BACKUP_FILE:
|
|
case ID_MENU_RECOVER_BOARD_AUTOSAVE:
|
|
{
|
|
wxFileName currfn = Prj().AbsolutePath( GetBoard()->GetFileName() );
|
|
wxFileName fn = currfn;
|
|
|
|
if( id == ID_MENU_RECOVER_BOARD_AUTOSAVE )
|
|
{
|
|
wxString rec_name = GetAutoSaveFilePrefix() + fn.GetName();
|
|
fn.SetName( rec_name );
|
|
}
|
|
else
|
|
{
|
|
wxString backup_ext = fn.GetExt() + GetBackupSuffix();
|
|
fn.SetExt( backup_ext );
|
|
}
|
|
|
|
if( !fn.FileExists() )
|
|
{
|
|
msg.Printf( _( "Recovery file \"%s\" not found." ), fn.GetFullPath() );
|
|
DisplayInfoMessage( this, msg );
|
|
return false;
|
|
}
|
|
|
|
msg.Printf( _( "OK to load recovery or backup file \"%s\"" ), fn.GetFullPath() );
|
|
|
|
if( !IsOK( this, msg ) )
|
|
return false;
|
|
|
|
GetScreen()->ClrModify(); // 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_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;
|
|
}
|
|
|
|
if( !Clear_Pcb( false ) )
|
|
return false;
|
|
|
|
wxFileName fn( wxStandardPaths::Get().GetDocumentsDir(), wxT( "noname" ),
|
|
ProjectFileExtension );
|
|
|
|
Prj().SetProjectFullName( fn.GetFullPath() );
|
|
|
|
onBoardLoaded();
|
|
|
|
OnModify();
|
|
return true;
|
|
}
|
|
|
|
case ID_SAVE_BOARD:
|
|
if( !GetBoard()->GetFileName().IsEmpty() )
|
|
return SavePcbFile( Prj().AbsolutePath( GetBoard()->GetFileName() ) );
|
|
// Fall through
|
|
|
|
case ID_COPY_BOARD_AS:
|
|
case ID_SAVE_BOARD_AS:
|
|
{
|
|
wxString pro_dir = wxPathOnly( Prj().GetProjectFullName() );
|
|
wxFileName fn( pro_dir, _( "noname" ), KiCadPcbFileExtension );
|
|
wxString filename = fn.GetFullPath();
|
|
|
|
if( AskSaveBoardFileName( this, &filename ) )
|
|
{
|
|
if( id == ID_COPY_BOARD_AS )
|
|
return SavePcbCopy( filename );
|
|
else
|
|
return SavePcbFile( filename, NO_BACKUP_FILE );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
default:
|
|
wxLogDebug( wxT( "File_io Internal Error" ) );
|
|
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 ower 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 )
|
|
{
|
|
// both legacy and eagle share a common file extension.
|
|
pluginType = ( aCtl & KICTL_EAGLE_BRD ) ? IO_MGR::EAGLE : IO_MGR::LEGACY;
|
|
}
|
|
else if( fn.GetExt().CmpNoCase( IO_MGR::GetFileExtension( IO_MGR::PCAD ) ) == 0 )
|
|
{
|
|
pluginType = IO_MGR::PCAD;
|
|
}
|
|
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_LINE_T )
|
|
{
|
|
int itemWidth = static_cast<DRAWSEGMENT*>( 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.
|
|
wxMessageBox( _( "If the zones on this board are refilled the Copper Edge Clearance\n"
|
|
"setting will be used (see Board Setup > Design Rules). This may\n"
|
|
"result in different fills from previous Kicad versions which used\n"
|
|
"the line thickness of the board boundary on the Edge Cuts layer." ),
|
|
_( "Edge Clearance Warning" ), wxOK|wxICON_WARNING, this );
|
|
}
|
|
|
|
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 only a single filename", __func__ );
|
|
DisplayError( this, msg );
|
|
return false;
|
|
}
|
|
|
|
wxString fullFileName( aFileSet[0] );
|
|
|
|
// We insist on caller sending us an absolute path, if it does not, we say it's a bug.
|
|
wxASSERT_MSG( wxFileName( fullFileName ).IsAbsolute(), wxT( "Path is not absolute!" ) );
|
|
|
|
std::unique_ptr<wxSingleInstanceChecker> lockFile = ::LockFile( fullFileName );
|
|
|
|
if( !lockFile )
|
|
{
|
|
wxString msg = wxString::Format( _( "PCB file \"%s\" is already open." ), fullFileName );
|
|
DisplayError( this, msg );
|
|
return false;
|
|
}
|
|
|
|
if( IsContentModified() )
|
|
{
|
|
if( !HandleUnsavedChanges( this, _( "The current PCB has been modified. Save changes?" ),
|
|
[&]()->bool { return SavePcbFile( GetBoard()->GetFileName(), CREATE_BACKUP_FILE ); } ) )
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Release the lock file, until the new file is actually loaded
|
|
ReleaseFile();
|
|
|
|
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.
|
|
wxString ask = wxString::Format( _( "PCB \"%s\" does not exist. Do you wish to create it?" ),
|
|
fullFileName );
|
|
if( !IsOK( this, ask ) )
|
|
return false;
|
|
}
|
|
|
|
wxWindowUpdateLocker no_update( m_Layers ); // Avoid flicker when rebuilding m_Layers
|
|
|
|
Clear_Pcb( false ); // pass false since we prompted above for a modified board
|
|
|
|
IO_MGR::PCB_FILE_T pluginType = plugin_type( fullFileName, aCtl );
|
|
|
|
bool converted = pluginType != IO_MGR::LEGACY && pluginType != IO_MGR::KICAD_SEXP;
|
|
|
|
if( !converted )
|
|
{
|
|
// PROJECT::SetProjectFullName() is an impactful function. It should only be
|
|
// called 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.
|
|
Prj().SetProjectFullName( pro.GetFullPath() );
|
|
|
|
// load project settings before BOARD
|
|
LoadProjectSettings();
|
|
}
|
|
|
|
if( is_new )
|
|
{
|
|
OnModify();
|
|
}
|
|
else
|
|
{
|
|
BOARD* loadedBoard = 0; // it will be set to non-NULL if loaded OK
|
|
|
|
PLUGIN::RELEASER pi( IO_MGR::PluginFind( pluginType ) );
|
|
|
|
// This will rename the file if there is an autosave and the user want to recover
|
|
CheckForAutoSaveFile( fullFileName );
|
|
|
|
try
|
|
{
|
|
PROPERTIES props;
|
|
char xbuf[30];
|
|
char ybuf[30];
|
|
|
|
// EAGLE_PLUGIN can use this info to center the BOARD, but it does not yet.
|
|
sprintf( xbuf, "%d", GetPageSizeIU().x );
|
|
sprintf( ybuf, "%d", GetPageSizeIU().y );
|
|
|
|
props["page_width"] = xbuf;
|
|
props["page_height"] = ybuf;
|
|
|
|
#if USE_INSTRUMENTATION
|
|
// measure the time to load a BOARD.
|
|
unsigned startTime = GetRunningMicroSecs();
|
|
#endif
|
|
|
|
loadedBoard = pi->Load( fullFileName, NULL, &props );
|
|
|
|
#if USE_INSTRUMENTATION
|
|
unsigned stopTime = GetRunningMicroSecs();
|
|
printf( "PLUGIN::Load(): %u usecs\n", stopTime - startTime );
|
|
#endif
|
|
}
|
|
catch( const IO_ERROR& ioe )
|
|
{
|
|
if( ioe.Problem() != wxT( "CANCEL" ) )
|
|
{
|
|
wxString msg = wxString::Format( _( "Error loading board file:\n%s" ), fullFileName );
|
|
DisplayErrorMessage( this, msg, ioe.What() );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
BOARD_DESIGN_SETTINGS& bds = loadedBoard->m_designSettings;
|
|
|
|
if( bds.m_CopperEdgeClearance == Millimeter2iu( LEGACY_COPPEREDGECLEARANCE ) )
|
|
{
|
|
// 5.1 boards stored some settings in the config so as not to bump the file version.
|
|
// These will have been loaded into the config-initialized board, so we copy them
|
|
// from there.
|
|
BOARD_DESIGN_SETTINGS& configBds = GetBoard()->GetDesignSettings();
|
|
|
|
bds.m_RequireCourtyards = configBds.m_RequireCourtyards;
|
|
bds.m_ProhibitOverlappingCourtyards = configBds.m_ProhibitOverlappingCourtyards;
|
|
bds.m_HoleToHoleMin = configBds.m_HoleToHoleMin;
|
|
bds.m_LineThickness[LAYER_CLASS_OTHERS] = configBds.m_LineThickness[LAYER_CLASS_OTHERS];
|
|
bds.m_TextSize[LAYER_CLASS_OTHERS] = configBds.m_TextSize[LAYER_CLASS_OTHERS];
|
|
bds.m_TextThickness[LAYER_CLASS_OTHERS] = configBds.m_TextThickness[LAYER_CLASS_OTHERS];
|
|
std::copy( configBds.m_TextItalic, configBds.m_TextItalic + 4, bds.m_TextItalic );
|
|
std::copy( configBds.m_TextUpright, configBds.m_TextUpright + 4, bds.m_TextUpright );
|
|
bds.m_DiffPairDimensionsList = configBds.m_DiffPairDimensionsList;
|
|
bds.m_CopperEdgeClearance = configBds.m_CopperEdgeClearance;
|
|
|
|
// 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( bds.m_CopperEdgeClearance == Millimeter2iu( LEGACY_COPPEREDGECLEARANCE ) )
|
|
bds.SetCopperEdgeClearance( inferLegacyEdgeClearance( loadedBoard ) );
|
|
}
|
|
|
|
// 6.0 TODO: some of the 5.1 settings still haven't moved because they're waiting on
|
|
// the new DRC architecture
|
|
BOARD_DESIGN_SETTINGS& configBds = GetBoard()->GetDesignSettings();
|
|
bds.m_RequireCourtyards = configBds.m_RequireCourtyards;
|
|
bds.m_ProhibitOverlappingCourtyards = configBds.m_ProhibitOverlappingCourtyards;
|
|
bds.m_HoleToHoleMin = configBds.m_HoleToHoleMin;
|
|
|
|
SetBoard( loadedBoard );
|
|
|
|
// we should not ask PLUGINs to do these items:
|
|
loadedBoard->BuildListOfNets();
|
|
loadedBoard->SynchronizeNetsAndNetClasses();
|
|
|
|
if( loadedBoard->IsModified() )
|
|
OnModify();
|
|
else
|
|
GetScreen()->ClrModify();
|
|
|
|
if( pluginType == IO_MGR::LEGACY &&
|
|
loadedBoard->GetFileFormatVersionAtLoad() < LEGACY_BOARD_FILE_VERSION )
|
|
{
|
|
DisplayInfoMessage( this,
|
|
_( "This file was created by an older version of Pcbnew.\n"
|
|
"It will be stored in the new file format when you save this file again." ) );
|
|
}
|
|
}
|
|
|
|
{
|
|
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( lockFile.release() );
|
|
|
|
if( !converted )
|
|
UpdateFileHistory( GetBoard()->GetFileName() );
|
|
|
|
// Select netclass Default as current netclass (it always exists)
|
|
SetCurrentNetClass( NETCLASS::Default );
|
|
|
|
// Rebuild list of nets (full ratsnest rebuild)
|
|
Compile_Ratsnest( true );
|
|
GetBoard()->BuildConnectivity();
|
|
|
|
onBoardLoaded();
|
|
|
|
// Refresh the 3D view, if any
|
|
EDA_3D_VIEWER* 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;
|
|
}
|
|
|
|
|
|
wxString PCB_EDIT_FRAME::createBackupFile( const wxString& aFileName )
|
|
{
|
|
wxFileName fn = aFileName;
|
|
wxFileName backupFileName = aFileName;
|
|
|
|
backupFileName.SetExt( fn.GetExt() + GetBackupSuffix() );
|
|
|
|
// If an old backup file exists, delete it. If an old board file exists,
|
|
// rename it to the backup file name.
|
|
if( fn.FileExists() )
|
|
{
|
|
// Remove the old file xxx.000 if it exists.
|
|
if( backupFileName.FileExists() )
|
|
wxRemoveFile( backupFileName.GetFullPath() );
|
|
|
|
// Rename the current file from <xxx>.kicad_pcb to <xxx>.kicad_pcb-bak
|
|
if( !wxRenameFile( fn.GetFullPath(), backupFileName.GetFullPath() ) )
|
|
{
|
|
wxString msg = wxString::Format( _(
|
|
"Warning: unable to create backup file \"%s\"" ),
|
|
GetChars( backupFileName.GetFullPath() )
|
|
);
|
|
DisplayError( NULL, msg );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
backupFileName.Clear();
|
|
}
|
|
|
|
return backupFileName.GetFullPath();
|
|
}
|
|
|
|
|
|
bool PCB_EDIT_FRAME::SavePcbFile( const wxString& aFileName, bool aCreateBackupFile )
|
|
{
|
|
// please, keep it simple. prompting goes elsewhere.
|
|
|
|
wxFileName pcbFileName = aFileName;
|
|
|
|
if( pcbFileName.GetExt() == LegacyPcbFileExtension )
|
|
pcbFileName.SetExt( KiCadPcbFileExtension );
|
|
|
|
if( !IsWritable( pcbFileName ) )
|
|
{
|
|
wxString msg = wxString::Format( _(
|
|
"No access rights to write to file \"%s\"" ),
|
|
GetChars( pcbFileName.GetFullPath() )
|
|
);
|
|
|
|
DisplayError( this, msg );
|
|
return false;
|
|
}
|
|
|
|
wxString backupFileName;
|
|
|
|
if( aCreateBackupFile )
|
|
{
|
|
backupFileName = createBackupFile( aFileName );
|
|
}
|
|
|
|
GetBoard()->SynchronizeNetsAndNetClasses();
|
|
|
|
// Select default Netclass before writing file.
|
|
// Useful to save default values in headers
|
|
SetCurrentNetClass( NETCLASS::Default );
|
|
|
|
ClearMsgPanel();
|
|
|
|
wxString upperTxt;
|
|
wxString lowerTxt;
|
|
|
|
try
|
|
{
|
|
PLUGIN::RELEASER pi( IO_MGR::PluginFind( IO_MGR::KICAD_SEXP ) );
|
|
|
|
wxASSERT( pcbFileName.IsAbsolute() );
|
|
|
|
pi->Save( pcbFileName.GetFullPath(), GetBoard(), NULL );
|
|
}
|
|
catch( const IO_ERROR& ioe )
|
|
{
|
|
wxString msg = wxString::Format( _(
|
|
"Error saving board file \"%s\".\n%s" ),
|
|
GetChars( pcbFileName.GetFullPath() ),
|
|
GetChars( ioe.What() )
|
|
);
|
|
DisplayError( this, msg );
|
|
|
|
lowerTxt.Printf( _( "Failed to create \"%s\"" ), GetChars( pcbFileName.GetFullPath() ) );
|
|
|
|
AppendMsgPanel( upperTxt, lowerTxt, CYAN );
|
|
|
|
return false;
|
|
}
|
|
|
|
GetBoard()->SetFileName( pcbFileName.GetFullPath() );
|
|
UpdateTitle();
|
|
|
|
// Put the saved file in File History, unless aCreateBackupFile
|
|
// is false.
|
|
// aCreateBackupFile == false is mainly used to write autosave files
|
|
// and not need to have an autosave file in file history
|
|
if( aCreateBackupFile )
|
|
UpdateFileHistory( GetBoard()->GetFileName() );
|
|
|
|
// Delete auto save file on successful save.
|
|
wxFileName autoSaveFileName = pcbFileName;
|
|
|
|
autoSaveFileName.SetName( GetAutoSaveFilePrefix() + pcbFileName.GetName() );
|
|
|
|
if( autoSaveFileName.FileExists() )
|
|
wxRemoveFile( autoSaveFileName.GetFullPath() );
|
|
|
|
if( !!backupFileName )
|
|
upperTxt.Printf( _( "Backup file: \"%s\"" ), GetChars( backupFileName ) );
|
|
|
|
lowerTxt.Printf( _( "Wrote board file: \"%s\"" ), GetChars( pcbFileName.GetFullPath() ) );
|
|
|
|
AppendMsgPanel( upperTxt, lowerTxt, CYAN );
|
|
|
|
GetScreen()->ClrModify();
|
|
GetScreen()->ClrSave();
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PCB_EDIT_FRAME::SavePcbCopy( const wxString& aFileName )
|
|
{
|
|
wxFileName pcbFileName = aFileName;
|
|
|
|
// Ensure the file ext is the right ext:
|
|
pcbFileName.SetExt( KiCadPcbFileExtension );
|
|
|
|
if( !IsWritable( pcbFileName ) )
|
|
{
|
|
wxString msg = wxString::Format( _(
|
|
"No access rights to write to file \"%s\"" ),
|
|
GetChars( pcbFileName.GetFullPath() )
|
|
);
|
|
|
|
DisplayError( this, msg );
|
|
return false;
|
|
}
|
|
|
|
GetBoard()->SynchronizeNetsAndNetClasses();
|
|
|
|
// Select default Netclass before writing file.
|
|
// Useful to save default values in headers
|
|
SetCurrentNetClass( NETCLASS::Default );
|
|
|
|
try
|
|
{
|
|
PLUGIN::RELEASER pi( IO_MGR::PluginFind( IO_MGR::KICAD_SEXP ) );
|
|
|
|
wxASSERT( pcbFileName.IsAbsolute() );
|
|
|
|
pi->Save( pcbFileName.GetFullPath(), GetBoard(), NULL );
|
|
}
|
|
catch( const IO_ERROR& ioe )
|
|
{
|
|
wxString msg = wxString::Format( _(
|
|
"Error saving board file \"%s\".\n%s" ),
|
|
GetChars( pcbFileName.GetFullPath() ),
|
|
GetChars( ioe.What() )
|
|
);
|
|
DisplayError( this, msg );
|
|
|
|
return false;
|
|
}
|
|
|
|
DisplayInfoMessage( this, wxString::Format( _( "Board copied to:\n\"%s\"" ),
|
|
GetChars( pcbFileName.GetFullPath() ) ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool PCB_EDIT_FRAME::doAutoSave()
|
|
{
|
|
wxFileName tmpFileName;
|
|
|
|
if( GetBoard()->GetFileName().IsEmpty() )
|
|
{
|
|
tmpFileName = wxFileName( wxStandardPaths::Get().GetDocumentsDir(), wxT( "noname" ),
|
|
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 writabe, give up.
|
|
if( !autoSaveFileName.IsDirWritable() )
|
|
{
|
|
autoSaveFileName.SetPath( wxFileName::GetTempDir() );
|
|
|
|
if( !autoSaveFileName.IsOk() || !autoSaveFileName.IsDirWritable() )
|
|
return false;
|
|
}
|
|
|
|
wxLogTrace( traceAutoSave, "Creating auto save file <" + autoSaveFileName.GetFullPath() + ">" );
|
|
|
|
if( SavePcbFile( autoSaveFileName.GetFullPath(), NO_BACKUP_FILE ) )
|
|
{
|
|
GetScreen()->SetModify();
|
|
GetBoard()->SetFileName( tmpFileName.GetFullPath() );
|
|
UpdateTitle();
|
|
m_autoSaveState = false;
|
|
return true;
|
|
}
|
|
|
|
GetBoard()->SetFileName( tmpFileName.GetFullPath() );
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool PCB_EDIT_FRAME::importFile( const wxString& aFileName, int aFileType )
|
|
{
|
|
switch( (IO_MGR::PCB_FILE_T) aFileType )
|
|
{
|
|
case IO_MGR::EAGLE:
|
|
if( OpenProjectFiles( std::vector<wxString>( 1, aFileName ), KICTL_EAGLE_BRD ) )
|
|
{
|
|
wxString projectpath = Kiway().Prj().GetProjectPath();
|
|
wxFileName newfilename;
|
|
|
|
newfilename.SetPath( Prj().GetProjectPath() );
|
|
newfilename.SetName( Prj().GetProjectName() );
|
|
newfilename.SetExt( KiCadPcbFileExtension );
|
|
|
|
GetBoard()->SetFileName( newfilename.GetFullPath() );
|
|
UpdateTitle();
|
|
OnModify();
|
|
|
|
// Extract a footprint library from the design and add it to the fp-lib-table
|
|
wxString newLibPath;
|
|
ArchiveModulesOnBoard( true, newfilename.GetName(), &newLibPath );
|
|
|
|
if( newLibPath.Length() > 0 )
|
|
{
|
|
FP_LIB_TABLE* prjlibtable = Prj().PcbFootprintLibs();
|
|
const wxString& project_env = PROJECT_VAR_NAME;
|
|
wxString rel_path, env_path;
|
|
|
|
wxGetEnv( project_env, &env_path );
|
|
|
|
wxString result( newLibPath );
|
|
rel_path = result.Replace( env_path,
|
|
wxString( "$(" + project_env + ")" ) ) ? result : "" ;
|
|
|
|
if( !rel_path.IsEmpty() )
|
|
newLibPath = rel_path;
|
|
|
|
FP_LIB_TABLE_ROW* row = new FP_LIB_TABLE_ROW( newfilename.GetName(),
|
|
newLibPath, wxT( "KiCad" ), wxEmptyString );
|
|
prjlibtable->InsertRow( row );
|
|
}
|
|
|
|
if( !GetBoard()->GetFileName().IsEmpty() )
|
|
{
|
|
wxString tblName = Prj().FootprintLibTblName();
|
|
|
|
try
|
|
{
|
|
Prj().PcbFootprintLibs()->Save( tblName );
|
|
}
|
|
catch( const IO_ERROR& ioe )
|
|
{
|
|
wxString msg = wxString::Format( _(
|
|
"Error occurred saving project specific footprint library "
|
|
"table:\n\n%s" ),
|
|
GetChars( ioe.What() ) );
|
|
wxMessageBox( msg, _( "File Save Error" ), wxOK | wxICON_ERROR );
|
|
}
|
|
}
|
|
|
|
|
|
// Update module LIB_IDs to point to the just imported Eagle library
|
|
for( MODULE* module : GetBoard()->Modules() )
|
|
{
|
|
LIB_ID libId = module->GetFPID();
|
|
|
|
if( libId.GetLibItemName().empty() )
|
|
continue;
|
|
|
|
libId.SetLibNickname( newfilename.GetName() );
|
|
module->SetFPID( libId );
|
|
}
|
|
|
|
|
|
// Store net names for all pads, to create net remap information
|
|
std::unordered_map<D_PAD*, wxString> netMap;
|
|
|
|
for( const auto& pad : GetBoard()->GetPads() )
|
|
{
|
|
NETINFO_ITEM* netinfo = pad->GetNet();
|
|
|
|
if( netinfo->GetNet() > 0 && !netinfo->GetNetname().IsEmpty() )
|
|
netMap[pad] = netinfo->GetNetname();
|
|
}
|
|
|
|
// Two stage netlist update:
|
|
// - first, assign valid timestamps to footprints (no reannotation)
|
|
// - second, perform schematic annotation and update footprint references
|
|
// based on timestamps
|
|
NETLIST netlist;
|
|
FetchNetlistFromSchematic( netlist, NO_ANNOTATION );
|
|
DoUpdatePCBFromNetlist( netlist, false );
|
|
FetchNetlistFromSchematic( netlist, QUIET_ANNOTATION );
|
|
DoUpdatePCBFromNetlist( netlist, true );
|
|
|
|
std::unordered_map<wxString, wxString> netRemap;
|
|
|
|
// Compare the old net names with the new net names and create a net map
|
|
for( const auto& pad : GetBoard()->GetPads() )
|
|
{
|
|
auto it = netMap.find( pad );
|
|
|
|
if( it == netMap.end() )
|
|
continue;
|
|
|
|
NETINFO_ITEM* netinfo = pad->GetNet();
|
|
|
|
// Net name has changed, create a remap entry
|
|
if( netinfo->GetNet() > 0 && netMap[pad] != netinfo->GetNetname() )
|
|
netRemap[netMap[pad]] = netinfo->GetNetname();
|
|
}
|
|
|
|
if( !netRemap.empty() )
|
|
fixEagleNets( netRemap );
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool PCB_EDIT_FRAME::fixEagleNets( const std::unordered_map<wxString, wxString>& aRemap )
|
|
{
|
|
bool result = true;
|
|
BOARD* board = GetBoard();
|
|
|
|
// perform netlist matching to prevent orphaned zones.
|
|
for( auto zone : board->Zones() )
|
|
{
|
|
auto it = aRemap.find( zone->GetNet()->GetNetname() );
|
|
|
|
if( it != aRemap.end() )
|
|
{
|
|
NETINFO_ITEM* net = board->FindNet( it->second );
|
|
|
|
if( !net )
|
|
{
|
|
wxFAIL;
|
|
result = false;
|
|
continue;
|
|
}
|
|
|
|
zone->SetNet( net );
|
|
}
|
|
}
|
|
|
|
|
|
// perform netlist matching to prevent orphaned tracks/vias.
|
|
for( auto track : board->Tracks() )
|
|
{
|
|
auto it = aRemap.find( track->GetNet()->GetNetname() );
|
|
|
|
if( it != aRemap.end() )
|
|
{
|
|
NETINFO_ITEM* net = board->FindNet( it->second );
|
|
|
|
if( !net )
|
|
{
|
|
wxFAIL;
|
|
result = false;
|
|
continue;
|
|
}
|
|
|
|
track->SetNet( net );
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|