mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-15 23:12:08 +00:00
cleanup
This commit is contained in:
@@ -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(""));
|
||||
|
||||
|
||||
@@ -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<ConfigOptionStrings>();
|
||||
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<std::string>)) {
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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<std::string()> 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;
|
||||
|
||||
@@ -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<PluginConfigField*>(field.get()))
|
||||
if (auto config_group = dynamic_cast<ConfigOptionsGroup*>(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 {};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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<std::string> type_labels;
|
||||
std::vector<PluginChangelogView> 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<PluginCapabilityView> 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<PluginCapabilityIdentifier> 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");
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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<std::string, std::string> capability_identity(const PluginCapabilityInterface& capability, const char* api_name)
|
||||
{
|
||||
std::pair<std::string, std::string> id{capability.audit_plugin_key(), capability.audit_capability_name()};
|
||||
@@ -44,11 +42,11 @@ std::pair<std::string, std::string> 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::vector<PluginCapabi
|
||||
|
||||
nlohmann::json payload = nlohmann::json::array();
|
||||
for (const PluginCapabilityIdentifier& id : caps) {
|
||||
// Read has_config_ui off the live capability rather than trusting the caller's copy: a
|
||||
// capability that has been unloaded since the list was built has nothing to configure.
|
||||
// A capability unloaded since the list was built has nothing to configure.
|
||||
const auto capability = loader.get_plugin_capability_by_name(id);
|
||||
if (!capability)
|
||||
continue;
|
||||
@@ -243,9 +238,8 @@ nlohmann::json PluginConfig::capabilities_payload(const std::vector<PluginCapabi
|
||||
return payload;
|
||||
}
|
||||
|
||||
// Replies with one capability's stored config, plus the custom HTML UI when the capability provides
|
||||
// one. Config is sent as a JSON value, not text: the default editor pretty-prints it into its
|
||||
// textarea, and a custom UI receives it as-is through window.orca.
|
||||
// Config is sent as a JSON value, not text: the default editor pretty-prints it into its textarea,
|
||||
// and a custom UI receives it as-is through window.orca.
|
||||
nlohmann::json PluginConfig::get_config_response(const PluginCapabilityIdentifier& id)
|
||||
{
|
||||
nlohmann::json response;
|
||||
@@ -258,7 +252,7 @@ nlohmann::json PluginConfig::get_config_response(const PluginCapabilityIdentifie
|
||||
response["error"] = "";
|
||||
|
||||
// Scoped to the full identity, so a stale request from a page that has not caught up with a
|
||||
// refresh cannot read a different plugin's config — it just misses.
|
||||
// refresh misses rather than reading a different plugin's config.
|
||||
const auto cap = PluginManager::instance().get_loader().get_plugin_capability_by_name(id);
|
||||
if (!cap) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Ignoring config request for a capability that is no longer loaded. plugin_key="
|
||||
@@ -270,9 +264,8 @@ nlohmann::json PluginConfig::get_config_response(const PluginCapabilityIdentifie
|
||||
response["config"] = PluginManager::instance().get_config().get_config(id.plugin_key, id.name).config;
|
||||
|
||||
if (cap->has_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<std::string>(), 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;
|
||||
|
||||
@@ -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<PluginCapabilityIdentifier>& 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
|
||||
|
||||
@@ -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<Preset::Type, std::unordered_map<std::string, MissingPlugin>> 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<PluginCapabilityRef> 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<PluginCapabilityRef> 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<PluginCapabilityIdentifier>& 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<PluginCapabilityIdentifier> 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<bool, bool> 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<std::string>& refs, PluginInstallProgress progress)
|
||||
{
|
||||
// Collect the unique cloud UUIDs to install; local refs are handled via the browser flow.
|
||||
std::vector<std::string> 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<std::string>& 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<std::string>& 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<std::string, std::vector<std::string>> 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<std::string>& 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<std::pair<std::string, std::vector<std::string>>> 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<std::string>& refs)
|
||||
|
||||
void open_missing_plugins_on_cloud(const std::vector<std::string>& 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);
|
||||
|
||||
@@ -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<MissingPlugin> get_missing_cloud_plugins();
|
||||
std::vector<MissingPlugin> 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<MissingPlugin> 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<MissingPlugin> 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<void()> 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<std::string>& 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<std::string>& 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<std::string>& 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<PluginCapabilityRef> referenced_capabilities(Preset::Type type, const Preset& preset);
|
||||
std::vector<PluginCapabilityIdentifier> 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<PluginCapabilityIdentifier> capabilities_in_use(const PresetBundle& preset_bundle, Preset::Type type);
|
||||
|
||||
bool check_capability_in_use(const std::string& capability_refs);
|
||||
|
||||
@@ -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<const GUI::GUI_App*>(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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<PluginCapabi
|
||||
public:
|
||||
using PyPluginCommonTrampoline<PluginCapabilityInterface>::PyPluginCommonTrampoline;
|
||||
|
||||
// get_name is implemented in PyPluginCommonTrampoline (PYBIND11_OVERRIDE_PURE).
|
||||
|
||||
PluginCapabilityType get_type() const override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user