diff --git a/.claude/skills/orca-profiles/references/extruder-variants.md b/.claude/skills/orca-profiles/references/extruder-variants.md index 5059fd5c6b..633443cbab 100644 --- a/.claude/skills/orca-profiles/references/extruder-variants.md +++ b/.claude/skills/orca-profiles/references/extruder-variants.md @@ -105,11 +105,12 @@ Rules: `single_extruder_multi_material` off, and in the app when the printer tab loads a printer with a different number of extruders, the pair is rebuilt from `extruder_variant_list` (one `Direct Drive Standard` per extruder when the list is absent) and the variant arrays are resized to - the rebuilt pair, padded with their first value or cut. The resize skips the `machine_max_*` limits - and `hotend_heating_rate` / `hotend_cooling_rate`: they keep their width, and an extruder beyond it - reads their first value, so extruder 2 and up of a list-less printer take extruder 1's normal limit - as their silent one too. With the three in agreement that changes nothing; a pair written without - the list is replaced. A listed variant the pair lacks is a menu choice that reads variant index 0. + the rebuilt pair, padded with their first value or cut. The `machine_max_*` limits are padded the + same way, so extruder 2 and up of a list-less printer take extruder 1's normal limit as their + silent one too. The resize skips `hotend_heating_rate` / `hotend_cooling_rate`: they keep their + width, and an extruder beyond it reads their first value. With the three in agreement that changes + nothing; a pair written without the list is replaced. A listed variant the pair lacks is a menu + choice that reads variant index 0. - The pair without `extruder_variant_list` slices, but the sidebar offers no variant switch and the app cannot add variants to a list-less process: nothing is lost while every extruder has exactly one variant, and every further variant is unreachable. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 21e1b0fbc3..cd60a8f471 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -10030,48 +10030,13 @@ static void extend_extruder_variant(DynamicPrintConfig& config, const unsigned i printer_extruder_variant_opt->values.insert(printer_extruder_variant_opt->values.end(), variants_list.begin(), variants_list.end()); } } -} -// Options in printer_options_with_variant_2 are stored as (normal,silent) pairs per printer variant. -// Some legacy presets/projects carry a variant list but still store only one pair; normalize to avoid crashes. -static void normalize_stride2_floats(ConfigOptionFloats &opt, size_t expected_size) -{ - auto &v = opt.values; - if (expected_size == 0) { - v.clear(); - return; - } - if (v.empty()) { - // Fallback: keep behavior predictable instead of crashing. This should be rare. - v.resize(expected_size, 0.0); - return; - } - - const double first = v[0]; - const double second = (v.size() >= 2) ? v[1] : first; - - // Ensure we have at least one (normal,silent) pair to replicate. - if (v.size() < 2) { - v.resize(2, first); - v[1] = second; - } - // Keep pair alignment if some legacy preset produced odd length. - if (v.size() % 2 != 0) - v.push_back(second); - - if (v.size() > expected_size) { - v.resize(expected_size); - return; - } - - const size_t have_variants = v.size() / 2; - const size_t want_variants = expected_size / 2; - v.resize(expected_size); - for (size_t vi = have_variants; vi < want_variants; ++vi) { - v[vi * 2] = first; - if (vi * 2 + 1 < v.size()) - v[vi * 2 + 1] = second; - } + // 3. Size the machine limits to the rebuilt variants, padded with their first value like the other variant keys. + // They are not extruder option keys, so the resize loop in set_num_extruders skips them. + const auto &defaults = FullPrintConfig::defaults(); + for (const std::string &key : printer_options_with_variant_2) + if (auto *opt = config.option(key)) + opt->resize(config.get_parameter_size(key, num_extruders), defaults.option(key)); } void DynamicPrintConfig::set_num_extruders(unsigned int num_extruders) @@ -10091,11 +10056,6 @@ void DynamicPrintConfig::set_num_extruders(unsigned int num_extruders) static_cast(opt)->resize(get_parameter_size(key, num_extruders), defaults.option(key)); } } - // Orca: machine limits are not extruder option keys and need a pair-aware resize, so that every - // printer variant has its own (normal, silent) pair to edit. - for (const std::string &key : printer_options_with_variant_2) - if (auto *opt = this->option(key)) - normalize_stride2_floats(*opt, get_parameter_size(key, num_extruders)); } // BBS @@ -11509,6 +11469,48 @@ void DynamicPrintConfig::update_filament_config_values_for_multiple_extruders(Dy } namespace { +// Options in printer_options_with_variant_2 are stored as (normal,silent) pairs per printer variant. +// Some legacy presets/projects carry a variant list but still store only one pair; normalize to avoid crashes. +static void normalize_stride2_floats(ConfigOptionFloats &opt, size_t expected_size) +{ + auto &v = opt.values; + if (expected_size == 0) { + v.clear(); + return; + } + if (v.empty()) { + // Fallback: keep behavior predictable instead of crashing. This should be rare. + v.resize(expected_size, 0.0); + return; + } + + const double first = v[0]; + const double second = (v.size() >= 2) ? v[1] : first; + + // Ensure we have at least one (normal,silent) pair to replicate. + if (v.size() < 2) { + v.resize(2, first); + v[1] = second; + } + // Keep pair alignment if some legacy preset produced odd length. + if (v.size() % 2 != 0) + v.push_back(second); + + if (v.size() > expected_size) { + v.resize(expected_size); + return; + } + + const size_t have_variants = v.size() / 2; + const size_t want_variants = expected_size / 2; + v.resize(expected_size); + for (size_t vi = have_variants; vi < want_variants; ++vi) { + v[vi * 2] = first; + if (vi * 2 + 1 < v.size()) + v[vi * 2 + 1] = second; + } +} + static void log_normalize_legacy_vector_size(const char *fn, const std::string &key, int stride, size_t src_size, size_t dest_size, size_t expected_size, size_t restore_n, int cur_variant_count, int target_variant_count, size_t cur_ids, size_t target_ids, const ConfigOption *opt_src, const ConfigOption *opt_target) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 93fc22ae53..5e5761c6fe 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1161,9 +1161,8 @@ std::string Tab::options_list_storage_key(const std::string& opt_key) const // Orca: deep_diff() flags every vector entry at or past the reference vector's length as changed, // whatever its value (e.g. the values of an extruder added by raising the extruder count). A vector -// grows by copying its first entry (ConfigOptionVector::resize), and machine limits by copying their -// first (normal, silent) pair (set_num_extruders), so such an entry only counts as changed when it -// differs from the reference entry it was copied from, as before Orca's deep_diff() change. +// grows by copying its first entry (ConfigOptionVector::resize), so such an entry only counts as +// changed when it differs from the reference's first entry, as before Orca's deep_diff() change. // The change of the count itself shows on "extruders_count". static void drop_unchanged_added_entries(std::vector &options, const DynamicPrintConfig ¤t, const Preset *reference) { @@ -1172,7 +1171,7 @@ static void drop_unchanged_added_entries(std::vector &options, cons // deep_diff() lists a key's entries one after another, so serialize each key's vectors once. std::string serialized_key; std::vector cur_values; - std::vector ref_values; + std::string ref_first; options.erase(std::remove_if(options.begin(), options.end(), [&](const std::string &opt) { const auto pos = opt.find('#'); if (pos == std::string::npos) @@ -1186,10 +1185,9 @@ static void drop_unchanged_added_entries(std::vector &options, cons if (key != serialized_key) { serialized_key = key; cur_values = cur->vserialize(); - ref_values = ref->vserialize(); + ref_first = ref->vserialize().front(); } - const size_t copied_from = printer_options_with_variant_2.count(key) > 0 && idx % 2 < ref_values.size() ? idx % 2 : 0; - return cur_values[idx] == ref_values[copied_from]; + return cur_values[idx] == ref_first; }), options.end()); } diff --git a/tests/libslic3r/test_config_variant_expansion.cpp b/tests/libslic3r/test_config_variant_expansion.cpp index 95e94fa117..40fdce384a 100644 --- a/tests/libslic3r/test_config_variant_expansion.cpp +++ b/tests/libslic3r/test_config_variant_expansion.cpp @@ -807,11 +807,11 @@ TEST_CASE("set_num_extruders gives every printer variant its own pair of machine DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); auto speed_x = [&config]() { return config.option("machine_max_speed_x")->values; }; - SECTION("a single pair is copied to every extruder") { + SECTION("a new extruder's pair is padded with the first value, like the other variant keys") { config.option("machine_max_speed_x")->values = {500., 200.}; config.set_num_extruders(3); REQUIRE(config.option("printer_extruder_variant")->size() == 3); - REQUIRE(speed_x() == std::vector({500., 200., 500., 200., 500., 200.})); + REQUIRE(speed_x() == std::vector({500., 200., 500., 500., 500., 500.})); } SECTION("per-extruder pairs are kept, and removing an extruder removes its pair") { @@ -822,8 +822,8 @@ TEST_CASE("set_num_extruders gives every printer variant its own pair of machine REQUIRE(speed_x() == std::vector({500., 200., 400., 150.})); } - SECTION("a printer with nozzle variants gets a pair per variant column") { - // 2 extruders x 2 variants = 4 columns + SECTION("a printer with nozzle variants gets a pair per variant") { + // 2 extruders x 2 variants each = 4 variants config.option("extruder_variant_list", true)->values = {"Direct Drive Standard,Direct Drive High Flow", "Direct Drive Standard,Direct Drive High Flow"}; const std::vector per_variant = {500., 200., 510., 210., 520., 220., 530., 230.}; @@ -833,7 +833,7 @@ TEST_CASE("set_num_extruders gives every printer variant its own pair of machine config.option("machine_max_speed_x")->values = {500., 200.}; config.set_num_extruders(2); - REQUIRE(speed_x() == std::vector({500., 200., 500., 200., 500., 200., 500., 200.})); + REQUIRE(speed_x() == std::vector({500., 200., 500., 500., 500., 500., 500., 500.})); } }