KiCad PCB EDA Suite
FILENAME_RESOLVER Class Reference

Provide an extensible class to resolve 3D model paths. More...

#include <filename_resolver.h>

Public Member Functions

 FILENAME_RESOLVER ()
 
bool Set3DConfigDir (const wxString &aConfigDir)
 Set the user's configuration directory for 3D models. More...
 
bool SetProject (PROJECT *aProject, bool *flgChanged=nullptr)
 Set the current KiCad project directory as the first entry in the model path list. More...
 
wxString GetProjectDir () const
 
void SetProgramBase (PGM_BASE *aBase)
 Set a pointer to the application's PGM_BASE instance used to extract the local env vars. More...
 
bool UpdatePathList (const std::vector< SEARCH_PATH > &aPathList)
 Clear the current path list and substitutes the given path list and update the path configuration file on success. More...
 
bool WritePathList (const wxString &aDir, const wxString &aFilename, bool aResolvePaths)
 Write the current path list to a config file. More...
 
wxString ResolvePath (const wxString &aFileName)
 Determines the full path of the given file name. More...
 
wxString ShortenPath (const wxString &aFullPathName)
 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. More...
 
const std::list< SEARCH_PATH > * GetPaths () const
 Return a pointer to the internal path list; the items in:load. More...
 
bool SplitAlias (const wxString &aFileName, wxString &anAlias, wxString &aRelPath) const
 Return true if the given name contains an alias and populates the string anAlias with the alias and aRelPath with the relative path. More...
 
bool ValidateFileName (const wxString &aFileName, bool &hasAlias) const
 Returns true if the given path is a valid aliased relative path. More...
 
bool GetKicadPaths (std::list< wxString > &paths) const
 Return a list of path environment variables local to KiCad. More...
 

Private Member Functions

bool createPathList (void)
 Build the path list using available information such as KICAD6_3DMODEL_DIR and the 3d_path_list configuration file. More...
 
bool addPath (const SEARCH_PATH &aPath)
 Check that a path is valid and adds it to the search list. More...
 
bool readPathList (void)
 Read a list of path names from a configuration file. More...
 
void checkEnvVarPath (const wxString &aPath)
 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. More...
 

Private Attributes

wxString m_configDir
 
std::list< SEARCH_PATHm_paths
 
int m_errflags
 
PGM_BASEm_pgm
 
PROJECTm_project
 
wxString m_curProjDir
 

Detailed Description

Provide an extensible class to resolve 3D model paths.

Initially the legacy behavior will be implemented and an incomplete path would be checked against the project directory or the KICAD6_3DMODEL_DIR environment variable. In the future a configurable set of search paths may be specified.

Definition at line 55 of file filename_resolver.h.

Constructor & Destructor Documentation

◆ FILENAME_RESOLVER()

FILENAME_RESOLVER::FILENAME_RESOLVER ( )

Definition at line 54 of file filename_resolver.cpp.

54  :
55  m_pgm( nullptr ),
56  m_project( nullptr )
57 {
58  m_errflags = 0;
59 }

References m_errflags.

Member Function Documentation

◆ addPath()

bool FILENAME_RESOLVER::addPath ( const SEARCH_PATH aPath)
private

Check that a path is valid and adds it to the search list.

Parameters
aPathis the alias set to be checked and added.
Returns
true if aPath is valid.

Definition at line 406 of file filename_resolver.cpp.

407 {
408  if( aPath.m_Alias.empty() || aPath.m_Pathvar.empty() )
409  return false;
410 
411  std::lock_guard<std::mutex> lock( mutex_resolver );
412 
413  SEARCH_PATH tpath = aPath;
414 
415  #ifdef _WIN32
416  while( tpath.m_Pathvar.EndsWith( wxT( "\\" ) ) )
417  tpath.m_Pathvar.erase( tpath.m_Pathvar.length() - 1 );
418  #else
419  while( tpath.m_Pathvar.EndsWith( wxT( "/" ) ) && tpath.m_Pathvar.length() > 1 )
420  tpath.m_Pathvar.erase( tpath.m_Pathvar.length() - 1 );
421  #endif
422 
423  wxFileName path( ExpandEnvVarSubstitutions( tpath.m_Pathvar, m_project ), "" );
424 
425  path.Normalize();
426 
427  if( !path.DirExists() )
428  {
429  if( aPath.m_Pathvar == "${KICAD6_3DMODEL_DIR}"
430  || aPath.m_Pathvar == "${KIPRJMOD}" || aPath.m_Pathvar == "$(KIPRJMOD)"
431  || aPath.m_Pathvar == "${KISYS3DMOD}" || aPath.m_Pathvar == "$(KISYS3DMOD)" )
432  {
433  // suppress the message if the missing pathvar is a system variable
434  }
435  else
436  {
437  wxString msg = _( "The given path does not exist" );
438  msg.append( wxT( "\n" ) );
439  msg.append( tpath.m_Pathvar );
440  wxMessageBox( msg, _( "3D model search path" ) );
441  }
442 
443  tpath.m_Pathexp.clear();
444  }
445  else
446  {
447  tpath.m_Pathexp = path.GetFullPath();
448 
449 #ifdef _WIN32
450  while( tpath.m_Pathexp.EndsWith( wxT( "\\" ) ) )
451  tpath.m_Pathexp.erase( tpath.m_Pathexp.length() - 1 );
452 #else
453  while( tpath.m_Pathexp.EndsWith( wxT( "/" ) ) && tpath.m_Pathexp.length() > 1 )
454  tpath.m_Pathexp.erase( tpath.m_Pathexp.length() - 1 );
455 #endif
456  }
457 
458  std::list< SEARCH_PATH >::iterator sPL = m_paths.begin();
459  std::list< SEARCH_PATH >::iterator ePL = m_paths.end();
460 
461  while( sPL != ePL )
462  {
463  if( tpath.m_Alias == sPL->m_Alias )
464  {
465  wxString msg = _( "Alias: " );
466  msg.append( tpath.m_Alias );
467  msg.append( wxT( "\n" ) );
468  msg.append( _( "This path:" ) + wxS( " " ) );
469  msg.append( tpath.m_Pathvar );
470  msg.append( wxT( "\n" ) );
471  msg.append( _( "Existing path:" ) + wxS( " " ) );
472  msg.append( sPL->m_Pathvar );
473  wxMessageBox( msg, _( "Bad alias (duplicate name)" ) );
474 
475  return false;
476  }
477 
478  ++sPL;
479  }
480 
481  m_paths.push_back( tpath );
482  return true;
483 }
std::list< SEARCH_PATH > m_paths
const wxString ExpandEnvVarSubstitutions(const wxString &aString, PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition: common.cpp:267
wxString m_Alias
wxString m_Pathvar
#define _(s)
static std::mutex mutex_resolver
wxString m_Pathexp

References _, ExpandEnvVarSubstitutions(), SEARCH_PATH::m_Alias, SEARCH_PATH::m_Pathexp, m_paths, SEARCH_PATH::m_Pathvar, m_project, mutex_resolver, and path.

Referenced by readPathList(), and UpdatePathList().

◆ checkEnvVarPath()

void FILENAME_RESOLVER::checkEnvVarPath ( const wxString &  aPath)
private

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.

Definition at line 676 of file filename_resolver.cpp.

677 {
678  bool useParen = false;
679 
680  if( aPath.StartsWith( "$(" ) )
681  useParen = true;
682  else if( !aPath.StartsWith( "${" ) )
683  return;
684 
685  size_t pEnd;
686 
687  if( useParen )
688  pEnd = aPath.find( ")" );
689  else
690  pEnd = aPath.find( "}" );
691 
692  if( pEnd == wxString::npos )
693  return;
694 
695  wxString envar = aPath.substr( 0, pEnd + 1 );
696 
697  // check if the alias exists; if not then add it to the end of the
698  // env var section of the path list
699  auto sPL = m_paths.begin();
700  auto ePL = m_paths.end();
701 
702  while( sPL != ePL )
703  {
704  if( sPL->m_Alias == envar )
705  return;
706 
707  if( !sPL->m_Alias.StartsWith( "${" ) )
708  break;
709 
710  ++sPL;
711  }
712 
713  SEARCH_PATH lpath;
714  lpath.m_Alias = envar;
715  lpath.m_Pathvar = lpath.m_Alias;
716  wxFileName tmpFN( ExpandEnvVarSubstitutions( lpath.m_Alias, m_project ), "" );
717 
718  wxUniChar psep = tmpFN.GetPathSeparator();
719  tmpFN.Normalize();
720 
721  if( !tmpFN.DirExists() )
722  return;
723 
724  lpath.m_Pathexp = tmpFN.GetFullPath();
725 
726  if( !lpath.m_Pathexp.empty() && psep == *lpath.m_Pathexp.rbegin() )
727  lpath.m_Pathexp.erase( --lpath.m_Pathexp.end() );
728 
729  if( lpath.m_Pathexp.empty() )
730  return;
731 
732  m_paths.insert( sPL, lpath );
733 }
std::list< SEARCH_PATH > m_paths
const wxString ExpandEnvVarSubstitutions(const wxString &aString, PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition: common.cpp:267
wxString m_Alias
wxString m_Pathvar
wxString m_Pathexp

References ExpandEnvVarSubstitutions(), SEARCH_PATH::m_Alias, SEARCH_PATH::m_Pathexp, m_paths, SEARCH_PATH::m_Pathvar, and m_project.

Referenced by ResolvePath().

◆ createPathList()

bool FILENAME_RESOLVER::createPathList ( void  )
private

Build the path list using available information such as KICAD6_3DMODEL_DIR and the 3d_path_list configuration file.

Warning
Invalid paths are silently discarded and removed from the configuration file.
Returns
true if at least one valid path was found.

Definition at line 159 of file filename_resolver.cpp.

160 {
161  if( !m_paths.empty() )
162  return true;
163 
164  wxString kmod;
165 
166  // add an entry for the default search path; at this point
167  // we cannot set a sensible default so we use an empty string.
168  // the user may change this later with a call to SetProjectDir()
169 
170  SEARCH_PATH lpath;
171  lpath.m_Alias = "${KIPRJMOD}";
172  lpath.m_Pathvar = "${KIPRJMOD}";
173  lpath.m_Pathexp = m_curProjDir;
174  m_paths.push_back( lpath );
175  wxFileName fndummy;
176  wxUniChar psep = fndummy.GetPathSeparator();
177  std::list< wxString > epaths;
178 
179  if( GetKicadPaths( epaths ) )
180  {
181  for( const wxString& curr_path : epaths )
182  {
183  wxString pathVal = ExpandEnvVarSubstitutions( curr_path, m_project );
184 
185  if( pathVal.empty() )
186  {
187  lpath.m_Pathexp.clear();
188  }
189  else
190  {
191  fndummy.Assign( pathVal, "" );
192  fndummy.Normalize();
193  lpath.m_Pathexp = fndummy.GetFullPath();
194  }
195 
196  lpath.m_Alias = curr_path;
197  lpath.m_Pathvar = curr_path;
198 
199  if( !lpath.m_Pathexp.empty() && psep == *lpath.m_Pathexp.rbegin() )
200  lpath.m_Pathexp.erase( --lpath.m_Pathexp.end() );
201 
202  m_paths.push_back( lpath );
203  }
204  }
205 
206  if( !m_configDir.empty() )
207  readPathList();
208 
209  if( m_paths.empty() )
210  return false;
211 
212 #ifdef DEBUG
213  wxLogTrace( MASK_3D_RESOLVER, " * [3D model] search paths:\n" );
214  std::list< SEARCH_PATH >::const_iterator sPL = m_paths.begin();
215 
216  while( sPL != m_paths.end() )
217  {
218  wxLogTrace( MASK_3D_RESOLVER, " + %s : '%s'\n", (*sPL).m_Alias.GetData(),
219  (*sPL).m_Pathexp.GetData() );
220  ++sPL;
221  }
222 #endif
223 
224  return true;
225 }
std::list< SEARCH_PATH > m_paths
bool readPathList(void)
Read a list of path names from a configuration file.
const wxString ExpandEnvVarSubstitutions(const wxString &aString, PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition: common.cpp:267
wxString m_Alias
wxString m_Pathvar
bool GetKicadPaths(std::list< wxString > &paths) const
Return a list of path environment variables local to KiCad.
#define MASK_3D_RESOLVER
wxString m_Pathexp

References ExpandEnvVarSubstitutions(), GetKicadPaths(), SEARCH_PATH::m_Alias, m_configDir, m_curProjDir, SEARCH_PATH::m_Pathexp, m_paths, SEARCH_PATH::m_Pathvar, m_project, MASK_3D_RESOLVER, and readPathList().

Referenced by ResolvePath(), Set3DConfigDir(), SetProgramBase(), and ShortenPath().

◆ GetKicadPaths()

bool FILENAME_RESOLVER::GetKicadPaths ( std::list< wxString > &  paths) const

Return a list of path environment variables local to KiCad.

This list always includes KICAD6_3DMODEL_DIR even if it is not defined locally.

Definition at line 1049 of file filename_resolver.cpp.

1050 {
1051  paths.clear();
1052 
1053  if( !m_pgm )
1054  return false;
1055 
1056  bool hasKisys3D = false;
1057 
1058 
1059  // iterate over the list of internally defined ENV VARs
1060  // and add them to the paths list
1063 
1064  while( mS != mE )
1065  {
1066  // filter out URLs, template directories, and known system paths
1067  if( mS->first == wxString( "KICAD_PTEMPLATES" )
1068  || mS->first == wxString( "KICAD6_FOOTPRINT_DIR" ) )
1069  {
1070  ++mS;
1071  continue;
1072  }
1073 
1074  if( wxString::npos != mS->second.GetValue().find( wxString( "://" ) ) )
1075  {
1076  ++mS;
1077  continue;
1078  }
1079 
1080  wxString tmp( "${" );
1081  tmp.Append( mS->first );
1082  tmp.Append( "}" );
1083  paths.push_back( tmp );
1084 
1085  if( tmp == "${KICAD6_3DMODEL_DIR}" )
1086  hasKisys3D = true;
1087 
1088  ++mS;
1089  }
1090 
1091  if( !hasKisys3D )
1092  paths.emplace_back("${KICAD6_3DMODEL_DIR}" );
1093 
1094  return true;
1095 }
std::map< wxString, ENV_VAR_ITEM >::const_iterator ENV_VAR_MAP_CITER
virtual ENV_VAR_MAP & GetLocalEnvVariables() const
Definition: pgm_base.cpp:640

References PGM_BASE::GetLocalEnvVariables(), and m_pgm.

Referenced by createPathList().

◆ GetPaths()

const std::list< SEARCH_PATH > * FILENAME_RESOLVER::GetPaths ( ) const

Return a pointer to the internal path list; the items in:load.

The list can be used to set up the list of search paths available to a 3D file browser.

Returns
pointer to the internal path list.

Definition at line 827 of file filename_resolver.cpp.

828 {
829  return &m_paths;
830 }
std::list< SEARCH_PATH > m_paths

References m_paths.

Referenced by DIALOG_CONFIGURE_PATHS::TransferDataToWindow(), and DIALOG_SELECT_3DMODEL::updateDirChoiceList().

◆ GetProjectDir()

wxString FILENAME_RESOLVER::GetProjectDir ( ) const

Definition at line 140 of file filename_resolver.cpp.

141 {
142  return m_curProjDir;
143 }

References m_curProjDir.

◆ readPathList()

bool FILENAME_RESOLVER::readPathList ( void  )
private

Read a list of path names from a configuration file.

Returns
true if a file was found and contained at least one valid path.

Definition at line 486 of file filename_resolver.cpp.

487 {
488  if( m_configDir.empty() )
489  {
490  std::ostringstream ostr;
491  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
492  wxString errmsg = "3D configuration directory is unknown";
493  ostr << " * " << errmsg.ToUTF8();
494  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
495  return false;
496  }
497 
498  wxFileName cfgpath( m_configDir, RESOLVER_CONFIG );
499  cfgpath.Normalize();
500  wxString cfgname = cfgpath.GetFullPath();
501 
502  size_t nitems = m_paths.size();
503 
504  std::ifstream cfgFile;
505  std::string cfgLine;
506 
507  if( !wxFileName::Exists( cfgname ) )
508  {
509  std::ostringstream ostr;
510  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
511  wxString errmsg = "no 3D configuration file";
512  ostr << " * " << errmsg.ToUTF8() << " '";
513  ostr << cfgname.ToUTF8() << "'";
514  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
515  return false;
516  }
517 
518  cfgFile.open( cfgname.ToUTF8() );
519 
520  if( !cfgFile.is_open() )
521  {
522  std::ostringstream ostr;
523  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
524  wxString errmsg = "Could not open configuration file";
525  ostr << " * " << errmsg.ToUTF8() << " '" << cfgname.ToUTF8() << "'";
526  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
527  return false;
528  }
529 
530  int lineno = 0;
531  SEARCH_PATH al;
532  size_t idx;
533  int vnum = 0; // version number
534 
535  while( cfgFile.good() )
536  {
537  cfgLine.clear();
538  std::getline( cfgFile, cfgLine );
539  ++lineno;
540 
541  if( cfgLine.empty() )
542  {
543  if( cfgFile.eof() )
544  break;
545 
546  continue;
547  }
548 
549  if( 1 == lineno && cfgLine.compare( 0, 2, "#V" ) == 0 )
550  {
551  // extract the version number and parse accordingly
552  if( cfgLine.size() > 2 )
553  {
554  std::istringstream istr;
555  istr.str( cfgLine.substr( 2 ) );
556  istr >> vnum;
557  }
558 
559  continue;
560  }
561 
562  idx = 0;
563 
564  if( !getHollerith( cfgLine, idx, al.m_Alias ) )
565  continue;
566 
567  // Don't add KICAD6_3DMODEL_DIR, one of its legacy equivalents, or KIPRJMOD from a
568  // config file. They're system variables are are defined at runtime.
569  if( al.m_Alias == "${KICAD6_3DMODEL_DIR}"
570  || al.m_Alias == "${KIPRJMOD}" || al.m_Alias == "$(KIPRJMOD)"
571  || al.m_Alias == "${KISYS3DMOD}" || al.m_Alias == "$(KISYS3DMOD)" )
572  {
573  continue;
574  }
575 
576  if( !getHollerith( cfgLine, idx, al.m_Pathvar ) )
577  continue;
578 
579  if( !getHollerith( cfgLine, idx, al.m_Description ) )
580  continue;
581 
582  addPath( al );
583  }
584 
585  cfgFile.close();
586 
587  if( vnum < CFGFILE_VERSION )
589 
590  return( m_paths.size() != nitems );
591 }
#define CFGFILE_VERSION
std::list< SEARCH_PATH > m_paths
bool WritePathList(const wxString &aDir, const wxString &aFilename, bool aResolvePaths)
Write the current path list to a config file.
static bool getHollerith(const std::string &aString, size_t &aIndex, wxString &aResult)
wxString m_Alias
bool addPath(const SEARCH_PATH &aPath)
Check that a path is valid and adds it to the search list.
wxString m_Description
wxString m_Pathvar
#define RESOLVER_CONFIG
#define MASK_3D_RESOLVER

References addPath(), CFGFILE_VERSION, getHollerith(), SEARCH_PATH::m_Alias, m_configDir, SEARCH_PATH::m_Description, m_paths, SEARCH_PATH::m_Pathvar, MASK_3D_RESOLVER, RESOLVER_CONFIG, and WritePathList().

Referenced by createPathList().

◆ ResolvePath()

wxString FILENAME_RESOLVER::ResolvePath ( const wxString &  aFileName)

Determines 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 aFileName into aResolvedName if the URI is valid.

Definition at line 242 of file filename_resolver.cpp.

243 {
244  std::lock_guard<std::mutex> lock( mutex_resolver );
245 
246  if( aFileName.empty() )
247  return wxEmptyString;
248 
249  if( m_paths.empty() )
250  createPathList();
251 
252  // first attempt to use the name as specified:
253  wxString tname = aFileName;
254 
255  #ifdef _WIN32
256  // translate from KiCad's internal UNIX-like path to MSWin paths
257  tname.Replace( wxT( "/" ), wxT( "\\" ) );
258  #endif
259 
260  // Note: variable expansion must preferably be performed via a threadsafe wrapper for the
261  // getenv() system call. If we allow the wxFileName::Normalize() routine to perform expansion
262  // then we will have a race condition since wxWidgets does not assure a threadsafe wrapper
263  // for getenv().
264  tname = ExpandEnvVarSubstitutions( tname, m_project );
265 
266  wxFileName tmpFN( tname );
267 
268  // this case covers full paths, leading expanded vars, and paths relative to the current
269  // working directory (which is not necessarily the current project directory)
270  if( tmpFN.FileExists() )
271  {
272  tmpFN.Normalize();
273  tname = tmpFN.GetFullPath();
274 
275  // special case: if a path begins with ${ENV_VAR} but is not in the resolver's path list
276  // then add it.
277  if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) )
278  checkEnvVarPath( aFileName );
279 
280  return tname;
281  }
282 
283  // if a path begins with ${ENV_VAR}/$(ENV_VAR) and is not resolved then the file either does
284  // not exist or the ENV_VAR is not defined
285  if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) )
286  {
287  if( !( m_errflags & ERRFLG_ENVPATH ) )
288  {
290  wxString errmsg = "[3D File Resolver] No such path; ensure the environment var is defined";
291  errmsg.append( "\n" );
292  errmsg.append( tname );
293  errmsg.append( "\n" );
294  wxLogTrace( tracePathsAndFiles, errmsg );
295  }
296 
297  return wxEmptyString;
298  }
299 
300  // at this point aFileName is:
301  // a. an aliased shortened name or
302  // b. cannot be determined
303 
304  // check the path relative to the current project directory;
305  // NB: this is not necessarily the same as the current working directory, which has already
306  // been checked. This case accounts for partial paths which do not contain ${KIPRJMOD}.
307  // This check is performed before checking the path relative to ${KICAD6_3DMODEL_DIR} so that
308  // users can potentially override a model within ${KICAD6_3DMODEL_DIR}.
309  if( !m_paths.begin()->m_Pathexp.empty() && !tname.StartsWith( ":" ) )
310  {
311  tmpFN.Assign( m_paths.begin()->m_Pathexp, "" );
312  wxString fullPath = tmpFN.GetPathWithSep() + tname;
313 
314  fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
315 
316  if( wxFileName::FileExists( fullPath ) )
317  {
318  tmpFN.Assign( fullPath );
319  tmpFN.Normalize();
320  tname = tmpFN.GetFullPath();
321  return tname;
322  }
323 
324  }
325 
326  // check the partial path relative to ${KICAD6_3DMODEL_DIR} (legacy behavior)
327  if( !tname.StartsWith( ":" ) )
328  {
329  wxFileName fpath;
330  wxString fullPath( "${KICAD6_3DMODEL_DIR}" );
331  fullPath.Append( fpath.GetPathSeparator() );
332  fullPath.Append( tname );
333  fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
334  fpath.Assign( fullPath );
335 
336  if( fpath.Normalize() && fpath.FileExists() )
337  {
338  tname = fpath.GetFullPath();
339  return tname;
340  }
341 
342  }
343 
344  // at this point the filename must contain an alias or else it is invalid
345  wxString alias; // the alias portion of the short filename
346  wxString relpath; // the path relative to the alias
347 
348  if( !SplitAlias( tname, alias, relpath ) )
349  {
350  if( !( m_errflags & ERRFLG_RELPATH ) )
351  {
352  // this can happen if the file was intended to be relative to ${KICAD6_3DMODEL_DIR}
353  // but ${KICAD6_3DMODEL_DIR} is not set or is incorrect.
355  wxString errmsg = "[3D File Resolver] No such path";
356  errmsg.append( "\n" );
357  errmsg.append( tname );
358  errmsg.append( "\n" );
359  wxLogTrace( tracePathsAndFiles, errmsg );
360  }
361 
362  return wxEmptyString;
363  }
364 
365  for( const SEARCH_PATH& path : m_paths )
366  {
367  // ${ENV_VAR} paths have already been checked; skip them
368  if( path.m_Alias.StartsWith( "${" ) || path.m_Alias.StartsWith( "$(" ) )
369  continue;
370 
371  if( path.m_Alias == alias && !path.m_Pathexp.empty() )
372  {
373  wxFileName fpath( wxFileName::DirName( path.m_Pathexp ) );
374  wxString fullPath = fpath.GetPathWithSep() + relpath;
375 
376  fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
377 
378  if( wxFileName::FileExists( fullPath ) )
379  {
380  tname = fullPath;
381 
382  wxFileName tmp( fullPath );
383 
384  if( tmp.Normalize() )
385  tname = tmp.GetFullPath();
386 
387  return tname;
388  }
389  }
390  }
391 
392  if( !( m_errflags & ERRFLG_ALIAS ) )
393  {
395  wxString errmsg = "[3D File Resolver] No such path; ensure the path alias is defined";
396  errmsg.append( "\n" );
397  errmsg.append( tname.substr( 1 ) );
398  errmsg.append( "\n" );
399  wxLogTrace( tracePathsAndFiles, errmsg );
400  }
401 
402  return wxEmptyString;
403 }
std::list< SEARCH_PATH > m_paths
#define ERRFLG_ENVPATH
bool SplitAlias(const wxString &aFileName, wxString &anAlias, wxString &aRelPath) const
Return true if the given name contains an alias and populates the string anAlias with the alias and a...
const wxChar *const tracePathsAndFiles
Flag to enable path and file name debug output.
bool createPathList(void)
Build the path list using available information such as KICAD6_3DMODEL_DIR and the 3d_path_list confi...
const wxString ExpandEnvVarSubstitutions(const wxString &aString, PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition: common.cpp:267
#define ERRFLG_ALIAS
#define ERRFLG_RELPATH
static std::mutex mutex_resolver
void checkEnvVarPath(const wxString &aPath)
Check the ${ENV_VAR} component of a path and adds it to the resolver's path list if it is not yet in ...

References checkEnvVarPath(), createPathList(), ERRFLG_ALIAS, ERRFLG_ENVPATH, ERRFLG_RELPATH, ExpandEnvVarSubstitutions(), m_errflags, m_paths, m_project, mutex_resolver, path, SplitAlias(), and tracePathsAndFiles.

Referenced by EXPORTER_PCB_VRML::ExportVrmlFootprint(), idf_export_footprint(), S3D_CACHE::load(), and PANEL_FP_PROPERTIES_3D_MODEL::validateModelExists().

◆ Set3DConfigDir()

bool FILENAME_RESOLVER::Set3DConfigDir ( const wxString &  aConfigDir)

Set the user's configuration directory for 3D models.

Parameters
aConfigDir
Returns
true if the call succeeds (directory exists).

Definition at line 62 of file filename_resolver.cpp.

63 {
64  if( aConfigDir.empty() )
65  return false;
66 
67  wxFileName cfgdir( ExpandEnvVarSubstitutions( aConfigDir, m_project ), "" );
68 
69  cfgdir.Normalize();
70 
71  if( !cfgdir.DirExists() )
72  return false;
73 
74  m_configDir = cfgdir.GetPath();
76 
77  return true;
78 }
bool createPathList(void)
Build the path list using available information such as KICAD6_3DMODEL_DIR and the 3d_path_list confi...
const wxString ExpandEnvVarSubstitutions(const wxString &aString, PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition: common.cpp:267

References createPathList(), ExpandEnvVarSubstitutions(), m_configDir, and m_project.

Referenced by S3D_CACHE::Set3DConfigDir().

◆ SetProgramBase()

void FILENAME_RESOLVER::SetProgramBase ( PGM_BASE aBase)

Set a pointer to the application's PGM_BASE instance used to extract the local env vars.

Definition at line 146 of file filename_resolver.cpp.

147 {
148  m_pgm = aBase;
149 
150  if( !m_pgm || m_paths.empty() )
151  return;
152 
153  // recreate the path list
154  m_paths.clear();
155  createPathList();
156 }
std::list< SEARCH_PATH > m_paths
bool createPathList(void)
Build the path list using available information such as KICAD6_3DMODEL_DIR and the 3d_path_list confi...

References createPathList(), m_paths, and m_pgm.

Referenced by S3D_CACHE::SetProgramBase().

◆ SetProject()

bool FILENAME_RESOLVER::SetProject ( PROJECT aProject,
bool *  flgChanged = nullptr 
)

Set the current KiCad project directory as the first entry in the model path list.

Parameters
[in]aProjDircurrent project directory
[out]flgChangedoptional, set to true if directory was changed
Return values
truesuccess
falsefailure

Definition at line 81 of file filename_resolver.cpp.

82 {
83  m_project = aProject;
84 
85  if( !aProject )
86  return false;
87 
88  wxFileName projdir( ExpandEnvVarSubstitutions( aProject->GetProjectPath(), aProject ), "" );
89 
90  projdir.Normalize();
91 
92  if( !projdir.DirExists() )
93  return false;
94 
95  m_curProjDir = projdir.GetPath();
96 
97  if( flgChanged )
98  *flgChanged = false;
99 
100  if( m_paths.empty() )
101  {
102  SEARCH_PATH al;
103  al.m_Alias = "${KIPRJMOD}";
104  al.m_Pathvar = "${KIPRJMOD}";
105  al.m_Pathexp = m_curProjDir;
106  m_paths.push_back( al );
107 
108  if( flgChanged )
109  *flgChanged = true;
110  }
111  else
112  {
113  if( m_paths.front().m_Pathexp != m_curProjDir )
114  {
115  m_paths.front().m_Pathexp = m_curProjDir;
116 
117  if( flgChanged )
118  *flgChanged = true;
119  }
120  else
121  {
122  return true;
123  }
124  }
125 
126 #ifdef DEBUG
127  {
128  std::ostringstream ostr;
129  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
130  ostr << " * [INFO] changed project dir to ";
131  ostr << m_paths.front().m_Pathexp.ToUTF8();
132  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
133  }
134 #endif
135 
136  return true;
137 }
std::list< SEARCH_PATH > m_paths
const wxString ExpandEnvVarSubstitutions(const wxString &aString, PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition: common.cpp:267
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition: project.cpp:122
wxString m_Alias
wxString m_Pathvar
#define MASK_3D_RESOLVER
wxString m_Pathexp

References ExpandEnvVarSubstitutions(), PROJECT::GetProjectPath(), SEARCH_PATH::m_Alias, m_curProjDir, SEARCH_PATH::m_Pathexp, m_paths, SEARCH_PATH::m_Pathvar, m_project, and MASK_3D_RESOLVER.

Referenced by S3D_CACHE::SetProject().

◆ ShortenPath()

wxString FILENAME_RESOLVER::ShortenPath ( const wxString &  aFullPathName)

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.

Parameters
aFullPathNameis an absolute path to shorten.
Returns
the shortened path or aFullPathName.

Definition at line 736 of file filename_resolver.cpp.

737 {
738  wxString fname = aFullPathName;
739 
740  if( m_paths.empty() )
741  createPathList();
742 
743  std::lock_guard<std::mutex> lock( mutex_resolver );
744 
745  std::list< SEARCH_PATH >::const_iterator sL = m_paths.begin();
746  size_t idx;
747 
748  while( sL != m_paths.end() )
749  {
750  // undefined paths do not participate in the
751  // file name shortening procedure
752  if( sL->m_Pathexp.empty() )
753  {
754  ++sL;
755  continue;
756  }
757 
758  wxFileName fpath;
759 
760  // in the case of aliases, ensure that we use the most recent definition
761  if( sL->m_Alias.StartsWith( "${" ) || sL->m_Alias.StartsWith( "$(" ) )
762  {
763  wxString tpath = ExpandEnvVarSubstitutions( sL->m_Alias, m_project );
764 
765  if( tpath.empty() )
766  {
767  ++sL;
768  continue;
769  }
770 
771  fpath.Assign( tpath, wxT( "" ) );
772  }
773  else
774  {
775  fpath.Assign( sL->m_Pathexp, wxT( "" ) );
776  }
777 
778  wxString fps = fpath.GetPathWithSep();
779  wxString tname;
780 
781  idx = fname.find( fps );
782 
783  if( idx == 0 )
784  {
785  fname = fname.substr( fps.size() );
786 
787  #ifdef _WIN32
788  // ensure only the '/' separator is used in the internal name
789  fname.Replace( wxT( "\\" ), wxT( "/" ) );
790  #endif
791 
792  if( sL->m_Alias.StartsWith( "${" ) || sL->m_Alias.StartsWith( "$(" ) )
793  {
794  // old style ENV_VAR
795  tname = sL->m_Alias;
796  tname.Append( "/" );
797  tname.append( fname );
798  }
799  else
800  {
801  // new style alias
802  tname = ":";
803  tname.append( sL->m_Alias );
804  tname.append( ":" );
805  tname.append( fname );
806  }
807 
808  return tname;
809  }
810 
811  ++sL;
812  }
813 
814 #ifdef _WIN32
815  // it is strange to convert an MSWin full path to use the
816  // UNIX separator but this is done for consistency and can
817  // be helpful even when transferring project files from
818  // MSWin to *NIX.
819  fname.Replace( wxT( "\\" ), wxT( "/" ) );
820 #endif
821 
822  return fname;
823 }
std::list< SEARCH_PATH > m_paths
bool createPathList(void)
Build the path list using available information such as KICAD6_3DMODEL_DIR and the 3d_path_list confi...
const wxString ExpandEnvVarSubstitutions(const wxString &aString, PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition: common.cpp:267
static std::mutex mutex_resolver

References createPathList(), ExpandEnvVarSubstitutions(), m_paths, m_project, and mutex_resolver.

Referenced by DIALOG_SELECT_3DMODEL::TransferDataFromWindow().

◆ SplitAlias()

bool FILENAME_RESOLVER::SplitAlias ( const wxString &  aFileName,
wxString &  anAlias,
wxString &  aRelPath 
) const

Return true if the given name contains an alias and populates the string anAlias with the alias and aRelPath with the relative path.

Definition at line 833 of file filename_resolver.cpp.

835 {
836  anAlias.clear();
837  aRelPath.clear();
838 
839  size_t searchStart = 0;
840 
841  if( aFileName.StartsWith( wxT( ":" ) ) )
842  searchStart = 1;
843 
844  size_t tagpos = aFileName.find( wxT( ":" ), searchStart );
845 
846  if( tagpos == wxString::npos || tagpos == searchStart )
847  return false;
848 
849  if( tagpos + 1 >= aFileName.length() )
850  return false;
851 
852  anAlias = aFileName.substr( searchStart, tagpos - searchStart );
853  aRelPath = aFileName.substr( tagpos + 1 );
854 
855  return true;
856 }

Referenced by PANEL_FP_PROPERTIES_3D_MODEL::OnAdd3DModel(), PANEL_FP_PROPERTIES_3D_MODEL::ReloadModelsFromFootprint(), and ResolvePath().

◆ UpdatePathList()

bool FILENAME_RESOLVER::UpdatePathList ( const std::vector< SEARCH_PATH > &  aPathList)

Clear the current path list and substitutes the given path list and update the path configuration file on success.

Definition at line 228 of file filename_resolver.cpp.

229 {
230  wxUniChar envMarker( '$' );
231 
232  while( !m_paths.empty() && envMarker != *m_paths.back().m_Alias.rbegin() )
233  m_paths.pop_back();
234 
235  for( const SEARCH_PATH& path : aPathList )
236  addPath( path );
237 
238  return WritePathList( m_configDir, RESOLVER_CONFIG, false );
239 }
std::list< SEARCH_PATH > m_paths
bool WritePathList(const wxString &aDir, const wxString &aFilename, bool aResolvePaths)
Write the current path list to a config file.
bool addPath(const SEARCH_PATH &aPath)
Check that a path is valid and adds it to the search list.
#define RESOLVER_CONFIG

References addPath(), m_configDir, m_paths, path, RESOLVER_CONFIG, and WritePathList().

Referenced by DIALOG_CONFIGURE_PATHS::TransferDataFromWindow().

◆ ValidateFileName()

bool FILENAME_RESOLVER::ValidateFileName ( const wxString &  aFileName,
bool &  hasAlias 
) const

Returns true if the given path is a valid aliased relative path.

If the path contains an alias then hasAlias is set true.

Definition at line 954 of file filename_resolver.cpp.

955 {
956  // Rules:
957  // 1. The generic form of an aliased 3D relative path is:
958  // ALIAS:relative/path
959  // 2. ALIAS is a UTF string excluding wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" )
960  // 3. The relative path must be a valid relative path for the platform
961  hasAlias = false;
962 
963  if( aFileName.empty() )
964  return false;
965 
966  wxString filename = aFileName;
967  wxString lpath;
968  size_t aliasStart = aFileName.StartsWith( ':' ) ? 1 : 0;
969  size_t aliasEnd = aFileName.find( ':', aliasStart );
970 
971  // ensure that the file separators suit the current platform
972 #ifdef __WINDOWS__
973  filename.Replace( wxT( "/" ), wxT( "\\" ) );
974 
975  // if we see the :\ pattern then it must be a drive designator
976  if( aliasEnd != wxString::npos )
977  {
978  size_t pos1 = filename.find( wxT( ":\\" ) );
979 
980  if( pos1 != wxString::npos && ( pos1 != aliasEnd || pos1 != 1 ) )
981  return false;
982 
983  // if we have a drive designator then we have no alias
984  if( pos1 != wxString::npos )
985  aliasEnd = wxString::npos;
986  }
987 #else
988  filename.Replace( wxT( "\\" ), wxT( "/" ) );
989 #endif
990 
991  // names may not end with ':'
992  if( aliasEnd == aFileName.length() -1 )
993  return false;
994 
995  if( aliasEnd != wxString::npos )
996  {
997  // ensure the alias component is not empty
998  if( aliasEnd == aliasStart )
999  return false;
1000 
1001  lpath = filename.substr( aliasStart, aliasEnd );
1002 
1003  // check the alias for restricted characters
1004  if( wxString::npos != lpath.find_first_of( wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" ) ) )
1005  return false;
1006 
1007  hasAlias = true;
1008  lpath = aFileName.substr( aliasEnd + 1 );
1009  }
1010  else
1011  {
1012  lpath = aFileName;
1013 
1014  // in the case of ${ENV_VAR}|$(ENV_VAR)/path, strip the
1015  // environment string before testing
1016  aliasEnd = wxString::npos;
1017 
1018  if( aFileName.StartsWith( "${" ) )
1019  aliasEnd = aFileName.find( '}' );
1020  else if( aFileName.StartsWith( "$(" ) )
1021  aliasEnd = aFileName.find( ')' );
1022 
1023  if( aliasEnd != wxString::npos )
1024  lpath = aFileName.substr( aliasEnd + 1 );
1025 
1026  }
1027 
1028  // Test for forbidden chars in filenames. Should be wxFileName::GetForbiddenChars()
1029  // On MSW, the list returned by wxFileName::GetForbiddenChars() contains separators
1030  // '\'and '/' used here because lpath can be a full path.
1031  // So remove separators
1032  wxString lpath_no_sep = lpath;
1033 #ifdef __WINDOWS__
1034  lpath_no_sep.Replace( "/", " " );
1035  lpath_no_sep.Replace( "\\", " " );
1036 
1037  // A disk identifier is allowed, and therefore remove its separator
1038  if( lpath_no_sep.Length() > 1 && lpath_no_sep[1] == ':' )
1039  lpath_no_sep[1] = ' ';
1040 #endif
1041 
1042  if( wxString::npos != lpath_no_sep.find_first_of( wxFileName::GetForbiddenChars() ) )
1043  return false;
1044 
1045  return true;
1046 }

Referenced by PANEL_FP_PROPERTIES_3D_MODEL::On3DModelCellChanged(), and PANEL_FP_PROPERTIES_3D_MODEL::validateModelExists().

◆ WritePathList()

bool FILENAME_RESOLVER::WritePathList ( const wxString &  aDir,
const wxString &  aFilename,
bool  aResolvePaths 
)

Write the current path list to a config file.

Parameters
aResolvePathsindicates whether env vars should also be written out or not

Definition at line 594 of file filename_resolver.cpp.

596 {
597  if( aDir.empty() )
598  {
599  std::ostringstream ostr;
600  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
601  wxString errmsg = _( "3D configuration directory is unknown" );
602  ostr << " * " << errmsg.ToUTF8();
603  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
604  wxMessageBox( errmsg, _( "Write 3D search path list" ) );
605 
606  return false;
607  }
608 
609  std::list<SEARCH_PATH>::const_iterator sPL = m_paths.begin();
610 
611  if( !aResolvePaths )
612  {
613  // skip all ${ENV_VAR} alias names
614 
615  while( sPL != m_paths.end()
616  && ( sPL->m_Alias.StartsWith( "${" ) || sPL->m_Alias.StartsWith( "$(" ) ) )
617  {
618  ++sPL;
619  }
620  }
621 
622  wxFileName cfgpath( aDir, aFilename );
623  wxString cfgname = cfgpath.GetFullPath();
624  std::ofstream cfgFile;
625 
626  cfgFile.open( cfgname.ToUTF8(), std::ios_base::trunc );
627 
628  if( !cfgFile.is_open() )
629  {
630  std::ostringstream ostr;
631  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
632  wxString errmsg = _( "Could not open configuration file" );
633  ostr << " * " << errmsg.ToUTF8() << " '" << cfgname.ToUTF8() << "'";
634  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
635  wxMessageBox( errmsg, _( "Write 3D search path list" ) );
636 
637  return false;
638  }
639 
640  cfgFile << "#V" << CFGFILE_VERSION << "\n";
641  std::string tstr;
642 
643  while( sPL != m_paths.end() )
644  {
645  tstr = sPL->m_Alias.ToUTF8();
646  cfgFile << "\"" << tstr.size() << ":" << tstr << "\",";
647 
648  if( aResolvePaths )
649  tstr = ExpandEnvVarSubstitutions( sPL->m_Pathvar, m_project ).ToUTF8();
650  else
651  tstr = sPL->m_Pathvar.ToUTF8();
652 
653  cfgFile << "\"" << tstr.size() << ":" << tstr << "\",";
654 
655  tstr = sPL->m_Description.ToUTF8();
656  cfgFile << "\"" << tstr.size() << ":" << tstr << "\"\n";
657 
658  ++sPL;
659  }
660 
661  bool bad = cfgFile.bad();
662  cfgFile.close();
663 
664  if( bad )
665  {
666  wxMessageBox( _( "Problems writing configuration file" ),
667  _( "Write 3D search path list" ) );
668 
669  return false;
670  }
671 
672  return true;
673 }
#define CFGFILE_VERSION
std::list< SEARCH_PATH > m_paths
const wxString ExpandEnvVarSubstitutions(const wxString &aString, PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition: common.cpp:267
#define _(s)
#define MASK_3D_RESOLVER

References _, CFGFILE_VERSION, ExpandEnvVarSubstitutions(), m_paths, m_project, and MASK_3D_RESOLVER.

Referenced by DIALOG_EXPORT_STEP::onExportButton(), readPathList(), and UpdatePathList().

Member Data Documentation

◆ m_configDir

wxString FILENAME_RESOLVER::m_configDir
private

Definition at line 177 of file filename_resolver.h.

Referenced by createPathList(), readPathList(), Set3DConfigDir(), and UpdatePathList().

◆ m_curProjDir

wxString FILENAME_RESOLVER::m_curProjDir
private

Definition at line 182 of file filename_resolver.h.

Referenced by createPathList(), GetProjectDir(), and SetProject().

◆ m_errflags

int FILENAME_RESOLVER::m_errflags
private

Definition at line 179 of file filename_resolver.h.

Referenced by FILENAME_RESOLVER(), and ResolvePath().

◆ m_paths

◆ m_pgm

PGM_BASE* FILENAME_RESOLVER::m_pgm
private

Definition at line 180 of file filename_resolver.h.

Referenced by GetKicadPaths(), and SetProgramBase().

◆ m_project

PROJECT* FILENAME_RESOLVER::m_project
private

The documentation for this class was generated from the following files: