test_retraction.cpp 13 KB

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