← Back to team overview

kicad-developers team mailing list archive

Re: 3D filename resolution

 

Attached is an updated patch made against r6854.  A patch to fix Bug
#1585714 prevented the previous patch from applying cleanly.

- Cirilo


On Thu, Apr 21, 2016 at 5:01 AM, Wayne Stambaugh <stambaughw@xxxxxxxxx>
wrote:

> The environment variables defined externally to kicad should take
> precedence.
>
> On 4/19/2016 8:19 PM, Cirilo Bernardo wrote:
> > Hi folks,
> >
> >  I'm cleaning up the new 3D filename resolution code, removing some
> > redundant code and fixing corner-case bugs which break with legacy
> > behavior. I'd just like some input on one item: if a ENV_VAR path has
> > been defined via "Preferences->Configure Paths" and that ENV_VAR
> > already exists on the system, which one has preference? For most
> > UNIX applications the behavior is to use the ENV_VAR defined within
> > the shell rather than the locally defined values.
> >
> > - Cirilo
> >
> >
> >
> > _______________________________________________
> > Mailing list: https://launchpad.net/~kicad-developers
> > Post to     : kicad-developers@xxxxxxxxxxxxxxxxxxx
> > Unsubscribe : https://launchpad.net/~kicad-developers
> > More help   : https://help.launchpad.net/ListHelp
> >
>
> _______________________________________________
> Mailing list: https://launchpad.net/~kicad-developers
> Post to     : kicad-developers@xxxxxxxxxxxxxxxxxxx
> Unsubscribe : https://launchpad.net/~kicad-developers
> More help   : https://help.launchpad.net/ListHelp
>
=== modified file '3d-viewer/3d_cache/3d_cache.cpp'
--- 3d-viewer/3d_cache/3d_cache.cpp	2016-04-21 07:19:08 +0000
+++ 3d-viewer/3d_cache/3d_cache.cpp	2016-05-30 08:45:37 +0000
@@ -1,7 +1,7 @@
 /*
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
- * Copyright (C) 2015 Cirilo Bernardo <cirilo.bernardo@xxxxxxxxx>
+ * Copyright (C) 2015-2016 Cirilo Bernardo <cirilo.bernardo@xxxxxxxxx>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -32,8 +32,6 @@
 #include <wx/datetime.h>
 #include <wx/filename.h>
 #include <wx/log.h>
-#include <wx/thread.h>
-#include <wx/utils.h>
 #include <wx/stdpaths.h>
 
 #include <boost/uuid/sha1.hpp>
@@ -43,13 +41,13 @@
 
 #include "3d_cache.h"
 #include "3d_info.h"
+#include "common.h"
 #include "sg/scenegraph.h"
 #include "3d_filename_resolver.h"
 #include "3d_plugin_manager.h"
 #include "plugins/3dapi/ifsg_api.h"
 
 
-#define CACHE_CONFIG_NAME wxT( "cache.cfg" )
 #define MASK_3D_CACHE "3D_CACHE"
 
 static wxCriticalSection lock3D_cache;
@@ -225,19 +223,23 @@
     if( mi != m_CacheMap.end() )
     {
         wxFileName fname( full3Dpath );
-        wxDateTime fmdate = fname.GetModificationTime();
         bool reload = false;
 
-        if( fmdate != mi->second->modTime )
+        if( fname.FileExists() )
         {
-            unsigned char hashSum[20];
-            getSHA1( full3Dpath, hashSum );
-            mi->second->modTime = fmdate;
+            wxDateTime fmdate = fname.GetModificationTime();
 
-            if( !isSHA1Same( hashSum, mi->second->sha1sum ) )
+            if( fmdate != mi->second->modTime )
             {
-                mi->second->SetSHA1( hashSum );
-                reload = true;
+                unsigned char hashSum[20];
+                getSHA1( full3Dpath, hashSum );
+                mi->second->modTime = fmdate;
+
+                if( !isSHA1Same( hashSum, mi->second->sha1sum ) )
+                {
+                    mi->second->SetSHA1( hashSum );
+                    reload = true;
+                }
             }
         }
 
@@ -538,7 +540,13 @@
     if( !m_ConfigDir.empty() )
         return false;
 
-    wxFileName cfgdir( aConfigDir, wxT( "" ) );
+    wxFileName cfgdir;
+
+    if( aConfigDir.StartsWith( "${" ) || aConfigDir.StartsWith( "$(" ) )
+        cfgdir.Assign( ExpandEnvVarSubstitutions( aConfigDir ), "" );
+    else
+        cfgdir.Assign( aConfigDir, "" );
+
     cfgdir.Normalize();
 
     if( !cfgdir.DirExists() )
@@ -620,9 +628,9 @@
     cfgpath.AssignDir( wxStandardPaths::Get().GetUserConfigDir() );
 
 #if !defined( __WINDOWS__ ) && !defined( __WXMAC__ )
-    wxString envstr;
+    wxString envstr = ExpandEnvVarSubstitutions( "XDG_CONFIG_HOME" );
 
-    if( !wxGetEnv( wxT( "XDG_CONFIG_HOME" ), &envstr ) || envstr.IsEmpty() )
+    if( envstr.IsEmpty() )
     {
         // XDG_CONFIG_HOME is not set, so use the fallback
         cfgpath.AppendDir( wxT( ".config" ) );

=== modified file '3d-viewer/3d_cache/3d_cache_wrapper.cpp'
--- 3d-viewer/3d_cache/3d_cache_wrapper.cpp	2016-04-17 22:35:32 +0000
+++ 3d-viewer/3d_cache/3d_cache_wrapper.cpp	2016-05-30 08:45:37 +0000
@@ -1,7 +1,7 @@
 /*
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
- * Copyright (C) 2015 Cirilo Bernardo <cirilo.bernardo@xxxxxxxxx>
+ * Copyright (C) 2015-2016 Cirilo Bernardo <cirilo.bernardo@xxxxxxxxx>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -23,7 +23,6 @@
 
 
 #include <common.h>
-#include <wx/thread.h>
 #include <pgm_base.h>
 #include "3d_cache_wrapper.h"
 

=== modified file '3d-viewer/3d_cache/3d_filename_resolver.cpp'
--- 3d-viewer/3d_cache/3d_filename_resolver.cpp	2016-05-30 08:22:19 +0000
+++ 3d-viewer/3d_cache/3d_filename_resolver.cpp	2016-05-30 08:47:03 +0000
@@ -1,7 +1,7 @@
 /*
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
- * Copyright (C) 2015 Cirilo Bernardo <cirilo.bernardo@xxxxxxxxx>
+ * Copyright (C) 2015-2016 Cirilo Bernardo <cirilo.bernardo@xxxxxxxxx>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -21,19 +21,16 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
  */
 
-#include <iostream>
 #include <sstream>
-#include <cstdlib>
 #include <cstring>
 #include <fstream>
 #include <sstream>
 #include <wx/filename.h>
 #include <wx/log.h>
-#include <wx/thread.h>
-#include <wx/utils.h>
 #include <wx/msgdlg.h>
 #include <pgm_base.h>
 
+#include "common.h"
 #include "3d_filename_resolver.h"
 
 // configuration file version
@@ -64,7 +61,13 @@
     if( aConfigDir.empty() )
         return false;
 
-    wxFileName cfgdir( aConfigDir, wxT( "" ) );
+    wxFileName cfgdir;
+
+    if( aConfigDir.StartsWith( "${" ) || aConfigDir.StartsWith( "$(" ) )
+        cfgdir.Assign( ExpandEnvVarSubstitutions( aConfigDir ), "" );
+    else
+        cfgdir.Assign( aConfigDir, "" );
+
     cfgdir.Normalize();
 
     if( false == cfgdir.DirExists() )
@@ -82,7 +85,13 @@
     if( aProjDir.empty() )
         return false;
 
-    wxFileName projdir( aProjDir, wxT( "" ) );
+    wxFileName projdir;
+
+    if( aProjDir.StartsWith( "${" ) || aProjDir.StartsWith( "$(" ) )
+        projdir.Assign( ExpandEnvVarSubstitutions( aProjDir ), "" );
+    else
+        projdir.Assign( aProjDir, "" );
+
     projdir.Normalize();
 
     if( false == projdir.DirExists() )
@@ -96,10 +105,9 @@
     if( m_Paths.empty() )
     {
         S3D_ALIAS al;
-        al.m_alias = _( "(DEFAULT)" );
-        al.m_pathvar = _( "${PROJDIR}" );
+        al.m_alias = "${KIPRJMOD}";
+        al.m_pathvar = "${KIPRJMOD}";
         al.m_pathexp = m_curProjDir;
-        al.m_description = _( "Current project directory" );
         m_Paths.push_back( al );
         m_NameMap.clear();
 
@@ -177,6 +185,7 @@
     m_Paths.push_back( lpath );
     wxFileName fndummy;
     wxUniChar psep = fndummy.GetPathSeparator();
+    bool hasKISYS3DMOD = false;
 
     // iterate over the list of internally defined ENV VARs
     // and add existing paths to the resolver
@@ -202,21 +211,29 @@
                 continue;
             }
 
-            fndummy.Assign( mS->second.GetValue(), "" );
-
-            if( !fndummy.DirExists() )
-            {
-                ++mS;
-                continue;
-            }
-
+            // ensure system ENV VARs supercede internally defined vars
             wxString tmp( "${" );
             tmp.Append( mS->first );
             tmp.Append( "}" );
+            wxString pathVal = ExpandEnvVarSubstitutions( tmp );
+
+            if( pathVal.empty() )
+            {
+                pathVal = mS->second.GetValue();
+
+                if( pathVal.StartsWith( "${" ) || pathVal.StartsWith( "$(" ) )
+                    pathVal = ExpandEnvVarSubstitutions( pathVal );
+            }
+
+            fndummy.Assign( pathVal, "" );
+            fndummy.Normalize();
+
+            if( tmp == "${KISYS3DMOD}" )
+                hasKISYS3DMOD = true;
 
             lpath.m_alias =  tmp;
             lpath.m_pathvar = tmp;
-            lpath.m_pathexp = mS->second.GetValue();
+            lpath.m_pathexp = fndummy.GetFullPath();
 
             if( !lpath.m_pathexp.empty() && psep == *lpath.m_pathexp.rbegin() )
                 lpath.m_pathexp.erase( --lpath.m_pathexp.end() );
@@ -227,6 +244,26 @@
         }
     }
 
+    // special case: if KISYSMOD is not internally defined but is defined by
+    // the system, then create an entry here
+    wxString envar = ExpandEnvVarSubstitutions( "${KISYS3DMOD}" );
+
+    if( !hasKISYS3DMOD && !envar.empty() )
+    {
+        lpath.m_alias = "${KISYS3DMOD}";
+        lpath.m_pathvar = "${KISYS3DMOD}";
+        fndummy.Assign( envar, "" );
+        fndummy.Normalize();
+        lpath.m_pathexp = fndummy.GetFullPath();
+
+        if( !lpath.m_pathexp.empty() && psep == *lpath.m_pathexp.rbegin() )
+            lpath.m_pathexp.erase( --lpath.m_pathexp.end() );
+
+        if( !lpath.m_pathexp.empty() )
+            m_Paths.push_back( lpath );
+
+    }
+
     if( !m_ConfigDir.empty() )
         readPathList();
 
@@ -268,6 +305,7 @@
 wxString S3D_FILENAME_RESOLVER::ResolvePath( const wxString& aFileName )
 {
     wxCriticalSectionLocker lock( lock3D_resolver );
+
     if( aFileName.empty() )
         return wxEmptyString;
 
@@ -289,43 +327,49 @@
     tname.Replace( wxT( "/" ), wxT( "\\" ) );
     #endif
 
-    // this case covers full paths and paths relative to
-    // the current working directory (which is not necessarily
+    // Note: variable expansion must be performed using 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 = ExpandEnvVarSubstitutions( tname );
+
+    wxFileName tmpFN( tname );
+
+    // in the case of absolute filenames we don't store a map item
+    if( !aFileName.StartsWith( "${" ) && !aFileName.StartsWith( "$(" )
+        && !aFileName.StartsWith( ":" ) && tmpFN.IsAbsolute() )
+    {
+        tmpFN.Normalize();
+
+        if( tmpFN.FileExists() )
+            return tmpFN.GetFullPath();
+
+        return wxEmptyString;
+    }
+
+    // this case covers full paths, leading expanded vars, and paths
+    // relative to the current working directory (which is not necessarily
     // the current project directory)
-    if( wxFileName::FileExists( tname ) )
+    if( tmpFN.FileExists() )
     {
-        wxFileName tmp( tname );
-
-        if( tmp.Normalize() )
-            tname = tmp.GetFullPath();
-
+        tmpFN.Normalize();
+        tname = tmpFN.GetFullPath();
         m_NameMap.insert( std::pair< wxString, wxString > ( 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( "${" ) || aFileName.StartsWith( "$(" ) )
+            checkEnvVarPath( aFileName );
+
         return tname;
     }
 
-    // at this point aFileName:
-    // a. is a legacy ${} shortened name
-    // b. an aliased shortened name
-    // c. cannot be determined
-
-    if( aFileName.StartsWith( wxT( "${" ) ) )
+    // 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( "${" ) || aFileName.StartsWith( "$(" ) )
     {
-        wxFileName tmp( aFileName );
-
-        if( tmp.Normalize() )
-        {
-            tname = tmp.GetFullPath();
-            m_NameMap.insert( std::pair< wxString, wxString > ( aFileName, tname ) );
-
-            return tname;
-        }
-        else if( resolveVirtualEnv( aFileName, tname ) )
-        {
-            m_NameMap.insert( std::pair< wxString, wxString > ( aFileName, tname ) );
-            return tname;
-        }
-
         if( !( m_errflags & ERRFLG_ENVPATH ) )
         {
             m_errflags |= ERRFLG_ENVPATH;
@@ -338,6 +382,10 @@
         return wxEmptyString;
     }
 
+    // at this point aFileName is:
+    // a. an aliased shortened name or
+    // b. cannot be determined
+
     std::list< S3D_ALIAS >::const_iterator sPL = m_Paths.begin();
     std::list< S3D_ALIAS >::const_iterator ePL = m_Paths.end();
 
@@ -345,49 +393,52 @@
     // note: 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}.
-    if( !sPL->m_pathexp.empty() )
+    // This check is performed before checking the path relative to
+    // ${KISYS3DMOD} so that users can potentially override a model
+    // within ${KISYS3DMOD}
+    if( !sPL->m_pathexp.empty() && !tname.StartsWith( ":" ) )
     {
-        wxFileName fpath( wxFileName::DirName( sPL->m_pathexp ) );
-        wxString fullPath = fpath.GetPathWithSep() + tname;
+        tmpFN.Assign( sPL->m_pathexp, "" );
+        wxString fullPath = tmpFN.GetPathWithSep() + tname;
+
+        if( fullPath.StartsWith( "${" ) || fullPath.StartsWith( "$(" ) )
+            fullPath = ExpandEnvVarSubstitutions( fullPath );
 
         if( wxFileName::FileExists( fullPath ) )
         {
-            wxFileName tmp( fullPath );
-
-            if( tmp.Normalize() )
-                tname = tmp.GetFullPath();
-
+            tmpFN.Assign( fullPath );
+            tmpFN.Normalize();
+            tname = tmpFN.GetFullPath();
             m_NameMap.insert( std::pair< wxString, wxString > ( aFileName, tname ) );
 
             return tname;
         }
+
     }
 
-    // ${ENV_VAR} paths have already been checked; skip all but
-    // ${KISYS3DMOD}, since legacy behavior was to check if paths
-    // were relative to ${KISYS3DMOD}
-    while( sPL != ePL && sPL->m_alias.StartsWith( "${" ) )
+    // check the partial path relative to ${KISYS3DMOD} (legacy behavior)
+    if( !tname.StartsWith( ":" ) )
     {
-        if( sPL->m_alias == "${KISYS3DMOD}" )
+        wxFileName fpath;
+        wxString fullPath( "${KISYS3DMOD}" );
+        fullPath.Append( fpath.GetPathSeparator() );
+        fullPath.Append( tname );
+        fullPath = ExpandEnvVarSubstitutions( fullPath );
+        fpath.Assign( fullPath );
+
+        if( fpath.Normalize() && fpath.FileExists() )
         {
-            wxFileName fpath( wxFileName::DirName( sPL->m_pathexp ) );
-            wxString fullPath = fpath.GetPathWithSep() + tname;
-
-            if( wxFileName::FileExists( fullPath ) )
-            {
-                wxFileName tmp( fullPath );
-
-                if( tmp.Normalize() )
-                    tname = tmp.GetFullPath();
-
-                m_NameMap.insert( std::pair< wxString, wxString > ( aFileName, tname ) );
-
-                return tname;
-            }
+            tname = fpath.GetFullPath();
+            m_NameMap.insert( std::pair< wxString, wxString > ( aFileName, tname ) );
+            return tname;
         }
 
+    }
+
+    // ${ENV_VAR} paths have already been checked; skip them
+    while( sPL != ePL && ( sPL->m_alias.StartsWith( "${" )
+        || sPL->m_alias.StartsWith( "$(" ) ) )
         ++sPL;
-    }
 
     // at this point the filename must contain an alias or else it is invalid
     wxString alias;         // the alias portion of the short filename
@@ -416,6 +467,9 @@
             wxFileName fpath( wxFileName::DirName( sPL->m_pathexp ) );
             wxString fullPath = fpath.GetPathWithSep() + relpath;
 
+            if( fullPath.StartsWith( "${") || fullPath.StartsWith( "$(" ) )
+                fullPath = ExpandEnvVarSubstitutions( fullPath );
+
             if( wxFileName::FileExists( fullPath ) )
             {
                 wxFileName tmp( fullPath );
@@ -424,7 +478,6 @@
                     tname = tmp.GetFullPath();
 
                 m_NameMap.insert( std::pair< wxString, wxString > ( aFileName, tname ) );
-
                 return tname;
             }
         }
@@ -462,7 +515,13 @@
         tpath.m_pathvar.erase( tpath.m_pathvar.length() - 1 );
     #endif
 
-    wxFileName path( tpath.m_pathvar, wxT( "" ) );
+    wxFileName path;
+
+    if( tpath.m_pathvar.StartsWith( "${" ) || tpath.m_pathvar.StartsWith( "$(" ) )
+        path.Assign( ExpandEnvVarSubstitutions( tpath.m_pathvar ), "" );
+    else
+        path.Assign( tpath.m_pathvar, "" );
+
     path.Normalize();
 
     if( !path.DirExists() )
@@ -645,7 +704,8 @@
     std::list< S3D_ALIAS >::const_iterator sPL = m_Paths.begin();
     std::list< S3D_ALIAS >::const_iterator ePL = m_Paths.end();
 
-    while( sPL != ePL && sPL->m_alias.StartsWith( "${" ) )
+    while( sPL != ePL && ( sPL->m_alias.StartsWith( "${" )
+        || sPL->m_alias.StartsWith( "$(" ) ) )
         ++sPL;
 
     wxFileName cfgpath( m_ConfigDir, S3D_RESOLVER_CONFIG );
@@ -721,47 +781,69 @@
 }
 
 
-bool S3D_FILENAME_RESOLVER::resolveVirtualEnv( const wxString& aFileName, wxString& aFullPath )
+void S3D_FILENAME_RESOLVER::checkEnvVarPath( const wxString& aPath )
 {
-    aFullPath.clear();
-
-    if( !aFileName.StartsWith( "${" ) )
-        return false;
-
-    size_t eDelim = aFileName.find( '}' );
-
-    if( eDelim == wxString::npos || eDelim + 2 >= aFileName.length() )
-        return false;
-
-    wxString tPath = aFileName.substr( 0, eDelim + 1 );
-
+    bool useParen = false;
+
+    if( aPath.StartsWith( "$(" ) )
+        useParen = true;
+    else if( !aPath.StartsWith( "${" ) )
+        return;
+
+    size_t pEnd;
+
+    if( useParen )
+        pEnd = aPath.find( ")" );
+    else
+        pEnd = aPath.find( "}" );
+
+    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< S3D_ALIAS >::const_iterator sPL = m_Paths.begin();
     std::list< S3D_ALIAS >::const_iterator ePL = m_Paths.end();
 
     while( sPL != ePL )
     {
+        if( sPL->m_alias == envar )
+            return;
+
         if( !sPL->m_alias.StartsWith( "${" ) )
-            return false;
-
-        if( sPL->m_alias == tPath )
-        {
-            tPath.Append( aFileName.substr( eDelim + 2 ) );
-            wxFileName tFile( tPath );
-            tFile.Normalize();
-
-            if( tFile.FileExists() )
-            {
-                aFullPath = tFile.GetFullPath();
-                return true;
-            }
-
-            return false;
-        }
+            break;
 
         ++sPL;
     }
 
-    return false;
+    S3D_ALIAS lpath;
+    lpath.m_alias = envar;
+    lpath.m_pathvar = lpath.m_alias;
+    wxFileName tmpFN;
+
+    if( lpath.m_alias.StartsWith( "${" ) || lpath.m_alias.StartsWith( "$(" ) )
+        tmpFN.Assign( ExpandEnvVarSubstitutions( lpath.m_alias ), "" );
+    else
+        tmpFN.Assign( lpath.m_alias, "" );
+
+    wxUniChar psep = tmpFN.GetPathSeparator();
+    tmpFN.Normalize();
+
+    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;
 }
 
 
@@ -802,7 +884,7 @@
             fname.Replace( wxT( "\\" ), wxT( "/" ) );
             #endif
 
-            if( sL->m_alias.StartsWith( "${" ) )
+            if( sL->m_alias.StartsWith( "${" ) || sL->m_alias.StartsWith( "$(" ) )
             {
                 // old style ENV_VAR
                 tname = sL->m_alias;
@@ -1009,6 +1091,7 @@
 
         wxString lpath = filename.substr( 0, pos0 );
 
+        // check the alias for restricted characters
         if( wxString::npos != lpath.find_first_of( wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" ) ) )
             return false;
 

=== modified file '3d-viewer/3d_cache/3d_filename_resolver.h'
--- 3d-viewer/3d_cache/3d_filename_resolver.h	2016-04-17 22:35:32 +0000
+++ 3d-viewer/3d_cache/3d_filename_resolver.h	2016-05-30 08:45:37 +0000
@@ -1,7 +1,7 @@
 /*
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
- * Copyright (C) 2015 Cirilo Bernardo <cirilo.bernardo@xxxxxxxxx>
+ * Copyright (C) 2015-2016 Cirilo Bernardo <cirilo.bernardo@xxxxxxxxx>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -99,12 +99,12 @@
     bool writePathList( void );
 
     /**
-     * Function resolveVirtualEnv
-     * extracts the ${ENV_VAR} component of aFileName and puts a
-     * resolved path in aFullPath if the ${ENV_VAR} exists in the
-     * alias list and the referenced file exists.
+     * Function checkEnvVarPath
+     * checks the ${ENV_VAR} component of a path and adds
+     * it to the resolver's path list if it is not yet in
+     * the list
      */
-    bool resolveVirtualEnv( const wxString& aFileName, wxString& aFullPath );
+    void checkEnvVarPath( const wxString& aPath );
 
 public:
     S3D_FILENAME_RESOLVER();

=== modified file '3d-viewer/3d_cache/3d_plugin_manager.cpp'
--- 3d-viewer/3d_cache/3d_plugin_manager.cpp	2016-04-05 10:32:22 +0000
+++ 3d-viewer/3d_cache/3d_plugin_manager.cpp	2016-05-30 08:45:37 +0000
@@ -285,7 +285,7 @@
     // Per definition a loadable "xxx.bundle" is similar to an "xxx.app" app
     // bundle being a folder with some special content in it. We obviously don't
     // want to have that here for our loadable module, so just use ".so".
-    nameFilter.Append( wxT( ".so" ) );
+    nameFilter.Append( ".so" );
 #endif
 
     wxString lp = wd.GetNameWithSep();
@@ -316,7 +316,13 @@
     if( aPath.empty() || !wxFileName::FileExists( aPath ) )
         return;
 
-    wxFileName path( aPath );
+    wxFileName path;
+
+    if( aPath.StartsWith( "${" ) || aPath.StartsWith( "$(" ) )
+        path.Assign( ExpandEnvVarSubstitutions( aPath ) );
+    else
+        path.Assign( aPath );
+
     path.Normalize();
 
     // determine if the path is already in the list
@@ -355,7 +361,13 @@
         aPath.ToUTF8() );
     #endif
 
-    wxFileName path( wxFileName::DirName( aPath ) );
+    wxFileName path;
+
+    if( aPath.StartsWith( "${" ) || aPath.StartsWith( "$(" ) )
+        path.Assign( ExpandEnvVarSubstitutions( aPath ), "" );
+    else
+        path.Assign( aPath, "" );
+
     path.Normalize();
 
     if( !wxFileName::DirExists( path.GetFullPath() ) )

=== modified file '3d-viewer/3d_cache/dialogs/dlg_3d_pathconfig.cpp'
--- 3d-viewer/3d_cache/dialogs/dlg_3d_pathconfig.cpp	2016-05-28 09:33:20 +0000
+++ 3d-viewer/3d_cache/dialogs/dlg_3d_pathconfig.cpp	2016-05-30 08:45:37 +0000
@@ -54,7 +54,8 @@
         size_t listsize = rpaths->size();
         size_t listidx = 0;
 
-        while( rI != rE && (*rI).m_alias.StartsWith( "${" ) )
+        while( rI != rE && ( (*rI).m_alias.StartsWith( "${" )
+            || (*rI).m_alias.StartsWith( "$(" ) ) )
         {
             ++listidx;
             ++rI;

=== modified file '3d-viewer/3d_class.cpp'
--- 3d-viewer/3d_class.cpp	2016-04-06 00:32:50 +0000
+++ 3d-viewer/3d_class.cpp	2016-05-30 08:45:37 +0000
@@ -1,7 +1,7 @@
 /*
  * This program source code file is part of KiCad, a free EDA CAD application.
  *
- * Copyright (C) 1992-2015 KiCad Developers, see AUTHORS.txt for contributors.
+ * Copyright (C) 1992-2016 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
@@ -29,6 +29,7 @@
 
 #include "3d_viewer.h"
 #include "3d_struct.h"
+#include "common.h"
 #include "modelparsers.h"
 #include <kiway.h>
 #include <3d_cache.h>
@@ -136,8 +137,9 @@
     // if the m_Shape3DName is not an absolute path the default path
     // given by the environment variable KISYS3DMOD will be used
 
-    if( m_Shape3DName.StartsWith( wxT("${") ) )
-        m_Shape3DFullFilename = wxExpandEnvVars( m_Shape3DName );
+    if( m_Shape3DName.StartsWith( wxT("${") ) ||
+        m_Shape3DName.StartsWith( wxT("$(") ) )
+        m_Shape3DFullFilename = ExpandEnvVarSubstitutions( m_Shape3DName );
     else
         m_Shape3DFullFilename = m_Shape3DName;
 


Follow ups

References