From 852faddc5b92039af48b1d7c8786ac8a74972b98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Fri, 1 Dec 2023 16:31:30 +0100 Subject: [PATCH] Rewrite retraction.t to c++ --- t/retraction.t | 260 --------------------- tests/fff_print/CMakeLists.txt | 1 + tests/fff_print/test_retraction.cpp | 337 ++++++++++++++++++++++++++++ 3 files changed, 338 insertions(+), 260 deletions(-) delete mode 100644 t/retraction.t create mode 100644 tests/fff_print/test_retraction.cpp diff --git a/t/retraction.t b/t/retraction.t deleted file mode 100644 index f773c3f080..0000000000 --- a/t/retraction.t +++ /dev/null @@ -1,260 +0,0 @@ -use Test::More tests => 26; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; - use local::lib "$FindBin::Bin/../local-lib"; -} - -use List::Util qw(any); -use Slic3r; -use Slic3r::Test qw(_eq); - -{ - my $config = Slic3r::Config::new_from_defaults; - my $duplicate = 1; - - my $test = sub { - my ($conf) = @_; - $conf ||= $config; - - my $print = Slic3r::Test::init_print('20mm_cube', config => $conf, duplicate => $duplicate); - - my $tool = 0; - my @toolchange_count = (); # track first usages so that we don't expect retract_length_toolchange when extruders are used for the first time - my @retracted = (0); - my @retracted_length = (0); - my $lifted = 0; - my $lift_dist = 0; # track lifted distance for toolchanges and extruders with different retract_lift values - my $changed_tool = 0; - my $wait_for_toolchange = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd =~ /^T(\d+)/) { - $tool = $1; - $changed_tool = 1; - $wait_for_toolchange = 0; - $toolchange_count[$tool] //= 0; - $toolchange_count[$tool]++; - } elsif ($cmd =~ /^G[01]$/ && !$args->{Z}) { # ignore lift taking place after retraction - fail 'toolchange happens right after retraction' if $wait_for_toolchange; - } - - if ($info->{dist_Z}) { - # lift move or lift + change layer - if (_eq($info->{dist_Z}, $print->print->config->get_at('retract_lift', $tool)) - || (_eq($info->{dist_Z}, $conf->layer_height + $print->print->config->get_at('retract_lift', $tool)) && $print->print->config->get_at('retract_lift', $tool) > 0)) { - fail 'only lifting while retracted' if !$retracted[$tool]; - fail 'double lift' if $lifted; - $lifted = 1; - $lift_dist = $info->{dist_Z}; - } - if ($info->{dist_Z} < 0) { - fail 'going down only after lifting' if !$lifted; - fail 'going down by the same amount of the lift or by the amount needed to get to next layer' - if !_eq($info->{dist_Z}, -$lift_dist) - && !_eq($info->{dist_Z}, -lift_dist + $conf->layer_height); - $lift_dist = 0; - $lifted = 0; - } - fail 'move Z at travel speed' if ($args->{F} // $self->F) != $conf->travel_speed * 60; - } - if ($info->{retracting}) { - $retracted[$tool] = 1; - $retracted_length[$tool] += -$info->{dist_E}; - if (_eq($retracted_length[$tool], $print->print->config->get_at('retract_length', $tool))) { - # okay - } elsif (_eq($retracted_length[$tool], $print->print->config->get_at('retract_length_toolchange', $tool))) { - $wait_for_toolchange = 1; - } else { - fail 'retracted by the correct amount'; - } - } - if ($info->{extruding}) { - fail 'only extruding while not lifted' if $lifted; - if ($retracted[$tool]) { - my $expected_amount = $retracted_length[$tool] + $print->print->config->get_at('retract_restart_extra', $tool); - if ($changed_tool && $toolchange_count[$tool] > 1) { - $expected_amount = $print->print->config->get_at('retract_length_toolchange', $tool) + $print->print->config->get_at('retract_restart_extra_toolchange', $tool); - $changed_tool = 0; - } - fail 'unretracted by the correct amount' && exit - if !_eq($info->{dist_E}, $expected_amount); - $retracted[$tool] = 0; - $retracted_length[$tool] = 0; - } - } - if ($info->{travel} && $info->{dist_XY} >= $print->print->config->get_at('retract_before_travel', $tool)) { - fail 'retracted before long travel move' if !$retracted[$tool]; - } - }); - - 1; - }; - - $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); - $config->set('first_layer_height', $config->layer_height); - $config->set('first_layer_speed', '100%'); - $config->set('start_gcode', ''); # to avoid dealing with the nozzle lift in start G-code - $config->set('retract_length', [1.5]); - $config->set('retract_before_travel', [3]); - $config->set('only_retract_when_crossing_perimeters', 0); - - my $retract_tests = sub { - my ($descr) = @_; - - ok $test->(), "retraction$descr"; - - my $conf = $config->clone; - $conf->set('retract_restart_extra', [1]); - ok $test->($conf), "restart extra length$descr"; - - $conf->set('retract_restart_extra', [-1]); - ok $test->($conf), "negative restart extra length$descr"; - - $conf->set('retract_lift', [1, 2]); - ok $test->($conf), "lift$descr"; - }; - - $retract_tests->(''); - - $duplicate = 2; - $retract_tests->(' (duplicate)'); - - $duplicate = 1; - $config->set('infill_extruder', 2); - $config->set('skirts', 4); - $config->set('skirt_height', 3); - $retract_tests->(' (dual extruder with multiple skirt layers)'); -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('start_gcode', ''); # prevent any default priming Z move from affecting our lift detection - $config->set('retract_length', [0]); - $config->set('retract_layer_change', [0]); - $config->set('retract_lift', [0.2]); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my $retracted = 0; - my $layer_changes_with_retraction = 0; - my $retractions = my $z_restores = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($info->{retracting}) { - $retracted = 1; - $retractions++; - } elsif ($info->{extruding} && $retracted) { - $retracted = 0; - } - - if ($info->{dist_Z} && $retracted) { - $layer_changes_with_retraction++; - } - if ($info->{dist_Z} && $args->{Z} < $self->{Z}) { - $z_restores++; - } - }); - - is $layer_changes_with_retraction, 0, 'no retraction on layer change'; - is $retractions, 0, 'no retractions'; - is $z_restores, 0, 'no lift'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('use_firmware_retraction', 1); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my $retracted = 0; - my $double_retractions = my $double_unretractions = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G10') { - $double_retractions++ if $retracted; - $retracted = 1; - } elsif ($cmd eq 'G11') { - $double_unretractions++ if !$retracted; - $retracted = 0; - } - }); - - is $double_retractions, 0, 'no double retractions'; - is $double_unretractions, 0, 'no double unretractions'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('use_firmware_retraction', 1); - $config->set('retract_length', [0]); - - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my $retracted = 0; - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G10') { - $retracted = 1; - } - }); - - ok $retracted, 'retracting also when --retract-length is 0 but --use-firmware-retraction is enabled'; -} - -{ - my $config = Slic3r::Config::new_from_defaults; - $config->set('nozzle_diameter', [0.6,0.6,0.6,0.6]); - $config->set('start_gcode', ''); - $config->set('retract_lift', [3, 4]); - - my @lifted_at = (); - my $test = sub { - my $print = Slic3r::Test::init_print('20mm_cube', config => $config, duplicate => 2); - @lifted_at = (); - Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { - my ($self, $cmd, $args, $info) = @_; - - if ($cmd eq 'G1' && $info->{dist_Z} < 0) { - push @lifted_at, $info->{new_Z}; - } - }); - }; - - $config->set('retract_lift_above', [0, 0]); - $config->set('retract_lift_below', [0, 0]); - $test->(); - ok !!@lifted_at, 'lift takes place when above/below == 0'; - - $config->set('retract_lift_above', [5, 6]); - $config->set('retract_lift_below', [15, 13]); - $test->(); - ok !!@lifted_at, 'lift takes place when above/below != 0'; - ok !(any { $_ < $config->get_at('retract_lift_above', 0) } @lifted_at), - 'Z is not lifted below the configured value'; - ok !(any { $_ > $config->get_at('retract_lift_below', 0) } @lifted_at), - 'Z is not lifted above the configured value'; - - # check lifting with different values for 2. extruder - $config->set('perimeter_extruder', 2); - $config->set('infill_extruder', 2); - $config->set('retract_lift_above', [0, 0]); - $config->set('retract_lift_below', [0, 0]); - $test->(); - ok !!@lifted_at, 'lift takes place when above/below == 0 for 2. extruder'; - - $config->set('retract_lift_above', [5, 6]); - $config->set('retract_lift_below', [15, 13]); - $test->(); - ok !!@lifted_at, 'lift takes place when above/below != 0 for 2. extruder'; - ok !(any { $_ < $config->get_at('retract_lift_above', 1) } @lifted_at), - 'Z is not lifted below the configured value for 2. extruder'; - ok !(any { $_ > $config->get_at('retract_lift_below', 1) } @lifted_at), - 'Z is not lifted above the configured value for 2. extruder'; -} - -__END__ diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index ed128bf2e0..b21d543969 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -17,6 +17,7 @@ add_executable(${_TEST_NAME}_tests test_gcode_layer_changes.cpp test_gcodefindreplace.cpp test_gcodewriter.cpp + test_retraction.cpp test_model.cpp test_multi.cpp test_perimeters.cpp diff --git a/tests/fff_print/test_retraction.cpp b/tests/fff_print/test_retraction.cpp new file mode 100644 index 0000000000..d75a92368b --- /dev/null +++ b/tests/fff_print/test_retraction.cpp @@ -0,0 +1,337 @@ +/** + * Ported from t/retraction.t + */ + +#include + +#include +#include + +#include "test_data.hpp" +#include + +using namespace Slic3r; +using namespace Test; + +void check_gcode(std::initializer_list meshes, const DynamicPrintConfig& config) { + std::string gcode = Slic3r::Test::slice(meshes, config); + + constexpr std::size_t tools_count = 4; + std::size_t tool = 0; + std::array toolchange_count{0}; // Track first usages so that we don't expect retract_length_toolchange when extruders are used for the first time + std::array retracted{false}; + std::array retracted_length{0}; + bool lifted = false; + double lift_dist = 0; // Track lifted distance for toolchanges and extruders with different retract_lift values + bool changed_tool = false; + bool wait_for_toolchange = false; + + + GCodeReader parser; + parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + std::regex regex{"^T(\\d+)"}; + std::smatch matches; + std::string cmd{line.cmd()}; + if (std::regex_match(cmd, matches, regex)) { + tool = std::stoul(matches[1].str()); + changed_tool = true; + wait_for_toolchange = false; + toolchange_count[tool]++; + } else if (std::regex_match(cmd, std::regex{"^G[01]$"}) && !line.has(Z)) { // ignore lift taking place after retraction + INFO("Toolchange must not happen right after retraction."); + CHECK(!wait_for_toolchange); + } + + const double retract_length = config.option("retract_length")->get_at(tool); + const double retract_before_travel = config.option("retract_before_travel")->get_at(tool); + const double retract_length_toolchange = config.option("retract_length_toolchange")->get_at(tool); + const double retract_restart_extra = config.option("retract_restart_extra")->get_at(tool); + const double retract_restart_extra_toolchange = config.option("retract_restart_extra_toolchange")->get_at(tool); + + if (line.dist_Z(self) != 0) { + // lift move or lift + change layer + const double retract_lift = config.option("retract_lift")->get_at(tool); + if ( + line.dist_Z(self) == Approx(retract_lift) + || ( + line.dist_Z(self) == Approx(config.opt_float("layer_height") + retract_lift) + && retract_lift > 0 + ) + ) { + INFO("Only lift while retracted"); + CHECK(retracted[tool]); + INFO("No double lift"); + CHECK(!lifted); + lifted = true; + lift_dist = line.dist_Z(self); + } + if (line.dist_Z(self) < 0) { + INFO("Must be lifted before going down.") + CHECK(lifted); + INFO("Going down by the same amount of the lift or by the amount needed to get to next layer"); + CHECK(( + line.dist_Z(self) == Approx(-lift_dist) + || line.dist_Z(self) == Approx(-lift_dist + config.opt_float("layer_height")) + )); + lift_dist = 0; + lifted = false; + } + const double feedrate = line.has_f() ? line.f() : self.f(); + INFO("move Z at travel speed"); + CHECK(feedrate == Approx(config.opt_float("travel_speed") * 60)); + } + if (line.retracting(self)) { + retracted[tool] = true; + retracted_length[tool] += -line.dist_E(self); + if (retracted_length[tool] == Approx(retract_length)) { + // okay + } else if (retracted_length[tool] == Approx(retract_length_toolchange)) { + wait_for_toolchange = true; + } else { + INFO("Not retracted by the correct amount."); + CHECK(false); + } + } + if (line.extruding(self)) { + INFO("Only extruding while not lifted"); + CHECK(!lifted); + if (retracted[tool]) { + double expected_amount = retracted_length[tool] + retract_restart_extra; + if (changed_tool && toolchange_count[tool] > 1) { + expected_amount = retract_length_toolchange + retract_restart_extra_toolchange; + changed_tool = false; + } + INFO("Unretracted by the correct amount"); + REQUIRE(line.dist_E(self) == Approx(expected_amount)); + retracted[tool] = false; + retracted_length[tool] = 0; + } + } + if (line.travel() && line.dist_XY(self) >= retract_before_travel) { + INFO("Retracted before long travel move"); + CHECK(retracted[tool]); + } + }); +} + +void test_slicing(std::initializer_list meshes, DynamicPrintConfig& config) { + SECTION("Retraction") { + check_gcode(meshes, config); + } + + SECTION("Restart extra length") { + config.set_deserialize_strict({{ "retract_restart_extra", "1" }}); + check_gcode(meshes, config); + } + + SECTION("Negative restart extra length") { + config.set_deserialize_strict({{ "retract_restart_extra", "-1" }}); + check_gcode(meshes, config); + } + + SECTION("Retract_lift") { + config.set_deserialize_strict({{ "retract_lift", "1,2" }}); + check_gcode(meshes, config); + } + +} + +TEST_CASE("Slicing with retraction and lifing", "[retraction]") { + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "nozzle_diameter", "0.6,0.6,0.6,0.6" }, + { "first_layer_height", config.opt_float("layer_height") }, + { "first_layer_speed", "100%" }, + { "start_gcode", "" }, // To avoid dealing with the nozzle lift in start G-code + { "retract_length", "1.5" }, + { "retract_before_travel", "3" }, + { "only_retract_when_crossing_perimeters", 0 }, + }); + + SECTION("Standard run") { + //test_slicing({TestMesh::cube_20x20x20}, config); + } + SECTION("With duplicate cube") { + //test_slicing({TestMesh::cube_20x20x20, TestMesh::cube_20x20x20}, config); + } + SECTION("Dual extruder with multiple skirt layers") { + config.set_deserialize_strict({ + {"infill_extruder", 2}, + {"skirts", 4}, + {"skirt_height", 3}, + }); + test_slicing({TestMesh::cube_20x20x20}, config); + } +} + +TEST_CASE("Z moves", "[retraction]") { + + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "start_gcode", "" }, // To avoid dealing with the nozzle lift in start G-code + { "retract_length", "0" }, + { "retract_layer_change", "0" }, + { "retract_lift", "0.2" }, + }); + + bool retracted = false; + unsigned layer_changes_with_retraction = 0; + unsigned retractions = 0; + unsigned z_restores = 0; + + std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config); + GCodeReader parser; + parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.retracting(self)) { + retracted = true; + retractions++; + } else if (line.extruding(self) && retracted) { + retracted = 0; + } + + if (line.dist_Z(self) != 0 && retracted) { + layer_changes_with_retraction++; + } + + if (line.dist_Z(self) < 0) { + z_restores++; + } + }); + + INFO("no retraction on layer change"); + CHECK(layer_changes_with_retraction == 0); + INFO("no retractions"); + CHECK(retractions == 0); + INFO("no lift"); + CHECK(z_restores == 0); +} + +TEST_CASE("Firmware retraction handling", "[retraction]") { + + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "use_firmware_retraction", 1 }, + }); + + bool retracted = false; + unsigned double_retractions = 0; + unsigned double_unretractions = 0; + + std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config); + GCodeReader parser; + parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.cmd_is("G10")) { + if (retracted) + double_retractions++; + retracted = true; + } else if (line.cmd_is("G11")) { + if (!retracted) + double_unretractions++; + retracted = 0; + } + }); + INFO("No double retractions"); + CHECK(double_retractions == 0); + INFO("No double unretractions"); + CHECK(double_unretractions == 0); +} + +TEST_CASE("Firmware retraction when length is 0", "[retraction]") { + + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "use_firmware_retraction", 1 }, + { "retract_length", "0" }, + }); + + bool retracted = false; + + std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config); + GCodeReader parser; + parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.cmd_is("G10")) { + retracted = true; + } + }); + INFO("Retracting also when --retract-length is 0 but --use-firmware-retraction is enabled"); + CHECK(retracted); +} + +std::vector get_lift_layers(const DynamicPrintConfig& config) { + std::vector result; + const std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config); + GCodeReader parser; + parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { + if (line.cmd_is("G1") && line.dist_Z(self) < 0) { + result.push_back(line.new_Z(self)); + } + }); + return result; +} + +bool values_are_in_range(const std::vector& values, double from, double to) { + for (const double& value : values) { + if (value < from || value > to) { + return false; + } + } + return true; +} + +TEST_CASE("Lift above/bellow layers", "[retraction]") { + + DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict({ + { "nozzle_diameter", "0.6,0.6,0.6,0.6" }, + { "start_gcode", "" }, + { "retract_lift", "3,4" }, + }); + + config.set_deserialize_strict({ + { "retract_lift_above", "0, 0" }, + { "retract_lift_below", "0, 0" }, + }); + std::vector lift_layers = get_lift_layers(config); + INFO("lift takes place when above/below == 0"); + CHECK(!lift_layers.empty()); + + config.set_deserialize_strict({ + { "retract_lift_above", "5, 6" }, + { "retract_lift_below", "15, 13" }, + }); + lift_layers = get_lift_layers(config); + INFO("lift takes place when above/below != 0"); + CHECK(!lift_layers.empty()); + + double retract_lift_above = config.option("retract_lift_above")->get_at(0); + double retract_lift_below = config.option("retract_lift_below")->get_at(0); + + INFO("Z is not lifted above/below the configured value"); + CHECK(values_are_in_range(lift_layers, retract_lift_above, retract_lift_below)); + + // check lifting with different values for 2. extruder + config.set_deserialize_strict({ + {"perimeter_extruder", 2}, + {"infill_extruder", 2}, + {"retract_lift_above", "0, 0"}, + {"retract_lift_below", "0, 0"} + }); + + lift_layers = get_lift_layers(config); + INFO("lift takes place when above/below == 0 for 2. extruder"); + CHECK(!lift_layers.empty()); + + config.set_deserialize_strict({ + { "retract_lift_above", "5, 6" }, + { "retract_lift_below", "15, 13" }, + }); + lift_layers = get_lift_layers(config); + INFO("lift takes place when above/below != 0 for 2. extruder"); + CHECK(!lift_layers.empty()); + + retract_lift_above = config.option("retract_lift_above")->get_at(1); + retract_lift_below = config.option("retract_lift_below")->get_at(1); + + INFO("Z is not lifted above/below the configured value for 2. extruder"); + CHECK(values_are_in_range(lift_layers, retract_lift_above, retract_lift_below)); +}