test_skirt_brim.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. #include <catch2/catch.hpp>
  2. #include "libslic3r/GCodeReader.hpp"
  3. #include "libslic3r/Config.hpp"
  4. #include "libslic3r/Geometry.hpp"
  5. #include <boost/algorithm/string.hpp>
  6. #include "test_data.hpp" // get access to init_print, etc
  7. using namespace Slic3r::Test;
  8. using namespace Slic3r;
  9. /// Helper method to find the tool used for the brim (always the first extrusion)
  10. static int get_brim_tool(const std::string &gcode)
  11. {
  12. int brim_tool = -1;
  13. int tool = -1;
  14. GCodeReader parser;
  15. parser.parse_buffer(gcode, [&tool, &brim_tool] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
  16. {
  17. // if the command is a T command, set the the current tool
  18. if (boost::starts_with(line.cmd(), "T")) {
  19. tool = atoi(line.cmd().data() + 1);
  20. } else if (line.cmd() == "G1" && line.extruding(self) && line.dist_XY(self) > 0 && brim_tool < 0) {
  21. brim_tool = tool;
  22. }
  23. });
  24. return brim_tool;
  25. }
  26. TEST_CASE("Skirt height is honored", "[Skirt]") {
  27. DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
  28. config.set_deserialize_strict({
  29. { "skirts", 1 },
  30. { "skirt_height", 5 },
  31. { "perimeters", 0 },
  32. { "support_material_speed", 99 },
  33. // avoid altering speeds unexpectedly
  34. { "cooling", false },
  35. // avoid altering speeds unexpectedly
  36. { "first_layer_speed", "100%" }
  37. });
  38. std::string gcode;
  39. SECTION("printing a single object") {
  40. gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
  41. }
  42. SECTION("printing multiple objects") {
  43. gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20, TestMesh::cube_20x20x20}, config);
  44. }
  45. std::map<double, bool> layers_with_skirt;
  46. double support_speed = config.opt<Slic3r::ConfigOptionFloat>("support_material_speed")->value * MM_PER_MIN;
  47. GCodeReader parser;
  48. parser.parse_buffer(gcode, [&layers_with_skirt, &support_speed] (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) {
  49. if (line.extruding(self) && self.f() == Approx(support_speed)) {
  50. layers_with_skirt[self.z()] = 1;
  51. }
  52. });
  53. REQUIRE(layers_with_skirt.size() == (size_t)config.opt_int("skirt_height"));
  54. }
  55. SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") {
  56. GIVEN("A default configuration") {
  57. DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
  58. config.set_num_extruders(4);
  59. config.set_deserialize_strict({
  60. { "support_material_speed", 99 },
  61. { "first_layer_height", 0.3 },
  62. { "gcode_comments", true },
  63. // avoid altering speeds unexpectedly
  64. { "cooling", false },
  65. { "first_layer_speed", "100%" },
  66. // remove noise from top/solid layers
  67. { "top_solid_layers", 0 },
  68. { "bottom_solid_layers", 1 },
  69. { "start_gcode", "T[initial_tool]\n" }
  70. });
  71. WHEN("Brim width is set to 5") {
  72. config.set_deserialize_strict({
  73. { "perimeters", 0 },
  74. { "skirts", 0 },
  75. { "brim_width", 5 }
  76. });
  77. THEN("Brim is generated") {
  78. std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
  79. bool brim_generated = false;
  80. double support_speed = config.opt<Slic3r::ConfigOptionFloat>("support_material_speed")->value * MM_PER_MIN;
  81. Slic3r::GCodeReader parser;
  82. parser.parse_buffer(gcode, [&brim_generated, support_speed] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line) {
  83. if (self.z() == Approx(0.3) || line.new_Z(self) == Approx(0.3)) {
  84. if (line.extruding(self) && self.f() == Approx(support_speed)) {
  85. brim_generated = true;
  86. }
  87. }
  88. });
  89. REQUIRE(brim_generated);
  90. }
  91. }
  92. WHEN("Skirt area is smaller than the brim") {
  93. config.set_deserialize_strict({
  94. { "skirts", 1 },
  95. { "brim_width", 10}
  96. });
  97. THEN("Gcode generates") {
  98. REQUIRE(! Slic3r::Test::slice({TestMesh::cube_20x20x20}, config).empty());
  99. }
  100. }
  101. WHEN("Skirt height is 0 and skirts > 0") {
  102. config.set_deserialize_strict({
  103. { "skirts", 2 },
  104. { "skirt_height", 0 }
  105. });
  106. THEN("Gcode generates") {
  107. REQUIRE(! Slic3r::Test::slice({TestMesh::cube_20x20x20}, config).empty());
  108. }
  109. }
  110. #if 0
  111. // This is a real error! One shall print the brim with the external perimeter extruder!
  112. WHEN("Perimeter extruder = 2 and support extruders = 3") {
  113. THEN("Brim is printed with the extruder used for the perimeters of first object") {
  114. config.set_deserialize_strict({
  115. { "skirts", 0 },
  116. { "brim_width", 5 },
  117. { "perimeter_extruder", 2 },
  118. { "support_material_extruder", 3 },
  119. { "infill_extruder", 4 }
  120. });
  121. std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
  122. int tool = get_brim_tool(gcode);
  123. REQUIRE(tool == config.opt_int("perimeter_extruder") - 1);
  124. }
  125. }
  126. WHEN("Perimeter extruder = 2, support extruders = 3, raft is enabled") {
  127. THEN("brim is printed with same extruder as skirt") {
  128. config.set_deserialize_strict({
  129. { "skirts", 0 },
  130. { "brim_width", 5 },
  131. { "perimeter_extruder", 2 },
  132. { "support_material_extruder", 3 },
  133. { "infill_extruder", 4 },
  134. { "raft_layers", 1 }
  135. });
  136. std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
  137. int tool = get_brim_tool(gcode);
  138. REQUIRE(tool == config.opt_int("support_material_extruder") - 1);
  139. }
  140. }
  141. #endif
  142. WHEN("brim width to 1 with layer_width of 0.5") {
  143. config.set_deserialize_strict({
  144. { "skirts", 0 },
  145. { "first_layer_extrusion_width", 0.5 },
  146. { "brim_width", 1 }
  147. });
  148. THEN("2 brim lines") {
  149. Slic3r::Print print;
  150. Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, config);
  151. REQUIRE(print.brim().entities.size() == 2);
  152. }
  153. }
  154. #if 0
  155. WHEN("brim ears on a square") {
  156. config.set_deserialize_strict({
  157. { "skirts", 0 },
  158. { "first_layer_extrusion_width", 0.5 },
  159. { "brim_width", 1 },
  160. { "brim_ears", 1 },
  161. { "brim_ears_max_angle", 91 }
  162. });
  163. Slic3r::Print print;
  164. Slic3r::Test::init_and_process_print({TestMesh::cube_20x20x20}, print, config);
  165. THEN("Four brim ears") {
  166. REQUIRE(print.brim().entities.size() == 4);
  167. }
  168. }
  169. WHEN("brim ears on a square but with a too small max angle") {
  170. config.set_deserialize_strict({
  171. { "skirts", 0 },
  172. { "first_layer_extrusion_width", 0.5 },
  173. { "brim_width", 1 },
  174. { "brim_ears", 1 },
  175. { "brim_ears_max_angle", 89 }
  176. });
  177. THEN("no brim") {
  178. Slic3r::Print print;
  179. Slic3r::Test::init_and_process_print({ TestMesh::cube_20x20x20 }, print, config);
  180. REQUIRE(print.brim().entities.size() == 0);
  181. }
  182. }
  183. #endif
  184. WHEN("Object is plated with overhang support and a brim") {
  185. config.set_deserialize_strict({
  186. { "layer_height", 0.4 },
  187. { "first_layer_height", 0.4 },
  188. { "skirts", 1 },
  189. { "skirt_distance", 0 },
  190. { "support_material_speed", 99 },
  191. { "perimeter_extruder", 1 },
  192. { "support_material_extruder", 2 },
  193. { "infill_extruder", 3 }, // ensure that a tool command gets emitted.
  194. { "cooling", false }, // to prevent speeds to be altered
  195. { "first_layer_speed", "100%" }, // to prevent speeds to be altered
  196. { "start_gcode", "T[initial_tool]\n" }
  197. });
  198. THEN("overhang generates?") {
  199. //FIXME does it make sense?
  200. REQUIRE(! Slic3r::Test::slice({TestMesh::overhang}, config).empty());
  201. }
  202. // config.set("support_material", true); // to prevent speeds to be altered
  203. #if 0
  204. // This test is not finished.
  205. THEN("skirt length is large enough to contain object with support") {
  206. CHECK(config.opt_bool("support_material")); // test is not valid if support material is off
  207. std::string gcode = Slic3r::Test::slice({TestMesh::cube_20x20x20}, config);
  208. double support_speed = config.opt<ConfigOptionFloat>("support_material_speed")->value * MM_PER_MIN;
  209. double skirt_length = 0.0;
  210. Points extrusion_points;
  211. int tool = -1;
  212. GCodeReader parser;
  213. parser.parse_buffer(gcode, [config, &extrusion_points, &tool, &skirt_length, support_speed] (Slic3r::GCodeReader& self, const Slic3r::GCodeReader::GCodeLine& line) {
  214. // std::cerr << line.cmd() << "\n";
  215. if (boost::starts_with(line.cmd(), "T")) {
  216. tool = atoi(line.cmd().data() + 1);
  217. } else if (self.z() == Approx(config.opt<ConfigOptionFloat>("first_layer_height")->value)) {
  218. // on first layer
  219. if (line.extruding(self) && line.dist_XY(self) > 0) {
  220. float speed = ( self.f() > 0 ? self.f() : line.new_F(self));
  221. // std::cerr << "Tool " << tool << "\n";
  222. if (speed == Approx(support_speed) && tool == config.opt_int("perimeter_extruder") - 1) {
  223. // Skirt uses first material extruder, support material speed.
  224. skirt_length += line.dist_XY(self);
  225. } else
  226. extrusion_points.push_back(Slic3r::Point::new_scale(line.new_X(self), line.new_Y(self)));
  227. }
  228. }
  229. if (self.z() == Approx(0.3) || line.new_Z(self) == Approx(0.3)) {
  230. if (line.extruding(self) && self.f() == Approx(support_speed)) {
  231. }
  232. }
  233. });
  234. Slic3r::Polygon convex_hull = Slic3r::Geometry::convex_hull(extrusion_points);
  235. double hull_perimeter = unscale<double>(convex_hull.split_at_first_point().length());
  236. REQUIRE(skirt_length > hull_perimeter);
  237. }
  238. #endif
  239. }
  240. WHEN("Large minimum skirt length is used.") {
  241. config.set("min_skirt_length", 20);
  242. THEN("Gcode generation doesn't crash") {
  243. REQUIRE(! Slic3r::Test::slice({TestMesh::cube_20x20x20}, config).empty());
  244. }
  245. }
  246. }
  247. }