mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-16 13:32:44 +00:00
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.
This commit is contained in:
@@ -9,10 +9,12 @@ let selectedCapabilityType = "";
|
|||||||
let selectedHasPresetOverride = false;
|
let selectedHasPresetOverride = false;
|
||||||
let selectedReadOnly = false;
|
let selectedReadOnly = false;
|
||||||
|
|
||||||
// Identity of the capability whose custom UI is currently loaded in the frame. Saving re-sends the
|
// Whether the frame holds the selected capability's custom UI. Payloads are gated by
|
||||||
// whole capability_config payload, and rebuilding the frame from it would reload the plugin's page
|
// IsCurrentCapability and every selection change clears the view, so a loaded frame is always the
|
||||||
// under the user's cursor; when this still matches, the new values are posted in instead.
|
// selected capability's. Saving re-sends the whole capability_config payload, and rebuilding the
|
||||||
let customFrameKey = "";
|
// 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) {
|
function SafeJsonParse(text) {
|
||||||
try {
|
try {
|
||||||
@@ -173,10 +175,6 @@ function IsCurrentCapability(payload) {
|
|||||||
&& String(payload?.capability_type || "") === selectedCapabilityType;
|
&& String(payload?.capability_type || "") === selectedCapabilityType;
|
||||||
}
|
}
|
||||||
|
|
||||||
function CapabilityKey(payload) {
|
|
||||||
return JSON.stringify([payload?.plugin_key, payload?.capability_name, payload?.capability_type]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function RequestCapabilityConfig() {
|
function RequestCapabilityConfig() {
|
||||||
if (!selectedPluginKey || !selectedCapabilityName)
|
if (!selectedPluginKey || !selectedCapabilityName)
|
||||||
return;
|
return;
|
||||||
@@ -202,7 +200,7 @@ function ClearCapabilityConfigView() {
|
|||||||
if (custom) {
|
if (custom) {
|
||||||
custom.hidden = true;
|
custom.hidden = true;
|
||||||
custom.removeAttribute("srcdoc");
|
custom.removeAttribute("srcdoc");
|
||||||
customFrameKey = "";
|
customFrameLoaded = false;
|
||||||
}
|
}
|
||||||
if (text)
|
if (text)
|
||||||
text.value = "";
|
text.value = "";
|
||||||
@@ -260,10 +258,10 @@ function ApplyCapabilityConfig(payload) {
|
|||||||
if (custom) {
|
if (custom) {
|
||||||
const context = OrcaConfigContext(payload, "preset");
|
const context = OrcaConfigContext(payload, "preset");
|
||||||
custom.hidden = false;
|
custom.hidden = false;
|
||||||
if (customFrameKey === CapabilityKey(payload) && custom.contentWindow) {
|
if (customFrameLoaded && custom.contentWindow) {
|
||||||
custom.contentWindow.postMessage({ __orca: "config", config: config, context: context }, "*");
|
custom.contentWindow.postMessage({ __orca: "config", config: config, context: context }, "*");
|
||||||
} else {
|
} else {
|
||||||
customFrameKey = CapabilityKey(payload);
|
customFrameLoaded = true;
|
||||||
custom.srcdoc = BuildCustomConfigDocument(html, config, context);
|
custom.srcdoc = BuildCustomConfigDocument(html, config, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,7 +274,7 @@ function ApplyCapabilityConfig(payload) {
|
|||||||
if (custom) {
|
if (custom) {
|
||||||
custom.hidden = true;
|
custom.hidden = true;
|
||||||
custom.removeAttribute("srcdoc");
|
custom.removeAttribute("srcdoc");
|
||||||
customFrameKey = "";
|
customFrameLoaded = false;
|
||||||
}
|
}
|
||||||
if (editor)
|
if (editor)
|
||||||
editor.hidden = false;
|
editor.hidden = false;
|
||||||
@@ -416,7 +414,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
// OnCustomConfigMessage matches on the frame's contentWindow, not the origin ("null" when
|
// OnCustomConfigMessage matches on the frame's contentWindow, not the origin ("null" when
|
||||||
// sandboxed), and ignores anything else.
|
// sandboxed), and ignores anything else.
|
||||||
window.addEventListener("message", OnCustomConfigMessage);
|
window.addEventListener("message", OnCustomConfigMessage);
|
||||||
OrcaWatchThemeForFrame(() => document.getElementById("configCustom"));
|
OrcaWatchThemeForFrame("configCustom");
|
||||||
|
|
||||||
SendMessage("request_capabilities");
|
SendMessage("request_capabilities");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ function OnInit() {
|
|||||||
// OnCustomConfigMessage matches on the frame's contentWindow, not the origin ("null" when
|
// OnCustomConfigMessage matches on the frame's contentWindow, not the origin ("null" when
|
||||||
// sandboxed), and ignores anything else.
|
// sandboxed), and ignores anything else.
|
||||||
window.addEventListener("message", OnCustomConfigMessage);
|
window.addEventListener("message", OnCustomConfigMessage);
|
||||||
OrcaWatchThemeForFrame(() => document.getElementById("configCustom"));
|
OrcaWatchThemeForFrame("configCustom");
|
||||||
|
|
||||||
document.addEventListener("click", (event) => {
|
document.addEventListener("click", (event) => {
|
||||||
if (!event.target.closest(".ctx"))
|
if (!event.target.closest(".ctx"))
|
||||||
|
|||||||
@@ -3,41 +3,25 @@
|
|||||||
// opaque origin, so this bridge is its only channel, and both must offer plugin authors exactly the
|
// 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.
|
// 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
|
// The host theme "contract" (WebViewHostDialog::host_theme_vars_css) arrives as a complete
|
||||||
// stamps it on the top-level page only — it returns early in child frames — so a sandboxed config UI
|
// ":root{--orca-*;color-scheme:...}" rule in the injected <style id="orca-host-theme-vars">, which
|
||||||
// never sees it unless we hand it over.
|
// the document-start injector stamps on the top-level page only — it returns early in child frames —
|
||||||
const ORCA_THEME_VARS = [
|
// so a sandboxed config UI never sees it unless we hand it over. Relay that host-authored CSS
|
||||||
"--orca-bg",
|
// verbatim rather than re-deriving it variable by variable: the C++ function stays the only place
|
||||||
"--orca-fg",
|
// the contract is spelled out, and the host re-themes the style in place, so reading it now always
|
||||||
"--orca-muted",
|
// yields the live theme.
|
||||||
"--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.
|
|
||||||
function OrcaThemeSnapshot() {
|
function OrcaThemeSnapshot() {
|
||||||
const style = getComputedStyle(document.documentElement);
|
const style = document.getElementById("orca-host-theme-vars");
|
||||||
const vars = {};
|
|
||||||
ORCA_THEME_VARS.forEach((name) => {
|
|
||||||
// Values are host-produced colors and a pre-sanitized font stack; strip anything that could end
|
|
||||||
// the declaration or the <style> block it gets inlined into.
|
|
||||||
const value = String(style.getPropertyValue(name) || "").trim().replace(/[<>{};]/g, "");
|
|
||||||
if (value)
|
|
||||||
vars[name] = value;
|
|
||||||
});
|
|
||||||
return {
|
return {
|
||||||
theme: document.documentElement.getAttribute("data-orca-theme") === "dark" ? "dark" : "light",
|
theme: document.documentElement.getAttribute("data-orca-theme") === "dark" ? "dark" : "light",
|
||||||
vars: vars
|
css: style ? style.textContent : ""
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inlined into a <script> or a JSON payload: a stored "</script>" would close the tag early, so
|
// Inlined into a <script> or a JSON payload: a stored "</script>" would close the tag early, so
|
||||||
// escape "<" — the literal stays valid JSON.
|
// escape "<" — the literal stays valid JSON.
|
||||||
function OrcaInlineJson(value) {
|
function OrcaInlineJson(value) {
|
||||||
return JSON.stringify(value === undefined ? null : value).replace(/</g, "\\u003c");
|
return JSON.stringify(value).replace(/</g, "\\u003c");
|
||||||
}
|
}
|
||||||
|
|
||||||
// What a custom UI can learn about the surface it is being edited on. Kept to what changes the
|
// What a custom UI can learn about the surface it is being edited on. Kept to what changes the
|
||||||
@@ -66,13 +50,9 @@ function BuildCustomConfigDocument(html, config, context) {
|
|||||||
var theme = ${OrcaInlineJson(theme)};
|
var theme = ${OrcaInlineJson(theme)};
|
||||||
|
|
||||||
function applyTheme(next) {
|
function applyTheme(next) {
|
||||||
if (next && next.vars) theme = next;
|
if (next && typeof next.css === "string") theme = next;
|
||||||
var css = ":root{";
|
|
||||||
for (var name in theme.vars)
|
|
||||||
if (Object.prototype.hasOwnProperty.call(theme.vars, name)) css += name + ":" + theme.vars[name] + ";";
|
|
||||||
css += "color-scheme:" + theme.theme + ";}";
|
|
||||||
var style = document.getElementById("orca-host-theme-vars");
|
var style = document.getElementById("orca-host-theme-vars");
|
||||||
if (style) style.textContent = css;
|
if (style) style.textContent = theme.css;
|
||||||
if (document.documentElement) document.documentElement.setAttribute("data-orca-theme", theme.theme);
|
if (document.documentElement) document.documentElement.setAttribute("data-orca-theme", theme.theme);
|
||||||
themeHandlers.forEach(function (handler) {
|
themeHandlers.forEach(function (handler) {
|
||||||
try { handler(theme.theme); } catch (e) {}
|
try { handler(theme.theme); } catch (e) {}
|
||||||
@@ -116,9 +96,9 @@ function BuildCustomConfigDocument(html, config, context) {
|
|||||||
// The host re-themes an open dialog in place (WebViewHostDialog::host_theme_apply_js rewrites the
|
// The host re-themes an open dialog in place (WebViewHostDialog::host_theme_apply_js rewrites the
|
||||||
// injected variables and re-stamps data-orca-theme), which a sandboxed child frame never sees. Relay
|
// injected variables and re-stamps data-orca-theme), which a sandboxed child frame never sees. Relay
|
||||||
// it so a custom UI follows a light/dark switch without being reopened.
|
// it so a custom UI follows a light/dark switch without being reopened.
|
||||||
function OrcaWatchThemeForFrame(getFrame) {
|
function OrcaWatchThemeForFrame(frameId) {
|
||||||
const relay = () => {
|
const relay = () => {
|
||||||
const frame = getFrame();
|
const frame = document.getElementById(frameId);
|
||||||
if (!frame || frame.hidden || !frame.contentWindow)
|
if (!frame || frame.hidden || !frame.contentWindow)
|
||||||
return;
|
return;
|
||||||
frame.contentWindow.postMessage({ __orca: "theme", theme: OrcaThemeSnapshot() }, "*");
|
frame.contentWindow.postMessage({ __orca: "theme", theme: OrcaThemeSnapshot() }, "*");
|
||||||
|
|||||||
@@ -490,6 +490,8 @@ _CONFIG_UI = """
|
|||||||
draw();
|
draw();
|
||||||
restoreReadout();
|
restoreReadout();
|
||||||
refreshState();
|
refreshState();
|
||||||
|
labelRestore();
|
||||||
|
setInputsEnabled(!context.readOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
// "Restore defaults" writes the plugin's own defaults globally, but in a preset it discards that
|
// "Restore defaults" writes the plugin's own defaults globally, but in a preset it discards that
|
||||||
@@ -513,8 +515,6 @@ _CONFIG_UI = """
|
|||||||
|
|
||||||
buildRows();
|
buildRows();
|
||||||
load(window.orca ? window.orca.getConfig() : {});
|
load(window.orca ? window.orca.getConfig() : {});
|
||||||
labelRestore();
|
|
||||||
setInputsEnabled(!context.readOnly);
|
|
||||||
|
|
||||||
document.getElementById("save").addEventListener("click", function () {
|
document.getElementById("save").addEventListener("click", function () {
|
||||||
if (!window.orca) return;
|
if (!window.orca) return;
|
||||||
@@ -532,8 +532,6 @@ _CONFIG_UI = """
|
|||||||
if (first) { first = false; return; }
|
if (first) { first = false; return; }
|
||||||
if (window.orca.getContext) context = window.orca.getContext();
|
if (window.orca.getContext) context = window.orca.getContext();
|
||||||
load(config);
|
load(config);
|
||||||
labelRestore();
|
|
||||||
setInputsEnabled(!context.readOnly);
|
|
||||||
announce("Saved");
|
announce("Saved");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1203,7 +1203,7 @@ static std::vector<std::string> s_Preset_print_options{
|
|||||||
"post_process",
|
"post_process",
|
||||||
"slicing_pipeline_plugin",
|
"slicing_pipeline_plugin",
|
||||||
"plugins",
|
"plugins",
|
||||||
"plugin_config_overrides",
|
"print_plugin_config_overrides",
|
||||||
"process_change_extrusion_role_gcode",
|
"process_change_extrusion_role_gcode",
|
||||||
"min_length_factor",
|
"min_length_factor",
|
||||||
"wall_maximum_resolution",
|
"wall_maximum_resolution",
|
||||||
@@ -1378,7 +1378,7 @@ static std::vector<std::string> s_Preset_filament_options {/*"filament_colour",
|
|||||||
"filament_preheat_temperature_delta", "filament_retract_length_nc",
|
"filament_preheat_temperature_delta", "filament_retract_length_nc",
|
||||||
"filament_change_length_nc", "filament_prime_volume", "filament_prime_volume_nc",
|
"filament_change_length_nc", "filament_prime_volume", "filament_prime_volume_nc",
|
||||||
"long_retractions_when_ec", "retraction_distances_when_ec",
|
"long_retractions_when_ec", "retraction_distances_when_ec",
|
||||||
"plugin_config_overrides",
|
"filament_plugin_config_overrides",
|
||||||
//ams chamber
|
//ams chamber
|
||||||
"filament_dev_ams_drying_ams_limitations", "filament_dev_ams_drying_temperature", "filament_dev_ams_drying_time", "filament_dev_ams_drying_heat_distortion_temperature",
|
"filament_dev_ams_drying_ams_limitations", "filament_dev_ams_drying_temperature", "filament_dev_ams_drying_time", "filament_dev_ams_drying_heat_distortion_temperature",
|
||||||
"filament_dev_chamber_drying_bed_temperature", "filament_dev_chamber_drying_time",
|
"filament_dev_chamber_drying_bed_temperature", "filament_dev_chamber_drying_time",
|
||||||
@@ -1430,7 +1430,7 @@ static std::vector<std::string> s_Preset_printer_options {
|
|||||||
// Fast-purge printer flag + device/firmware-facing per-variant extruder-change
|
// Fast-purge printer flag + device/firmware-facing per-variant extruder-change
|
||||||
// deretraction speed (unconsumed by the slicer; carried by H2D/A2L/X2D/P2S machine profiles).
|
// deretraction speed (unconsumed by the slicer; carried by H2D/A2L/X2D/P2S machine profiles).
|
||||||
"support_fast_purge_mode", "deretract_speed_extruder_change",
|
"support_fast_purge_mode", "deretract_speed_extruder_change",
|
||||||
"plugin_config_overrides"
|
"printer_plugin_config_overrides"
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::vector<std::string> s_Preset_sla_print_options {
|
static std::vector<std::string> s_Preset_sla_print_options {
|
||||||
@@ -1542,6 +1542,15 @@ const std::vector<std::string>& Preset::printer_options()
|
|||||||
return s_opts;
|
return s_opts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* Preset::plugin_overrides_key(Type type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case TYPE_PRINTER: return "printer_plugin_config_overrides";
|
||||||
|
case TYPE_FILAMENT: return "filament_plugin_config_overrides";
|
||||||
|
default: return "print_plugin_config_overrides";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PresetCollection::PresetCollection(Preset::Type type, const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name) :
|
PresetCollection::PresetCollection(Preset::Type type, const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name) :
|
||||||
m_type(type),
|
m_type(type),
|
||||||
m_edited_preset(type, "", false),
|
m_edited_preset(type, "", false),
|
||||||
|
|||||||
@@ -407,6 +407,12 @@ public:
|
|||||||
// Printer machine limits, those are contained in printer_options().
|
// Printer machine limits, those are contained in printer_options().
|
||||||
static const std::vector<std::string>& machine_limits_options();
|
static const std::vector<std::string>& machine_limits_options();
|
||||||
|
|
||||||
|
// The option key holding this preset type's plugin capability overrides. The print, printer and
|
||||||
|
// filament presets each keep their own key so the values never clobber one another when the
|
||||||
|
// presets merge into a single full config; print doubles as the fallback for other types, which
|
||||||
|
// have no plugin-backed options.
|
||||||
|
static const char* plugin_overrides_key(Type type);
|
||||||
|
|
||||||
static const std::vector<std::string>& sla_printer_options();
|
static const std::vector<std::string>& sla_printer_options();
|
||||||
static const std::vector<std::string>& sla_material_options();
|
static const std::vector<std::string>& sla_material_options();
|
||||||
static const std::vector<std::string>& sla_print_options();
|
static const std::vector<std::string>& sla_print_options();
|
||||||
|
|||||||
@@ -285,6 +285,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
|||||||
steps.emplace_back(psSkirtBrim);
|
steps.emplace_back(psSkirtBrim);
|
||||||
} else if (
|
} else if (
|
||||||
opt_key == "slicing_pipeline_plugin"
|
opt_key == "slicing_pipeline_plugin"
|
||||||
|
|| opt_key == "print_plugin_config_overrides"
|
||||||
|| opt_key == "initial_layer_print_height"
|
|| opt_key == "initial_layer_print_height"
|
||||||
|| opt_key == "nozzle_diameter"
|
|| opt_key == "nozzle_diameter"
|
||||||
|| opt_key == "filament_shrink"
|
|| opt_key == "filament_shrink"
|
||||||
|
|||||||
@@ -1081,16 +1081,23 @@ void PrintConfigDef::init_common_params()
|
|||||||
def->set_default_value(new ConfigOptionString());
|
def->set_default_value(new ConfigOptionString());
|
||||||
}
|
}
|
||||||
|
|
||||||
def = this->add("plugin_config_overrides", coString);
|
// Per preset type (Preset::plugin_overrides_key): the print, printer and filament presets each hold
|
||||||
def->label = L("Capabilities");
|
// the capability overrides for their own plugin-backed options (slicing_pipeline_plugin,
|
||||||
def->tooltip = L("Configuration for the plugin capabilities this preset uses, overriding the global "
|
// printer_agent, ...). Separate keys keep them from clobbering each other when the presets merge
|
||||||
"Capabilities configuration. Stored as a raw JSON array and edited through the dialog "
|
// into one full config. They replace a single shared "plugin_config_overrides" deliberately without
|
||||||
"behind the button, never typed in directly.");
|
// handle_legacy migration: that key existed in nightly builds only, never in a release. Never shown
|
||||||
// Never shown as a text field: GUIType::plugin_config renders a button that opens PluginsConfigDialog.
|
// as a text field: GUIType::plugin_config renders a button that opens PluginsConfigDialog.
|
||||||
def->gui_type = ConfigOptionDef::GUIType::plugin_config;
|
for (const char* key : {"print_plugin_config_overrides", "printer_plugin_config_overrides", "filament_plugin_config_overrides"}) {
|
||||||
def->mode = comAdvanced;
|
def = this->add(key, coString);
|
||||||
def->cli = ConfigOptionDef::nocli;
|
def->label = L("Capabilities");
|
||||||
def->set_default_value(new ConfigOptionString(""));
|
def->tooltip = L("Configuration for the plugin capabilities this preset uses, overriding the global "
|
||||||
|
"Capabilities configuration. Stored as a raw JSON array and edited through the dialog "
|
||||||
|
"behind the button, never typed in directly.");
|
||||||
|
def->gui_type = ConfigOptionDef::GUIType::plugin_config;
|
||||||
|
def->mode = comAdvanced;
|
||||||
|
def->cli = ConfigOptionDef::nocli;
|
||||||
|
def->set_default_value(new ConfigOptionString(""));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrintConfigDef::init_fff_params()
|
void PrintConfigDef::init_fff_params()
|
||||||
|
|||||||
@@ -1780,6 +1780,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
|||||||
((ConfigOptionString, filename_format))
|
((ConfigOptionString, filename_format))
|
||||||
((ConfigOptionStrings, post_process))
|
((ConfigOptionStrings, post_process))
|
||||||
((ConfigOptionStrings, slicing_pipeline_plugin))
|
((ConfigOptionStrings, slicing_pipeline_plugin))
|
||||||
|
((ConfigOptionString, print_plugin_config_overrides))
|
||||||
((ConfigOptionString, printer_model))
|
((ConfigOptionString, printer_model))
|
||||||
((ConfigOptionFloat, resolution))
|
((ConfigOptionFloat, resolution))
|
||||||
((ConfigOptionFloats, retraction_minimum_travel))
|
((ConfigOptionFloats, retraction_minimum_travel))
|
||||||
|
|||||||
@@ -1254,8 +1254,7 @@ bool PrintObject::invalidate_state_by_config_options(
|
|||||||
|| opt_key == "interlocking_beam_layer_count"
|
|| opt_key == "interlocking_beam_layer_count"
|
||||||
|| opt_key == "interlocking_depth"
|
|| opt_key == "interlocking_depth"
|
||||||
|| opt_key == "interlocking_boundary_avoidance"
|
|| opt_key == "interlocking_boundary_avoidance"
|
||||||
|| opt_key == "interlocking_beam_width"
|
|| opt_key == "interlocking_beam_width") {
|
||||||
|| opt_key == "plugin_config_overrides") {
|
|
||||||
steps.emplace_back(posSlice);
|
steps.emplace_back(posSlice);
|
||||||
} else if (
|
} else if (
|
||||||
opt_key == "elefant_foot_compensation"
|
opt_key == "elefant_foot_compensation"
|
||||||
|
|||||||
@@ -1796,7 +1796,7 @@ 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
|
// 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.
|
// 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
|
// Also drop any plugin config override entries for a capability the change just stopped
|
||||||
// referencing (e.g. a plugin removed from slicing_pipeline_plugin), so a saved preset never
|
// 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
|
// 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
|
// field holding its own cached copy of that value, so it needs to be told explicitly, or it
|
||||||
@@ -1804,9 +1804,10 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
|
|||||||
if (const ConfigOptionDef* opt_def = m_config->def()->get(opt_key);
|
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();
|
m_config->update_plugin_manifest();
|
||||||
if (prune_stale_plugin_overrides(*m_config)) {
|
const std::string overrides_key = Preset::plugin_overrides_key(m_type);
|
||||||
if (Field* overrides_field = get_field(PLUGIN_OVERRIDES_OPTION_KEY))
|
if (prune_stale_plugin_overrides(*m_config, overrides_key)) {
|
||||||
overrides_field->set_value(boost::any(m_config->opt_string(PLUGIN_OVERRIDES_OPTION_KEY)), false);
|
if (Field* overrides_field = get_field(overrides_key))
|
||||||
|
overrides_field->set_value(boost::any(m_config->opt_string(overrides_key)), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3136,7 +3137,7 @@ void TabPrint::build()
|
|||||||
// Its own group: the one above hides its labels, and this row needs its label — and the revert
|
// Its own group: the one above hides its labels, and this row needs its label — and the revert
|
||||||
// arrow beside it — to show. No label-width override either, as a 0 there means "no label column".
|
// arrow beside it — to show. No label-width override either, as a 0 there means "no label column".
|
||||||
optgroup = page->new_optgroup(L("Plugin Configuration"), L"param_gcode");
|
optgroup = page->new_optgroup(L("Plugin Configuration"), L"param_gcode");
|
||||||
optgroup->append_single_option_line("plugin_config_overrides");
|
optgroup->append_single_option_line("print_plugin_config_overrides");
|
||||||
|
|
||||||
optgroup = page->new_optgroup(L("Notes"), "note", 0);
|
optgroup = page->new_optgroup(L("Notes"), "note", 0);
|
||||||
option = optgroup->get_option("notes");
|
option = optgroup->get_option("notes");
|
||||||
@@ -4552,7 +4553,7 @@ void TabFilament::build()
|
|||||||
optgroup->append_single_option_line(option);
|
optgroup->append_single_option_line(option);
|
||||||
|
|
||||||
optgroup = page->new_optgroup(L("Plugin Configuration"), L"param_gcode");
|
optgroup = page->new_optgroup(L("Plugin Configuration"), L"param_gcode");
|
||||||
optgroup->append_single_option_line("plugin_config_overrides");
|
optgroup->append_single_option_line("filament_plugin_config_overrides");
|
||||||
|
|
||||||
page = add_options_page(L("Multimaterial"), "custom-gcode_multi_material"); // ORCA: icon only visible on placeholders
|
page = add_options_page(L("Multimaterial"), "custom-gcode_multi_material"); // ORCA: icon only visible on placeholders
|
||||||
optgroup = page->new_optgroup(L("Wipe tower parameters"), "param_tower");
|
optgroup = page->new_optgroup(L("Wipe tower parameters"), "param_tower");
|
||||||
@@ -5061,7 +5062,7 @@ void TabPrinter::build_fff()
|
|||||||
optgroup->append_single_option_line("time_cost", "printer_basic_information_advanced#time-cost");
|
optgroup->append_single_option_line("time_cost", "printer_basic_information_advanced#time-cost");
|
||||||
|
|
||||||
optgroup = page->new_optgroup(L("Plugin Configuration"), L"param_gcode");
|
optgroup = page->new_optgroup(L("Plugin Configuration"), L"param_gcode");
|
||||||
optgroup->append_single_option_line("plugin_config_overrides");
|
optgroup->append_single_option_line("printer_plugin_config_overrides");
|
||||||
|
|
||||||
optgroup = page->new_optgroup(L("Cooling Fan"), "param_cooling_fan");
|
optgroup = page->new_optgroup(L("Cooling Fan"), "param_cooling_fan");
|
||||||
Line line = Line{ L("Fan speed-up time"), optgroup->get_option("fan_speedup_time").opt.tooltip };
|
Line line = Line{ L("Fan speed-up time"), optgroup->get_option("fan_speedup_time").opt.tooltip };
|
||||||
|
|||||||
@@ -324,12 +324,6 @@ bool PluginConfig::dirty() const
|
|||||||
return m_dirty;
|
return m_dirty;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string plugin_overrides_of(const Preset& preset)
|
|
||||||
{
|
|
||||||
const auto* opt = dynamic_cast<const ConfigOptionString*>(preset.config.option(PLUGIN_OVERRIDES_OPTION_KEY));
|
|
||||||
return opt == nullptr ? std::string() : opt->value;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool parse_plugin_overrides(const std::string& raw, CapabilityConfigDocument& document, std::string& error)
|
bool parse_plugin_overrides(const std::string& raw, CapabilityConfigDocument& document, std::string& error)
|
||||||
{
|
{
|
||||||
document = CapabilityConfigDocument();
|
document = CapabilityConfigDocument();
|
||||||
@@ -357,9 +351,9 @@ std::string serialize_plugin_overrides(const CapabilityConfigDocument& document)
|
|||||||
return document.empty() ? std::string() : document.serialize_entries().dump();
|
return document.empty() ? std::string() : document.serialize_entries().dump();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool prune_stale_plugin_overrides(DynamicConfig& config)
|
bool prune_stale_plugin_overrides(DynamicConfig& config, const std::string& overrides_key)
|
||||||
{
|
{
|
||||||
const auto* overrides_opt = dynamic_cast<const ConfigOptionString*>(config.option(PLUGIN_OVERRIDES_OPTION_KEY));
|
const auto* overrides_opt = dynamic_cast<const ConfigOptionString*>(config.option(overrides_key));
|
||||||
if (overrides_opt == nullptr || overrides_opt->value.empty())
|
if (overrides_opt == nullptr || overrides_opt->value.empty())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -397,7 +391,7 @@ bool prune_stale_plugin_overrides(DynamicConfig& config)
|
|||||||
if (!overrides.prune_unreferenced(referenced))
|
if (!overrides.prune_unreferenced(referenced))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
config.set_key_value(PLUGIN_OVERRIDES_OPTION_KEY, new ConfigOptionString(serialize_plugin_overrides(overrides)));
|
config.set_key_value(overrides_key, new ConfigOptionString(serialize_plugin_overrides(overrides)));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,27 +464,29 @@ EffectiveCapabilityConfig active_capability_config(const PluginCapabilityId& id)
|
|||||||
|
|
||||||
if (bundle != nullptr) {
|
if (bundle != nullptr) {
|
||||||
const std::string type_key = plugin_capability_type_to_string(id.type);
|
const std::string type_key = plugin_capability_type_to_string(id.type);
|
||||||
|
// The edited preset of each type that can hold plugin-backed options, keyed by its option list.
|
||||||
|
const std::pair<const std::vector<std::string>*, const Preset*> scopes[] = {
|
||||||
|
{&Preset::print_options(), &bundle->prints.get_edited_preset()},
|
||||||
|
{&Preset::printer_options(), &bundle->printers.get_edited_preset()},
|
||||||
|
{&Preset::filament_options(), &bundle->filaments.get_edited_preset()},
|
||||||
|
};
|
||||||
for (const auto& [key, def] : print_config_def.options) {
|
for (const auto& [key, def] : print_config_def.options) {
|
||||||
if (def.plugin_type != type_key)
|
if (def.plugin_type != type_key)
|
||||||
continue;
|
continue;
|
||||||
|
for (const auto& [options, edited] : scopes)
|
||||||
const auto& print_options = Preset::print_options();
|
if (contains(*options, key)) {
|
||||||
if (std::find(print_options.begin(), print_options.end(), key) != print_options.end()) {
|
preset = edited;
|
||||||
preset = &bundle->prints.get_edited_preset();
|
break;
|
||||||
|
}
|
||||||
|
if (preset != nullptr)
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
const auto& printer_options = Preset::printer_options();
|
|
||||||
if (std::find(printer_options.begin(), printer_options.end(), key) != printer_options.end()) {
|
|
||||||
preset = &bundle->printers.get_edited_preset();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (preset != nullptr) {
|
if (preset != nullptr) {
|
||||||
|
const auto* stored = dynamic_cast<const ConfigOptionString*>(preset->config.option(Preset::plugin_overrides_key(preset->type)));
|
||||||
std::string error;
|
std::string error;
|
||||||
if (!parse_plugin_overrides(plugin_overrides_of(*preset), overrides, error)) {
|
if (!parse_plugin_overrides(stored == nullptr ? std::string() : stored->value, overrides, error)) {
|
||||||
// Text we cannot read is not an override: log it and resolve against the base config.
|
// Text we cannot read is not an override: log it and resolve against the base config.
|
||||||
BOOST_LOG_TRIVIAL(error) << "Preset \"" << preset->name << "\": " << error;
|
BOOST_LOG_TRIVIAL(error) << "Preset \"" << preset->name << "\": " << error;
|
||||||
overrides = CapabilityConfigDocument();
|
overrides = CapabilityConfigDocument();
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
class Preset;
|
|
||||||
class DynamicConfig;
|
class DynamicConfig;
|
||||||
struct CapabilityConfigEntry
|
struct CapabilityConfigEntry
|
||||||
{
|
{
|
||||||
@@ -50,19 +49,16 @@ private:
|
|||||||
std::vector<nlohmann::json> m_opaque_entries;
|
std::vector<nlohmann::json> m_opaque_entries;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline constexpr const char* PLUGIN_OVERRIDES_OPTION_KEY = "plugin_config_overrides";
|
|
||||||
|
|
||||||
std::string plugin_overrides_of(const Preset& preset);
|
|
||||||
bool parse_plugin_overrides(const std::string& raw, CapabilityConfigDocument& document, std::string& error);
|
bool parse_plugin_overrides(const std::string& raw, CapabilityConfigDocument& document, std::string& error);
|
||||||
std::string serialize_plugin_overrides(const CapabilityConfigDocument& document);
|
std::string serialize_plugin_overrides(const CapabilityConfigDocument& document);
|
||||||
|
|
||||||
// Drops plugin_config_overrides entries for capabilities no longer named by any plugin-backed
|
// Drops plugin override entries for capabilities no longer named by any plugin-backed option's current
|
||||||
// option's current value in `config` (e.g. slicing_pipeline_plugin cleared or switched to a
|
// value in `config` (e.g. slicing_pipeline_plugin cleared or switched to a different capability), and
|
||||||
// different capability), and writes the result back if anything changed. Called wherever a
|
// writes the result back to `overrides_key` if anything changed. Called wherever a plugin-backed
|
||||||
// plugin-backed option's value changes, so a saved preset never carries configuration for a
|
// option's value changes, so a saved preset never carries configuration for a capability it no longer
|
||||||
// capability it no longer references. Returns true if `config` was modified, so a caller holding a
|
// references. Returns true if `config` was modified, so a caller holding a GUI field over
|
||||||
// GUI field over PLUGIN_OVERRIDES_OPTION_KEY knows it must refresh that field's displayed value.
|
// `overrides_key` knows it must refresh that field's displayed value.
|
||||||
bool prune_stale_plugin_overrides(DynamicConfig& config);
|
bool prune_stale_plugin_overrides(DynamicConfig& config, const std::string& overrides_key);
|
||||||
|
|
||||||
struct EffectiveCapabilityConfig
|
struct EffectiveCapabilityConfig
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -218,6 +218,20 @@ TEST_CASE("Changing slicing_pipeline_plugin invalidates posSlice", "[slicing_pip
|
|||||||
CHECK_FALSE(print.objects().front()->is_step_done(posSlice)); // re-slice required
|
CHECK_FALSE(print.objects().front()->is_step_done(posSlice)); // re-slice required
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Editing a slicing plugin's config (print_plugin_config_overrides) must re-run posSlice, where the
|
||||||
|
// plugin transforms each layer's geometry; otherwise the cached slice keeps the old config's result.
|
||||||
|
TEST_CASE("Changing print_plugin_config_overrides invalidates posSlice", "[slicing_pipeline]") {
|
||||||
|
Slic3r::Print print; Slic3r::Model model;
|
||||||
|
auto config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||||
|
init_print({cube(20)}, print, model, config);
|
||||||
|
print.process();
|
||||||
|
REQUIRE(print.objects().front()->is_step_done(posSlice));
|
||||||
|
config.set_key_value("print_plugin_config_overrides",
|
||||||
|
new Slic3r::ConfigOptionString("[{\"type\":\"slicing-pipeline\",\"name\":\"Twistify\",\"config\":{\"twist_deg_per_mm\":2.0}}]"));
|
||||||
|
print.apply(model, config);
|
||||||
|
CHECK_FALSE(print.objects().front()->is_step_done(posSlice)); // re-slice required
|
||||||
|
}
|
||||||
|
|
||||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||||
|
|
||||||
// A similarity transform (rotate + uniform scale) applied to slices at Step.posSlice, matching
|
// A similarity transform (rotate + uniform scale) applied to slices at Step.posSlice, matching
|
||||||
|
|||||||
@@ -464,3 +464,29 @@ TEST_CASE("Profile validator flags dangling and renamed preset references", "[Pr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Each preset type stores its plugin capability overrides under its own option key. Merging the print,
|
||||||
|
// printer and filament presets into one full config under a shared key would let the last preset applied
|
||||||
|
// overwrite the others' overrides -- the clobber that stopped an edited slicing-pipeline (print) override
|
||||||
|
// from reaching Print::apply's diff, so re-configuring a plugin never re-sliced. Distinct per-type keys
|
||||||
|
// make that collision impossible; guard the scoping here.
|
||||||
|
TEST_CASE("Plugin capability override keys are scoped per preset type", "[Preset][Plugin]")
|
||||||
|
{
|
||||||
|
// Pin the key names: presets and 3mf files store them verbatim, so a rename is a format change.
|
||||||
|
CHECK(Preset::plugin_overrides_key(Preset::TYPE_PRINT) == std::string("print_plugin_config_overrides"));
|
||||||
|
CHECK(Preset::plugin_overrides_key(Preset::TYPE_PRINTER) == std::string("printer_plugin_config_overrides"));
|
||||||
|
CHECK(Preset::plugin_overrides_key(Preset::TYPE_FILAMENT) == std::string("filament_plugin_config_overrides"));
|
||||||
|
|
||||||
|
// ...and each key lives on exactly its own preset type's option list, so no two ever share a slot.
|
||||||
|
const std::pair<Preset::Type, const std::vector<std::string>*> scopes[] = {
|
||||||
|
{Preset::TYPE_PRINT, &Preset::print_options()},
|
||||||
|
{Preset::TYPE_PRINTER, &Preset::printer_options()},
|
||||||
|
{Preset::TYPE_FILAMENT, &Preset::filament_options()},
|
||||||
|
};
|
||||||
|
for (const auto &owner : scopes)
|
||||||
|
for (const auto &scoped : scopes) {
|
||||||
|
const std::string key = Preset::plugin_overrides_key(scoped.first);
|
||||||
|
CAPTURE(owner.first, key);
|
||||||
|
CHECK(contains(*owner.second, key) == (owner.first == scoped.first));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user