From 6ffa51da586472addb07107cbb33befd68d20244 Mon Sep 17 00:00:00 2001
From: enricoturri1966 <enricoturri@seznam.cz>
Date: Tue, 23 Mar 2021 09:05:52 +0100
Subject: [PATCH] 1st installment of export to gcode of M73 lines for remaining
 time to next printer stop

---
 src/libslic3r/GCode/GCodeProcessor.cpp | 103 +++++++++++++++++++++++++
 src/libslic3r/GCode/GCodeProcessor.hpp |  11 +++
 src/libslic3r/Technologies.hpp         |   2 +
 3 files changed, 116 insertions(+)

diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp
index bd4e7a3ad..658351254 100644
--- a/src/libslic3r/GCode/GCodeProcessor.cpp
+++ b/src/libslic3r/GCode/GCodeProcessor.cpp
@@ -192,6 +192,9 @@ void GCodeProcessor::TimeMachine::reset()
     max_acceleration = 0.0f;
     extrude_factor_override_percentage = 1.0f;
     time = 0.0f;
+#if ENABLE_EXTENDED_M73_LINES
+    stop_times = std::vector<StopTime>();
+#endif // ENABLE_EXTENDED_M73_LINES
     curr.reset();
     prev.reset();
     gcode_time.reset();
@@ -319,6 +322,13 @@ void GCodeProcessor::TimeMachine::calculate_time(size_t keep_last_n_blocks)
             layers_time[block.layer_id - 1] += block_time;
         }
         g1_times_cache.push_back({ block.g1_line_id, time });
+#if ENABLE_EXTENDED_M73_LINES
+        // update times for remaining time to printer stop placeholders
+        auto it_stop_time = std::lower_bound(stop_times.begin(), stop_times.end(), block.g1_line_id,
+            [](const StopTime& t, unsigned int value) { return t.g1_line_id < value; });
+        if (it_stop_time != stop_times.end() && it_stop_time->g1_line_id == block.g1_line_id)
+            it_stop_time->elapsed_time = time;
+#endif // ENABLE_EXTENDED_M73_LINES
     }
 
     if (keep_last_n_blocks)
@@ -361,7 +371,11 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
         return int(::roundf(time_in_seconds / 60.0f));
     };
 
+#if ENABLE_EXTENDED_M73_LINES
+    auto format_line_M73_main = [](const std::string& mask, int percent, int time) {
+#else
     auto format_line_M73 = [](const std::string& mask, int percent, int time) {
+#endif // ENABLE_EXTENDED_M73_LINES
         char line_M73[64];
         sprintf(line_M73, mask.c_str(),
             std::to_string(percent).c_str(),
@@ -369,14 +383,35 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
         return std::string(line_M73);
     };
 
+#if ENABLE_EXTENDED_M73_LINES
+    auto format_line_M73_stop = [](const std::string& mask, int time) {
+        char line_M73[64];
+        sprintf(line_M73, mask.c_str(), std::to_string(time).c_str());
+        return std::string(line_M73);
+    };
+#endif // ENABLE_EXTENDED_M73_LINES
+
     GCodeReader parser;
     std::string gcode_line;
     size_t g1_lines_counter = 0;
     // keeps track of last exported pair <percent, remaining time>
+#if ENABLE_EXTENDED_M73_LINES
+    std::array<std::pair<int, int>, static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count)> last_exported_main;
+    for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+        last_exported_main[i] = { 0, time_in_minutes(machines[i].time) };
+    }
+
+    // keeps track of last exported remaining time to next printer stop
+    std::array<int, static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count)> last_exported_stop;
+    for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+        last_exported_stop[i] = time_in_minutes(machines[i].time);
+    }
+#else
     std::array<std::pair<int, int>, static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count)> last_exported;
     for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
         last_exported[i] = { 0, time_in_minutes(machines[i].time) };
     }
+#endif // ENABLE_EXTENDED_M73_LINES
 
     // buffer line to export only when greater than 64K to reduce writing calls
     std::string export_line;
@@ -399,12 +434,32 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
                 for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
                     const TimeMachine& machine = machines[i];
                     if (machine.enabled) {
+#if ENABLE_EXTENDED_M73_LINES
+                        // export pair <percent, remaining time>
+                        ret += format_line_M73_main(machine.line_m73_main_mask.c_str(),
+                            (line == reserved_tag(ETags::First_Line_M73_Placeholder)) ? 0 : 100,
+                            (line == reserved_tag(ETags::First_Line_M73_Placeholder)) ? time_in_minutes(machine.time) : 0);
+#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER
+                        ++extra_lines_count;
+#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
+
+                        // export remaining time to next printer stop
+                        if (line == reserved_tag(ETags::First_Line_M73_Placeholder) && !machine.stop_times.empty()) {
+                            int to_export_stop = time_in_minutes(machine.stop_times.front().elapsed_time);
+                            ret += format_line_M73_stop(machine.line_m73_stop_mask.c_str(), to_export_stop);
+                            last_exported_stop[i] = to_export_stop;
+#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER
+                            ++extra_lines_count;
+#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
+                        }
+#else
                         ret += format_line_M73(machine.line_m73_mask.c_str(),
                             (line == reserved_tag(ETags::First_Line_M73_Placeholder)) ? 0 : 100,
                             (line == reserved_tag(ETags::First_Line_M73_Placeholder)) ? time_in_minutes(machine.time) : 0);
 #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER
                         ++extra_lines_count;
 #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
+#endif // ENABLE_EXTENDED_M73_LINES
                     }
                 }
             }
@@ -473,11 +528,23 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
             for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
                 const TimeMachine& machine = machines[i];
                 if (machine.enabled) {
+                    // export pair <percent, remaining time>
                     // Skip all machine.g1_times_cache below g1_lines_counter.
                     auto& it = g1_times_cache_it[i];
                     while (it != machine.g1_times_cache.end() && it->id < g1_lines_counter)
                         ++it;
                     if (it != machine.g1_times_cache.end() && it->id == g1_lines_counter) {
+#if ENABLE_EXTENDED_M73_LINES
+                        std::pair<int, int> to_export_main = { int(100.0f * it->elapsed_time / machine.time),
+                                                               time_in_minutes(machine.time - it->elapsed_time) };
+                        if (last_exported_main[i] != to_export_main) {
+                            export_line += format_line_M73_main(machine.line_m73_main_mask.c_str(),
+                                to_export_main.first, to_export_main.second);
+                            last_exported_main[i] = to_export_main;
+#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER
+                            ++exported_lines_count;
+#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
+#else
                         float elapsed_time = it->elapsed_time;
                         std::pair<int, int> to_export = { int(100.0f * elapsed_time / machine.time),
                                                           time_in_minutes(machine.time - elapsed_time) };
@@ -488,7 +555,23 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename)
 #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER
                             ++exported_lines_count;
 #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
+#endif // ENABLE_EXTENDED_M73_LINES
                         }
+#if ENABLE_EXTENDED_M73_LINES
+                        // export remaining time to next printer stop
+                        auto it_stop = std::upper_bound(machine.stop_times.begin(), machine.stop_times.end(), it->elapsed_time,
+                            [](float value, const TimeMachine::StopTime& t) { return value < t.elapsed_time; });
+                        if (it_stop != machine.stop_times.end()) {
+                            int to_export_stop = time_in_minutes(it_stop->elapsed_time - it->elapsed_time);
+                            if (last_exported_stop[i] != to_export_stop) {
+                                export_line += format_line_M73_stop(machine.line_m73_stop_mask.c_str(), to_export_stop);
+                                last_exported_stop[i] = to_export_stop;
+#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER
+                                ++exported_lines_count;
+#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
+                            }
+                        }
+#endif // ENABLE_EXTENDED_M73_LINES
                     }
                 }
             }
@@ -654,8 +737,15 @@ bool GCodeProcessor::contains_reserved_tags(const std::string& gcode, unsigned i
 GCodeProcessor::GCodeProcessor()
 {
     reset();
+#if ENABLE_EXTENDED_M73_LINES
+    m_time_processor.machines[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Normal)].line_m73_main_mask = "M73 P%s R%s\n";
+    m_time_processor.machines[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Normal)].line_m73_stop_mask = "M73 C%s\n";
+    m_time_processor.machines[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].line_m73_main_mask = "M73 Q%s S%s\n";
+    m_time_processor.machines[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].line_m73_stop_mask = "M73 D%s\n";
+#else
     m_time_processor.machines[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Normal)].line_m73_mask = "M73 P%s R%s\n";
     m_time_processor.machines[static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].line_m73_mask = "M73 Q%s S%s\n";
+#endif // ENABLE_EXTENDED_M73_LINES
 }
 
 void GCodeProcessor::apply_config(const PrintConfig& config)
@@ -2589,6 +2679,19 @@ void GCodeProcessor::store_move_vertex(EMoveType type)
         static_cast<float>(m_result.moves.size())
     };
     m_result.moves.emplace_back(vertex);
+
+#if ENABLE_EXTENDED_M73_LINES
+    // stores stop time markers for later use
+    if (type == EMoveType::Color_change || type == EMoveType::Pause_Print) {
+        for (size_t i = 0; i < static_cast<size_t>(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) {
+            TimeMachine& machine = m_time_processor.machines[i];
+            if (!machine.enabled)
+                continue;
+
+            machine.stop_times.push_back({ m_g1_line_id, 0.0f });
+        }
+    }
+#endif // ENABLE_EXTENDED_M73_LINES
 }
 
 float GCodeProcessor::minimum_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const
diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp
index f6625452f..b263c13e5 100644
--- a/src/libslic3r/GCode/GCodeProcessor.hpp
+++ b/src/libslic3r/GCode/GCodeProcessor.hpp
@@ -253,7 +253,18 @@ namespace Slic3r {
             float max_acceleration; // mm/s^2
             float extrude_factor_override_percentage;
             float time; // s
+#if ENABLE_EXTENDED_M73_LINES
+            struct StopTime
+            {
+                unsigned int g1_line_id;
+                float elapsed_time;
+            };
+            std::vector<StopTime> stop_times;
+            std::string line_m73_main_mask;
+            std::string line_m73_stop_mask;
+#else
             std::string line_m73_mask;
+#endif // ENABLE_EXTENDED_M73_LINES
             State curr;
             State prev;
             CustomGCodeTime gcode_time;
diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp
index 63ee79805..117350b27 100644
--- a/src/libslic3r/Technologies.hpp
+++ b/src/libslic3r/Technologies.hpp
@@ -55,6 +55,8 @@
 #define ENABLE_VALIDATE_CUSTOM_GCODE (1 && ENABLE_2_3_1_ALPHA1)
 // Enable showing a imgui window containing gcode in preview
 #define ENABLE_GCODE_WINDOW (1 && ENABLE_2_3_1_ALPHA1)
+// Enable exporting lines M73 for remaining time to next printer stop to gcode
+#define ENABLE_EXTENDED_M73_LINES (1 && ENABLE_VALIDATE_CUSTOM_GCODE)
 
 
 #endif // _prusaslicer_technologies_h_