Merge ~pointhi/kicad:wx-python into ~kicad-product-committers/kicad:master

Proposed by Thomas Pointhuber
Status: Merged
Merge reported by: Maciej Suminski
Merged at revision: 00511f2a7f7811e1fe8cfbbaa1664edaaba25d91
Proposed branch: ~pointhi/kicad:wx-python
Merge into: ~kicad-product-committers/kicad:master
Diff against target: 274 lines (+95/-43)
6 files modified
CMakeLists.txt (+26/-2)
Documentation/development/compiling.md (+5/-0)
common/dialog_about/dialog_about.cpp (+7/-0)
pcbnew/python/kicad_pyshell/__init__.py (+5/-0)
pcbnew/swig/python_scripting.cpp (+47/-40)
pcbnew/swig/python_scripting.h (+5/-1)
Reviewer Review Type Date Requested Status
Wayne Stambaugh Approve
Maciej Suminski Approve
Seth Hillbrand Approve
Review via email: mp+357605@code.launchpad.net

Commit message

Add optional wxPhoenix support

Description of the change

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

It adds wxPhoenix support to KiCad while keeping support for the old wxPython binding. All known are resolved now. In my case testing took place with wx3.0.4+GTK3 on Python2+wxPython as well as Python3+wxPhoenix on Linux. It would be nice to get testing done on Windows as well as Mac at least with the old wxPython version to ensure nothing breaks. While there are currently some compiler problems to get wxPhoenix running on Windows, adding this would benefit especially Linux users on Python3 only systems to get shell support.

To post a comment you must log in.
Revision history for this message
Thomas Pointhuber (pointhi) wrote :
Revision history for this message
Seth Hillbrand (sethh) wrote :

Looks good. Tested on Debian Buster and works smoothly.

review: Approve
Revision history for this message
Maciej Suminski (orsonmmz) wrote :

I have briefly tested the branch with Phoenix/Python3/GTK3 and wxPython/Python2/GTK3 on Linux - no problems observed so far.

review: Approve
Revision history for this message
Wayne Stambaugh (stambaughw) wrote :

It doesn't build on msys2 when python 3 is enabled. It finds the python 3 interpreter without any issue but it fails finding the python3 library path. Here is the config error:

-- Found PythonInterp: C:/msys64/mingw64/bin/python3.exe (found suitable version "3.7.1", minimum required is "3.3")
-- Check for installed Python Interpreter -- found
-- Python module install path: lib/python3.7/site-packages
CMake Error at C:/msys64/mingw64/share/cmake-3.12/Modules/FindPackageHandleStandardArgs.cmake:137 (message):
  Could NOT find PythonLibs (missing: PYTHON_INCLUDE_DIRS) (Required is at
  least version "3.3")
Call Stack (most recent call first):
  C:/msys64/mingw64/share/cmake-3.12/Modules/FindPackageHandleStandardArgs.cmake:378 (_FPHSA_FAILURE_MESSAGE)
  CMakeModules/FindPythonLibs.cmake:233 (find_package_handle_standard_args)
  CMakeLists.txt:750 (find_package)

-- Configuring incomplete, errors occurred!

review: Needs Fixing
Revision history for this message
Thomas Pointhuber (pointhi) wrote :

> It doesn't build on msys2 when python 3 is enabled. It finds the python 3
> interpreter without any issue but it fails finding the python3 library path.
> Here is the config error:
>
> -- Found PythonInterp: C:/msys64/mingw64/bin/python3.exe (found suitable
> version "3.7.1", minimum required is "3.3")
> -- Check for installed Python Interpreter -- found
> -- Python module install path: lib/python3.7/site-packages
> CMake Error at C:/msys64/mingw64/share/cmake-3.12/Modules/FindPackageHandleSta
> ndardArgs.cmake:137 (message):
> Could NOT find PythonLibs (missing: PYTHON_INCLUDE_DIRS) (Required is at
> least version "3.3")
> Call Stack (most recent call first):
> C:/msys64/mingw64/share/cmake-3.12/Modules/FindPackageHandleStandardArgs.cma
> ke:378 (_FPHSA_FAILURE_MESSAGE)
> CMakeModules/FindPythonLibs.cmake:233 (find_package_handle_standard_args)
> CMakeLists.txt:750 (find_package)
>
>
> -- Configuring incomplete, errors occurred!

That's weird because this patch should not change Python3 stuff. Can you verify this problem does not exist upstream?

Revision history for this message
Nick Østergaard (nickoe) wrote :

@Wayne, try to add -DPYTHON_INCLUDE_DIR=${MINGW_PREFIX}/include/python3.6m

Revision history for this message
Nick Østergaard (nickoe) wrote :

Or probably 3.7m in your case.

Revision history for this message
Wayne Stambaugh (stambaughw) wrote :

@Nick, thanks for the tip. That did it. Do you have any idea why the 'm' suffix is added to the python include path? This should be easy enough to fix by updating our FindPythonLib.cmake file to look in the python version path with the 'm' suffix. I also built and tested the python 3 support and to make sure the python 2 support still works on msys2/mingw32/mingw64 so I approve merging this.

review: Approve
Revision history for this message
Maciej Suminski (orsonmmz) wrote :

Thank you Thomas!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/CMakeLists.txt b/CMakeLists.txt
2index cf55d49..42e4c60 100644
3--- a/CMakeLists.txt
4+++ b/CMakeLists.txt
5@@ -82,6 +82,10 @@ option( KICAD_SCRIPTING_WXPYTHON
6 "Build wxPython implementation for wx interface building in Python and py.shell (default ON)."
7 ON )
8
9+option( KICAD_SCRIPTING_WXPYTHON_PHOENIX
10+ "Use new wxPython binding (default OFF)."
11+ OFF )
12+
13 option( KICAD_SCRIPTING_ACTION_MENU
14 "Build a tools menu with registered python plugins: actions plugins (default ON)."
15 ON )
16@@ -389,6 +393,10 @@ if( KICAD_SCRIPTING_WXPYTHON )
17 add_definitions( -DKICAD_SCRIPTING_WXPYTHON )
18 endif()
19
20+if( KICAD_SCRIPTING_WXPYTHON_PHOENIX )
21+ add_definitions( -DKICAD_SCRIPTING_WXPYTHON_PHOENIX )
22+endif()
23+
24 if( KICAD_SCRIPTING_ACTION_MENU )
25 add_definitions( -DKICAD_SCRIPTING_ACTION_MENU )
26 endif()
27@@ -747,8 +755,24 @@ if( KICAD_SCRIPTING OR KICAD_SCRIPTING_MODULES )
28 if( KICAD_SCRIPTING_WXPYTHON )
29 # Check to see if the correct version of wxPython is installed based on the version of
30 # wxWidgets found. At least the major an minor version should match.
31- set( _wxpy_version "${wxWidgets_VERSION_MAJOR}.${wxWidgets_VERSION_MINOR}" )
32- set( _py_cmd "import wxversion;print(wxversion.checkInstalled('${_wxpy_version}'))" )
33+ if( KICAD_SCRIPTING_WXPYTHON_PHOENIX )
34+ set( _py_cmd "import wx;print(wx.version())" )
35+ execute_process( COMMAND ${PYTHON_EXECUTABLE} -c "${_py_cmd}"
36+ RESULT_VARIABLE WXPYTHON_VERSION_RESULT
37+ OUTPUT_VARIABLE WXPYTHON_VERSION_FOUND
38+ OUTPUT_STRIP_TRAILING_WHITESPACE
39+ )
40+ # Check to see if any version of wxPython is installed on the system.
41+ if( WXPYTHON_VERSION_RESULT GREATER 0 )
42+ message( FATAL_ERROR "wxPython does not appear to be installed on the system." )
43+ endif()
44+ set( _wxpy_version ${WXPYTHON_VERSION_FOUND} )
45+ set( _py_cmd "import wx;import re;print(re.search('wxWidgets ${wxWidgets_VERSION_MAJOR}.${wxWidgets_VERSION_MINOR}', wx.wxWidgets_version) != None)" )
46+ else()
47+ set( _wxpy_version "${wxWidgets_VERSION_MAJOR}.${wxWidgets_VERSION_MINOR}" )
48+ set( _py_cmd "import wxversion;print(wxversion.checkInstalled('${_wxpy_version}'))" )
49+ endif()
50+
51
52 # Add user specified Python site package path.
53 if( PYTHON_SITE_PACKAGE_PATH )
54diff --git a/Documentation/development/compiling.md b/Documentation/development/compiling.md
55index 4de0def..9af2fe5 100644
56--- a/Documentation/development/compiling.md
57+++ b/Documentation/development/compiling.md
58@@ -166,6 +166,11 @@ of Python 2. This option is disabled by default.
59 The KICAD_SCRIPTING_WXPYTHON option is used to enable building the wxPython interface into
60 Pcbnew including the wxPython console. This option is enabled by default.
61
62+## wxPython Phoenix Scripting Support ## {#wxpython_phoenix}
63+
64+The KICAD_SCRIPTING_WXPYTHON_PHOENIX option is used to enable building the wxPython interface with
65+the new Phoenix binding instead of the legacy one. This option is disabled by default.
66+
67 ## GitHub Plugin ## {#github_opt}
68
69 The BUILD_GITHUB_PLUGIN option is used to control if the GitHub plug in is built. This option is
70diff --git a/common/dialog_about/dialog_about.cpp b/common/dialog_about/dialog_about.cpp
71index 0af42f8..9fc8233 100644
72--- a/common/dialog_about/dialog_about.cpp
73+++ b/common/dialog_about/dialog_about.cpp
74@@ -554,6 +554,13 @@ void DIALOG_ABOUT::buildVersionInfoData( wxString& aMsg, bool aFormatHtml )
75 aMsg << OFF;
76 #endif
77
78+ aMsg << indent4 << "KICAD_SCRIPTING_WXPYTHON_PHOENIX=";
79+#ifdef KICAD_SCRIPTING_WXPYTHON_PHOENIX
80+ aMsg << ON;
81+#else
82+ aMsg << OFF;
83+#endif
84+
85 aMsg << indent4 << "KICAD_SCRIPTING_ACTION_MENU=";
86 #ifdef KICAD_SCRIPTING_ACTION_MENU
87 aMsg << ON;
88diff --git a/pcbnew/python/kicad_pyshell/__init__.py b/pcbnew/python/kicad_pyshell/__init__.py
89index 7f476d5..91bca5b 100644
90--- a/pcbnew/python/kicad_pyshell/__init__.py
91+++ b/pcbnew/python/kicad_pyshell/__init__.py
92@@ -86,6 +86,11 @@ class PcbnewPyShell(editor.EditorNotebookFrame):
93 self.autoSaveHistory = False
94 self.LoadSettings()
95
96+ # in case of wxPhoenix we need to create a wxApp first and store it
97+ # to prevent removal by gabage collector
98+ if 'phoenix' in wx.PlatformInfo:
99+ self.theApp = wx.App()
100+
101 self.crust = crust.Crust(parent=self.notebook,
102 intro=intro, locals=namespace,
103 rootLabel="locals()",
104diff --git a/pcbnew/swig/python_scripting.cpp b/pcbnew/swig/python_scripting.cpp
105index 4577406..17c3ee0 100644
106--- a/pcbnew/swig/python_scripting.cpp
107+++ b/pcbnew/swig/python_scripting.cpp
108@@ -30,6 +30,7 @@
109 #include <python_scripting.h>
110 #include <stdlib.h>
111 #include <string.h>
112+#include <sstream>
113
114 #include <fctsys.h>
115 #include <eda_base_frame.h>
116@@ -162,6 +163,7 @@ bool pcbnewInitPythonScripting( const char * aUserScriptingPath )
117 #ifdef KICAD_SCRIPTING_WXPYTHON
118 PyEval_InitThreads();
119
120+#ifndef KICAD_SCRIPTING_WXPYTHON_PHOENIX
121 #ifndef __WINDOWS__ // import wxversion.py currently not working under winbuilder, and not useful.
122 char cmd[1024];
123 // Make sure that that the correct version of wxPython is loaded. In systems where there
124@@ -190,13 +192,14 @@ bool pcbnewInitPythonScripting( const char * aUserScriptingPath )
125 Py_Finalize();
126 return false;
127 }
128+#endif
129
130 wxPythonLoaded = true;
131
132 // Save the current Python thread state and release the
133 // Global Interpreter Lock.
134+ g_PythonMainTState = PyEval_SaveThread();
135
136- g_PythonMainTState = wxPyBeginAllowThreads();
137 #endif // ifdef KICAD_SCRIPTING_WXPYTHON
138
139 // load pcbnew inside python, and load all the user plugins, TODO: add system wide plugins
140@@ -298,7 +301,7 @@ void pcbnewGetWizardsBackTrace( wxString& aNames )
141 void pcbnewFinishPythonScripting()
142 {
143 #ifdef KICAD_SCRIPTING_WXPYTHON
144- wxPyEndAllowThreads( g_PythonMainTState );
145+ PyEval_RestoreThread( g_PythonMainTState );
146 #endif
147 Py_Finalize();
148 }
149@@ -325,15 +328,25 @@ void RedirectStdio()
150
151 wxWindow* CreatePythonShellWindow( wxWindow* parent, const wxString& aFramenameId )
152 {
153- const char* pcbnew_pyshell =
154- "import kicad_pyshell\n"
155- "\n"
156- "def makeWindow(parent):\n"
157- " return kicad_pyshell.makePcbnewShellWindow(parent)\n"
158- "\n";
159+ // parent is actually *PCB_EDIT_FRAME
160+ const int parentId = parent->GetId();
161+ {
162+ wxWindow* parent2 = wxWindow::FindWindowById( parentId );
163+ wxASSERT( parent2 == parent );
164+ }
165
166- wxWindow* window = NULL;
167- PyObject* result;
168+ // passing window ids instead of pointers is because wxPython is not
169+ // exposing the needed c++ apis to make that possible.
170+ std::stringstream pcbnew_pyshell_one_step;
171+ pcbnew_pyshell_one_step << "import kicad_pyshell\n";
172+ pcbnew_pyshell_one_step << "import wx\n";
173+ pcbnew_pyshell_one_step << "\n";
174+ pcbnew_pyshell_one_step << "parent = wx.FindWindowById( " << parentId << " )\n";
175+ pcbnew_pyshell_one_step << "newshell = kicad_pyshell.makePcbnewShellWindow( parent )\n";
176+ pcbnew_pyshell_one_step << "newshell.SetName( \"" << aFramenameId << "\" )\n";
177+ // return value goes into a "global". It's not actually global, but rather
178+ // the dict that is passed to PyRun_String
179+ pcbnew_pyshell_one_step << "retval = newshell.GetId()\n";
180
181 // As always, first grab the GIL
182 PyLOCK lock;
183@@ -354,7 +367,7 @@ wxWindow* CreatePythonShellWindow( wxWindow* parent, const wxString& aFramenameI
184 Py_DECREF( builtins );
185
186 // Execute the code to make the makeWindow function we defined above
187- result = PyRun_String( pcbnew_pyshell, Py_file_input, globals, globals );
188+ PyObject* result = PyRun_String( pcbnew_pyshell_one_step.str().c_str(), Py_file_input, globals, globals );
189
190 // Was there an exception?
191 if( !result )
192@@ -365,43 +378,37 @@ wxWindow* CreatePythonShellWindow( wxWindow* parent, const wxString& aFramenameI
193
194 Py_DECREF( result );
195
196- // Now there should be an object named 'makeWindow' in the dictionary that
197- // we can grab a pointer to:
198- PyObject* func = PyDict_GetItemString( globals, "makeWindow" );
199- wxASSERT( PyCallable_Check( func ) );
200+ result = PyDict_GetItemString( globals, "retval" );
201
202- // Now build an argument tuple and call the Python function. Notice the
203- // use of another wxPython API to take a wxWindows object and build a
204- // wxPython object that wraps it.
205+#if PY_MAJOR_VERSION >= 3
206+ if( !PyLong_Check( result ) )
207+#else
208+ if( !PyInt_Check( result ) )
209+#endif
210+ {
211+ wxLogError("creation of scripting window didn't return a number");
212+ return NULL;
213+ }
214
215- PyObject* arg = wxPyMake_wxObject( parent, false );
216- wxASSERT( arg != NULL );
217+#if PY_MAJOR_VERSION >= 3
218+ const long windowId = PyLong_AsLong( result );
219+#else
220+ const long windowId = PyInt_AsLong( result );
221+#endif
222
223- PyObject* tuple = PyTuple_New( 1 );
224- PyTuple_SET_ITEM( tuple, 0, arg );
225+ // It's important not to decref globals before extracting the window id.
226+ // If you do it early, globals, and the retval int it contains, may/will be garbage collected.
227+ // We do not need to decref result, because GetItemString returns a borrowed reference.
228+ Py_DECREF( globals );
229
230- result = PyEval_CallObject( func, tuple );
231+ wxWindow* window = wxWindow::FindWindowById( windowId );
232
233- // Was there an exception?
234- if( !result )
235- PyErr_Print();
236- else
237+ if( !window )
238 {
239- // Otherwise, get the returned window out of Python-land and
240- // into C++-ville...
241- bool success = wxPyConvertSwigPtr( result, (void**) &window, "wxWindow" );
242- (void) success;
243-
244- wxASSERT_MSG( success, "Returned object was not a wxWindow!" );
245- Py_DECREF( result );
246-
247- window->SetName( aFramenameId );
248+ wxLogError("unable to find pyshell window with id %d", windowId);
249+ return NULL;
250 }
251
252- // Release the python objects we still have
253- Py_DECREF( globals );
254- Py_DECREF( tuple );
255-
256 return window;
257 }
258
259diff --git a/pcbnew/swig/python_scripting.h b/pcbnew/swig/python_scripting.h
260index 373cc66..2cc53b8 100644
261--- a/pcbnew/swig/python_scripting.h
262+++ b/pcbnew/swig/python_scripting.h
263@@ -39,7 +39,11 @@
264
265 #ifndef NO_WXPYTHON_EXTENSION_HEADERS
266 #ifdef KICAD_SCRIPTING_WXPYTHON
267- #include <wx/wxPython/wxPython.h>
268+ #ifdef KICAD_SCRIPTING_WXPYTHON_PHOENIX
269+ #include <wx/window.h>
270+ #else
271+ #include <wx/wxPython/wxPython.h>
272+ #endif
273 #endif
274 #endif
275

Subscribers

People subscribed via source and target branches

to status/vote changes: