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  * 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 <pgm_base.h>
40 #include "3d_plugin_manager.h"
41 #include "plugins/3d/3d_plugin.h"
42 #include "3d_cache/sg/scenegraph.h"
44 
45 
53 #define MASK_3D_PLUGINMGR "3D_PLUGIN_MANAGER"
54 
55 
57 {
58  // create the initial file filter list entry
59  m_FileFilters.push_back( _( "All Files (*.*)|*.*" ) );
60 
61  // discover and load plugins
62  loadPlugins();
63 
64 #ifdef DEBUG
65  if( !m_ExtMap.empty() )
66  {
67  std::multimap< const wxString, KICAD_PLUGIN_LDR_3D* >::const_iterator sM = m_ExtMap.begin();
68  std::multimap< const wxString, KICAD_PLUGIN_LDR_3D* >::const_iterator eM = m_ExtMap.end();
69  wxLogTrace( MASK_3D_PLUGINMGR, " * Extension [plugin name]:\n" );
70 
71  while( sM != eM )
72  {
73  wxLogTrace( MASK_3D_PLUGINMGR, " + '%s' [%s]\n", sM->first.GetData(),
74  sM->second->GetKicadPluginName() );
75  ++sM;
76  }
77 
78  }
79  else
80  {
81  wxLogTrace( MASK_3D_PLUGINMGR, " * No plugins available\n" );
82  }
83 
84 
85  if( !m_FileFilters.empty() )
86  {
88  std::list< wxString >::const_iterator sFF = m_FileFilters.begin();
89  std::list< wxString >::const_iterator eFF = m_FileFilters.end();
90  wxLogTrace( MASK_3D_PLUGINMGR, " * File filters:\n" );
91 
92  while( sFF != eFF )
93  {
94  wxLogTrace( MASK_3D_PLUGINMGR, " + '%s'\n", (*sFF).GetData() );
95  ++sFF;
96  }
97  }
98  else
99  {
100  wxLogTrace( MASK_3D_PLUGINMGR, " * No file filters available\n" );
101  }
102 #endif // DEBUG
103 }
104 
105 
107 {
108  std::list< KICAD_PLUGIN_LDR_3D* >::iterator sP = m_Plugins.begin();
109  std::list< KICAD_PLUGIN_LDR_3D* >::iterator eP = m_Plugins.end();
110 
111  while( sP != eP )
112  {
113  (*sP)->Close();
114  delete *sP;
115  ++sP;
116  }
117 
118  m_Plugins.clear();
119 }
120 
121 
123 {
124  std::list< wxString > searchpaths;
125  std::list< wxString > pluginlist;
126  wxFileName fn;
127 
128 #ifndef __WXMAC__
129 
130 #ifdef DEBUG
131  // set up to work from the build directory
132  fn.Assign( wxStandardPaths::Get().GetExecutablePath() );
133  fn.AppendDir( wxT( ".." ) );
134  fn.AppendDir( wxT( "plugins" ) );
135  fn.AppendDir( wxT( "3d" ) );
136 
137  std::string testpath = std::string( fn.GetPathWithSep().ToUTF8() );
138  checkPluginPath( testpath, searchpaths );
139 
140  // add subdirectories too
141  wxDir debugPluginDir;
142  wxString subdir;
143 
144  debugPluginDir.Open( testpath );
145 
146  if( debugPluginDir.IsOpened() && debugPluginDir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS ) )
147  {
148  checkPluginPath( testpath + subdir, searchpaths );
149 
150  while( debugPluginDir.GetNext( &subdir ) )
151  checkPluginPath( testpath + subdir, searchpaths );
152  }
153 #endif
154 
155  fn.AssignDir( PATHS::GetStockPlugins3DPath() );
156  checkPluginPath( std::string( fn.GetPathWithSep().ToUTF8() ), searchpaths );
157 
158  // check for per-user third party plugins
159  // note: GetUserDataDir() gives '.pcbnew' rather than '.kicad' since it uses the exe name;
160  fn.AssignDir( PATHS::GetUserPlugins3DPath() );
161  checkPluginPath( fn.GetPathWithSep(), searchpaths );
162 #else
163 
164  // Search path on OS X is
165  // (1) User ~/Library/Application Support/kicad/PlugIns/3d
166  checkPluginPath( PATHS::GetOSXKicadUserDataDir() + wxT( "/PlugIns/3d" ), searchpaths );
167 
168  // (2) Machine /Library/Application Support/kicad/PlugIns/3d
169  checkPluginPath( PATHS::GetOSXKicadMachineDataDir() + wxT( "/PlugIns/3d" ), searchpaths );
170 
171  // (3) Bundle kicad.app/Contents/PlugIns/3d
172  fn.AssignDir( PATHS::GetStockPlugins3DPath() );
173  checkPluginPath( fn.GetPathWithSep(), searchpaths );
174 
175 #endif
176 
177  std::list< wxString >::iterator sPL = searchpaths.begin();
178  std::list< wxString >::iterator ePL = searchpaths.end();
179 
180  while( sPL != ePL )
181  {
182  wxLogTrace( MASK_3D_PLUGINMGR, "%s:%s:%d * [DEBUG] searching path: '%s'",
183  __FILE__, __FUNCTION__, __LINE__, (*sPL).ToUTF8() );
184 
185  listPlugins( *sPL, pluginlist );
186  ++sPL;
187  }
188 
189  if( pluginlist.empty() )
190  return;
191 
192  sPL = pluginlist.begin();
193  ePL = pluginlist.end();
194 
195  while( sPL != ePL )
196  {
198 
199  if( pp->Open( sPL->ToUTF8() ) )
200  {
201  wxLogTrace( MASK_3D_PLUGINMGR, "%s:%s:%d * [DEBUG] adding plugin",
202  __FILE__, __FUNCTION__, __LINE__ );
203 
204  m_Plugins.push_back( pp );
205  int nf = pp->GetNFilters();
206 
207  wxLogTrace( MASK_3D_PLUGINMGR, "%s:%s:%d * [DEBUG] adding %d filters",
208  __FILE__, __FUNCTION__, __LINE__, nf );
209 
210  for( int i = 0; i < nf; ++i )
211  {
212  char const* cp = pp->GetFileFilter( i );
213 
214  if( cp )
215  addFilterString( wxString::FromUTF8Unchecked( cp ) );
216  }
217 
218  addExtensionMap( pp );
219 
220  // close the loaded library
221  pp->Close();
222  }
223  else
224  {
225  wxLogTrace( MASK_3D_PLUGINMGR, "%s:%s:%d * [DEBUG] deleting plugin",
226  __FILE__, __FUNCTION__, __LINE__ );
227 
228  delete pp;
229  }
230 
231  ++sPL;
232  }
233 
234  wxLogTrace( MASK_3D_PLUGINMGR, "%s:%s:%d * [DEBUG] plugins loaded",
235  __FILE__, __FUNCTION__, __LINE__ );
236 }
237 
238 
239 void S3D_PLUGIN_MANAGER::listPlugins( const wxString& aPath, std::list< wxString >& aPluginList )
240 {
241  // list potential plugins given a search path
242  wxString nameFilter; // filter for user-loadable libraries (aka footprints)
243  wxString lName; // stores name of enumerated files
244  wxString fName; // full name of file
245  wxDir wd;
246  wd.Open( aPath );
247 
248  if( !wd.IsOpened() )
249  return;
250 
251  nameFilter = wxT( "*" );
252 
253 #ifndef __WXMAC__
254  nameFilter.Append( wxDynamicLibrary::GetDllExt( wxDL_MODULE ) );
255 #else
256  // wxDynamicLibrary::GetDllExt( wxDL_MODULE ) will return ".bundle" on OS X.
257  // This might be correct, but cmake builds a ".so" for a library MODULE.
258  // Per definition a loadable "xxx.bundle" is similar to an "xxx.app" app
259  // bundle being a folder with some special content in it. We obviously don't
260  // want to have that here for our loadable module, so just use ".so".
261  nameFilter.Append( ".so" );
262 #endif
263 
264  wxString lp = wd.GetNameWithSep();
265 
266  if( wd.GetFirst( &lName, nameFilter, wxDIR_FILES ) )
267  {
268  fName = lp + lName;
269  checkPluginName( fName, aPluginList );
270 
271  while( wd.GetNext( &lName ) )
272  {
273  fName = lp + lName;
274  checkPluginName( fName, aPluginList );
275  }
276  }
277 
278  wd.Close();
279 }
280 
281 
282 void S3D_PLUGIN_MANAGER::checkPluginName( const wxString& aPath,
283  std::list< wxString >& aPluginList )
284 {
285  // check the existence of a plugin name and add it to the list
286 
287  if( aPath.empty() || !wxFileName::FileExists( aPath ) )
288  return;
289 
290  wxFileName path( ExpandEnvVarSubstitutions( aPath, nullptr ) );
291 
292  path.Normalize();
293 
294  // determine if the path is already in the list
295  wxString wxpath = path.GetFullPath();
296  std::list< wxString >::iterator bl = aPluginList.begin();
297  std::list< wxString >::iterator el = aPluginList.end();
298 
299  while( bl != el )
300  {
301  if( 0 == (*bl).Cmp( wxpath ) )
302  return;
303 
304  ++bl;
305  }
306 
307  aPluginList.push_back( wxpath );
308 
309  wxLogTrace( MASK_3D_PLUGINMGR, " * [INFO] found 3D plugin '%s'\n", wxpath.GetData() );
310 }
311 
312 
313 void S3D_PLUGIN_MANAGER::checkPluginPath( const wxString& aPath,
314  std::list< wxString >& aSearchList )
315 {
316  // check the existence of a path and add it to the path search list
317  if( aPath.empty() )
318  return;
319 
320  wxLogTrace( MASK_3D_PLUGINMGR, " * [INFO] checking for 3D plugins in '%s'\n", aPath.GetData() );
321 
322  wxFileName path;
323 
324  if( aPath.StartsWith( "${" ) || aPath.StartsWith( "$(" ) )
325  path.Assign( ExpandEnvVarSubstitutions( aPath, nullptr ), "" );
326  else
327  path.Assign( aPath, "" );
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, "%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 = 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.LowerCase();
416 #endif
417 
418  std::pair < std::multimap< const wxString, KICAD_PLUGIN_LDR_3D* >::iterator,
419  std::multimap< const wxString, KICAD_PLUGIN_LDR_3D* >::iterator > items;
420 
421  items = m_ExtMap.equal_range( ext );
422  std::multimap< const wxString, KICAD_PLUGIN_LDR_3D* >::iterator sL = items.first;
423 
424  while( sL != items.second )
425  {
426  if( sL->second->CanRender() )
427  {
428  SCENEGRAPH* sp = sL->second->Load( aFileName.ToUTF8() );
429 
430  if( nullptr != sp )
431  {
432  sL->second->GetPluginInfo( aPluginInfo );
433  return sp;
434  }
435  }
436 
437  ++sL;
438  }
439 
440  return nullptr;
441 }
442 
443 
445 {
446  std::list< KICAD_PLUGIN_LDR_3D* >::iterator sP = m_Plugins.begin();
447  std::list< KICAD_PLUGIN_LDR_3D* >::iterator eP = m_Plugins.end();
448 
449  wxLogTrace( MASK_3D_PLUGINMGR, "%s:%s:%d * [INFO] closing %d extensions",
450  __FILE__, __FUNCTION__, __LINE__, static_cast<int>( m_Plugins.size() ) );
451 
452  while( sP != eP )
453  {
454  (*sP)->Close();
455  ++sP;
456  }
457 }
458 
459 
460 bool S3D_PLUGIN_MANAGER::CheckTag( const char* aTag )
461 {
462  if( nullptr == aTag || aTag[0] == 0 || m_Plugins.empty() )
463  return false;
464 
465  std::string tname = aTag;
466  std::string pname; // plugin name
467 
468  size_t cpos = tname.find( ':' );
469 
470  // if there is no colon or plugin name then the tag is bad
471  if( cpos == std::string::npos || cpos == 0 )
472  return false;
473 
474  pname = tname.substr( 0, cpos );
475  std::string ptag; // tag from the plugin
476 
477  std::list< KICAD_PLUGIN_LDR_3D* >::iterator pS = m_Plugins.begin();
478  std::list< KICAD_PLUGIN_LDR_3D* >::iterator pE = m_Plugins.end();
479 
480  while( pS != pE )
481  {
482  ptag.clear();
483  (*pS)->GetPluginInfo( ptag );
484 
485  // if the plugin name matches then the version
486  // must also match
487  if( !ptag.compare( 0, pname.size(), pname ) )
488  {
489  if( ptag.compare( tname ) )
490  return false;
491 
492  return true;
493  }
494 
495  ++pS;
496  }
497 
498  return true;
499 }
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
static wxString GetStockPlugins3DPath()
Gets the stock (install) 3d viewer pluginspath.
Definition: paths.cpp:207
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:273
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
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
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)
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