diff --git a/CMakeLists.txt b/CMakeLists.txt index fca69e0902..215ac4e65c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,7 +56,10 @@ option(KICAD_SCRIPTING_MODULES "set this option ON to build kicad modules that can be used from scripting languages" ) - +option(KICAD_SCRIPTING_EXPERIMENT + "set this option ON to build Edwin's TEST code with linking to python and pycrust shell" + ) + #Set version option (stable or testing) @@ -136,7 +139,12 @@ endif(KICAD_SCRIPTING) if(KICAD_SCRIPTING_MODULES) add_definitions(-DKICAD_SCRIPTING_MODULES) endif(KICAD_SCRIPTING_MODULES) - + +if(KICAD_SCRIPTING_EXPERIMENT) + add_definitions(-DKICAD_SCRIPTING_EXPERIMENT) + + +endif(KICAD_SCRIPTING_EXPERIMENT) if(USE_WX_GRAPHICS_CONTEXT) add_definitions(-DUSE_WX_GRAPHICS_CONTEXT) diff --git a/include/appl_wxstruct.h b/include/appl_wxstruct.h index a30674c117..a97852a437 100644 --- a/include/appl_wxstruct.h +++ b/include/appl_wxstruct.h @@ -36,7 +36,10 @@ #include #include #include - +#ifdef KICAD_SCRIPTING_EXPERIMENT +#include +#include +#endif enum EDA_APP_T { APP_UNKNOWN_T, @@ -59,6 +62,10 @@ class wxHtmlHelpController; */ class EDA_APP : public wxApp { +#ifdef KICAD_SCRIPTING_EXPERIMENT + private: + PyThreadState* m_mainTState; +#endif protected: /// Used mainly to handle default paths libs m_Id = APP_EESCHEMA_T, APP_PCBNEW_T ... EDA_APP_T m_Id; @@ -115,8 +122,14 @@ public: * this is the first executed function (like main() ) * @return true if the application can be started. */ - bool OnInit(); - + bool OnInit(); // should this be virtual +#ifdef KICAD_SCRIPTING_EXPERIMENT + bool Init_wxPython(); +#endif + // This is only called if OnInit() returned true so it's a good place to do + // any cleanup matching the initializations done there. + //// virtual int OnExit() ;// { return(0) ; }; // TODO FIX THIS HACK MUST BE VIRTUAL + wxHtmlHelpController* GetHtmlHelpController() { return m_HtmlCtrl; } void SetHtmlHelpController( wxHtmlHelpController* aController ); diff --git a/include/wxstruct.h b/include/wxstruct.h index b50bc105f2..2bc471d242 100644 --- a/include/wxstruct.h +++ b/include/wxstruct.h @@ -470,7 +470,9 @@ public: void SetShowBorderAndTitleBlock( bool aShow ) { m_showBorderAndTitleBlock = aShow; } EDA_DRAW_PANEL* GetCanvas() { return m_canvas; } - + // HACK + // EDA_MSG_PANEL* GetMessagePanel() { return m_messagePanel; } + // end hack virtual wxString GetScreenDesc(); /** diff --git a/pcbnew/pcbnew.cpp b/pcbnew/pcbnew.cpp index 20afea14b5..ed4bf1e820 100644 --- a/pcbnew/pcbnew.cpp +++ b/pcbnew/pcbnew.cpp @@ -57,6 +57,7 @@ #include #endif + // Colors for layers and items COLORS_DESIGN_SETTINGS g_ColorsSettings; @@ -87,6 +88,7 @@ wxString g_DocModulesFileName = wxT( "footprints_doc/footprints.pdf" ); wxArrayString g_LibraryNames; +// wxWindow* DoPythonStuff(wxWindow* parent); // declaration IMPLEMENT_APP( EDA_APP ) @@ -105,6 +107,33 @@ void EDA_APP::MacOpenFile( const wxString& fileName ) frame->LoadOnePcbFile( fileName, false ); } +#ifdef KICAD_SCRIPTING_EXPERIMENT + +class MyFrame : public wxFrame { + public: + MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size); + void RedirectStdio(); + wxWindow* DoPythonStuff(wxWindow* parent); + void OnExit(wxCommandEvent& event); + void OnPyFrame(wxCommandEvent& event); + void ScriptingSetPcbEditFrame(PCB_EDIT_FRAME *aPCBEdaFrame); + private: + DECLARE_EVENT_TABLE() + PCB_EDIT_FRAME *PcbEditFrame ; +}; + +enum + { + ID_EXIT=1001, + ID_PYFRAME + }; + +BEGIN_EVENT_TABLE(MyFrame, wxFrame) + EVT_MENU(ID_EXIT, MyFrame::OnExit) + EVT_MENU(ID_PYFRAME, MyFrame::OnPyFrame) +END_EVENT_TABLE() + +#endif bool EDA_APP::OnInit() { @@ -112,10 +141,16 @@ bool EDA_APP::OnInit() PCB_EDIT_FRAME* frame = NULL; #ifdef KICAD_SCRIPTING - pcbnewInitPythonScripting(); + if ( !pcbnewInitPythonScripting(&m_mainTState) ) + { + return false; + } #endif +#ifdef KICAD_SCRIPTING_EXPERIMENT + MyFrame *zz = new MyFrame(_T("Embedded wxPython Test"),wxDefaultPosition, wxSize(700, 600)); + zz->Show(true); +#endif - InitEDA_Appl( wxT( "Pcbnew" ), APP_PCBNEW_T ); if( m_Checker && m_Checker->IsAnotherRunning() ) @@ -209,6 +244,217 @@ Changing extension to .brd." ), GetChars( fn.GetFullPath() ) ); */ frame->SetFocus(); frame->GetCanvas()->SetFocus(); +#ifdef KICAD_SCRIPTING_EXPERIMENT + zz->ScriptingSetPcbEditFrame(frame); // make the frame available to my python thing + // now to find a way to use it for something useful +#endif + return true; +} +#if 0 +// for some reason KiCad classes do not implement OnExit +// if I add it in the declaration, I need to fix it in every application +// so for now make a note TODO TODO +// we need to clean up python when the application exits +int EDA_APP::OnExit() { + // Restore the thread state and tell Python to cleanup after itself. + // wxPython will do its own cleanup as part of that process. This is done + // in OnExit instead of ~MyApp because OnExit is only called if OnInit is + // successful. +#if KICAD_SCRIPTING_EXPERIMENT + wxPyEndAllowThreads(m_mainTState); + Py_Finalize(); +#endif + return 0; +} +#endif + +#ifdef KICAD_SCRIPTING_EXPERIMENT +// stuff copied from WxPython examples +bool EDA_APP::Init_wxPython() { + // Initialize Python + Py_Initialize(); + PyEval_InitThreads(); + + // Load the wxPython core API. Imports the wx._core_ module and sets a + // local pointer to a function table located there. The pointer is used + // internally by the rest of the API functions. + if ( ! wxPyCoreAPI_IMPORT() ) { + wxLogError(wxT("***** Error importing the wxPython API! *****")); + PyErr_Print(); + Py_Finalize(); + return false; + } + + // Save the current Python thread state and release the + // Global Interpreter Lock. + m_mainTState = wxPyBeginAllowThreads(); return true; } + + +// +MyFrame::MyFrame(const wxString& title, const wxPoint& pos, const wxSize& size) + : wxFrame(NULL, -1, title, pos, size, + wxDEFAULT_FRAME_STYLE|wxNO_FULL_REPAINT_ON_RESIZE) +{ +// SetIcon(wxICON(mondrian)); + + wxMenuBar* mbar = new wxMenuBar; + wxMenu* menu = new wxMenu; + menu->Append(ID_PYFRAME, _T("Make wx&Python frame")); + menu->AppendSeparator(); + menu->Append(ID_EXIT, _T("&Close Frame\tAlt-X")); + mbar->Append(menu, _T("&File")); + SetMenuBar(mbar); + + CreateStatusBar(); + RedirectStdio(); + + // Make some child windows from C++ + wxSplitterWindow* sp = new wxSplitterWindow(this, -1); + wxPanel* p1 = new wxPanel(sp, -1, wxDefaultPosition, wxDefaultSize, wxSUNKEN_BORDER); + + new wxStaticText(p1, -1, + _T("The frame, menu, splitter, this panel and this text were created in C++..."), + wxPoint(10,10)); + + // And get a panel from Python + wxWindow* p2 = DoPythonStuff(sp); + + if (p2) + sp->SplitHorizontally(p1, p2, GetClientSize().y/4); +} + +void MyFrame::OnExit(wxCommandEvent& event) +{ + Close(); +} + +void MyFrame::ScriptingSetPcbEditFrame(PCB_EDIT_FRAME *aPCBEdaFrame) +{ + PcbEditFrame = aPCBEdaFrame; +} + +//---------------------------------------------------------------------- +// This is where the fun begins... + + +const char* python_code1 = "\ +import wx\n\ +f = wx.Frame(None, -1, 'Hello from wxPython!', size=(250, 150))\n\ +f.Show()\n\ +"; + +void MyFrame::OnPyFrame(wxCommandEvent& event) +{ + // For simple Python code that doesn't have to interact with the + // C++ code in any way, you can execute it with PyRun_SimpleString. + + + // First, whenever you do anything with Python objects or code, you + // *MUST* aquire the Global Interpreter Lock and block other + // Python threads from running. + wxPyBlock_t blocked = wxPyBeginBlockThreads(); + + // Execute the code in the __main__ module + PyRun_SimpleString(python_code1); + + // Finally, release the GIL and let other Python threads run. + wxPyEndBlockThreads(blocked); +} + + +void MyFrame::RedirectStdio() { + // This is a helpful little tidbit to help debugging and such. It + // redirects Python's stdout and stderr to a window that will popup + // only on demand when something is printed, like a traceback. + const char* python_redirect = "import sys\n\ +import wx\n\ +output = wx.PyOnDemandOutputWindow()\n\ +sys.stdin = sys.stderr = output\n"; + + wxPyBlock_t blocked = wxPyBeginBlockThreads(); + PyRun_SimpleString(python_redirect); + wxPyEndBlockThreads(blocked); +} + +const char* python_code2 = "\ +import sys\nimport os\n\ +sys.path.append(os.path.expanduser('~/.kicad_plugins'))\n\ +import embedded_sample\n\ +\n\ +def makeWindow(parent):\n\ + win = embedded_sample.MyPanel(parent)\n\ + return win\n\ +"; + +wxWindow* MyFrame::DoPythonStuff(wxWindow* parent) +{ + // More complex embedded situations will require passing C++ objects to + // Python and/or returning objects from Python to be used in C++. This + // sample shows one way to do it. NOTE: The above code could just have + // easily come from a file, or the whole thing could be in the Python + // module that is imported and manipulated directly in this C++ code. See + // the Python API for more details. + + wxWindow* window = NULL; + PyObject* result; + + // As always, first grab the GIL + wxPyBlock_t blocked = wxPyBeginBlockThreads(); + + // Now make a dictionary to serve as the global namespace when the code is + // executed. Put a reference to the builtins module in it. (Yes, the + // names are supposed to be different, I don't know why...) + PyObject* globals = PyDict_New(); + PyObject* builtins = PyImport_ImportModule("__builtin__"); + PyDict_SetItemString(globals, "__builtins__", builtins); + Py_DECREF(builtins); + + // Execute the code to make the makeWindow function + result = PyRun_String(python_code2, Py_file_input, globals, globals); + // Was there an exception? + if (! result) { + PyErr_Print(); + wxPyEndBlockThreads(blocked); + return NULL; + } + 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)); + + // 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 { + // Otherwise, get the returned window out of Python-land and + // into C++-ville... + bool success = wxPyConvertSwigPtr(result, (void**)&window, _T("wxWindow")); + wxASSERT_MSG(success, _T("Returned object was not a wxWindow!")); + Py_DECREF(result); + } + + // Release the python objects we still have + Py_DECREF(globals); + Py_DECREF(tuple); + + // Finally, after all Python stuff is done, release the GIL + wxPyEndBlockThreads(blocked); + + return window; +} + +#endif diff --git a/scripting/python_scripting.cpp b/scripting/python_scripting.cpp index 90b6807e7d..8c3ec062c8 100644 --- a/scripting/python_scripting.cpp +++ b/scripting/python_scripting.cpp @@ -28,6 +28,7 @@ */ #include +#include #include #include @@ -37,7 +38,7 @@ extern "C" void init_kicad( void ); extern "C" void init_pcbnew( void ); -#define EXTRA_PYTHON_MODULES 2 // this is the number of python +#define EXTRA_PYTHON_MODULES 10 // this is the number of python // modules that we want to add into the list @@ -112,21 +113,43 @@ static void swigSwitchPythonBuiltin() /* Function pcbnewInitPythonScripting * Initializes all the python environment and publish our interface inside it + * initializes all the wxpython interface, and returns the python thread control structure * */ -void pcbnewInitPythonScripting() +bool pcbnewInitPythonScripting(PyThreadState** aMainTState) { + swigAddBuiltin(); // add builtin functions swigAddModules(); // add our own modules swigSwitchPythonBuiltin(); // switch the python builtin modules to our new list - Py_Initialize(); // call the python library to get it initialized + Py_Initialize(); + PyEval_InitThreads(); + + // Load the wxPython core API. Imports the wx._core_ module and sets a + // local pointer to a function table located there. The pointer is used + // internally by the rest of the API functions. + if ( ! wxPyCoreAPI_IMPORT() ) { + wxLogError(wxT("***** Error importing the wxPython API! *****")); + PyErr_Print(); + Py_Finalize(); + return false; + } + + // Save the current Python thread state and release the + // Global Interpreter Lock. + + *aMainTState = wxPyBeginAllowThreads(); // load pcbnew inside python, and load all the user plugins, TODO: add system wide plugins + wxPyBlock_t blocked = wxPyBeginBlockThreads(); PyRun_SimpleString( "import sys\n" "sys.path.append(\".\")\n" "import pcbnew\n" "pcbnew.LoadPlugins()" ); + wxPyEndBlockThreads(blocked); + + return true; } diff --git a/scripting/python_scripting.h b/scripting/python_scripting.h index 9ebafd0126..420c65b79d 100644 --- a/scripting/python_scripting.h +++ b/scripting/python_scripting.h @@ -7,6 +7,6 @@ * Initializes the Python engine inside pcbnew */ -void pcbnewInitPythonScripting( void ); +bool pcbnewInitPythonScripting(PyThreadState** aMainTState); #endif