diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 85e98471ec..14f4a657a6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -58,6 +58,7 @@ add_library(libslic3r STATIC ${LIBDIR}/libslic3r/GCode/SpiralVase.cpp ${LIBDIR}/libslic3r/GCodeReader.cpp ${LIBDIR}/libslic3r/GCodeSender.cpp + ${LIBDIR}/libslic3r/GCodeTimeEstimator.cpp ${LIBDIR}/libslic3r/GCodeWriter.cpp ${LIBDIR}/libslic3r/Geometry.cpp ${LIBDIR}/libslic3r/IO.cpp diff --git a/utils/estimate-gcode-time.pl b/utils/estimate-gcode-time.pl new file mode 100755 index 0000000000..43c558b687 --- /dev/null +++ b/utils/estimate-gcode-time.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; + use local::lib "$FindBin::Bin/../local-lib"; +} + +use Slic3r; + +die "Usage: estimate-gcode-time.pl FILE\n" + if @ARGV != 1; + +my $estimator = Slic3r::GCode::TimeEstimator->new; +$estimator->parse_file($ARGV[0]); +printf "Time: %d minutes and %d seconds\n", int($estimator->time / 60), $estimator->time % 60; + +__END__ diff --git a/xs/MANIFEST b/xs/MANIFEST index 61a97afede..5b2925f2cf 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -72,6 +72,8 @@ src/libslic3r/GCodeReader.cpp src/libslic3r/GCodeReader.hpp src/libslic3r/GCodeSender.cpp src/libslic3r/GCodeSender.hpp +src/libslic3r/GCodeTimeEstimator.cpp +src/libslic3r/GCodeTimeEstimator.hpp src/libslic3r/GCodeWriter.cpp src/libslic3r/GCodeWriter.hpp src/libslic3r/Geometry.cpp @@ -180,6 +182,7 @@ xsp/Filler.xsp xsp/Flow.xsp xsp/GCode.xsp xsp/GCodeSender.xsp +xsp/GCodeTimeEstimator.xsp xsp/GCodeWriter.xsp xsp/Geometry.xsp xsp/GUI.xsp diff --git a/xs/src/libslic3r/GCode/SpiralVase.cpp b/xs/src/libslic3r/GCode/SpiralVase.cpp index b56c9e58b8..9299da86a9 100644 --- a/xs/src/libslic3r/GCode/SpiralVase.cpp +++ b/xs/src/libslic3r/GCode/SpiralVase.cpp @@ -39,7 +39,7 @@ SpiralVase::process_layer(const std::string &gcode) { GCodeReader r = this->_reader; // clone r.parse(gcode, [&total_layer_length, &layer_height, &z, &set_z] - (GCodeReader &, GCodeReader::GCodeLine &line) { + (GCodeReader &, const GCodeReader::GCodeLine &line) { if (line.cmd == "G1") { if (line.extruding()) { total_layer_length += line.dist_XY(); diff --git a/xs/src/libslic3r/GCodeReader.cpp b/xs/src/libslic3r/GCodeReader.cpp index 1a0742864f..6ad937bbd1 100644 --- a/xs/src/libslic3r/GCodeReader.cpp +++ b/xs/src/libslic3r/GCodeReader.cpp @@ -1,6 +1,7 @@ #include "GCodeReader.hpp" #include #include +#include #include namespace Slic3r { @@ -17,59 +18,73 @@ GCodeReader::parse(const std::string &gcode, callback_t callback) { std::istringstream ss(gcode); std::string line; - while (std::getline(ss, line)) { - GCodeLine gline(this); - gline.raw = line; - if (this->verbose) - std::cout << line << std::endl; - - // strip comment - { - size_t pos = line.find(';'); - if (pos != std::string::npos) { - gline.comment = line.substr(pos+1); - line.erase(pos); - } + while (std::getline(ss, line)) + this->parse_line(line, callback); +} + +void +GCodeReader::parse_line(std::string line, callback_t callback) +{ + GCodeLine gline(this); + gline.raw = line; + if (this->verbose) + std::cout << line << std::endl; + + // strip comment + { + size_t pos = line.find(';'); + if (pos != std::string::npos) { + gline.comment = line.substr(pos+1); + line.erase(pos); } + } + + // command and args + { + std::vector args; + boost::split(args, line, boost::is_any_of(" ")); - // command and args - { - std::vector args; - boost::split(args, line, boost::is_any_of(" ")); - - // first one is cmd - gline.cmd = args.front(); - args.erase(args.begin()); - - for (std::string &arg : args) { - if (arg.size() < 2) continue; - gline.args.insert(std::make_pair(arg[0], arg.substr(1))); - } - } + // first one is cmd + gline.cmd = args.front(); + args.erase(args.begin()); - // convert extrusion axis - if (this->_extrusion_axis != 'E') { - const auto it = gline.args.find(this->_extrusion_axis); - if (it != gline.args.end()) { - std::swap(gline.args['E'], it->second); - gline.args.erase(it); - } + for (std::string &arg : args) { + if (arg.size() < 2) continue; + gline.args.insert(std::make_pair(arg[0], arg.substr(1))); } - - if (gline.has('E') && this->_config.use_relative_e_distances) - this->E = 0; - - if (callback) callback(*this, gline); - - // update coordinates - if (gline.cmd == "G0" || gline.cmd == "G1" || gline.cmd == "G92") { - this->X = gline.new_X(); - this->Y = gline.new_Y(); - this->Z = gline.new_Z(); - this->E = gline.new_E(); - this->F = gline.new_F(); + } + + // convert extrusion axis + if (this->_extrusion_axis != 'E') { + const auto it = gline.args.find(this->_extrusion_axis); + if (it != gline.args.end()) { + std::swap(gline.args['E'], it->second); + gline.args.erase(it); } } + + if (gline.has('E') && this->_config.use_relative_e_distances) + this->E = 0; + + if (callback) callback(*this, gline); + + // update coordinates + if (gline.cmd == "G0" || gline.cmd == "G1" || gline.cmd == "G92") { + this->X = gline.new_X(); + this->Y = gline.new_Y(); + this->Z = gline.new_Z(); + this->E = gline.new_E(); + this->F = gline.new_F(); + } +} + +void +GCodeReader::parse_file(const std::string &file, callback_t callback) +{ + std::ifstream f(file); + std::string line; + while (std::getline(f, line)) + this->parse_line(line, callback); } void diff --git a/xs/src/libslic3r/GCodeReader.hpp b/xs/src/libslic3r/GCodeReader.hpp index 267ac0e186..c627fc851f 100644 --- a/xs/src/libslic3r/GCodeReader.hpp +++ b/xs/src/libslic3r/GCodeReader.hpp @@ -25,6 +25,7 @@ class GCodeReader { GCodeLine(GCodeReader* _reader) : reader(_reader) {}; bool has(char arg) const { return this->args.count(arg) > 0; }; + float get_float(char arg) const { return atof(this->args.at(arg).c_str()); }; float new_X() const { return this->has('X') ? atof(this->args.at('X').c_str()) : this->reader->X; }; float new_Y() const { return this->has('Y') ? atof(this->args.at('Y').c_str()) : this->reader->Y; }; float new_Z() const { return this->has('Z') ? atof(this->args.at('Z').c_str()) : this->reader->Z; }; @@ -44,7 +45,7 @@ class GCodeReader { bool travel() const { return this->cmd == "G1" && !this->has('E'); }; void set(char arg, std::string value); }; - typedef std::function callback_t; + typedef std::function callback_t; float X, Y, Z, E, F; bool verbose; @@ -53,6 +54,8 @@ class GCodeReader { GCodeReader() : X(0), Y(0), Z(0), E(0), F(0), verbose(false), _extrusion_axis('E') {}; void apply_config(const PrintConfigBase &config); void parse(const std::string &gcode, callback_t callback); + void parse_line(std::string line, callback_t callback); + void parse_file(const std::string &file, callback_t callback); private: GCodeConfig _config; diff --git a/xs/src/libslic3r/GCodeTimeEstimator.cpp b/xs/src/libslic3r/GCodeTimeEstimator.cpp new file mode 100644 index 0000000000..c6fa353b40 --- /dev/null +++ b/xs/src/libslic3r/GCodeTimeEstimator.cpp @@ -0,0 +1,78 @@ +#include "GCodeTimeEstimator.hpp" +#include +#include + +namespace Slic3r { + +void +GCodeTimeEstimator::parse(const std::string &gcode) +{ + GCodeReader::parse(gcode, boost::bind(&GCodeTimeEstimator::_parser, this, _1, _2)); +} + +void +GCodeTimeEstimator::parse_file(const std::string &file) +{ + GCodeReader::parse_file(file, boost::bind(&GCodeTimeEstimator::_parser, this, _1, _2)); +} + +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); + } + //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; + } + } +} + +// 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. +} + +} diff --git a/xs/src/libslic3r/GCodeTimeEstimator.hpp b/xs/src/libslic3r/GCodeTimeEstimator.hpp new file mode 100644 index 0000000000..dd301c929f --- /dev/null +++ b/xs/src/libslic3r/GCodeTimeEstimator.hpp @@ -0,0 +1,24 @@ +#ifndef slic3r_GCodeTimeEstimator_hpp_ +#define slic3r_GCodeTimeEstimator_hpp_ + +#include "libslic3r.h" +#include "GCodeReader.hpp" + +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); +}; + +} /* namespace Slic3r */ + +#endif /* slic3r_GCodeTimeEstimator_hpp_ */ diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 9ce012a36f..a60de72529 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -19,6 +19,7 @@ REGISTER_CLASS(SpiralVase, "GCode::SpiralVase"); REGISTER_CLASS(Wipe, "GCode::Wipe"); REGISTER_CLASS(GCode, "GCode"); REGISTER_CLASS(GCodeSender, "GCode::Sender"); +REGISTER_CLASS(GCodeTimeEstimator, "GCode::TimeEstimator"); REGISTER_CLASS(GCodeWriter, "GCode::Writer"); REGISTER_CLASS(Layer, "Layer"); REGISTER_CLASS(SupportLayer, "Layer::Support"); diff --git a/xs/xsp/GCodeTimeEstimator.xsp b/xs/xsp/GCodeTimeEstimator.xsp new file mode 100644 index 0000000000..9489ff917f --- /dev/null +++ b/xs/xsp/GCodeTimeEstimator.xsp @@ -0,0 +1,15 @@ +%module{Slic3r::XS}; + +%{ +#include +#include "libslic3r/GCodeTimeEstimator.hpp" +%} + +%name{Slic3r::GCode::TimeEstimator} class GCodeTimeEstimator { + GCodeTimeEstimator(); + ~GCodeTimeEstimator(); + + float time %get{time}; + void parse(std::string gcode); + void parse_file(std::string file); +}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index c47d79e005..b1f026eaa2 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -214,6 +214,10 @@ GCodeSender* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T +GCodeTimeEstimator* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + GCodeWriter* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T