From e59a10e0c28f3daeb7793a75a23c416d7112d05a Mon Sep 17 00:00:00 2001
From: bubnikv <bubnikv@gmail.com>
Date: Fri, 1 Feb 2019 17:15:41 +0100
Subject: [PATCH] Fix of a cooling slow down logic. fixes "Min print speed" to
 "Estimated Print Time" Inconsistencies #1488

---
 src/libslic3r/GCode/CoolingBuffer.cpp | 95 +++++++++++++++++++++------
 src/libslic3r/GCode/CoolingBuffer.hpp |  2 +-
 2 files changed, 76 insertions(+), 21 deletions(-)

diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp
index 40ccc7b09..09d211994 100644
--- a/src/libslic3r/GCode/CoolingBuffer.cpp
+++ b/src/libslic3r/GCode/CoolingBuffer.cpp
@@ -83,7 +83,7 @@ struct CoolingLine
 struct PerExtruderAdjustments 
 {
     // Calculate the total elapsed time per this extruder, adjusted for the slowdown.
-    float elapsed_time_total() {
+    float elapsed_time_total() const {
         float time_total = 0.f;
         for (const CoolingLine &line : lines)
             time_total += line.time;
@@ -91,7 +91,7 @@ struct PerExtruderAdjustments
     }
     // Calculate the total elapsed time when slowing down 
     // to the minimum extrusion feed rate defined for the current material.
-    float maximum_time_after_slowdown(bool slowdown_external_perimeters) {
+    float maximum_time_after_slowdown(bool slowdown_external_perimeters) const {
         float time_total = 0.f;
         for (const CoolingLine &line : lines)
             if (line.adjustable(slowdown_external_perimeters)) {
@@ -104,7 +104,7 @@ struct PerExtruderAdjustments
         return time_total;
     }
     // Calculate the adjustable part of the total time.
-    float adjustable_time(bool slowdown_external_perimeters) {
+    float adjustable_time(bool slowdown_external_perimeters) const {
         float time_total = 0.f;
         for (const CoolingLine &line : lines)
             if (line.adjustable(slowdown_external_perimeters))
@@ -112,7 +112,7 @@ struct PerExtruderAdjustments
         return time_total;
     }
     // Calculate the non-adjustable part of the total time.
-    float non_adjustable_time(bool slowdown_external_perimeters) {
+    float non_adjustable_time(bool slowdown_external_perimeters) const {
         float time_total = 0.f;
         for (const CoolingLine &line : lines)
             if (! line.adjustable(slowdown_external_perimeters))
@@ -169,7 +169,7 @@ struct PerExtruderAdjustments
     // Calculate the maximum time stretch when slowing down to min_feedrate.
     // Slowdown to min_feedrate shall be allowed for this extruder's material.
     // Used by non-proportional slow down.
-    float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) {
+    float time_stretch_when_slowing_down_to_feedrate(float min_feedrate) const {
         float time_stretch = 0.f;
         assert(this->min_print_speed < min_feedrate + EPSILON);
         for (size_t i = 0; i < n_lines_adjustable; ++ i) {
@@ -221,6 +221,61 @@ struct PerExtruderAdjustments
     size_t                      idx_line_end        = 0;
 };
 
+// Calculate a new feedrate when slowing down by time_stretch for segments faster than min_feedrate.
+// Used by non-proportional slow down.
+float new_feedrate_to_reach_time_stretch(
+    std::vector<PerExtruderAdjustments*>::const_iterator it_begin, std::vector<PerExtruderAdjustments*>::const_iterator it_end, 
+    float min_feedrate, float time_stretch, size_t max_iter = 20)
+{
+	float new_feedrate = min_feedrate;
+    for (size_t iter = 0; iter < max_iter; ++ iter) {
+        float nomin = 0;
+        float denom = time_stretch;
+        for (auto it = it_begin; it != it_end; ++ it) {
+			assert((*it)->min_print_speed < min_feedrate + EPSILON);
+			for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) {
+				const CoolingLine &line = (*it)->lines[i];
+                if (line.feedrate > min_feedrate) {
+                    nomin += line.time * line.feedrate;
+                    denom += line.time;
+                }
+            }
+        }
+        assert(denom > 0);
+        if (denom < 0)
+            return min_feedrate;
+        new_feedrate = nomin / denom;
+        assert(new_feedrate > min_feedrate - EPSILON);
+        if (new_feedrate < min_feedrate + EPSILON)
+            goto finished;
+        for (auto it = it_begin; it != it_end; ++ it)
+			for (size_t i = 0; i < (*it)->n_lines_adjustable; ++i) {
+				const CoolingLine &line = (*it)->lines[i];
+                if (line.feedrate > min_feedrate && line.feedrate < new_feedrate)
+                    // Some of the line segments taken into account in the calculation of nomin / denom are now slower than new_feedrate.
+                    // Re-run the calculation with a new min_feedrate limit.
+                    goto not_finished_yet;
+            }
+        goto finished;
+not_finished_yet:
+        min_feedrate = new_feedrate;
+    }
+    // Failed to find the new feedrate for the time_stretch.
+
+finished:
+    // Test whether the time_stretch was achieved.
+#ifndef NDEBUG
+    {
+        float time_stretch_final = 0.f;
+        for (auto it = it_begin; it != it_end; ++ it)
+            time_stretch_final += (*it)->time_stretch_when_slowing_down_to_feedrate(new_feedrate);
+        assert(std::abs(time_stretch - time_stretch_final) < EPSILON);
+    }
+#endif /* NDEBUG */
+
+	return new_feedrate;
+}
+
 std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_id)
 {
     std::vector<PerExtruderAdjustments> per_extruder_adjustments = this->parse_layer_gcode(gcode, m_current_pos);
@@ -241,12 +296,12 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
     std::vector<PerExtruderAdjustments> per_extruder_adjustments(extruders.size());
     std::vector<size_t>                 map_extruder_to_per_extruder_adjustment(num_extruders, 0);
     for (size_t i = 0; i < extruders.size(); ++ i) {
-		PerExtruderAdjustments &adj			= per_extruder_adjustments[i];
-		unsigned int			extruder_id = extruders[i].id();
-		adj.extruder_id				  = extruder_id;
-		adj.cooling_slow_down_enabled = config.cooling.get_at(extruder_id);
-		adj.slowdown_below_layer_time = config.slowdown_below_layer_time.get_at(extruder_id);
-		adj.min_print_speed			  = config.min_print_speed.get_at(extruder_id);
+        PerExtruderAdjustments &adj         = per_extruder_adjustments[i];
+        unsigned int            extruder_id = extruders[i].id();
+        adj.extruder_id               = extruder_id;
+        adj.cooling_slow_down_enabled = config.cooling.get_at(extruder_id);
+        adj.slowdown_below_layer_time = config.slowdown_below_layer_time.get_at(extruder_id);
+        adj.min_print_speed           = config.min_print_speed.get_at(extruder_id);
         map_extruder_to_per_extruder_adjustment[extruder_id] = i;
     }
 
@@ -452,14 +507,14 @@ static inline void extruder_range_slow_down_non_proportional(
     std::vector<PerExtruderAdjustments*> by_min_print_speed(it_begin, it_end);
     // Find the next highest adjustable feedrate among the extruders.
     float feedrate = 0;
-	for (PerExtruderAdjustments *adj : by_min_print_speed) {
-		adj->idx_line_begin = 0;
-		adj->idx_line_end   = 0;
-		assert(adj->idx_line_begin < adj->n_lines_adjustable);
-		if (adj->lines[adj->idx_line_begin].feedrate > feedrate)
-			feedrate = adj->lines[adj->idx_line_begin].feedrate;
-	}
-	assert(feedrate > 0.f);
+    for (PerExtruderAdjustments *adj : by_min_print_speed) {
+        adj->idx_line_begin = 0;
+        adj->idx_line_end   = 0;
+        assert(adj->idx_line_begin < adj->n_lines_adjustable);
+        if (adj->lines[adj->idx_line_begin].feedrate > feedrate)
+            feedrate = adj->lines[adj->idx_line_begin].feedrate;
+    }
+    assert(feedrate > 0.f);
     // Sort by min_print_speed, maximum speed first.
     std::sort(by_min_print_speed.begin(), by_min_print_speed.end(), 
         [](const PerExtruderAdjustments *p1, const PerExtruderAdjustments *p2){ return p1->min_print_speed > p2->min_print_speed; });
@@ -496,7 +551,7 @@ static inline void extruder_range_slow_down_non_proportional(
                 for (auto it = adj; it != by_min_print_speed.end(); ++ it)
                     time_stretch_max += (*it)->time_stretch_when_slowing_down_to_feedrate(feedrate_limit);
                 if (time_stretch_max >= time_stretch) {
-                    feedrate_limit = feedrate - (feedrate - feedrate_limit) * time_stretch / time_stretch_max;
+                    feedrate_limit = new_feedrate_to_reach_time_stretch(adj, by_min_print_speed.end(), feedrate_limit, time_stretch, 20);
                     done = true;
                 } else
                     time_stretch -= time_stretch_max;
diff --git a/src/libslic3r/GCode/CoolingBuffer.hpp b/src/libslic3r/GCode/CoolingBuffer.hpp
index bf4b082e2..511089ad0 100644
--- a/src/libslic3r/GCode/CoolingBuffer.hpp
+++ b/src/libslic3r/GCode/CoolingBuffer.hpp
@@ -9,7 +9,7 @@ namespace Slic3r {
 
 class GCode;
 class Layer;
-class PerExtruderAdjustments;
+struct PerExtruderAdjustments;
 
 // A standalone G-code filter, to control cooling of the print.
 // The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited