OrcaSlicer latest
OrcaSlicer
Loading...
Searching...
No Matches
Slic3r::plugin_loader Namespace Reference

Namespaces

namespace  anonymous_namespace{PluginLoader.cpp}

Functions

void unload (Plugin &plugin)
bool load (const PluginDescriptor &descriptor, bool skip_deps, const std::vector< std::string > &capabilities_to_enable, const std::function< std::string(const Plugin &)> &registry_precheck, Plugin &out, std::string &error, bool *registration_failed)
bool install_packages (const std::vector< std::string > &pkgs, std::string &error)
bool inspect_local_plugin_package (const boost::filesystem::path &filepath, PluginDescriptor &plugin_descriptor, bool &existing_installation, std::string &error)
bool install_plugin (const boost::filesystem::path &filepath, const std::string &cloud_user_id, std::string &error)
bool install_plugin (const boost::filesystem::path &filepath, const std::string &cloud_user_id, PluginDescriptor &plugin_descriptor, std::string &error)

Function Documentation

◆ inspect_local_plugin_package()

bool Slic3r::plugin_loader::inspect_local_plugin_package ( const boost::filesystem::path & filepath,
PluginDescriptor & plugin_descriptor,
bool & existing_installation,
std::string & error )
592{
593 namespace fs = boost::filesystem;
594
595 error.clear();
596 plugin_descriptor = PluginDescriptor{};
597 existing_installation = false;
598
599 auto fail = [&error](const std::string& message) {
600 error = "Plugin package inspection failed: " + message;
601 return false;
602 };
603
604 boost::system::error_code ec;
605 const fs::path source_path = canonical_or_absolute(filepath);
606
607 if (!fs::exists(source_path, ec) || !fs::is_regular_file(source_path, ec))
608 return fail("Plugin package is not a file: " + filepath.string());
609
610 std::string metadata_error;
611 if (!read_local_plugin_package_metadata(source_path, plugin_descriptor, metadata_error))
612 return fail(metadata_error);
613
614 const fs::path final_dir = local_plugin_install_dir(source_path);
615 assign_local_plugin_key(plugin_descriptor, source_path);
616 plugin_descriptor.plugin_root = final_dir.string();
617
618 ec.clear();
619 const fs::file_status final_status = fs::status(final_dir, ec);
620 if (ec && final_status.type() != fs::file_not_found)
621 return fail("Failed to check existing plugin " + final_dir.string() + ": " + ec.message());
622
623 existing_installation = final_status.type() != fs::file_not_found && final_status.type() != fs::status_error;
624
625 return true;
626}
Definition Utils.hpp:97
Definition PluginDescriptor.hpp:53
std::string plugin_root
Definition PluginDescriptor.hpp:62

References Slic3r::PluginDescriptor::plugin_root.

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

Here is the caller graph for this function:

◆ install_packages()

bool Slic3r::plugin_loader::install_packages ( const std::vector< std::string > & pkgs,
std::string & error )
479{
480 if (pkgs.empty())
481 return true;
482
483 const std::string uv_path = PythonInterpreter::bundled_uv_path();
484 if (uv_path.empty()) {
485 error = "Bundled uv executable not found. Python package installation is unavailable.";
486 return false;
487 }
488
489 const std::string python_executable = PythonInterpreter::bundled_python_executable();
490 if (python_executable.empty()) {
491 error = "Bundled Python executable not found. Python package installation is unavailable.";
492 return false;
493 }
494
495 const std::string target_dir = PythonInterpreter::shared_packages_dir();
496
497 namespace fs = boost::filesystem;
498 boost::system::error_code ec;
499 fs::create_directories(target_dir, ec);
500 if (ec) {
501 error = "Failed to create package target directory: " + ec.message();
502 return false;
503 }
504
505 std::vector<std::string> args = {"pip", "install", "--python", python_executable, "--no-python-downloads", "--target", target_dir};
506 args.insert(args.end(), pkgs.begin(), pkgs.end());
507
508 BOOST_LOG_TRIVIAL(info) << "Installing Python packages via uv for " << python_executable << ": "
509 << boost::algorithm::join(pkgs, ", ");
510
511 try {
512 namespace process = boost::process;
513
514 process::ipstream std_err;
515 process::child child(uv_path, process::args(args),
516#ifdef _WIN32
517 // uv.exe (and the python.exe it spawns) are console-subsystem programs.
518 // OrcaSlicer is a GUI app with no console of its own, so without this flag
519 // Windows allocates a fresh console window for the child that flashes on
520 // screen during startup plugin loading. Matches ProcessRunner/MediaPlayCtrl.
521 process::windows::create_no_window,
522#endif
523 process::std_err > std_err);
524
525 std::string err_output;
526 std::thread stderr_reader([&std_err, &err_output]() {
527 std::string line;
528 while (std::getline(std_err, line)) {
529 err_output += line + '\n';
530 }
531 });
532
533 // Wait up to 120 seconds for package install to complete.
534 //
535 // NOTE: we poll child.running() instead of calling child.wait_for().
536 // On macOS (which lacks sigtimedwait) boost.process v1 implements the
537 // timed waits with a sigwait()-based fallback that deadlocks inside a
538 // multi-threaded process: SIGCHLD is delivered to an arbitrary thread
539 // and consumed by an async handler, so the worker thread's sigwait()
540 // never returns and the timeout never fires — the plugin load hangs
541 // forever (the "Loading" status never clears). child.running() uses
542 // waitpid(WNOHANG), which behaves correctly on every platform.
543 // (boost.process v2, in Boost >= 1.86, implements timed waits via Asio
544 // and is unaffected — revisit this when the bundled Boost is upgraded.)
545 constexpr auto kInstallTimeout = std::chrono::seconds(120);
546 const auto deadline = std::chrono::steady_clock::now() + kInstallTimeout;
547
548 std::error_code run_ec;
549 while (child.running(run_ec)) {
550 if (run_ec) {
551 stderr_reader.join();
552 error = "Failed to query uv process status: " + run_ec.message();
553 BOOST_LOG_TRIVIAL(error) << error;
554 return false;
555 }
556 if (std::chrono::steady_clock::now() >= deadline) {
557 std::error_code term_ec;
558 child.terminate(term_ec);
559 child.wait(term_ec);
560 stderr_reader.join();
561 error = "uv pip install timed out after 120s";
562 BOOST_LOG_TRIVIAL(error) << error;
563 return false;
564 }
565 std::this_thread::sleep_for(std::chrono::milliseconds(100));
566 }
567 stderr_reader.join();
568
569 const int exit_code = child.exit_code();
570 if (exit_code != 0) {
571 if (!err_output.empty())
572 error = "uv pip install failed (exit code " + std::to_string(exit_code) + "): " + err_output;
573 else
574 error = "uv pip install failed with exit code " + std::to_string(exit_code);
575 BOOST_LOG_TRIVIAL(error) << error;
576 return false;
577 }
578
579 BOOST_LOG_TRIVIAL(info) << "Python packages installed successfully";
580 return true;
581 } catch (const std::exception& ex) {
582 error = std::string("Failed to run uv: ") + ex.what();
583 BOOST_LOG_TRIVIAL(error) << error;
584 return false;
585 }
586}
static std::string bundled_uv_path()
Definition PythonInterpreter.cpp:455
static std::string shared_packages_dir()
Definition PythonInterpreter.cpp:440
static std::string bundled_python_executable()
Definition PythonInterpreter.cpp:445
bool process(std::exception_ptr &eptr)
Definition EmbossJob.cpp:1427

References Slic3r::PythonInterpreter::bundled_python_executable(), Slic3r::PythonInterpreter::bundled_uv_path(), and Slic3r::PythonInterpreter::shared_packages_dir().

Referenced by load().

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

◆ install_plugin() [1/2]

bool Slic3r::plugin_loader::install_plugin ( const boost::filesystem::path & filepath,
const std::string & cloud_user_id,
PluginDescriptor & plugin_descriptor,
std::string & error )
638{
639 namespace fs = boost::filesystem;
640
641 const std::string cloud_uuid = plugin_descriptor.cloud_uuid();
642 const bool is_cloud_install = !cloud_uuid.empty();
643
644 fs::path backup_dir;
645 bool backup_created = false;
646
647 auto fail = [&error](const std::string& message) {
648 error = GUI::format(_u8L("Plugin installation failed: %1%"), message);
649 return false;
650 };
651
652 boost::system::error_code ec;
653 const fs::path source_path = canonical_or_absolute(filepath);
654
655 if (!fs::exists(source_path, ec) || !fs::is_regular_file(source_path, ec))
656 return fail(GUI::format(_u8L("Plugin package is not a file: %1%"), filepath.string()));
657
658 const std::string ext = plugin_package_extension(source_path);
659 if (ext != ".py" && ext != ".whl")
660 return fail(GUI::format(_u8L("Plugin package must be a .py or .whl file, got: %1%"), ext));
661 // The cloud UUID is concatenated into the install path (final_dir = plugin_root / cloud_uuid).
662 // It comes verbatim from the server's catalog JSON, so reject anything that could escape the
663 // per-user plugin root (traversal, leading dots, separators) before touching the filesystem.
664 if (is_cloud_install && !is_valid_plugin_id(cloud_uuid))
665 return fail(GUI::format(_u8L("Cloud plugin UUID is not a valid identifier: %1%"), cloud_uuid));
666
667 const fs::path plugin_root = is_cloud_install && !cloud_user_id.empty() ? fs::path(get_cloud_plugin_dir(cloud_user_id)) :
668 local_plugin_root();
669 fs::create_directories(plugin_root, ec);
670 if (ec)
671 // TRN %1% is a folder path, %2% the system error message.
672 return fail(GUI::format(_u8L("Failed to create plugin directory %1%: %2%"), plugin_root.string(), ec.message()));
673
674 std::string meta_error;
675 if (is_cloud_install) {
676 PluginDescriptor package_metadata;
677 if (!read_local_plugin_package_metadata(source_path, package_metadata, meta_error))
678 return fail(meta_error);
679 apply_plugin_metadata_fallbacks(plugin_descriptor, package_metadata);
680 plugin_descriptor.dependencies = std::move(package_metadata.dependencies);
681
682 if (plugin_descriptor.plugin_key.empty())
683 plugin_descriptor.plugin_key = cloud_uuid;
684 if (!plugin_descriptor.cloud.has_value())
685 plugin_descriptor.cloud = CloudPluginState{cloud_uuid, true, false, false, false};
686 else
687 plugin_descriptor.cloud->installed = true;
688 } else {
689 if (!read_local_plugin_package_metadata(source_path, plugin_descriptor, meta_error))
690 return fail(meta_error);
691 // A side-loaded .py with no (or incomplete) PEP 723 block parses as success but has no
692 // usable identity: an empty name collides in local_plugin_install_dir() (every nameless
693 // plugin maps to orca_plugins/"path"), so it installs but never loads. Wheels are exempt:
694 // read_wheel_plugin_metadata() already requires a Name and may legitimately leave the type
695 // Unknown.
696 if (ext == ".py" && plugin_descriptor.name.empty())
697 return fail("Side-loaded .py plugin is missing required PEP 723 metadata: 'name' is required");
698 }
699
700 const fs::path final_dir = is_cloud_install ? plugin_root / cloud_uuid : local_plugin_install_dir(source_path);
701 const fs::path dest_file = final_dir / source_path.filename();
702
703 // Local key is the plugin file stem; cloud key is the cloud UUID.
704 if (is_cloud_install)
705 plugin_descriptor.plugin_key = cloud_uuid;
706 else
707 assign_local_plugin_key(plugin_descriptor, source_path);
708
709 // Backup existing installation if present.
710 if (fs::exists(final_dir, ec)) {
711 backup_dir = plugin_root / (final_dir.filename().string() + ".backup-" + fs::unique_path("%%%%-%%%%-%%%%").string());
712 fs::rename(final_dir, backup_dir, ec);
713 if (ec)
714 // TRN %1% is a folder path, %2% the system error message.
715 return fail(GUI::format(_u8L("Failed to back up existing plugin %1%: %2%"), final_dir.string(), ec.message()));
716 backup_created = true;
717 }
718
719 // Create the plugin directory and copy the file.
720 fs::create_directories(final_dir, ec);
721 if (ec) {
722 if (backup_created) {
723 boost::system::error_code restore_ec;
724 fs::rename(backup_dir, final_dir, restore_ec);
725 }
726 return fail(GUI::format(_u8L("Failed to create plugin directory %1%: %2%"), final_dir.string(), ec.message()));
727 }
728
729 fs::copy_file(source_path, dest_file, ec);
730 if (ec) {
731 if (backup_created) {
732 boost::system::error_code restore_ec;
733 fs::remove_all(final_dir, restore_ec);
734 fs::rename(backup_dir, final_dir, restore_ec);
735 }
736 // TRN %1% is a file path, %2% the system error message.
737 return fail(GUI::format(_u8L("Failed to copy plugin file to %1%: %2%"), dest_file.string(), ec.message()));
738 }
739
740 // Update entry_path to the installed location.
741 plugin_descriptor.plugin_root = final_dir.string();
742 plugin_descriptor.entry_path = dest_file.string();
743 plugin_descriptor.set_metadata_valid(true);
744 plugin_descriptor.clear_error();
745
746 // Write install state sidecar before removing the backup.
747 {
748 PluginDescriptor installed_entry = plugin_descriptor;
749 if (is_cloud_install)
750 installed_entry.cloud = CloudPluginState{cloud_uuid, true, false, false, plugin_descriptor.cloud->is_mine};
751 if (!write_install_state(final_dir, installed_entry)) {
752 // Roll back: remove new files and restore backup.
753 boost::system::error_code rollback_ec;
754 fs::remove_all(final_dir, rollback_ec);
755 if (backup_created)
756 fs::rename(backup_dir, final_dir, rollback_ec);
757 return fail(GUI::format(_u8L("Failed to write plugin install state: %1%"), (final_dir / INSTALL_STATE_FILE).string()));
758 }
759 }
760
761 if (backup_created) {
762 boost::system::error_code remove_ec;
763 fs::remove_all(backup_dir, remove_ec);
764 if (remove_ec)
765 BOOST_LOG_TRIVIAL(warning) << "Failed to remove plugin backup " << backup_dir.string() << ": " << remove_ec.message();
766 }
767
768 BOOST_LOG_TRIVIAL(info) << "Installed plugin " << plugin_descriptor.name << " to " << final_dir.string();
769
770 if (is_cloud_install) {
771 boost::system::error_code remove_ec;
772 fs::remove(source_path, remove_ec);
773 }
774 return true;
775}
std::string format(const char *fmt, TArgs &&... args)
Definition format.hpp:36
bool read_local_plugin_package_metadata(const boost::filesystem::path &source_path, PluginDescriptor &plugin_descriptor, std::string &error)
Definition PluginLoader.cpp:91
void assign_local_plugin_key(PluginDescriptor &plugin_descriptor, const boost::filesystem::path &entry_file)
Definition PluginLoader.cpp:86
boost::filesystem::path local_plugin_install_dir(const boost::filesystem::path &source_path)
Definition PluginLoader.cpp:79
void apply_plugin_metadata_fallbacks(PluginDescriptor &target, const PluginDescriptor &fallback)
Definition PluginDescriptor.hpp:149
bool write_install_state(const boost::filesystem::path &plugin_dir, const PluginInstallState &state)
Definition PluginFsUtils.cpp:859
std::string get_cloud_plugin_dir(const std::string &user_id)
Definition PluginFsUtils.cpp:47
bool is_valid_plugin_id(const std::string &id)
Definition PluginFsUtils.cpp:346
const char *const INSTALL_STATE_FILE
Definition PluginFsUtils.cpp:39
Definition PluginDescriptor.hpp:18
#define _u8L(s)
Definition I18N.hpp:11
void set_metadata_valid(bool is_valid)
Definition PluginDescriptor.hpp:129
std::string plugin_key
Definition PluginDescriptor.hpp:54
std::string entry_path
Definition PluginDescriptor.hpp:63
std::optional< CloudPluginState > cloud
Definition PluginDescriptor.hpp:69
std::vector< std::string > dependencies
Definition PluginDescriptor.hpp:65
std::string name
Definition PluginDescriptor.hpp:55
std::string cloud_uuid() const
Definition PluginDescriptor.hpp:83
void clear_error()
Definition PluginDescriptor.hpp:145

References _u8L, Slic3r::apply_plugin_metadata_fallbacks(), Slic3r::PluginDescriptor::clear_error(), Slic3r::PluginDescriptor::cloud, Slic3r::PluginDescriptor::cloud_uuid(), Slic3r::PluginDescriptor::dependencies, Slic3r::PluginDescriptor::entry_path, Slic3r::GUI::format(), Slic3r::get_cloud_plugin_dir(), Slic3r::INSTALL_STATE_FILE, Slic3r::is_valid_plugin_id(), Slic3r::PluginDescriptor::name, Slic3r::PluginDescriptor::plugin_key, Slic3r::PluginDescriptor::plugin_root, Slic3r::PluginDescriptor::set_metadata_valid(), and Slic3r::write_install_state().

Here is the call graph for this function:

◆ install_plugin() [2/2]

bool Slic3r::plugin_loader::install_plugin ( const boost::filesystem::path & filepath,
const std::string & cloud_user_id,
std::string & error )
629{
630 PluginDescriptor descriptor{};
631 return install_plugin(filepath, cloud_user_id, descriptor, error);
632}
bool install_plugin(const boost::filesystem::path &filepath, const std::string &cloud_user_id, std::string &error)
Definition PluginLoader.cpp:628

References install_plugin().

Referenced by install_plugin(), and Slic3r::PluginManager::install_plugin().

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

◆ load()

bool Slic3r::plugin_loader::load ( const PluginDescriptor & descriptor,
bool skip_deps,
const std::vector< std::string > & capabilities_to_enable,
const std::function< std::string(const Plugin &)> & registry_precheck,
Plugin & out,
std::string & error,
bool * registration_failed )
234{
235 error.clear();
236 out = Plugin{};
237 if (registration_failed != nullptr)
238 *registration_failed = false;
239
240 BOOST_LOG_TRIVIAL(info) << "[plugin_loader::load] START plugin=" << descriptor.plugin_key
241 << " thread=" << std::this_thread::get_id();
242
244 if (!interpreter.is_initialized()) {
245 error = GUI::format(_u8L("Python interpreter not initialized: %1%"), interpreter.last_error());
246 return false;
247 }
248
249 if (!descriptor.is_metadata_valid()) {
250 error = GUI::format(_u8L("Plugin manifest is invalid: %1%"), descriptor.plugin_key);
251 if (descriptor.has_error())
252 error += " - " + descriptor.normalized_error();
253 return false;
254 }
255
256 // Serialize the entire load sequence — only one thread may run it at a time. This prevents
257 // sys.path / sys.modules races when two plugins share the same entry-point filename (e.g.
258 // plugin.py), and keeps the caller's check-then-commit of the registry free of an interleaved
259 // load (which could otherwise force a wasted on_load/on_unload rollback). install_packages()
260 // has a 120s timeout so this cannot block indefinitely.
261 static std::mutex load_serializer;
262 std::lock_guard<std::mutex> load_lock(load_serializer);
263
264 Plugin plugin;
265 plugin.descriptor = descriptor;
266 plugin.module_name = plugin_module_name(descriptor);
267
268 if (!skip_deps) {
269 std::string pkg_install_error;
270 if (!install_packages(descriptor.dependencies, pkg_install_error)) {
271 error = GUI::format(_u8L("Failed to install plugin dependencies: %1%"), pkg_install_error);
272 return false;
273 }
274 }
275
276 PythonPluginBridge& bridge = PythonPluginBridge::instance();
277 bridge.begin_plugin_capture(descriptor.entry_path);
278
279 std::string wheel_error;
280 if (!add_wheel_dependencies_to_sys_path(descriptor, plugin.plugin_sys_paths, wheel_error)) {
281 bridge.cancel_plugin_capture(descriptor.entry_path);
282 error = std::move(wheel_error);
283 return false;
284 }
285
286 std::string load_error;
287 PyObject* module = import_plugin_module(descriptor, plugin.plugin_sys_paths, plugin.plugin_modules, load_error);
288 if (module == nullptr) {
289 bridge.cancel_plugin_capture(descriptor.entry_path);
290 error = GUI::format(_u8L("Failed to load plugin module: %1%"), load_error);
291 return false;
292 }
293
294 // From here on the module reference is owned by `plugin`: every failure path below returns and
295 // lets ~Plugin release it.
296 plugin.module = module;
297
298 // finalize_plugin_capture runs the module's @orca.plugin package class register_capabilities()
299 // (while the active plugin key is set), then instantiates each registered capability and caches
300 // its get_name(). Returns one entry per capability.
301 std::string bridge_error;
302 auto capabilities_found = bridge.finalize_plugin_capture(descriptor.entry_path, descriptor.plugin_key, bridge_error);
303 if (!bridge_error.empty()) {
304 capabilities_found.clear();
305 error = GUI::format(_u8L("Plugin registration failed: %1%"), bridge_error);
306 if (registration_failed != nullptr)
307 *registration_failed = true;
308 return false;
309 }
310 if (capabilities_found.empty()) {
311 error = _u8L("Plugin module did not register any capabilities");
312 return false;
313 }
314
315 plugin.descriptor.clear_error();
316
317 // The user's per-capability enable choices live in the sidecar — the only durable record, since
318 // a capability has no state (and no existence) while the package is not loaded.
319 PluginInstallState install_state;
320 const bool have_install_state = !descriptor.plugin_root.empty() &&
321 read_install_state(boost::filesystem::path(descriptor.plugin_root), install_state);
322
323 // get_name()/get_type() are read exactly once — here, under the GIL this block already holds —
324 // and cached on the capability, which is the only place capability state lives.
325 std::unordered_map<PluginCapabilityType, std::unordered_set<std::string>> seen_capabilities;
326 std::vector<std::shared_ptr<PluginCapabilityInterface>> capabilities;
327 capabilities.reserve(capabilities_found.size());
328 std::string materialization_error;
329
330 {
331 PythonGILState gil;
332 if (!gil) {
333 materialization_error = "Python interpreter is shutting down";
334 } else {
335 try {
336 for (auto& found : capabilities_found) {
337 if (!found.instance) {
338 materialization_error = "Plugin capability instance is null";
339 break;
340 }
341
342 std::shared_ptr<PluginCapabilityInterface> instance = std::move(found.instance);
343 const PluginCapabilityType type = instance->get_type();
344
345 // An empty request restores the sidecar state (or enables capabilities by
346 // default). An explicit request overrides the sidecar for that capability;
347 // all other capabilities retain their persisted state.
348 const bool explicitly_requested =
349 std::find(capabilities_to_enable.begin(), capabilities_to_enable.end(), found.name) !=
350 capabilities_to_enable.end();
351 bool enabled = capabilities_to_enable.empty() || explicitly_requested;
352 if (have_install_state && !explicitly_requested) {
353 for (const auto& [cap_name, cap_enabled] : install_state.capabilities) {
354 if (cap_name == found.name) {
355 enabled = cap_enabled;
356 break;
357 }
358 }
359 }
360
361 if (!seen_capabilities[type].insert(found.name).second) {
362 // TRN %1% is the capability name, %2% the capability type (e.g. Script).
363 materialization_error = GUI::format(_u8L("Plugin declares duplicate capability '%1%' for type %2%"),
364 found.name, PluginConfig::capability_type_label(type));
365 break;
366 }
367
368 instance->set_audit_plugin_key(descriptor.plugin_key);
369 instance->set_resolved_identity(found.name, type);
370 instance->set_enabled(enabled);
371
372 // Cache has_config_ui() once, under this same GIL, so the GUI can pick the
373 // capability's custom UI vs. the host JSON editor without touching Python. It is
374 // optional and plugin-authored: a raising or non-bool override only costs this
375 // capability its custom UI, so it is caught locally rather than failing the load.
376 try {
377 instance->set_config_ui_available(instance->has_config_ui());
378 } catch (const std::exception& ex) {
379 BOOST_LOG_TRIVIAL(warning)
380 << "Plugin capability '" << found.name << "' of plugin '" << descriptor.plugin_key
381 << "': has_config_ui() failed (" << ex.what() << "); falling back to the default JSON editor";
382 instance->set_config_ui_available(false);
383 }
384
385 // ResourceTexture capabilities resolve their texture directory here, under the
386 // GIL this block already holds, and cache the validated result. A resolution
387 // fault only costs this capability its textures, so it is caught locally like
388 // has_config_ui() above rather than failing the load.
389 if (auto* resource_texture = dynamic_cast<ResourceTexturePluginCapability*>(instance.get())) {
390 try {
391 const std::string texture_dir = resource_texture->get_texture_dir();
392 if (texture_dir.empty()) {
393 resource_texture->set_texture_dir({});
394 } else {
395 std::string texture_error;
396 collect_resource_texture_files(texture_dir, descriptor.plugin_root, texture_error);
397 if (texture_error.empty()) {
398 resource_texture->set_texture_dir(texture_dir);
399 } else {
400 BOOST_LOG_TRIVIAL(warning)
401 << "Plugin capability '" << found.name << "' of plugin '" << descriptor.plugin_key
402 << "': texture directory rejected (" << texture_error
403 << "); the capability will provide no textures";
404 resource_texture->set_texture_dir({});
405 }
406 }
407 } catch (const std::exception& ex) {
408 BOOST_LOG_TRIVIAL(warning)
409 << "Plugin capability '" << found.name << "' of plugin '" << descriptor.plugin_key
410 << "': get_texture_dir() failed (" << ex.what()
411 << "); the capability will provide no textures";
412 resource_texture->set_texture_dir({});
413 }
414 }
415
416 capabilities.push_back(std::move(instance));
417 }
418 } catch (const std::exception& ex) {
419 materialization_error = GUI::format(_u8L("Plugin capability materialization failed: %1%"), ex.what());
420 } catch (...) {
421 materialization_error = _u8L("Plugin capability materialization failed");
422 }
423 }
424
425 if (!materialization_error.empty()) {
426 capabilities.clear();
427 capabilities_found.clear();
428 }
429 }
430 if (!materialization_error.empty()) {
431 error = std::move(materialization_error);
432 return false;
433 }
434 capabilities_found.clear();
435
436 plugin.capabilities = std::move(capabilities);
437
438 // Let the caller reject the load against its registry BEFORE on_load() runs, so a duplicate
439 // package or a capability-name collision costs no on_load/on_unload cycle.
440 if (registry_precheck) {
441 std::string precheck_error = registry_precheck(plugin);
442 if (!precheck_error.empty()) {
443 teardown_capabilities(plugin.capabilities, 0);
444 error = std::move(precheck_error);
445 return false;
446 }
447 }
448
449 // lifecycle_count is incremented BEFORE on_load(), so a capability whose on_load() throws
450 // still gets its on_unload().
451 std::size_t lifecycle_count = 0;
452 try {
453 PythonGILState gil;
454 if (!gil)
455 throw std::runtime_error("Python interpreter is shutting down");
456 for (const auto& capability : plugin.capabilities) {
457 ++lifecycle_count;
458 capability->on_load();
459 }
460 } catch (const std::exception& ex) {
461 teardown_capabilities(plugin.capabilities, lifecycle_count);
462 // TRN on_load is the name of the plugin's startup function; keep it untranslated.
463 error = GUI::format(_u8L("Plugin on_load failed: %1%"), ex.what());
464 return false;
465 } catch (...) {
466 teardown_capabilities(plugin.capabilities, lifecycle_count);
467 error = _u8L("Plugin on_load failed");
468 return false;
469 }
470
471 out = std::move(plugin);
472
473 BOOST_LOG_TRIVIAL(info) << "[plugin_loader::load] SUCCESS plugin=" << descriptor.plugin_key
474 << " thread=" << std::this_thread::get_id();
475 return true;
476}
static std::string capability_type_label(PluginCapabilityType type)
Definition PluginConfig.cpp:522
Definition PythonInterpreter.hpp:40
bool is_initialized() const
Definition PythonInterpreter.hpp:48
static PythonInterpreter & instance()
Definition PythonInterpreter.cpp:434
const std::string & last_error() const
Definition PythonInterpreter.hpp:54
static PythonPluginBridge & instance()
Definition PythonPluginBridge.cpp:97
@ Plugin
Definition ActionRegistry.hpp:25
bool add_wheel_dependencies_to_sys_path(const PluginDescriptor &descriptor, std::vector< std::string > &plugin_paths, std::string &error)
Definition PluginLoader.cpp:152
std::string plugin_module_name(const PluginDescriptor &descriptor)
Definition PluginLoader.cpp:210
PyObject * import_plugin_module(const PluginDescriptor &descriptor, std::vector< std::string > &plugin_paths, std::vector< std::string > &plugin_modules, std::string &error)
Definition PluginLoader.cpp:192
void teardown_capabilities(std::vector< std::shared_ptr< PluginCapabilityInterface > > &capabilities, std::size_t lifecycle_count)
Definition PluginLoader.cpp:119
bool install_packages(const std::vector< std::string > &pkgs, std::string &error)
Definition PluginLoader.cpp:478
PluginCapabilityType
Definition PythonPluginInterface.hpp:17
std::vector< std::string > collect_resource_texture_files(const std::string &dir, const std::string &plugin_root, std::string &error)
Definition PluginFsUtils.cpp:1262
FileTransferModule & module()
Definition FileTransferUtils.hpp:248
bool read_install_state(const boost::filesystem::path &plugin_dir, PluginDescriptor &entry)
Definition PluginFsUtils.cpp:738
bool has_error() const
Definition PluginDescriptor.hpp:105
std::string normalized_error() const
Definition PluginDescriptor.hpp:95
bool is_metadata_valid() const
Definition PluginDescriptor.hpp:85
Definition PluginManager.hpp:50

References _u8L, Slic3r::PythonPluginBridge::begin_plugin_capture(), Slic3r::PythonPluginBridge::cancel_plugin_capture(), Slic3r::Plugin::capabilities, Slic3r::PluginInstallState::capabilities, Slic3r::PluginConfig::capability_type_label(), Slic3r::PluginDescriptor::clear_error(), Slic3r::collect_resource_texture_files(), Slic3r::PluginDescriptor::dependencies, Slic3r::Plugin::descriptor, Slic3r::PluginDescriptor::entry_path, Slic3r::PythonPluginBridge::finalize_plugin_capture(), Slic3r::GUI::format(), Slic3r::PluginDescriptor::has_error(), install_packages(), Slic3r::PythonInterpreter::instance(), Slic3r::PythonPluginBridge::instance(), Slic3r::PythonInterpreter::is_initialized(), Slic3r::PluginDescriptor::is_metadata_valid(), Slic3r::PythonInterpreter::last_error(), Slic3r::module(), Slic3r::Plugin::module_name, Slic3r::PluginDescriptor::normalized_error(), Slic3r::PluginDescriptor::plugin_key, Slic3r::Plugin::plugin_modules, Slic3r::PluginDescriptor::plugin_root, Slic3r::Plugin::plugin_sys_paths, and Slic3r::read_install_state().

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

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

◆ unload()

void Slic3r::plugin_loader::unload ( Plugin & plugin)
220{
221 // Dropping the capabilities drops their state with them; the user's enable choices survive in
222 // the .install_state.json sidecar, which the next load seeds from.
223 teardown_capabilities(plugin.capabilities, plugin.capabilities.size());
224 plugin.release_module();
225}
std::vector< std::shared_ptr< PluginCapabilityInterface > > capabilities
Definition PluginManager.hpp:58
void release_module()
Definition PluginManager.cpp:62

References Slic3r::Plugin::capabilities, and Slic3r::Plugin::release_module().

Referenced by Slic3r::PluginManager::load_plugin_impl(), Slic3r::PluginManager::unload_all_plugins(), and Slic3r::PluginManager::unload_plugin().

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