← Back to team overview

kicad-developers team mailing list archive

PATCH: 3D filename resolver

 

The attached patch makes the following changes:

1. Paths defined via pcbnew->Preferences->Configure Paths
are now included in the 3D file browser's drop-down list except
for KICAD_PTEMPLATES, KIGITHUB, KISYSMOD, any paths
resembling a URL, and any non-existent paths. When an
absolute path is shortened using one of these path aliases,
let's say 'KISYS3DMOD' then the name appears in the pcb file
as "${KISYS3DMOD}/blah.wrl". If a user defines and actual
environment variable with the same name, then that user
defined variable will have precedence over the internally defined
variable.

2. Paths relative to the current project directory are no longer
represented as "some/path/to/file.wrl"; for compatibility with
previous kicad versions it shall always be stored as
${KIPRJMOD}/some/path/to/file.wrl.

3. Various restrictions on alias path values have been removed
since the imposed restrictions could result in corrupted user
data (aliases are silently dropped).

Overall the patch should improve back-compatibility by
providing a ${ENV_VAR} mechanism for supporting
different model root directories and ensuring that paths
relative to KIPRJMOD are expressed in a way that is
compatible with earlier kicad versions. This allows users
more flexibility and the ability to work in a way that ensures
3D model files can be correctly resolved in earlier versions
of kicad. Users who do not care about back-compatibility
may prefer to use the newer alias system.

- Cirilo
=== modified file '3d-viewer/3d_cache/3d_cache.cpp'
--- 3d-viewer/3d_cache/3d_cache.cpp	2016-04-11 03:03:39 +0000
+++ 3d-viewer/3d_cache/3d_cache.cpp	2016-04-15 05:16:09 +0000
@@ -686,6 +686,13 @@
 }
 
 
+void S3D_CACHE::SetProgramBase( PGM_BASE* aBase )
+{
+    m_FNResolver->SetProgramBase( aBase );
+    return;
+}
+
+
 wxString S3D_CACHE::GetProjectDir( void )
 {
     return m_FNResolver->GetProjectDir();

=== modified file '3d-viewer/3d_cache/3d_cache.h'
--- 3d-viewer/3d_cache/3d_cache.h	2016-03-20 00:45:55 +0000
+++ 3d-viewer/3d_cache/3d_cache.h	2016-04-15 05:15:15 +0000
@@ -38,6 +38,7 @@
 #include "plugins/3dapi/c3dmodel.h"
 
 
+class  PGM_BASE;
 class  S3D_CACHE;
 class  S3D_CACHE_ENTRY;
 class  SCENEGRAPH;
@@ -139,6 +140,14 @@
     bool SetProjectDir( const wxString& aProjDir );
 
     /**
+     * Function SetProgramBase
+     * sets the filename resolver's pointer to the application's
+     * PGM_BASE instance; the pointer is used to extract the
+     * local env vars.
+     */
+    void SetProgramBase( PGM_BASE* aBase );
+
+    /**
      * Function GetProjectDir
      * returns the current project's working directory
      */

=== modified file '3d-viewer/3d_cache/3d_cache_wrapper.cpp'
--- 3d-viewer/3d_cache/3d_cache_wrapper.cpp	2016-04-11 03:03:39 +0000
+++ 3d-viewer/3d_cache/3d_cache_wrapper.cpp	2016-04-15 05:23:41 +0000
@@ -24,6 +24,7 @@
 
 #include <common.h>
 #include <wx/thread.h>
+#include <pgm_base.h>
 #include "3d_cache_wrapper.h"
 
 static wxCriticalSection lock3D_wrapper;
@@ -57,6 +58,7 @@
         wxFileName cfgpath;
         cfgpath.AssignDir( GetKicadConfigPath() );
         cfgpath.AppendDir( wxT( "3d" ) );
+        cache->SetProgramBase( &Pgm() );
         cache->Set3DConfigDir( cfgpath.GetFullPath() );
         SetElem( ELEM_3DCACHE, cw );
         updateProjDir = true;

=== modified file '3d-viewer/3d_cache/3d_filename_resolver.cpp'
--- 3d-viewer/3d_cache/3d_filename_resolver.cpp	2016-04-11 03:03:39 +0000
+++ 3d-viewer/3d_cache/3d_filename_resolver.cpp	2016-04-15 22:06:11 +0000
@@ -32,6 +32,7 @@
 #include <wx/thread.h>
 #include <wx/utils.h>
 #include <wx/msgdlg.h>
+#include <pgm_base.h>
 
 #include "3d_filename_resolver.h"
 
@@ -54,6 +55,7 @@
 S3D_FILENAME_RESOLVER::S3D_FILENAME_RESOLVER()
 {
     m_errflags = 0;
+    m_pgm = NULL;
 }
 
 
@@ -86,7 +88,7 @@
     if( false == projdir.DirExists() )
         return false;
 
-    wxString path = projdir.GetPath();
+    m_curProjDir = projdir.GetPath();
 
     if( flgChanged )
         *flgChanged = false;
@@ -96,7 +98,7 @@
         S3D_ALIAS al;
         al.m_alias = _( "(DEFAULT)" );
         al.m_pathvar = _( "${PROJDIR}" );
-        al.m_pathexp = path;
+        al.m_pathexp = m_curProjDir;
         al.m_description = _( "Current project directory" );
         m_Paths.push_back( al );
         m_NameMap.clear();
@@ -107,9 +109,9 @@
     }
     else
     {
-        if( m_Paths.front().m_pathexp.Cmp( path ) )
+        if( m_Paths.front().m_pathexp.Cmp( m_curProjDir ) )
         {
-            m_Paths.front().m_pathexp = path;
+            m_Paths.front().m_pathexp = m_curProjDir;
             m_NameMap.clear();
 
             if( flgChanged )
@@ -138,10 +140,22 @@
 
 wxString S3D_FILENAME_RESOLVER::GetProjectDir( void )
 {
-    if( m_Paths.empty() )
-        return wxEmptyString;
-
-    return m_Paths.front().m_pathexp;
+    return m_curProjDir;
+}
+
+
+void S3D_FILENAME_RESOLVER::SetProgramBase( PGM_BASE* aBase )
+{
+    m_pgm = aBase;
+
+    if( NULL == m_pgm || m_Paths.empty() )
+        return;
+
+    // recreate the path list
+    m_Paths.clear();
+    createPathList();
+
+    return;
 }
 
 
@@ -157,15 +171,61 @@
     // the user may change this later with a call to SetProjectDir()
 
     S3D_ALIAS lpath;
-    lpath.m_alias = _( "(DEFAULT)" );
-    lpath.m_pathvar = _( "${PROJDIR}" );
-    lpath.m_description = _( "Current project directory" );
+    lpath.m_alias = _( "${KIPRJMOD}" );
+    lpath.m_pathvar = _( "${KIPRJMOD}" );
+    lpath.m_pathexp = m_curProjDir;
     m_Paths.push_back( lpath );
-
-    lpath.m_alias = wxT( "KISYS3DMOD" );
-    lpath.m_pathvar = wxT( "${KISYS3DMOD}" );
-    lpath.m_description = _( "Legacy 3D environment path" );
-    addPath( lpath );
+    wxFileName fndummy;
+    wxUniChar psep = fndummy.GetPathSeparator();
+
+    // iterate over the list of internally defined ENV VARs
+    // and add existing paths to the resolver
+    if( m_pgm )
+    {
+        ENV_VAR_MAP_CITER mS = m_pgm->GetLocalEnvVariables().begin();
+        ENV_VAR_MAP_CITER mE = m_pgm->GetLocalEnvVariables().end();
+
+        while( mS != mE )
+        {
+            // filter out URLs, template directories, and known system paths
+            if( mS->first == wxString( "KICAD_PTEMPLATES" )
+                || mS->first == wxString( "KIGITHUB" )
+                || mS->first == wxString( "KISYSMOD" ) )
+            {
+                ++mS;
+                continue;
+            }
+
+            if( wxString::npos != mS->second.GetValue().find( wxString( "://" ) ) )
+            {
+                ++mS;
+                continue;
+            }
+
+            fndummy.Assign( mS->second.GetValue(), "" );
+
+            if( !fndummy.DirExists() )
+            {
+                ++mS;
+                continue;
+            }
+
+            wxString tmp( "${" );
+            tmp.Append( mS->first );
+            tmp.Append( "}" );
+
+            lpath.m_alias =  tmp;
+            lpath.m_pathvar = tmp;
+            lpath.m_pathexp = mS->second.GetValue();
+
+            if( !lpath.m_pathexp.empty() && psep == *lpath.m_pathexp.rbegin() )
+                lpath.m_pathexp.erase( --lpath.m_pathexp.end() );
+
+            m_Paths.push_back( lpath );
+
+            ++mS;
+        }
+    }
 
     if( !m_ConfigDir.empty() )
         readPathList();
@@ -191,7 +251,9 @@
 
 bool S3D_FILENAME_RESOLVER::UpdatePathList( std::vector< S3D_ALIAS >& aPathList )
 {
-    while( m_Paths.size() > 2 )
+    wxUniChar envMarker( '$' );
+
+    while( !m_Paths.empty() && envMarker != *m_Paths.back().m_alias.rbegin() )
         m_Paths.pop_back();
 
     size_t nI = aPathList.size();
@@ -258,6 +320,11 @@
 
             return tname;
         }
+        else if( resolveVirtualEnv( aFileName, tname ) )
+        {
+            m_NameMap.insert( std::pair< wxString, wxString > ( aFileName, tname ) );
+            return tname;
+        }
 
         if( !( m_errflags & ERRFLG_ENVPATH ) )
         {
@@ -276,47 +343,53 @@
 
     // check the path relative to the current project directory;
     // note: this is not necessarily the same as the current working
-    // directory, which has already been checked
-    if( !sPL->m_pathexp.empty() )
-    {
-        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;
-        }
-    }
-
-    ++sPL;  // skip to item 2: KISYS3DMOD
-
-    // check if the path is relative to KISYS3DMOD but lacking
-    // the "KISYS3DMOD:" alias tag
-    if( !sPL->m_pathexp.empty() )
-    {
-        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;
-        }
-    }
-
+    // directory, which has already been checked. This case accounts
+    // for partial paths which do not contain ${KIPRJMOD}.
+    if( !sPL->m_pathexp.empty() )
+    {
+        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;
+        }
+    }
+
+    // ${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( "${" ) )
+    {
+        if( sPL->m_alias == "${KISYS3DMOD}" )
+        {
+            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;
+            }
+        }
+
+        ++sPL;
+    }
+
+    // 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
 
@@ -324,8 +397,10 @@
     {
         if( !( m_errflags & ERRFLG_RELPATH ) )
         {
+            // this can happen if the file was intended to be relative to
+            // ${KISYS3DMOD} but ${KISYS3DMOD} not set or incorrect.
             m_errflags |= ERRFLG_RELPATH;
-            wxString errmsg = _( "[3D File Resolver] No such path; ensure KISYS3DMOD is correctly defined" );
+            wxString errmsg = _( "[3D File Resolver] No such path" );
             errmsg.append( "\n" );
             errmsg.append( tname );
             wxLogTrace( MASK_3D_RESOLVER, "%s\n", errmsg.ToUTF8() );
@@ -378,7 +453,6 @@
     wxCriticalSectionLocker lock( lock3D_resolver );
 
     S3D_ALIAS tpath = aPath;
-    tpath.m_duplicate = false;
 
     #ifdef _WIN32
     while( tpath.m_pathvar.EndsWith( wxT( "\\" ) ) )
@@ -424,83 +498,6 @@
 
     while( sPL != ePL )
     {
-        // aliases with the same m_pathvar are forbidden and the
-        // user must be forced to fix the problem in order to
-        // obtain good filename resolution
-        if( !sPL->m_pathvar.empty() && !tpath.m_pathvar.empty()
-            && !tpath.m_pathvar.Cmp( sPL->m_pathvar ) )
-        {
-            wxString msg = _( "This alias: " );
-            msg.append( tpath.m_alias );
-            msg.append( wxT( "\n" ) );
-            msg.append( _( "This path: " ) );
-            msg.append( tpath.m_pathvar );
-            msg.append( wxT( "\n" ) );
-            msg.append( _( "Existing alias: " ) );
-            msg.append( sPL->m_alias );
-            msg.append( wxT( "\n" ) );
-            msg.append( _( "Existing path: " ) );
-            msg.append( sPL->m_pathvar );
-            wxMessageBox( msg, _( "Bad alias (duplicate path)" ) );
-
-            return false;
-        }
-
-        // aliases with the same m_pathexp are acceptable (one or both
-        // aliases being tested may be expanded variables) but when shortening
-        // names the preference is for (a) a fully specified path in m_pathvar
-        // then (b) the more senior alias in the list
-        if( !sPL->m_pathexp.empty() && !tpath.m_pathexp.empty() )
-        {
-            if( !tpath.m_pathexp.Cmp( sPL->m_pathexp ) )
-            {
-                wxString msg = _( "This alias: " );
-                msg.append( tpath.m_alias );
-                msg.append( wxT( "\n" ) );
-                msg.append( _( "Existing alias: " ) );
-                msg.append( sPL->m_alias );
-                msg.append( wxT( "\n" ) );
-                msg.append( _( "This path: " ) );
-                msg.append( tpath.m_pathexp );
-                msg.append( wxT( "\n" ) );
-                msg.append( _( "Existing path: " ) );
-                msg.append( sPL->m_pathexp );
-                msg.append( wxT( "\n" ) );
-                msg.append( _( "This full path: " ) );
-                msg.append( tpath.m_pathexp );
-                msg.append( wxT( "\n" ) );
-                msg.append( _( "Existing full path: " ) );
-                msg.append( sPL->m_pathexp );
-                wxMessageBox( msg, _( "Bad alias (duplicate path)" ) );
-
-                if( tpath.m_pathvar.StartsWith( wxT( "${" ) ) )
-                    tpath.m_duplicate = true;
-                else if( sPL->m_pathvar.StartsWith( wxT( "${" ) ) )
-                    sPL->m_duplicate = true;
-
-            }
-
-            if( ( tpath.m_pathexp.find( sPL->m_pathexp ) != wxString::npos
-                || sPL->m_pathexp.find( tpath.m_pathexp ) != wxString::npos )
-                && tpath.m_pathexp.Cmp( sPL->m_pathexp ) )
-            {
-                wxString msg = _( "This alias: " );
-                msg.append( tpath.m_alias );
-                msg.append( wxT( "\n" ) );
-                msg.append( _( "This path: " ) );
-                msg.append( tpath.m_pathexp );
-                msg.append( wxT( "\n" ) );
-                msg.append( _( "Existing alias: " ) );
-                msg.append( sPL->m_alias );
-                msg.append( wxT( "\n" ) );
-                msg.append( _( "Existing path: " ) );
-                msg.append( sPL->m_pathexp );
-                wxMessageBox( msg, _( "Bad alias (common path)" ) );
-
-                return false;
-            }
-        }
-
         if( !tpath.m_alias.Cmp( sPL->m_alias ) )
         {
             wxString msg = _( "Alias: " );
@@ -519,8 +516,6 @@
         ++sPL;
     }
 
-    // Note: at this point we may still have duplicated paths
-
     m_Paths.push_back( tpath );
     return true;
 }
@@ -572,7 +567,6 @@
 
     int lineno = 0;
     S3D_ALIAS al;
-    al.m_duplicate = false;
     size_t idx;
     int vnum = 0;           // version number
 
@@ -647,11 +641,18 @@
         return false;
     }
 
+    // skip all ${ENV_VAR} alias names
+    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( "${" ) )
+        ++sPL;
+
     wxFileName cfgpath( m_ConfigDir, S3D_RESOLVER_CONFIG );
     wxString cfgname = cfgpath.GetFullPath();
     std::ofstream cfgFile;
 
-    if( m_Paths.empty() || 1 == m_Paths.size() )
+    if( sPL == ePL )
     {
         wxMessageDialog md( NULL,
             _( "3D search path list is empty;\ncontinue to write empty file?" ),
@@ -692,23 +693,10 @@
     }
 
     cfgFile << "#V" << CFGFILE_VERSION << "\n";
-    std::list< S3D_ALIAS >::const_iterator sPL = m_Paths.begin();
-    std::list< S3D_ALIAS >::const_iterator ePL = m_Paths.end();
-
-    // the first entry is the current project dir; we never add the implicit
-    // project dir to the path list in the configuration file
-    ++sPL;
     std::string tstr;
 
     while( sPL != ePL )
     {
-        // never write the KISYS3DMOD entry
-        if( !sPL->m_alias.Cmp( wxT( "KISYS3DMOD") ) )
-        {
-            ++sPL;
-            continue;
-        }
-
         tstr = sPL->m_alias.ToUTF8();
         cfgFile << "\"" << tstr.size() << ":" << tstr << "\",";
         tstr = sPL->m_pathvar.ToUTF8();
@@ -733,6 +721,50 @@
 }
 
 
+bool S3D_FILENAME_RESOLVER::resolveVirtualEnv( const wxString& aFileName, wxString& aFullPath )
+{
+    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 );
+
+    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.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;
+        }
+
+        ++sPL;
+    }
+
+    return false;
+}
+
+
 wxString S3D_FILENAME_RESOLVER::ShortenPath( const wxString& aFullPathName )
 {
     wxString fname = aFullPathName;
@@ -745,32 +777,11 @@
     std::list< S3D_ALIAS >::const_iterator eL = m_Paths.end();
     size_t idx;
 
-    // test for files within the current project directory
-    // and KISYS3DMOD directory
-    for( int i = 0; i < 2 && sL != eL; ++i )
-    {
-        if( !sL->m_pathexp.empty() )
-        {
-            wxFileName fpath( sL->m_pathexp, wxT( "" ) );
-            wxString fps = fpath.GetPathWithSep();
-
-            idx = fname.find( fps );
-
-            if( std::string::npos != idx && 0 == idx  )
-            {
-                fname = fname.substr( fps.size() );
-                return fname;
-            }
-        }
-
-        ++sL;
-    }
-
     while( sL != eL )
     {
-        // undefined paths and duplicates do not participate
-        // in the file name shortening procedure
-        if( sL->m_pathexp.empty() || sL->m_duplicate )
+        // undefined paths do not participate in the
+        // file name shortening procedure
+        if( sL->m_pathexp.empty() )
         {
             ++sL;
             continue;
@@ -791,10 +802,21 @@
             fname.Replace( wxT( "\\" ), wxT( "/" ) );
             #endif
 
-            tname = ":";
-            tname.append( sL->m_alias );
-            tname.append( ":" );
-            tname.append( fname );
+            if( sL->m_alias.StartsWith( "${" ) )
+            {
+                // old style ENV_VAR
+                tname = sL->m_alias;
+                tname.Append( "/" );
+                tname.append( fname );
+            }
+            else
+            {
+                // new style alias
+                tname = ":";
+                tname.append( sL->m_alias );
+                tname.append( ":" );
+                tname.append( fname );
+            }
 
             return tname;
         }

=== modified file '3d-viewer/3d_cache/3d_filename_resolver.h'
--- 3d-viewer/3d_cache/3d_filename_resolver.h	2016-01-23 03:52:54 +0000
+++ 3d-viewer/3d_cache/3d_filename_resolver.h	2016-04-15 21:33:52 +0000
@@ -39,9 +39,10 @@
 #include <wx/string.h>
 #include "str_rsort.h"
 
+class PGM_BASE;
+
 struct S3D_ALIAS
 {
-    bool m_duplicate;
     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
@@ -56,6 +57,8 @@
     // mapping of (short) file names to resolved names
     std::map< wxString, wxString, S3D::rsort_wxString > m_NameMap;
     int m_errflags;
+    PGM_BASE* m_pgm;
+    wxString m_curProjDir;
 
     /**
      * Function createPathList
@@ -95,6 +98,14 @@
      */
     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.
+     */
+    bool resolveVirtualEnv( const wxString& aFileName, wxString& aFullPath );
+
 public:
     S3D_FILENAME_RESOLVER();
 
@@ -120,6 +131,13 @@
     bool SetProjectDir( const wxString& aProjDir, bool* flgChanged = NULL );
     wxString GetProjectDir( void );
 
+    /**
+     * Function SetProgramBase
+     * sets a pointer to the application's PGM_BASE instance;
+     * the pointer is used to extract the local env vars.
+     */
+    void SetProgramBase( PGM_BASE* aBase );
+
 
     /**
      * Function UpdatePathList

=== modified file '3d-viewer/3d_cache/dialogs/dlg_3d_pathconfig.cpp'
--- 3d-viewer/3d_cache/dialogs/dlg_3d_pathconfig.cpp	2016-01-25 04:55:36 +0000
+++ 3d-viewer/3d_cache/dialogs/dlg_3d_pathconfig.cpp	2016-04-15 09:21:08 +0000
@@ -42,33 +42,35 @@
         m_aliasValidator.SetCharExcludes( wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" ) );
 
         const std::list< S3D_ALIAS >* rpaths = m_resolver->GetPaths();
+        std::list< S3D_ALIAS >::const_iterator rI = rpaths->begin();
+        std::list< S3D_ALIAS >::const_iterator rE = rpaths->end();
         size_t listsize = rpaths->size();
-
-        if( listsize > 0 )
-            m_curdir = rpaths->front().m_pathexp;
-
-        if( listsize < 3 )
+        size_t listidx = 0;
+
+        while( rI != rE && (*rI).m_alias.StartsWith( "${" ) )
+        {
+            ++listidx;
+            ++rI;
+        }
+
+        if( listidx < listsize )
+            m_curdir = (*rI).m_pathexp;
+        else
             return;
 
-        listsize = listsize - 2 - m_Aliases->GetNumberRows();
+        listsize = listsize - listidx - m_Aliases->GetNumberRows();
 
         // note: if the list allocation fails we have bigger problems
         // and there is no point in trying to notify the user here
         if( listsize > 0 && !m_Aliases->InsertRows( 0, listsize ) )
             return;
 
-        std::list< S3D_ALIAS >::const_iterator sL = rpaths->begin();
-        std::list< S3D_ALIAS >::const_iterator eL = rpaths->end();
         int nitems = 0;
-
-        // skip the current project dir and KISYS3DMOD
-        ++sL;
-        ++sL;
         wxGridCellTextEditor* pEdAlias;
 
-        while( sL != eL )
+        while( rI != rE )
         {
-            m_Aliases->SetCellValue( nitems, 0, sL->m_alias );
+            m_Aliases->SetCellValue( nitems, 0, rI->m_alias );
 
             if( 0 == nitems )
             {
@@ -83,12 +85,12 @@
                 m_Aliases->SetCellEditor( nitems, 0, pEdAlias );
             }
 
-            m_Aliases->SetCellValue( nitems, 1, sL->m_pathvar );
-            m_Aliases->SetCellValue( nitems++, 2, sL->m_description );
+            m_Aliases->SetCellValue( nitems, 1, rI->m_pathvar );
+            m_Aliases->SetCellValue( nitems++, 2, rI->m_description );
 
             // TODO: implement a wxGridCellEditor which invokes a wxDirDialog
 
-            ++sL;
+            ++rI;
         }
 
         m_Aliases->AutoSize();

=== modified file '3d-viewer/3d_cache/dialogs/dlg_select_3dmodel.cpp'
--- 3d-viewer/3d_cache/dialogs/dlg_select_3dmodel.cpp	2016-04-07 01:41:52 +0000
+++ 3d-viewer/3d_cache/dialogs/dlg_select_3dmodel.cpp	2016-04-15 10:01:46 +0000
@@ -22,6 +22,7 @@
  */
 
 
+#include <set>
 #include "dlg_select_3dmodel.h"
 #include "project.h"
 #include "3d_cache/3d_info.h"
@@ -240,12 +241,20 @@
     std::list< S3D_ALIAS > const* md = m_resolver->GetPaths();
     std::list< S3D_ALIAS >::const_iterator sL = md->begin();
     std::list< S3D_ALIAS >::const_iterator eL = md->end();
-    std::vector< wxString > cl;
+    std::set< wxString > cl;
+    wxString prjDir;
+
+    // extract the current project dir
+    if( sL != eL )
+    {
+        prjDir = sL->m_pathexp;
+        ++sL;
+    }
 
     while( sL != eL )
     {
-        if( !sL->m_pathexp.empty() && !sL->m_duplicate )
-            cl.push_back( sL->m_pathexp );
+        if( !sL->m_pathexp.empty() && sL->m_pathexp.compare( prjDir ) )
+            cl.insert( sL->m_pathexp );
 
         ++sL;
     }
@@ -253,7 +262,19 @@
     if( !cl.empty() )
     {
         dirChoices->Clear();
-        dirChoices->Append( (int)cl.size(), &cl[0] );
+
+        if( !prjDir.empty() )
+            dirChoices->Append( prjDir );
+
+        std::set< wxString >::const_iterator sI = cl.begin();
+        std::set< wxString >::const_iterator eI = cl.end();
+
+        while( sI != eI )
+        {
+            dirChoices->Append( *sI );
+            ++sI;
+        }
+
         dirChoices->Select( 0 );
     }
 

=== modified file 'pcbnew/dialogs/dialog_edit_module_for_BoardEditor.cpp'
--- pcbnew/dialogs/dialog_edit_module_for_BoardEditor.cpp	2016-04-10 22:28:06 +0000
+++ pcbnew/dialogs/dialog_edit_module_for_BoardEditor.cpp	2016-04-15 08:10:22 +0000
@@ -75,6 +75,7 @@
     m_OrientValueCtrl->SetValidator( m_OrientValidator );
     m_OrientValidator.SetWindow( m_OrientValueCtrl );
 
+    aParent->Prj().Get3DCacheManager()->GetResolver()->SetProgramBase( &Pgm() );
     m_PreviewPane = new PANEL_PREV_3D( m_Panel3D, aParent->Prj().Get3DCacheManager() );
     bLowerSizer3D->Add( m_PreviewPane, 1, wxEXPAND, 5 );
 

=== modified file 'pcbnew/dialogs/dialog_edit_module_for_Modedit.cpp'
--- pcbnew/dialogs/dialog_edit_module_for_Modedit.cpp	2016-02-27 23:17:58 +0000
+++ pcbnew/dialogs/dialog_edit_module_for_Modedit.cpp	2016-04-15 08:10:56 +0000
@@ -50,6 +50,7 @@
 #include <module_editor_frame.h>
 #include <dialog_edit_module_for_Modedit.h>
 #include <wildcards_and_files_ext.h>
+#include <pgm_base.h>
 #include "3d_cache/dialogs/panel_prev_model.h"
 #include "3d_cache/dialogs/3d_cache_dialogs.h"
 #include "3d_cache/3d_cache.h"
@@ -70,6 +71,7 @@
     icon.CopyFromBitmap( KiBitmap( icon_modedit_xpm ) );
     SetIcon( icon );
 
+    aParent->Prj().Get3DCacheManager()->GetResolver()->SetProgramBase( &Pgm() );
     m_PreviewPane = new PANEL_PREV_3D( m_Panel3D, aParent->Prj().Get3DCacheManager() );
     bLowerSizer3D->Add( m_PreviewPane, 1, wxEXPAND, 5 );
 


Follow ups