kicad-developers team mailing list archive
-
kicad-developers team
-
Mailing list archive
-
Message #22017
Re: PATCH : Enhanced Python Shell - Proposed version
Hi Wayne,
Thanks for testing this on windows, I really appreciate it a lot.
I moved the offending comments from being inline comments and placed
them into the Docstring. It has the added benefit of making them more
visible to end users.
See: http://i.imgur.com/Zq1pdyV.jpg
(Apologies on the quality of the screen capture.)
I also ran the pure python code I am submitting for the shell through PEP8,
PEP257 and pyflakes and corrected all problems they highlighted. NOTE:
They are all style issues and not logic faults.
I don't believe there are style guides for python and KiCad yet, but I
figured PEP8 and PEP257 would be a minimum.
I believe I fixed the style problem with the C++ code.
Revised patch attached.
Updated code is also on my github.
Steven
On 17/12/15 05:56, Wayne Stambaugh wrote:
Your patch fails to build using swig 3.0.6 on windows due to the
comments you added to kicadplugins.i. Here is the output:
C:/msys64/home/wstambaugh/src/kicad/product/pcbnew/../scripting\kicadplugins.i(101)
: Error: Unknown SWIG preprocessor directive: The (if this is a block of
target language code, delimit it with %{ and %})
C:/msys64/home/wstambaugh/src/kicad/product/pcbnew/../scripting\kicadplugins.i(105)
: Error: Unknown SWIG preprocessor directive: The (if this is a block of
target language code, delimit it with %{ and %})
C:/msys64/home/wstambaugh/src/kicad/product/pcbnew/../scripting\kicadplugins.i(109)
: Error: Unknown SWIG preprocessor directive: The (if this is a block of
target language code, delimit it with %{ and %})
C:/msys64/home/wstambaugh/src/kicad/product/pcbnew/../scripting\kicadplugins.i(113)
: Error: Unknown SWIG preprocessor directive: And (if this is a block of
target language code, delimit it with %{ and %})
C:/msys64/home/wstambaugh/src/kicad/product/pcbnew/../scripting\kicadplugins.i(118)
: Error: Unknown SWIG preprocessor directive: NOTE (if this is a block
of target language code, delimit it with %{ and %})
pcbnew/CMakeFiles/_pcbnew.dir/build.make:61: recipe for target
'pcbnew/pcbnewPYTHON_wrap.cxx' failed
I tried using the recommended %{ %} to wrap the comments. It built fine
but crashed when I ran it. Your best bet is to get rid of the comments
or figure out the proper swig technique for wrapping python comments. I
tried removing the comments and it worked fine. You also have some
coding policy violations ( missing spaces before and after parenthesis )
that need to fixed.
On 12/16/2015 5:58 AM, Strontium wrote:
Hello List,
I believe I have cleaned up my patch and made the enhanced shell do
everything it should.
It has a tabbed interface, with simple text editing capabilities.
Its GUI can be used to introspect the full internal state of pcbnew, as
exposed by the python interface.
The editor can be used to code python scripts (with syntax
highlighting), and it has auto-completion built in. Ie, type word press
(dot) and it gives you a list of possibilities which follow. Further,
you can use the editor to load up KiCad files and edit them without
having to use an external editor, if one desires.
It will allow you to save your interactive history, and restore it in a
new session. It has a startup file which is executed when the python
shell first starts, and automatically creates a default if it doesn't
exist. All of its configuration, history and startup files are stored
in the users KiCad configuration directory on each platform.
The default startup file doesn't do anything, but includes a commented
out example which will automatically load pcbnew into the shell, and get
the current board on screen. Two very common things that need to be
done most times when you start the python shell.
The shell is fully written in python, and uses the foundation of
PyAlamode included with wxpython. It does not introduce any further
dependencies on KiCad, everything it achieves is already built into
KiCad, and not currently exposed.
It should be possible to extend the shell in future and add features,
without requiring to hack core code inside pcbnew.
The changes to the core of pcbnew, that I did, were to facilitate this,
and I discuss why I did them in earlier posts. They haven't changed in
this patch. There may be better ways to achieve this and I am open to
suggestions.
I have only tested on Linux, but I do not believe any of the OS specific
code changes that I made should negatively effect Windows or Mac,
nevertheless it needs testing on those platforms and I do not have
access to them.
Screenshots of it in action:
http://i.imgur.com/lTFFddR.png
http://i.imgur.com/qbACtPR.png
http://i.imgur.com/Yny4Lnc.png
http://i.imgur.com/q2AjrPi.png
The code is also available on my github:
https://github.com/stevenj/kicad-source-mirror/tree/enahnced-python-shell
Hopefully this is useful.
Steven
_______________________________________________
Mailing list: https://launchpad.net/~kicad-developers
Post to : kicad-developers@xxxxxxxxxxxxxxxxxxx
Unsubscribe : https://launchpad.net/~kicad-developers
More help : https://help.launchpad.net/ListHelp
_______________________________________________
Mailing list: https://launchpad.net/~kicad-developers
Post to : kicad-developers@xxxxxxxxxxxxxxxxxxx
Unsubscribe : https://launchpad.net/~kicad-developers
More help : https://help.launchpad.net/ListHelp
diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt
index ff99a93..469ddbf 100644
--- a/pcbnew/CMakeLists.txt
+++ b/pcbnew/CMakeLists.txt
@@ -678,6 +678,12 @@ if( KICAD_SCRIPTING )
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 )
diff --git a/pcbnew/pcbframe.cpp b/pcbnew/pcbframe.cpp
index b19fca1..185fcb9 100644
--- a/pcbnew/pcbframe.cpp
+++ b/pcbnew/pcbframe.cpp
@@ -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 @@ void PCB_EDIT_FRAME::UpdateTitle()
}
-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 @@ void PCB_EDIT_FRAME::ScriptingConsoleEnableDisable( wxCommandEvent& aEvent )
bool pythonPanelShown = true;
if( pythonPanelFrame == NULL )
- pythonPanelFrame = new PYTHON_CONSOLE_FRAME( this, pythonConsoleNameId() );
+ pythonPanelFrame = CreatePythonShellWindow( this, pythonConsoleNameId() );
else
pythonPanelShown = ! pythonPanelFrame->IsShown();
diff --git a/pcbnew/pcbnew.cpp b/pcbnew/pcbnew.cpp
index 0abbd00..2277c65 100644
--- a/pcbnew/pcbnew.cpp
+++ b/pcbnew/pcbnew.cpp
@@ -241,36 +241,26 @@ static bool scriptingSetup()
}
}
- // 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 @@ static bool scriptingSetup()
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." ) );
diff --git a/pcbnew/scripting/kicad_pyshell/__init__.py b/pcbnew/scripting/kicad_pyshell/__init__.py
new file mode 100644
index 0000000..7da0005
--- /dev/null
+++ b/pcbnew/scripting/kicad_pyshell/__init__.py
@@ -0,0 +1,223 @@
+# -*- coding: utf-8 -*-
+"""KiCad Python Shell.
+
+This module provides the python shell for KiCad.
+
+Currently the shell is only available inside PCBNEW.
+
+PCBNEW starts the shell once, by calling makePcbnewShellWindow() the
+first time it is opened, subsequently the shell window is just hidden
+or shown, as per user requirements.
+
+IF makePcbnewShellWindow() is called again, a second/third shell window
+can be created. PCBNEW does not do this, but a user may call this from
+the first shell if they require.
+
+"""
+import wx
+import sys
+import os
+
+from wx.py import crust, editor, version, dispatcher
+from wx.py.editor import EditorNotebook
+
+import pcbnew
+
+INTRO = "KiCAD:PCBNEW - Python Shell - PyAlaMode %s" % version.VERSION
+
+
+class PcbnewPyShell(editor.EditorNotebookFrame):
+
+ """The Pythonshell of PCBNEW."""
+
+ def _setup_startup(self):
+ """Initialise the startup script."""
+ # Create filename for startup script.
+ self.startup_file = os.path.join(self.config_dir,
+ "PyShell_pcbnew_startup.py")
+ self.execStartupScript = True
+
+ # Check if startup script exists
+ if not os.path.isfile(self.startup_file):
+ # Not, so create a default.
+ default_startup = open(self.startup_file, 'w')
+ # provide the content for the default startup file.
+ default_startup.write(
+ "### DEFAULT STARTUP FILE FOR KiCad:PCBNEW Python Shell\n" +
+ "# Enter any Python code you would like to execute when" +
+ " the PCBNEW python shell first runs.\n" +
+ "\n" +
+ "# Eg:\n" +
+ "\n" +
+ "# import pcbnew\n" +
+ "# board = pcbnew.GetBoard()\n")
+ default_startup.close()
+
+ def _setup(self):
+ """
+ Setup prior to first buffer creation.
+
+ Called automatically by base class during init.
+ """
+ self.notebook = EditorNotebook(parent=self)
+ intro = 'Py %s' % version.VERSION
+ import imp
+ module = imp.new_module('__main__')
+ import __builtin__
+ module.__dict__['__builtins__'] = __builtin__
+ namespace = module.__dict__.copy()
+
+ self.config_dir = pcbnew.GetKicadConfigPath()
+ self.dataDir = self.config_dir
+
+ self._setup_startup()
+ self.history_file = os.path.join(self.config_dir,
+ "PyShell_pcbnew.history")
+
+ self.config_file = os.path.join(self.config_dir,
+ "PyShell_pcbnew.cfg")
+ self.config = wx.FileConfig(localFilename=self.config_file)
+ self.config.SetRecordDefaults(True)
+ self.autoSaveSettings = False
+ self.autoSaveHistory = False
+ self.LoadSettings()
+
+ self.crust = crust.Crust(parent=self.notebook,
+ intro=intro, locals=namespace,
+ rootLabel="locals()",
+ startupScript=self.startup_file,
+ execStartupScript=self.execStartupScript)
+
+ self.shell = self.crust.shell
+ # Override the filling so that status messages go to the status bar.
+ self.crust.filling.tree.setStatusText = self.SetStatusText
+ # Override the shell so that status messages go to the status bar.
+ self.shell.setStatusText = self.SetStatusText
+ # Fix a problem with the sash shrinking to nothing.
+ self.crust.filling.SetSashPosition(200)
+ self.notebook.AddPage(page=self.crust, text='*Shell*', select=True)
+ self.setEditor(self.crust.editor)
+ self.crust.editor.SetFocus()
+
+ self.LoadHistory()
+
+ def OnAbout(self, event):
+ """Display an About window."""
+ title = 'About : KiCad:PCBNEW - Python Shell'
+ text = "Enahnced Python Shell for KiCad:PCBNEW\n\n" + \
+ "This KiCad Python Shell is based on wxPython PyAlaMode.\n\n" + \
+ "see: http://wiki.wxpython.org/PyAlaMode\n\n" + \
+ "KiCad Revision: %s\n" % "??.??" + \
+ "PyAlaMode Revision : %s\n" % version.VERSION + \
+ "Platform: %s\n" % sys.platform + \
+ "Python Version: %s\n" % sys.version.split()[0] + \
+ "wxPython Version: %s\n" % wx.VERSION_STRING + \
+ ("\t(%s)\n" % ", ".join(wx.PlatformInfo[1:]))
+
+ dialog = wx.MessageDialog(self, text, title,
+ wx.OK | wx.ICON_INFORMATION)
+ dialog.ShowModal()
+ dialog.Destroy()
+
+ def EditStartupScript(self):
+ """Open a Edit buffer of the startup script file."""
+ self.bufferCreate(filename=self.startup_file)
+
+ def LoadSettings(self):
+ """Load settings for the shell."""
+ if self.config is not None:
+ editor.EditorNotebookFrame.LoadSettings(self, self.config)
+ self.autoSaveSettings = \
+ self.config.ReadBool('Options/AutoSaveSettings', False)
+ self.execStartupScript = \
+ self.config.ReadBool('Options/ExecStartupScript', True)
+ self.autoSaveHistory = \
+ self.config.ReadBool('Options/AutoSaveHistory', False)
+ self.hideFoldingMargin = \
+ self.config.ReadBool('Options/HideFoldingMargin', True)
+
+ def SaveSettings(self, force=False):
+ """
+ Save settings for the shell.
+
+ Arguments:
+
+ force -- False - Autosaving. True - Manual Saving.
+ """
+ if self.config is not None:
+ # always save these
+ self.config.WriteBool('Options/AutoSaveSettings',
+ self.autoSaveSettings)
+ if self.autoSaveSettings or force:
+ editor.EditorNotebookFrame.SaveSettings(self, self.config)
+
+ self.config.WriteBool('Options/AutoSaveHistory',
+ self.autoSaveHistory)
+ self.config.WriteBool('Options/ExecStartupScript',
+ self.execStartupScript)
+ self.config.WriteBool('Options/HideFoldingMargin',
+ self.hideFoldingMargin)
+ if self.autoSaveHistory:
+ self.SaveHistory()
+
+ def DoSaveSettings(self):
+ """Menu function to trigger saving the shells settings."""
+ if self.config is not None:
+ self.SaveSettings(force=True)
+ self.config.Flush()
+
+ def SaveHistory(self):
+ """Save shell history to the shell history file."""
+ if self.dataDir:
+ try:
+ name = self.history_file
+ f = file(name, 'w')
+ hist = []
+ enc = wx.GetDefaultPyEncoding()
+ for h in self.shell.history:
+ if isinstance(h, unicode):
+ h = h.encode(enc)
+ hist.append(h)
+ hist = '\x00\n'.join(hist)
+ f.write(hist)
+ f.close()
+ except:
+ d = wx.MessageDialog(self, "Error saving history file.",
+ "Error", wx.ICON_EXCLAMATION | wx.OK)
+ d.ShowModal()
+ d.Destroy()
+ raise
+
+ def LoadHistory(self):
+ """Load shell history from the shell history file."""
+ if self.dataDir:
+ name = self.history_file
+ if os.path.exists(name):
+ try:
+ f = file(name, 'U')
+ hist = f.read()
+ f.close()
+ self.shell.history = hist.split('\x00\n')
+ dispatcher.send(signal="Shell.loadHistory",
+ history=self.shell.history)
+ except:
+ d = wx.MessageDialog(self,
+ "Error loading history file!",
+ "Error", wx.ICON_EXCLAMATION | wx.OK)
+ d.ShowModal()
+ d.Destroy()
+
+
+def makePcbnewShellWindow(parent=None):
+ """
+ Create a new Shell Window and return its handle.
+
+ Arguments:
+ parent -- The parent window to attach to.
+
+ Returns:
+ The handle to the new window.
+ """
+ pyshell = PcbnewPyShell(parent, id=-1, title=INTRO)
+ pyshell.Show()
+ return pyshell
diff --git a/scripting/kicadplugins.i b/scripting/kicadplugins.i
index 9a310fa..918896d 100644
--- a/scripting/kicadplugins.i
+++ b/scripting/kicadplugins.i
@@ -78,21 +78,59 @@ def ReloadPlugins():
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.
+
+ NOTE: These are all of the possible "default" search paths for kicad
+ python scripts. These paths will ONLY be added to the python
+ search path ONLY IF they already exist.
+
+ The Scripts bundled with the KiCad installation:
+ <bundlepath>/
+ <bundlepath>/plugins/
+
+ The Scripts relative to the KiCad search path environment variable:
+ [KICAD_PATH]/scripting/
+ [KICAD_PATH]/scripting/plugins/
+
+ The Scripts relative to the KiCad Users configuration:
+ <kicad_config_path>/scripting/
+ <kicad_config_path>/scripting/plugins/
+
+ And on Linux ONLY, extra paths relative to the users home directory:
+ ~/.kicad_plugins/
+ ~/.kicad/scripting/
+ ~/.kicad/scripting/plugins/
+ """
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)
+ 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:
diff --git a/scripting/python_scripting.cpp b/scripting/python_scripting.cpp
index c74abf5..f2b3d22 100644
--- a/scripting/python_scripting.cpp
+++ b/scripting/python_scripting.cpp
@@ -136,7 +136,7 @@ static void swigSwitchPythonBuiltin()
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 @@ bool pcbnewInitPythonScripting( const char * aUserPluginsPath )
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 @@ void RedirectStdio()
}
-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 @@ wxWindow* CreatePythonShellWindow( wxWindow* parent )
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 @@ wxWindow* CreatePythonShellWindow( wxWindow* parent )
wxASSERT_MSG( success, _T( "Returned object was not a wxWindow!" ) );
Py_DECREF( result );
+
+ window->SetName( aFramenameId );
}
// Release the python objects we still have
diff --git a/scripting/python_scripting.h b/scripting/python_scripting.h
index 8d0ae17..ca25de4 100644
--- a/scripting/python_scripting.h
+++ b/scripting/python_scripting.h
@@ -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