Skip to content

Commit

Permalink
New GCodeTimeEstimator class, with basic estimation code by @lordofhy…
Browse files Browse the repository at this point in the history
  • Loading branch information
alranel committed Mar 24, 2017
1 parent 5d2626f commit 969f28f
Show file tree
Hide file tree
Showing 11 changed files with 214 additions and 49 deletions.
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions 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__
3 changes: 3 additions & 0 deletions xs/MANIFEST
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion xs/src/libslic3r/GCode/SpiralVase.cpp
Expand Up @@ -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();
Expand Down
109 changes: 62 additions & 47 deletions xs/src/libslic3r/GCodeReader.cpp
@@ -1,6 +1,7 @@
#include "GCodeReader.hpp"
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <fstream>
#include <iostream>

namespace Slic3r {
Expand All @@ -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<std::string> args;
boost::split(args, line, boost::is_any_of(" "));

// command and args
{
std::vector<std::string> 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
Expand Down
5 changes: 4 additions & 1 deletion xs/src/libslic3r/GCodeReader.hpp
Expand Up @@ -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; };
Expand All @@ -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<void(GCodeReader&, GCodeLine&)> callback_t;
typedef std::function<void(GCodeReader&, const GCodeLine&)> callback_t;

float X, Y, Z, E, F;
bool verbose;
Expand All @@ -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;
Expand Down
78 changes: 78 additions & 0 deletions xs/src/libslic3r/GCodeTimeEstimator.cpp
@@ -0,0 +1,78 @@
#include "GCodeTimeEstimator.hpp"
#include <boost/bind.hpp>
#include <cmath>

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.
}

}
24 changes: 24 additions & 0 deletions 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_ */
1 change: 1 addition & 0 deletions xs/src/perlglue.cpp
Expand Up @@ -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");
Expand Down
15 changes: 15 additions & 0 deletions xs/xsp/GCodeTimeEstimator.xsp
@@ -0,0 +1,15 @@
%module{Slic3r::XS};

%{
#include <xsinit.h>
#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);
};
4 changes: 4 additions & 0 deletions xs/xsp/my.map
Expand Up @@ -214,6 +214,10 @@ GCodeSender* O_OBJECT_SLIC3R
Ref<GCodeSender> O_OBJECT_SLIC3R_T
Clone<GCodeSender> O_OBJECT_SLIC3R_T

GCodeTimeEstimator* O_OBJECT_SLIC3R
Ref<GCodeTimeEstimator> O_OBJECT_SLIC3R_T
Clone<GCodeTimeEstimator> O_OBJECT_SLIC3R_T

GCodeWriter* O_OBJECT_SLIC3R
Ref<GCodeWriter> O_OBJECT_SLIC3R_T
Clone<GCodeWriter> O_OBJECT_SLIC3R_T
Expand Down

0 comments on commit 969f28f

Please sign in to comment.