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 "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, " * Extension [plugin name]:\n" );
69 
70  while( sM != eM )
71  {
72  wxLogTrace( MASK_3D_PLUGINMGR, " + '%s' [%s]\n", sM->first.GetData(),
73  sM->second->GetKicadPluginName() );
74  ++sM;
75  }
76 
77  }
78  else
79  {
80  wxLogTrace( MASK_3D_PLUGINMGR, " * 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, " * File filters:\n" );
90 
91  while( sFF != eFF )
92  {
93  wxLogTrace( MASK_3D_PLUGINMGR, " + '%s'\n", (*sFF).GetData() );
94  ++sFF;
95  }
96  }
97  else
98  {
99  wxLogTrace( MASK_3D_PLUGINMGR, " * 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, "%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, "%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, "%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, "%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, "%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, " * [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, " * [INFO] checking for 3D plugins in '%s'\n", aPath.GetData() );
320 
321  wxFileName path;
322 
323  if( aPath.StartsWith( "${" ) || aPath.StartsWith( "$(" ) )
324  path.Assign( ExpandEnvVarSubstitutions( aPath, nullptr ), "" );
325  else
326  path.Assign( aPath, "" );
327 
328  path.Normalize();
329 
330  if( !wxFileName::DirExists( path.GetFullPath() ) )
331  return;
332 
333  // determine if the directory is already in the list
334  wxString wxpath = path.GetFullPath();
335  std::list< wxString >::iterator bl = aSearchList.begin();
336  std::list< wxString >::iterator el = aSearchList.end();
337 
338  while( bl != el )
339  {
340  if( 0 == (*bl).Cmp( wxpath ) )
341  return;
342 
343  ++bl;
344  }
345 
346  aSearchList.push_back( wxpath );
347 }
348 
349 
350 void S3D_PLUGIN_MANAGER::addFilterString( const wxString& aFilterString )
351 {
352  // add an entry to the file filter list
353  if( aFilterString.empty() )
354  return;
355 
356  std::list< wxString >::iterator sFF = m_FileFilters.begin();
357  std::list< wxString >::iterator eFF = m_FileFilters.end();
358 
359  while( sFF != eFF )
360  {
361  if( 0 == (*sFF).Cmp( aFilterString ) )
362  return;
363 
364  ++sFF;
365  }
366 
367  m_FileFilters.push_back( aFilterString );
368  return;
369 }
370 
371 
373 {
374  // add entries to the extension map
375  if( nullptr == aPlugin )
376  return;
377 
378  int nExt = aPlugin->GetNExtensions();
379 
380  wxLogTrace( MASK_3D_PLUGINMGR, "%s:%s:%d * [INFO] adding %d extensions",
381  __FILE__, __FUNCTION__, __LINE__, nExt );
382 
383  for( int i = 0; i < nExt; ++i )
384  {
385  char const* cp = aPlugin->GetModelExtension( i );
386  wxString ws;
387 
388  if( cp )
389  ws = wxString::FromUTF8Unchecked( cp );
390 
391  if( !ws.empty() )
392  {
393  m_ExtMap.insert( std::pair< const wxString, KICAD_PLUGIN_LDR_3D* >( ws, aPlugin ) );
394  }
395 
396  }
397 }
398 
399 
400 std::list< wxString > const* S3D_PLUGIN_MANAGER::GetFileFilters( void ) const noexcept
401 {
402  return &m_FileFilters;
403 }
404 
405 
406 SCENEGRAPH* S3D_PLUGIN_MANAGER::Load3DModel( const wxString& aFileName, std::string& aPluginInfo )
407 {
408  wxFileName raw( aFileName );
409  wxString ext_to_find = raw.GetExt();
410 
411 #ifdef _WIN32
412  // note: plugins only have a lowercase filter within Windows; including an uppercase
413  // filter will result in duplicate file entries and should be avoided.
414  ext_to_find.MakeLower();
415 #endif
416 
417  // .gz files are compressed versions that may have additional information in the previous extension
418  if( ext_to_find == "gz" )
419  {
420  wxFileName second( raw.GetName() );
421  ext_to_find = second.GetExt() + ".gz";
422  }
423 
424  std::pair < std::multimap< const wxString, KICAD_PLUGIN_LDR_3D* >::iterator,
425  std::multimap< const wxString, KICAD_PLUGIN_LDR_3D* >::iterator > items;
426 
427  items = m_ExtMap.equal_range( ext_to_find );
428  std::multimap< const wxString, KICAD_PLUGIN_LDR_3D* >::iterator sL = items.first;
429 
430  while( sL != items.second )
431  {
432  if( sL->second->CanRender() )
433  {
434  SCENEGRAPH* sp = sL->second->Load( aFileName.ToUTF8() );
435 
436  if( nullptr != sp )
437  {
438  sL->second->GetPluginInfo( aPluginInfo );
439  return sp;
440  }
441  }
442 
443  ++sL;
444  }
445 
446  return nullptr;
447 }
448 
449 
451 {
452  std::list< KICAD_PLUGIN_LDR_3D* >::iterator sP = m_Plugins.begin();
453  std::list< KICAD_PLUGIN_LDR_3D* >::iterator eP = m_Plugins.end();
454 
455  wxLogTrace( MASK_3D_PLUGINMGR, "%s:%s:%d * [INFO] closing %d extensions",
456  __FILE__, __FUNCTION__, __LINE__, static_cast<int>( m_Plugins.size() ) );
457 
458  while( sP != eP )
459  {
460  (*sP)->Close();
461  ++sP;
462  }
463 }
464 
465 
466 bool S3D_PLUGIN_MANAGER::CheckTag( const char* aTag )
467 {
468  if( nullptr == aTag || aTag[0] == 0 || m_Plugins.empty() )
469  return false;
470 
471  std::string tname = aTag;
472  std::string pname; // plugin name
473 
474  size_t cpos = tname.find( ':' );
475 
476  // if there is no colon or plugin name then the tag is bad
477  if( cpos == std::string::npos || cpos == 0 )
478  return false;
479 
480  pname = tname.substr( 0, cpos );
481  std::string ptag; // tag from the plugin
482 
483  std::list< KICAD_PLUGIN_LDR_3D* >::iterator pS = m_Plugins.begin();
484  std::list< KICAD_PLUGIN_LDR_3D* >::iterator pE = m_Plugins.end();
485 
486  while( pS != pE )
487  {
488  ptag.clear();
489  (*pS)->GetPluginInfo( ptag );
490 
491  // if the plugin name matches then the version
492  // must also match
493  if( !ptag.compare( 0, pname.size(), pname ) )
494  {
495  if( ptag.compare( tname ) )
496  return false;
497 
498  return true;
499  }
500 
501  ++pS;
502  }
503 
504  return true;
505 }
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:228
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