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