diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 928b80dd9..a42ed25cf 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -65,7 +65,6 @@ use Slic3r::Format::AMF; use Slic3r::Format::OBJ; use Slic3r::Format::STL; use Slic3r::GCode::ArcFitting; -use Slic3r::GCode::CoolingBuffer; use Slic3r::GCode::MotionPlanner; use Slic3r::GCode::PressureRegulator; use Slic3r::GCode::Reader; diff --git a/lib/Slic3r/Print/GCode.pm b/lib/Slic3r/Print/GCode.pm index d7bdcc591..9f0421e45 100644 --- a/lib/Slic3r/Print/GCode.pm +++ b/lib/Slic3r/Print/GCode.pm @@ -100,10 +100,7 @@ sub BUILD { } } - $self->_cooling_buffer(Slic3r::GCode::CoolingBuffer->new( - config => $self->config, - gcodegen => $self->_gcodegen, - )); + $self->_cooling_buffer(Slic3r::GCode::CoolingBuffer->new($self->_gcodegen)); $self->_spiral_vase(Slic3r::GCode::SpiralVase->new(config => $self->config)) if $self->config->spiral_vase; diff --git a/t/cooling.t b/t/cooling.t index f39fa0f52..2d76bf747 100644 --- a/t/cooling.t +++ b/t/cooling.t @@ -12,20 +12,17 @@ BEGIN { use Slic3r; use Slic3r::Test; +my $gcodegen; sub buffer { my $config = shift || Slic3r::Config->new; my $print_config = Slic3r::Config::Print->new; $print_config->apply_dynamic($config); - my $gcodegen = Slic3r::GCode->new; + $gcodegen = Slic3r::GCode->new; $gcodegen->apply_print_config($print_config); $gcodegen->set_layer_count(10); - my $buffer = Slic3r::GCode::CoolingBuffer->new( - config => $print_config, - gcodegen => $gcodegen, - ); - return $buffer; + return Slic3r::GCode::CoolingBuffer->new($gcodegen); } my $config = Slic3r::Config->new_from_defaults; @@ -33,14 +30,14 @@ $config->set('disable_fan_first_layers', 0); { my $buffer = buffer($config); - $buffer->gcodegen->set_elapsed_time($buffer->config->slowdown_below_layer_time + 1); + $buffer->gcodegen->set_elapsed_time($buffer->gcodegen->config->slowdown_below_layer_time + 1); my $gcode = $buffer->append('G1 F3000;_EXTRUDE_SET_SPEED\nG1 X100 E1', 0, 0, 0.4) . $buffer->flush; like $gcode, qr/F3000/, 'speed is not altered when elapsed time is greater than slowdown threshold'; } { my $buffer = buffer($config); - $buffer->gcodegen->set_elapsed_time($buffer->config->slowdown_below_layer_time - 1); + $buffer->gcodegen->set_elapsed_time($buffer->gcodegen->config->slowdown_below_layer_time - 1); my $gcode = $buffer->append( "G1 X50 F2500\n" . "G1 F3000;_EXTRUDE_SET_SPEED\n" . @@ -55,7 +52,7 @@ $config->set('disable_fan_first_layers', 0); { my $buffer = buffer($config); - $buffer->gcodegen->set_elapsed_time($buffer->config->fan_below_layer_time + 1); + $buffer->gcodegen->set_elapsed_time($buffer->gcodegen->config->fan_below_layer_time + 1); my $gcode = $buffer->append('G1 X100 E1 F3000', 0, 0, 0.4) . $buffer->flush; unlike $gcode, qr/M106/, 'fan is not activated when elapsed time is greater than fan threshold'; } @@ -65,7 +62,7 @@ $config->set('disable_fan_first_layers', 0); my $gcode = ""; for my $obj_id (0 .. 1) { # use an elapsed time which is < the slowdown threshold but greater than it when summed twice - $buffer->gcodegen->set_elapsed_time($buffer->config->slowdown_below_layer_time - 1); + $buffer->gcodegen->set_elapsed_time($buffer->gcodegen->config->slowdown_below_layer_time - 1); $gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, 0, 0.4); } $gcode .= $buffer->flush; @@ -78,7 +75,7 @@ $config->set('disable_fan_first_layers', 0); for my $layer_id (0 .. 1) { for my $obj_id (0 .. 1) { # use an elapsed time which is < the threshold but greater than it when summed twice - $buffer->gcodegen->set_elapsed_time($buffer->config->fan_below_layer_time - 1); + $buffer->gcodegen->set_elapsed_time($buffer->gcodegen->config->fan_below_layer_time - 1); $gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, $layer_id, 0.4 + 0.4*$layer_id + 0.1*$obj_id); # print same layer at distinct heights } } @@ -92,7 +89,7 @@ $config->set('disable_fan_first_layers', 0); for my $layer_id (0 .. 1) { for my $obj_id (0 .. 1) { # use an elapsed time which is < the threshold even when summed twice - $buffer->gcodegen->set_elapsed_time($buffer->config->fan_below_layer_time/2 - 1); + $buffer->gcodegen->set_elapsed_time($buffer->gcodegen->config->fan_below_layer_time/2 - 1); $gcode .= $buffer->append("G1 X100 E1 F3000\n", $obj_id, $layer_id, 0.4 + 0.4*$layer_id + 0.1*$obj_id); # print same layer at distinct heights } } @@ -134,4 +131,28 @@ $config->set('disable_fan_first_layers', 0); ok !$bridge_with_no_fan, 'bridge fan is turned on for all bridges'; } +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('cooling', 1); + $config->set('fan_below_layer_time', 0); + $config->set('slowdown_below_layer_time', 10); + $config->set('min_print_speed', 0); + $config->set('start_gcode', ''); + + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my @layer_times = (0); # in seconds + Slic3r::GCode::Reader->new->parse(my $gcode = Slic3r::Test::gcode($print), sub { + my ($self, $cmd, $args, $info) = @_; + + if ($cmd eq 'G1') { + if ($info->{dist_Z}) { + push @layer_times, 0; + } + $layer_times[-1] += abs($info->{dist_XY} || $info->{dist_E} || $info->{dist_Z} || 0) / ($args->{F} // $self->F) * 60; + } + }); + my $all_below = !defined first { $_ > 0 && $_ < $config->slowdown_below_layer_time } @layer_times; + ok $all_below, 'slowdown_below_layer_time is honored'; +} + __END__ diff --git a/xs/MANIFEST b/xs/MANIFEST index db101d689..03e52ae50 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -52,6 +52,8 @@ src/libslic3r/Flow.cpp src/libslic3r/Flow.hpp src/libslic3r/GCode.cpp src/libslic3r/GCode.hpp +src/libslic3r/GCode/CoolingBuffer.cpp +src/libslic3r/GCode/CoolingBuffer.hpp src/libslic3r/GCodeSender.cpp src/libslic3r/GCodeSender.hpp src/libslic3r/GCodeWriter.cpp diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.cpp b/xs/src/libslic3r/GCode/CoolingBuffer.cpp new file mode 100644 index 000000000..84df9cfd3 --- /dev/null +++ b/xs/src/libslic3r/GCode/CoolingBuffer.cpp @@ -0,0 +1,132 @@ +#include "CoolingBuffer.hpp" +#include +#include +#include + +namespace Slic3r { + +std::string +CoolingBuffer::append(const std::string &gcode, std::string obj_id, size_t layer_id, float print_z) +{ + std::string out; + if (this->_last_z.find(obj_id) != this->_last_z.end()) { + // A layer was finished, Z of the object's layer changed. Process the layer. + out = this->flush(); + } + + this->_layer_id = layer_id; + this->_last_z[obj_id] = print_z; + this->_gcode += gcode; + // This is a very rough estimate of the print time, + // not taking into account the acceleration curves generated by the printer firmware. + this->_elapsed_time += this->_gcodegen->elapsed_time; + this->_gcodegen->elapsed_time = 0; + + return out; +} + +void +apply_speed_factor(std::string &line, float speed_factor, float min_print_speed) +{ + // find pos of F + size_t pos = line.find_first_of('F'); + size_t last_pos = line.find_first_of(' ', pos+1); + + // extract current speed + float speed; + { + std::istringstream iss(line.substr(pos+1, last_pos)); + iss >> speed; + } + + // change speed + speed *= speed_factor; + speed = std::max(speed, min_print_speed); + + // replace speed in string + { + std::ostringstream oss; + oss << speed; + line.replace(pos+1, (last_pos-pos), oss.str()); + } +} + +std::string +CoolingBuffer::flush() +{ + GCode &gg = *this->_gcodegen; + + std::string gcode = this->_gcode; + float elapsed = this->_elapsed_time; + this->_gcode = ""; + this->_elapsed_time = 0; + this->_last_z.clear(); // reset the whole table otherwise we would compute overlapping times + + int fan_speed = gg.config.fan_always_on ? gg.config.min_fan_speed.value : 0; + + float speed_factor = 1.0; + + if (gg.config.cooling) { + #ifdef SLIC3R_DEBUG + printf("Layer %zu estimated printing time: %f seconds\n", layer_id, elapsed); + #endif + + if (elapsed < (float)gg.config.slowdown_below_layer_time) { + // Layer time very short. Enable the fan to a full throttle and slow down the print + // (stretch the layer print time to slowdown_below_layer_time). + fan_speed = gg.config.max_fan_speed; + speed_factor = elapsed / (float)gg.config.slowdown_below_layer_time; + } else if (elapsed < (float)gg.config.fan_below_layer_time) { + // Layer time quite short. Enable the fan proportionally according to the current layer time. + fan_speed = gg.config.max_fan_speed + - (gg.config.max_fan_speed - gg.config.min_fan_speed) + * (elapsed - (float)gg.config.slowdown_below_layer_time) + / (gg.config.fan_below_layer_time - gg.config.slowdown_below_layer_time); + } + + #ifdef SLIC3R_DEBUG + printf(" fan = %d%%, speed = %f%%\n", fan_speed, speed_factor * 100); + #endif + + if (speed_factor < 1.0) { + // Adjust feed rate of G1 commands marked with an _EXTRUDE_SET_SPEED + // as long as they are not _WIPE moves (they cannot if they are _EXTRUDE_SET_SPEED) + // and they are not preceded directly by _BRIDGE_FAN_START (do not adjust bridging speed). + std::string new_gcode; + std::istringstream ss(gcode); + std::string line; + bool bridge_fan_start = false; + while (std::getline(ss, line)) { + if (boost::starts_with(line, "G1") + && boost::contains(line, ";_EXTRUDE_SET_SPEED") + && !boost::contains(line, ";_WIPE") + && !bridge_fan_start) { + apply_speed_factor(line, speed_factor, this->_min_print_speed); + boost::replace_first(line, ";_EXTRUDE_SET_SPEED", ""); + } + bridge_fan_start = boost::contains(line, ";_BRIDGE_FAN_START"); + new_gcode += line + '\n'; + } + gcode = new_gcode; + } + } + if (this->_layer_id < gg.config.disable_fan_first_layers) + fan_speed = 0; + + gcode = gg.writer.set_fan(fan_speed) + gcode; + + // bridge fan speed + if (!gg.config.cooling || gg.config.bridge_fan_speed == 0 || this->_layer_id < gg.config.disable_fan_first_layers) { + boost::replace_all(gcode, ";_BRIDGE_FAN_START", ""); + boost::replace_all(gcode, ";_BRIDGE_FAN_END", ""); + } else { + boost::replace_all(gcode, ";_BRIDGE_FAN_START", gg.writer.set_fan(gg.config.bridge_fan_speed, true)); + boost::replace_all(gcode, ";_BRIDGE_FAN_END", gg.writer.set_fan(fan_speed, true)); + } + boost::replace_all(gcode, ";_WIPE", ""); + boost::replace_all(gcode, ";_EXTRUDE_SET_SPEED", ""); + + return gcode; +} + +} diff --git a/xs/src/libslic3r/GCode/CoolingBuffer.hpp b/xs/src/libslic3r/GCode/CoolingBuffer.hpp new file mode 100644 index 000000000..01770a58d --- /dev/null +++ b/xs/src/libslic3r/GCode/CoolingBuffer.hpp @@ -0,0 +1,39 @@ +#ifndef slic3r_CoolingBuffer_hpp_ +#define slic3r_CoolingBuffer_hpp_ + +#include "libslic3r.h" +#include "GCode.hpp" +#include +#include + +namespace Slic3r { + +/* +A standalone G-code filter, to control cooling of the print. +The G-code is processed per layer. Once a layer is collected, fan start / stop commands are edited +and the print is modified to stretch over a minimum layer time. +*/ + +class CoolingBuffer { + public: + CoolingBuffer(GCode &gcodegen) + : _gcodegen(&gcodegen), _elapsed_time(0.), _layer_id(0) + { + this->_min_print_speed = this->_gcodegen->config.min_print_speed * 60; + }; + std::string append(const std::string &gcode, std::string obj_id, size_t layer_id, float print_z); + std::string flush(); + GCode* gcodegen() { return this->_gcodegen; }; + + private: + GCode* _gcodegen; + std::string _gcode; + float _elapsed_time; + size_t _layer_id; + std::map _last_z; + float _min_print_speed; +}; + +} + +#endif diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 523d3fe06..552524eaa 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -15,6 +15,7 @@ REGISTER_CLASS(ExtrusionSimulator, "ExtrusionSimulator"); REGISTER_CLASS(Filler, "Filler"); REGISTER_CLASS(Flow, "Flow"); REGISTER_CLASS(AvoidCrossingPerimeters, "GCode::AvoidCrossingPerimeters"); +REGISTER_CLASS(CoolingBuffer, "GCode::CoolingBuffer"); REGISTER_CLASS(OozePrevention, "GCode::OozePrevention"); REGISTER_CLASS(Wipe, "GCode::Wipe"); REGISTER_CLASS(GCode, "GCode"); diff --git a/xs/xsp/GCode.xsp b/xs/xsp/GCode.xsp index 507132dcf..78f1f9d1f 100644 --- a/xs/xsp/GCode.xsp +++ b/xs/xsp/GCode.xsp @@ -3,6 +3,7 @@ %{ #include #include "libslic3r/GCode.hpp" +#include "libslic3r/GCode/CoolingBuffer.hpp" %} %name{Slic3r::GCode::AvoidCrossingPerimeters} class AvoidCrossingPerimeters { @@ -70,6 +71,16 @@ %code{% THIS->path = *value; %}; }; +%name{Slic3r::GCode::CoolingBuffer} class CoolingBuffer { + CoolingBuffer(GCode* gcode) + %code{% RETVAL = new CoolingBuffer(*gcode); %}; + ~CoolingBuffer(); + Ref gcodegen(); + + std::string append(std::string gcode, std::string obj_id, size_t layer_id, float print_z); + std::string flush(); +}; + %name{Slic3r::GCode} class GCode { GCode(); ~GCode(); diff --git a/xs/xsp/my.map b/xs/xsp/my.map index c6308aca7..8f5f67d4e 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -197,6 +197,10 @@ OozePrevention* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T +CoolingBuffer* O_OBJECT_SLIC3R +Ref O_OBJECT_SLIC3R_T +Clone O_OBJECT_SLIC3R_T + GCode* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index e32271ac9..57109055a 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -164,6 +164,10 @@ %typemap{Ref}{simple}; %typemap{Clone}{simple}; +%typemap{CoolingBuffer*}; +%typemap{Ref}{simple}; +%typemap{Clone}{simple}; + %typemap{GCode*}; %typemap{Ref}{simple}; %typemap{Clone}{simple};