test_fill.cpp 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921
  1. //#define CATCH_CONFIG_DISABLE
  2. #include <catch_main.hpp>
  3. #include "test_data.hpp"
  4. #include <libslic3r/Fill/Fill.hpp>
  5. #include <libslic3r/Print.hpp>
  6. #include <libslic3r/ExtrusionEntity.hpp>
  7. #include <libslic3r/Layer.hpp>
  8. #include <libslic3r/Geometry.hpp>
  9. #include <libslic3r/Flow.hpp>
  10. #include <libslic3r/ClipperUtils.hpp>
  11. #include <libslic3r/SVG.hpp>
  12. #include <libslic3r/Format/3mf.hpp>
  13. using namespace Slic3r;
  14. using namespace Slic3r::Geometry;
  15. using namespace Slic3r::Test;
  16. bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_spacing, double angle = 0, double density = 1.0);
  17. //TEST_CASE("Fill: adjusted solid distance") {
  18. // Print print;
  19. // int surface_width {250};
  20. //
  21. // int distance {Slic3r::Flow::solid_spacing(surface_width, 47)};
  22. //
  23. // REQUIRE(distance == Approx(50));
  24. // REQUIRE(surface_width % distance == 0);
  25. //}
  26. Polylines test(const ExPolygon& poly, Fill &filler, const FillParams &params){
  27. Surface surface{ Slic3r::Surface((stPosTop | stDensSolid), poly) };
  28. return filler.fill_surface(&surface, params);
  29. }
  30. TEST_CASE("Fill: Pattern Path Length") {
  31. Fill* filler {Slic3r::Fill::new_from_type("rectilinear")};
  32. filler->angle = -(PI) / 2.0;
  33. FillParams params{};
  34. params.dont_adjust = true;
  35. params.density = 0.1; // 5/50
  36. filler->set_bounding_box(BoundingBox(Point(0, 0), Point::new_scale(Point(100, 100))));
  37. filler->init_spacing(5, params);
  38. //params.endpoints_overlap = false;
  39. SECTION("Square") {
  40. Points test_set{};
  41. test_set.reserve(4);
  42. Points points {Point{0,0}, Point{100,0}, Point{100,100}, Point{0,100}};
  43. for (size_t i = 0; i < 4; ++i) {
  44. std::transform(points.cbegin()+i, points.cend(), std::back_inserter(test_set), [] (const Point& a) -> Point { return Point::new_scale(a); } );
  45. std::transform(points.cbegin(), points.cbegin()+i, std::back_inserter(test_set), [] (const Point& a) -> Point { return Point::new_scale(a); } );
  46. Slic3r::ExPolygon expoly{};
  47. expoly.contour = Slic3r::Polygon{ test_set };
  48. Polylines paths {test(expoly, *filler, params)};
  49. REQUIRE(paths.size() == 1); // one continuous path
  50. // TODO: determine what the "Expected length" should be for rectilinear fill of a 100x100 polygon.
  51. // This check only checks that it's above scale(3*100 + 2*50) + scaled_epsilon.
  52. // ok abs($paths->[0]->length - scale(3*100 + 2*50)) - scaled_epsilon, 'path has expected length';
  53. REQUIRE(std::abs(paths[0].length() - static_cast<double>(scale_(3*100 + 2*50))) - SCALED_EPSILON > 0); // path has expected length
  54. test_set.clear();
  55. }
  56. }
  57. SECTION("Diamond with endpoints on grid") {
  58. Points points {Point{0,0}, Point{100,0}, Point{150,50}, Point{100,100}, Point{0,100}, Point{-50,50}};
  59. Points test_set{};
  60. test_set.reserve(6);
  61. std::transform(points.cbegin(), points.cend(), std::back_inserter(test_set), [] (const Point& a) -> Point { return Point::new_scale(a); } );
  62. Slic3r::ExPolygon expoly;
  63. expoly.contour = Slic3r::Polygon(test_set);
  64. Polylines paths {test(expoly, *filler, params)};
  65. REQUIRE(paths.size() == 1); // one continuous path
  66. }
  67. SECTION("Square with hole") {
  68. Points square { Point{0,0}, Point{100,0}, Point{100,100}, Point{0,100}};
  69. Points hole {Point{25,25}, Point{75,25}, Point{75,75}, Point{25,75} };
  70. std::reverse(hole.begin(), hole.end());
  71. Points test_hole{};
  72. Points test_square{};
  73. std::transform(square.cbegin(), square.cend(), std::back_inserter(test_square), [] (const Point& a) -> Point { return Point::new_scale(a); } );
  74. std::transform(hole.cbegin(), hole.cend(), std::back_inserter(test_hole), [] (const Point& a) -> Point { return Point::new_scale(a); } );
  75. for (double angle : {-(PI/2.0), -(PI/4.0), -(PI), PI/2.0, PI}) {
  76. for (double spacing : {25.0, 5.0, 7.5, 8.5}) {
  77. FillParams params_local = params;
  78. params_local.density = filler->get_spacing() / spacing;
  79. filler->angle = angle;
  80. Slic3r::ExPolygon e{};
  81. e.contour = Slic3r::Polygon(test_square);
  82. e.holes = Slic3r::Polygons{Slic3r::Polygon(test_hole)};
  83. Polylines paths {test(e, *filler, params_local)};
  84. //std::cout << "paths.size="<<paths.size() << "\n";
  85. //{
  86. // std::stringstream stri;
  87. // stri << "squarewithhole.svg";
  88. // SVG svg(stri.str());
  89. // svg.draw(paths);
  90. // svg.draw(e);
  91. // svg.Close();
  92. //}
  93. //path CAN loop around the hole
  94. REQUIRE(paths.size() >= 1);
  95. REQUIRE(paths.size() <= 3);
  96. // paths don't cross hole
  97. REQUIRE(diff_pl(paths, offset(e, (float)(+SCALED_EPSILON * 10))).size() == 0);
  98. }
  99. }
  100. }
  101. SECTION("Regression: Missing infill segments in some rare circumstances") {
  102. FillParams params_local = params;
  103. params_local.density = 1;
  104. params_local.dont_adjust = false;
  105. Fill* filler_local = { Slic3r::Fill::new_from_type("rectilinear") };
  106. filler_local->angle = (PI/4.0);
  107. filler_local->set_bounding_box(BoundingBox(Point(0, 0), Point(2512749, 2512749)));
  108. filler_local->init_spacing(0.654498, params_local);
  109. //filler_local->endpoints_overlap = unscale(359974);
  110. filler_local->layer_id = 66;
  111. filler_local->z = 20.15;
  112. Points points {Point{25771516,14142125},Point{14142138,25771515},Point{2512749,14142131},Point{14142125,2512749}};
  113. Slic3r::ExPolygon expoly{};
  114. expoly.contour = Slic3r::Polygon(points);
  115. Polylines paths {test(expoly, *filler_local, params_local)};
  116. REQUIRE(paths.size() == 1); // one continuous path
  117. // TODO: determine what the "Expected length" should be for rectilinear fill of a 100x100 polygon.
  118. // This check only checks that it's above scale(3*100 + 2*50) + scaled_epsilon.
  119. // ok abs($paths->[0]->length - scale(3*100 + 2*50)) - scaled_epsilon, 'path has expected length';
  120. REQUIRE(std::abs(paths[0].length() - static_cast<double>(scale_(3*100 + 2*50))) - SCALED_EPSILON > 0); // path has expected length
  121. }
  122. SECTION("Rotated Square") {
  123. Points square { Point::new_scale(0,0), Point::new_scale(50,0), Point::new_scale(50,50), Point::new_scale(0,50)};
  124. ExPolygon expolygon{};
  125. expolygon.contour = Slic3r::Polygon(square);
  126. auto filler {Slic3r::Fill::new_from_type("rectilinear")};
  127. filler->bounding_box = expolygon.contour.bounding_box();
  128. filler->angle = 0.F;
  129. Surface surface {(stPosTop|stDensSolid), expolygon};
  130. Flow flow = Flow::new_from_width(0.69f, 0.4f, 0.50f, 1.f, false); //width, height, nozzle_diameter, spacing_ratio, is_bridge
  131. params.density = 1.0;
  132. filler->init_spacing(flow.spacing(), params);
  133. for (auto angle : { 0.0, 45.0}) {
  134. surface.expolygon.rotate(angle, Point{0,0});
  135. Polylines paths = filler->fill_surface(&surface, params);
  136. REQUIRE(paths.size() == 1);
  137. }
  138. }
  139. SECTION("Solid surface fill") {
  140. Points points {
  141. Point(6883102, 9598327),
  142. Point(6883102, 20327272),
  143. Point(3116896, 20327272),
  144. Point(3116896, 9598327)
  145. };
  146. Slic3r::ExPolygon expolygon{};
  147. expolygon.contour = Slic3r::Polygon{ points };
  148. REQUIRE(test_if_solid_surface_filled(expolygon, 0.55) == true);
  149. for (size_t i = 0; i <= 20; ++i)
  150. {
  151. expolygon.scale(1.05);
  152. //FIXME number overflow.
  153. REQUIRE(test_if_solid_surface_filled(expolygon, 0.55) == true);
  154. }
  155. }
  156. SECTION("Solid surface fill") {
  157. Points points {
  158. Point{59515297,5422499},Point{59531249,5578697},Point{59695801,6123186},
  159. Point{59965713,6630228},Point{60328214,7070685},Point{60773285,7434379},
  160. Point{61274561,7702115},Point{61819378,7866770},Point{62390306,7924789},
  161. Point{62958700,7866744},Point{63503012,7702244},Point{64007365,7434357},
  162. Point{64449960,7070398},Point{64809327,6634999},Point{65082143,6123325},
  163. Point{65245005,5584454},Point{65266967,5422499},Point{66267307,5422499},
  164. Point{66269190,8310081},Point{66275379,17810072},Point{66277259,20697500},
  165. Point{65267237,20697500},Point{65245004,20533538},Point{65082082,19994444},
  166. Point{64811462,19488579},Point{64450624,19048208},Point{64012101,18686514},
  167. Point{63503122,18415781},Point{62959151,18251378},Point{62453416,18198442},
  168. Point{62390147,18197355},Point{62200087,18200576},Point{61813519,18252990},
  169. Point{61274433,18415918},Point{60768598,18686517},Point{60327567,19047892},
  170. Point{59963609,19493297},Point{59695865,19994587},Point{59531222,20539379},
  171. Point{59515153,20697500},Point{58502480,20697500},Point{58502480,5422499}
  172. };
  173. Slic3r::ExPolygon expolygon;
  174. expolygon.contour = Slic3r::Polygon{ points };
  175. REQUIRE(test_if_solid_surface_filled(expolygon, 0.55) == true);
  176. REQUIRE(test_if_solid_surface_filled(expolygon, 0.55, PI/2.0) == true);
  177. }
  178. SECTION("Solid surface fill") {
  179. Points points {
  180. Point::new_scale(0,0),Point::new_scale(98,0),Point::new_scale(98,10), Point::new_scale(0,10)
  181. };
  182. Slic3r::ExPolygon expolygon{};
  183. expolygon.contour = Slic3r::Polygon{ points };
  184. REQUIRE(test_if_solid_surface_filled(expolygon, 0.5, 45.0, 0.99) == true);
  185. }
  186. }
  187. TEST_CASE("Fill area: check if periemter give the good values")
  188. {
  189. Model model{};
  190. TriangleMesh sample_mesh = make_cube(5, 5, 0.2);
  191. double volume = (5 * 5 * 0.2);
  192. DynamicPrintConfig &config = Slic3r::DynamicPrintConfig::full_print_config();
  193. config.set_key_value("perimeters", new ConfigOptionInt(1));
  194. config.set_key_value("top_solid_layers", new ConfigOptionInt(1));
  195. config.set_key_value("bottom_solid_layers", new ConfigOptionInt(1));
  196. config.set_key_value("enforce_full_fill_volume", new ConfigOptionBool(false));
  197. config.set_key_value("infill_overlap", new ConfigOptionFloatOrPercent(0.1, false));
  198. config.set_key_value("skirts", new ConfigOptionInt(0));
  199. config.set_key_value("layer_height", new ConfigOptionFloat(0.2)); // get a known number of layers
  200. config.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(0.2, false));
  201. config.set_key_value("extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  202. config.set_key_value("infill_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  203. config.set_key_value("perimeter_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  204. config.set_key_value("first_layer_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  205. config.set_key_value("external_perimeter_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  206. config.set_key_value("solid_infill_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  207. config.set_key_value("top_infill_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  208. SECTION("no overlap")
  209. {
  210. config.set_key_value("infill_overlap", new ConfigOptionFloatOrPercent(0, false));
  211. config.set_key_value("external_perimeter_overlap", new ConfigOptionPercent(0));
  212. config.set_key_value("solid_infill_overlap", new ConfigOptionPercent(0));
  213. Print print{};
  214. Slic3r::Test::init_print(print, { sample_mesh }, model, &config);
  215. print.process();
  216. const LayerRegion *lr = print.get_object(0)->get_layer(0)->regions()[0];
  217. double area_infill = unscaled(unscaled(lr->fill_surfaces.surfaces.front().area()));
  218. REQUIRE(lr->fill_no_overlap_expolygons.empty());
  219. double area_computed = (5-0.5*2) * (5-0.5*2);
  220. REQUIRE(std::abs(area_computed - area_infill) < 0.001);
  221. }
  222. SECTION("only encroachment (0.2mm)")
  223. {
  224. config.set_key_value("infill_overlap", new ConfigOptionFloatOrPercent(0.2, false));
  225. config.set_key_value("external_perimeter_overlap", new ConfigOptionPercent(0));
  226. config.set_key_value("perimeter_overlap", new ConfigOptionPercent(100));
  227. config.set_key_value("solid_infill_overlap", new ConfigOptionPercent(0));
  228. Print print{};
  229. Slic3r::Test::init_print(print, { sample_mesh }, model, &config);
  230. print.process();
  231. const LayerRegion *lr = print.get_object(0)->get_layer(0)->regions()[0];
  232. REQUIRE(1 == lr->fill_surfaces.surfaces.size());
  233. REQUIRE(1 == lr->fill_no_overlap_expolygons.size());
  234. double area_infill = unscaled(unscaled(lr->fill_surfaces.surfaces[0].area()));
  235. double area_infill_no_overlap = unscaled(unscaled(lr->fill_no_overlap_expolygons[0].area())); // note: don't need to intersect as there is only one fill_surfaces
  236. double area_no_encroach_computed = (5-0.5*2) * (5-0.5*2);
  237. double area_computed = (5-0.3*2) * (5-0.3*2);
  238. REQUIRE(area_infill_no_overlap < area_infill);
  239. REQUIRE(std::abs(area_computed - area_infill) < 0.001);
  240. REQUIRE(std::abs(area_no_encroach_computed - area_infill_no_overlap) < 0.001);
  241. }
  242. SECTION("only encroachment (40%)")
  243. {
  244. // % over (perimeter_spacing + solid_fill_spacing)/2, but no periemter overlap
  245. // (note: here it's the external perimeter, as we have only one perimeter)
  246. config.set_key_value("infill_overlap", new ConfigOptionFloatOrPercent(40, true)); // 40% -> 0.2 value
  247. config.set_key_value("external_perimeter_overlap", new ConfigOptionPercent(0));
  248. config.set_key_value("perimeter_overlap", new ConfigOptionPercent(100));
  249. config.set_key_value("solid_infill_overlap", new ConfigOptionPercent(0));
  250. Print print{};
  251. Slic3r::Test::init_print(print, { sample_mesh }, model, &config);
  252. print.process();
  253. const LayerRegion *lr = print.get_object(0)->get_layer(0)->regions()[0];
  254. REQUIRE(1 == lr->fill_surfaces.surfaces.size());
  255. REQUIRE(1 == lr->fill_no_overlap_expolygons.size());
  256. double area_infill = unscaled(unscaled(lr->fill_surfaces.surfaces[0].area()));
  257. double area_infill_no_overlap = unscaled(unscaled(lr->fill_no_overlap_expolygons[0].area())); // note: don't need to intersect as there is only one fill_surfaces
  258. double area_no_encroach_computed = (5-0.5*2) * (5-0.5*2);
  259. double area_computed = (5-0.3*2) * (5-0.3*2);
  260. REQUIRE(area_infill_no_overlap < area_infill);
  261. REQUIRE(std::abs(area_computed - area_infill) < 0.001);
  262. REQUIRE(std::abs(area_no_encroach_computed - area_infill_no_overlap) < 0.001);
  263. }
  264. SECTION("only overlap")
  265. {
  266. config.set_key_value("infill_overlap", new ConfigOptionFloatOrPercent(0, false));
  267. config.set_key_value("external_perimeter_overlap", new ConfigOptionPercent(100));
  268. config.set_key_value("perimeter_overlap", new ConfigOptionPercent(100));
  269. config.set_key_value("solid_infill_overlap", new ConfigOptionPercent(100));
  270. Print print{};
  271. Slic3r::Test::init_print(print, { sample_mesh }, model, &config);
  272. print.process();
  273. const LayerRegion *lr = print.get_object(0)->get_layer(0)->regions()[0];
  274. REQUIRE(1 == lr->fill_surfaces.surfaces.size());
  275. REQUIRE(lr->fill_no_overlap_expolygons.empty());
  276. double area_infill = unscaled(unscaled(lr->fill_surfaces.surfaces[0].area()));
  277. double spacing_diff = (0.5f - Flow::rounded_rectangle_extrusion_spacing(0.5f, 0.2f, 1.f))/2;
  278. double area_computed = (5-(0.5-spacing_diff)*2) * (5-(0.5-spacing_diff)*2);
  279. REQUIRE(std::abs(area_computed - area_infill) < 0.001);
  280. }
  281. SECTION("both")
  282. {
  283. config.set_key_value("infill_overlap", new ConfigOptionFloatOrPercent(0.1, false));
  284. config.set_key_value("external_perimeter_overlap", new ConfigOptionPercent(100));
  285. config.set_key_value("perimeter_overlap", new ConfigOptionPercent(100));
  286. config.set_key_value("solid_infill_overlap", new ConfigOptionPercent(100));
  287. Print print{};
  288. Slic3r::Test::init_print(print, { sample_mesh }, model, &config);
  289. print.process();
  290. const LayerRegion *lr = print.get_object(0)->get_layer(0)->regions()[0];
  291. REQUIRE(1 == lr->fill_surfaces.surfaces.size());
  292. REQUIRE(1 == lr->fill_no_overlap_expolygons.size());
  293. double area_infill = unscaled(unscaled(lr->fill_surfaces.surfaces[0].area()));
  294. double area_infill_no_overlap = unscaled(unscaled(lr->fill_no_overlap_expolygons[0].area())); // note: don't need to intersect as there is only one fill_surfaces
  295. double spacing_diff = (0.5f - Flow::rounded_rectangle_extrusion_spacing(0.5f, 0.2f, 1.f))/2;
  296. double area_no_encroach_computed = (5-(0.5-spacing_diff)*2) * (5-(0.5-spacing_diff)*2);
  297. double area_computed = (5-(0.4-spacing_diff)*2) * (5-(0.4-spacing_diff)*2);
  298. REQUIRE(area_infill_no_overlap < area_infill);
  299. REQUIRE(std::abs(area_computed - area_infill) < 0.001);
  300. REQUIRE(std::abs(area_no_encroach_computed - area_infill_no_overlap) < 0.001);
  301. }
  302. }
  303. void test_all(DynamicPrintConfig &config, double& extrusion_width){
  304. SECTION("45°"){
  305. config.set_deserialize("fill_angle", "45");
  306. config.set_key_value("first_layer_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  307. extrusion_width = 0.5;
  308. //test all solid fills
  309. SECTION("rectilinear") { config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipRectilinear)); }
  310. SECTION("rectilinear with gap fill") { config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipRectilinearWGapFill)); }
  311. SECTION("ipMonotonic") { config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipMonotonic)); }
  312. SECTION("ipMonotonicWGapFill") { config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipMonotonicWGapFill)); }
  313. SECTION("ipConcentric") { config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipConcentric)); }
  314. SECTION("ipConcentricGapFill") { config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipConcentricGapFill)); }
  315. SECTION("ipHilbertCurve") { config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipHilbertCurve)); }
  316. SECTION("ipArchimedeanChords") { config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipArchimedeanChords)); }
  317. SECTION("ipOctagramSpiral") { config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipOctagramSpiral)); }
  318. SECTION("ipSmooth") { config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipSmooth)); }
  319. }
  320. SECTION("0° with bad spacing") {
  321. config.set_deserialize("fill_angle", "0");
  322. config.set_key_value("first_layer_extrusion_width", new ConfigOptionFloatOrPercent(0.415, false));
  323. extrusion_width = 0.415;
  324. SECTION("rectilinear") { config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipRectilinear)); }
  325. SECTION("rectilinear with gap fill") { config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipRectilinearWGapFill)); }
  326. SECTION("ipMonotonic") { config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipMonotonic)); }
  327. SECTION("ipMonotonicWGapFill") { config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipMonotonicWGapFill)); }
  328. SECTION("ipConcentric") { config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipConcentric)); }
  329. SECTION("ipConcentricGapFill") { config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipConcentricGapFill)); }
  330. SECTION("ipHilbertCurve") { config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipHilbertCurve)); }
  331. SECTION("ipArchimedeanChords") { config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipArchimedeanChords)); }
  332. SECTION("ipOctagramSpiral") { config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipOctagramSpiral)); }
  333. SECTION("ipSmooth") { config.set_key_value("bottom_fill_pattern", new ConfigOptionEnum<InfillPattern>(ipSmooth)); }
  334. }
  335. }
  336. #include "libslic3r/GCodeReader.hpp"
  337. TEST_CASE("Fill: extrude gcode and check it")
  338. {
  339. SECTION("simple square")
  340. {
  341. Model model{};
  342. TriangleMesh sample_mesh = make_cube(5, 5, 0.2);
  343. const double volume = (5 * 5 * 0.2);
  344. //sample_mesh.repair();
  345. DynamicPrintConfig &config = Slic3r::DynamicPrintConfig::full_print_config();
  346. config.set_key_value("perimeters", new ConfigOptionInt(1));
  347. config.set_key_value("top_solid_layers", new ConfigOptionInt(1));
  348. config.set_key_value("bottom_solid_layers", new ConfigOptionInt(1));
  349. config.set_key_value("infill_overlap", new ConfigOptionFloatOrPercent(0.1, false));
  350. config.set_key_value("external_perimeter_overlap", new ConfigOptionPercent(100));
  351. config.set_key_value("perimeter_overlap", new ConfigOptionPercent(100));
  352. config.set_key_value("solid_infill_overlap", new ConfigOptionPercent(100));
  353. config.set_key_value("extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  354. config.set_key_value("infill_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  355. config.set_key_value("perimeter_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  356. config.set_key_value("first_layer_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  357. config.set_key_value("external_perimeter_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  358. config.set_key_value("solid_infill_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  359. config.set_key_value("top_infill_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  360. double extrusion_width = 0.5;
  361. config.set_deserialize("only_one_perimeter_top", "0");
  362. SECTION("classic"){
  363. config.set_key_value("perimeter_generator", new ConfigOptionEnum<PerimeterGeneratorType>(PerimeterGeneratorType::Classic));
  364. test_all(config, extrusion_width);
  365. }
  366. SECTION("arachne"){
  367. config.set_key_value("perimeter_generator", new ConfigOptionEnum<PerimeterGeneratorType>(PerimeterGeneratorType::Arachne));
  368. test_all(config, extrusion_width);
  369. }
  370. config.set_key_value("enforce_full_fill_volume", new ConfigOptionBool(true));
  371. config.set_key_value("skirts", new ConfigOptionInt(0));
  372. //simplier than auto opt = new ConfigOptionFloatsOrPercents{FloatOrPercent{0, false}}; opt.set_is_extruder_size(true);
  373. config.set_deserialize("seam_gap", "0");
  374. config.set_key_value("layer_height", new ConfigOptionFloat(0.2)); // get a known number of layers
  375. config.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(0.2, false));
  376. auto event_counter{ 0U };
  377. std::string stage;
  378. Print print{};
  379. Slic3r::Test::init_print(print, { sample_mesh }, model, &config);
  380. print.process();
  381. std::string gcode_filepath{ "" };
  382. Slic3r::Test::gcode(gcode_filepath, print);
  383. //std::cout << "gcode generation done\n";
  384. std::string gcode_from_file = read_to_string(gcode_filepath);
  385. // model = print.model();
  386. // Slic3r::store_3mf("test.3mf", &model, &print.full_print_config(), OptionStore3mf{});
  387. //string[] lineArray = gcode_from_file
  388. GCodeReader parser;
  389. double volume_extruded = 0;
  390. int idx = 0;
  391. double volume_perimeter_extruded = 0;
  392. double volume_infill_extruded = 0;
  393. // add remaining time lines where needed
  394. parser.parse_buffer(gcode_from_file,
  395. [&](GCodeReader& reader, const GCodeReader::GCodeLine& line)
  396. {
  397. if (line.cmd_is("G1"))
  398. {
  399. if (line.dist_E(reader) > 0 && line.dist_XY(reader) > 0) {
  400. //std::cout << "add " << line.dist_E(reader)<<" now "<< volume_extruded<<"=>";
  401. volume_extruded += line.dist_E(reader)*(PI*1.75*1.75 / 4.);
  402. //std::cout << volume_extruded << "\n";
  403. if (idx<4)volume_perimeter_extruded += line.dist_E(reader)*(PI*1.75*1.75 / 4.);
  404. else volume_infill_extruded += line.dist_E(reader)*(PI*1.75*1.75 / 4.);
  405. idx++;
  406. }
  407. }
  408. });
  409. ExPolygons perimeter_center_line = offset_ex(print.get_object(0)->get_layer(0)->lslices[0], -scale_t(0.25f));
  410. //double perimeterRoundGapRemove = unscaled(print.get_object(0)->get_layer(0)->lslices[0].contour.length()) * 0.1*0.1 * (2 - (PI / 2));
  411. double perimeterRoundGapRemove = unscaled(perimeter_center_line[0].contour.length()) * 0.1*0.1 * (2 - (PI / 2));
  412. //double perimeterRoundGapAdd = unscaled(print.get_object(0)->get_layer(0)->lslices[0].contour.length()) * 0.1*0.1 * ((PI / 2));
  413. //for (Line &l : print.get_object(0)->get_layer(0)->slices.expolygons[0].contour.lines()) {
  414. //}
  415. //std::cout << "flow mm3permm: " << Flow{ 0.5f,0.2f,0.4f,false }.mm3_per_mm() << "\n";
  416. //std::cout << "perimeter : " << unscaled(print.get_object(0)->get_layer(0)->slices.expolygons[0].contour.length()) << " != " << (PI * 10) << "\n";
  417. //std::cout << "created a mesh of volume " << volume << " and i have extruded " << volume_extruded << " mm3.\n";
  418. //std::cout << "Note that if we remove the bits of the external extrusion, it's only a volume of " << (volume - perimeterRoundGapRemove) << " that needs to be filled\n";
  419. //std::cout << "Note that if we add the bits of the external extrusion, it's a volume of " << (volume + perimeterRoundGapAdd) << " that needs to be filled\n";
  420. double volumeExtrPerimeter = ExtrusionVolume{}.get(print.get_object(0)->get_layer(0)->regions()[0]->perimeters);
  421. double volumeExtrInfill = ExtrusionVolume{}.get(print.get_object(0)->get_layer(0)->regions()[0]->fills);
  422. double volumeInfill = 0;
  423. for (const ExPolygon & p : print.get_object(0)->get_layer(0)->regions()[0]->fill_no_overlap_expolygons) {
  424. volumeInfill += unscaled(unscaled(p.area()));
  425. }
  426. double spacing_diff = (extrusion_width - Flow::rounded_rectangle_extrusion_spacing(extrusion_width, 0.2f, 1.f))/2;
  427. double fill_raw_area_no_encroach = (5-(extrusion_width-spacing_diff)*2) * (5-(extrusion_width-spacing_diff)*2);
  428. double fill_raw_area = (5-(extrusion_width-0.1-spacing_diff)*2) * (5-(extrusion_width-0.1-spacing_diff)*2);
  429. double compute_perimeter_area = (5-extrusion_width)*4*Flow::rounded_rectangle_extrusion_spacing(extrusion_width, 0.2f, 1.f);
  430. std::cout << "area fill_no_overlap_expolygons= " << (unscaled(unscaled(print.get_object(0)->get_layer(0)->regions()[0]->fill_no_overlap_expolygons.front().contour.area()))) << "\n";
  431. volumeInfill *= 0.2;
  432. std::cout << "\nvolumeRealr=" << (volume_perimeter_extruded + volume_infill_extruded) << " volumeRealPerimeter= " << volume_perimeter_extruded << " and volumeRealInfill=" << volume_infill_extruded << " mm3." << "\n";
  433. std::cout << "volumeExtr=" << (volumeExtrPerimeter + volumeExtrInfill) << " volumeExtrPerimeter= " << volumeExtrPerimeter << " and volumeExtrInfill=" << volumeExtrInfill << " mm3." << "\n";
  434. std::cout << "volumePerimeter= " << (volume - volumeInfill) << " volumePerimeter(wo/bits)= " << (volume - volumeInfill- perimeterRoundGapRemove) << " and volumeInfill=" << volumeInfill << " mm3." << "\n";
  435. std::cout << "volume= " << (volume) << " raw_fill_volume="<<fill_raw_area*0.2<<" raw_fill_volume_no_encroach=" << fill_raw_area_no_encroach*0.2 << "\n";
  436. std::cout << "raw_fill_area="<<fill_raw_area<<" raw_fill_area_no_encroach=" << fill_raw_area_no_encroach << "\n";
  437. std::cout << "computed peri= " << (unscaled(perimeter_center_line[0].contour.length())*0.2*0.5 - 2*perimeterRoundGapRemove)<< " perimeterRoundGapRemove= " << (perimeterRoundGapRemove) << "\n";
  438. //Flow fl{0.5f, 0.2f, 0.4f, false};
  439. //{
  440. // std::stringstream stri;
  441. // stri << "extrusion_width_learning" << ".svg";
  442. // SVG svg(stri.str());
  443. // //svg.draw(bounds);
  444. // svg.draw(print.get_object(0)->get_layer(0)->slices.expolygons[0].contour, "green");
  445. // svg.draw(print.get_object(0)->get_layer(0)->regions()[0]->fill_no_overlap_expolygons, "black", scale_(0.01));
  446. // svg.draw(print.get_object(0)->get_layer(0)->regions()[0]->perimeters.as_polylines(), "orange", fl.scaled_width());
  447. // svg.draw(print.get_object(0)->get_layer(0)->regions()[0]->perimeters.as_polylines(), "red", fl.scaled_spacing());
  448. // svg.draw(print.get_object(0)->get_layer(0)->regions()[0]->fills.as_polylines(), "cyan", fl.scaled_width());
  449. // svg.draw(print.get_object(0)->get_layer(0)->regions()[0]->fills.as_polylines(), "blue", fl.scaled_spacing());
  450. // svg.Close();
  451. //}
  452. REQUIRE(abs(fill_raw_area_no_encroach*0.2 - volumeInfill) < 0.01);
  453. REQUIRE(abs(compute_perimeter_area * 0.2 - volumeExtrPerimeter) < 0.01);
  454. //std::cout << gcode_from_file;
  455. if(abs(volumeInfill - volumeExtrInfill) > EPSILON*5) // *5 for archimean chords
  456. std::cout<<"stop";
  457. REQUIRE(abs(volumeInfill - volumeExtrInfill) < EPSILON * 5);// *5 for archimean chords
  458. REQUIRE(abs(volumeInfill - volume_infill_extruded) < 0.01);
  459. REQUIRE(abs((volume - volumeInfill - perimeterRoundGapRemove) - volumeExtrPerimeter) < 0.01);
  460. REQUIRE(abs((volume - volumeInfill - perimeterRoundGapRemove) - volume_perimeter_extruded) < 0.1); //there are a bit less for seam mitigation
  461. // lower than the full volume because of the rounded extenral perimeter
  462. REQUIRE(volume_extruded < volume);
  463. //lower than the full volume - rounded external perimeter (because I used 4*5 as perimeter length instead of (5-offset)*4 )
  464. REQUIRE(volume_extruded > volume - ((5*4) * 0.2 * (0.5f-Flow::rounded_rectangle_extrusion_spacing(extrusion_width, 0.2f, 1.f))/2));
  465. clean_file(gcode_filepath, "gcode");
  466. }
  467. SECTION("simple disk") {
  468. Model model{};
  469. TriangleMesh sample_mesh = make_cylinder(5, 0.2);
  470. const double volume = (PI * 25 * 0.2);
  471. DynamicPrintConfig &config = Slic3r::DynamicPrintConfig::full_print_config();
  472. config.set_key_value("perimeters", new ConfigOptionInt(1));
  473. config.set_key_value("top_solid_layers", new ConfigOptionInt(1));
  474. config.set_key_value("bottom_solid_layers", new ConfigOptionInt(1));
  475. config.set_key_value("enforce_full_fill_volume", new ConfigOptionBool(true));
  476. config.set_key_value("infill_overlap", new ConfigOptionFloatOrPercent(0.1, true));
  477. config.set_key_value("perimeter_overlap", new ConfigOptionPercent(100));
  478. config.set_key_value("external_perimeter_overlap", new ConfigOptionPercent(100));
  479. config.set_deserialize("external_perimeter_cut_corners", "0");
  480. config.set_key_value("skirts", new ConfigOptionInt(0));
  481. config.set_key_value("layer_height", new ConfigOptionFloat(0.2)); // get a known number of layers
  482. config.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(0.2, false));
  483. config.set_key_value("extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  484. config.set_key_value("infill_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  485. config.set_key_value("perimeter_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  486. config.set_key_value("first_layer_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  487. config.set_key_value("external_perimeter_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  488. config.set_key_value("solid_infill_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  489. config.set_key_value("top_infill_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  490. auto event_counter{ 0U };
  491. std::string stage;
  492. Print print{};
  493. Slic3r::Test::init_print(print, { sample_mesh }, model, &config);
  494. print.process();
  495. std::string gcode_filepath{ "" };
  496. Slic3r::Test::gcode(gcode_filepath, print);
  497. //std::cout << "gcode generation done\n";
  498. std::string gcode_from_file = read_to_string(gcode_filepath);
  499. //string[] lineArray = gcode_from_file
  500. GCodeReader parser;
  501. double volume_extruded = 0;
  502. //int idx = 0;
  503. int step = 0;
  504. double volume_perimeter_extruded = 0;
  505. double volume_infill_extruded = 0;
  506. // add remaining time lines where needed
  507. parser.parse_buffer(gcode_from_file,
  508. [&](GCodeReader& reader, const GCodeReader::GCodeLine& line)
  509. {
  510. if(line.comment() == "TYPE:External perimeter")
  511. step = 1;
  512. if(line.comment() == "TYPE:Solid infill")
  513. step = 2;
  514. if (line.cmd_is("G1") && step > 0)
  515. {
  516. if (line.dist_E(reader) > 0 && line.dist_XY(reader) > 0) {
  517. //std::cout << "add " << line.dist_E(reader)<<" now "<< volume_extruded<<"=>";
  518. volume_extruded += line.dist_E(reader)*(PI*1.75*1.75 / 4.);
  519. //std::cout << volume_extruded << "\n";
  520. if (step == 1) volume_perimeter_extruded += line.dist_E(reader)*(PI*1.75*1.75 / 4.);
  521. else if (step == 2) volume_infill_extruded += line.dist_E(reader)*(PI*1.75*1.75 / 4.);
  522. }
  523. }
  524. });
  525. ExPolygons perimeter_center_line = offset_ex(print.get_object(0)->get_layer(0)->lslices[0], -scale_t(0.25f));
  526. //double perimeterRoundGapRemove = unscaled(print.get_object(0)->get_layer(0)->lslices[0].contour.length()) * 0.1*0.1 * (2 - (PI / 2));
  527. double perimeterRoundGapRemove = unscaled(perimeter_center_line[0].contour.length()) * 0.1*0.1 * (2 - (PI / 2));
  528. //double perimeterRoundGapAdd = unscaled(print.get_object(0)->get_layer(0)->lslices[0].contour.length()) * 0.1*0.1 * ((PI / 2));
  529. double volumeExtrPerimeter = ExtrusionVolume{}.get(print.get_object(0)->get_layer(0)->regions()[0]->perimeters);
  530. double volumeExtrInfill = ExtrusionVolume{}.get(print.get_object(0)->get_layer(0)->regions()[0]->fills);
  531. double volumeInfill = 0;
  532. ExPolygons infill_area = intersection_ex(print.get_object(0)->get_layer(0)->regions()[0]->fill_no_overlap_expolygons, print.get_object(0)->get_layer(0)->regions()[0]->fill_expolygons);
  533. for (const ExPolygon & p : infill_area) {
  534. volumeInfill += unscaled(unscaled(p.area()));
  535. }
  536. volumeInfill *= 0.2;
  537. std::cout << "volumeRealr=" << (volume_perimeter_extruded + volume_infill_extruded) << " volumeRealPerimeter= " << volume_perimeter_extruded << " and volumeRealInfill=" << volume_infill_extruded << " mm3." << "\n";
  538. std::cout << "volumeExtr=" << (volumeExtrPerimeter + volumeExtrInfill) << " volumeExtrPerimeter= " << volumeExtrPerimeter << " and volumeExtrInfill=" << volumeExtrInfill << " mm3." << "\n";
  539. std::cout << "volumePerimeter= " << (volume - volumeInfill) << " volumePerimeter(wo/bits)= " << (volume - volumeInfill - perimeterRoundGapRemove) << " and volumeInfill=" << volumeInfill << " mm3." << "\n";
  540. std::cout << "volume= " << (volume) << "\n";
  541. REQUIRE(abs(volumeInfill - volumeExtrInfill) < 0.001);
  542. REQUIRE(abs(volumeInfill - volume_infill_extruded) < 0.001);
  543. REQUIRE(abs((volume - volumeInfill - perimeterRoundGapRemove) - volumeExtrPerimeter) < 0.1); //there are a bit less for seam mitigation
  544. REQUIRE(abs(volumeExtrPerimeter - volume_perimeter_extruded) < 0.01);
  545. clean_file(gcode_filepath, "gcode");
  546. }
  547. }
  548. /*
  549. {
  550. my $collection = Slic3r::Polyline::Collection->new(
  551. Slic3r::Polyline->new([0,15], [0,18], [0,20]),
  552. Slic3r::Polyline->new([0,10], [0,8], [0,5]),
  553. );
  554. is_deeply
  555. [ map $_->[Y], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ],
  556. [20, 18, 15, 10, 8, 5],
  557. 'chained path';
  558. }
  559. {
  560. my $collection = Slic3r::Polyline::Collection->new(
  561. Slic3r::Polyline->new([4,0], [10,0], [15,0]),
  562. Slic3r::Polyline->new([10,5], [15,5], [20,5]),
  563. );
  564. is_deeply
  565. [ map $_->[X], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ],
  566. [reverse 4, 10, 15, 10, 15, 20],
  567. 'chained path';
  568. }
  569. {
  570. my $collection = Slic3r::ExtrusionPath::Collection->new(
  571. map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1),
  572. Slic3r::Polyline->new([0,15], [0,18], [0,20]),
  573. Slic3r::Polyline->new([0,10], [0,8], [0,5]),
  574. );
  575. is_deeply
  576. [ map $_->[Y], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ],
  577. [20, 18, 15, 10, 8, 5],
  578. 'chained path';
  579. }
  580. {
  581. my $collection = Slic3r::ExtrusionPath::Collection->new(
  582. map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1),
  583. Slic3r::Polyline->new([15,0], [10,0], [4,0]),
  584. Slic3r::Polyline->new([10,5], [15,5], [20,5]),
  585. );
  586. is_deeply
  587. [ map $_->[X], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ],
  588. [reverse 4, 10, 15, 10, 15, 20],
  589. 'chained path';
  590. }
  591. for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) {
  592. my $config = Slic3r::Config->new_from_defaults;
  593. $config->set('fill_pattern', $pattern);
  594. $config->set('external_fill_pattern', $pattern);
  595. $config->set('perimeters', 1);
  596. $config->set('skirts', 0);
  597. $config->set('fill_density', 20);
  598. $config->set('layer_height', 0.05);
  599. $config->set('perimeter_extruder', 1);
  600. $config->set('infill_extruder', 2);
  601. my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2);
  602. ok my $gcode = Slic3r::Test::gcode($print), "successful $pattern infill generation";
  603. my $tool = undef;
  604. my @perimeter_points = my @infill_points = ();
  605. Slic3r::GCode::Reader->new->parse($gcode, sub {
  606. my ($self, $cmd, $args, $info) = @_;
  607. if ($cmd =~ /^T(\d+)/) {
  608. $tool = $1;
  609. } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
  610. if ($tool == $config->perimeter_extruder-1) {
  611. push @perimeter_points, Slic3r::Point->new_scale($args->{X}, $args->{Y});
  612. } elsif ($tool == $config->infill_extruder-1) {
  613. push @infill_points, Slic3r::Point->new_scale($args->{X}, $args->{Y});
  614. }
  615. }
  616. });
  617. my $convex_hull = convex_hull(\@perimeter_points);
  618. ok !(defined first { !$convex_hull->contains_point($_) } @infill_points), "infill does not exceed perimeters ($pattern)";
  619. }
  620. {
  621. my $config = Slic3r::Config->new_from_defaults;
  622. $config->set('infill_only_where_needed', 1);
  623. $config->set('bottom_solid_layers', 0);
  624. $config->set('infill_extruder', 2);
  625. $config->set('infill_extrusion_width', 0.5);
  626. $config->set('fill_density', 40);
  627. $config->set('cooling', 0); # for preventing speeds from being altered
  628. $config->set('first_layer_speed', '100%'); # for preventing speeds from being altered
  629. my $test = sub {
  630. my $print = Slic3r::Test::init_print('pyramid', config => $config);
  631. my $tool = undef;
  632. my @infill_extrusions = (); # array of polylines
  633. Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
  634. my ($self, $cmd, $args, $info) = @_;
  635. if ($cmd =~ /^T(\d+)/) {
  636. $tool = $1;
  637. } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
  638. if ($tool == $config->infill_extruder-1) {
  639. push @infill_extrusions, Slic3r::Line->new_scale(
  640. [ $self->X, $self->Y ],
  641. [ $info->{new_X}, $info->{new_Y} ],
  642. );
  643. }
  644. }
  645. });
  646. return 0 if !@infill_extrusions; # prevent calling convex_hull() with no points
  647. my $convex_hull = convex_hull([ map $_->pp, map @$_, @infill_extrusions ]);
  648. return unscale unscale sum(map $_->area, @{offset([$convex_hull], scale(+$config->infill_extrusion_width/2))});
  649. };
  650. my $tolerance = 5; # mm^2
  651. $config->set('solid_infill_below_area', 0);
  652. ok $test->() < $tolerance,
  653. 'no infill is generated when using infill_only_where_needed on a pyramid';
  654. $config->set('solid_infill_below_area', 70);
  655. ok abs($test->() - $config->solid_infill_below_area) < $tolerance,
  656. 'infill is only generated under the forced solid shells';
  657. }
  658. {
  659. my $config = Slic3r::Config->new_from_defaults;
  660. $config->set('skirts', 0);
  661. $config->set('perimeters', 1);
  662. $config->set('fill_density', 0);
  663. $config->set('top_solid_layers', 0);
  664. $config->set('bottom_solid_layers', 0);
  665. $config->set('solid_infill_below_area', 20000000);
  666. $config->set('solid_infill_every_layers', 2);
  667. $config->set('perimeter_speed', 99);
  668. $config->set('external_perimeter_speed', 99);
  669. $config->set('cooling', 0);
  670. $config->set('first_layer_speed', '100%');
  671. my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
  672. my %layers_with_extrusion = ();
  673. Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
  674. my ($self, $cmd, $args, $info) = @_;
  675. if ($cmd eq 'G1' && $info->{dist_XY} > 0 && $info->{extruding}) {
  676. if (($args->{F} // $self->F) != $config->perimeter_speed*60) {
  677. $layers_with_extrusion{$self->Z} = ($args->{F} // $self->F);
  678. }
  679. }
  680. });
  681. ok !%layers_with_extrusion,
  682. "solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0";
  683. }
  684. {
  685. my $config = Slic3r::Config->new_from_defaults;
  686. $config->set('skirts', 0);
  687. $config->set('perimeters', 3);
  688. $config->set('fill_density', 0);
  689. $config->set('layer_height', 0.2);
  690. $config->set('first_layer_height', 0.2);
  691. $config->set('nozzle_diameter', [0.35]);
  692. $config->set('infill_extruder', 2);
  693. $config->set('solid_infill_extruder', 2);
  694. $config->set('infill_extrusion_width', 0.52);
  695. $config->set('solid_infill_extrusion_width', 0.52);
  696. $config->set('first_layer_extrusion_width', 0);
  697. my $print = Slic3r::Test::init_print('A', config => $config);
  698. my %infill = (); # Z => [ Line, Line ... ]
  699. my $tool = undef;
  700. Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
  701. my ($self, $cmd, $args, $info) = @_;
  702. if ($cmd =~ /^T(\d+)/) {
  703. $tool = $1;
  704. } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
  705. if ($tool == $config->infill_extruder-1) {
  706. my $z = 1 * $self->Z;
  707. $infill{$z} ||= [];
  708. push @{$infill{$z}}, Slic3r::Line->new_scale(
  709. [ $self->X, $self->Y ],
  710. [ $info->{new_X}, $info->{new_Y} ],
  711. );
  712. }
  713. }
  714. });
  715. my $grow_d = scale($config->infill_extrusion_width)/2;
  716. my $layer0_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.2} } ]);
  717. my $layer1_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.4} } ]);
  718. my $diff = diff($layer0_infill, $layer1_infill);
  719. $diff = offset2_ex($diff, -$grow_d, +$grow_d);
  720. $diff = [ grep { $_->area > 2*(($grow_d*2)**2) } @$diff ];
  721. is scalar(@$diff), 0, 'no missing parts in solid shell when fill_density is 0';
  722. }
  723. {
  724. # GH: #2697
  725. my $config = Slic3r::Config->new_from_defaults;
  726. $config->set('perimeter_extrusion_width', 0.72);
  727. $config->set('top_infill_extrusion_width', 0.1);
  728. $config->set('infill_extruder', 2); # in order to distinguish infill
  729. $config->set('solid_infill_extruder', 2); # in order to distinguish infill
  730. my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
  731. my %infill = (); # Z => [ Line, Line ... ]
  732. my %other = (); # Z => [ Line, Line ... ]
  733. my $tool = undef;
  734. Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
  735. my ($self, $cmd, $args, $info) = @_;
  736. if ($cmd =~ /^T(\d+)/) {
  737. $tool = $1;
  738. } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
  739. my $z = 1 * $self->Z;
  740. my $line = Slic3r::Line->new_scale(
  741. [ $self->X, $self->Y ],
  742. [ $info->{new_X}, $info->{new_Y} ],
  743. );
  744. if ($tool == $config->infill_extruder-1) {
  745. $infill{$z} //= [];
  746. push @{$infill{$z}}, $line;
  747. } else {
  748. $other{$z} //= [];
  749. push @{$other{$z}}, $line;
  750. }
  751. }
  752. });
  753. my $top_z = max(keys %infill);
  754. my $top_infill_grow_d = scale($config->top_infill_extrusion_width)/2;
  755. my $top_infill = union([ map @{$_->grow($top_infill_grow_d)}, @{ $infill{$top_z} } ]);
  756. my $perimeters_grow_d = scale($config->perimeter_extrusion_width)/2;
  757. my $perimeters = union([ map @{$_->grow($perimeters_grow_d)}, @{ $other{$top_z} } ]);
  758. my $covered = union_ex([ @$top_infill, @$perimeters ]);
  759. my @holes = map @{$_->holes}, @$covered;
  760. ok sum(map unscale unscale $_->area*-1, @holes) < 1, 'no gaps between top solid infill and perimeters';
  761. }
  762. */
  763. class ExtrusionGetExtrusionArea : public ExtrusionVisitor {
  764. Polygons grown_paths;
  765. public:
  766. ExtrusionGetExtrusionArea() {}
  767. void use(ExtrusionPath &path) override {
  768. polygons_append(grown_paths, offset(path.as_polyline().as_polyline(), scale_t(path.width)));
  769. }
  770. void use(ExtrusionPath3D &path3D) override { assert(false); }
  771. void use(ExtrusionMultiPath &multipath) override { for (ExtrusionPath path : multipath.paths) path.visit(*this); }
  772. void use(ExtrusionMultiPath3D &multipath) override { for (ExtrusionPath path : multipath.paths) path.visit(*this); }
  773. void use(ExtrusionLoop &loop) override { for (ExtrusionPath path : loop.paths) path.visit(*this); }
  774. void use(ExtrusionEntityCollection &collection) override { for (ExtrusionEntity *entity : collection.entities()) entity->visit(*this); }
  775. Polygons get(ExtrusionEntityCollection &coll) {
  776. for (ExtrusionEntity *entity : coll.entities()) entity->visit(*this);
  777. return grown_paths;
  778. }
  779. };
  780. //TODO: also check by volume extruded
  781. //TODO: replace the simple area coverage check by one that takes into account the width of the path, not only the default flow spacing
  782. //TODO: test more fills
  783. bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_width, double angle, double density) {
  784. auto* filler {Slic3r::Fill::new_from_type("concentricgapfill")};
  785. filler->bounding_box = expolygon.contour.bounding_box();
  786. filler->angle = angle;
  787. FillParams params;
  788. params.dont_adjust = false;
  789. FullPrintConfig config= FullPrintConfig::defaults();
  790. params.config = &config;
  791. Surface surface((stPosBottom | stDensSolid), expolygon);
  792. //note: here we do flow.width = flow_width , flow.gheight = 0.4, flow.nozzle_size = flow_width;
  793. params.flow = Flow::new_from_width( float(flow_width), 0.4, float(flow_width), 1.f, false);
  794. params.density = density;
  795. filler->init_spacing(params.flow.spacing(), params);
  796. // concentricgapfill can't output only Polylines, as it's a composed thing with gapfill
  797. //Polylines paths {filler->fill_surface(&surface, params)};
  798. ExtrusionEntityCollection coll;
  799. filler->fill_surface_extrusion(&surface, params, coll.set_entities());
  800. // check whether any part was left uncovered
  801. // figure out what is actually going on here re: data types
  802. Polygons grown_paths = ExtrusionGetExtrusionArea{}.get(coll);
  803. //grown_paths.reserve(paths.size());
  804. //std::for_each(paths.begin(), paths.end(), [filler, &grown_paths] (const Slic3r::Polyline& p) {
  805. // polygons_append(grown_paths, offset(p, scale_(filler->get_spacing() / 2.0)));
  806. //});
  807. ExPolygons uncovered = diff_ex(expolygon, grown_paths, Slic3r::ApplySafetyOffset::Yes);
  808. // ignore very small dots
  809. const auto scaled_flow_width { std::pow(scale_(flow_width), 2) };
  810. auto iter {std::remove_if(uncovered.begin(), uncovered.end(), [scaled_flow_width] (const ExPolygon& poly) {
  811. return poly.area() > scaled_flow_width;
  812. }) };
  813. uncovered.erase(iter, uncovered.end());
  814. double uncovered_area = 0;
  815. for (ExPolygon &p : uncovered) uncovered_area += unscaled(unscaled(p.area()));
  816. std::cout << "uncovered size =" << uncovered_area << " / "<< unscaled(unscaled(expolygon.area()))<<"\n";
  817. return uncovered_area < 0.05; // solid surface is (almost) fully filled
  818. }