ENH: refine filament group algorithm

1.Use max flow network to handle limit
2.Support setting master extruder id
3.Fix the issue in the KMedoids algorithm where data is overwritten
after each retry.

jira:NONE

Signed-off-by: xun.zhang <xun.zhang@bambulab.com>
Change-Id: Idd2bedf39f61e7a65eb4199852f60b8fbebe0a7d
(cherry picked from commit 3cfb49a1b9dc2c76066ec441f1028f99a4bf99c4)
This commit is contained in:
xun.zhang
2024-09-30 16:30:34 +08:00
committed by Noisyfox
parent be5d9d7c19
commit ae367bacfd
9 changed files with 345 additions and 73 deletions

View File

@@ -8,8 +8,123 @@
namespace Slic3r
{
MCMF::MCMF(const FlushMatrix& matrix_, const std::vector<int>& u_nodes, const std::vector<int>& v_nodes)
MaxFlow::MaxFlow(const std::vector<int>& u_nodes, const std::vector<int>& v_nodes,
const std::unordered_map<int, std::vector<int>>& uv_link_limits,
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits,
const std::vector<int>& u_capacity,
const std::vector<int>& v_capacity)
{
assert(u_capacity.empty() || u_capacity.size() == u_nodes.size());
assert(v_capacity.empty() || v_capacity.size() == v_nodes.size());
l_nodes = u_nodes;
r_nodes = v_nodes;
total_nodes = u_nodes.size() + v_nodes.size() + 2;
source_id = total_nodes - 2;
sink_id = total_nodes - 1;
adj.resize(total_nodes);
// add edge from source to left nodes
for (int idx = 0; idx < l_nodes.size(); ++idx) {
int capacity = u_capacity.empty() ? 1 : u_capacity[idx];
add_edge(source_id, idx, capacity);
}
// add edge from right nodes to sink node
for (int idx = 0; idx < r_nodes.size(); ++idx) {
int capacity = v_capacity.empty() ? 1 : v_capacity[idx];
add_edge(l_nodes.size() + idx, sink_id, capacity);
}
// add edge from left nodes to right nodes
for (int i = 0; i < l_nodes.size(); ++i) {
int from_idx = i;
// process link limits , i can only link to uv_link_limits
if (auto iter = uv_link_limits.find(i); iter != uv_link_limits.end()) {
for (auto r_id : iter->second)
add_edge(from_idx, l_nodes.size() + r_id, 1);
continue;
}
// process unlink limits
std::optional<std::vector<int>> unlink_limits;
if (auto iter = uv_unlink_limits.find(i); iter != uv_unlink_limits.end())
unlink_limits = iter->second;
for (int j = 0; j < r_nodes.size(); ++j) {
// check whether i can link to j
if (unlink_limits.has_value() && std::find(unlink_limits->begin(), unlink_limits->end(), j) != unlink_limits->end())
continue;
add_edge(from_idx, l_nodes.size() + j, 1);
}
}
}
void MaxFlow::add_edge(int from, int to, int capacity)
{
adj[from].emplace_back(edges.size());
edges.emplace_back(from, to, capacity);
//also add reverse edge ,set capacity to zero
adj[to].emplace_back(edges.size());
edges.emplace_back(to, from, 0);
}
std::vector<int> MaxFlow::solve() {
std::vector<int> augment;
std::vector<int> previous(total_nodes, 0);
while (1) {
std::vector<int>(total_nodes, 0).swap(augment);
std::queue<int> travel;
travel.push(source_id);
augment[source_id] = INF;
while (!travel.empty()) {
int from = travel.front();
travel.pop();
// traverse all linked edges
for (int i = 0; i < adj[from].size(); ++i) {
int eid = adj[from][i];
Edge& tmp = edges[eid];
if (augment[tmp.to] == 0 && tmp.capacity > tmp.flow) {
previous[tmp.to] = eid;
augment[tmp.to] = std::min(augment[from], tmp.capacity - tmp.flow);
travel.push(tmp.to);
}
}
// already find an extend path, stop and do update
if (augment[sink_id] != 0)
break;
}
// no longer have extend path
if (augment[sink_id] == 0)
break;
for (int i = sink_id; i != source_id; i = edges[previous[i]].from) {
edges[previous[i]].flow += augment[sink_id];
edges[previous[i] ^ 1].flow -= augment[sink_id];
}
}
std::vector<int> matching(l_nodes.size(), -1);
// to get the match info, just traverse the left nodes and
// check the edge with flow > 0 and linked to right nodes
for (int u = 0; u < l_nodes.size(); ++u) {
for (int eid : adj[u]) {
Edge& e = edges[eid];
if (e.flow > 0 && e.to >= l_nodes.size() && e.to < l_nodes.size() + r_nodes.size())
matching[e.from] = r_nodes[e.to - l_nodes.size()];
}
}
return matching;
}
MinCostMaxFlow::MinCostMaxFlow(const std::vector<std::vector<float>>& matrix_, const std::vector<int>& u_nodes, const std::vector<int>& v_nodes,
const std::unordered_map<int, std::vector<int>>& uv_link_limits,
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits,
const std::vector<int>& u_capacity,
const std::vector<int>& v_capacity)
{
assert(u_capacity.empty() || u_capacity.size() == u_nodes.size());
assert(v_capacity.empty() || v_capacity.size() == v_nodes.size());
matrix = matrix_;
l_nodes = u_nodes;
r_nodes = v_nodes;
@@ -21,24 +136,39 @@ namespace Slic3r
adj.resize(total_nodes);
//add edge from source to left nodes,set capacity to 1, cost to 0
for (int i = 0; i < l_nodes.size(); ++i)
add_edge(source_id, i, 1, 0);
//add edge from right nodes to sink,set capacity to 1, cost to 0
for (int i = 0; i < r_nodes.size(); ++i)
add_edge(l_nodes.size() + i, sink_id, 1, 0);
// add edge from source to left nodes,cost to 0
for (int i = 0; i < l_nodes.size(); ++i) {
int capacity = u_capacity.empty() ? 1 : u_capacity[i];
add_edge(source_id, i, capacity, 0);
}
// add edge from right nodes to sink,cost to 0
for (int i = 0; i < r_nodes.size(); ++i) {
int capacity = v_capacity.empty() ? 1 : v_capacity[i];
add_edge(l_nodes.size() + i, sink_id, capacity, 0);
}
// add edge from left node to right nodes
for (int i = 0; i < l_nodes.size(); ++i) {
int from_idx = i;
// process link limits, i can only link to link_limits
if (auto iter = uv_link_limits.find(i); iter != uv_link_limits.end()) {
for (auto r_id : iter->second)
add_edge(from_idx, l_nodes.size() + r_id, 1, get_distance(i, r_id));
continue;
}
// process unlink limits, check whether i can link to j
std::optional<std::vector<int>> unlink_limits;
if (auto iter = uv_unlink_limits.find(i); iter != uv_unlink_limits.end())
unlink_limits = iter->second;
for (int j = 0; j < r_nodes.size(); ++j) {
int to_idx = l_nodes.size() + j;
add_edge(from_idx, to_idx, 1, get_distance(i, j));
if (unlink_limits.has_value() && std::find(unlink_limits->begin(), unlink_limits->end(), j) != unlink_limits->end())
continue;
add_edge(from_idx, l_nodes.size() + j, 1, get_distance(i, j));
}
}
}
std::vector<int> MCMF::solve()
std::vector<int> MinCostMaxFlow::solve()
{
while (spfa(source_id, sink_id));
@@ -56,7 +186,7 @@ namespace Slic3r
return matching;
}
void MCMF::add_edge(int from, int to, int capacity, int cost)
void MinCostMaxFlow::add_edge(int from, int to, int capacity, int cost)
{
adj[from].emplace_back(edges.size());
edges.emplace_back(from, to, capacity, cost);
@@ -65,7 +195,7 @@ namespace Slic3r
edges.emplace_back(to, from, 0, -cost);
}
bool MCMF::spfa(int source, int sink)
bool MinCostMaxFlow::spfa(int source, int sink)
{
std::vector<int>dist(total_nodes, INF);
std::vector<bool>in_queue(total_nodes, false);
@@ -111,7 +241,7 @@ namespace Slic3r
return true;
}
int MCMF::get_distance(int idx_in_left, int idx_in_right)
int MinCostMaxFlow::get_distance(int idx_in_left, int idx_in_right)
{
if (l_nodes[idx_in_left] == -1) {
return 0;

View File

@@ -9,7 +9,37 @@ namespace Slic3r {
using FlushMatrix = std::vector<std::vector<float>>;
class MCMF
class MaxFlow
{
private:
const int INF = std::numeric_limits<int>::max();
struct Edge {
int from, to, capacity, flow;
Edge(int u, int v, int cap) :from(u), to(v), capacity(cap), flow(0) {}
};
public:
MaxFlow(const std::vector<int>& u_nodes, const std::vector<int>& v_nodes,
const std::unordered_map<int, std::vector<int>>& uv_link_limits = {},
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits = {},
const std::vector<int>& u_capacity = {},
const std::vector<int>& v_capacity = {}
);
std::vector<int> solve();
private:
void add_edge(int from, int to, int capacity);
int total_nodes;
int source_id;
int sink_id;
std::vector<Edge>edges;
std::vector<int>l_nodes;
std::vector<int>r_nodes;
std::vector<std::vector<int>>adj;
};
class MinCostMaxFlow
{
const int INF = std::numeric_limits<int>::max();
struct Edge
@@ -19,7 +49,12 @@ class MCMF
};
public:
MCMF(const FlushMatrix &matrix_, const std::vector<int> &u_nodes, const std::vector<int> &v_nodes);
MinCostMaxFlow(const std::vector<std::vector<float>>& matrix_, const std::vector<int>& u_nodes, const std::vector<int>& v_nodes,
const std::unordered_map<int, std::vector<int>>& uv_link_limits = {},
const std::unordered_map<int, std::vector<int>>& uv_unlink_limits = {},
const std::vector<int>& u_capacity = {},
const std::vector<int>& v_capacity = {}
);
std::vector<int> solve();
private:
@@ -28,16 +63,15 @@ private:
int get_distance(int idx_in_left, int idx_in_right);
private:
FlushMatrix matrix;
std::vector<std::vector<float>> matrix;
std::vector<int> l_nodes;
std::vector<int> r_nodes;
std::vector<Edge> edges;
std::vector<std::vector<int>> adj;
int total_nodes;
int source_id;
int sink_id;
std::vector<Edge> edges;
std::vector<std::vector<int>> adj;
};
std::vector<unsigned int> get_extruders_order(const std::vector<std::vector<float>> &wipe_volumes,

View File

@@ -36,8 +36,10 @@ static std::set<int>get_filament_by_type(const std::vector<unsigned int>& used_f
return target_filaments;
}
std::vector<std::set<int>> ToolOrdering::get_physical_unprintables(const std::vector<unsigned int>& used_filaments, const PrintConfig* config, int master_extruder_id)
std::vector<std::set<int>> ToolOrdering::get_physical_unprintables(const std::vector<unsigned int>& used_filaments, const PrintConfig* config)
{
// master saved in config is 1 based,so we should transfer to 0 based here
int master_extruder_id = config->master_extruder_id.value - 1;
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."));
@@ -540,9 +542,10 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto
}
}
bool ToolOrdering::check_tpu_group(const std::vector<unsigned int>&used_filaments,const std::vector<int>& filament_maps,const PrintConfig* config,int master_extruder_id)
bool ToolOrdering::check_tpu_group(const std::vector<unsigned int>&used_filaments,const std::vector<int>& filament_maps,const PrintConfig* config)
{
int check_extruder_id = -1;
int master_extruder_id = config->master_extruder_id.value - 1; // transfer to 0 based idx
std::map<int, std::vector<int>> extruder_filament_nums;
for (unsigned int filament_id : used_filaments) {
int extruder_id = filament_maps[filament_id];
@@ -559,7 +562,7 @@ bool ToolOrdering::check_tpu_group(const std::vector<unsigned int>&used_filament
}
// TPU can only place in master extruder, and it should have no other filaments in the same extruder
if (check_extruder_id != -1 && (check_extruder_id!=master_extruder_id || extruder_filament_nums[check_extruder_id].size() > 1)) {
if (check_extruder_id != -1 && (check_extruder_id != master_extruder_id || extruder_filament_nums[check_extruder_id].size() > 1)) {
return false;
}
@@ -943,17 +946,21 @@ std::vector<int> ToolOrdering::get_recommended_filament_maps(const std::vector<s
const auto &ams_count = extruder_ams_counts[i];
for (auto iter = ams_count.begin(); iter != ams_count.end(); ++iter) { group_size[i] += iter->first * iter->second; }
}
// When the AMS count is 0, only external filament can be used, so set the capacity to 1.
for(auto& size: group_size)
if(size == 0)
size = 1;
}
FilamentGroupContext context;
context.flush_matrix = std::move(nozzle_flush_mtx);
context.geometric_unprintables = geometric_unprintables;
context.physical_unprintables = physical_unprintables;
context.max_group_size = std::move(group_size);
context.total_filament_num = (int)filament_nums;
// TODO: load master extruder id from config
int master_extruder_id = 1;
{
context.flush_matrix = std::move(nozzle_flush_mtx);
context.geometric_unprintables = geometric_unprintables;
context.physical_unprintables = physical_unprintables;
context.max_group_size = std::move(group_size);
context.total_filament_num = (int)filament_nums;
context.master_extruder_id = print_config->master_extruder_id.value - 1; // transfer to 0 based idx
}
// speacially handle tpu filaments
auto used_filaments = collect_sorted_used_filaments(layer_filaments);
auto tpu_filaments = get_filament_by_type(used_filaments, print_config, "TPU");
@@ -961,9 +968,9 @@ std::vector<int> ToolOrdering::get_recommended_filament_maps(const std::vector<s
if (!tpu_filaments.empty()) {
for (size_t fidx = 0; fidx < filament_nums; ++fidx) {
if (tpu_filaments.count(fidx))
ret[fidx] = master_extruder_id;
ret[fidx] = context.master_extruder_id;
else
ret[fidx] = 1 - master_extruder_id;
ret[fidx] = 1 - context.master_extruder_id;
}
}
else {
@@ -972,7 +979,16 @@ std::vector<int> ToolOrdering::get_recommended_filament_maps(const std::vector<s
fg.get_custom_seq = get_custom_seq;
ret = fg.calc_filament_group(layer_filaments, FGStrategy::BestFit);
auto memoryed_maps = fg.get_memoryed_groups();
// optimize for master extruder id
optimize_group_for_master_extruder(used_filaments, context, ret);
// optimize according to AMS filaments
std::vector<std::vector<int>>memoryed_maps{ ret };
{
auto tmp_maps = fg.get_memoryed_groups();
memoryed_maps.insert(memoryed_maps.end(), std::make_move_iterator(tmp_maps.begin()), std::make_move_iterator(tmp_maps.end()));
}
std::vector<std::string>used_colors;
for (size_t idx = 0; idx < used_filaments.size(); ++idx)
@@ -1073,8 +1089,7 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first
}
std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value - 1; });
// TODO: load the master extruder_id from config
if (!check_tpu_group(used_filaments, filament_maps, print_config, 1)) {
if (!check_tpu_group(used_filaments, filament_maps, print_config)) {
if (map_mode == FilamentMapMode::fmmManual) {
throw Slic3r::RuntimeError(std::string("Manual grouping error: TPU can only be placed in a nozzle alone."));
}

View File

@@ -236,9 +236,9 @@ public:
*/
static std::vector<int> get_recommended_filament_maps(const std::vector<std::vector<unsigned int>>& layer_filaments, const PrintConfig* print_config, const Print* print, const std::vector<std::set<int>>& physical_unprintables, const std::vector<std::set<int>>& geometric_unprintables);
static std::vector<std::set<int>> get_physical_unprintables(const std::vector<unsigned int>& layer_filaments, const PrintConfig* config, int master_extruder_id = 1);
static std::vector<std::set<int>> get_physical_unprintables(const std::vector<unsigned int>& layer_filaments, const PrintConfig* config);
static std::vector<std::set<int>> get_geometrical_unprintables(const std::vector<std::vector<int>>& unprintable_arrs, const PrintConfig* config);
static bool check_tpu_group(const std::vector<unsigned int>&used_filaments,const std::vector<int>& filament_maps,const PrintConfig* config,int master_extruder_id);
static bool check_tpu_group(const std::vector<unsigned int>&used_filaments,const std::vector<int>& filament_maps,const PrintConfig* config);
// should be called after doing reorder
FilamentChangeStats get_filament_change_stats(FilamentChangeMode mode);