KiCad PCB EDA Suite
3d_plugin_manager.cpp
Go to the documentation of this file.
1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2015-2016 Cirilo Bernardo <[email protected]>
5  * Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, you may find one here:
19  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  * or you may search the http://www.gnu.org website for the version 2 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23  */
24 
25 
26 #include <utility>
27 #include <iostream>
28 #include <sstream>
29 
30 #include <wx/dir.h>
31 #include <wx/dynlib.h>
32 #include <wx/filename.h>
33 #include <wx/log.h>
34 #include <wx/stdpaths.h>
35 #include <wx/string.h>
36 
37 #include <common.h>
38 #include <paths.h>
39 #include "3d_plugin_manager.h"
40 #include "plugins/3d/3d_plugin.h"
41 #include "3d_cache/sg/scenegraph.h"
43 
44 
52 #define MASK_3D_PLUGINMGR "3D_PLUGIN_MANAGER"
53 
54 
56 {
57  // create the initial file filter list entry
58  m_FileFilters.push_back( _( "All Files" ) + wxT( " (*.*)|*.*" ) );
59 
60  // discover and load plugins
61  loadPlugins();
62 
63 #ifdef DEBUG
64  if( !m_ExtMap.empty() )
65  {
66  std::multimap< const wxString, KICAD_PLUGIN_LDR_3D* >::const_iterator sM = m_ExtMap.begin();
67  std::multimap< const wxString, KICAD_PLUGIN_LDR_3D* >::const_iterator eM = m_ExtMap.end();
68  wxLogTrace( MASK_3D_PLUGINMGR, wxT( " * Extension [plugin name]:\n" ) );
69 
70  while( sM != eM )
71  {
72  wxLogTrace( MASK_3D_PLUGINMGR, wxT( " + '%s' [%s]\n" ), sM->first.GetData(),
73  sM->second->GetKicadPluginName() );
74  ++sM;
75  }
76 
77  }
78  else
79  {
80  wxLogTrace( MASK_3D_PLUGINMGR, wxT( " * No plugins available\n" ) );
81  }
82 
83 
84  if( !m_FileFilters.empty() )
85  {
87  std::list< wxString >::const_iterator sFF = m_FileFilters.begin();
88  std::list< wxString >::const_iterator eFF = m_FileFilters.end();
89  wxLogTrace( MASK_3D_PLUGINMGR, wxT( " * File filters:\n" ) );
90 
91  while( sFF != eFF )
92  {
93  wxLogTrace( MASK_3D_PLUGINMGR, wxT( " + '%s'\n" ), (*sFF).GetData() );
94  ++sFF;
95  }
96  }
97  else
98  {
99  wxLogTrace( MASK_3D_PLUGINMGR, wxT( " * No file filters available\n" ) );
100  }
101 #endif // DEBUG
102 }
103 
104 
106 {
107  std::list< KICAD_PLUGIN_LDR_3D* >::iterator sP = m_Plugins.begin();
108  std::list< KICAD_PLUGIN_LDR_3D* >::iterator eP = m_Plugins.end();
109 
110  while( sP != eP )
111  {
112  (*sP)->Close();
113  delete *sP;
114  ++sP;
115  }
116 
117  m_Plugins.clear();
118 }
119 
120 
122 {
123  std::list< wxString > searchpaths;
124  std::list< wxString > pluginlist;
125  wxFileName fn;
126 
127 #ifndef __WXMAC__
128 
129 #ifdef DEBUG
130  // set up to work from the build directory
131  fn.Assign( wxStandardPaths::Get().GetExecutablePath() );
132  fn.AppendDir( wxT( ".." ) );
133  fn.AppendDir( wxT( "plugins" ) );
134  fn.AppendDir( wxT( "3d" ) );
135 
136  std::string testpath = std::string( fn.GetPathWithSep().ToUTF8() );
137  checkPluginPath( testpath, searchpaths );
138 
139  // add subdirectories too
140  wxDir debugPluginDir;
141  wxString subdir;
142 
143  debugPluginDir.Open( testpath );
144 
145  if( debugPluginDir.IsOpened() && debugPluginDir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS ) )
146  {
147  checkPluginPath( testpath + subdir, searchpaths );
148 
149  while( debugPluginDir.GetNext( &subdir ) )
150  checkPluginPath( testpath + subdir, searchpaths );
151  }
152 #endif
153 
154  fn.AssignDir( PATHS::GetStockPlugins3DPath() );
155  checkPluginPath( std::string( fn.GetPathWithSep().ToUTF8() ), searchpaths );
156 
157  // check for per-user third party plugins
158  // note: GetUserDataDir() gives '.pcbnew' rather than '.kicad' since it uses the exe name;
159  fn.AssignDir( PATHS::GetUserPlugins3DPath() );
160  checkPluginPath( fn.GetPathWithSep(), searchpaths );
161 #else
162 
163  // Search path on OS X is
164  // (1) User ~/Library/Application Support/kicad/PlugIns/3d
165  checkPluginPath( PATHS::GetOSXKicadUserDataDir() + wxT( "/PlugIns/3d" ), searchpaths );
166 
167  // (2) Machine /Library/Application Support/kicad/PlugIns/3d
168  checkPluginPath( PATHS::GetOSXKicadMachineDataDir() + wxT( "/PlugIns/3d" ), searchpaths );
169 
170  // (3) Bundle kicad.app/Contents/PlugIns/3d
171  fn.AssignDir( PATHS::GetStockPlugins3DPath() );
172  checkPluginPath( fn.GetPathWithSep(), searchpaths );
173 
174 #endif
175 
176  std::list< wxString >::iterator sPL = searchpaths.begin();
177  std::list< wxString >::iterator ePL = searchpaths.end();
178 
179  while( sPL != ePL )
180  {
181  wxLogTrace( MASK_3D_PLUGINMGR, wxT( "%s:%s:%d * [DEBUG] searching path: '%s'" ),
182  __FILE__, __FUNCTION__, __LINE__, (*sPL).ToUTF8() );
183 
184  listPlugins( *sPL, pluginlist );
185  ++sPL;
186  }
187 
188  if( pluginlist.empty() )
189  return;
190 
191  sPL = pluginlist.begin();
192  ePL = pluginlist.end();
193 
194  while( sPL != ePL )
195  {
197 
198  if( pp->Open( sPL->ToUTF8() ) )
199  {
200  wxLogTrace( MASK_3D_PLUGINMGR, wxT( "%s:%s:%d * [DEBUG] adding plugin" ),
201  __FILE__, __FUNCTION__, __LINE__ );
202 
203  m_Plugins.push_back( pp );
204  int nf = pp->GetNFilters();
205 
206  wxLogTrace( MASK_3D_PLUGINMGR, wxT( "%s:%s:%d * [DEBUG] adding %d filters" ),
207  __FILE__, __FUNCTION__, __LINE__, nf );
208 
209  for( int i = 0; i < nf; ++i )
210  {
211  char const* cp = pp->GetFileFilter( i );
212 
213  if( cp )
214  addFilterString( wxString::FromUTF8Unchecked( cp ) );
215  }
216 
217  addExtensionMap( pp );
218 
219  // close the loaded library
220  pp->Close();
221  }
222  else
223  {
224  wxLogTrace( MASK_3D_PLUGINMGR, wxT( "%s:%s:%d * [DEBUG] deleting plugin" ),
225  __FILE__, __FUNCTION__, __LINE__ );
226 
227  delete pp;
228  }
229 
230  ++sPL;
231  }
232 
233  wxLogTrace( MASK_3D_PLUGINMGR, wxT( "%s:%s:%d * [DEBUG] plugins loaded" ),
234  __FILE__, __FUNCTION__, __LINE__ );
235 }
236 
237 
238 void S3D_PLUGIN_MANAGER::listPlugins( const wxString& aPath, std::list< wxString >& aPluginList )
239 {
240  // list potential plugins given a search path
241  wxString nameFilter; // filter for user-loadable libraries (aka footprints)
242  wxString lName; // stores name of enumerated files
243  wxString fName; // full name of file
244  wxDir wd;
245  wd.Open( aPath );
246 
247  if( !wd.IsOpened() )
248  return;
249 
250  nameFilter = wxT( "*" );
251 
252 #ifndef __WXMAC__
253  nameFilter.Append( wxDynamicLibrary::GetDllExt( wxDL_MODULE ) );
254 #else
255  // wxDynamicLibrary::GetDllExt( wxDL_MODULE ) will return ".bundle" on OS X.
256  // This might be correct, but cmake builds a ".so" for a library MODULE.
257  // Per definition a loadable "xxx.bundle" is similar to an "xxx.app" app
258  // bundle being a folder with some special content in it. We obviously don't
259  // want to have that here for our loadable module, so just use ".so".
260  nameFilter.Append( ".so" );
261 #endif
262 
263  wxString lp = wd.GetNameWithSep();
264 
265  if( wd.GetFirst( &lName, nameFilter, wxDIR_FILES ) )
266  {
267  fName = lp + lName;
268  checkPluginName( fName, aPluginList );
269 
270  while( wd.GetNext( &lName ) )
271  {
272  fName = lp + lName;
273  checkPluginName( fName, aPluginList );
274  }
275  }
276 
277  wd.Close();
278 }
279 
280 
281 void S3D_PLUGIN_MANAGER::checkPluginName( const wxString& aPath,
282  std::list< wxString >& aPluginList )
283 {
284  // check the existence of a plugin name and add it to the list
285 
286  if( aPath.empty() || !wxFileName::FileExists( aPath ) )
287  return;
288 
289  wxFileName path( ExpandEnvVarSubstitutions( aPath, nullptr ) );
290 
291  path.Normalize();
292 
293  // determine if the path is already in the list
294  wxString wxpath = path.GetFullPath();
295  std::list< wxString >::iterator bl = aPluginList.begin();
296  std::list< wxString >::iterator el = aPluginList.end();
297 
298  while( bl != el )
299  {
300  if( 0 == (*bl).Cmp( wxpath ) )
301  return;
302 
303  ++bl;
304  }
305 
306  aPluginList.push_back( wxpath );
307 
308  wxLogTrace( MASK_3D_PLUGINMGR, wxT( " * [INFO] found 3D plugin '%s'\n" ), wxpath.GetData() );
309 }
310 
311 
312 void S3D_PLUGIN_MANAGER::checkPluginPath( const wxString& aPath,
313  std::list< wxString >& aSearchList )
314 {
315  // check the existence of a path and add it to the path search list
316  if( aPath.empty() )
317  return;
318 
319  wxLogTrace( MASK_3D_PLUGINMGR, wxT( " * [INFO] checking for 3D plugins in '%s'\n" ),
320  aPath.GetData() );
321 
322  wxFileName path;
323 
324  if( aPath.StartsWith( wxT( "${" ) ) || aPath.StartsWith( wxT( "$(" ) ) )
325  path.Assign( ExpandEnvVarSubstitutions( aPath, nullptr ), wxEmptyString );
326  else
327  path.Assign( aPath, wxEmptyString );
328 
329  path.Normalize();
330 
331  if( !wxFileName::DirExists( path.GetFullPath() ) )
332  return;
333 
334  // determine if the directory is already in the list
335  wxString wxpath = path.GetFullPath();
336  std::list< wxString >::iterator bl = aSearchList.begin();
337  std::list< wxString >::iterator el = aSearchList.end();
338 
339  while( bl != el )
340  {
341  if( 0 == (*bl).Cmp( wxpath ) )
342  return;
343 
344  ++bl;
345  }
346 
347  aSearchList.push_back( wxpath );
348 }
349 
350 
351 void S3D_PLUGIN_MANAGER::addFilterString( const wxString& aFilterString )
352 {
353  // add an entry to the file filter list
354  if( aFilterString.empty() )
355  return;
356 
357  std::list< wxString >::iterator sFF = m_FileFilters.begin();
358  std::list< wxString >::iterator eFF = m_FileFilters.end();
359 
360  while( sFF != eFF )
361  {
362  if( 0 == (*sFF).Cmp( aFilterString ) )
363  return;
364 
365  ++sFF;
366  }
367 
368  m_FileFilters.push_back( aFilterString );
369  return;
370 }
371 
372 
374 {
375  // add entries to the extension map
376  if( nullptr == aPlugin )
377  return;
378 
379  int nExt = aPlugin->GetNExtensions();
380 
381  wxLogTrace( MASK_3D_PLUGINMGR, wxT( "%s:%s:%d * [INFO] adding %d extensions" ),
382  __FILE__, __FUNCTION__, __LINE__, nExt );
383 
384  for( int i = 0; i < nExt; ++i )
385  {
386  char const* cp = aPlugin->GetModelExtension( i );
387  wxString ws;
388 
389  if( cp )
390  ws = wxString::FromUTF8Unchecked( cp );
391 
392  if( !ws.empty() )
393  {
394  m_ExtMap.insert( std::pair< const wxString, KICAD_PLUGIN_LDR_3D* >( ws, aPlugin ) );
395  }
396 
397  }
398 }
399 
400 
401 std::list< wxString > const* S3D_PLUGIN_MANAGER::GetFileFilters( void ) const noexcept
402 {
403  return &m_FileFilters;
404 }
405 
406 
407 SCENEGRAPH* S3D_PLUGIN_MANAGER::Load3DModel( const wxString& aFileName, std::string& aPluginInfo )
408 {
409  wxFileName raw( aFileName );
410  wxString ext_to_find = raw.GetExt();
411 
412 #ifdef _WIN32
413  // note: plugins only have a lowercase filter within Windows; including an uppercase
414  // filter will result in duplicate file entries and should be avoided.
415  ext_to_find.MakeLower();
416 #endif
417 
418  // .gz files are compressed versions that may have additional information in the previous extension
419  if( ext_to_find == wxT( "gz" ) )
420  {
421  wxFileName second( raw.GetName() );
422  ext_to_find = second.GetExt() + wxT( ".gz" );
423  }
424 
425  std::pair < std::multimap< const wxString, KICAD_PLUGIN_LDR_3D* >::iterator,
426  std::multimap< const wxString, KICAD_PLUGIN_LDR_3D* >::iterator > items;
427 
428  items = m_ExtMap.equal_range( ext_to_find );
429  std::multimap< const wxString, KICAD_PLUGIN_LDR_3D* >::iterator sL = items.first;
430 
431  while( sL != items.second )
432  {
433  if( sL->second->CanRender() )
434  {
435  SCENEGRAPH* sp = sL->second->Load( aFileName.ToUTF8() );
436 
437  if( nullptr != sp )
438  {
439  sL->second->GetPluginInfo( aPluginInfo );
440  return sp;
441  }
442  }
443 
444  ++sL;
445  }
446 
447  return nullptr;
448 }
449 
450 
452 {
453  std::list< KICAD_PLUGIN_LDR_3D* >::iterator sP = m_Plugins.begin();
454  std::list< KICAD_PLUGIN_LDR_3D* >::iterator eP = m_Plugins.end();
455 
456  wxLogTrace( MASK_3D_PLUGINMGR, wxT( "%s:%s:%d * [INFO] closing %d extensions" ),
457  __FILE__, __FUNCTION__, __LINE__, static_cast<int>( m_Plugins.size() ) );
458 
459  while( sP != eP )
460  {
461  (*sP)->Close();
462  ++sP;
463  }
464 }
465 
466 
467 bool S3D_PLUGIN_MANAGER::CheckTag( const char* aTag )
468 {
469  if( nullptr == aTag || aTag[0] == 0 || m_Plugins.empty() )
470  return false;
471 
472  std::string tname = aTag;
473  std::string pname; // plugin name
474 
475  size_t cpos = tname.find( ':' );
476 
477  // if there is no colon or plugin name then the tag is bad
478  if( cpos == std::string::npos || cpos == 0 )
479  return false;
480 
481  pname = tname.substr( 0, cpos );
482  std::string ptag; // tag from the plugin
483 
484  std::list< KICAD_PLUGIN_LDR_3D* >::iterator pS = m_Plugins.begin();
485  std::list< KICAD_PLUGIN_LDR_3D* >::iterator pE = m_Plugins.end();
486 
487  while( pS != pE )
488  {
489  ptag.clear();
490  (*pS)->GetPluginInfo( ptag );
491 
492  // if the plugin name matches then the version
493  // must also match
494  if( !ptag.compare( 0, pname.size(), pname ) )
495  {
496  if( ptag.compare( tname ) )
497  return false;
498 
499  return true;
500  }
501 
502  ++pS;
503  }
504 
505  return true;
506 }
void Close(void) override
Clean up and closes/unloads the plugin.
std::list< wxString > m_FileFilters
list of file filters
std::multimap< const wxString, KICAD_PLUGIN_LDR_3D * > m_ExtMap
mapping of extensions to available plugins
static wxString GetStockPlugins3DPath()
Gets the stock (install) 3d viewer plugins path.
Definition: paths.cpp:278
bool Open(const wxString &aFullFileName) override
Open a plugin of the given class, performs version compatibility checks, and links all required funct...
Definition: pluginldr3D.cpp:61
const wxString ExpandEnvVarSubstitutions(const wxString &aString, PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition: common.cpp:267
void addFilterString(const wxString &aFilterString)
add an entry to the file filter list
describes the runtime-loadable interface to support loading and parsing of 3D models.
SCENEGRAPH * Load3DModel(const wxString &aFileName, std::string &aPluginInfo)
void listPlugins(const wxString &aPath, std::list< wxString > &aPluginList)
list potential plugins
int GetNExtensions(void)
static wxString GetUserPlugins3DPath()
Gets the user path for 3d viewer plugin.
Definition: paths.cpp:64
std::list< KICAD_PLUGIN_LDR_3D * > m_Plugins
list of discovered plugins
char const * GetFileFilter(int aIndex)
#define MASK_3D_PLUGINMGR
Flag to enable 3D plugin manager debug tracing.
void checkPluginPath(const wxString &aPath, std::list< wxString > &aSearchList)
check the existence of a path and add it to the path search list
#define _(s)
char const * GetModelExtension(int aIndex)
bool CheckTag(const char *aTag)
Check the given tag and returns true if the plugin named in the tag is not loaded or the plugin is lo...
std::list< wxString > const * GetFileFilters(void) const noexcept
Return the list of file filters; this will contain at least the default "All Files (*....
void addExtensionMap(KICAD_PLUGIN_LDR_3D *aPlugin)
add entries to the extension map
manages 3D model plugins
void loadPlugins(void)
load plugins
The common library.
void ClosePlugins(void)
Iterate through all discovered plugins and closes them to reclaim memory.
Define the basic data set required to represent a 3D model.
Definition: scenegraph.h:44
void checkPluginName(const wxString &aPath, std::list< wxString > &aPluginList)
check the existence of a plugin name and add it to the list