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