diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 9cfd165398..caef0fb5b1 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -1944,6 +1944,7 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first std::vector>geometric_unprintables = m_print->get_geometric_unprintable_filaments(); std::vector>physical_unprintables = m_print->get_physical_unprintable_filaments(used_filaments); + auto filament_unprintable_volumes = m_print->get_filament_unprintable_flow(used_filaments); filament_maps = m_print->get_filament_maps(); map_mode = m_print->get_filament_map_mode(); @@ -2012,7 +2013,7 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first // per-layer maps in a selector (4-arg create) result — which sets support_dynamic_nozzle_map and // lights the GCode per-layer hotend/nozzle placeholders. filament_sequences is produced here, so // the static reorder below is skipped for this branch. - auto grouping_context = build_filament_group_context(m_print, layer_filaments, physical_unprintables, geometric_unprintables, {}, FilamentMapMode::fmmAutoForFlush, + auto grouping_context = build_filament_group_context(m_print, layer_filaments, physical_unprintables, geometric_unprintables, filament_unprintable_volumes, FilamentMapMode::fmmAutoForFlush, m_initial_nozzle_status.get_nozzle_filament_map()); // The time estimator's per-extruder print times are global and do not apply to per-range // grouping. @@ -2020,7 +2021,7 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first m_nozzle_status = m_initial_nozzle_status; auto dynamic_plan_res = plan_filament_mapping_and_order_by_combo_ranges(m_print, grouping_context, get_custom_seq, FilamentMapMode::fmmAutoForFlush, - physical_unprintables, geometric_unprintables, {}, &m_nozzle_status); + physical_unprintables, geometric_unprintables, filament_unprintable_volumes, &m_nozzle_status); std::vector> nozzle_map_per_layer; for (auto& res : dynamic_plan_res) { @@ -2042,7 +2043,7 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first } // only check and map in sequence mode, in by object mode, we check the map in print.cpp else if (print_config->print_sequence != PrintSequence::ByObject || m_print->objects().size() == 1) { - grouping_result = ToolOrdering::get_recommended_filament_maps(layer_filaments, m_print, map_mode, physical_unprintables, geometric_unprintables); + grouping_result = ToolOrdering::get_recommended_filament_maps(layer_filaments, m_print, map_mode, physical_unprintables, geometric_unprintables, filament_unprintable_volumes); std::vector derived_maps = grouping_result.get_extruder_map(false); // 1-based extruder map if (map_mode < FilamentMapMode::fmmManual) { @@ -2145,12 +2146,12 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first // is_dynamic_group_reorder() implies filament_map_mode == fmmAutoForFlush, contradicting // this map_mode != fmmAutoForFlush guard, so this sub-branch is unreachable under the // current predicate. It is provably inert. - auto best_context = build_filament_group_context(m_print, layer_filaments, physical_unprintables, geometric_unprintables, {}, FilamentMapMode::fmmAutoForFlush, + auto best_context = build_filament_group_context(m_print, layer_filaments, physical_unprintables, geometric_unprintables, filament_unprintable_volumes, FilamentMapMode::fmmAutoForFlush, m_initial_nozzle_status.get_nozzle_filament_map()); best_context.speed_info.group_with_time = false; MultiNozzleUtils::NozzleStatusRecorder best_nozzle_status = m_initial_nozzle_status; auto best_plan = plan_filament_mapping_and_order_by_combo_ranges(m_print, best_context, get_custom_seq, FilamentMapMode::fmmAutoForFlush, - physical_unprintables, geometric_unprintables, {}, &best_nozzle_status); + physical_unprintables, geometric_unprintables, filament_unprintable_volumes, &best_nozzle_status); std::vector> best_nozzle_map_per_layer; for (auto& res : best_plan) { best_sequences.emplace_back(cast(res.fil_order)); @@ -2164,7 +2165,7 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first // Best-for-flush grouping (nozzle-aware result). The extruder-level map fed to the flush // reorder is derived exactly as before, so best_sequences (and the flush weight) are // identical; only flush_filament_change_count is now charged per physical nozzle. - auto best_group_result = get_recommended_filament_maps(layer_filaments, m_print, fmmAutoForFlush, physical_unprintables, geometric_unprintables); + auto best_group_result = get_recommended_filament_maps(layer_filaments, m_print, fmmAutoForFlush, physical_unprintables, geometric_unprintables, filament_unprintable_volumes); std::vector best_maps = best_group_result.get_extruder_map(); reorder_filaments_for_minimum_flush_volume( filament_lists, diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 33cec8a404..38d5655f1e 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -2504,13 +2505,14 @@ void Print::process(long long *time_cost_with_cache, bool use_cache) auto physical_unprintables = this->get_physical_unprintable_filaments(used_filaments); auto geometric_unprintables = this->get_geometric_unprintable_filaments(); + auto filament_unprintable_volumes = this->get_filament_unprintable_flow(used_filaments); std::vectorfilament_maps = this->get_filament_maps(); auto map_mode = get_filament_map_mode(); // get recommended filament map if (map_mode < FilamentMapMode::fmmManual) { // Grouping returns a nozzle-aware result; the 1-based extruder map // for the by-object path is derived from it. - auto grouping_result = ToolOrdering::get_recommended_filament_maps(all_filaments, this, map_mode, physical_unprintables, geometric_unprintables); + auto grouping_result = ToolOrdering::get_recommended_filament_maps(all_filaments, this, map_mode, physical_unprintables, geometric_unprintables, filament_unprintable_volumes); auto derived_maps = grouping_result.get_extruder_map(false); if (!derived_maps.empty()) { filament_maps = derived_maps; @@ -3280,6 +3282,41 @@ std::vector> Print::get_physical_unprintable_filaments(const std:: return physical_unprintables; } +std::map> Print::get_filament_unprintable_flow(const std::vector &used_filaments) const +{ + std::map> ret; + std::vector extruder_variant_list = m_config.printer_extruder_variant.values; + // A filament that declares no extruder variants carries no flow restriction. + const ConfigOptionStrings *filament_variant_opt = m_ori_full_print_config.option("filament_extruder_variant"); + if (filament_variant_opt == nullptr) + return ret; + std::vector filament_variant_list = filament_variant_opt->values; + std::vector filament_self_index; + if (!m_ori_full_print_config.has("filament_self_index")) + filament_self_index.resize(filament_variant_list.size(), 1); + else + filament_self_index = m_ori_full_print_config.option("filament_self_index")->values; + std::unordered_set used_fils_set(used_filaments.begin(), used_filaments.end()); + + std::unordered_map> filament_variant_map; + for(int i = 0; i < filament_variant_list.size(); ++i){ + NozzleVolumeType volume = convert_to_nvt_type(filament_variant_list[i]); + if(volume != nvtHybrid) filament_variant_map[filament_self_index[i]].insert(volume); + } + + for (auto iter : filament_variant_map) { + int fil_idx = iter.first - 1; + if (used_fils_set.find(fil_idx) == used_fils_set.end()) continue; + const std::set &volumes = iter.second; + for (int exd_idx = 0; exd_idx < extruder_variant_list.size(); ++exd_idx) { + auto exd_volume = convert_to_nvt_type(extruder_variant_list[exd_idx]); + assert(exd_volume != nvtHybrid); + if (volumes.find(exd_volume) == volumes.end() && exd_volume != nvtHybrid) ret[fil_idx].insert(exd_volume); + } + } + return ret; +} + std::vector Print::get_extruder_printable_height() const { diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index bf9b5bfd48..2f68ad527e 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -1057,6 +1057,18 @@ public: */ std::vector> get_physical_unprintable_filaments(const std::vector& used_filaments) const; + /** + * @brief Determines the forbidden nozzle volume types for each used filament + * + * A filament may declare the extruder variants it supports. Every volume type offered by the + * printer's extruders that the filament does not support is forbidden for that filament. + * Hybrid volumes are ignored on both sides, and filaments declaring no variants are unrestricted. + * + * @param used_filaments Totally used filaments when slicing + * @return A map from used filament index to the set of nozzle volume types it cannot print on + */ + std::map> get_filament_unprintable_flow(const std::vector &used_filaments) const; + std::vector get_extruder_printable_height() const; std::vector get_extruder_printable_polygons() const; std::vector get_extruder_unprintable_polygons() const; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 4018b8e967..5e3e3d55d9 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -689,6 +689,35 @@ std::vector save_extruder_ams_count_to_string(const std::vector::get_enum_names(); + + auto trim = [](std::string &s) { + s.erase(s.find_last_not_of(" \t\r\n") + 1); + s.erase(0, s.find_first_not_of(" \t\r\n")); + }; + + for (auto ext_type : ext_types) { + size_t pos = variant_str.find(ext_type); + if (pos == std::string::npos) + continue; + + std::string result = variant_str; + result.erase(pos, ext_type.size()); + trim(result); + + auto iter = s_keys_map_NozzleVolumeType.find(result); + if (iter != s_keys_map_NozzleVolumeType.end()) + return NozzleVolumeType(iter->second); + } + + return nvtHybrid; +} + // Parses the extruder_nozzle_stats option: one "|"-separated token list per // extruder, each token // "#". Empty string => no per-type stats for that extruder. diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index cd8d9a9ddc..78335fe4ed 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -568,6 +568,9 @@ extern bool is_filament_extruder_override_key(const std::string &opt_key); extern std::vector> get_extruder_ams_count(const std::vector &strs); extern std::vector save_extruder_ams_count_to_string(const std::vector> &extruder_ams_count); +// maps a full extruder variant string (e.g. "Direct Drive High Flow") to its NozzleVolumeType; nvtHybrid if unparsable +extern NozzleVolumeType convert_to_nvt_type(const std::string& variant_str); + // for parse extruder_nozzle_stats (per-extruder physical nozzle inventory by volume type) extern std::vector> get_extruder_nozzle_stats(const std::vector &strs); extern std::vector save_extruder_nozzle_stats_to_string(const std::vector> &extruder_nozzle_stats); diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 5608f2b683..b8571dd0fa 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -21,6 +21,7 @@ add_executable(${_TEST_NAME}_tests test_polygon.cpp test_mutable_polygon.cpp test_mutable_priority_queue.cpp + test_nozzle_volume_type.cpp test_stl.cpp test_meshboolean.cpp test_marchingsquares.cpp diff --git a/tests/libslic3r/test_nozzle_volume_type.cpp b/tests/libslic3r/test_nozzle_volume_type.cpp new file mode 100644 index 0000000000..0542bde6af --- /dev/null +++ b/tests/libslic3r/test_nozzle_volume_type.cpp @@ -0,0 +1,31 @@ +#include + +#include "libslic3r/PrintConfig.hpp" + +using namespace Slic3r; + +TEST_CASE("convert_to_nvt_type maps extruder variant strings to nozzle volume types", "[Config]") +{ + SECTION("Direct Drive variants") { + REQUIRE(convert_to_nvt_type("Direct Drive Standard") == nvtStandard); + REQUIRE(convert_to_nvt_type("Direct Drive High Flow") == nvtHighFlow); + REQUIRE(convert_to_nvt_type("Direct Drive TPU High Flow") == nvtTPUHighFlow); + } + + SECTION("Bowden variants") { + REQUIRE(convert_to_nvt_type("Bowden Standard") == nvtStandard); + REQUIRE(convert_to_nvt_type("Bowden High Flow") == nvtHighFlow); + } + + SECTION("Unparsable strings fall back to hybrid") { + REQUIRE(convert_to_nvt_type("Unknown Extruder") == nvtHybrid); + REQUIRE(convert_to_nvt_type("") == nvtHybrid); + REQUIRE(convert_to_nvt_type("High Flow") == nvtHybrid); + REQUIRE(convert_to_nvt_type("Direct Drive") == nvtHybrid); + } + + SECTION("Whitespace around the volume-type remainder is trimmed") { + REQUIRE(convert_to_nvt_type("Direct Drive High Flow ") == nvtHighFlow); + REQUIRE(convert_to_nvt_type(" Bowden Standard") == nvtStandard); + } +}