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>
39#include <python_scripting.h>
40#include <string_utils.h>
41#include <tools/pcb_actions.h>
42#include <launch_ext.h>
43
44#ifdef KICAD_IPC_API
46#endif
47
48using initfunc = PyObject* (*)(void);
49
51 PCB_TOOL_BASE( "pcbnew.ScriptingTool" )
52{}
53
54
57
58
60{
61}
62
63
64/* Legacy init based on https://peps.python.org/pep-0489/ */
65static PyObject* module_legacy_init( PyModuleDef* def )
66{
67 PyModuleDef_Slot* slots = def->m_slots;
68 def->m_slots = NULL;
69 PyObject* mod = PyModule_Create( def );
70 while( mod && slots->slot )
71 {
72 if( slots->slot == Py_mod_exec )
73 {
74 int ( *mod_exec )( PyObject* ) = (int ( * )( PyObject* )) slots->value;
75 if( mod_exec( mod ) != 0 )
76 {
77 Py_DECREF( mod );
78 mod = NULL;
79 }
80 }
81 ++slots;
82 }
83 return mod;
84}
85
86
88{
89 PyLOCK lock;
90 std::string pymodule( "_pcbnew" );
91
92 if( !SCRIPTING::IsModuleLoaded( pymodule ) )
93 {
94 KIFACE* kiface = frame()->Kiway().KiFACE( KIWAY::FACE_PCB );
95 initfunc pcbnew_init = reinterpret_cast<initfunc>( kiface->IfaceOrAddress( KIFACE_SCRIPTING_LEGACY ) );
96
97 // swig-4.4.0 implements PEP-489 multi-phase initialization.
98 // Handle both old single-phase and new multi-phase initialization.
99 // Modules that use multi-phase initialization will return a PyModuleDef, so then we force a legacy single-phase initialization.
100 PyObject* module_or_module_def = pcbnew_init();
101 if( !module_or_module_def )
102 {
103 wxMessageBox(
104 wxString::Format( _( "Failed first phase initializing Python module '%s', through Python C API." ),
105 pymodule.c_str() ),
106 _( "Scripting init" ), wxOK | wxICON_ERROR );
107 return false;
108 }
109
110 PyObject* mod = NULL;
111 if( PyObject_TypeCheck( module_or_module_def, &PyModuleDef_Type ) )
112 {
113 mod = module_legacy_init( (PyModuleDef*) module_or_module_def );
114 if( !mod )
115 {
116 wxMessageBox( wxString::Format(
117 _( "Failed second phase initializing Python module '%s', through Python C API." ),
118 pymodule.c_str() ),
119 _( "Scripting init" ), wxOK | wxICON_ERROR );
120 return false;
121 }
122 }
123 else
124 {
125 mod = module_or_module_def;
126 }
127
128 PyObject* sys_mod = PyImport_GetModuleDict();
129 PyDict_SetItemString( sys_mod, "_pcbnew", mod );
130 Py_DECREF( mod );
131
132 // plugins will be loaded later via ReloadPlugins()
133 }
134
135 return true;
136}
137
138
140{
141 // Reload Python plugins if they are newer than the already loaded, and load new plugins
142 // Remove all action plugins so that we don't keep references to old versions
144
145 try
146 {
147 PyLOCK lock;
149 }
150 catch( ... )
151 {}
152}
153
154
156{
157 // Reload Python plugins if they are newer than the already loaded, and load new plugins
158 // Remove all action plugins so that we don't keep references to old versions
160
161 try
162 {
163 PyLOCK lock;
165 }
166 catch( ... )
167 {
168 return -1;
169 }
170
171#ifdef KICAD_IPC_API
172 // TODO move this elsewhere when SWIG plugins are removed
173 if( Pgm().GetCommonSettings()->m_Api.enable_server )
174 Pgm().GetPluginManager().ReloadPlugins();
175#endif
176
178 {
179 // Action plugins can be modified, therefore the plugins menu must be updated:
180 frame()->ReCreateMenuBar();
181 // Recreate top toolbar to add action plugin buttons
182 frame()->ReCreateHToolbar();
183 // Post a size event to force resizing toolbar by the AUI manager:
184 frame()->PostSizeEvent();
185 }
186
187 return 0;
188}
189
190
192{
193 // Load pcbnew inside Python and load all the user plugins and package-based plugins
194 using namespace pybind11::literals;
195
196 auto locals = pybind11::dict(
197 "sys_path"_a = TO_UTF8( SCRIPTING::PyScriptingPath( SCRIPTING::PATH_TYPE::STOCK ) ),
198 "user_path"_a = TO_UTF8( SCRIPTING::PyScriptingPath( SCRIPTING::PATH_TYPE::USER ) ),
199 "third_party_path"_a =
200 TO_UTF8( SCRIPTING::PyPluginsPath( SCRIPTING::PATH_TYPE::THIRDPARTY ) ) );
201
202 pybind11::exec( R"(
203import sys
204import pcbnew
205pcbnew.LoadPlugins( sys_path, user_path, third_party_path )
206 )",
207 pybind11::globals(), locals );
208}
209
210
212{
213 wxString pluginpath( SCRIPTING::PyPluginsPath( SCRIPTING::PATH_TYPE::USER ) );
214
215 if( wxFileName::DirExists( pluginpath ) )
216 {
217 if( !LaunchExternal( pluginpath ) )
218 {
219 wxMessageBox( wxString::Format( _( "Unable to open plugin directory '%s'." ), pluginpath ),
220 _( "Plugin Directory" ), wxOK | wxICON_ERROR );
221 }
222
223 return;
224 }
225
226 wxString msg = wxString::Format( _( "The plugin directory '%s' does not exist. Create it?" ), pluginpath );
227
228 wxMessageDialog dlg( nullptr, msg, _( "Plugin Directory" ), wxYES_NO | wxICON_QUESTION );
229
230 if( dlg.ShowModal() == wxID_YES )
231 {
232 if( wxFileName::Mkdir( pluginpath, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
233 {
234 if( !LaunchExternal( pluginpath ) )
235 {
236 wxMessageBox( wxString::Format( _( "Unable to open plugin directory '%s'." ), pluginpath ),
237 _( "Plugin Directory" ), wxOK | wxICON_ERROR );
238 }
239 }
240 else
241 {
242 wxMessageBox( wxString::Format( _( "Unable to create plugin directory '%s'." ), pluginpath ),
243 _( "Plugin Directory" ), wxOK | wxICON_ERROR );
244 }
245 }
246}
247
248
250{
252 return 0;
253}
254
255
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:300
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).
#define _(s)
@ KIFACE_SCRIPTING_LEGACY
Definition kiface_ids.h:44
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.
Definition pgm_base.cpp:946
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:155
IFACE KIFACE_BASE kiface("pcb_test_frame", KIWAY::FACE_PCB)