Browse Source

Merged the branch time_estimate

bubnikv 7 years ago
parent
commit
02256e900f

+ 2 - 0
lib/Slic3r/GUI/Plater.pm

@@ -434,6 +434,7 @@ sub new {
                 fil_mm3 => "Used Filament (mm^3)",
                 fil_g   => "Used Filament (g)",
                 cost    => "Cost",
+                time    => "Estimated printing time",
             );
             while (my $field = shift @info) {
                 my $label = shift @info;
@@ -1428,6 +1429,7 @@ sub on_export_completed {
     $self->{"print_info_cost"}->SetLabel(sprintf("%.2f" , $self->{print}->total_cost));
     $self->{"print_info_fil_g"}->SetLabel(sprintf("%.2f" , $self->{print}->total_weight));
     $self->{"print_info_fil_mm3"}->SetLabel(sprintf("%.2f" , $self->{print}->total_extruded_volume));
+    $self->{"print_info_time"}->SetLabel($self->{print}->estimated_print_time);
     $self->{"print_info_fil_m"}->SetLabel(sprintf("%.2f" , $self->{print}->total_used_filament / 1000));
     $self->{"print_info_box_show"}->(1);
 

+ 100 - 67
xs/src/libslic3r/GCode.cpp

@@ -267,22 +267,6 @@ std::string WipeTowerIntegration::finalize(GCode &gcodegen)
 
 #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_writer.extruder()->id())
 
-inline void write(FILE *file, const std::string &what)
-{
-    fwrite(what.data(), 1, what.size(), file);
-}
-
-// Write a string into a file. Add a newline, if the string does not end with a newline already.
-// Used to export a custom G-code section processed by the PlaceholderParser.
-inline void writeln(FILE *file, const std::string &what)
-{
-    if (! what.empty()) {
-        write(file, what);
-        if (what.back() != '\n')
-            fprintf(file, "\n");
-    }
-}
-
 // Collect pairs of object_layer + support_layer sorted by print_z.
 // object_layer & support_layer are considered to be on the same print_z, if they are not further than EPSILON.
 std::vector<GCode::LayerToPrint> GCode::collect_layers_to_print(const PrintObject &object)
@@ -395,6 +379,7 @@ void GCode::do_export(Print *print, const char *path)
         msg += "        !!!!! End of an error report for the custom G-code template ...\n";
         throw std::runtime_error(msg);
     }
+
     if (boost::nowide::rename(path_tmp.c_str(), path) != 0)
         throw std::runtime_error(
             std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' +
@@ -403,6 +388,9 @@ void GCode::do_export(Print *print, const char *path)
 
 void GCode::_do_export(Print &print, FILE *file)
 {
+    // resets time estimator
+    m_time_estimator.reset();
+
     // How many times will be change_layer() called?
     // change_layer() in turn increments the progress bar status.
     m_layer_count = 0;
@@ -486,7 +474,7 @@ void GCode::_do_export(Print &print, FILE *file)
     m_enable_extrusion_role_markers = (bool)m_pressure_equalizer;
 
     // Write information on the generator.
-    fprintf(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str());
+    _write_format(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str());
     // Write notes (content of the Print Settings tab -> Notes)
     {
         std::list<std::string> lines;
@@ -495,10 +483,10 @@ void GCode::_do_export(Print &print, FILE *file)
             // Remove the trailing '\r' from the '\r\n' sequence.
             if (! line.empty() && line.back() == '\r')
                 line.pop_back();
-            fprintf(file, "; %s\n", line.c_str());
+            _write_format(file, "; %s\n", line.c_str());
         }
         if (! lines.empty())
-            fprintf(file, "\n");
+            _write(file, "\n");
     }
     // Write some terse information on the slicing parameters.
     const PrintObject *first_object         = print.objects.front();
@@ -506,16 +494,16 @@ void GCode::_do_export(Print &print, FILE *file)
     const double       first_layer_height   = first_object->config.first_layer_height.get_abs_value(layer_height);
     for (size_t region_id = 0; region_id < print.regions.size(); ++ region_id) {
         auto region = print.regions[region_id];
-        fprintf(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(frExternalPerimeter, layer_height, false, false, -1., *first_object).width);
-        fprintf(file, "; perimeters extrusion width = %.2fmm\n",          region->flow(frPerimeter,         layer_height, false, false, -1., *first_object).width);
-        fprintf(file, "; infill extrusion width = %.2fmm\n",              region->flow(frInfill,            layer_height, false, false, -1., *first_object).width);
-        fprintf(file, "; solid infill extrusion width = %.2fmm\n",        region->flow(frSolidInfill,       layer_height, false, false, -1., *first_object).width);
-        fprintf(file, "; top infill extrusion width = %.2fmm\n",          region->flow(frTopSolidInfill,    layer_height, false, false, -1., *first_object).width);
+        _write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(frExternalPerimeter, layer_height, false, false, -1., *first_object).width);
+        _write_format(file, "; perimeters extrusion width = %.2fmm\n",          region->flow(frPerimeter,         layer_height, false, false, -1., *first_object).width);
+        _write_format(file, "; infill extrusion width = %.2fmm\n",              region->flow(frInfill,            layer_height, false, false, -1., *first_object).width);
+        _write_format(file, "; solid infill extrusion width = %.2fmm\n",        region->flow(frSolidInfill,       layer_height, false, false, -1., *first_object).width);
+        _write_format(file, "; top infill extrusion width = %.2fmm\n",          region->flow(frTopSolidInfill,    layer_height, false, false, -1., *first_object).width);
         if (print.has_support_material())
-            fprintf(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width);
+            _write_format(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width);
         if (print.config.first_layer_extrusion_width.value > 0)
-            fprintf(file, "; first layer extrusion width = %.2fmm\n",   region->flow(frPerimeter, first_layer_height, false, true, -1., *first_object).width);
-        fprintf(file, "\n");
+            _write_format(file, "; first layer extrusion width = %.2fmm\n",   region->flow(frPerimeter, first_layer_height, false, true, -1., *first_object).width);
+        _write_format(file, "\n");
     }
     
     // Prepare the helper object for replacing placeholders in custom G-code and output filename.
@@ -558,7 +546,7 @@ void GCode::_do_export(Print &print, FILE *file)
 
     // Disable fan.
     if (! print.config.cooling.get_at(initial_extruder_id) || print.config.disable_fan_first_layers.get_at(initial_extruder_id))
-        write(file, m_writer.set_fan(0, true));
+        _write(file, m_writer.set_fan(0, true));
 
     // Let the start-up script prime the 1st printing tool.
     m_placeholder_parser.set("initial_tool", initial_extruder_id);
@@ -575,24 +563,24 @@ void GCode::_do_export(Print &print, FILE *file)
     // Set extruder(s) temperature before and after start G-code.
     this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false);
     // Write the custom start G-code
-    writeln(file, start_gcode);
+    _writeln(file, start_gcode);
     // Process filament-specific gcode in extruder order.
     if (print.config.single_extruder_multi_material) {
         if (has_wipe_tower) {
             // Wipe tower will control the extruder switching, it will call the start_filament_gcode.
         } else {
             // Only initialize the initial extruder.
-            writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config.start_filament_gcode.values[initial_extruder_id], initial_extruder_id));
+            _writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config.start_filament_gcode.values[initial_extruder_id], initial_extruder_id));
         }
     } else {
         for (const std::string &start_gcode : print.config.start_filament_gcode.values)
-            writeln(file, this->placeholder_parser_process("start_gcode", start_gcode, (unsigned int)(&start_gcode - &print.config.start_filament_gcode.values.front())));
+            _writeln(file, this->placeholder_parser_process("start_gcode", start_gcode, (unsigned int)(&start_gcode - &print.config.start_filament_gcode.values.front())));
     }
     this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, true);
     
     // Set other general things.
-    write(file, this->preamble());
-    
+    _write(file, this->preamble());
+
     // Initialize a motion planner for object-to-object travel moves.
     if (print.config.avoid_crossing_perimeters.value) {
         // Collect outer contours of all objects over all layers.
@@ -640,7 +628,7 @@ void GCode::_do_export(Print &print, FILE *file)
     }
     
     // Set initial extruder only after custom start G-code.
-    write(file, this->set_extruder(initial_extruder_id));
+    _write(file, this->set_extruder(initial_extruder_id));
 
     // Do all objects for each layer.
     if (print.config.complete_objects.value) {
@@ -670,8 +658,8 @@ void GCode::_do_export(Print &print, FILE *file)
                     // This happens before Z goes down to layer 0 again, so that no collision happens hopefully.
                     m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer
                     m_avoid_crossing_perimeters.use_external_mp_once = true;
-                    write(file, this->retract());
-                    write(file, this->travel_to(Point(0, 0), erNone, "move to origin position for next object"));
+                    _write(file, this->retract());
+                    _write(file, this->travel_to(Point(0, 0), erNone, "move to origin position for next object"));
                     m_enable_cooling_markers = true;
                     // Disable motion planner when traveling to first object point.
                     m_avoid_crossing_perimeters.disable_once = true;
@@ -683,7 +671,7 @@ void GCode::_do_export(Print &print, FILE *file)
                     // Set first layer bed and extruder temperatures, don't wait for it to reach the temperature.
                     this->_print_first_layer_bed_temperature(file, print, between_objects_gcode, initial_extruder_id, false);
                     this->_print_first_layer_extruder_temperatures(file, print, between_objects_gcode, initial_extruder_id, false);
-                    writeln(file, between_objects_gcode);
+                    _writeln(file, between_objects_gcode);
                 }
                 // Reset the cooling buffer internal state (the current position, feed rate, accelerations).
                 m_cooling_buffer->reset();
@@ -696,7 +684,7 @@ void GCode::_do_export(Print &print, FILE *file)
                     this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), &copy - object._shifted_copies.data());
                 }
                 if (m_pressure_equalizer)
-                    write(file, m_pressure_equalizer->process("", true));
+                    _write(file, m_pressure_equalizer->process("", true));
                 ++ finished_objects;
                 // Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed.
                 // Reset it when starting another object from 1st layer.
@@ -716,8 +704,8 @@ void GCode::_do_export(Print &print, FILE *file)
         // Prusa Multi-Material wipe tower.
         if (has_wipe_tower && ! layers_to_print.empty()) {
             m_wipe_tower.reset(new WipeTowerIntegration(print.config, *print.m_wipe_tower_priming.get(), print.m_wipe_tower_tool_changes, *print.m_wipe_tower_final_purge.get()));
-            write(file, m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height"));
-		    write(file, m_wipe_tower->prime(*this));
+            _write(file, m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height"));
+		    _write(file, m_wipe_tower->prime(*this));
             // Verify, whether the print overaps the priming extrusions.
             BoundingBoxf bbox_print(get_print_extrusions_extents(print));
             coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON;
@@ -727,16 +715,17 @@ void GCode::_do_export(Print &print, FILE *file)
             BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print));
             bbox_prime.offset(0.5f);
             // Beep for 500ms, tone 800Hz. Yet better, play some Morse.
-            write(file, this->retract());
-            fprintf(file, "M300 S800 P500\n");
+            _write(file, this->retract());
+            _write(file, "M300 S800 P500\n");
             if (bbox_prime.overlap(bbox_print)) {
                 // Wait for the user to remove the priming extrusions, otherwise they would
                 // get covered by the print.
-                fprintf(file, "M1 Remove priming towers and click button.\n");
-            } else {
+                _write(file, "M1 Remove priming towers and click button.\n");
+            }
+            else {
                 // Just wait for a bit to let the user check, that the priming succeeded.
                 //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix.
-                fprintf(file, "M1 S10\n");
+                _write(file, "M1 S10\n");
             }
         }
         // Extrude the layers.
@@ -747,26 +736,29 @@ void GCode::_do_export(Print &print, FILE *file)
             this->process_layer(file, print, layer.second, layer_tools, size_t(-1));
         }
         if (m_pressure_equalizer)
-            write(file, m_pressure_equalizer->process("", true));
+            _write(file, m_pressure_equalizer->process("", true));
         if (m_wipe_tower)
             // Purge the extruder, pull out the active filament.
-            write(file, m_wipe_tower->finalize(*this));
+            _write(file, m_wipe_tower->finalize(*this));
     }
 
     // Write end commands to file.
-    write(file, this->retract());
-    write(file, m_writer.set_fan(false));
+    _write(file, this->retract());
+    _write(file, m_writer.set_fan(false));
     // Process filament-specific gcode in extruder order.
     if (print.config.single_extruder_multi_material) {
         // Process the end_filament_gcode for the active filament only.
-        writeln(file, this->placeholder_parser_process("end_filament_gcode", print.config.end_filament_gcode.get_at(m_writer.extruder()->id()), m_writer.extruder()->id()));
+        _writeln(file, this->placeholder_parser_process("end_filament_gcode", print.config.end_filament_gcode.get_at(m_writer.extruder()->id()), m_writer.extruder()->id()));
     } else {
         for (const std::string &end_gcode : print.config.end_filament_gcode.values)
-            writeln(file, this->placeholder_parser_process("end_gcode", end_gcode, (unsigned int)(&end_gcode - &print.config.end_filament_gcode.values.front())));
+            _writeln(file, this->placeholder_parser_process("end_gcode", end_gcode, (unsigned int)(&end_gcode - &print.config.end_filament_gcode.values.front())));
     }
-    writeln(file, this->placeholder_parser_process("end_gcode", print.config.end_gcode, m_writer.extruder()->id()));
-    write(file, m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100%
-    write(file, m_writer.postamble());
+    _writeln(file, this->placeholder_parser_process("end_gcode", print.config.end_gcode, m_writer.extruder()->id()));
+    _write(file, m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100%
+    _write(file, m_writer.postamble());
+
+    // calculates estimated printing time
+    m_time_estimator.calculate_time();
 
     // Get filament stats.
     print.filament_stats.clear();
@@ -774,35 +766,37 @@ void GCode::_do_export(Print &print, FILE *file)
     print.total_extruded_volume  = 0.;
     print.total_weight           = 0.;
     print.total_cost             = 0.;
+    print.estimated_print_time   = m_time_estimator.get_time_hms();
     for (const Extruder &extruder : m_writer.extruders()) {
         double used_filament   = extruder.used_filament();
         double extruded_volume = extruder.extruded_volume();
         double filament_weight = extruded_volume * extruder.filament_density() * 0.001;
         double filament_cost   = filament_weight * extruder.filament_cost()    * 0.001;
         print.filament_stats.insert(std::pair<size_t,float>(extruder.id(), used_filament));
-        fprintf(file, "; filament used = %.1lfmm (%.1lfcm3)\n", used_filament, extruded_volume * 0.001);
+        _write_format(file, "; filament used = %.1lfmm (%.1lfcm3)\n", used_filament, extruded_volume * 0.001);
         if (filament_weight > 0.) {
             print.total_weight = print.total_weight + filament_weight;
-            fprintf(file, "; filament used = %.1lf\n", filament_weight);
+            _write_format(file, "; filament used = %.1lf\n", filament_weight);
             if (filament_cost > 0.) {
                 print.total_cost = print.total_cost + filament_cost;
-                fprintf(file, "; filament cost = %.1lf\n", filament_cost);
+                _write_format(file, "; filament cost = %.1lf\n", filament_cost);
             }
         }
-        print.total_used_filament   = print.total_used_filament + used_filament;
+        print.total_used_filament = print.total_used_filament + used_filament;
         print.total_extruded_volume = print.total_extruded_volume + extruded_volume;
     }
-    fprintf(file, "; total filament cost = %.1lf\n", print.total_cost);
+    _write_format(file, "; total filament cost = %.1lf\n", print.total_cost);
+    _write_format(file, "; estimated printing time = %s\n", m_time_estimator.get_time_hms());
 
     // Append full config.
-    fprintf(file, "\n");
+    _write(file, "\n");
     {
         StaticPrintConfig *configs[] = { &print.config, &print.default_object_config, &print.default_region_config };
         for (size_t i = 0; i < sizeof(configs) / sizeof(configs[0]); ++ i) {
             StaticPrintConfig *cfg = configs[i];
         for (const std::string &key : cfg->keys())
             if (key != "compatible_printers")
-                fprintf(file, "; %s = %s\n", key.c_str(), cfg->serialize(key).c_str());
+                _write_format(file, "; %s = %s\n", key.c_str(), cfg->serialize(key).c_str());
         }
     }
 }
@@ -893,7 +887,7 @@ void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const s
     // the custom start G-code emited these.
     std::string set_temp_gcode = m_writer.set_bed_temperature(temp, wait);
     if (! temp_set_by_gcode)
-        write(file, set_temp_gcode);
+        _write(file, set_temp_gcode);
 }
 
 // Write 1st layer extruder temperatures into the G-code.
@@ -916,7 +910,7 @@ void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, c
             // Set temperature of the first printing extruder only.
             int temp = print.config.first_layer_temperature.get_at(first_printing_extruder_id);
             if (temp > 0)
-                write(file, m_writer.set_temperature(temp, wait, first_printing_extruder_id));
+                _write(file, m_writer.set_temperature(temp, wait, first_printing_extruder_id));
         } else {
             // Set temperatures of all the printing extruders.
             for (unsigned int tool_id : print.extruders()) {
@@ -924,7 +918,7 @@ void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, c
                 if (print.config.ooze_prevention.value)
                     temp += print.config.standby_temperature_delta.value;
                 if (temp > 0)
-                    write(file, m_writer.set_temperature(temp, wait, tool_id));
+                    _write(file, m_writer.set_temperature(temp, wait, tool_id));
             }
         }
     }
@@ -1358,7 +1352,7 @@ void GCode::process_layer(
         gcode = m_pressure_equalizer->process(gcode.c_str(), false);
     // printf("G-code after filter:\n%s\n", out.c_str());
 
-    write(file, gcode);
+    _write(file, gcode);
 }
 
 void GCode::apply_print_config(const PrintConfig &print_config)
@@ -1993,6 +1987,46 @@ std::string GCode::extrude_support(const ExtrusionEntityCollection &support_fill
     return gcode;
 }
 
+void GCode::_write(FILE* file, const std::string& what)
+{
+    if (!what.empty()) {
+        // writes string to file
+        fwrite(what.data(), 1, what.size(), file);
+        // updates time estimator and gcode lines vector
+        const char endLine = '\n';
+        std::string::size_type beginPos = 0;
+        std::string::size_type endPos = what.find_first_of(endLine, beginPos);
+        while (endPos != std::string::npos) {
+            std::string line = what.substr(beginPos, endPos - beginPos + 1);
+            m_time_estimator.add_gcode_line(line);
+            beginPos = endPos + 1;
+            endPos = what.find_first_of(endLine, beginPos);
+        }
+    }
+}
+
+void GCode::_writeln(FILE* file, const std::string& what)
+{
+    if (!what.empty()) {
+        if (what.back() != '\n')
+            _write_format(file, "%s\n", what.c_str());
+        else
+            _write(file, what);
+    }
+}
+
+void GCode::_write_format(FILE* file, const char* format, ...)
+{
+    char buffer[1024];
+    va_list args;
+    va_start(args, format);
+    int res = ::vsnprintf(buffer, 1024, format, args);
+    va_end(args);
+
+    if (res >= 0)
+        _writeln(file, buffer);
+}
+
 std::string GCode::_extrude(const ExtrusionPath &path, std::string description, double speed)
 {
     std::string gcode;
@@ -2182,8 +2216,7 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role)
     return true;
 }
 
-std::string
-GCode::retract(bool toolchange)
+std::string GCode::retract(bool toolchange)
 {
     std::string gcode;
     

+ 15 - 0
xs/src/libslic3r/GCode.hpp

@@ -15,6 +15,7 @@
 #include "GCode/SpiralVase.hpp"
 #include "GCode/ToolOrdering.hpp"
 #include "GCode/WipeTower.hpp"
+#include "GCodeTimeEstimator.hpp"
 #include "EdgeGrid.hpp"
 
 #include <memory>
@@ -273,6 +274,20 @@ protected:
     // Index of a last object copy extruded.
     std::pair<const PrintObject*, Point> m_last_obj_copy;
 
+    // Time estimator
+    GCodeTimeEstimator m_time_estimator;
+
+    // Write a string into a file.
+    void _write(FILE* file, const std::string& what);
+
+    // Write a string into a file. 
+    // Add a newline, if the string does not end with a newline already.
+    // Used to export a custom G-code section processed by the PlaceholderParser.
+    void _writeln(FILE* file, const std::string& what);
+
+    // Formats and write into a file the given data. 
+    void _write_format(FILE* file, const char* format, ...);
+
     std::string _extrude(const ExtrusionPath &path, std::string description = "", double speed = -1);
     void _print_first_layer_bed_temperature(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);
     void _print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait);

+ 967 - 64
xs/src/libslic3r/GCodeTimeEstimator.cpp

@@ -2,77 +2,980 @@
 #include <boost/bind.hpp>
 #include <cmath>
 
+static const std::string AXIS_STR = "XYZE";
+static const float MMMIN_TO_MMSEC = 1.0f / 60.0f;
+static const float MILLISEC_TO_SEC = 0.001f;
+static const float INCHES_TO_MM = 25.4f;
+static const float DEFAULT_FEEDRATE = 1500.0f; // from Prusa Firmware (Marlin_main.cpp)
+static const float DEFAULT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2
+static const float DEFAULT_RETRACT_ACCELERATION = 1500.0f; // Prusa Firmware 1_75mm_MK2
+static const float DEFAULT_AXIS_MAX_FEEDRATE[] = { 500.0f, 500.0f, 12.0f, 120.0f }; // Prusa Firmware 1_75mm_MK2
+static const float DEFAULT_AXIS_MAX_ACCELERATION[] = { 9000.0f, 9000.0f, 500.0f, 10000.0f }; // Prusa Firmware 1_75mm_MK2
+static const float DEFAULT_AXIS_MAX_JERK[] = { 10.0f, 10.0f, 0.2f, 2.5f }; // from Prusa Firmware (Configuration.h)
+static const float DEFAULT_MINIMUM_FEEDRATE = 0.0f; // from Prusa Firmware (Configuration_adv.h)
+static const float DEFAULT_MINIMUM_TRAVEL_FEEDRATE = 0.0f; // from Prusa Firmware (Configuration_adv.h)
+
+static const float PREVIOUS_FEEDRATE_THRESHOLD = 0.0001f;
+
 namespace Slic3r {
 
-void
-GCodeTimeEstimator::parse(const std::string &gcode)
-{
-    GCodeReader::parse(gcode, boost::bind(&GCodeTimeEstimator::_parser, this, _1, _2));
-}
+  void GCodeTimeEstimator::Feedrates::reset()
+  {
+    feedrate = 0.0f;
+    safe_feedrate = 0.0f;
+    ::memset(axis_feedrate, 0, Num_Axis * sizeof(float));
+    ::memset(abs_axis_feedrate, 0, Num_Axis * sizeof(float));
+  }
 
-void
-GCodeTimeEstimator::parse_file(const std::string &file)
-{
-    GCodeReader::parse_file(file, boost::bind(&GCodeTimeEstimator::_parser, this, _1, _2));
-}
+  float GCodeTimeEstimator::Block::Trapezoid::acceleration_time(float acceleration) const
+  {
+    return acceleration_time_from_distance(feedrate.entry, accelerate_until, acceleration);
+  }
+
+  float GCodeTimeEstimator::Block::Trapezoid::cruise_time() const
+  {
+    return (feedrate.cruise != 0.0f) ? cruise_distance() / feedrate.cruise : 0.0f;
+  }
+
+  float GCodeTimeEstimator::Block::Trapezoid::deceleration_time(float acceleration) const
+  {
+    return acceleration_time_from_distance(feedrate.cruise, (distance - decelerate_after), -acceleration);
+  }
+
+  float GCodeTimeEstimator::Block::Trapezoid::cruise_distance() const
+  {
+    return decelerate_after - accelerate_until;
+  }
+
+  float GCodeTimeEstimator::Block::Trapezoid::acceleration_time_from_distance(float initial_feedrate, float distance, float acceleration)
+  {
+    return (acceleration != 0.0f) ? (speed_from_distance(initial_feedrate, distance, acceleration) - initial_feedrate) / acceleration : 0.0f;
+  }
+
+  float GCodeTimeEstimator::Block::Trapezoid::speed_from_distance(float initial_feedrate, float distance, float acceleration)
+  {
+    return ::sqrt(sqr(initial_feedrate) + 2.0f * acceleration * distance);
+  }
+
+  float GCodeTimeEstimator::Block::move_length() const
+  {
+    float length = ::sqrt(sqr(delta_pos[X]) + sqr(delta_pos[Y]) + sqr(delta_pos[Z]));
+    return (length > 0.0f) ? length : ::abs(delta_pos[E]);
+  }
+
+  float GCodeTimeEstimator::Block::is_extruder_only_move() const
+  {
+    return (delta_pos[X] == 0.0f) && (delta_pos[Y] == 0.0f) && (delta_pos[Z] == 0.0f) && (delta_pos[E] != 0.0f);
+  }
+
+  float GCodeTimeEstimator::Block::is_travel_move() const
+  {
+    return delta_pos[E] == 0.0f;
+  }
+
+  float GCodeTimeEstimator::Block::acceleration_time() const
+  {
+    return trapezoid.acceleration_time(acceleration);
+  }
+
+  float GCodeTimeEstimator::Block::cruise_time() const
+  {
+    return trapezoid.cruise_time();
+  }
+
+  float GCodeTimeEstimator::Block::deceleration_time() const
+  {
+    return trapezoid.deceleration_time(acceleration);
+  }
+
+  float GCodeTimeEstimator::Block::cruise_distance() const
+  {
+    return trapezoid.cruise_distance();
+  }
+
+  void GCodeTimeEstimator::Block::calculate_trapezoid()
+  {
+    float distance = move_length();
+
+    trapezoid.distance = distance;
+    trapezoid.feedrate = feedrate;
+
+    float accelerate_distance = estimate_acceleration_distance(feedrate.entry, feedrate.cruise, acceleration);
+    float decelerate_distance = estimate_acceleration_distance(feedrate.cruise, feedrate.exit, -acceleration);
+    float cruise_distance = distance - accelerate_distance - decelerate_distance;
+
+    // Not enough space to reach the nominal feedrate.
+    // This means no cruising, and we'll have to use intersection_distance() to calculate when to abort acceleration 
+    // and start braking in order to reach the exit_feedrate exactly at the end of this block.
+    if (cruise_distance < 0.0f)
+    {
+      accelerate_distance = clamp(0.0f, distance, intersection_distance(feedrate.entry, feedrate.exit, acceleration, distance));
+      cruise_distance = 0.0f;
+      trapezoid.feedrate.cruise = Trapezoid::speed_from_distance(feedrate.entry, accelerate_distance, acceleration);
+    }
+
+    trapezoid.accelerate_until = accelerate_distance;
+    trapezoid.decelerate_after = accelerate_distance + cruise_distance;
+  }
+
+  float GCodeTimeEstimator::Block::max_allowable_speed(float acceleration, float target_velocity, float distance)
+  {
+    return ::sqrt(sqr(target_velocity) - 2.0f * acceleration * distance);
+  }
+
+  float GCodeTimeEstimator::Block::estimate_acceleration_distance(float initial_rate, float target_rate, float acceleration)
+  {
+    return (acceleration == 0.0f) ? 0.0f : (sqr(target_rate) - sqr(initial_rate)) / (2.0f * acceleration);
+  }
+
+  float GCodeTimeEstimator::Block::intersection_distance(float initial_rate, float final_rate, float acceleration, float distance)
+  {
+    return (acceleration == 0.0f) ? 0.0f : (2.0f * acceleration * distance - sqr(initial_rate) + sqr(final_rate)) / (4.0f * acceleration);
+  }
+
+  GCodeTimeEstimator::GCodeTimeEstimator()
+  {
+    reset();
+    set_default();
+  }
+
+  void GCodeTimeEstimator::calculate_time_from_text(const std::string& gcode)
+  {
+    _parser.parse(gcode, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2));
+    _calculate_time();
+    reset();
+  }
+
+  void GCodeTimeEstimator::calculate_time_from_file(const std::string& file)
+  {
+    _parser.parse_file(file, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2));
+    _calculate_time();
+    reset();
+  }
+
+  void GCodeTimeEstimator::calculate_time_from_lines(const std::vector<std::string>& gcode_lines)
+  {
+    for (const std::string& line : gcode_lines)
+    {
+      _parser.parse_line(line, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2));
+    }
+    _calculate_time();
+    reset();
+  }
+
+  void GCodeTimeEstimator::add_gcode_line(const std::string& gcode_line)
+  {
+    _parser.parse_line(gcode_line, boost::bind(&GCodeTimeEstimator::_process_gcode_line, this, _1, _2));
+  }
+
+  void GCodeTimeEstimator::calculate_time()
+  {
+    _calculate_time();
+    _reset();
+  }
+
+  void GCodeTimeEstimator::set_axis_position(EAxis axis, float position)
+  {
+    _state.axis[axis].position = position;
+  }
+
+  void GCodeTimeEstimator::set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec)
+  {
+    _state.axis[axis].max_feedrate = feedrate_mm_sec;
+  }
+
+  void GCodeTimeEstimator::set_axis_max_acceleration(EAxis axis, float acceleration)
+  {
+    _state.axis[axis].max_acceleration = acceleration;
+  }
+
+  void GCodeTimeEstimator::set_axis_max_jerk(EAxis axis, float jerk)
+  {
+    _state.axis[axis].max_jerk = jerk;
+  }
+
+  float GCodeTimeEstimator::get_axis_position(EAxis axis) const
+  {
+    return _state.axis[axis].position;
+  }
+
+  float GCodeTimeEstimator::get_axis_max_feedrate(EAxis axis) const
+  {
+    return _state.axis[axis].max_feedrate;
+  }
+
+  float GCodeTimeEstimator::get_axis_max_acceleration(EAxis axis) const
+  {
+    return _state.axis[axis].max_acceleration;
+  }
+
+  float GCodeTimeEstimator::get_axis_max_jerk(EAxis axis) const
+  {
+    return _state.axis[axis].max_jerk;
+  }
+
+  void GCodeTimeEstimator::set_feedrate(float feedrate_mm_sec)
+  {
+    _state.feedrate = feedrate_mm_sec;
+  }
+
+  float GCodeTimeEstimator::get_feedrate() const
+  {
+    return _state.feedrate;
+  }
+
+  void GCodeTimeEstimator::set_acceleration(float acceleration_mm_sec2)
+  {
+    _state.acceleration = acceleration_mm_sec2;
+  }
+
+  float GCodeTimeEstimator::get_acceleration() const
+  {
+    return _state.acceleration;
+  }
+
+  void GCodeTimeEstimator::set_retract_acceleration(float acceleration_mm_sec2)
+  {
+    _state.retract_acceleration = acceleration_mm_sec2;
+  }
+
+  float GCodeTimeEstimator::get_retract_acceleration() const
+  {
+    return _state.retract_acceleration;
+  }
+
+  void GCodeTimeEstimator::set_minimum_feedrate(float feedrate_mm_sec)
+  {
+    _state.minimum_feedrate = feedrate_mm_sec;
+  }
+
+  float GCodeTimeEstimator::get_minimum_feedrate() const
+  {
+    return _state.minimum_feedrate;
+  }
+
+  void GCodeTimeEstimator::set_minimum_travel_feedrate(float feedrate_mm_sec)
+  {
+    _state.minimum_travel_feedrate = feedrate_mm_sec;
+  }
 
-void
-GCodeTimeEstimator::_parser(GCodeReader&, const GCodeReader::GCodeLine &line)
-{
-    // std::cout << "[" << this->time << "] " << line.raw << std::endl;
-    if (line.cmd == "G1") {
-        const float dist_XY = line.dist_XY();
-        const float new_F = line.new_F();
-        
-        if (dist_XY > 0) {
-            //this->time += dist_XY / new_F * 60;
-            this->time += _accelerated_move(dist_XY, new_F/60, this->acceleration);
-        } else {
-            //this->time += std::abs(line.dist_E()) / new_F * 60;
-            this->time += _accelerated_move(std::abs(line.dist_E()), new_F/60, this->acceleration);
+  float GCodeTimeEstimator::get_minimum_travel_feedrate() const
+  {
+    return _state.minimum_travel_feedrate;
+  }
+
+  void GCodeTimeEstimator::set_dialect(GCodeTimeEstimator::EDialect dialect)
+  {
+    _state.dialect = dialect;
+  }
+
+  GCodeTimeEstimator::EDialect GCodeTimeEstimator::get_dialect() const
+  {
+    return _state.dialect;
+  }
+
+  void GCodeTimeEstimator::set_units(GCodeTimeEstimator::EUnits units)
+  {
+    _state.units = units;
+  }
+
+  GCodeTimeEstimator::EUnits GCodeTimeEstimator::get_units() const
+  {
+    return _state.units;
+  }
+
+  void GCodeTimeEstimator::set_positioning_xyz_type(GCodeTimeEstimator::EPositioningType type)
+  {
+    _state.positioning_xyz_type = type;
+  }
+
+  GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_positioning_xyz_type() const
+  {
+    return _state.positioning_xyz_type;
+  }
+
+  void GCodeTimeEstimator::set_positioning_e_type(GCodeTimeEstimator::EPositioningType type)
+  {
+    _state.positioning_e_type = type;
+  }
+
+  GCodeTimeEstimator::EPositioningType GCodeTimeEstimator::get_positioning_e_type() const
+  {
+    return _state.positioning_e_type;
+  }
+
+  void GCodeTimeEstimator::add_additional_time(float timeSec)
+  {
+    _state.additional_time += timeSec;
+  }
+
+  void GCodeTimeEstimator::set_additional_time(float timeSec)
+  {
+    _state.additional_time = timeSec;
+  }
+
+  float GCodeTimeEstimator::get_additional_time() const
+  {
+    return _state.additional_time;
+  }
+
+  void GCodeTimeEstimator::set_default()
+  {
+    set_units(Millimeters);
+    set_dialect(Unknown);
+    set_positioning_xyz_type(Absolute);
+    set_positioning_e_type(Relative);
+
+    set_feedrate(DEFAULT_FEEDRATE);
+    set_acceleration(DEFAULT_ACCELERATION);
+    set_retract_acceleration(DEFAULT_RETRACT_ACCELERATION);
+    set_minimum_feedrate(DEFAULT_MINIMUM_FEEDRATE);
+    set_minimum_travel_feedrate(DEFAULT_MINIMUM_TRAVEL_FEEDRATE);
+
+    for (unsigned char a = X; a < Num_Axis; ++a)
+    {
+      EAxis axis = (EAxis)a;
+      set_axis_max_feedrate(axis, DEFAULT_AXIS_MAX_FEEDRATE[a]);
+      set_axis_max_acceleration(axis, DEFAULT_AXIS_MAX_ACCELERATION[a]);
+      set_axis_max_jerk(axis, DEFAULT_AXIS_MAX_JERK[a]);
+    }
+  }
+
+  void GCodeTimeEstimator::reset()
+  {
+    _blocks.clear();
+    _reset();
+  }
+
+  float GCodeTimeEstimator::get_time() const
+  {
+    return _time;
+  }
+
+  std::string GCodeTimeEstimator::get_time_hms() const
+  {
+    float timeinsecs = get_time();
+    int hours = (int)(timeinsecs / 3600.0f);
+    timeinsecs -= (float)hours * 3600.0f;
+    int minutes = (int)(timeinsecs / 60.0f);
+    timeinsecs -= (float)minutes * 60.0f;
+
+    char buffer[64];
+    if (hours > 0)
+      ::sprintf(buffer, "%dh %dm %ds", hours, minutes, (int)timeinsecs);
+    else if (minutes > 0)
+      ::sprintf(buffer, "%dm %ds", minutes, (int)timeinsecs);
+    else
+      ::sprintf(buffer, "%ds", (int)timeinsecs);
+
+    return buffer;
+  }
+
+  void GCodeTimeEstimator::_reset()
+  {
+    _curr.reset();
+    _prev.reset();
+
+    set_axis_position(X, 0.0f);
+    set_axis_position(Y, 0.0f);
+    set_axis_position(Z, 0.0f);
+
+    set_additional_time(0.0f);
+  }
+
+  void GCodeTimeEstimator::_calculate_time()
+  {
+    _forward_pass();
+    _reverse_pass();
+    _recalculate_trapezoids();
+
+    _time = get_additional_time();
+
+    for (const Block& block : _blocks)
+    {
+      _time += block.acceleration_time();
+      _time += block.cruise_time();
+      _time += block.deceleration_time();
+    }
+  }
+
+  void GCodeTimeEstimator::_process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line)
+  {
+    if (line.cmd.length() > 1)
+    {
+      switch (::toupper(line.cmd[0]))
+      {
+      case 'G':
+        {
+          switch (::atoi(&line.cmd[1]))
+          {
+          case 1: // Move
+            {
+              _processG1(line);
+              break;
+            }
+          case 4: // Dwell
+            {
+              _processG4(line);
+              break;
+            }
+          case 20: // Set Units to Inches
+            {
+              _processG20(line);
+              break;
+            }
+          case 21: // Set Units to Millimeters
+            {
+              _processG21(line);
+              break;
+            }
+          case 28: // Move to Origin (Home)
+            {
+              _processG28(line);
+              break;
+            }
+          case 90: // Set to Absolute Positioning
+            {
+              _processG90(line);
+              break;
+            }
+          case 91: // Set to Relative Positioning
+            {
+              _processG91(line);
+              break;
+            }
+          case 92: // Set Position
+            {
+              _processG92(line);
+              break;
+            }
+          }
+
+          break;
         }
-        //this->time += std::abs(line.dist_Z()) / new_F * 60;
-        this->time += _accelerated_move(std::abs(line.dist_Z()), new_F/60, this->acceleration);
-    } else if (line.cmd == "M204" && line.has('S')) {
-        this->acceleration = line.get_float('S');
-    } else if (line.cmd == "G4") { // swell
-        if (line.has('S')) {
-            this->time += line.get_float('S');
-        } else if (line.has('P')) {
-            this->time += line.get_float('P')/1000;
+      case 'M':
+        {
+          switch (::atoi(&line.cmd[1]))
+          {
+          case 82: // Set extruder to absolute mode
+            {
+              _processM82(line);
+              break;
+            }
+          case 83: // Set extruder to relative mode
+            {
+              _processM83(line);
+              break;
+            }
+          case 109: // Set Extruder Temperature and Wait
+            {
+              _processM109(line);
+              break;
+            }
+          case 201: // Set max printing acceleration
+            {
+              _processM201(line);
+              break;
+            }
+          case 203: // Set maximum feedrate
+            {
+              _processM203(line);
+              break;
+            }
+          case 204: // Set default acceleration
+            {
+              _processM204(line);
+              break;
+            }
+          case 205: // Advanced settings
+            {
+              _processM205(line);
+              break;
+            }
+          case 566: // Set allowable instantaneous speed change
+            {
+              _processM566(line);
+              break;
+            }
+          }
+
+          break;
         }
+      }
     }
-}
+  }
 
-// Wildly optimistic acceleration "bell" curve modeling.
-// Returns an estimate of how long the move with a given accel
-// takes in seconds.
-// It is assumed that the movement is smooth and uniform.
-float
-GCodeTimeEstimator::_accelerated_move(double length, double v, double acceleration) 
-{
-    // for half of the move, there are 2 zones, where the speed is increasing/decreasing and 
-    // where the speed is constant.
-    // Since the slowdown is assumed to be uniform, calculate the average velocity for half of the 
-    // expected displacement.
-    // final velocity v = a*t => a * (dx / 0.5v) => v^2 = 2*a*dx
-    // v_avg = 0.5v => 2*v_avg = v
-    // d_x = v_avg*t => t = d_x / v_avg
-    acceleration = (acceleration == 0.0 ? 4000.0 : acceleration); // Set a default accel to use for print time in case it's 0 somehow.
-    auto half_length = length / 2.0;
-    auto t_init = v / acceleration; // time to final velocity
-    auto dx_init = (0.5*v*t_init); // Initial displacement for the time to get to final velocity
-    auto t = 0.0;
-    if (half_length >= dx_init) {
-        half_length -= (0.5*v*t_init);
-        t += t_init;
-        t += (half_length / v); // rest of time is at constant speed.
-    } else {
-        // If too much displacement for the expected final velocity, we don't hit the max, so reduce 
-        // the average velocity to fit the displacement we actually are looking for.
-        t += std::sqrt(std::abs(length) * 2.0 * acceleration) / acceleration;
-    }
-    return 2.0*t; // cut in half before, so double to get full time spent.
-}
+  // Returns the new absolute position on the given axis in dependence of the given parameters
+  float axis_absolute_position_from_G1_line(GCodeTimeEstimator::EAxis axis, const GCodeReader::GCodeLine& lineG1, GCodeTimeEstimator::EUnits units, GCodeTimeEstimator::EPositioningType type, float current_absolute_position)
+  {
+    float lengthsScaleFactor = (units == GCodeTimeEstimator::Inches) ? INCHES_TO_MM : 1.0f;
+    if (lineG1.has(AXIS_STR[axis]))
+    {
+      float ret = lineG1.get_float(AXIS_STR[axis]) * lengthsScaleFactor;
+      return (type == GCodeTimeEstimator::Absolute) ? ret : current_absolute_position + ret;
+    }
+    else
+      return current_absolute_position;
+  }
+
+  void GCodeTimeEstimator::_processG1(const GCodeReader::GCodeLine& line)
+  {
+    // updates axes positions from line
+    EUnits units = get_units();
+    float new_pos[Num_Axis];
+    for (unsigned char a = X; a < Num_Axis; ++a)
+    {
+      new_pos[a] = axis_absolute_position_from_G1_line((EAxis)a, line, units, (a == E) ? get_positioning_e_type() : get_positioning_xyz_type(), get_axis_position((EAxis)a));
+    }
+
+    // updates feedrate from line, if present
+    if (line.has('F'))
+      set_feedrate(std::max(line.get_float('F') * MMMIN_TO_MMSEC, get_minimum_feedrate()));
+
+    // fills block data
+    Block block;
+
+    // calculates block movement deltas
+    float max_abs_delta = 0.0f;
+    for (unsigned char a = X; a < Num_Axis; ++a)
+    {
+      block.delta_pos[a] = new_pos[a] - get_axis_position((EAxis)a);
+      max_abs_delta = std::max(max_abs_delta, ::abs(block.delta_pos[a]));
+    }
+
+    // is it a move ?
+    if (max_abs_delta == 0.0f)
+      return;
+
+    // calculates block feedrate
+    _curr.feedrate = std::max(get_feedrate(), block.is_travel_move() ? get_minimum_travel_feedrate() : get_minimum_feedrate());
+
+    float distance = block.move_length();
+    float invDistance = 1.0f / distance;
+
+    float min_feedrate_factor = 1.0f;
+    for (unsigned char a = X; a < Num_Axis; ++a)
+    {
+      _curr.axis_feedrate[a] = _curr.feedrate * block.delta_pos[a] * invDistance;
+      _curr.abs_axis_feedrate[a] = ::abs(_curr.axis_feedrate[a]);
+      if (_curr.abs_axis_feedrate[a] > 0.0f)
+        min_feedrate_factor = std::min(min_feedrate_factor, get_axis_max_feedrate((EAxis)a) / _curr.abs_axis_feedrate[a]);
+    }
+    
+    block.feedrate.cruise = min_feedrate_factor * _curr.feedrate;
+
+    for (unsigned char a = X; a < Num_Axis; ++a)
+    {
+      _curr.axis_feedrate[a] *= min_feedrate_factor;
+      _curr.abs_axis_feedrate[a] *= min_feedrate_factor;
+    }
+
+    // calculates block acceleration
+    float acceleration = block.is_extruder_only_move() ? get_retract_acceleration() : get_acceleration();
+
+    for (unsigned char a = X; a < Num_Axis; ++a)
+    {
+      float axis_max_acceleration = get_axis_max_acceleration((EAxis)a);
+      if (acceleration * ::abs(block.delta_pos[a]) * invDistance > axis_max_acceleration)
+        acceleration = axis_max_acceleration;
+    }
 
+    block.acceleration = acceleration;
+
+    // calculates block exit feedrate
+    _curr.safe_feedrate = block.feedrate.cruise;
+
+    for (unsigned char a = X; a < Num_Axis; ++a)
+    {
+      float axis_max_jerk = get_axis_max_jerk((EAxis)a);
+      if (_curr.abs_axis_feedrate[a] > axis_max_jerk)
+        _curr.safe_feedrate = std::min(_curr.safe_feedrate, axis_max_jerk);
+    }
+
+    block.feedrate.exit = _curr.safe_feedrate;
+
+    // calculates block entry feedrate
+    float vmax_junction = _curr.safe_feedrate;
+    if (!_blocks.empty() && (_prev.feedrate > PREVIOUS_FEEDRATE_THRESHOLD))
+    {
+      bool prev_speed_larger = _prev.feedrate > block.feedrate.cruise;
+      float smaller_speed_factor = prev_speed_larger ? (block.feedrate.cruise / _prev.feedrate) : (_prev.feedrate / block.feedrate.cruise);
+      // Pick the smaller of the nominal speeds. Higher speed shall not be achieved at the junction during coasting.
+      vmax_junction = prev_speed_larger ? block.feedrate.cruise : _prev.feedrate;
+
+      float v_factor = 1.0f;
+      bool limited = false;
+
+      for (unsigned char a = X; a < Num_Axis; ++a)
+      {
+        // Limit an axis. We have to differentiate coasting from the reversal of an axis movement, or a full stop.
+        float v_exit = _prev.axis_feedrate[a];
+        float v_entry = _curr.axis_feedrate[a];
+
+        if (prev_speed_larger)
+          v_exit *= smaller_speed_factor;
+
+        if (limited)
+        {
+          v_exit *= v_factor;
+          v_entry *= v_factor;
+        }
+
+        // Calculate the jerk depending on whether the axis is coasting in the same direction or reversing a direction.
+        float jerk =
+          (v_exit > v_entry) ?
+          (((v_entry > 0.0f) || (v_exit < 0.0f)) ?
+          // coasting
+          (v_exit - v_entry) :
+          // axis reversal
+          std::max(v_exit, -v_entry)) :
+          // v_exit <= v_entry
+          (((v_entry < 0.0f) || (v_exit > 0.0f)) ?
+          // coasting
+          (v_entry - v_exit) :
+          // axis reversal
+          std::max(-v_exit, v_entry));
+
+        float axis_max_jerk = get_axis_max_jerk((EAxis)a);
+        if (jerk > axis_max_jerk)
+        {
+          v_factor *= axis_max_jerk / jerk;
+          limited = true;
+        }
+      }
+
+      if (limited)
+        vmax_junction *= v_factor;
+
+      // Now the transition velocity is known, which maximizes the shared exit / entry velocity while
+      // respecting the jerk factors, it may be possible, that applying separate safe exit / entry velocities will achieve faster prints.
+      float vmax_junction_threshold = vmax_junction * 0.99f;
+
+      // Not coasting. The machine will stop and start the movements anyway, better to start the segment from start.
+      if ((_prev.safe_feedrate > vmax_junction_threshold) && (_curr.safe_feedrate > vmax_junction_threshold))
+        vmax_junction = _curr.safe_feedrate;
+    }
+
+    float v_allowable = Block::max_allowable_speed(-acceleration, _curr.safe_feedrate, distance);
+    block.feedrate.entry = std::min(vmax_junction, v_allowable);
+
+    block.max_entry_speed = vmax_junction;
+    block.flags.nominal_length = (block.feedrate.cruise <= v_allowable);
+    block.flags.recalculate = true;
+    block.safe_feedrate = _curr.safe_feedrate;
+
+    // calculates block trapezoid
+    block.calculate_trapezoid();
+
+    // updates previous
+    _prev = _curr;
+
+    // updates axis positions
+    for (unsigned char a = X; a < Num_Axis; ++a)
+    {
+      set_axis_position((EAxis)a, new_pos[a]);
+    }
+
+    // adds block to blocks list
+    _blocks.push_back(block);
+  }
+
+  void GCodeTimeEstimator::_processG4(const GCodeReader::GCodeLine& line)
+  {
+    EDialect dialect = get_dialect();
+
+    if (line.has('P'))
+      add_additional_time(line.get_float('P') * MILLISEC_TO_SEC);
+
+    // see: http://reprap.org/wiki/G-code#G4:_Dwell
+    if ((dialect == Repetier) ||
+        (dialect == Marlin) ||
+        (dialect == Smoothieware) ||
+        (dialect == RepRapFirmware))
+    {
+      if (line.has('S'))
+        add_additional_time(line.get_float('S'));
+    }
+  }
+
+  void GCodeTimeEstimator::_processG20(const GCodeReader::GCodeLine& line)
+  {
+    set_units(Inches);
+  }
+
+  void GCodeTimeEstimator::_processG21(const GCodeReader::GCodeLine& line)
+  {
+    set_units(Millimeters);
+  }
+
+  void GCodeTimeEstimator::_processG28(const GCodeReader::GCodeLine& line)
+  {
+    // TODO
+  }
+
+  void GCodeTimeEstimator::_processG90(const GCodeReader::GCodeLine& line)
+  {
+    set_positioning_xyz_type(Absolute);
+  }
+
+  void GCodeTimeEstimator::_processG91(const GCodeReader::GCodeLine& line)
+  {
+    // TODO: THERE ARE DIALECT VARIANTS
+
+    set_positioning_xyz_type(Relative);
+  }
+
+  void GCodeTimeEstimator::_processM82(const GCodeReader::GCodeLine& line)
+  {
+    set_positioning_e_type(Absolute);
+  }
+
+  void GCodeTimeEstimator::_processM83(const GCodeReader::GCodeLine& line)
+  {
+    set_positioning_e_type(Relative);
+  }
+
+  void GCodeTimeEstimator::_processG92(const GCodeReader::GCodeLine& line)
+  {
+    float lengthsScaleFactor = (get_units() == Inches) ? INCHES_TO_MM : 1.0f;
+    bool anyFound = false;
+
+    if (line.has('X'))
+    {
+      set_axis_position(X, line.get_float('X') * lengthsScaleFactor);
+      anyFound = true;
+    }
+
+    if (line.has('Y'))
+    {
+      set_axis_position(Y, line.get_float('Y') * lengthsScaleFactor);
+      anyFound = true;
+    }
+
+    if (line.has('Z'))
+    {
+      set_axis_position(Z, line.get_float('Z') * lengthsScaleFactor);
+      anyFound = true;
+    }
+
+    if (line.has('E'))
+    {
+      set_axis_position(E, line.get_float('E') * lengthsScaleFactor);
+      anyFound = true;
+    }
+
+    if (!anyFound)
+    {
+      for (unsigned char a = X; a < Num_Axis; ++a)
+      {
+        set_axis_position((EAxis)a, 0.0f);
+      }
+    }
+  }
+
+  void GCodeTimeEstimator::_processM109(const GCodeReader::GCodeLine& line)
+  {
+    // TODO
+  }
+
+  void GCodeTimeEstimator::_processM201(const GCodeReader::GCodeLine& line)
+  {
+    EDialect dialect = get_dialect();
+
+    // see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration
+    float factor = ((dialect != RepRapFirmware) && (get_units() == GCodeTimeEstimator::Inches)) ? INCHES_TO_MM : 1.0f;
+
+    if (line.has('X'))
+      set_axis_max_acceleration(X, line.get_float('X') * factor);
+
+    if (line.has('Y'))
+      set_axis_max_acceleration(Y, line.get_float('Y') * factor);
+
+    if (line.has('Z'))
+      set_axis_max_acceleration(Z, line.get_float('Z') * factor);
+
+    if (line.has('E'))
+      set_axis_max_acceleration(E, line.get_float('E') * factor);
+  }
+
+  void GCodeTimeEstimator::_processM203(const GCodeReader::GCodeLine& line)
+  {
+    EDialect dialect = get_dialect();
+
+    // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate
+    if (dialect == Repetier)
+      return;
+
+    // see http://reprap.org/wiki/G-code#M203:_Set_maximum_feedrate
+    float factor = (dialect == Marlin) ? 1.0f : MMMIN_TO_MMSEC;
+
+    if (line.has('X'))
+      set_axis_max_feedrate(X, line.get_float('X') * factor);
+
+    if (line.has('Y'))
+      set_axis_max_feedrate(Y, line.get_float('Y') * factor);
+
+    if (line.has('Z'))
+      set_axis_max_feedrate(Z, line.get_float('Z') * factor);
+
+    if (line.has('E'))
+      set_axis_max_feedrate(E, line.get_float('E') * factor);
+  }
+
+  void GCodeTimeEstimator::_processM204(const GCodeReader::GCodeLine& line)
+  {
+    if (line.has('S'))
+      set_acceleration(line.get_float('S'));
+
+    if (line.has('T'))
+      set_retract_acceleration(line.get_float('T'));
+  }
+
+  void GCodeTimeEstimator::_processM205(const GCodeReader::GCodeLine& line)
+  {
+    if (line.has('X'))
+    {
+      float max_jerk = line.get_float('X');
+      set_axis_max_jerk(X, max_jerk);
+      set_axis_max_jerk(Y, max_jerk);
+    }
+
+    if (line.has('Y'))
+      set_axis_max_jerk(Y, line.get_float('Y'));
+
+    if (line.has('Z'))
+      set_axis_max_jerk(Z, line.get_float('Z'));
+
+    if (line.has('E'))
+      set_axis_max_jerk(E, line.get_float('E'));
+
+    if (line.has('S'))
+      set_minimum_feedrate(line.get_float('S'));
+
+    if (line.has('T'))
+      set_minimum_travel_feedrate(line.get_float('T'));
+  }
+
+  void GCodeTimeEstimator::_processM566(const GCodeReader::GCodeLine& line)
+  {
+    if (line.has('X'))
+      set_axis_max_jerk(X, line.get_float('X') * MMMIN_TO_MMSEC);
+
+    if (line.has('Y'))
+      set_axis_max_jerk(Y, line.get_float('Y') * MMMIN_TO_MMSEC);
+
+    if (line.has('Z'))
+      set_axis_max_jerk(Z, line.get_float('Z') * MMMIN_TO_MMSEC);
+
+    if (line.has('E'))
+      set_axis_max_jerk(E, line.get_float('E') * MMMIN_TO_MMSEC);
+  }
+
+  void GCodeTimeEstimator::_forward_pass()
+  {
+    Block* block[2] = { nullptr, nullptr };
+
+    for (Block& b : _blocks)
+    {
+      block[0] = block[1];
+      block[1] = &b;
+      _planner_forward_pass_kernel(block[0], block[1]);
+    }
+
+    _planner_forward_pass_kernel(block[1], nullptr);
+  }
+
+  void GCodeTimeEstimator::_reverse_pass()
+  {
+    Block* block[2] = { nullptr, nullptr };
+
+    for (int i = (int)_blocks.size() - 1; i >= 0; --i)
+    {
+      block[1] = block[0];
+      block[0] = &_blocks[i];
+      _planner_reverse_pass_kernel(block[0], block[1]);
+    }
+  }
+
+  void GCodeTimeEstimator::_planner_forward_pass_kernel(Block* prev, Block* curr)
+  {
+    if (prev == nullptr)
+      return;
+
+    // If the previous block is an acceleration block, but it is not long enough to complete the
+    // full speed change within the block, we need to adjust the entry speed accordingly. Entry
+    // speeds have already been reset, maximized, and reverse planned by reverse planner.
+    // If nominal length is true, max junction speed is guaranteed to be reached. No need to recheck.
+    if (!prev->flags.nominal_length)
+    {
+      if (prev->feedrate.entry < curr->feedrate.entry)
+      {
+        float entry_speed = std::min(curr->feedrate.entry, Block::max_allowable_speed(-prev->acceleration, prev->feedrate.entry, prev->move_length()));
+
+        // Check for junction speed change
+        if (curr->feedrate.entry != entry_speed)
+        {
+          curr->feedrate.entry = entry_speed;
+          curr->flags.recalculate = true;
+        }
+      }
+    }
+  }
+
+  void GCodeTimeEstimator::_planner_reverse_pass_kernel(Block* curr, Block* next)
+  {
+    if ((curr == nullptr) || (next == nullptr))
+      return;
+
+    // If entry speed is already at the maximum entry speed, no need to recheck. Block is cruising.
+    // If not, block in state of acceleration or deceleration. Reset entry speed to maximum and
+    // check for maximum allowable speed reductions to ensure maximum possible planned speed.
+    if (curr->feedrate.entry != curr->max_entry_speed)
+    {
+      // If nominal length true, max junction speed is guaranteed to be reached. Only compute
+      // for max allowable speed if block is decelerating and nominal length is false.
+      if (!curr->flags.nominal_length && (curr->max_entry_speed > next->feedrate.entry))
+        curr->feedrate.entry = std::min(curr->max_entry_speed, Block::max_allowable_speed(-curr->acceleration, next->feedrate.entry, curr->move_length()));
+      else
+        curr->feedrate.entry = curr->max_entry_speed;
+
+      curr->flags.recalculate = true;
+    }
+  }
+
+  void GCodeTimeEstimator::_recalculate_trapezoids()
+  {
+    Block* curr = nullptr;
+    Block* next = nullptr;
+
+    for (Block& b : _blocks)
+    {
+      curr = next;
+      next = &b;
+
+      if (curr != nullptr)
+      {
+        // Recalculate if current block entry or exit junction speed has changed.
+        if (curr->flags.recalculate || next->flags.recalculate)
+        {
+          // NOTE: Entry and exit factors always > 0 by all previous logic operations.
+          Block block = *curr;
+          block.feedrate.exit = next->feedrate.entry;
+          block.calculate_trapezoid();
+          curr->trapezoid = block.trapezoid;
+          curr->flags.recalculate = false; // Reset current only to ensure next trapezoid is computed
+        }
+      }
+    }
+
+    // Last/newest block in buffer. Always recalculated.
+    if (next != nullptr)
+    {
+      Block block = *next;
+      block.feedrate.exit = next->safe_feedrate;
+      block.calculate_trapezoid();
+      next->trapezoid = block.trapezoid;
+      next->flags.recalculate = false;
+    }
+  }
 }

+ 301 - 12
xs/src/libslic3r/GCodeTimeEstimator.hpp

@@ -6,18 +6,307 @@
 
 namespace Slic3r {
 
-class GCodeTimeEstimator : public GCodeReader {
-    public:
-    float time = 0;  // in seconds
-    
-    void parse(const std::string &gcode);
-    void parse_file(const std::string &file);
-    
-    protected:
-    float acceleration = 9000;
-    void _parser(GCodeReader&, const GCodeReader::GCodeLine &line);
-    static float _accelerated_move(double length, double v, double acceleration);
-};
+  class GCodeTimeEstimator
+  {
+  public:
+    enum EUnits : unsigned char
+    {
+      Millimeters,
+      Inches
+    };
+
+    enum EAxis : unsigned char
+    {
+      X,
+      Y,
+      Z,
+      E,
+      Num_Axis
+    };
+
+    enum EDialect : unsigned char
+    {
+      Unknown,
+      Marlin,
+      Repetier,
+      Smoothieware,
+      RepRapFirmware,
+      Teacup,
+      Num_Dialects
+    };
+
+    enum EPositioningType : unsigned char
+    {
+      Absolute,
+      Relative
+    };
+
+  private:
+    struct Axis
+    {
+      float position;         // mm
+      float max_feedrate;     // mm/s
+      float max_acceleration; // mm/s^2
+      float max_jerk;         // mm/s
+    };
+
+    struct Feedrates
+    {
+      float feedrate;                    // mm/s
+      float axis_feedrate[Num_Axis];     // mm/s
+      float abs_axis_feedrate[Num_Axis]; // mm/s
+      float safe_feedrate;               // mm/s
+
+      void reset();
+    };
+
+    struct State
+    {
+      EDialect dialect;
+      EUnits units;
+      EPositioningType positioning_xyz_type;
+      EPositioningType positioning_e_type;
+      Axis axis[Num_Axis];
+      float feedrate;                     // mm/s
+      float acceleration;                 // mm/s^2
+      float retract_acceleration;         // mm/s^2
+      float additional_time;              // s
+      float minimum_feedrate;             // mm/s
+      float minimum_travel_feedrate;      // mm/s
+    };
+
+  public:
+    struct Block
+    {
+      struct FeedrateProfile
+      {
+        float entry;  // mm/s
+        float cruise; // mm/s
+        float exit;   // mm/s
+      };
+
+      struct Trapezoid
+      {
+        float distance;         // mm
+        float accelerate_until; // mm
+        float decelerate_after; // mm
+        FeedrateProfile feedrate;
+
+        float acceleration_time(float acceleration) const;
+        float cruise_time() const;
+        float deceleration_time(float acceleration) const;
+        float cruise_distance() const;
+
+        // This function gives the time needed to accelerate from an initial speed to reach a final distance.
+        static float acceleration_time_from_distance(float initial_feedrate, float distance, float acceleration);
+
+        // This function gives the final speed while accelerating at the given constant acceleration from the given initial speed along the given distance.
+        static float speed_from_distance(float initial_feedrate, float distance, float acceleration);
+      };
+
+      struct Flags
+      {
+        bool recalculate;
+        bool nominal_length;
+      };
+
+      Flags flags;
+
+      float delta_pos[Num_Axis]; // mm
+      float acceleration;        // mm/s^2
+      float max_entry_speed;     // mm/s
+      float safe_feedrate;       // mm/s
+
+      FeedrateProfile feedrate;
+      Trapezoid trapezoid;
+
+      // Returns the length of the move covered by this block, in mm
+      float move_length() const;
+
+      // Returns true if this block is a retract/unretract move only
+      float is_extruder_only_move() const;
+
+      // Returns true if this block is a move with no extrusion
+      float is_travel_move() const;
+
+      // Returns the time spent accelerating toward cruise speed, in seconds
+      float acceleration_time() const;
+
+      // Returns the time spent at cruise speed, in seconds
+      float cruise_time() const;
+
+      // Returns the time spent decelerating from cruise speed, in seconds
+      float deceleration_time() const;
+
+      // Returns the distance covered at cruise speed, in mm
+      float cruise_distance() const;
+
+      // Calculates this block's trapezoid
+      void calculate_trapezoid();
+
+      // Calculates the maximum allowable speed at this point when you must be able to reach target_velocity using the 
+      // acceleration within the allotted distance.
+      static float max_allowable_speed(float acceleration, float target_velocity, float distance);
+
+      // Calculates the distance (not time) it takes to accelerate from initial_rate to target_rate using the given acceleration:
+      static float estimate_acceleration_distance(float initial_rate, float target_rate, float acceleration);
+
+      // This function gives you the point at which you must start braking (at the rate of -acceleration) if 
+      // you started at speed initial_rate and accelerated until this point and want to end at the final_rate after
+      // a total travel of distance. This can be used to compute the intersection point between acceleration and
+      // deceleration in the cases where the trapezoid has no plateau (i.e. never reaches maximum speed)
+      static float intersection_distance(float initial_rate, float final_rate, float acceleration, float distance);
+    };
+
+    typedef std::vector<Block> BlocksList;
+
+  private:
+    GCodeReader _parser;
+    State _state;
+    Feedrates _curr;
+    Feedrates _prev;
+    BlocksList _blocks;
+    float _time; // s
+
+  public:
+    GCodeTimeEstimator();
+
+    // Calculates the time estimate from the given gcode in string format
+    void calculate_time_from_text(const std::string& gcode);
+
+    // Calculates the time estimate from the gcode contained in the file with the given filename
+    void calculate_time_from_file(const std::string& file);
+
+    // Calculates the time estimate from the gcode contained in given list of gcode lines
+    void calculate_time_from_lines(const std::vector<std::string>& gcode_lines);
+
+    // Adds the given gcode line
+    void add_gcode_line(const std::string& gcode_line);
+
+    // Calculates the time estimate from the gcode lines added using add_gcode_line()
+    void calculate_time();
+
+    // Set current position on the given axis with the given value
+    void set_axis_position(EAxis axis, float position);
+
+    void set_axis_max_feedrate(EAxis axis, float feedrate_mm_sec);
+    void set_axis_max_acceleration(EAxis axis, float acceleration);
+    void set_axis_max_jerk(EAxis axis, float jerk);
+
+    // Returns current position on the given axis
+    float get_axis_position(EAxis axis) const;
+
+    float get_axis_max_feedrate(EAxis axis) const;
+    float get_axis_max_acceleration(EAxis axis) const;
+    float get_axis_max_jerk(EAxis axis) const;
+
+    void set_feedrate(float feedrate_mm_sec);
+    float get_feedrate() const;
+
+    void set_acceleration(float acceleration_mm_sec2);
+    float get_acceleration() const;
+
+    void set_retract_acceleration(float acceleration_mm_sec2);
+    float get_retract_acceleration() const;
+
+    void set_minimum_feedrate(float feedrate_mm_sec);
+    float get_minimum_feedrate() const;
+
+    void set_minimum_travel_feedrate(float feedrate_mm_sec);
+    float get_minimum_travel_feedrate() const;
+
+    void set_dialect(EDialect dialect);
+    EDialect get_dialect() const;
+
+    void set_units(EUnits units);
+    EUnits get_units() const;
+
+    void set_positioning_xyz_type(EPositioningType type);
+    EPositioningType get_positioning_xyz_type() const;
+
+    void set_positioning_e_type(EPositioningType type);
+    EPositioningType get_positioning_e_type() const;
+
+    void add_additional_time(float timeSec);
+    void set_additional_time(float timeSec);
+    float get_additional_time() const;
+
+    void set_default();
+
+    // Call this method before to start adding lines using add_gcode_line() when reusing an instance of GCodeTimeEstimator
+    void reset();
+
+    // Returns the estimated time, in seconds
+    float get_time() const;
+
+    // Returns the estimated time, in format HHh MMm SSs
+    std::string get_time_hms() const;
+
+  private:
+    void _reset();
+
+    // Calculates the time estimate
+    void _calculate_time();
+
+    // Processes the given gcode line
+    void _process_gcode_line(GCodeReader&, const GCodeReader::GCodeLine& line);
+
+    // Move
+    void _processG1(const GCodeReader::GCodeLine& line);
+
+    // Dwell
+    void _processG4(const GCodeReader::GCodeLine& line);
+
+    // Set Units to Inches
+    void _processG20(const GCodeReader::GCodeLine& line);
+
+    // Set Units to Millimeters
+    void _processG21(const GCodeReader::GCodeLine& line);
+
+    // Move to Origin (Home)
+    void _processG28(const GCodeReader::GCodeLine& line);
+
+    // Set to Absolute Positioning
+    void _processG90(const GCodeReader::GCodeLine& line);
+
+    // Set to Relative Positioning
+    void _processG91(const GCodeReader::GCodeLine& line);
+
+    // Set Position
+    void _processG92(const GCodeReader::GCodeLine& line);
+
+    // Set extruder to absolute mode
+    void _processM82(const GCodeReader::GCodeLine& line);
+
+    // Set extruder to relative mode
+    void _processM83(const GCodeReader::GCodeLine& line);
+
+    // Set Extruder Temperature and Wait
+    void _processM109(const GCodeReader::GCodeLine& line);
+
+    // Set max printing acceleration
+    void _processM201(const GCodeReader::GCodeLine& line);
+
+    // Set maximum feedrate
+    void _processM203(const GCodeReader::GCodeLine& line);
+
+    // Set default acceleration
+    void _processM204(const GCodeReader::GCodeLine& line);
+
+    // Advanced settings
+    void _processM205(const GCodeReader::GCodeLine& line);
+
+    // Set allowable instantaneous speed change
+    void _processM566(const GCodeReader::GCodeLine& line);
+
+    void _forward_pass();
+    void _reverse_pass();
+
+    void _planner_forward_pass_kernel(Block* prev, Block* curr);
+    void _planner_reverse_pass_kernel(Block* curr, Block* next);
+
+    void _recalculate_trapezoids();
+  };
 
 } /* namespace Slic3r */
 

+ 2 - 1
xs/src/libslic3r/Print.hpp

@@ -233,8 +233,9 @@ public:
     PrintRegionPtrs regions;
     PlaceholderParser placeholder_parser;
     // TODO: status_cb
+    std::string                     estimated_print_time;
     double                          total_used_filament, total_extruded_volume, total_cost, total_weight;
-    std::map<size_t,float>          filament_stats;
+    std::map<size_t, float>         filament_stats;
     PrintState<PrintStep, psCount>  state;
 
     // ordered collections of extrusion paths to build skirt loops and brim

+ 3 - 2
xs/xsp/Print.xsp

@@ -152,6 +152,8 @@ _constant()
         %code%{ RETVAL = &THIS->skirt; %};
     Ref<ExtrusionEntityCollection> brim()
         %code%{ RETVAL = &THIS->brim; %};
+    std::string estimated_print_time()
+        %code%{ RETVAL = THIS->estimated_print_time; %};
 
     PrintObjectPtrs* objects()
         %code%{ RETVAL = &THIS->objects; %};
@@ -280,7 +282,6 @@ Print::total_cost(...)
         }
         RETVAL = THIS->total_cost;
     OUTPUT:
-        RETVAL
-
+        RETVAL        
 %}
 };