From 6c627be4c18cc747df9040ed3bceb2bc14823f0b Mon Sep 17 00:00:00 2001
From: bubnikv <bubnikv@gmail.com>
Date: Wed, 25 Apr 2018 10:37:31 +0200
Subject: [PATCH 1/4] New cooling logic to equalize extrusion velocity. The old
 behavior caused bad outer surface print quality on Prusa i3 MK3

---
 xs/src/libslic3r/GCode/CoolingBuffer.cpp | 479 +++++++++++++++--------
 1 file changed, 306 insertions(+), 173 deletions(-)

diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.cpp b/xs/src/libslic3r/GCode/CoolingBuffer.cpp
index cd2baeffb..121cbb017 100644
--- a/xs/src/libslic3r/GCode/CoolingBuffer.cpp
+++ b/xs/src/libslic3r/GCode/CoolingBuffer.cpp
@@ -30,6 +30,174 @@ void CoolingBuffer::reset()
     m_current_pos[4] = float(m_gcodegen.config().travel_speed.value);
 }
 
+struct CoolingLine
+{
+    enum Type {
+        TYPE_SET_TOOL           = 1 << 0,
+        TYPE_EXTRUDE_END        = 1 << 1,
+        TYPE_BRIDGE_FAN_START   = 1 << 2,
+        TYPE_BRIDGE_FAN_END     = 1 << 3,
+        TYPE_G0                 = 1 << 4,
+        TYPE_G1                 = 1 << 5,
+        TYPE_ADJUSTABLE         = 1 << 6,
+        TYPE_EXTERNAL_PERIMETER = 1 << 7,
+        // The line sets a feedrate.
+        TYPE_HAS_F              = 1 << 8,
+        TYPE_WIPE               = 1 << 9,
+        TYPE_G4                 = 1 << 10,
+        TYPE_G92                = 1 << 11,
+    };
+
+    CoolingLine(unsigned int type, size_t  line_start, size_t  line_end) :
+        type(type), line_start(line_start), line_end(line_end),
+        length(0.f), time(0.f), time_max(0.f), slowdown(false) {}
+
+    bool adjustable(bool slowdown_external_perimeters) const {
+        return (this->type & TYPE_ADJUSTABLE) && 
+               (! (this->type & TYPE_EXTERNAL_PERIMETER) || slowdown_external_perimeters) &&
+               this->time < this->time_max;
+    }
+
+    bool adjustable() const {
+        return (this->type & TYPE_ADJUSTABLE) && this->time < this->time_max;
+    }
+
+    size_t  type;
+    // Start of this line at the G-code snippet.
+    size_t  line_start;
+    // End of this line at the G-code snippet.
+    size_t  line_end;
+    // XY Euclidian length of this segment.
+    float   length;
+    // Current feedrate, possibly adjusted.
+    float   feedrate;
+    // Current duration of this segment.
+    float   time;
+    // Maximum duration of this segment.
+    float   time_max;
+    // If marked with the "slowdown" flag, the line has been slowed down.
+    bool    slowdown;
+};
+
+// Calculate the required per extruder time stretches.
+struct PerExtruderAdjustments 
+{
+    // Calculate the total elapsed time per this extruder, adjusted for the slowdown.
+    float elapsed_time_total() {
+        float time_total = 0.f;
+        for (const CoolingLine &line : lines)
+            time_total += line.time;
+        return time_total;
+    }
+    // Calculate the maximum time when slowing down.
+    float maximum_time(bool slowdown_external_perimeters) {
+        float time_total = 0.f;
+        for (const CoolingLine &line : lines)
+            if (line.adjustable(slowdown_external_perimeters)) {
+                if (line.time_max == FLT_MAX)
+                    return FLT_MAX;
+                else
+                    time_total += line.time_max;
+            } else
+                time_total += line.time;
+        return time_total;
+    }
+    // Calculate the non-adjustable part of the total time.
+    float non_adjustable_time(bool slowdown_external_perimeters) {
+        float time_total = 0.f;
+        for (const CoolingLine &line : lines)
+            if (! line.adjustable(slowdown_external_perimeters))
+                time_total += line.time;
+        return time_total;
+    }
+    float slow_down_maximum(bool slowdown_external_perimeters) {
+        float time_total = 0.f;
+        for (CoolingLine &line : lines) {
+            if (line.adjustable(slowdown_external_perimeters)) {
+                assert(line.time_max >= 0.f && line.time_max < FLT_MAX);
+                line.slowdown = true;
+                line.time     = line.time_max;
+                line.feedrate = line.length / line.time;
+            }
+            time_total += line.time;
+        }
+        return time_total;
+    }
+    float slow_down_proportional(float factor, bool slowdown_external_perimeters) {
+        assert(factor >= 1.f);
+        float time_total = 0.f;
+        for (CoolingLine &line : lines) {
+            if (line.adjustable(slowdown_external_perimeters)) {
+                line.slowdown = true;
+                line.time     = std::min(line.time_max, line.time * factor);
+                line.feedrate = line.length / line.time;
+            }
+            time_total += line.time;
+        }
+        return time_total;
+    }
+
+    bool operator<(const PerExtruderAdjustments &rhs) const { return this->extruder_id < rhs.extruder_id; }
+
+    // Sort the lines, adjustable first, higher feedrate first.
+    void sort_lines_by_decreasing_feedrate() {
+        std::sort(lines.begin(), lines.end(), [](const CoolingLine &l1, const CoolingLine &l2) {
+            bool adj1 = l1.adjustable();
+            bool adj2 = l2.adjustable();
+            return (adj1 == adj2) ? l1.feedrate > l2.feedrate : adj1;
+        });
+        for (n_lines_adjustable = 0; n_lines_adjustable < lines.size(); ++ n_lines_adjustable)
+            if ((this->lines[n_lines_adjustable].type & CoolingLine::TYPE_ADJUSTABLE) == 0)
+                break;
+        time_non_adjustable = 0.f;
+        for (size_t i = n_lines_adjustable; i < lines.size(); ++ i)
+            time_non_adjustable += lines[i].time;
+    }
+
+    // Calculate the maximum time when slowing down.
+    float time_stretch_when_slowing_down_to(float min_feedrate) {
+        float time_stretch = 0.f;
+        if (this->min_print_speed < min_feedrate + EPSILON) {
+            for (size_t i = 0; i < n_lines_adjustable; ++ i) {
+                const CoolingLine &line = lines[i];
+                if (line.feedrate > min_feedrate)
+                    time_stretch += line.time * (line.feedrate / min_feedrate - 1.f);
+            }
+        }
+        return time_stretch;
+    }
+
+    void slow_down_to(float min_feedrate) {
+        if (this->min_print_speed < min_feedrate + EPSILON) {
+            for (size_t i = 0; i < n_lines_adjustable; ++ i) {
+                CoolingLine &line = lines[i];
+                if (line.feedrate > min_feedrate) {
+                    line.time *= std::max(1.f, line.feedrate / min_feedrate);
+                    line.feedrate = min_feedrate;
+                    line.slowdown = true;
+                }
+            }
+        }
+    }
+
+    // Extruder, for which the G-code will be adjusted.
+    unsigned int                extruder_id         = 0;
+    // Minimum print speed allowed for this extruder.
+    float                       min_print_speed     = 0.f;
+
+    // Parsed lines.
+    std::vector<CoolingLine>    lines;
+    // The following two values are set by sort_lines_by_decreasing_feedrate():
+    // Number of adjustable lines, at the start of lines.
+    size_t                      n_lines_adjustable  = 0;
+    // Non-adjustable time of lines starting with n_lines_adjustable. 
+    float                       time_non_adjustable = 0;
+
+    // Temporaries for processing the slow down. Both thresholds go from 0 to n_lines_adjustable.
+    size_t                      idx_line_begin      = 0;
+    size_t                      idx_line_end        = 0;
+};
+
 #define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder)
 
 std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_id)
@@ -38,125 +206,23 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
     const std::vector<Extruder> &extruders     = m_gcodegen.writer().extruders();
     const size_t                 num_extruders = extruders.size();
 
-    // Calculate the required per extruder time stretches.
-    struct Adjustment {
-        Adjustment(unsigned int extruder_id = 0) : extruder_id(extruder_id) {}
-        // Calculate the total elapsed time per this extruder, adjusted for the slowdown.
-        float elapsed_time_total() {
-            float time_total = 0.f;
-            for (const Line &line : lines)
-                time_total += line.time;
-            return time_total;
-        }
-        // Calculate the maximum time when slowing down.
-        float maximum_time(bool slowdown_external_perimeters) {
-            float time_total = 0.f;
-			for (const Line &line : lines)
-				if (line.adjustable(slowdown_external_perimeters)) {
-					if (line.time_max == FLT_MAX)
-						return FLT_MAX;
-					else
-						time_total += line.time_max;
-				} else
-					time_total += line.time;
-            return time_total;
-        }
-        // Calculate the non-adjustable part of the total time.
-        float non_adjustable_time(bool slowdown_external_perimeters) {
-            float time_total = 0.f;
-            for (const Line &line : lines)
-                if (! line.adjustable(slowdown_external_perimeters))
-                    time_total += line.time;
-            return time_total;
-        }
-        float slow_down_maximum(bool slowdown_external_perimeters) {
-            float time_total = 0.f;
-            for (Line &line : lines) {
-                if (line.adjustable(slowdown_external_perimeters)) {
-					assert(line.time_max >= 0.f && line.time_max < FLT_MAX);
-                    line.slowdown = true;
-                    line.time = line.time_max;
-                }
-                time_total += line.time;
-            }
-            return time_total;
-        }
-        float slow_down_proportional(float factor, bool slowdown_external_perimeters) {
-            assert(factor >= 1.f);
-            float time_total = 0.f;
-            for (Line &line : lines) {
-                if (line.adjustable(slowdown_external_perimeters)) {
-                    line.slowdown = true;
-                    line.time = std::min(line.time_max, line.time * factor);
-                }
-                time_total += line.time;
-            }
-            return time_total;
-        }
-
-        bool operator<(const Adjustment &rhs) const { return this->extruder_id < rhs.extruder_id; }
-
-        struct Line
-        {
-            enum Type {
-                TYPE_SET_TOOL           = 1 << 0,
-                TYPE_EXTRUDE_END        = 1 << 1,
-                TYPE_BRIDGE_FAN_START   = 1 << 2,
-                TYPE_BRIDGE_FAN_END     = 1 << 3,
-                TYPE_G0                 = 1 << 4,
-                TYPE_G1                 = 1 << 5,
-                TYPE_ADJUSTABLE         = 1 << 6,
-                TYPE_EXTERNAL_PERIMETER = 1 << 7,
-                // The line sets a feedrate.
-                TYPE_HAS_F              = 1 << 8,
-                TYPE_WIPE               = 1 << 9,
-                TYPE_G4                 = 1 << 10,
-                TYPE_G92                = 1 << 11,
-            };
-
-            Line(unsigned int type, size_t  line_start, size_t  line_end) :
-                type(type), line_start(line_start), line_end(line_end),
-                length(0.f), time(0.f), time_max(0.f), slowdown(false) {}
-
-            bool adjustable(bool slowdown_external_perimeters) const {
-                return (this->type & TYPE_ADJUSTABLE) && 
-                       (! (this->type & TYPE_EXTERNAL_PERIMETER) || slowdown_external_perimeters) &&
-                       this->time < this->time_max;
-            }
-
-            size_t  type;
-            // Start of this line at the G-code snippet.
-            size_t  line_start;
-            // End of this line at the G-code snippet.
-            size_t  line_end;
-            // XY Euclidian length of this segment.
-            float   length;
-            // Current duration of this segment.
-            float   time;
-            // Maximum duration of this segment.
-            float   time_max;
-            // If marked with the "slowdown" flag, the line has been slowed down.
-            bool    slowdown;
-        };
-
-        // Extruder, for which the G-code will be adjusted.
-        unsigned int        extruder_id;
-        // Parsed lines.
-        std::vector<Line>   lines;
-    };
-    std::vector<Adjustment> adjustments(num_extruders, Adjustment());
-    for (size_t i = 0; i < num_extruders; ++ i)
-        adjustments[i].extruder_id = extruders[i].id();
-    const std::string       toolchange_prefix = m_gcodegen.writer().toolchange_prefix();
+    std::vector<PerExtruderAdjustments> per_extruder_adjustments(num_extruders);
+    std::vector<size_t>                 map_extruder_to_per_extruder_adjustment(num_extruders, 0);
+    for (size_t i = 0; i < num_extruders; ++ i) {
+        unsigned int extruder_id = extruders[i].id();
+        per_extruder_adjustments[i].extruder_id = extruder_id;
+        per_extruder_adjustments[i].min_print_speed = config.min_print_speed.get_at(i);
+        map_extruder_to_per_extruder_adjustment[extruder_id] = i;
+    }
+    const std::string     toolchange_prefix = m_gcodegen.writer().toolchange_prefix();
     // Parse the layer G-code for the moves, which could be adjusted.
     {
-        float             min_print_speed   = float(EXTRUDER_CONFIG(min_print_speed));
-        auto              adjustment        = std::lower_bound(adjustments.begin(), adjustments.end(), Adjustment(m_current_extruder));
+        PerExtruderAdjustments *adjustment  = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[m_current_extruder]];
         unsigned int      initial_extruder  = m_current_extruder;
 		const char       *line_start = gcode.c_str();
 		const char		 *line_end   = line_start;
         const char        extrusion_axis = config.get_extrusion_axis()[0];
-        // Index of an existing Adjustment::Line of the current adjustment, which holds the feedrate setting command
+        // Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command
         // for a sequence of extrusion moves.
         size_t            active_speed_modifier = size_t(-1);
 		for (; *line_start != 0; line_start = line_end) {
@@ -164,16 +230,16 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
                 ++ line_end;
             // sline will not contain the trailing '\n'.
             std::string sline(line_start, line_end);
-            // Adjustment::Line will contain the trailing '\n'.
+            // CoolingLine will contain the trailing '\n'.
             if (*line_end == '\n')
                 ++ line_end;
-            Adjustment::Line line(0, line_start - gcode.c_str(), line_end - gcode.c_str());
+            CoolingLine line(0, line_start - gcode.c_str(), line_end - gcode.c_str());
             if (boost::starts_with(sline, "G0 "))
-                line.type = Adjustment::Line::TYPE_G0;
+                line.type = CoolingLine::TYPE_G0;
             else if (boost::starts_with(sline, "G1 "))
-                line.type = Adjustment::Line::TYPE_G1;
+                line.type = CoolingLine::TYPE_G1;
             else if (boost::starts_with(sline, "G92 "))
-                line.type = Adjustment::Line::TYPE_G92;
+                line.type = CoolingLine::TYPE_G92;
             if (line.type) {
                 // G0, G1 or G92
                 // Parse the G-code line.
@@ -192,9 +258,9 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
 						if (axis == 4) {
 							// Convert mm/min to mm/sec.
 							new_pos[4] /= 60.f;
-                            if ((line.type & Adjustment::Line::TYPE_G92) == 0)
+                            if ((line.type & CoolingLine::TYPE_G92) == 0)
                                 // This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls.
-                                line.type |= Adjustment::Line::TYPE_HAS_F;
+                                line.type |= CoolingLine::TYPE_HAS_F;
                         }
 					}
                     // Skip this word.
@@ -203,14 +269,14 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
                 bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER");
                 bool wipe               = boost::contains(sline, ";_WIPE");
                 if (external_perimeter)
-                    line.type |= Adjustment::Line::TYPE_EXTERNAL_PERIMETER;
+                    line.type |= CoolingLine::TYPE_EXTERNAL_PERIMETER;
                 if (wipe)
-                    line.type |= Adjustment::Line::TYPE_WIPE;
+                    line.type |= CoolingLine::TYPE_WIPE;
                 if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && ! wipe) {
-                    line.type |= Adjustment::Line::TYPE_ADJUSTABLE;
+                    line.type |= CoolingLine::TYPE_ADJUSTABLE;
                     active_speed_modifier = adjustment->lines.size();
                 }
-                if ((line.type & Adjustment::Line::TYPE_G92) == 0) {
+                if ((line.type & CoolingLine::TYPE_G92) == 0) {
                     // G0 or G1. Calculate the duration.
                     if (config.use_relative_e_distances.value)
                         // Reset extruder accumulator.
@@ -227,15 +293,17 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
                         // Movement in the extruder axis.
                         line.length = std::abs(dif[3]);
                     }
-                    if (line.length > 0)
-                        line.time   = line.length / new_pos[4]; // current F
+                    if (line.length > 0) {
+                        line.feedrate = new_pos[4]; // current F
+                        line.time   = line.length / line.feedrate;
+                    }
                     line.time_max = line.time;
-					if ((line.type & Adjustment::Line::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1))
-                        line.time_max = (min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / min_print_speed);
-					if (active_speed_modifier < adjustment->lines.size() && (line.type & Adjustment::Line::TYPE_G1)) {
+					if ((line.type & CoolingLine::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1))
+                        line.time_max = (adjustment->min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->min_print_speed);
+					if (active_speed_modifier < adjustment->lines.size() && (line.type & CoolingLine::TYPE_G1)) {
                         // Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry.
-                        assert((line.type & Adjustment::Line::TYPE_HAS_F) == 0);
-						Adjustment::Line &sm = adjustment->lines[active_speed_modifier];
+                        assert((line.type & CoolingLine::TYPE_HAS_F) == 0);
+						CoolingLine &sm = adjustment->lines[active_speed_modifier];
 						sm.length   += line.length;
 						sm.time     += line.time;
 						if (sm.time_max != FLT_MAX) {
@@ -250,24 +318,23 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
 				}
                 m_current_pos = std::move(new_pos);
             } else if (boost::starts_with(sline, ";_EXTRUDE_END")) {
-                line.type = Adjustment::Line::TYPE_EXTRUDE_END;
+                line.type = CoolingLine::TYPE_EXTRUDE_END;
                 active_speed_modifier = size_t(-1);
             } else if (boost::starts_with(sline, toolchange_prefix)) {
                 // Switch the tool.
-                line.type = Adjustment::Line::TYPE_SET_TOOL;
+                line.type = CoolingLine::TYPE_SET_TOOL;
                 unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + toolchange_prefix.size());
                 if (new_extruder != m_current_extruder) {
                     m_current_extruder = new_extruder;
-                    min_print_speed    = float(EXTRUDER_CONFIG(min_print_speed));
-                    adjustment         = std::lower_bound(adjustments.begin(), adjustments.end(), Adjustment(m_current_extruder));
+                    adjustment         = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[m_current_extruder]];
                 }
             } else if (boost::starts_with(sline, ";_BRIDGE_FAN_START")) {
-                line.type = Adjustment::Line::TYPE_BRIDGE_FAN_START;
+                line.type = CoolingLine::TYPE_BRIDGE_FAN_START;
             } else if (boost::starts_with(sline, ";_BRIDGE_FAN_END")) {
-                line.type = Adjustment::Line::TYPE_BRIDGE_FAN_END;
+                line.type = CoolingLine::TYPE_BRIDGE_FAN_END;
             } else if (boost::starts_with(sline, "G4 ")) {
                 // Parse the wait time.
-                line.type = Adjustment::Line::TYPE_G4;
+                line.type = CoolingLine::TYPE_G4;
                 size_t pos_S = sline.find('S', 3);
                 size_t pos_P = sline.find('P', 3);
                 line.time = line.time_max = float(
@@ -281,18 +348,19 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
     }
 
     // Sort the extruders by the increasing slowdown_below_layer_time.
-    std::vector<size_t> by_slowdown_layer_time;
-    by_slowdown_layer_time.reserve(num_extruders);
+    std::vector<size_t> extruder_by_slowdown_time;
+    extruder_by_slowdown_time.reserve(num_extruders);
     // Only insert entries, which are adjustable (have cooling enabled and non-zero stretchable time).
     // Collect total print time of non-adjustable extruders.
     float elapsed_time_total_non_adjustable = 0.f;
     for (size_t i = 0; i < num_extruders; ++ i) {
-        if (config.cooling.get_at(extruders[i].id()))
-            by_slowdown_layer_time.emplace_back(i);
-        else
-            elapsed_time_total_non_adjustable += adjustments[i].elapsed_time_total();
+        if (config.cooling.get_at(extruders[i].id())) {
+            extruder_by_slowdown_time.emplace_back(i);
+            per_extruder_adjustments[i].sort_lines_by_decreasing_feedrate();
+        } else
+            elapsed_time_total_non_adjustable += per_extruder_adjustments[i].elapsed_time_total();
     }
-    std::sort(by_slowdown_layer_time.begin(), by_slowdown_layer_time.end(),
+    std::sort(extruder_by_slowdown_time.begin(), extruder_by_slowdown_time.end(),
         [&config, &extruders](const size_t idx1, const size_t idx2){
             return config.slowdown_below_layer_time.get_at(extruders[idx1].id()) < 
                    config.slowdown_below_layer_time.get_at(extruders[idx2].id());
@@ -303,18 +371,18 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
     {
         // Elapsed time for the already adjusted extruders.
 		float elapsed_time_total0 = elapsed_time_total_non_adjustable;
-        for (size_t i_by_slowdown_layer_time = 0; i_by_slowdown_layer_time < by_slowdown_layer_time.size(); ++ i_by_slowdown_layer_time) {
-            // Idx in adjustments.
-            size_t idx = by_slowdown_layer_time[i_by_slowdown_layer_time];
-            // Macro to sum or adjust all sections starting with i_by_slowdown_layer_time.
+        for (size_t i_extruder_by_slowdown_time = 0; i_extruder_by_slowdown_time < extruder_by_slowdown_time.size(); ++ i_extruder_by_slowdown_time) {
+            // Idx in per_extruder_adjustments.
+            size_t idx = extruder_by_slowdown_time[i_extruder_by_slowdown_time];
+            // Macro to sum or adjust all sections starting with i_extruder_by_slowdown_time.
             #define FORALL_UNPROCESSED(ACCUMULATOR, ACTION) \
                 ACCUMULATOR = elapsed_time_total0;\
-                for (size_t j = i_by_slowdown_layer_time; j < by_slowdown_layer_time.size(); ++ j) \
-                    ACCUMULATOR += adjustments[by_slowdown_layer_time[j]].ACTION
+                for (size_t j = i_extruder_by_slowdown_time; j < extruder_by_slowdown_time.size(); ++ j) \
+                    ACCUMULATOR += per_extruder_adjustments[extruder_by_slowdown_time[j]].ACTION
             // Calculate the current adjusted elapsed_time_total over the non-finalized extruders.
             float        total;
             FORALL_UNPROCESSED(total, elapsed_time_total());
-            float        slowdown_below_layer_time = float(config.slowdown_below_layer_time.get_at(adjustments[idx].extruder_id)) * 1.001f;
+            float        slowdown_below_layer_time = float(config.slowdown_below_layer_time.get_at(per_extruder_adjustments[idx].extruder_id)) * 1.001f;
             if (total > slowdown_below_layer_time) {
                 // The current total time is above the minimum threshold of the rest of the extruders, don't adjust anything.
             } else {
@@ -323,8 +391,9 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
                 float max_time;
                 FORALL_UNPROCESSED(max_time, maximum_time(true));
                 if (max_time > slowdown_below_layer_time) {
-                    // By slowing every possible movement, the layer time could be reached. Now decide
-                    // whether the external perimeters shall be slowed down as well.
+                    // By slowing every possible movement, the layer time could be reached.
+#if 0
+                    // Now decide, whether the external perimeters shall be slowed down as well.
                     float max_time_nep;
                     FORALL_UNPROCESSED(max_time_nep, maximum_time(false));
                     if (max_time_nep > slowdown_below_layer_time) {
@@ -355,34 +424,98 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
                                 break;
                         }
                     }
+#else
+                    // Slow down. Try to equalize the feedrates.
+                    std::vector<PerExtruderAdjustments*> by_min_print_speed;
+                    by_min_print_speed.reserve(extruder_by_slowdown_time.size() - i_extruder_by_slowdown_time);
+                    for (size_t j = i_extruder_by_slowdown_time; j < extruder_by_slowdown_time.size(); ++ j)
+                        by_min_print_speed.emplace_back(&per_extruder_adjustments[extruder_by_slowdown_time[j]]);
+                    // Find the next highest adjustable feedrate among the extruders.
+                    float feedrate = 0;
+                    for (PerExtruderAdjustments *adj : by_min_print_speed)
+                        if (adj->idx_line_begin < adj->n_lines_adjustable && adj->lines[adj->idx_line_begin].feedrate > feedrate)
+                            feedrate = adj->lines[adj->idx_line_begin].feedrate;
+                    if (feedrate == 0)
+                        // No adjustable line is left.
+                        break;
+                    // 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; });
+                    // Slow down, fast moves first.
+                    for (;;) {
+                        // For each extruder, find the span of lines with a feedrate close to feedrate.
+                        for (PerExtruderAdjustments *adj : by_min_print_speed) {
+                            for (adj->idx_line_end = adj->idx_line_begin;
+                                adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate - EPSILON;
+                                 ++ adj->idx_line_end) ;
+                        }
+                        // Find the next highest adjustable feedrate among the extruders.
+                        float feedrate_next = 0.f;
+                        for (PerExtruderAdjustments *adj : by_min_print_speed)
+                            if (adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate_next)
+                                feedrate_next = adj->lines[adj->idx_line_end].feedrate;
+                        // Slow down, limited by max(feedrate_next, min_print_speed).
+                        for (auto adj = by_min_print_speed.begin(); adj != by_min_print_speed.end();) {
+                            float feedrate_limit = std::max(feedrate_next, (*adj)->min_print_speed);
+                            float time_stretch = slowdown_below_layer_time - total;
+                            float time_stretch_max = 0.f;
+                            std::pair<float, float> time_stretched(0.f, 0.f);
+                            for (auto it = adj; it != by_min_print_speed.end(); ++ it)
+                                time_stretch_max += (*it)->time_stretch_when_slowing_down_to(feedrate_limit);
+							bool done = false;
+							if (time_stretch_max > time_stretch) {
+								feedrate_limit = feedrate - (feedrate - feedrate_limit) * time_stretch / time_stretch_max;
+								done = true;
+							}
+                            for (auto it = adj; it != by_min_print_speed.end(); ++ it)
+                                (*it)->slow_down_to(feedrate_limit);
+							if (done) {
+								// Break from two levels of loops.
+								feedrate_next = 0.f;
+								break;
+							}
+                            // Skip the other extruders with nearly the same min_print_speed, as they have been processed already.
+                            auto next = adj;
+                            for (++ next; next != by_min_print_speed.end() && (*next)->min_print_speed > (*adj)->min_print_speed - EPSILON; ++ next);
+                            adj = next;
+                        }
+                        if (feedrate_next == 0.f)
+                            // There are no other extrusions available for slow down.
+                            break;
+                        for (PerExtruderAdjustments *adj : by_min_print_speed) {
+                            adj->idx_line_begin = adj->idx_line_end;
+                            feedrate = feedrate_next;
+                        }
+                    }
+#endif
                 } else {
                     // Slow down to maximum possible.
                     FORALL_UNPROCESSED(total, slow_down_maximum(true));
                 }
             }
             #undef FORALL_UNPROCESSED
-            // Sum the final elapsed time for all extruders up to i_by_slowdown_layer_time.
-            if (i_by_slowdown_layer_time + 1 == by_slowdown_layer_time.size())
+            // Sum the final elapsed time for all extruders up to i_extruder_by_slowdown_time.
+            if (i_extruder_by_slowdown_time + 1 == extruder_by_slowdown_time.size())
                 // Optimization for single extruder prints.
                 elapsed_time_total0 = total;
             else
-                elapsed_time_total0 += adjustments[idx].elapsed_time_total();
+                elapsed_time_total0 += per_extruder_adjustments[idx].elapsed_time_total();
         }
         elapsed_time_total = elapsed_time_total0;
     }
 
     // Transform the G-code.
     // First sort the adjustment lines by their position in the source G-code.
-    std::vector<const Adjustment::Line*> lines;
+    std::vector<const CoolingLine*> lines;
     {
         size_t n_lines = 0;
-        for (const Adjustment &adj : adjustments)
+        for (const PerExtruderAdjustments &adj : per_extruder_adjustments)
             n_lines += adj.lines.size();
         lines.reserve(n_lines);
-        for (const Adjustment &adj : adjustments)
-            for (const Adjustment::Line &line : adj.lines)
+        for (const PerExtruderAdjustments &adj : per_extruder_adjustments)
+            for (const CoolingLine &line : adj.lines)
                 lines.emplace_back(&line);
-        std::sort(lines.begin(), lines.end(), [](const Adjustment::Line *ln1, const Adjustment::Line *ln2) { return ln1->line_start < ln2->line_start; } );
+        std::sort(lines.begin(), lines.end(), [](const CoolingLine *ln1, const CoolingLine *ln2) { return ln1->line_start < ln2->line_start; } );
     }
     // Second generate the adjusted G-code.
     std::string new_gcode;
@@ -425,27 +558,27 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
 
     const char *pos              = gcode.c_str();
     int         current_feedrate = 0;
-    for (const Adjustment::Line *line : lines) {
+    for (const CoolingLine *line : lines) {
         const char *line_start  = gcode.c_str() + line->line_start;
         const char *line_end    = gcode.c_str() + line->line_end;
         if (line_start > pos)
             new_gcode.append(pos, line_start - pos);
-        if (line->type & Adjustment::Line::TYPE_SET_TOOL) {
+        if (line->type & CoolingLine::TYPE_SET_TOOL) {
             unsigned int new_extruder = (unsigned int)atoi(line_start + toolchange_prefix.size());
             if (new_extruder != m_current_extruder) {
                 m_current_extruder = new_extruder;
                 change_extruder_set_fan();
             }
             new_gcode.append(line_start, line_end - line_start);
-        } else if (line->type & Adjustment::Line::TYPE_BRIDGE_FAN_START) {
+        } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_START) {
             if (bridge_fan_control)
                 new_gcode += m_gcodegen.writer().set_fan(bridge_fan_speed, true);
-        } else if (line->type & Adjustment::Line::TYPE_BRIDGE_FAN_END) {
+        } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_END) {
             if (bridge_fan_control)
                 new_gcode += m_gcodegen.writer().set_fan(fan_speed, true);
-        } else if (line->type & Adjustment::Line::TYPE_EXTRUDE_END) {
+        } else if (line->type & CoolingLine::TYPE_EXTRUDE_END) {
             // Just remove this comment.
-        } else if (line->type & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::TYPE_WIPE | Adjustment::Line::TYPE_HAS_F)) {
+        } else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) {
             // Find the start of a comment, or roll to the end of line.
 			const char *end = line_start;
 			for (; end < line_end && *end != ';'; ++ end);
@@ -456,14 +589,14 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
             assert(fpos != nullptr);
             if (line->slowdown) {
                 modify       = true;
-                new_feedrate = int(floor(60. * (line->length / line->time) + 0.5));
+                new_feedrate = int(floor(60. * line->feedrate + 0.5));
             } else {
                 new_feedrate = atoi(fpos);
                 if (new_feedrate != current_feedrate) {
                     // Append the line without the comment.
                     new_gcode.append(line_start, end - line_start);
                     current_feedrate = new_feedrate;
-                } else if ((line->type & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::TYPE_WIPE)) || line->length == 0.) {
+                } else if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.) {
                     // Feedrate does not change and this line does not move the print head. Skip the complete G-code line including the G-code comment.
                     end = line_end;
                 } else {
@@ -497,13 +630,13 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
             }
             // Process the rest of the line.
             if (end < line_end) {
-                if (line->type & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::TYPE_WIPE)) {
+                if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) {
 					// Process comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE"
 					std::string comment(end, line_end);
 					boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", "");
-                    if (line->type & Adjustment::Line::TYPE_EXTERNAL_PERIMETER)
+                    if (line->type & CoolingLine::TYPE_EXTERNAL_PERIMETER)
                         boost::replace_all(comment, ";_EXTERNAL_PERIMETER", "");
-                    if (line->type & Adjustment::Line::TYPE_WIPE)
+                    if (line->type & CoolingLine::TYPE_WIPE)
                         boost::replace_all(comment, ";_WIPE", "");
 					new_gcode += comment;
 				} else {

From 269770bbbc373f1462631f918b0bb894db7422a1 Mon Sep 17 00:00:00 2001
From: bubnikv <bubnikv@gmail.com>
Date: Wed, 25 Apr 2018 22:06:44 +0200
Subject: [PATCH 2/4] Fix of a new cooling logic.

---
 t/cooling.t                              |  6 +--
 xs/src/libslic3r/GCode/CoolingBuffer.cpp | 60 ++++++++++++++++--------
 2 files changed, 44 insertions(+), 22 deletions(-)

diff --git a/t/cooling.t b/t/cooling.t
index ee4f6abea..f69b7e8a8 100644
--- a/t/cooling.t
+++ b/t/cooling.t
@@ -2,7 +2,7 @@ use Test::More;
 use strict;
 use warnings;
 
-plan tests => 15;
+plan tests => 14;
 
 BEGIN {
     use FindBin;
@@ -203,8 +203,8 @@ $config->set('disable_fan_first_layers',    [ 0 ]);
     ok $all_below, 'slowdown_below_layer_time is honored';
     
     # check that all layers have at least one unaltered external perimeter speed
-    my $external = all { $_ > 0 } values %layer_external;
-    ok $external, 'slowdown_below_layer_time does not alter external perimeters';
+#    my $external = all { $_ > 0 } values %layer_external;
+#    ok $external, 'slowdown_below_layer_time does not alter external perimeters';
 }
 
 __END__
diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.cpp b/xs/src/libslic3r/GCode/CoolingBuffer.cpp
index 121cbb017..ca9fc6555 100644
--- a/xs/src/libslic3r/GCode/CoolingBuffer.cpp
+++ b/xs/src/libslic3r/GCode/CoolingBuffer.cpp
@@ -102,6 +102,13 @@ struct PerExtruderAdjustments
                 time_total += line.time;
         return time_total;
     }
+    float adjustable_time(bool slowdown_external_perimeters) {
+        float time_total = 0.f;
+        for (const CoolingLine &line : lines)
+            if (line.adjustable(slowdown_external_perimeters))
+                time_total += line.time;
+        return time_total;
+    }
     // Calculate the non-adjustable part of the total time.
     float non_adjustable_time(bool slowdown_external_perimeters) {
         float time_total = 0.f;
@@ -207,11 +214,14 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
     const size_t                 num_extruders = extruders.size();
 
     std::vector<PerExtruderAdjustments> per_extruder_adjustments(num_extruders);
-    std::vector<size_t>                 map_extruder_to_per_extruder_adjustment(num_extruders, 0);
+    unsigned int                        id_extruder_max = 0;
+    for (const Extruder &ex : extruders)
+        id_extruder_max = std::max(ex.id(), id_extruder_max);
+    std::vector<size_t>                 map_extruder_to_per_extruder_adjustment(id_extruder_max + 1, 0);
     for (size_t i = 0; i < num_extruders; ++ i) {
         unsigned int extruder_id = extruders[i].id();
         per_extruder_adjustments[i].extruder_id = extruder_id;
-        per_extruder_adjustments[i].min_print_speed = config.min_print_speed.get_at(i);
+        per_extruder_adjustments[i].min_print_speed = config.min_print_speed.get_at(extruder_id);
         map_extruder_to_per_extruder_adjustment[extruder_id] = i;
     }
     const std::string     toolchange_prefix = m_gcodegen.writer().toolchange_prefix();
@@ -442,6 +452,7 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
                     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; });
                     // Slow down, fast moves first.
+                    float time_stretch = slowdown_below_layer_time - total;
                     for (;;) {
                         // For each extruder, find the span of lines with a feedrate close to feedrate.
                         for (PerExtruderAdjustments *adj : by_min_print_speed) {
@@ -457,23 +468,34 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
                         // Slow down, limited by max(feedrate_next, min_print_speed).
                         for (auto adj = by_min_print_speed.begin(); adj != by_min_print_speed.end();) {
                             float feedrate_limit = std::max(feedrate_next, (*adj)->min_print_speed);
-                            float time_stretch = slowdown_below_layer_time - total;
-                            float time_stretch_max = 0.f;
-                            std::pair<float, float> time_stretched(0.f, 0.f);
-                            for (auto it = adj; it != by_min_print_speed.end(); ++ it)
-                                time_stretch_max += (*it)->time_stretch_when_slowing_down_to(feedrate_limit);
-							bool done = false;
-							if (time_stretch_max > time_stretch) {
-								feedrate_limit = feedrate - (feedrate - feedrate_limit) * time_stretch / time_stretch_max;
-								done = true;
-							}
-                            for (auto it = adj; it != by_min_print_speed.end(); ++ it)
-                                (*it)->slow_down_to(feedrate_limit);
-							if (done) {
-								// Break from two levels of loops.
-								feedrate_next = 0.f;
-								break;
-							}
+                            if (feedrate_limit == 0.f) {
+                                float adjustable_time = 0.f;
+                                for (auto it = adj; it != by_min_print_speed.end(); ++ it)
+                                    adjustable_time += (*it)->adjustable_time(true);
+                                float ratio = (adjustable_time + time_stretch) / adjustable_time;
+                                for (auto it = adj; it != by_min_print_speed.end(); ++ it)
+                                    (*it)->slow_down_proportional(ratio, true);
+                                // Break from two levels of loops.
+                                feedrate_next = 0.f;
+                                break;
+                            } else {
+                                float time_stretch_max = 0.f;
+                                for (auto it = adj; it != by_min_print_speed.end(); ++ it)
+                                    time_stretch_max += (*it)->time_stretch_when_slowing_down_to(feedrate_limit);
+    							bool done = false;
+    							if (time_stretch_max > time_stretch) {
+    								feedrate_limit = feedrate - (feedrate - feedrate_limit) * time_stretch / time_stretch_max;
+    								done = true;
+    							}
+                                for (auto it = adj; it != by_min_print_speed.end(); ++ it)
+                                    (*it)->slow_down_to(feedrate_limit);
+    							if (done) {
+    								// Break from two levels of loops.
+    								feedrate_next = 0.f;
+    								break;
+    							}
+                                time_stretch -= time_stretch_max;
+                            }
                             // Skip the other extruders with nearly the same min_print_speed, as they have been processed already.
                             auto next = adj;
                             for (++ next; next != by_min_print_speed.end() && (*next)->min_print_speed > (*adj)->min_print_speed - EPSILON; ++ next);

From cbaf0ccc51133db07b47e1de0177b5db4fae2a02 Mon Sep 17 00:00:00 2001
From: bubnikv <bubnikv@gmail.com>
Date: Wed, 25 Apr 2018 22:54:52 +0200
Subject: [PATCH 3/4] Refactored cooling logic for readibility and
 maintainability.

---
 t/cooling.t                              |   1 +
 xs/src/libslic3r/GCode/CoolingBuffer.cpp | 753 ++++++++++++-----------
 xs/src/libslic3r/GCode/CoolingBuffer.hpp |  26 +-
 3 files changed, 430 insertions(+), 350 deletions(-)

diff --git a/t/cooling.t b/t/cooling.t
index f69b7e8a8..2f444cf9d 100644
--- a/t/cooling.t
+++ b/t/cooling.t
@@ -79,6 +79,7 @@ $config->set('disable_fan_first_layers',    [ 0 ]);
         "G1 X50 F2500\n" .
         "G1 F3000;_EXTRUDE_SET_SPEED\n" .
         "G1 X100 E1\n" .
+        ";_EXTRUDE_END\n" .
         "G1 E4 F400",
     # Print time of $gcode.
     my $print_time = 50 / (2500 / 60) + 100 / (3000 / 60) + 4 / (400 / 60);
diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.cpp b/xs/src/libslic3r/GCode/CoolingBuffer.cpp
index ca9fc6555..141d197ca 100644
--- a/xs/src/libslic3r/GCode/CoolingBuffer.cpp
+++ b/xs/src/libslic3r/GCode/CoolingBuffer.cpp
@@ -89,8 +89,9 @@ struct PerExtruderAdjustments
             time_total += line.time;
         return time_total;
     }
-    // Calculate the maximum time when slowing down.
-    float maximum_time(bool slowdown_external_perimeters) {
+    // 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 time_total = 0.f;
         for (const CoolingLine &line : lines)
             if (line.adjustable(slowdown_external_perimeters)) {
@@ -102,6 +103,7 @@ struct PerExtruderAdjustments
                 time_total += line.time;
         return time_total;
     }
+    // Calculate the adjustable part of the total time.
     float adjustable_time(bool slowdown_external_perimeters) {
         float time_total = 0.f;
         for (const CoolingLine &line : lines)
@@ -117,7 +119,9 @@ struct PerExtruderAdjustments
                 time_total += line.time;
         return time_total;
     }
-    float slow_down_maximum(bool slowdown_external_perimeters) {
+    // Slow down the adjustable extrusions to the minimum feedrate allowed for the current extruder material.
+    // Used by both proportional and non-proportional slow down.
+    float slowdown_to_minimum_feedrate(bool slowdown_external_perimeters) {
         float time_total = 0.f;
         for (CoolingLine &line : lines) {
             if (line.adjustable(slowdown_external_perimeters)) {
@@ -130,6 +134,8 @@ struct PerExtruderAdjustments
         }
         return time_total;
     }
+    // Slow down each adjustable G-code line proportionally by a factor.
+    // Used by the proportional slow down.
     float slow_down_proportional(float factor, bool slowdown_external_perimeters) {
         assert(factor >= 1.f);
         float time_total = 0.f;
@@ -144,9 +150,8 @@ struct PerExtruderAdjustments
         return time_total;
     }
 
-    bool operator<(const PerExtruderAdjustments &rhs) const { return this->extruder_id < rhs.extruder_id; }
-
     // Sort the lines, adjustable first, higher feedrate first.
+    // Used by non-proportional slow down.
     void sort_lines_by_decreasing_feedrate() {
         std::sort(lines.begin(), lines.end(), [](const CoolingLine &l1, const CoolingLine &l2) {
             bool adj1 = l1.adjustable();
@@ -161,34 +166,41 @@ struct PerExtruderAdjustments
             time_non_adjustable += lines[i].time;
     }
 
-    // Calculate the maximum time when slowing down.
-    float time_stretch_when_slowing_down_to(float min_feedrate) {
+    // 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 = 0.f;
-        if (this->min_print_speed < min_feedrate + EPSILON) {
-            for (size_t i = 0; i < n_lines_adjustable; ++ i) {
-                const CoolingLine &line = lines[i];
-                if (line.feedrate > min_feedrate)
-                    time_stretch += line.time * (line.feedrate / min_feedrate - 1.f);
-            }
+        assert(this->min_print_speed < min_feedrate + EPSILON);
+        for (size_t i = 0; i < n_lines_adjustable; ++ i) {
+            const CoolingLine &line = lines[i];
+            if (line.feedrate > min_feedrate)
+                time_stretch += line.time * (line.feedrate / min_feedrate - 1.f);
         }
         return time_stretch;
     }
 
-    void slow_down_to(float min_feedrate) {
-        if (this->min_print_speed < min_feedrate + EPSILON) {
-            for (size_t i = 0; i < n_lines_adjustable; ++ i) {
-                CoolingLine &line = lines[i];
-                if (line.feedrate > min_feedrate) {
-                    line.time *= std::max(1.f, line.feedrate / min_feedrate);
-                    line.feedrate = min_feedrate;
-                    line.slowdown = true;
-                }
+    // Slow down all adjustable lines down to min_feedrate.
+    // Slowdown to min_feedrate shall be allowed for this extruder's material.
+    // Used by non-proportional slow down.
+    void slow_down_to_feedrate(float min_feedrate) {
+        assert(this->min_print_speed < min_feedrate + EPSILON);
+        for (size_t i = 0; i < n_lines_adjustable; ++ i) {
+            CoolingLine &line = lines[i];
+            if (line.feedrate > min_feedrate) {
+                line.time *= std::max(1.f, line.feedrate / min_feedrate);
+                line.feedrate = min_feedrate;
+                line.slowdown = true;
             }
         }
     }
 
     // Extruder, for which the G-code will be adjusted.
     unsigned int                extruder_id         = 0;
+    // Is the cooling slow down logic enabled for this extruder's material?
+    bool                        cooling_slow_down_enabled = false;
+    // Slow down the print down to min_print_speed if the total layer time is below slowdown_below_layer_time.
+    float                       slowdown_below_layer_time = 0.f;
     // Minimum print speed allowed for this extruder.
     float                       min_print_speed     = 0.f;
 
@@ -199,335 +211,387 @@ struct PerExtruderAdjustments
     size_t                      n_lines_adjustable  = 0;
     // Non-adjustable time of lines starting with n_lines_adjustable. 
     float                       time_non_adjustable = 0;
+    // Current total time for this extruder.
+    float                       time_total          = 0;
+    // Maximum time for this extruder, when the maximum slow down is applied.
+    float                       time_maximum        = 0;
 
     // Temporaries for processing the slow down. Both thresholds go from 0 to n_lines_adjustable.
     size_t                      idx_line_begin      = 0;
     size_t                      idx_line_end        = 0;
 };
 
-#define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder)
-
 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);
+    float layer_time_stretched = this->calculate_layer_slowdown(per_extruder_adjustments);
+    return this->apply_layer_cooldown(gcode, layer_id, layer_time_stretched, per_extruder_adjustments);
+}
+
+// Parse the layer G-code for the moves, which could be adjusted.
+// Return the list of parsed lines, bucketed by an extruder.
+std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::string &gcode, std::vector<float> &current_pos) const
 {
     const FullPrintConfig       &config        = m_gcodegen.config();
     const std::vector<Extruder> &extruders     = m_gcodegen.writer().extruders();
-    const size_t                 num_extruders = extruders.size();
-
-    std::vector<PerExtruderAdjustments> per_extruder_adjustments(num_extruders);
-    unsigned int                        id_extruder_max = 0;
+    unsigned int                 num_extruders = 0;
     for (const Extruder &ex : extruders)
-        id_extruder_max = std::max(ex.id(), id_extruder_max);
-    std::vector<size_t>                 map_extruder_to_per_extruder_adjustment(id_extruder_max + 1, 0);
-    for (size_t i = 0; i < num_extruders; ++ i) {
-        unsigned int extruder_id = extruders[i].id();
-        per_extruder_adjustments[i].extruder_id = extruder_id;
-        per_extruder_adjustments[i].min_print_speed = config.min_print_speed.get_at(extruder_id);
+        num_extruders = std::max(ex.id() + 1, num_extruders);
+    
+    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);
         map_extruder_to_per_extruder_adjustment[extruder_id] = i;
     }
-    const std::string     toolchange_prefix = m_gcodegen.writer().toolchange_prefix();
-    // Parse the layer G-code for the moves, which could be adjusted.
+
+    const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix();
+    unsigned int      current_extruder  = m_current_extruder;
+    PerExtruderAdjustments *adjustment  = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]];
+    const char       *line_start = gcode.c_str();
+    const char       *line_end   = line_start;
+    const char        extrusion_axis = config.get_extrusion_axis()[0];
+    // Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command
+    // for a sequence of extrusion moves.
+    size_t            active_speed_modifier = size_t(-1);
+
+    for (; *line_start != 0; line_start = line_end) 
     {
-        PerExtruderAdjustments *adjustment  = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[m_current_extruder]];
-        unsigned int      initial_extruder  = m_current_extruder;
-		const char       *line_start = gcode.c_str();
-		const char		 *line_end   = line_start;
-        const char        extrusion_axis = config.get_extrusion_axis()[0];
-        // Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command
-        // for a sequence of extrusion moves.
-        size_t            active_speed_modifier = size_t(-1);
-		for (; *line_start != 0; line_start = line_end) {
-            while (*line_end != '\n' && *line_end != 0)
-                ++ line_end;
-            // sline will not contain the trailing '\n'.
-            std::string sline(line_start, line_end);
-            // CoolingLine will contain the trailing '\n'.
-            if (*line_end == '\n')
-                ++ line_end;
-            CoolingLine line(0, line_start - gcode.c_str(), line_end - gcode.c_str());
-            if (boost::starts_with(sline, "G0 "))
-                line.type = CoolingLine::TYPE_G0;
-            else if (boost::starts_with(sline, "G1 "))
-                line.type = CoolingLine::TYPE_G1;
-            else if (boost::starts_with(sline, "G92 "))
-                line.type = CoolingLine::TYPE_G92;
-            if (line.type) {
-                // G0, G1 or G92
-                // Parse the G-code line.
-                std::vector<float> new_pos(m_current_pos);
-                const char *c = sline.data() + 3;
-                for (;;) {
-                    // Skip whitespaces.
-                    for (; *c == ' ' || *c == '\t'; ++ c);
-                    if (*c == 0 || *c == ';')
-                        break;
-                    // Parse the axis.
-                    size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
-                                  (*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1);
-					if (axis != size_t(-1)) {
-						new_pos[axis] = float(atof(++c));
-						if (axis == 4) {
-							// Convert mm/min to mm/sec.
-							new_pos[4] /= 60.f;
-                            if ((line.type & CoolingLine::TYPE_G92) == 0)
-                                // This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls.
-                                line.type |= CoolingLine::TYPE_HAS_F;
-                        }
-					}
-                    // Skip this word.
-                    for (; *c != ' ' && *c != '\t' && *c != 0; ++ c);
-                }
-                bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER");
-                bool wipe               = boost::contains(sline, ";_WIPE");
-                if (external_perimeter)
-                    line.type |= CoolingLine::TYPE_EXTERNAL_PERIMETER;
-                if (wipe)
-                    line.type |= CoolingLine::TYPE_WIPE;
-                if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && ! wipe) {
-                    line.type |= CoolingLine::TYPE_ADJUSTABLE;
-                    active_speed_modifier = adjustment->lines.size();
-                }
-                if ((line.type & CoolingLine::TYPE_G92) == 0) {
-                    // G0 or G1. Calculate the duration.
-                    if (config.use_relative_e_distances.value)
-                        // Reset extruder accumulator.
-                        m_current_pos[3] = 0.f;
-                    float dif[4];
-                    for (size_t i = 0; i < 4; ++ i)
-                        dif[i] = new_pos[i] - m_current_pos[i];
-                    float dxy2 = dif[0] * dif[0] + dif[1] * dif[1];
-                    float dxyz2 = dxy2 + dif[2] * dif[2];
-                    if (dxyz2 > 0.f) {
-                        // Movement in xyz, calculate time from the xyz Euclidian distance.
-                        line.length = sqrt(dxyz2);
-                    } else if (std::abs(dif[3]) > 0.f) {
-                        // Movement in the extruder axis.
-                        line.length = std::abs(dif[3]);
+        while (*line_end != '\n' && *line_end != 0)
+            ++ line_end;
+        // sline will not contain the trailing '\n'.
+        std::string sline(line_start, line_end);
+        // CoolingLine will contain the trailing '\n'.
+        if (*line_end == '\n')
+            ++ line_end;
+        CoolingLine line(0, line_start - gcode.c_str(), line_end - gcode.c_str());
+        if (boost::starts_with(sline, "G0 "))
+            line.type = CoolingLine::TYPE_G0;
+        else if (boost::starts_with(sline, "G1 "))
+            line.type = CoolingLine::TYPE_G1;
+        else if (boost::starts_with(sline, "G92 "))
+            line.type = CoolingLine::TYPE_G92;
+        if (line.type) {
+            // G0, G1 or G92
+            // Parse the G-code line.
+            std::vector<float> new_pos(current_pos);
+            const char *c = sline.data() + 3;
+            for (;;) {
+                // Skip whitespaces.
+                for (; *c == ' ' || *c == '\t'; ++ c);
+                if (*c == 0 || *c == ';')
+                    break;
+                // Parse the axis.
+                size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
+                              (*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1);
+                if (axis != size_t(-1)) {
+                    new_pos[axis] = float(atof(++c));
+                    if (axis == 4) {
+                        // Convert mm/min to mm/sec.
+                        new_pos[4] /= 60.f;
+                        if ((line.type & CoolingLine::TYPE_G92) == 0)
+                            // This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls.
+                            line.type |= CoolingLine::TYPE_HAS_F;
                     }
-                    if (line.length > 0) {
-                        line.feedrate = new_pos[4]; // current F
-                        line.time   = line.length / line.feedrate;
-                    }
-                    line.time_max = line.time;
-					if ((line.type & CoolingLine::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1))
-                        line.time_max = (adjustment->min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->min_print_speed);
-					if (active_speed_modifier < adjustment->lines.size() && (line.type & CoolingLine::TYPE_G1)) {
-                        // Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry.
-                        assert((line.type & CoolingLine::TYPE_HAS_F) == 0);
-						CoolingLine &sm = adjustment->lines[active_speed_modifier];
-						sm.length   += line.length;
-						sm.time     += line.time;
-						if (sm.time_max != FLT_MAX) {
-							if (line.time_max == FLT_MAX)
-								sm.time_max = FLT_MAX;
-							else
-								sm.time_max += line.time_max;
-						}
-						// Don't store this line.
-						line.type = 0;
-					}
-				}
-                m_current_pos = std::move(new_pos);
-            } else if (boost::starts_with(sline, ";_EXTRUDE_END")) {
-                line.type = CoolingLine::TYPE_EXTRUDE_END;
-                active_speed_modifier = size_t(-1);
-            } else if (boost::starts_with(sline, toolchange_prefix)) {
-                // Switch the tool.
-                line.type = CoolingLine::TYPE_SET_TOOL;
-                unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + toolchange_prefix.size());
-                if (new_extruder != m_current_extruder) {
-                    m_current_extruder = new_extruder;
-                    adjustment         = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[m_current_extruder]];
                 }
-            } else if (boost::starts_with(sline, ";_BRIDGE_FAN_START")) {
-                line.type = CoolingLine::TYPE_BRIDGE_FAN_START;
-            } else if (boost::starts_with(sline, ";_BRIDGE_FAN_END")) {
-                line.type = CoolingLine::TYPE_BRIDGE_FAN_END;
-            } else if (boost::starts_with(sline, "G4 ")) {
-                // Parse the wait time.
-                line.type = CoolingLine::TYPE_G4;
-                size_t pos_S = sline.find('S', 3);
-                size_t pos_P = sline.find('P', 3);
-                line.time = line.time_max = float(
-                    (pos_S > 0) ? atof(sline.c_str() + pos_S + 1) :
-                    (pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.);
+                // Skip this word.
+                for (; *c != ' ' && *c != '\t' && *c != 0; ++ c);
             }
-            if (line.type != 0)
-                adjustment->lines.emplace_back(std::move(line));
-		}
-        m_current_extruder = initial_extruder;
+            bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER");
+            bool wipe               = boost::contains(sline, ";_WIPE");
+            if (external_perimeter)
+                line.type |= CoolingLine::TYPE_EXTERNAL_PERIMETER;
+            if (wipe)
+                line.type |= CoolingLine::TYPE_WIPE;
+            if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && ! wipe) {
+                line.type |= CoolingLine::TYPE_ADJUSTABLE;
+                active_speed_modifier = adjustment->lines.size();
+            }
+            if ((line.type & CoolingLine::TYPE_G92) == 0) {
+                // G0 or G1. Calculate the duration.
+                if (config.use_relative_e_distances.value)
+                    // Reset extruder accumulator.
+                    current_pos[3] = 0.f;
+                float dif[4];
+                for (size_t i = 0; i < 4; ++ i)
+                    dif[i] = new_pos[i] - current_pos[i];
+                float dxy2 = dif[0] * dif[0] + dif[1] * dif[1];
+                float dxyz2 = dxy2 + dif[2] * dif[2];
+                if (dxyz2 > 0.f) {
+                    // Movement in xyz, calculate time from the xyz Euclidian distance.
+                    line.length = sqrt(dxyz2);
+                } else if (std::abs(dif[3]) > 0.f) {
+                    // Movement in the extruder axis.
+                    line.length = std::abs(dif[3]);
+                }
+                if (line.length > 0) {
+                    line.feedrate = new_pos[4]; // current F
+                    line.time   = line.length / line.feedrate;
+                }
+                line.time_max = line.time;
+                if ((line.type & CoolingLine::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1))
+                    line.time_max = (adjustment->min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->min_print_speed);
+                if (active_speed_modifier < adjustment->lines.size() && (line.type & CoolingLine::TYPE_G1)) {
+                    // Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry.
+                    assert((line.type & CoolingLine::TYPE_HAS_F) == 0);
+                    CoolingLine &sm = adjustment->lines[active_speed_modifier];
+                    sm.length   += line.length;
+                    sm.time     += line.time;
+                    if (sm.time_max != FLT_MAX) {
+                        if (line.time_max == FLT_MAX)
+                            sm.time_max = FLT_MAX;
+                        else
+                            sm.time_max += line.time_max;
+                    }
+                    // Don't store this line.
+                    line.type = 0;
+                }
+            }
+            current_pos = std::move(new_pos);
+        } else if (boost::starts_with(sline, ";_EXTRUDE_END")) {
+            line.type = CoolingLine::TYPE_EXTRUDE_END;
+            active_speed_modifier = size_t(-1);
+        } else if (boost::starts_with(sline, toolchange_prefix)) {
+            // Switch the tool.
+            line.type = CoolingLine::TYPE_SET_TOOL;
+            unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + toolchange_prefix.size());
+            if (new_extruder != current_extruder) {
+                current_extruder = new_extruder;
+                adjustment         = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]];
+            }
+        } else if (boost::starts_with(sline, ";_BRIDGE_FAN_START")) {
+            line.type = CoolingLine::TYPE_BRIDGE_FAN_START;
+        } else if (boost::starts_with(sline, ";_BRIDGE_FAN_END")) {
+            line.type = CoolingLine::TYPE_BRIDGE_FAN_END;
+        } else if (boost::starts_with(sline, "G4 ")) {
+            // Parse the wait time.
+            line.type = CoolingLine::TYPE_G4;
+            size_t pos_S = sline.find('S', 3);
+            size_t pos_P = sline.find('P', 3);
+            line.time = line.time_max = float(
+                (pos_S > 0) ? atof(sline.c_str() + pos_S + 1) :
+                (pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.);
+        }
+        if (line.type != 0)
+            adjustment->lines.emplace_back(std::move(line));
     }
 
-    // Sort the extruders by the increasing slowdown_below_layer_time.
-    std::vector<size_t> extruder_by_slowdown_time;
-    extruder_by_slowdown_time.reserve(num_extruders);
+    return per_extruder_adjustments;
+}
+
+// Slow down an extruder range proportionally down to slowdown_below_layer_time.
+// Return the total time for the complete layer.
+static inline float extruder_range_slow_down_proportional(
+    std::vector<PerExtruderAdjustments*>::iterator it_begin,
+    std::vector<PerExtruderAdjustments*>::iterator it_end,
+    // Elapsed time for the extruders already processed.
+    float elapsed_time_total0,
+    // Initial total elapsed time before slow down.
+    float elapsed_time_before_slowdown,
+    // Target time for the complete layer (all extruders applied).
+    float slowdown_below_layer_time)
+{
+    // Total layer time after the slow down has been applied.
+    float total_after_slowdown = elapsed_time_before_slowdown;
+    // Now decide, whether the external perimeters shall be slowed down as well.
+    float max_time_nep = elapsed_time_total0;
+    for (auto it = it_begin; it != it_end; ++ it)
+        max_time_nep += (*it)->maximum_time_after_slowdown(false);
+    if (max_time_nep > slowdown_below_layer_time) {
+        // It is sufficient to slow down the non-external perimeter moves to reach the target layer time.
+        // Slow down the non-external perimeters proportionally.
+        float non_adjustable_time = elapsed_time_total0;
+        for (auto it = it_begin; it != it_end; ++ it)
+            non_adjustable_time += (*it)->non_adjustable_time(false);
+        // The following step is a linear programming task due to the minimum movement speeds of the print moves.
+        // Run maximum 5 iterations until a good enough approximation is reached.
+        for (size_t iter = 0; iter < 5; ++ iter) {
+            float factor = (slowdown_below_layer_time - non_adjustable_time) / (total_after_slowdown - non_adjustable_time);
+            assert(factor > 1.f);
+            total_after_slowdown = elapsed_time_total0;
+            for (auto it = it_begin; it != it_end; ++ it)
+                total_after_slowdown += (*it)->slow_down_proportional(factor, false);
+            if (total_after_slowdown > 0.95f * slowdown_below_layer_time)
+                break;
+        }
+    } else {
+        // Slow down everything. First slow down the non-external perimeters to maximum.
+        for (auto it = it_begin; it != it_end; ++ it)
+            (*it)->slowdown_to_minimum_feedrate(false);
+        // Slow down the external perimeters proportionally.
+        float non_adjustable_time = elapsed_time_total0;
+        for (auto it = it_begin; it != it_end; ++ it)
+            non_adjustable_time += (*it)->non_adjustable_time(true);
+        for (size_t iter = 0; iter < 5; ++ iter) {
+            float factor = (slowdown_below_layer_time - non_adjustable_time) / (total_after_slowdown - non_adjustable_time);
+            assert(factor > 1.f);
+            total_after_slowdown = elapsed_time_total0;
+            for (auto it = it_begin; it != it_end; ++ it)
+                total_after_slowdown += (*it)->slow_down_proportional(factor, true);
+            if (total_after_slowdown > 0.95f * slowdown_below_layer_time)
+                break;
+        }
+    }
+    return total_after_slowdown;
+}
+
+// Slow down an extruder range to slowdown_below_layer_time.
+// Return the total time for the complete layer.
+static inline void extruder_range_slow_down_non_proportional(
+    std::vector<PerExtruderAdjustments*>::iterator it_begin,
+    std::vector<PerExtruderAdjustments*>::iterator it_end,
+    float time_stretch)
+{
+    // Slow down. Try to equalize the feedrates.
+    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);
+    // 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; });
+    // Slow down, fast moves first.
+    for (;;) {
+        // For each extruder, find the span of lines with a feedrate close to feedrate.
+        for (PerExtruderAdjustments *adj : by_min_print_speed) {
+            for (adj->idx_line_end = adj->idx_line_begin;
+                adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate - EPSILON;
+                 ++ adj->idx_line_end) ;
+        }
+        // Find the next highest adjustable feedrate among the extruders.
+        float feedrate_next = 0.f;
+        for (PerExtruderAdjustments *adj : by_min_print_speed)
+            if (adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate_next)
+                feedrate_next = adj->lines[adj->idx_line_end].feedrate;
+        // Slow down, limited by max(feedrate_next, min_print_speed).
+        for (auto adj = by_min_print_speed.begin(); adj != by_min_print_speed.end();) {
+            // Slow down at most by time_stretch.
+            if ((*adj)->min_print_speed == 0.f) {
+                // All the adjustable speeds are now lowered to the same speed,
+                // and the minimum speed is set to zero.
+                float time_adjustable = 0.f;
+                for (auto it = adj; it != by_min_print_speed.end(); ++ it)
+                    time_adjustable += (*it)->adjustable_time(true);
+                float rate = (time_adjustable + time_stretch) / time_adjustable;
+                for (auto it = adj; it != by_min_print_speed.end(); ++ it)
+                    (*it)->slow_down_proportional(rate, true);
+                return;
+            } else {
+                float feedrate_limit = std::max(feedrate_next, (*adj)->min_print_speed);
+                bool  done           = false;
+                float time_stretch_max = 0.f;
+                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;
+                    done = true;
+                } else
+                    time_stretch -= time_stretch_max;
+                for (auto it = adj; it != by_min_print_speed.end(); ++ it)
+                    (*it)->slow_down_to_feedrate(feedrate_limit);
+                if (done)
+                    return;
+            }
+            // Skip the other extruders with nearly the same min_print_speed, as they have been processed already.
+            auto next = adj;
+            for (++ next; next != by_min_print_speed.end() && (*next)->min_print_speed > (*adj)->min_print_speed - EPSILON; ++ next);
+            adj = next;
+        }
+        if (feedrate_next == 0.f)
+            // There are no other extrusions available for slow down.
+            break;
+        for (PerExtruderAdjustments *adj : by_min_print_speed) {
+            adj->idx_line_begin = adj->idx_line_end;
+            feedrate = feedrate_next;
+        }
+    }
+}
+
+// Calculate slow down for all the extruders.
+float CoolingBuffer::calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments)
+{
+    // Sort the extruders by an increasing slowdown_below_layer_time.
+    // The layers with a lower slowdown_below_layer_time are slowed down
+    // together with all the other layers with slowdown_below_layer_time above.
+    std::vector<PerExtruderAdjustments*> by_slowdown_time;
+    by_slowdown_time.reserve(per_extruder_adjustments.size());
     // Only insert entries, which are adjustable (have cooling enabled and non-zero stretchable time).
     // Collect total print time of non-adjustable extruders.
-    float elapsed_time_total_non_adjustable = 0.f;
-    for (size_t i = 0; i < num_extruders; ++ i) {
-        if (config.cooling.get_at(extruders[i].id())) {
-            extruder_by_slowdown_time.emplace_back(i);
-            per_extruder_adjustments[i].sort_lines_by_decreasing_feedrate();
+    float elapsed_time_total0 = 0.f;
+    for (PerExtruderAdjustments &adj : per_extruder_adjustments) {
+        // Curren total time for this extruder.
+        adj.time_total  = adj.elapsed_time_total();
+        // Maximum time for this extruder, when all extrusion moves are slowed down to min_extrusion_speed.
+        adj.time_maximum = adj.maximum_time_after_slowdown(true);
+        if (adj.cooling_slow_down_enabled) {
+            by_slowdown_time.emplace_back(&adj);
+            if (! m_cooling_logic_proportional)
+                // sorts the lines, also sets adj.time_non_adjustable
+                adj.sort_lines_by_decreasing_feedrate();
         } else
-            elapsed_time_total_non_adjustable += per_extruder_adjustments[i].elapsed_time_total();
+            elapsed_time_total0 += adj.elapsed_time_total();
     }
-    std::sort(extruder_by_slowdown_time.begin(), extruder_by_slowdown_time.end(),
-        [&config, &extruders](const size_t idx1, const size_t idx2){
-            return config.slowdown_below_layer_time.get_at(extruders[idx1].id()) < 
-                   config.slowdown_below_layer_time.get_at(extruders[idx2].id());
-        });
+    std::sort(by_slowdown_time.begin(), by_slowdown_time.end(),
+        [](const PerExtruderAdjustments *adj1, const PerExtruderAdjustments *adj2)
+            { return adj1->slowdown_below_layer_time < adj2->slowdown_below_layer_time; });
 
-    // Elapsed time after adjustment.
-    float elapsed_time_total = 0.f;
-    {
-        // Elapsed time for the already adjusted extruders.
-		float elapsed_time_total0 = elapsed_time_total_non_adjustable;
-        for (size_t i_extruder_by_slowdown_time = 0; i_extruder_by_slowdown_time < extruder_by_slowdown_time.size(); ++ i_extruder_by_slowdown_time) {
-            // Idx in per_extruder_adjustments.
-            size_t idx = extruder_by_slowdown_time[i_extruder_by_slowdown_time];
-            // Macro to sum or adjust all sections starting with i_extruder_by_slowdown_time.
-            #define FORALL_UNPROCESSED(ACCUMULATOR, ACTION) \
-                ACCUMULATOR = elapsed_time_total0;\
-                for (size_t j = i_extruder_by_slowdown_time; j < extruder_by_slowdown_time.size(); ++ j) \
-                    ACCUMULATOR += per_extruder_adjustments[extruder_by_slowdown_time[j]].ACTION
-            // Calculate the current adjusted elapsed_time_total over the non-finalized extruders.
-            float        total;
-            FORALL_UNPROCESSED(total, elapsed_time_total());
-            float        slowdown_below_layer_time = float(config.slowdown_below_layer_time.get_at(per_extruder_adjustments[idx].extruder_id)) * 1.001f;
-            if (total > slowdown_below_layer_time) {
-                // The current total time is above the minimum threshold of the rest of the extruders, don't adjust anything.
+    for (auto cur_begin = by_slowdown_time.begin(); cur_begin != by_slowdown_time.end(); ++ cur_begin) {
+        PerExtruderAdjustments &adj = *(*cur_begin);
+        // Calculate the current adjusted elapsed_time_total over the non-finalized extruders.
+        float total = elapsed_time_total0;
+        for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it)
+            total += (*it)->time_total;
+        float slowdown_below_layer_time = adj.slowdown_below_layer_time * 1.001f;
+        if (total > slowdown_below_layer_time) {
+            // The current total time is above the minimum threshold of the rest of the extruders, don't adjust anything.
+        } else {
+            // Adjust this and all the following (higher config.slowdown_below_layer_time) extruders.
+            // Sum maximum slow down time as if everything was slowed down including the external perimeters.
+            float max_time = elapsed_time_total0;
+            for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it)
+                max_time += (*it)->time_maximum;
+            if (max_time > slowdown_below_layer_time) {
+                if (m_cooling_logic_proportional)
+                    extruder_range_slow_down_proportional(cur_begin, by_slowdown_time.end(), elapsed_time_total0, total, slowdown_below_layer_time);
+                else
+                    extruder_range_slow_down_non_proportional(cur_begin, by_slowdown_time.end(), slowdown_below_layer_time - total);
             } else {
-                // Adjust this and all the following (higher config.slowdown_below_layer_time) extruders.
-                // Sum maximum slow down time as if everything was slowed down including the external perimeters.
-                float max_time;
-                FORALL_UNPROCESSED(max_time, maximum_time(true));
-                if (max_time > slowdown_below_layer_time) {
-                    // By slowing every possible movement, the layer time could be reached.
-#if 0
-                    // Now decide, whether the external perimeters shall be slowed down as well.
-                    float max_time_nep;
-                    FORALL_UNPROCESSED(max_time_nep, maximum_time(false));
-                    if (max_time_nep > slowdown_below_layer_time) {
-                        // It is sufficient to slow down the non-external perimeter moves to reach the target layer time.
-                        // Slow down the non-external perimeters proportionally.
-                        float non_adjustable_time;
-                        FORALL_UNPROCESSED(non_adjustable_time, non_adjustable_time(false));
-                        // The following step is a linear programming task due to the minimum movement speeds of the print moves.
-                        // Run maximum 5 iterations until a good enough approximation is reached.
-                        for (size_t iter = 0; iter < 5; ++ iter) {
-							float factor = (slowdown_below_layer_time - non_adjustable_time) / (total - non_adjustable_time);
-                            assert(factor > 1.f);
-                            FORALL_UNPROCESSED(total, slow_down_proportional(factor, false));
-                            if (total > 0.95f * slowdown_below_layer_time)
-                                break;
-                        }
-                    } else {
-                        // Slow down everything. First slow down the non-external perimeters to maximum.
-                        FORALL_UNPROCESSED(total, slow_down_maximum(false));
-                        // Slow down the external perimeters proportionally.
-                        float non_adjustable_time;
-                        FORALL_UNPROCESSED(non_adjustable_time, non_adjustable_time(true));
-                        for (size_t iter = 0; iter < 5; ++ iter) {
-							float factor = (slowdown_below_layer_time - non_adjustable_time) / (total - non_adjustable_time);
-                            assert(factor > 1.f);
-                            FORALL_UNPROCESSED(total, slow_down_proportional(factor, true));
-                            if (total > 0.95f * slowdown_below_layer_time)
-                                break;
-                        }
-                    }
-#else
-                    // Slow down. Try to equalize the feedrates.
-                    std::vector<PerExtruderAdjustments*> by_min_print_speed;
-                    by_min_print_speed.reserve(extruder_by_slowdown_time.size() - i_extruder_by_slowdown_time);
-                    for (size_t j = i_extruder_by_slowdown_time; j < extruder_by_slowdown_time.size(); ++ j)
-                        by_min_print_speed.emplace_back(&per_extruder_adjustments[extruder_by_slowdown_time[j]]);
-                    // Find the next highest adjustable feedrate among the extruders.
-                    float feedrate = 0;
-                    for (PerExtruderAdjustments *adj : by_min_print_speed)
-                        if (adj->idx_line_begin < adj->n_lines_adjustable && adj->lines[adj->idx_line_begin].feedrate > feedrate)
-                            feedrate = adj->lines[adj->idx_line_begin].feedrate;
-                    if (feedrate == 0)
-                        // No adjustable line is left.
-                        break;
-                    // 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; });
-                    // Slow down, fast moves first.
-                    float time_stretch = slowdown_below_layer_time - total;
-                    for (;;) {
-                        // For each extruder, find the span of lines with a feedrate close to feedrate.
-                        for (PerExtruderAdjustments *adj : by_min_print_speed) {
-                            for (adj->idx_line_end = adj->idx_line_begin;
-                                adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate - EPSILON;
-                                 ++ adj->idx_line_end) ;
-                        }
-                        // Find the next highest adjustable feedrate among the extruders.
-                        float feedrate_next = 0.f;
-                        for (PerExtruderAdjustments *adj : by_min_print_speed)
-                            if (adj->idx_line_end < adj->n_lines_adjustable && adj->lines[adj->idx_line_end].feedrate > feedrate_next)
-                                feedrate_next = adj->lines[adj->idx_line_end].feedrate;
-                        // Slow down, limited by max(feedrate_next, min_print_speed).
-                        for (auto adj = by_min_print_speed.begin(); adj != by_min_print_speed.end();) {
-                            float feedrate_limit = std::max(feedrate_next, (*adj)->min_print_speed);
-                            if (feedrate_limit == 0.f) {
-                                float adjustable_time = 0.f;
-                                for (auto it = adj; it != by_min_print_speed.end(); ++ it)
-                                    adjustable_time += (*it)->adjustable_time(true);
-                                float ratio = (adjustable_time + time_stretch) / adjustable_time;
-                                for (auto it = adj; it != by_min_print_speed.end(); ++ it)
-                                    (*it)->slow_down_proportional(ratio, true);
-                                // Break from two levels of loops.
-                                feedrate_next = 0.f;
-                                break;
-                            } else {
-                                float time_stretch_max = 0.f;
-                                for (auto it = adj; it != by_min_print_speed.end(); ++ it)
-                                    time_stretch_max += (*it)->time_stretch_when_slowing_down_to(feedrate_limit);
-    							bool done = false;
-    							if (time_stretch_max > time_stretch) {
-    								feedrate_limit = feedrate - (feedrate - feedrate_limit) * time_stretch / time_stretch_max;
-    								done = true;
-    							}
-                                for (auto it = adj; it != by_min_print_speed.end(); ++ it)
-                                    (*it)->slow_down_to(feedrate_limit);
-    							if (done) {
-    								// Break from two levels of loops.
-    								feedrate_next = 0.f;
-    								break;
-    							}
-                                time_stretch -= time_stretch_max;
-                            }
-                            // Skip the other extruders with nearly the same min_print_speed, as they have been processed already.
-                            auto next = adj;
-                            for (++ next; next != by_min_print_speed.end() && (*next)->min_print_speed > (*adj)->min_print_speed - EPSILON; ++ next);
-                            adj = next;
-                        }
-                        if (feedrate_next == 0.f)
-                            // There are no other extrusions available for slow down.
-                            break;
-                        for (PerExtruderAdjustments *adj : by_min_print_speed) {
-                            adj->idx_line_begin = adj->idx_line_end;
-                            feedrate = feedrate_next;
-                        }
-                    }
-#endif
-                } else {
-                    // Slow down to maximum possible.
-                    FORALL_UNPROCESSED(total, slow_down_maximum(true));
-                }
+                // Slow down to maximum possible.
+                for (auto it = cur_begin; it != by_slowdown_time.end(); ++ it)
+                    (*it)->slowdown_to_minimum_feedrate(true);
             }
-            #undef FORALL_UNPROCESSED
-            // Sum the final elapsed time for all extruders up to i_extruder_by_slowdown_time.
-            if (i_extruder_by_slowdown_time + 1 == extruder_by_slowdown_time.size())
-                // Optimization for single extruder prints.
-                elapsed_time_total0 = total;
-            else
-                elapsed_time_total0 += per_extruder_adjustments[idx].elapsed_time_total();
         }
-        elapsed_time_total = elapsed_time_total0;
+        elapsed_time_total0 += adj.elapsed_time_total();
     }
 
-    // Transform the G-code.
-    // First sort the adjustment lines by their position in the source G-code.
+    return elapsed_time_total0;
+}
+
+// Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
+// Returns the adjusted G-code.
+std::string CoolingBuffer::apply_layer_cooldown(
+    // Source G-code for the current layer.
+    const std::string                      &gcode,
+    // ID of the current layer, used to disable fan for the first n layers.
+    size_t                                  layer_id, 
+    // Total time of this layer after slow down, used to control the fan.
+    float                                   layer_time,
+    // Per extruder list of G-code lines and their cool down attributes.
+    std::vector<PerExtruderAdjustments>    &per_extruder_adjustments)
+{
+    // First sort the adjustment lines by of multiple extruders by their position in the source G-code.
     std::vector<const CoolingLine*> lines;
     {
         size_t n_lines = 0;
@@ -545,8 +609,9 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
     int  fan_speed          = -1;
     bool bridge_fan_control = false;
     int  bridge_fan_speed   = 0;
-    auto change_extruder_set_fan = [ this, layer_id, elapsed_time_total, &new_gcode, &fan_speed, &bridge_fan_control, &bridge_fan_speed ]() {
+    auto change_extruder_set_fan = [ this, layer_id, layer_time, &new_gcode, &fan_speed, &bridge_fan_control, &bridge_fan_speed ]() {
         const FullPrintConfig &config = m_gcodegen.config();
+#define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder)
         int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed);
         int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0;
         if (layer_id >= EXTRUDER_CONFIG(disable_fan_first_layers)) {
@@ -554,17 +619,18 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
             float slowdown_below_layer_time = float(EXTRUDER_CONFIG(slowdown_below_layer_time));
             float fan_below_layer_time      = float(EXTRUDER_CONFIG(fan_below_layer_time));
             if (EXTRUDER_CONFIG(cooling)) {
-                if (elapsed_time_total < slowdown_below_layer_time) {
+                if (layer_time < slowdown_below_layer_time) {
                     // Layer time very short. Enable the fan to a full throttle.
                     fan_speed_new = max_fan_speed;
-                } else if (elapsed_time_total < fan_below_layer_time) {
+                } else if (layer_time < fan_below_layer_time) {
                     // Layer time quite short. Enable the fan proportionally according to the current layer time.
-                    assert(elapsed_time_total >= slowdown_below_layer_time);
-                    double t = (elapsed_time_total - slowdown_below_layer_time) / (fan_below_layer_time - slowdown_below_layer_time);
+                    assert(layer_time >= slowdown_below_layer_time);
+                    double t = (layer_time - slowdown_below_layer_time) / (fan_below_layer_time - slowdown_below_layer_time);
                     fan_speed_new = int(floor(t * min_fan_speed + (1. - t) * max_fan_speed) + 0.5);
                 }
             }
             bridge_fan_speed   = EXTRUDER_CONFIG(bridge_fan_speed);
+#undef EXTRUDER_CONFIG
             bridge_fan_control = bridge_fan_speed > fan_speed_new;
         } else {
             bridge_fan_control = false;
@@ -576,10 +642,11 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
             new_gcode += m_gcodegen.writer().set_fan(fan_speed);
         }
     };
-	change_extruder_set_fan();
 
-    const char *pos              = gcode.c_str();
-    int         current_feedrate = 0;
+    const char         *pos               = gcode.c_str();
+    int                 current_feedrate  = 0;
+    const std::string   toolchange_prefix = m_gcodegen.writer().toolchange_prefix();
+    change_extruder_set_fan();
     for (const CoolingLine *line : lines) {
         const char *line_start  = gcode.c_str() + line->line_start;
         const char *line_end    = gcode.c_str() + line->line_end;
@@ -602,9 +669,9 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
             // Just remove this comment.
         } else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) {
             // Find the start of a comment, or roll to the end of line.
-			const char *end = line_start;
-			for (; end < line_end && *end != ';'; ++ end);
-			// Find the 'F' word.
+            const char *end = line_start;
+            for (; end < line_end && *end != ';'; ++ end);
+            // Find the 'F' word.
             const char *fpos            = strstr(line_start + 2, " F") + 2;
             int         new_feedrate    = current_feedrate;
             bool        modify          = false;
@@ -643,7 +710,7 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
                     new_gcode.append(line_start, f - line_start + 1);
                 }
                 // Skip the non-whitespaces of the F parameter up the comment or end of line.
-				for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++fpos);
+                for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++fpos);
                 // Append the rest of the line without the comment.
                 if (fpos < end)
                     new_gcode.append(fpos, end - fpos);
@@ -653,21 +720,21 @@ std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_
             // Process the rest of the line.
             if (end < line_end) {
                 if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) {
-					// Process comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE"
-					std::string comment(end, line_end);
-					boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", "");
+                    // Process comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE"
+                    std::string comment(end, line_end);
+                    boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", "");
                     if (line->type & CoolingLine::TYPE_EXTERNAL_PERIMETER)
                         boost::replace_all(comment, ";_EXTERNAL_PERIMETER", "");
                     if (line->type & CoolingLine::TYPE_WIPE)
                         boost::replace_all(comment, ";_WIPE", "");
-					new_gcode += comment;
-				} else {
-					// Just attach the rest of the source line.
-					new_gcode.append(end, line_end - end);
-				}
+                    new_gcode += comment;
+                } else {
+                    // Just attach the rest of the source line.
+                    new_gcode.append(end, line_end - end);
+                }
             }
         } else {
-			new_gcode.append(line_start, line_end - line_start);
+            new_gcode.append(line_start, line_end - line_start);
         }
         pos = line_end;
     }
diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.hpp b/xs/src/libslic3r/GCode/CoolingBuffer.hpp
index f85c470b3..bf4b082e2 100644
--- a/xs/src/libslic3r/GCode/CoolingBuffer.hpp
+++ b/xs/src/libslic3r/GCode/CoolingBuffer.hpp
@@ -9,13 +9,17 @@ namespace Slic3r {
 
 class GCode;
 class Layer;
+class 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
-and the print is modified to stretch over a minimum layer time.
-*/
-
+// 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
+// and the print is modified to stretch over a minimum layer time.
+//
+// The simple it sounds, the actual implementation is significantly more complex.
+// Namely, for a multi-extruder print, each material may require a different cooling logic.
+// For example, some materials may not like to print too slowly, while with some materials 
+// we may slow down significantly.
+//
 class CoolingBuffer {
 public:
     CoolingBuffer(GCode &gcodegen);
@@ -25,7 +29,12 @@ public:
     GCode* 	    gcodegen() { return &m_gcodegen; }
 
 private:
-	CoolingBuffer& operator=(const CoolingBuffer&);
+	CoolingBuffer& operator=(const CoolingBuffer&) = delete;
+    std::vector<PerExtruderAdjustments> parse_layer_gcode(const std::string &gcode, std::vector<float> &current_pos) const;
+    float       calculate_layer_slowdown(std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
+    // Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed.
+    // Returns the adjusted G-code.
+    std::string apply_layer_cooldown(const std::string &gcode, size_t layer_id, float layer_time, std::vector<PerExtruderAdjustments> &per_extruder_adjustments);
 
     GCode&              m_gcodegen;
     std::string         m_gcode;
@@ -34,6 +43,9 @@ private:
     std::vector<char>   m_axis;
     std::vector<float>  m_current_pos;
     unsigned int        m_current_extruder;
+
+    // Old logic: proportional.
+    bool                m_cooling_logic_proportional = false;
 };
 
 }

From 25d47c1da8bac02d5b1408d9a39ba7356b14fc56 Mon Sep 17 00:00:00 2001
From: bubnikv <bubnikv@gmail.com>
Date: Thu, 26 Apr 2018 18:56:16 +0200
Subject: [PATCH 4/4] Fix of the new cooling logic.

---
 xs/src/libslic3r/GCode/CoolingBuffer.cpp | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.cpp b/xs/src/libslic3r/GCode/CoolingBuffer.cpp
index 141d197ca..b786f9bce 100644
--- a/xs/src/libslic3r/GCode/CoolingBuffer.cpp
+++ b/xs/src/libslic3r/GCode/CoolingBuffer.cpp
@@ -50,7 +50,7 @@ struct CoolingLine
 
     CoolingLine(unsigned int type, size_t  line_start, size_t  line_end) :
         type(type), line_start(line_start), line_end(line_end),
-        length(0.f), time(0.f), time_max(0.f), slowdown(false) {}
+        length(0.f), feedrate(0.f), time(0.f), time_max(0.f), slowdown(false) {}
 
     bool adjustable(bool slowdown_external_perimeters) const {
         return (this->type & TYPE_ADJUSTABLE) && 
@@ -158,9 +158,9 @@ struct PerExtruderAdjustments
             bool adj2 = l2.adjustable();
             return (adj1 == adj2) ? l1.feedrate > l2.feedrate : adj1;
         });
-        for (n_lines_adjustable = 0; n_lines_adjustable < lines.size(); ++ n_lines_adjustable)
-            if ((this->lines[n_lines_adjustable].type & CoolingLine::TYPE_ADJUSTABLE) == 0)
-                break;
+        for (n_lines_adjustable = 0; 
+            n_lines_adjustable < lines.size() && this->lines[n_lines_adjustable].adjustable();
+            ++ n_lines_adjustable);
         time_non_adjustable = 0.f;
         for (size_t i = n_lines_adjustable; i < lines.size(); ++ i)
             time_non_adjustable += lines[i].time;
@@ -329,10 +329,10 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
                     // Movement in the extruder axis.
                     line.length = std::abs(dif[3]);
                 }
-                if (line.length > 0) {
-                    line.feedrate = new_pos[4]; // current F
-                    line.time   = line.length / line.feedrate;
-                }
+                line.feedrate = new_pos[4];
+                assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f);
+                if (line.length > 0)
+                    line.time = line.length / line.feedrate;
                 line.time_max = line.time;
                 if ((line.type & CoolingLine::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1))
                     line.time_max = (adjustment->min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->min_print_speed);
@@ -340,6 +340,7 @@ std::vector<PerExtruderAdjustments> CoolingBuffer::parse_layer_gcode(const std::
                     // Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry.
                     assert((line.type & CoolingLine::TYPE_HAS_F) == 0);
                     CoolingLine &sm = adjustment->lines[active_speed_modifier];
+                    assert(sm.feedrate > 0.f);
                     sm.length   += line.length;
                     sm.time     += line.time;
                     if (sm.time_max != FLT_MAX) {