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