Scripting: replace PyCrust shell with enhanced PyAlaMode shell.

This commit is contained in:
Strontium 2015-12-21 09:55:31 -05:00 committed by Wayne Stambaugh
parent a11bbffe9b
commit c3d3a7a4c8
7 changed files with 294 additions and 43 deletions

View File

@ -679,6 +679,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 )

View File

@ -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
@ -984,9 +982,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 )
{
@ -994,7 +989,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();

View File

@ -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." ) );

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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
{