FillConcentric.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. ///|/ Copyright (c) Prusa Research 2016 - 2023 Vojtěch Bubník @bubnikv, Lukáš Hejl @hejllukas
  2. ///|/
  3. ///|/ ported from lib/Slic3r/Fill/Concentric.pm:
  4. ///|/ Copyright (c) Prusa Research 2016 Vojtěch Bubník @bubnikv
  5. ///|/ Copyright (c) Slic3r 2011 - 2015 Alessandro Ranellucci @alranel
  6. ///|/ Copyright (c) 2012 Mark Hindess
  7. ///|/
  8. ///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
  9. ///|/
  10. #include "../ClipperUtils.hpp"
  11. #include "../ExPolygon.hpp"
  12. #include "../Surface.hpp"
  13. #include "../ExtrusionEntity.hpp"
  14. #include "../ExtrusionEntityCollection.hpp"
  15. #include "../Geometry/MedialAxis.hpp"
  16. #include "Arachne/WallToolPaths.hpp"
  17. #include "FillConcentric.hpp"
  18. namespace Slic3r {
  19. void
  20. FillConcentric::init_spacing(coordf_t spacing, const FillParams &params)
  21. {
  22. Fill::init_spacing(spacing, params);
  23. if (params.density > 0.9999f && !params.dont_adjust) {
  24. this->spacing_priv = unscaled(this->_adjust_solid_spacing(bounding_box.size()(0), _line_spacing_for_density(params)));
  25. }
  26. }
  27. void
  28. FillConcentric::_fill_surface_single(
  29. const FillParams &params,
  30. unsigned int thickness_layers,
  31. const std::pair<float, Point> &direction,
  32. ExPolygon expolygon,
  33. Polylines &polylines_out) const
  34. {
  35. // no rotation is supported for this infill pattern
  36. BoundingBox bounding_box = expolygon.contour.bounding_box();
  37. coord_t distance = _line_spacing_for_density(params);
  38. if (params.density > 0.9999f && !params.dont_adjust) {
  39. //it's == Slic3r::FillConcentric::_adjust_solid_spacing(bounding_box.size()(0), _line_spacing_for_density(params.density)) because of the init_spacing()
  40. distance = scale_t(this->get_spacing());
  41. }
  42. Polygons loops = to_polygons(expolygon);
  43. ExPolygons last { std::move(expolygon) };
  44. while (! last.empty()) {
  45. last = offset2_ex(last, -double(distance + scale_(this->get_spacing()) /2), +double(scale_(this->get_spacing()) /2));
  46. append(loops, to_polygons(last));
  47. }
  48. // generate paths from the outermost to the innermost, to avoid
  49. // adhesion problems of the first central tiny loops
  50. loops = union_pt_chained_outside_in(loops);
  51. ensure_valid(loops, params.fill_resolution);
  52. // split paths using a nearest neighbor search
  53. size_t iPathFirst = polylines_out.size();
  54. Point last_pos(0, 0);
  55. for (const Polygon &loop : loops) {
  56. polylines_out.emplace_back(loop.split_at_index(nearest_point_index(loop.points, last_pos)));
  57. last_pos = polylines_out.back().last_point();
  58. }
  59. // clip the paths to prevent the extruder from getting exactly on the first point of the loop
  60. // Keep valid paths only.
  61. size_t j = iPathFirst;
  62. for (size_t i = iPathFirst; i < polylines_out.size(); ++ i) {
  63. polylines_out[i].clip_end(coordf_t(this->loop_clipping));
  64. if (polylines_out[i].is_valid()) {
  65. if (j < i)
  66. polylines_out[j] = std::move(polylines_out[i]);
  67. ++ j;
  68. }
  69. }
  70. if (j < polylines_out.size())
  71. polylines_out.erase(polylines_out.begin() + int(j), polylines_out.end());
  72. //TODO: return ExtrusionLoop objects to get better chained paths,
  73. // otherwise the outermost loop starts at the closest point to (0, 0).
  74. // We want the loops to be split inside the G-code generator to get optimum path planning.
  75. assert_valid(polylines_out);
  76. }
  77. void append_loop_into_collection(ExtrusionEntityCollection& storage, ExtrusionRole& good_role, const FillParams& params, Polygon& polygon) {
  78. double flow = params.flow.mm3_per_mm();
  79. double width = params.flow.width();
  80. double height = params.flow.height();
  81. if (polygon.is_valid()) {
  82. //default to ccw
  83. polygon.make_counter_clockwise();
  84. ExtrusionPath path(ExtrusionAttributes{good_role, ExtrusionFlow{flow, float(width), float(height)}}, false);
  85. path.polyline.append(std::move(polygon.points));
  86. path.polyline.append(path.polyline.front());
  87. storage.append(ExtrusionLoop{ std::move(path) });
  88. }
  89. }
  90. void
  91. FillConcentricWGapFill::fill_surface_extrusion(
  92. const Surface *surface,
  93. const FillParams &params,
  94. ExtrusionEntitiesPtr &out) const {
  95. ExtrusionEntitiesPtr out_to_check;
  96. double min_gapfill_area = double(params.flow.scaled_width()) * double(params.flow.scaled_width());
  97. if (params.config != nullptr) min_gapfill_area = scale_d(params.config->gap_fill_min_area.get_abs_value(params.flow.width())) * double(params.flow.scaled_width());
  98. // Perform offset. //FIXME: can miss gapfill outside of this first perimeter
  99. Slic3r::ExPolygons expp = offset_ex(surface->expolygon, double(scale_(0 - 0.5 * this->get_spacing())));
  100. // Create the infills for each of the regions.
  101. Polylines polylines_out;
  102. for (size_t i = 0; i < expp.size(); ++i) {
  103. //_fill_surface_single(
  104. //params,
  105. //surface->thickness_layers,
  106. //_infill_direction(surface),
  107. //expp[i],
  108. //polylines_out);
  109. ExPolygon expolygon = expp[i];
  110. coordf_t init_spacing = this->get_spacing();
  111. // no rotation is supported for this infill pattern
  112. BoundingBox bounding_box = expolygon.contour.bounding_box();
  113. coord_t distance = _line_spacing_for_density(params);
  114. if (params.density > 0.9999f && !params.dont_adjust) {
  115. distance = scale_t(this->get_spacing());
  116. }
  117. std::vector<std::vector<Polygons>> bunch_2_shell_2_loops;
  118. bunch_2_shell_2_loops.emplace_back(); // create a new bunch before a gap
  119. bunch_2_shell_2_loops.back().push_back(to_polygons(expolygon)); // add first shell
  120. std::vector<ExPolygons> bunch_2_gaps; // size = bunch_2_shell_2_loops.size() (-1)
  121. ExPolygons last = { expolygon };
  122. bool first = true;
  123. while (!last.empty()) {
  124. ExPolygons next_onion = offset2_ex(last, -(distance + scale_d(this->get_spacing()) / 2), +(scale_d(this->get_spacing()) / 2));
  125. ExPolygons new_gaps = diff_ex(
  126. offset_ex(last, -0.5f * distance),
  127. offset_ex(next_onion, 0.5f * distance + 10)); // 10 = safety offset
  128. //add next shell (into the last collection)
  129. bunch_2_shell_2_loops.back().push_back(to_polygons(next_onion));
  130. //if there is some gaps, then we need to create a new collection for the next shells, so they will be peirnted after the gaps.
  131. if (!new_gaps.empty()) {
  132. if (first && !this->no_overlap_expolygons.empty()) {
  133. new_gaps = intersection_ex(new_gaps, this->no_overlap_expolygons);
  134. }
  135. bunch_2_gaps.push_back(std::move(new_gaps));
  136. if (!bunch_2_shell_2_loops.back().empty() && bunch_2_shell_2_loops.back().back().empty()) {
  137. bunch_2_shell_2_loops.back().pop_back();
  138. }
  139. //create a new collection for next bunch (if the loop don't stop here).
  140. if (!next_onion.empty()) {
  141. bunch_2_shell_2_loops.emplace_back();
  142. }
  143. }
  144. // refresh before next iteration
  145. last = next_onion;
  146. first = false;
  147. }
  148. if (!bunch_2_shell_2_loops.back().empty() && bunch_2_shell_2_loops.back().back().empty()) {
  149. bunch_2_shell_2_loops.back().pop_back();
  150. }
  151. if (bunch_2_shell_2_loops.back().empty()) {
  152. assert(bunch_2_shell_2_loops.size() >= bunch_2_gaps.size());
  153. if (bunch_2_shell_2_loops.size() == bunch_2_gaps.size()) {
  154. assert(bunch_2_gaps.size() > 1);
  155. if (bunch_2_gaps.size() > 1) {
  156. // merge last two gaps (inside the last perimeter & after the last perimeter are both extruded
  157. // after the last perimeter)
  158. append(bunch_2_gaps[bunch_2_gaps.size() - 2], bunch_2_gaps.back());
  159. bunch_2_gaps.pop_back();
  160. }
  161. }
  162. bunch_2_shell_2_loops.pop_back();
  163. if (!bunch_2_shell_2_loops.empty() && !bunch_2_shell_2_loops.back().empty() && bunch_2_shell_2_loops.back().back().empty()) {
  164. bunch_2_shell_2_loops.back().pop_back();
  165. }
  166. }
  167. //check no empty shell
  168. assert(!bunch_2_shell_2_loops.back().empty());
  169. //check no empty gap
  170. assert(std::find_if(bunch_2_gaps.begin(), bunch_2_gaps.end(), [](const auto &vec) { return vec.empty(); }) ==
  171. bunch_2_gaps.end());
  172. // check size there is 3 posibilities:
  173. // 1) no gap: 1 shell
  174. // 2) gap but not in the last to iteration: X xhells, x-1 gap
  175. // 3) gap and there is some to print after the last shell: X shell, X gap
  176. assert(bunch_2_shell_2_loops.size() == bunch_2_gaps.size() + 1 ||
  177. bunch_2_shell_2_loops.size() == bunch_2_gaps.size());
  178. // generate paths from the outermost to the innermost, to avoid
  179. // adhesion problems of the first central tiny loops
  180. //note: useless if we don't apply no_sort flag
  181. //loops = union_pt_chained(loops, false);
  182. //get the role
  183. ExtrusionRole good_role = getRoleFromSurfaceType(params, surface);
  184. ExtrusionEntityCollection* root_collection_nosort = new ExtrusionEntityCollection(false, false);
  185. //pattern (don't modify/move it)
  186. const ExtrusionEntityCollection eec_pattern_no_sort{ false, false };
  187. assert(bunch_2_shell_2_loops.size() == bunch_2_gaps.size() || bunch_2_shell_2_loops.size() == bunch_2_gaps.size() + 1);
  188. //for each "shell" (loop to print before a gap)
  189. for (int idx_bunch = 0; idx_bunch < bunch_2_shell_2_loops.size(); idx_bunch++) {
  190. //we have some "starting loops". for each 'shell', we get each loop and find (by searching which one it fit inside) its island.
  191. // if there is none or multiple, then we have to start again from these new loops.
  192. std::vector<Polygons>& shells = bunch_2_shell_2_loops[idx_bunch];
  193. assert(!shells.empty());
  194. // leafs where you can add a loop (only one per shell, or you need to split it)
  195. std::vector<ExtrusionEntityCollection*> leafs;
  196. //initialisation with first shell
  197. root_collection_nosort->append(ExtrusionEntityCollection{});
  198. ExtrusionEntityCollection* root_sortable = static_cast<ExtrusionEntityCollection*>(root_collection_nosort->set_entities().back());
  199. struct Leaf {
  200. size_t count;
  201. ExtrusionEntityCollection* sortable;
  202. };
  203. // for each shell of this bunch
  204. for (size_t idx_shell = 0; idx_shell < shells.size(); ++idx_shell) {
  205. Polygons& islands = shells[idx_shell];
  206. std::vector<Leaf> nb_childs(leafs.size(), Leaf{ 0, nullptr });
  207. assert(nb_childs.size() == leafs.size());
  208. // for each island
  209. for (size_t idx_island = 0; idx_island < islands.size(); ++idx_island) {
  210. //find a leafs to append it.
  211. size_t added_idx = size_t(-1);
  212. if (islands[idx_island].is_clockwise()) {
  213. //hole -> it's the leafs that should be inside me
  214. islands[idx_island].make_counter_clockwise();
  215. for (size_t idx_child = 0; idx_child < nb_childs.size(); ++idx_child) {
  216. assert(!leafs[idx_child]->entities().empty());
  217. assert(leafs[idx_child]->entities().back()->is_loop());
  218. if (islands[idx_island].contains(leafs[idx_child]->entities().back()->first_point())) {
  219. added_idx = idx_child;
  220. break;
  221. }
  222. }
  223. } else {
  224. for (size_t idx_child = 0; idx_child < nb_childs.size(); ++idx_child) {
  225. assert(!leafs[idx_child]->entities().empty());
  226. assert(leafs[idx_child]->entities().back()->is_loop());
  227. if (leafs[idx_child]->entities().back()->is_loop() &&
  228. static_cast<ExtrusionLoop*>(leafs[idx_child]->entities().back())->polygon().contains(islands[idx_island].first_point())) {
  229. added_idx = idx_child;
  230. break;
  231. }
  232. }
  233. }
  234. if (added_idx != size_t(-1)) {
  235. Leaf& leaf_count = nb_childs[added_idx];
  236. ExtrusionEntityCollection* leaf_coll = leafs[added_idx];
  237. //check if it's taken
  238. if (leaf_count.count == 0) {
  239. append_loop_into_collection(*leaf_coll, good_role, params, islands[idx_island]);
  240. } else if (leaf_count.count == 1) {
  241. //remove last entity (from the count==0) to put it into a new collection
  242. ExtrusionEntity* elt = leaf_coll->set_entities().back();
  243. leaf_coll->set_entities().pop_back();
  244. //add sortbale collection inside
  245. leaf_coll->append(ExtrusionEntityCollection{});
  246. leaf_count.sortable = static_cast<ExtrusionEntityCollection*>(leaf_coll->set_entities().back());
  247. ExtrusionEntityCollection new_coll_nosort{ false, false };
  248. new_coll_nosort.append(ExtrusionEntitiesPtr{elt});
  249. leaf_count.sortable->append(std::move(new_coll_nosort));
  250. }
  251. if (leaf_count.sortable) {
  252. //add new collection
  253. ExtrusionEntityCollection new_coll_nosort{ false, false };
  254. append_loop_into_collection(new_coll_nosort, good_role, params, islands[idx_island]);
  255. leaf_count.sortable->append(std::move(new_coll_nosort));
  256. }
  257. ++leaf_count.count;
  258. } else {
  259. //create new root (should only happen on the first shell 'initialisation')
  260. root_sortable->append(eec_pattern_no_sort);
  261. leafs.push_back(static_cast<ExtrusionEntityCollection*>(root_sortable->set_entities().back()));
  262. append_loop_into_collection(*leafs.back(), good_role, params, islands[idx_island]);
  263. }
  264. }
  265. //remove old leafs
  266. assert(leafs.size() >= nb_childs.size());
  267. for (size_t idx_child = nb_childs.size() - 1; idx_child < nb_childs.size(); ++idx_child) {
  268. if (nb_childs[idx_child].count > 1) {
  269. leafs.erase(leafs.begin() + idx_child);
  270. }
  271. }
  272. }
  273. //TODO: move items that are alone in a collection to the upper collection.
  274. //add gapfills
  275. if (idx_bunch < bunch_2_gaps.size() && !bunch_2_gaps[idx_bunch].empty() && params.density >= 1) {
  276. // get parameters
  277. coordf_t min = 0.2 * distance * (1 - INSET_OVERLAP_TOLERANCE);
  278. //be sure we don't gapfill where the perimeters are already touching each other (negative spacing).
  279. min = std::max(min, double(Flow::new_from_spacing((float)EPSILON, (float)params.flow.nozzle_diameter(), (float)params.flow.height(), (float)params.flow.spacing_ratio(), false).scaled_width()));
  280. coordf_t real_max = 2.5 * distance;
  281. const coordf_t minwidth = scale_d(params.config->get_abs_value("gap_fill_min_width", params.flow.width()));
  282. const coordf_t maxwidth = scale_d(params.config->get_abs_value("gap_fill_max_width", params.flow.width()));
  283. const coord_t minlength = scale_t(params.config->get_abs_value("gap_fill_min_length", params.flow.width()));
  284. if (minwidth > 0) {
  285. min = std::max(min, minwidth);
  286. }
  287. coordf_t max = real_max;
  288. if (maxwidth > 0) {
  289. max = std::min(max, maxwidth);
  290. }
  291. const coord_t gapfill_extension = scale_t(params.config->get_abs_value("gap_fill_extension", params.flow.width()));
  292. // collapse
  293. ExPolygons gaps_ex = diff_ex(
  294. offset2_ex(bunch_2_gaps[idx_bunch], -min / 2, +min / 2),
  295. offset2_ex(bunch_2_gaps[idx_bunch], -max / 2, +max / 2),
  296. ApplySafetyOffset::Yes);
  297. ThickPolylines polylines;
  298. for (const ExPolygon& ex : gaps_ex) {
  299. //remove too small gaps that are too hard to fill.
  300. //ie one that are smaller than an extrusion with width of min and a length of max.
  301. if (ex.area() > min_gapfill_area) {
  302. Geometry::MedialAxis md{ ex, coord_t(real_max), coord_t(min), scale_t(params.flow.height()) };
  303. if (minlength > 0) {
  304. md.set_min_length(minlength);
  305. }
  306. if (gapfill_extension > 0) {
  307. md.set_extension_length(gapfill_extension);
  308. }
  309. md.set_biggest_width(max);
  310. md.build(polylines);
  311. }
  312. }
  313. ////search if we can add some at the end of a leaf
  314. //for (size_t idx_polyline = 0; idx_polyline < polylines.size(); ++idx_polyline) {
  315. // ThickPolyline& poly = polylines[idx_polyline];
  316. // assert(!poly.empty());
  317. // for (size_t idx_leaf = 0; idx_leaf < leafs.size(); ++idx_leaf) {
  318. // assert(!leafs[idx_leaf]->entities().empty());
  319. // const ExtrusionEntitiesPtr& leaf_entities = leafs[idx_leaf]->entities();
  320. // //get last loop
  321. // size_t idx_last_loop = leaf_entities.size() - 1;
  322. // while (!leaf_entities[idx_last_loop]->is_loop()) {
  323. // if (idx_last_loop == 0) {
  324. // assert(false);
  325. // //goto goto_next_polyline;
  326. // }
  327. // --idx_last_loop;
  328. // }
  329. // //test
  330. // assert(leafs[idx_leaf]->entities()[idx_last_loop]->is_loop());
  331. // if (leafs[idx_leaf]->entities()[idx_last_loop]->is_loop() &&
  332. // static_cast<ExtrusionLoop*>(leafs[idx_leaf]->entities()[idx_last_loop])->polygon().contains(poly.points[poly.size() / 2])) {
  333. // //do gapfill locally
  334. // leafs[idx_leaf]->append(
  335. // Geometry::variable_width(
  336. // poly, ExtrusionRole::GapFill,
  337. // params.flow,
  338. // scale_t(params.config->get_computed_value("resolution_internal")),
  339. // params.flow.scaled_width() / 10)
  340. // );
  341. // polylines.erase(polylines.begin() + idx_polyline);
  342. // --idx_polyline;
  343. // break;
  344. // }
  345. // }
  346. // //goto_next_polyline:
  347. //}
  348. bool fill_bridge = good_role.is_bridge() || params.flow.bridge();
  349. // allow bridged gapfill, mostly for support bottom interface.
  350. assert(!good_role.is_bridge());
  351. if (!polylines.empty()) {
  352. ExtrusionEntitiesPtr gap_fill_entities = Geometry::thin_variable_width(polylines, ExtrusionRole::GapFill, params.flow, scale_t(params.config->get_computed_value("resolution_internal")), true);
  353. if (!gap_fill_entities.empty()) {
  354. // set role if needed
  355. if (fill_bridge || (good_role != ExtrusionRole::SolidInfill && good_role != ExtrusionRole::TopSolidInfill)) {
  356. ExtrusionSetRole set_good_role(good_role);
  357. for (ExtrusionEntity* ptr : gap_fill_entities)
  358. ptr->visit(set_good_role);
  359. }
  360. //move them into the collection
  361. if (gap_fill_entities.size() == 1) {
  362. root_collection_nosort->append(std::move(gap_fill_entities));
  363. } else {
  364. ExtrusionEntityCollection gapsCollection;
  365. gapsCollection.append(std::move(gap_fill_entities));
  366. root_collection_nosort->append(std::move(gapsCollection));
  367. }
  368. }
  369. }
  370. }
  371. }
  372. if (!root_collection_nosort->entities().empty())
  373. out_to_check.push_back(root_collection_nosort);
  374. else delete root_collection_nosort;
  375. }
  376. // external gapfill
  377. ExPolygons gapfill_areas = diff_ex(ExPolygons{ surface->expolygon }, offset_ex(expp, double(scale_(0.5 * this->get_spacing()))));
  378. gapfill_areas = union_safety_offset_ex(gapfill_areas);
  379. if (gapfill_areas.size() > 0 && no_overlap_expolygons.size() > 0) {
  380. double minarea = double(params.flow.scaled_width()) * double(params.flow.scaled_width());
  381. if (params.config != nullptr) minarea = scale_d(params.config->gap_fill_min_area.get_abs_value(params.flow.width())) * double(params.flow.scaled_width());
  382. for (int i = 0; i < gapfill_areas.size(); i++) {
  383. if (gapfill_areas[i].area() < minarea) {
  384. gapfill_areas.erase(gapfill_areas.begin() + i);
  385. i--;
  386. }
  387. }
  388. FillParams params2{ params };
  389. params2.role = ExtrusionRole::GapFill;
  390. do_gap_fill(intersection_ex(gapfill_areas, no_overlap_expolygons), params2, out_to_check);
  391. }
  392. // check volume coverage
  393. if (!out_to_check.empty()) {
  394. double mult_flow = 1;
  395. // check if not over-extruding
  396. if (!params.dont_adjust && params.full_infill() && !params.flow.bridge() && params.fill_exactly) {
  397. // compute the path of the nozzle -> extruded volume
  398. double length_tot = 0;
  399. int nb_lines = 0;
  400. ExtrusionVolume get_volume;
  401. for (ExtrusionEntity *ee : out_to_check) ee->visit(get_volume);
  402. // compute flow to remove spacing_ratio from the equation
  403. // compute real volume to fill
  404. double polyline_volume = compute_unscaled_volume_to_fill(surface, params);
  405. if (get_volume.volume != 0 && polyline_volume != 0)
  406. mult_flow = polyline_volume / get_volume.volume;
  407. // failsafe, it can happen
  408. if (mult_flow > 1.3)
  409. mult_flow = 1.3;
  410. if (mult_flow < 0.8)
  411. mult_flow = 0.8;
  412. BOOST_LOG_TRIVIAL(info) << "concentric Infill (with gapfil) process extrude " << get_volume.volume
  413. << " mm3 for a volume of " << polyline_volume << " mm3 : we mult the flow by "
  414. << mult_flow;
  415. }
  416. mult_flow *= params.flow_mult;
  417. if (mult_flow != 1) {
  418. // apply to extrusions
  419. ExtrusionModifyFlow visitor(mult_flow);
  420. for (ExtrusionEntity *ee : out_to_check) {
  421. ee->visit(visitor);
  422. }
  423. }
  424. }
  425. out.insert(out.end(), out_to_check.begin(), out_to_check.end());
  426. }
  427. void FillConcentric::_fill_surface_single(const FillParams &params,
  428. unsigned int thickness_layers,
  429. const std::pair<float, Point> &direction,
  430. ExPolygon expolygon,
  431. ThickPolylines &thick_polylines_out) const
  432. {
  433. assert(params.use_arachne);
  434. assert(this->print_config != nullptr && this->print_object_config != nullptr);
  435. // no rotation is supported for this infill pattern
  436. Point bbox_size = expolygon.contour.bounding_box().size();
  437. coord_t min_spacing = scale_t(this->get_spacing());
  438. coord_t min_width = params.flow.scaled_width();
  439. if (params.density > 0.9999f && !params.dont_adjust) {
  440. coord_t loops_count = std::max(bbox_size.x(), bbox_size.y()) / min_spacing + 1;
  441. Polygons polygons = offset(expolygon, float(min_spacing) / 2.f);
  442. Arachne::WallToolPaths wallToolPaths(polygons, min_spacing, min_width, min_spacing, min_width, loops_count, 0, params.layer_height, *params.config, *this->print_config);
  443. std::vector<Arachne::VariableWidthLines> loops = wallToolPaths.getToolPaths();
  444. std::vector<const Arachne::ExtrusionLine *> all_extrusions;
  445. for (Arachne::VariableWidthLines &loop : loops) {
  446. if (loop.empty())
  447. continue;
  448. for (const Arachne::ExtrusionLine &wall : loop)
  449. all_extrusions.emplace_back(&wall);
  450. }
  451. // Split paths using a nearest neighbor search.
  452. size_t firts_poly_idx = thick_polylines_out.size();
  453. Point last_pos(0, 0);
  454. for (const Arachne::ExtrusionLine *extrusion : all_extrusions) {
  455. if (extrusion->empty())
  456. continue;
  457. ThickPolyline thick_polyline = Arachne::to_thick_polyline(*extrusion);
  458. if (extrusion->is_closed)
  459. thick_polyline.start_at_index(nearest_point_index(thick_polyline.points, last_pos));
  460. thick_polylines_out.emplace_back(std::move(thick_polyline));
  461. last_pos = thick_polylines_out.back().back();
  462. }
  463. // clip the paths to prevent the extruder from getting exactly on the first point of the loop
  464. // Keep valid paths only.
  465. size_t j = firts_poly_idx;
  466. for (size_t i = firts_poly_idx; i < thick_polylines_out.size(); ++i) {
  467. thick_polylines_out[i].clip_end(this->loop_clipping);
  468. if (thick_polylines_out[i].is_valid()) {
  469. if (j < i)
  470. thick_polylines_out[j] = std::move(thick_polylines_out[i]);
  471. ++j;
  472. }
  473. }
  474. if (j < thick_polylines_out.size())
  475. thick_polylines_out.erase(thick_polylines_out.begin() + int(j), thick_polylines_out.end());
  476. } else {
  477. Polylines polylines;
  478. this->_fill_surface_single(params, thickness_layers, direction, expolygon, polylines);
  479. append(thick_polylines_out, to_thick_polylines(std::move(polylines), min_spacing));
  480. }
  481. }
  482. } // namespace Slic3r