diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index bfe66069d1..f9464d838e 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -3504,7 +3504,7 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt) { //BBS: add bed_exclude_area - if (opt_key == "printable_area" || opt_key == "bed_exclude_area" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "thumbnails" || opt_key == "wrapping_exclude_area") { + if (opt_key == "printable_area" || opt_key == "bed_exclude_area" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "thumbnails" || opt_key == "wrapping_exclude_area", "slicing_pipeline_plugin") { // Scalar variable, or a vector variable, which is independent from number of extruders, // thus the vector is presented to the user as a single input. diff.emplace_back(opt_key); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 326425bc4f..e2eb67a5fb 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -34,6 +34,7 @@ #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" #include "slic3r/Utils/PresetUpdater.hpp" +#include "slic3r/plugin/PluginConfig.hpp" #include "Plater.hpp" #include "MainFrame.hpp" #include "format.hpp" @@ -1795,9 +1796,19 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) // Keep this preset's "plugins" manifest in sync when a plugin picker changes, so full_config() and // save_to_json() always find resolved "name;uuid;capability" references and rebuild it nowhere else. + // Also drop any plugin_config_overrides entries for a capability the change just stopped + // referencing (e.g. a plugin removed from slicing_pipeline_plugin), so a saved preset never + // carries configuration for a capability it no longer names. The Configure button is a separate + // field holding its own cached copy of that value, so it needs to be told explicitly, or it + // keeps showing the stale count until something else happens to refresh it. if (const ConfigOptionDef* opt_def = m_config->def()->get(opt_key); - opt_def && opt_def->is_plugin_backed()) + opt_def && opt_def->is_plugin_backed()) { m_config->update_plugin_manifest(); + if (prune_stale_plugin_overrides(*m_config)) { + if (Field* overrides_field = get_field(PLUGIN_OVERRIDES_OPTION_KEY)) + overrides_field->set_value(boost::any(m_config->opt_string(PLUGIN_OVERRIDES_OPTION_KEY)), false); + } + } if (opt_key == "gcode_flavor" && m_type == Preset::TYPE_PRINTER) { if (auto printer_tab = dynamic_cast(this)) diff --git a/src/slic3r/plugin/PluginConfig.cpp b/src/slic3r/plugin/PluginConfig.cpp index 1efbb7dfc6..7b6baa1fd1 100644 --- a/src/slic3r/plugin/PluginConfig.cpp +++ b/src/slic3r/plugin/PluginConfig.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -164,6 +165,20 @@ bool CapabilityConfigDocument::erase(const PluginCapabilityId& id) return erased; } +bool CapabilityConfigDocument::prune_unreferenced(const std::set>& referenced) +{ + bool changed = false; + for (auto it = m_entries.begin(); it != m_entries.end();) { + if (referenced.count({it->first.type, it->first.name}) != 0) { + ++it; + } else { + it = m_entries.erase(it); + changed = true; + } + } + return changed; +} + bool CapabilityConfigDocument::empty() const { return m_entries.empty() && m_opaque_entries.empty(); @@ -342,6 +357,50 @@ std::string serialize_plugin_overrides(const CapabilityConfigDocument& document) return document.empty() ? std::string() : document.serialize_entries().dump(); } +bool prune_stale_plugin_overrides(DynamicConfig& config) +{ + const auto* overrides_opt = dynamic_cast(config.option(PLUGIN_OVERRIDES_OPTION_KEY)); + if (overrides_opt == nullptr || overrides_opt->value.empty()) + return false; + + CapabilityConfigDocument overrides; + std::string error; + if (!parse_plugin_overrides(overrides_opt->value, overrides, error)) { + // Malformed text is not ours to fix up here: leave it untouched rather than risk + // discarding data the user might still be able to recover. + BOOST_LOG_TRIVIAL(error) << "prune_stale_plugin_overrides: " << error; + return false; + } + + // Capability names currently referenced by a plugin-backed option's value(s) — e.g. + // slicing_pipeline_plugin's ConfigOptionStrings entries name SlicingPipeline capabilities + // directly, the same raw values save_plugin_collection() resolves into the "plugins" manifest. + std::set> referenced; + const ConfigDef* def = config.def(); + for (const std::string& opt_key : config.keys()) { + const ConfigOptionDef* opt_def = def != nullptr ? def->get(opt_key) : nullptr; + if (opt_def == nullptr || !opt_def->is_plugin_backed()) + continue; + + const ConfigOption* opt = config.option(opt_key); + const PluginCapabilityType type = plugin_capability_type_from_string(opt_def->plugin_type); + if (const auto* string_opt = dynamic_cast(opt)) { + if (!string_opt->value.empty()) + referenced.emplace(type, string_opt->value); + } else if (const auto* vector_opt = dynamic_cast(opt)) { + for (const std::string& value : vector_opt->vserialize()) + if (!value.empty()) + referenced.emplace(type, value); + } + } + + if (!overrides.prune_unreferenced(referenced)) + return false; + + config.set_key_value(PLUGIN_OVERRIDES_OPTION_KEY, new ConfigOptionString(serialize_plugin_overrides(overrides))); + return true; +} + EffectiveCapabilityConfig PresetPluginConfigService::get_effective_config(const CapabilityConfigDocument& overrides, const PluginCapabilityId& id) const { diff --git a/src/slic3r/plugin/PluginConfig.hpp b/src/slic3r/plugin/PluginConfig.hpp index 75d468e82f..2d541a58f4 100644 --- a/src/slic3r/plugin/PluginConfig.hpp +++ b/src/slic3r/plugin/PluginConfig.hpp @@ -8,7 +8,9 @@ #include #include #include +#include #include +#include #include #define PLUGIN_CONFIG_DIR "config.json" @@ -16,6 +18,7 @@ namespace Slic3r { class Preset; +class DynamicConfig; struct CapabilityConfigEntry { PluginCapabilityId id; @@ -35,6 +38,9 @@ public: bool contains(const PluginCapabilityId& id) const; bool upsert(CapabilityConfigEntry entry); bool erase(const PluginCapabilityId& id); + // Drops every entry whose (type, name) is not in `referenced`, e.g. capabilities a preset's + // plugin-backed options no longer name. Returns true if anything was removed. + bool prune_unreferenced(const std::set>& referenced); bool empty() const; nlohmann::json serialize_entries() const; nlohmann::json root_json() const; @@ -50,6 +56,14 @@ std::string plugin_overrides_of(const Preset& preset); bool parse_plugin_overrides(const std::string& raw, CapabilityConfigDocument& document, std::string& error); std::string serialize_plugin_overrides(const CapabilityConfigDocument& document); +// Drops plugin_config_overrides entries for capabilities no longer named by any plugin-backed +// option's current value in `config` (e.g. slicing_pipeline_plugin cleared or switched to a +// different capability), and writes the result back if anything changed. Called wherever a +// plugin-backed option's value changes, so a saved preset never carries configuration for a +// capability it no longer references. Returns true if `config` was modified, so a caller holding a +// GUI field over PLUGIN_OVERRIDES_OPTION_KEY knows it must refresh that field's displayed value. +bool prune_stale_plugin_overrides(DynamicConfig& config); + struct EffectiveCapabilityConfig { PluginCapabilityId id;