Add initial support for Phoenix (new wxPython binding)

Based on the work of @mmccoo:
https://kicad.mmccoo.com/2017/11/23/learnings-from-moving-kicad-to-wxpython-4-0/
and this additional patchset to remove wxpy_api.h dependency:
http://mmccoo.com/random/0001-Remove-dependence-on-pywx_api.h.patch

Please note CreatePythonShellWindow changed quite a lot. Throughful testing
should be made for the old as well as new wxPython version on all platforms
This commit is contained in:
Thomas Pointhuber 2018-08-13 22:14:06 +02:00 committed by Maciej Suminski
parent 633bc7f2d5
commit 0e0b4d52a2
6 changed files with 97 additions and 45 deletions

View File

@ -82,6 +82,10 @@ option( KICAD_SCRIPTING_WXPYTHON
"Build wxPython implementation for wx interface building in Python and py.shell (default ON)."
ON )
option( KICAD_SCRIPTING_WXPYTHON_PHOENIX
"Use new wxPython binding (default OFF)."
OFF )
option( KICAD_SCRIPTING_ACTION_MENU
"Build a tools menu with registered python plugins: actions plugins (default ON)."
ON )
@ -393,6 +397,10 @@ if( KICAD_SCRIPTING_WXPYTHON )
add_definitions( -DKICAD_SCRIPTING_WXPYTHON )
endif()
if( KICAD_SCRIPTING_WXPYTHON_PHOENIX )
add_definitions( -DKICAD_SCRIPTING_WXPYTHON_PHOENIX )
endif()
if( KICAD_SCRIPTING_ACTION_MENU )
add_definitions( -DKICAD_SCRIPTING_ACTION_MENU )
endif()
@ -751,8 +759,24 @@ if( KICAD_SCRIPTING OR KICAD_SCRIPTING_MODULES )
if( KICAD_SCRIPTING_WXPYTHON )
# Check to see if the correct version of wxPython is installed based on the version of
# wxWidgets found. At least the major an minor version should match.
if( KICAD_SCRIPTING_WXPYTHON_PHOENIX )
set( _py_cmd "import wx;print(wx.version())" )
execute_process( COMMAND ${PYTHON_EXECUTABLE} -c "${_py_cmd}"
RESULT_VARIABLE WXPYTHON_VERSION_RESULT
OUTPUT_VARIABLE WXPYTHON_VERSION_FOUND
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# Check to see if any version of wxPython is installed on the system.
if( WXPYTHON_VERSION_RESULT GREATER 0 )
message( FATAL_ERROR "wxPython does not appear to be installed on the system." )
endif()
set( _wxpy_version ${WXPYTHON_VERSION_FOUND} )
set( _py_cmd "import wx;import re;print(re.search('wxWidgets ${wxWidgets_VERSION_MAJOR}.${wxWidgets_VERSION_MINOR}', wx.wxWidgets_version) != None)" )
else()
set( _wxpy_version "${wxWidgets_VERSION_MAJOR}.${wxWidgets_VERSION_MINOR}" )
set( _py_cmd "import wxversion;print(wxversion.checkInstalled('${_wxpy_version}'))" )
endif()
# Add user specified Python site package path.
if( PYTHON_SITE_PACKAGE_PATH )

View File

@ -166,6 +166,11 @@ of Python 2. This option is disabled by default.
The KICAD_SCRIPTING_WXPYTHON option is used to enable building the wxPython interface into
Pcbnew including the wxPython console. This option is enabled by default.
## wxPython Phoenix Scripting Support ## {#wxpython_phoenix}
The KICAD_SCRIPTING_WXPYTHON_PHOENIX option is used to enable building the wxPython interface with
the new Phoenix binding instead of the legacy one. This option is disabled by default.
## GitHub Plugin ## {#github_opt}
The BUILD_GITHUB_PLUGIN option is used to control if the GitHub plug in is built. This option is

View File

@ -554,6 +554,13 @@ void DIALOG_ABOUT::buildVersionInfoData( wxString& aMsg, bool aFormatHtml )
aMsg << OFF;
#endif
aMsg << indent4 << "KICAD_SCRIPTING_WXPYTHON_PHOENIX=";
#ifdef KICAD_SCRIPTING_WXPYTHON_PHOENIX
aMsg << ON;
#else
aMsg << OFF;
#endif
aMsg << indent4 << "KICAD_SCRIPTING_ACTION_MENU=";
#ifdef KICAD_SCRIPTING_ACTION_MENU
aMsg << ON;

View File

@ -86,6 +86,11 @@ class PcbnewPyShell(editor.EditorNotebookFrame):
self.autoSaveHistory = False
self.LoadSettings()
# in case of wxPhoenix we need to create a wxApp first and store it
# to prevent removal by gabage collector
if 'phoenix' in wx.PlatformInfo:
self.theApp = wx.App()
self.crust = crust.Crust(parent=self.notebook,
intro=intro, locals=namespace,
rootLabel="locals()",

View File

@ -30,6 +30,7 @@
#include <python_scripting.h>
#include <stdlib.h>
#include <string.h>
#include <sstream>
#include <fctsys.h>
#include <eda_base_frame.h>
@ -162,6 +163,7 @@ bool pcbnewInitPythonScripting( const char * aUserScriptingPath )
#ifdef KICAD_SCRIPTING_WXPYTHON
PyEval_InitThreads();
#ifndef KICAD_SCRIPTING_WXPYTHON_PHOENIX
#ifndef __WINDOWS__ // import wxversion.py currently not working under winbuilder, and not useful.
char cmd[1024];
// Make sure that that the correct version of wxPython is loaded. In systems where there
@ -190,13 +192,14 @@ bool pcbnewInitPythonScripting( const char * aUserScriptingPath )
Py_Finalize();
return false;
}
#endif
wxPythonLoaded = true;
// Save the current Python thread state and release the
// Global Interpreter Lock.
g_PythonMainTState = PyEval_SaveThread();
g_PythonMainTState = wxPyBeginAllowThreads();
#endif // ifdef KICAD_SCRIPTING_WXPYTHON
// load pcbnew inside python, and load all the user plugins, TODO: add system wide plugins
@ -298,7 +301,7 @@ void pcbnewGetWizardsBackTrace( wxString& aNames )
void pcbnewFinishPythonScripting()
{
#ifdef KICAD_SCRIPTING_WXPYTHON
wxPyEndAllowThreads( g_PythonMainTState );
PyEval_RestoreThread( g_PythonMainTState );
#endif
Py_Finalize();
}
@ -325,15 +328,25 @@ void RedirectStdio()
wxWindow* CreatePythonShellWindow( wxWindow* parent, const wxString& aFramenameId )
{
const char* pcbnew_pyshell =
"import kicad_pyshell\n"
"\n"
"def makeWindow(parent):\n"
" return kicad_pyshell.makePcbnewShellWindow(parent)\n"
"\n";
// parent is actually *PCB_EDIT_FRAME
const int parentId = parent->GetId();
{
wxWindow* parent2 = wxWindow::FindWindowById( parentId );
wxASSERT( parent2 == parent );
}
wxWindow* window = NULL;
PyObject* result;
// passing window ids instead of pointers is because wxPython is not
// exposing the needed c++ apis to make that possible.
std::stringstream pcbnew_pyshell_one_step;
pcbnew_pyshell_one_step << "import kicad_pyshell\n";
pcbnew_pyshell_one_step << "import wx\n";
pcbnew_pyshell_one_step << "\n";
pcbnew_pyshell_one_step << "parent = wx.FindWindowById( " << parentId << " )\n";
pcbnew_pyshell_one_step << "newshell = kicad_pyshell.makePcbnewShellWindow( parent )\n";
pcbnew_pyshell_one_step << "newshell.SetName( \"" << aFramenameId << "\" )\n";
// return value goes into a "global". It's not actually global, but rather
// the dict that is passed to PyRun_String
pcbnew_pyshell_one_step << "retval = newshell.GetId()\n";
// As always, first grab the GIL
PyLOCK lock;
@ -354,7 +367,7 @@ wxWindow* CreatePythonShellWindow( wxWindow* parent, const wxString& aFramenameI
Py_DECREF( builtins );
// Execute the code to make the makeWindow function we defined above
result = PyRun_String( pcbnew_pyshell, Py_file_input, globals, globals );
PyObject* result = PyRun_String( pcbnew_pyshell_one_step.str().c_str(), Py_file_input, globals, globals );
// Was there an exception?
if( !result )
@ -365,42 +378,36 @@ wxWindow* CreatePythonShellWindow( wxWindow* parent, const wxString& aFramenameI
Py_DECREF( result );
// Now there should be an object named 'makeWindow' in the dictionary that
// we can grab a pointer to:
PyObject* func = PyDict_GetItemString( globals, "makeWindow" );
wxASSERT( PyCallable_Check( func ) );
result = PyDict_GetItemString( globals, "retval" );
// Now build an argument tuple and call the Python function. Notice the
// use of another wxPython API to take a wxWindows object and build a
// wxPython object that wraps it.
PyObject* arg = wxPyMake_wxObject( parent, false );
wxASSERT( arg != NULL );
PyObject* tuple = PyTuple_New( 1 );
PyTuple_SET_ITEM( tuple, 0, arg );
result = PyEval_CallObject( func, tuple );
// Was there an exception?
if( !result )
PyErr_Print();
else
#if PY_MAJOR_VERSION >= 3
if( !PyLong_Check( result ) )
#else
if( !PyInt_Check( result ) )
#endif
{
// Otherwise, get the returned window out of Python-land and
// into C++-ville...
bool success = wxPyConvertSwigPtr( result, (void**) &window, "wxWindow" );
(void) success;
wxASSERT_MSG( success, "Returned object was not a wxWindow!" );
Py_DECREF( result );
window->SetName( aFramenameId );
wxLogError("creation of scripting window didn't return a number");
return NULL;
}
// Release the python objects we still have
#if PY_MAJOR_VERSION >= 3
const long windowId = PyLong_AsLong( result );
#else
const long windowId = PyInt_AsLong( result );
#endif
// It's important not to decref globals before extracting the window id.
// If you do it early, globals, and the retval int it contains, may/will be garbage collected.
// We do not need to decref result, because GetItemString returns a borrowed reference.
Py_DECREF( globals );
Py_DECREF( tuple );
wxWindow* window = wxWindow::FindWindowById( windowId );
if( !window )
{
wxLogError("unable to find pyshell window with id %d", windowId);
return NULL;
}
return window;
}

View File

@ -39,9 +39,13 @@
#ifndef NO_WXPYTHON_EXTENSION_HEADERS
#ifdef KICAD_SCRIPTING_WXPYTHON
#ifdef KICAD_SCRIPTING_WXPYTHON_PHOENIX
#include <wx/window.h>
#else
#include <wx/wxPython/wxPython.h>
#endif
#endif
#endif
#include <wx/string.h>
#include <wx/arrstr.h>