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