test_cooling.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. #include <catch2/catch.hpp>
  2. #include <numeric>
  3. #include <sstream>
  4. #include "test_data.hpp" // get access to init_print, etc
  5. #include "libslic3r/Config.hpp"
  6. #include "libslic3r/GCode.hpp"
  7. #include "libslic3r/GCodeReader.hpp"
  8. #include "libslic3r/GCode/CoolingBuffer.hpp"
  9. #include "libslic3r/libslic3r.h"
  10. using namespace Slic3r;
  11. std::unique_ptr<CoolingBuffer> make_cooling_buffer(
  12. GCodeGenerator &gcode,
  13. const DynamicPrintConfig &config = DynamicPrintConfig{},
  14. const std::vector<unsigned int> &extruder_ids = { 0 })
  15. {
  16. PrintConfig print_config;
  17. print_config.apply(config, true); // ignore_nonexistent
  18. gcode.apply_print_config(print_config);
  19. gcode.set_layer_count(10);
  20. gcode.writer().set_extruders(extruder_ids);
  21. gcode.writer().set_extruder(0);
  22. return std::make_unique<CoolingBuffer>(gcode);
  23. }
  24. SCENARIO("Cooling unit tests", "[Cooling]") {
  25. const std::string gcode1 = "G1 X100 E1 F3000\n";
  26. // 2 sec
  27. const double print_time1 = 100. / (3000. / 60.);
  28. const std::string gcode2 = gcode1 + "G1 X0 E1 F3000\n";
  29. // 4 sec
  30. const double print_time2 = 2. * print_time1;
  31. auto config = DynamicPrintConfig::full_print_config_with({
  32. // Default cooling settings.
  33. { "bridge_fan_speed", "100" },
  34. { "cooling", "1" },
  35. { "fan_always_on", "0" },
  36. { "fan_below_layer_time", "60" },
  37. { "max_fan_speed", "100" },
  38. { "min_print_speed", "10" },
  39. { "slowdown_below_layer_time", "5" },
  40. // Default print speeds.
  41. { "bridge_speed", 60 },
  42. { "external_perimeter_speed", "50%" },
  43. { "first_layer_speed", 30 },
  44. { "gap_fill_speed", 20 },
  45. { "infill_speed", 80 },
  46. { "perimeter_speed", 60 },
  47. { "small_perimeter_speed", 15 },
  48. { "solid_infill_speed", 20 },
  49. { "top_solid_infill_speed", 15 },
  50. { "max_print_speed", 80 },
  51. // Override for tests.
  52. { "disable_fan_first_layers", "0" }
  53. });
  54. WHEN("G-code block 3") {
  55. THEN("speed is not altered when elapsed time is greater than slowdown threshold") {
  56. // Print time of gcode.
  57. const double print_time = 100. / (3000. / 60.);
  58. //FIXME slowdown_below_layer_time is rounded down significantly from 1.8s to 1s.
  59. config.set_deserialize_strict({ { "slowdown_below_layer_time", { int(print_time * 0.999) } } });
  60. GCodeGenerator gcodegen;
  61. auto buffer = make_cooling_buffer(gcodegen, config);
  62. std::string gcode = buffer->process_layer("G1 F3000;_EXTRUDE_SET_SPEED\nG1 X100 E1", 0, true);
  63. bool speed_not_altered = gcode.find("F3000") != gcode.npos;
  64. REQUIRE(speed_not_altered);
  65. }
  66. }
  67. WHEN("G-code block 4") {
  68. const std::string gcode_src =
  69. "G1 X50 F2500\n"
  70. "G1 F3000;_EXTRUDE_SET_SPEED\n"
  71. "G1 X100 E1\n"
  72. ";_EXTRUDE_END\n"
  73. "G1 E4 F400";
  74. // Print time of gcode.
  75. const double print_time = 50. / (2500. / 60.) + 100. / (3000. / 60.) + 4. / (400. / 60.);
  76. config.set_deserialize_strict({ { "slowdown_below_layer_time", { int(print_time * 1.001) } } });
  77. GCodeGenerator gcodegen;
  78. auto buffer = make_cooling_buffer(gcodegen, config);
  79. std::string gcode = buffer->process_layer(gcode_src, 0, true);
  80. THEN("speed is altered when elapsed time is lower than slowdown threshold") {
  81. bool speed_is_altered = gcode.find("F3000") == gcode.npos;
  82. REQUIRE(speed_is_altered);
  83. }
  84. THEN("speed is not altered for travel moves") {
  85. bool speed_not_altered = gcode.find("F2500") != gcode.npos;
  86. REQUIRE(speed_not_altered);
  87. }
  88. THEN("speed is not altered for extruder-only moves") {
  89. bool speed_not_altered = gcode.find("F400") != gcode.npos;
  90. REQUIRE(speed_not_altered);
  91. }
  92. }
  93. WHEN("G-code block 1") {
  94. THEN("fan is not activated when elapsed time is greater than fan threshold") {
  95. config.set_deserialize_strict({
  96. { "fan_below_layer_time" , int(print_time1 * 0.88) },
  97. { "slowdown_below_layer_time" , int(print_time1 * 0.99) }
  98. });
  99. GCodeGenerator gcodegen;
  100. auto buffer = make_cooling_buffer(gcodegen, config);
  101. std::string gcode = buffer->process_layer(gcode1, 0, true);
  102. bool fan_not_activated = gcode.find("M106") == gcode.npos;
  103. REQUIRE(fan_not_activated);
  104. }
  105. }
  106. WHEN("G-code block 1 with two extruders") {
  107. config.set_deserialize_strict({
  108. { "cooling", "1, 0" },
  109. { "fan_below_layer_time", { int(print_time2 + 1.), int(print_time2 + 1.) } },
  110. { "slowdown_below_layer_time", { int(print_time2 + 2.), int(print_time2 + 2.) } }
  111. });
  112. GCodeGenerator gcodegen;
  113. auto buffer = make_cooling_buffer(gcodegen, config, { 0, 1 });
  114. std::string gcode = buffer->process_layer(gcode1 + "T1\nG1 X0 E1 F3000\n", 0, true);
  115. THEN("fan is activated for the 1st tool") {
  116. bool ok = gcode.find("M106") == 0;
  117. REQUIRE(ok);
  118. }
  119. THEN("fan is disabled for the 2nd tool") {
  120. bool ok = gcode.find("\nM107") > 0;
  121. REQUIRE(ok);
  122. }
  123. }
  124. WHEN("G-code block 2") {
  125. THEN("slowdown is computed on all objects printing at the same Z") {
  126. config.set_deserialize_strict({ { "slowdown_below_layer_time", int(print_time2 * 0.99) } });
  127. GCodeGenerator gcodegen;
  128. auto buffer = make_cooling_buffer(gcodegen, config);
  129. std::string gcode = buffer->process_layer(gcode2, 0, true);
  130. bool ok = gcode.find("F3000") != gcode.npos;
  131. REQUIRE(ok);
  132. }
  133. THEN("fan is not activated on all objects printing at different Z") {
  134. config.set_deserialize_strict({
  135. { "fan_below_layer_time", int(print_time2 * 0.65) },
  136. { "slowdown_below_layer_time", int(print_time2 * 0.7) }
  137. });
  138. GCodeGenerator gcodegen;
  139. auto buffer = make_cooling_buffer(gcodegen, config);
  140. // use an elapsed time which is < the threshold but greater than it when summed twice
  141. std::string gcode = buffer->process_layer(gcode2, 0, true) + buffer->process_layer(gcode2, 1, true);
  142. bool fan_not_activated = gcode.find("M106") == gcode.npos;
  143. REQUIRE(fan_not_activated);
  144. }
  145. THEN("fan is activated on all objects printing at different Z") {
  146. // use an elapsed time which is < the threshold even when summed twice
  147. config.set_deserialize_strict({
  148. { "fan_below_layer_time", int(print_time2 + 1) },
  149. { "slowdown_below_layer_time", int(print_time2 + 1) }
  150. });
  151. GCodeGenerator gcodegen;
  152. auto buffer = make_cooling_buffer(gcodegen, config);
  153. // use an elapsed time which is < the threshold but greater than it when summed twice
  154. std::string gcode = buffer->process_layer(gcode2, 0, true) + buffer->process_layer(gcode2, 1, true);
  155. bool fan_activated = gcode.find("M106") != gcode.npos;
  156. REQUIRE(fan_activated);
  157. }
  158. }
  159. }
  160. SCENARIO("Cooling integration tests", "[Cooling]") {
  161. GIVEN("overhang") {
  162. auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
  163. { "cooling", { 1 } },
  164. { "bridge_fan_speed", { 100 } },
  165. { "fan_below_layer_time", { 0 } },
  166. { "slowdown_below_layer_time", { 0 } },
  167. { "bridge_speed", 99 },
  168. { "enable_dynamic_overhang_speeds", false },
  169. // internal bridges use solid_infil speed
  170. { "bottom_solid_layers", 1 },
  171. // internal bridges use solid_infil speed
  172. });
  173. GCodeReader parser;
  174. int fan = 0;
  175. int fan_with_incorrect_speeds = 0;
  176. int fan_with_incorrect_print_speeds = 0;
  177. int bridge_with_no_fan = 0;
  178. const double bridge_speed = config.opt_float("bridge_speed") * 60;
  179. parser.parse_buffer(
  180. Slic3r::Test::slice({ Slic3r::Test::TestMesh::overhang }, config),
  181. [&fan, &fan_with_incorrect_speeds, &fan_with_incorrect_print_speeds, &bridge_with_no_fan, bridge_speed]
  182. (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
  183. {
  184. if (line.cmd_is("M106")) {
  185. line.has_value('S', fan);
  186. if (fan != 255)
  187. ++ fan_with_incorrect_speeds;
  188. } else if (line.cmd_is("M107")) {
  189. fan = 0;
  190. } else if (line.extruding(self) && line.dist_XY(self) > 0) {
  191. if (is_approx<double>(line.new_F(self), bridge_speed)) {
  192. if (fan != 255)
  193. ++ bridge_with_no_fan;
  194. } else {
  195. if (fan != 0)
  196. ++ fan_with_incorrect_print_speeds;
  197. }
  198. }
  199. });
  200. THEN("bridge fan speed is applied correctly") {
  201. REQUIRE(fan_with_incorrect_speeds == 0);
  202. }
  203. THEN("bridge fan is only turned on for bridges") {
  204. REQUIRE(fan_with_incorrect_print_speeds == 0);
  205. }
  206. THEN("bridge fan is turned on for all bridges") {
  207. REQUIRE(bridge_with_no_fan == 0);
  208. }
  209. }
  210. GIVEN("20mm cube") {
  211. auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
  212. { "cooling", { 1 } },
  213. { "fan_below_layer_time", { 0 } },
  214. { "slowdown_below_layer_time", { 10 } },
  215. { "min_print_speed", { 0 } },
  216. { "start_gcode", "" },
  217. { "first_layer_speed", "100%" },
  218. { "external_perimeter_speed", 99 }
  219. });
  220. GCodeReader parser;
  221. const double external_perimeter_speed = config.opt<ConfigOptionFloatOrPercent>("external_perimeter_speed")->value * 60;
  222. std::vector<double> layer_times;
  223. // z => 1
  224. std::map<coord_t, int> layer_external;
  225. parser.parse_buffer(
  226. Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config),
  227. [&layer_times, &layer_external, external_perimeter_speed]
  228. (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
  229. {
  230. if (line.cmd_is("G1")) {
  231. if (line.dist_Z(self) != 0) {
  232. layer_times.emplace_back(0.);
  233. layer_external[scaled<coord_t>(line.new_Z(self))] = 0;
  234. }
  235. double l = line.dist_XY(self);
  236. if (l == 0)
  237. l = line.dist_E(self);
  238. if (l == 0)
  239. l = line.dist_Z(self);
  240. if (l > 0.) {
  241. if (layer_times.empty())
  242. layer_times.emplace_back(0.);
  243. layer_times.back() += 60. * std::abs(l) / line.new_F(self);
  244. }
  245. if (line.has('F') && line.f() == external_perimeter_speed)
  246. ++ layer_external[scaled<coord_t>(self.z())];
  247. }
  248. });
  249. THEN("slowdown_below_layer_time is honored") {
  250. // Account for some inaccuracies.
  251. const double slowdown_below_layer_time = config.opt<ConfigOptionInts>("slowdown_below_layer_time")->values.front() - 0.5;
  252. size_t minimum_time_honored = std::count_if(layer_times.begin(), layer_times.end(),
  253. [slowdown_below_layer_time](double t){ return t > slowdown_below_layer_time; });
  254. REQUIRE(minimum_time_honored == layer_times.size());
  255. }
  256. THEN("slowdown_below_layer_time does not alter external perimeters") {
  257. // Broken by Vojtech
  258. // check that all layers have at least one unaltered external perimeter speed
  259. // my $external = all { $_ > 0 } values %layer_external;
  260. // ok $external, '';
  261. }
  262. }
  263. }