OrcaSlicer latest
OrcaSlicer
Loading...
Searching...
No Matches
Slic3r::PythonPluginBridge Class Reference

#include </home/runner/work/OrcaSlicer/OrcaSlicer/src/slic3r/plugin/PythonPluginBridge.hpp>

Public Member Functions

void begin_plugin_capture (const std::string &plugin_key)
std::vector< CapturedCapability > finalize_plugin_capture (const std::string &capture_key, const std::string &plugin_key, std::string &error)
void cancel_plugin_capture (const std::string &plugin_key)
void clear_pending_captures ()

Static Public Member Functions

static PythonPluginBridge & instance ()

Private Member Functions

 PythonPluginBridge ()=default
 PythonPluginBridge (const PythonPluginBridge &)=delete
PythonPluginBridge & operator= (const PythonPluginBridge &)=delete

Detailed Description

Constructor & Destructor Documentation

◆ PythonPluginBridge() [1/2]

Slic3r::PythonPluginBridge::PythonPluginBridge ( )
privatedefault

Referenced by PythonPluginBridge(), instance(), and operator=().

Here is the caller graph for this function:

◆ PythonPluginBridge() [2/2]

Slic3r::PythonPluginBridge::PythonPluginBridge ( const PythonPluginBridge & )
privatedelete

References PythonPluginBridge().

Here is the call graph for this function:

Member Function Documentation

◆ begin_plugin_capture()

void Slic3r::PythonPluginBridge::begin_plugin_capture ( const std::string & plugin_key)
104{
105 PythonGILState gil;
106 if (!gil) {
107 BOOST_LOG_TRIVIAL(warning) << "Cannot begin Python plugin capture while the interpreter is shutting down";
108 return;
109 }
110 BOOST_LOG_TRIVIAL(info) << "Beginning Python plugin capture for key " << plugin_key;
111 {
112 std::lock_guard<std::mutex> lock(g_registry_mutex);
113 // Start from a clean slot in case a previous failed load left pending Python classes
114 // for this same entry path.
115 g_pending_capabilities.erase(plugin_key);
116 g_pending_package.erase(plugin_key);
117 g_pending_permissions.erase(plugin_key);
118 }
119 // From now until finalize/cancel, @orca.plugin and register_capability() calls made by
120 // Python code on this thread are attributed to this plugin.
121 g_active_plugin_key = plugin_key;
122}
std::unordered_map< std::string, PluginPermissions > g_pending_permissions
Definition PythonPluginBridge.cpp:53
std::unordered_map< std::string, std::vector< py::object > > g_pending_capabilities
Definition PythonPluginBridge.cpp:51
std::string g_active_plugin_key
Definition PythonPluginBridge.cpp:49
std::unordered_map< std::string, py::object > g_pending_package
Definition PythonPluginBridge.cpp:52
@ lock
Definition GLGizmoEmboss.cpp:209

Referenced by Slic3r::plugin_loader::load().

Here is the caller graph for this function:

◆ cancel_plugin_capture()

void Slic3r::PythonPluginBridge::cancel_plugin_capture ( const std::string & plugin_key)
313{
314 PythonGILState gil;
315 BOOST_LOG_TRIVIAL(warning) << "Cancelling Python plugin capture for key " << plugin_key;
316
317 if (!gil) {
319 if (g_active_plugin_key == plugin_key)
320 g_active_plugin_key.clear();
321 return;
322 }
323
324 {
325 std::lock_guard<std::mutex> lock(g_registry_mutex);
326 // Import or dependency setup failed before finalization. Drop anything the module
327 // may already have registered under this key.
328 g_pending_capabilities.erase(plugin_key);
329 g_pending_package.erase(plugin_key);
330 g_pending_permissions.erase(plugin_key);
331 }
332
333 if (g_active_plugin_key == plugin_key)
334 g_active_plugin_key.clear();
335}
void discard_pending_capture_without_python(const std::string &plugin_key)
Definition PythonPluginBridge.cpp:76

Referenced by Slic3r::plugin_loader::load().

Here is the caller graph for this function:

◆ clear_pending_captures()

void Slic3r::PythonPluginBridge::clear_pending_captures ( )
338{
339 PythonGILState gil;
340 if (!gil) {
341 std::lock_guard<std::mutex> lock(g_registry_mutex);
342 BOOST_LOG_TRIVIAL(info) << "Clearing " << g_pending_capabilities.size()
343 << " pending Python plugin capture(s) without Python interpreter";
344 // py::object destruction would decref Python objects. If the interpreter is already
345 // gone, release the wrappers instead and intentionally skip decref.
346 for (auto& [plugin_key, plugins] : g_pending_capabilities) {
347 (void) plugin_key;
348 for (py::object& plugin : plugins)
349 (void) plugin.release();
350 }
352 for (auto& [plugin_key, pkg] : g_pending_package) {
353 (void) plugin_key;
354 (void) pkg.release();
355 }
356 g_pending_package.clear();
357 g_pending_permissions.clear();
358 g_active_plugin_key.clear();
359 return;
360 }
361
362 std::lock_guard<std::mutex> lock(g_registry_mutex);
363 BOOST_LOG_TRIVIAL(info) << "Clearing " << g_pending_capabilities.size() << " pending Python plugin capture(s)";
365 g_pending_package.clear();
366 g_pending_permissions.clear();
367 g_active_plugin_key.clear();
368}

Referenced by Slic3r::PluginManager::shutdown().

Here is the caller graph for this function:

◆ finalize_plugin_capture()

std::vector< CapturedCapability > Slic3r::PythonPluginBridge::finalize_plugin_capture ( const std::string & capture_key,
const std::string & plugin_key,
std::string & error )
127{
128 PythonGILState gil;
129 if (!gil) {
130 error = "Python interpreter is shutting down";
131 return {};
132 }
133 BOOST_LOG_TRIVIAL(info) << "Finalizing Python plugin capture for key " << capture_key;
134
135 // Phase 1: run the package class's register_capabilities() while the active key is
136 // still set. That method is expected to call orca.register_capability() once per
137 // capability class, and register_capability() needs g_active_plugin_key to know which
138 // pending bucket to append to.
139 {
140 auto clear_active_key = [&capture_key]() {
141 if (g_active_plugin_key == capture_key)
142 g_active_plugin_key.clear();
143 };
144 auto discard_pending_for_key = [&capture_key]() {
145 std::lock_guard<std::mutex> lock(g_registry_mutex);
146 g_pending_capabilities.erase(capture_key);
147 g_pending_package.erase(capture_key);
148 g_pending_permissions.erase(capture_key);
149 };
150
151 try {
152 // The @orca.plugin decorator records the package class during module import.
153 // Move it into a local py::object and remove it from the pending map so the
154 // registry no longer owns it once finalization starts.
155 py::object package_cls;
156 {
157 std::lock_guard<std::mutex> lock(g_registry_mutex);
158 auto it = g_pending_package.find(capture_key);
159 if (it != g_pending_package.end()) {
160 package_cls = it->second;
161 g_pending_package.erase(it);
162 }
163 }
164 if (!package_cls) {
165 error = "Plugin did not register a package class; decorate it with @orca.plugin";
166 BOOST_LOG_TRIVIAL(error) << error << " for key " << plugin_key;
167 discard_pending_for_key();
168 clear_active_key();
169 return {};
170 }
171
172 // The package instance is only a registration coordinator. It is not returned
173 // to the rest of the plugin system; only the capability classes it registers
174 // are kept.
175 py::object package = package_cls();
176 package.attr("register_capabilities")();
177
178 // Permission declarations are collected while register_capabilities() runs so the
179 // plugin can describe its needs without touching wx from the Python load worker. Ask
180 // only after registration returns, before capability instances are materialized.
181 PluginPermissions requested_permissions;
182 {
183 std::lock_guard<std::mutex> lock(g_registry_mutex);
184 auto it = g_pending_permissions.find(capture_key);
185 if (it != g_pending_permissions.end()) {
186 requested_permissions = std::move(it->second);
187 g_pending_permissions.erase(it);
188 }
189 }
190 const bool has_permission_request =
191 !requested_permissions.fs_read.empty() || !requested_permissions.fs_readwrite.empty() ||
192 !requested_permissions.network_http.empty() || !requested_permissions.network_socket.empty() ||
193 !requested_permissions.process.empty() || !requested_permissions.threading.empty();
194 if (has_permission_request) {
195 bool granted = false;
196 {
197 py::gil_scoped_release release;
198 granted = PluginAuditManager::instance().request_permissions(plugin_key, requested_permissions);
199 }
200 if (!granted) {
201 error = _u8L("Plugin permission request denied");
202 BOOST_LOG_TRIVIAL(warning) << error << " for key " << plugin_key;
203 discard_pending_for_key();
204 clear_active_key();
205 return {};
206 }
207 }
208 } catch (py::error_already_set& err) {
210 error = err.what();
211 BOOST_LOG_TRIVIAL(error) << "Plugin register_capabilities raised Python exception for key " << plugin_key
212 << " error=" << error;
213 discard_pending_for_key();
214 clear_active_key();
215 return {};
216 } catch (const std::exception& ex) {
217 error = ex.what();
218 BOOST_LOG_TRIVIAL(error) << "Plugin register_capabilities raised exception for key " << plugin_key
219 << " error=" << error;
220 discard_pending_for_key();
221 clear_active_key();
222 return {};
223 }
224 }
225
226 // Phase 2: move the capability classes that register_capabilities() appended into a
227 // local vector. From this point the pending registry no longer owns these py::objects.
228 std::vector<py::object> classes;
229 {
230 std::lock_guard<std::mutex> lock(g_registry_mutex);
231 auto it = g_pending_capabilities.find(capture_key);
232 if (it != g_pending_capabilities.end()) {
233 classes = std::move(it->second);
234 g_pending_capabilities.erase(it);
235 }
236 }
237
238 // Registration is complete. Later register_capability() calls should fail instead of
239 // accidentally attaching themselves to this plugin.
240 if (g_active_plugin_key == capture_key)
241 g_active_plugin_key.clear();
242
243 BOOST_LOG_TRIVIAL(info) << "Collected " << classes.size() << " registered capability class(es) for key " << capture_key;
244
245 std::vector<CapturedCapability> capabilities;
246 capabilities.reserve(classes.size());
247
248 // Phase 3: instantiate each registered capability class and convert it to the common
249 // C++ interface used by the rest of OrcaSlicer.
250 for (auto& cls : classes) {
251 try {
252 py::object instance = cls();
253 if (!py::isinstance<PluginCapabilityInterface>(instance)) {
254 error = "Registered capability must inherit from a PluginCapability base";
255 BOOST_LOG_TRIVIAL(error) << "Python plugin capture failed type check for key " << capture_key
256 << " error=" << error;
257 return {};
258 }
259
260 auto capability_iface = instance.cast<std::shared_ptr<PluginCapabilityInterface>>();
261 if (!capability_iface) {
262 error = "Failed to cast Python capability to PluginCapabilityInterface";
263 BOOST_LOG_TRIVIAL(error) << "Python plugin capture failed cast for key " << capture_key
264 << " error=" << error;
265 return {};
266 }
267
268 // This is a registered capability, not the transient orca.base package.
269 // get_name() is required on capabilities and is cached for preset lookup.
270 std::string name = capability_iface->get_name();
271
272 // Capability names feed ';'-delimited config/preset serialization and drive
273 // dispatch, so unlike display names they cannot be silently rewritten — a ';'
274 // here is a hard error that rejects the whole plugin capture.
275 if (name.find(';') != std::string::npos) {
276 error = "Capability name must not contain ';': " + name;
277 BOOST_LOG_TRIVIAL(error) << "Python plugin capture rejected capability for key " << capture_key
278 << " error=" << error;
279 return {};
280 }
281
282 auto handle = std::make_shared<PluginInstanceHandle>();
283 handle->keep_alive = instance;
284 handle->plugin = std::move(capability_iface);
285
286 CapturedCapability captured;
287 // Return a shared_ptr<PluginCapabilityInterface> while keeping PluginInstanceHandle
288 // as the owner, so the Python instance stays alive as long as the C++ interface does.
289 captured.instance = std::shared_ptr<PluginCapabilityInterface>(handle, handle->plugin.get());
290 captured.name = std::move(name);
291 capabilities.emplace_back(std::move(captured));
292 } catch (py::error_already_set& err) {
293 // Direct Python call (cls() / get_name() above), not a trampoline override —
294 // log the traceback here. GIL is held for the duration of finalize_plugin_capture.
296 error = err.what();
297 BOOST_LOG_TRIVIAL(error) << "Python plugin capture raised Python exception for key " << capture_key
298 << " error=" << error;
299 return {};
300 } catch (const std::exception& ex) {
301 error = ex.what();
302 BOOST_LOG_TRIVIAL(error) << "Python plugin capture raised exception for key " << capture_key
303 << " error=" << error;
304 return {};
305 }
306 }
307
308 BOOST_LOG_TRIVIAL(info) << "Instantiated " << capabilities.size() << " Python capability instance(s) for key " << plugin_key;
309 return capabilities;
310}
static PythonPluginBridge & instance()
Definition PythonPluginBridge.cpp:97
void log_python_exception_keep(pybind11::error_already_set &err)
Definition PythonInterpreter.cpp:96
#define _u8L(s)
Definition I18N.hpp:11

References _u8L, Slic3r::PluginPermissions::fs_read, Slic3r::PluginPermissions::fs_readwrite, Slic3r::CapturedCapability::instance, Slic3r::PluginAuditManager::instance(), instance(), Slic3r::log_python_exception_keep(), Slic3r::CapturedCapability::name, Slic3r::PluginPermissions::network_http, Slic3r::PluginPermissions::network_socket, Slic3r::PluginPermissions::process, and Slic3r::PluginPermissions::threading.

Referenced by Slic3r::plugin_loader::load().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ instance()

PythonPluginBridge & Slic3r::PythonPluginBridge::instance ( )
static
98{
99 static PythonPluginBridge bridge;
100 return bridge;
101}

References PythonPluginBridge().

Referenced by finalize_plugin_capture(), Slic3r::plugin_loader::load(), and Slic3r::PluginManager::shutdown().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ operator=()

PythonPluginBridge & Slic3r::PythonPluginBridge::operator= ( const PythonPluginBridge & )
privatedelete

References PythonPluginBridge().

Here is the call graph for this function:

The documentation for this class was generated from the following files: