Finalized implementation of a cooling buffer for multiple extruders
with different settings.
This commit is contained in:
parent
ab21a253e0
commit
1158ce41df
107
t/cooling.t
107
t/cooling.t
@ -15,7 +15,16 @@ use Slic3r::Test;
|
|||||||
|
|
||||||
my $gcodegen;
|
my $gcodegen;
|
||||||
sub buffer {
|
sub buffer {
|
||||||
my $config = shift || Slic3r::Config->new;
|
my $config = shift;
|
||||||
|
if (defined($config)) {
|
||||||
|
$config = $config->clone();
|
||||||
|
} else {
|
||||||
|
$config = Slic3r::Config->new;
|
||||||
|
}
|
||||||
|
my $config_override = shift;
|
||||||
|
foreach my $key (keys %{$config_override}) {
|
||||||
|
$config->set($key, ${$config_override}{$key});
|
||||||
|
}
|
||||||
|
|
||||||
my $print_config = Slic3r::Config::Print->new;
|
my $print_config = Slic3r::Config::Print->new;
|
||||||
$print_config->apply_dynamic($config);
|
$print_config->apply_dynamic($config);
|
||||||
@ -23,80 +32,96 @@ sub buffer {
|
|||||||
$gcodegen = Slic3r::GCode->new;
|
$gcodegen = Slic3r::GCode->new;
|
||||||
$gcodegen->apply_print_config($print_config);
|
$gcodegen->apply_print_config($print_config);
|
||||||
$gcodegen->set_layer_count(10);
|
$gcodegen->set_layer_count(10);
|
||||||
|
$gcodegen->set_elapsed_time(0);
|
||||||
return Slic3r::GCode::CoolingBuffer->new($gcodegen);
|
return Slic3r::GCode::CoolingBuffer->new($gcodegen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
my $gcode1 = "G1 X100 E1 F3000\n";
|
||||||
|
my $print_time1 = 100 / (3000 / 60); # 2 sec
|
||||||
|
my $gcode2 = $gcode1 . "G1 X0 E1 F3000\n";
|
||||||
|
my $print_time2 = 2 * $print_time1; # 4 sec
|
||||||
|
|
||||||
my $config = Slic3r::Config->new_from_defaults;
|
my $config = Slic3r::Config->new_from_defaults;
|
||||||
|
# Default cooling settings.
|
||||||
|
$config->set('bridge_fan_speed', [ 100 ]);
|
||||||
|
$config->set('cooling', [ 1 ]);
|
||||||
|
$config->set('fan_always_on', [ 0 ]);
|
||||||
|
$config->set('fan_below_layer_time', [ 60 ]);
|
||||||
|
$config->set('max_fan_speed', [ 100 ]);
|
||||||
|
$config->set('min_print_speed', [ 10 ]);
|
||||||
|
$config->set('slowdown_below_layer_time', [ 5 ]);
|
||||||
|
# Default print speeds.
|
||||||
|
$config->set('bridge_speed', 60);
|
||||||
|
$config->set('external_perimeter_speed', '50%');
|
||||||
|
$config->set('first_layer_speed', 30);
|
||||||
|
$config->set('gap_fill_speed', 20);
|
||||||
|
$config->set('infill_speed', 80);
|
||||||
|
$config->set('perimeter_speed', 60);
|
||||||
|
$config->set('small_perimeter_speed', 15);
|
||||||
|
$config->set('solid_infill_speed', 20);
|
||||||
|
$config->set('top_solid_infill_speed', 15);
|
||||||
|
$config->set('max_print_speed', 80);
|
||||||
|
# Override for tests.
|
||||||
$config->set('disable_fan_first_layers', [ 0 ]);
|
$config->set('disable_fan_first_layers', [ 0 ]);
|
||||||
|
|
||||||
{
|
{
|
||||||
my $buffer = buffer($config);
|
my $gcode_src = "G1 F3000;_EXTRUDE_SET_SPEED\nG1 X100 E1";
|
||||||
$buffer->gcodegen->set_elapsed_time($buffer->gcodegen->config->slowdown_below_layer_time->[0] + 1);
|
# Print time of $gcode.
|
||||||
my $gcode = $buffer->process_layer('G1 F3000;_EXTRUDE_SET_SPEED\nG1 X100 E1', 0);
|
my $print_time = 100 / (3000 / 60);
|
||||||
|
my $buffer = buffer($config, { 'slowdown_below_layer_time' => [ $print_time * 0.999 ] });
|
||||||
|
my $gcode = $buffer->process_layer($gcode_src, 0);
|
||||||
like $gcode, qr/F3000/, 'speed is not altered when elapsed time is greater than slowdown threshold';
|
like $gcode, qr/F3000/, 'speed is not altered when elapsed time is greater than slowdown threshold';
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
my $buffer = buffer($config);
|
my $gcode_src =
|
||||||
$buffer->gcodegen->set_elapsed_time($buffer->gcodegen->config->slowdown_below_layer_time->[0] - 1);
|
|
||||||
my $gcode = $buffer->process_layer(
|
|
||||||
"G1 X50 F2500\n" .
|
"G1 X50 F2500\n" .
|
||||||
"G1 F3000;_EXTRUDE_SET_SPEED\n" .
|
"G1 F3000;_EXTRUDE_SET_SPEED\n" .
|
||||||
"G1 X100 E1\n" .
|
"G1 X100 E1\n" .
|
||||||
"G1 E4 F400",
|
"G1 E4 F400",
|
||||||
0);
|
# Print time of $gcode.
|
||||||
|
my $print_time = 50 / (2500 / 60) + 100 / (3000 / 60) + 4 / (400 / 60);
|
||||||
|
my $buffer = buffer($config, { 'slowdown_below_layer_time' => [ $print_time * 1.001 ] });
|
||||||
|
my $gcode = $buffer->process_layer($gcode_src, 0);
|
||||||
unlike $gcode, qr/F3000/, 'speed is altered when elapsed time is lower than slowdown threshold';
|
unlike $gcode, qr/F3000/, 'speed is altered when elapsed time is lower than slowdown threshold';
|
||||||
like $gcode, qr/F2500/, 'speed is not altered for travel moves';
|
like $gcode, qr/F2500/, 'speed is not altered for travel moves';
|
||||||
like $gcode, qr/F400/, 'speed is not altered for extruder-only moves';
|
like $gcode, qr/F400/, 'speed is not altered for extruder-only moves';
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
my $buffer = buffer($config);
|
my $buffer = buffer($config, {
|
||||||
$buffer->gcodegen->set_elapsed_time($buffer->gcodegen->config->fan_below_layer_time->[0] + 1);
|
'fan_below_layer_time' => [ $print_time1 * 0.88 ],
|
||||||
my $gcode = $buffer->process_layer('G1 X100 E1 F3000', 0);
|
'slowdown_below_layer_time' => [ $print_time1 * 0.99 ]
|
||||||
|
});
|
||||||
|
my $gcode = $buffer->process_layer($gcode1, 0);
|
||||||
unlike $gcode, qr/M106/, 'fan is not activated when elapsed time is greater than fan threshold';
|
unlike $gcode, qr/M106/, 'fan is not activated when elapsed time is greater than fan threshold';
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
my $buffer = buffer($config);
|
my $gcode .= buffer($config, { 'slowdown_below_layer_time', [ $print_time2 * 0.99 ] })->process_layer($gcode2, 0);
|
||||||
my $gcode = "";
|
|
||||||
for my $obj_id (0 .. 1) {
|
|
||||||
$gcode .= "G1 X100 E1 F3000\n";
|
|
||||||
}
|
|
||||||
# use an elapsed time which is < the slowdown threshold but greater than it when summed twice
|
|
||||||
$buffer->gcodegen->set_elapsed_time(2 * ($buffer->gcodegen->config->slowdown_below_layer_time->[0] - 1));
|
|
||||||
$gcode .= $buffer->process_layer($gcode, 0);
|
|
||||||
like $gcode, qr/F3000/, 'slowdown is computed on all objects printing at the same Z';
|
like $gcode, qr/F3000/, 'slowdown is computed on all objects printing at the same Z';
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
my $buffer = buffer($config);
|
|
||||||
my $gcode = "";
|
|
||||||
for my $layer_id (0 .. 1) {
|
|
||||||
my $layer_gcode = "";
|
|
||||||
for my $obj_id (0 .. 1) {
|
|
||||||
$layer_gcode .= "G1 X100 E1 F3000\n";
|
|
||||||
}
|
|
||||||
# use an elapsed time which is < the threshold but greater than it when summed twice
|
# use an elapsed time which is < the threshold but greater than it when summed twice
|
||||||
$buffer->gcodegen->set_elapsed_time(2 * ($buffer->gcodegen->config->fan_below_layer_time->[0] - 1));
|
my $buffer = buffer($config, {
|
||||||
$gcode .= $buffer->process_layer($layer_gcode, $layer_id);
|
'fan_below_layer_time' => [ $print_time2 * 0.65],
|
||||||
}
|
'slowdown_below_layer_time' => [ $print_time2 * 0.7 ]
|
||||||
unlike $gcode, qr/M106/, 'fan activation is computed on all objects printing at different Z';
|
});
|
||||||
|
my $gcode = $buffer->process_layer($gcode2, 0) .
|
||||||
|
$buffer->process_layer($gcode2, 1);
|
||||||
|
unlike $gcode, qr/M106/, 'fan is not activated on all objects printing at different Z';
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
my $buffer = buffer($config);
|
|
||||||
my $gcode = "";
|
|
||||||
for my $layer_id (0 .. 1) {
|
|
||||||
my $layer_gcode = "";
|
|
||||||
for my $obj_id (0 .. 1) {
|
|
||||||
$layer_gcode .= "G1 X100 E1 F3000\n";
|
|
||||||
}
|
|
||||||
# use an elapsed time which is < the threshold even when summed twice
|
# use an elapsed time which is < the threshold even when summed twice
|
||||||
$buffer->gcodegen->set_elapsed_time(2 * ($buffer->gcodegen->config->fan_below_layer_time->[0]/2 - 1));
|
my $buffer = buffer($config, {
|
||||||
$gcode .= $buffer->process_layer($layer_gcode, $layer_id);
|
'fan_below_layer_time' => [ $print_time2 + 1 ],
|
||||||
}
|
'slowdown_below_layer_time' => [ $print_time2 + 2 ]
|
||||||
like $gcode, qr/M106/, 'fan activation is computed on all objects printing at different Z';
|
});
|
||||||
|
my $gcode = $buffer->process_layer($gcode2, 0) .
|
||||||
|
$buffer->process_layer($gcode2, 1);
|
||||||
|
like $gcode, qr/M106/, 'fan is activated on all objects printing at different Z';
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -171,6 +171,7 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
|
|||||||
// Accumulate the elapsed time for the correct calculation of layer cooling.
|
// Accumulate the elapsed time for the correct calculation of layer cooling.
|
||||||
//FIXME currently disabled as Slic3r PE needs to be updated to differentiate the moves it could slow down
|
//FIXME currently disabled as Slic3r PE needs to be updated to differentiate the moves it could slow down
|
||||||
// from the moves it could not.
|
// from the moves it could not.
|
||||||
|
gcodegen.writer().elapsed_time()->total += tcr.elapsed_time;
|
||||||
gcodegen.writer().elapsed_time()->other += tcr.elapsed_time;
|
gcodegen.writer().elapsed_time()->other += tcr.elapsed_time;
|
||||||
// A phony move to the end position at the wipe tower.
|
// A phony move to the end position at the wipe tower.
|
||||||
gcodegen.writer().travel_to_xy(Pointf(tcr.end_pos.x, tcr.end_pos.y));
|
gcodegen.writer().travel_to_xy(Pointf(tcr.end_pos.x, tcr.end_pos.y));
|
||||||
@ -570,7 +571,6 @@ bool GCode::do_export(FILE *file, Print &print)
|
|||||||
initial_extruder_id = new_extruder_id;
|
initial_extruder_id = new_extruder_id;
|
||||||
final_extruder_id = tool_ordering.last_extruder();
|
final_extruder_id = tool_ordering.last_extruder();
|
||||||
assert(final_extruder_id != (unsigned int)-1);
|
assert(final_extruder_id != (unsigned int)-1);
|
||||||
m_cooling_buffer->set_current_extruder(initial_extruder_id);
|
|
||||||
}
|
}
|
||||||
this->set_origin(unscale(copy.x), unscale(copy.y));
|
this->set_origin(unscale(copy.x), unscale(copy.y));
|
||||||
if (finished_objects > 0) {
|
if (finished_objects > 0) {
|
||||||
@ -590,6 +590,9 @@ bool GCode::do_export(FILE *file, Print &print)
|
|||||||
// Set first layer extruder.
|
// Set first layer extruder.
|
||||||
this->_print_first_layer_extruder_temperatures(file, print, initial_extruder_id, false);
|
this->_print_first_layer_extruder_temperatures(file, print, initial_extruder_id, false);
|
||||||
}
|
}
|
||||||
|
// Reset the cooling buffer internal state (the current position, feed rate, accelerations).
|
||||||
|
m_cooling_buffer->reset();
|
||||||
|
m_cooling_buffer->set_current_extruder(initial_extruder_id);
|
||||||
// Pair the object layers with the support layers by z, extrude them.
|
// Pair the object layers with the support layers by z, extrude them.
|
||||||
std::vector<LayerToPrint> layers_to_print = collect_layers_to_print(object);
|
std::vector<LayerToPrint> layers_to_print = collect_layers_to_print(object);
|
||||||
for (const LayerToPrint <p : layers_to_print) {
|
for (const LayerToPrint <p : layers_to_print) {
|
||||||
@ -597,6 +600,8 @@ bool GCode::do_export(FILE *file, Print &print)
|
|||||||
lrs.emplace_back(std::move(ltp));
|
lrs.emplace_back(std::move(ltp));
|
||||||
this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), © - object._shifted_copies.data());
|
this->process_layer(file, print, lrs, tool_ordering.tools_for_layer(ltp.print_z()), © - object._shifted_copies.data());
|
||||||
}
|
}
|
||||||
|
if (m_pressure_equalizer)
|
||||||
|
write(file, m_pressure_equalizer->process("", true));
|
||||||
++ finished_objects;
|
++ finished_objects;
|
||||||
// Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed.
|
// Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed.
|
||||||
// Reset it when starting another object from 1st layer.
|
// Reset it when starting another object from 1st layer.
|
||||||
@ -624,6 +629,8 @@ bool GCode::do_export(FILE *file, Print &print)
|
|||||||
m_wipe_tower->next_layer();
|
m_wipe_tower->next_layer();
|
||||||
this->process_layer(file, print, layer.second, layer_tools, size_t(-1));
|
this->process_layer(file, print, layer.second, layer_tools, size_t(-1));
|
||||||
}
|
}
|
||||||
|
if (m_pressure_equalizer)
|
||||||
|
write(file, m_pressure_equalizer->process("", true));
|
||||||
if (m_wipe_tower)
|
if (m_wipe_tower)
|
||||||
// Purge the extruder, pull out the active filament.
|
// Purge the extruder, pull out the active filament.
|
||||||
write(file, m_wipe_tower->finalize(*this));
|
write(file, m_wipe_tower->finalize(*this));
|
||||||
@ -1099,28 +1106,21 @@ void GCode::process_layer(
|
|||||||
// (we must feed all the G-code into the post-processor, including the first
|
// (we must feed all the G-code into the post-processor, including the first
|
||||||
// bottom non-spiral layers otherwise it will mess with positions)
|
// bottom non-spiral layers otherwise it will mess with positions)
|
||||||
// we apply spiral vase at this stage because it requires a full layer.
|
// we apply spiral vase at this stage because it requires a full layer.
|
||||||
// Just a reminder: A spiral vase mode is allowed for a single object, single material print only.
|
// Just a reminder: A spiral vase mode is allowed for a single object per layer, single material print only.
|
||||||
if (m_spiral_vase)
|
if (m_spiral_vase)
|
||||||
gcode = m_spiral_vase->process_layer(gcode);
|
gcode = m_spiral_vase->process_layer(gcode);
|
||||||
|
|
||||||
// Apply cooling logic; this may alter speeds.
|
// Apply cooling logic; this may alter speeds.
|
||||||
if (m_cooling_buffer)
|
if (m_cooling_buffer)
|
||||||
//FIXME Update the CoolingBuffer class to ignore the object ID, which does not make sense anymore
|
|
||||||
// once all extrusions of a layer are processed at once.
|
|
||||||
// Update the test cases.
|
|
||||||
gcode = m_cooling_buffer->process_layer(gcode, layer.id());
|
gcode = m_cooling_buffer->process_layer(gcode, layer.id());
|
||||||
write(file, this->filter(std::move(gcode), false));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string GCode::filter(std::string &&gcode, bool flush)
|
// Apply pressure equalization if enabled;
|
||||||
{
|
|
||||||
// apply pressure equalization if enabled;
|
|
||||||
// printf("G-code before filter:\n%s\n", gcode.c_str());
|
// printf("G-code before filter:\n%s\n", gcode.c_str());
|
||||||
std::string out = m_pressure_equalizer ?
|
if (m_pressure_equalizer)
|
||||||
m_pressure_equalizer->process(gcode.c_str(), flush) :
|
gcode = m_pressure_equalizer->process(gcode.c_str(), false);
|
||||||
std::move(gcode);
|
|
||||||
// printf("G-code after filter:\n%s\n", out.c_str());
|
// printf("G-code after filter:\n%s\n", out.c_str());
|
||||||
return out;
|
|
||||||
|
write(file, gcode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GCode::apply_print_config(const PrintConfig &print_config)
|
void GCode::apply_print_config(const PrintConfig &print_config)
|
||||||
@ -1841,12 +1841,17 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
|||||||
gcode += buf;
|
gcode += buf;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is_bridge(path.role()) && m_enable_cooling_markers)
|
std::string comment;
|
||||||
|
if (m_enable_cooling_markers) {
|
||||||
|
if (is_bridge(path.role()))
|
||||||
gcode += ";_BRIDGE_FAN_START\n";
|
gcode += ";_BRIDGE_FAN_START\n";
|
||||||
std::string comment = ";_EXTRUDE_SET_SPEED";
|
else
|
||||||
|
comment = ";_EXTRUDE_SET_SPEED";
|
||||||
if (path.role() == erExternalPerimeter)
|
if (path.role() == erExternalPerimeter)
|
||||||
comment += ";_EXTERNAL_PERIMETER";
|
comment += ";_EXTERNAL_PERIMETER";
|
||||||
gcode += m_writer.set_speed(F, "", m_enable_cooling_markers ? comment : "");
|
}
|
||||||
|
// F is mm per minute.
|
||||||
|
gcode += m_writer.set_speed(F, "", comment);
|
||||||
double path_length = 0.;
|
double path_length = 0.;
|
||||||
{
|
{
|
||||||
std::string comment = m_config.gcode_comments ? description : "";
|
std::string comment = m_config.gcode_comments ? description : "";
|
||||||
@ -1859,18 +1864,26 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
|
|||||||
comment);
|
comment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is_bridge(path.role()) && m_enable_cooling_markers)
|
if (m_enable_cooling_markers)
|
||||||
gcode += ";_BRIDGE_FAN_END\n";
|
gcode += is_bridge(path.role()) ? ";_BRIDGE_FAN_END\n" : ";_EXTRUDE_END\n";
|
||||||
|
|
||||||
this->set_last_pos(path.last_point());
|
this->set_last_pos(path.last_point());
|
||||||
|
|
||||||
if (m_config.cooling.values.front()) {
|
if (m_config.cooling.values.front()) {
|
||||||
float t = path_length / F * 60.f;
|
float t = float(path_length / F * 60);
|
||||||
m_writer.elapsed_time()->total += t;
|
m_writer.elapsed_time()->total += t;
|
||||||
|
assert(! (is_bridge(path.role()) && path.role() == erExternalPerimeter));
|
||||||
if (is_bridge(path.role()))
|
if (is_bridge(path.role()))
|
||||||
m_writer.elapsed_time()->bridges += t;
|
m_writer.elapsed_time()->bridges += t;
|
||||||
|
else {
|
||||||
|
// Maximum print time of this extrusion, respecting the min_print_speed.
|
||||||
|
float t_max = std::max(t, float(path_length / std::max(0.1, EXTRUDER_CONFIG(min_print_speed))));
|
||||||
if (path.role() == erExternalPerimeter)
|
if (path.role() == erExternalPerimeter)
|
||||||
m_writer.elapsed_time()->external_perimeters += t;
|
m_writer.elapsed_time()->external_perimeters += t;
|
||||||
|
else
|
||||||
|
m_writer.elapsed_time()->max_stretch_time_no_ext_perimetes += t_max;
|
||||||
|
m_writer.elapsed_time()->max_stretch_time_total += t_max;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return gcode;
|
return gcode;
|
||||||
|
@ -267,8 +267,6 @@ protected:
|
|||||||
// this flag triggers first layer speeds
|
// this flag triggers first layer speeds
|
||||||
bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0; }
|
bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0; }
|
||||||
|
|
||||||
std::string filter(std::string &&gcode, bool flush);
|
|
||||||
|
|
||||||
friend ObjectByExtruder& object_by_extruder(
|
friend ObjectByExtruder& object_by_extruder(
|
||||||
std::map<unsigned int, std::vector<ObjectByExtruder>> &by_extruder,
|
std::map<unsigned int, std::vector<ObjectByExtruder>> &by_extruder,
|
||||||
unsigned int extruder_id,
|
unsigned int extruder_id,
|
||||||
|
@ -4,125 +4,463 @@
|
|||||||
#include <boost/algorithm/string/replace.hpp>
|
#include <boost/algorithm/string/replace.hpp>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
#define DEBUG
|
||||||
|
#define _DEBUG
|
||||||
|
#undef NDEBUG
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
void apply_speed_factor(std::string &line, float speed_factor, float min_print_speed)
|
CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_gcodegen(gcodegen), m_current_extruder(0)
|
||||||
{
|
{
|
||||||
// find pos of F
|
this->reset();
|
||||||
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::process_layer(const std::string &gcode_src, size_t layer_id)
|
void CoolingBuffer::reset()
|
||||||
|
{
|
||||||
|
m_current_pos.assign(5, 0.f);
|
||||||
|
Pointf3 pos = m_gcodegen.writer().get_position();
|
||||||
|
m_current_pos[0] = float(pos.x);
|
||||||
|
m_current_pos[1] = float(pos.y);
|
||||||
|
m_current_pos[2] = float(pos.z);
|
||||||
|
m_current_pos[4] = float(m_gcodegen.config().travel_speed.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define EXTRUDER_CONFIG(OPT) config.OPT.get_at(m_current_extruder)
|
||||||
|
|
||||||
|
std::string CoolingBuffer::process_layer(const std::string &gcode, size_t layer_id)
|
||||||
{
|
{
|
||||||
const FullPrintConfig &config = m_gcodegen.config();
|
const FullPrintConfig &config = m_gcodegen.config();
|
||||||
|
const auto &elapsed_times = m_gcodegen.writer().elapsed_times();
|
||||||
|
const size_t num_extruders = elapsed_times.size();
|
||||||
|
|
||||||
std::string gcode = gcode_src;
|
// Calculate the required per extruder time stretches.
|
||||||
int fan_speed = config.fan_always_on.values.front() ? config.min_fan_speed.values.front() : 0;
|
struct Adjustment {
|
||||||
float speed_factor = 1.0;
|
Adjustment() {}
|
||||||
bool slowdown_external = true;
|
// Calculate the total elapsed time per this extruder, adjusted for the slowdown.
|
||||||
|
float elapsed_time_total() {
|
||||||
const std::vector<ElapsedTime> &elapsed_times = m_gcodegen.writer().elapsed_times();
|
float time_total = 0.f;
|
||||||
ElapsedTime elapsed_time;
|
for (const Line &line : lines)
|
||||||
for (const ElapsedTime &et : elapsed_times)
|
time_total += line.time;
|
||||||
elapsed_time += et;
|
return time_total;
|
||||||
|
}
|
||||||
if (config.cooling.values.front()) {
|
// Calculate the maximum time when slowing down.
|
||||||
#ifdef SLIC3R_DEBUG
|
float maximum_time(bool slowdown_external_perimeters) {
|
||||||
printf("Layer %zu estimated printing time: %f seconds\n", layer_id, elapsed_time.total);
|
float time_total = 0.f;
|
||||||
#endif
|
for (const Line &line : lines)
|
||||||
if (elapsed_time.total < (float)config.slowdown_below_layer_time.values.front()) {
|
if (line.adjustable(slowdown_external_perimeters)) {
|
||||||
// Layer time very short. Enable the fan to a full throttle and slow down the print
|
if (line.time_max == FLT_MAX)
|
||||||
// (stretch the layer print time to slowdown_below_layer_time).
|
return FLT_MAX;
|
||||||
fan_speed = config.max_fan_speed.values.front();
|
else
|
||||||
|
time_total += line.time_max;
|
||||||
// We are not altering speed of bridges.
|
} else
|
||||||
float time_to_stretch = elapsed_time.stretchable();
|
time_total += line.time;
|
||||||
float target_time = (float)config.slowdown_below_layer_time.values.front() - elapsed_time.non_stretchable();
|
return time_total;
|
||||||
|
}
|
||||||
// If we spend most of our time on external perimeters include them in the slowdown,
|
// Calculate the non-adjustable part of the total time.
|
||||||
// otherwise only alter other extrusions.
|
float non_adjustable_time(bool slowdown_external_perimeters) {
|
||||||
if (elapsed_time.external_perimeters < 0.5f * time_to_stretch) {
|
float time_total = 0.f;
|
||||||
time_to_stretch -= elapsed_time.external_perimeters;
|
for (const Line &line : lines)
|
||||||
target_time -= elapsed_time.external_perimeters;
|
if (! line.adjustable(slowdown_external_perimeters))
|
||||||
slowdown_external = false;
|
time_total += line.time;
|
||||||
|
return time_total;
|
||||||
|
}
|
||||||
|
float slow_down_maximum(bool slowdown_external_perimeters) {
|
||||||
|
float time_total = 0.f;
|
||||||
|
for (Line &line : lines) {
|
||||||
|
if (line.adjustable(slowdown_external_perimeters)) {
|
||||||
|
assert(line.time_max >= 0.f && line.time_max < FLT_MAX);
|
||||||
|
line.slowdown = true;
|
||||||
|
line.time = line.time_max;
|
||||||
|
}
|
||||||
|
time_total += line.time;
|
||||||
|
}
|
||||||
|
return time_total;
|
||||||
|
}
|
||||||
|
float slow_down_proportional(float factor, bool slowdown_external_perimeters) {
|
||||||
|
assert(factor >= 1.f);
|
||||||
|
float time_total = 0.f;
|
||||||
|
for (Line &line : lines) {
|
||||||
|
if (line.adjustable(slowdown_external_perimeters)) {
|
||||||
|
line.slowdown = true;
|
||||||
|
line.time = std::min(line.time_max, line.time * factor);
|
||||||
|
}
|
||||||
|
time_total += line.time;
|
||||||
|
}
|
||||||
|
return time_total;
|
||||||
}
|
}
|
||||||
|
|
||||||
speed_factor = time_to_stretch / target_time;
|
struct Line
|
||||||
} else if (elapsed_time.total < (float)config.fan_below_layer_time.values.front()) {
|
{
|
||||||
// Layer time quite short. Enable the fan proportionally according to the current layer time.
|
enum Type {
|
||||||
fan_speed = config.max_fan_speed.values.front()
|
TYPE_SET_TOOL = 1 << 0,
|
||||||
- (config.max_fan_speed.values.front() - config.min_fan_speed.values.front())
|
TYPE_EXTRUDE_END = 1 << 1,
|
||||||
* (elapsed_time.total - (float)config.slowdown_below_layer_time.values.front())
|
TYPE_BRIDGE_FAN_START = 1 << 2,
|
||||||
/ (config.fan_below_layer_time.values.front() - config.slowdown_below_layer_time.values.front());
|
TYPE_BRIDGE_FAN_END = 1 << 3,
|
||||||
|
TYPE_G0 = 1 << 4,
|
||||||
|
TYPE_G1 = 1 << 5,
|
||||||
|
TYPE_ADJUSTABLE = 1 << 6,
|
||||||
|
TYPE_EXTERNAL_PERIMETER = 1 << 7,
|
||||||
|
TYPE_WIPE = 1 << 8,
|
||||||
|
TYPE_G4 = 1 << 9,
|
||||||
|
TYPE_G92 = 1 << 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
Line(unsigned int type, size_t line_start, size_t line_end) :
|
||||||
|
type(type), line_start(line_start), line_end(line_end),
|
||||||
|
length(0.f), time(0.f), time_max(0.f), slowdown(false) {}
|
||||||
|
|
||||||
|
bool adjustable(bool slowdown_external_perimeters) const {
|
||||||
|
return (this->type & TYPE_ADJUSTABLE) &&
|
||||||
|
(! (this->type & TYPE_EXTERNAL_PERIMETER) || slowdown_external_perimeters) &&
|
||||||
|
this->time < this->time_max;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef SLIC3R_DEBUG
|
size_t type;
|
||||||
printf(" fan = %d%%, speed = %f%%\n", fan_speed, speed_factor * 100);
|
// Start of this line at the G-code snippet.
|
||||||
#endif
|
size_t line_start;
|
||||||
|
// End of this line at the G-code snippet.
|
||||||
|
size_t line_end;
|
||||||
|
// XY Euclidian length of this segment.
|
||||||
|
float length;
|
||||||
|
// Current duration of this segment.
|
||||||
|
float time;
|
||||||
|
// Maximum duration of this segment.
|
||||||
|
float time_max;
|
||||||
|
// If marked with the "slowdown" flag, the line has been slowed down.
|
||||||
|
bool slowdown;
|
||||||
|
};
|
||||||
|
|
||||||
if (speed_factor < 1.0) {
|
// Parsed lines.
|
||||||
// Adjust feed rate of G1 commands marked with an _EXTRUDE_SET_SPEED
|
std::vector<Line> lines;
|
||||||
// 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::vector<Adjustment> adjustments(num_extruders, Adjustment());
|
||||||
std::string new_gcode;
|
const std::string toolchange_prefix = m_gcodegen.writer().toolchange_prefix();
|
||||||
std::istringstream ss(gcode);
|
// Parse the layer G-code for the moves, which could be adjusted.
|
||||||
std::string line;
|
{
|
||||||
bool bridge_fan_start = false;
|
float min_print_speed = float(EXTRUDER_CONFIG(min_print_speed));
|
||||||
float min_print_speed = float(config.min_print_speed.values.front() * 60.);
|
auto it_elapsed_time = std::lower_bound(elapsed_times.begin(), elapsed_times.end(), ElapsedTime(m_current_extruder));
|
||||||
while (std::getline(ss, line)) {
|
Adjustment *adjustment = &adjustments[it_elapsed_time - elapsed_times.begin()];
|
||||||
if (boost::starts_with(line, "G1")
|
unsigned int initial_extruder = m_current_extruder;
|
||||||
&& boost::contains(line, ";_EXTRUDE_SET_SPEED")
|
const char *line_start = gcode.c_str();
|
||||||
&& !boost::contains(line, ";_WIPE")
|
const char *line_end = line_start;
|
||||||
&& !bridge_fan_start
|
const char extrusion_axis = config.get_extrusion_axis()[0];
|
||||||
&& (slowdown_external || !boost::contains(line, ";_EXTERNAL_PERIMETER"))) {
|
// Index of an existing Adjustment::Line of the current adjustment, which holds the feedrate setting command
|
||||||
apply_speed_factor(line, speed_factor, min_print_speed);
|
// for a sequence of extrusion moves.
|
||||||
boost::replace_first(line, ";_EXTRUDE_SET_SPEED", "");
|
size_t active_speed_modifier = size_t(-1);
|
||||||
|
for (; *line_start != 0; line_start = line_end) {
|
||||||
|
while (*line_end != '\n' && *line_end != 0)
|
||||||
|
++ line_end;
|
||||||
|
std::string sline(line_start, line_end);
|
||||||
|
Adjustment::Line line(0, line_start - gcode.c_str(), line_end - gcode.c_str());
|
||||||
|
if (boost::starts_with(sline, "G0 "))
|
||||||
|
line.type = Adjustment::Line::TYPE_G0;
|
||||||
|
else if (boost::starts_with(sline, "G1 "))
|
||||||
|
line.type = Adjustment::Line::TYPE_G1;
|
||||||
|
else if (boost::starts_with(sline, "G92 "))
|
||||||
|
line.type = Adjustment::Line::TYPE_G92;
|
||||||
|
if (line.type) {
|
||||||
|
// G0, G1 or G92
|
||||||
|
// Parse the G-code line.
|
||||||
|
std::vector<float> new_pos(m_current_pos);
|
||||||
|
const char *c = sline.data() + 3;
|
||||||
|
for (;;) {
|
||||||
|
// Skip whitespaces.
|
||||||
|
for (; *c == ' ' || *c == '\t'; ++ c);
|
||||||
|
if (*c == 0 || *c == ';')
|
||||||
|
break;
|
||||||
|
// Parse the axis.
|
||||||
|
size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') :
|
||||||
|
(*c == extrusion_axis) ? 3 : (*c == 'F') ? 4 : size_t(-1);
|
||||||
|
if (axis != size_t(-1)) {
|
||||||
|
new_pos[axis] = float(atof(++c));
|
||||||
|
if (axis == 4)
|
||||||
|
// Convert mm/min to mm/sec.
|
||||||
|
new_pos[4] /= 60.f;
|
||||||
}
|
}
|
||||||
bridge_fan_start = boost::starts_with(line, ";_BRIDGE_FAN_START");
|
// Skip this word.
|
||||||
new_gcode += line + '\n';
|
for (; *c != ' ' && *c != '\t' && *c != 0; ++ c);
|
||||||
}
|
}
|
||||||
gcode = new_gcode;
|
bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER");
|
||||||
|
bool wipe = boost::contains(sline, ";_WIPE");
|
||||||
|
if (external_perimeter)
|
||||||
|
line.type |= Adjustment::Line::TYPE_EXTERNAL_PERIMETER;
|
||||||
|
if (wipe)
|
||||||
|
line.type |= Adjustment::Line::TYPE_WIPE;
|
||||||
|
if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && ! wipe) {
|
||||||
|
line.type |= Adjustment::Line::TYPE_ADJUSTABLE;
|
||||||
|
active_speed_modifier = adjustment->lines.size();
|
||||||
|
}
|
||||||
|
if ((line.type & Adjustment::Line::TYPE_G92) == 0) {
|
||||||
|
// G0 or G1. Calculate the duration.
|
||||||
|
if (config.use_relative_e_distances.value)
|
||||||
|
// Reset extruder accumulator.
|
||||||
|
m_current_pos[3] = 0.f;
|
||||||
|
float dif[4];
|
||||||
|
for (size_t i = 0; i < 4; ++ i)
|
||||||
|
dif[i] = new_pos[i] - m_current_pos[i];
|
||||||
|
float dxy2 = dif[0] * dif[0] + dif[1] * dif[1];
|
||||||
|
float dxyz2 = dxy2 + dif[2] * dif[2];
|
||||||
|
if (dxyz2 > 0.f) {
|
||||||
|
// Movement in xyz, calculate time from the xyz Euclidian distance.
|
||||||
|
line.length = sqrt(dxyz2);
|
||||||
|
} else if (std::abs(dif[3]) > 0.f) {
|
||||||
|
// Movement in the extruder axis.
|
||||||
|
line.length = std::abs(dif[3]);
|
||||||
|
}
|
||||||
|
if (line.length > 0)
|
||||||
|
line.time = line.length / new_pos[4]; // current F
|
||||||
|
line.time_max = line.time;
|
||||||
|
if ((line.type & Adjustment::Line::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1))
|
||||||
|
line.time_max = (min_print_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / min_print_speed);
|
||||||
|
if (active_speed_modifier < adjustment->lines.size() && (line.type & Adjustment::Line::TYPE_G1)) {
|
||||||
|
Adjustment::Line &sm = adjustment->lines[active_speed_modifier];
|
||||||
|
sm.length += line.length;
|
||||||
|
sm.time += line.time;
|
||||||
|
if (sm.time_max != FLT_MAX) {
|
||||||
|
if (line.time_max == FLT_MAX)
|
||||||
|
sm.time_max = FLT_MAX;
|
||||||
|
else
|
||||||
|
sm.time_max += line.time_max;
|
||||||
|
}
|
||||||
|
// Don't store this line.
|
||||||
|
line.type = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (layer_id < config.disable_fan_first_layers.values.front())
|
m_current_pos = std::move(new_pos);
|
||||||
fan_speed = 0;
|
} else if (boost::starts_with(sline, ";_EXTRUDE_END")) {
|
||||||
|
line.type = Adjustment::Line::TYPE_EXTRUDE_END;
|
||||||
|
active_speed_modifier = size_t(-1);
|
||||||
|
} else if (boost::starts_with(sline, toolchange_prefix)) {
|
||||||
|
// Switch the tool.
|
||||||
|
unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + toolchange_prefix.size());
|
||||||
|
if (new_extruder != m_current_extruder) {
|
||||||
|
m_current_extruder = new_extruder;
|
||||||
|
min_print_speed = float(EXTRUDER_CONFIG(min_print_speed));
|
||||||
|
it_elapsed_time = std::lower_bound(elapsed_times.begin(), elapsed_times.end(), ElapsedTime(m_current_extruder));
|
||||||
|
adjustment = &adjustments[it_elapsed_time - elapsed_times.begin()];
|
||||||
|
}
|
||||||
|
} else if (boost::starts_with(sline, ";_BRIDGE_FAN_START")) {
|
||||||
|
line.type = Adjustment::Line::TYPE_BRIDGE_FAN_START;
|
||||||
|
} else if (boost::starts_with(sline, ";_BRIDGE_FAN_END")) {
|
||||||
|
line.type = Adjustment::Line::TYPE_BRIDGE_FAN_END;
|
||||||
|
} else if (boost::starts_with(sline, "G4 ")) {
|
||||||
|
// Parse the wait time.
|
||||||
|
line.type = Adjustment::Line::TYPE_G4;
|
||||||
|
size_t pos_S = sline.find('S', 3);
|
||||||
|
size_t pos_P = sline.find('P', 3);
|
||||||
|
line.time = line.time_max = float(
|
||||||
|
(pos_S > 0) ? atof(sline.c_str() + pos_S + 1) :
|
||||||
|
(pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.);
|
||||||
|
}
|
||||||
|
if (line.type != 0)
|
||||||
|
adjustment->lines.emplace_back(std::move(line));
|
||||||
|
while (*line_end == '\n')
|
||||||
|
++ line_end;
|
||||||
|
}
|
||||||
|
m_current_extruder = initial_extruder;
|
||||||
|
}
|
||||||
|
|
||||||
gcode = m_gcodegen.writer().set_fan(fan_speed) + gcode;
|
// Sort the extruders by the increasing slowdown_below_layer_time.
|
||||||
|
std::vector<size_t> by_slowdown_layer_time;
|
||||||
|
by_slowdown_layer_time.reserve(num_extruders);
|
||||||
|
// Only insert entries, which are adjustable (have cooling enabled and non-zero stretchable time).
|
||||||
|
// Collect total print time of non-adjustable extruders.
|
||||||
|
float elapsed_time_total_non_adjustable = 0.f;
|
||||||
|
for (size_t i = 0; i < num_extruders; ++ i) {
|
||||||
|
if (config.cooling.get_at(elapsed_times[i].extruder_id))
|
||||||
|
by_slowdown_layer_time.emplace_back(i);
|
||||||
|
else
|
||||||
|
elapsed_time_total_non_adjustable += adjustments[i].elapsed_time_total();
|
||||||
|
}
|
||||||
|
std::sort(by_slowdown_layer_time.begin(), by_slowdown_layer_time.end(),
|
||||||
|
[&config, &elapsed_times](const size_t idx1, const size_t idx2){
|
||||||
|
return config.slowdown_below_layer_time.get_at(elapsed_times[idx1].extruder_id) <
|
||||||
|
config.slowdown_below_layer_time.get_at(elapsed_times[idx2].extruder_id);
|
||||||
|
});
|
||||||
|
|
||||||
// bridge fan speed
|
// Elapsed time after adjustment.
|
||||||
if (!config.cooling.values.front() || config.bridge_fan_speed.values.front() == 0 || layer_id < config.disable_fan_first_layers.values.front()) {
|
float elapsed_time_total = 0.f;
|
||||||
boost::replace_all(gcode, ";_BRIDGE_FAN_START", "");
|
{
|
||||||
boost::replace_all(gcode, ";_BRIDGE_FAN_END", "");
|
// Elapsed time for the already adjusted extruders.
|
||||||
|
float elapsed_time_total0 = elapsed_time_total_non_adjustable;
|
||||||
|
for (size_t i_by_slowdown_layer_time = 0; i_by_slowdown_layer_time < by_slowdown_layer_time.size(); ++ i_by_slowdown_layer_time) {
|
||||||
|
// Idx in elapsed_times and adjustments.
|
||||||
|
size_t idx = by_slowdown_layer_time[i_by_slowdown_layer_time];
|
||||||
|
// Macro to sum or adjust all sections starting with i_by_slowdown_layer_time.
|
||||||
|
#define FORALL_UNPROCESSED(ACCUMULATOR, ACTION) \
|
||||||
|
ACCUMULATOR = elapsed_time_total0;\
|
||||||
|
for (size_t j = i_by_slowdown_layer_time; j < by_slowdown_layer_time.size(); ++ j) \
|
||||||
|
ACCUMULATOR += adjustments[by_slowdown_layer_time[j]].ACTION
|
||||||
|
// Calculate the current adjusted elapsed_time_total over the non-finalized extruders.
|
||||||
|
float total;
|
||||||
|
FORALL_UNPROCESSED(total, elapsed_time_total());
|
||||||
|
float slowdown_below_layer_time = float(config.slowdown_below_layer_time.get_at(elapsed_times[idx].extruder_id)) * 1.001f;
|
||||||
|
if (total > slowdown_below_layer_time) {
|
||||||
|
// The current total time is above the minimum threshold of the rest of the extruders, don't adjust anything.
|
||||||
} else {
|
} else {
|
||||||
boost::replace_all(gcode, ";_BRIDGE_FAN_START", m_gcodegen.writer().set_fan(config.bridge_fan_speed.values.front(), true));
|
// Adjust this and all the following (higher config.slowdown_below_layer_time) extruders.
|
||||||
boost::replace_all(gcode, ";_BRIDGE_FAN_END", m_gcodegen.writer().set_fan(fan_speed, true));
|
// Sum maximum slow down time as if everything was slowed down including the external perimeters.
|
||||||
|
float max_time;
|
||||||
|
FORALL_UNPROCESSED(max_time, maximum_time(true));
|
||||||
|
if (max_time > slowdown_below_layer_time) {
|
||||||
|
// By slowing every possible movement, the layer time could be reached. Now decide
|
||||||
|
// whether the external perimeters shall be slowed down as well.
|
||||||
|
float max_time_nep;
|
||||||
|
FORALL_UNPROCESSED(max_time_nep, maximum_time(false));
|
||||||
|
if (max_time_nep > slowdown_below_layer_time) {
|
||||||
|
// It is sufficient to slow down the non-external perimeter moves to reach the target layer time.
|
||||||
|
// Slow down the non-external perimeters proportionally.
|
||||||
|
float non_adjustable_time;
|
||||||
|
FORALL_UNPROCESSED(non_adjustable_time, non_adjustable_time(false));
|
||||||
|
// The following step is a linear programming task due to the minimum movement speeds of the print moves.
|
||||||
|
// Run maximum 5 iterations until a good enough approximation is reached.
|
||||||
|
for (size_t iter = 0; iter < 5; ++ iter) {
|
||||||
|
float factor = (slowdown_below_layer_time - non_adjustable_time) / (total - non_adjustable_time);
|
||||||
|
assert(factor > 1.f);
|
||||||
|
FORALL_UNPROCESSED(total, slow_down_proportional(factor, false));
|
||||||
|
if (total > 0.95f * slowdown_below_layer_time)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Slow down everything. First slow down the non-external perimeters to maximum.
|
||||||
|
FORALL_UNPROCESSED(total, slow_down_maximum(false));
|
||||||
|
// Slow down the external perimeters proportionally.
|
||||||
|
float non_adjustable_time;
|
||||||
|
FORALL_UNPROCESSED(non_adjustable_time, non_adjustable_time(true));
|
||||||
|
for (size_t iter = 0; iter < 5; ++ iter) {
|
||||||
|
float factor = (slowdown_below_layer_time - non_adjustable_time) / (total - non_adjustable_time);
|
||||||
|
assert(factor > 1.f);
|
||||||
|
FORALL_UNPROCESSED(total, slow_down_proportional(factor, true));
|
||||||
|
if (total > 0.95f * slowdown_below_layer_time)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Slow down to maximum possible.
|
||||||
|
FORALL_UNPROCESSED(total, slow_down_maximum(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#undef FORALL_UNPROCESSED
|
||||||
|
// Sum the final elapsed time for all extruders up to i_by_slowdown_layer_time.
|
||||||
|
if (i_by_slowdown_layer_time + 1 == by_slowdown_layer_time.size())
|
||||||
|
// Optimization for single extruder prints.
|
||||||
|
elapsed_time_total0 = total;
|
||||||
|
else
|
||||||
|
elapsed_time_total0 += adjustments[idx].elapsed_time_total();
|
||||||
|
}
|
||||||
|
elapsed_time_total = elapsed_time_total0;
|
||||||
}
|
}
|
||||||
boost::replace_all(gcode, ";_WIPE", "");
|
|
||||||
boost::replace_all(gcode, ";_EXTRUDE_SET_SPEED", "");
|
|
||||||
boost::replace_all(gcode, ";_EXTERNAL_PERIMETER", "");
|
|
||||||
|
|
||||||
m_object_ids_visited.clear();
|
// Transform the G-code.
|
||||||
|
// First sort the adjustment lines by their position in the source G-code.
|
||||||
|
std::vector<const Adjustment::Line*> lines;
|
||||||
|
{
|
||||||
|
size_t n_lines = 0;
|
||||||
|
for (const Adjustment &adj : adjustments)
|
||||||
|
n_lines += adj.lines.size();
|
||||||
|
lines.reserve(n_lines);
|
||||||
|
for (const Adjustment &adj : adjustments)
|
||||||
|
for (const Adjustment::Line &line : adj.lines)
|
||||||
|
lines.emplace_back(&line);
|
||||||
|
std::sort(lines.begin(), lines.end(), [](const Adjustment::Line *ln1, const Adjustment::Line *ln2) { return ln1->line_start < ln2->line_start; } );
|
||||||
|
}
|
||||||
|
// Second generate the adjusted G-code.
|
||||||
|
std::string new_gcode;
|
||||||
|
new_gcode.reserve(gcode.size() * 2);
|
||||||
|
int fan_speed = -1;
|
||||||
|
bool bridge_fan_control = false;
|
||||||
|
int bridge_fan_speed = 0;
|
||||||
|
auto change_extruder_set_fan = [ this, layer_id, elapsed_time_total, &new_gcode, &fan_speed, &bridge_fan_control, &bridge_fan_speed ]() {
|
||||||
|
const FullPrintConfig &config = m_gcodegen.config();
|
||||||
|
int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed);
|
||||||
|
int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0;
|
||||||
|
if (layer_id >= EXTRUDER_CONFIG(disable_fan_first_layers)) {
|
||||||
|
int max_fan_speed = EXTRUDER_CONFIG(max_fan_speed);
|
||||||
|
float slowdown_below_layer_time = float(EXTRUDER_CONFIG(slowdown_below_layer_time));
|
||||||
|
float fan_below_layer_time = float(EXTRUDER_CONFIG(fan_below_layer_time));
|
||||||
|
if (EXTRUDER_CONFIG(cooling)) {
|
||||||
|
if (elapsed_time_total < slowdown_below_layer_time) {
|
||||||
|
// Layer time very short. Enable the fan to a full throttle.
|
||||||
|
fan_speed_new = max_fan_speed;
|
||||||
|
} else if (elapsed_time_total < fan_below_layer_time) {
|
||||||
|
// Layer time quite short. Enable the fan proportionally according to the current layer time.
|
||||||
|
assert(elapsed_time_total >= slowdown_below_layer_time);
|
||||||
|
double t = (elapsed_time_total - slowdown_below_layer_time) / (fan_below_layer_time - slowdown_below_layer_time);
|
||||||
|
fan_speed_new = int(floor(t * min_fan_speed + (1. - t) * max_fan_speed) + 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bridge_fan_speed = EXTRUDER_CONFIG(bridge_fan_speed);
|
||||||
|
bridge_fan_control = bridge_fan_speed > fan_speed_new;
|
||||||
|
} else {
|
||||||
|
bridge_fan_control = false;
|
||||||
|
bridge_fan_speed = 0;
|
||||||
|
}
|
||||||
|
if (fan_speed_new != fan_speed) {
|
||||||
|
fan_speed = fan_speed_new;
|
||||||
|
new_gcode += m_gcodegen.writer().set_fan(fan_speed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
change_extruder_set_fan();
|
||||||
|
|
||||||
|
size_t pos = 0;
|
||||||
|
for (const Adjustment::Line *line : lines) {
|
||||||
|
if (line->line_start > pos)
|
||||||
|
new_gcode.append(gcode.c_str() + pos, line->line_start - pos);
|
||||||
|
if (line->type & Adjustment::Line::TYPE_SET_TOOL) {
|
||||||
|
unsigned int new_extruder = (unsigned int)atoi(gcode.c_str() + line->line_start + toolchange_prefix.size());
|
||||||
|
if (new_extruder != m_current_extruder) {
|
||||||
|
m_current_extruder = new_extruder;
|
||||||
|
change_extruder_set_fan();
|
||||||
|
}
|
||||||
|
} else if (line->type & Adjustment::Line::TYPE_BRIDGE_FAN_START) {
|
||||||
|
if (bridge_fan_control)
|
||||||
|
new_gcode += m_gcodegen.writer().set_fan(bridge_fan_speed, true) + "\n";
|
||||||
|
} else if (line->type & Adjustment::Line::TYPE_BRIDGE_FAN_END) {
|
||||||
|
if (bridge_fan_control)
|
||||||
|
new_gcode += m_gcodegen.writer().set_fan(fan_speed, true) + "\n";
|
||||||
|
} else if (line->type & Adjustment::Line::TYPE_EXTRUDE_END) {
|
||||||
|
// Just remove this comment.
|
||||||
|
} else if (line->type & (Adjustment::Line::TYPE_ADJUSTABLE | Adjustment::Line::TYPE_EXTERNAL_PERIMETER | Adjustment::Line::TYPE_WIPE)) {
|
||||||
|
// Start of the comment. The line type indicates there must be some comment present.
|
||||||
|
const char *end = strchr(gcode.c_str() + line->line_start, ';');
|
||||||
|
if (line->slowdown) {
|
||||||
|
// Replace the feedrate.
|
||||||
|
const char *pos = strstr(gcode.c_str() + line->line_start + 2, " F") + 2;
|
||||||
|
new_gcode.append(gcode.c_str() + line->line_start, pos - gcode.c_str() - line->line_start);
|
||||||
|
char buf[64];
|
||||||
|
sprintf(buf, "%d", int(floor(60. * (line->length / line->time) + 0.5)));
|
||||||
|
new_gcode += buf;
|
||||||
|
// Skip the non-whitespaces up to the comment.
|
||||||
|
for (; *pos != ' ' && *pos != ';'; ++ pos);
|
||||||
|
// Append the rest of the line without the comment.
|
||||||
|
if (pos < end)
|
||||||
|
new_gcode.append(pos, end - pos);
|
||||||
|
} else {
|
||||||
|
// Append the line without the comment.
|
||||||
|
new_gcode.append(gcode.c_str() + line->line_start, end - gcode.c_str() - line->line_start);
|
||||||
|
}
|
||||||
|
// Process the comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE"
|
||||||
|
std::string comment(end, gcode.c_str() + line->line_end);
|
||||||
|
boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", "");
|
||||||
|
if (line->type & Adjustment::Line::TYPE_EXTERNAL_PERIMETER)
|
||||||
|
boost::replace_all(comment, ";_EXTERNAL_PERIMETER", "");
|
||||||
|
if (line->type & Adjustment::Line::TYPE_WIPE)
|
||||||
|
boost::replace_all(comment, ";_WIPE", "");
|
||||||
|
new_gcode += comment;
|
||||||
|
} else {
|
||||||
|
new_gcode.append(gcode.c_str() + line->line_start, line->line_end - line->line_start);
|
||||||
|
}
|
||||||
|
pos = line->line_end;
|
||||||
|
}
|
||||||
|
if (pos < gcode.size())
|
||||||
|
new_gcode.append(gcode.c_str() + pos, gcode.size() - pos);
|
||||||
|
|
||||||
m_gcodegen.writer().reset_elapsed_times();
|
m_gcodegen.writer().reset_elapsed_times();
|
||||||
return gcode;
|
return new_gcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} // namespace Slic3r
|
||||||
|
@ -13,7 +13,10 @@ class Layer;
|
|||||||
struct ElapsedTime
|
struct ElapsedTime
|
||||||
{
|
{
|
||||||
ElapsedTime(unsigned int extruder_id = 0) : extruder_id(extruder_id) { this->reset(); }
|
ElapsedTime(unsigned int extruder_id = 0) : extruder_id(extruder_id) { this->reset(); }
|
||||||
void reset() { total = bridges = external_perimeters = travel = other = 0.f; }
|
void reset() {
|
||||||
|
total = bridges = external_perimeters = travel = other = 0.f;
|
||||||
|
max_stretch_time_total = max_stretch_time_no_ext_perimetes = 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
ElapsedTime& operator+=(const ElapsedTime &rhs) {
|
ElapsedTime& operator+=(const ElapsedTime &rhs) {
|
||||||
this->total += rhs.total;
|
this->total += rhs.total;
|
||||||
@ -21,13 +24,17 @@ struct ElapsedTime
|
|||||||
this->external_perimeters += rhs.external_perimeters;
|
this->external_perimeters += rhs.external_perimeters;
|
||||||
this->travel += rhs.travel;
|
this->travel += rhs.travel;
|
||||||
this->other += rhs.other;
|
this->other += rhs.other;
|
||||||
|
this->max_stretch_time_total += rhs.max_stretch_time_total;
|
||||||
|
this->max_stretch_time_no_ext_perimetes += rhs.max_stretch_time_no_ext_perimetes;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Potion of the total time, which cannot be stretched to heed the minimum layer print time.
|
// Potion of the total time, which cannot be stretched to heed the minimum layer print time.
|
||||||
float non_stretchable() const { return this->bridges + this->travel + this->other; }
|
float non_stretchable(bool stretch_external_perimeters = true) const
|
||||||
|
{ return this->bridges + this->travel + this->other + (stretch_external_perimeters ? 0.f : this->external_perimeters); }
|
||||||
// Potion of the total time, which could be stretched to heed the minimum layer print time.
|
// Potion of the total time, which could be stretched to heed the minimum layer print time.
|
||||||
float stretchable() const { return this->total - this->non_stretchable(); }
|
float stretchable(bool stretch_external_perimeters = true) const
|
||||||
|
{ return this->total - this->non_stretchable(stretch_external_perimeters); }
|
||||||
|
|
||||||
// For which extruder ID has this statistics been collected?
|
// For which extruder ID has this statistics been collected?
|
||||||
unsigned int extruder_id;
|
unsigned int extruder_id;
|
||||||
@ -38,6 +45,11 @@ struct ElapsedTime
|
|||||||
float external_perimeters;
|
float external_perimeters;
|
||||||
float travel;
|
float travel;
|
||||||
float other;
|
float other;
|
||||||
|
// Per feature maximum time, to which the extrusion could be stretched to respect the extruder specific min_print_speed.
|
||||||
|
// Maximum stretch time, to which the time this->stretchable() could be extended.
|
||||||
|
float max_stretch_time_total;
|
||||||
|
// Maximum stretch time, to which the time (this->stretchable() - external_perimeters) could be extended.
|
||||||
|
float max_stretch_time_no_ext_perimetes;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sort ElapsedTime objects by the extruder id by default.
|
// Sort ElapsedTime objects by the extruder id by default.
|
||||||
@ -54,7 +66,8 @@ and the print is modified to stretch over a minimum layer time.
|
|||||||
|
|
||||||
class CoolingBuffer {
|
class CoolingBuffer {
|
||||||
public:
|
public:
|
||||||
CoolingBuffer(GCode &gcodegen) : m_gcodegen(gcodegen) {}
|
CoolingBuffer(GCode &gcodegen);
|
||||||
|
void reset();
|
||||||
void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; }
|
void set_current_extruder(unsigned int extruder_id) { m_current_extruder = extruder_id; }
|
||||||
std::string process_layer(const std::string &gcode, size_t layer_id);
|
std::string process_layer(const std::string &gcode, size_t layer_id);
|
||||||
GCode* gcodegen() { return &m_gcodegen; }
|
GCode* gcodegen() { return &m_gcodegen; }
|
||||||
@ -64,8 +77,11 @@ private:
|
|||||||
|
|
||||||
GCode& m_gcodegen;
|
GCode& m_gcodegen;
|
||||||
std::string m_gcode;
|
std::string m_gcode;
|
||||||
|
// Internal data.
|
||||||
|
// X,Y,Z,E,F
|
||||||
|
std::vector<char> m_axis;
|
||||||
|
std::vector<float> m_current_pos;
|
||||||
unsigned int m_current_extruder;
|
unsigned int m_current_extruder;
|
||||||
std::set<size_t> m_object_ids_visited;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,15 +6,13 @@
|
|||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
void
|
void GCodeReader::apply_config(const PrintConfigBase &config)
|
||||||
GCodeReader::apply_config(const PrintConfigBase &config)
|
|
||||||
{
|
{
|
||||||
this->_config.apply(config, true);
|
m_config.apply(config, true);
|
||||||
this->_extrusion_axis = this->_config.get_extrusion_axis()[0];
|
m_extrusion_axis = m_config.get_extrusion_axis()[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void GCodeReader::parse(const std::string &gcode, callback_t callback)
|
||||||
GCodeReader::parse(const std::string &gcode, callback_t callback)
|
|
||||||
{
|
{
|
||||||
std::istringstream ss(gcode);
|
std::istringstream ss(gcode);
|
||||||
std::string line;
|
std::string line;
|
||||||
@ -22,8 +20,7 @@ GCodeReader::parse(const std::string &gcode, callback_t callback)
|
|||||||
this->parse_line(line, callback);
|
this->parse_line(line, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void GCodeReader::parse_line(std::string line, callback_t callback)
|
||||||
GCodeReader::parse_line(std::string line, callback_t callback)
|
|
||||||
{
|
{
|
||||||
GCodeLine gline(this);
|
GCodeLine gline(this);
|
||||||
gline.raw = line;
|
gline.raw = line;
|
||||||
@ -55,15 +52,15 @@ GCodeReader::parse_line(std::string line, callback_t callback)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// convert extrusion axis
|
// convert extrusion axis
|
||||||
if (this->_extrusion_axis != 'E') {
|
if (m_extrusion_axis != 'E') {
|
||||||
const auto it = gline.args.find(this->_extrusion_axis);
|
const auto it = gline.args.find(m_extrusion_axis);
|
||||||
if (it != gline.args.end()) {
|
if (it != gline.args.end()) {
|
||||||
std::swap(gline.args['E'], it->second);
|
std::swap(gline.args['E'], it->second);
|
||||||
gline.args.erase(it);
|
gline.args.erase(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gline.has('E') && this->_config.use_relative_e_distances)
|
if (gline.has('E') && m_config.use_relative_e_distances)
|
||||||
this->E = 0;
|
this->E = 0;
|
||||||
|
|
||||||
if (callback) callback(*this, gline);
|
if (callback) callback(*this, gline);
|
||||||
@ -78,8 +75,7 @@ GCodeReader::parse_line(std::string line, callback_t callback)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void GCodeReader::parse_file(const std::string &file, callback_t callback)
|
||||||
GCodeReader::parse_file(const std::string &file, callback_t callback)
|
|
||||||
{
|
{
|
||||||
std::ifstream f(file);
|
std::ifstream f(file);
|
||||||
std::string line;
|
std::string line;
|
||||||
@ -87,8 +83,7 @@ GCodeReader::parse_file(const std::string &file, callback_t callback)
|
|||||||
this->parse_line(line, callback);
|
this->parse_line(line, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void GCodeReader::GCodeLine::set(char arg, std::string value)
|
||||||
GCodeReader::GCodeLine::set(char arg, std::string value)
|
|
||||||
{
|
{
|
||||||
const std::string space(" ");
|
const std::string space(" ");
|
||||||
if (this->has(arg)) {
|
if (this->has(arg)) {
|
||||||
|
@ -40,7 +40,7 @@ public:
|
|||||||
};
|
};
|
||||||
bool extruding() const { return this->cmd == "G1" && this->dist_E() > 0; };
|
bool extruding() const { return this->cmd == "G1" && this->dist_E() > 0; };
|
||||||
bool retracting() const { return this->cmd == "G1" && this->dist_E() < 0; };
|
bool retracting() const { return this->cmd == "G1" && this->dist_E() < 0; };
|
||||||
bool travel() const { return this->cmd == "G1" && !this->has('E'); };
|
bool travel() const { return this->cmd == "G1" && ! this->has('E'); };
|
||||||
void set(char arg, std::string value);
|
void set(char arg, std::string value);
|
||||||
};
|
};
|
||||||
typedef std::function<void(GCodeReader&, const GCodeLine&)> callback_t;
|
typedef std::function<void(GCodeReader&, const GCodeLine&)> callback_t;
|
||||||
@ -49,15 +49,15 @@ public:
|
|||||||
bool verbose;
|
bool verbose;
|
||||||
callback_t callback;
|
callback_t callback;
|
||||||
|
|
||||||
GCodeReader() : X(0), Y(0), Z(0), E(0), F(0), verbose(false), _extrusion_axis('E') {};
|
GCodeReader() : X(0), Y(0), Z(0), E(0), F(0), verbose(false), m_extrusion_axis('E') {};
|
||||||
void apply_config(const PrintConfigBase &config);
|
void apply_config(const PrintConfigBase &config);
|
||||||
void parse(const std::string &gcode, callback_t callback);
|
void parse(const std::string &gcode, callback_t callback);
|
||||||
void parse_line(std::string line, callback_t callback);
|
void parse_line(std::string line, callback_t callback);
|
||||||
void parse_file(const std::string &file, callback_t callback);
|
void parse_file(const std::string &file, callback_t callback);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GCodeConfig _config;
|
GCodeConfig m_config;
|
||||||
char _extrusion_axis;
|
char m_extrusion_axis;
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* namespace Slic3r */
|
} /* namespace Slic3r */
|
||||||
|
@ -181,11 +181,14 @@ std::string GCodeWriter::set_acceleration(unsigned int acceleration)
|
|||||||
|
|
||||||
std::ostringstream gcode;
|
std::ostringstream gcode;
|
||||||
if (FLAVOR_IS(gcfRepetier)) {
|
if (FLAVOR_IS(gcfRepetier)) {
|
||||||
|
// M201: Set max printing acceleration
|
||||||
gcode << "M201 X" << acceleration << " Y" << acceleration;
|
gcode << "M201 X" << acceleration << " Y" << acceleration;
|
||||||
if (this->config.gcode_comments) gcode << " ; adjust acceleration";
|
if (this->config.gcode_comments) gcode << " ; adjust acceleration";
|
||||||
gcode << "\n";
|
gcode << "\n";
|
||||||
|
// M202: Set max travel acceleration
|
||||||
gcode << "M202 X" << acceleration << " Y" << acceleration;
|
gcode << "M202 X" << acceleration << " Y" << acceleration;
|
||||||
} else {
|
} else {
|
||||||
|
// M204: Set default acceleration
|
||||||
gcode << "M204 S" << acceleration;
|
gcode << "M204 S" << acceleration;
|
||||||
}
|
}
|
||||||
if (this->config.gcode_comments) gcode << " ; adjust acceleration";
|
if (this->config.gcode_comments) gcode << " ; adjust acceleration";
|
||||||
@ -233,6 +236,12 @@ std::string GCodeWriter::update_progress(unsigned int num, unsigned int tot, boo
|
|||||||
return gcode.str();
|
return gcode.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string GCodeWriter::toolchange_prefix() const
|
||||||
|
{
|
||||||
|
return FLAVOR_IS(gcfMakerWare) ? "M135 T" :
|
||||||
|
FLAVOR_IS(gcfSailfish) ? "M108 T" : "T";
|
||||||
|
}
|
||||||
|
|
||||||
std::string GCodeWriter::toolchange(unsigned int extruder_id)
|
std::string GCodeWriter::toolchange(unsigned int extruder_id)
|
||||||
{
|
{
|
||||||
// set the new extruder
|
// set the new extruder
|
||||||
@ -248,17 +257,10 @@ std::string GCodeWriter::toolchange(unsigned int extruder_id)
|
|||||||
// if we are running a single-extruder setup, just set the extruder and return nothing
|
// if we are running a single-extruder setup, just set the extruder and return nothing
|
||||||
std::ostringstream gcode;
|
std::ostringstream gcode;
|
||||||
if (this->multiple_extruders) {
|
if (this->multiple_extruders) {
|
||||||
if (FLAVOR_IS(gcfMakerWare)) {
|
gcode << this->toolchange_prefix() << extruder_id;
|
||||||
gcode << "M135 T";
|
if (this->config.gcode_comments)
|
||||||
} else if (FLAVOR_IS(gcfSailfish)) {
|
gcode << " ; change extruder";
|
||||||
gcode << "M108 T";
|
|
||||||
} else {
|
|
||||||
gcode << "T";
|
|
||||||
}
|
|
||||||
gcode << extruder_id;
|
|
||||||
if (this->config.gcode_comments) gcode << " ; change extruder";
|
|
||||||
gcode << "\n";
|
gcode << "\n";
|
||||||
|
|
||||||
gcode << this->reset_e(true);
|
gcode << this->reset_e(true);
|
||||||
}
|
}
|
||||||
return gcode.str();
|
return gcode.str();
|
||||||
|
@ -54,6 +54,9 @@ public:
|
|||||||
{ return m_extruder == nullptr || m_extruder->id() != extruder_id; }
|
{ return m_extruder == nullptr || m_extruder->id() != extruder_id; }
|
||||||
std::string set_extruder(unsigned int extruder_id)
|
std::string set_extruder(unsigned int extruder_id)
|
||||||
{ return this->need_toolchange(extruder_id) ? this->toolchange(extruder_id) : ""; }
|
{ return this->need_toolchange(extruder_id) ? this->toolchange(extruder_id) : ""; }
|
||||||
|
// Prefix of the toolchange G-code line, to be used by the CoolingBuffer to separate sections of the G-code
|
||||||
|
// printed with the same extruder.
|
||||||
|
std::string toolchange_prefix() const;
|
||||||
std::string toolchange(unsigned int extruder_id);
|
std::string toolchange(unsigned int extruder_id);
|
||||||
std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string()) const;
|
std::string set_speed(double F, const std::string &comment = std::string(), const std::string &cooling_marker = std::string()) const;
|
||||||
std::string travel_to_xy(const Pointf &point, const std::string &comment = std::string());
|
std::string travel_to_xy(const Pointf &point, const std::string &comment = std::string());
|
||||||
|
Loading…
Reference in New Issue
Block a user