kicad/3d-viewer/3d_cache/3d_plugin_manager.cpp

586 lines
16 KiB
C++
Raw Normal View History

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2015-2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
*
* 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/config.h>
#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 "pgm_base.h"
#include "3d_plugin_dir.h"
#include "3d_plugin_manager.h"
#include "plugins/3d/3d_plugin.h"
#include "3d_cache/sg/scenegraph.h"
#include "plugins/ldr/3d/pluginldr3D.h"
#define MASK_3D_PLUGINMGR "3D_PLUGIN_MANAGER"
S3D_PLUGIN_MANAGER::S3D_PLUGIN_MANAGER()
{
// create the initial file filter list entry
m_FileFilters.push_back( _( "All Files (*.*)|*.*" ) );
// 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, " * Extension [plugin name]:\n" );
while( sM != eM )
{
wxLogTrace( MASK_3D_PLUGINMGR, " + '%s' [%s]\n", sM->first.GetData(),
sM->second->GetKicadPluginName() );
++sM;
}
}
else
{
wxLogTrace( MASK_3D_PLUGINMGR, " * 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, " * File filters:\n" );
while( sFF != eFF )
{
wxLogTrace( MASK_3D_PLUGINMGR, " + '%s'\n", (*sFF).GetData() );
++sFF;
}
}
else
{
wxLogTrace( MASK_3D_PLUGINMGR, " * No file filters available\n" );
}
#endif // DEBUG
return;
}
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();
return;
}
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( testpath );
wxString subdir;
if( debugPluginDir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS ) )
{
checkPluginPath( testpath + subdir, searchpaths );
while( debugPluginDir.GetNext( &subdir ) )
checkPluginPath( testpath + subdir, searchpaths );
}
#endif
#ifndef _WIN32
// multiarch friendly determination of the plugin directory: the executable dir
// is first determined via wxStandardPaths::Get().GetExecutablePath() and then
// the CMAKE_INSTALL_LIBDIR path is appended relative to the executable dir.
fn.Assign( wxStandardPaths::Get().GetExecutablePath() );
fn.RemoveLastDir();
wxString tfname = fn.GetPathWithSep();
tfname.Append( wxString::FromUTF8Unchecked( PLUGINDIR ) );
fn.Assign( tfname, "");
fn.AppendDir( "kicad" );
2016-01-06 09:05:29 +00:00
#else
// on windows the plugins directory is within the executable's directory
2016-01-06 09:05:29 +00:00
fn.Assign( wxStandardPaths::Get().GetExecutablePath() );
#endif
2016-01-06 09:05:29 +00:00
fn.AppendDir( wxT( "plugins" ) );
fn.AppendDir( wxT( "3d" ) );
// checks plugin directory relative to executable path
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.Assign( wxStandardPaths::Get().GetUserDataDir(), "" );
fn.RemoveLastDir();
#ifdef _WIN32
fn.AppendDir( wxT( "kicad" ) );
#else
fn.AppendDir( wxT( ".kicad" ) );
#endif
fn.AppendDir( wxT( "plugins" ) );
fn.AppendDir( wxT( "3d" ) );
checkPluginPath( fn.GetPathWithSep(), searchpaths );
#else
// Search path on OS X is
// (1) User ~/Library/Application Support/kicad/PlugIns/3d
checkPluginPath( GetOSXKicadUserDataDir() + wxT( "/PlugIns/3d" ), searchpaths );
// (2) Machine /Library/Application Support/kicad/PlugIns/3d
checkPluginPath( GetOSXKicadMachineDataDir() + wxT( "/PlugIns/3d" ), searchpaths );
// (3) Bundle kicad.app/Contents/PlugIns/3d
fn.Assign( Pgm().GetExecutablePath() );
fn.AppendDir( wxT( "Contents" ) );
fn.AppendDir( wxT( "PlugIns" ) );
fn.AppendDir( wxT( "3d" ) );
checkPluginPath( fn.GetPathWithSep(), searchpaths );
#endif
std::list< wxString >::iterator sPL = searchpaths.begin();
std::list< wxString >::iterator ePL = searchpaths.end();
while( sPL != ePL )
{
#ifdef DEBUG
do {
std::ostringstream ostr;
ostr << __FILE__ << ":" << __FUNCTION__ << ":" << __LINE__ << ":\n";
ostr << " * [DEBUG] searching path: '" << (*sPL).ToUTF8() << "'";
wxLogTrace( MASK_3D_PLUGINMGR, "%s\n", ostr.str().c_str() );
} while( 0 );
#endif
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() ) )
{
#ifdef DEBUG
do {
std::ostringstream ostr;
ostr << __FILE__ << ":" << __FUNCTION__ << ":" << __LINE__ << ":\n";
ostr << "* [DEBUG] adding plugin";
wxLogTrace( MASK_3D_PLUGINMGR, "%s\n", ostr.str().c_str() );
} while( 0 );
#endif
m_Plugins.push_back( pp );
int nf = pp->GetNFilters();
#ifdef DEBUG
do {
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * [INFO] adding " << nf << " filters";
wxLogTrace( MASK_3D_PLUGINMGR, "%s\n", ostr.str().c_str() );
} while( 0 );
#endif
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
{
#ifdef DEBUG
do {
std::ostringstream ostr;
ostr << __FILE__ << ":" << __FUNCTION__ << ":" << __LINE__ << ":\n";
ostr << "* [DEBUG] deleting plugin";
wxLogTrace( MASK_3D_PLUGINMGR, "%s\n", ostr.str().c_str() );
} while( 0 );
#endif
delete pp;
}
++sPL;
}
#ifdef DEBUG
do {
std::ostringstream ostr;
ostr << __FILE__ << ":" << __FUNCTION__ << ":" << __LINE__ << ":\n";
ostr << "* [DEBUG] plugins loaded";
wxLogTrace( MASK_3D_PLUGINMGR, "%s\n", ostr.str().c_str() );
} while( 0 );
#endif
return;
}
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 modules)
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".
2016-06-09 05:48:49 +00:00
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();
return;
}
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;
2016-06-09 05:48:49 +00:00
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
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 );
#ifdef DEBUG
wxLogTrace( MASK_3D_PLUGINMGR, " * [INFO] found 3D plugin '%s'\n",
wxpath.GetData() );
#endif
return;
}
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;
#ifdef DEBUG
wxLogTrace( MASK_3D_PLUGINMGR, " * [INFO] checking for 3D plugins in '%s'\n",
aPath.GetData() );
#endif
2016-06-09 05:48:49 +00:00
wxFileName path;
if( aPath.StartsWith( "${" ) || aPath.StartsWith( "$(" ) )
path.Assign( ExpandEnvVarSubstitutions( aPath ), "" );
else
path.Assign( aPath, "" );
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 );
return;
}
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( NULL == aPlugin )
return;
int nExt = aPlugin->GetNExtensions();
#ifdef DEBUG
do {
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * [INFO] adding " << nExt << " extensions";
wxLogTrace( MASK_3D_PLUGINMGR, "%s\n", ostr.str().c_str() );
} while( 0 );
#endif
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.insert( std::pair< const wxString, KICAD_PLUGIN_LDR_3D* >( ws, aPlugin ) );
}
}
return;
}
std::list< wxString > const* S3D_PLUGIN_MANAGER::GetFileFilters( void ) const
{
return &m_FileFilters;
}
SCENEGRAPH* S3D_PLUGIN_MANAGER::Load3DModel( const wxString& aFileName, std::string& aPluginInfo )
{
wxFileName raw( aFileName );
wxString ext = 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.LowerCase();
#endif
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 );
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( NULL != sp )
{
sL->second->GetPluginInfo( aPluginInfo );
return sp;
}
}
++sL;
}
return NULL;
}
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();
#ifdef DEBUG
do {
std::ostringstream ostr;
ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
ostr << " * [INFO] closing " << m_Plugins.size() << " plugins";
wxLogTrace( MASK_3D_PLUGINMGR, "%s\n", ostr.str().c_str() );
} while( 0 );
#endif
while( sP != eP )
{
(*sP)->Close();
++sP;
}
return;
}
bool S3D_PLUGIN_MANAGER::CheckTag( const char* aTag )
{
if( NULL == 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;
}