test_perimeters.cpp 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. #include <catch2/catch.hpp>
  2. #include <numeric>
  3. #include <sstream>
  4. #include "libslic3r/Config.hpp"
  5. #include "libslic3r/ClipperUtils.hpp"
  6. #include "libslic3r/Layer.hpp"
  7. #include "libslic3r/PerimeterGenerator.hpp"
  8. #include "libslic3r/Print.hpp"
  9. #include "libslic3r/PrintConfig.hpp"
  10. #include "libslic3r/SurfaceCollection.hpp"
  11. #include "libslic3r/libslic3r.h"
  12. #include "test_data.hpp"
  13. using namespace Slic3r;
  14. SCENARIO("Perimeter nesting", "[Perimeters]")
  15. {
  16. struct TestData {
  17. ExPolygons expolygons;
  18. // expected number of loops
  19. int total;
  20. // expected number of external loops
  21. int external;
  22. // expected external perimeter
  23. std::vector<bool> ext_order;
  24. // expected number of internal contour loops
  25. int cinternal;
  26. // expected number of ccw loops
  27. int ccw;
  28. // expected ccw/cw order
  29. std::vector<bool> ccw_order;
  30. // expected nesting order
  31. std::vector<std::vector<int>> nesting;
  32. };
  33. FullPrintConfig config;
  34. auto test = [&config](const TestData &data) {
  35. SurfaceCollection slices;
  36. slices.append(data.expolygons, stInternal);
  37. ExtrusionEntityCollection loops;
  38. ExtrusionEntityCollection gap_fill;
  39. ExPolygons fill_expolygons;
  40. Flow flow(1., 1., 1.);
  41. PerimeterGenerator::Parameters perimeter_generator_params(
  42. 1., // layer height
  43. -1, // layer ID
  44. flow, flow, flow, flow,
  45. static_cast<const PrintRegionConfig&>(config),
  46. static_cast<const PrintObjectConfig&>(config),
  47. static_cast<const PrintConfig&>(config),
  48. false); // spiral_vase
  49. Polygons lower_layer_polygons_cache;
  50. for (const Surface &surface : slices)
  51. // FIXME Lukas H.: Disable this test for Arachne because it is failing and needs more investigation.
  52. // if (config.perimeter_generator == PerimeterGeneratorType::Arachne)
  53. // PerimeterGenerator::process_arachne();
  54. // else
  55. PerimeterGenerator::process_classic(
  56. // input:
  57. perimeter_generator_params,
  58. surface,
  59. nullptr,
  60. // cache:
  61. lower_layer_polygons_cache,
  62. // output:
  63. loops, gap_fill, fill_expolygons);
  64. THEN("expected number of collections") {
  65. REQUIRE(loops.entities.size() == data.expolygons.size());
  66. }
  67. loops = loops.flatten();
  68. THEN("expected number of loops") {
  69. REQUIRE(loops.entities.size() == data.total);
  70. }
  71. THEN("expected number of external loops") {
  72. size_t num_external = std::count_if(loops.entities.begin(), loops.entities.end(),
  73. [](const ExtrusionEntity *ee){ return ee->role() == ExtrusionRole::ExternalPerimeter; });
  74. REQUIRE(num_external == data.external);
  75. }
  76. THEN("expected external order") {
  77. std::vector<bool> ext_order;
  78. for (auto *ee : loops.entities)
  79. ext_order.emplace_back(ee->role() == ExtrusionRole::ExternalPerimeter);
  80. REQUIRE(ext_order == data.ext_order);
  81. }
  82. THEN("expected number of internal contour loops") {
  83. size_t cinternal = std::count_if(loops.entities.begin(), loops.entities.end(),
  84. [](const ExtrusionEntity *ee){ return dynamic_cast<const ExtrusionLoop*>(ee)->loop_role() == elrContourInternalPerimeter; });
  85. REQUIRE(cinternal == data.cinternal);
  86. }
  87. THEN("expected number of ccw loops") {
  88. size_t ccw = std::count_if(loops.entities.begin(), loops.entities.end(),
  89. [](const ExtrusionEntity *ee){ return dynamic_cast<const ExtrusionLoop*>(ee)->polygon().is_counter_clockwise(); });
  90. REQUIRE(ccw == data.ccw);
  91. }
  92. THEN("expected ccw/cw order") {
  93. std::vector<bool> ccw_order;
  94. for (auto *ee : loops.entities)
  95. ccw_order.emplace_back(dynamic_cast<const ExtrusionLoop*>(ee)->polygon().is_counter_clockwise());
  96. REQUIRE(ccw_order == data.ccw_order);
  97. }
  98. THEN("expected nesting order") {
  99. for (const std::vector<int> &nesting : data.nesting) {
  100. for (size_t i = 1; i < nesting.size(); ++ i)
  101. REQUIRE(dynamic_cast<const ExtrusionLoop*>(loops.entities[nesting[i - 1]])->polygon().contains(loops.entities[nesting[i]]->first_point()));
  102. }
  103. }
  104. };
  105. WHEN("Rectangle") {
  106. config.perimeters.value = 3;
  107. TestData data;
  108. data.expolygons = {
  109. ExPolygon{ Polygon::new_scale({ {0,0}, {100,0}, {100,100}, {0,100} }) }
  110. };
  111. data.total = 3;
  112. data.external = 1;
  113. data.ext_order = { false, false, true };
  114. data.cinternal = 1;
  115. data.ccw = 3;
  116. data.ccw_order = { true, true, true };
  117. data.nesting = { { 2, 1, 0 } };
  118. test(data);
  119. }
  120. WHEN("Rectangle with hole") {
  121. config.perimeters.value = 3;
  122. TestData data;
  123. data.expolygons = {
  124. ExPolygon{ Polygon::new_scale({ {0,0}, {100,0}, {100,100}, {0,100} }),
  125. Polygon::new_scale({ {40,40}, {40,60}, {60,60}, {60,40} }) }
  126. };
  127. data.total = 6;
  128. data.external = 2;
  129. data.ext_order = { false, false, true, false, false, true };
  130. data.cinternal = 1;
  131. data.ccw = 3;
  132. data.ccw_order = { false, false, false, true, true, true };
  133. data.nesting = { { 5, 4, 3, 0, 1, 2 } };
  134. test(data);
  135. }
  136. WHEN("Nested rectangles with holes") {
  137. config.perimeters.value = 3;
  138. TestData data;
  139. data.expolygons = {
  140. ExPolygon{ Polygon::new_scale({ {0,0}, {200,0}, {200,200}, {0,200} }),
  141. Polygon::new_scale({ {20,20}, {20,180}, {180,180}, {180,20} }) },
  142. ExPolygon{ Polygon::new_scale({ {50,50}, {150,50}, {150,150}, {50,150} }),
  143. Polygon::new_scale({ {80,80}, {80,120}, {120,120}, {120,80} }) }
  144. };
  145. data.total = 4*3;
  146. data.external = 4;
  147. data.ext_order = { false, false, true, false, false, true, false, false, true, false, false, true };
  148. data.cinternal = 2;
  149. data.ccw = 2*3;
  150. data.ccw_order = { false, false, false, true, true, true, false, false, false, true, true, true };
  151. test(data);
  152. }
  153. WHEN("Rectangle with multiple holes") {
  154. config.perimeters.value = 2;
  155. TestData data;
  156. ExPolygon expoly{ Polygon::new_scale({ {0,0}, {50,0}, {50,50}, {0,50} }) };
  157. expoly.holes.emplace_back(Polygon::new_scale({ {7.5,7.5}, {7.5,12.5}, {12.5,12.5}, {12.5,7.5} }));
  158. expoly.holes.emplace_back(Polygon::new_scale({ {7.5,17.5}, {7.5,22.5}, {12.5,22.5}, {12.5,17.5} }));
  159. expoly.holes.emplace_back(Polygon::new_scale({ {7.5,27.5}, {7.5,32.5}, {12.5,32.5}, {12.5,27.5} }));
  160. expoly.holes.emplace_back(Polygon::new_scale({ {7.5,37.5}, {7.5,42.5}, {12.5,42.5}, {12.5,37.5} }));
  161. expoly.holes.emplace_back(Polygon::new_scale({ {17.5,7.5}, {17.5,12.5}, {22.5,12.5}, {22.5,7.5} }));
  162. data.expolygons = { expoly };
  163. data.total = 12;
  164. data.external = 6;
  165. data.ext_order = { false, true, false, true, false, true, false, true, false, true, false, true };
  166. data.cinternal = 1;
  167. data.ccw = 2;
  168. data.ccw_order = { false, false, false, false, false, false, false, false, false, false, true, true };
  169. data.nesting = { {0,1},{2,3},{4,5},{6,7},{8,9} };
  170. test(data);
  171. };
  172. }
  173. SCENARIO("Perimeters", "[Perimeters]")
  174. {
  175. auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
  176. { "skirts", 0 },
  177. { "fill_density", 0 },
  178. { "perimeters", 3 },
  179. { "top_solid_layers", 0 },
  180. { "bottom_solid_layers", 0 },
  181. // to prevent speeds from being altered
  182. { "cooling", "0" },
  183. // to prevent speeds from being altered
  184. { "first_layer_speed", "100%" }
  185. });
  186. WHEN("Bridging perimeters disabled") {
  187. std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::overhang }, config);
  188. THEN("all perimeters extruded ccw") {
  189. GCodeReader parser;
  190. bool has_cw_loops = false;
  191. Polygon current_loop;
  192. parser.parse_buffer(gcode, [&has_cw_loops, &current_loop](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
  193. {
  194. if (line.extruding(self) && line.dist_XY(self) > 0) {
  195. if (current_loop.empty())
  196. current_loop.points.emplace_back(self.xy_scaled());
  197. current_loop.points.emplace_back(line.new_XY_scaled(self));
  198. } else if (! line.cmd_is("M73")) {
  199. // skips remaining time lines (M73)
  200. if (! current_loop.empty() && current_loop.is_clockwise())
  201. has_cw_loops = true;
  202. current_loop.clear();
  203. }
  204. });
  205. REQUIRE(! has_cw_loops);
  206. }
  207. }
  208. auto test = [&config](Test::TestMesh model) {
  209. // we test two copies to make sure ExtrusionLoop objects are not modified in-place (the second object would not detect cw loops and thus would calculate wrong)
  210. std::string gcode = Slic3r::Test::slice({ model, model }, config);
  211. GCodeReader parser;
  212. bool has_cw_loops = false;
  213. bool has_outwards_move = false;
  214. bool starts_on_convex_point = false;
  215. // print_z => count of external loops
  216. std::map<coord_t, int> external_loops;
  217. Polygon current_loop;
  218. const double external_perimeter_speed = config.get_abs_value("external_perimeter_speed") * 60.;
  219. parser.parse_buffer(gcode, [&has_cw_loops, &has_outwards_move, &starts_on_convex_point, &external_loops, &current_loop, external_perimeter_speed, model]
  220. (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
  221. {
  222. if (line.extruding(self) && line.dist_XY(self) > 0) {
  223. if (current_loop.empty())
  224. current_loop.points.emplace_back(self.xy_scaled());
  225. current_loop.points.emplace_back(line.new_XY_scaled(self));
  226. } else if (! line.cmd_is("M73")) {
  227. // skips remaining time lines (M73)
  228. if (! current_loop.empty()) {
  229. if (current_loop.is_clockwise())
  230. has_cw_loops = true;
  231. if (is_approx<double>(self.f(), external_perimeter_speed)) {
  232. // reset counter for second object
  233. coord_t z = scaled<coord_t>(self.z());
  234. auto it = external_loops.find(z);
  235. if (it == external_loops.end())
  236. it = external_loops.insert(std::make_pair(z, 0)).first;
  237. else if (it->second == 2)
  238. it->second = 0;
  239. ++ it->second;
  240. bool is_contour = it->second == 2;
  241. bool is_hole = it->second == 1;
  242. // Testing whether the move point after loop ends up inside the extruded loop.
  243. bool loop_contains_point = current_loop.contains(line.new_XY_scaled(self));
  244. if (// contour should include destination
  245. (! loop_contains_point && is_contour) ||
  246. // hole should not
  247. (loop_contains_point && is_hole))
  248. has_outwards_move = true;
  249. if (model == Test::TestMesh::cube_with_concave_hole) {
  250. // check that loop starts at a concave vertex
  251. double cross = cross2((current_loop.points.front() - current_loop.points[current_loop.points.size() - 2]).cast<double>(), (current_loop.points[1] - current_loop.points.front()).cast<double>());
  252. bool convex = cross > 0.;
  253. if ((convex && is_contour) || (! convex && is_hole))
  254. starts_on_convex_point = true;
  255. }
  256. }
  257. current_loop.clear();
  258. }
  259. }
  260. });
  261. THEN("all perimeters extruded ccw") {
  262. REQUIRE(! has_cw_loops);
  263. }
  264. // FIXME Lukas H.: Arachne is printing external loops before hole loops in this test case.
  265. if (config.opt_enum<PerimeterGeneratorType>("perimeter_generator") == Slic3r::PerimeterGeneratorType::Arachne) {
  266. THEN("move outwards after completing external loop") {
  267. // REQUIRE(! has_outwards_move);
  268. }
  269. // FIXME Lukas H.: Disable this test for Arachne because it is failing and needs more investigation.
  270. THEN("loops start on concave point if any") {
  271. // REQUIRE(! starts_on_convex_point);
  272. }
  273. } else {
  274. THEN("move inwards after completing external loop") {
  275. REQUIRE(! has_outwards_move);
  276. }
  277. THEN("loops start on concave point if any") {
  278. REQUIRE(! starts_on_convex_point);
  279. }
  280. }
  281. };
  282. // Reusing the config above.
  283. config.set_deserialize_strict({
  284. { "external_perimeter_speed", 68 }
  285. });
  286. GIVEN("Cube with hole") { test(Test::TestMesh::cube_with_hole); }
  287. GIVEN("Cube with concave hole") { test(Test::TestMesh::cube_with_concave_hole); }
  288. WHEN("Bridging perimeters enabled") {
  289. // Reusing the config above.
  290. config.set_deserialize_strict({
  291. { "perimeters", 1 },
  292. { "perimeter_speed", 77 },
  293. { "external_perimeter_speed", 66 },
  294. { "enable_dynamic_overhang_speeds", false },
  295. { "bridge_speed", 99 },
  296. { "cooling", "1" },
  297. { "fan_below_layer_time", "0" },
  298. { "slowdown_below_layer_time", "0" },
  299. { "bridge_fan_speed", "100" },
  300. // arbitrary value
  301. { "bridge_flow_ratio", 33 },
  302. { "overhangs", true }
  303. });
  304. std::string gcode = Slic3r::Test::slice({ mesh(Slic3r::Test::TestMesh::overhang) }, config);
  305. THEN("Bridging is applied to bridging perimeters") {
  306. GCodeReader parser;
  307. // print Z => speeds
  308. std::map<coord_t, std::set<double>> layer_speeds;
  309. int fan_speed = 0;
  310. const double perimeter_speed = config.opt_float("perimeter_speed") * 60.;
  311. const double external_perimeter_speed = config.get_abs_value("external_perimeter_speed") * 60.;
  312. const double bridge_speed = config.opt_float("bridge_speed") * 60.;
  313. const double nozzle_dmr = config.opt<ConfigOptionFloats>("nozzle_diameter")->get_at(0);
  314. const double filament_dmr = config.opt<ConfigOptionFloats>("filament_diameter")->get_at(0);
  315. const double bridge_mm_per_mm = sqr(nozzle_dmr / filament_dmr) * config.opt_float("bridge_flow_ratio");
  316. parser.parse_buffer(gcode, [&layer_speeds, &fan_speed, perimeter_speed, external_perimeter_speed, bridge_speed, nozzle_dmr, filament_dmr, bridge_mm_per_mm]
  317. (Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
  318. {
  319. if (line.cmd_is("M107"))
  320. fan_speed = 0;
  321. else if (line.cmd_is("M106"))
  322. line.has_value('S', fan_speed);
  323. else if (line.extruding(self) && line.dist_XY(self) > 0) {
  324. double feedrate = line.new_F(self);
  325. REQUIRE((is_approx(feedrate, perimeter_speed) || is_approx(feedrate, external_perimeter_speed) || is_approx(feedrate, bridge_speed)));
  326. layer_speeds[self.z()].insert(feedrate);
  327. bool bridging = is_approx(feedrate, bridge_speed);
  328. double mm_per_mm = line.dist_E(self) / line.dist_XY(self);
  329. // Fan enabled at full speed when bridging, disabled when not bridging.
  330. REQUIRE((! bridging || fan_speed == 255));
  331. REQUIRE((bridging || fan_speed == 0));
  332. // When bridging, bridge flow is applied.
  333. REQUIRE((! bridging || std::abs(mm_per_mm - bridge_mm_per_mm) <= 0.01));
  334. }
  335. });
  336. // only overhang layer has more than one speed
  337. size_t num_overhangs = std::count_if(layer_speeds.begin(), layer_speeds.end(), [](const std::pair<double, std::set<double>> &v){ return v.second.size() > 1; });
  338. REQUIRE(num_overhangs == 1);
  339. }
  340. }
  341. GIVEN("iPad stand") {
  342. WHEN("Extra perimeters enabled") {
  343. auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
  344. { "skirts", 0 },
  345. { "perimeters", 3 },
  346. { "layer_height", 0.4 },
  347. { "first_layer_height", 0.35 },
  348. { "extra_perimeters", 1 },
  349. // to prevent speeds from being altered
  350. { "cooling", "0" },
  351. // to prevent speeds from being altered
  352. { "first_layer_speed", "100%" },
  353. { "perimeter_speed", 99 },
  354. { "external_perimeter_speed", 99 },
  355. { "small_perimeter_speed", 99 },
  356. { "thin_walls", 0 },
  357. });
  358. std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::ipadstand }, config);
  359. // z => number of loops
  360. std::map<coord_t, int> perimeters;
  361. bool in_loop = false;
  362. const double perimeter_speed = config.opt_float("perimeter_speed") * 60.;
  363. GCodeReader parser;
  364. parser.parse_buffer(gcode, [&perimeters, &in_loop, perimeter_speed](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
  365. {
  366. if (line.extruding(self) && line.dist_XY(self) > 0 && is_approx<double>(line.new_F(self), perimeter_speed)) {
  367. if (! in_loop) {
  368. coord_t z = scaled<coord_t>(self.z());
  369. auto it = perimeters.find(z);
  370. if (it == perimeters.end())
  371. it = perimeters.insert(std::make_pair(z, 0)).first;
  372. ++ it->second;
  373. }
  374. in_loop = true;
  375. } else if (! line.cmd_is("M73")) {
  376. // skips remaining time lines (M73)
  377. in_loop = false;
  378. }
  379. });
  380. THEN("no superfluous extra perimeters") {
  381. const int num_perimeters = config.opt_int("perimeters");
  382. size_t extra_perimeters = std::count_if(perimeters.begin(), perimeters.end(), [num_perimeters](const std::pair<const coord_t, int> &v){ return (v.second % num_perimeters) > 0; });
  383. REQUIRE(extra_perimeters == 0);
  384. }
  385. }
  386. }
  387. }
  388. SCENARIO("Some weird coverage test", "[Perimeters]")
  389. {
  390. auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
  391. { "nozzle_diameter", "0.4" },
  392. { "perimeters", 2 },
  393. { "perimeter_extrusion_width", 0.4 },
  394. { "external_perimeter_extrusion_width", 0.4 },
  395. { "infill_extrusion_width", 0.53 },
  396. { "solid_infill_extrusion_width", 0.53 }
  397. });
  398. // we just need a pre-filled Print object
  399. Print print;
  400. Model model;
  401. Slic3r::Test::init_print({ Test::TestMesh::cube_20x20x20 }, print, model, config);
  402. // override a layer's slices
  403. ExPolygon expolygon;
  404. expolygon.contour = {
  405. {-71974463,-139999376},{-71731792,-139987456},{-71706544,-139985616},{-71682119,-139982639},{-71441248,-139946912},{-71417487,-139942895},{-71379384,-139933984},{-71141800,-139874480},
  406. {-71105247,-139862895},{-70873544,-139779984},{-70838592,-139765856},{-70614943,-139660064},{-70581783,-139643567},{-70368368,-139515680},{-70323751,-139487872},{-70122160,-139338352},
  407. {-70082399,-139306639},{-69894800,-139136624},{-69878679,-139121327},{-69707992,-138933008},{-69668575,-138887343},{-69518775,-138685359},{-69484336,-138631632},{-69356423,-138418207},
  408. {-69250040,-138193296},{-69220920,-138128976},{-69137992,-137897168},{-69126095,-137860255},{-69066568,-137622608},{-69057104,-137582511},{-69053079,-137558751},{-69017352,-137317872},
  409. {-69014392,-137293456},{-69012543,-137268207},{-68999369,-137000000},{-63999999,-137000000},{-63705947,-136985551},{-63654984,-136977984},{-63414731,-136942351},{-63364756,-136929840},
  410. {-63129151,-136870815},{-62851950,-136771631},{-62585807,-136645743},{-62377483,-136520895},{-62333291,-136494415},{-62291908,-136463728},{-62096819,-136319023},{-62058644,-136284432},
  411. {-61878676,-136121328},{-61680968,-135903184},{-61650275,-135861807},{-61505591,-135666719},{-61354239,-135414191},{-61332211,-135367615},{-61228359,-135148063},{-61129179,-134870847},
  412. {-61057639,-134585262},{-61014451,-134294047},{-61000000,-134000000},{-61000000,-107999999},{-61014451,-107705944},{-61057639,-107414736},{-61129179,-107129152},{-61228359,-106851953},
  413. {-61354239,-106585808},{-61505591,-106333288},{-61680967,-106096816},{-61878675,-105878680},{-62096820,-105680967},{-62138204,-105650279},{-62333292,-105505591},{-62585808,-105354239},
  414. {-62632384,-105332207},{-62851951,-105228360},{-62900463,-105211008},{-63129152,-105129183},{-63414731,-105057640},{-63705947,-105014448},{-63999999,-105000000},{-68999369,-105000000},
  415. {-69012543,-104731792},{-69014392,-104706544},{-69017352,-104682119},{-69053079,-104441248},{-69057104,-104417487},{-69066008,-104379383},{-69125528,-104141799},{-69137111,-104105248},
  416. {-69220007,-103873544},{-69234136,-103838591},{-69339920,-103614943},{-69356415,-103581784},{-69484328,-103368367},{-69512143,-103323752},{-69661647,-103122160},{-69693352,-103082399},
  417. {-69863383,-102894800},{-69878680,-102878679},{-70066999,-102707992},{-70112656,-102668576},{-70314648,-102518775},{-70368367,-102484336},{-70581783,-102356424},{-70806711,-102250040},
  418. {-70871040,-102220919},{-71102823,-102137992},{-71139752,-102126095},{-71377383,-102066568},{-71417487,-102057104},{-71441248,-102053079},{-71682119,-102017352},{-71706535,-102014392},
  419. {-71731784,-102012543},{-71974456,-102000624},{-71999999,-102000000},{-104000000,-102000000},{-104025536,-102000624},{-104268207,-102012543},{-104293455,-102014392},
  420. {-104317880,-102017352},{-104558751,-102053079},{-104582512,-102057104},{-104620616,-102066008},{-104858200,-102125528},{-104894751,-102137111},{-105126455,-102220007},
  421. {-105161408,-102234136},{-105385056,-102339920},{-105418215,-102356415},{-105631632,-102484328},{-105676247,-102512143},{-105877839,-102661647},{-105917600,-102693352},
  422. {-106105199,-102863383},{-106121320,-102878680},{-106292007,-103066999},{-106331424,-103112656},{-106481224,-103314648},{-106515663,-103368367},{-106643575,-103581783},
  423. {-106749959,-103806711},{-106779080,-103871040},{-106862007,-104102823},{-106873904,-104139752},{-106933431,-104377383},{-106942896,-104417487},{-106946920,-104441248},
  424. {-106982648,-104682119},{-106985607,-104706535},{-106987456,-104731784},{-107000630,-105000000},{-112000000,-105000000},{-112294056,-105014448},{-112585264,-105057640},
  425. {-112870848,-105129184},{-112919359,-105146535},{-113148048,-105228360},{-113194624,-105250392},{-113414191,-105354239},{-113666711,-105505591},{-113708095,-105536279},
  426. {-113903183,-105680967},{-114121320,-105878679},{-114319032,-106096816},{-114349720,-106138200},{-114494408,-106333288},{-114645760,-106585808},{-114667792,-106632384},
  427. {-114771640,-106851952},{-114788991,-106900463},{-114870815,-107129151},{-114942359,-107414735},{-114985551,-107705943},{-115000000,-107999999},{-115000000,-134000000},
  428. {-114985551,-134294048},{-114942359,-134585263},{-114870816,-134870847},{-114853464,-134919359},{-114771639,-135148064},{-114645759,-135414192},{-114494407,-135666720},
  429. {-114319031,-135903184},{-114121320,-136121327},{-114083144,-136155919},{-113903184,-136319023},{-113861799,-136349712},{-113666711,-136494416},{-113458383,-136619264},
  430. {-113414192,-136645743},{-113148049,-136771631},{-112870848,-136870815},{-112820872,-136883327},{-112585264,-136942351},{-112534303,-136949920},{-112294056,-136985551},
  431. {-112000000,-137000000},{-107000630,-137000000},{-106987456,-137268207},{-106985608,-137293440},{-106982647,-137317872},{-106946920,-137558751},{-106942896,-137582511},
  432. {-106933991,-137620624},{-106874471,-137858208},{-106862888,-137894751},{-106779992,-138126463},{-106765863,-138161424},{-106660080,-138385055},{-106643584,-138418223},
  433. {-106515671,-138631648},{-106487855,-138676256},{-106338352,-138877839},{-106306647,-138917600},{-106136616,-139105199},{-106121320,-139121328},{-105933000,-139291999},
  434. {-105887344,-139331407},{-105685351,-139481232},{-105631632,-139515663},{-105418216,-139643567},{-105193288,-139749951},{-105128959,-139779072},{-104897175,-139862016},
  435. {-104860247,-139873904},{-104622616,-139933423},{-104582511,-139942896},{-104558751,-139946912},{-104317880,-139982656},{-104293463,-139985616},{-104268216,-139987456},
  436. {-104025544,-139999376},{-104000000,-140000000},{-71999999,-140000000}
  437. };
  438. expolygon.holes = {
  439. {{-105000000,-138000000},{-105000000,-104000000},{-71000000,-104000000},{-71000000,-138000000}},
  440. {{-69000000,-132000000},{-69000000,-110000000},{-64991180,-110000000},{-64991180,-132000000}},
  441. {{-111008824,-132000000},{-111008824,-110000000},{-107000000,-110000000},{-107000000,-132000000}}
  442. };
  443. PrintObject *object = print.get_object(0);
  444. object->slice();
  445. Layer *layer = object->get_layer(1);
  446. LayerRegion *layerm = layer->get_region(0);
  447. layerm->m_slices.clear();
  448. layerm->m_slices.append({ expolygon }, stInternal);
  449. layer->lslices = { expolygon };
  450. layer->lslices_ex = { { get_extents(expolygon) } };
  451. // make perimeters
  452. layer->make_perimeters();
  453. // compute the covered area
  454. Flow pflow = layerm->flow(frPerimeter);
  455. Flow iflow = layerm->flow(frInfill);
  456. Polygons covered_by_perimeters;
  457. Polygons covered_by_infill;
  458. {
  459. Polygons acc;
  460. for (const ExtrusionEntity *ee : layerm->perimeters())
  461. for (const ExtrusionEntity *ee : dynamic_cast<const ExtrusionEntityCollection*>(ee)->entities)
  462. append(acc, offset(dynamic_cast<const ExtrusionLoop*>(ee)->polygon().split_at_first_point(), float(pflow.scaled_width() / 2.f + SCALED_EPSILON)));
  463. covered_by_perimeters = union_(acc);
  464. }
  465. {
  466. Polygons acc;
  467. for (const ExPolygon &expolygon : layerm->fill_expolygons())
  468. append(acc, to_polygons(expolygon));
  469. for (const ExtrusionEntity *ee : layerm->thin_fills().entities)
  470. append(acc, offset(dynamic_cast<const ExtrusionPath*>(ee)->polyline, float(iflow.scaled_width() / 2.f + SCALED_EPSILON)));
  471. covered_by_infill = union_(acc);
  472. }
  473. // compute the non covered area
  474. ExPolygons non_covered = diff_ex(to_polygons(layerm->slices().surfaces), union_(covered_by_perimeters, covered_by_infill));
  475. /*
  476. if (0) {
  477. printf "max non covered = %f\n", List::Util::max(map unscale unscale $_->area, @$non_covered);
  478. require "Slic3r/SVG.pm";
  479. Slic3r::SVG::output(
  480. "gaps.svg",
  481. expolygons => [ map $_->expolygon, @{$layerm->slices} ],
  482. red_expolygons => union_ex([ map @$_, (@$covered_by_perimeters, @$covered_by_infill) ]),
  483. green_expolygons => union_ex($non_covered),
  484. no_arrows => 1,
  485. polylines => [
  486. map $_->polygon->split_at_first_point, map @$_, @{$layerm->perimeters},
  487. ],
  488. );
  489. }
  490. */
  491. THEN("no gap between perimeters and infill") {
  492. size_t num_non_convered = std::count_if(non_covered.begin(), non_covered.end(),
  493. [&iflow](const ExPolygon &ex){ return ex.area() > sqr(double(iflow.scaled_width())); });
  494. REQUIRE(num_non_convered == 0);
  495. }
  496. }
  497. SCENARIO("Perimeters3", "[Perimeters]")
  498. {
  499. auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
  500. { "skirts", 0 },
  501. { "perimeters", 3 },
  502. { "layer_height", 0.15 },
  503. { "bridge_speed", 99 },
  504. { "enable_dynamic_overhang_speeds", false },
  505. // to prevent bridging over sparse infill
  506. { "fill_density", 0 },
  507. { "overhangs", true },
  508. // to prevent speeds from being altered
  509. { "cooling", "0" },
  510. // to prevent speeds from being altered
  511. { "first_layer_speed", "100%" }
  512. });
  513. auto test = [&config](const Vec3d &scale) {
  514. std::string gcode = Slic3r::Test::slice({ mesh(Slic3r::Test::TestMesh::V, Vec3d::Zero(), scale) }, config);
  515. GCodeReader parser;
  516. std::set<coord_t> z_with_bridges;
  517. const double bridge_speed = config.opt_float("bridge_speed") * 60.;
  518. parser.parse_buffer(gcode, [&z_with_bridges, bridge_speed](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
  519. {
  520. if (line.extruding(self) && line.dist_XY(self) > 0 && is_approx<double>(line.new_F(self), bridge_speed))
  521. z_with_bridges.insert(scaled<coord_t>(self.z()));
  522. });
  523. return z_with_bridges.size();
  524. };
  525. GIVEN("V shape, unscaled") {
  526. int n = test(Vec3d(1., 1., 1.));
  527. // One bridge layer under the V middle and one layer (two briding areas) under tops
  528. THEN("no overhangs printed with bridge speed") {
  529. REQUIRE(n == 2);
  530. }
  531. }
  532. GIVEN("V shape, scaled 3x in X") {
  533. int n = test(Vec3d(3., 1., 1.));
  534. // except for the two internal solid layers above void
  535. THEN("overhangs printed with bridge speed") {
  536. REQUIRE(n > 2);
  537. }
  538. }
  539. }
  540. SCENARIO("Perimeters4", "[Perimeters]")
  541. {
  542. auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
  543. { "seam_position", "random" }
  544. });
  545. std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config);
  546. THEN("successful generation of G-code with seam_position = random") {
  547. REQUIRE(! gcode.empty());
  548. }
  549. }
  550. SCENARIO("Seam alignment", "[Perimeters]")
  551. {
  552. auto test = [](Test::TestMesh model) {
  553. auto config = Slic3r::DynamicPrintConfig::full_print_config_with({
  554. { "seam_position", "aligned" },
  555. { "skirts", 0 },
  556. { "perimeters", 1 },
  557. { "fill_density", 0 },
  558. { "top_solid_layers", 0 },
  559. { "bottom_solid_layers", 0 },
  560. { "retract_layer_change", "0" }
  561. });
  562. std::string gcode = Slic3r::Test::slice({ model }, config);
  563. bool was_extruding = false;
  564. Points seam_points;
  565. GCodeReader parser;
  566. parser.parse_buffer(gcode, [&was_extruding, &seam_points](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line)
  567. {
  568. if (line.extruding(self)) {
  569. if (! was_extruding)
  570. seam_points.emplace_back(self.xy_scaled());
  571. was_extruding = true;
  572. } else if (! line.cmd_is("M73")) {
  573. // skips remaining time lines (M73)
  574. was_extruding = false;
  575. }
  576. });
  577. THEN("seam is aligned") {
  578. size_t num_not_aligned = 0;
  579. for (size_t i = 1; i < seam_points.size(); ++ i) {
  580. double d = (seam_points[i] - seam_points[i - 1]).cast<double>().norm();
  581. // Seams shall be aligned up to 3mm.
  582. if (d > scaled<double>(3.))
  583. ++ num_not_aligned;
  584. }
  585. REQUIRE(num_not_aligned == 0);
  586. }
  587. };
  588. GIVEN("20mm cube") {
  589. test(Slic3r::Test::TestMesh::cube_20x20x20);
  590. }
  591. GIVEN("small_dorito") {
  592. test(Slic3r::Test::TestMesh::small_dorito);
  593. }
  594. }