test_retraction.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. /**
  2. * Ported from t/retraction.t
  3. */
  4. #include <catch2/catch.hpp>
  5. #include <libslic3r/GCodeReader.hpp>
  6. #include <libslic3r/Config.hpp>
  7. #include "test_data.hpp"
  8. #include <regex>
  9. #include <fstream>
  10. using namespace Slic3r;
  11. using namespace Test;
  12. constexpr bool debug_files {false};
  13. void check_gcode(std::initializer_list<TestMesh> meshes, const DynamicPrintConfig& config, const unsigned duplicate) {
  14. constexpr std::size_t tools_count = 4;
  15. std::size_t tool = 0;
  16. std::array<unsigned, tools_count> toolchange_count{0}; // Track first usages so that we don't expect retract_length_toolchange when extruders are used for the first time
  17. std::array<bool, tools_count> retracted{false};
  18. std::array<double, tools_count> retracted_length{0};
  19. bool lifted = false;
  20. double lift_dist = 0; // Track lifted distance for toolchanges and extruders with different retract_lift values
  21. bool changed_tool = false;
  22. bool wait_for_toolchange = false;
  23. Print print;
  24. Model model;
  25. Test::init_print({TestMesh::cube_20x20x20}, print, model, config, false, duplicate);
  26. std::string gcode = Test::gcode(print);
  27. GCodeReader parser;
  28. parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
  29. std::regex regex{"^T(\\d+)"};
  30. std::smatch matches;
  31. std::string cmd{line.cmd()};
  32. if (std::regex_match(cmd, matches, regex)) {
  33. tool = std::stoul(matches[1].str());
  34. changed_tool = true;
  35. wait_for_toolchange = false;
  36. toolchange_count[tool]++;
  37. } else if (std::regex_match(cmd, std::regex{"^G[01]$"}) && !line.has(Z)) { // ignore lift taking place after retraction
  38. INFO("Toolchange must not happen right after retraction.");
  39. CHECK(!wait_for_toolchange);
  40. }
  41. const double retract_length = config.option<ConfigOptionFloats>("retract_length")->get_at(tool);
  42. const double retract_before_travel = config.option<ConfigOptionFloats>("retract_before_travel")->get_at(tool);
  43. const double retract_length_toolchange = config.option<ConfigOptionFloats>("retract_length_toolchange")->get_at(tool);
  44. const double retract_restart_extra = config.option<ConfigOptionFloats>("retract_restart_extra")->get_at(tool);
  45. const double retract_restart_extra_toolchange = config.option<ConfigOptionFloats>("retract_restart_extra_toolchange")->get_at(tool);
  46. if (line.dist_Z(self) != 0) {
  47. // lift move or lift + change layer
  48. const double retract_lift = config.option<ConfigOptionFloats>("retract_lift")->get_at(tool);
  49. if (
  50. line.dist_Z(self) == Approx(retract_lift)
  51. || (
  52. line.dist_Z(self) == Approx(config.opt_float("layer_height") + retract_lift)
  53. && retract_lift > 0
  54. )
  55. ) {
  56. INFO("Only lift while retracted");
  57. CHECK(retracted[tool]);
  58. INFO("No double lift");
  59. CHECK(!lifted);
  60. lifted = true;
  61. lift_dist = line.dist_Z(self);
  62. }
  63. if (line.dist_Z(self) < 0) {
  64. INFO("Must be lifted before going down.")
  65. CHECK(lifted);
  66. INFO("Going down by the same amount of the lift or by the amount needed to get to next layer");
  67. CHECK((
  68. line.dist_Z(self) == Approx(-lift_dist)
  69. || line.dist_Z(self) == Approx(-lift_dist + config.opt_float("layer_height"))
  70. ));
  71. lift_dist = 0;
  72. lifted = false;
  73. }
  74. const double feedrate = line.has_f() ? line.f() : self.f();
  75. INFO("move Z at travel speed");
  76. CHECK(feedrate == Approx(config.opt_float("travel_speed") * 60));
  77. }
  78. if (line.retracting(self)) {
  79. retracted[tool] = true;
  80. retracted_length[tool] += -line.dist_E(self);
  81. if (retracted_length[tool] == Approx(retract_length)) {
  82. // okay
  83. } else if (retracted_length[tool] == Approx(retract_length_toolchange)) {
  84. wait_for_toolchange = true;
  85. } else {
  86. INFO("Not retracted by the correct amount.");
  87. CHECK(false);
  88. }
  89. }
  90. if (line.extruding(self)) {
  91. INFO("Only extruding while not lifted");
  92. CHECK(!lifted);
  93. if (retracted[tool]) {
  94. double expected_amount = retracted_length[tool] + retract_restart_extra;
  95. if (changed_tool && toolchange_count[tool] > 1) {
  96. expected_amount = retract_length_toolchange + retract_restart_extra_toolchange;
  97. changed_tool = false;
  98. }
  99. INFO("Unretracted by the correct amount");
  100. REQUIRE(line.dist_E(self) == Approx(expected_amount));
  101. retracted[tool] = false;
  102. retracted_length[tool] = 0;
  103. }
  104. }
  105. if (line.travel() && line.dist_XY(self) >= retract_before_travel) {
  106. INFO("Retracted before long travel move");
  107. CHECK(retracted[tool]);
  108. }
  109. });
  110. }
  111. void test_slicing(std::initializer_list<TestMesh> meshes, DynamicPrintConfig& config, const unsigned duplicate = 1) {
  112. SECTION("Retraction") {
  113. check_gcode(meshes, config, duplicate);
  114. }
  115. SECTION("Restart extra length") {
  116. config.set_deserialize_strict({{ "retract_restart_extra", "1" }});
  117. check_gcode(meshes, config, duplicate);
  118. }
  119. SECTION("Negative restart extra length") {
  120. config.set_deserialize_strict({{ "retract_restart_extra", "-1" }});
  121. check_gcode(meshes, config, duplicate);
  122. }
  123. SECTION("Retract_lift") {
  124. config.set_deserialize_strict({{ "retract_lift", "1,2" }});
  125. check_gcode(meshes, config, duplicate);
  126. }
  127. }
  128. TEST_CASE("Slicing with retraction and lifing", "[retraction]") {
  129. DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
  130. config.set_deserialize_strict({
  131. { "nozzle_diameter", "0.6,0.6,0.6,0.6" },
  132. { "first_layer_height", config.opt_float("layer_height") },
  133. { "first_layer_speed", "100%" },
  134. { "start_gcode", "" }, // To avoid dealing with the nozzle lift in start G-code
  135. { "retract_length", "1.5" },
  136. { "retract_before_travel", "3" },
  137. { "retract_layer_change", "1" },
  138. { "only_retract_when_crossing_perimeters", 0 },
  139. });
  140. SECTION("Standard run") {
  141. test_slicing({TestMesh::cube_20x20x20}, config);
  142. }
  143. SECTION("With duplicate cube") {
  144. test_slicing({TestMesh::cube_20x20x20}, config, 2);
  145. }
  146. SECTION("Dual extruder with multiple skirt layers") {
  147. config.set_deserialize_strict({
  148. {"infill_extruder", 2},
  149. {"skirts", 4},
  150. {"skirt_height", 3},
  151. });
  152. test_slicing({TestMesh::cube_20x20x20}, config);
  153. }
  154. }
  155. TEST_CASE("Z moves", "[retraction]") {
  156. DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
  157. config.set_deserialize_strict({
  158. { "start_gcode", "" }, // To avoid dealing with the nozzle lift in start G-code
  159. { "retract_length", "0" },
  160. { "retract_layer_change", "0" },
  161. { "retract_lift", "0.2" },
  162. });
  163. bool retracted = false;
  164. unsigned layer_changes_with_retraction = 0;
  165. unsigned retractions = 0;
  166. unsigned z_restores = 0;
  167. std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
  168. if constexpr(debug_files) {
  169. std::ofstream file{"zmoves.gcode"};
  170. file << gcode;
  171. }
  172. GCodeReader parser;
  173. parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
  174. if (line.retracting(self)) {
  175. retracted = true;
  176. retractions++;
  177. } else if (line.extruding(self) && retracted) {
  178. retracted = 0;
  179. }
  180. if (line.dist_Z(self) != 0 && retracted) {
  181. layer_changes_with_retraction++;
  182. }
  183. if (line.dist_Z(self) < 0) {
  184. z_restores++;
  185. }
  186. });
  187. INFO("no retraction on layer change");
  188. CHECK(layer_changes_with_retraction == 0);
  189. INFO("no retractions");
  190. CHECK(retractions == 0);
  191. INFO("no lift other than for the first move");
  192. CHECK(z_restores == 1);
  193. }
  194. TEST_CASE("Firmware retraction handling", "[retraction]") {
  195. DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
  196. config.set_deserialize_strict({
  197. { "use_firmware_retraction", 1 },
  198. });
  199. bool retracted = false;
  200. unsigned double_retractions = 0;
  201. unsigned double_unretractions = 0;
  202. std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
  203. GCodeReader parser;
  204. parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
  205. if (line.cmd_is("G10")) {
  206. if (retracted)
  207. double_retractions++;
  208. retracted = true;
  209. } else if (line.cmd_is("G11")) {
  210. if (!retracted)
  211. double_unretractions++;
  212. retracted = 0;
  213. }
  214. });
  215. INFO("No double retractions");
  216. CHECK(double_retractions == 0);
  217. INFO("No double unretractions");
  218. CHECK(double_unretractions == 0);
  219. }
  220. TEST_CASE("Firmware retraction when length is 0", "[retraction]") {
  221. DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
  222. config.set_deserialize_strict({
  223. { "use_firmware_retraction", 1 },
  224. { "retract_length", "0" },
  225. });
  226. bool retracted = false;
  227. std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
  228. GCodeReader parser;
  229. parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
  230. if (line.cmd_is("G10")) {
  231. retracted = true;
  232. }
  233. });
  234. INFO("Retracting also when --retract-length is 0 but --use-firmware-retraction is enabled");
  235. CHECK(retracted);
  236. }
  237. std::vector<double> get_lift_layers(const DynamicPrintConfig& config) {
  238. Print print;
  239. Model model;
  240. Test::init_print({TestMesh::cube_20x20x20}, print, model, config, false, 2);
  241. std::string gcode = Test::gcode(print);
  242. std::vector<double> result;
  243. GCodeReader parser;
  244. parser.parse_buffer(gcode, [&] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
  245. if (line.cmd_is("G1") && line.dist_Z(self) < 0) {
  246. result.push_back(line.new_Z(self));
  247. }
  248. });
  249. return result;
  250. }
  251. bool values_are_in_range(const std::vector<double>& values, double from, double to) {
  252. for (const double& value : values) {
  253. if (value < from || value > to) {
  254. return false;
  255. }
  256. }
  257. return true;
  258. }
  259. TEST_CASE("Lift above/bellow layers", "[retraction]") {
  260. DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
  261. config.set_deserialize_strict({
  262. { "nozzle_diameter", "0.6,0.6,0.6,0.6" },
  263. { "start_gcode", "" },
  264. { "retract_lift", "3,4" },
  265. });
  266. config.set_deserialize_strict({
  267. { "retract_lift_above", "0, 0" },
  268. { "retract_lift_below", "0, 0" },
  269. });
  270. std::vector<double> lift_layers = get_lift_layers(config);
  271. INFO("lift takes place when above/below == 0");
  272. CHECK(!lift_layers.empty());
  273. config.set_deserialize_strict({
  274. { "retract_lift_above", "5, 6" },
  275. { "retract_lift_below", "15, 13" },
  276. });
  277. lift_layers = get_lift_layers(config);
  278. INFO("lift takes place when above/below != 0");
  279. CHECK(!lift_layers.empty());
  280. double retract_lift_above = config.option<ConfigOptionFloats>("retract_lift_above")->get_at(0);
  281. double retract_lift_below = config.option<ConfigOptionFloats>("retract_lift_below")->get_at(0);
  282. INFO("Z is not lifted above/below the configured value");
  283. CHECK(values_are_in_range(lift_layers, retract_lift_above, retract_lift_below));
  284. // check lifting with different values for 2. extruder
  285. config.set_deserialize_strict({
  286. {"perimeter_extruder", 2},
  287. {"infill_extruder", 2},
  288. {"retract_lift_above", "0, 0"},
  289. {"retract_lift_below", "0, 0"}
  290. });
  291. lift_layers = get_lift_layers(config);
  292. INFO("lift takes place when above/below == 0 for 2. extruder");
  293. CHECK(!lift_layers.empty());
  294. config.set_deserialize_strict({
  295. { "retract_lift_above", "5, 6" },
  296. { "retract_lift_below", "15, 13" },
  297. });
  298. lift_layers = get_lift_layers(config);
  299. INFO("lift takes place when above/below != 0 for 2. extruder");
  300. CHECK(!lift_layers.empty());
  301. retract_lift_above = config.option<ConfigOptionFloats>("retract_lift_above")->get_at(1);
  302. retract_lift_below = config.option<ConfigOptionFloats>("retract_lift_below")->get_at(1);
  303. INFO("Z is not lifted above/below the configured value for 2. extruder");
  304. CHECK(values_are_in_range(lift_layers, retract_lift_above, retract_lift_below));
  305. }