wxString S3D_FILENAME_RESOLVER::ResolvePath( const wxString& aFileName )
{
    wxCriticalSectionLocker lock( lock3D_resolver );

    if( aFileName.empty() )
        return wxEmptyString;

    if( m_Paths.empty() )
        createPathList();

    // first attempt to use the name as specified:
    wxString tname = aFileName;

    #ifdef _WIN32
    // translate from KiCad's internal UNIX-like path to MSWin paths
    tname.Replace( wxT( "/" ), wxT( "\\" ) );
    #endif

    // Note: variable expansion must 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( "${" ) || tname.StartsWith( "$(" ) )
        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( tmpFN.FileExists() )
    {
        tmpFN.Normalize();
        tname = tmpFN.GetFullPath();

        // 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;
    }

    // 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( "$(" ) )
    {
        if( !( m_errflags & ERRFLG_ENVPATH ) )
        {
            m_errflags |= ERRFLG_ENVPATH;
            wxString errmsg = "[3D File Resolver] No such path; ensure the environment var is defined";
            errmsg.append( "\n" );
            errmsg.append( tname );
            wxLogMessage( errmsg );
        }

        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();

    // 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. This case accounts
    // for partial paths which do not contain ${KIPRJMOD}.
    // 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( ":" ) )
    {
        tmpFN.Assign( sPL->m_pathexp, "" );
        wxString fullPath = tmpFN.GetPathWithSep() + tname;

        if( fullPath.StartsWith( "${" ) || fullPath.StartsWith( "$(" ) )
            fullPath = ExpandEnvVarSubstitutions( fullPath );

        if( wxFileName::FileExists( fullPath ) )
        {
            tmpFN.Assign( fullPath );
            tmpFN.Normalize();
            tname = tmpFN.GetFullPath();
            return tname;
        }

    }

    // check the partial path relative to ${KISYS3DMOD} (legacy behavior)
    if( !tname.StartsWith( ":" ) )
    {
        wxFileName fpath;
        wxString fullPath( "${KISYS3DMOD}" );
        fullPath.Append( fpath.GetPathSeparator() );
        fullPath.Append( tname );
        fullPath = ExpandEnvVarSubstitutions( fullPath );
        fpath.Assign( fullPath );

        if( fpath.Normalize() && fpath.FileExists() )
        {
            tname = fpath.GetFullPath();
            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
    wxString relpath;       // the path relative to the alias

    if( !SplitAlias( tname, alias, relpath ) )
    {
        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";
            errmsg.append( "\n" );
            errmsg.append( tname );
            wxLogTrace( MASK_3D_RESOLVER, errmsg );
        }

        return wxEmptyString;
    }

    while( sPL != ePL )
    {
        if( !sPL->m_alias.Cmp( alias ) && !sPL->m_pathexp.empty() )
        {
            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 );

                if( tmp.Normalize() )
                    tname = tmp.GetFullPath();

                return tname;
            }
        }

        ++sPL;
    }

    if( !( m_errflags & ERRFLG_ALIAS ) )
    {
        m_errflags |= ERRFLG_ALIAS;
        wxString errmsg = "[3D File Resolver] No such path; ensure the path alias is defined";
        errmsg.append( "\n" );
        errmsg.append( tname.substr( 1 ) );
        wxLogTrace( MASK_3D_RESOLVER, errmsg );
    }

    return wxEmptyString;
}
wxString S3D_FILENAME_RESOLVER::ResolvePath( const wxString& aFileName )
{
    wxCriticalSectionLocker lock( lock3D_resolver );
    if( aFileName.empty() )
        return wxEmptyString;

    if( m_Paths.empty() )
        createPathList();

    // look up the filename in the internal filename map
    std::map< wxString, wxString, S3D::rsort_wxString >::iterator mi;
    mi = m_NameMap.find( aFileName );

    if( mi != m_NameMap.end() )
        return mi->second;

    // first attempt to use the name as specified:
    wxString tname = aFileName;

    #ifdef _WIN32
    // translate from KiCad's internal UNIX-like path to MSWin paths
    tname.Replace( wxT( "/" ), wxT( "\\" ) );
    #endif

    // this case covers full paths and paths relative to
    // the current working directory (which is not necessarily
    // the current project directory)
    if( wxFileName::FileExists( tname ) )
    {
        wxFileName tmp( tname );

        if( tmp.Normalize() )
            tname = tmp.GetFullPath();

        m_NameMap.insert( std::pair< wxString, wxString > ( aFileName, tname ) );

        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( "${" ) ) )
    {
        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;
            wxString errmsg = _( "[3D File Resolver] No such path; ensure the environment var is defined" );
            errmsg.append( "\n" );
            errmsg.append( tname );
            wxLogMessage( "%s\n", errmsg.ToUTF8() );
        }

        return wxEmptyString;
    }

    std::list< S3D_ALIAS >::const_iterator sPL = m_Paths.begin();
    std::list< S3D_ALIAS >::const_iterator ePL = m_Paths.end();

    // 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. 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

    if( !SplitAlias( tname, alias, relpath ) )
    {
        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" );
            errmsg.append( "\n" );
            errmsg.append( tname );
            wxLogTrace( MASK_3D_RESOLVER, "%s\n", errmsg.ToUTF8() );
        }

        return wxEmptyString;
    }

    while( sPL != ePL )
    {
        if( !sPL->m_alias.Cmp( alias ) && !sPL->m_pathexp.empty() )
        {
            wxFileName fpath( wxFileName::DirName( sPL->m_pathexp ) );
            wxString fullPath = fpath.GetPathWithSep() + relpath;

            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;
    }

    if( !( m_errflags & ERRFLG_ALIAS ) )
    {
        m_errflags |= ERRFLG_ALIAS;
        wxString errmsg = _( "[3D File Resolver] No such path; ensure the path alias is defined" );
        errmsg.append( "\n" );
        errmsg.append( tname.substr( 1 ) );
        wxLogTrace( MASK_3D_RESOLVER, "%s\n", errmsg.ToUTF8() );
    }

    return wxEmptyString;
}