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 #ifdef KICAD_SCRIPTING_WXPYTHON_PHOENIX
370  auto app_ptr = wxTheApp;
371 #endif
372  // Execute the code to make the makeWindow function we defined above
373  PyObject* result = PyRun_String( pcbnew_pyshell_one_step.str().c_str(), Py_file_input, globals, globals );
374 
375 #ifdef KICAD_SCRIPTING_WXPYTHON_PHOENIX
376  // This absolutely ugly hack is to work around the pyshell re-writing the global
377  // wxApp variable. Once we can initialize Phoenix to access the appropriate instance
378  // of wx, we can remove this line
379  wxApp::SetInstance( app_ptr );
380 #endif
381  // Was there an exception?
382  if( !result )
383  {
384  PyErr_Print();
385  return NULL;
386  }
387 
388  Py_DECREF( result );
389 
390  result = PyDict_GetItemString( globals, "retval" );
391 
392 #if PY_MAJOR_VERSION >= 3
393  if( !PyLong_Check( result ) )
394 #else
395  if( !PyInt_Check( result ) )
396 #endif
397  {
398  wxLogError("creation of scripting window didn't return a number");
399  return NULL;
400  }
401 
402 #if PY_MAJOR_VERSION >= 3
403  const long windowId = PyLong_AsLong( result );
404 #else
405  const long windowId = PyInt_AsLong( result );
406 #endif
407 
408  // It's important not to decref globals before extracting the window id.
409  // If you do it early, globals, and the retval int it contains, may/will be garbage collected.
410  // We do not need to decref result, because GetItemString returns a borrowed reference.
411  Py_DECREF( globals );
412 
413  wxWindow* window = wxWindow::FindWindowById( windowId );
414 
415  if( !window )
416  {
417  wxLogError("unable to find pyshell window with id %d", windowId);
418  return NULL;
419  }
420 
421  return window;
422 }
423 
424 
425 #endif
426 
427 wxString PyStringToWx( PyObject* aString )
428 {
429  wxString ret;
430 
431  if( !aString )
432  return ret;
433 
434 #if PY_MAJOR_VERSION >= 3
435  const char* str_res = NULL;
436  PyObject* temp_bytes = PyUnicode_AsEncodedString( aString, "UTF-8", "strict" );
437  if( temp_bytes != NULL )
438  {
439  str_res = PyBytes_AS_STRING( temp_bytes );
440  ret = FROM_UTF8( str_res );
441  Py_DECREF( temp_bytes );
442  }
443  else
444  {
445  wxLogMessage( "cannot encode unicode python string" );
446  }
447 #else
448  const char* str_res = PyString_AsString( aString );
449  ret = FROM_UTF8( str_res );
450 #endif
451 
452  return ret;
453 }
454 
455 
456 wxArrayString PyArrayStringToWx( PyObject* aArrayString )
457 {
458  wxArrayString ret;
459 
460  if( !aArrayString )
461  return ret;
462 
463  int list_size = PyList_Size( aArrayString );
464 
465  for( int n = 0; n < list_size; n++ )
466  {
467  PyObject* element = PyList_GetItem( aArrayString, n );
468 
469  if( element )
470  {
471 #if PY_MAJOR_VERSION >= 3
472  const char* str_res = NULL;
473  PyObject* temp_bytes = PyUnicode_AsEncodedString( element, "UTF-8", "strict" );
474  if( temp_bytes != NULL )
475  {
476  str_res = PyBytes_AS_STRING( temp_bytes );
477  ret.Add( FROM_UTF8( str_res ), 1 );
478  Py_DECREF( temp_bytes );
479  }
480  else
481  {
482  wxLogMessage( "cannot encode unicode python string" );
483  }
484 #else
485  ret.Add( FROM_UTF8( PyString_AsString( element ) ), 1 );
486 #endif
487  }
488  }
489 
490  return ret;
491 }
492 
493 
495 {
496  wxString err;
497 
498  if( !PyErr_Occurred() )
499  return err;
500 
501  PyObject* type;
502  PyObject* value;
503  PyObject* traceback;
504 
505  PyErr_Fetch( &type, &value, &traceback );
506 
507  PyErr_NormalizeException( &type, &value, &traceback );
508  if( traceback == NULL )
509  {
510  traceback = Py_None;
511  Py_INCREF( traceback );
512  }
513 
514 #if PY_MAJOR_VERSION >= 3
515  PyException_SetTraceback( value, traceback );
516 
517  PyObject* tracebackModuleString = PyUnicode_FromString( "traceback" );
518 #else
519  PyObject* tracebackModuleString = PyString_FromString( "traceback" );
520 #endif
521  PyObject* tracebackModule = PyImport_Import( tracebackModuleString );
522  Py_DECREF( tracebackModuleString );
523 
524  PyObject* formatException = PyObject_GetAttrString( tracebackModule,
525  "format_exception" );
526  Py_DECREF( tracebackModule );
527 
528  PyObject* args = Py_BuildValue( "(O,O,O)", type, value, traceback );
529  PyObject* result = PyObject_CallObject( formatException, args );
530  Py_XDECREF( formatException );
531  Py_XDECREF( args );
532  Py_XDECREF( type );
533  Py_XDECREF( value );
534  Py_XDECREF( traceback );
535 
536  wxArrayString res = PyArrayStringToWx( result );
537 
538  for( unsigned i = 0; i<res.Count(); i++ )
539  {
540  err += res[i] + wxT( "\n" );
541  }
542 
543  PyErr_Clear();
544 
545  return err;
546 }
547 
551 wxString PyScriptingPath()
552 {
553  wxString path;
554 
555  //TODO should this be a user configurable variable eg KISCRIPT ?
556 #if defined( __WXMAC__ )
557  path = GetOSXKicadDataDir() + wxT( "/scripting" );
558 #else
559  path = Pgm().GetExecutablePath() + wxT( "../share/kicad/scripting" );
560 #endif
561 
562  wxFileName scriptPath( path );
563  scriptPath.MakeAbsolute();
564 
565  // Convert '\' to '/' in path, because later python script read \n or \r
566  // as escaped sequence, and create issues, when calling it by PyRun_SimpleString() method.
567  // It can happen on Windows.
568  path = scriptPath.GetFullPath();
569  path.Replace( '\\', '/' );
570 
571  return path;
572 }
573 
574 wxString PyPluginsPath()
575 {
576  // Note we are using unix path separator, because window separator sometimes
577  // creates issues when passing a command string to a python method by PyRun_SimpleString
578  return PyScriptingPath() + '/' + "plugins";
579 }
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.
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
VTBL_ENTRY const wxString & GetExecutablePath() const
Definition: pgm_base.h:217
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