mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-15 15:03:49 +00:00
refactor(plugin): move libslic3r hook wiring out of GUI_App into PluginHooks
GUI_App::on_init_inner() carried the plugin dispatch policy inline (the
capability resolver and the slicing-pipeline dispatcher) and would grow
with every capability that fires from inside libslic3r.
plugin/PluginHooks.{hpp,cpp} now owns one file-local installer per hook,
aggregated by plugin_hooks::install() -- called from
PluginManager::initialize(), reset in shutdown() so no hook can enter
Python after the interpreter finalizes. The wx-side loader subscriptions
move into GUI_App::init_plugin_gui_wiring().
No behavior change; dispatch bodies moved verbatim.
This commit is contained in:
@@ -618,6 +618,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
plugin/PluginLoader.cpp
|
||||
plugin/PluginLoader.hpp
|
||||
plugin/PluginDescriptor.hpp
|
||||
plugin/PluginHooks.cpp
|
||||
plugin/PluginHooks.hpp
|
||||
plugin/PluginManager.cpp
|
||||
plugin/PluginManager.hpp
|
||||
plugin/PluginAuditManager.cpp
|
||||
|
||||
@@ -81,7 +81,6 @@
|
||||
#include "slic3r/plugin/PluginManager.hpp"
|
||||
#include "slic3r/plugin/host/PluginHostUi.hpp"
|
||||
#include "slic3r/plugin/PythonInterpreter.hpp"
|
||||
#include "slic3r/plugin/pluginTypes/slicingPipeline/SlicingPipelinePluginCapability.hpp"
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_Utils.hpp"
|
||||
@@ -2701,6 +2700,54 @@ std::string get_system_info()
|
||||
return out.str();
|
||||
}
|
||||
|
||||
// wx/app-level plugin wiring, kept in one place: subscriptions to plugin
|
||||
// loader events that drive GUI policy (plugins dialog refresh, network-agent
|
||||
// registration, plate revalidation). The libslic3r dispatch hooks are NOT
|
||||
// wired here -- PluginManager::initialize() installs those via
|
||||
// plugin_hooks::install().
|
||||
void GUI_App::init_plugin_gui_wiring()
|
||||
{
|
||||
PluginManager& plugin_mgr = PluginManager::instance();
|
||||
|
||||
auto refresh_plugins_dialog = [] {
|
||||
if (!wxTheApp)
|
||||
return;
|
||||
|
||||
GUI_App* app = &GUI::wxGetApp();
|
||||
if (app->is_closing())
|
||||
return;
|
||||
|
||||
app->CallAfter([app] {
|
||||
if (!app->is_closing() && app->m_plugins_dlg)
|
||||
app->m_plugins_dlg->update_plugin_dialog_ui();
|
||||
});
|
||||
};
|
||||
|
||||
plugin_mgr.get_loader().subscribe_on_load_callback([refresh_plugins_dialog](const std::string&) { refresh_plugins_dialog(); });
|
||||
plugin_mgr.get_loader().subscribe_on_unload_callback([refresh_plugins_dialog](const std::string&) { refresh_plugins_dialog(); });
|
||||
plugin_mgr.get_loader().subscribe_on_load_callback(NetworkAgentFactory::register_python_plugin);
|
||||
plugin_mgr.get_loader().subscribe_on_unload_callback(NetworkAgentFactory::deregister_python_plugin);
|
||||
plugin_mgr.get_loader().subscribe_on_capability_load_callback(
|
||||
[refresh_plugins_dialog](const PluginCapabilityIdentifier& capability) {
|
||||
if (capability.type == PluginCapabilityType::PrinterConnection)
|
||||
NetworkAgentFactory::register_python_printer_agent(capability.plugin_key, capability.name);
|
||||
refresh_plugins_dialog();
|
||||
// A newly loaded capability may satisfy a missing-plugin notification; re-validate the
|
||||
// current plate (on the UI thread) so the notification clears once its plugin is available.
|
||||
if (wxTheApp && !wxGetApp().is_closing())
|
||||
wxGetApp().CallAfter([]() {
|
||||
if (Plater* plater = wxGetApp().plater())
|
||||
plater->revalidate_current_plate_if_plugins_missing();
|
||||
});
|
||||
});
|
||||
plugin_mgr.get_loader().subscribe_on_capability_unload_callback(
|
||||
[refresh_plugins_dialog](const PluginCapabilityIdentifier& capability) {
|
||||
if (capability.type == PluginCapabilityType::PrinterConnection)
|
||||
NetworkAgentFactory::deregister_python_printer_agent(capability.plugin_key, capability.name);
|
||||
refresh_plugins_dialog();
|
||||
});
|
||||
}
|
||||
|
||||
bool GUI_App::on_init_inner()
|
||||
{
|
||||
wxLog::SetActiveTarget(new wxBoostLog());
|
||||
@@ -3104,94 +3151,12 @@ bool GUI_App::on_init_inner()
|
||||
on_init_network();
|
||||
|
||||
// Initialize plugins after network then register on_load callbacks so once the plugin loads finish, it gets registered automatically.
|
||||
// initialize() also installs the libslic3r hooks (capability resolver,
|
||||
// slicing-pipeline dispatcher) via plugin_hooks::install() -- no
|
||||
// per-capability wiring belongs here.
|
||||
PluginManager& plugin_mgr = PluginManager::instance();
|
||||
plugin_mgr.initialize();
|
||||
|
||||
ConfigBase::set_resolve_capability_fn([&plugin_mgr](const std::string& cap_name, const std::string& cap_type) {
|
||||
auto plugin_cap = plugin_mgr.get_loader().try_get_plugin_capability_by_name_and_type(cap_name, plugin_capability_type_from_string(cap_type));
|
||||
if (!plugin_cap)
|
||||
return std::string();
|
||||
|
||||
PluginDescriptor descriptor;
|
||||
if (!plugin_mgr.get_catalog().try_get_plugin_descriptor(plugin_cap->plugin_key, descriptor))
|
||||
return std::string();
|
||||
|
||||
// Cloud plugins are resolved at runtime via the UUID in the middle field, so the first
|
||||
// field keeps the friendly display name. Local plugins are looked up by plugin_key (the
|
||||
// first field, with an empty UUID), so emit the plugin_key to keep them resolvable.
|
||||
const std::string identity = descriptor.is_cloud_plugin() ? descriptor.name : descriptor.plugin_key;
|
||||
return identity + ';' + descriptor.cloud_uuid() + ';' + cap_name;
|
||||
});
|
||||
|
||||
// Orca: register the slicing-pipeline plugin dispatcher (mirrors set_resolve_capability_fn: the
|
||||
// GUI/plugin layer supplies the Python bridge so libslic3r stays free of any plugin dependency).
|
||||
// Print::process() fires this hook at each pipeline seam on the slicing worker thread; here we run
|
||||
// the picker-selected SlicingPipeline capabilities. Per capability we acquire the GIL, honor
|
||||
// cancellation, and convert a plugin failure into a (non-critical) SlicingError so it surfaces as a
|
||||
// slicing-error notification rather than the fatal-crash dialog.
|
||||
Slic3r::Print::set_slicing_pipeline_hook_fn(
|
||||
[](Slic3r::Print& print, const Slic3r::PrintObject* object, Slic3r::SlicingPipelineStepPlugin step) {
|
||||
const auto* caps = print.config().option<ConfigOptionStrings>("slicing_pipeline_plugin");
|
||||
// `plugins` is a dynamic-only manifest key (not a static PrintConfig member), so it
|
||||
// must be read from the full/dynamic config -- reading it off print.config() (the
|
||||
// static PrintConfig) always yields nullptr and skips every capability. Mirrors the
|
||||
// post-process path (PostProcessor.cpp, via BackgroundSlicingProcess::full_print_config()).
|
||||
const auto* plugs = print.full_print_config().option<ConfigOptionStrings>("plugins");
|
||||
if (caps == nullptr || caps->values.empty())
|
||||
return;
|
||||
|
||||
Slic3r::execute_capabilities_from_refs<Slic3r::SlicingPipelinePluginCapability>(
|
||||
*caps, plugs, Slic3r::PluginCapabilityType::SlicingPipeline,
|
||||
[&](std::shared_ptr<Slic3r::SlicingPipelinePluginCapability> cap, const Slic3r::PluginCapabilityRef& ref) {
|
||||
Slic3r::ExecutionResult r;
|
||||
try {
|
||||
// GIL is acquired per capability (not once for the whole dispatch) so it
|
||||
// is released between capabilities.
|
||||
PythonGILState gil;
|
||||
// throw_if_canceled() is protected on PrintBase; canceled() is the public
|
||||
// equivalent check (same cancel flag), so honor cancellation via it.
|
||||
if (print.canceled())
|
||||
throw Slic3r::CanceledException();
|
||||
Slic3r::SlicingPipelineContext ctx;
|
||||
ctx.orca_version = SoftFever_VERSION;
|
||||
ctx.step = step;
|
||||
ctx.print = &print;
|
||||
ctx.object = object;
|
||||
// hand the plugin its own [tool.orcaslicer.plugin.settings] as ctx.params
|
||||
// (same plugin_key the capability was resolved by, so it always matches).
|
||||
const std::string plugin_key = ref.uuid.empty() ? ref.name : ref.uuid;
|
||||
ctx.params = Slic3r::PluginManager::instance().get_loader().get_plugin_settings(plugin_key);
|
||||
r = cap->execute(ctx);
|
||||
} catch (const Slic3r::CanceledException&) {
|
||||
throw; // cancellation must reach process(), never become a slicing error
|
||||
} catch (const std::exception& ex) {
|
||||
// A Python raise reaches here as pybind11::error_already_set; surface it as a
|
||||
// (non-critical) slicing error instead of a crash.
|
||||
throw Slic3r::SlicingError(std::string("Slicing pipeline plugin '") +
|
||||
ref.capability_name + "' error: " + ex.what());
|
||||
}
|
||||
if (r.status == Slic3r::PluginResult::FatalError)
|
||||
throw Slic3r::SlicingError(std::string("Slicing pipeline plugin '") +
|
||||
ref.capability_name + "' error: " + r.message);
|
||||
// log a non-empty success/skipped message instead of dropping it. This is
|
||||
// log-only by design: every pipeline hook fires AFTER set_done() (see Print.cpp),
|
||||
// so the Print-level m_step_active is -1 here. Calling active_step_add_warning()
|
||||
// would then index m_state[-1] (out-of-bounds; the guarding assert is compiled
|
||||
// out in Release), so it must NOT be called from a pipeline hook.
|
||||
if (!r.message.empty()) {
|
||||
static const char* const kStepNames[] = {
|
||||
"posSlice", "posPerimeters", "posEstimateCurledExtrusions", "posPrepareInfill", "posInfill",
|
||||
"posIroning", "posContouring", "posSupportMaterial", "posDetectOverhangsForLift",
|
||||
"posSimplifyPath", "psWipeTower", "psSkirtBrim", "psGCodePostProcess"
|
||||
}; // order must match Slic3r::SlicingPipelineStepPlugin
|
||||
const char* step_name = static_cast<size_t>(step) < sizeof(kStepNames) / sizeof(kStepNames[0])
|
||||
? kStepNames[static_cast<int>(step)] : "Unknown";
|
||||
BOOST_LOG_TRIVIAL(info) << "Slicing pipeline plugin '" << ref.capability_name
|
||||
<< "' [" << step_name << "]: " << r.message;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Set cloud plugin directory from previous session so cloud-installed
|
||||
// plugins are discovered even before the network agent is ready.
|
||||
const std::string preset_folder = app_config->get("preset_folder");
|
||||
@@ -3202,43 +3167,7 @@ bool GUI_App::on_init_inner()
|
||||
|
||||
plugin_mgr.discover_plugins(false, true);
|
||||
|
||||
auto refresh_plugins_dialog = [] {
|
||||
if (!wxTheApp)
|
||||
return;
|
||||
|
||||
GUI_App* app = &GUI::wxGetApp();
|
||||
if (app->is_closing())
|
||||
return;
|
||||
|
||||
app->CallAfter([app] {
|
||||
if (!app->is_closing() && app->m_plugins_dlg)
|
||||
app->m_plugins_dlg->update_plugin_dialog_ui();
|
||||
});
|
||||
};
|
||||
|
||||
plugin_mgr.get_loader().subscribe_on_load_callback([refresh_plugins_dialog](const std::string&) { refresh_plugins_dialog(); });
|
||||
plugin_mgr.get_loader().subscribe_on_unload_callback([refresh_plugins_dialog](const std::string&) { refresh_plugins_dialog(); });
|
||||
plugin_mgr.get_loader().subscribe_on_load_callback(NetworkAgentFactory::register_python_plugin);
|
||||
plugin_mgr.get_loader().subscribe_on_unload_callback(NetworkAgentFactory::deregister_python_plugin);
|
||||
plugin_mgr.get_loader().subscribe_on_capability_load_callback(
|
||||
[refresh_plugins_dialog](const PluginCapabilityIdentifier& capability) {
|
||||
if (capability.type == PluginCapabilityType::PrinterConnection)
|
||||
NetworkAgentFactory::register_python_printer_agent(capability.plugin_key, capability.name);
|
||||
refresh_plugins_dialog();
|
||||
// A newly loaded capability may satisfy a missing-plugin notification; re-validate the
|
||||
// current plate (on the UI thread) so the notification clears once its plugin is available.
|
||||
if (wxTheApp && !wxGetApp().is_closing())
|
||||
wxGetApp().CallAfter([]() {
|
||||
if (Plater* plater = wxGetApp().plater())
|
||||
plater->revalidate_current_plate_if_plugins_missing();
|
||||
});
|
||||
});
|
||||
plugin_mgr.get_loader().subscribe_on_capability_unload_callback(
|
||||
[refresh_plugins_dialog](const PluginCapabilityIdentifier& capability) {
|
||||
if (capability.type == PluginCapabilityType::PrinterConnection)
|
||||
NetworkAgentFactory::deregister_python_printer_agent(capability.plugin_key, capability.name);
|
||||
refresh_plugins_dialog();
|
||||
});
|
||||
init_plugin_gui_wiring();
|
||||
|
||||
for (const std::string& plugin_key : plugin_mgr.get_catalog().get_enabled_plugin_keys()) {
|
||||
if (!plugin_mgr.get_loader().is_plugin_loaded(plugin_key)) {
|
||||
|
||||
@@ -772,6 +772,9 @@ private:
|
||||
bool on_init_network(bool try_backup = false);
|
||||
void init_networking_callbacks();
|
||||
void init_app_config();
|
||||
// GUI-side subscriptions to plugin loader events (dialog refresh,
|
||||
// network-agent registration, plate revalidation).
|
||||
void init_plugin_gui_wiring();
|
||||
void remove_old_networking_plugins();
|
||||
void drain_pending_events(int timeout_ms);
|
||||
bool wait_for_network_idle(int timeout_ms);
|
||||
|
||||
129
src/slic3r/plugin/PluginHooks.cpp
Normal file
129
src/slic3r/plugin/PluginHooks.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#include "PluginHooks.hpp"
|
||||
|
||||
#include "PluginManager.hpp"
|
||||
#include "PythonInterpreter.hpp"
|
||||
#include "PythonPluginInterface.hpp"
|
||||
#include "pluginTypes/slicingPipeline/SlicingPipelinePluginCapability.hpp"
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Exception.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r_version.h"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r::plugin_hooks {
|
||||
namespace {
|
||||
|
||||
// Manifest resolver: turns the bare capability name a preset stores into the full
|
||||
// "name;uuid;capability" reference the dispatchers consume (see
|
||||
// ConfigBase::collect_plugin_manifest / update_plugin_manifest).
|
||||
void install_capability_resolver()
|
||||
{
|
||||
ConfigBase::set_resolve_capability_fn([](const std::string& cap_name, const std::string& cap_type) {
|
||||
PluginManager& plugin_mgr = PluginManager::instance();
|
||||
auto plugin_cap = plugin_mgr.get_loader().try_get_plugin_capability_by_name_and_type(cap_name, plugin_capability_type_from_string(cap_type));
|
||||
if (!plugin_cap)
|
||||
return std::string();
|
||||
|
||||
PluginDescriptor descriptor;
|
||||
if (!plugin_mgr.get_catalog().try_get_plugin_descriptor(plugin_cap->plugin_key, descriptor))
|
||||
return std::string();
|
||||
|
||||
// Cloud plugins are resolved at runtime via the UUID in the middle field, so the first
|
||||
// field keeps the friendly display name. Local plugins are looked up by plugin_key (the
|
||||
// first field, with an empty UUID), so emit the plugin_key to keep them resolvable.
|
||||
const std::string identity = descriptor.is_cloud_plugin() ? descriptor.name : descriptor.plugin_key;
|
||||
return identity + ';' + descriptor.cloud_uuid() + ';' + cap_name;
|
||||
});
|
||||
}
|
||||
|
||||
// Print::process() fires this hook at each pipeline seam on the slicing worker
|
||||
// thread; here we run the picker-selected SlicingPipeline capabilities. Per
|
||||
// capability we acquire the GIL, honor cancellation, and convert a plugin
|
||||
// failure into a (non-critical) SlicingError so it surfaces as a slicing-error
|
||||
// notification rather than the fatal-crash dialog.
|
||||
void install_slicing_pipeline_hook()
|
||||
{
|
||||
Print::set_slicing_pipeline_hook_fn(
|
||||
[](Print& print, const PrintObject* object, SlicingPipelineStepPlugin step) {
|
||||
const auto* caps = print.config().option<ConfigOptionStrings>("slicing_pipeline_plugin");
|
||||
// `plugins` is a dynamic-only manifest key (not a static PrintConfig member), so it
|
||||
// must be read from the full/dynamic config -- reading it off print.config() (the
|
||||
// static PrintConfig) always yields nullptr and skips every capability. Mirrors the
|
||||
// post-process path (PostProcessor.cpp, via BackgroundSlicingProcess::full_print_config()).
|
||||
const auto* plugs = print.full_print_config().option<ConfigOptionStrings>("plugins");
|
||||
if (caps == nullptr || caps->values.empty())
|
||||
return;
|
||||
|
||||
execute_capabilities_from_refs<SlicingPipelinePluginCapability>(
|
||||
*caps, plugs, PluginCapabilityType::SlicingPipeline,
|
||||
[&](std::shared_ptr<SlicingPipelinePluginCapability> cap, const PluginCapabilityRef& ref) {
|
||||
ExecutionResult r;
|
||||
try {
|
||||
// GIL is acquired per capability (not once for the whole dispatch) so it
|
||||
// is released between capabilities.
|
||||
PythonGILState gil;
|
||||
// throw_if_canceled() is protected on PrintBase; canceled() is the public
|
||||
// equivalent check (same cancel flag), so honor cancellation via it.
|
||||
if (print.canceled())
|
||||
throw CanceledException();
|
||||
SlicingPipelineContext ctx;
|
||||
ctx.orca_version = SoftFever_VERSION;
|
||||
ctx.step = step;
|
||||
ctx.print = &print;
|
||||
ctx.object = object;
|
||||
// hand the plugin its own [tool.orcaslicer.plugin.settings] as ctx.params
|
||||
// (same plugin_key the capability was resolved by, so it always matches).
|
||||
const std::string plugin_key = ref.uuid.empty() ? ref.name : ref.uuid;
|
||||
ctx.params = PluginManager::instance().get_loader().get_plugin_settings(plugin_key);
|
||||
r = cap->execute(ctx);
|
||||
} catch (const CanceledException&) {
|
||||
throw; // cancellation must reach process(), never become a slicing error
|
||||
} catch (const std::exception& ex) {
|
||||
// A Python raise reaches here as pybind11::error_already_set; surface it as a
|
||||
// (non-critical) slicing error instead of a crash.
|
||||
throw SlicingError(std::string("Slicing pipeline plugin '") +
|
||||
ref.capability_name + "' error: " + ex.what());
|
||||
}
|
||||
if (r.status == PluginResult::FatalError)
|
||||
throw SlicingError(std::string("Slicing pipeline plugin '") +
|
||||
ref.capability_name + "' error: " + r.message);
|
||||
// log a non-empty success/skipped message instead of dropping it. This is
|
||||
// log-only by design: every pipeline hook fires AFTER set_done() (see Print.cpp),
|
||||
// so the Print-level m_step_active is -1 here. Calling active_step_add_warning()
|
||||
// would then index m_state[-1] (out-of-bounds; the guarding assert is compiled
|
||||
// out in Release), so it must NOT be called from a pipeline hook.
|
||||
if (!r.message.empty()) {
|
||||
static const char* const kStepNames[] = {
|
||||
"posSlice", "posPerimeters", "posEstimateCurledExtrusions", "posPrepareInfill", "posInfill",
|
||||
"posIroning", "posContouring", "posSupportMaterial", "posDetectOverhangsForLift",
|
||||
"posSimplifyPath", "psWipeTower", "psSkirtBrim", "psGCodePostProcess"
|
||||
}; // order must match SlicingPipelineStepPlugin
|
||||
const char* step_name = static_cast<size_t>(step) < sizeof(kStepNames) / sizeof(kStepNames[0])
|
||||
? kStepNames[static_cast<int>(step)] : "Unknown";
|
||||
BOOST_LOG_TRIVIAL(info) << "Slicing pipeline plugin '" << ref.capability_name
|
||||
<< "' [" << step_name << "]: " << r.message;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void install()
|
||||
{
|
||||
install_capability_resolver();
|
||||
install_slicing_pipeline_hook();
|
||||
}
|
||||
|
||||
void uninstall()
|
||||
{
|
||||
ConfigBase::set_resolve_capability_fn(nullptr);
|
||||
Print::set_slicing_pipeline_hook_fn(nullptr);
|
||||
}
|
||||
|
||||
} // namespace Slic3r::plugin_hooks
|
||||
21
src/slic3r/plugin/PluginHooks.hpp
Normal file
21
src/slic3r/plugin/PluginHooks.hpp
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
// The plugin layer's installers for the hooks libslic3r exposes. libslic3r
|
||||
// stays free of any plugin/Python dependency: it exposes static setter seams
|
||||
// (ConfigBase::set_resolve_capability_fn, Print::set_slicing_pipeline_hook_fn,
|
||||
// ...) and this unit injects the dispatchers -- one file-local installer per
|
||||
// hook, aggregated by install(). Capabilities dispatched from the GUI layer
|
||||
// (e.g. PostProcessor.cpp) call execute_capabilities_from_refs at their own
|
||||
// call site and need no hook here.
|
||||
|
||||
namespace Slic3r::plugin_hooks {
|
||||
|
||||
// Install every hook. Called once from PluginManager::initialize().
|
||||
void install();
|
||||
|
||||
// Reset every hook to null so none can enter Python after the interpreter
|
||||
// finalizes. Called from PluginManager::shutdown(); callers must have stopped
|
||||
// background slicing first (resetting a hook while process() runs is a race).
|
||||
void uninstall();
|
||||
|
||||
} // namespace Slic3r::plugin_hooks
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "PythonPluginBridge.hpp"
|
||||
#include "PluginFsUtils.hpp"
|
||||
#include "PluginHooks.hpp"
|
||||
#include "PythonFileUtils.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
@@ -120,6 +121,10 @@ bool PluginManager::initialize()
|
||||
|
||||
m_initialized = true;
|
||||
|
||||
// Install the libslic3r hooks (capability resolver, slicing-pipeline
|
||||
// dispatcher). Uninstalled in shutdown() before the interpreter finalizes.
|
||||
plugin_hooks::install();
|
||||
|
||||
// Persist auto-load / capability state to each plugin's .install_state.json sidecar.
|
||||
// On load: write enabled=true plus current capability flags. On unload: flip enabled=false.
|
||||
// The on-unload callback is skipped during shutdown (run_on_unload_callbacks is gated by
|
||||
@@ -154,6 +159,10 @@ void PluginManager::shutdown()
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": PluginManager shutdown enter";
|
||||
|
||||
// Detach the libslic3r hooks first so nothing dispatches into Python while
|
||||
// (or after) plugins unload. Callers stop background slicing before this.
|
||||
plugin_hooks::uninstall();
|
||||
|
||||
// Signal the loader to reject new plugin loads before we drain.
|
||||
m_loader.set_shutting_down();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user