KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcb_scripting_tool.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/gpl-3.0.html
19 * or you may search the http://www.gnu.org website for the version 3 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include "pcb_scripting_tool.h"
25
26#include <Python.h>
27
28#include <wx/string.h>
29#include <wx/filename.h>
30#include <wx/msgdlg.h>
31
32#include <pybind11/eval.h>
33
34#include <action_plugin.h>
35#include <kiface_ids.h>
36#include <kiway.h>
37#include <macros.h>
38#include <pgm_base.h>
40#include <python_scripting.h>
41#include <string_utils.h>
42#include <tools/pcb_actions.h>
43#include <launch_ext.h>
44#include <confirm.h>
45
46#ifdef KICAD_IPC_API
48#endif
49
50using initfunc = PyObject* (*)(void);
51
53 PCB_TOOL_BASE( "pcbnew.ScriptingTool" )
54{}
55
56
59
60
62{
63}
64
65
66/* Legacy init based on https://peps.python.org/pep-0489/ */
67static PyObject* module_legacy_init( PyModuleDef* def )
68{
69 PyModuleDef_Slot* slots = def->m_slots;
70 def->m_slots = NULL;
71 PyObject* mod = PyModule_Create( def );
72 while( mod && slots->slot )
73 {
74 if( slots->slot == Py_mod_exec )
75 {
76 int ( *mod_exec )( PyObject* ) = (int ( * )( PyObject* )) slots->value;
77 if( mod_exec( mod ) != 0 )
78 {
79 Py_DECREF( mod );
80 mod = NULL;
81 }
82 }
83 ++slots;
84 }
85 return mod;
86}
87
88
90{
91 PyLOCK lock;
92 std::string pymodule( "_pcbnew" );
93
94 if( !SCRIPTING::IsModuleLoaded( pymodule ) )
95 {
96 KIFACE* kiface = frame()->Kiway().KiFACE( KIWAY::FACE_PCB );
97 initfunc pcbnew_init = reinterpret_cast<initfunc>( kiface->IfaceOrAddress( KIFACE_SCRIPTING_LEGACY ) );
98
99 // swig-4.4.0 implements PEP-489 multi-phase initialization.
100 // Handle both old single-phase and new multi-phase initialization.
101 // Modules that use multi-phase initialization will return a PyModuleDef, so then we force a legacy single-phase initialization.
102 PyObject* module_or_module_def = pcbnew_init();
103 if( !module_or_module_def )
104 {
105 wxMessageBox(
106 wxString::Format( _( "Failed first phase initializing Python module '%s', through Python C API." ),
107 pymodule.c_str() ),
108 _( "Scripting init" ), wxOK | wxICON_ERROR );
109 return false;
110 }
111
112 PyObject* mod = NULL;
113 if( PyObject_TypeCheck( module_or_module_def, &PyModuleDef_Type ) )
114 {
115 mod = module_legacy_init( (PyModuleDef*) module_or_module_def );
116 if( !mod )
117 {
118 wxMessageBox( wxString::Format(
119 _( "Failed second phase initializing Python module '%s', through Python C API." ),
120 pymodule.c_str() ),
121 _( "Scripting init" ), wxOK | wxICON_ERROR );
122 return false;
123 }
124 }
125 else
126 {
127 mod = module_or_module_def;
128 }
129
130 PyObject* sys_mod = PyImport_GetModuleDict();
131 PyDict_SetItemString( sys_mod, "_pcbnew", mod );
132 Py_DECREF( mod );
133
134 // plugins will be loaded later via ReloadPlugins()
135 }
136
137 return true;
138}
139
140
142{
143 // Reload Python plugins if they are newer than the already loaded, and load new plugins
144 // Remove all action plugins so that we don't keep references to old versions
146
147 try
148 {
149 PyLOCK lock;
151 }
152 catch( ... )
153 {}
154}
155
156
158{
159 // Reload Python plugins if they are newer than the already loaded, and load new plugins
160 // Remove all action plugins so that we don't keep references to old versions
162
163 try
164 {
165 PyLOCK lock;
167 }
168 catch( ... )
169 {
170 return -1;
171 }
172
173#ifdef KICAD_IPC_API
174 // TODO move this elsewhere when SWIG plugins are removed
175 if( Pgm().GetCommonSettings()->m_Api.enable_server )
176 Pgm().GetPluginManager().ReloadPlugins();
177#endif
178
180 {
181 // Action plugins can be modified, therefore the plugins menu must be updated:
182 frame()->ReCreateMenuBar();
183 // Recreate top toolbar to add action plugin buttons
184 frame()->RecreateToolbars();
185 // Post a size event to force resizing toolbar by the AUI manager:
186 frame()->PostSizeEvent();
187 }
188
189 return 0;
190}
191
192
194{
195 // Load pcbnew inside Python and load all the user plugins and package-based plugins
196 using namespace pybind11::literals;
197
198 auto locals = pybind11::dict(
199 "sys_path"_a = TO_UTF8( SCRIPTING::PyScriptingPath( SCRIPTING::PATH_TYPE::STOCK ) ),
200 "user_path"_a = TO_UTF8( SCRIPTING::PyScriptingPath( SCRIPTING::PATH_TYPE::USER ) ),
201 "third_party_path"_a =
202 TO_UTF8( SCRIPTING::PyPluginsPath( SCRIPTING::PATH_TYPE::THIRDPARTY ) ) );
203
204 pybind11::exec( R"(
205import sys
206import pcbnew
207pcbnew.LoadPlugins( sys_path, user_path, third_party_path )
208 )",
209 pybind11::globals(), locals );
210}
211
212
214{
215 wxString pluginpath( SCRIPTING::PyPluginsPath( SCRIPTING::PATH_TYPE::USER ) );
216
217 if( wxFileName::DirExists( pluginpath ) )
218 {
219 if( !LaunchExternal( pluginpath ) )
220 {
221 wxMessageBox( wxString::Format( _( "Unable to open plugin directory '%s'." ), pluginpath ),
222 _( "Plugin Directory" ), wxOK | wxICON_ERROR );
223 }
224
225 return;
226 }
227
228 wxString msg = wxString::Format( _( "The plugin directory '%s' does not exist. Create it?" ), pluginpath );
229
230 KICAD_MESSAGE_DIALOG dlg( nullptr, msg, _( "Plugin Directory" ), wxYES_NO | wxICON_QUESTION );
231
232 if( dlg.ShowModal() == wxID_YES )
233 {
234 if( wxFileName::Mkdir( pluginpath, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
235 {
236 if( !LaunchExternal( pluginpath ) )
237 {
238 wxMessageBox( wxString::Format( _( "Unable to open plugin directory '%s'." ), pluginpath ),
239 _( "Plugin Directory" ), wxOK | wxICON_ERROR );
240 }
241 }
242 else
243 {
244 wxMessageBox( wxString::Format( _( "Unable to create plugin directory '%s'." ), pluginpath ),
245 _( "Plugin Directory" ), wxOK | wxICON_ERROR );
246 }
247 }
248}
249
250
252{
254 return 0;
255}
256
257
Class PCBNEW_ACTION_PLUGINS.
static TOOL_ACTION pluginsReload
Definition actions.h:294
static void UnloadAll()
Unload (deregister) all action plugins.
@ FACE_PCB
pcbnew DSO
Definition kiway.h:303
static TOOL_ACTION pluginsShowFolder
Scripting Actions.
T * frame() const
PCB_TOOL_BASE(TOOL_ID aId, const std::string &aName)
Constructor.
int reloadPlugins(const TOOL_EVENT &aEvent)
< Reload Python plugins and reset toolbar (if in pcbnew)
~SCRIPTING_TOOL()
React to model/view changes.
void Reset(RESET_REASON aReason) override
Basic initialization.
bool Init() override
Init() is called once upon a registration of the tool.
static void callLoadPlugins()
Open the user's plugin folder in the system browser.
int showPluginFolder(const TOOL_EVENT &aEvent)
Bind handlers to corresponding TOOL_ACTIONs.
static void ShowPluginFolder()
static void ReloadPlugins()
void setTransitions() override
This method is meant to be overridden in order to specify handlers for events.
RESET_REASON
Determine the reason of reset for a tool.
Definition tool_base.h:78
Generic, UI-independent tool event.
Definition tool_event.h:171
void Go(int(T::*aStateFunc)(const TOOL_EVENT &), const TOOL_EVENT_LIST &aConditions=TOOL_EVENT(TC_ANY, TA_ANY))
Define which state (aStateFunc) to go when a certain event arrives (aConditions).
This file is part of the common library.
#define KICAD_MESSAGE_DIALOG
Definition confirm.h:52
#define _(s)
@ KIFACE_SCRIPTING_LEGACY
Definition kiface_ids.h:43
bool LaunchExternal(const wxString &aPath)
Launches the given file or folder in the host OS.
This file contains miscellaneous commonly used macros and functions.
static PyObject * module_legacy_init(PyModuleDef *def)
PyObject *(*)(void) initfunc
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Implement a participant in the KIWAY alchemy.
Definition kiway.h:156
IFACE KIFACE_BASE kiface("pcb_test_frame", KIWAY::FACE_PCB)