test_fill.cpp 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. //#define CATCH_CONFIG_DISABLE
  2. #include <catch2/catch.hpp>
  3. #include "test_data.hpp"
  4. #include <libslic3r/Fill/Fill.hpp>
  5. #include <libslic3r/Print.hpp>
  6. #include <libslic3r/Geometry.hpp>
  7. #include <libslic3r/Flow.hpp>
  8. #include <libslic3r/ClipperUtils.hpp>
  9. #include <libslic3r/SVG.hpp>
  10. using namespace Slic3r;
  11. using namespace Slic3r::Geometry;
  12. using namespace Slic3r::Test;
  13. bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_spacing, double angle = 0, double density = 1.0);
  14. //TEST_CASE("Fill: adjusted solid distance") {
  15. // Print print;
  16. // int surface_width {250};
  17. //
  18. // int distance {Slic3r::Flow::solid_spacing(surface_width, 47)};
  19. //
  20. // REQUIRE(distance == Approx(50));
  21. // REQUIRE(surface_width % distance == 0);
  22. //}
  23. Polylines test(const ExPolygon& poly, Fill &filler, const FillParams &params){
  24. Surface surface{ Slic3r::Surface((stPosTop | stDensSolid), poly) };
  25. return filler.fill_surface(&surface, params);
  26. }
  27. TEST_CASE("Fill: Pattern Path Length") {
  28. Fill* filler {Slic3r::Fill::new_from_type("rectilinear")};
  29. filler->angle = -(PI) / 2.0;
  30. FillParams params{};
  31. params.dont_adjust = true;
  32. params.density = 0.1; // 5/50
  33. filler->set_bounding_box(BoundingBox(Point(0, 0), Point::new_scale(Point(100, 100))));
  34. filler->init_spacing(5, params);
  35. //params.endpoints_overlap = false;
  36. SECTION("Square") {
  37. Points test_set{};
  38. test_set.reserve(4);
  39. Points points {Point{0,0}, Point{100,0}, Point{100,100}, Point{0,100}};
  40. for (size_t i = 0; i < 4; ++i) {
  41. std::transform(points.cbegin()+i, points.cend(), std::back_inserter(test_set), [] (const Point& a) -> Point { return Point::new_scale(a); } );
  42. std::transform(points.cbegin(), points.cbegin()+i, std::back_inserter(test_set), [] (const Point& a) -> Point { return Point::new_scale(a); } );
  43. Slic3r::ExPolygon expoly{};
  44. expoly.contour = Slic3r::Polygon{ test_set };
  45. Polylines paths {test(expoly, *filler, params)};
  46. REQUIRE(paths.size() == 1); // one continuous path
  47. // TODO: determine what the "Expected length" should be for rectilinear fill of a 100x100 polygon.
  48. // This check only checks that it's above scale(3*100 + 2*50) + scaled_epsilon.
  49. // ok abs($paths->[0]->length - scale(3*100 + 2*50)) - scaled_epsilon, 'path has expected length';
  50. REQUIRE(std::abs(paths[0].length() - static_cast<double>(scale_(3*100 + 2*50))) - SCALED_EPSILON > 0); // path has expected length
  51. test_set.clear();
  52. }
  53. }
  54. SECTION("Diamond with endpoints on grid") {
  55. Points points {Point{0,0}, Point{100,0}, Point{150,50}, Point{100,100}, Point{0,100}, Point{-50,50}};
  56. Points test_set{};
  57. test_set.reserve(6);
  58. std::transform(points.cbegin(), points.cend(), std::back_inserter(test_set), [] (const Point& a) -> Point { return Point::new_scale(a); } );
  59. Slic3r::ExPolygon expoly;
  60. expoly.contour = Slic3r::Polygon(test_set);
  61. Polylines paths {test(expoly, *filler, params)};
  62. REQUIRE(paths.size() == 1); // one continuous path
  63. }
  64. SECTION("Square with hole") {
  65. Points square { Point{0,0}, Point{100,0}, Point{100,100}, Point{0,100}};
  66. Points hole {Point{25,25}, Point{75,25}, Point{75,75}, Point{25,75} };
  67. std::reverse(hole.begin(), hole.end());
  68. Points test_hole{};
  69. Points test_square{};
  70. std::transform(square.cbegin(), square.cend(), std::back_inserter(test_square), [] (const Point& a) -> Point { return Point::new_scale(a); } );
  71. std::transform(hole.cbegin(), hole.cend(), std::back_inserter(test_hole), [] (const Point& a) -> Point { return Point::new_scale(a); } );
  72. for (double angle : {-(PI/2.0), -(PI/4.0), -(PI), PI/2.0, PI}) {
  73. for (double spacing : {25.0, 5.0, 7.5, 8.5}) {
  74. FillParams params_local = params;
  75. params_local.density = filler->get_spacing() / spacing;
  76. filler->angle = angle;
  77. Slic3r::ExPolygon e{};
  78. e.contour = Slic3r::Polygon(test_square);
  79. e.holes = Slic3r::Polygons(Slic3r::Polygon(test_hole));
  80. Polylines paths {test(e, *filler, params_local)};
  81. //std::cout << "paths.size="<<paths.size() << "\n";
  82. //{
  83. // std::stringstream stri;
  84. // stri << "squarewithhole.svg";
  85. // SVG svg(stri.str());
  86. // svg.draw(paths);
  87. // svg.draw(e);
  88. // svg.Close();
  89. //}
  90. //path CAN loop around the hole
  91. REQUIRE(paths.size() >= 1);
  92. REQUIRE(paths.size() <= 3);
  93. // paths don't cross hole
  94. REQUIRE(diff_pl(paths, offset(e, (float)(+SCALED_EPSILON * 10))).size() == 0);
  95. }
  96. }
  97. }
  98. SECTION("Regression: Missing infill segments in some rare circumstances") {
  99. FillParams params_local = params;
  100. params_local.density = 1;
  101. params_local.dont_adjust = false;
  102. Fill* filler_local = { Slic3r::Fill::new_from_type("rectilinear") };
  103. filler_local->angle = (PI/4.0);
  104. filler_local->set_bounding_box(BoundingBox(Point(0, 0), Point(2512749, 2512749)));
  105. filler_local->init_spacing(0.654498, params_local);
  106. //filler_local->endpoints_overlap = unscale(359974);
  107. filler_local->layer_id = 66;
  108. filler_local->z = 20.15;
  109. Points points {Point{25771516,14142125},Point{14142138,25771515},Point{2512749,14142131},Point{14142125,2512749}};
  110. Slic3r::ExPolygon expoly{};
  111. expoly.contour = Slic3r::Polygon(points);
  112. Polylines paths {test(expoly, *filler_local, params_local)};
  113. REQUIRE(paths.size() == 1); // one continuous path
  114. // TODO: determine what the "Expected length" should be for rectilinear fill of a 100x100 polygon.
  115. // This check only checks that it's above scale(3*100 + 2*50) + scaled_epsilon.
  116. // ok abs($paths->[0]->length - scale(3*100 + 2*50)) - scaled_epsilon, 'path has expected length';
  117. REQUIRE(std::abs(paths[0].length() - static_cast<double>(scale_(3*100 + 2*50))) - SCALED_EPSILON > 0); // path has expected length
  118. }
  119. SECTION("Rotated Square") {
  120. Points square { Point::new_scale(0,0), Point::new_scale(50,0), Point::new_scale(50,50), Point::new_scale(0,50)};
  121. ExPolygon expolygon{};
  122. expolygon.contour = Slic3r::Polygon(square);
  123. auto filler {Slic3r::Fill::new_from_type("rectilinear")};
  124. filler->bounding_box = expolygon.contour.bounding_box();
  125. filler->angle = 0.F;
  126. Surface surface {(stPosTop|stDensSolid), expolygon};
  127. Flow flow {0.69f, 0.4f, 0.50f};
  128. params.density = 1.0;
  129. filler->init_spacing(flow.spacing(), params);
  130. for (auto angle : { 0.0, 45.0}) {
  131. surface.expolygon.rotate(angle, Point{0,0});
  132. Polylines paths = filler->fill_surface(&surface, params);
  133. REQUIRE(paths.size() == 1);
  134. }
  135. }
  136. SECTION("Solid surface fill") {
  137. Points points {
  138. Point::new_scale(6883102, 9598327.01296997),
  139. Point::new_scale(6883102, 20327272.01297),
  140. Point::new_scale(3116896, 20327272.01297),
  141. Point::new_scale(3116896, 9598327.01296997)
  142. };
  143. Slic3r::ExPolygon expolygon{};
  144. expolygon.contour = Slic3r::Polygon{ points };
  145. REQUIRE(test_if_solid_surface_filled(expolygon, 0.55) == true);
  146. for (size_t i = 0; i <= 20; ++i)
  147. {
  148. expolygon.scale(1.05);
  149. //FIXME number overflow.
  150. //REQUIRE(test_if_solid_surface_filled(expolygon, 0.55) == true);
  151. }
  152. }
  153. SECTION("Solid surface fill") {
  154. Points points {
  155. Point{59515297,5422499},Point{59531249,5578697},Point{59695801,6123186},
  156. Point{59965713,6630228},Point{60328214,7070685},Point{60773285,7434379},
  157. Point{61274561,7702115},Point{61819378,7866770},Point{62390306,7924789},
  158. Point{62958700,7866744},Point{63503012,7702244},Point{64007365,7434357},
  159. Point{64449960,7070398},Point{64809327,6634999},Point{65082143,6123325},
  160. Point{65245005,5584454},Point{65266967,5422499},Point{66267307,5422499},
  161. Point{66269190,8310081},Point{66275379,17810072},Point{66277259,20697500},
  162. Point{65267237,20697500},Point{65245004,20533538},Point{65082082,19994444},
  163. Point{64811462,19488579},Point{64450624,19048208},Point{64012101,18686514},
  164. Point{63503122,18415781},Point{62959151,18251378},Point{62453416,18198442},
  165. Point{62390147,18197355},Point{62200087,18200576},Point{61813519,18252990},
  166. Point{61274433,18415918},Point{60768598,18686517},Point{60327567,19047892},
  167. Point{59963609,19493297},Point{59695865,19994587},Point{59531222,20539379},
  168. Point{59515153,20697500},Point{58502480,20697500},Point{58502480,5422499}
  169. };
  170. Slic3r::ExPolygon expolygon;
  171. expolygon.contour = Slic3r::Polygon{ points };
  172. REQUIRE(test_if_solid_surface_filled(expolygon, 0.55) == true);
  173. REQUIRE(test_if_solid_surface_filled(expolygon, 0.55, PI/2.0) == true);
  174. }
  175. SECTION("Solid surface fill") {
  176. Points points {
  177. Point::new_scale(0,0),Point::new_scale(98,0),Point::new_scale(98,10), Point::new_scale(0,10)
  178. };
  179. Slic3r::ExPolygon expolygon{};
  180. expolygon.contour = Slic3r::Polygon{ points };
  181. REQUIRE(test_if_solid_surface_filled(expolygon, 0.5, 45.0, 0.99) == true);
  182. }
  183. }
  184. class ExtrusionGetVolume : public ExtrusionVisitor {
  185. double volume = 0;
  186. public:
  187. ExtrusionGetVolume() {}
  188. void use(ExtrusionPath &path) override {
  189. volume += unscaled(path.length()) * path.mm3_per_mm; }
  190. void use(ExtrusionPath3D &path3D) override { volume += unscaled(path3D.length()) * path3D.mm3_per_mm; }
  191. void use(ExtrusionMultiPath &multipath) override { for (ExtrusionPath path : multipath.paths) path.visit(*this); }
  192. void use(ExtrusionMultiPath3D &multipath) override { for (ExtrusionPath path : multipath.paths) path.visit(*this); }
  193. void use(ExtrusionLoop &loop) override { for (ExtrusionPath path : loop.paths) path.visit(*this); }
  194. void use(ExtrusionEntityCollection &collection) override { for (ExtrusionEntity *entity : collection.entities) entity->visit(*this); }
  195. double get(ExtrusionEntityCollection &coll) {
  196. for (ExtrusionEntity *entity : coll.entities) entity->visit(*this);
  197. return volume;
  198. }
  199. };
  200. #include "libslic3r/GCodeReader.hpp"
  201. TEST_CASE("Fill: extrude gcode and check it")
  202. {
  203. SECTION("simple square") {
  204. Model model{};
  205. TriangleMesh sample_mesh = make_cube(5, 5, 0.2);
  206. double volume = (5 * 5 * 0.2);
  207. sample_mesh.repair();
  208. DynamicPrintConfig &config = Slic3r::DynamicPrintConfig::full_print_config();
  209. config.set_key_value("perimeters", new ConfigOptionInt(1));
  210. config.set_key_value("top_solid_layers", new ConfigOptionInt(1));
  211. config.set_key_value("bottom_solid_layers", new ConfigOptionInt(1));
  212. config.set_key_value("enforce_full_fill_volume", new ConfigOptionBool(true));
  213. config.set_key_value("infill_overlap", new ConfigOptionFloatOrPercent(0.1, true));
  214. config.set_key_value("skirts", new ConfigOptionInt(0));
  215. config.set_key_value("layer_height", new ConfigOptionFloat(0.2)); // get a known number of layers
  216. config.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(0.2, false));
  217. config.set_key_value("extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  218. config.set_key_value("infill_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  219. config.set_key_value("perimeter_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  220. config.set_key_value("first_layer_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  221. config.set_key_value("external_perimeter_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  222. config.set_key_value("solid_infill_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  223. config.set_key_value("top_infill_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  224. auto event_counter{ 0U };
  225. std::string stage;
  226. Print print{};
  227. Slic3r::Test::init_print(print, { sample_mesh }, model, &config);
  228. print.process();
  229. std::string gcode_filepath{ "" };
  230. Slic3r::Test::gcode(gcode_filepath, print);
  231. //std::cout << "gcode generation done\n";
  232. std::string gcode_from_file = read_to_string(gcode_filepath);
  233. //string[] lineArray = gcode_from_file
  234. GCodeReader parser;
  235. double volume_extruded = 0;
  236. int idx = 0;
  237. double volume_perimeter_extruded = 0;
  238. double volume_infill_extruded = 0;
  239. // add remaining time lines where needed
  240. parser.parse_buffer(gcode_from_file,
  241. [&](GCodeReader& reader, const GCodeReader::GCodeLine& line)
  242. {
  243. if (line.cmd_is("G1"))
  244. {
  245. if (line.dist_E(reader) > 0 && line.dist_XY(reader) > 0) {
  246. //std::cout << "add " << line.dist_E(reader)<<" now "<< volume_extruded<<"=>";
  247. volume_extruded += line.dist_E(reader)*(PI*1.75*1.75 / 4.);
  248. //std::cout << volume_extruded << "\n";
  249. if (idx<4)volume_perimeter_extruded += line.dist_E(reader)*(PI*1.75*1.75 / 4.);
  250. else volume_infill_extruded += line.dist_E(reader)*(PI*1.75*1.75 / 4.);
  251. idx++;
  252. }
  253. }
  254. });
  255. double perimeterRoundGapRemove = unscaled(print.objects()[0]->layers()[0]->lslices[0].contour.length()) * 0.1*0.1 * (2 - (PI / 2));
  256. double perimeterRoundGapAdd = unscaled(print.objects()[0]->layers()[0]->lslices[0].contour.length()) * 0.1*0.1 * ((PI / 2));
  257. //for (Line &l : print.objects()[0]->layers()[0]->slices.expolygons[0].contour.lines()) {
  258. //}
  259. //std::cout << "flow mm3permm: " << Flow{ 0.5f,0.2f,0.4f,false }.mm3_per_mm() << "\n";
  260. //std::cout << "perimeter : " << unscaled(print.objects()[0]->layers()[0]->slices.expolygons[0].contour.length()) << " != " << (PI * 10) << "\n";
  261. //std::cout << "created a mesh of volume " << volume << " and i have extruded " << volume_extruded << " mm3.\n";
  262. //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";
  263. //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";
  264. double volumeExtrPerimeter = ExtrusionGetVolume{}.get(print.objects()[0]->layers()[0]->regions()[0]->perimeters);
  265. double volumeExtrInfill = ExtrusionGetVolume{}.get(print.objects()[0]->layers()[0]->regions()[0]->fills);
  266. double volumeInfill = 0;
  267. for (const ExPolygon & p : print.objects()[0]->layers()[0]->regions()[0]->fill_no_overlap_expolygons) {
  268. volumeInfill += unscaled(unscaled(p.area()));
  269. }
  270. volumeInfill *= 0.2;/*
  271. std::cout << "volumeRealr=" << (volume_perimeter_extruded + volume_infill_extruded) << " volumeRealPerimeter= " << volume_perimeter_extruded << " and volumeRealInfill=" << volume_infill_extruded << " mm3." << "\n";
  272. std::cout << "volumeExtr=" << (volumeExtrPerimeter + volumeExtrInfill) << " volumeExtrPerimeter= " << volumeExtrPerimeter << " and volumeExtrInfill=" << volumeExtrInfill << " mm3." << "\n";
  273. std::cout << "volumePerimeter= " << (volume - volumeInfill) << " volumePerimeter(wo/bits)= " << (volume - volumeInfill- perimeterRoundGapRemove) << " and volumeInfill=" << volumeInfill << " mm3." << "\n";*/
  274. //Flow fl{0.5f, 0.2f, 0.4f, false};
  275. //{
  276. // std::stringstream stri;
  277. // stri << "extrusion_width_learning" << ".svg";
  278. // SVG svg(stri.str());
  279. // //svg.draw(bounds);
  280. // svg.draw(print.objects()[0]->layers()[0]->slices.expolygons[0].contour, "green");
  281. // svg.draw(print.objects()[0]->layers()[0]->regions()[0]->fill_no_overlap_expolygons, "black", scale_(0.01));
  282. // svg.draw(print.objects()[0]->layers()[0]->regions()[0]->perimeters.as_polylines(), "orange", fl.scaled_width());
  283. // svg.draw(print.objects()[0]->layers()[0]->regions()[0]->perimeters.as_polylines(), "red", fl.scaled_spacing());
  284. // svg.draw(print.objects()[0]->layers()[0]->regions()[0]->fills.as_polylines(), "cyan", fl.scaled_width());
  285. // svg.draw(print.objects()[0]->layers()[0]->regions()[0]->fills.as_polylines(), "blue", fl.scaled_spacing());
  286. // svg.Close();
  287. //}
  288. //std::cout << gcode_from_file;
  289. REQUIRE(abs(volumeInfill - volumeExtrInfill) < EPSILON);
  290. REQUIRE(abs(volumeInfill - volume_infill_extruded) < 0.01);
  291. REQUIRE(abs((volume - volumeInfill - perimeterRoundGapRemove) - volumeExtrPerimeter) < 0.01);
  292. REQUIRE(abs((volume - volumeInfill - perimeterRoundGapRemove) - volume_perimeter_extruded) < 0.1); //there are a bit less for seam mitigation
  293. clean_file(gcode_filepath, "gcode");
  294. }
  295. SECTION("simple disk") {
  296. Model model{};
  297. TriangleMesh sample_mesh = make_cylinder(5, 0.2);
  298. double volume = (PI * 25 * 0.2);
  299. sample_mesh.repair();
  300. DynamicPrintConfig &config = Slic3r::DynamicPrintConfig::full_print_config();
  301. config.set_key_value("perimeters", new ConfigOptionInt(1));
  302. config.set_key_value("top_solid_layers", new ConfigOptionInt(1));
  303. config.set_key_value("bottom_solid_layers", new ConfigOptionInt(1));
  304. config.set_key_value("enforce_full_fill_volume", new ConfigOptionBool(true));
  305. config.set_key_value("infill_overlap", new ConfigOptionFloatOrPercent(0.1, true));
  306. config.set_key_value("skirts", new ConfigOptionInt(0));
  307. config.set_key_value("layer_height", new ConfigOptionFloat(0.2)); // get a known number of layers
  308. config.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(0.2, false));
  309. config.set_key_value("extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  310. config.set_key_value("infill_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  311. config.set_key_value("perimeter_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  312. config.set_key_value("first_layer_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  313. config.set_key_value("external_perimeter_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  314. config.set_key_value("solid_infill_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  315. config.set_key_value("top_infill_extrusion_width", new ConfigOptionFloatOrPercent(0.5, false));
  316. auto event_counter{ 0U };
  317. std::string stage;
  318. Print print{};
  319. Slic3r::Test::init_print(print, { sample_mesh }, model, &config);
  320. print.process();
  321. std::string gcode_filepath{ "" };
  322. Slic3r::Test::gcode(gcode_filepath, print);
  323. //std::cout << "gcode generation done\n";
  324. std::string gcode_from_file = read_to_string(gcode_filepath);
  325. //string[] lineArray = gcode_from_file
  326. GCodeReader parser;
  327. double volume_extruded = 0;
  328. int idx = 0;
  329. double volume_perimeter_extruded = 0;
  330. double volume_infill_extruded = 0;
  331. // add remaining time lines where needed
  332. parser.parse_buffer(gcode_from_file,
  333. [&](GCodeReader& reader, const GCodeReader::GCodeLine& line)
  334. {
  335. if (line.cmd_is("G1"))
  336. {
  337. if (line.dist_E(reader) > 0 && line.dist_XY(reader) > 0) {
  338. //std::cout << "add " << line.dist_E(reader)<<" now "<< volume_extruded<<"=>";
  339. volume_extruded += line.dist_E(reader)*(PI*1.75*1.75 / 4.);
  340. //std::cout << volume_extruded << "\n";
  341. if (idx<36)volume_perimeter_extruded += line.dist_E(reader)*(PI*1.75*1.75 / 4.);
  342. else volume_infill_extruded += line.dist_E(reader)*(PI*1.75*1.75 / 4.);
  343. idx++;
  344. }
  345. }
  346. });
  347. double perimeterRoundGapRemove = unscaled(print.objects()[0]->layers()[0]->lslices[0].contour.length()) * 0.1*0.1 * (2 - (PI / 2));
  348. double perimeterRoundGapAdd = unscaled(print.objects()[0]->layers()[0]->lslices[0].contour.length()) * 0.1*0.1 * ((PI / 2));
  349. double volumeExtrPerimeter = ExtrusionGetVolume{}.get(print.objects()[0]->layers()[0]->regions()[0]->perimeters);
  350. double volumeExtrInfill = ExtrusionGetVolume{}.get(print.objects()[0]->layers()[0]->regions()[0]->fills);
  351. double volumeInfill = 0;
  352. for (const ExPolygon & p : print.objects()[0]->layers()[0]->regions()[0]->fill_no_overlap_expolygons) {
  353. volumeInfill += unscaled(unscaled(p.area()));
  354. }
  355. volumeInfill *= 0.2;
  356. std::cout << "volumeRealr=" << (volume_perimeter_extruded + volume_infill_extruded) << " volumeRealPerimeter= " << volume_perimeter_extruded << " and volumeRealInfill=" << volume_infill_extruded << " mm3." << "\n";
  357. std::cout << "volumeExtr=" << (volumeExtrPerimeter + volumeExtrInfill) << " volumeExtrPerimeter= " << volumeExtrPerimeter << " and volumeExtrInfill=" << volumeExtrInfill << " mm3." << "\n";
  358. std::cout << "volumePerimeter= " << (volume - volumeInfill) << " volumePerimeter(wo/bits)= " << (volume - volumeInfill - perimeterRoundGapRemove) << " and volumeInfill=" << volumeInfill << " mm3." << "\n";
  359. REQUIRE(abs(volumeInfill - volumeExtrInfill) < EPSILON);
  360. REQUIRE(abs(volumeInfill - volume_infill_extruded) < 0.01);
  361. REQUIRE(abs((volume - volumeInfill - perimeterRoundGapRemove) - volumeExtrPerimeter) < EPSILON);
  362. REQUIRE(abs((volume - volumeInfill - perimeterRoundGapRemove) - volume_perimeter_extruded) < 0.1); //there are a bit less for seam mitigation
  363. clean_file(gcode_filepath, "gcode");
  364. }
  365. }
  366. /*
  367. {
  368. my $collection = Slic3r::Polyline::Collection->new(
  369. Slic3r::Polyline->new([0,15], [0,18], [0,20]),
  370. Slic3r::Polyline->new([0,10], [0,8], [0,5]),
  371. );
  372. is_deeply
  373. [ map $_->[Y], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ],
  374. [20, 18, 15, 10, 8, 5],
  375. 'chained path';
  376. }
  377. {
  378. my $collection = Slic3r::Polyline::Collection->new(
  379. Slic3r::Polyline->new([4,0], [10,0], [15,0]),
  380. Slic3r::Polyline->new([10,5], [15,5], [20,5]),
  381. );
  382. is_deeply
  383. [ map $_->[X], map @$_, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ],
  384. [reverse 4, 10, 15, 10, 15, 20],
  385. 'chained path';
  386. }
  387. {
  388. my $collection = Slic3r::ExtrusionPath::Collection->new(
  389. map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1),
  390. Slic3r::Polyline->new([0,15], [0,18], [0,20]),
  391. Slic3r::Polyline->new([0,10], [0,8], [0,5]),
  392. );
  393. is_deeply
  394. [ map $_->[Y], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(0,30), 0)} ],
  395. [20, 18, 15, 10, 8, 5],
  396. 'chained path';
  397. }
  398. {
  399. my $collection = Slic3r::ExtrusionPath::Collection->new(
  400. map Slic3r::ExtrusionPath->new(polyline => $_, role => 0, mm3_per_mm => 1),
  401. Slic3r::Polyline->new([15,0], [10,0], [4,0]),
  402. Slic3r::Polyline->new([10,5], [15,5], [20,5]),
  403. );
  404. is_deeply
  405. [ map $_->[X], map @{$_->polyline}, @{$collection->chained_path_from(Slic3r::Point->new(30,0), 0)} ],
  406. [reverse 4, 10, 15, 10, 15, 20],
  407. 'chained path';
  408. }
  409. for my $pattern (qw(rectilinear honeycomb hilbertcurve concentric)) {
  410. my $config = Slic3r::Config->new_from_defaults;
  411. $config->set('fill_pattern', $pattern);
  412. $config->set('external_fill_pattern', $pattern);
  413. $config->set('perimeters', 1);
  414. $config->set('skirts', 0);
  415. $config->set('fill_density', 20);
  416. $config->set('layer_height', 0.05);
  417. $config->set('perimeter_extruder', 1);
  418. $config->set('infill_extruder', 2);
  419. my $print = Slic3r::Test::init_print('20mm_cube', config => $config, scale => 2);
  420. ok my $gcode = Slic3r::Test::gcode($print), "successful $pattern infill generation";
  421. my $tool = undef;
  422. my @perimeter_points = my @infill_points = ();
  423. Slic3r::GCode::Reader->new->parse($gcode, sub {
  424. my ($self, $cmd, $args, $info) = @_;
  425. if ($cmd =~ /^T(\d+)/) {
  426. $tool = $1;
  427. } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
  428. if ($tool == $config->perimeter_extruder-1) {
  429. push @perimeter_points, Slic3r::Point->new_scale($args->{X}, $args->{Y});
  430. } elsif ($tool == $config->infill_extruder-1) {
  431. push @infill_points, Slic3r::Point->new_scale($args->{X}, $args->{Y});
  432. }
  433. }
  434. });
  435. my $convex_hull = convex_hull(\@perimeter_points);
  436. ok !(defined first { !$convex_hull->contains_point($_) } @infill_points), "infill does not exceed perimeters ($pattern)";
  437. }
  438. {
  439. my $config = Slic3r::Config->new_from_defaults;
  440. $config->set('infill_only_where_needed', 1);
  441. $config->set('bottom_solid_layers', 0);
  442. $config->set('infill_extruder', 2);
  443. $config->set('infill_extrusion_width', 0.5);
  444. $config->set('fill_density', 40);
  445. $config->set('cooling', 0); # for preventing speeds from being altered
  446. $config->set('first_layer_speed', '100%'); # for preventing speeds from being altered
  447. my $test = sub {
  448. my $print = Slic3r::Test::init_print('pyramid', config => $config);
  449. my $tool = undef;
  450. my @infill_extrusions = (); # array of polylines
  451. Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
  452. my ($self, $cmd, $args, $info) = @_;
  453. if ($cmd =~ /^T(\d+)/) {
  454. $tool = $1;
  455. } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
  456. if ($tool == $config->infill_extruder-1) {
  457. push @infill_extrusions, Slic3r::Line->new_scale(
  458. [ $self->X, $self->Y ],
  459. [ $info->{new_X}, $info->{new_Y} ],
  460. );
  461. }
  462. }
  463. });
  464. return 0 if !@infill_extrusions; # prevent calling convex_hull() with no points
  465. my $convex_hull = convex_hull([ map $_->pp, map @$_, @infill_extrusions ]);
  466. return unscale unscale sum(map $_->area, @{offset([$convex_hull], scale(+$config->infill_extrusion_width/2))});
  467. };
  468. my $tolerance = 5; # mm^2
  469. $config->set('solid_infill_below_area', 0);
  470. ok $test->() < $tolerance,
  471. 'no infill is generated when using infill_only_where_needed on a pyramid';
  472. $config->set('solid_infill_below_area', 70);
  473. ok abs($test->() - $config->solid_infill_below_area) < $tolerance,
  474. 'infill is only generated under the forced solid shells';
  475. }
  476. {
  477. my $config = Slic3r::Config->new_from_defaults;
  478. $config->set('skirts', 0);
  479. $config->set('perimeters', 1);
  480. $config->set('fill_density', 0);
  481. $config->set('top_solid_layers', 0);
  482. $config->set('bottom_solid_layers', 0);
  483. $config->set('solid_infill_below_area', 20000000);
  484. $config->set('solid_infill_every_layers', 2);
  485. $config->set('perimeter_speed', 99);
  486. $config->set('external_perimeter_speed', 99);
  487. $config->set('cooling', 0);
  488. $config->set('first_layer_speed', '100%');
  489. my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
  490. my %layers_with_extrusion = ();
  491. Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
  492. my ($self, $cmd, $args, $info) = @_;
  493. if ($cmd eq 'G1' && $info->{dist_XY} > 0 && $info->{extruding}) {
  494. if (($args->{F} // $self->F) != $config->perimeter_speed*60) {
  495. $layers_with_extrusion{$self->Z} = ($args->{F} // $self->F);
  496. }
  497. }
  498. });
  499. ok !%layers_with_extrusion,
  500. "solid_infill_below_area and solid_infill_every_layers are ignored when fill_density is 0";
  501. }
  502. {
  503. my $config = Slic3r::Config->new_from_defaults;
  504. $config->set('skirts', 0);
  505. $config->set('perimeters', 3);
  506. $config->set('fill_density', 0);
  507. $config->set('layer_height', 0.2);
  508. $config->set('first_layer_height', 0.2);
  509. $config->set('nozzle_diameter', [0.35]);
  510. $config->set('infill_extruder', 2);
  511. $config->set('solid_infill_extruder', 2);
  512. $config->set('infill_extrusion_width', 0.52);
  513. $config->set('solid_infill_extrusion_width', 0.52);
  514. $config->set('first_layer_extrusion_width', 0);
  515. my $print = Slic3r::Test::init_print('A', config => $config);
  516. my %infill = (); # Z => [ Line, Line ... ]
  517. my $tool = undef;
  518. Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
  519. my ($self, $cmd, $args, $info) = @_;
  520. if ($cmd =~ /^T(\d+)/) {
  521. $tool = $1;
  522. } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
  523. if ($tool == $config->infill_extruder-1) {
  524. my $z = 1 * $self->Z;
  525. $infill{$z} ||= [];
  526. push @{$infill{$z}}, Slic3r::Line->new_scale(
  527. [ $self->X, $self->Y ],
  528. [ $info->{new_X}, $info->{new_Y} ],
  529. );
  530. }
  531. }
  532. });
  533. my $grow_d = scale($config->infill_extrusion_width)/2;
  534. my $layer0_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.2} } ]);
  535. my $layer1_infill = union([ map @{$_->grow($grow_d)}, @{ $infill{0.4} } ]);
  536. my $diff = diff($layer0_infill, $layer1_infill);
  537. $diff = offset2_ex($diff, -$grow_d, +$grow_d);
  538. $diff = [ grep { $_->area > 2*(($grow_d*2)**2) } @$diff ];
  539. is scalar(@$diff), 0, 'no missing parts in solid shell when fill_density is 0';
  540. }
  541. {
  542. # GH: #2697
  543. my $config = Slic3r::Config->new_from_defaults;
  544. $config->set('perimeter_extrusion_width', 0.72);
  545. $config->set('top_infill_extrusion_width', 0.1);
  546. $config->set('infill_extruder', 2); # in order to distinguish infill
  547. $config->set('solid_infill_extruder', 2); # in order to distinguish infill
  548. my $print = Slic3r::Test::init_print('20mm_cube', config => $config);
  549. my %infill = (); # Z => [ Line, Line ... ]
  550. my %other = (); # Z => [ Line, Line ... ]
  551. my $tool = undef;
  552. Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub {
  553. my ($self, $cmd, $args, $info) = @_;
  554. if ($cmd =~ /^T(\d+)/) {
  555. $tool = $1;
  556. } elsif ($cmd eq 'G1' && $info->{extruding} && $info->{dist_XY} > 0) {
  557. my $z = 1 * $self->Z;
  558. my $line = Slic3r::Line->new_scale(
  559. [ $self->X, $self->Y ],
  560. [ $info->{new_X}, $info->{new_Y} ],
  561. );
  562. if ($tool == $config->infill_extruder-1) {
  563. $infill{$z} //= [];
  564. push @{$infill{$z}}, $line;
  565. } else {
  566. $other{$z} //= [];
  567. push @{$other{$z}}, $line;
  568. }
  569. }
  570. });
  571. my $top_z = max(keys %infill);
  572. my $top_infill_grow_d = scale($config->top_infill_extrusion_width)/2;
  573. my $top_infill = union([ map @{$_->grow($top_infill_grow_d)}, @{ $infill{$top_z} } ]);
  574. my $perimeters_grow_d = scale($config->perimeter_extrusion_width)/2;
  575. my $perimeters = union([ map @{$_->grow($perimeters_grow_d)}, @{ $other{$top_z} } ]);
  576. my $covered = union_ex([ @$top_infill, @$perimeters ]);
  577. my @holes = map @{$_->holes}, @$covered;
  578. ok sum(map unscale unscale $_->area*-1, @holes) < 1, 'no gaps between top solid infill and perimeters';
  579. }
  580. */
  581. //TODO: also check by volume extruded
  582. //TODO: replace the simple area coverage check by one that takes into account the width of the path, not only the default flow spacing
  583. //TODO: test more fills
  584. bool test_if_solid_surface_filled(const ExPolygon& expolygon, double flow_width, double angle, double density) {
  585. auto* filler {Slic3r::Fill::new_from_type("concentricgapfill")};
  586. filler->bounding_box = expolygon.contour.bounding_box();
  587. filler->angle = angle;
  588. FillParams params;
  589. params.dont_adjust = false;
  590. Surface surface((stPosBottom | stDensSolid), expolygon);
  591. //note: here we do flow.width = flow_width , flow.gheight = 0.4, flow.nozzle_size = flow_width;
  592. Flow flow(flow_width, 0.4, flow_width);
  593. params.density = density;
  594. filler->init_spacing(flow.spacing(), params);
  595. Polylines paths {filler->fill_surface(&surface, params)};
  596. // check whether any part was left uncovered
  597. Polygons grown_paths;
  598. grown_paths.reserve(paths.size());
  599. // figure out what is actually going on here re: data types
  600. std::for_each(paths.begin(), paths.end(), [filler, &grown_paths] (const Slic3r::Polyline& p) {
  601. polygons_append(grown_paths, offset(p, scale_(filler->get_spacing() / 2.0)));
  602. });
  603. ExPolygons uncovered = diff_ex(expolygon, grown_paths, true);
  604. // ignore very small dots
  605. const auto scaled_flow_width { std::pow(scale_(flow_width), 2) };
  606. auto iter {std::remove_if(uncovered.begin(), uncovered.end(), [scaled_flow_width] (const ExPolygon& poly) {
  607. return poly.area() > scaled_flow_width;
  608. }) };
  609. uncovered.erase(iter, uncovered.end());
  610. double uncovered_area = 0;
  611. for (ExPolygon &p : uncovered) uncovered_area += unscaled(unscaled(p.area()));
  612. std::cout << "uncovered size =" << uncovered_area << " / "<< unscaled(unscaled(expolygon.area()))<<"\n";
  613. return uncovered.size() == 0; // solid surface is fully filled
  614. }