diff --git a/src/libslic3r/FilamentGroup.cpp b/src/libslic3r/FilamentGroup.cpp index d49399a40..e2aa18bce 100644 --- a/src/libslic3r/FilamentGroup.cpp +++ b/src/libslic3r/FilamentGroup.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace Slic3r { @@ -52,6 +53,161 @@ namespace Slic3r return true; } + static int calc_color_distance(const Color &src, const Color &dst) + { + double rmean = (src.r + dst.r) / 2.f; + double dr = src.r - dst.r; + double dg = src.g - dst.g; + double db = src.b - dst.b; + + return sqrt((512 + rmean) / 256.f * dr * dr + 4 * dg * dg + (767 - rmean) / 256 * db * db); + } + + // clear the array and heap,save the groups in heap to the array + static void change_memoryed_heaps_to_arrays(FilamentGroupUtils::MemoryedGroupHeap& heap,const int total_filament_num,const std::vector& used_filaments, std::vector>& arrs) + { + // switch the label idx + arrs.clear(); + while (!heap.empty()) { + auto top = heap.top(); + heap.pop(); + std::vector labels_tmp(total_filament_num, 0); + for (size_t idx = 0; idx < top.group.size(); ++idx) + labels_tmp[used_filaments[idx]] = top.group[idx]; + arrs.emplace_back(std::move(labels_tmp)); + } + } + + Color::Color(const std::string& hexstr) { + if (hexstr.empty() || (hexstr.length() != 9 && hexstr.length() != 7) || hexstr[0] != '#') + { + assert(false); + r = 0, g = 0, b = 0, a = 255; + return; + } + + auto hexToByte = [](const std::string& hex)->unsigned char + { + unsigned int byte; + std::istringstream(hex) >> std::hex >> byte; + return static_cast(byte); + }; + r = hexToByte(hexstr.substr(1, 2)); + g = hexToByte(hexstr.substr(3, 2)); + b = hexToByte(hexstr.substr(5, 2)); + if (hexstr.size() == 9) + a = hexToByte(hexstr.substr(7, 2)); + } + + std::vector select_best_group_for_ams(const std::vector>& map_lists, const std::vector& used_filaments, const std::vector& used_filament_colors_str, const std::vector>& ams_filament_colors_str) + { + assert(used_filaments.size() == ams_filament_colors_str.size()); + // change the color str to real colors + std::vectorused_filament_colors; + std::vector>ams_filament_colors; + for (auto& item : used_filament_colors_str) + used_filament_colors.emplace_back(Color(item)); + + for (auto& arr : ams_filament_colors_str) { + std::vectortmp; + for (auto& item : arr) + tmp.emplace_back(Color(item)); + ams_filament_colors.emplace_back(std::move(tmp)); + } + + + int best_cost = std::numeric_limits::max(); + std::vectorbest_map; + for (auto& map : map_lists) { + std::vector>group_colors(2); + + for (size_t i = 0; i < used_filaments.size(); ++i) { + if (map[used_filaments[i]] == 0) + group_colors[0].emplace_back(used_filament_colors[i]); + else + group_colors[1].emplace_back(used_filament_colors[i]); + } + int tmp_cost = 0; + for (size_t i = 0; i < 2; ++i) { + if (group_colors[i].empty() || ams_filament_colors[i].empty()) + continue; + std::vector>distance_matrix(group_colors[i].size(), std::vector(ams_filament_colors[i].size())); + + // calculate color distance matrix + for (size_t src = 0; src < group_colors[i].size(); ++src) { + for (size_t dst = 0; dst < ams_filament_colors[i].size(); ++dst) + distance_matrix[src][dst] = calc_color_distance(group_colors[i][src], ams_filament_colors[i][dst]); + } + + // get min cost by min cost max flow + std::vectorl_nodes(group_colors[i].size()), r_nodes(ams_filament_colors[i].size()); + std::iota(l_nodes.begin(), l_nodes.end(), 0); + std::iota(r_nodes.begin(), r_nodes.end(), 0); + MCMF mcmf(distance_matrix, l_nodes, r_nodes); + auto ams_map = mcmf.solve(); + + for (size_t idx = 0; idx < ams_map.size(); ++idx) { + if (ams_map[idx] == -1) + continue; + tmp_cost += distance_matrix[idx][ams_map[idx]]; + } + } + + if (tmp_cost < best_cost) { + best_cost = tmp_cost; + best_map = map; + } + } + + return best_map; + } + + + void FilamentGroupUtils::update_memoryed_groups(const MemoryedGroup& item, const double gap_threshold, MemoryedGroupHeap& groups) + { + auto emplace_if_accepatle = [gap_threshold](MemoryedGroupHeap& heap, const MemoryedGroup& elem, const MemoryedGroup& best) { + if (best.cost == 0) { + if (std::abs(elem.cost - best.cost) <= ABSOLUTE_FLUSH_GAP_TOLERANCE) + heap.push(elem); + return; + } + double gap_rate = (double)std::abs(elem.cost - best.cost) / (double)best.cost; + if (gap_rate < gap_threshold) + heap.push(elem); + }; + + if (groups.empty()) { + groups.push(item); + } + else { + auto top = groups.top(); + // we only memory items with the highest prefer level + if (top.prefer_level > item.prefer_level) + return; + else if (top.prefer_level == item.prefer_level) { + if (top.cost <= item.cost) { + emplace_if_accepatle(groups, item, top); + } + // find a group with lower cost, rebuild the heap + else { + MemoryedGroupHeap new_heap; + new_heap.push(item); + while (!groups.empty()) { + auto top = groups.top(); + groups.pop(); + emplace_if_accepatle(new_heap, top, item); + } + groups = std::move(new_heap); + } + } + // find a group with the higher prefer level, rebuild the heap + else { + groups = MemoryedGroupHeap(); + groups.push(item); + } + } + } + std::vector collect_sorted_used_filaments(const std::vector>& layer_filaments) { std::setused_filaments_set; @@ -217,7 +373,7 @@ namespace Slic3r void KMediods2::do_clustering(const FGStrategy& g_strategy, int timeout_ms) { - FlushTimeMachine T; + FilamentGroupUtils::FlushTimeMachine T; T.time_machine_start(); if (m_elem_count < m_k) { @@ -245,6 +401,15 @@ namespace Slic3r best_cost = new_cost; best_labels = new_labels; } + + { + MemoryedGroup g; + g.prefer_level = 1; // in non enum mode, we use the same prefer level + g.cost = new_cost; + g.group = new_labels; + update_memoryed_groups(g, memory_threshold, memoryed_groups); + } + if (T.time_machine_end() > timeout_ms) break; } @@ -281,6 +446,9 @@ namespace Slic3r static constexpr int UNPLACEABLE_LIMIT_REWARD = 100; // reward value if the group result follows the unprintable limit static constexpr int MAX_SIZE_LIMIT_REWARD = 10; // reward value if the group result follows the max size per extruder static constexpr int BEST_FIT_LIMIT_REWARD = 1; // reward value if the group result try to fill the max size per extruder + + MemoryedGroupHeap memoryed_groups; + auto bit_count_one = [](uint64_t n) { int count = 0; @@ -360,15 +528,26 @@ namespace Slic3r best_cost = total_cost; best_label = filament_maps; } + + { + MemoryedGroup mg; + mg.prefer_level = prefer_level; + mg.cost = total_cost; + mg.group = std::move(filament_maps); + update_memoryed_groups(mg, memory_threshold, memoryed_groups); + } } if (cost) *cost = best_cost; std::vector filament_labels(m_context.total_filament_num, 0); - for (int i = 0; i < best_label.size(); ++i) + for (size_t i = 0; i < best_label.size(); ++i) filament_labels[used_filaments[i]] = best_label[i]; + + change_memoryed_heaps_to_arrays(memoryed_groups, m_context.total_filament_num, used_filaments, m_memoryed_groups); + return filament_labels; } @@ -400,9 +579,16 @@ namespace Slic3r KMediods2 PAM((int)used_filaments.size(),distance_evaluator); PAM.set_max_cluster_size(m_context.max_group_size); PAM.set_unplaceable_limits(unplaceable_limits); + PAM.set_memory_threshold(memory_threshold); PAM.do_clustering(g_strategy, timeout_ms); std::vectorfilament_labels = PAM.get_cluster_labels(); + + { + auto memoryed_groups = PAM.get_memoryed_groups(); + change_memoryed_heaps_to_arrays(memoryed_groups, m_context.total_filament_num, used_filaments, m_memoryed_groups); + } + if(cost) *cost=reorder_filaments_for_minimum_flush_volume(used_filaments,filament_labels,layer_filaments,m_context.flush_matrix,std::nullopt,nullptr); diff --git a/src/libslic3r/FilamentGroup.hpp b/src/libslic3r/FilamentGroup.hpp index ca2088e79..0d20b8064 100644 --- a/src/libslic3r/FilamentGroup.hpp +++ b/src/libslic3r/FilamentGroup.hpp @@ -7,38 +7,68 @@ #include #include #include +#include #include "GCode/ToolOrderUtils.hpp" const static int DEFAULT_CLUSTER_SIZE = 16; +const static int ABSOLUTE_FLUSH_GAP_TOLERANCE = 2000; + namespace Slic3r { std::vectorcollect_sorted_used_filaments(const std::vector>& layer_filaments); - struct FlushTimeMachine - { - private: - std::chrono::high_resolution_clock::time_point start; - - public: - void time_machine_start() - { - start = std::chrono::high_resolution_clock::now(); - } - - int time_machine_end() - { - auto end = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(end - start); - return duration.count(); - } - }; - enum FGStrategy { BestCost, BestFit }; + struct Color + { + unsigned char r = 0; + unsigned char g = 0; + unsigned char b = 0; + unsigned char a = 255; + Color(unsigned char r_ = 0, unsigned char g_ = 0, unsigned char b_ = 0, unsigned a_ = 255) :r(r_), g(g_), b(b_), a(a_) {} + Color(const std::string& hexstr); + }; + + std::vector select_best_group_for_ams(const std::vector>& map_lists, const std::vector& used_filaments, const std::vector& used_filament_colors, const std::vector>& ams_filament_colros); + + namespace FilamentGroupUtils + { + struct FlushTimeMachine + { + private: + std::chrono::high_resolution_clock::time_point start; + + public: + void time_machine_start() + { + start = std::chrono::high_resolution_clock::now(); + } + + int time_machine_end() + { + auto end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end - start); + return duration.count(); + } + }; + + struct MemoryedGroup { + int cost{ 0 }; + int prefer_level{ 0 }; + std::vectorgroup; + bool operator>(const MemoryedGroup& other) const { + return prefer_level < other.prefer_level || (prefer_level == other.prefer_level && cost > other.cost); + } + }; + using MemoryedGroupHeap = std::priority_queue, std::greater>; + + void update_memoryed_groups(const MemoryedGroup& item,const double gap_threshold, MemoryedGroupHeap& groups); + } + struct FilamentGroupContext { std::vector flush_matrix; @@ -62,14 +92,21 @@ namespace Slic3r class FilamentGroup { + using MemoryedGroupHeap = FilamentGroupUtils::MemoryedGroupHeap; + using MemoryedGroup = FilamentGroupUtils::MemoryedGroup; public: FilamentGroup(const FilamentGroupContext& context); std::vector calc_filament_group(const std::vector>& layer_filaments, const FGStrategy& g_strategy = FGStrategy::BestFit, int* cost = nullptr); public: std::vector calc_filament_group_by_enum(const std::vector>& layer_filaments, const std::vector& used_filaments, const FGStrategy& g_strategy, int* cost = nullptr); std::vector calc_filament_group_by_pam2(const std::vector>& layer_filaments, const std::vector& used_filaments, const FGStrategy& g_strategy, int* cost = nullptr, int timeout_ms = 300); + void set_memory_threshold(double threshold) { memory_threshold = threshold; } + std::vector> get_memoryed_groups()const { return m_memoryed_groups; } private: FilamentGroupContext m_context; + double memory_threshold{ 0 }; + std::vector> m_memoryed_groups; + public: std::optional&)>> get_custom_seq; }; @@ -77,6 +114,9 @@ namespace Slic3r class KMediods2 { + using MemoryedGroupHeap = FilamentGroupUtils::MemoryedGroupHeap; + using MemoryedGroup = FilamentGroupUtils::MemoryedGroup; + enum INIT_TYPE { Random = 0, @@ -97,6 +137,10 @@ namespace Slic3r void set_unplaceable_limits(const std::map& placeable_limits) { m_unplaceable_limits = placeable_limits; } void do_clustering(const FGStrategy& g_strategy,int timeout_ms = 100); + + void set_memory_threshold(double threshold) { memory_threshold; } + MemoryedGroupHeap get_memoryed_groups()const { return memoryed_groups; } + std::vectorget_cluster_labels()const { return m_cluster_labels; } private: @@ -110,6 +154,9 @@ namespace Slic3r int m_elem_count; const int m_k = 2; + double memory_threshold{ 0 }; + FilamentGroupUtils::MemoryedGroupHeap memoryed_groups; + std::vectorm_cluster_labels; }; } diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index cad3ba67a..d568b7995 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -889,7 +889,7 @@ float get_flush_volume(const std::vector &filament_maps, const std::vector< return flush_volume; } -std::vector ToolOrdering::get_recommended_filament_maps(const std::vector>& layer_filaments, const PrintConfig* print_config, const std::vector>&physical_unprintables,const std::vector>&geometric_unprintables) +std::vector ToolOrdering::get_recommended_filament_maps(const std::vector>& layer_filaments, const PrintConfig* print_config, const Print* print, const std::vector>&physical_unprintables,const std::vector>&geometric_unprintables) { if (!print_config || layer_filaments.empty()) return std::vector(); @@ -967,8 +967,25 @@ std::vector ToolOrdering::get_recommended_filament_maps(const std::vectorused_colors; + for (size_t idx = 0; idx < used_filaments.size(); ++idx) + used_colors.emplace_back(print_config->filament_colour.get_at(used_filaments[idx])); + + auto ams_filament_info = print->get_extruder_filament_info(); + std::vector> ams_colors; + for (auto& arr : ams_filament_info) { + std::vectorcolors; + for (auto& item : arr) + colors.emplace_back(item.option("filament_colour")->get_at(0)); + ams_colors.emplace_back(std::move(colors)); + } + ret = select_best_group_for_ams(memoryed_maps, used_filaments, used_colors, ams_colors); } } @@ -1040,7 +1057,7 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first print_config = &(m_print_object_ptr->print()->config()); } - filament_maps = ToolOrdering::get_recommended_filament_maps(layer_filaments, print_config, physical_unprintables, geometric_unprintables); + filament_maps = ToolOrdering::get_recommended_filament_maps(layer_filaments, print_config, m_print, physical_unprintables, geometric_unprintables); if (filament_maps.empty()) return; @@ -1138,7 +1155,7 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first if (map_mode == fmmManual) { std::vector>filament_sequences_one_extruder; - std::vectorfilament_maps_auto = get_recommended_filament_maps(layer_filaments, print_config, physical_unprintables, geometric_unprintables); + std::vectorfilament_maps_auto = get_recommended_filament_maps(layer_filaments, print_config, m_print, physical_unprintables, geometric_unprintables); reorder_filaments_for_minimum_flush_volume( filament_lists, filament_maps_auto, diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index 903fa5891..76364ef3d 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -234,7 +234,7 @@ public: * called in dual extruder mode, the value in map will be 0 or 1 * 0 based group id */ - static std::vector get_recommended_filament_maps(const std::vector>& layer_filaments, const PrintConfig* print_config, const std::vector>& physical_unprintables, const std::vector>& geometric_unprintables); + static std::vector get_recommended_filament_maps(const std::vector>& layer_filaments, const PrintConfig* print_config, const Print* print, const std::vector>& physical_unprintables, const std::vector>& geometric_unprintables); static std::vector> get_physical_unprintables(const std::vector& layer_filaments, const PrintConfig* config, int master_extruder_id = 1); static std::vector> get_geometrical_unprintables(const std::vector>& unprintable_arrs, const PrintConfig* config); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index e3208e963..af4718956 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1879,7 +1879,7 @@ void Print::process(std::unordered_map* slice_time, bool auto map_mode = get_filament_map_mode(); // get recommended filament map if (map_mode == FilamentMapMode::fmmAuto) { - filament_maps = ToolOrdering::get_recommended_filament_maps(all_filaments, &config(), physical_unprintables, geometric_unprintables); + filament_maps = ToolOrdering::get_recommended_filament_maps(all_filaments, &config(), this, physical_unprintables, geometric_unprintables); std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value + 1; }); update_filament_maps_to_config(filament_maps); }