← Back to team overview

kicad-developers team mailing list archive

Re: PATCH V2 : Enhanced Python Shell

 

I attach an enhanced patch to the python shell.

This one is a bigger change than the previous patch. But it moves the code to the point where the whole shell is contained as an external python file, which makes it a lot easier to develop and enhance.

Explanation of the changes:
- I created a "pcbnew/scripting/kicad_pyshell" directory to contain the shell python code. So i added the required installer directives.

- I don't need or use "scripting/python_console_frame.h" so all reference to its removed from the code, but the file still exists in the source tree.

- I changed the Bundled search path to be "scripting" not "scripting/plugins". "plugins" is only one type of possible scripting and also common to all 3 platforms, so its handled lower down. This means its possible to have other types of "scripting". The directory it produces is therefore just the scripting "bundled" search path, and doesn't need to be over-ridden by the end user, as far as i can tell. So I removed the comments about that being desirable.

- I removed the TODO comments, the way the code works now, there are a bunch of default search paths added, some referencing the kicad install path, some from the KICAD_PATH environment variable and some from the users root directory and users kicad config directory. Its also easily possible for a user to add their own include paths, all they need to do is add python code to a module in the search path to do it, and the python interpreter will load that and execute it in the process of initialising the plugins. Note, these search paths are only possible search paths, they dont get added if they dont exist.

- I modified LoadPlugins in kicadplugins.i to properly add all the scripting paths, including plugin paths.

The full list of possible default search paths is now:
+    # The Scripts bundled with Kicad:
+    #   <bundlepath>
+    #   <bundlepath>/plugins
+    #
+    # The Scripts relative to the Kicad environment variable search path:
+    #   <kicad_path>/scripting
+    #   <kicad_path>/scripting/plugins
+    #
+    # The Scripts relative to the Kicad Users configuration:
+    #   <config_path>/scripting
+    #   <config_path>/scripting/plugins
+    #
+    # And on Linux, extra paths relative to the home directory:
+    #   ~/.kicad_plugins
+    #   ~/.kicad/scripting
+    #   ~/.kicad/scripting/plugins

And i include that as a comment in the code.

- I changed some names from <blah>PluginsPath to <blah>ScriptingPath to make the parameters reflect their current use.

- I reduced the python code embedded within CreatePythonShellWindow to the bare minimum, which eases maintenance, as further changes or enhancements to the python shell are all in pure python. I also renamed it from pycrust_panel to pcbnew_shell to better reflect what it is. Its possible in future the shell wont be based on pycrust, there is no need to do it, except that currently its already a dependency of Kicad and easy enough to leverage.

It also seemed to make sense to refer it to pcbnew, where it is embedded because i could imagine a time when eeschema has python scripting and its own shell. And, at that time both programs could share a lot of the same python shell code.

Steven
=== modified file 'pcbnew/CMakeLists.txt'
--- pcbnew/CMakeLists.txt	2015-12-10 19:20:35 +0000
+++ pcbnew/CMakeLists.txt	2015-12-15 07:31:41 +0000
@@ -678,6 +678,12 @@
         DESTINATION ${KICAD_DATA}/scripting/plugins
         FILE_PERMISSIONS OWNER_EXECUTE OWNER_READ OWNER_WRITE GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ
     )
+
+    # scripting python shell
+    install( DIRECTORY ${PROJECT_SOURCE_DIR}/pcbnew/scripting/kicad_pyshell/
+        DESTINATION ${KICAD_DATA}/scripting/kicad_pyshell
+        FILE_PERMISSIONS OWNER_EXECUTE OWNER_READ OWNER_WRITE GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ
+    )
 endif()
 
 if( KICAD_SCRIPTING_MODULES )

=== modified file 'pcbnew/pcbframe.cpp'
--- pcbnew/pcbframe.cpp	2015-11-29 06:56:27 +0000
+++ pcbnew/pcbframe.cpp	2015-12-15 07:30:47 +0000
@@ -70,8 +70,6 @@
 #include <tool/tool_dispatcher.h>
 #include <tools/common_actions.h>
 
-#include <scripting/python_console_frame.h>
-
 #if defined(KICAD_SCRIPTING) || defined(KICAD_SCRIPTING_WXPYTHON)
 #include <python_scripting.h>
 #endif
@@ -980,9 +978,6 @@
 }
 
 
-wxSize PYTHON_CONSOLE_FRAME::m_frameSize;   ///< The size of the PYTHON_CONSOLE_FRAME frame, stored during a session
-wxPoint PYTHON_CONSOLE_FRAME::m_framePos;   ///< The position ofPYTHON_CONSOLE_FRAME  the frame, stored during a session
-
 void PCB_EDIT_FRAME::ScriptingConsoleEnableDisable( wxCommandEvent& aEvent )
 {
 
@@ -990,7 +985,7 @@
     bool pythonPanelShown = true;
 
     if( pythonPanelFrame == NULL )
-        pythonPanelFrame = new PYTHON_CONSOLE_FRAME( this, pythonConsoleNameId() );
+        pythonPanelFrame = CreatePythonShellWindow( this, pythonConsoleNameId() );
     else
         pythonPanelShown = ! pythonPanelFrame->IsShown();
 

=== modified file 'pcbnew/pcbnew.cpp'
--- pcbnew/pcbnew.cpp	2015-11-04 08:48:34 +0000
+++ pcbnew/pcbnew.cpp	2015-12-15 07:32:00 +0000
@@ -241,36 +241,26 @@
         }
     }
 
-    // TODO: make this path definable by the user, and set more than one path
-    // (and remove the fixed paths from <src>/scripting/kicadplugins.i)
-
-    // wizard plugins are stored in kicad/bin/plugins.
-    // so add this path to python scripting default search paths
+    // wizard plugins are stored in ../share/kicad/scripting/plugins.
+    // so add the base scripting path to python scripting default search paths
     // which are ( [KICAD_PATH] is an environment variable to define)
+    // [KICAD_PATH]/scripting
     // [KICAD_PATH]/scripting/plugins
     // Add this default search path:
-    path_frag = Pgm().GetExecutablePath() + wxT( "../share/kicad/scripting/plugins" );
+    path_frag = Pgm().GetExecutablePath() + wxT( "../share/kicad/scripting" );
 
 #elif defined( __WXMAC__ )
-    // TODO:
-    // For scripting currently only the bundle scripting path and the path
-    // defined by $(KICAD_PATH)/scripting/plugins is defined.
-    // These paths are defined here and in kicadplugins.i
-    // In future, probably more paths are of interest:
-    // * User folder (~/Library/Application Support/kicad/scripting/plugins)
-    //   => GetOSXKicadUserDataDir() + wxT( "/scripting/plugins" );
-    // * Machine folder (/Library/Application Support/kicad/scripting/plugins)
-    //   => GetOSXKicadMachineDataDir() + wxT( "/scripting/plugins" );
 
     // This path is given to LoadPlugins() from kicadplugins.i, which
-    // only supports one path. Only use bundle scripting path for now.
-    path_frag = GetOSXKicadDataDir() + wxT( "/scripting/plugins" );
+    // only supports one path, the bundle scripting path for now.
+    // All other paths are determined by the pcbnew.py initialisation code
+    path_frag = GetOSXKicadDataDir() + wxT( "/scripting" );
 
     // Add default paths to PYTHONPATH
     wxString pypath;
 
-    // Bundle scripting folder (<kicad.app>/Contents/SharedSupport/scripting/plugins)
-    pypath += GetOSXKicadDataDir() + wxT( "/scripting/plugins" );
+    // Bundle scripting folder (<kicad.app>/Contents/SharedSupport/scripting)
+    pypath += GetOSXKicadDataDir() + wxT( "/scripting" );
 
     // $(KICAD_PATH)/scripting/plugins is always added in kicadplugins.i
     if( wxGetenv("KICAD_PATH") != NULL )
@@ -303,9 +293,11 @@
     wxSetEnv( wxT( "PYTHONPATH" ), pypath );
 
     // Add this default search path:
-    path_frag = Pgm().GetExecutablePath() + wxT( "../share/kicad/scripting/plugins" );
+    path_frag = Pgm().GetExecutablePath() + wxT( "../share/kicad/scripting" );
 #endif
 
+    // path_frag is the path to the bundled scripts and plugins, all other paths are
+    // determined by the python pcbnew.py initialisation code.
     if( !pcbnewInitPythonScripting( TO_UTF8( path_frag ) ) )
     {
         wxLogError( wxT( "pcbnewInitPythonScripting() failed." ) );

=== added directory 'pcbnew/scripting/kicad_pyshell'
=== added file 'pcbnew/scripting/kicad_pyshell/__init__.py'
--- pcbnew/scripting/kicad_pyshell/__init__.py	1970-01-01 00:00:00 +0000
+++ pcbnew/scripting/kicad_pyshell/__init__.py	2015-12-15 07:32:59 +0000
@@ -0,0 +1,25 @@
+
+import wx
+from wx.py import editor, version
+
+intro = "KiCAD:PCBNEW Python Shell - PyAlaMode %s" % version.VERSION
+
+class PcbnewPyShell(editor.EditorNotebookFrame):
+    """ The Pythonshell of PCBNEW. """
+
+    def OnAbout(self, event):
+        '''Display an About window.'''
+        title = 'About : KiCad Python Shell'
+        text = """Enahnced Python Shell for KiCad:pcbnew
+The KiCad Python Shell is based on wxPython PyAlaMode.
+see: http://wiki.wxpython.org/PyAlaMode""";
+
+        dialog = wx.MessageDialog(self, text, title,
+                                    wx.OK | wx.ICON_INFORMATION)
+        dialog.ShowModal()
+        dialog.Destroy()
+
+def makePcbnewShellWindow(parent):
+    pyshell = PcbnewPyShell(parent, id=-1, title=intro)
+    pyshell.Show()
+    return pyshell

=== modified file 'scripting/kicadplugins.i'
--- scripting/kicadplugins.i	2015-10-31 11:54:48 +0000
+++ scripting/kicadplugins.i	2015-12-15 07:32:00 +0000
@@ -78,21 +78,59 @@
             ReloadPlugin(k)
 
 
-def LoadPlugins(plugpath):
+def LoadPlugins(bundlepath=None):
+    """
+    Initialise Scripting/Plugin python environment and load plugins.
+
+    Arguments:
+    scriptpath -- The path to the bundled scripts.
+                  The bunbled Plugins are relative to this path, in the
+                  "plugins" subdirectory.
+    """
     import os
     import sys
+    import pcbnew
 
     kicad_path = os.environ.get('KICAD_PATH')
+    config_path = pcbnew.GetKicadConfigPath()
     plugin_directories=[]
 
-    if plugpath:
-        plugin_directories.append(plugpath)
+    # These are all of the possible "default" search paths for kicad python scripts.
+    #
+    # The Scripts bundled with Kicad:
+    #   <bundlepath>
+    #   <bundlepath>/plugins
+    #
+    # The Scripts relative to the Kicad search path:
+    #   <kicad_path>/scripting
+    #   <kicad_path>/scripting/plugins
+    #
+    # The Scripts relative to the Kicad Users configuration:
+    #   <config_path>/scripting
+    #   <config_path>/scripting/plugins
+    #
+    # And on Linux, extra paths relative to the home directory:
+    #   ~/.kicad_plugins
+    #   ~/.kicad/scripting
+    #   ~/.kicad/scripting/plugins
+    #
+    #  NOTE: These paths will ONLY be added to the python search path IF they exist.
+
+    if bundlepath:
+        plugin_directories.append(bundlepath)
+        plugin_directories.append(os.path.join(bundlepath, 'plugins'))
 
     if kicad_path:
+        plugin_directories.append(os.path.join(kicad_path, 'scripting'))
         plugin_directories.append(os.path.join(kicad_path, 'scripting', 'plugins'))
 
+    if config_path:
+        plugin_directories.append(os.path.join(config_path, 'scripting'))
+        plugin_directories.append(os.path.join(config_path, 'scripting', 'plugins'))
+
     if sys.platform.startswith('linux'):
         plugin_directories.append(os.environ['HOME']+'/.kicad_plugins/')
+        plugin_directories.append(os.environ['HOME']+'/.kicad/scripting/')
         plugin_directories.append(os.environ['HOME']+'/.kicad/scripting/plugins/')
 
     for plugins_dir in plugin_directories:

=== modified file 'scripting/python_scripting.cpp'
--- scripting/python_scripting.cpp	2015-08-24 18:32:56 +0000
+++ scripting/python_scripting.cpp	2015-12-15 07:32:00 +0000
@@ -136,7 +136,7 @@
 
 PyThreadState* g_PythonMainTState;
 
-bool pcbnewInitPythonScripting( const char * aUserPluginsPath )
+bool pcbnewInitPythonScripting( const char * aUserScriptingPath )
 {
     swigAddBuiltin();           // add builtin functions
     swigAddModules();           // add our own modules
@@ -191,7 +191,7 @@
         snprintf( cmd, sizeof(cmd), "import sys, traceback\n"
                       "sys.path.append(\".\")\n"
                       "import pcbnew\n"
-                      "pcbnew.LoadPlugins(\"%s\")", aUserPluginsPath );
+                      "pcbnew.LoadPlugins(\"%s\")", aUserScriptingPath );
         PyRun_SimpleString( cmd );
     }
 
@@ -227,20 +227,15 @@
 }
 
 
-wxWindow* CreatePythonShellWindow( wxWindow* parent )
+wxWindow* CreatePythonShellWindow( wxWindow* parent, const wxString& aFramenameId )
 {
-    const char* pycrust_panel =
-        "import wx\n"
-        "from wx.py import shell, version\n"
-        "\n"
-        "intro = \"PyCrust %s - KiCAD Python Shell\" % version.VERSION\n"
+    const char* pcbnew_pyshell =
+        "import kicad_pyshell\n"
         "\n"
         "def makeWindow(parent):\n"
-        "    pycrust = shell.Shell(parent, -1, introText=intro)\n"
-        "    return pycrust\n"
+        "    return kicad_pyshell.makePcbnewShellWindow(parent)\n"
         "\n";
 
-
     wxWindow*   window = NULL;
     PyObject*   result;
 
@@ -257,7 +252,7 @@
     Py_DECREF( builtins );
 
     // Execute the code to make the makeWindow function we defined above
-    result = PyRun_String( pycrust_panel, Py_file_input, globals, globals );
+    result = PyRun_String( pcbnew_pyshell, Py_file_input, globals, globals );
 
     // Was there an exception?
     if( !result )
@@ -297,6 +292,8 @@
 
         wxASSERT_MSG( success, _T( "Returned object was not a wxWindow!" ) );
         Py_DECREF( result );
+
+        window->SetName(aFramenameId);
     }
 
     // Release the python objects we still have

=== modified file 'scripting/python_scripting.h'
--- scripting/python_scripting.h	2013-07-19 18:27:22 +0000
+++ scripting/python_scripting.h	2015-12-15 07:32:00 +0000
@@ -24,14 +24,14 @@
  * Initializes the Python engine inside pcbnew
  */
 
-bool        pcbnewInitPythonScripting( const char * aUserPluginsPath );
+bool        pcbnewInitPythonScripting( const char * aUserScriptingPath );
 void        pcbnewFinishPythonScripting();
 
 
 #ifdef KICAD_SCRIPTING_WXPYTHON
 
 void        RedirectStdio();
-wxWindow*   CreatePythonShellWindow( wxWindow* parent );
+wxWindow*   CreatePythonShellWindow( wxWindow* parent, const wxString& aFramenameId );
 
 class PyLOCK
 {


Follow ups

References