Published 3MF: import Full Publish materials as standalone detached presets inside the project

This commit is contained in:
Lam Wei Lun
2026-08-25 12:27:45 +08:00
parent 3ed1f64f2e
commit 52aa5a52e9
5 changed files with 259 additions and 8 deletions
+75
View File
@@ -3053,6 +3053,81 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det
this->get_selected_preset().save(nullptr);
}
// A detached standalone preset for the Full Publish receiver: create a user preset holding
// the full resolved filament config (no inheritance, no vendor/alias links), parentless and
// universally compatible. Mirrors save_current_preset(detach=true)'s creation branch but
// does not force-select or diff against a parent; the caller decides whether to select it.
// The published entry's filament_id is forwarded so user bases keep their stable
// material grouping (get_filament_presets() groups user bases by filament_id).
// save_to_project=true (the Full Publish default) creates a project-embedded preset:
// it lives inside the loaded project only (serialized into the saved .3mf, restored by
// load_project_embedded_presets) and never touches the user's library directory;
// Preset::save() early-returns for embedded presets, so persistence is skipped here too.
// Returns the final (uniquified) name; on collision "<base>" -> "<base> (Published)" ->
// "<base> (Published 2)" ...
std::string PresetCollection::add_detached_preset(const std::string &name_base, DynamicPrintConfig config,
const std::string &filament_id, bool save_to_project)
{
if (name_base.empty())
return std::string();
Preset stored(m_type, name_base);
stored.config = std::move(config);
stored.filament_id = filament_id;
// Uniquify verbatim; only on collision append " (Published)" then " (Published 2)".
const std::string base_name = name_base;
std::string final_name = base_name;
auto exists = [this](const std::string &candidate) -> bool {
const auto it = this->find_preset_internal(candidate);
return it != m_presets.end() && it->name == candidate;
};
if (exists(final_name)) {
final_name = base_name + " (Published)";
for (int i = 2; exists(final_name); ++i)
final_name = base_name + " (Published " + std::to_string(i) + ")";
}
// Creation branch of save_current_preset(detach=true), without its selection side
// effects or project-embedded path.
lock();
const auto it = this->find_preset_internal(final_name);
Preset &preset = *m_presets.insert(it, stored);
stored.name.clear(); // avoid stale copied name being used below
stored.config.clear();
preset.name = final_name;
preset.vendor = nullptr;
preset.alias.clear();
preset.renamed_from.clear();
preset.m_excluded_from.clear();
preset.setting_id.clear();
preset.inherits().clear();
preset.version = Semver::parse(SoftFever_VERSION) ? *Semver::parse(SoftFever_VERSION) : Semver();
preset.is_default = false;
preset.is_system = false;
preset.is_external = false;
preset.bundle_id.clear();
preset.file = this->path_for_preset(preset);
preset.is_visible = true;
preset.is_project_embedded = save_to_project;
if (m_type == Preset::TYPE_PRINT)
preset.config.option<ConfigOptionString>("print_settings_id", true)->value = final_name;
else if (m_type == Preset::TYPE_FILAMENT)
preset.config.option<ConfigOptionStrings>("filament_settings_id", true)->values[0] = final_name;
else if (m_type == Preset::TYPE_PRINTER)
preset.config.option<ConfigOptionString>("printer_settings_id", true)->value = final_name;
unlock();
if (!save_to_project) {
// Persist the full resolved config (no parent). Project-embedded presets are
// serialized into the .3mf instead; Preset::save() would early-return anyway.
// find by final_name — m_presets may have reallocated, so don't keep a raw ref.
auto persist_it = this->find_preset_internal(final_name);
if (persist_it != m_presets.end() && persist_it->name == final_name)
persist_it->save(nullptr);
}
return final_name;
}
bool PresetCollection::delete_current_preset()
{
Preset &selected = this->get_selected_preset();