From 7e63fce706a5fabb5763bea804e7e81b71876c0f Mon Sep 17 00:00:00 2001 From: Argo <52103738+Argolein@users.noreply.github.com> Date: Sun, 15 Feb 2026 13:47:04 +0100 Subject: [PATCH] Fix 3MF import crash for silent-mode machine limits with legacy vector sizes (#12289) Normalize printer_options_with_variant_2 (stride=2) machine limit vectors during preset merge to handle legacy 3MF/projects that store only a single (normal,silent) pair despite multiple printer variants, preventing set_with_restore() size-mismatch crashes. Crash error message during 3MF import as project: image 3MF attached that causes the bug. [cube.3mf.zip](https://github.com/user-attachments/files/25318309/cube.3mf.zip) --- src/libslic3r/PrintConfig.cpp | 107 ++++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 4 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 4db7db98be..8762e6f574 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -9288,6 +9288,61 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen } } +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) +{ + BOOST_LOG_TRIVIAL(debug) << fn << ": normalizing legacy vector size for key '" << key << "'" + << " stride=" << stride << " src_size=" << src_size << " dest_size=" << dest_size << " expected=" << expected_size + << " restore_index.size=" << restore_n << " cur_variants=" << cur_variant_count << " target_variants=" << target_variant_count + << " cur_ids=" << cur_ids << " target_ids=" << target_ids << " cur_value=" << opt_src->serialize() + << " target_value=" << opt_target->serialize(); +} +} // namespace + void DynamicPrintConfig::update_non_diff_values_to_base_config(DynamicPrintConfig& new_config, const t_config_option_keys& keys, const std::set& different_keys, std::string extruder_id_name, std::string extruder_variant_name, std::set& key_set1, std::set& key_set2) { @@ -9310,7 +9365,10 @@ void DynamicPrintConfig::update_non_diff_values_to_base_config(DynamicPrintConfi variant_index.resize(target_variant_count, -1); if (cur_variant_count == 0) { - variant_index[0] = 0; + // Defensive: target_variant_count may be 0 if the preset doesn't carry extruder_variant_name. + // In that case keep variant_index empty and let the downstream size checks produce a useful error. + if (!variant_index.empty()) + variant_index[0] = 0; } else if ((cur_extruder_ids.size() > 0) && cur_variant_count != cur_extruder_ids.size()){ //should not happen @@ -9352,12 +9410,53 @@ void DynamicPrintConfig::update_non_diff_values_to_base_config(DynamicPrintConfi //nothing to do, keep the original one } else { - ConfigOptionVectorBase* opt_vec_src = static_cast(opt_src); - const ConfigOptionVectorBase* opt_vec_dest = static_cast(opt_target); int stride = 1; if (key_set2.find(opt) != key_set2.end()) stride = 2; - opt_vec_src->set_with_restore(opt_vec_dest, variant_index, stride); + + const size_t restore_n = variant_index.size(); + const size_t expected_size = restore_n * size_t(stride); + + if (stride == 2) { + // Options in key_set2 are machine limits stored as (normal,silent) pairs per printer variant. + if (opt_src->type() != coFloats || opt_target->type() != coFloats) + throw ConfigurationError((boost::format("%1%: key '%2%' is expected to be ConfigOptionFloats for stride=2.") % __FUNCTION__ % opt).str()); + + auto *src_f = static_cast(opt_src); + ConfigOptionFloats rhs_tmp(*static_cast(opt_target)); + + const size_t src_size = src_f->values.size(); + const size_t dest_size = rhs_tmp.values.size(); + if (src_size != expected_size || dest_size != expected_size) + log_normalize_legacy_vector_size(__FUNCTION__, opt, stride, src_size, dest_size, expected_size, restore_n, cur_variant_count, + target_variant_count, cur_extruder_ids.size(), target_extruder_ids.size(), opt_src, opt_target); + + // Normalize src in-place so backup_values indexing is safe, normalize rhs via a temporary copy. + normalize_stride2_floats(*src_f, expected_size); + normalize_stride2_floats(rhs_tmp, expected_size); + src_f->set_with_restore(&rhs_tmp, variant_index, stride); + } else { + ConfigOptionVectorBase* opt_vec_src = static_cast(opt_src); + + const size_t src_size = opt_vec_src->size(); + const size_t dest_size = static_cast(opt_target)->size(); + if (src_size != expected_size || dest_size != expected_size) + log_normalize_legacy_vector_size(__FUNCTION__, opt, stride, src_size, dest_size, expected_size, restore_n, cur_variant_count, + target_variant_count, cur_extruder_ids.size(), target_extruder_ids.size(), opt_src, opt_target); + + if (opt_vec_src->size() != expected_size) + opt_vec_src->resize(expected_size, opt_target); + + // Normalize rhs via a cloned temporary (rhs itself is const). + ConfigOptionUniquePtr rhs_owner(opt_target->clone()); + ConfigOptionVectorBase *rhs_vec = dynamic_cast(rhs_owner.get()); + if (rhs_vec == nullptr) + throw ConfigurationError((boost::format("%1%: key '%2%' is expected to be a vector option.") % __FUNCTION__ % opt).str()); + if (rhs_vec->size() != expected_size) + rhs_vec->resize(expected_size, opt_target); + + opt_vec_src->set_with_restore(rhs_vec, variant_index, stride); + } } } }