Don't use the 3d resolver config anymore
This commit is contained in:
parent
27f8abd8a1
commit
3b63ce8a48
|
@ -499,88 +499,6 @@ bool FILENAME_RESOLVER::addPath( const SEARCH_PATH& aPath )
|
|||
}
|
||||
|
||||
|
||||
bool FILENAME_RESOLVER::WritePathList( const wxString& aDir, const wxString& aFilename,
|
||||
bool aResolvePaths )
|
||||
{
|
||||
if( aDir.empty() )
|
||||
{
|
||||
std::ostringstream ostr;
|
||||
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
|
||||
wxString errmsg = _( "3D configuration directory is unknown" );
|
||||
ostr << " * " << errmsg.ToUTF8();
|
||||
wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
|
||||
wxMessageBox( errmsg, _( "Write 3D search path list" ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::list<SEARCH_PATH>::const_iterator sPL = m_paths.begin();
|
||||
|
||||
if( !aResolvePaths )
|
||||
{
|
||||
// skip all ${ENV_VAR} alias names
|
||||
|
||||
while( sPL != m_paths.end()
|
||||
&& ( sPL->m_Alias.StartsWith( "${" ) || sPL->m_Alias.StartsWith( "$(" ) ) )
|
||||
{
|
||||
++sPL;
|
||||
}
|
||||
}
|
||||
|
||||
wxFileName cfgpath( aDir, aFilename );
|
||||
wxString cfgname = cfgpath.GetFullPath();
|
||||
std::ofstream cfgFile;
|
||||
|
||||
cfgFile.open( cfgname.ToUTF8(), std::ios_base::trunc );
|
||||
|
||||
if( !cfgFile.is_open() )
|
||||
{
|
||||
std::ostringstream ostr;
|
||||
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
|
||||
wxString errmsg = _( "Could not open configuration file" );
|
||||
ostr << " * " << errmsg.ToUTF8() << " '" << cfgname.ToUTF8() << "'";
|
||||
wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
|
||||
wxMessageBox( errmsg, _( "Write 3D search path list" ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
cfgFile << "#V" << CFGFILE_VERSION << "\n";
|
||||
std::string tstr;
|
||||
|
||||
while( sPL != m_paths.end() )
|
||||
{
|
||||
tstr = sPL->m_Alias.ToUTF8();
|
||||
cfgFile << "\"" << tstr.size() << ":" << tstr << "\",";
|
||||
|
||||
if( aResolvePaths )
|
||||
tstr = ExpandEnvVarSubstitutions( sPL->m_Pathvar, m_project ).ToUTF8();
|
||||
else
|
||||
tstr = sPL->m_Pathvar.ToUTF8();
|
||||
|
||||
cfgFile << "\"" << tstr.size() << ":" << tstr << "\",";
|
||||
|
||||
tstr = sPL->m_Description.ToUTF8();
|
||||
cfgFile << "\"" << tstr.size() << ":" << tstr << "\"\n";
|
||||
|
||||
++sPL;
|
||||
}
|
||||
|
||||
bool bad = cfgFile.bad();
|
||||
cfgFile.close();
|
||||
|
||||
if( bad )
|
||||
{
|
||||
wxMessageBox( _( "Problems writing configuration file" ),
|
||||
_( "Write 3D search path list" ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void FILENAME_RESOLVER::checkEnvVarPath( const wxString& aPath )
|
||||
{
|
||||
bool useParen = false;
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
#include <vector>
|
||||
#include <wx/string.h>
|
||||
|
||||
class PROJECT;
|
||||
class PGM_BASE;
|
||||
|
||||
struct SEARCH_PATH
|
||||
|
@ -89,12 +90,6 @@ public:
|
|||
*/
|
||||
bool UpdatePathList( const std::vector<SEARCH_PATH>& aPathList );
|
||||
|
||||
/**
|
||||
* Write the current path list to a config file.
|
||||
* @param aResolvePaths indicates whether env vars should also be written out or not
|
||||
*/
|
||||
bool WritePathList( const wxString& aDir, const wxString& aFilename, bool aResolvePaths );
|
||||
|
||||
/**
|
||||
* Determines the full path of the given file name.
|
||||
*
|
||||
|
|
|
@ -363,11 +363,6 @@ void DIALOG_EXPORT_STEP::onExportButton( wxCommandEvent& aEvent )
|
|||
return;
|
||||
}
|
||||
|
||||
FILENAME_RESOLVER* fnResolver = m_parent->Prj().Get3DFilenameResolver();
|
||||
|
||||
fnResolver->WritePathList( wxStandardPaths::Get().GetTempDir(), wxT( "ExportPaths.cfg" ),
|
||||
true );
|
||||
|
||||
DIALOG_EXPORT_STEP::STEP_ORG_OPT orgOpt = GetOriginOption();
|
||||
double xOrg = 0.0;
|
||||
double yOrg = 0.0;
|
||||
|
|
|
@ -5,7 +5,7 @@ include_directories( SYSTEM
|
|||
|
||||
set( KS2_LIB_FILES
|
||||
kicad2step.cpp
|
||||
pcb/3d_resolver.cpp
|
||||
|
||||
pcb/base.cpp
|
||||
pcb/kicadmodel.cpp
|
||||
pcb/kicadfootprint.cpp
|
||||
|
|
|
@ -1,797 +0,0 @@
|
|||
/*
|
||||
* This program source code file is part kicad2mcad
|
||||
*
|
||||
* Copyright (C) 2015-2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
|
||||
* Copyright (C) 2020-2022 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 <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
|
||||
#include <wx/fileconf.h>
|
||||
#include <wx/log.h>
|
||||
#include <wx/thread.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/stdpaths.h>
|
||||
|
||||
#include <wx_filename.h>
|
||||
|
||||
#include "3d_resolver.h"
|
||||
|
||||
// configuration file version
|
||||
#define CFGFILE_VERSION 1
|
||||
#define S3D_RESOLVER_CONFIG "ExportPaths.cfg"
|
||||
|
||||
// flag bits used to track different one-off messages to users
|
||||
#define ERRFLG_ALIAS (1)
|
||||
#define ERRFLG_RELPATH (2)
|
||||
#define ERRFLG_ENVPATH (4)
|
||||
|
||||
|
||||
/**
|
||||
* Flag to enable plugin loader trace output.
|
||||
*
|
||||
* @ingroup trace_env_vars
|
||||
*/
|
||||
const wxChar* const trace3dResolver = wxT( "KICAD_3D_RESOLVER" );
|
||||
|
||||
|
||||
static std::mutex mutex3D_resolver;
|
||||
|
||||
|
||||
static bool getHollerith( const std::string& aString, size_t& aIndex, wxString& aResult );
|
||||
|
||||
|
||||
S3D_RESOLVER::S3D_RESOLVER()
|
||||
{
|
||||
m_errflags = 0;
|
||||
}
|
||||
|
||||
|
||||
bool S3D_RESOLVER::Set3DConfigDir( const wxString& aConfigDir )
|
||||
{
|
||||
createPathList();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool S3D_RESOLVER::createPathList( void )
|
||||
{
|
||||
if( !m_Paths.empty() )
|
||||
return true;
|
||||
|
||||
readPathList();
|
||||
|
||||
if( m_Paths.empty() )
|
||||
return false;
|
||||
|
||||
#ifdef DEBUG
|
||||
wxLogTrace( trace3dResolver, wxT( " * [3D model] search paths:\n" ) );
|
||||
|
||||
for( const SEARCH_PATH& searchPath : m_Paths )
|
||||
wxLogTrace( trace3dResolver, wxT( " + '%s'\n" ), searchPath.m_Pathexp );
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
wxString S3D_RESOLVER::ResolvePath( const wxString& aFileName,
|
||||
std::vector<wxString>& aSearchedPaths )
|
||||
{
|
||||
std::lock_guard<std::mutex> lock( mutex3D_resolver );
|
||||
|
||||
if( aFileName.empty() )
|
||||
return wxEmptyString;
|
||||
|
||||
if( m_Paths.empty() )
|
||||
createPathList();
|
||||
|
||||
// look up the filename in the internal filename map
|
||||
std::map<wxString, wxString, S3D::rsort_wxString>::iterator mi;
|
||||
mi = m_NameMap.find( aFileName );
|
||||
|
||||
if( mi != m_NameMap.end() )
|
||||
return mi->second;
|
||||
|
||||
// first attempt to use the name as specified:
|
||||
wxString tname = aFileName;
|
||||
|
||||
#ifdef _WIN32
|
||||
// translate from KiCad's internal UNIX-like path to MSWin paths
|
||||
tname.Replace( wxT( "/" ), wxT( "\\" ) );
|
||||
#endif
|
||||
|
||||
// Note: variable expansion must preferably be performed via a threadsafe wrapper for the
|
||||
// getenv() system call. If we allow the wxFileName::Normalize() routine to perform expansion
|
||||
// then we will have a race condition since wxWidgets does not assure a threadsafe wrapper
|
||||
// for getenv().
|
||||
if( tname.StartsWith( wxT( "${" ) ) || tname.StartsWith( wxT( "$(" ) ) )
|
||||
tname = expandVars( tname );
|
||||
|
||||
wxFileName tmpFN( tname );
|
||||
|
||||
// in the case of absolute filenames we don't store a map item
|
||||
if( !aFileName.StartsWith( wxT( "${" ) ) && !aFileName.StartsWith( wxT( "$(" ) )
|
||||
&& tmpFN.IsAbsolute() )
|
||||
{
|
||||
if( tmpFN.FileExists() )
|
||||
{
|
||||
tmpFN.Normalize( FN_NORMALIZE_FLAGS );
|
||||
return tmpFN.GetFullPath();
|
||||
}
|
||||
else
|
||||
{
|
||||
aSearchedPaths.push_back( tmpFN.GetFullPath() );
|
||||
}
|
||||
}
|
||||
|
||||
// this case covers full paths, leading expanded vars, and paths relative to the current
|
||||
// working directory (which is not necessarily the current project directory)
|
||||
tmpFN.Normalize( FN_NORMALIZE_FLAGS );
|
||||
|
||||
if( tmpFN.FileExists() )
|
||||
{
|
||||
tname = tmpFN.GetFullPath();
|
||||
m_NameMap[ aFileName ] = tname;
|
||||
|
||||
// special case: if a path begins with ${ENV_VAR} but is not in the resolver's path list
|
||||
// then add it
|
||||
if( aFileName.StartsWith( wxT( "${" ) ) || aFileName.StartsWith( wxT( "$(" ) ) )
|
||||
checkEnvVarPath( aFileName );
|
||||
|
||||
return tname;
|
||||
}
|
||||
else if( tmpFN.GetFullPath() != aFileName )
|
||||
{
|
||||
aSearchedPaths.push_back( tmpFN.GetFullPath() );
|
||||
}
|
||||
|
||||
// if a path begins with ${ENV_VAR}/$(ENV_VAR) and is not resolved then the file either does
|
||||
// not exist or the ENV_VAR is not defined
|
||||
if( aFileName.StartsWith( wxT( "${" ) ) || aFileName.StartsWith( wxT( "$(" ) ) )
|
||||
{
|
||||
m_errflags |= ERRFLG_ENVPATH;
|
||||
return aFileName;
|
||||
}
|
||||
|
||||
// at this point aFileName is:
|
||||
// a. an aliased shortened name or
|
||||
// b. cannot be determined
|
||||
|
||||
// check the path relative to the current project directory;
|
||||
// NB: this is not necessarily the same as the current working directory, which has already
|
||||
// been checked. This case accounts for partial paths which do not contain ${KIPRJMOD}.
|
||||
// This check is performed before checking the path relative to ${KICAD6_3DMODEL_DIR} so that
|
||||
// users can potentially override a model within ${KICAD6_3DMODEL_DIR}.
|
||||
if( !m_Paths.empty() && !m_Paths.begin()->m_Pathexp.empty() && !tname.StartsWith( wxT( ":" ) ) )
|
||||
{
|
||||
tmpFN.Assign( m_Paths.begin()->m_Pathexp, wxT( "" ) );
|
||||
wxString fullPath = tmpFN.GetPathWithSep() + tname;
|
||||
|
||||
if( fullPath.StartsWith( wxT( "${" ) ) || fullPath.StartsWith( wxT( "$(" ) ) )
|
||||
fullPath = expandVars( fullPath );
|
||||
|
||||
tmpFN.Assign( fullPath );
|
||||
tmpFN.Normalize( FN_NORMALIZE_FLAGS );
|
||||
|
||||
if( tmpFN.FileExists() )
|
||||
{
|
||||
tname = tmpFN.GetFullPath();
|
||||
m_NameMap[ aFileName ] = tname;
|
||||
return tname;
|
||||
}
|
||||
else if( tmpFN.GetFullPath() != aFileName )
|
||||
{
|
||||
aSearchedPaths.push_back( tmpFN.GetFullPath() );
|
||||
}
|
||||
}
|
||||
|
||||
// check the partial path relative to ${KICAD6_3DMODEL_DIR} (legacy behavior)
|
||||
if( !tname.Contains( wxT( ":" ) ) )
|
||||
{
|
||||
wxFileName fpath;
|
||||
wxString fullPath( wxT( "${KICAD6_3DMODEL_DIR}" ) );
|
||||
fullPath.Append( fpath.GetPathSeparator() );
|
||||
fullPath.Append( tname );
|
||||
fullPath = expandVars( fullPath );
|
||||
fpath.Assign( fullPath );
|
||||
fpath.Normalize( FN_NORMALIZE_FLAGS );
|
||||
|
||||
if( fpath.FileExists() )
|
||||
{
|
||||
tname = fpath.GetFullPath();
|
||||
m_NameMap[ aFileName ] = tname;
|
||||
return tname;
|
||||
}
|
||||
else
|
||||
{
|
||||
aSearchedPaths.push_back( fpath.GetFullPath() );
|
||||
}
|
||||
}
|
||||
|
||||
// at this point the filename must contain an alias or else it is invalid
|
||||
wxString alias; // the alias portion of the short filename
|
||||
wxString relpath; // the path relative to the alias
|
||||
|
||||
if( !SplitAlias( tname, alias, relpath ) )
|
||||
{
|
||||
// this can happen if the file was intended to be relative to ${KICAD6_3DMODEL_DIR}
|
||||
// but ${KICAD6_3DMODEL_DIR} is not set or is incorrect.
|
||||
m_errflags |= ERRFLG_RELPATH;
|
||||
return aFileName;
|
||||
}
|
||||
|
||||
for( const SEARCH_PATH& path : m_Paths )
|
||||
{
|
||||
// ${ENV_VAR} paths have already been checked; skip them
|
||||
if( path.m_Alias.StartsWith( wxT( "${" ) ) || path.m_Alias.StartsWith( wxT( "$(" ) ) )
|
||||
continue;
|
||||
|
||||
if( path.m_Alias == alias && !path.m_Pathexp.empty() )
|
||||
{
|
||||
wxFileName fpath( wxFileName::DirName( path.m_Pathexp ) );
|
||||
wxString fullPath = fpath.GetPathWithSep() + relpath;
|
||||
|
||||
if( fullPath.StartsWith( wxT( "${" ) ) || fullPath.StartsWith( wxT( "$(" ) ) )
|
||||
fullPath = expandVars( fullPath );
|
||||
|
||||
wxFileName tmp( fullPath );
|
||||
tmp.Normalize( FN_NORMALIZE_FLAGS );
|
||||
|
||||
if( tmp.FileExists() )
|
||||
{
|
||||
tname = tmp.GetFullPath();
|
||||
m_NameMap[ aFileName ] = tname;
|
||||
return tname;
|
||||
}
|
||||
else
|
||||
{
|
||||
aSearchedPaths.push_back( tmp.GetFullPath() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_errflags |= ERRFLG_ALIAS;
|
||||
return aFileName;
|
||||
}
|
||||
|
||||
|
||||
bool S3D_RESOLVER::addPath( const SEARCH_PATH& aPath )
|
||||
{
|
||||
if( aPath.m_Alias.empty() || aPath.m_Pathvar.empty() )
|
||||
return false;
|
||||
|
||||
std::lock_guard<std::mutex> lock( mutex3D_resolver );
|
||||
|
||||
SEARCH_PATH tpath = aPath;
|
||||
|
||||
#ifdef _WIN32
|
||||
while( tpath.m_Pathvar.EndsWith( wxT( "\\" ) ) )
|
||||
tpath.m_Pathvar.erase( tpath.m_Pathvar.length() - 1 );
|
||||
#else
|
||||
while( tpath.m_Pathvar.EndsWith( wxT( "/" ) ) && tpath.m_Pathvar.length() > 1 )
|
||||
tpath.m_Pathvar.erase( tpath.m_Pathvar.length() - 1 );
|
||||
#endif
|
||||
|
||||
wxFileName path( tpath.m_Pathvar, wxT( "" ) );
|
||||
path.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
|
||||
|
||||
if( !path.DirExists() )
|
||||
{
|
||||
// Show a message only in debug mode
|
||||
#ifdef DEBUG
|
||||
if( aPath.m_Pathvar == wxT( "${KICAD6_3DMODEL_DIR}" )
|
||||
|| aPath.m_Pathvar == wxT( "${KIPRJMOD}" )
|
||||
|| aPath.m_Pathvar == wxT( "${KISYS3DMOD}" ) )
|
||||
{
|
||||
// suppress the message if the missing pathvar is a system variable
|
||||
}
|
||||
else
|
||||
{
|
||||
wxString msg = _( "The given path does not exist" );
|
||||
msg.append( wxT( "\n" ) );
|
||||
msg.append( tpath.m_Pathvar );
|
||||
wxLogMessage( wxT( "%s\n" ), msg.ToUTF8() );
|
||||
}
|
||||
#endif
|
||||
|
||||
tpath.m_Pathexp.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
tpath.m_Pathexp = path.GetFullPath();
|
||||
|
||||
#ifdef _WIN32
|
||||
while( tpath.m_Pathexp.EndsWith( wxT( "\\" ) ) )
|
||||
tpath.m_Pathexp.erase( tpath.m_Pathexp.length() - 1 );
|
||||
#else
|
||||
while( tpath.m_Pathexp.EndsWith( wxT( "/" ) ) && tpath.m_Pathexp.length() > 1 )
|
||||
tpath.m_Pathexp.erase( tpath.m_Pathexp.length() - 1 );
|
||||
#endif
|
||||
}
|
||||
|
||||
std::list< SEARCH_PATH >::iterator sPL = m_Paths.begin();
|
||||
std::list< SEARCH_PATH >::iterator ePL = m_Paths.end();
|
||||
|
||||
while( sPL != ePL )
|
||||
{
|
||||
if( tpath.m_Alias == sPL->m_Alias )
|
||||
{
|
||||
wxString msg = _( "Alias:" ) + wxS( " " );
|
||||
msg.append( tpath.m_Alias );
|
||||
msg.append( wxS( "\n" ) );
|
||||
msg.append( _( "This path:" ) + wxS( " " ) );
|
||||
msg.append( tpath.m_Pathvar );
|
||||
msg.append( wxS( "\n" ) );
|
||||
msg.append( _( "Existing path:" ) + wxS( " " ) );
|
||||
msg.append( sPL->m_Pathvar );
|
||||
wxMessageBox( msg, _( "Bad alias (duplicate name)" ) );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
++sPL;
|
||||
}
|
||||
|
||||
m_Paths.push_back( tpath );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool S3D_RESOLVER::readPathList( void )
|
||||
{
|
||||
wxFileName cfgpath( wxStandardPaths::Get().GetTempDir(), S3D_RESOLVER_CONFIG );
|
||||
cfgpath.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
|
||||
wxString cfgname = cfgpath.GetFullPath();
|
||||
|
||||
size_t nitems = m_Paths.size();
|
||||
|
||||
std::ifstream cfgFile;
|
||||
std::string cfgLine;
|
||||
|
||||
if( !wxFileName::Exists( cfgname ) )
|
||||
{
|
||||
wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n * no 3D configuration file '%s'" ),
|
||||
__FILE__, __FUNCTION__, __LINE__, cfgname );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
cfgFile.open( cfgname.ToUTF8() );
|
||||
|
||||
if( !cfgFile.is_open() )
|
||||
{
|
||||
wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n * Could not open configuration file '%s'" ),
|
||||
__FILE__, __FUNCTION__, __LINE__, cfgname );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int lineno = 0;
|
||||
SEARCH_PATH al;
|
||||
size_t idx;
|
||||
int vnum = 0; // version number
|
||||
|
||||
while( cfgFile.good() )
|
||||
{
|
||||
cfgLine.clear();
|
||||
std::getline( cfgFile, cfgLine );
|
||||
++lineno;
|
||||
|
||||
if( cfgLine.empty() )
|
||||
{
|
||||
if( cfgFile.eof() )
|
||||
break;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if( 1 == lineno && cfgLine.compare( 0, 2, "#V" ) == 0 )
|
||||
{
|
||||
// extract the version number and parse accordingly
|
||||
if( cfgLine.size() > 2 )
|
||||
{
|
||||
std::istringstream istr;
|
||||
istr.str( cfgLine.substr( 2 ) );
|
||||
istr >> vnum;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
idx = 0;
|
||||
|
||||
if( !getHollerith( cfgLine, idx, al.m_Alias ) )
|
||||
continue;
|
||||
|
||||
if( !getHollerith( cfgLine, idx, al.m_Pathvar ) )
|
||||
continue;
|
||||
|
||||
if( !getHollerith( cfgLine, idx, al.m_Description ) )
|
||||
continue;
|
||||
|
||||
addPath( al );
|
||||
}
|
||||
|
||||
cfgFile.close();
|
||||
|
||||
if( m_Paths.size() != nitems )
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void S3D_RESOLVER::checkEnvVarPath( const wxString& aPath )
|
||||
{
|
||||
bool useParen = false;
|
||||
|
||||
if( aPath.StartsWith( wxT( "$(" ) ) )
|
||||
useParen = true;
|
||||
else if( !aPath.StartsWith( wxT( "${" ) ) )
|
||||
return;
|
||||
|
||||
size_t pEnd;
|
||||
|
||||
if( useParen )
|
||||
pEnd = aPath.find( wxT( ")" ) );
|
||||
else
|
||||
pEnd = aPath.find( wxT( "}" ) );
|
||||
|
||||
if( pEnd == wxString::npos )
|
||||
return;
|
||||
|
||||
wxString envar = aPath.substr( 0, pEnd + 1 );
|
||||
|
||||
// check if the alias exists; if not then add it to the end of the
|
||||
// env var section of the path list
|
||||
std::list< SEARCH_PATH >::iterator sPL = m_Paths.begin();
|
||||
std::list< SEARCH_PATH >::iterator ePL = m_Paths.end();
|
||||
|
||||
while( sPL != ePL )
|
||||
{
|
||||
if( sPL->m_Alias == envar )
|
||||
return;
|
||||
|
||||
if( !sPL->m_Alias.StartsWith( wxT( "${" ) ) )
|
||||
break;
|
||||
|
||||
++sPL;
|
||||
}
|
||||
|
||||
SEARCH_PATH lpath;
|
||||
lpath.m_Alias = envar;
|
||||
lpath.m_Pathvar = lpath.m_Alias;
|
||||
wxFileName tmpFN( lpath.m_Alias, wxT( "" ) );
|
||||
wxUniChar psep = tmpFN.GetPathSeparator();
|
||||
tmpFN.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
|
||||
|
||||
if( !tmpFN.DirExists() )
|
||||
return;
|
||||
|
||||
lpath.m_Pathexp = tmpFN.GetFullPath();
|
||||
|
||||
if( !lpath.m_Pathexp.empty() && psep == *lpath.m_Pathexp.rbegin() )
|
||||
lpath.m_Pathexp.erase( --lpath.m_Pathexp.end() );
|
||||
|
||||
if( lpath.m_Pathexp.empty() )
|
||||
return;
|
||||
|
||||
m_Paths.insert( sPL, lpath );
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
wxString S3D_RESOLVER::expandVars( const wxString& aPath )
|
||||
{
|
||||
if( aPath.empty() )
|
||||
return wxEmptyString;
|
||||
|
||||
wxString result;
|
||||
|
||||
for( const std::pair<const wxString, wxString>& i : m_EnvVars )
|
||||
{
|
||||
if( !aPath.compare( 2, i.first.length(), i.first ) )
|
||||
{
|
||||
result = i.second;
|
||||
result.append( aPath.substr( 3 + i.first.length() ) );
|
||||
|
||||
if( result.StartsWith( wxT( "${" ) ) || result.StartsWith( wxT( "$(" ) ) )
|
||||
result = expandVars( result );
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
result = wxExpandEnvVars( aPath );
|
||||
|
||||
if( result == aPath )
|
||||
return wxEmptyString;
|
||||
|
||||
if( result.StartsWith( wxT( "${" ) ) || result.StartsWith( wxT( "$(" ) ) )
|
||||
result = expandVars( result );
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
wxString S3D_RESOLVER::ShortenPath( const wxString& aFullPathName )
|
||||
{
|
||||
wxString fname = aFullPathName;
|
||||
|
||||
if( m_Paths.empty() )
|
||||
createPathList();
|
||||
|
||||
std::lock_guard<std::mutex> lock( mutex3D_resolver );
|
||||
|
||||
std::list< SEARCH_PATH >::const_iterator sL = m_Paths.begin();
|
||||
std::list< SEARCH_PATH >::const_iterator eL = m_Paths.end();
|
||||
size_t idx;
|
||||
|
||||
while( sL != eL )
|
||||
{
|
||||
// undefined paths do not participate in the file name shortening procedure.
|
||||
if( sL->m_Pathexp.empty() )
|
||||
{
|
||||
++sL;
|
||||
continue;
|
||||
}
|
||||
|
||||
wxFileName fpath( sL->m_Pathexp, wxT( "" ) );
|
||||
wxString fps = fpath.GetPathWithSep();
|
||||
wxString tname;
|
||||
|
||||
idx = fname.find( fps );
|
||||
|
||||
if( std::string::npos != idx && 0 == idx )
|
||||
{
|
||||
fname = fname.substr( fps.size() );
|
||||
|
||||
#ifdef _WIN32
|
||||
// ensure only the '/' separator is used in the internal name
|
||||
fname.Replace( wxT( "\\" ), wxT( "/" ) );
|
||||
#endif
|
||||
|
||||
if( sL->m_Alias.StartsWith( wxT( "${" ) ) || sL->m_Alias.StartsWith( wxT( "$(" ) ) )
|
||||
{
|
||||
// old style ENV_VAR
|
||||
tname = sL->m_Alias;
|
||||
tname.Append( wxT( "/" ) );
|
||||
tname.append( fname );
|
||||
}
|
||||
else
|
||||
{
|
||||
// new style alias
|
||||
tname = wxT( ":" );
|
||||
tname.append( sL->m_Alias );
|
||||
tname.append( wxT( ":" ) );
|
||||
tname.append( fname );
|
||||
}
|
||||
|
||||
return tname;
|
||||
}
|
||||
|
||||
++sL;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// it is strange to convert an MSWin full path to use the
|
||||
// UNIX separator but this is done for consistency and can
|
||||
// be helpful even when transferring project files from
|
||||
// MSWin to *NIX.
|
||||
fname.Replace( wxT( "\\" ), wxT( "/" ) );
|
||||
#endif
|
||||
|
||||
return fname;
|
||||
}
|
||||
|
||||
|
||||
const std::list< SEARCH_PATH >* S3D_RESOLVER::GetPaths( void )
|
||||
{
|
||||
return &m_Paths;
|
||||
}
|
||||
|
||||
|
||||
bool S3D_RESOLVER::SplitAlias( const wxString& aFileName, wxString& anAlias, wxString& aRelPath )
|
||||
{
|
||||
anAlias.clear();
|
||||
aRelPath.clear();
|
||||
|
||||
size_t searchStart = 0;
|
||||
|
||||
if( aFileName.StartsWith( wxT( ":" ) ) )
|
||||
searchStart = 1;
|
||||
|
||||
size_t tagpos = aFileName.find( wxT( ":" ), searchStart );
|
||||
|
||||
if( tagpos == wxString::npos || tagpos == searchStart )
|
||||
return false;
|
||||
|
||||
if( tagpos + 1 >= aFileName.length() )
|
||||
return false;
|
||||
|
||||
anAlias = aFileName.substr( searchStart, tagpos - searchStart );
|
||||
aRelPath = aFileName.substr( tagpos + 1 );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool getHollerith( const std::string& aString, size_t& aIndex, wxString& aResult )
|
||||
{
|
||||
aResult.clear();
|
||||
|
||||
if( aIndex >= aString.size() )
|
||||
{
|
||||
wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n * Bad Hollerith string in line '%s'" ),
|
||||
__FILE__, __FUNCTION__, __LINE__, aString );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t i2 = aString.find( '"', aIndex );
|
||||
|
||||
if( std::string::npos == i2 )
|
||||
{
|
||||
wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n * missing opening quote mark in line '%s'" ),
|
||||
__FILE__, __FUNCTION__, __LINE__, aString );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
++i2;
|
||||
|
||||
if( i2 >= aString.size() )
|
||||
{
|
||||
wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n * unexpected end of line in line '%s'" ),
|
||||
__FILE__, __FUNCTION__, __LINE__, aString );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string tnum;
|
||||
|
||||
while( aString[i2] >= '0' && aString[i2] <= '9' )
|
||||
tnum.append( 1, aString[i2++] );
|
||||
|
||||
if( tnum.empty() || aString[i2++] != ':' )
|
||||
{
|
||||
wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n * Bad Hollerith string in line '%s'" ),
|
||||
__FILE__, __FUNCTION__, __LINE__, aString );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::istringstream istr;
|
||||
istr.str( tnum );
|
||||
size_t nchars;
|
||||
istr >> nchars;
|
||||
|
||||
if( ( i2 + nchars ) >= aString.size() )
|
||||
{
|
||||
wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n * unexpected end of line in line '%s'" ),
|
||||
__FILE__, __FUNCTION__, __LINE__, aString );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if( nchars > 0 )
|
||||
{
|
||||
aResult = wxString::FromUTF8( aString.substr( i2, nchars ).c_str() );
|
||||
i2 += nchars;
|
||||
}
|
||||
|
||||
if( i2 >= aString.size() || aString[i2] != '"' )
|
||||
{
|
||||
wxLogTrace( trace3dResolver, wxT( "%s:%s:%d\n * missing closing quote mark in line '%s'" ),
|
||||
__FILE__, __FUNCTION__, __LINE__, aString );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
aIndex = i2 + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool S3D_RESOLVER::ValidateFileName( const wxString& aFileName, bool& hasAlias )
|
||||
{
|
||||
// Rules:
|
||||
// 1. The generic form of an aliased 3D relative path is:
|
||||
// ALIAS:relative/path
|
||||
// 2. ALIAS is a UTF string excluding "{}[]()%~<>\"='`;:.,&?/\\|$"
|
||||
// 3. The relative path must be a valid relative path for the platform
|
||||
hasAlias = false;
|
||||
|
||||
if( aFileName.empty() )
|
||||
return false;
|
||||
|
||||
wxString filename = aFileName;
|
||||
wxString lpath;
|
||||
size_t aliasStart = aFileName.StartsWith( ':' ) ? 1 : 0;
|
||||
size_t aliasEnd = aFileName.find( ':' );
|
||||
|
||||
// ensure that the file separators suit the current platform
|
||||
#ifdef __WINDOWS__
|
||||
filename.Replace( wxT( "/" ), wxT( "\\" ) );
|
||||
|
||||
// if we see the :\ pattern then it must be a drive designator
|
||||
if( aliasEnd != wxString::npos )
|
||||
{
|
||||
size_t pos1 = aFileName.find( wxT( ":\\" ) );
|
||||
|
||||
if( pos1 != wxString::npos && ( pos1 != aliasEnd || pos1 != 1 ) )
|
||||
return false;
|
||||
|
||||
// if we have a drive designator then we have no alias
|
||||
if( pos1 != wxString::npos )
|
||||
aliasEnd = wxString::npos;
|
||||
}
|
||||
#else
|
||||
filename.Replace( wxT( "\\" ), wxT( "/" ) );
|
||||
#endif
|
||||
|
||||
// names may not end with ':'
|
||||
if( aliasEnd == aFileName.length() - 1 )
|
||||
return false;
|
||||
|
||||
if( aliasEnd != wxString::npos )
|
||||
{
|
||||
// ensure the alias component is not empty
|
||||
if( aliasEnd == aliasStart )
|
||||
return false;
|
||||
|
||||
lpath = filename.substr( aliasStart, aliasEnd );
|
||||
|
||||
// check the alias for restricted characters
|
||||
if( wxString::npos != lpath.find_first_of( "{}[]()%~<>\"='`;:.,&?/\\|$" ) )
|
||||
return false;
|
||||
|
||||
hasAlias = true;
|
||||
lpath = aFileName.substr( aliasEnd + 1 );
|
||||
}
|
||||
else
|
||||
{
|
||||
lpath = aFileName;
|
||||
|
||||
// in the case of ${ENV_VAR}|$(ENV_VAR)/path, strip the
|
||||
// environment string before testing
|
||||
aliasEnd = wxString::npos;
|
||||
|
||||
if( aFileName.StartsWith( wxT( "${" ) ) )
|
||||
aliasEnd = aFileName.find( '}' );
|
||||
else if( aFileName.StartsWith( wxT( "$(" ) ) )
|
||||
aliasEnd = aFileName.find( ')' );
|
||||
|
||||
if( aliasEnd != wxString::npos )
|
||||
lpath = aFileName.substr( aliasEnd + 1 );
|
||||
}
|
||||
|
||||
if( wxString::npos != lpath.find_first_of( wxFileName::GetForbiddenChars() ) )
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
|
@ -1,203 +0,0 @@
|
|||
/*
|
||||
* This program source code file is part of KiCad, a free EDA CAD application.
|
||||
*
|
||||
* Copyright (C) 2015-2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
|
||||
* Copyright (C) 2021 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file 3d_resolver.h
|
||||
* provides an extensible class to resolve 3D model paths.
|
||||
* Derived from 3d_filename_resolver.h,cpp and modified for
|
||||
* use in stand-alone utilities.
|
||||
*/
|
||||
|
||||
#ifndef RESOLVER_3D_H
|
||||
#define RESOLVER_3D_H
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <wx/string.h>
|
||||
|
||||
namespace S3D
|
||||
{
|
||||
struct rsort_wxString
|
||||
{
|
||||
bool operator() (const wxString& strA, const wxString& strB ) const
|
||||
{
|
||||
// sort a wxString using the reverse character order; for 3d model
|
||||
// filenames this will typically be a much faster operation than
|
||||
// a normal alphabetic sort
|
||||
wxString::const_reverse_iterator sA = strA.rbegin();
|
||||
wxString::const_reverse_iterator eA = strA.rend();
|
||||
|
||||
wxString::const_reverse_iterator sB = strB.rbegin();
|
||||
wxString::const_reverse_iterator eB = strB.rend();
|
||||
|
||||
if( strA.empty() )
|
||||
{
|
||||
if( strB.empty() )
|
||||
return false;
|
||||
|
||||
// note: this rule implies that a null string is first in the sort order
|
||||
return true;
|
||||
}
|
||||
|
||||
if( strB.empty() )
|
||||
return false;
|
||||
|
||||
while( sA != eA && sB != eB )
|
||||
{
|
||||
if( (*sA) == (*sB) )
|
||||
{
|
||||
++sA;
|
||||
++sB;
|
||||
continue;
|
||||
}
|
||||
|
||||
if( (*sA) < (*sB) )
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
if( sB == eB )
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
}; // end NAMESPACE
|
||||
|
||||
|
||||
class KICADPCB;
|
||||
|
||||
struct SEARCH_PATH
|
||||
{
|
||||
wxString m_Alias; // alias to the base path
|
||||
wxString m_Pathvar; // base path as stored in the config file
|
||||
wxString m_Pathexp; // expanded base path
|
||||
wxString m_Description; // description of the aliased path
|
||||
};
|
||||
|
||||
|
||||
class S3D_RESOLVER
|
||||
{
|
||||
public:
|
||||
S3D_RESOLVER();
|
||||
|
||||
/**
|
||||
* Set the user's configuration directory for 3D models.
|
||||
*
|
||||
* @param aConfigDir
|
||||
* @return true if the call succeeds (directory exists).
|
||||
*/
|
||||
bool Set3DConfigDir( const wxString& aConfigDir );
|
||||
|
||||
/**
|
||||
* Determine the full path of the given file name.
|
||||
*
|
||||
* In the future remote files may be supported, in which case it is best to require a full
|
||||
* URI in which case #ResolvePath should check that the URI conforms to RFC-2396 and related
|
||||
* documents and copies \a aFileName into the resolved name if the URI is valid.
|
||||
*
|
||||
* If the file is not found, \a aSearchedPaths will contain the paths that were searched.
|
||||
*/
|
||||
wxString ResolvePath( const wxString& aFileName, std::vector<wxString>& aSearchedPaths );
|
||||
|
||||
/**
|
||||
* Produce a relative path based on the existing search directories or returns the same path
|
||||
* if the path is not a superset of an existing search path.
|
||||
*
|
||||
* @param aFullPathName is an absolute path to shorten.
|
||||
* @return the shortened path or aFullPathName.
|
||||
*/
|
||||
wxString ShortenPath( const wxString& aFullPathName );
|
||||
|
||||
/**
|
||||
* Return a pointer to the internal path list.
|
||||
*
|
||||
* The list can be used to set up the list of search paths available to a 3D file browser.
|
||||
*
|
||||
* @return the search path list.
|
||||
*/
|
||||
const std::list< SEARCH_PATH >* GetPaths( void );
|
||||
|
||||
/**
|
||||
* Return true if the given name contains an alias and populates the string with the alias
|
||||
* and the relative path.
|
||||
*/
|
||||
bool SplitAlias( const wxString& aFileName, wxString& anAlias, wxString& aRelPath );
|
||||
|
||||
/**
|
||||
* If the path contains an alias then \a hasAlias is set true.
|
||||
*
|
||||
* @return true if the given path is a valid aliased relative path.
|
||||
*/
|
||||
bool ValidateFileName( const wxString& aFileName, bool& hasAlias );
|
||||
|
||||
private:
|
||||
/**
|
||||
* Build the path list using available information such as KICAD6_3DMODEL_DIR and the
|
||||
* 3d_path_list configuration file.
|
||||
*
|
||||
* Invalid paths are silently discarded and removed from the configuration file.
|
||||
*
|
||||
* @return true if at least one valid path was found
|
||||
*/
|
||||
bool createPathList( void );
|
||||
|
||||
/**
|
||||
* Check that \a aPath is valid and adds it to the search list.
|
||||
*
|
||||
* @param aPath is the alias set to be checked and added.
|
||||
* @return true if \a aPath is valid.
|
||||
*/
|
||||
bool addPath( const SEARCH_PATH& aPath );
|
||||
|
||||
/**
|
||||
* Read a list of path names from a configuration file.
|
||||
*
|
||||
* @return true if a file was found and contained at least one valid path.
|
||||
*/
|
||||
bool readPathList( void );
|
||||
|
||||
/**
|
||||
* Check the ${ENV_VAR} component of a path and adds it to the resolver's path list if it
|
||||
* is not yet in the list.
|
||||
*/
|
||||
void checkEnvVarPath( const wxString& aPath );
|
||||
|
||||
wxString expandVars( const wxString& aPath );
|
||||
|
||||
std::list< SEARCH_PATH > m_Paths; ///< List of base search paths.
|
||||
|
||||
///< Mapping of (short) file names to resolved names.
|
||||
std::map< wxString, wxString, S3D::rsort_wxString > m_NameMap;
|
||||
int m_errflags;
|
||||
wxString m_curProjDir;
|
||||
|
||||
///< Environment variables.
|
||||
std::map< wxString, wxString > m_EnvVars;
|
||||
};
|
||||
|
||||
#endif // RESOLVER_3D_H
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
#include "kicadfootprint.h"
|
||||
|
||||
#include "3d_resolver.h"
|
||||
#include <filename_resolver.h>
|
||||
#include "kicadcurve.h"
|
||||
#include "kicadmodel.h"
|
||||
#include "kicadpad.h"
|
||||
|
@ -358,7 +358,7 @@ bool KICADFOOTPRINT::parsePad( SEXPR::SEXPR* data )
|
|||
}
|
||||
|
||||
|
||||
bool KICADFOOTPRINT::ComposePCB( class PCBMODEL* aPCB, S3D_RESOLVER* resolver,
|
||||
bool KICADFOOTPRINT::ComposePCB( class PCBMODEL* aPCB, FILENAME_RESOLVER* resolver,
|
||||
DOUBLET aOrigin, bool aComposeVirtual, bool aSubstituteModels )
|
||||
{
|
||||
// translate pads and curves to final position and append to PCB.
|
||||
|
@ -439,22 +439,14 @@ bool KICADFOOTPRINT::ComposePCB( class PCBMODEL* aPCB, S3D_RESOLVER* resolver,
|
|||
continue;
|
||||
|
||||
std::vector<wxString> searchedPaths;
|
||||
mname = resolver->ResolvePath( mname, searchedPaths );
|
||||
mname = resolver->ResolvePath( mname, wxEmptyString );
|
||||
|
||||
if( !wxFileName::FileExists( mname ) )
|
||||
{
|
||||
wxString paths;
|
||||
|
||||
for( const wxString& path : searchedPaths )
|
||||
paths += " " + path + "\n";
|
||||
|
||||
ReportMessage( wxString::Format( wxT( "Could not add 3D model to %s.\n"
|
||||
"File not found: %s\n"
|
||||
"Searched paths:\n"
|
||||
"%s" ),
|
||||
"File not found: %s\n" ),
|
||||
m_refdes,
|
||||
mname,
|
||||
paths) );
|
||||
mname) );
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ class KICADPAD;
|
|||
class KICADCURVE;
|
||||
class KICADMODEL;
|
||||
class PCBMODEL;
|
||||
class S3D_RESOLVER;
|
||||
class FILENAME_RESOLVER;
|
||||
|
||||
class KICADFOOTPRINT
|
||||
{
|
||||
|
@ -50,7 +50,7 @@ public:
|
|||
|
||||
bool Read( SEXPR::SEXPR* aEntry );
|
||||
|
||||
bool ComposePCB( class PCBMODEL* aPCB, S3D_RESOLVER* resolver,
|
||||
bool ComposePCB( class PCBMODEL* aPCB, FILENAME_RESOLVER* resolver,
|
||||
DOUBLET aOrigin, bool aComposeVirtual = true, bool aSubstituteModels = true );
|
||||
|
||||
private:
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
#include <wx/colour.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "3d_resolver.h"
|
||||
#include <filename_resolver.h>
|
||||
#include "base.h"
|
||||
|
||||
#ifdef SUPPORTS_IGES
|
||||
|
@ -102,7 +102,7 @@ private:
|
|||
bool parsePolygon( SEXPR::SEXPR* data );
|
||||
|
||||
private:
|
||||
S3D_RESOLVER m_resolver;
|
||||
FILENAME_RESOLVER m_resolver;
|
||||
wxString m_filename;
|
||||
PCBMODEL* m_pcb_model;
|
||||
DOUBLET m_origin;
|
||||
|
|
Loading…
Reference in New Issue