KiCad PCB EDA Suite
python_scripting.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) 2012 NBEE Embedded Systems, Miguel Angel Ajo <miguelangel@nbee.es>
5  * Copyright (C) 1992-2019 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 
30 #include <python_scripting.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <sstream>
34 
35 #include <fctsys.h>
36 #include <eda_base_frame.h>
37 #include <common.h>
38 #include <gal/color4d.h>
39 #include <macros.h>
40 
41 #include <pgm_base.h>
42 
43 /* init functions defined by swig */
44 
45 #if PY_MAJOR_VERSION >= 3
46 extern "C" PyObject* PyInit__pcbnew( void );
47 #else
48 extern "C" void init_kicad( void );
49 
50 extern "C" void init_pcbnew( void );
51 #endif
52 
53 #define EXTRA_PYTHON_MODULES 10 // this is the number of python
54  // modules that we want to add into the list
55 
56 
57 /* python inittab that links module names to module init functions
58  * we will rebuild it to include the original python modules plus
59  * our own ones
60  */
61 
62 struct _inittab* SwigImportInittab;
63 static int SwigNumModules = 0;
64 
66 static bool wxPythonLoaded = false;
67 
68 
70 {
71  return wxPythonLoaded;
72 }
73 
74 
79 #if PY_MAJOR_VERSION >= 3
80 static void swigAddModule( const char* name, PyObject* (* initfunc)() )
81 #else
82 static void swigAddModule( const char* name, void (* initfunc)() )
83 #endif
84 {
85  SwigImportInittab[SwigNumModules].name = (char*) name;
86  SwigImportInittab[SwigNumModules].initfunc = initfunc;
88  SwigImportInittab[SwigNumModules].name = (char*) 0;
89  SwigImportInittab[SwigNumModules].initfunc = 0;
90 }
91 
92 
96 static void swigAddBuiltin()
97 {
98  int i = 0;
99 
100  /* discover the length of the pyimport inittab */
101  while( PyImport_Inittab[i].name )
102  i++;
103 
104  /* allocate memory for the python module table */
105  SwigImportInittab = (struct _inittab*) malloc(
106  sizeof( struct _inittab ) * ( i + EXTRA_PYTHON_MODULES ) );
107 
108  /* copy all pre-existing python modules into our newly created table */
109  i = 0;
110 
111  while( PyImport_Inittab[i].name )
112  {
113  swigAddModule( PyImport_Inittab[i].name, PyImport_Inittab[i].initfunc );
114  i++;
115  }
116 }
117 
118 
122 static void swigAddModules()
123 {
124 #if PY_MAJOR_VERSION >= 3
125  swigAddModule( "_pcbnew", PyInit__pcbnew );
126 #else
127  swigAddModule( "_pcbnew", init_pcbnew );
128 #endif
129 
130  // finally it seems better to include all in just one module
131  // but in case we needed to include any other modules,
132  // it must be done like this:
133  // swigAddModule( "_kicad", init_kicad );
134 }
135 
136 
141 {
142  PyImport_Inittab = SwigImportInittab;
143 }
144 
145 
146 PyThreadState* g_PythonMainTState;
147 
148 
154 bool pcbnewInitPythonScripting( const char * aUserScriptingPath )
155 {
156  int retv;
157  char cmd[1024];
158 
159  swigAddBuiltin(); // add builtin functions
160  swigAddModules(); // add our own modules
161  swigSwitchPythonBuiltin(); // switch the python builtin modules to our new list
162 
163  Py_Initialize();
164  PySys_SetArgv( Pgm().App().argc, Pgm().App().argv );
165 
166 #ifdef KICAD_SCRIPTING_WXPYTHON
167  PyEval_InitThreads();
168 
169 #ifndef KICAD_SCRIPTING_WXPYTHON_PHOENIX
170 #ifndef __WINDOWS__ // import wxversion.py currently not working under winbuilder, and not useful.
171  // Make sure that that the correct version of wxPython is loaded. In systems where there
172  // are different versions of wxPython installed this can lead to select wrong wxPython
173  // version being selected.
174  snprintf( cmd, sizeof( cmd ), "import wxversion; wxversion.select( '%s' )", WXPYTHON_VERSION );
175 
176  retv = PyRun_SimpleString( cmd );
177 
178  if( retv != 0 )
179  {
180  wxLogError( "Python error %d occurred running command:\n\n`%s`", retv, cmd );
181  return false;
182  }
183 #endif // ifndef __WINDOWS__
184 
185  // Load the wxPython core API. Imports the wx._core_ module and sets a
186  // local pointer to a function table located there. The pointer is used
187  // internally by the rest of the API functions.
188  if( !wxPyCoreAPI_IMPORT() )
189  {
190  wxLogError( "***** Error importing the wxPython API! *****" );
191  PyErr_Print();
192  Py_Finalize();
193  return false;
194  }
195 #endif
196 
197 #if defined( DEBUG )
198  RedirectStdio();
199 #endif
200 
201  wxPythonLoaded = true;
202 
203  // Save the current Python thread state and release the
204  // Global Interpreter Lock.
205  g_PythonMainTState = PyEval_SaveThread();
206 
207 #endif // ifdef KICAD_SCRIPTING_WXPYTHON
208 
209  // Load pcbnew inside Python and load all the user plugins, TODO: add system wide plugins
210  {
211  PyLOCK lock;
212 
213  snprintf( cmd, sizeof( cmd ), "import sys, traceback\n"
214  "sys.path.append(\".\")\n"
215  "import pcbnew\n"
216  "pcbnew.LoadPlugins(\"%s\")", aUserScriptingPath );
217  retv = PyRun_SimpleString( cmd );
218 
219  if( retv != 0 )
220  wxLogError( "Python error %d occurred running command:\n\n`%s`", retv, cmd );
221  }
222 
223  return true;
224 }
225 
226 
233 static void pcbnewRunPythonMethodWithReturnedString( const char* aMethodName, wxString& aNames )
234 {
235  aNames.Clear();
236 
237  PyLOCK lock;
238  PyErr_Clear();
239 
240  PyObject* builtins = PyImport_ImportModule( "pcbnew" );
241  wxASSERT( builtins );
242 
243  if( !builtins ) // Something is wrong in pcbnew.py module (incorrect version?)
244  return;
245 
246  PyObject* globals = PyDict_New();
247  PyDict_SetItemString( globals, "pcbnew", builtins );
248  Py_DECREF( builtins );
249 
250  // Build the python code
251  char cmd[1024];
252  snprintf( cmd, sizeof(cmd), "result = %s()", aMethodName );
253 
254  // Execute the python code and get the returned data
255  PyObject* localDict = PyDict_New();
256  PyObject* pobj = PyRun_String( cmd, Py_file_input, globals, localDict);
257  Py_DECREF( globals );
258 
259  if( pobj )
260  {
261  PyObject* str = PyDict_GetItemString(localDict, "result" );
262 #if PY_MAJOR_VERSION >= 3
263  const char* str_res = NULL;
264 
265  if(str)
266  {
267  PyObject* temp_bytes = PyUnicode_AsEncodedString( str, "UTF-8", "strict" );
268 
269  if( temp_bytes != NULL )
270  {
271  str_res = PyBytes_AS_STRING( temp_bytes );
272  str_res = strdup( str_res );
273  Py_DECREF( temp_bytes );
274  }
275  else
276  {
277  wxLogMessage( "cannot encode unicode python string" );
278  }
279  }
280 #else
281  const char* str_res = str ? PyString_AsString( str ) : 0;
282 #endif
283  aNames = FROM_UTF8( str_res );
284  Py_DECREF( pobj );
285  }
286 
287  Py_DECREF( localDict );
288 
289  if( PyErr_Occurred() )
290  wxLogMessage( PyErrStringWithTraceback() );
291 }
292 
293 
294 void pcbnewGetUnloadableScriptNames( wxString& aNames )
295 {
296  pcbnewRunPythonMethodWithReturnedString( "pcbnew.GetUnLoadableWizards", aNames );
297 }
298 
299 
300 void pcbnewGetScriptsSearchPaths( wxString& aNames )
301 {
302  pcbnewRunPythonMethodWithReturnedString( "pcbnew.GetWizardsSearchPaths", aNames );
303 }
304 
305 
306 void pcbnewGetWizardsBackTrace( wxString& aNames )
307 {
308  pcbnewRunPythonMethodWithReturnedString( "pcbnew.GetWizardsBackTrace", aNames );
309 }
310 
311 
313 {
314 #ifdef KICAD_SCRIPTING_WXPYTHON
315  PyEval_RestoreThread( g_PythonMainTState );
316 #endif
317  Py_Finalize();
318 }
319 
320 
321 #if defined( KICAD_SCRIPTING_WXPYTHON )
322 void RedirectStdio()
323 {
324  // This is a helpful little tidbit to help debugging and such. It
325  // redirects Python's stdout and stderr to a window that will popup
326  // only on demand when something is printed, like a traceback.
327  const char* python_redirect =
328  "import sys\n"
329  "import wx\n"
330  "output = wx.PyOnDemandOutputWindow()\n"
331  "sys.stderr = output\n";
332 
333  PyLOCK lock;
334 
335  int retv = PyRun_SimpleString( python_redirect );
336 
337  if( retv != 0 )
338  wxLogError( "Python error %d occurred running command:\n\n`%s`", retv, python_redirect );
339 }
340 
341 
342 wxWindow* CreatePythonShellWindow( wxWindow* parent, const wxString& aFramenameId )
343 {
344  // parent is actually *PCB_EDIT_FRAME
345  const int parentId = parent->GetId();
346  {
347  wxWindow* parent2 = wxWindow::FindWindowById( parentId );
348  wxASSERT( parent2 == parent );
349  }
350 
351  // passing window ids instead of pointers is because wxPython is not
352  // exposing the needed c++ apis to make that possible.
353  std::stringstream pcbnew_pyshell_one_step;
354  pcbnew_pyshell_one_step << "import kicad_pyshell\n";
355  pcbnew_pyshell_one_step << "import wx\n";
356  pcbnew_pyshell_one_step << "\n";
357  pcbnew_pyshell_one_step << "parent = wx.FindWindowById( " << parentId << " )\n";
358  pcbnew_pyshell_one_step << "newshell = kicad_pyshell.makePcbnewShellWindow( parent )\n";
359  pcbnew_pyshell_one_step << "newshell.SetName( \"" << aFramenameId << "\" )\n";
360  // return value goes into a "global". It's not actually global, but rather
361  // the dict that is passed to PyRun_String
362  pcbnew_pyshell_one_step << "retval = newshell.GetId()\n";
363 
364  // As always, first grab the GIL
365  PyLOCK lock;
366 
367  // Now make a dictionary to serve as the global namespace when the code is
368  // executed. Put a reference to the builtins module in it.
369 
370  PyObject* globals = PyDict_New();
371 #if PY_MAJOR_VERSION >= 3
372  PyObject* builtins = PyImport_ImportModule( "builtins" );
373 #else
374  PyObject* builtins = PyImport_ImportModule( "__builtin__" );
375 #endif
376 
377  wxASSERT( builtins );
378 
379  PyDict_SetItemString( globals, "__builtins__", builtins );
380  Py_DECREF( builtins );
381 
382 #ifdef KICAD_SCRIPTING_WXPYTHON_PHOENIX
383  auto app_ptr = wxTheApp;
384 #endif
385  // Execute the code to make the makeWindow function we defined above
386  PyObject* result = PyRun_String( pcbnew_pyshell_one_step.str().c_str(), Py_file_input,
387  globals, globals );
388 
389 #ifdef KICAD_SCRIPTING_WXPYTHON_PHOENIX
390  // This absolutely ugly hack is to work around the pyshell re-writing the global
391  // wxApp variable. Once we can initialize Phoenix to access the appropriate instance
392  // of wx, we can remove this line
393  wxApp::SetInstance( app_ptr );
394 #endif
395  // Was there an exception?
396  if( !result )
397  {
398  PyErr_Print();
399  return NULL;
400  }
401 
402  Py_DECREF( result );
403 
404  result = PyDict_GetItemString( globals, "retval" );
405 
406 #if PY_MAJOR_VERSION >= 3
407  if( !PyLong_Check( result ) )
408 #else
409  if( !PyInt_Check( result ) )
410 #endif
411  {
412  wxLogError( "creation of scripting window didn't return a number" );
413  return NULL;
414  }
415 
416 #if PY_MAJOR_VERSION >= 3
417  const long windowId = PyLong_AsLong( result );
418 #else
419  const long windowId = PyInt_AsLong( result );
420 #endif
421 
422  // It's important not to decref globals before extracting the window id.
423  // If you do it early, globals, and the retval int it contains, may/will be garbage collected.
424  // We do not need to decref result, because GetItemString returns a borrowed reference.
425  Py_DECREF( globals );
426 
427  wxWindow* window = wxWindow::FindWindowById( windowId );
428 
429  if( !window )
430  {
431  wxLogError( "unable to find pyshell window with id %d", windowId );
432  return NULL;
433  }
434 
435  return window;
436 }
437 #endif
438 
439 
440 wxString PyStringToWx( PyObject* aString )
441 {
442  wxString ret;
443 
444  if( !aString )
445  return ret;
446 
447 #if PY_MAJOR_VERSION >= 3
448  const char* str_res = NULL;
449  PyObject* temp_bytes = PyUnicode_AsEncodedString( aString, "UTF-8", "strict" );
450 
451  if( temp_bytes != NULL )
452  {
453  str_res = PyBytes_AS_STRING( temp_bytes );
454  ret = FROM_UTF8( str_res );
455  Py_DECREF( temp_bytes );
456  }
457  else
458  {
459  wxLogMessage( "cannot encode unicode python string" );
460  }
461 #else
462  const char* str_res = PyString_AsString( aString );
463  ret = FROM_UTF8( str_res );
464 #endif
465 
466  return ret;
467 }
468 
469 
470 wxArrayString PyArrayStringToWx( PyObject* aArrayString )
471 {
472  wxArrayString ret;
473 
474  if( !aArrayString )
475  return ret;
476 
477  int list_size = PyList_Size( aArrayString );
478 
479  for( int n = 0; n < list_size; n++ )
480  {
481  PyObject* element = PyList_GetItem( aArrayString, n );
482 
483  if( element )
484  {
485 #if PY_MAJOR_VERSION >= 3
486  const char* str_res = NULL;
487  PyObject* temp_bytes = PyUnicode_AsEncodedString( element, "UTF-8", "strict" );
488 
489  if( temp_bytes != NULL )
490  {
491  str_res = PyBytes_AS_STRING( temp_bytes );
492  ret.Add( FROM_UTF8( str_res ), 1 );
493  Py_DECREF( temp_bytes );
494  }
495  else
496  {
497  wxLogMessage( "cannot encode unicode python string" );
498  }
499 #else
500  ret.Add( FROM_UTF8( PyString_AsString( element ) ), 1 );
501 #endif
502  }
503  }
504 
505  return ret;
506 }
507 
508 
510 {
511  wxString err;
512 
513  if( !PyErr_Occurred() )
514  return err;
515 
516  PyObject* type;
517  PyObject* value;
518  PyObject* traceback;
519 
520  PyErr_Fetch( &type, &value, &traceback );
521 
522  PyErr_NormalizeException( &type, &value, &traceback );
523 
524  if( traceback == NULL )
525  {
526  traceback = Py_None;
527  Py_INCREF( traceback );
528  }
529 
530 #if PY_MAJOR_VERSION >= 3
531  PyException_SetTraceback( value, traceback );
532 
533  PyObject* tracebackModuleString = PyUnicode_FromString( "traceback" );
534 #else
535  PyObject* tracebackModuleString = PyString_FromString( "traceback" );
536 #endif
537  PyObject* tracebackModule = PyImport_Import( tracebackModuleString );
538  Py_DECREF( tracebackModuleString );
539 
540  PyObject* formatException = PyObject_GetAttrString( tracebackModule,
541  "format_exception" );
542  Py_DECREF( tracebackModule );
543 
544  PyObject* args = Py_BuildValue( "(O,O,O)", type, value, traceback );
545  PyObject* result = PyObject_CallObject( formatException, args );
546  Py_XDECREF( formatException );
547  Py_XDECREF( args );
548  Py_XDECREF( type );
549  Py_XDECREF( value );
550  Py_XDECREF( traceback );
551 
552  wxArrayString res = PyArrayStringToWx( result );
553 
554  for( unsigned i = 0; i<res.Count(); i++ )
555  {
556  err += res[i] + wxT( "\n" );
557  }
558 
559  PyErr_Clear();
560 
561  return err;
562 }
563 
564 
568 wxString PyScriptingPath()
569 {
570  wxString path;
571 
572  //@todo This should this be a user configurable variable eg KISCRIPT?
573 #if defined( __WXMAC__ )
574  path = GetOSXKicadDataDir() + wxT( "/scripting" );
575 #else
576  path = Pgm().GetExecutablePath() + wxT( "../share/kicad/scripting" );
577 #endif
578 
579  wxFileName scriptPath( path );
580  scriptPath.MakeAbsolute();
581 
582  // Convert '\' to '/' in path, because later python script read \n or \r
583  // as escaped sequence, and create issues, when calling it by PyRun_SimpleString() method.
584  // It can happen on Windows.
585  path = scriptPath.GetFullPath();
586  path.Replace( '\\', '/' );
587 
588  return path;
589 }
590 
591 
592 wxString PyPluginsPath()
593 {
594  // Note we are using unix path separator, because window separator sometimes
595  // creates issues when passing a command string to a python method by PyRun_SimpleString
596  return PyScriptingPath() + '/' + "plugins";
597 }
wxString PyStringToWx(PyObject *aString)
static void pcbnewRunPythonMethodWithReturnedString(const char *aMethodName, wxString &aNames)
Run a python method from the pcbnew module.
static wxString FROM_UTF8(const char *cstring)
function FROM_UTF8 converts a UTF8 encoded C string to a wxString for all wxWidgets build modes.
Definition: macros.h:62
wxString PyPluginsPath()
bool pcbnewInitPythonScripting(const char *aUserScriptingPath)
Initialize the python environment and publish the Pcbnew interface inside it.
PGM_BASE & Pgm()
The global Program "get" accessor.
Definition: kicad.cpp:65
static void swigAddBuiltin()
Add the builtin python modules.
void pcbnewFinishPythonScripting()
#define EXTRA_PYTHON_MODULES
const string & str
Definition: json11.cpp:596
This file contains miscellaneous commonly used macros and functions.
void pcbnewGetWizardsBackTrace(wxString &aNames)
Return the backtrace of errors (if any) when wizard python scripts are loaded.
wxString PyScriptingPath()
Find the Python scripting path.
static void swigAddModules()
Add the internal modules to the python scripting so they will be available to the scripts.
Base window classes and related definitions.
static void swigAddModule(const char *name, void(*initfunc)())
Add a name + initfuction to our SwigImportInittab.
wxArrayString PyArrayStringToWx(PyObject *aArrayString)
static void swigSwitchPythonBuiltin()
Switch the python module table to the Pcbnew built one.
string & err
Definition: json11.cpp:598
static bool wxPythonLoaded
True if the wxPython scripting layer was successfully loaded.
void init_pcbnew(void)
void pcbnewGetUnloadableScriptNames(wxString &aNames)
Collect the list of python scripts which could not be loaded.
see class PGM_BASE
const char * name
Definition: DXF_plotter.cpp:61
VTBL_ENTRY const wxString & GetExecutablePath() const
Definition: pgm_base.h:232
size_t i
Definition: json11.cpp:597
void init_kicad(void)
wxString PyErrStringWithTraceback()
The common library.
struct _inittab * SwigImportInittab
bool IsWxPythonLoaded()
void pcbnewGetScriptsSearchPaths(wxString &aNames)
Collect the list of paths where python scripts are searched.
static int SwigNumModules
PyThreadState * g_PythonMainTState