507 lines
14 KiB
C++
507 lines
14 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2015-2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
|
|
* Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, you may find one here:
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
* or you may search the http://www.gnu.org website for the version 2 license,
|
|
* or you may write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
|
|
#include <utility>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
|
|
#include <wx/dir.h>
|
|
#include <wx/dynlib.h>
|
|
#include <wx/filename.h>
|
|
#include <wx/log.h>
|
|
#include <wx/stdpaths.h>
|
|
#include <wx/string.h>
|
|
|
|
#include <common.h>
|
|
#include <paths.h>
|
|
#include "3d_plugin_manager.h"
|
|
#include "plugins/3d/3d_plugin.h"
|
|
#include "3d_cache/sg/scenegraph.h"
|
|
#include "plugins/ldr/3d/pluginldr3D.h"
|
|
|
|
|
|
/**
|
|
* Flag to enable 3D plugin manager debug tracing.
|
|
*
|
|
* Use "KI_TRACE_EDA_3D_VIEWER" to enable.
|
|
*
|
|
* @ingroup trace_env_vars
|
|
*/
|
|
#define MASK_3D_PLUGINMGR "3D_PLUGIN_MANAGER"
|
|
|
|
|
|
S3D_PLUGIN_MANAGER::S3D_PLUGIN_MANAGER()
|
|
{
|
|
// create the initial file filter list entry
|
|
m_FileFilters.emplace_back( _( "All Files" ) + wxT( " (*.*)|*.*" ) );
|
|
|
|
// discover and load plugins
|
|
loadPlugins();
|
|
|
|
#ifdef DEBUG
|
|
if( !m_ExtMap.empty() )
|
|
{
|
|
std::multimap< const wxString, KICAD_PLUGIN_LDR_3D* >::const_iterator sM = m_ExtMap.begin();
|
|
std::multimap< const wxString, KICAD_PLUGIN_LDR_3D* >::const_iterator eM = m_ExtMap.end();
|
|
wxLogTrace( MASK_3D_PLUGINMGR, wxT( " * Extension [plugin name]:\n" ) );
|
|
|
|
while( sM != eM )
|
|
{
|
|
wxLogTrace( MASK_3D_PLUGINMGR, wxT( " + '%s' [%s]\n" ), sM->first.GetData(),
|
|
sM->second->GetKicadPluginName() );
|
|
++sM;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
wxLogTrace( MASK_3D_PLUGINMGR, wxT( " * No plugins available\n" ) );
|
|
}
|
|
|
|
|
|
if( !m_FileFilters.empty() )
|
|
{
|
|
/// list of file filters
|
|
std::list< wxString >::const_iterator sFF = m_FileFilters.begin();
|
|
std::list< wxString >::const_iterator eFF = m_FileFilters.end();
|
|
wxLogTrace( MASK_3D_PLUGINMGR, wxT( " * File filters:\n" ) );
|
|
|
|
while( sFF != eFF )
|
|
{
|
|
wxLogTrace( MASK_3D_PLUGINMGR, wxT( " + '%s'\n" ), (*sFF).GetData() );
|
|
++sFF;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
wxLogTrace( MASK_3D_PLUGINMGR, wxT( " * No file filters available\n" ) );
|
|
}
|
|
#endif // DEBUG
|
|
}
|
|
|
|
|
|
S3D_PLUGIN_MANAGER::~S3D_PLUGIN_MANAGER()
|
|
{
|
|
std::list< KICAD_PLUGIN_LDR_3D* >::iterator sP = m_Plugins.begin();
|
|
std::list< KICAD_PLUGIN_LDR_3D* >::iterator eP = m_Plugins.end();
|
|
|
|
while( sP != eP )
|
|
{
|
|
(*sP)->Close();
|
|
delete *sP;
|
|
++sP;
|
|
}
|
|
|
|
m_Plugins.clear();
|
|
}
|
|
|
|
|
|
void S3D_PLUGIN_MANAGER::loadPlugins( void )
|
|
{
|
|
std::list< wxString > searchpaths;
|
|
std::list< wxString > pluginlist;
|
|
wxFileName fn;
|
|
|
|
#ifndef __WXMAC__
|
|
|
|
#ifdef DEBUG
|
|
// set up to work from the build directory
|
|
fn.Assign( wxStandardPaths::Get().GetExecutablePath() );
|
|
fn.AppendDir( wxT( ".." ) );
|
|
fn.AppendDir( wxT( "plugins" ) );
|
|
fn.AppendDir( wxT( "3d" ) );
|
|
|
|
std::string testpath = std::string( fn.GetPathWithSep().ToUTF8() );
|
|
checkPluginPath( testpath, searchpaths );
|
|
|
|
// add subdirectories too
|
|
wxDir debugPluginDir;
|
|
wxString subdir;
|
|
|
|
debugPluginDir.Open( testpath );
|
|
|
|
if( debugPluginDir.IsOpened() && debugPluginDir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS ) )
|
|
{
|
|
checkPluginPath( testpath + subdir, searchpaths );
|
|
|
|
while( debugPluginDir.GetNext( &subdir ) )
|
|
checkPluginPath( testpath + subdir, searchpaths );
|
|
}
|
|
#endif
|
|
|
|
fn.AssignDir( PATHS::GetStockPlugins3DPath() );
|
|
checkPluginPath( std::string( fn.GetPathWithSep().ToUTF8() ), searchpaths );
|
|
|
|
// check for per-user third party plugins
|
|
// note: GetUserDataDir() gives '.pcbnew' rather than '.kicad' since it uses the exe name;
|
|
fn.AssignDir( PATHS::GetUserPlugins3DPath() );
|
|
checkPluginPath( fn.GetPathWithSep(), searchpaths );
|
|
#else
|
|
|
|
// Search path on OS X is
|
|
// (1) User ~/Library/Application Support/kicad/PlugIns/3d
|
|
checkPluginPath( PATHS::GetOSXKicadUserDataDir() + wxT( "/PlugIns/3d" ), searchpaths );
|
|
|
|
// (2) Machine /Library/Application Support/kicad/PlugIns/3d
|
|
checkPluginPath( PATHS::GetOSXKicadMachineDataDir() + wxT( "/PlugIns/3d" ), searchpaths );
|
|
|
|
// (3) Bundle kicad.app/Contents/PlugIns/3d
|
|
fn.AssignDir( PATHS::GetStockPlugins3DPath() );
|
|
checkPluginPath( fn.GetPathWithSep(), searchpaths );
|
|
|
|
#endif
|
|
|
|
std::list< wxString >::iterator sPL = searchpaths.begin();
|
|
std::list< wxString >::iterator ePL = searchpaths.end();
|
|
|
|
while( sPL != ePL )
|
|
{
|
|
wxLogTrace( MASK_3D_PLUGINMGR, wxT( "%s:%s:%d * [DEBUG] searching path: '%s'" ),
|
|
__FILE__, __FUNCTION__, __LINE__, (*sPL).ToUTF8() );
|
|
|
|
listPlugins( *sPL, pluginlist );
|
|
++sPL;
|
|
}
|
|
|
|
if( pluginlist.empty() )
|
|
return;
|
|
|
|
sPL = pluginlist.begin();
|
|
ePL = pluginlist.end();
|
|
|
|
while( sPL != ePL )
|
|
{
|
|
KICAD_PLUGIN_LDR_3D* pp = new KICAD_PLUGIN_LDR_3D;
|
|
|
|
if( pp->Open( sPL->ToUTF8() ) )
|
|
{
|
|
wxLogTrace( MASK_3D_PLUGINMGR, wxT( "%s:%s:%d * [DEBUG] adding plugin" ),
|
|
__FILE__, __FUNCTION__, __LINE__ );
|
|
|
|
m_Plugins.push_back( pp );
|
|
int nf = pp->GetNFilters();
|
|
|
|
wxLogTrace( MASK_3D_PLUGINMGR, wxT( "%s:%s:%d * [DEBUG] adding %d filters" ),
|
|
__FILE__, __FUNCTION__, __LINE__, nf );
|
|
|
|
for( int i = 0; i < nf; ++i )
|
|
{
|
|
char const* cp = pp->GetFileFilter( i );
|
|
|
|
if( cp )
|
|
addFilterString( wxString::FromUTF8Unchecked( cp ) );
|
|
}
|
|
|
|
addExtensionMap( pp );
|
|
|
|
// close the loaded library
|
|
pp->Close();
|
|
}
|
|
else
|
|
{
|
|
wxLogTrace( MASK_3D_PLUGINMGR, wxT( "%s:%s:%d * [DEBUG] deleting plugin" ),
|
|
__FILE__, __FUNCTION__, __LINE__ );
|
|
|
|
delete pp;
|
|
}
|
|
|
|
++sPL;
|
|
}
|
|
|
|
wxLogTrace( MASK_3D_PLUGINMGR, wxT( "%s:%s:%d * [DEBUG] plugins loaded" ),
|
|
__FILE__, __FUNCTION__, __LINE__ );
|
|
}
|
|
|
|
|
|
void S3D_PLUGIN_MANAGER::listPlugins( const wxString& aPath, std::list< wxString >& aPluginList )
|
|
{
|
|
// list potential plugins given a search path
|
|
wxString nameFilter; // filter for user-loadable libraries (aka footprints)
|
|
wxString lName; // stores name of enumerated files
|
|
wxString fName; // full name of file
|
|
wxDir wd;
|
|
wd.Open( aPath );
|
|
|
|
if( !wd.IsOpened() )
|
|
return;
|
|
|
|
nameFilter = wxT( "*" );
|
|
|
|
#ifndef __WXMAC__
|
|
nameFilter.Append( wxDynamicLibrary::GetDllExt( wxDL_MODULE ) );
|
|
#else
|
|
// wxDynamicLibrary::GetDllExt( wxDL_MODULE ) will return ".bundle" on OS X.
|
|
// This might be correct, but cmake builds a ".so" for a library MODULE.
|
|
// 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( ".so" );
|
|
#endif
|
|
|
|
wxString lp = wd.GetNameWithSep();
|
|
|
|
if( wd.GetFirst( &lName, nameFilter, wxDIR_FILES ) )
|
|
{
|
|
fName = lp + lName;
|
|
checkPluginName( fName, aPluginList );
|
|
|
|
while( wd.GetNext( &lName ) )
|
|
{
|
|
fName = lp + lName;
|
|
checkPluginName( fName, aPluginList );
|
|
}
|
|
}
|
|
|
|
wd.Close();
|
|
}
|
|
|
|
|
|
void S3D_PLUGIN_MANAGER::checkPluginName( const wxString& aPath,
|
|
std::list< wxString >& aPluginList )
|
|
{
|
|
// check the existence of a plugin name and add it to the list
|
|
|
|
if( aPath.empty() || !wxFileName::FileExists( aPath ) )
|
|
return;
|
|
|
|
wxFileName path( ExpandEnvVarSubstitutions( aPath, nullptr ) );
|
|
|
|
path.Normalize();
|
|
|
|
// determine if the path is already in the list
|
|
wxString wxpath = path.GetFullPath();
|
|
std::list< wxString >::iterator bl = aPluginList.begin();
|
|
std::list< wxString >::iterator el = aPluginList.end();
|
|
|
|
while( bl != el )
|
|
{
|
|
if( 0 == (*bl).Cmp( wxpath ) )
|
|
return;
|
|
|
|
++bl;
|
|
}
|
|
|
|
aPluginList.push_back( wxpath );
|
|
|
|
wxLogTrace( MASK_3D_PLUGINMGR, wxT( " * [INFO] found 3D plugin '%s'\n" ), wxpath.GetData() );
|
|
}
|
|
|
|
|
|
void S3D_PLUGIN_MANAGER::checkPluginPath( const wxString& aPath,
|
|
std::list< wxString >& aSearchList )
|
|
{
|
|
// check the existence of a path and add it to the path search list
|
|
if( aPath.empty() )
|
|
return;
|
|
|
|
wxLogTrace( MASK_3D_PLUGINMGR, wxT( " * [INFO] checking for 3D plugins in '%s'\n" ),
|
|
aPath.GetData() );
|
|
|
|
wxFileName path;
|
|
|
|
if( aPath.StartsWith( wxT( "${" ) ) || aPath.StartsWith( wxT( "$(" ) ) )
|
|
path.Assign( ExpandEnvVarSubstitutions( aPath, nullptr ), wxEmptyString );
|
|
else
|
|
path.Assign( aPath, wxT( "" ) );
|
|
|
|
path.Normalize();
|
|
|
|
if( !wxFileName::DirExists( path.GetFullPath() ) )
|
|
return;
|
|
|
|
// determine if the directory is already in the list
|
|
wxString wxpath = path.GetFullPath();
|
|
std::list< wxString >::iterator bl = aSearchList.begin();
|
|
std::list< wxString >::iterator el = aSearchList.end();
|
|
|
|
while( bl != el )
|
|
{
|
|
if( 0 == (*bl).Cmp( wxpath ) )
|
|
return;
|
|
|
|
++bl;
|
|
}
|
|
|
|
aSearchList.push_back( wxpath );
|
|
}
|
|
|
|
|
|
void S3D_PLUGIN_MANAGER::addFilterString( const wxString& aFilterString )
|
|
{
|
|
// add an entry to the file filter list
|
|
if( aFilterString.empty() )
|
|
return;
|
|
|
|
std::list< wxString >::iterator sFF = m_FileFilters.begin();
|
|
std::list< wxString >::iterator eFF = m_FileFilters.end();
|
|
|
|
while( sFF != eFF )
|
|
{
|
|
if( 0 == (*sFF).Cmp( aFilterString ) )
|
|
return;
|
|
|
|
++sFF;
|
|
}
|
|
|
|
m_FileFilters.push_back( aFilterString );
|
|
return;
|
|
}
|
|
|
|
|
|
void S3D_PLUGIN_MANAGER::addExtensionMap( KICAD_PLUGIN_LDR_3D* aPlugin )
|
|
{
|
|
// add entries to the extension map
|
|
if( nullptr == aPlugin )
|
|
return;
|
|
|
|
int nExt = aPlugin->GetNExtensions();
|
|
|
|
wxLogTrace( MASK_3D_PLUGINMGR, wxT( "%s:%s:%d * [INFO] adding %d extensions" ),
|
|
__FILE__, __FUNCTION__, __LINE__, nExt );
|
|
|
|
for( int i = 0; i < nExt; ++i )
|
|
{
|
|
char const* cp = aPlugin->GetModelExtension( i );
|
|
wxString ws;
|
|
|
|
if( cp )
|
|
ws = wxString::FromUTF8Unchecked( cp );
|
|
|
|
if( !ws.empty() )
|
|
{
|
|
m_ExtMap.emplace( ws, aPlugin );
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
std::list< wxString > const* S3D_PLUGIN_MANAGER::GetFileFilters( void ) const noexcept
|
|
{
|
|
return &m_FileFilters;
|
|
}
|
|
|
|
|
|
SCENEGRAPH* S3D_PLUGIN_MANAGER::Load3DModel( const wxString& aFileName, std::string& aPluginInfo )
|
|
{
|
|
wxFileName raw( aFileName );
|
|
wxString ext_to_find = raw.GetExt();
|
|
|
|
#ifdef _WIN32
|
|
// note: plugins only have a lowercase filter within Windows; including an uppercase
|
|
// filter will result in duplicate file entries and should be avoided.
|
|
ext_to_find.MakeLower();
|
|
#endif
|
|
|
|
// .gz files are compressed versions that may have additional information in the previous extension
|
|
if( ext_to_find == wxT( "gz" ) )
|
|
{
|
|
wxFileName second( raw.GetName() );
|
|
ext_to_find = second.GetExt() + wxT( ".gz" );
|
|
}
|
|
|
|
std::pair < std::multimap< const wxString, KICAD_PLUGIN_LDR_3D* >::iterator,
|
|
std::multimap< const wxString, KICAD_PLUGIN_LDR_3D* >::iterator > items;
|
|
|
|
items = m_ExtMap.equal_range( ext_to_find );
|
|
std::multimap< const wxString, KICAD_PLUGIN_LDR_3D* >::iterator sL = items.first;
|
|
|
|
while( sL != items.second )
|
|
{
|
|
if( sL->second->CanRender() )
|
|
{
|
|
SCENEGRAPH* sp = sL->second->Load( aFileName.ToUTF8() );
|
|
|
|
if( nullptr != sp )
|
|
{
|
|
sL->second->GetPluginInfo( aPluginInfo );
|
|
return sp;
|
|
}
|
|
}
|
|
|
|
++sL;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
void S3D_PLUGIN_MANAGER::ClosePlugins( void )
|
|
{
|
|
std::list< KICAD_PLUGIN_LDR_3D* >::iterator sP = m_Plugins.begin();
|
|
std::list< KICAD_PLUGIN_LDR_3D* >::iterator eP = m_Plugins.end();
|
|
|
|
wxLogTrace( MASK_3D_PLUGINMGR, wxT( "%s:%s:%d * [INFO] closing %d extensions" ),
|
|
__FILE__, __FUNCTION__, __LINE__, static_cast<int>( m_Plugins.size() ) );
|
|
|
|
while( sP != eP )
|
|
{
|
|
(*sP)->Close();
|
|
++sP;
|
|
}
|
|
}
|
|
|
|
|
|
bool S3D_PLUGIN_MANAGER::CheckTag( const char* aTag )
|
|
{
|
|
if( nullptr == aTag || aTag[0] == 0 || m_Plugins.empty() )
|
|
return false;
|
|
|
|
std::string tname = aTag;
|
|
std::string pname; // plugin name
|
|
|
|
size_t cpos = tname.find( ':' );
|
|
|
|
// if there is no colon or plugin name then the tag is bad
|
|
if( cpos == std::string::npos || cpos == 0 )
|
|
return false;
|
|
|
|
pname = tname.substr( 0, cpos );
|
|
std::string ptag; // tag from the plugin
|
|
|
|
std::list< KICAD_PLUGIN_LDR_3D* >::iterator pS = m_Plugins.begin();
|
|
std::list< KICAD_PLUGIN_LDR_3D* >::iterator pE = m_Plugins.end();
|
|
|
|
while( pS != pE )
|
|
{
|
|
ptag.clear();
|
|
(*pS)->GetPluginInfo( ptag );
|
|
|
|
// if the plugin name matches then the version
|
|
// must also match
|
|
if( !ptag.compare( 0, pname.size(), pname ) )
|
|
{
|
|
if( ptag.compare( tname ) )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
++pS;
|
|
}
|
|
|
|
return true;
|
|
}
|