From 6e1a6cc67859ac0c83af8f8df4a87c8df8e5650b Mon Sep 17 00:00:00 2001 From: SoftFever Date: Sat, 25 Jul 2026 15:28:14 +0800 Subject: [PATCH] Scope plugin config overrides per preset type Replace the shared plugin_config_overrides key with print/printer/filament scoped keys so merging presets into one full config cannot clobber an edited override, letting a slicing plugin config change invalidate the slice step. --- .../web/dialog/PluginsConfigDialog/index.js | 24 +++++----- resources/web/dialog/PluginsDialog/index.js | 2 +- resources/web/dialog/js/plugin-config-ui.js | 48 ++++++------------- sandboxes/orca_twistify_plugin_example_any.py | 6 +-- src/libslic3r/Preset.cpp | 15 ++++-- src/libslic3r/Preset.hpp | 6 +++ src/libslic3r/Print.cpp | 1 + src/libslic3r/PrintConfig.cpp | 27 +++++++---- src/libslic3r/PrintConfig.hpp | 1 + src/libslic3r/PrintObject.cpp | 3 +- src/slic3r/GUI/Tab.cpp | 15 +++--- src/slic3r/plugin/PluginConfig.cpp | 38 +++++++-------- src/slic3r/plugin/PluginConfig.hpp | 18 +++---- .../fff_print/test_slicing_pipeline_hook.cpp | 14 ++++++ .../libslic3r/test_preset_bundle_loading.cpp | 26 ++++++++++ 15 files changed, 138 insertions(+), 106 deletions(-) diff --git a/resources/web/dialog/PluginsConfigDialog/index.js b/resources/web/dialog/PluginsConfigDialog/index.js index 3df85e0a33..c97263d3ac 100644 --- a/resources/web/dialog/PluginsConfigDialog/index.js +++ b/resources/web/dialog/PluginsConfigDialog/index.js @@ -9,10 +9,12 @@ let selectedCapabilityType = ""; let selectedHasPresetOverride = false; let selectedReadOnly = false; -// Identity of the capability whose custom UI is currently loaded in the frame. Saving re-sends the -// whole capability_config payload, and rebuilding the frame from it would reload the plugin's page -// under the user's cursor; when this still matches, the new values are posted in instead. -let customFrameKey = ""; +// Whether the frame holds the selected capability's custom UI. Payloads are gated by +// IsCurrentCapability and every selection change clears the view, so a loaded frame is always the +// selected capability's. Saving re-sends the whole capability_config payload, and rebuilding the +// frame from it would reload the plugin's page under the user's cursor; while the frame is loaded, +// the new values are posted in instead. +let customFrameLoaded = false; function SafeJsonParse(text) { try { @@ -173,10 +175,6 @@ function IsCurrentCapability(payload) { && String(payload?.capability_type || "") === selectedCapabilityType; } -function CapabilityKey(payload) { - return JSON.stringify([payload?.plugin_key, payload?.capability_name, payload?.capability_type]); -} - function RequestCapabilityConfig() { if (!selectedPluginKey || !selectedCapabilityName) return; @@ -202,7 +200,7 @@ function ClearCapabilityConfigView() { if (custom) { custom.hidden = true; custom.removeAttribute("srcdoc"); - customFrameKey = ""; + customFrameLoaded = false; } if (text) text.value = ""; @@ -260,10 +258,10 @@ function ApplyCapabilityConfig(payload) { if (custom) { const context = OrcaConfigContext(payload, "preset"); custom.hidden = false; - if (customFrameKey === CapabilityKey(payload) && custom.contentWindow) { + if (customFrameLoaded && custom.contentWindow) { custom.contentWindow.postMessage({ __orca: "config", config: config, context: context }, "*"); } else { - customFrameKey = CapabilityKey(payload); + customFrameLoaded = true; custom.srcdoc = BuildCustomConfigDocument(html, config, context); } } @@ -276,7 +274,7 @@ function ApplyCapabilityConfig(payload) { if (custom) { custom.hidden = true; custom.removeAttribute("srcdoc"); - customFrameKey = ""; + customFrameLoaded = false; } if (editor) editor.hidden = false; @@ -416,7 +414,7 @@ document.addEventListener("DOMContentLoaded", () => { // OnCustomConfigMessage matches on the frame's contentWindow, not the origin ("null" when // sandboxed), and ignores anything else. window.addEventListener("message", OnCustomConfigMessage); - OrcaWatchThemeForFrame(() => document.getElementById("configCustom")); + OrcaWatchThemeForFrame("configCustom"); SendMessage("request_capabilities"); }); diff --git a/resources/web/dialog/PluginsDialog/index.js b/resources/web/dialog/PluginsDialog/index.js index 036fc8552a..ea081a23aa 100644 --- a/resources/web/dialog/PluginsDialog/index.js +++ b/resources/web/dialog/PluginsDialog/index.js @@ -90,7 +90,7 @@ function OnInit() { // OnCustomConfigMessage matches on the frame's contentWindow, not the origin ("null" when // sandboxed), and ignores anything else. window.addEventListener("message", OnCustomConfigMessage); - OrcaWatchThemeForFrame(() => document.getElementById("configCustom")); + OrcaWatchThemeForFrame("configCustom"); document.addEventListener("click", (event) => { if (!event.target.closest(".ctx")) diff --git a/resources/web/dialog/js/plugin-config-ui.js b/resources/web/dialog/js/plugin-config-ui.js index 0f16c0c48c..5dfd47fc96 100644 --- a/resources/web/dialog/js/plugin-config-ui.js +++ b/resources/web/dialog/js/plugin-config-ui.js @@ -3,41 +3,25 @@ // opaque origin, so this bridge is its only channel, and both must offer plugin authors exactly the // same one — hence a single module rather than a copy per dialog. -// The host theme "contract" (WebViewHostDialog::host_theme_vars_css). The document-start injector -// stamps it on the top-level page only — it returns early in child frames — so a sandboxed config UI -// never sees it unless we hand it over. -const ORCA_THEME_VARS = [ - "--orca-bg", - "--orca-fg", - "--orca-muted", - "--orca-border", - "--orca-accent", - "--orca-accent-fg", - "--orca-font" -]; - -// Read the contract off this page as it is rendering right now, so the frame always opens in the -// live theme rather than whatever the app started in. +// The host theme "contract" (WebViewHostDialog::host_theme_vars_css) arrives as a complete +// ":root{--orca-*;color-scheme:...}" rule in the injected