mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-10 10:47:16 +00:00
Bug fix for perfect name matching
This commit is contained in:
@@ -4894,11 +4894,19 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
// Mirror first_visible_idx()'s start index so suppressed default presets are
|
||||
// never picked as a slot material.
|
||||
const size_t first_candidate = this->filaments.is_default_suppressed() ? this->filaments.num_default_presets() : 0;
|
||||
// Candidate preference for a published entry: exact setting_id (variant-level,
|
||||
// since "Generic PLA" and "Generic PLA Matte" share filament_id), then exact
|
||||
// filament_id, then vendor+type, then type only (a type-only pick may surface an
|
||||
// unrelated preset, e.g. a different vendor's PLA).
|
||||
// Candidate preference for a published entry: exact preset name (unambiguous
|
||||
// even when ids are shared between variants or missing from older files), then
|
||||
// exact setting_id (variant-level, since "Generic PLA" and "Generic PLA Matte"
|
||||
// share filament_id), then exact filament_id, then vendor+type, then type only
|
||||
// (a type-only pick may surface an unrelated preset, e.g. a different vendor's
|
||||
// PLA).
|
||||
auto candidate_score = [](const Preset &candidate, const PublishedMaterialEntry &entry) -> int {
|
||||
if (!entry.preset_name.empty()) {
|
||||
const std::string bare_name = entry.preset_name.substr(0, entry.preset_name.find('@'));
|
||||
if (candidate.name == entry.preset_name || candidate.alias == entry.preset_name ||
|
||||
candidate.name == bare_name || candidate.alias == bare_name)
|
||||
return 4;
|
||||
}
|
||||
if (!entry.setting_id.empty() && candidate.setting_id == entry.setting_id)
|
||||
return 3;
|
||||
const ConfigOptionStrings *types = candidate.config.opt<ConfigOptionStrings>("filament_type");
|
||||
@@ -4926,10 +4934,13 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
int best_score = -1;
|
||||
for (size_t i = first_candidate; i < this->filaments.size(); ++i) {
|
||||
const Preset &candidate = this->filaments.preset(i);
|
||||
if (!candidate.is_visible || used_preset_names.count(candidate.name) != 0)
|
||||
continue;
|
||||
const int score = candidate_score(candidate, entry);
|
||||
if (score > best_score) {
|
||||
// Exact identity tiers (name / setting_id) win even when the
|
||||
// preset is hidden or already referenced by another slot; the
|
||||
// alias re-pointing pass below still de-aliases afterwards.
|
||||
if (score < 3 && (!candidate.is_visible || used_preset_names.count(candidate.name) != 0))
|
||||
continue;
|
||||
best_score = score;
|
||||
initial_preset = candidate.name;
|
||||
}
|
||||
@@ -4979,9 +4990,13 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
continue;
|
||||
for (size_t i = first_candidate; i < this->filaments.size(); ++i) {
|
||||
const Preset &candidate = this->filaments.preset(i);
|
||||
if (!candidate.is_visible || referenced_elsewhere(candidate.name, size_t(-1)))
|
||||
continue;
|
||||
const int score = candidate_score(candidate, entry);
|
||||
// Exact identity tiers (name / setting_id) may use a hidden preset;
|
||||
// the referenced check stays: re-pointing exists to de-alias.
|
||||
if (score < 3 && !candidate.is_visible)
|
||||
continue;
|
||||
if (referenced_elsewhere(candidate.name, size_t(-1)))
|
||||
continue;
|
||||
if (score > best_score) {
|
||||
best_score = score;
|
||||
replacement = candidate.name;
|
||||
@@ -5106,11 +5121,14 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
std::string best_name;
|
||||
for (size_t i = first_candidate; i < this->filaments.size(); ++i) {
|
||||
const Preset &candidate = this->filaments.preset(i);
|
||||
if (!candidate.is_visible)
|
||||
continue;
|
||||
const int score = candidate_score(candidate, entry);
|
||||
if (score <= best_score)
|
||||
continue;
|
||||
// Lower tiers (vendor+type, type only) need a visible preset;
|
||||
// an exact identity match (name / setting_id) wins even when
|
||||
// the preset is hidden in the library.
|
||||
if (score < 3 && !candidate.is_visible)
|
||||
continue;
|
||||
if (unreferenced_only) {
|
||||
bool used = false;
|
||||
for (size_t s = 0; s < this->filament_presets.size(); ++s)
|
||||
|
||||
@@ -40,6 +40,10 @@ struct PublishedMaterialEntry {
|
||||
// used on load to match the exact published variant, which filament_id alone cannot
|
||||
// distinguish ("Generic PLA" and "Generic PLA Matte" share their inherited id).
|
||||
std::string setting_id;
|
||||
// Canonical name of the author's slot preset (e.g. "Generic PLA @System"). The receiver
|
||||
// prefers an exact name/alias match over id matching: ids can be shared across variants
|
||||
// or missing from older files, the name is what the author actually selected.
|
||||
std::string preset_name;
|
||||
// 0-based author filament slot; -1 (hand-crafted files) is skipped.
|
||||
int slot{-1};
|
||||
std::vector<std::string> keys;
|
||||
|
||||
@@ -7252,6 +7252,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
entry.filament_id = mat["filament_id"].get<std::string>();
|
||||
if (mat.contains("setting_id") && mat["setting_id"].is_string())
|
||||
entry.setting_id = mat["setting_id"].get<std::string>();
|
||||
if (mat.contains("name") && mat["name"].is_string())
|
||||
entry.preset_name = mat["name"].get<std::string>();
|
||||
}
|
||||
if (m.contains("slot") && m["slot"].is_number_integer())
|
||||
entry.slot = m["slot"].get<int>();
|
||||
@@ -16251,7 +16253,7 @@ int Plater::export_published_3mf(const std::vector<std::string>& published_keys,
|
||||
j.push_back(key);
|
||||
nlohmann::json jm = nlohmann::json::array();
|
||||
for (const Slic3r::PublishedMaterialEntry& e : material_keys)
|
||||
jm.push_back({ {"material", {{"filament_type", e.filament_type}, {"filament_vendor", e.filament_vendor}, {"filament_id", e.filament_id}, {"setting_id", e.setting_id}}}, {"slot", e.slot}, {"keys", e.keys},
|
||||
jm.push_back({ {"material", {{"filament_type", e.filament_type}, {"filament_vendor", e.filament_vendor}, {"filament_id", e.filament_id}, {"setting_id", e.setting_id}, {"name", e.preset_name}}}, {"slot", e.slot}, {"keys", e.keys},
|
||||
{"full", e.full}, {"full_keys", e.full_keys},
|
||||
{"publish_type", e.publish_type}, {"type", e.publish_type_value},
|
||||
{"publish_color", e.publish_color}, {"color", e.color} });
|
||||
|
||||
@@ -924,11 +924,14 @@ std::vector<Slic3r::PublishedMaterialEntry> PublishSettingsDialog::GetPublishedM
|
||||
entry.filament_id = cat.filament_id;
|
||||
entry.slot = static_cast<int>(cat.filament_slot);
|
||||
// The author's preset id distinguishes exact variants that share filament_id
|
||||
// ("Generic PLA" vs "Generic PLA Matte"), so the receiver can match precisely.
|
||||
// ("Generic PLA" vs "Generic PLA Matte"), so the receiver can match precisely; the
|
||||
// preset name is the most direct identity and is matched first on load.
|
||||
PresetBundle *bundle = wxGetApp().preset_bundle;
|
||||
if (bundle != nullptr && cat.filament_slot < bundle->filament_presets.size()) {
|
||||
if (const Preset *preset = bundle->filaments.find_preset(bundle->filament_presets[cat.filament_slot], false, true))
|
||||
entry.setting_id = preset->setting_id;
|
||||
if (const Preset *preset = bundle->filaments.find_preset(bundle->filament_presets[cat.filament_slot], false, true)) {
|
||||
entry.setting_id = preset->setting_id;
|
||||
entry.preset_name = preset->name;
|
||||
}
|
||||
}
|
||||
// "Full Publish": the whole filament preset is embedded; type and colour are implicitly
|
||||
// published, and the per-key rows are disabled / their state ignored.
|
||||
|
||||
@@ -684,7 +684,7 @@ SCENARIO("Minimal published 3MF serialization filters config and omits embedded
|
||||
|
||||
const std::vector<std::string> published_keys = { "layer_height", "retraction_length" };
|
||||
const std::vector<PublishedMaterialEntry> material_keys = {
|
||||
{ "PLA", "Generic", "GFL99", "", 0, { "filament_retraction_length" } }
|
||||
{ "PLA", "Generic", "GFL99", "", "Generic PLA", 0, { "filament_retraction_length" } }
|
||||
};
|
||||
|
||||
DynamicPrintConfig filtered_cfg = filter_published_config(full_cfg, published_keys, material_keys);
|
||||
@@ -792,7 +792,7 @@ SCENARIO("Published 3MF round-trips the extended material metadata", "[3mf]") {
|
||||
model.add_default_instances();
|
||||
|
||||
const std::string material_keys_json =
|
||||
R"([{"material":{"filament_type":"PLA","filament_vendor":"Generic","filament_id":"GFL99","setting_id":"RFs9eCKYOMUSmvZf"},"slot":1,"keys":[],"full":true,"full_keys":["filament_retraction_length","filament_colour"],"publish_type":true,"type":"PLA","publish_color":false,"color":""}])";
|
||||
R"([{"material":{"filament_type":"PLA","filament_vendor":"Generic","filament_id":"GFL99","setting_id":"RFs9eCKYOMUSmvZf","name":"Generic PLA Matte @System"},"slot":1,"keys":[],"full":true,"full_keys":["filament_retraction_length","filament_colour"],"publish_type":true,"type":"PLA","publish_color":false,"color":""}])";
|
||||
|
||||
model.model_info = std::make_shared<ModelInfo>();
|
||||
model.model_info->metadata_items["published_material_keys"] = material_keys_json;
|
||||
|
||||
@@ -942,6 +942,205 @@ TEST_CASE("Published 3MF replaces a mismatched slot with the exact published mat
|
||||
CHECK(pub.skipped_keys.empty());
|
||||
}
|
||||
|
||||
// The author's preset name travels in the file, so a type mismatch resolves to the exact
|
||||
// published material even when the identity fields are absent or stale (older files): a
|
||||
// "Generic PLA" author must land on the receiver's "Generic PLA", never on a same-type
|
||||
// substitute like "Bambu PLA Basic".
|
||||
TEST_CASE("Published 3MF replaces a mismatched slot with the exact preset by name", "[Preset][Bundle][Published]")
|
||||
{
|
||||
auto make_file_config = [] {
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.opt<ConfigOptionFloats>("filament_diameter")->values = { 1.75 };
|
||||
config.opt<ConfigOptionInts>("filament_self_index")->values = { 1 };
|
||||
config.opt<ConfigOptionStrings>("filament_extruder_variant")->values = { "Direct Drive Standard" };
|
||||
config.opt<ConfigOptionStrings>("filament_colour")->values = { "#FF0000" };
|
||||
config.opt<ConfigOptionStrings>("filament_type")->values = { "PLA" };
|
||||
config.opt<ConfigOptionStrings>("filament_vendor")->values = { "Generic" };
|
||||
config.opt<ConfigOptionStrings>("filament_ids")->values = { "OGFL99" };
|
||||
config.option<ConfigOptionFloatsNullable>("filament_retraction_length", true)->values = { 0.9 };
|
||||
return config;
|
||||
};
|
||||
|
||||
auto add_pla = [](PresetBundle &bundle, const char *name, const char *id, const char *vendor, const char *setting_id) {
|
||||
Preset &preset = add_inmemory_preset(bundle.filaments, name);
|
||||
preset.filament_id = id;
|
||||
preset.setting_id = setting_id;
|
||||
preset.config.opt_string("filament_type", 0u) = "PLA";
|
||||
preset.config.opt_string("filament_vendor", 0u) = vendor;
|
||||
preset.config.opt<ConfigOptionFloatsNullable>("filament_retraction_length", true)->values = { 0.5 };
|
||||
return &preset;
|
||||
};
|
||||
|
||||
SECTION("full identity (name, setting_id, filament_id): the name match wins") {
|
||||
PresetBundle bundle;
|
||||
Preset &petg = add_inmemory_preset(bundle.filaments, "My PETG");
|
||||
petg.config.opt_string("filament_type", 0u) = "PETG";
|
||||
petg.config.opt<ConfigOptionFloatsNullable>("filament_retraction_length", true)->values = { 0.6 };
|
||||
add_pla(bundle, "Generic PLA @System", "OGFL99", "Generic", "RcBNzytWgwRrwXXz");
|
||||
add_pla(bundle, "Bambu PLA Basic @System", "OGFA00", "Bambu Lab", "zkc85XTKi4cb6cOw");
|
||||
bundle.filament_presets = { "My PETG" };
|
||||
|
||||
PublishedMaterialEntry entry;
|
||||
entry.slot = 0;
|
||||
entry.full = true;
|
||||
entry.publish_type = true;
|
||||
entry.publish_type_value = "PLA";
|
||||
entry.filament_id = "OGFL99";
|
||||
entry.filament_vendor = "Generic";
|
||||
entry.setting_id = "RcBNzytWgwRrwXXz";
|
||||
entry.preset_name = "Generic PLA @System";
|
||||
entry.full_keys = { "filament_retraction_length" };
|
||||
|
||||
PublishedConfig pub;
|
||||
pub.published = true;
|
||||
pub.material_keys = { entry };
|
||||
DynamicPrintConfig config = make_file_config();
|
||||
Preset::normalize(config);
|
||||
bundle.load_config_model("test.3mf", std::move(config), Semver(), &pub);
|
||||
|
||||
CHECK(bundle.filament_presets[0] == "Generic PLA @System");
|
||||
CHECK(bundle.filaments.find_preset("Generic PLA @System", false, true)->config.opt<ConfigOptionFloatsNullable>("filament_retraction_length")->values == std::vector<double>{ 0.9 });
|
||||
CHECK(bundle.filaments.find_preset("My PETG", false, true)->config.opt<ConfigOptionFloatsNullable>("filament_retraction_length")->values == std::vector<double>{ 0.6 });
|
||||
REQUIRE(pub.material_replacements.size() == 1);
|
||||
CHECK(pub.material_replacements[0] == "slot 0: My PETG -> Generic PLA @System");
|
||||
CHECK(pub.skipped_keys.empty());
|
||||
}
|
||||
|
||||
SECTION("only the name is present (broken/stale ids): still the exact preset") {
|
||||
PresetBundle bundle;
|
||||
Preset &petg = add_inmemory_preset(bundle.filaments, "My PETG");
|
||||
petg.config.opt_string("filament_type", 0u) = "PETG";
|
||||
petg.config.opt<ConfigOptionFloatsNullable>("filament_retraction_length", true)->values = { 0.6 };
|
||||
add_pla(bundle, "Generic PLA @System", "OGFL99", "Generic", "RcBNzytWgwRrwXXz");
|
||||
add_pla(bundle, "Bambu PLA Basic @System", "OGFA00", "Bambu Lab", "zkc85XTKi4cb6cOw");
|
||||
bundle.filament_presets = { "My PETG" };
|
||||
|
||||
PublishedMaterialEntry entry;
|
||||
entry.slot = 0;
|
||||
entry.full = true;
|
||||
entry.publish_type = true;
|
||||
entry.publish_type_value = "PLA";
|
||||
entry.preset_name = "Generic PLA @System";
|
||||
entry.full_keys = { "filament_retraction_length" };
|
||||
|
||||
PublishedConfig pub;
|
||||
pub.published = true;
|
||||
pub.material_keys = { entry };
|
||||
DynamicPrintConfig config = make_file_config();
|
||||
Preset::normalize(config);
|
||||
bundle.load_config_model("test.3mf", std::move(config), Semver(), &pub);
|
||||
|
||||
CHECK(bundle.filament_presets[0] == "Generic PLA @System");
|
||||
CHECK(bundle.filaments.find_preset("Bambu PLA Basic @System", false, true)->config.opt<ConfigOptionFloatsNullable>("filament_retraction_length")->values == std::vector<double>{ 0.5 });
|
||||
REQUIRE(pub.material_replacements.size() == 1);
|
||||
CHECK(pub.material_replacements[0] == "slot 0: My PETG -> Generic PLA @System");
|
||||
CHECK(pub.skipped_keys.empty());
|
||||
}
|
||||
|
||||
SECTION("the exact preset is hidden in the library: the exact match still wins") {
|
||||
PresetBundle bundle;
|
||||
Preset &petg = add_inmemory_preset(bundle.filaments, "My PETG");
|
||||
petg.config.opt_string("filament_type", 0u) = "PETG";
|
||||
petg.config.opt<ConfigOptionFloatsNullable>("filament_retraction_length", true)->values = { 0.6 };
|
||||
Preset &generic_pla = *add_pla(bundle, "Generic PLA @System", "OGFL99", "Generic", "RcBNzytWgwRrwXXz");
|
||||
generic_pla.is_visible = false;
|
||||
add_pla(bundle, "Bambu PLA Basic @System", "OGFA00", "Bambu Lab", "zkc85XTKi4cb6cOw");
|
||||
bundle.filament_presets = { "My PETG" };
|
||||
|
||||
PublishedMaterialEntry entry;
|
||||
entry.slot = 0;
|
||||
entry.full = true;
|
||||
entry.publish_type = true;
|
||||
entry.publish_type_value = "PLA";
|
||||
entry.preset_name = "Generic PLA @System";
|
||||
entry.full_keys = { "filament_retraction_length" };
|
||||
|
||||
PublishedConfig pub;
|
||||
pub.published = true;
|
||||
pub.material_keys = { entry };
|
||||
DynamicPrintConfig config = make_file_config();
|
||||
Preset::normalize(config);
|
||||
bundle.load_config_model("test.3mf", std::move(config), Semver(), &pub);
|
||||
|
||||
CHECK(bundle.filament_presets[0] == "Generic PLA @System");
|
||||
CHECK(pub.skipped_keys.empty());
|
||||
}
|
||||
|
||||
SECTION("grown slot (author slot 1) is seeded with the exact preset by name") {
|
||||
auto two_slot_config = [] {
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.opt<ConfigOptionFloats>("filament_diameter")->values = { 1.75, 1.75 };
|
||||
config.opt<ConfigOptionInts>("filament_self_index")->values = { 1, 2 };
|
||||
config.opt<ConfigOptionStrings>("filament_extruder_variant")->values = { "Direct Drive Standard", "Direct Drive Standard" };
|
||||
config.opt<ConfigOptionStrings>("filament_colour")->values = { "#FF0000", "#FFFF00" };
|
||||
config.opt<ConfigOptionStrings>("filament_type")->values = { "PLA", "PLA" };
|
||||
config.opt<ConfigOptionStrings>("filament_vendor")->values = { "Generic", "Generic" };
|
||||
config.opt<ConfigOptionStrings>("filament_ids")->values = { "OGFL99", "OGFL99" };
|
||||
config.option<ConfigOptionFloatsNullable>("filament_retraction_length", true)->values = { 0.9, 0.8 };
|
||||
return config;
|
||||
};
|
||||
|
||||
PresetBundle bundle;
|
||||
Preset &petg = add_inmemory_preset(bundle.filaments, "My PETG");
|
||||
petg.config.opt_string("filament_type", 0u) = "PETG";
|
||||
petg.config.opt<ConfigOptionFloatsNullable>("filament_retraction_length", true)->values = { 0.6 };
|
||||
add_pla(bundle, "Generic PLA @System", "OGFL99", "Generic", "RcBNzytWgwRrwXXz");
|
||||
add_pla(bundle, "Bambu PLA Basic @System", "OGFA00", "Bambu Lab", "zkc85XTKi4cb6cOw");
|
||||
bundle.filament_presets = { "My PETG" };
|
||||
|
||||
PublishedMaterialEntry entry;
|
||||
entry.slot = 1;
|
||||
entry.full = true;
|
||||
entry.publish_type = true;
|
||||
entry.publish_type_value = "PLA";
|
||||
entry.preset_name = "Generic PLA @System";
|
||||
entry.full_keys = { "filament_retraction_length" };
|
||||
|
||||
PublishedConfig pub;
|
||||
pub.published = true;
|
||||
pub.material_keys = { entry };
|
||||
DynamicPrintConfig config = two_slot_config();
|
||||
Preset::normalize(config);
|
||||
bundle.load_config_model("test.3mf", std::move(config), Semver(), &pub);
|
||||
|
||||
REQUIRE(bundle.filament_presets.size() == 2);
|
||||
CHECK(bundle.filament_presets[0] == "My PETG");
|
||||
CHECK(bundle.filament_presets[1] == "Generic PLA @System");
|
||||
// The full dump applies the author's slot-1 value onto the grown slot's preset.
|
||||
CHECK(bundle.filaments.find_preset("Generic PLA @System", false, true)->config.opt<ConfigOptionFloatsNullable>("filament_retraction_length")->values == std::vector<double>{ 0.8 });
|
||||
CHECK(pub.skipped_keys.empty());
|
||||
}
|
||||
|
||||
SECTION("no exact preset in the library: falls back to the substitute") {
|
||||
PresetBundle bundle;
|
||||
Preset &petg = add_inmemory_preset(bundle.filaments, "My PETG");
|
||||
petg.config.opt_string("filament_type", 0u) = "PETG";
|
||||
petg.config.opt<ConfigOptionFloatsNullable>("filament_retraction_length", true)->values = { 0.6 };
|
||||
add_pla(bundle, "Bambu PLA Basic @System", "OGFA00", "Bambu Lab", "zkc85XTKi4cb6cOw");
|
||||
bundle.filament_presets = { "My PETG" };
|
||||
|
||||
PublishedMaterialEntry entry;
|
||||
entry.slot = 0;
|
||||
entry.full = true;
|
||||
entry.publish_type = true;
|
||||
entry.publish_type_value = "PLA";
|
||||
entry.preset_name = "Generic PLA @System";
|
||||
entry.filament_id = "OGFL99";
|
||||
entry.full_keys = { "filament_retraction_length" };
|
||||
|
||||
PublishedConfig pub;
|
||||
pub.published = true;
|
||||
pub.material_keys = { entry };
|
||||
DynamicPrintConfig config = make_file_config();
|
||||
Preset::normalize(config);
|
||||
bundle.load_config_model("test.3mf", std::move(config), Semver(), &pub);
|
||||
|
||||
CHECK(bundle.filament_presets[0] == "Bambu PLA Basic @System");
|
||||
REQUIRE(pub.material_replacements.size() == 1);
|
||||
CHECK(pub.material_replacements[0] == "slot 0: My PETG -> Bambu PLA Basic @System (substitute: no exact material match)");
|
||||
}
|
||||
}
|
||||
|
||||
// The replacement search prefers the published identity: exact filament_id, then vendor+type,
|
||||
// then type only (collection order decides equal scores; the pick is reported as a substitute
|
||||
// when it is not the exact published material).
|
||||
|
||||
Reference in New Issue
Block a user