|
# -*- coding: utf-8 -*- |
|
|
|
import wx |
|
import wx.xrc |
|
|
|
ID_DIRECTORY = 1000 |
|
ID_RELOAD = 1001 |
|
ID_EXECUTE = 1002 |
|
|
|
class _MacroDialogBase ( wx.Dialog ): |
|
|
|
def __init__( self, parent ): |
|
wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = u"Macros - Pcbnew", |
|
pos = wx.DefaultPosition, size = wx.Size( 200,250 ), style = wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER ) |
|
|
|
self.SetSizeHintsSz( wx.Size( 200,250 ), wx.DefaultSize ) |
|
|
|
bSizer3 = wx.BoxSizer( wx.VERTICAL ) |
|
|
|
bSizer3.SetMinSize( wx.Size( 200,250 ) ) |
|
self.m_toolBar1 = wx.ToolBar( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TB_HORIZONTAL ) |
|
self.m_tool1 = self.m_toolBar1.AddLabelTool( ID_DIRECTORY, u"Change Directory", wx.ArtProvider.GetBitmap( wx.ART_FILE_OPEN, ), wx.NullBitmap, wx.ITEM_NORMAL, u"Change Directory", wx.EmptyString, None ) |
|
|
|
self.m_tool2 = self.m_toolBar1.AddLabelTool( ID_RELOAD, u"Reload", wx.ArtProvider.GetBitmap( wx.ART_GO_BACK, ), wx.NullBitmap, wx.ITEM_NORMAL, u"Reload", wx.EmptyString, None ) |
|
|
|
self.m_toolBar1.AddSeparator() |
|
|
|
self.m_tool3 = self.m_toolBar1.AddLabelTool( ID_EXECUTE, u"Execute", wx.ArtProvider.GetBitmap( wx.ART_EXECUTABLE_FILE, ), wx.NullBitmap, wx.ITEM_NORMAL, u"Execute", wx.EmptyString, None ) |
|
|
|
self.m_toolBar1.Realize() |
|
|
|
bSizer3.Add( self.m_toolBar1, 0, wx.EXPAND, 5 ) |
|
|
|
m_MacrosListBoxChoices = [] |
|
self.m_MacrosListBox = wx.ListBox( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, m_MacrosListBoxChoices, wx.LB_SINGLE ) |
|
self.m_MacrosListBox.SetMinSize( wx.Size( 180,150 ) ) |
|
|
|
bSizer3.Add( self.m_MacrosListBox, 1, wx.ALL|wx.EXPAND, 5 ) |
|
|
|
|
|
self.SetSizer( bSizer3 ) |
|
self.Layout() |
|
|
|
self.Centre( wx.BOTH ) |
|
|
|
# Connect Events |
|
self.Bind( wx.EVT_CLOSE, self.OnClose ) |
|
self.Bind( wx.EVT_TOOL, self.OnToolClicked, id = self.m_tool1.GetId() ) |
|
self.Bind( wx.EVT_TOOL, self.OnToolClicked, id = self.m_tool2.GetId() ) |
|
self.Bind( wx.EVT_TOOL, self.OnToolClicked, id = self.m_tool3.GetId() ) |
|
self.m_MacrosListBox.Bind( wx.EVT_KEY_DOWN, self.OnKeyDownMacrosListBox ) |
|
self.m_MacrosListBox.Bind( wx.EVT_LEFT_DCLICK, self.OnLeftDClickMacrosListBox ) |
|
|
|
def __del__( self ): |
|
pass |
|
|
|
# Virtual event handlers, overide them in your derived class |
|
def OnClose( self, event ): |
|
event.Skip() |
|
|
|
def OnToolClicked( self, event ): |
|
event.Skip() |
|
|
|
def OnKeyDownMacrosListBox( self, event ): |
|
event.Skip() |
|
|
|
def OnLeftDClickMacrosListBox( self, event ): |
|
event.Skip() |
|
|
|
### |
|
|
|
import os |
|
import os.path |
|
import sys |
|
import traceback |
|
import json |
|
import weakref |
|
import threading |
|
try: |
|
from UserDict import UserDict |
|
except: |
|
from collections import UserDict |
|
|
|
|
|
class MacroSettings: |
|
""" Macro settings manager. """ |
|
|
|
class Settings(UserDict): |
|
def __init__(self, parent, key, initialdata={}): |
|
UserDict.__init__(self, initialdata=None) |
|
self.parent = parent |
|
self.key = key |
|
self.data = initialdata |
|
|
|
def _save(self): |
|
self.parent.save() |
|
|
|
|
|
def __init__(self, settings_path): |
|
self.settings_path = settings_path |
|
self.settings = None |
|
self.load() |
|
|
|
def request_settings(self, key): |
|
""" Request to obtain the configuration specified by unique key. |
|
@param key unique key to your macro |
|
@return dict like object that holds settings, which has |
|
save function to store new value. |
|
""" |
|
settings = self.settings.get(key, None) |
|
if settings is None: |
|
settings = {} |
|
self.settings[key] = settings |
|
return self.__class__.Settings(self, key, settings) |
|
|
|
def load(self): |
|
if os.path.exists(self.settings_path): |
|
try: |
|
with open(self.settings_path) as f: |
|
self.settings = json.load(f) |
|
return |
|
except: |
|
pass |
|
self.settings = {} |
|
|
|
def save(self): |
|
# ToDo needs lock among Pcbnew instances |
|
try: |
|
with open(self.settings_path, "w") as f: |
|
json.dump(self.settings, f) |
|
except Exception as e: |
|
print(e) |
|
|
|
|
|
class _MacroDialog( _MacroDialogBase ): |
|
""" Provides easy way to execute macros from the specified directory. |
|
|
|
The path to the directory which your macros stored in can be set |
|
by _KIMACROS variable in the main menu - Preferences - Configure Paths. |
|
""" |
|
|
|
ENV_NAME = "kicad_script" |
|
SETTINGS_FILE_NAME = "macro_settings.json" |
|
SETTINGS_KEY = "macros.py_settings_key" |
|
POSITION_NAME = "position" |
|
macros_path = os.environ.get("_KIMACROS", "") |
|
settings_path = os.path.join(macros_path, SETTINGS_FILE_NAME) |
|
# the same with the macros because of the plugin directory might be readonly |
|
|
|
def __init__( self, parent ): |
|
_MacroDialogBase.__init__( self, parent ) |
|
self.macro_settings = MacroSettings(self.__class__.settings_path) |
|
self.settings = self.macro_settings.request_settings(self.__class__.SETTINGS_KEY) |
|
self.macros = None |
|
self.modules = {} |
|
self.set_macros_path(self.macros_path) |
|
self.reload_macros() |
|
|
|
self.SetPosition(self.load_initial_position()) |
|
|
|
def load_initial_position(self): |
|
""" Load last selected position in the macros list. """ |
|
key = self.__class__.POSITION_NAME |
|
if not key in self.settings: |
|
self.settings.update({key: (-1, -1)}) |
|
return self.settings[key] |
|
|
|
def save_current_position(self): |
|
""" Save last selected position in the list. """ |
|
try: |
|
position = self.GetPosition() |
|
self.settings[self.__class__.POSITION_NAME] = (position.x, position.y) |
|
self.settings._save() |
|
except Exception as e: |
|
print(e) |
|
|
|
def set_macros_path(self, path): |
|
""" Set macro path. """ |
|
self.__class__.macros_path = path |
|
if not path in sys.path: |
|
sys.path.append(path) |
|
|
|
def reload_macros(self): |
|
""" Reload macro list. |
|
Macro file has .py file extension and its name does not start with _. |
|
""" |
|
# ToDo support sub-directories |
|
self.macros = [] |
|
self._clear_macros_listbox() |
|
|
|
if not os.path.exists(self.macros_path): |
|
return |
|
for item in sorted(os.listdir(self.macros_path)): |
|
if not item.startswith("_") and item.endswith(".py"): |
|
self.macros.append(item) |
|
|
|
self._macros_listbox_insert_items(self.macros, 0) |
|
|
|
def exec_file(self, path): |
|
""" Execute specified file as a macro. |
|
@param path path to the macro file to execute |
|
""" |
|
try: |
|
with open(path) as f: |
|
exec(compile(f.read(), path, "exec"), |
|
{"__file__": path, |
|
"__name__": self.__class__.ENV_NAME, |
|
"_request_settings": self.macro_settings.request_settings}) |
|
except Exception: |
|
self.show_error(traceback.format_exc()) |
|
|
|
def execute_from_listbox(self): |
|
""" Execute selected macro in the list. """ |
|
n = self._macros_listbox_get_selection() |
|
if n != wx.NOT_FOUND: |
|
path = os.path.join(self.macros_path, self.macros[n]) |
|
if os.path.exists(path): |
|
self.exec_file(path) |
|
else: |
|
self.show_error("{} not found.".format(path)) |
|
|
|
def _clear_macros_listbox(self): |
|
self.m_MacrosListBox.Clear() |
|
|
|
def _macros_listbox_insert_items(self, items, pos): |
|
self.m_MacrosListBox.InsertItems(items, pos) |
|
|
|
def _macros_listbox_get_selection(self): |
|
return self.m_MacrosListBox.GetSelection() |
|
|
|
def show_error(self, message, caption="Error"): |
|
wx.MessageBox(message, caption=caption, style=wx.ICON_ERROR) |
|
|
|
# Handlers for MacroWindow events. |
|
def OnClose( self, event ): |
|
self.save_current_position() |
|
self.Destroy() |
|
|
|
def OnToolClicked( self, event ): |
|
id = event.GetId() |
|
if id == ID_EXECUTE: |
|
self.execute_from_listbox() |
|
|
|
elif id == ID_RELOAD: |
|
self.reload_macros() |
|
|
|
elif id == ID_DIRECTORY: |
|
try: |
|
dir_path = wx.DirSelector("Macro directory", self.macros_path) |
|
if dir_path: |
|
self.set_macros_path(dir_path) |
|
self.reload_macros() |
|
except Exception as e: |
|
wx.MessageBox(str(e)) |
|
|
|
def OnKeyDownMacrosListBox( self, event ): |
|
if event.GetKeyCode() == wx.WXK_SPACE:#wx.WXK_RETURN: |
|
self.execute_from_listbox() |
|
else: |
|
event.Skip() |
|
|
|
def OnLeftDClickMacrosListBox( self, event ): |
|
self.execute_from_listbox() |
|
|
|
def deregister(self): |
|
self.OnClose(None) |
|
|
|
|
|
def run(): |
|
""" Start macros dialog. |
|
Call this function in the main menu - Tools - Scripting Console as follows: |
|
import macros |
|
macros.run() |
|
""" |
|
d = _MacroDialog(None) |
|
d.Show() |
|
|
|
|
|
def register(): |
|
""" Register this as plug-in. """ |
|
# If _KIMACROSAUTOSTART is defined in the main menu - Preferences - Configure Paths, |
|
# and the value match is one of "1", "True" or "true", the macro dialog is automatically shown |
|
# at start up of the Pcbnew window. |
|
value = os.environ.get("_KIMACROSAUTOSTART", "0") |
|
if value in ("1", "True", "true"): |
|
return run() |