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) {