From ac7674b85aba7d870882b627c090a010de3c7dc1 Mon Sep 17 00:00:00 2001
From: Vojtech Bubnik <bubnikv@gmail.com>
Date: Tue, 21 Sep 2021 15:30:37 +0200
Subject: [PATCH] Fixed visualization of G-code lines in G-code viewer (3D
 view). Improved speed of parsing external G-code.

---
 src/libslic3r/GCode/GCodeProcessor.cpp | 107 ++++++++++++++-----------
 src/libslic3r/GCodeReader.cpp          |  95 ++++++++++++++++------
 src/libslic3r/GCodeReader.hpp          |  14 ++++
 src/libslic3r/LocalesUtils.cpp         |   2 +-
 src/libslic3r/LocalesUtils.hpp         |   3 +-
 src/libslic3r/Utils.hpp                |  13 +++
 src/libslic3r/utils.cpp                |   2 +-
 7 files changed, 163 insertions(+), 73 deletions(-)

diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp
index 64c9ba428..4e731c8b4 100644
--- a/src/libslic3r/GCode/GCodeProcessor.cpp
+++ b/src/libslic3r/GCode/GCodeProcessor.cpp
@@ -343,18 +343,6 @@ void GCodeProcessor::TimeProcessor::reset()
     machines[static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Normal)].enabled = true;
 }
 
-struct FilePtr {
-    FilePtr(FILE *f) : f(f) {}
-    ~FilePtr() { this->close(); }
-    void close() { 
-        if (this->f) {
-            ::fclose(this->f);
-            this->f = nullptr;
-        }
-    }
-    FILE* f = nullptr;
-};
-
 void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, std::vector<MoveVertex>& moves, std::vector<size_t>& lines_ends)
 {
     FilePtr in{ boost::nowide::fopen(filename.c_str(), "rb") };
@@ -755,11 +743,11 @@ void GCodeProcessor::Result::reset() {
 #endif // ENABLE_GCODE_VIEWER_STATISTICS
 
 const std::vector<std::pair<GCodeProcessor::EProducer, std::string>> GCodeProcessor::Producers = {
-    { EProducer::PrusaSlicer, "PrusaSlicer" },
-    { EProducer::Slic3rPE,    "Slic3r Prusa Edition" },
-    { EProducer::Slic3r,      "Slic3r" },
+    { EProducer::PrusaSlicer, "generated by PrusaSlicer" },
+    { EProducer::Slic3rPE,    "generated by Slic3r Prusa Edition" },
+    { EProducer::Slic3r,      "generated by Slic3r" },
     { EProducer::Cura,        "Cura_SteamEngine" },
-    { EProducer::Simplify3D,  "Simplify3D" },
+    { EProducer::Simplify3D,  "G-Code generated by Simplify3D(R)" },
     { EProducer::CraftWare,   "CraftWare" },
     { EProducer::ideaMaker,   "ideaMaker" },
     { EProducer::KissSlicer,  "KISSlicer" }
@@ -1191,6 +1179,16 @@ void GCodeProcessor::reset()
 #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING
 }
 
+static inline const char* skip_whitespaces(const char *begin, const char *end) {
+    for (; begin != end && (*begin == ' ' || *begin == '\t'); ++ begin);
+    return begin;
+}
+
+static inline const char* remove_eols(const char *begin, const char *end) {
+    for (; begin != end && (*(end - 1) == '\r' || *(end - 1) == '\n'); -- end);
+    return end;
+}
+
 void GCodeProcessor::process_file(const std::string& filename, std::function<void()> cancel_callback)
 {
     CNumericLocalesSetter locales_setter;
@@ -1202,14 +1200,16 @@ void GCodeProcessor::process_file(const std::string& filename, std::function<voi
     // pre-processing
     // parse the gcode file to detect its producer
     {
-        m_parser.parse_file(filename, [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) {
-            const std::string_view cmd = line.cmd();
-            if (cmd.empty()) {
-                const std::string_view comment = line.comment();
-                if (comment.length() > 1 && detect_producer(comment))
+        m_parser.parse_file_raw(filename, [this](GCodeReader& reader, const char *begin, const char *end) {
+            begin = skip_whitespaces(begin, end);
+            if (begin != end && *begin == ';') {
+                // Comment.
+                begin = skip_whitespaces(++ begin, end);
+                end   = remove_eols(begin, end);
+                if (begin != end && detect_producer(std::string_view(begin, end - begin)))
                     m_parser.quit_parsing();
             }
-            });
+        });
         m_parser.reset();
 
         // if the gcode was produced by PrusaSlicer,
@@ -1374,9 +1374,11 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename)
     };
 
     BedSize bed_size;
+    bool    producer_detected = false;
 
-    m_parser.parse_file(filename, [this, &bed_size](GCodeReader& reader, const GCodeReader::GCodeLine& line) {
-        auto extract_double = [](const std::string& cmt, const std::string& key, double& out) {
+    m_parser.parse_file_raw(filename, [this, &bed_size, &producer_detected](GCodeReader& reader, const char* begin, const char* end) {
+
+        auto extract_double = [](const std::string_view cmt, const std::string& key, double& out) {
             size_t pos = cmt.find(key);
             if (pos != cmt.npos) {
                 pos = cmt.find(',', pos);
@@ -1388,12 +1390,12 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename)
             return false;
         };
 
-        auto extract_floats = [](const std::string& cmt, const std::string& key, std::vector<float>& out) {
+        auto extract_floats = [](const std::string_view cmt, const std::string& key, std::vector<float>& out) {
             size_t pos = cmt.find(key);
             if (pos != cmt.npos) {
                 pos = cmt.find(',', pos);
                 if (pos != cmt.npos) {
-                    std::string data_str = cmt.substr(pos + 1);
+                    const std::string_view data_str = cmt.substr(pos + 1);
                     std::vector<std::string> values_str;
                     boost::split(values_str, data_str, boost::is_any_of("|,"), boost::token_compress_on);
                     for (const std::string& s : values_str) {
@@ -1404,28 +1406,39 @@ void GCodeProcessor::apply_config_simplify3d(const std::string& filename)
             }
             return false;
         };
-
-        const std::string& comment = line.raw();
-        if (comment.length() > 2 && comment.front() == ';') {
-            if (bed_size.x == 0.0 && comment.find("strokeXoverride") != comment.npos)
-                extract_double(comment, "strokeXoverride", bed_size.x);
-            else if (bed_size.y == 0.0 && comment.find("strokeYoverride") != comment.npos)
-                extract_double(comment, "strokeYoverride", bed_size.y);
-            else if (comment.find("filamentDiameters") != comment.npos) {
-                m_result.filament_diameters.clear();
-                extract_floats(comment, "filamentDiameters", m_result.filament_diameters);
+        
+        begin = skip_whitespaces(begin, end);
+        end   = remove_eols(begin, end);
+        if (begin != end)
+            if (*begin == ';') {
+                // Comment.
+                begin = skip_whitespaces(++ begin, end);
+                if (begin != end) {
+                    std::string_view comment(begin, end - begin);
+                    if (producer_detected) {
+                        if (bed_size.x == 0.0 && comment.find("strokeXoverride") != comment.npos)
+                            extract_double(comment, "strokeXoverride", bed_size.x);
+                        else if (bed_size.y == 0.0 && comment.find("strokeYoverride") != comment.npos)
+                            extract_double(comment, "strokeYoverride", bed_size.y);
+                        else if (comment.find("filamentDiameters") != comment.npos) {
+                            m_result.filament_diameters.clear();
+                            extract_floats(comment, "filamentDiameters", m_result.filament_diameters);
+                        } else if (comment.find("filamentDensities") != comment.npos) {
+                            m_result.filament_densities.clear();
+                            extract_floats(comment, "filamentDensities", m_result.filament_densities);
+                        } else if (comment.find("extruderDiameter") != comment.npos) {
+                            std::vector<float> extruder_diameters;
+                            extract_floats(comment, "extruderDiameter", extruder_diameters);
+                            m_result.extruders_count = extruder_diameters.size();
+                        }
+                    } else if (boost::starts_with(comment, "G-Code generated by Simplify3D(R)"))
+                        producer_detected = true;
+                }
+            } else {
+                // Some non-empty G-code line detected, stop parsing config comments.
+                reader.quit_parsing();
             }
-            else if (comment.find("filamentDensities") != comment.npos) {
-                m_result.filament_densities.clear();
-                extract_floats(comment, "filamentDensities", m_result.filament_densities);
-            }
-            else if (comment.find("extruderDiameter") != comment.npos) {
-                std::vector<float> extruder_diameters;
-                extract_floats(comment, "extruderDiameter", extruder_diameters);
-                m_result.extruders_count = extruder_diameters.size();
-            }
-        }
-        });
+    });
 
     if (m_result.extruders_count == 0)
         m_result.extruders_count = std::max<size_t>(1, std::min(m_result.filament_diameters.size(), m_result.filament_densities.size()));
diff --git a/src/libslic3r/GCodeReader.cpp b/src/libslic3r/GCodeReader.cpp
index 61ab10f22..657f47f0e 100644
--- a/src/libslic3r/GCodeReader.cpp
+++ b/src/libslic3r/GCodeReader.cpp
@@ -5,6 +5,7 @@
 #include <fstream>
 #include <iostream>
 #include <iomanip>
+#include "Utils.hpp"
 
 #include "LocalesUtils.hpp"
 
@@ -126,44 +127,92 @@ void GCodeReader::update_coordinates(GCodeLine &gline, std::pair<const char*, co
     }
 }
 
-bool GCodeReader::parse_file(const std::string &file, callback_t callback)
+template<typename ParseLineCallback, typename LineEndCallback>
+bool GCodeReader::parse_file_raw_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback)
 {
-    boost::nowide::ifstream f(file);
-    f.sync_with_stdio(false);
+    FilePtr in{ boost::nowide::fopen(filename.c_str(), "rb") };
+
+    // Read the input stream 64kB at a time, extract lines and process them.
     std::vector<char> buffer(65536 * 10, 0);
-    std::string line;
+    // Line buffer.
+    std::string gcode_line;
+    size_t file_pos = 0;
     m_parsing = true;
-    GCodeLine gline;
-    while (m_parsing && ! f.eof()) {
-        f.read(buffer.data(), buffer.size());
-        if (! f.eof() && ! f.good())
-            // Reading the input file failed.
+    for (;;) {
+        size_t cnt_read = ::fread(buffer.data(), 1, buffer.size(), in.f);
+        if (::ferror(in.f))
             return false;
+        bool eof       = cnt_read == 0;
         auto it        = buffer.begin();
-        auto it_bufend = buffer.begin() + f.gcount();
-        while (it != it_bufend) {
-            bool eol = false;
+        auto it_bufend = buffer.begin() + cnt_read;
+        while (it != it_bufend || (eof && ! gcode_line.empty())) {
+            // Find end of line.
+            bool eol    = false;
             auto it_end = it;
-            for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end) ;
-            eol |= f.eof() && it_end == it_bufend;
+            for (; it_end != it_bufend && ! (eol = *it_end == '\r' || *it_end == '\n'); ++ it_end)
+                if (*it_end == '\n')
+                    line_end_callback((it_end - buffer.begin()) + 1);
+            // End of line is indicated also if end of file was reached.
+            eol |= eof && it_end == it_bufend;
             if (eol) {
-                gline.reset();
-                if (line.empty())
-                    this->parse_line(&(*it), &(*it_end), gline, callback);
+                if (gcode_line.empty())
+                    parse_line_callback(&(*it), &(*it_end));
                 else {
-                    line.insert(line.end(), it, it_end);
-                    this->parse_line(line.c_str(), line.c_str() + line.size(), gline, callback);
-                    line.clear();
+                    gcode_line.insert(gcode_line.end(), it, it_end);
+                    parse_line_callback(gcode_line.c_str(), gcode_line.c_str() + gcode_line.size());
+                    gcode_line.clear();
                 }
+                if (! m_parsing)
+                    // The callback wishes to exit.
+                    return true;
             } else
-                line.insert(line.end(), it, it_end);
-            // Skip all the empty lines.
-            for (it = it_end; it != it_bufend && (*it == '\r' || *it == '\n'); ++ it) ;
+                gcode_line.insert(gcode_line.end(), it, it_end);
+            // Skip EOL.
+            it = it_end; 
+            if (it != it_bufend && *it == '\r')
+                ++ it;
+            if (it != it_bufend && *it == '\n') {
+                line_end_callback((it - buffer.begin()) + 1);
+                ++ it;
+            }
         }
+        if (eof)
+            break;
+        file_pos += cnt_read;
     }
     return true;
 }
 
+template<typename ParseLineCallback, typename LineEndCallback>
+bool GCodeReader::parse_file_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback)
+{
+    GCodeLine gline;    
+    return this->parse_file_raw_internal(filename, 
+        [this, &gline, parse_line_callback](const char *begin, const char *end) {
+            gline.reset();
+            this->parse_line(begin, end, gline, parse_line_callback);
+        }, 
+        line_end_callback);
+}
+
+bool GCodeReader::parse_file(const std::string &file, callback_t callback)
+{
+    return this->parse_file_internal(file, callback, [](size_t){});
+}
+
+bool GCodeReader::parse_file(const std::string &file, callback_t callback, std::vector<size_t> &lines_ends)
+{
+    lines_ends.clear();
+    return this->parse_file_internal(file, callback, [&lines_ends](size_t file_pos){ lines_ends.emplace_back(file_pos); });
+}
+
+bool GCodeReader::parse_file_raw(const std::string &filename, raw_line_callback_t line_callback)
+{
+    return this->parse_file_raw_internal(filename,
+        [this, line_callback](const char *begin, const char *end) { line_callback(*this, begin, end); }, 
+        [](size_t){});
+}
+
 bool GCodeReader::GCodeLine::has(char axis) const
 {
     const char *c = m_raw.c_str();
diff --git a/src/libslic3r/GCodeReader.hpp b/src/libslic3r/GCodeReader.hpp
index 15376c0fc..0ab268139 100644
--- a/src/libslic3r/GCodeReader.hpp
+++ b/src/libslic3r/GCodeReader.hpp
@@ -76,6 +76,7 @@ public:
     };
 
     typedef std::function<void(GCodeReader&, const GCodeLine&)> callback_t;
+    typedef std::function<void(GCodeReader&, const char*, const char*)> raw_line_callback_t;
     
     GCodeReader() : m_verbose(false), m_extrusion_axis('E') { this->reset(); }
     void reset() { memset(m_position, 0, sizeof(m_position)); }
@@ -114,6 +115,13 @@ public:
 
     // Returns false if reading the file failed.
     bool parse_file(const std::string &file, callback_t callback);
+    // Collect positions of line ends in the binary G-code to be used by the G-code viewer when memory mapping and displaying section of G-code
+    // as an overlay in the 3D scene.
+    bool parse_file(const std::string &file, callback_t callback, std::vector<size_t> &lines_ends);
+    // Just read the G-code file line by line, calls callback (const char *begin, const char *end). Returns false if reading the file failed.
+    bool parse_file_raw(const std::string &file, raw_line_callback_t callback);
+
+    // To be called by the callback to stop parsing.
     void quit_parsing() { m_parsing = false; }
 
     float& x()       { return m_position[X]; }
@@ -132,6 +140,11 @@ public:
 //  void   set_extrusion_axis(char axis) { m_extrusion_axis = axis; }
 
 private:
+    template<typename ParseLineCallback, typename LineEndCallback>
+    bool        parse_file_raw_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback);
+    template<typename ParseLineCallback, typename LineEndCallback>
+    bool        parse_file_internal(const std::string &filename, ParseLineCallback parse_line_callback, LineEndCallback line_end_callback);
+
     const char* parse_line_internal(const char *ptr, const char *end, GCodeLine &gline, std::pair<const char*, const char*> &command);
     void        update_coordinates(GCodeLine &gline, std::pair<const char*, const char*> &command);
 
@@ -154,6 +167,7 @@ private:
     char        m_extrusion_axis;
     float       m_position[NUM_AXES];
     bool        m_verbose;
+    // To be set by the callback to stop parsing.
     bool        m_parsing{ false };
 };
 
diff --git a/src/libslic3r/LocalesUtils.cpp b/src/libslic3r/LocalesUtils.cpp
index 8c42a36ba..d32107233 100644
--- a/src/libslic3r/LocalesUtils.cpp
+++ b/src/libslic3r/LocalesUtils.cpp
@@ -51,7 +51,7 @@ bool is_decimal_separator_point()
 }
 
 
-double string_to_double_decimal_point(const std::string& str, size_t* pos /* = nullptr*/)
+double string_to_double_decimal_point(const std::string_view str, size_t* pos /* = nullptr*/)
 {
     double out;
     size_t p = fast_float::from_chars(str.data(), str.data() + str.size(), out).ptr - str.data();
diff --git a/src/libslic3r/LocalesUtils.hpp b/src/libslic3r/LocalesUtils.hpp
index 113cfa04b..f63c3572f 100644
--- a/src/libslic3r/LocalesUtils.hpp
+++ b/src/libslic3r/LocalesUtils.hpp
@@ -5,6 +5,7 @@
 #include <clocale>
 #include <iomanip>
 #include <cassert>
+#include <string_view>
 
 #ifdef __APPLE__
 #include <xlocale.h>
@@ -40,7 +41,7 @@ bool is_decimal_separator_point();
 // (We use user C locales and "C" C++ locales in most of the code.)
 std::string float_to_string_decimal_point(double value, int precision = -1);
 //std::string float_to_string_decimal_point(float value,  int precision = -1);
-double string_to_double_decimal_point(const std::string& str, size_t* pos = nullptr);
+double string_to_double_decimal_point(const std::string_view str, size_t* pos = nullptr);
 
 } // namespace Slic3r
 
diff --git a/src/libslic3r/Utils.hpp b/src/libslic3r/Utils.hpp
index a01e63166..cfb4cfa92 100644
--- a/src/libslic3r/Utils.hpp
+++ b/src/libslic3r/Utils.hpp
@@ -255,6 +255,19 @@ template<typename T> struct IsTriviallyCopyable { static constexpr bool value =
 template<typename T> struct IsTriviallyCopyable : public std::is_trivially_copyable<T> {};
 #endif
 
+// A very lightweight ROII wrapper around C FILE.
+// The old C file API is much faster than C++ streams, thus they are recommended for processing large / huge files.
+struct FilePtr {
+    FilePtr(FILE *f) : f(f) {}
+    ~FilePtr() { this->close(); }
+    void close() { 
+        if (this->f) {
+            ::fclose(this->f);
+            this->f = nullptr;
+        }
+    }
+    FILE* f = nullptr;
+};
 
 class ScopeGuard
 {
diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp
index d63ce1f63..efffcfe24 100644
--- a/src/libslic3r/utils.cpp
+++ b/src/libslic3r/utils.cpp
@@ -889,7 +889,7 @@ std::string string_printf(const char *format, ...)
 
 std::string header_slic3r_generated()
 {
-	return std::string("generated by " SLIC3R_APP_NAME " " SLIC3R_VERSION " on " ) + Utils::utc_timestamp();
+	return std::string("generated by PrusaSlicer " SLIC3R_VERSION " on " ) + Utils::utc_timestamp();
 }
 
 std::string header_gcodeviewer_generated()