test_flow.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. //#define CATCH_CONFIG_DISABLE
  2. #include <catch2/catch.hpp>
  3. #include <numeric>
  4. #include <sstream>
  5. #include "test_data.hpp" // get access to init_print, etc
  6. #include <libslic3r/Config.hpp>
  7. #include <libslic3r/Model.hpp>"
  8. #include <libslic3r/Config.hpp>
  9. #include <libslic3r/GCodeReader.hpp>
  10. #include <libslic3r/Flow.hpp>
  11. #include <libslic3r/libslic3r.h>
  12. using namespace Slic3r::Test;
  13. using namespace Slic3r;
  14. SCENARIO("Extrusion width specifics", "[!mayfail]") {
  15. GIVEN("A config with a skirt, brim, some fill density, 3 perimeters, and 1 bottom solid layer and a 20mm cube mesh") {
  16. // this is a sharedptr
  17. DynamicPrintConfig &config {Slic3r::DynamicPrintConfig::full_print_config()};
  18. config.set_key_value("skirts", new ConfigOptionInt{1});
  19. config.set_key_value("brim_width", new ConfigOptionFloat{2});
  20. config.set_key_value("perimeters", new ConfigOptionInt{3});
  21. config.set_key_value("fill_density", new ConfigOptionPercent{40});
  22. config.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent{100, true});
  23. WHEN("first layer width set to 2mm") {
  24. Slic3r::Model model;
  25. config.set_key_value("first_layer_extrusion_width", new ConfigOptionFloatOrPercent{2.0, false});
  26. Print print;
  27. Slic3r::Test::init_print(print, { TestMesh::cube_20x20x20 }, model, &config);
  28. //std::cout << "model pos: " << model.objects.front()->instances.front()->get_offset().x() << ": " << model.objects.front()->instances.front()->get_offset().x() << "\n";
  29. //Print print;
  30. //for (auto* mo : model.objects)
  31. // print.auto_assign_extruders(mo);
  32. //print.apply(model, *config);
  33. ////std::cout << "print volume: " << print.<< ": " << model.objects().front()->copies().front().x() << "\n";
  34. //std::string err = print.validate();
  35. std::vector<double> E_per_mm_bottom;
  36. std::string gcode_filepath("");
  37. Slic3r::Test::gcode(gcode_filepath, print);
  38. GCodeReader parser {Slic3r::GCodeReader()};
  39. const double layer_height = config.opt_float("layer_height");
  40. std::string gcode_from_file= read_to_string(gcode_filepath);
  41. parser.parse_buffer(gcode_from_file, [&E_per_mm_bottom, layer_height] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line)
  42. {
  43. if (self.z() == Approx(layer_height).margin(0.01)) { // only consider first layer
  44. if (line.extruding(self) && line.dist_XY(self) > 0) {
  45. E_per_mm_bottom.emplace_back(line.dist_E(self) / line.dist_XY(self));
  46. }
  47. }
  48. });
  49. THEN(" First layer width applies to everything on first layer.") {
  50. bool pass = false;
  51. auto avg_E {std::accumulate(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), 0.0) / static_cast<double>(E_per_mm_bottom.size())};
  52. pass = (std::count_if(E_per_mm_bottom.cbegin(), E_per_mm_bottom.cend(), [avg_E] (const double& v) { return v == Approx(avg_E); }) == 0);
  53. REQUIRE(pass == true);
  54. REQUIRE(E_per_mm_bottom.size() > 0); // make sure it actually passed because of extrusion
  55. }
  56. THEN(" First layer width does not apply to upper layer.") {
  57. }
  58. clean_file(gcode_filepath, "gcode");
  59. }
  60. }
  61. }
  62. // needs gcode export
  63. SCENARIO(" Bridge flow specifics.", "[!mayfail]") {
  64. GIVEN("A default config with no cooling and a fixed bridge speed, flow ratio and an overhang mesh.") {
  65. WHEN("bridge_flow_ratio is set to 1.0") {
  66. THEN("Output flow is as expected.") {
  67. }
  68. }
  69. WHEN("bridge_flow_ratio is set to 0.5") {
  70. THEN("Output flow is as expected.") {
  71. }
  72. }
  73. WHEN("bridge_flow_ratio is set to 2.0") {
  74. THEN("Output flow is as expected.") {
  75. }
  76. }
  77. }
  78. GIVEN("A default config with no cooling and a fixed bridge speed, flow ratio, fixed extrusion width of 0.4mm and an overhang mesh.") {
  79. WHEN("bridge_flow_ratio is set to 1.0") {
  80. THEN("Output flow is as expected.") {
  81. }
  82. }
  83. WHEN("bridge_flow_ratio is set to 0.5") {
  84. THEN("Output flow is as expected.") {
  85. }
  86. }
  87. WHEN("bridge_flow_ratio is set to 2.0") {
  88. THEN("Output flow is as expected.") {
  89. }
  90. }
  91. }
  92. }
  93. /// Test the expected behavior for auto-width,
  94. /// spacing, etc
  95. SCENARIO("Flow: Flow math for non-bridges", "[!mayfail]") {
  96. GIVEN("Nozzle Diameter of 0.4, a desired width of 1mm and layer height of 0.5") {
  97. auto width {ConfigOptionFloatOrPercent{1.0, false}};
  98. float spacing {0.4f};
  99. float nozzle_diameter {0.4f};
  100. float bridge_flow {1.0f};
  101. float layer_height {0.5f};
  102. // Spacing for non-bridges is has some overlap
  103. THEN("External perimeter flow has a default spacing fixed to 1.05*nozzle_diameter") {
  104. Flow flow {Flow::new_from_config_width(frExternalPerimeter, ConfigOptionFloatOrPercent{0, false}, nozzle_diameter, layer_height, 0.0f)};
  105. REQUIRE(flow.spacing() == Approx((1.05f*nozzle_diameter) - layer_height * (1.0 - PI / 4.0)));
  106. }
  107. THEN("Internal perimeter flow has a default spacing fixed to 1.125*nozzle_diameter") {
  108. Flow flow {Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent{0, false}, nozzle_diameter, layer_height, 0.0f)};
  109. REQUIRE(flow.spacing() == Approx((1.125*nozzle_diameter) - layer_height * (1.0 - PI / 4.0)));
  110. }
  111. THEN("Spacing for supplied width is 0.8927f") {
  112. Flow flow {Flow::new_from_config_width(frExternalPerimeter, width, nozzle_diameter, layer_height, 0.0f)};
  113. REQUIRE(flow.spacing() == Approx(width - layer_height * (1.0 - PI / 4.0)));
  114. flow = Flow::new_from_config_width(frPerimeter, width, nozzle_diameter, layer_height, 0.0f);
  115. REQUIRE(flow.spacing() == Approx(width - layer_height * (1.0 - PI / 4.0)));
  116. }
  117. }
  118. /// Check the min/max
  119. GIVEN("Nozzle Diameter of 0.25 with extreme width") {
  120. float nozzle_diameter {0.25f};
  121. float layer_height {0.5f};
  122. WHEN("layer height is set to 0.15") {
  123. layer_height = 5.f;
  124. THEN("Max width is respected.") {
  125. auto flow {Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent{0, false}, nozzle_diameter, layer_height, 0.0f)};
  126. REQUIRE(flow.width <= Approx(1.4*nozzle_diameter));
  127. }
  128. THEN("Min width is respected") {
  129. auto flow{ Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent{0, false}, nozzle_diameter, layer_height, 0.0f) };
  130. REQUIRE(flow.width >= Approx(1.05*nozzle_diameter));
  131. }
  132. }
  133. WHEN("Layer height is set to 0.3") {
  134. layer_height = 0.01f;
  135. THEN("Max width is respected.") {
  136. auto flow{ Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent{0, false}, nozzle_diameter, layer_height, 0.0f) };
  137. REQUIRE(flow.width <= Approx(1.4*nozzle_diameter));
  138. }
  139. THEN("Min width is respected.") {
  140. auto flow{ Flow::new_from_config_width(frPerimeter, ConfigOptionFloatOrPercent{0, false}, nozzle_diameter, layer_height, 0.0f) };
  141. REQUIRE(flow.width >= Approx(1.05*nozzle_diameter));
  142. }
  143. }
  144. }
  145. ///// Check for an edge case in the maths where the spacing could be 0; original
  146. ///// math is 0.99. Slic3r issue #4654
  147. //GIVEN("Input spacing of 0.414159 and a total width of 2") {
  148. // double in_spacing = 0.414159;
  149. // double total_width = 2.0;
  150. // auto flow {Flow::new_from_spacing(1.0, 0.4, 0.3, false)};
  151. // WHEN("solid_spacing() is called") {
  152. // double result = flow.solid_spacing(total_width, in_spacing);
  153. // THEN("Yielded spacing is greater than 0") {
  154. // REQUIRE(result > 0);
  155. // }
  156. // }
  157. //}
  158. }
  159. /// Spacing, width calculation for bridge extrusions
  160. SCENARIO("Flow: Flow math for bridges", "[!mayfail]") {
  161. GIVEN("Nozzle Diameter of 0.4, a desired width of 1mm and layer height of 0.5") {
  162. auto width {ConfigOptionFloatOrPercent{1.0, false}};
  163. auto spacing {0.4};
  164. auto nozzle_diameter {0.4};
  165. auto bridge_flow {1.0};
  166. auto layer_height {0.5};
  167. WHEN("Flow role is frExternalPerimeter") {
  168. auto flow {Flow::new_from_config_width(frExternalPerimeter, width, nozzle_diameter, layer_height, bridge_flow)};
  169. THEN("Bridge width is same as nozzle diameter") {
  170. REQUIRE(flow.width == Approx(nozzle_diameter));
  171. }
  172. THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING_MULT * nozzle_diameter") {
  173. REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING_MULT * nozzle_diameter));
  174. }
  175. }
  176. WHEN("Flow role is frInfill") {
  177. auto flow {Flow::new_from_config_width(frInfill, width, nozzle_diameter, layer_height, bridge_flow)};
  178. THEN("Bridge width is same as nozzle diameter") {
  179. REQUIRE(flow.width == Approx(nozzle_diameter));
  180. }
  181. THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING_MULT * nozzle_diameter") {
  182. REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING_MULT * nozzle_diameter));
  183. }
  184. }
  185. WHEN("Flow role is frPerimeter") {
  186. auto flow {Flow::new_from_config_width(frPerimeter, width, nozzle_diameter, layer_height, bridge_flow)};
  187. THEN("Bridge width is same as nozzle diameter") {
  188. REQUIRE(flow.width == Approx(nozzle_diameter));
  189. }
  190. THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING_MULT * nozzle_diameter") {
  191. REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING_MULT * nozzle_diameter));
  192. }
  193. }
  194. WHEN("Flow role is frSupportMaterial") {
  195. auto flow {Flow::new_from_config_width(frSupportMaterial, width, nozzle_diameter, layer_height, bridge_flow)};
  196. THEN("Bridge width is same as nozzle diameter") {
  197. REQUIRE(flow.width == Approx(nozzle_diameter));
  198. }
  199. THEN("Bridge spacing is same as nozzle diameter + BRIDGE_EXTRA_SPACING_MULT * nozzle_diameter") {
  200. REQUIRE(flow.spacing() == Approx(nozzle_diameter + BRIDGE_EXTRA_SPACING_MULT * nozzle_diameter));
  201. }
  202. }
  203. }
  204. }