← Back to team overview

kicad-developers team mailing list archive

Re: Current state of ActionPlugin

 

Hi all,

Please find attached the patch for the action plugin menu.

There is a build option to enable/disable this feature:
KICAD_SCRIPTING_ACTION_MENU
=> It's disabled by default

Now there is a refresh button. It took new plugins, update existing ...
like in footprint wizard dialog.

I had remove the example from the patch as asked.

Regards,
Le 11/01/2017 à 18:58, jp charras a écrit :
> Le 11/01/2017 à 17:36, Jean-Samuel Reynaud a écrit :
>>>> Pay a particular attention to the fact plugins can be *now* updated (when the source file has
>>>> changed), and therefore menus can also be updated.
>>>> the "PCB_EDIT_FRAME::createActionPluginMenus()" method calls Connect, but Disconnect is never called
>>>> by destructors.
>>>> It can create issues when updating scripts, because it could be called more than once.
>>>
>>> If you need to update the menus when a script file changes, you would
>>> have to connect a wxFileWatcherEvent to the appropriate wxMainFrame
>>> object to monitor the loaded script file and update the menus
>>> accordingly.  It can be done but I would proceed with caution here.
>>>
>> Is it really necessary to add a file watcher ? Once python plugins are
>> loaded, python subsystem run the plugin as it was at start time (As I
>> understand).
>> I had perform a little test for that. If you modify a plugin while
>> pcbnew is running, this is always the "old" version that is running. Not
>> sure that is true for all OS...
> 
> I am thinking you are talking only about tests on footprint wizards.
> 
> On Windows, the newer is reloaded (as long as you run onUpdatePythonModulesClick).
> This is easy to see if the description string in the footprint plugin is modified.
> 
>> So menu are not supposed to change even if file change.
>> An other point: if I implement that file watching, I should also
>> implement a "directory watcher". In case a new plugins is installed
>> during pcbnew is running ;)
>> Perhaps an "update button": like onUpdatePythonModulesClick for
>> footprint plugins ?
> 
> From my experience when debugging a footprint wizard module:
> An "update button" is certainly enough: it is mainly for python script developers when they are
> writing and debugging a python script, not for an user who just uses a script.
> An automatic update is not useful, and perhaps not good.
> 
> The update button was asked since a long time, and I perfectly understand this wish.
> (The old way was: closing and rerun kicad)
> It is intended to reload python modules during their development.
> 
> Remark: onUpdatePythonModulesClick updates all loaded python modules.
> Of course only python modules which are newer than the currently loaded are reloaded.
> Therefore onUpdatePythonModulesClick will also update the action plugins.
> 

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8d71af2..52de977 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -63,6 +63,10 @@ option( KICAD_SCRIPTING_WXPYTHON
     "Build wxPython implementation for wx interface building in Python and py.shell (default OFF)."
     )
 
+option( KICAD_SCRIPTING_ACTION_MENU
+    "Build a tools menu with registred python plugins: actions plugins (default OFF)."
+    )
+
 option( KICAD_USE_SCH_IO_MANAGER
     "Build Eeschema with the I/O manager for handling schematic and symbol library I/O. (default OFF)"
     )
@@ -87,6 +91,12 @@ if ( KICAD_SCRIPTING_MODULES AND NOT KICAD_SCRIPTING )
     set ( KICAD_SCRIPTING ON )
 endif()
 
+# same with KICAD_SCRIPTING_ACTION_MENUS
+if ( KICAD_SCRIPTING_ACTION_MENU AND NOT KICAD_SCRIPTING )
+    message(STATUS "Changing KICAD_SCRIPTING to ON as needed by KICAD_SCRIPTING_ACTION_MENU")
+    set ( KICAD_SCRIPTING ON )
+endif()
+
 option( BUILD_GITHUB_PLUGIN "Build the GITHUB_PLUGIN for pcbnew." ON )
 
 option( KICAD_SPICE "Build Kicad with internal Spice simulator." OFF )
@@ -289,6 +299,10 @@ if( KICAD_SCRIPTING_WXPYTHON )
     add_definitions( -DKICAD_SCRIPTING_WXPYTHON )
 endif()
 
+if( KICAD_SCRIPTING_ACTION_MENU )
+    add_definitions( -DKICAD_SCRIPTING_ACTION_MENU )
+endif()
+
 if( KICAD_SPICE )
     add_definitions( -DKICAD_SPICE )
 endif()
diff --git a/include/wxPcbStruct.h b/include/wxPcbStruct.h
index 2968614..dc4200e 100644
--- a/include/wxPcbStruct.h
+++ b/include/wxPcbStruct.h
@@ -117,6 +117,28 @@ protected:
      */
     void enableGALSpecificMenus();
 
+#if defined(KICAD_SCRIPTING) && defined(KICAD_SCRIPTING_ACTION_MENU)
+    /**
+     * Function initActionPluginMenus
+     * Fill action menu with all registred action plugins
+     */
+    void initActionPluginMenus();
+
+    /**
+     * Function OnActionPlugin
+     * Launched by the menu when an action is called
+     * @param aEvent sent by wx
+     */
+    void OnActionPlugin( wxCommandEvent& aEvent);
+
+    /**
+     * Function OnActionPluginRefresh
+     * Refresh plugin list (reload Python plugins)
+     * @param aEvent sent by wx
+     */
+    void OnActionPluginRefresh( wxCommandEvent& aEvent);
+#endif
+
 
     // Has meaning only if DKICAD_SCRIPTING_WXPYTHON option is on
     /**
diff --git a/pcbnew/CMakeLists.txt b/pcbnew/CMakeLists.txt
index be46007..f6f7023 100644
--- a/pcbnew/CMakeLists.txt
+++ b/pcbnew/CMakeLists.txt
@@ -274,6 +274,7 @@ set( PCBNEW_CLASS_SRCS
     zones_polygons_test_connections.cpp
     zones_test_and_combine_areas.cpp
     class_footprint_wizard.cpp
+    class_action_plugin.cpp
 
     tools/selection_tool.cpp
     tools/selection_area.cpp
@@ -320,6 +321,7 @@ set( PCBNEW_SCRIPTING_PYTHON_HELPERS
     swig/python_scripting.cpp
     swig/pcbnew_scripting_helpers.cpp
     swig/pcbnew_footprint_wizards.cpp
+    swig/pcbnew_action_plugins.cpp
     )
 
 if( KICAD_SCRIPTING )
diff --git a/pcbnew/class_action_plugin.cpp b/pcbnew/class_action_plugin.cpp
new file mode 100644
index 0000000..4133907
--- /dev/null
+++ b/pcbnew/class_action_plugin.cpp
@@ -0,0 +1,155 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2017 KiCad Developers, see CHANGELOG.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+
+/**
+ * @file  class_action_plugin.cpp
+ * @brief Class ACTION_PLUGIN and ACTION_PLUGINS
+ */
+
+#include "class_action_plugin.h"
+
+
+ACTION_PLUGIN::~ACTION_PLUGIN()
+{
+}
+
+
+void ACTION_PLUGIN::register_action()
+{
+    ACTION_PLUGINS::register_action( this );
+}
+
+
+std::vector<ACTION_PLUGIN*> ACTION_PLUGINS::m_Actions;
+std::vector<int> ACTION_PLUGINS::m_ActionsMenu;
+
+
+ACTION_PLUGIN* ACTION_PLUGINS::GetAction( int aIndex )
+{
+    return m_Actions[aIndex];
+}
+
+
+ACTION_PLUGIN* ACTION_PLUGINS::GetActionByMenu( int menu )
+{
+    int max = GetActionsCount();
+
+    for( int i = 0; i<max; i++ )
+    {
+        if( m_ActionsMenu[i] == menu )
+            return m_Actions[i];
+    }
+
+    return NULL;
+}
+
+
+void ACTION_PLUGINS::SetActionMenu( int aIndex, int idMenu )
+{
+    m_ActionsMenu[aIndex] = idMenu;
+}
+
+
+int ACTION_PLUGINS::GetActionMenu( int aIndex )
+{
+    return m_ActionsMenu[aIndex];
+}
+
+
+ACTION_PLUGIN* ACTION_PLUGINS::GetAction( wxString aName )
+{
+    int max = GetActionsCount();
+
+    for( int i = 0; i<max; i++ )
+    {
+        ACTION_PLUGIN* action = GetAction( i );
+
+        wxString name = action->GetName();
+
+        if( name.Cmp( aName )==0 )
+            return action;
+    }
+
+    return NULL;
+}
+
+
+int ACTION_PLUGINS::GetActionsCount()
+{
+    return m_Actions.size();
+}
+
+
+void ACTION_PLUGINS::register_action( ACTION_PLUGIN* aAction )
+{
+    int updatedMenu = 0;
+
+    // Search for this entry do not register twice this action:
+    for( int ii = 0; ii < GetActionsCount(); ii++ )
+    {
+        if( aAction == GetAction( ii ) ) // Already registered
+            return;
+    }
+
+    // Search for a action with the same name, and remove it if found
+    for( int ii = 0; ii < GetActionsCount(); ii++ )
+    {
+        ACTION_PLUGIN* action = GetAction( ii );
+
+        if( action->GetName() == aAction->GetName() )
+        {
+            updatedMenu = GetActionMenu( ii );
+            m_Actions.erase( m_Actions.begin() + ii );
+            m_ActionsMenu.erase( m_ActionsMenu.begin() + ii );
+
+            delete action;
+
+            break;
+        }
+    }
+
+    m_Actions.push_back( aAction );
+    m_ActionsMenu.push_back( updatedMenu );
+}
+
+
+bool ACTION_PLUGINS::deregister_object( void* aObject )
+{
+    int max = GetActionsCount();
+
+    for( int i = 0; i<max; i++ )
+    {
+        ACTION_PLUGIN* action = GetAction( i );
+
+        if( action->GetObject() == aObject )
+        {
+            m_Actions.erase( m_Actions.begin() + i );
+            m_ActionsMenu.erase( m_ActionsMenu.begin() + i );
+            delete action;
+            return true;
+        }
+    }
+
+    return false;
+}
diff --git a/pcbnew/class_action_plugin.h b/pcbnew/class_action_plugin.h
new file mode 100644
index 0000000..5249e93
--- /dev/null
+++ b/pcbnew/class_action_plugin.h
@@ -0,0 +1,173 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2017 KiCad Developers, see CHANGELOG.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+
+/**
+ * @file  class_action_plugin.h
+ * @brief Class PCBNEW_ACTION_PLUGINS
+ */
+
+#ifndef CLASS_ACTION_PLUGIN_H
+#define CLASS_ACTION_PLUGIN_H
+#include <vector>
+#include <wxPcbStruct.h>
+
+/**
+ * Class ACTION_PLUGIN
+ * This is the parent class from where any action plugin class must
+ * derive
+ */
+class ACTION_PLUGIN
+{
+public:
+    ACTION_PLUGIN() {}
+    virtual ~ACTION_PLUGIN();
+
+    /**
+     * Function GetCategoryName
+     * @return the category name of the action (to be able to group action under the same submenu)
+     */
+    virtual wxString GetCategoryName() = 0;
+
+    /**
+     * Function GetName
+     * @return the name of the action
+     */
+
+    virtual wxString GetName() = 0;
+
+    /**
+     * Function GetDescription
+     * @return a description of the action plugin
+     */
+    virtual wxString GetDescription() = 0;
+
+    /**
+     * Function GetObject
+     * This method gets the pointer to the object from where this action constructs
+     * @return  it's a void pointer, as it could be a PyObject or any other
+     */
+    virtual void* GetObject() = 0;
+
+    /**
+     * Function Run
+     * This method the the action
+     */
+    virtual void Run() = 0;
+
+    /**
+     * Function register_action
+     * It's the standard method of a "ACTION_PLUGIN" to register itself into
+     * the ACTION_PLUGINS singleton manager
+     */
+    void register_action();
+};
+
+
+/**
+ * Class ACTION_PLUGINS
+ * Mainly static. Storing all plugins informations.
+ */
+class ACTION_PLUGINS
+{
+private:
+    /**
+     * ACTION_PLUGIN system wide static list
+     */
+    static std::vector<ACTION_PLUGIN*> m_Actions;
+
+    /**
+     * system wide static association between Plugin and menu id
+     */
+    static std::vector<int> m_ActionsMenu;
+
+public:
+
+    /**
+     * Function register_action
+     * An action calls this static method when it wants to register itself
+     * into the system actions
+     *
+     * @param aAction is the action plugin to be registered
+     */
+    static void register_action( ACTION_PLUGIN* aAction );
+
+    /**
+     * Function deregister_object
+     * Anyone calls this method to deregister an object which builds a action,
+     * it will lookup on the vector calling GetObject until find, then removed
+     * and deleted
+     *
+     * @param aObject is the action plugin object to be deregistered
+     */
+    static bool deregister_object( void* aObject );
+
+    /**
+     * Function GetAction
+     * @param aName is the action plugin name
+     * @return a action object by it's name or NULL if it isn't available.
+     */
+    static ACTION_PLUGIN* GetAction( wxString aName );
+
+    /**
+     * Function SetActionMenu
+     * Associate a menu id to an action plugin
+     * @param aInded is the action index
+     * @param idMenu is the associated menuitem id
+     */
+    static void SetActionMenu( int aIndex, int idMenu );
+
+
+    /**
+     * Function GetActionMenu
+     * Provide menu id for a plugin index
+     * @param aIndex is the action index
+     * @return associated menuitem id
+     */
+    static int GetActionMenu( int aIndex );
+
+
+    /**
+     * Function GetActionByMenu
+     * find action plugin associated to a menu id
+     * @param menu is the menu id (defined with SetActionMenu)
+     * @return the associated ACTION_PLUGIN (or null if not found)
+     */
+    static ACTION_PLUGIN* GetActionByMenu( int menu );
+
+
+    /**
+     * Function GetAction
+     * @return a action object by it's number or NULL if it isn't available.
+     * @param  aIndex is the action index in list
+     */
+    static ACTION_PLUGIN* GetAction( int aIndex );
+
+    /**
+     * Function GetActionsCount
+     * @return the number of actions available into the system
+     */
+    static int GetActionsCount();
+};
+
+#endif /* PCBNEW_ACTION_PLUGINS_H */
diff --git a/pcbnew/menubar_pcbframe.cpp b/pcbnew/menubar_pcbframe.cpp
index dbc9ebc..f1fb778 100644
--- a/pcbnew/menubar_pcbframe.cpp
+++ b/pcbnew/menubar_pcbframe.cpp
@@ -642,6 +642,25 @@ void PCB_EDIT_FRAME::ReCreateMenuBar()
                  KiBitmap( py_script_xpm ) );
 #endif
 
+#if defined(KICAD_SCRIPTING) && defined(KICAD_SCRIPTING_ACTION_MENU)
+    wxMenu* submenuActionPluginsMenu = new wxMenu();
+
+    AddMenuItem( toolsMenu, submenuActionPluginsMenu, ID_TOOLBARH_PCB_ACTION_PLUGIN,
+                 _( "&External plugins" ),
+                 _( "Give access to python action plugin" ),
+                 KiBitmap( hammer_xpm ) );
+
+    AddMenuItem( submenuActionPluginsMenu, ID_TOOLBARH_PCB_ACTION_PLUGIN_REFRESH,
+                 _( "&Refresh" ),
+                 _( "Reload all python plugins and refresh menu" ),
+                 KiBitmap( reload_xpm ) );
+
+    submenuActionPluginsMenu->AppendSeparator();
+
+#endif
+
+
+
     wxMenu* designRulesMenu = new wxMenu;
 
     AddMenuItem( designRulesMenu, ID_MENU_PCB_SHOW_DESIGN_RULES_DIALOG,
diff --git a/pcbnew/pcbframe.cpp b/pcbnew/pcbframe.cpp
index 35c743e..c38ba75 100644
--- a/pcbnew/pcbframe.cpp
+++ b/pcbnew/pcbframe.cpp
@@ -218,6 +218,11 @@ BEGIN_EVENT_TABLE( PCB_EDIT_FRAME, PCB_BASE_FRAME )
     EVT_TOOL( ID_TOOLBARH_PCB_MODE_TRACKS, PCB_EDIT_FRAME::OnSelectAutoPlaceMode )
     EVT_TOOL( ID_TOOLBARH_PCB_FREEROUTE_ACCESS, PCB_EDIT_FRAME::Access_to_External_Tool )
 
+
+#if defined(KICAD_SCRIPTING) && defined(KICAD_SCRIPTING_ACTION_MENU)
+    EVT_TOOL( ID_TOOLBARH_PCB_ACTION_PLUGIN_REFRESH, PCB_EDIT_FRAME::OnActionPluginRefresh )
+#endif
+
 #if defined( KICAD_SCRIPTING_WXPYTHON )
     // has meaning only with KICAD_SCRIPTING_WXPYTHON enabled
     EVT_TOOL( ID_TOOLBARH_PCB_SCRIPTING_CONSOLE, PCB_EDIT_FRAME::ScriptingConsoleEnableDisable )
@@ -474,6 +479,10 @@ PCB_EDIT_FRAME::PCB_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
 
     enableGALSpecificMenus();
 
+#if defined(KICAD_SCRIPTING) && defined(KICAD_SCRIPTING_ACTION_MENU)
+    initActionPluginMenus();
+#endif
+
     // disable Export STEP item if kicad2step does not exist
     wxString strK2S = Pgm().GetExecutablePath();
     #ifdef __WXMAC__
diff --git a/pcbnew/pcbnew_id.h b/pcbnew/pcbnew_id.h
index 19614c0..8e98979 100644
--- a/pcbnew/pcbnew_id.h
+++ b/pcbnew/pcbnew_id.h
@@ -280,6 +280,9 @@ enum pcbnew_ids
     ID_TOOLBARH_PCB_FREEROUTE_ACCESS,
     ID_TOOLBARH_PCB_SCRIPTING_CONSOLE,
 
+    ID_TOOLBARH_PCB_ACTION_PLUGIN,
+    ID_TOOLBARH_PCB_ACTION_PLUGIN_REFRESH,
+
     ID_AUX_TOOLBAR_PCB_SELECT_LAYER_PAIR,
 
     ID_PCB_GEN_POS_MODULES_FILE,
diff --git a/pcbnew/swig/pcbnew_action_plugins.cpp b/pcbnew/swig/pcbnew_action_plugins.cpp
new file mode 100644
index 0000000..5a2a248
--- /dev/null
+++ b/pcbnew/swig/pcbnew_action_plugins.cpp
@@ -0,0 +1,242 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2017 KiCad Developers, see CHANGELOG.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+/**
+ * @file  pcbnew_action_plugins.cpp
+ * @brief Class PCBNEW_PYTHON_ACTION_PLUGINS
+ */
+
+#include "pcbnew_action_plugins.h"
+#include <python_scripting.h>
+#include <stdio.h>
+#include <macros.h>
+#include <pcbnew_id.h>
+#include <menus_helpers.h>
+#include <class_drawpanel.h>    // m_canvas
+#include <class_board.h>
+#include <class_module.h>
+#include <class_track.h>
+#include <board_commit.h>
+#include <kicad_device_context.h>
+
+PYTHON_ACTION_PLUGIN::PYTHON_ACTION_PLUGIN( PyObject* aAction )
+{
+    PyLOCK lock;
+
+    this->m_PyAction = aAction;
+    Py_XINCREF( aAction );
+}
+
+
+PYTHON_ACTION_PLUGIN::~PYTHON_ACTION_PLUGIN()
+{
+    PyLOCK lock;
+
+    Py_XDECREF( this->m_PyAction );
+}
+
+
+PyObject* PYTHON_ACTION_PLUGIN::CallMethod( const char* aMethod, PyObject* aArglist )
+{
+    PyLOCK lock;
+
+    PyErr_Clear();
+    // pFunc is a new reference to the desired method
+    PyObject* pFunc = PyObject_GetAttrString( this->m_PyAction, aMethod );
+
+    if( pFunc && PyCallable_Check( pFunc ) )
+    {
+        PyObject* result = PyObject_CallObject( pFunc, aArglist );
+
+        if( PyErr_Occurred() )
+        {
+            wxMessageBox( PyErrStringWithTraceback(),
+                    wxT( "Exception on python action plugin code" ),
+                    wxICON_ERROR | wxOK );
+        }
+
+        if( result )
+        {
+            Py_XDECREF( pFunc );
+            return result;
+        }
+    }
+    else
+    {
+        printf( "method not found, or not callable: %s\n", aMethod );
+    }
+
+    if( pFunc )
+    {
+        Py_XDECREF( pFunc );
+    }
+
+    return NULL;
+}
+
+
+wxString PYTHON_ACTION_PLUGIN::CallRetStrMethod( const char* aMethod, PyObject* aArglist )
+{
+    wxString    ret;
+    PyLOCK      lock;
+
+    PyObject* result = CallMethod( aMethod, aArglist );
+
+    if( result )
+    {
+        const char* str_res = PyString_AsString( result );
+        ret = FROM_UTF8( str_res );
+        Py_DECREF( result );
+    }
+
+    return ret;
+}
+
+
+wxString PYTHON_ACTION_PLUGIN::GetCategoryName()
+{
+    PyLOCK lock;
+
+    return CallRetStrMethod( "GetCategoryName" );
+}
+
+
+wxString PYTHON_ACTION_PLUGIN::GetName()
+{
+    PyLOCK lock;
+
+    return CallRetStrMethod( "GetName" );
+}
+
+
+wxString PYTHON_ACTION_PLUGIN::GetDescription()
+{
+    PyLOCK lock;
+
+    return CallRetStrMethod( "GetDescription" );
+}
+
+
+void PYTHON_ACTION_PLUGIN::Run()
+{
+    PyLOCK lock;
+
+    CallMethod( "Run" );
+}
+
+
+void* PYTHON_ACTION_PLUGIN::GetObject()
+{
+    return (void*) m_PyAction;
+}
+
+
+void PYTHON_ACTION_PLUGINS::register_action( PyObject* aPyAction )
+{
+    PYTHON_ACTION_PLUGIN* fw = new PYTHON_ACTION_PLUGIN( aPyAction );
+
+    fw->register_action();
+}
+
+
+void PYTHON_ACTION_PLUGINS::deregister_action( PyObject* aPyAction )
+{
+    // deregister also destroyes the previously created "PYTHON_ACTION_PLUGIN object"
+    ACTION_PLUGINS::deregister_object( (void*) aPyAction );
+}
+
+
+#if defined(KICAD_SCRIPTING) && defined(KICAD_SCRIPTING_ACTION_MENU)
+
+void PCB_EDIT_FRAME::OnActionPlugin( wxCommandEvent& aEvent )
+{
+    int id = aEvent.GetId();
+
+    ACTION_PLUGIN* actionPlugin = ACTION_PLUGINS::GetActionByMenu( id );
+
+    if( actionPlugin )
+    {
+        // TODO: Adding recovery point for jobs
+        // BOARD_COMMIT commit( this );
+        // commit.Push( _( "External plugin" ) );
+
+        actionPlugin->Run();
+
+        OnModify();
+
+        if( IsGalCanvasActive() )
+        {
+            UseGalCanvas( GetGalCanvas() );
+        }
+        else
+        {
+            GetScreen()->SetModify();
+            Refresh();
+        }
+    }
+}
+
+
+void PCB_EDIT_FRAME::OnActionPluginRefresh( wxCommandEvent& aEvent )
+{
+    char cmd[1024];
+
+    snprintf( cmd, sizeof(cmd),
+            "pcbnew.LoadPlugins(\"%s\")", TO_UTF8( PyScriptingPath() ) );
+
+    PyLOCK lock;
+    // ReRun the Python method pcbnew.LoadPlugins (already called when starting Pcbnew)
+    PyRun_SimpleString( cmd );
+
+    initActionPluginMenus();
+}
+
+
+void PCB_EDIT_FRAME::initActionPluginMenus()
+{
+    wxMenu* actionMenu = GetMenuBar()->FindItem( ID_TOOLBARH_PCB_ACTION_PLUGIN )->GetSubMenu();
+
+    for( int i = 0; i < ACTION_PLUGINS::GetActionsCount(); i++ )
+    {
+        // Init menu only for not already created Items
+        if( ACTION_PLUGINS::GetActionMenu( i ) == 0 )
+        {
+            wxMenuItem* item = AddMenuItem( actionMenu, wxID_ANY,
+                    ACTION_PLUGINS::GetAction( i )->GetName(),
+                    ACTION_PLUGINS::GetAction( i )->GetDescription(),
+                    KiBitmap( hammer_xpm ) );
+
+            ACTION_PLUGINS::SetActionMenu( i, item->GetId() );
+
+            Connect(
+                    item->GetId(), wxEVT_COMMAND_MENU_SELECTED,
+                    (wxObjectEventFunction) (wxEventFunction) (wxCommandEventFunction) &
+                    PCB_EDIT_FRAME::OnActionPlugin );
+        }
+
+        // Delete is not handled by plugins system (yet)
+    }
+}
+
+
+#endif
diff --git a/pcbnew/swig/pcbnew_action_plugins.h b/pcbnew/swig/pcbnew_action_plugins.h
new file mode 100644
index 0000000..da2cbdb
--- /dev/null
+++ b/pcbnew/swig/pcbnew_action_plugins.h
@@ -0,0 +1,62 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2017 KiCad Developers, see CHANGELOG.TXT for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+/**
+ * @file  pcbnew_action_plugins.h
+ * @brief Class PCBNEW_ACTION_PLUGINS
+ */
+
+#ifndef PCBNEW_ACTION_PLUGINS_H
+#define PCBNEW_ACTION_PLUGINS_H
+#include <Python.h>
+#include <vector>
+#include <class_action_plugin.h>
+
+
+class PYTHON_ACTION_PLUGIN : public ACTION_PLUGIN
+{
+    PyObject* m_PyAction;
+    PyObject* CallMethod( const char* aMethod,
+            PyObject* aArglist = NULL );
+    wxString CallRetStrMethod( const char* aMethod,
+            PyObject* aArglist = NULL );
+
+public:
+    PYTHON_ACTION_PLUGIN( PyObject* action );
+    ~PYTHON_ACTION_PLUGIN();
+    wxString    GetCategoryName() override;
+    wxString    GetName() override;
+    wxString    GetDescription() override;
+    void        Run() override;
+    void*       GetObject() override;
+};
+
+
+class PYTHON_ACTION_PLUGINS
+{
+public:
+    static void register_action( PyObject* aPyAction );
+    static void deregister_action( PyObject* aPyAction );
+};
+
+#endif /* PCBNEW_ACTION_PLUGINS_H */
diff --git a/pcbnew/swig/plugins.i b/pcbnew/swig/plugins.i
index cff768b..118d407 100644
--- a/pcbnew/swig/plugins.i
+++ b/pcbnew/swig/plugins.i
@@ -24,6 +24,7 @@
 
 %{
 #include <pcbnew_footprint_wizards.h>
+#include <pcbnew_action_plugins.h>
 %}
 
 class PYTHON_FOOTPRINT_WIZARDS
@@ -33,3 +34,11 @@ public:
     static void deregister_wizard( PyObject* wizard );
 
 };
+
+class PYTHON_ACTION_PLUGINS
+{
+public:
+    static void register_action( PyObject* wizard );
+    static void deregister_action( PyObject* wizard );
+
+};
diff --git a/scripting/kicadplugins.i b/scripting/kicadplugins.i
index 57a3405..a008d04 100644
--- a/scripting/kicadplugins.i
+++ b/scripting/kicadplugins.i
@@ -208,7 +208,8 @@ class KiCadPlugin:
             return
 
         if isinstance(self,ActionPlugin):
-            pass # register to action plugins in C++
+            PYTHON_ACTION_PLUGINS.register_action(self)
+            return
 
         return
 
@@ -221,7 +222,8 @@ class KiCadPlugin:
             return
 
         if isinstance(self,ActionPlugin):
-            pass # deregister to action plugins in C++
+            PYTHON_ACTION_PLUGINS.deregister_action(self)
+            return
 
         return
 
@@ -560,8 +562,26 @@ class FootprintWizardPlugin(KiCadPlugin, object):
 
         return text
 
-class ActionPlugin(KiCadPlugin):
-    def __init__(self):
-        KiCadPlugin.__init__(self)
+class ActionPlugin(KiCadPlugin, object):
+    def __init__( self ):
+        KiCadPlugin.__init__( self )
+        self.defaults()
+
+    def defaults( self ):
+        self.name = "Undefined Action plugin"
+        self.category = "Undefined"
+        self.description = ""
+
+    def GetName( self ):
+        return self.name
+
+    def GetCategoryName( self ):
+        return self.category
+
+    def GetDescription( self ):
+        return self.description
+
+    def Run(self):
+        return
 
 }

Follow ups

References