From c53a35856d8d1cbd3a632a8510f1ddfdf9117106 Mon Sep 17 00:00:00 2001 From: "xun.zhang" Date: Mon, 2 Sep 2024 21:10:01 +0800 Subject: [PATCH] ENH: add filament cluster algorithm 1.Add new KMediods algorithm 2.Consider physical and geometric printables 3.Refine code structure jira:NONE Signed-off-by: xun.zhang Change-Id: I1412835c3c6380f9cedb44ff6914004365bba889 --- src/libslic3r/FilamentGroup.cpp | 608 +++++++++++++++------------ src/libslic3r/FilamentGroup.hpp | 91 ++-- src/libslic3r/GCode/ToolOrdering.cpp | 159 ++++--- src/libslic3r/GCode/ToolOrdering.hpp | 7 +- src/libslic3r/Print.cpp | 22 +- src/libslic3r/Print.hpp | 7 + 6 files changed, 536 insertions(+), 358 deletions(-) diff --git a/src/libslic3r/FilamentGroup.cpp b/src/libslic3r/FilamentGroup.cpp index e27de7a0f..d49399a40 100644 --- a/src/libslic3r/FilamentGroup.cpp +++ b/src/libslic3r/FilamentGroup.cpp @@ -1,280 +1,72 @@ #include "FilamentGroup.hpp" #include "GCode/ToolOrderUtils.hpp" #include +#include +#include namespace Slic3r { - void KMediods::fit(const FGStrategy&g_strategy , int timeout_ms) - { - std::vectorbest_medoids; - std::vectorbest_labels; - int best_cost = std::numeric_limits::max(); - - FlushTimeMachine T; - T.time_machine_start(); - - int count = 0; - while (true) - { - std::vectormedoids; - std::vectorlabels; - if (count == 0) - medoids = initialize(INIT_TYPE::Farthest); - else - medoids = initialize(INIT_TYPE::Random); - - labels = assign_label(medoids,g_strategy); - int cost = calc_cost(labels, medoids); - - for (int i = 0; i < m_filament_num; ++i) { - if (std::find(medoids.begin(), medoids.end(), i) != medoids.end()) - continue; - - for (int j = 0; j < 2; ++j) { - std::vector new_medoids = medoids; - new_medoids[j] = i; - std::vector new_labels = assign_label(new_medoids,g_strategy); - int new_cost = calc_cost(new_labels, new_medoids); - - if (new_cost < cost) - { - labels = new_labels; - cost = new_cost; - medoids = new_medoids; - } - } - } - - if (cost < best_cost) - { - best_cost = cost; - best_labels = labels; - best_medoids = medoids; - } - count += 1; - - if (T.time_machine_end() > timeout_ms || m_medoids_set.size() == (m_filament_num * (m_filament_num - 1) / 2)) - break; + static void remove_intersection(std::set& a, std::set& b) { + std::vectorintersection; + std::set_intersection(a.begin(), a.end(), b.begin(), b.end(), std::back_inserter(intersection)); + for (auto& item : intersection) { + a.erase(item); + b.erase(item); } - - this->m_filament_labels = best_labels; } - std::vector KMediods::assign_label(const std::vector& medoids,const FGStrategy&g_strategy) + static bool extract_indices(const std::vector& used_filaments, const std::vector>& physical_unprintable_elems, const std::vector>& geometric_unprintable_elems, + std::vector>& physical_unprintable_idxs, std::vector>& geometric_unprintable_idxs) { - std::vectorlabels(m_filament_num); - struct Comp { - bool operator()(const std::pair& a, const std::pair& b) { - return a.second > b.second; - } - }; - std::priority_queue, std::vector>,Comp>min_heap; + assert(physical_unprintable_elems.size() == geometric_unprintable_elems.size()); + std::vector>(physical_unprintable_elems.size()).swap(physical_unprintable_idxs); + std::vector>(geometric_unprintable_elems.size()).swap(geometric_unprintable_idxs); - for (int i = 0; i < m_filament_num; ++i) { - int distancec_to_0 = m_distance_matrix[i][medoids[0]]; - int distancec_to_1 = m_distance_matrix[i][medoids[1]]; - min_heap.push({ i,distancec_to_0 - distancec_to_1 }); - } - std::set group_0, group_1; - bool have_enough_size = (m_filament_num <= (m_max_group_size[0] + m_max_group_size[1])); - if (have_enough_size || g_strategy == FGStrategy::BestFit) { - while (!min_heap.empty()) { - auto top = min_heap.top(); - min_heap.pop(); - if (group_0.size() < m_max_group_size[0] && (top.second <= 0 || group_1.size() >= m_max_group_size[1])) - group_0.insert(top.first); - else if (group_1.size() < m_max_group_size[1] && (top.second > 0 || group_0.size() >= m_max_group_size[0])) - group_1.insert(top.first); - else { - if (top.second <= 0) - group_0.insert(top.first); - else - group_1.insert(top.first); - } - } - } - else if (g_strategy == FGStrategy::BestCost) { - while (!min_heap.empty()) { - auto top = min_heap.top(); - min_heap.pop(); - if (top.second <= 0) - group_0.insert(top.first); - else - group_1.insert(top.first); + for (size_t gid = 0; gid < physical_unprintable_elems.size(); ++gid) { + for (auto& f : physical_unprintable_elems[gid]) { + auto iter = std::find(used_filaments.begin(), used_filaments.end(), (unsigned)f); + if (iter != used_filaments.end()) + physical_unprintable_idxs[gid].insert(iter - used_filaments.begin()); } } - for (auto& item : group_0) - labels[item] = 0; - for (auto& item : group_1) - labels[item] = 1; - - return labels; + for (size_t gid = 0; gid < geometric_unprintable_elems.size(); ++gid) { + for (auto& f : geometric_unprintable_elems[gid]) { + auto iter = std::find(used_filaments.begin(), used_filaments.end(), (unsigned)f); + if (iter != used_filaments.end()) + geometric_unprintable_idxs[gid].insert(iter - used_filaments.begin()); + } + } + return true; } - int KMediods::calc_cost(const std::vector& labels, const std::vector& medoids) + static bool check_printable(const std::vector>& groups, const std::map& unprintable) { - int total_cost = 0; - for (int i = 0; i < m_filament_num; ++i) - total_cost += m_distance_matrix[i][medoids[labels[i]]]; - return total_cost; - } - - std::vector KMediods::initialize(INIT_TYPE type) - { - auto hash_func = [](int n1, int n2) { - return n1 * 100 + n2; - }; - srand(time(nullptr)); - std::vectorret; - if (type == INIT_TYPE::Farthest) { - //get the farthest items - int target_i = 0, target_j = 0, target_val = std::numeric_limits::min(); - for (int i = 0; i < m_distance_matrix.size(); ++i) { - for (int j = 0; j < m_distance_matrix[0].size(); ++j) { - if (i != j && m_distance_matrix[i][j] > target_val) { - target_val = m_distance_matrix[i][j]; - target_i = i; - target_j = j; - } - } - } - ret.emplace_back(std::min(target_i, target_j)); - ret.emplace_back(std::max(target_i, target_j)); - } - else if (type == INIT_TYPE::Random) { - while (true) { - std::vectormedoids; - while (medoids.size() < k) - { - int candidate = rand() % m_filament_num; - if (std::find(medoids.begin(), medoids.end(), candidate) == medoids.end()) - medoids.push_back(candidate); - } - std::sort(medoids.begin(), medoids.end()); - - if (m_medoids_set.find(hash_func(medoids[0], medoids[1])) != m_medoids_set.end() && m_medoids_set.size() != (m_filament_num * (m_filament_num - 1) / 2)) - continue; - else { - ret = medoids; - break; - } + for (size_t i = 0; i < groups.size(); ++i) { + auto& group = groups[i]; + for (auto& filament : group) { + if (auto iter = unprintable.find(filament); iter != unprintable.end() && i == iter->second) + return false; } } - m_medoids_set.insert(hash_func(ret[0],ret[1])); - return ret; + return true; } - - std::vector FilamentGroup::calc_filament_group(const std::vector>& layer_filaments, const FGStrategy& g_strategy,int* cost) + std::vector collect_sorted_used_filaments(const std::vector>& layer_filaments) { std::setused_filaments_set; for (const auto& lf : layer_filaments) - for (const auto& extruder : lf) - used_filaments_set.insert(extruder); - - std::vectorused_filaments = std::vector(used_filaments_set.begin(), used_filaments_set.end()); + for (const auto& f : lf) + used_filaments_set.insert(f); + std::vectorused_filaments(used_filaments_set.begin(), used_filaments_set.end()); std::sort(used_filaments.begin(), used_filaments.end()); - - int used_filament_num = used_filaments.size(); - - std::vector filament_labels(m_total_filament_num, 0); - - if (used_filament_num <= 1) { - if (cost) - *cost = 0; - return filament_labels; - } - if (used_filament_num < 10) - return calc_filament_group_by_enum(layer_filaments, used_filaments, g_strategy, cost); - else - return calc_filament_group_by_pam(layer_filaments, used_filaments, g_strategy, cost, 100); - + return used_filaments; } - std::vector FilamentGroup::calc_filament_group_by_enum(const std::vector>& layer_filaments, const std::vector& used_filaments, const FGStrategy& g_strategy,int*cost) + FlushDistanceEvaluator::FlushDistanceEvaluator(const FlushMatrix& flush_matrix, const std::vector& used_filaments, const std::vector>& layer_filaments, double p) { - auto bit_count_one = [](uint64_t n) - { - int count = 0; - while (n != 0) - { - n &= n - 1; - count++; - } - return count; - }; - - int used_filament_num = used_filaments.size(); - bool have_enough_size = (used_filament_num <= (m_max_group_size[0] + m_max_group_size[1])); - - uint64_t max_group_num = (static_cast(1) << used_filament_num); - int best_cost = std::numeric_limits::max(); - std::vectorbest_label; - - for (uint64_t i = 0; i < max_group_num; ++i) { - int num_to_group_1 = bit_count_one(i); - int num_to_group_0 = used_filament_num - num_to_group_1; - bool should_accept = false; - if (have_enough_size) - should_accept = (num_to_group_0 <= m_max_group_size[0] && num_to_group_1 <= m_max_group_size[1]); - else if (g_strategy == FGStrategy::BestCost) - should_accept = true; - else if (g_strategy == FGStrategy::BestFit) - should_accept = (num_to_group_0 >= m_max_group_size[0] && num_to_group_1 >= m_max_group_size[1]); - - if (!should_accept) - continue; - - std::setgroup_0, group_1; - for (int j = 0; j < used_filament_num; ++j) { - if (i & (static_cast(1) << j)) - group_1.insert(used_filaments[j]); - else - group_0.insert(used_filaments[j]); - } - - std::vectorfilament_maps(used_filament_num); - for (int i = 0; i < used_filament_num; ++i) { - if (group_0.find(used_filaments[i]) != group_0.end()) - filament_maps[i] = 0; - if (group_1.find(used_filaments[i]) != group_1.end()) - filament_maps[i] = 1; - } - - int total_cost = reorder_filaments_for_minimum_flush_volume( - used_filaments, - filament_maps, - layer_filaments, - m_flush_matrix, - get_custom_seq, - nullptr - ); - - if (total_cost < best_cost) { - best_cost = total_cost; - best_label = filament_maps; - } - } - - if (cost) - *cost = best_cost; - - std::vector filament_labels(m_total_filament_num, 0); - for (int i = 0; i < best_label.size(); ++i) - filament_labels[used_filaments[i]] = best_label[i]; - - return filament_labels; - } - - std::vector FilamentGroup::calc_filament_group_by_pam(const std::vector>& layer_filaments, const std::vector& used_filaments, const FGStrategy& g_strategy, int*cost,int timeout_ms) - { - std::vectorfilament_labels_ret(m_total_filament_num, 0); - int used_filament_num = used_filaments.size(); - if (used_filaments.size() == 1) - return filament_labels_ret; //calc pair counts - std::vector>count_matrix(used_filament_num, std::vector(used_filament_num)); + std::vector>count_matrix(used_filaments.size(), std::vector(used_filaments.size())); for (const auto& lf : layer_filaments) { for (auto iter = lf.begin(); iter != lf.end(); ++iter) { auto id_iter1 = std::find(used_filaments.begin(), used_filaments.end(), *iter); @@ -292,29 +84,327 @@ namespace Slic3r } } - //calc distance matrix - std::vector>distance_matrix(used_filament_num, std::vector(used_filament_num)); + m_distance_matrix.resize(used_filaments.size(), std::vector(used_filaments.size())); + for (size_t i = 0; i < used_filaments.size(); ++i) { for (size_t j = 0; j < used_filaments.size(); ++j) { if (i == j) - distance_matrix[i][j] = 0; + m_distance_matrix[i][j] = 0; else { //TODO: check m_flush_matrix - float max_val = std::max(m_flush_matrix[0][used_filaments[i]][used_filaments[j]], m_flush_matrix[0][used_filaments[j]][used_filaments[i]]); - float min_val = std::min(m_flush_matrix[0][used_filaments[i]][used_filaments[j]], m_flush_matrix[0][used_filaments[j]][used_filaments[i]]); + float max_val = std::max(flush_matrix[used_filaments[i]][used_filaments[j]], flush_matrix[used_filaments[j]][used_filaments[i]]); + float min_val = std::min(flush_matrix[used_filaments[i]][used_filaments[j]], flush_matrix[used_filaments[j]][used_filaments[i]]); + m_distance_matrix[i][j] = (max_val * p + min_val * (1 - p)) * count_matrix[i][j]; + } + } + } + } - double p = 0.65; - distance_matrix[i][j] = (max_val * p + min_val * (1 - p)) * count_matrix[i][j]; + double FlushDistanceEvaluator::get_distance(int idx_a, int idx_b) const + { + assert(0 <= idx_a && idx_a < m_distance_matrix.size()); + assert(0 <= idx_b && idx_b < m_distance_matrix.size()); + + return m_distance_matrix[idx_a][idx_b]; + } + + std::vector KMediods2::cluster_small_data(const std::map& unplaceable_limits, const std::vector& group_size) + { + std::vectorlabels(m_elem_count, -1); + std::vectornew_group_size = group_size; + + for (auto& [elem, center] : unplaceable_limits) { + if (labels[elem] == -1) { + int gid = 1 - center; + labels[elem] = gid; + new_group_size[gid] -= 1; + } + } + + for (auto& label : labels) { + if (label == -1) { + int gid = -1; + for (size_t idx = 0; idx < new_group_size.size(); ++idx) { + if (new_group_size[idx] > 0) { + gid = idx; + break; + } + } + if (gid != -1) { + label = gid; + new_group_size[gid] -= 1; + } + else { + label = 0; } } } - KMediods PAM(distance_matrix, used_filament_num, m_max_group_size); - PAM.fit(g_strategy, timeout_ms); - std::vectorfilament_labels = PAM.get_filament_labels(); + return labels; + } + + std::vector KMediods2::assign_cluster_label(const std::vector& center, const std::map& unplaceable_limtis, const std::vector& group_size, const FGStrategy& strategy) + { + struct Comp { + bool operator()(const std::pair& a, const std::pair& b) { + return a.second > b.second; + } + }; + + std::vector>groups(2); + std::vectornew_max_group_size = group_size; + // store filament idx and distance gap between center 0 and center 1 + std::priority_queue, std::vector>, Comp>min_heap; + + for (int i = 0; i < m_elem_count; ++i) { + if (auto it = unplaceable_limtis.find(i); it != unplaceable_limtis.end()) { + int gid = it->second; + assert(gid == 0 || gid == 1); + groups[1 - gid].insert(i); // insert to group + new_max_group_size[1 - gid] = std::max(new_max_group_size[1 - gid] - 1, 0); // decrease group_size + continue; + } + int distance_to_0 = m_evaluator->get_distance(i, center[0]); + int distance_to_1 = m_evaluator->get_distance(i, center[1]); + min_heap.push({ i,distance_to_0 - distance_to_1 }); + } + + bool have_enough_size = (min_heap.size() <= (new_max_group_size[0] + new_max_group_size[1])); + + if (have_enough_size || strategy == FGStrategy::BestFit) { + while (!min_heap.empty()) { + auto top = min_heap.top(); + min_heap.pop(); + if (groups[0].size() < new_max_group_size[0] && (top.second <= 0 || groups[1].size() >= new_max_group_size[1])) + groups[0].insert(top.first); + else if (groups[1].size() < new_max_group_size[1] && (top.second > 0 || groups[0].size() >= new_max_group_size[0])) + groups[1].insert(top.first); + else { + if (top.second <= 0) + groups[0].insert(top.first); + else + groups[1].insert(top.first); + } + } + } + else { + while (!min_heap.empty()) { + auto top = min_heap.top(); + min_heap.pop(); + if (top.second <= 0) + groups[0].insert(top.first); + else + groups[1].insert(top.first); + } + } + + std::vectorlabels(m_elem_count); + for (auto& f : groups[0]) + labels[f] = 0; + for (auto& f : groups[1]) + labels[f] = 1; + + return labels; + } + + int KMediods2::calc_cost(const std::vector& labels, const std::vector& medoids) + { + int total_cost = 0; + for (int i = 0; i < m_elem_count; ++i) + total_cost += m_evaluator->get_distance(i, medoids[labels[i]]); + return total_cost; + } + + void KMediods2::do_clustering(const FGStrategy& g_strategy, int timeout_ms) + { + FlushTimeMachine T; + T.time_machine_start(); + + if (m_elem_count < m_k) { + m_cluster_labels = cluster_small_data(m_unplaceable_limits, m_max_cluster_size); + return; + } + + std::vectorbest_labels; + int best_cost = std::numeric_limits::max(); + + for (int center_0 = 0; center_0 < m_elem_count; ++center_0) { + if (auto iter = m_unplaceable_limits.find(center_0); iter != m_unplaceable_limits.end() && iter->second == 0) + continue; + for (int center_1 = 0; center_1 < m_elem_count; ++center_1) { + if (center_0 == center_1) + continue; + if (auto iter = m_unplaceable_limits.find(center_1); iter != m_unplaceable_limits.end() && iter->second == 1) + continue; + + std::vectornew_centers = { center_0,center_1 }; + std::vectornew_labels = assign_cluster_label(new_centers, m_unplaceable_limits, m_max_cluster_size, g_strategy); + + int new_cost = calc_cost(new_labels, new_centers); + if (new_cost < best_cost) { + best_cost = new_cost; + best_labels = new_labels; + } + if (T.time_machine_end() > timeout_ms) + break; + } + if (T.time_machine_end() > timeout_ms) + break; + } + this->m_cluster_labels = best_labels; + } + + FilamentGroup::FilamentGroup(const FilamentGroupContext& context) + { + assert(context.flush_matrix.size() == 2); + assert(context.flush_matrix.size() == context.max_group_size.size()); + assert(context.max_group_size.size() == context.physical_unprintables.size()); + assert(context.physical_unprintables.size() == context.geometric_unprintables.size()); + + m_context = context; + } + + std::vector FilamentGroup::calc_filament_group(const std::vector>& layer_filaments, const FGStrategy& g_strategy, int* cost) + { + std::vector used_filaments = collect_sorted_used_filaments(layer_filaments); + + int used_filament_num = used_filaments.size(); + if (used_filament_num < 10) + return calc_filament_group_by_enum(layer_filaments, used_filaments, g_strategy, cost); + else + return calc_filament_group_by_pam2(layer_filaments, used_filaments, g_strategy, cost, 100); + } + + // sorted used_filaments + std::vector FilamentGroup::calc_filament_group_by_enum(const std::vector>& layer_filaments, const std::vector& used_filaments, const FGStrategy& g_strategy,int*cost) + { + 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 + auto bit_count_one = [](uint64_t n) + { + int count = 0; + while (n != 0) + { + n &= n - 1; + count++; + } + return count; + }; + + std::mapunplaceable_limits; + { + // if the filament cannot be placed in both extruder, we just ignore it + std::vector>physical_unprintables = m_context.physical_unprintables; + std::vector>geometric_unprintables = m_context.geometric_unprintables; + // TODO: should we instantly fail here later? + remove_intersection(physical_unprintables[0], physical_unprintables[1]); + remove_intersection(geometric_unprintables[0], geometric_unprintables[1]); + + for (auto& unprintables : { physical_unprintables, geometric_unprintables }) { + for (size_t group_id = 0; group_id < 2; ++group_id) { + for (size_t elem = 0; elem < used_filaments.size(); ++elem) { + for (auto f : unprintables[group_id]) { + if (unplaceable_limits.count(f) == 0) + unplaceable_limits[f] = group_id; + } + } + } + } + } + + int used_filament_num = used_filaments.size(); + uint64_t max_group_num = (static_cast(1) << used_filament_num); + + int best_cost = std::numeric_limits::max(); + std::vectorbest_label; + int best_prefer_level = 0; + + for (uint64_t i = 0; i < max_group_num; ++i) { + std::vector>groups(2); + for (int j = 0; j < used_filament_num; ++j) { + if (i & (static_cast(1) << j)) + groups[1].insert(used_filaments[j]); + else + groups[0].insert(used_filaments[j]); + } + + int prefer_level = 0; + + if (check_printable(groups, unplaceable_limits)) + prefer_level += UNPLACEABLE_LIMIT_REWARD; + if (groups[0].size() <= m_context.max_group_size[0] && groups[1].size() <= m_context.max_group_size[1]) + prefer_level += MAX_SIZE_LIMIT_REWARD; + if (FGStrategy::BestFit == g_strategy && groups[0].size() >= m_context.max_group_size[0] && groups[1].size() >= m_context.max_group_size[1]) + prefer_level += BEST_FIT_LIMIT_REWARD; + + std::vectorfilament_maps(used_filament_num); + for (int i = 0; i < used_filament_num; ++i) { + if (groups[0].find(used_filaments[i]) != groups[0].end()) + filament_maps[i] = 0; + if (groups[1].find(used_filaments[i]) != groups[1].end()) + filament_maps[i] = 1; + } + + int total_cost = reorder_filaments_for_minimum_flush_volume( + used_filaments, + filament_maps, + layer_filaments, + m_context.flush_matrix, + get_custom_seq, + nullptr + ); + + if (prefer_level > best_prefer_level || (prefer_level == best_prefer_level && total_cost < best_cost)) { + best_prefer_level = prefer_level; + best_cost = total_cost; + best_label = filament_maps; + } + } + + if (cost) + *cost = best_cost; + + std::vector filament_labels(m_context.total_filament_num, 0); + for (int i = 0; i < best_label.size(); ++i) + filament_labels[used_filaments[i]] = best_label[i]; + + return filament_labels; + } + + // sorted used_filaments + std::vector FilamentGroup::calc_filament_group_by_pam2(const std::vector>& layer_filaments, const std::vector& used_filaments, const FGStrategy& g_strategy, int*cost,int timeout_ms) + { + std::vectorfilament_labels_ret(m_context.total_filament_num, 0); + if (used_filaments.size() == 1) + return filament_labels_ret; + + std::mapunplaceable_limits; + { + // map the unprintable filaments to idx of used filaments , if not used ,just ignore + std::vector> physical_unprintable_idxs, geometric_unprintable_idxs; + extract_indices(used_filaments, m_context.physical_unprintables, m_context.geometric_unprintables, physical_unprintable_idxs, geometric_unprintable_idxs); + remove_intersection(physical_unprintable_idxs[0], physical_unprintable_idxs[1]); + remove_intersection(geometric_unprintable_idxs[0], geometric_unprintable_idxs[1]); + for (auto& unprintables : { physical_unprintable_idxs, geometric_unprintable_idxs }) { + for (size_t group_id = 0; group_id < 2; ++group_id) { + for(auto f:unprintables[group_id]){ + if(unplaceable_limits.count(f)==0) + unplaceable_limits[f]=group_id; + } + } + } + } + + auto distance_evaluator = std::make_shared(m_context.flush_matrix[0], used_filaments, layer_filaments); + 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.do_clustering(g_strategy, timeout_ms); + std::vectorfilament_labels = PAM.get_cluster_labels(); if(cost) - *cost=reorder_filaments_for_minimum_flush_volume(used_filaments,filament_labels,layer_filaments,m_flush_matrix,std::nullopt,nullptr); + *cost=reorder_filaments_for_minimum_flush_volume(used_filaments,filament_labels,layer_filaments,m_context.flush_matrix,std::nullopt,nullptr); for (int i = 0; i < filament_labels.size(); ++i) filament_labels_ret[used_filaments[i]] = filament_labels[i]; diff --git a/src/libslic3r/FilamentGroup.hpp b/src/libslic3r/FilamentGroup.hpp index 075787873..ca2088e79 100644 --- a/src/libslic3r/FilamentGroup.hpp +++ b/src/libslic3r/FilamentGroup.hpp @@ -1,12 +1,20 @@ #ifndef FILAMENT_GROUP_HPP #define FILAMENT_GROUP_HPP -#include -#include +#include +#include +#include +#include +#include +#include #include "GCode/ToolOrderUtils.hpp" +const static int DEFAULT_CLUSTER_SIZE = 16; + namespace Slic3r { + std::vectorcollect_sorted_used_filaments(const std::vector>& layer_filaments); + struct FlushTimeMachine { private: @@ -31,29 +39,43 @@ namespace Slic3r BestFit }; + struct FilamentGroupContext + { + std::vector flush_matrix; + std::vector>physical_unprintables; + std::vector>geometric_unprintables; + std::vectormax_group_size; + int total_filament_num; + }; + + + class FlushDistanceEvaluator + { + public: + FlushDistanceEvaluator(const FlushMatrix& flush_matrix,const std::vector&used_filaments,const std::vector>& layer_filaments, double p = 0.65); + ~FlushDistanceEvaluator() = default; + double get_distance(int idx_a, int idx_b) const; + private: + std::vector>m_distance_matrix; + + }; + class FilamentGroup { public: - FilamentGroup(const std::vector& flush_matrix, const int total_filament_num, const std::vector& max_group_size) : - m_flush_matrix{ flush_matrix }, - m_total_filament_num{ total_filament_num }, - m_max_group_size{ max_group_size } - {} - + FilamentGroup(const FilamentGroupContext& context); std::vector calc_filament_group(const std::vector>& layer_filaments, const FGStrategy& g_strategy = FGStrategy::BestFit, int* cost = nullptr); - private: + 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_pam(const std::vector>& layer_filaments, const std::vector& used_filaments, const FGStrategy& g_strategy, int* cost = nullptr, int timeout_ms = 300); + 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); private: - std::vectorm_flush_matrix; - std::vectorm_max_group_size; - int m_total_filament_num; + FilamentGroupContext m_context; public: std::optional&)>> get_custom_seq; }; - class KMediods + class KMediods2 { enum INIT_TYPE { @@ -61,29 +83,34 @@ namespace Slic3r Farthest }; public: - KMediods(const std::vector>& distance_matrix, const int filament_num,const std::vector& max_group_size) : - m_distance_matrix{ distance_matrix }, - m_filament_num{ filament_num }, - m_max_group_size{ max_group_size }{} - - void fit(const FGStrategy& g_strategy,int timeout_ms = 300); - std::vectorget_filament_labels()const { - return m_filament_labels; + KMediods2(const int elem_count, const std::shared_ptr& evaluator) : + m_evaluator{ evaluator }, + m_elem_count{ elem_count } + { + m_max_cluster_size = std::vector(m_k, DEFAULT_CLUSTER_SIZE); } + // set max group size + void set_max_cluster_size(const std::vector& group_size) { m_max_cluster_size = group_size; } + + // key stores elem idx, value stores the cluster id that elem cnanot be placed + 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); + std::vectorget_cluster_labels()const { return m_cluster_labels; } + private: - std::vectorinitialize(INIT_TYPE type); - std::vectorassign_label(const std::vector& medoids,const FGStrategy&g_strategy); + std::vectorcluster_small_data(const std::map& unplaceable_limits, const std::vector& group_size); + std::vectorassign_cluster_label(const std::vector& center, const std::map& unplaceable_limits, const std::vector& group_size, const FGStrategy& strategy); int calc_cost(const std::vector& labels, const std::vector& medoids); private: - std::vector>m_distance_matrix; - int m_filament_num; - std::vectorm_max_group_size; - std::setm_medoids_set; - const int k = 2; - private: - std::vectorm_filament_labels; - }; + std::shared_ptr m_evaluator; + std::mapm_unplaceable_limits; + std::vectorm_max_cluster_size; + int m_elem_count; + const int m_k = 2; + std::vectorm_cluster_labels; + }; } #endif // !FILAMENT_GROUP_HPP diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 52c81f85a..ac33c3fc4 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -24,6 +24,60 @@ namespace Slic3r { const static bool g_wipe_into_objects = false; +static std::setget_filament_by_type(const std::vector& used_filaments, const PrintConfig* print_config, const std::string& type) +{ + std::set target_filaments; + for (unsigned int filament_id : used_filaments) { + std::string filament_type = print_config->filament_type.get_at(filament_id); + if (filament_type == type) + target_filaments.insert(filament_id); + } + return target_filaments; +} + +std::vector> ToolOrdering::get_physical_unprintables(const std::vector& used_filaments, const PrintConfig* config, int master_extruder_id) +{ + auto tpu_filaments = get_filament_by_type(used_filaments, config, "TPU"); + if (tpu_filaments.size() > 1) { + throw Slic3r::RuntimeError(std::string("Only supports up to one TPU filament.")); + } + + // consider tpu, only place tpu in extruder with ams + std::vector>physical_unprintables(config->nozzle_diameter.size()); + int extruder_without_tpu = 1 - master_extruder_id; + for (auto& f : tpu_filaments) + physical_unprintables[extruder_without_tpu].insert(f); + + // consider nozzle hrc, nozzle hrc should larger than filament hrc + for (size_t eid = 0; eid < physical_unprintables.size(); ++eid) { + auto nozzle_type = config->nozzle_type.get_at(eid); + int nozzle_hrc = Print::get_hrc_by_nozzle_type(NozzleType(nozzle_type)); + for (auto& f : used_filaments) { + int filament_hrc = config->required_nozzle_HRC.get_at(f); + if(filament_hrc>nozzle_hrc){ + physical_unprintables[eid].insert(f); + } + } + } + + return physical_unprintables; +} + +std::vector> ToolOrdering::get_geometrical_unprintables(const std::vector>& unprintable_arrs, const PrintConfig* config) +{ + auto arrs_idx_switched = unprintable_arrs; + int extruder_nums = config->nozzle_diameter.size(); + std::vector> unprintables(extruder_nums); + for (auto& arr : arrs_idx_switched) + for (auto& item : arr) + item -= 1; + + for (size_t idx = 0; idx < arrs_idx_switched.size(); ++idx) + unprintables[idx] = std::set(arrs_idx_switched[idx].begin(), arrs_idx_switched[idx].end()); + + return unprintables; +} + // Returns true in case that extruder a comes before b (b does not have to be present). False otherwise. bool LayerTools::is_extruder_order(unsigned int a, unsigned int b) const { @@ -474,25 +528,6 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto } } -std::set ToolOrdering::get_tpu_filaments() const -{ - std::vector all_filaments; - for (const auto < : m_layer_tools) { - append(all_filaments, lt.extruders); - sort_remove_duplicates(all_filaments); - } - - std::set tpu_filaments; - for (unsigned int filament_id : all_filaments) { - std::string filament_name = m_print->config().filament_type.get_at(filament_id); - if (filament_name == "TPU") { - tpu_filaments.insert(filament_id); - } - } - - return tpu_filaments; -} - bool ToolOrdering::check_tpu_group(std::vector filament_maps) const { std::vector all_filaments; @@ -855,12 +890,12 @@ 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) +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) { if (!print_config || layer_filaments.empty()) return std::vector(); - const unsigned int filament_nums = (unsigned int) (print_config->filament_colour.values.size() + EPSILON); + const unsigned int filament_nums = (unsigned int)(print_config->filament_colour.values.size() + EPSILON); // get flush matrix std::vector nozzle_flush_mtx; @@ -875,27 +910,27 @@ std::vector ToolOrdering::get_recommended_filament_maps(const std::vector other_layers_seqs; - const ConfigOptionInts * other_layers_print_sequence_op = print_config->option("other_layers_print_sequence"); - const ConfigOptionInt * other_layers_print_sequence_nums_op = print_config->option("other_layers_print_sequence_nums"); + const ConfigOptionInts* other_layers_print_sequence_op = print_config->option("other_layers_print_sequence"); + const ConfigOptionInt* other_layers_print_sequence_nums_op = print_config->option("other_layers_print_sequence_nums"); if (other_layers_print_sequence_op && other_layers_print_sequence_nums_op) { - const std::vector &print_sequence = other_layers_print_sequence_op->values; - int sequence_nums = other_layers_print_sequence_nums_op->value; - other_layers_seqs = get_other_layers_print_sequence(sequence_nums, print_sequence); + const std::vector& print_sequence = other_layers_print_sequence_op->values; + int sequence_nums = other_layers_print_sequence_nums_op->value; + other_layers_seqs = get_other_layers_print_sequence(sequence_nums, print_sequence); } // other_layers_seq: the layer_idx and extruder_idx are base on 1 - auto get_custom_seq = [&other_layers_seqs](int layer_idx, std::vector &out_seq) -> bool { + auto get_custom_seq = [&other_layers_seqs](int layer_idx, std::vector& out_seq) -> bool { for (size_t idx = other_layers_seqs.size() - 1; idx != size_t(-1); --idx) { - const auto &other_layers_seq = other_layers_seqs[idx]; + const auto& other_layers_seq = other_layers_seqs[idx]; if (layer_idx + 1 >= other_layers_seq.first.first && layer_idx + 1 <= other_layers_seq.first.second) { out_seq = other_layers_seq.second; return true; } } return false; - }; + }; - std::vectorret(filament_nums,0); + std::vectorret(filament_nums, 0); // if mutli_extruder, calc group,otherwise set to 0 if (extruder_nums == 2) { std::vector extruder_ams_count_str = print_config->extruder_ams_count.values; @@ -910,9 +945,32 @@ std::vector ToolOrdering::get_recommended_filament_maps(const std::vector>; size_t nozzle_nums = print_config->nozzle_diameter.values.size(); - std::vector> extruder_tpu_status(2, std::set()); - if (nozzle_nums > 1) { - std::set tpu_filaments = get_tpu_filaments(); - if (tpu_filaments.size() > 1) { - throw Slic3r::RuntimeError(std::string("Only supports up to one TPU filament.")); - } - - // todo multi_exturder: Need to determine whether the TPU can be placed on the left or right head according to the print model. - extruder_tpu_status[0] = tpu_filaments; - } - std::vector nozzle_flush_mtx; for (size_t nozzle_id = 0; nozzle_id < nozzle_nums; ++nozzle_id) { std::vector flush_matrix(cast(get_flush_volumes_matrix(print_config->flush_volumes_matrix.values, nozzle_id, nozzle_nums))); @@ -972,6 +1019,17 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first std::vectorfilament_maps(number_of_extruders, 0); FilamentMapMode map_mode = FilamentMapMode::fmmAuto; + + std::vector> layer_filaments; + for (auto& lt : m_layer_tools) { + layer_filaments.emplace_back(lt.extruders); + } + + std::vector used_filaments = collect_sorted_used_filaments(layer_filaments); + + std::vector>geometric_unprintables = get_geometrical_unprintables(m_print->get_unprintable_filament_ids(), print_config); + std::vector>physical_unprintables = get_physical_unprintables(used_filaments, print_config); + if (nozzle_nums > 1) { filament_maps = m_print->get_filament_maps(); map_mode = m_print->get_filament_map_mode(); @@ -982,12 +1040,7 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first print_config = &(m_print_object_ptr->print()->config()); } - std::vector> layer_filaments; - for (auto& lt : m_layer_tools) { - layer_filaments.emplace_back(lt.extruders); - } - - filament_maps = ToolOrdering::get_recommended_filament_maps(layer_filaments, print_config); + filament_maps = ToolOrdering::get_recommended_filament_maps(layer_filaments, print_config, physical_unprintables, geometric_unprintables); if (filament_maps.empty()) return; @@ -1009,10 +1062,6 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first std::vector>filament_sequences; std::vectorfilament_lists(number_of_extruders); std::iota(filament_lists.begin(), filament_lists.end(), 0); - std::vector>layer_filaments; - for (auto& lt : m_layer_tools) { - layer_filaments.emplace_back(lt.extruders); - } std::vector other_layers_seqs; const ConfigOptionInts* other_layers_print_sequence_op = print_config->option("other_layers_print_sequence"); @@ -1083,7 +1132,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); + std::vectorfilament_maps_auto = get_recommended_filament_maps(layer_filaments, print_config, 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 c291b8952..85a2dbfa1 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -229,7 +229,11 @@ public: std::vector& layer_tools() { return m_layer_tools; } bool has_wipe_tower() const { return ! m_layer_tools.empty() && m_first_printing_extruder != (unsigned int)-1 && m_layer_tools.front().has_wipe_tower; } - static std::vector get_recommended_filament_maps(const std::vector>& layer_filaments, const PrintConfig *print_config); + + 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_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); // should be called after doing reorder FilamentChangeStats get_filament_change_stats(FilamentChangeMode mode); @@ -238,7 +242,6 @@ private: void initialize_layers(std::vector &zs); void collect_extruders(const PrintObject &object, const std::vector> &per_layer_extruder_switches); void reorder_extruders(unsigned int last_extruder_id); - std::set get_tpu_filaments() const; bool check_tpu_group(std::vector filament_maps) const; // BBS diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 914ab2382..814dbea64 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1861,19 +1861,21 @@ void Print::process(std::unordered_map* slice_time, bool if (this->config().print_sequence == PrintSequence::ByObject) { // Order object instances for sequential print. print_object_instances_ordering = sort_object_instances_by_model_order(*this); + std::vector> all_filaments; + for (print_object_instance_sequential_active = print_object_instances_ordering.begin(); print_object_instance_sequential_active != print_object_instances_ordering.end(); ++print_object_instance_sequential_active) { + tool_ordering = ToolOrdering(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id); + for (const auto& layer_tool : tool_ordering.layer_tools()) { + all_filaments.emplace_back(layer_tool.extruders); + } + } + + auto used_filaments = collect_sorted_used_filaments(all_filaments); + auto physical_unprintables = ToolOrdering::get_physical_unprintables(used_filaments, &m_config); + auto geometric_unprintables = ToolOrdering::get_geometrical_unprintables(get_unprintable_filament_ids(), &m_config); // get recommended filament map if (get_filament_map_mode() == FilamentMapMode::fmmAuto) { - std::vector> all_filaments; - print_object_instance_sequential_active = print_object_instances_ordering.begin(); - for (; print_object_instance_sequential_active != print_object_instances_ordering.end(); ++print_object_instance_sequential_active) { - tool_ordering = ToolOrdering(*(*print_object_instance_sequential_active)->print_object, initial_extruder_id); - for (const auto &layer_tool : tool_ordering.layer_tools()) { - all_filaments.emplace_back(layer_tool.extruders); - } - } - - std::vector recomended_maps = ToolOrdering::get_recommended_filament_maps(all_filaments, &config()); + std::vector recomended_maps = ToolOrdering::get_recommended_filament_maps(all_filaments, &config(), physical_unprintables, geometric_unprintables); std::transform(recomended_maps.begin(), recomended_maps.end(), recomended_maps.begin(), [](int value) { return value + 1; }); update_filament_maps_to_config(recomended_maps); } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index b6aee316e..9a5956960 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -827,6 +827,13 @@ public: // get the group label of filament size_t get_extruder_id(unsigned int filament_id) const; + // 1 based ids + const std::vector>& get_unprintable_filament_ids() const { return m_unprintable_filament_ids; } + void set_unprintable_filament_ids(const std::vector> &filament_ids) { m_unprintable_filament_ids = filament_ids; } + + std::vector get_printable_area(); + std::vector> get_extruder_printable_area(); + bool enable_timelapse_print() const; std::string output_filename(const std::string &filename_base = std::string()) const override;