SLAPrintSteps.cpp 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  1. #include <libslic3r/SLAPrintSteps.hpp>
  2. #include <libslic3r/MeshBoolean.hpp>
  3. // Need the cylinder method for the the drainholes in hollowing step
  4. #include <libslic3r/SLA/SupportTreeBuilder.hpp>
  5. #include <libslic3r/SLA/Concurrency.hpp>
  6. #include <libslic3r/SLA/Pad.hpp>
  7. #include <libslic3r/SLA/SupportPointGenerator.hpp>
  8. #include <libslic3r/ClipperUtils.hpp>
  9. // For geometry algorithms with native Clipper types (no copies and conversions)
  10. #include <libnest2d/backends/clipper/geometries.hpp>
  11. #include <boost/log/trivial.hpp>
  12. #include "I18N.hpp"
  13. //! macro used to mark string used at localization,
  14. //! return same string
  15. #define L(s) Slic3r::I18N::translate(s)
  16. namespace Slic3r {
  17. namespace {
  18. const std::array<unsigned, slaposCount> OBJ_STEP_LEVELS = {
  19. 10, // slaposHollowing,
  20. 10, // slaposDrillHoles
  21. 10, // slaposObjectSlice,
  22. 20, // slaposSupportPoints,
  23. 10, // slaposSupportTree,
  24. 10, // slaposPad,
  25. 30, // slaposSliceSupports,
  26. };
  27. std::string OBJ_STEP_LABELS(size_t idx)
  28. {
  29. switch (idx) {
  30. case slaposHollowing: return L("Hollowing model");
  31. case slaposDrillHoles: return L("Drilling holes into model.");
  32. case slaposObjectSlice: return L("Slicing model");
  33. case slaposSupportPoints: return L("Generating support points");
  34. case slaposSupportTree: return L("Generating support tree");
  35. case slaposPad: return L("Generating pad");
  36. case slaposSliceSupports: return L("Slicing supports");
  37. default:;
  38. }
  39. assert(false);
  40. return "Out of bounds!";
  41. };
  42. const std::array<unsigned, slapsCount> PRINT_STEP_LEVELS = {
  43. 10, // slapsMergeSlicesAndEval
  44. 90, // slapsRasterize
  45. };
  46. std::string PRINT_STEP_LABELS(size_t idx)
  47. {
  48. switch (idx) {
  49. case slapsMergeSlicesAndEval: return L("Merging slices and calculating statistics");
  50. case slapsRasterize: return L("Rasterizing layers");
  51. default:;
  52. }
  53. assert(false); return "Out of bounds!";
  54. };
  55. }
  56. SLAPrint::Steps::Steps(SLAPrint *print)
  57. : m_print{print}
  58. , objcount{m_print->m_objects.size()}
  59. , ilhd{m_print->m_material_config.initial_layer_height.getFloat()}
  60. , ilh{float(ilhd)}
  61. , ilhs{scaled(ilhd)}
  62. , objectstep_scale{(max_objstatus - min_objstatus) / (objcount * 100.0)}
  63. {}
  64. void SLAPrint::Steps::hollow_model(SLAPrintObject &po)
  65. {
  66. po.m_hollowing_data.reset();
  67. if (! po.m_config.hollowing_enable.getBool()) {
  68. BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!";
  69. return;
  70. }
  71. BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!";
  72. double thickness = po.m_config.hollowing_min_thickness.getFloat();
  73. double quality = po.m_config.hollowing_quality.getFloat();
  74. double closing_d = po.m_config.hollowing_closing_distance.getFloat();
  75. sla::HollowingConfig hlwcfg{thickness, quality, closing_d};
  76. auto meshptr = generate_interior(po.transformed_mesh(), hlwcfg);
  77. if (meshptr->empty())
  78. BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!";
  79. else {
  80. po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
  81. po.m_hollowing_data->interior = *meshptr;
  82. }
  83. }
  84. // Drill holes into the hollowed/original mesh.
  85. void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
  86. {
  87. bool needs_drilling = ! po.m_model_object->sla_drain_holes.empty();
  88. bool is_hollowed = (po.m_hollowing_data && ! po.m_hollowing_data->interior.empty());
  89. if (! is_hollowed && ! needs_drilling) {
  90. // In this case we can dump any data that might have been
  91. // generated on previous runs.
  92. po.m_hollowing_data.reset();
  93. return;
  94. }
  95. if (! po.m_hollowing_data)
  96. po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
  97. // Hollowing and/or drilling is active, m_hollowing_data is valid.
  98. // Regenerate hollowed mesh, even if it was there already. It may contain
  99. // holes that are no longer on the frontend.
  100. TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes;
  101. hollowed_mesh = po.transformed_mesh();
  102. if (! po.m_hollowing_data->interior.empty()) {
  103. hollowed_mesh.merge(po.m_hollowing_data->interior);
  104. hollowed_mesh.require_shared_vertices();
  105. }
  106. if (! needs_drilling) {
  107. BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes).";
  108. return;
  109. }
  110. BOOST_LOG_TRIVIAL(info) << "Drilling drainage holes.";
  111. sla::DrainHoles drainholes = po.transformed_drainhole_points();
  112. TriangleMesh holes_mesh;
  113. for (const sla::DrainHole &holept : drainholes)
  114. holes_mesh.merge(sla::to_triangle_mesh(holept.to_mesh()));
  115. holes_mesh.require_shared_vertices();
  116. if (!holes_mesh.is_manifold() || MeshBoolean::cgal::does_self_intersect(holes_mesh)) {
  117. MeshBoolean::self_union(holes_mesh);
  118. }
  119. try {
  120. MeshBoolean::cgal::minus(hollowed_mesh, holes_mesh);
  121. } catch (const std::runtime_error &) {
  122. throw std::runtime_error(L(
  123. "Drilling holes into the mesh failed. "
  124. "This is usually caused by broken model. Try to fix it first."));
  125. }
  126. hollowed_mesh.require_shared_vertices();
  127. }
  128. // The slicing will be performed on an imaginary 1D grid which starts from
  129. // the bottom of the bounding box created around the supported model. So
  130. // the first layer which is usually thicker will be part of the supports
  131. // not the model geometry. Exception is when the model is not in the air
  132. // (elevation is zero) and no pad creation was requested. In this case the
  133. // model geometry starts on the ground level and the initial layer is part
  134. // of it. In any case, the model and the supports have to be sliced in the
  135. // same imaginary grid (the height vector argument to TriangleMeshSlicer).
  136. void SLAPrint::Steps::slice_model(SLAPrintObject &po)
  137. {
  138. const TriangleMesh &mesh = po.get_mesh_to_print();
  139. // We need to prepare the slice index...
  140. double lhd = m_print->m_objects.front()->m_config.layer_height.getFloat();
  141. float lh = float(lhd);
  142. coord_t lhs = scaled(lhd);
  143. auto && bb3d = mesh.bounding_box();
  144. double minZ = bb3d.min(Z) - po.get_elevation();
  145. double maxZ = bb3d.max(Z);
  146. auto minZf = float(minZ);
  147. coord_t minZs = scaled(minZ);
  148. coord_t maxZs = scaled(maxZ);
  149. po.m_slice_index.clear();
  150. size_t cap = size_t(1 + (maxZs - minZs - ilhs) / lhs);
  151. po.m_slice_index.reserve(cap);
  152. po.m_slice_index.emplace_back(minZs + ilhs, minZf + ilh / 2.f, ilh);
  153. for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs)
  154. po.m_slice_index.emplace_back(h, unscaled<float>(h) - lh / 2.f, lh);
  155. // Just get the first record that is from the model:
  156. auto slindex_it =
  157. po.closest_slice_record(po.m_slice_index, float(bb3d.min(Z)));
  158. if(slindex_it == po.m_slice_index.end())
  159. //TRN To be shown at the status bar on SLA slicing error.
  160. throw std::runtime_error(
  161. L("Slicing had to be stopped due to an internal error: "
  162. "Inconsistent slice index."));
  163. po.m_model_height_levels.clear();
  164. po.m_model_height_levels.reserve(po.m_slice_index.size());
  165. for(auto it = slindex_it; it != po.m_slice_index.end(); ++it)
  166. po.m_model_height_levels.emplace_back(it->slice_level());
  167. TriangleMeshSlicer slicer(&mesh);
  168. po.m_model_slices.clear();
  169. float closing_r = float(po.config().slice_closing_radius.value);
  170. auto thr = [this]() { m_print->throw_if_canceled(); };
  171. auto &slice_grid = po.m_model_height_levels;
  172. slicer.slice(slice_grid, closing_r, &po.m_model_slices, thr);
  173. // sla::DrainHoles drainholes = po.transformed_drainhole_points();
  174. // cut_drainholes(po.m_model_slices, slice_grid, closing_r, drainholes, thr);
  175. auto mit = slindex_it;
  176. double doffs = m_print->m_printer_config.absolute_correction.getFloat();
  177. coord_t clpr_offs = scaled(doffs);
  178. for(size_t id = 0;
  179. id < po.m_model_slices.size() && mit != po.m_slice_index.end();
  180. id++)
  181. {
  182. // We apply the printer correction offset here.
  183. if(clpr_offs != 0)
  184. po.m_model_slices[id] =
  185. offset_ex(po.m_model_slices[id], float(clpr_offs));
  186. mit->set_model_slice_idx(po, id); ++mit;
  187. }
  188. if(po.m_config.supports_enable.getBool() || po.m_config.pad_enable.getBool())
  189. {
  190. po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh));
  191. }
  192. }
  193. // In this step we check the slices, identify island and cover them with
  194. // support points. Then we sprinkle the rest of the mesh.
  195. void SLAPrint::Steps::support_points(SLAPrintObject &po)
  196. {
  197. // If supports are disabled, we can skip the model scan.
  198. if(!po.m_config.supports_enable.getBool()) return;
  199. const TriangleMesh &mesh = po.get_mesh_to_print();
  200. if (!po.m_supportdata)
  201. po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh));
  202. const ModelObject& mo = *po.m_model_object;
  203. BOOST_LOG_TRIVIAL(debug) << "Support point count "
  204. << mo.sla_support_points.size();
  205. // Unless the user modified the points or we already did the calculation,
  206. // we will do the autoplacement. Otherwise we will just blindly copy the
  207. // frontend data into the backend cache.
  208. if (mo.sla_points_status != sla::PointsStatus::UserModified) {
  209. // calculate heights of slices (slices are calculated already)
  210. const std::vector<float>& heights = po.m_model_height_levels;
  211. // Tell the mesh where drain holes are. Although the points are
  212. // calculated on slices, the algorithm then raycasts the points
  213. // so they actually lie on the mesh.
  214. // po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
  215. throw_if_canceled();
  216. sla::SupportPointGenerator::Config config;
  217. const SLAPrintObjectConfig& cfg = po.config();
  218. // the density config value is in percents:
  219. config.density_relative = float(cfg.support_points_density_relative / 100.f);
  220. config.minimal_distance = float(cfg.support_points_minimal_distance);
  221. config.head_diameter = float(cfg.support_head_front_diameter);
  222. // scaling for the sub operations
  223. double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0;
  224. double init = current_status();
  225. auto statuscb = [this, d, init](unsigned st)
  226. {
  227. double current = init + st * d;
  228. if(std::round(current_status()) < std::round(current))
  229. report_status(current, OBJ_STEP_LABELS(slaposSupportPoints));
  230. };
  231. // Construction of this object does the calculation.
  232. throw_if_canceled();
  233. sla::SupportPointGenerator auto_supports(
  234. po.m_supportdata->emesh, po.get_model_slices(), heights, config,
  235. [this]() { throw_if_canceled(); }, statuscb);
  236. // Now let's extract the result.
  237. const std::vector<sla::SupportPoint>& points = auto_supports.output();
  238. throw_if_canceled();
  239. po.m_supportdata->pts = points;
  240. BOOST_LOG_TRIVIAL(debug) << "Automatic support points: "
  241. << po.m_supportdata->pts.size();
  242. // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass
  243. // the update status to GLGizmoSlaSupports
  244. report_status(-1, L("Generating support points"),
  245. SlicingStatus::RELOAD_SLA_SUPPORT_POINTS);
  246. } else {
  247. // There are either some points on the front-end, or the user
  248. // removed them on purpose. No calculation will be done.
  249. po.m_supportdata->pts = po.transformed_support_points();
  250. }
  251. // If the zero elevation mode is engaged, we have to filter out all the
  252. // points that are on the bottom of the object
  253. if (is_zero_elevation(po.config())) {
  254. double tolerance = po.config().pad_enable.getBool() ?
  255. po.m_config.pad_wall_thickness.getFloat() :
  256. po.m_config.support_base_height.getFloat();
  257. remove_bottom_points(po.m_supportdata->pts,
  258. po.m_supportdata->emesh.ground_level(),
  259. tolerance);
  260. }
  261. }
  262. void SLAPrint::Steps::support_tree(SLAPrintObject &po)
  263. {
  264. if(!po.m_supportdata) return;
  265. sla::PadConfig pcfg = make_pad_cfg(po.m_config);
  266. if (pcfg.embed_object)
  267. po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm);
  268. po.m_supportdata->cfg = make_support_cfg(po.m_config);
  269. // po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
  270. // scaling for the sub operations
  271. double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0;
  272. double init = current_status();
  273. sla::JobController ctl;
  274. ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) {
  275. double current = init + st * d;
  276. if (std::round(current_status()) < std::round(current))
  277. report_status(current, OBJ_STEP_LABELS(slaposSupportTree),
  278. SlicingStatus::DEFAULT, logmsg);
  279. };
  280. ctl.stopcondition = [this]() { return canceled(); };
  281. ctl.cancelfn = [this]() { throw_if_canceled(); };
  282. po.m_supportdata->create_support_tree(ctl);
  283. if (!po.m_config.supports_enable.getBool()) return;
  284. throw_if_canceled();
  285. // Create the unified mesh
  286. auto rc = SlicingStatus::RELOAD_SCENE;
  287. // This is to prevent "Done." being displayed during merged_mesh()
  288. report_status(-1, L("Visualizing supports"));
  289. BOOST_LOG_TRIVIAL(debug) << "Processed support point count "
  290. << po.m_supportdata->pts.size();
  291. // Check the mesh for later troubleshooting.
  292. if(po.support_mesh().empty())
  293. BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty";
  294. report_status(-1, L("Visualizing supports"), rc);
  295. }
  296. void SLAPrint::Steps::generate_pad(SLAPrintObject &po) {
  297. // this step can only go after the support tree has been created
  298. // and before the supports had been sliced. (or the slicing has to be
  299. // repeated)
  300. if(po.m_config.pad_enable.getBool()) {
  301. // Get the distilled pad configuration from the config
  302. sla::PadConfig pcfg = make_pad_cfg(po.m_config);
  303. ExPolygons bp; // This will store the base plate of the pad.
  304. double pad_h = pcfg.full_height();
  305. const TriangleMesh &trmesh = po.transformed_mesh();
  306. if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) {
  307. // No support (thus no elevation) or zero elevation mode
  308. // we sometimes call it "builtin pad" is enabled so we will
  309. // get a sample from the bottom of the mesh and use it for pad
  310. // creation.
  311. sla::pad_blueprint(trmesh, bp, float(pad_h),
  312. float(po.m_config.layer_height.getFloat()),
  313. [this](){ throw_if_canceled(); });
  314. }
  315. po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg);
  316. auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(sla::MeshType::Pad);
  317. if (!validate_pad(pad_mesh, pcfg))
  318. throw std::runtime_error(
  319. L("No pad can be generated for this model with the "
  320. "current configuration"));
  321. } else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) {
  322. po.m_supportdata->support_tree_ptr->remove_pad();
  323. }
  324. throw_if_canceled();
  325. report_status(-1, L("Visualizing supports"), SlicingStatus::RELOAD_SCENE);
  326. }
  327. // Slicing the support geometries similarly to the model slicing procedure.
  328. // If the pad had been added previously (see step "base_pool" than it will
  329. // be part of the slices)
  330. void SLAPrint::Steps::slice_supports(SLAPrintObject &po) {
  331. auto& sd = po.m_supportdata;
  332. if(sd) sd->support_slices.clear();
  333. // Don't bother if no supports and no pad is present.
  334. if (!po.m_config.supports_enable.getBool() && !po.m_config.pad_enable.getBool())
  335. return;
  336. if(sd && sd->support_tree_ptr) {
  337. auto heights = reserve_vector<float>(po.m_slice_index.size());
  338. for(auto& rec : po.m_slice_index) heights.emplace_back(rec.slice_level());
  339. sd->support_slices = sd->support_tree_ptr->slice(
  340. heights, float(po.config().slice_closing_radius.value));
  341. }
  342. double doffs = m_print->m_printer_config.absolute_correction.getFloat();
  343. coord_t clpr_offs = scaled(doffs);
  344. for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i) {
  345. // We apply the printer correction offset here.
  346. if (clpr_offs != 0)
  347. sd->support_slices[i] = offset_ex(sd->support_slices[i], float(clpr_offs));
  348. po.m_slice_index[i].set_support_slice_idx(po, i);
  349. }
  350. // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update
  351. // status to the 3D preview to load the SLA slices.
  352. report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
  353. }
  354. using ClipperPoint = ClipperLib::IntPoint;
  355. using ClipperPolygon = ClipperLib::Polygon; // see clipper_polygon.hpp in libnest2d
  356. using ClipperPolygons = std::vector<ClipperPolygon>;
  357. static ClipperPolygons polyunion(const ClipperPolygons &subjects)
  358. {
  359. ClipperLib::Clipper clipper;
  360. bool closed = true;
  361. for(auto& path : subjects) {
  362. clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
  363. clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed);
  364. }
  365. auto mode = ClipperLib::pftPositive;
  366. return libnest2d::clipper_execute(clipper, ClipperLib::ctUnion, mode, mode);
  367. }
  368. static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPolygons& clips)
  369. {
  370. ClipperLib::Clipper clipper;
  371. bool closed = true;
  372. for(auto& path : subjects) {
  373. clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
  374. clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed);
  375. }
  376. for(auto& path : clips) {
  377. clipper.AddPath(path.Contour, ClipperLib::ptClip, closed);
  378. clipper.AddPaths(path.Holes, ClipperLib::ptClip, closed);
  379. }
  380. auto mode = ClipperLib::pftPositive;
  381. return libnest2d::clipper_execute(clipper, ClipperLib::ctDifference, mode, mode);
  382. }
  383. // get polygons for all instances in the object
  384. static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o)
  385. {
  386. namespace sl = libnest2d::sl;
  387. if (!record.print_obj()) return {};
  388. ClipperPolygons polygons;
  389. auto &input_polygons = record.get_slice(o);
  390. auto &instances = record.print_obj()->instances();
  391. bool is_lefthanded = record.print_obj()->is_left_handed();
  392. polygons.reserve(input_polygons.size() * instances.size());
  393. for (const ExPolygon& polygon : input_polygons) {
  394. if(polygon.contour.empty()) continue;
  395. for (size_t i = 0; i < instances.size(); ++i)
  396. {
  397. ClipperPolygon poly;
  398. // We need to reverse if is_lefthanded is true but
  399. bool needreverse = is_lefthanded;
  400. // should be a move
  401. poly.Contour.reserve(polygon.contour.size() + 1);
  402. auto& cntr = polygon.contour.points;
  403. if(needreverse)
  404. for(auto it = cntr.rbegin(); it != cntr.rend(); ++it)
  405. poly.Contour.emplace_back(it->x(), it->y());
  406. else
  407. for(auto& p : cntr)
  408. poly.Contour.emplace_back(p.x(), p.y());
  409. for(auto& h : polygon.holes) {
  410. poly.Holes.emplace_back();
  411. auto& hole = poly.Holes.back();
  412. hole.reserve(h.points.size() + 1);
  413. if(needreverse)
  414. for(auto it = h.points.rbegin(); it != h.points.rend(); ++it)
  415. hole.emplace_back(it->x(), it->y());
  416. else
  417. for(auto& p : h.points)
  418. hole.emplace_back(p.x(), p.y());
  419. }
  420. if(is_lefthanded) {
  421. for(auto& p : poly.Contour) p.X = -p.X;
  422. for(auto& h : poly.Holes) for(auto& p : h) p.X = -p.X;
  423. }
  424. sl::rotate(poly, double(instances[i].rotation));
  425. sl::translate(poly, ClipperPoint{instances[i].shift(X),
  426. instances[i].shift(Y)});
  427. polygons.emplace_back(std::move(poly));
  428. }
  429. }
  430. return polygons;
  431. }
  432. void SLAPrint::Steps::initialize_printer_input()
  433. {
  434. auto &printer_input = m_print->m_printer_input;
  435. // clear the rasterizer input
  436. printer_input.clear();
  437. size_t mx = 0;
  438. for(SLAPrintObject * o : m_print->m_objects) {
  439. if(auto m = o->get_slice_index().size() > mx) mx = m;
  440. }
  441. printer_input.reserve(mx);
  442. auto eps = coord_t(SCALED_EPSILON);
  443. for(SLAPrintObject * o : m_print->m_objects) {
  444. coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs;
  445. for(const SliceRecord& slicerecord : o->get_slice_index()) {
  446. if (!slicerecord.is_valid())
  447. throw std::runtime_error(
  448. L("There are unprintable objects. Try to "
  449. "adjust support settings to make the "
  450. "objects printable."));
  451. coord_t lvlid = slicerecord.print_level() - gndlvl;
  452. // Neat trick to round the layer levels to the grid.
  453. lvlid = eps * (lvlid / eps);
  454. auto it = std::lower_bound(printer_input.begin(),
  455. printer_input.end(),
  456. PrintLayer(lvlid));
  457. if(it == printer_input.end() || it->level() != lvlid)
  458. it = printer_input.insert(it, PrintLayer(lvlid));
  459. it->add(slicerecord);
  460. }
  461. }
  462. }
  463. // Merging the slices from all the print objects into one slice grid and
  464. // calculating print statistics from the merge result.
  465. void SLAPrint::Steps::merge_slices_and_eval_stats() {
  466. initialize_printer_input();
  467. auto &print_statistics = m_print->m_print_statistics;
  468. auto &printer_config = m_print->m_printer_config;
  469. auto &material_config = m_print->m_material_config;
  470. auto &printer_input = m_print->m_printer_input;
  471. print_statistics.clear();
  472. // libnest calculates positive area for clockwise polygons, Slic3r is in counter-clockwise
  473. auto areafn = [](const ClipperPolygon& poly) { return - libnest2d::sl::area(poly); };
  474. const double area_fill = printer_config.area_fill.getFloat()*0.01;// 0.5 (50%);
  475. const double fast_tilt = printer_config.fast_tilt_time.getFloat();// 5.0;
  476. const double slow_tilt = printer_config.slow_tilt_time.getFloat();// 8.0;
  477. const double init_exp_time = material_config.initial_exposure_time.getFloat();
  478. const double exp_time = material_config.exposure_time.getFloat();
  479. const int fade_layers_cnt = m_print->m_default_object_config.faded_layers.getInt();// 10 // [3;20]
  480. const auto width = scaled<double>(printer_config.display_width.getFloat());
  481. const auto height = scaled<double>(printer_config.display_height.getFloat());
  482. const double display_area = width*height;
  483. double supports_volume(0.0);
  484. double models_volume(0.0);
  485. double estim_time(0.0);
  486. size_t slow_layers = 0;
  487. size_t fast_layers = 0;
  488. const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1);
  489. double fade_layer_time = init_exp_time;
  490. sla::ccr::SpinningMutex mutex;
  491. using Lock = std::lock_guard<sla::ccr::SpinningMutex>;
  492. // Going to parallel:
  493. auto printlayerfn = [
  494. // functions and read only vars
  495. areafn, area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, delta_fade_time,
  496. // write vars
  497. &mutex, &models_volume, &supports_volume, &estim_time, &slow_layers,
  498. &fast_layers, &fade_layer_time](PrintLayer& layer, size_t sliced_layer_cnt)
  499. {
  500. // vector of slice record references
  501. auto& slicerecord_references = layer.slices();
  502. if(slicerecord_references.empty()) return;
  503. // Layer height should match for all object slices for a given level.
  504. const auto l_height = double(slicerecord_references.front().get().layer_height());
  505. // Calculation of the consumed material
  506. ClipperPolygons model_polygons;
  507. ClipperPolygons supports_polygons;
  508. size_t c = std::accumulate(layer.slices().begin(),
  509. layer.slices().end(),
  510. size_t(0),
  511. [](size_t a, const SliceRecord &sr) {
  512. return a + sr.get_slice(soModel).size();
  513. });
  514. model_polygons.reserve(c);
  515. c = std::accumulate(layer.slices().begin(),
  516. layer.slices().end(),
  517. size_t(0),
  518. [](size_t a, const SliceRecord &sr) {
  519. return a + sr.get_slice(soModel).size();
  520. });
  521. supports_polygons.reserve(c);
  522. for(const SliceRecord& record : layer.slices()) {
  523. ClipperPolygons modelslices = get_all_polygons(record, soModel);
  524. for(ClipperPolygon& p_tmp : modelslices) model_polygons.emplace_back(std::move(p_tmp));
  525. ClipperPolygons supportslices = get_all_polygons(record, soSupport);
  526. for(ClipperPolygon& p_tmp : supportslices) supports_polygons.emplace_back(std::move(p_tmp));
  527. }
  528. model_polygons = polyunion(model_polygons);
  529. double layer_model_area = 0;
  530. for (const ClipperPolygon& polygon : model_polygons)
  531. layer_model_area += areafn(polygon);
  532. if (layer_model_area < 0 || layer_model_area > 0) {
  533. Lock lck(mutex); models_volume += layer_model_area * l_height;
  534. }
  535. if(!supports_polygons.empty()) {
  536. if(model_polygons.empty()) supports_polygons = polyunion(supports_polygons);
  537. else supports_polygons = polydiff(supports_polygons, model_polygons);
  538. // allegedly, union of subject is done withing the diff according to the pftPositive polyFillType
  539. }
  540. double layer_support_area = 0;
  541. for (const ClipperPolygon& polygon : supports_polygons)
  542. layer_support_area += areafn(polygon);
  543. if (layer_support_area < 0 || layer_support_area > 0) {
  544. Lock lck(mutex); supports_volume += layer_support_area * l_height;
  545. }
  546. // Here we can save the expensively calculated polygons for printing
  547. ClipperPolygons trslices;
  548. trslices.reserve(model_polygons.size() + supports_polygons.size());
  549. for(ClipperPolygon& poly : model_polygons) trslices.emplace_back(std::move(poly));
  550. for(ClipperPolygon& poly : supports_polygons) trslices.emplace_back(std::move(poly));
  551. layer.transformed_slices(polyunion(trslices));
  552. // Calculation of the slow and fast layers to the future controlling those values on FW
  553. const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill;
  554. const double tilt_time = is_fast_layer ? fast_tilt : slow_tilt;
  555. { Lock lck(mutex);
  556. if (is_fast_layer)
  557. fast_layers++;
  558. else
  559. slow_layers++;
  560. // Calculation of the printing time
  561. if (sliced_layer_cnt < 3)
  562. estim_time += init_exp_time;
  563. else if (fade_layer_time > exp_time)
  564. {
  565. fade_layer_time -= delta_fade_time;
  566. estim_time += fade_layer_time;
  567. }
  568. else
  569. estim_time += exp_time;
  570. estim_time += tilt_time;
  571. }
  572. };
  573. // sequential version for debugging:
  574. // for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i);
  575. sla::ccr::enumerate(printer_input.begin(), printer_input.end(), printlayerfn);
  576. auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR;
  577. print_statistics.support_used_material = supports_volume * SCALING2;
  578. print_statistics.objects_used_material = models_volume * SCALING2;
  579. // Estimated printing time
  580. // A layers count o the highest object
  581. if (printer_input.size() == 0)
  582. print_statistics.estimated_print_time = std::nan("");
  583. else
  584. print_statistics.estimated_print_time = estim_time;
  585. print_statistics.fast_layers_count = fast_layers;
  586. print_statistics.slow_layers_count = slow_layers;
  587. report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
  588. }
  589. // Rasterizing the model objects, and their supports
  590. void SLAPrint::Steps::rasterize()
  591. {
  592. if(canceled()) return;
  593. auto &print_statistics = m_print->m_print_statistics;
  594. auto &printer_input = m_print->m_printer_input;
  595. // Set up the printer, allocate space for all the layers
  596. sla::RasterWriter &printer = m_print->init_printer();
  597. auto lvlcnt = unsigned(printer_input.size());
  598. printer.layers(lvlcnt);
  599. // coefficient to map the rasterization state (0-99) to the allocated
  600. // portion (slot) of the process state
  601. double sd = (100 - max_objstatus) / 100.0;
  602. // slot is the portion of 100% that is realted to rasterization
  603. unsigned slot = PRINT_STEP_LEVELS[slapsRasterize];
  604. // pst: previous state
  605. double pst = current_status();
  606. double increment = (slot * sd) / printer_input.size();
  607. double dstatus = current_status();
  608. sla::ccr::SpinningMutex slck;
  609. using Lock = std::lock_guard<sla::ccr::SpinningMutex>;
  610. // procedure to process one height level. This will run in parallel
  611. auto lvlfn =
  612. [this, &slck, &printer, increment, &dstatus, &pst]
  613. (PrintLayer& printlayer, size_t idx)
  614. {
  615. if(canceled()) return;
  616. auto level_id = unsigned(idx);
  617. // Switch to the appropriate layer in the printer
  618. printer.begin_layer(level_id);
  619. for(const ClipperLib::Polygon& poly : printlayer.transformed_slices())
  620. printer.draw_polygon(poly, level_id);
  621. // Finish the layer for later saving it.
  622. printer.finish_layer(level_id);
  623. // Status indication guarded with the spinlock
  624. {
  625. Lock lck(slck);
  626. dstatus += increment;
  627. double st = std::round(dstatus);
  628. if(st > pst) {
  629. report_status(st, PRINT_STEP_LABELS(slapsRasterize));
  630. pst = st;
  631. }
  632. }
  633. };
  634. // last minute escape
  635. if(canceled()) return;
  636. // Sequential version (for testing)
  637. // for(unsigned l = 0; l < lvlcnt; ++l) lvlfn(l);
  638. // Print all the layers in parallel
  639. sla::ccr::enumerate(printer_input.begin(), printer_input.end(), lvlfn);
  640. // Set statistics values to the printer
  641. sla::RasterWriter::PrintStatistics stats;
  642. stats.used_material = (print_statistics.objects_used_material +
  643. print_statistics.support_used_material) / 1000;
  644. int num_fade = m_print->m_default_object_config.faded_layers.getInt();
  645. stats.num_fade = num_fade >= 0 ? size_t(num_fade) : size_t(0);
  646. stats.num_fast = print_statistics.fast_layers_count;
  647. stats.num_slow = print_statistics.slow_layers_count;
  648. stats.estimated_print_time_s = print_statistics.estimated_print_time;
  649. printer.set_statistics(stats);
  650. }
  651. std::string SLAPrint::Steps::label(SLAPrintObjectStep step)
  652. {
  653. return OBJ_STEP_LABELS(step);
  654. }
  655. std::string SLAPrint::Steps::label(SLAPrintStep step)
  656. {
  657. return PRINT_STEP_LABELS(step);
  658. }
  659. double SLAPrint::Steps::progressrange(SLAPrintObjectStep step) const
  660. {
  661. return OBJ_STEP_LEVELS[step] * objectstep_scale;
  662. }
  663. double SLAPrint::Steps::progressrange(SLAPrintStep step) const
  664. {
  665. return PRINT_STEP_LEVELS[step] * (100 - max_objstatus) / 100.0;
  666. }
  667. void SLAPrint::Steps::execute(SLAPrintObjectStep step, SLAPrintObject &obj)
  668. {
  669. switch(step) {
  670. case slaposHollowing: hollow_model(obj); break;
  671. case slaposDrillHoles: drill_holes(obj); break;
  672. case slaposObjectSlice: slice_model(obj); break;
  673. case slaposSupportPoints: support_points(obj); break;
  674. case slaposSupportTree: support_tree(obj); break;
  675. case slaposPad: generate_pad(obj); break;
  676. case slaposSliceSupports: slice_supports(obj); break;
  677. case slaposCount: assert(false);
  678. }
  679. }
  680. void SLAPrint::Steps::execute(SLAPrintStep step)
  681. {
  682. switch (step) {
  683. case slapsMergeSlicesAndEval: merge_slices_and_eval_stats(); break;
  684. case slapsRasterize: rasterize(); break;
  685. case slapsCount: assert(false);
  686. }
  687. }
  688. }