From 2d6ec834ec4d3948f369c46f0cf76a411db8d098 Mon Sep 17 00:00:00 2001 From: Rad Date: Wed, 11 Feb 2026 16:08:02 +0100 Subject: [PATCH] Refactor config comparison logic: Enhance equality checks for ConfigOption instances by including type comparison. Update related methods in ConfigBase, DynamicConfig, and Preset classes to ensure accurate configuration diffs and equality assessments. Improve logging in MixedFilamentManager for better tracking of custom entry loading and processing. --- src/libslic3r/Config.cpp | 27 +++-- src/libslic3r/MixedFilament.cpp | 36 +++++- src/libslic3r/PlaceholderParser.cpp | 2 +- src/libslic3r/Preset.cpp | 145 +++++++++++++++++++++--- src/libslic3r/PresetBundle.cpp | 50 +++++--- src/libslic3r/PrintApply.cpp | 35 +++++- src/libslic3r/utils.cpp | 17 ++- src/slic3r/GUI/Plater.cpp | 61 ++++++---- src/slic3r/GUI/UnsavedChangesDialog.cpp | 12 +- 9 files changed, 317 insertions(+), 68 deletions(-) diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index b4bee54cd3..7c8c30babe 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -473,8 +473,12 @@ bool ConfigBase::equals(const ConfigBase &other, const std::set* sk continue; const ConfigOption *this_opt = this->option(opt_key); const ConfigOption *other_opt = other.option(opt_key); - if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt) - return false; + if (this_opt != nullptr && other_opt != nullptr) { + if (this_opt->type() != other_opt->type()) + return false; + if (*this_opt != *other_opt) + return false; + } } return true; } @@ -486,8 +490,10 @@ t_config_option_keys ConfigBase::diff(const ConfigBase &other) const for (const t_config_option_key &opt_key : this->keys()) { const ConfigOption *this_opt = this->option(opt_key); const ConfigOption *other_opt = other.option(opt_key); - if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt) - diff.emplace_back(opt_key); + if (this_opt != nullptr && other_opt != nullptr) { + if (this_opt->type() != other_opt->type() || *this_opt != *other_opt) + diff.emplace_back(opt_key); + } } return diff; } @@ -499,7 +505,8 @@ t_config_option_keys ConfigBase::equal(const ConfigBase &other) const for (const t_config_option_key &opt_key : this->keys()) { const ConfigOption *this_opt = this->option(opt_key); const ConfigOption *other_opt = other.option(opt_key); - if (this_opt != nullptr && other_opt != nullptr && *this_opt == *other_opt) + if (this_opt != nullptr && other_opt != nullptr && + this_opt->type() == other_opt->type() && *this_opt == *other_opt) equal.emplace_back(opt_key); } return equal; @@ -1763,7 +1770,11 @@ static inline bool dynamic_config_iterate(const DynamicConfig &lhs, const Dynami bool DynamicConfig::equals(const DynamicConfig &other, const std::set* skipped_keys) const { return ! dynamic_config_iterate(*this, other, - [](const t_config_option_key & /* key */, const ConfigOption *l, const ConfigOption *r) { return *l != *r; }, + [](const t_config_option_key & /* key */, const ConfigOption *l, const ConfigOption *r) { + if (l->type() != r->type()) + return true; + return *l != *r; + }, skipped_keys); } @@ -1773,7 +1784,7 @@ t_config_option_keys DynamicConfig::diff(const DynamicConfig &other) const t_config_option_keys diff; dynamic_config_iterate(*this, other, [&diff](const t_config_option_key &key, const ConfigOption *l, const ConfigOption *r) { - if (*l != *r) + if (l->type() != r->type() || *l != *r) diff.emplace_back(key); // Continue iterating. return false; @@ -1787,7 +1798,7 @@ t_config_option_keys DynamicConfig::equal(const DynamicConfig &other) const t_config_option_keys equal; dynamic_config_iterate(*this, other, [&equal](const t_config_option_key &key, const ConfigOption *l, const ConfigOption *r) { - if (*l == *r) + if (l->type() == r->type() && *l == *r) equal.emplace_back(key); // Continue iterating. return false; diff --git a/src/libslic3r/MixedFilament.cpp b/src/libslic3r/MixedFilament.cpp index 2ed842126e..93af56c1ab 100644 --- a/src/libslic3r/MixedFilament.cpp +++ b/src/libslic3r/MixedFilament.cpp @@ -1,6 +1,7 @@ #include "MixedFilament.hpp" #include +#include #include #include #include @@ -440,23 +441,43 @@ std::string MixedFilamentManager::serialize_custom_entries() const void MixedFilamentManager::load_custom_entries(const std::string &serialized, const std::vector &filament_colours) { const size_t n = filament_colours.size(); - if (serialized.empty() || n < 2) + if (serialized.empty() || n < 2) { + BOOST_LOG_TRIVIAL(debug) << "MixedFilamentManager::load_custom_entries skipped" + << ", serialized_empty=" << (serialized.empty() ? 1 : 0) + << ", physical_count=" << n; return; + } + + size_t parsed_rows = 0; + size_t loaded_rows = 0; + size_t updated_auto = 0; + size_t skipped_rows = 0; std::stringstream all(serialized); std::string row; while (std::getline(all, row, ';')) { if (row.empty()) continue; + ++parsed_rows; unsigned int a = 0; unsigned int b = 0; bool enabled = true; bool custom = true; int mix = 50; - if (!parse_row_definition(row, a, b, enabled, custom, mix)) + if (!parse_row_definition(row, a, b, enabled, custom, mix)) { + ++skipped_rows; + BOOST_LOG_TRIVIAL(warning) << "MixedFilamentManager::load_custom_entries invalid row format: " << row; continue; - if (a == 0 || b == 0 || a > n || b > n || a == b) + } + if (a == 0 || b == 0 || a > n || b > n || a == b) { + ++skipped_rows; + BOOST_LOG_TRIVIAL(warning) << "MixedFilamentManager::load_custom_entries row rejected" + << ", row=" << row + << ", a=" << a + << ", b=" << b + << ", physical_count=" << n; continue; + } if (!custom) { auto it_auto = std::find_if(m_mixed.begin(), m_mixed.end(), [a, b](const MixedFilament &mf) { @@ -465,6 +486,7 @@ void MixedFilamentManager::load_custom_entries(const std::string &serialized, co if (it_auto != m_mixed.end()) { it_auto->enabled = enabled; it_auto->mix_b_percent = mix; + ++updated_auto; continue; } } @@ -478,8 +500,16 @@ void MixedFilamentManager::load_custom_entries(const std::string &serialized, co mf.enabled = enabled; mf.custom = custom; m_mixed.push_back(std::move(mf)); + ++loaded_rows; } refresh_display_colors(filament_colours); + BOOST_LOG_TRIVIAL(info) << "MixedFilamentManager::load_custom_entries" + << ", physical_count=" << n + << ", parsed_rows=" << parsed_rows + << ", loaded_rows=" << loaded_rows + << ", updated_auto_rows=" << updated_auto + << ", skipped_rows=" << skipped_rows + << ", mixed_total=" << m_mixed.size(); } unsigned int MixedFilamentManager::resolve(unsigned int filament_id, diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 2ef4933b06..e9ce850fa2 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -111,7 +111,7 @@ static inline bool opts_equal(const DynamicConfig &config_old, const DynamicConf const ConfigOption *opt_old = config_old.option(opt_key); const ConfigOption *opt_new = config_new.option(opt_key); assert(opt_new != nullptr); - return opt_old != nullptr && *opt_new == *opt_old; + return opt_old != nullptr && opt_new->type() == opt_old->type() && *opt_new == *opt_old; } std::vector PlaceholderParser::config_diff(const DynamicPrintConfig &rhs) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 694cb20a72..b76114d9dd 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -550,12 +550,35 @@ void Preset::save(DynamicPrintConfig* parent_config) //BBS: only save difference if it has parent if (parent_config) { + auto option_differs_from_default = [](const ConfigBase &cfg, const t_config_option_key &opt_key, const ConfigOption *opt) { + if (opt == nullptr) + return false; + const ConfigDef *def = cfg.def(); + if (def == nullptr) + return true; + const ConfigOptionDef *opt_def = def->get(opt_key); + if (opt_def == nullptr || opt_def->default_value.get() == nullptr) + return true; + if (opt->type() != opt_def->default_value->type()) + return true; + return *opt != *opt_def->default_value; + }; + DynamicPrintConfig temp_config; std::vector dirty_options = config.diff(*parent_config); + for (const t_config_option_key &opt_key : config.keys()) { + const ConfigOption *opt_src = config.option(opt_key); + if (opt_src != nullptr && parent_config->option(opt_key) == nullptr && + option_differs_from_default(config, opt_key, opt_src)) + dirty_options.emplace_back(opt_key); + } + std::sort(dirty_options.begin(), dirty_options.end()); + dirty_options.erase(std::unique(dirty_options.begin(), dirty_options.end()), dirty_options.end()); - for (auto option: dirty_options) - { - ConfigOption *opt_src = config.option(option); + for (const std::string &option : dirty_options) { + const ConfigOption *opt_src = config.option(option); + if (opt_src == nullptr) + continue; ConfigOption *opt_dst = temp_config.option(option, true); opt_dst->set(opt_src); } @@ -1298,12 +1321,35 @@ Preset* PresetCollection::get_preset_differed_for_save(Preset& preset) parent_preset = this->find_preset(inherits, false, true); } if (parent_preset) { + auto option_differs_from_default = [](const ConfigBase &cfg, const t_config_option_key &opt_key, const ConfigOption *opt) { + if (opt == nullptr) + return false; + const ConfigDef *def = cfg.def(); + if (def == nullptr) + return true; + const ConfigOptionDef *opt_def = def->get(opt_key); + if (opt_def == nullptr || opt_def->default_value.get() == nullptr) + return true; + if (opt->type() != opt_def->default_value->type()) + return true; + return *opt != *opt_def->default_value; + }; + DynamicPrintConfig temp_config; std::vector dirty_options = preset.config.diff(parent_preset->config); + for (const t_config_option_key &opt_key : preset.config.keys()) { + const ConfigOption *opt_src = preset.config.option(opt_key); + if (opt_src != nullptr && parent_preset->config.option(opt_key) == nullptr && + option_differs_from_default(preset.config, opt_key, opt_src)) + dirty_options.emplace_back(opt_key); + } + std::sort(dirty_options.begin(), dirty_options.end()); + dirty_options.erase(std::unique(dirty_options.begin(), dirty_options.end()), dirty_options.end()); - for (auto option: dirty_options) - { - ConfigOption *opt_src = preset.config.option(option); + for (const std::string &option : dirty_options) { + const ConfigOption *opt_src = preset.config.option(option); + if (opt_src == nullptr) + continue; ConfigOption *opt_dst = temp_config.option(option, true); opt_dst->set(opt_src); } @@ -1329,12 +1375,33 @@ int PresetCollection::get_differed_values_to_update(Preset& preset, std::mapfind_preset(inherit_preset, false, true); } if (parent_preset) { + auto option_differs_from_default = [](const ConfigBase &cfg, const t_config_option_key &opt_key, const ConfigOption *opt) { + if (opt == nullptr) + return false; + const ConfigDef *def = cfg.def(); + if (def == nullptr) + return true; + const ConfigOptionDef *opt_def = def->get(opt_key); + if (opt_def == nullptr || opt_def->default_value.get() == nullptr) + return true; + if (opt->type() != opt_def->default_value->type()) + return true; + return *opt != *opt_def->default_value; + }; + DynamicPrintConfig temp_config; std::vector dirty_options = preset.config.diff(parent_preset->config); + for (const t_config_option_key &opt_key : preset.config.keys()) { + const ConfigOption *opt_src = preset.config.option(opt_key); + if (opt_src != nullptr && parent_preset->config.option(opt_key) == nullptr && + option_differs_from_default(preset.config, opt_key, opt_src)) + dirty_options.emplace_back(opt_key); + } + std::sort(dirty_options.begin(), dirty_options.end()); + dirty_options.erase(std::unique(dirty_options.begin(), dirty_options.end()), dirty_options.end()); - for (auto option: dirty_options) - { - ConfigOption *opt_src = preset.config.option(option); + for (const std::string &option : dirty_options) { + const ConfigOption *opt_src = preset.config.option(option); if (opt_src) key_values[option] = opt_src->serialize(); } @@ -1930,7 +1997,7 @@ std::pair PresetCollection::load_external_preset( continue; ConfigOption *opt_src = iter->config.option(opt); ConfigOption *opt_dst = cfg.option(opt); - if (opt_src && opt_dst && (*opt_src != *opt_dst)) { + if (opt_src && opt_dst && opt_src->type() == opt_dst->type() && (*opt_src != *opt_dst)) { BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" change key %1% from old_value %2% to inherit's value %3%, preset_name %4%, inherits_name %5%") %opt %(opt_dst->serialize()) %(opt_src->serialize()) %original_name %inherits; opt_dst->set(opt_src); @@ -1944,7 +2011,7 @@ std::pair PresetCollection::load_external_preset( continue; ConfigOption *opt_src = it->config.option(opt); ConfigOption *opt_dst = cfg.option(opt); - if (opt_src && opt_dst && (*opt_src != *opt_dst)) { + if (opt_src && opt_dst && opt_src->type() == opt_dst->type() && (*opt_src != *opt_dst)) { BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" change key %1% from old_value %2% to new_value %3%, preset_name %4%") %opt %(opt_dst->serialize()) %(opt_src->serialize()) %original_name; opt_dst->set(opt_src); @@ -2720,6 +2787,42 @@ void add_correct_opts_to_diff(const std::string &opt_key, t_config_option_keys& } } +static bool option_differs_from_default(const ConfigBase &cfg, const t_config_option_key &opt_key, const ConfigOption *opt) +{ + if (opt == nullptr) + return false; + const ConfigDef *def = cfg.def(); + if (def == nullptr) + return true; + const ConfigOptionDef *opt_def = def->get(opt_key); + if (opt_def == nullptr || opt_def->default_value.get() == nullptr) + return true; + if (opt->type() != opt_def->default_value->type()) + return true; + return *opt != *opt_def->default_value; +} + +static void append_missing_nondefault_options(const ConfigBase &edited, const ConfigBase &reference, + t_config_option_keys &diff, const std::set *skip = nullptr) +{ + for (const t_config_option_key &opt_key : edited.keys()) { + if (skip && skip->count(opt_key) != 0) + continue; + const ConfigOption *edited_opt = edited.option(opt_key); + if (edited_opt == nullptr || reference.option(opt_key) != nullptr) + continue; + if (option_differs_from_default(edited, opt_key, edited_opt)) + diff.emplace_back(opt_key); + } +} + +static bool has_missing_nondefault_option(const ConfigBase &edited, const ConfigBase &reference, const std::set *skip = nullptr) +{ + t_config_option_keys missing; + append_missing_nondefault_options(edited, reference, missing, skip); + return !missing.empty(); +} + // Use deep_diff to correct return of changed options, considering individual options for each extruder. inline t_config_option_keys deep_diff(const ConfigBase &config_this, const ConfigBase &config_other) { @@ -2727,8 +2830,13 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi for (const t_config_option_key &opt_key : config_this.keys()) { const ConfigOption *this_opt = config_this.option(opt_key); const ConfigOption *other_opt = config_other.option(opt_key); - if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt) - { + if (this_opt != nullptr && other_opt != nullptr) { + if (this_opt->type() != other_opt->type()) { + diff.emplace_back(opt_key); + continue; + } + if (*this_opt == *other_opt) + continue; //BBS: add bed_exclude_area if (opt_key == "printable_area" || opt_key == "bed_exclude_area" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "thumbnails") { // Scalar variable, or a vector variable, which is independent from number of extruders, @@ -2762,6 +2870,9 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi } } } + append_missing_nondefault_options(config_this, config_other, diff); + std::sort(diff.begin(), diff.end()); + diff.erase(std::unique(diff.begin(), diff.end()), diff.end()); return diff; } @@ -2775,6 +2886,8 @@ bool PresetCollection::is_dirty(const Preset *edited, const Preset *reference) // Only compares options existing in both configs. if (! reference->config.equals(edited->config, &skipped_in_dirty)) return true; + if (has_missing_nondefault_option(edited->config, reference->config, &skipped_in_dirty)) + return true; // The "compatible_printers" option key is handled differently from the others: // It is not mandatory. If the key is missing, it means it is compatible with any printer. // If the key exists and it is empty, it means it is compatible with no printer. @@ -2793,12 +2906,15 @@ std::vector PresetCollection::dirty_options(const Preset *edited, c changed = deep_compare ? deep_diff(edited->config, reference->config) : reference->config.diff(edited->config); + append_missing_nondefault_options(edited->config, reference->config, changed); // The "compatible_printers" option key is handled differently from the others: // It is not mandatory. If the key is missing, it means it is compatible with any printer. // If the key exists and it is empty, it means it is compatible with no printer. for (auto &opt_key : optional_keys) if (reference->config.has(opt_key) != edited->config.has(opt_key)) changed.emplace_back(opt_key); + std::sort(changed.begin(), changed.end()); + changed.erase(std::unique(changed.begin(), changed.end()), changed.end()); } return changed; } @@ -2812,6 +2928,7 @@ std::vector PresetCollection::dirty_options_without_option_list(con changed = deep_compare ? deep_diff(edited->config, reference->config) : reference->config.diff(edited->config); + append_missing_nondefault_options(edited->config, reference->config, changed); // The "compatible_printers" option key is handled differently from the others: // It is not mandatory. If the key is missing, it means it is compatible with any printer. // If the key exists and it is empty, it means it is compatible with no printer. @@ -2819,6 +2936,8 @@ std::vector PresetCollection::dirty_options_without_option_list(con if (reference->config.has(opt_key) != edited->config.has(opt_key)) changed.emplace_back(opt_key); } + std::sort(changed.begin(), changed.end()); + changed.erase(std::unique(changed.begin(), changed.end()), changed.end()); auto iter = changed.begin(); while (iter != changed.end()) { if (option_ignore_list.find(*iter) != option_ignore_list.end()) { diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 19b1349bff..4e9ecb9299 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -41,6 +41,18 @@ static std::vector s_project_options { "wipe_tower_rotation_angle", "curr_bed_type", "flush_multiplier", + // Mixed filament / local-Z settings + "mixed_filament_gradient_mode", + "mixed_filament_height_lower_bound", + "mixed_filament_height_upper_bound", + "mixed_filament_cycle_layers", + "mixed_filament_advanced_dithering", + "mixed_filament_definitions", + "mixed_color_layer_height_a", + "mixed_color_layer_height_b", + "dithering_z_step_size", + "dithering_local_z_mode", + "dithering_step_painted_zones_only", }; // SM_FEATURE: add Snapmaker machine as default @@ -3276,41 +3288,53 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam if (color_opt) { DynamicPrintConfig &print_cfg = this->prints.get_edited_preset().config; auto get_mixed_int = [this, &print_cfg](const std::string &key, int fallback) { + if (this->project_config.has(key)) + return this->project_config.opt_int(key); if (print_cfg.has(key)) return print_cfg.opt_int(key); - return this->project_config.has(key) ? this->project_config.opt_int(key) : fallback; + return fallback; }; auto get_mixed_bool = [this, &print_cfg](const std::string &key, bool fallback) { - if (const ConfigOptionBool *opt = print_cfg.option(key)) - return opt->value; - if (const ConfigOptionInt *opt = print_cfg.option(key)) - return opt->value != 0; if (const ConfigOptionBool *opt = this->project_config.option(key)) return opt->value; if (const ConfigOptionInt *opt = this->project_config.option(key)) return opt->value != 0; + if (const ConfigOptionBool *opt = print_cfg.option(key)) + return opt->value; + if (const ConfigOptionInt *opt = print_cfg.option(key)) + return opt->value != 0; return fallback; }; auto get_mixed_mode = [this, &print_cfg](bool fallback) { - if (const ConfigOptionBool *opt = print_cfg.option("mixed_filament_gradient_mode")) - return opt->value; - if (const ConfigOptionInt *opt = print_cfg.option("mixed_filament_gradient_mode")) - return opt->value != 0; if (const ConfigOptionBool *opt = this->project_config.option("mixed_filament_gradient_mode")) return opt->value; if (const ConfigOptionInt *opt = this->project_config.option("mixed_filament_gradient_mode")) return opt->value != 0; + if (const ConfigOptionBool *opt = print_cfg.option("mixed_filament_gradient_mode")) + return opt->value; + if (const ConfigOptionInt *opt = print_cfg.option("mixed_filament_gradient_mode")) + return opt->value != 0; return fallback; }; auto get_mixed_float = [this, &print_cfg](const std::string &key, float fallback) { + if (this->project_config.has(key)) + return float(this->project_config.opt_float(key)); if (print_cfg.has(key)) return float(print_cfg.opt_float(key)); - return this->project_config.has(key) ? float(this->project_config.opt_float(key)) : fallback; + return fallback; }; auto get_mixed_string = [this, &print_cfg](const std::string &key) { - if (print_cfg.has(key)) - return print_cfg.opt_string(key); - return this->project_config.has(key) ? this->project_config.opt_string(key) : std::string(); + std::string project_value; + if (this->project_config.has(key)) + project_value = this->project_config.opt_string(key); + if (!project_value.empty()) + return project_value; + if (print_cfg.has(key)) { + const std::string print_value = print_cfg.opt_string(key); + if (!print_value.empty()) + return print_value; + } + return project_value; }; auto set_mixed_string = [this, &print_cfg](const std::string &key, const std::string &value) { if (ConfigOptionString *opt = print_cfg.option(key)) diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index bba3848a85..c75b822ead 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -219,6 +219,15 @@ static bool custom_per_printz_gcodes_tool_changes_differ(const std::vectortype() != rhs->type()) + return false; + return *lhs == *rhs; +} + static t_config_option_keys print_config_diffs( const PrintConfig ¤t_config, const DynamicPrintConfig &new_full_config, @@ -245,12 +254,12 @@ static t_config_option_keys print_config_diffs( if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) { // An extruder retract override is available at some of the filament presets. bool overriden = opt_new->overriden_by(opt_new_filament); - if (overriden || *opt_old != *opt_new) { + if (overriden || !config_options_equal(opt_old, opt_new)) { auto opt_copy = opt_new->clone(); if (!((opt_key == "long_retractions_when_cut" || opt_key == "retraction_distances_when_cut") && new_full_config.option("enable_long_retraction_when_cut")->value != LongRectrationLevel::EnableFilament)) // ugly code, remove it later if firmware supports opt_copy->apply_override(opt_new_filament); - bool changed = *opt_old != *opt_copy; + bool changed = !config_options_equal(opt_old, opt_copy); if (changed) print_diff.emplace_back(opt_key); if (changed || overriden) { @@ -262,7 +271,7 @@ static t_config_option_keys print_config_diffs( } else delete opt_copy; } - } else if (*opt_new != *opt_old) { + } else if (!config_options_equal(opt_new, opt_old)) { //BBS: add plate_index logic for wipe_tower_x/wipe_tower_y if (!opt_key.compare("wipe_tower_x") || !opt_key.compare("wipe_tower_y")) { const ConfigOptionFloats* option_new = dynamic_cast(opt_new); @@ -293,7 +302,7 @@ static t_config_option_keys full_print_config_diffs(const DynamicPrintConfig &cu for (const t_config_option_key &opt_key : new_full_config.keys()) { const ConfigOption *opt_old = current_full_config.option(opt_key); const ConfigOption *opt_new = new_full_config.option(opt_key); - if (opt_old == nullptr || *opt_new != *opt_old) { + if (opt_old == nullptr || !config_options_equal(opt_new, opt_old)) { //BBS: add plate_index logic for wipe_tower_x/wipe_tower_y if (opt_old && (!opt_key.compare("wipe_tower_x") || !opt_key.compare("wipe_tower_y"))) { const ConfigOptionFloats* option_new = dynamic_cast(opt_new); @@ -1249,6 +1258,15 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ mixed_height_upper = std::max(mixed_height_lower, mixed_height_upper); mixed_cycle_layers = std::max(2, mixed_cycle_layers); + BOOST_LOG_TRIVIAL(info) << "Print::apply mixed settings" + << ", gradient_mode=" << mixed_gradient_mode + << ", lower=" << mixed_height_lower + << ", upper=" << mixed_height_upper + << ", cycle_layers=" << mixed_cycle_layers + << ", advanced_dither=" << (mixed_advanced_dither ? 1 : 0) + << ", custom_definitions_len=" << mixed_custom_definitions.size() + << ", physical_extruders=" << num_extruders; + // Regenerate mixed (virtual) filaments from physical filament colours and // re-apply user custom mixed definitions. std::vector physical_filament_colors = m_config.filament_colour.values; @@ -1261,6 +1279,15 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ mixed_height_upper, mixed_cycle_layers, mixed_advanced_dither); + size_t mixed_custom_count = 0; + for (const auto &mf : m_mixed_filament_mgr.mixed_filaments()) + if (mf.custom) + ++mixed_custom_count; + + BOOST_LOG_TRIVIAL(info) << "Print::apply mixed manager state" + << ", mixed_total=" << m_mixed_filament_mgr.mixed_filaments().size() + << ", mixed_enabled=" << m_mixed_filament_mgr.enabled_count() + << ", mixed_custom=" << mixed_custom_count; // Total filaments = physical extruders + enabled mixed (virtual) filaments. // Used for extruder ID clamping so that virtual IDs are accepted. size_t num_total_filaments = m_mixed_filament_mgr.total_filaments(num_extruders); diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 79709062a8..0580b0df50 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -2,6 +2,7 @@ #include "I18N.hpp" #include +#include #include #include #include @@ -337,11 +338,17 @@ void set_log_path_and_level(const std::string& file, unsigned int level) } #endif - //BBS log file at C:\\Users\\[yourname]\\AppData\\Roaming\\Snapmaker_Orca\\log\\[log_filename].log - auto log_folder = boost::filesystem::path(g_data_dir) / "log"; - if (!boost::filesystem::exists(log_folder)) { - boost::filesystem::create_directory(log_folder); - } + // Prefer LOCALAPPDATA on Windows so runtime logs are written under: + // C:\\Users\\[user]\\AppData\\Local\\Snapmaker_Orca\\log\\*.log + // Keep g_data_dir fallback for non-Windows or missing environment. + boost::filesystem::path log_folder = boost::filesystem::path(g_data_dir) / "log"; +#ifdef _WIN32 + if (const char *local_appdata = std::getenv("LOCALAPPDATA"); local_appdata != nullptr && *local_appdata != '\0') + log_folder = boost::filesystem::path(local_appdata) / "Snapmaker_Orca" / "log"; +#endif + if (!boost::filesystem::exists(log_folder)) { + boost::filesystem::create_directories(log_folder); + } auto full_path = (log_folder / file).make_preferred(); g_log_sink = boost::log::add_file_log( diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 6a1e8e073b..fd7b09822e 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2143,8 +2143,14 @@ void Sidebar::on_filaments_change(size_t num_filaments) { auto& choices = combos_filament(); - if (num_filaments == choices.size()) + if (num_filaments == choices.size()) { + // Project load may keep the same physical filament count while mixed + // definitions changed. Refresh mixed panel even without count changes. + update_ui_from_settings(); + update_dynamic_filament_list(); + update_mixed_filament_panel(); return; + } if (choices.size() == 1 || num_filaments == 1) choices[0]->GetDropDown().Invalidate(); @@ -2341,45 +2347,57 @@ void Sidebar::update_mixed_filament_panel() physical_colors.resize(num_physical, "#26A69A"); auto get_mixed_int = [preset_bundle, print_cfg](const std::string &key, int fallback) { + if (preset_bundle->project_config.has(key)) + return preset_bundle->project_config.opt_int(key); if (print_cfg && print_cfg->has(key)) return print_cfg->opt_int(key); - return preset_bundle->project_config.has(key) ? preset_bundle->project_config.opt_int(key) : fallback; + return fallback; }; auto get_mixed_bool = [preset_bundle, print_cfg](const std::string &key, bool fallback) { + if (const ConfigOptionBool *opt = preset_bundle->project_config.option(key)) + return opt->value; + if (const ConfigOptionInt *opt = preset_bundle->project_config.option(key)) + return opt->value != 0; if (print_cfg) { if (const ConfigOptionBool *opt = print_cfg->option(key)) return opt->value; if (const ConfigOptionInt *opt = print_cfg->option(key)) return opt->value != 0; } - if (const ConfigOptionBool *opt = preset_bundle->project_config.option(key)) - return opt->value; - if (const ConfigOptionInt *opt = preset_bundle->project_config.option(key)) - return opt->value != 0; return fallback; }; auto get_mixed_mode = [preset_bundle, print_cfg](bool fallback) { + if (const ConfigOptionBool *opt = preset_bundle->project_config.option("mixed_filament_gradient_mode")) + return opt->value; + if (const ConfigOptionInt *opt = preset_bundle->project_config.option("mixed_filament_gradient_mode")) + return opt->value != 0; if (print_cfg) { if (const ConfigOptionBool *opt = print_cfg->option("mixed_filament_gradient_mode")) return opt->value; if (const ConfigOptionInt *opt = print_cfg->option("mixed_filament_gradient_mode")) return opt->value != 0; } - if (const ConfigOptionBool *opt = preset_bundle->project_config.option("mixed_filament_gradient_mode")) - return opt->value; - if (const ConfigOptionInt *opt = preset_bundle->project_config.option("mixed_filament_gradient_mode")) - return opt->value != 0; return fallback; }; auto get_mixed_float = [preset_bundle, print_cfg](const std::string &key, float fallback) { + if (preset_bundle->project_config.has(key)) + return float(preset_bundle->project_config.opt_float(key)); if (print_cfg && print_cfg->has(key)) return float(print_cfg->opt_float(key)); - return preset_bundle->project_config.has(key) ? float(preset_bundle->project_config.opt_float(key)) : fallback; + return fallback; }; auto get_mixed_string = [preset_bundle, print_cfg](const std::string &key, const std::string &fallback = std::string()) { - if (print_cfg && print_cfg->has(key)) - return print_cfg->opt_string(key); - return preset_bundle->project_config.has(key) ? preset_bundle->project_config.opt_string(key) : fallback; + std::string project_value; + if (preset_bundle->project_config.has(key)) + project_value = preset_bundle->project_config.opt_string(key); + if (!project_value.empty()) + return project_value; + if (print_cfg && print_cfg->has(key)) { + const std::string print_value = print_cfg->opt_string(key); + if (!print_value.empty()) + return print_value; + } + return project_value.empty() ? fallback : project_value; }; auto set_mixed_int = [preset_bundle, print_cfg](const std::string &key, int value) { if (print_cfg) { @@ -2456,11 +2474,16 @@ void Sidebar::update_mixed_filament_panel() mixed_mgr.load_custom_entries(mixed_definitions, physical_colors); mixed_mgr.apply_gradient_settings(gradient_mode, lower_bound, upper_bound, cycle_layers, advanced_dithering); - set_mixed_mode(height_weighted_mode); - set_mixed_float("mixed_filament_height_lower_bound", lower_bound); - set_mixed_float("mixed_filament_height_upper_bound", upper_bound); - set_mixed_int("mixed_filament_cycle_layers", cycle_layers); - set_mixed_string("mixed_filament_definitions", mixed_mgr.serialize_custom_entries()); + // During project load, sidebar may refresh before physical filament combos + // finish syncing. Avoid overwriting persisted mixed definitions while the + // physical filament set is incomplete. + if (num_physical >= 2) { + set_mixed_mode(height_weighted_mode); + set_mixed_float("mixed_filament_height_lower_bound", lower_bound); + set_mixed_float("mixed_filament_height_upper_bound", upper_bound); + set_mixed_int("mixed_filament_cycle_layers", cycle_layers); + set_mixed_string("mixed_filament_definitions", mixed_mgr.serialize_custom_entries()); + } auto &mixed = mixed_mgr.mixed_filaments(); diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 8bb7c85636..f9a1713028 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -1195,10 +1195,13 @@ static wxString get_full_label(std::string opt_key, const DynamicPrintConfig& co { opt_key = get_pure_opt_key(opt_key); - if (config.option(opt_key)->is_nil()) + const ConfigOption *raw_opt = config.option(opt_key); + if (raw_opt == nullptr || raw_opt->is_nil()) return _L("N/A"); const ConfigOptionDef* opt = config.def()->get(opt_key); + if (opt == nullptr) + return from_u8(opt_key); return opt->full_label.empty() ? opt->label : opt->full_label; } @@ -1215,12 +1218,17 @@ static wxString get_string_value(std::string opt_key, const DynamicPrintConfig& opt_idx = orig_opt_idx >= 0 ? orig_opt_idx : 0; opt_key = get_pure_opt_key(opt_key); - if (config.option(opt_key)->is_nil()) + const ConfigOption *raw_opt = config.option(opt_key); + if (raw_opt == nullptr || raw_opt->is_nil()) return _L("N/A"); wxString out; const ConfigOptionDef* opt = config.def()->get(opt_key); + if (opt == nullptr) + return from_u8(raw_opt->serialize()); + if (raw_opt->type() != opt->type) + return from_u8(raw_opt->serialize()); bool is_nullable = opt->nullable; switch (opt->type) {