Plugin configuration
-
+
@@ -34,8 +32,7 @@
autocomplete="off" autocapitalize="off" aria-label="Capability configuration (JSON)">
+ an opaque origin and reaches the host only through the injected window.orca bridge. -->
diff --git a/resources/web/dialog/PluginsConfigDialog/index.js b/resources/web/dialog/PluginsConfigDialog/index.js
index 6e76cbeaa1..342349e52c 100644
--- a/resources/web/dialog/PluginsConfigDialog/index.js
+++ b/resources/web/dialog/PluginsConfigDialog/index.js
@@ -1,10 +1,8 @@
-// The capabilities the active preset uses, as sent by PluginsConfigDialog::send_capabilities.
-// Each row is {plugin_key, name, type, type_key, has_config_ui} — the shape
-// PluginConfig::capabilities_payload emits, shared with the Plugins dialog's Config tab.
+// Capability rows the active preset uses, as PluginConfig::capabilities_payload emits them:
+// {plugin_key, name, type, type_key, has_config_ui}.
let capabilities = [];
-// The selected row's full identity. plugin_key is part of it because this list spans plugins,
-// unlike the Plugins dialog's config tab where every row belongs to the one selected plugin.
+// The selected row's identity; plugin_key is part of it because this list spans plugins.
let selectedPluginKey = "";
let selectedCapabilityName = "";
let selectedCapabilityType = "";
@@ -105,8 +103,7 @@ function RenderCapabilities() {
empty.hidden = true;
layout.hidden = false;
- // Keep the selection across a refresh when the capability is still there; otherwise fall back to
- // the first one, which is also the initial selection.
+ // Keep the selection across a refresh if the capability is still there, else select the first.
if (!capabilities.some(IsSameCapability)) {
selectedPluginKey = String(capabilities[0].plugin_key || "");
selectedCapabilityName = String(capabilities[0].name || "");
@@ -158,26 +155,17 @@ function OnConfigSidebarClick(event) {
selectedCapabilityName = name;
selectedCapabilityType = typeKey;
- // Drop the outgoing capability's view immediately: the native reply is asynchronous and its
- // content must never appear under the newly selected capability.
+ // The native reply is async: clear now so the old config cannot appear under the new selection.
ClearCapabilityConfigView();
RequestCapabilityConfig();
RenderCapabilities();
}
-// ---------------------------------------------------------------------------
-// Config editor
-//
-// The right side shows either the host's JSON editor or, when the capability ships one, its own
-// HTML UI in a sandboxed frame. Both edit the same stored config: the page holds no config state
-// of its own, it renders what the native side sends and sends back what the user saves. Ported
-// from PluginsDialog/index.js, whose config tab lives inside a single selected plugin (it reads
-// selectedPluginId from a page-global); this list spans plugins, so every row carries its own
-// plugin_key and the page tracks selectedPluginKey instead.
-// ---------------------------------------------------------------------------
+// Config editor: the host's JSON editor, or the capability's own HTML UI in a sandboxed frame.
+// Both edit the same stored config; the page renders what the native side sends.
-// A reply is only applied when it still matches the selected row. The native side is asynchronous,
-// and unlike the Plugins dialog this list spans plugins, so plugin_key is part of the match.
+// Replies are async: apply one only if it still matches the selected row (plugin_key included,
+// since this list spans plugins), so a stale reply never lands under another capability.
function IsCurrentCapability(payload) {
return String(payload?.plugin_key || "") === selectedPluginKey
&& String(payload?.capability_name || "") === selectedCapabilityName
@@ -195,9 +183,8 @@ function RequestCapabilityConfig() {
});
}
-// Empties both editors, so nothing from the previously selected capability can linger while the
-// next one is still in flight. The footer goes with them: until a config has actually loaded there
-// is nothing to save or restore.
+// Empties both editors and the footer, so nothing from the previous capability lingers while the
+// next one is in flight.
function ClearCapabilityConfigView() {
const editor = document.getElementById("configEditor");
const custom = document.getElementById("configCustom");
@@ -243,7 +230,6 @@ function UpdateConfigMeta(payload) {
(source === "base" ? "Inherited from global configuration" : "No saved configuration");
if (save)
save.disabled = selectedReadOnly;
- // There is nothing to discard when the preset holds no override of its own.
if (restore)
restore.disabled = selectedReadOnly || !selectedHasPresetOverride;
meta.hidden = false;
@@ -268,12 +254,10 @@ function ApplyCapabilityConfig(payload) {
const html = String(payload?.custom_html || "");
UpdateConfigMeta(payload);
- // Restore is host chrome and applies to either editor; Save and the validation message belong to
- // the JSON editor, since a custom UI saves through its own controls via the bridge.
+ // Restore applies to either editor; Save and validation belong to the JSON editor only.
ShowConfigFooter(!html);
if (html) {
- // A capability with its own UI: hand it the config through the bridge, never the raw file.
if (custom) {
custom.hidden = false;
custom.srcdoc = BuildCustomConfigDocument(html, config);
@@ -283,8 +267,7 @@ function ApplyCapabilityConfig(payload) {
return;
}
- // Default editor. The native side already reported why a custom UI is unavailable (if it was
- // meant to have one) in payload.error, and we fall back to editing the same config here.
+ // Default editor: any reason a custom UI is unavailable already arrived in payload.error.
if (custom) {
custom.hidden = true;
custom.removeAttribute("srcdoc");
@@ -296,8 +279,7 @@ function ApplyCapabilityConfig(payload) {
SetConfigValidation("");
}
-// Reveals the footer for the loaded capability. `withEditorControls` is false for a custom UI,
-// leaving Restore on its own.
+// withEditorControls is false for a custom UI, which saves through its own controls.
function ShowConfigFooter(withEditorControls) {
const footer = document.getElementById("configFooter");
const save = document.getElementById("configSaveBtn");
@@ -318,8 +300,7 @@ function SetConfigValidation(message) {
node.textContent = message;
node.classList.toggle("invalid", message !== "");
}
- // Invalid JSON can never be saved: the button is the only way to persist, and it is disabled
- // while the text does not parse. The native side re-validates regardless.
+ // Invalid JSON is never saved: Save is the only way to persist. The native side re-validates.
if (save)
save.disabled = selectedReadOnly || message !== "";
}
@@ -359,10 +340,8 @@ function SaveCapabilityConfig() {
});
}
-// Drops the preset's override, which is what "default" means here: the capability falls back to the
-// global configuration, the same as a preset that was never overridden. The native side confirms
-// before discarding it and then re-sends the capability's config, so the editors reload from what is
-// now effective rather than from what was typed.
+// "Restore defaults" here drops the preset's override, so the capability falls back to the global
+// configuration. The native side confirms, then re-sends the config that is now effective.
function RestoreCapabilityConfig() {
if (!selectedPluginKey || !selectedCapabilityName || !selectedHasPresetOverride)
return;
@@ -387,8 +366,7 @@ function ApplyCapabilityConfigSaved(payload) {
if (payload?.ok !== true)
return;
- // Reload from what was actually persisted, so both editors show the stored state rather than
- // whatever was typed.
+ // Reload from what was persisted, not from what was typed.
const config = payload && Object.prototype.hasOwnProperty.call(payload, "config") ? payload.config : {};
const custom = document.getElementById("configCustom");
const text = document.getElementById("configText");
@@ -401,13 +379,11 @@ function ApplyCapabilityConfigSaved(payload) {
SetConfigValidation("");
}
-// The whole host surface a custom config UI gets: read the config it was opened with, save a new
-// one, and be told when a save lands. Everything else about the dialog stays out of reach — the
-// frame is sandboxed into an opaque origin, so this bridge is its only way to talk to the host.
+// The whole host surface a custom config UI gets: read the config, save one, be told when a save
+// lands. The frame is sandboxed into an opaque origin, so this bridge is its only channel.
function BuildCustomConfigDocument(html, config) {
- // The config is inlined into a " would
- // otherwise close the tag early and inject the rest as markup. Escaping "<" keeps the literal
- // valid JSON while making that impossible.
+ // Inlined into a " would close the tag early, so escape "<" — the
+ // literal stays valid JSON.
const seed = JSON.stringify(config).replace(/
(function () {
@@ -446,8 +422,6 @@ function OnCustomConfigMessage(event) {
if (!selectedPluginKey || !selectedCapabilityName)
return;
- // The custom UI persists through the same native command as the JSON editor, so there is one
- // stored config and one code path that writes it.
SendMessage("save_capability_config", {
plugin_key: selectedPluginKey,
capability_name: selectedCapabilityName,
@@ -473,9 +447,9 @@ document.addEventListener("DOMContentLoaded", () => {
if (text)
text.addEventListener("input", ValidateConfigText);
- // The custom capability UI is sandboxed into an opaque origin, so it reaches us only through
- // postMessage. OnCustomConfigMessage matches on the frame's own contentWindow rather than the
- // origin (which is "null" for a sandboxed frame) and ignores anything else on the channel.
+ // The custom UI is sandboxed into an opaque origin, so postMessage is its only channel.
+ // OnCustomConfigMessage matches on the frame's contentWindow, not the origin ("null" when
+ // sandboxed), and ignores anything else.
window.addEventListener("message", OnCustomConfigMessage);
SendMessage("request_capabilities");
diff --git a/resources/web/dialog/PluginsDialog/index.html b/resources/web/dialog/PluginsDialog/index.html
index 99ba76daa4..247c021dde 100644
--- a/resources/web/dialog/PluginsDialog/index.html
+++ b/resources/web/dialog/PluginsDialog/index.html
@@ -153,15 +153,12 @@
autocomplete="off" autocapitalize="off" aria-label="Capability configuration (JSON)">
+ runs in an opaque origin and reaches the host only through the injected
+ window.orca bridge. -->
-
+
diff --git a/resources/web/dialog/PluginsDialog/index.js b/resources/web/dialog/PluginsDialog/index.js
index 0d07757244..408bae8494 100644
--- a/resources/web/dialog/PluginsDialog/index.js
+++ b/resources/web/dialog/PluginsDialog/index.js
@@ -12,22 +12,18 @@ const pluginInstallActions = {
let expandedPluginIds = new Set();
-// why: transient per-search override on top of expandedPluginIds. A search
-// auto-expands rows whose capabilities match, display-only. This lets a
-// triangle click during search collapse/reopen such a row without touching
-// the base (id -> bool).
+// why: transient per-search override (id -> bool) on top of expandedPluginIds. A search auto-expands
+// matching rows, so a triangle click during search must not touch the base expand state.
let searchExpandOverride = new Map();
let selectedPluginId = "";
let contextPluginId = "";
let activeDetailTab = "plugin-info";
let selectedInstallAction = "explore";
-// Config tab: the capability whose config is currently shown, scoped to selectedPluginId. Name and
-// type together address the capability on the native side. Cleared whenever the plugin changes.
+// Config tab: the capability whose config is shown. Name and type together address it natively.
let selectedCapabilityName = "";
let selectedCapabilityType = "";
-// The plugin the capability selection above belongs to. Capability names are only unique within a
-// plugin, so two plugins can each expose e.g. a "main" script capability; without remembering the
-// owner, selecting the second plugin would keep the selection and show the first plugin's config.
+// The plugin that selection belongs to. Capability names are only unique within a plugin, so
+// without the owner, selecting another plugin could keep the selection and show the wrong config.
let configPluginId = "";
let pluginList = null;
@@ -73,15 +69,14 @@ function OnInit() {
document.getElementById("configRestoreBtn")?.addEventListener("click", RestoreCapabilityConfig);
const configText = document.getElementById("configText");
- // why: common.js installs a document-level onkeydown that cancels the default action of every key
- // (returnValue=false) to block webview shortcuts; on the way up it also swallows typing. Stop
- // the editor's keydowns from bubbling to it so the textarea stays editable, leaving the global
- // guard intact. Same treatment as the search field (see plugin-search.js).
+ // why: common.js cancels every key at the document level (returnValue=false) to block webview
+ // shortcuts, which also swallows typing; don't bubble to it, so the textarea stays editable.
+ // Same treatment as the search field (see plugin-search.js).
configText?.addEventListener("keydown", (event) => event.stopPropagation());
configText?.addEventListener("input", ValidateConfigText);
- // The custom capability UI is sandboxed into an opaque origin, so it reaches us only through
- // postMessage. Match on the frame's own contentWindow rather than the origin (which is "null"
- // for a sandboxed frame) and ignore anything else on the channel.
+ // The custom UI is sandboxed into an opaque origin, so postMessage is its only channel.
+ // OnCustomConfigMessage matches on the frame's contentWindow, not the origin ("null" when
+ // sandboxed), and ignores anything else.
window.addEventListener("message", OnCustomConfigMessage);
document.addEventListener("click", (event) => {
@@ -258,8 +253,7 @@ function HandleStudio(value) {
}
}
-// Renders the latest plugin/capability operation result in the footer status bar. The result
-// persists until the next operation replaces it; the native side already localizes the text.
+// Footer status bar for the latest operation result; the native side already localizes the text.
function ShowStatusMessage(message, level) {
const bar = document.getElementById("statusBar");
const text = document.getElementById("statusText");
@@ -333,7 +327,6 @@ function SyncPluginListHeaderGutter() {
}
// why: paint matched-character ranges as without an innerHTML build
-// note: if no ranges -> return the plain text node
function ApplyHighlight(container, text, ranges) {
if (!ranges || !ranges.length) {
container.appendChild(document.createTextNode(text));
@@ -369,7 +362,7 @@ function RenderPlugins() {
}
// why: stable filter over the existing C++ sort order - no scoring, no reorder. The empty query
- // short-circuits (searching=false), leaving every existing render path untouched.
+ // short-circuits, leaving every existing render path untouched.
const searching = typeof PluginSearchActive === "function" && PluginSearchActive();
let shown = 0;
@@ -381,10 +374,8 @@ function RenderPlugins() {
continue;
shown++;
- // why: transient override wins. Otherwise while searching start collapsed and auto-expand only
- // capability matches (the persistent expand state is ignored so unrelated caps don't clutter
- // results); when not searching use the persistent state. The base is never written while
- // searching, so clearing the search restores exactly what the user had.
+ // why: the transient override wins; while searching, auto-expand only capability matches. The
+ // base is never written while searching, so clearing it restores exactly what the user had.
const open = searchExpandOverride.has(pluginKey)
? searchExpandOverride.get(pluginKey)
: (searching ? match.hasCapMatch : expandedPluginIds.has(pluginKey));
@@ -455,7 +446,6 @@ function GetLatestVersion(plugin) {
return String(plugin?.latest_version || plugin?.version || "");
}
-// Primary version shown in the row: the installed version once installed, otherwise the latest available.
function GetDisplayVersion(plugin) {
const installed = GetInstalledVersion(plugin);
if (IsPluginInstalled(plugin) && installed)
@@ -570,7 +560,6 @@ function LabelCell(plugin, isExpanded = false, capabilityCount = 0, nameRanges =
const labelCell = document.createElement("span");
labelCell.className = "label-cell";
- // Add hyperlink
const hasCloudLink = plugin.source === "mine" || plugin.source === "subscribed";
const pluginLabelText = plugin.label || plugin.name || plugin.plugin_id || "";
const canExpand = capabilityCount > 0;
@@ -816,19 +805,12 @@ function RenderDetails() {
RenderConfig(plugin);
}
-// ---------------------------------------------------------------------------
-// Config tab
-//
-// The sidebar lists the selected plugin's configurable capabilities; the right side shows either
-// the host's JSON editor or, when the capability ships one, its own HTML UI in a sandboxed frame.
-// Both edit the same stored config: the page holds no config state of its own, it renders what the
-// native side sends and sends back what the user saves.
-// ---------------------------------------------------------------------------
+// Config tab: the sidebar lists the selected plugin's configurable capabilities; the view shows the
+// host's JSON editor or, when the capability ships one, its own HTML UI in a sandboxed frame. Both
+// edit the same stored config; the page renders what the native side sends.
-// The config sidebar's rows, built natively by PluginConfig::capabilities_payload and shared with
-// PluginsConfigDialog. Only loaded, addressable capabilities appear: the descriptor-only rows the
-// list tab shows for a plugin that is not activated carry no capability name, so there is nothing to
-// configure and nothing to address on the native side.
+// Rows built natively by PluginConfig::capabilities_payload. Only loaded, addressable capabilities
+// appear: the descriptor-only rows of an inactive plugin carry no name, so nothing to address.
function GetConfigurableCapabilities(plugin) {
return Array.isArray(plugin?.config_capabilities) ? plugin.config_capabilities : [];
}
@@ -843,8 +825,7 @@ function RenderConfig(plugin) {
const capabilities = plugin ? GetConfigurableCapabilities(plugin) : [];
const pluginKey = String(plugin?.plugin_key || "");
- // A different plugin than the one the current selection belongs to: drop the selection outright
- // rather than trusting the name to mean the same thing here.
+ // A different plugin: drop the selection rather than trusting the name to mean the same here.
if (pluginKey !== configPluginId) {
configPluginId = pluginKey;
selectedCapabilityName = "";
@@ -853,8 +834,8 @@ function RenderConfig(plugin) {
}
if (!plugin || capabilities.length === 0) {
- // Capabilities are only materialized once the plugin is activated, so an inactive plugin has
- // nothing to configure yet — say that, rather than claiming it has no capabilities.
+ // Capabilities only exist once the plugin is activated: say that, rather than claiming it has
+ // none to configure.
empty.textContent = !plugin
? "Select a plugin to configure its capabilities"
: (IsPluginLoading(plugin)
@@ -874,8 +855,7 @@ function RenderConfig(plugin) {
empty.hidden = true;
layout.hidden = false;
- // Keep the selection across a refresh when the capability is still there; otherwise fall back to
- // the first one, which is also the initial selection for a newly selected plugin.
+ // Keep the selection across a refresh if the capability is still there, else select the first.
const stillPresent = capabilities.some((capability) =>
capability.name === selectedCapabilityName && String(capability.type_key || "") === selectedCapabilityType);
if (!stillPresent) {
@@ -927,8 +907,7 @@ function OnConfigSidebarClick(event) {
selectedCapabilityName = name;
selectedCapabilityType = typeKey;
- // Drop the outgoing capability's view immediately: the native reply is asynchronous and its
- // content must never appear under the newly selected capability.
+ // The native reply is async: clear now so the old config cannot appear under the new selection.
ClearCapabilityConfigView();
RequestCapabilityConfig();
RenderConfig(pluginsById.get(selectedPluginId));
@@ -945,9 +924,8 @@ function RequestCapabilityConfig() {
});
}
-// Empties both editors, so nothing from the previously selected capability can linger while the
-// next one is still in flight. The footer goes with them: until a config has actually loaded there
-// is nothing to save or restore.
+// Empties both editors and the footer, so nothing from the previous capability lingers while the
+// next one is in flight.
function ClearCapabilityConfigView() {
const editor = document.getElementById("configEditor");
const custom = document.getElementById("configCustom");
@@ -972,8 +950,8 @@ function ClearCapabilityConfigView() {
SetConfigValidation("");
}
-// True when a native reply still matches what the user has selected. A reply for a capability the
-// user has already navigated away from is dropped rather than rendered into the current view.
+// Replies are async: one for a capability the user has navigated away from is dropped, never
+// rendered into the current view.
function IsCurrentCapability(payload) {
return String(payload?.plugin_key || "") === selectedPluginId &&
String(payload?.capability_name || "") === selectedCapabilityName;
@@ -997,12 +975,10 @@ function ApplyCapabilityConfig(payload) {
const config = (payload && typeof payload.config === "object" && payload.config !== null) ? payload.config : {};
const html = String(payload?.custom_html || "");
- // Restore is host chrome and applies to either editor; Save and the validation message belong to
- // the JSON editor, since a custom UI saves through its own controls via the bridge.
+ // Restore applies to either editor; Save and validation belong to the JSON editor only.
ShowConfigFooter(!html);
if (html) {
- // A capability with its own UI: hand it the config through the bridge, never the raw file.
if (custom) {
custom.hidden = false;
custom.srcdoc = BuildCustomConfigDocument(html, config);
@@ -1012,8 +988,7 @@ function ApplyCapabilityConfig(payload) {
return;
}
- // Default editor. The native side already reported why a custom UI is unavailable (if it was
- // meant to have one) in payload.error, and we fall back to editing the same config here.
+ // Default editor: any reason a custom UI is unavailable already arrived in payload.error.
if (custom) {
custom.hidden = true;
custom.removeAttribute("srcdoc");
@@ -1025,8 +1000,7 @@ function ApplyCapabilityConfig(payload) {
SetConfigValidation("");
}
-// Reveals the footer for the loaded capability. `withEditorControls` is false for a custom UI,
-// leaving Restore on its own.
+// withEditorControls is false for a custom UI, which saves through its own controls.
function ShowConfigFooter(withEditorControls) {
const footer = document.getElementById("configFooter");
const save = document.getElementById("configSaveBtn");
@@ -1047,8 +1021,7 @@ function SetConfigValidation(message) {
node.textContent = message;
node.classList.toggle("invalid", message !== "");
}
- // Invalid JSON can never be saved: the button is the only way to persist, and it is disabled
- // while the text does not parse. The native side re-validates regardless.
+ // Invalid JSON is never saved: Save is the only way to persist. The native side re-validates.
if (save)
save.disabled = message !== "";
}
@@ -1084,10 +1057,8 @@ function SaveCapabilityConfig() {
});
}
-// Asks the native side to write the capability's default config over whatever is stored. The
-// defaults come from the capability's get_default_config(), never from this page — the host does not
-// know what a given plugin considers default. The native side confirms before discarding anything,
-// and replies with the same "saved" payload, so both editors reload from what was persisted.
+// Writes the capability's own get_default_config() over whatever is stored — the host does not know
+// what a plugin considers default. The native side confirms first, then replies as if it were a save.
function RestoreCapabilityConfig() {
if (!selectedPluginId || !selectedCapabilityName)
return;
@@ -1112,8 +1083,7 @@ function ApplyCapabilityConfigSaved(payload) {
if (payload?.ok !== true)
return;
- // Reload from what was actually persisted, so both editors show the stored state rather than
- // whatever was typed.
+ // Reload from what was persisted, not from what was typed.
const config = (payload && typeof payload.config === "object" && payload.config !== null) ? payload.config : {};
const custom = document.getElementById("configCustom");
const text = document.getElementById("configText");
@@ -1126,13 +1096,11 @@ function ApplyCapabilityConfigSaved(payload) {
SetConfigValidation("");
}
-// The whole host surface a custom config UI gets: read the config it was opened with, save a new
-// one, and be told when a save lands. Everything else about the dialog stays out of reach — the
-// frame is sandboxed into an opaque origin, so this bridge is its only way to talk to the host.
+// The whole host surface a custom config UI gets: read the config, save one, be told when a save
+// lands. The frame is sandboxed into an opaque origin, so this bridge is its only channel.
function BuildCustomConfigDocument(html, config) {
- // The config is inlined into a " would
- // otherwise close the tag early and inject the rest as markup. Escaping "<" keeps the literal
- // valid JSON while making that impossible.
+ // Inlined into a " would close the tag early, so escape "<" — the
+ // literal stays valid JSON.
const seed = JSON.stringify(config).replace(/
(function () {
@@ -1171,8 +1139,6 @@ function OnCustomConfigMessage(event) {
if (!selectedPluginId || !selectedCapabilityName)
return;
- // The custom UI persists through the same native command as the JSON editor, so there is one
- // stored config and one code path that writes it.
SendMessage("save_capability_config", {
plugin_key: selectedPluginId,
capability_name: selectedCapabilityName,
@@ -1203,8 +1169,8 @@ function RenderDescription(plugin) {
node.replaceChildren();
- // Descriptions come only from the plugin's Python header (local/installed plugins). A cloud plugin
- // that is not installed yet has no header, so show a link to view it on OrcaCloud instead.
+ // Descriptions come from the plugin's Python header; a cloud plugin that is not installed yet has
+ // no header, so link to OrcaCloud instead.
const description = String(plugin?.description || "").trim();
if (description && description !== "No description.") {
node.textContent = description;
@@ -1275,8 +1241,8 @@ function SetText(id, text) {
function ApplyDetailUpdateBadge(node, plugin) {
node.className = "version-update-badge";
- // The "update_available" state is represented by the actionable Update button next to the
- // version, so the detail panel only shows the passive badge for the unauthorized warning.
+ // "update_available" is already the actionable Update button, so the badge only warns about
+ // unauthorized.
const updateStatus = plugin ? GetUpdateStatus(plugin) : "normal";
if (updateStatus === "unauthorized") {
node.hidden = false;
@@ -1376,9 +1342,8 @@ function OnPluginListClick(event) {
const pluginKey = String(block.dataset.pluginKey || "");
selectedPluginId = pluginKey;
- // why: during a search the triangle writes to the transient override (read from the on-screen open
- // state), so an auto-expanded row collapses without touching the saved layout. With no search
- // active, toggle the persistent base exactly as before.
+ // why: during a search the triangle writes to the transient override (read from the on-screen
+ // open state), so an auto-expanded row collapses without touching the saved layout.
if (typeof PluginSearchActive === "function" && PluginSearchActive())
searchExpandOverride.set(pluginKey, !block.classList.contains("expanded"));
else if (expandedPluginIds.has(pluginKey))
diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp
index 430d302349..4245a4d56a 100644
--- a/src/libslic3r/PrintConfig.cpp
+++ b/src/libslic3r/PrintConfig.cpp
@@ -828,9 +828,8 @@ void PrintConfigDef::init_common_params()
def->tooltip = L("Select the network agent implementation for printer communication.");
def->mode = comAdvanced;
def->cli = ConfigOptionDef::nocli;
- // Plugin-backed like the pickers, but edited via a dedicated Choice widget rather than a
- // plugin_picker field. plugin_type marks it plugin-backed and names its capability type, so its
- // "plugins" manifest reference is derived generically (see ConfigOptionDef::is_plugin_backed).
+ // Plugin-backed (see ConfigOptionDef::is_plugin_backed), but edited through the Choice widget above
+ // rather than a plugin_picker field.
def->plugin_type = "printer-connection";
def->set_default_value(new ConfigOptionString(""));
diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp
index c0faaaeb83..c7b5a9eae0 100644
--- a/src/slic3r/GUI/Field.cpp
+++ b/src/slic3r/GUI/Field.cpp
@@ -1996,8 +1996,6 @@ void Choice::msw_rescale()
void PluginField::BUILD()
{
- // Wrap the dynamic rows in a single container panel so the field is a proper window-field
- // (getWindow() != null). The panel owns m_main_sizer; all row controls are children of it.
auto* panel = new wxPanel(m_parent, wxID_ANY);
wxGetApp().UpdateDarkUI(panel);
window = panel;
@@ -2005,7 +2003,6 @@ void PluginField::BUILD()
m_main_sizer = new wxBoxSizer(wxVERTICAL);
panel->SetSizer(m_main_sizer);
- // Initialize with default values or empty
if (m_opt.type == coStrings) {
const ConfigOptionStrings* vec = m_opt.get_default_value();
if (vec != nullptr && !vec->values.empty()) {
@@ -2096,13 +2093,11 @@ void PluginField::add_plugin_row(const wxString& value, bool is_last)
const auto button_size = wxSize(def_width_thinner() * m_em_unit, -1);
auto row_sizer = new wxBoxSizer(wxHORIZONTAL);
- // Select button with search icon
ScalableButton* select_btn = new ScalableButton(window, wxID_ANY, "search", wxEmptyString,
button_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER, true, 16);
wxGetApp().UpdateDarkUI(select_btn);
select_btn->SetToolTip(_L("Select plugin"));
- // Display text control
wxTextCtrl* display = new wxTextCtrl(window, wxID_ANY, value,
wxDefaultPosition, wxSize(def_width_wider() * m_em_unit, wxDefaultCoord),
wxTE_READONLY);
@@ -2118,7 +2113,6 @@ void PluginField::add_plugin_row(const wxString& value, bool is_last)
remove_btn->SetToolTip(_L("Remove plugin"));
}
- // Add button (only on last row)
ScalableButton* add_btn = nullptr;
if (is_last && !m_opt.readonly) {
add_btn = new ScalableButton(window, wxID_ANY, "param_add", wxEmptyString,
@@ -2239,7 +2233,6 @@ void PluginField::set_value(const boost::any& value, bool change_event)
{
m_disable_change_event = !change_event;
- // Handle different input types
if (value.empty()) {
m_values.clear();
} else if (value.type() == typeid(std::vector)) {
@@ -2312,9 +2305,7 @@ void PluginField::msw_rescale()
namespace {
-// The stored text as a document, or an empty array when it is absent or unparseable. Used to compare
-// two versions of the value semantically: re-serializing an unchanged document can reorder keys or
-// drop whitespace, and that alone must not be allowed to mark the preset dirty.
+// The stored text as a document, or an empty array when it is absent or unparseable.
nlohmann::json plugin_overrides_as_json(const std::string& text)
{
if (text.empty())
@@ -2326,12 +2317,8 @@ nlohmann::json plugin_overrides_as_json(const std::string& text)
void PluginConfigField::BUILD()
{
- // The button *is* the field's window (the ColourPicker idiom). Wrapping it in a panel would make
- // this a container that OG_CustomCtrl sizes but never lays out, leaving the button unsized — and
- // a zero-height row collapses its whole option group out of the page.
m_button = new ::Button(m_parent, _L("Configure"));
- // ButtonType::Parameter is the style for a button sitting next to parameter boxes: it is what
- // gives the button the same height as the fields above it, which a bare wxButton does not.
+ // ButtonType::Parameter gives the button the same height as the parameter fields above it.
m_button->SetStyle(ButtonStyle::Regular, ButtonType::Parameter);
wxSize size(def_width_wider() * m_em_unit, m_button->GetMinSize().GetHeight());
@@ -2360,8 +2347,6 @@ void PluginConfigField::update_button_label()
const nlohmann::json entries = plugin_overrides_as_json(m_json);
const size_t count = entries.is_array() ? entries.size() : 0;
- // The count is the whole state this row can show: which capabilities they belong to, and what
- // they hold, is the dialog's job.
m_button->SetLabel(count == 0 ? _L("Configure")
: wxString::Format(_L("Configure (%d)"), int(count)));
}
@@ -2379,16 +2364,14 @@ void PluginConfigField::open_dialog()
edited = dlg.overrides_json();
}
- // Only a semantic change is a change: reopening the dialog and closing it must not dirty the
- // preset just because the document round-tripped through the serializer.
+ // Compare semantically: a round-trip through the serializer can reorder keys or drop whitespace,
+ // and that alone must not dirty the preset.
if (plugin_overrides_as_json(m_json) == plugin_overrides_as_json(edited))
return;
m_json = edited;
m_value = m_json;
update_button_label();
- // The one call that makes this behave like every other setting: it drives change_opt_value into
- // the preset config, Tab::update_dirty(), and the revert arrow.
on_change_field();
}
diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp
index 448b492475..4e9c65da5d 100644
--- a/src/slic3r/GUI/Field.hpp
+++ b/src/slic3r/GUI/Field.hpp
@@ -32,8 +32,7 @@
#define wxMSW false
#endif
-// Orca's styled button (Widgets/Button.hpp), used by PluginConfigField. Declared at global scope,
-// which is where the widget lives.
+// Orca's styled button (Widgets/Button.hpp), used by PluginConfigField. It lives at global scope.
class Button;
namespace Slic3r { namespace GUI {
@@ -487,9 +486,8 @@ public:
void enable() override;
void disable() override;
- // Window-field: the dynamic rows live inside a single container panel (assigned to the base
- // `window` in BUILD), so the field exposes one window instead of a bare sizer. This lets focus/
- // scroll, full-width sizing and teardown operate on the whole field.
+ // The rows live in one container panel (the base `window`), so the field exposes a window instead
+ // of a bare sizer and focus, sizing and teardown apply to the whole field.
wxWindow* getWindow() override { return window; }
void msw_rescale() override;
@@ -521,11 +519,10 @@ private:
std::function m_selector;
};
-// A settings row whose value is a raw JSON document that nobody should type by hand: the row shows a
-// button, the button opens PluginsConfigDialog, and the document it hands back becomes the field's
-// value. Routing the edit through the ordinary Field value/on_change_field path is what earns the row
-// the same dirty state and revert arrow as every other setting — the dialog itself never touches the
-// preset.
+// A settings row whose value is a raw JSON document nobody types by hand: the button opens
+// PluginsConfigDialog and the document it hands back becomes the field's value. The edit goes through
+// the ordinary Field value/on_change_field path, so the row gets the same dirty state and revert arrow
+// as any other setting — the dialog never touches the preset.
class PluginConfigField : public Field {
using Field::Field;
public:
@@ -535,9 +532,8 @@ public:
void BUILD() override;
- // Which preset's capabilities the dialog lists. Supplied by the option group, which already
- // carries its Tab's type; an int for the same reason OptionsGroup::m_config_type is one — it
- // keeps Preset.hpp out of this header.
+ // Which preset's capabilities the dialog lists; set by the option group. An int for the same
+ // reason OptionsGroup::m_config_type is one: it keeps Preset.hpp out of this header.
void set_preset_type(int type) { m_preset_type = type; }
void set_value(const boost::any& value, bool change_event = false) override;
@@ -546,8 +542,8 @@ public:
void enable() override;
void disable() override;
- // Window-field: the button is the whole field, so it is the window the option group sizes and
- // positions (the ColourPicker idiom). A container panel would be sized but never laid out.
+ // The button is the whole field, so it is the window the option group sizes and positions (the
+ // ColourPicker idiom). A container panel would be sized but never laid out, collapsing the row.
wxWindow* getWindow() override { return window; }
void msw_rescale() override;
diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp
index d8db505976..8e2503fd68 100644
--- a/src/slic3r/GUI/OptionsGroup.cpp
+++ b/src/slic3r/GUI/OptionsGroup.cpp
@@ -127,9 +127,8 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co
});
}
- // The dialog behind the button edits one preset's overrides, so it has to know which preset. The
- // group already carries its Tab's type, and fields are built lazily on activate() — too late for
- // the Tab to reach in and set it afterwards.
+ // The dialog behind the button edits one preset's overrides, so it has to know which preset. Set
+ // here because fields are built lazily on activate() — too late for the Tab to reach in afterwards.
if (auto plugin_config_field = dynamic_cast(field.get()))
if (auto config_group = dynamic_cast(this))
plugin_config_field->set_preset_type(config_group->config_type());
@@ -717,9 +716,8 @@ std::string OptionsGroup::pick_plugin(const ConfigOptionDef& opt)
if (selection.plugin_key.empty() || selection.name.empty())
return {};
- // Validate that the selected capability's plugin is resolvable, but only the bare capability
- // name is stored in the option; the full "name;uuid;capability" reference is derived lazily
- // when the preset is serialized (see ConfigBase::save_plugin_collection).
+ // Only the bare capability name is stored; the full "name;uuid;capability" reference is derived when
+ // the preset is serialized (see ConfigBase::save_plugin_collection). Resolve here to reject bad picks.
Slic3r::PluginDescriptor descriptor;
if (!manager.get_catalog().try_get_plugin_descriptor(selection.plugin_key, descriptor) || descriptor.name.empty())
return {};
diff --git a/src/slic3r/GUI/PluginsConfigDialog.cpp b/src/slic3r/GUI/PluginsConfigDialog.cpp
index c3227e2ecb..6172be5e5c 100644
--- a/src/slic3r/GUI/PluginsConfigDialog.cpp
+++ b/src/slic3r/GUI/PluginsConfigDialog.cpp
@@ -33,8 +33,8 @@ PluginsConfigDialog::PluginsConfigDialog(wxWindow* parent, Preset::Type type, co
: WebViewHostDialog(parent, wxID_ANY, preset_type_title(type))
, m_type(type)
{
- // Unparseable text is kept, not replaced: parse_plugin_overrides leaves the document empty and
- // every row goes read-only, so a preset we cannot understand is never silently overwritten.
+ // On failure the document stays empty and every row goes read-only (see m_parse_error), so a preset
+ // we cannot understand is never silently overwritten.
if (!parse_plugin_overrides(overrides_json, m_overrides, m_parse_error))
BOOST_LOG_TRIVIAL(error) << "Plugins Config dialog: " << m_parse_error;
@@ -103,9 +103,8 @@ void PluginsConfigDialog::on_script_message(const nlohmann::json& payload)
send_capability_config(id);
show_status(_L("Configuration updated. Save the preset to persist it."), "success");
} else if (command == "remove_preset_override") {
- // "Restore defaults" for a preset means holding no override at all: the capability goes back
- // to the global configuration, which is where an untouched preset takes its values from. The
- // plugin's own get_default_config() is the global config's default, not the preset's.
+ // "Restore defaults" for a preset means holding no override at all: the capability falls back
+ // to the global configuration, not to the plugin's own get_default_config().
if (!m_parse_error.empty()) {
send_save_error(id, m_parse_error);
return;
diff --git a/src/slic3r/GUI/PluginsConfigDialog.hpp b/src/slic3r/GUI/PluginsConfigDialog.hpp
index 3c6e1c7448..cdc7dcb412 100644
--- a/src/slic3r/GUI/PluginsConfigDialog.hpp
+++ b/src/slic3r/GUI/PluginsConfigDialog.hpp
@@ -8,16 +8,12 @@
namespace Slic3r { namespace GUI {
-// The config half of the Plugins dialog, scoped to one preset instead of one plugin: it lists the
-// capabilities the edited preset of `m_type` actually uses (see capabilities_in_use) and edits each
-// one's stored config, falling back to the global config where the preset has no override.
+// Lists the plugin capabilities the edited preset of `m_type` uses (see capabilities_in_use) and edits
+// each one's config, falling back to the global config where the preset has no override.
//
-// It is a pure editor over a JSON document: it never writes to the preset and never writes to the
-// base config file. The caller seeds it with the preset's raw override text and reads the edited
-// text back from overrides_json(). PluginConfigField, which owns the value, then feeds that through
-// the normal field/dirty pipeline — which is what makes the revert arrow behave like any other
-// setting. The capability list and config payloads are shared with PluginsDialog's Config tab
-// through PluginConfig's statics.
+// A pure editor over a JSON document: it never writes to the preset and never writes to the base config
+// file. The caller seeds it with the preset's raw override text and reads the edited text back from
+// overrides_json(); PluginConfigField owns the value and feeds it through the normal field/dirty pipeline.
class PluginsConfigDialog : public WebViewHostDialog
{
public:
diff --git a/src/slic3r/GUI/PluginsDialog.cpp b/src/slic3r/GUI/PluginsDialog.cpp
index f484411d41..dec63aefbc 100644
--- a/src/slic3r/GUI/PluginsDialog.cpp
+++ b/src/slic3r/GUI/PluginsDialog.cpp
@@ -66,10 +66,8 @@ struct PluginCapabilityView
bool enabled = false;
bool can_toggle = false;
bool can_run = false;
- // Whether the capability supplies its own config UI, cached on the loaded capability at load
- // time. False for the descriptor-only rows shown for a plugin that is not loaded: its
- // capabilities have not been materialized yet, so there is nothing to configure until it is
- // activated. Every real capability is configurable; this only picks the editor.
+ // Whether the capability supplies its own config UI; every capability is configurable, this only
+ // picks the editor. False for descriptor-only rows: an unloaded plugin has no capabilities yet.
bool has_config_ui = false;
};
@@ -83,7 +81,6 @@ struct PluginChangelogView
// View-model for one plugin row in the dialog
struct PluginDialogItem
{
- // Identity and display text
std::string plugin_key;
std::string plugin_id;
std::string display_name;
@@ -100,7 +97,6 @@ struct PluginDialogItem
std::vector type_labels;
std::vector changelog;
- // Derived UI state
PluginSource source = PluginSource::Local;
PluginStatus status = PluginStatus::Inactive;
PluginUpdateStatus update_status = PluginUpdateStatus::Normal;
@@ -109,7 +105,6 @@ struct PluginDialogItem
bool is_loaded = false;
bool loading = false;
- // Installation and capability flags
bool is_cloud_plugin = false;
bool has_local_package = false;
bool unauthorized = false;
@@ -119,7 +114,6 @@ struct PluginDialogItem
// Runtime capabilities in registration order, or descriptor-only type rows when unloaded.
std::vector capabilities;
- // Row-level actions
PluginAvailableActions available_actions;
};
@@ -154,9 +148,8 @@ PluginDescriptor as_cloud_only_descriptor(PluginDescriptor descriptor)
return descriptor;
}
-// rescan_plugins() clears the whole catalog and rediscovers only local packages. When
-// callers do not need fresh cloud data, reuse the current cloud rows after the local
-// rescan so cloud-only rows and cloud-derived UI state do not disappear.
+// rescan_plugins() clears the whole catalog and rediscovers only local packages, so when no fresh cloud
+// data is needed, restore the current cloud rows afterwards or cloud-only rows would disappear.
void refresh_plugin_catalog_blocking(bool fetch_cloud)
{
PluginManager& manager = PluginManager::instance();
@@ -220,10 +213,9 @@ nlohmann::json build_plugin_payload_item(const PluginDialogItem& dialog_item)
}
payload_item["capabilities"] = std::move(caps);
- // The Config tab's sidebar, built by the shared builder so it stays identical to
- // PluginsConfigDialog's. Deliberately not the `capabilities` array above: that one is the list
- // tab's, carrying enable/run state and descriptor-only rows for plugins that are not activated
- // yet — neither of which the config view has any use for.
+ // The Config tab's sidebar, built by the shared builder so it stays identical to PluginsConfigDialog's.
+ // Not the `capabilities` array above: that is the list tab's, with enable/run state and descriptor-only
+ // rows the config view has no use for.
std::vector config_ids;
for (const PluginCapabilityView& capability : dialog_item.capabilities)
if (!capability.name.empty())
@@ -329,20 +321,17 @@ PluginDialogItem build_plugin_dialog_item(const PluginDescriptor& descriptor)
(descriptor.description.empty() ? "No description." : descriptor.description);
item.author = descriptor.author;
item.version = descriptor.version;
- // Installed version: the cloud merge overwrites `version` with the latest cloud version, so prefer
- // the preserved local version, falling back to `version` for local-only / pre-merge descriptors.
+ // The cloud merge overwrites `version` with the latest cloud version, so prefer the preserved local
+ // one, falling back to `version` for local-only / pre-merge descriptors.
item.installed_version = descriptor.has_local_package() ?
(descriptor.installed_version.empty() ? descriptor.version : descriptor.installed_version) :
std::string{};
item.latest_version = descriptor.latest_available_version();
- // why: sort by the same version the row displays (GetDisplayVersion in index.js) - installed when
- // installed, otherwise latest - so the Version sort matches what the user sees.
item.sort_version = item.installed_version.empty() ? item.latest_version : item.installed_version;
item.type_label = descriptor.type_label();
item.type_key = plugin_capability_type_to_string(descriptor.primary_capability_type());
- // "types" is the display-only compatibility list. Cloud plugins show the raw labels the
- // service returned (which may not map to real capability types); local plugins derive them
- // from the capabilities actually discovered/loaded.
+ // "types" is display-only: cloud plugins show the raw labels the service returned (which may not map
+ // to real capability types); local plugins derive them from the capabilities actually discovered.
if (descriptor.is_cloud_plugin() && !descriptor.display_types.empty()) {
for (const std::string& label : descriptor.display_types)
if (std::find(item.type_labels.begin(), item.type_labels.end(), label) == item.type_labels.end())
@@ -506,10 +495,8 @@ void PluginsDialog::on_script_message(const nlohmann::json& payload)
const std::string command = payload.value("command", "");
if (command == "request_plugins") {
- // The web page finished loading and is asking for the current catalog. Plugin
- // discovery already runs at startup and on login, so the shared catalog is up to
- // date by the time the dialog opens. Just render it here - no blocking fetch on
- // open. The Refresh button (refresh_plugins) is what triggers a fresh discovery.
+ // Discovery already runs at startup and on login, so the shared catalog is up to date by the
+ // time the dialog opens: render it without a blocking fetch. Refresh is what rediscovers.
send_plugins();
} else if (command == "refresh_plugins") {
refresh_plugins();
@@ -588,7 +575,6 @@ nlohmann::json PluginsDialog::build_plugins_payload() const
for (const PluginDescriptor& row : invalid)
items.push_back(build_plugin_dialog_item(row));
- // In-place sort
sort_plugin_items_for_dialog(items, m_plugin_sort_key, m_plugin_sort_order);
for (const PluginDialogItem& item : items)
@@ -635,9 +621,7 @@ void PluginsDialog::toggle_plugin(const std::string& plugin_key, bool enabled)
PluginDescriptor row_data;
if (!get_descriptor(plugin_key, row_data)) {
- // The row no longer maps to a catalog entry (the catalog changed under the UI).
- // Toggling is a local action, so just re-render from the current catalog; a cloud
- // fetch (or clearing rescan) adds nothing here.
+ // The catalog changed under the UI. Toggling is local, so just re-render from it.
send_plugins();
return;
}
@@ -663,7 +647,6 @@ void PluginsDialog::toggle_plugin(const std::string& plugin_key, bool enabled)
return;
}
- // Check for capabilities that are currently in use
auto loaded_capabilities = manager.get_loader().get_loaded_plugin_capabilities(plugin_key);
if (!available_actions.can_toggle) {
@@ -684,8 +667,7 @@ void PluginsDialog::toggle_plugin(const std::string& plugin_key, bool enabled)
}
BOOST_LOG_TRIVIAL(info) << "Cloud plugin installed locally from Plugins dialog: " << plugin_key;
- // download_and_install_cloud_plugin updates the descriptor with the installed
- // local package state, so no rescan or cloud fetch is needed before loading.
+ // download_and_install_cloud_plugin already updated the descriptor, so no rescan is needed here.
if (!get_descriptor(plugin_key, row_data)) {
send_plugins();
return;
@@ -731,9 +713,7 @@ void PluginsDialog::toggle_plugin_capability(const std::string& plugin_key, Plug
PluginDescriptor row_data;
if (!get_descriptor(plugin_key, row_data)) {
- // The row no longer maps to a catalog entry (the catalog changed under the UI).
- // Toggling is a local action, so just re-render from the current catalog; a cloud
- // fetch (or clearing rescan) adds nothing here.
+ // The catalog changed under the UI. Toggling is local, so just re-render from it.
send_plugins();
return;
}
@@ -950,9 +930,8 @@ void PluginsDialog::restore_capability_config(const std::string& plugin_key,
PluginCapabilityType type,
const std::string& capability_name)
{
- // Discards whatever the user had stored, so confirm first — same as the other destructive
- // actions in this dialog. The confirmation stays here rather than in PluginConfig: it needs a
- // parent window, and it is the dialog's business to ask.
+ // Destructive, so confirm first. The confirmation stays here rather than in PluginConfig: it needs
+ // a parent window.
const int rc = wxMessageBox(wxString::Format(_L("Restore the default configuration for \"%s\"?\n\n"
"This discards the settings currently saved for this capability."),
from_u8(capability_name)),
@@ -1013,8 +992,8 @@ void PluginsDialog::run_script_plugin(const std::string& plugin_key, const std::
send_plugins();
- // The row now shows an "Error" status and the Diagnostics tab holds the full text, so surface
- // the outcome in the footer status bar instead of a modal box (prefer the friendlier override).
+ // The row already shows "Error" and the Diagnostics tab holds the full text, so report in the
+ // footer status bar instead of a modal box.
const wxString message = status_message.empty() ? from_u8(normalized_error) : status_message;
show_status(message, "error");
};
@@ -1063,13 +1042,11 @@ void PluginsDialog::run_script_plugin(const std::string& plugin_key, const std::
std::string error;
ExecutionResult result;
- // Script plugins run on the main/UI thread (not a worker). They hold live, non-owning
- // ModelObject*/ModelVolume*/ModelInstance* aliases into host data and can mint ObjectIDs,
- // which libslic3r requires on the main thread (ObjectID.hpp's non-atomic s_last_id). Running
- // here makes those reads/instantiations legal and means nothing mutates the model underneath
- // a run. The trade-off is that a slow execute() freezes the UI, so the contract is to keep
- // execute() quick and offload heavy work to the plugin's own threading.Thread. orca.host.ui
- // calls already no-op their main-thread marshaling here.
+ // Script plugins run on the main/UI thread, not a worker: they hold live, non-owning
+ // ModelObject*/ModelVolume*/ModelInstance* aliases into host data and can mint ObjectIDs, which
+ // libslic3r requires on the main thread (ObjectID.hpp's non-atomic s_last_id). The trade-off is that
+ // a slow execute() freezes the UI, so plugins must keep execute() quick and offload heavy work to
+ // their own threading.Thread.
{
wxBusyCursor busy;
try {
@@ -1098,7 +1075,6 @@ void PluginsDialog::run_script_plugin(const std::string& plugin_key, const std::
if (failed) {
plugin.reset();
cap.reset();
- // complete_with_error normalizes an empty message to "Script plugin failed." and reports via the status bar.
complete_with_error(result.message, wxString());
return;
}
@@ -1122,9 +1098,8 @@ void PluginsDialog::update_plugin(const std::string& plugin_key)
PluginDescriptor descriptor;
const wxString name = get_descriptor(plugin_key, descriptor) ? from_u8(descriptor.name) : from_u8(plugin_key);
- // update_cloud_plugin unloads the old plugin, deletes its local package, then downloads and
- // reinstalls the latest version. Each of those steps already runs off the main thread in the
- // delete/install paths, so run the whole operation on the worker and pump a progress dialog.
+ // update_cloud_plugin unloads the old plugin, deletes its local package and reinstalls the latest
+ // version; all of that is off-main-thread work, so run it on the worker behind a progress dialog.
std::string error;
bool updated = false;
try {
@@ -1144,8 +1119,7 @@ void PluginsDialog::update_plugin(const std::string& plugin_key)
return;
}
- // update_cloud_plugin installs the package and updates the in-memory descriptor
- // (installed=true, update_available=false) on success.
+ // update_cloud_plugin already updated the in-memory descriptor, so a UI refresh is enough here.
send_plugins();
show_status(wxString::Format(_L("Updated \"%s\"."), name), "success");
}
@@ -1332,10 +1306,8 @@ void PluginsDialog::delete_mine_local_and_cloud_plugin(const std::string& plugin
return;
}
- // delete_mine_local_and_cloud_plugin already updated the in-memory catalog
- // (finalize_cloud_plugin_removal removes the row and, when a local package existed,
- // re-syncs the cloud list itself), so a UI refresh is sufficient here - an extra
- // clearing rescan + cloud fetch would be redundant.
+ // delete_mine_local_and_cloud_plugin already updated the in-memory catalog (see
+ // finalize_cloud_plugin_removal), so a UI refresh is sufficient here.
send_plugins();
show_status(wxString::Format(_L("Deleted \"%s\"."), plugin_name), "success");
}
diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp
index 0ba9867f5d..4d0d05d8a8 100644
--- a/src/slic3r/GUI/Tab.cpp
+++ b/src/slic3r/GUI/Tab.cpp
@@ -1790,9 +1790,8 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
return;
}
- // Keep this preset's "plugins" manifest in sync when a plugin picker changes, so the edited preset
- // always carries resolved "name;uuid;capability" references that full_config() and save_to_json()
- // then pass downstream as-is -- no separate rebuild anywhere else.
+ // 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.
if (const ConfigOptionDef* opt_def = m_config->def()->get(opt_key);
opt_def && opt_def->is_plugin_backed())
m_config->update_plugin_manifest();
@@ -3086,10 +3085,8 @@ void TabPrint::build()
option.opt.full_width = true;
optgroup->append_single_option_line(option, "others_settings_plugin_picker");
- // Its own group rather than the one above: that one hides its labels, and this row needs its
- // label — and so the revert arrow beside it — to show. No label-width override either: a 0
- // there means "no label column", which is what the neighbouring full-width groups want and
- // what would collapse this one.
+ // 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".
optgroup = page->new_optgroup(L("Plugin Preferences"), L"param_gcode");
optgroup->append_single_option_line("plugin_preference_overrides");
diff --git a/src/slic3r/plugin/PluginConfig.cpp b/src/slic3r/plugin/PluginConfig.cpp
index f36d038b39..3f262591da 100644
--- a/src/slic3r/plugin/PluginConfig.cpp
+++ b/src/slic3r/plugin/PluginConfig.cpp
@@ -20,9 +20,8 @@ namespace Slic3r {
namespace {
-// The version of the plugin package currently running. PluginDescriptor::version is
-// overwritten with the latest cloud version when a cloud merge happens, so it can name a
-// version that is not the one on disk; installed_version is what actually loaded.
+// PluginDescriptor::version is overwritten with the latest cloud version on a cloud merge, so it can
+// name a version that is not the one on disk; installed_version is what actually loaded.
std::string running_plugin_version(const std::string& plugin_key)
{
PluginDescriptor descriptor;
@@ -31,10 +30,9 @@ std::string running_plugin_version(const std::string& plugin_key)
return descriptor.installed_version.empty() ? descriptor.version : descriptor.installed_version;
}
-// The identity a capability is allowed to address: its own. PluginLoader stamps both halves
-// onto the instance when it materializes the capability, so the caller never supplies them
-// and cannot name another capability's entry. Empty means the instance was never materialized
-// (so it has no config to address) and we refuse rather than read or clobber a wrong entry.
+// PluginLoader stamps both halves onto the instance when it materializes the capability, so the
+// caller never supplies them and cannot name another capability's entry. Empty means the instance
+// was never materialized: refuse rather than read or clobber a wrong entry.
std::pair capability_identity(const PluginCapabilityInterface& capability, const char* api_name)
{
std::pair id{capability.audit_plugin_key(), capability.audit_capability_name()};
@@ -44,11 +42,11 @@ std::pair capability_identity(const PluginCapabilityIn
return id;
}
-// The identity above, completed with the type, which decides which preset may override the
-// capability (see preset_type_for_capability). Taken from the instance rather than from the loader's
-// registry: a capability calling get_config() from on_load() is not registered yet, and it must
-// still see its preset's config. get_type() is the plugin's own method, so a raising one costs it
-// only the preset layer — Unknown names no preset, and the base config answers as it always did.
+// The identity above plus the type, which decides which preset may override the capability (see
+// preset_type_for_capability). Taken from the instance, not the loader's registry: a capability
+// calling get_config() from on_load() is not registered yet and must still see its preset's config.
+// A raising get_type() costs it only the preset layer — Unknown names no preset, so the base config
+// answers.
PluginCapabilityIdentifier capability_full_identity(const PluginCapabilityInterface& capability, const char* api_name)
{
const auto [plugin_key, capability_name] = capability_identity(capability, api_name);
@@ -113,8 +111,8 @@ bool PluginConfig::save()
return false;
}
- // Write to a PID-suffixed file and rename it into place, so a crash mid-write cannot
- // truncate an existing config. Same approach as AppConfig::save().
+ // Write to a PID-suffixed file and rename it into place, so a crash mid-write cannot truncate an
+ // existing config. Same approach as AppConfig::save().
const std::string path_pid = (boost::format("%1%.%2%") % path % get_current_pid()).str();
boost::nowide::ofstream file;
@@ -198,18 +196,16 @@ bool PluginConfig::dirty() const
nlohmann::json capability_get_config(const PluginCapabilityInterface& capability)
{
- // The active preset's override, when it has one, is the config this run must use: it is what the
- // user attached to the preset being sliced, and config.json is the fallback. The resolution is
- // shared with the dialogs, so a capability reads back exactly the config its UI showed as
- // effective. Stored in neither layer: an empty object, so a plugin can index it unconditionally.
+ // Shares its resolution with the dialogs, so a capability reads back exactly the config its UI
+ // showed as effective. Stored in neither layer: an empty object, so a plugin can index it
+ // unconditionally.
return active_capability_config(capability_full_identity(capability, "get_config")).config;
}
std::string capability_get_config_version(const PluginCapabilityInterface& capability)
{
- // The version that wrote the config get_config() just handed out, whichever layer that was: the
- // two must come from the same layer, or a plugin would migrate one layer's config by another's
- // version stamp.
+ // Must resolve through the same layer as get_config(), or a plugin would migrate one layer's
+ // config by another's version stamp.
return active_capability_config(capability_full_identity(capability, "get_config_version")).stored_plugin_version;
}
@@ -226,8 +222,7 @@ nlohmann::json PluginConfig::capabilities_payload(const std::vectorhas_config_ui) {
- // Plugin-authored HTML. A raising or empty get_config_ui() costs the capability only its
- // custom UI: we report the failure and let the page fall back to the default JSON editor,
- // which edits the very same stored config.
+ // A raising or empty get_config_ui() costs the capability only its custom UI: report the
+ // failure and let the page fall back to the default JSON editor over the same stored config.
std::string html;
std::string error;
{
@@ -324,8 +317,8 @@ nlohmann::json PluginConfig::save_config_response(const PluginCapabilityIdentifi
nlohmann::json parsed = config;
if (config.is_string()) {
- // The page validates as the user types, but it is not the authority: re-parse here so a
- // malformed document is rejected before it can reach config.json.
+ // The page validates as the user types, but it is not the authority: re-parse so a malformed
+ // document is rejected before it can reach config.json.
parsed = nlohmann::json::parse(config.get(), nullptr, /* allow_exceptions */ false);
if (parsed.is_discarded()) {
response["error"] = GUI::into_u8(_L("The configuration is not valid JSON. Your changes were not saved."));
@@ -342,15 +335,14 @@ nlohmann::json PluginConfig::save_config_response(const PluginCapabilityIdentifi
BOOST_LOG_TRIVIAL(info) << "Saved plugin capability config. plugin_key=" << id.plugin_key << " capability_name=" << id.name;
- // Echo the persisted value back so the editor reloads from what is actually stored rather than
- // from what the user typed.
+ // Echo back what was persisted, not what the user typed, so the editor reloads from the store.
response["ok"] = true;
response["config"] = PluginManager::instance().get_config().get_config(id.plugin_key, id.name).config;
return response;
}
-// The host does not invent the default: a capability that does not override get_default_config()
-// restores an empty config, which is exactly right for one that applies its own defaults on read.
+// The host never invents the default: a capability that does not override get_default_config()
+// restores an empty config, which is right for one that applies its own defaults on read.
nlohmann::json PluginConfig::restore_config_response(const PluginCapabilityIdentifier& id)
{
nlohmann::json response;
@@ -383,8 +375,8 @@ nlohmann::json PluginConfig::restore_config_response(const PluginCapabilityIdent
}
}
- // A raising hook leaves the stored config exactly as it was: better to restore nothing than to
- // wipe the user's settings on the strength of a broken plugin.
+ // A raising hook leaves the stored config as it was: better to restore nothing than to wipe the
+ // user's settings on the strength of a broken plugin.
if (!error.empty()) {
BOOST_LOG_TRIVIAL(error) << "Plugin capability get_default_config() failed. plugin_key=" << id.plugin_key
<< " capability_name=" << id.name << " error=" << error;
@@ -404,7 +396,6 @@ nlohmann::json PluginConfig::restore_config_response(const PluginCapabilityIdent
BOOST_LOG_TRIVIAL(info) << "Restored default plugin capability config. plugin_key=" << id.plugin_key
<< " capability_name=" << id.name;
- // Reuses the saved reply, so both editors reload from what was actually persisted.
response["ok"] = true;
response["config"] = PluginManager::instance().get_config().get_config(id.plugin_key, id.name).config;
return response;
diff --git a/src/slic3r/plugin/PluginConfig.hpp b/src/slic3r/plugin/PluginConfig.hpp
index 5da2b97384..a672ec2f6e 100644
--- a/src/slic3r/plugin/PluginConfig.hpp
+++ b/src/slic3r/plugin/PluginConfig.hpp
@@ -32,32 +32,8 @@ Example config.json shape
"plugin_key": "some_name",
"capability": "capability_name",
"plugin_version": "1.0.0",
- "cap_config": {
- "some": "plugin",
- "capability": "specific",
- "stuff": "here"
- }
- },
- {
- "plugin_key": "some_name",
- "capability": "capability_name",
- "plugin_version": "1.0.0",
- "cap_config": {
- "some": "plugin",
- "capability": "specific",
- "stuff": "here"
- }
- },
- {
- "plugin_key": "some_name",
- "capability": "capability_name",
- "plugin_version": "1.0.0",
- "cap_config": {
- "some": "plugin",
- "capability": "specific",
- "stuff": "here"
- }
- },
+ "cap_config": { "plugin": "specific", "stuff": "here" }
+ }
]
}
*/
@@ -73,14 +49,12 @@ struct BaseConfig {
bool empty() const { return plugin_key.empty() || capability_name.empty(); }
};
-// Consolidated store for every plugin capability's configuration, persisted as a single
-// config.json alongside the installed plugins. The shape of `cap_config` belongs to the
-// plugin; this class only round-trips it.
+// Store for every plugin capability's configuration, persisted as a single config.json alongside the
+// installed plugins. The shape of `cap_config` belongs to the plugin; this class only round-trips it.
//
-// A capability is identified by (plugin_key, capability_name). `plugin_version` is metadata
-// recording which version last wrote the entry, letting an upgraded plugin spot a stale
-// config and migrate it. Version is deliberately not part of the identity, so upgrading a
-// plugin does not silently reset the user's settings.
+// A capability is identified by (plugin_key, capability_name). `plugin_version` records which version
+// last wrote the entry, so an upgraded plugin can spot a stale config and migrate it. It is
+// deliberately not part of the identity: upgrading a plugin must not reset the user's settings.
//
// Plugin code runs on worker threads, so every entry point is mutex-guarded.
class PluginConfig
@@ -88,51 +62,42 @@ class PluginConfig
public:
static const std::string plugin_config_file() { return (boost::filesystem::path(get_orca_plugins_dir()) / PLUGIN_CONFIG_DIR).string(); }
- // Replaces the in-memory store with what is on disk. A missing or malformed file leaves
- // the store empty rather than throwing: a bad plugin config must not block startup.
+ // A missing or malformed file leaves the store empty rather than throwing: a bad plugin config
+ // must not block startup.
void load();
- // Rewrites config.json atomically. Clears the dirty flag only once the file is in place.
- // False means the config on disk is unchanged.
+ // Rewrites config.json atomically. False means the config on disk is unchanged.
bool save();
void save_config(const std::string& plugin_key, const std::string& capability_name, const std::string& version, const nlohmann::json& config);
void save_config(const BaseConfig& config);
- // Replaces one capability's cap_config and writes config.json straight away, stamping the
- // entry with the plugin version currently running. Every other entry is round-tripped
- // untouched, so saving one capability cannot disturb another's config.
- // The single mutation entry point for both the Plugins dialog and the Python binding.
+ // Replaces one capability's cap_config and writes config.json straight away, stamping the entry
+ // with the plugin version currently running. Every other entry is round-tripped untouched, so
+ // saving one capability cannot disturb another's config.
bool store_capability_config(const std::string& plugin_key, const std::string& capability_name, const nlohmann::json& config);
bool erase_capability_config(const std::string& plugin_key, const std::string& capability_name);
- // Returns a default-constructed BaseConfig (see BaseConfig::empty) when the capability has
- // no stored config.
+ // A default-constructed BaseConfig (see BaseConfig::empty) when there is no stored config.
BaseConfig get_config(const std::string& plugin_key, const std::string& capability_name) const;
bool has_config(const std::string& plugin_key, const std::string& capability_name) const;
bool dirty() const;
// ---- Webview-facing helpers, shared by PluginsDialog's Config tab and PluginsConfigDialog ----
- //
- // These build the payloads both dialogs' config views speak, so the two pages stay in step and
- // neither dialog owns the config protocol. They are static because a capability's config is
- // addressed globally by (plugin_key, capability_name) through PluginManager's store, not through
- // any one PluginConfig instance.
- //
- // The caller owns the UI: it confirms destructive restores and shows status toasts. These only
- // touch the store, the loaded capability, and the payload.
+ // Static because a capability's config is addressed globally by (plugin_key, capability_name)
+ // through PluginManager's store, not through any one PluginConfig instance. The caller owns the
+ // UI: it confirms destructive restores and shows status toasts.
- // The config sidebar's rows: one entry per capability, in the order given. Capabilities that are
- // no longer loaded are skipped — the sidebar only offers what can actually be configured.
+ // The config sidebar's rows, in the order given. Capabilities that are no longer loaded are
+ // skipped — the sidebar only offers what can actually be configured.
static nlohmann::json capabilities_payload(const std::vector& caps);
// One capability's stored config, plus its custom HTML UI when it provides one.
static nlohmann::json get_config_response(const PluginCapabilityIdentifier& id);
- // Persists one capability's config. `config` is either text straight from the default editor
- // (re-parsed here, so malformed JSON can never reach config.json) or a structured value from a
- // custom UI.
+ // Persists one capability's config. `config` is either text from the default editor (re-parsed
+ // here, so malformed JSON can never reach config.json) or a structured value from a custom UI.
static nlohmann::json save_config_response(const PluginCapabilityIdentifier& id, const nlohmann::json& config);
// Overwrites one capability's stored config with its get_default_config(). The caller must have
@@ -145,23 +110,21 @@ private:
bool m_dirty = false;
};
-// Host implementations behind the capability-level Python config API (bound onto every
-// capability class in PythonPluginBridge). The capability addresses only itself: the
-// (plugin_key, capability_name) pair is read off the instance the call arrived on, never
-// passed in from Python, so a capability cannot reach another capability's config.
-// Throw std::runtime_error (surfacing to Python as RuntimeError) on an unmaterialized instance.
+// Host implementations behind the capability-level Python config API (bound onto every capability
+// class in PythonPluginBridge). The capability addresses only itself: the (plugin_key,
+// capability_name) pair is read off the instance the call arrived on, never passed in from Python,
+// so a capability cannot reach another capability's config. Throws std::runtime_error (RuntimeError
+// in Python) on an unmaterialized instance.
-// Only the user-editable cap_config, resolved the way the config UI presents it: the active preset's
-// override when it has one, otherwise the config stored here (see active_capability_config). An
-// empty object when neither layer holds one.
+// The cap_config resolved as the config UI presents it: the active preset's override when it has
+// one, otherwise the config stored here (see active_capability_config). Empty object when neither.
nlohmann::json capability_get_config(const PluginCapabilityInterface& capability);
-// The plugin version that last wrote the config get_config() returns — the same layer it came from —
-// so a plugin can migrate a stale cap_config. Empty when the capability has no stored config.
+// The plugin version that wrote the config get_config() returns — the same layer it came from — so a
+// plugin can migrate a stale cap_config. Empty when the capability has no stored config.
std::string capability_get_config_version(const PluginCapabilityInterface& capability);
-// Replaces cap_config and persists. Host-managed identity and version metadata are preserved.
-// Writes the store here, never a preset: presets are the user's to edit, and a plugin saving from a
-// worker thread cannot mark one dirty. A capability whose active preset overrides it will therefore
-// keep reading that override back, not what it saved.
+// Replaces cap_config and persists. Always writes the store, never a preset: presets are the user's
+// to edit, and a plugin saving from a worker thread cannot mark one dirty. A capability whose active
+// preset overrides it will therefore keep reading that override back, not what it saved.
bool capability_save_config(const PluginCapabilityInterface& capability, const nlohmann::json& config);
} // namespace Slic3r
diff --git a/src/slic3r/plugin/PluginResolver.cpp b/src/slic3r/plugin/PluginResolver.cpp
index c102dc50b0..a164082b38 100644
--- a/src/slic3r/plugin/PluginResolver.cpp
+++ b/src/slic3r/plugin/PluginResolver.cpp
@@ -28,18 +28,16 @@ namespace Slic3r {
namespace {
-// Return the name of the tracked option in `preset` whose value references `ref`'s capability, or
-// an empty string when no active option uses it. The result doubles as the "Jump to" target and as
-// the signal that the plugin is still required: a missing plugin with no referencing option is
-// considered resolved and dropped from the missing set.
+// The tracked option in `preset` whose value references `ref`'s capability, or "" when none does.
+// Doubles as the "Jump to" target and as the signal that the plugin is still required: a missing
+// plugin with no referencing option is dropped from the set.
std::string find_option_for_capability(Preset::Type type, const Preset& preset, const PluginCapabilityRef& ref)
{
if (type != Preset::TYPE_PRINT && type != Preset::TYPE_PRINTER && type != Preset::TYPE_FILAMENT)
return {};
- // Plugin-bearing options opt in via ConfigOptionDef::is_plugin_backed (a non-empty plugin_type),
- // so scan the preset's definition rather than maintaining a hardcoded per-type field list. A typed
- // preset's config only contains keys for its own type, so this naturally stays scoped to `type`.
+ // Options opt in via ConfigOptionDef::is_plugin_backed, so scan the definition rather than keep a
+ // hardcoded per-type field list. A typed preset's config only holds keys for its own type.
const ConfigDef* def = preset.config.def();
if (def == nullptr)
return {};
@@ -80,8 +78,7 @@ std::string find_option_for_capability(Preset::Type type, const Preset& preset,
} // namespace
-// One missing-plugin set per tracked preset type, keyed by the full "name;uuid;capability" ref.
-// Only TYPE_PRINT (process), TYPE_PRINTER (machine) and TYPE_FILAMENT are tracked.
+// One set per tracked preset type, keyed by the full "name;uuid;capability" ref.
static std::map> s_missing;
static std::mutex s_missing_mutex;
// Installed-but-inactive capabilities (not loaded, or loaded-but-disabled); resolvable locally.
@@ -108,7 +105,6 @@ std::vector referenced_capabilities(Preset::Type type, cons
const auto ref = parse_capability_ref(entry);
if (!ref)
continue;
- // The same test the missing-plugin path uses: an entry no option points at is not in use.
if (find_option_for_capability(type, preset, *ref).empty())
continue;
refs.push_back(*ref);
@@ -119,15 +115,15 @@ std::vector referenced_capabilities(Preset::Type type, cons
namespace {
// Resolve one preset's referenced capabilities to loaded capability identifiers, appending to `out`.
-// A ref whose plugin is not in the catalog, or whose capability is not currently loaded, is dropped:
-// there is no instance to ask for a config UI or defaults, so there is nothing to configure.
+// A ref not in the catalog, or whose capability is not loaded, is dropped: no instance means nothing
+// to configure.
void collect_capabilities_in_use(Preset::Type type, const Preset& preset, std::vector& out)
{
PluginLoader& loader = PluginManager::instance().get_loader();
const PluginCatalog& catalog = PluginManager::instance().get_catalog();
for (const PluginCapabilityRef& ref : referenced_capabilities(type, preset)) {
- // Cloud plugins resolve by UUID, local plugins by plugin_key — the rule refresh_missing_plugins uses.
+ // Cloud plugins resolve by UUID, local plugins by plugin_key.
const std::string key = ref.uuid.empty() ? ref.name : ref.uuid;
if (key.empty())
continue;
@@ -156,8 +152,8 @@ std::vector capabilities_in_use(const PresetBundle&
} else if (type == Preset::TYPE_PRINTER) {
collect_capabilities_in_use(type, preset_bundle.printers.get_edited_preset(), result);
} else {
- // Filament: every selected filament preset, tested against its own config. (Note that
- // refresh_missing_plugins cannot do this — it unions the manifests and loses the preset.)
+ // Each filament preset is tested against its own config; refresh_missing_plugins cannot do
+ // that, as it unions the manifests and loses the preset.
for (const std::string& filament_name : preset_bundle.filament_presets)
if (const Preset* filament = preset_bundle.filaments.find_preset(filament_name))
collect_capabilities_in_use(type, *filament, result);
@@ -233,9 +229,7 @@ std::string resolve_recovery_url(const PluginCapabilityRef& ref)
return resolve_cloud_base_url() + "/app/plugins/plugin-hub?search=" + Http::url_encode(ref.name);
}
-// Reports whether the loaded plugin currently exposes the referenced capability (in any enabled
-// state) and whether that capability is enabled. Returns {false, false} when the plugin is not
-// loaded or does not provide the capability.
+// {present, enabled}; {false, false} when the plugin is not loaded or does not provide the capability.
static std::pair loaded_capability_state(const std::string& plugin_key, const PluginCapabilityRef& ref)
{
bool present = false, enabled = false;
@@ -312,7 +306,7 @@ void refresh_missing_plugins(Preset::Type type, const ConfigOptionStrings* manif
continue;
if (!installed) {
- // Not on disk — needs download/install (existing behavior).
+ // Not on disk — needs download/install.
std::string recovery_url = ref->uuid.empty() ? resolve_recovery_url(*ref) : std::string();
missing_set.emplace(entry, MissingPlugin{*ref, std::move(recovery_url), std::move(opt), type, PluginCapabilityType::Unknown});
} else if (!loaded || cap_present) {
@@ -406,7 +400,6 @@ static void report_install_failure(const std::string& message)
void resolve_missing_plugins(const std::vector& refs, PluginInstallProgress progress)
{
- // Collect the unique cloud UUIDs to install; local refs are handled via the browser flow.
std::vector uuids;
for (const std::string& r : refs) {
const auto ref = parse_capability_ref(r);
@@ -430,7 +423,6 @@ void resolve_missing_plugins(const std::vector& refs, PluginInstall
const std::string& uuid = uuids[i];
- // Use a friendly name for the progress message when the catalog already knows it.
std::string display_name = uuid;
PluginDescriptor known;
if (mgr.get_catalog().try_get_plugin_descriptor(uuid, known) && !known.name.empty())
@@ -466,8 +458,7 @@ void resolve_inactive_plugins(const std::vector& refs)
PluginManager& mgr = PluginManager::instance();
PluginCatalog& catalog = mgr.get_catalog();
- // Group the requested capabilities by owning plugin so each plugin is loaded once with the full
- // set to enable.
+ // Group by owning plugin so each plugin is loaded once with the full set to enable.
std::map> by_plugin;
for (const std::string& r : refs) {
const auto ref = parse_capability_ref(r);
@@ -482,11 +473,9 @@ void resolve_inactive_plugins(const std::vector& refs)
if (by_plugin.empty())
return;
- // load_plugin loads+enables a not-loaded plugin (async) and enables the listed capabilities on an
- // already-loaded one. The fresh-load path does NOT fire the capability-load callback the GUI uses
- // to clear the notification, so wait for each load off the UI thread and then re-validate once —
- // mirroring the cloud-install flow. This clears the inactive notification, or flips it to broken
- // if the loaded plugin turns out not to provide the capability.
+ // The fresh-load path does NOT fire the capability-load callback the GUI uses to clear the
+ // notification, so wait for each load off the UI thread and re-validate once. That clears the
+ // inactive notification, or flips it to broken if the plugin does not provide the capability.
std::vector>> work(by_plugin.begin(), by_plugin.end());
std::thread([work = std::move(work)]() {
PluginManager& mgr = PluginManager::instance();
@@ -506,7 +495,6 @@ void resolve_inactive_plugins(const std::vector& refs)
void open_missing_plugins_on_cloud(const std::vector& local_refs)
{
- // One missing plugin: deep-link a search for it. Multiple: just open the plugin hub.
if (local_refs.size() == 1) {
if (const auto ref = parse_capability_ref(local_refs.front())) {
wxLaunchDefaultBrowser(GUI::from_u8(resolve_recovery_url(*ref)), wxBROWSER_NEW_WINDOW);
diff --git a/src/slic3r/plugin/PluginResolver.hpp b/src/slic3r/plugin/PluginResolver.hpp
index c7c02173ca..d4ab27b4f6 100644
--- a/src/slic3r/plugin/PluginResolver.hpp
+++ b/src/slic3r/plugin/PluginResolver.hpp
@@ -24,10 +24,9 @@ struct MissingPlugin
PluginCapabilityType type{PluginCapabilityType::Unknown};
};
-// Rebuild the missing-plugin set owned by a single preset type from that preset's "plugins"
-// manifest, comparing each ref against the live plugin catalog and loaded/enabled capabilities. A
-// null/empty manifest clears the set for that type. Only TYPE_PRINT (process), TYPE_PRINTER
-// (machine) and TYPE_FILAMENT are tracked; other types are ignored.
+// Rebuild one preset type's missing-plugin set from its "plugins" manifest, comparing each ref
+// against the live catalog and loaded/enabled capabilities. A null/empty manifest clears the set.
+// Only TYPE_PRINT, TYPE_PRINTER and TYPE_FILAMENT are tracked.
void refresh_missing_plugins(Preset::Type type, const ConfigOptionStrings* manifest, const Preset* preset = nullptr);
void refresh_missing_plugins(const PresetBundle& preset_bundle);
@@ -37,22 +36,16 @@ std::vector get_missing_cloud_plugins();
std::vector get_missing_local_plugins();
bool has_missing_plugins();
-// Installed-but-inactive capabilities: the plugin has a local package but the referenced capability
-// is not active because the plugin is not loaded, or it is loaded but the capability is disabled.
-// Resolved locally by loading the plugin and/or enabling the capability — no download.
+// Installed-but-inactive: the plugin has a local package but is not loaded, or is loaded with the
+// capability disabled. Resolved locally by loading and/or enabling — no download.
std::vector get_inactive_plugins();
bool has_inactive_plugins();
// Broken references: the plugin is installed AND loaded but does not provide the referenced
-// capability at all (renamed/removed/outdated plugin). Activation cannot fix these; surfaced as an
-// informational notification pointing the user at OrcaCloud to update the plugin.
+// capability at all (renamed/removed/outdated plugin). Activation cannot fix these.
std::vector get_broken_plugins();
bool has_broken_plugins();
-// Resolution actions invoked from the missing-plugin notifications:
-// - cloud refs are subscribed/installed and loaded on a detached worker thread; failures are
-// reported through a non-blocking notification. Non-cloud refs are ignored.
-
// Optional progress hook for the cloud install worker. All three callbacks fire on the worker
// thread; implementations must only touch thread-safe state or marshal to the UI thread.
struct PluginInstallProgress
@@ -65,43 +58,39 @@ struct PluginInstallProgress
std::function on_finished;
};
-// Cloud refs only; local refs are handled via the browser flow. `progress` is optional — a
-// default-constructed value preserves the previous silent behavior.
+// Subscribe, install and load the cloud refs on a detached worker; failures are reported through a
+// non-blocking notification. Local refs are ignored — they go through the browser flow below.
void resolve_missing_plugins(const std::vector& refs,
PluginInstallProgress progress = {});
-// Activate inactive plugins: load each referenced plugin (passing the capabilities to enable) and/or
-// enable already-loaded-but-disabled capabilities. Local only — no network. The loads run on a
-// background worker that waits for them and then re-validates the plate, clearing the notification
-// (or reclassifying the ref as broken if the loaded plugin turns out not to provide the capability).
+// Load each referenced plugin and/or enable its disabled capabilities. Local only — no network. The
+// loads run on a background worker that waits for them and then re-validates the plate, clearing the
+// notification (or reclassifying the ref as broken if the plugin does not provide the capability).
void resolve_inactive_plugins(const std::vector& refs);
-// - local refs are opened on the OrcaCloud plugin hub (search when exactly one ref, hub otherwise).
+// Opens the OrcaCloud plugin hub (a search when there is exactly one ref, the hub otherwise).
void open_missing_plugins_on_cloud(const std::vector& local_refs);
std::string create_full_ref(const PluginCapabilityRef& ref);
std::string resolve_recovery_url(const PluginCapabilityRef& ref);
// The capabilities `preset`'s "plugins" manifest declares AND that one of its plugin-backed options
-// (ConfigOptionDef::is_plugin_backed) currently references. A manifest entry nobody points at is not
-// in use. Pure preset logic — the plugin catalog and loader are not consulted. Empty for untracked
-// preset types.
+// (ConfigOptionDef::is_plugin_backed) currently references: a manifest entry nobody points at is not
+// in use. Pure preset logic — the catalog and loader are not consulted. Empty for untracked types.
std::vector referenced_capabilities(Preset::Type type, const Preset& preset);
std::vector capabilities_in_use(Preset::Type type, const Preset& preset);
-// The preset type that owns capabilities of `type` — the one whose presets can reference them and
-// therefore carry their overrides. Derived from the ConfigDef rather than hardcoded: a plugin-backed
-// option names the capability type it accepts (ConfigOptionDef::plugin_type) and belongs to exactly
-// one preset type, so declaring the option is all it takes to map a new capability type.
-// TYPE_INVALID when no option accepts the type (nothing can reference it, so no preset owns it).
+// The preset type whose presets can reference capabilities of `type` and therefore carry their
+// overrides. Derived from the ConfigDef rather than hardcoded: a plugin-backed option names the
+// capability type it accepts (ConfigOptionDef::plugin_type) and belongs to exactly one preset type,
+// so declaring the option is all it takes to map a new capability type. TYPE_INVALID when no option
+// accepts the type — nothing can reference it, so no preset owns it.
Preset::Type preset_type_for_capability(PluginCapabilityType type);
-// The capabilities the active preset(s) of `type` reference (see referenced_capabilities) and that
-// are loaded right now: the set that can actually be configured. Missing and broken refs are absent,
-// having no instance to ask for a config UI or defaults. A loaded-but-disabled capability IS listed —
-// it still has stored config worth editing, and disabling it is not a reason to hide that.
-// TYPE_FILAMENT unions every selected filament preset; a capability used by two extruders is listed
-// once. Deduped on the full identity, so two plugins exposing a same-named capability stay distinct.
+// The referenced capabilities of the active preset(s) of `type` that are loaded right now: the set
+// that can actually be configured. Missing and broken refs are absent, having no instance to ask for
+// a config UI or defaults. A loaded-but-disabled capability IS listed — it still has stored config
+// worth editing.
std::vector capabilities_in_use(const PresetBundle& preset_bundle, Preset::Type type);
bool check_capability_in_use(const std::string& capability_refs);
diff --git a/src/slic3r/plugin/PresetPluginConfig.cpp b/src/slic3r/plugin/PresetPluginConfig.cpp
index 61ed41dda2..efe744b471 100644
--- a/src/slic3r/plugin/PresetPluginConfig.cpp
+++ b/src/slic3r/plugin/PresetPluginConfig.cpp
@@ -26,8 +26,8 @@ std::string running_plugin_version(const std::string& plugin_key)
return descriptor.installed_version.empty() ? descriptor.version : descriptor.installed_version;
}
-// Null wherever the plugin host runs without the GUI app (the unit tests): there are no presets
-// then, only the base config. wxGetApp() dereferences the app unconditionally, so ask wxWidgets.
+// Null wherever the plugin host runs without the GUI app (the unit tests). wxGetApp() dereferences
+// the app unconditionally, so ask wxWidgets instead.
const PresetBundle* active_preset_bundle()
{
const auto* app = dynamic_cast(wxApp::GetInstance());
@@ -46,22 +46,15 @@ const Preset* active_preset_for(Preset::Type type)
case Preset::TYPE_PRINT: return &bundle->prints.get_edited_preset();
case Preset::TYPE_PRINTER: return &bundle->printers.get_edited_preset();
- // Deliberately unimplemented, not forgotten.
- //
- // There is no single active filament preset: one is selected per extruder, and a capability
- // calls get_config() without saying which extruder it is running for, so we cannot tell which
- // of them owns the config. Guessing (first override wins, or extruder 0) would hand a plugin
- // another extruder's settings and look like a plugin bug, so it reads the base config instead —
- // the value it had before presets could override anything.
- //
- // Nothing reaches this today: preset_type_for_capability only names TYPE_FILAMENT once a
- // filament option declares a plugin_type, and none does (the two plugin-backed options are
- // print and printer ones). Solve it with the first filament-backed capability, by pushing the
- // extruder onto the plugin call context the trampoline already maintains
- // (ScopedPluginAuditContext) along with the override text snapshotted off the preset, and
- // resolving the preset here from that instead of from the bundle. The extruder must be optional:
- // whole slicing steps (posSlice, psGCodePostProcess) span every extruder and have no current
- // filament, and this fallback is the honest answer for them.
+ // Deliberately unimplemented, not forgotten. There is no single active filament preset — one is
+ // selected per extruder, and get_config() does not say which extruder the capability runs for —
+ // so guessing (extruder 0, or first override wins) would hand a plugin another extruder's
+ // settings. Filament capabilities read the base config instead. Nothing reaches this today:
+ // preset_type_for_capability only names TYPE_FILAMENT once a filament option declares a
+ // plugin_type, and none does. To lift it, push the extruder onto the plugin call context the
+ // trampoline already maintains (ScopedPluginAuditContext) and resolve the preset from that. The
+ // extruder must be optional: whole slicing steps (posSlice, psGCodePostProcess) span every
+ // extruder and have no current filament, and this fallback is the honest answer for them.
case Preset::TYPE_FILAMENT: return nullptr;
default: return nullptr;
@@ -146,8 +139,8 @@ MutationResult PresetPluginConfigService::set_preset_override(CapabilityConfigDo
const CapabilityConfigId config_id = make_id(id);
const std::string version = running_plugin_version(id.plugin_key);
- // A no-op is a successful unchanged result, not a reason to rewrite the preset: re-saving the
- // displayed value must not be able to mark it dirty.
+ // A no-op is a successful unchanged result: re-saving the displayed value must not mark the
+ // preset dirty.
const auto existing = overrides.find(config_id);
if (existing && existing->cap_config == value && existing->plugin_version == version) {
result.ok = true;
@@ -169,7 +162,6 @@ MutationResult PresetPluginConfigService::remove_preset_override(CapabilityConfi
MutationResult result;
result.ok = true;
result.changed = overrides.erase(make_id(id));
- // Reads back as the base value now that the override is gone.
result.effective = get_effective_config(overrides, id);
return result;
}
@@ -182,8 +174,7 @@ EffectiveCapabilityConfig active_capability_config(const PluginCapabilityIdentif
if (const Preset* preset = active_preset_for(preset_type_for_capability(id.type))) {
std::string error;
if (!parse_plugin_overrides(plugin_overrides_of(*preset), overrides, error)) {
- // Text we cannot read is not an override. Say so and resolve against the base config,
- // which is what the capability ran with before anything was written into the preset.
+ // Text we cannot read is not an override: log it and resolve against the base config.
BOOST_LOG_TRIVIAL(error) << "Preset \"" << preset->name << "\": " << error;
overrides = CapabilityConfigDocument();
}
diff --git a/src/slic3r/plugin/PresetPluginConfig.hpp b/src/slic3r/plugin/PresetPluginConfig.hpp
index 315a73ad3c..c241035995 100644
--- a/src/slic3r/plugin/PresetPluginConfig.hpp
+++ b/src/slic3r/plugin/PresetPluginConfig.hpp
@@ -23,7 +23,7 @@ std::string plugin_overrides_of(const Preset& preset);
bool parse_plugin_overrides(const std::string& raw, CapabilityConfigDocument& document, std::string& error);
// The document as compact JSON text, and "" once it holds no entries. Empty text — rather than a
-// removed option — is what records "cleared here" against an inheriting parent that has overrides.
+// removed option — records "cleared here" against an inheriting parent that has overrides.
std::string serialize_plugin_overrides(const CapabilityConfigDocument& document);
struct EffectiveCapabilityConfig
@@ -49,12 +49,10 @@ struct MutationResult
std::string plugin_config_source_to_string(PluginConfigSource source);
// Resolves a capability's effective config as `preset override -> base config -> none`, and mutates
-// the override layer.
-//
-// It works on a CapabilityConfigDocument the caller owns, never on a Preset and never on the base
-// config file. That is what keeps the two layers from writing to each other: PluginConfigField holds
-// the document, and feeds the edited text back through the normal field/dirty pipeline, so the
-// preset is written exactly the way every other setting is.
+// the override layer. It works on a CapabilityConfigDocument the caller owns, never on a Preset and
+// never on the base config file, which is what keeps the two layers from writing to each other:
+// PluginConfigField holds the document and feeds the edited text back through the normal field/dirty
+// pipeline, so the preset is written the way every other setting is.
class PresetPluginConfigService
{
public:
@@ -67,15 +65,11 @@ public:
const PluginCapabilityIdentifier& id) const;
};
-// The same `preset override -> base config -> none` resolution, against the preset that is active
-// right now instead of a document the caller holds: this is what a running capability reads through
-// the Python config API, so a preset that overrides a capability configures the slice it drives.
-//
-// Only one preset type can reference a given capability type (preset_type_for_capability), so there
-// is exactly one preset to consult. Filament capabilities are the exception and are not supported:
-// the active filament preset is per-extruder and a capability does not say which extruder it runs
-// for, so they read the base config. See active_preset_for() for why, and for what it will take to
-// lift that. Base config also in a host with no preset bundle (the plugin unit tests).
+// The same resolution against the preset that is active right now, rather than a document the caller
+// holds: this is what a running capability reads through the Python config API. Only one preset type
+// can reference a given capability type (preset_type_for_capability), so there is exactly one preset
+// to consult. Falls back to the base config for filament capabilities (see active_preset_for) and in
+// a host with no preset bundle (the plugin unit tests).
EffectiveCapabilityConfig active_capability_config(const PluginCapabilityIdentifier& id);
} // namespace Slic3r
diff --git a/src/slic3r/plugin/PyPluginTrampoline.hpp b/src/slic3r/plugin/PyPluginTrampoline.hpp
index ba478de451..468e519ff1 100644
--- a/src/slic3r/plugin/PyPluginTrampoline.hpp
+++ b/src/slic3r/plugin/PyPluginTrampoline.hpp
@@ -12,17 +12,14 @@
#include "PythonJsonUtils.hpp"
#include "PluginAuditManager.hpp"
-// Trampoline variants of pybind11's override macros. Every C++->Python plugin call
-// crosses through a trampoline method, so this single boundary is where we (1) log the
-// full Python traceback (to sys.stderr -> session log) and rethrow the exception intact,
-// and (2) open the plugin's filesystem audit scope for the duration of the call.
-// We catch ONLY error_already_set (a Python-side raise); other pybind11_fail/runtime_error
-// like a pure-virtual-missing failure must keep their own path and are deliberately not
-// caught here.
+// Trampoline variants of pybind11's override macros. Every C++->Python plugin call crosses a
+// trampoline method, so this single boundary is where we (1) log the full Python traceback and
+// rethrow the exception intact, and (2) open the plugin's filesystem audit scope for the call.
+// We catch ONLY error_already_set (a Python-side raise); other pybind11_fail/runtime_error, such as
+// a pure-virtual-missing failure, must keep their own path and are deliberately not caught here.
-// Logs (and rethrows) a Python exception from a pybind11 override call, preserving the
-// traceback. Internal helper shared by the public macros below and by trampolines that
-// manage their own audit scope (e.g. the G-code plugin).
+// Shared by the macros below and by trampolines that manage their own audit scope (e.g. the G-code
+// plugin).
#define ORCA_PY_LOGGED_OVERRIDE_BODY(override_call) \
try { \
override_call; \
@@ -31,10 +28,9 @@
throw; \
}
-// Opens the plugin's filesystem audit scope for the duration of a C++ -> Python call
-// when this trampoline instance carries a non-empty audit plugin key. Also publishes the
-// calling capability's name, so host APIs invoked from Python can tell which capability
-// they are serving. Declares a local `_orca_audit_scope`.
+// Opens the plugin's filesystem audit scope for the duration of a C++ -> Python call, and publishes
+// the calling capability's name so host APIs invoked from Python can tell which capability they are
+// serving. No-op without an audit plugin key. Declares a local `_orca_audit_scope`.
#define ORCA_PY_AUDIT_SCOPE(mode) \
std::optional<::Slic3r::ScopedPluginAuditContext> _orca_audit_scope; \
if (const std::string& _orca_audit_key = this->audit_plugin_key(); \
@@ -68,9 +64,8 @@ public:
}
// Config UI hooks. Available on every capability type, so they live here rather than in
- // PyPluginInterfaceTrampoline. Audited like any other C++ -> Python call; a Python
- // exception is logged with its traceback and rethrown, and the caller (PluginLoader at
- // load time, PluginsDialog when opening the Config tab) decides the fallback.
+ // PyPluginInterfaceTrampoline. A Python exception is rethrown and the caller decides the
+ // fallback.
bool has_config_ui() const override
{
ORCA_PY_OVERRIDE_AUDITED(
@@ -95,15 +90,11 @@ public:
// Hand-rolled rather than PYBIND11_OVERRIDE: the macro casts the Python result to the return
// type, and nlohmann::json has no pybind caster (config crosses this boundary through the
- // explicit py_to_json/json_to_py helpers instead). Otherwise identical — same audit scope, and
- // a Python exception is logged with its traceback and rethrown for the caller to handle.
+ // explicit py_to_json/json_to_py helpers). Otherwise identical — same audit scope, same rethrow.
//
- // The hook is optional, and "not implemented" must mean an EMPTY config, never a null or a
- // stray scalar landing in cap_config. Two ways to not implement it, both resolved here:
- // - no override at all -> the base's empty object
- // - an override that returns None, or any -> likewise. `def get_default_config(self): pass`
- // non-object (a list, a string, a number) is the easy mistake, and it must not be able to
- // write `"cap_config": null` to config.json.
+ // The hook is optional, and "not implemented" must mean an EMPTY config: no override, or an
+ // override returning None or any non-object (`def get_default_config(self): pass` is the easy
+ // mistake), both fall back to the base's empty object rather than writing `"cap_config": null`.
nlohmann::json get_default_config() const override
{
ORCA_PY_AUDIT_SCOPE(::Slic3r::PluginAuditManager::AuditMode::Loading);
@@ -127,7 +118,6 @@ public:
}
}
- // All plugins may define their own on_load/unload functions.
void on_load() override
{
ORCA_PY_OVERRIDE_AUDITED(
@@ -156,8 +146,6 @@ class PyPluginInterfaceTrampoline : public PyPluginCommonTrampoline::PyPluginCommonTrampoline;
- // get_name is implemented in PyPluginCommonTrampoline (PYBIND11_OVERRIDE_PURE).
-
PluginCapabilityType get_type() const override
{
ORCA_PY_OVERRIDE_AUDITED(
diff --git a/src/slic3r/plugin/PythonPluginInterface.hpp b/src/slic3r/plugin/PythonPluginInterface.hpp
index d6c2ce6f5f..373dedbd90 100644
--- a/src/slic3r/plugin/PythonPluginInterface.hpp
+++ b/src/slic3r/plugin/PythonPluginInterface.hpp
@@ -105,41 +105,33 @@ public:
// Optional APIs
virtual PluginCapabilityType get_type() const { return PluginCapabilityType::Unknown; }
- // Every capability is configurable: it always appears in the Plugins dialog's Config
- // sidebar and always has the host's default JSON editor over its stored config. The only
- // question a capability answers is whether it supplies its own UI to edit that config
- // *instead of* the JSON editor.
- //
- // True when the capability ships a custom configuration UI. get_config_ui() is called
- // only when this returns true.
+ // Every capability is configurable and always gets the host's default JSON editor over its
+ // stored config; this only says whether it supplies its own UI *instead of* that editor.
+ // get_config_ui() is called only when it returns true.
virtual bool has_config_ui() const { return false; }
- // An HTML snippet for the custom configuration UI. An empty or throwing result is
- // treated as "no custom UI" and falls back to the default JSON editor.
+ // An HTML snippet for the custom configuration UI. An empty or throwing result is treated as
+ // "no custom UI" and falls back to the default JSON editor.
virtual std::string get_config_ui() const { return ""; }
- // The config the Config tab's "Restore defaults" action writes back. Optional.
- //
- // Not overridden -> an empty object, which is the right answer for a capability that keeps
- // its stored config sparse and applies its own defaults on read: clearing the overrides
- // *is* restoring the defaults, and it keeps a later release free to change them.
- // Override it to write an explicit starting config instead (e.g. to seed a form UI with
- // every field present). The host neither invents nor validates this value; it only stores
- // whatever comes back, so a throwing override leaves the stored config untouched.
+ // The config the "Restore defaults" action writes back. Not overridden -> an empty object, which
+ // is right for a capability that keeps its stored config sparse and applies its own defaults on
+ // read. Override it to write an explicit starting config instead (e.g. to seed a form UI with
+ // every field present). The host neither invents nor validates this value, so a throwing override
+ // leaves the stored config untouched.
virtual nlohmann::json get_default_config() const { return nlohmann::json::object(); }
virtual void on_load() {}
virtual void on_unload() {}
- // C++-only audit identity (never exposed to Python). Set by PluginLoader after
- // plugin capture so trampoline calls can scope filesystem enforcement to this
- // plugin. This is PluginDescriptor::plugin_key, the canonical runtime id.
+ // C++-only audit identity (never exposed to Python), set by PluginLoader after plugin capture so
+ // trampoline calls can scope filesystem enforcement to this plugin. PluginDescriptor::plugin_key.
void set_audit_plugin_key(std::string key) { m_audit_plugin_key = std::move(key); }
const std::string& audit_plugin_key() const { return m_audit_plugin_key; }
- // The cached get_name() captured at load, paired with the audit plugin key to identify
- // which capability a trampoline call belongs to. Cached rather than read live: get_name()
- // is itself a trampoline call, so calling it from inside a trampoline would recurse.
- // Empty until PluginLoader materializes the capability.
+ // get_name() cached at load, paired with the audit plugin key to identify which capability a
+ // trampoline call belongs to. Cached rather than read live: get_name() is itself a trampoline
+ // call, so calling it from inside a trampoline would recurse. Empty until PluginLoader
+ // materializes the capability.
void set_audit_capability_name(std::string name) { m_audit_capability_name = std::move(name); }
const std::string& audit_capability_name() const { return m_audit_capability_name; }
diff --git a/tests/slic3rutils/test_plugin_capabilities_in_use.cpp b/tests/slic3rutils/test_plugin_capabilities_in_use.cpp
index faedb8804c..db50de68ec 100644
--- a/tests/slic3rutils/test_plugin_capabilities_in_use.cpp
+++ b/tests/slic3rutils/test_plugin_capabilities_in_use.cpp
@@ -12,8 +12,7 @@ using namespace Slic3r;
namespace {
-// A print preset carrying a "plugins" manifest and the one plugin-backed print option
-// (slicing_pipeline_plugin, a coStrings vector).
+// A print preset carrying a "plugins" manifest and the one plugin-backed print option.
Preset make_print_preset(const std::vector& manifest, const std::vector& pipeline)
{
Preset preset(Preset::TYPE_PRINT, "test-print");
@@ -76,16 +75,15 @@ TEST_CASE("referenced_capabilities skips malformed manifest entries", "[PluginRe
TEST_CASE("preset_type_for_capability names the preset type whose options reference the capability", "[PluginResolver]")
{
- // Read out of the ConfigDef: slicing_pipeline_plugin is a print option, printer_agent a printer
- // one. Declaring a plugin_type on an option is what puts its capability type on this map.
+ // Read out of the ConfigDef: declaring a plugin_type on an option is what puts its capability
+ // type on this map.
CHECK(preset_type_for_capability(PluginCapabilityType::SlicingPipeline) == Preset::TYPE_PRINT);
CHECK(preset_type_for_capability(PluginCapabilityType::PrinterConnection) == Preset::TYPE_PRINTER);
}
TEST_CASE("preset_type_for_capability leaves capability types no option accepts unowned", "[PluginResolver]")
{
- // Nothing can reference these from a preset, so no preset can override them either: they read
- // their config from the base config.json alone.
+ // No option accepts them, so no preset can override them: they read config.json alone.
CHECK(preset_type_for_capability(PluginCapabilityType::Automation) == Preset::TYPE_INVALID);
CHECK(preset_type_for_capability(PluginCapabilityType::Unknown) == Preset::TYPE_INVALID);
}
diff --git a/tests/slic3rutils/test_plugin_capability_config.cpp b/tests/slic3rutils/test_plugin_capability_config.cpp
index 59b879e191..a95094b892 100644
--- a/tests/slic3rutils/test_plugin_capability_config.cpp
+++ b/tests/slic3rutils/test_plugin_capability_config.cpp
@@ -23,8 +23,8 @@ namespace {
void ensure_python_initialized()
{
- // Same rationale as test_plugin_host_api.cpp: `orca` is an embedded module compiled into this
- // binary, so a bare interpreter is enough and does not need the bundled Python home.
+ // As in test_plugin_host_api.cpp: `orca` is compiled into this binary, so a bare interpreter is
+ // enough and does not need the bundled Python home.
if (!Py_IsInitialized()) {
static py::scoped_interpreter interpreter;
(void) interpreter;
@@ -38,16 +38,15 @@ py::module_ import_orca_module()
return py::module_::import("orca");
}
-// Builds a Python capability from `body` and materializes it the way PluginLoader does: the audit
-// identity is stamped on by the host, never supplied by the plugin, and it is what scopes every
-// config call to this one capability.
+// Builds a Python capability the way PluginLoader does: the audit identity is stamped on by the
+// host, never supplied by the plugin, and it scopes every config call to this one capability.
py::object make_capability(const std::string& class_name,
const std::string& body,
const std::string& plugin_key,
const std::string& capability_name)
{
- // Import first: it is what brings the interpreter up, and constructing any py:: object
- // beforehand would touch a Python that does not exist yet.
+ // Import first: it brings the interpreter up, and any py:: object built before it would touch a
+ // Python that does not exist yet.
py::module_ orca = import_orca_module();
py::dict globals;
@@ -69,12 +68,10 @@ std::shared_ptr as_interface(const py::object& instan
return instance.cast>();
}
-// The config the Python API actually writes to: capability_save_config persists through the
-// PluginManager singleton, so that is where the assertions read from.
+// The Python API writes through the PluginManager singleton, so that is where assertions read from.
PluginConfig& host_config() { return PluginManager::instance().get_config(); }
-// The Python config API speaks JSON text, not dicts: get_config()/get_default_config() hand back a
-// JSON string and save_config() takes one. These helpers keep the tests written in terms of values.
+// The Python config API speaks JSON text, not dicts; these helpers keep the tests in terms of values.
json py_get_config(const py::object& cap) { return json::parse(cap.attr("get_config")().cast()); }
bool py_save_config(const py::object& cap, const json& value) { return cap.attr("save_config")(value.dump()).cast(); }
@@ -87,8 +84,8 @@ TEST_CASE("Capability config API is exposed on every Python capability", "[Plugi
REQUIRE(py::hasattr(orca, "PythonPluginBase"));
py::object base = orca.attr("PythonPluginBase");
- // Host-provided (the capability calls these). Every capability has a config, so these are
- // always available — there is no hook to opt in or out of being configurable.
+ // Host-provided: every capability has a config, so there is no hook to opt out of being
+ // configurable.
CHECK(py::hasattr(base, "get_config"));
CHECK(py::hasattr(base, "save_config"));
CHECK(py::hasattr(base, "get_config_version"));
@@ -97,8 +94,8 @@ TEST_CASE("Capability config API is exposed on every Python capability", "[Plugi
CHECK(py::hasattr(base, "get_config_ui"));
CHECK(py::hasattr(base, "get_default_config"));
- // Config is reached through the capability, never as a free orca.config.* function, so a
- // capability cannot name — and therefore cannot touch — a config that is not its own.
+ // Config is reached only through the capability, never as a free orca.config.* function, so a
+ // capability cannot name — and cannot touch — a config that is not its own.
CHECK_FALSE(py::hasattr(orca, "config"));
}
@@ -109,8 +106,8 @@ TEST_CASE("get_config returns only cap_config and save_config persists it", "[Pl
py::object cap = make_capability("RoundTripCap", " def get_name(self): return 'cap_a'\n", "plugin_a", "cap_a");
- // Nothing stored yet: the JSON text of an empty object, not None, so a plugin can json.loads()
- // and index it unconditionally.
+ // Nothing stored yet: the JSON text of an empty object, not None, so a plugin can json.loads() it
+ // unconditionally.
py::object initial = cap.attr("get_config")();
REQUIRE(py::isinstance(initial));
CHECK(json::parse(initial.cast()) == json::object());
@@ -118,12 +115,11 @@ TEST_CASE("get_config returns only cap_config and save_config persists it", "[Pl
REQUIRE(py_save_config(cap, json{{"speed", 5}, {"name", "fast"}}));
- // Persisted through PluginConfig, under this capability's identity only.
const BaseConfig stored = host_config().get_config("plugin_a", "cap_a");
REQUIRE_FALSE(stored.empty());
CHECK(stored.config == json{{"speed", 5}, {"name", "fast"}});
- // And read back through Python as exactly cap_config — no host metadata.
+ // Python reads back exactly cap_config — no host metadata.
const json reloaded = py_get_config(cap);
CHECK(reloaded.size() == 2);
CHECK(reloaded.contains("speed"));
@@ -142,8 +138,7 @@ TEST_CASE("save_config rejects a string that is not valid JSON", "[PluginConfig]
REQUIRE(py_save_config(cap, json{{"keep", "me"}}));
- // The binding parses the string it is handed. Unparseable text is refused, and refusing it must
- // leave the previously stored config alone rather than clobbering it with nothing.
+ // Refusing unparseable text must leave the previously stored config alone.
CHECK_FALSE(cap.attr("save_config")("{not json").cast());
CHECK(host_config().get_config("plugin_a", "cap_a").config == json{{"keep", "me"}});
}
@@ -154,8 +149,8 @@ TEST_CASE("Saving one capability's config does not touch another's", "[PluginCon
host_config().load();
const std::string body = " def get_name(self): return 'cap'\n";
- // Same capability name under two different plugins, plus a second capability of plugin_a:
- // each addresses only the entry matching its own stamped identity.
+ // Same capability name under two plugins, plus a second capability of plugin_a: each addresses
+ // only the entry matching its own stamped identity.
py::object a_cap1 = make_capability("IsoCapA1", body, "plugin_a", "cap_a");
py::object a_cap2 = make_capability("IsoCapA2", body, "plugin_a", "cap_b");
py::object b_cap1 = make_capability("IsoCapB1", body, "plugin_b", "cap_a");
@@ -170,7 +165,6 @@ TEST_CASE("Saving one capability's config does not touch another's", "[PluginCon
CHECK(host_config().get_config("plugin_a", "cap_b").config == json{{"value", 2}});
CHECK(host_config().get_config("plugin_b", "cap_a").config == json{{"value", 3}});
- // Each capability still reads back its own value.
CHECK(py_get_config(a_cap2).at("value") == 2);
CHECK(py_get_config(b_cap1).at("value") == 3);
}
@@ -180,8 +174,8 @@ TEST_CASE("Config API refuses a capability the host never materialized", "[Plugi
ScopedDataDir data_dir_guard("plugin-config-py-unowned");
host_config().load();
- // No audit identity: the instance was never loaded by the host, so it has no config to address.
- // Refused rather than served from, or written to, some arbitrary entry.
+ // No audit identity: never loaded by the host, so it has no config to address. Refused rather
+ // than served from, or written to, some arbitrary entry.
py::object orphan = make_capability("OrphanCap", " def get_name(self): return 'cap'\n", "", "");
CHECK_THROWS(orphan.attr("get_config")());
@@ -208,9 +202,8 @@ TEST_CASE("A capability that omits the config UI hooks gets the default editor",
ScopedDataDir data_dir_guard("plugin-config-py-bare");
host_config().load();
- // Both hooks are optional and only choose the editor. A capability that overrides neither is
- // still configurable — it just gets the host's JSON editor — so it stays in the Config sidebar
- // and its config API keeps working. There is no way for a capability to opt out of having one.
+ // Both hooks are optional and only choose the editor: a capability that overrides neither is
+ // still configurable, it just gets the host's JSON editor. There is no way to opt out.
py::object bare = make_capability("BareCap", " def get_name(self): return 'cap_a'\n", "plugin_a", "cap_a");
auto iface = as_interface(bare);
@@ -226,8 +219,8 @@ TEST_CASE("get_default_config supplies the value Restore defaults writes back",
{
SECTION("not overridden -> an empty config")
{
- // Which is already "restore defaults" for a capability that keeps its stored config sparse
- // and applies its own defaults on read: clearing the overrides restores them.
+ // Already "restore defaults" for a capability that keeps its stored config sparse and applies
+ // its own defaults on read.
py::object bare = make_capability("NoDefaultsCap", " def get_name(self): return 'cap_a'\n", "plugin_a", "cap_a");
auto iface = as_interface(bare);
@@ -251,8 +244,8 @@ TEST_CASE("get_default_config supplies the value Restore defaults writes back",
SECTION("overridden but returns None -> an empty config, never a null")
{
- // `def get_default_config(self): pass` is the easy mistake. It must not be able to store
- // "cap_config": null — an unimplemented hook means an empty config, however it is spelled.
+ // `def get_default_config(self): pass` is the easy mistake, and it must not store
+ // "cap_config": null.
py::object cap = make_capability("NoneDefaultsCap",
" def get_name(self): return 'cap_a'\n"
" def get_default_config(self): pass\n",
@@ -298,7 +291,6 @@ TEST_CASE("Restoring defaults overwrites only the target capability", "[PluginCo
REQUIRE(host_config().store_capability_config("plugin_a", "cap_a", iface->get_default_config()));
CHECK(host_config().get_config("plugin_a", "cap_a").config == json{{"speed", 1}});
- // The same capability name under another plugin keeps its edited value.
CHECK(host_config().get_config("plugin_b", "cap_a").config == json{{"speed", 99}});
}
@@ -333,19 +325,16 @@ TEST_CASE("A raising config UI hook surfaces as an exception the host can catch"
auto iface = as_interface(cap);
REQUIRE(iface);
- // The trampoline logs the traceback and rethrows; callers (PluginLoader when caching the flag,
- // PluginsDialog when opening the Config tab) catch it and fall back to the default JSON editor
- // instead of crashing.
+ // The trampoline rethrows; callers catch it and fall back to the default JSON editor.
CHECK_THROWS_AS(iface->get_config_ui(), py::error_already_set);
- // Catching it leaves the interpreter usable — the host is still able to talk to the capability.
+ // Catching it leaves the interpreter usable.
CHECK(iface->get_name() == "cap_a");
}
TEST_CASE("A config UI hook returning the wrong type does not crash the host", "[PluginConfig][Python]")
{
- // has_config_ui() is plugin-authored, so it can return anything. Whatever pybind makes of a
- // non-bool, the host must survive the call: it either converts or throws, never crashes.
+ // has_config_ui() is plugin-authored, so it can return anything; the host must survive the call.
py::object cap = make_capability("BadTypeCap",
" def get_name(self): return 'cap_a'\n"
" def has_config_ui(self): return 'not a bool'\n",
@@ -354,15 +343,15 @@ TEST_CASE("A config UI hook returning the wrong type does not crash the host", "
auto iface = as_interface(cap);
REQUIRE(iface);
- // Deliberately not REQUIRE_THROWS: pybind may coerce the value or reject it, and both are
- // acceptable. What must hold is that the call is survivable — a throw is what PluginLoader's
- // guard turns into "no custom UI".
+ // Deliberately not REQUIRE_THROWS: pybind may coerce or reject the value, and both are fine.
+ // What must hold is that the call is survivable — PluginLoader's guard turns a throw into
+ // "no custom UI".
try {
(void) iface->has_config_ui();
} catch (const std::exception&) {
}
- // The capability is still usable afterwards: the bad hook cost it nothing but its own answer.
+ // The capability is still usable afterwards.
CHECK(iface->get_name() == "cap_a");
CHECK(iface->get_config_ui().empty());
}
diff --git a/tests/slic3rutils/test_plugin_config.cpp b/tests/slic3rutils/test_plugin_config.cpp
index 3ed3303882..4ee18e85b2 100644
--- a/tests/slic3rutils/test_plugin_config.cpp
+++ b/tests/slic3rutils/test_plugin_config.cpp
@@ -65,8 +65,7 @@ TEST_CASE("PluginConfig updates only the target capability's cap_config", "[Plug
ScopedDataDir data_dir_guard("plugin-config-isolation");
PluginConfig config;
- // Two capabilities in one plugin, plus a same-named capability in a different plugin: the
- // identity is the (plugin_key, capability) pair, so all three are separate records.
+ // The identity is the (plugin_key, capability) pair, so all three below are separate records.
REQUIRE(config.store_capability_config("plugin_a", "cap_a", json{{"value", 1}}));
REQUIRE(config.store_capability_config("plugin_a", "cap_b", json{{"value", 2}}));
REQUIRE(config.store_capability_config("plugin_b", "cap_a", json{{"value", 3}}));
@@ -116,9 +115,8 @@ TEST_CASE("PluginConfig keeps a capability's config after its plugin goes away",
REQUIRE(config.store_capability_config("plugin_a", "cap_a", json{{"token", "keep me"}}));
}
- // Nothing here installs, uninstalls or unsubscribes a plugin: config.json is deliberately not
- // keyed to installed plugins, so a record outlives the plugin and is still there when the user
- // reinstalls or resubscribes. This asserts no cleanup path silently drops it.
+ // config.json is deliberately not keyed to installed plugins: a record outlives its plugin and is
+ // still there on reinstall. Asserts no cleanup path silently drops it.
PluginConfig after_removal;
after_removal.load();
CHECK(after_removal.get_config("plugin_a", "cap_a").config == json{{"token", "keep me"}});