#include #include #include #include #include // Need the cylinder method for the the drainholes in hollowing step #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include #include #include "I18N.hpp" #include //! macro used to mark string used at localization, //! return same string #define L(s) Slic3r::I18N::translate(s) namespace Slic3r { namespace { const std::array OBJ_STEP_LEVELS = { 13, // slaposAssembly 13, // slaposHollowing, 13, // slaposDrillHoles 13, // slaposObjectSlice, 13, // slaposSupportPoints, 13, // slaposSupportTree, 11, // slaposPad, 11, // slaposSliceSupports, }; std::string OBJ_STEP_LABELS(size_t idx) { switch (idx) { case slaposAssembly: return L("Assembling model from parts"); case slaposHollowing: return L("Hollowing model"); case slaposDrillHoles: return L("Drilling holes into model."); case slaposObjectSlice: return L("Slicing model"); case slaposSupportPoints: return L("Generating support points"); case slaposSupportTree: return L("Generating support tree"); case slaposPad: return L("Generating pad"); case slaposSliceSupports: return L("Slicing supports"); default:; } assert(false); return "Out of bounds!"; } const std::array PRINT_STEP_LEVELS = { 10, // slapsMergeSlicesAndEval 90, // slapsRasterize }; std::string PRINT_STEP_LABELS(size_t idx) { switch (idx) { case slapsMergeSlicesAndEval: return L("Merging slices and calculating statistics"); case slapsRasterize: return L("Rasterizing layers"); default:; } assert(false); return "Out of bounds!"; } } SLAPrint::Steps::Steps(SLAPrint *print) : m_print{print} , objcount{m_print->m_objects.size()} , ilhd{m_print->m_material_config.initial_layer_height.getFloat()} , ilh{float(ilhd)} , ilhs{scaled(ilhd)} , objectstep_scale{(max_objstatus - min_objstatus) / (objcount * 100.0)} {} void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin o) { if (o == soSupport && !po.m_supportdata) return; auto faded_lyrs = size_t(po.m_config.faded_layers.getInt()); double min_w = m_print->m_printer_config.elefant_foot_min_width.getFloat() / 2.; double start_efc = m_print->m_printer_config.elefant_foot_compensation.getFloat(); double doffs = m_print->m_printer_config.absolute_correction.getFloat(); coord_t clpr_offs = scaled(doffs); faded_lyrs = std::min(po.m_slice_index.size(), faded_lyrs); size_t faded_lyrs_efc = std::max(size_t(1), faded_lyrs - 1); auto efc = [start_efc, faded_lyrs_efc](size_t pos) { return (faded_lyrs_efc - pos) * start_efc / faded_lyrs_efc; }; std::vector &slices = o == soModel ? po.m_model_slices : po.m_supportdata->support_slices; if (clpr_offs != 0) for (size_t i = 0; i < po.m_slice_index.size(); ++i) { size_t idx = po.m_slice_index[i].get_slice_idx(o); if (idx < slices.size()) slices[idx] = offset_ex(slices[idx], float(clpr_offs)); } if (start_efc > 0.) for (size_t i = 0; i < faded_lyrs; ++i) { size_t idx = po.m_slice_index[i].get_slice_idx(o); if (idx < slices.size()) slices[idx] = elephant_foot_compensation(slices[idx], min_w, efc(i)); } } template bool is_all_positive(const Cont &csgmesh) { bool is_all_pos = std::all_of(csgmesh.begin(), csgmesh.end(), [](auto &part) { return csg::get_operation(part) == csg::CSGType::Union; }); return is_all_pos; } template static indexed_triangle_set csgmesh_merge_positive_parts(const Cont &csgmesh) { indexed_triangle_set m; for (auto &csgpart : csgmesh) { auto op = csg::get_operation(csgpart); const indexed_triangle_set * pmesh = csg::get_mesh(csgpart); if (pmesh && op == csg::CSGType::Union) { indexed_triangle_set mcpy = *pmesh; its_transform(mcpy, csg::get_transform(csgpart)); its_merge(m, mcpy); } } return m; } indexed_triangle_set SLAPrint::Steps::generate_preview_vdb( SLAPrintObject &po, SLAPrintObjectStep step) { // Empirical upper limit to not get excessive performance hit constexpr double MaxPreviewVoxelScale = 12.; // update preview mesh double vscale = std::min(MaxPreviewVoxelScale, 1. / po.m_config.layer_height.getFloat()); auto voxparams = csg::VoxelizeParams{} .voxel_scale(vscale) .exterior_bandwidth(1.f) .interior_bandwidth(1.f); voxparams.statusfn([&po](int){ return po.m_print->cancel_status() != CancelStatus::NOT_CANCELED; }); auto r = range(po.m_mesh_to_slice); auto grid = csg::voxelize_csgmesh(r, voxparams); auto m = grid ? grid_to_mesh(*grid, 0., 0.01) : indexed_triangle_set{}; float loss_less_max_error = 1e-6; its_quadric_edge_collapse(m, 0U, &loss_less_max_error); return m; } void SLAPrint::Steps::generate_preview(SLAPrintObject &po, SLAPrintObjectStep step) { Benchmark bench; bench.start(); auto r = range(po.m_mesh_to_slice); auto m = indexed_triangle_set{}; bool handled = false; if (is_all_positive(r)) { m = csgmesh_merge_positive_parts(r); handled = true; } else if (csg::check_csgmesh_booleans(r) == r.end()) { auto cgalmeshptr = csg::perform_csgmesh_booleans(r); if (cgalmeshptr) { m = MeshBoolean::cgal::cgal_to_indexed_triangle_set(*cgalmeshptr); handled = true; } else { BOOST_LOG_TRIVIAL(warning) << "CSG mesh is not egligible for proper CGAL booleans!"; } } else { // Normal cgal processing failed. If there are no negative volumes, // the hollowing can be tried with the old algorithm which didn't handled volumes. // If that fails for any of the drillholes, the voxelization fallback is // used. bool is_pure_model = is_all_positive(po.mesh_to_slice(slaposAssembly)); bool can_hollow = po.m_hollowing_data && po.m_hollowing_data->interior && !sla::get_mesh(*po.m_hollowing_data->interior).empty(); bool hole_fail = false; if (step == slaposHollowing && is_pure_model) { if (can_hollow) { m = csgmesh_merge_positive_parts(r); sla::hollow_mesh(m, *po.m_hollowing_data->interior, sla::hfRemoveInsideTriangles); } handled = true; } else if (step == slaposDrillHoles && is_pure_model) { if (po.m_model_object->sla_drain_holes.empty()) { // Get the last printable preview auto &meshp = po.get_mesh_to_print(); if (meshp) m = *(meshp); handled = true; } else if (can_hollow) { m = csgmesh_merge_positive_parts(r); sla::hollow_mesh(m, *po.m_hollowing_data->interior); sla::DrainHoles drainholes = po.transformed_drainhole_points(); auto ret = sla::hollow_mesh_and_drill( m, *po.m_hollowing_data->interior, drainholes, [/*&po, &drainholes, */&hole_fail](size_t i) { hole_fail = /*drainholes[i].failed = po.model_object()->sla_drain_holes[i].failed =*/ true; }); if (ret & static_cast(sla::HollowMeshResult::FaultyMesh)) { po.active_step_add_warning( PrintStateBase::WarningLevel::NON_CRITICAL, L("Mesh to be hollowed is not suitable for hollowing (does not " "bound a volume).")); } if (ret & static_cast(sla::HollowMeshResult::FaultyHoles)) { po.active_step_add_warning( PrintStateBase::WarningLevel::NON_CRITICAL, L("Unable to drill the current configuration of holes into the " "model.")); } handled = true; if (ret & static_cast(sla::HollowMeshResult::DrillingFailed)) { po.active_step_add_warning( PrintStateBase::WarningLevel::NON_CRITICAL, L( "Drilling holes into the mesh failed. " "This is usually caused by broken model. Try to fix it first.")); handled = false; } if (hole_fail) { po.active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, L("Failed to drill some holes into the model")); handled = false; } } } } if (!handled) { // Last resort to voxelization. po.active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL, L("Can't perform full mesh booleans! " "Some parts of the print will be previewed with approximated meshes. " "This does not affect the quality of slices or the physical print in any way.")); m = generate_preview_vdb(po, step); } if (!m.empty()) po.m_preview_meshes[step] = std::make_shared(std::move(m)); for (size_t i = size_t(step) + 1; i < slaposCount; ++i) { po.m_preview_meshes[i] = {}; } bench.stop(); if (!m.empty()) BOOST_LOG_TRIVIAL(trace) << "Preview gen took: " << bench.getElapsedSec(); else BOOST_LOG_TRIVIAL(error) << "Preview failed!"; using namespace std::string_literals; report_status(-2, "Reload preview from step "s + std::to_string(int(step)), SlicingStatus::RELOAD_SLA_PREVIEW); } static inline void clear_csg(std::multiset &s, SLAPrintObjectStep step) { auto r = s.equal_range(step); s.erase(r.first, r.second); } struct csg_inserter { std::multiset &m; SLAPrintObjectStep key; csg_inserter &operator*() { return *this; } void operator=(csg::CSGPart &&part) { part.its_ptr.convert_unique_to_shared(); m.emplace(key, std::move(part)); } csg_inserter& operator++() { return *this; } }; void SLAPrint::Steps::mesh_assembly(SLAPrintObject &po) { po.m_mesh_to_slice.clear(); po.m_supportdata.reset(); po.m_hollowing_data.reset(); csg::model_to_csgmesh(*po.model_object(), po.trafo(), csg_inserter{po.m_mesh_to_slice, slaposAssembly}, csg::mpartsPositive | csg::mpartsNegative | csg::mpartsDoSplits); generate_preview(po, slaposAssembly); } void SLAPrint::Steps::hollow_model(SLAPrintObject &po) { po.m_hollowing_data.reset(); po.m_supportdata.reset(); clear_csg(po.m_mesh_to_slice, slaposDrillHoles); clear_csg(po.m_mesh_to_slice, slaposHollowing); if (! po.m_config.hollowing_enable.getBool()) { BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!"; return; } BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!"; double thickness = po.m_config.hollowing_min_thickness.getFloat(); double quality = po.m_config.hollowing_quality.getFloat(); double closing_d = po.m_config.hollowing_closing_distance.getFloat(); sla::HollowingConfig hlwcfg{thickness, quality, closing_d}; sla::JobController ctl; ctl.stopcondition = [this]() { return canceled(); }; ctl.cancelfn = [this]() { throw_if_canceled(); }; sla::InteriorPtr interior = generate_interior(po.mesh_to_slice(), hlwcfg, ctl); if (!interior || sla::get_mesh(*interior).empty()) BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!"; else { po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); po.m_hollowing_data->interior = std::move(interior); indexed_triangle_set &m = sla::get_mesh(*po.m_hollowing_data->interior); if (!m.empty()) { // simplify mesh lossless float loss_less_max_error = 2*std::numeric_limits::epsilon(); its_quadric_edge_collapse(m, 0U, &loss_less_max_error); its_compactify_vertices(m); its_merge_vertices(m); } // Put the interior into the target mesh as a negative po.m_mesh_to_slice .emplace(slaposHollowing, csg::CSGPart{std::make_shared(m), csg::CSGType::Difference}); generate_preview(po, slaposHollowing); } } // Drill holes into the hollowed/original mesh. void SLAPrint::Steps::drill_holes(SLAPrintObject &po) { po.m_supportdata.reset(); clear_csg(po.m_mesh_to_slice, slaposDrillHoles); csg::model_to_csgmesh(*po.model_object(), po.trafo(), csg_inserter{po.m_mesh_to_slice, slaposDrillHoles}, csg::mpartsDrillHoles); auto r = po.m_mesh_to_slice.equal_range(slaposDrillHoles); // update preview mesh if (r.first != r.second) generate_preview(po, slaposDrillHoles); else po.m_preview_meshes[slaposDrillHoles] = po.get_mesh_to_print(); // Release the data, won't be needed anymore, takes huge amount of ram if (po.m_hollowing_data && po.m_hollowing_data->interior) po.m_hollowing_data->interior.reset(); } template static std::vector slice_volumes( const ModelVolumePtrs &volumes, const std::vector &slice_grid, const Transform3d &trafo, const MeshSlicingParamsEx &slice_params, Pred &&predicate) { indexed_triangle_set mesh; for (const ModelVolume *vol : volumes) { if (predicate(vol)) { indexed_triangle_set vol_mesh = vol->mesh().its; its_transform(vol_mesh, trafo * vol->get_matrix()); its_merge(mesh, vol_mesh); } } std::vector out; if (!mesh.empty()) { out = slice_mesh_ex(mesh, slice_grid, slice_params); } return out; } template BoundingBoxf3 csgmesh_positive_bb(const Cont &csg) { // Calculate the biggest possible bounding box of the mesh to be sliced // from all the positive parts that it contains. BoundingBoxf3 bb3d; bool skip = false; for (const auto &m : csg) { auto op = csg::get_operation(m); auto stackop = csg::get_stack_operation(m); if (stackop == csg::CSGStackOp::Push && op != csg::CSGType::Union) skip = true; if (!skip && csg::get_mesh(m) && op == csg::CSGType::Union) bb3d.merge(bounding_box(*csg::get_mesh(m), csg::get_transform(m))); if (stackop == csg::CSGStackOp::Pop) skip = false; } return bb3d; } // The slicing will be performed on an imaginary 1D grid which starts from // the bottom of the bounding box created around the supported model. So // the first layer which is usually thicker will be part of the supports // not the model geometry. Exception is when the model is not in the air // (elevation is zero) and no pad creation was requested. In this case the // model geometry starts on the ground level and the initial layer is part // of it. In any case, the model and the supports have to be sliced in the // same imaginary grid (the height vector argument to TriangleMeshSlicer). void SLAPrint::Steps::slice_model(SLAPrintObject &po) { // The first mesh in the csg sequence is assumed to be a positive part assert(po.m_mesh_to_slice.empty() || csg::get_operation(*po.m_mesh_to_slice.begin()) == csg::CSGType::Union); auto bb3d = csgmesh_positive_bb(po.m_mesh_to_slice); // We need to prepare the slice index... double lhd = m_print->m_objects.front()->m_config.layer_height.getFloat(); float lh = float(lhd); coord_t lhs = scaled(lhd); double minZ = bb3d.min(Z) - po.get_elevation(); double maxZ = bb3d.max(Z); auto minZf = float(minZ); coord_t minZs = scaled(minZ); coord_t maxZs = scaled(maxZ); po.m_slice_index.clear(); size_t cap = size_t(1 + (maxZs - minZs - ilhs) / lhs); po.m_slice_index.reserve(cap); po.m_slice_index.emplace_back(minZs + ilhs, minZf + ilh / 2.f, ilh); for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs) po.m_slice_index.emplace_back(h, unscaled(h) - lh / 2.f, lh); // Just get the first record that is from the model: auto slindex_it = po.closest_slice_record(po.m_slice_index, float(bb3d.min(Z))); if(slindex_it == po.m_slice_index.end()) //TRN To be shown at the status bar on SLA slicing error. throw Slic3r::RuntimeError( L("Slicing had to be stopped due to an internal error: " "Inconsistent slice index.")); po.m_model_height_levels.clear(); po.m_model_height_levels.reserve(po.m_slice_index.size()); for(auto it = slindex_it; it != po.m_slice_index.end(); ++it) po.m_model_height_levels.emplace_back(it->slice_level()); po.m_model_slices.clear(); MeshSlicingParamsEx params; params.closing_radius = float(po.config().slice_closing_radius.value); switch (po.config().slicing_mode.value) { case SlicingMode::Regular: params.mode = MeshSlicingParams::SlicingMode::Regular; break; case SlicingMode::EvenOdd: params.mode = MeshSlicingParams::SlicingMode::EvenOdd; break; case SlicingMode::CloseHoles: params.mode = MeshSlicingParams::SlicingMode::Positive; break; } auto thr = [this]() { m_print->throw_if_canceled(); }; auto &slice_grid = po.m_model_height_levels; po.m_model_slices = slice_csgmesh_ex(po.mesh_to_slice(), slice_grid, params, thr); auto mit = slindex_it; for (size_t id = 0; id < po.m_model_slices.size() && mit != po.m_slice_index.end(); id++) { mit->set_model_slice_idx(po, id); ++mit; } // We apply the printer correction offset here. apply_printer_corrections(po, soModel); // po.m_preview_meshes[slaposObjectSlice] = po.get_mesh_to_print(); // report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW); } struct SuppPtMask { const std::vector &blockers; const std::vector &enforcers; bool enforcers_only = false; }; static void filter_support_points_by_modifiers(sla::SupportPoints &pts, const SuppPtMask &mask, const std::vector &slice_grid) { assert((mask.blockers.empty() || mask.blockers.size() == slice_grid.size()) && (mask.enforcers.empty() || mask.enforcers.size() == slice_grid.size())); auto new_pts = reserve_vector(pts.size()); for (size_t i = 0; i < pts.size(); ++i) { const sla::SupportPoint &sp = pts[i]; Point sp2d = scaled(to_2d(sp.pos)); auto it = std::lower_bound(slice_grid.begin(), slice_grid.end(), sp.pos.z()); if (it != slice_grid.end()) { size_t idx = std::distance(slice_grid.begin(), it); bool is_enforced = false; if (idx < mask.enforcers.size()) { for (size_t enf_idx = 0; !is_enforced && enf_idx < mask.enforcers[idx].size(); ++enf_idx) { if (mask.enforcers[idx][enf_idx].contains(sp2d)) is_enforced = true; } } bool is_blocked = false; if (!is_enforced) { if (!mask.enforcers_only) { if (idx < mask.blockers.size()) { for (size_t blk_idx = 0; !is_blocked && blk_idx < mask.blockers[idx].size(); ++blk_idx) { if (mask.blockers[idx][blk_idx].contains(sp2d)) is_blocked = true; } } } else { is_blocked = true; } } if (!is_blocked) new_pts.emplace_back(sp); } } pts.swap(new_pts); } // In this step we check the slices, identify island and cover them with // support points. Then we sprinkle the rest of the mesh. void SLAPrint::Steps::support_points(SLAPrintObject &po) { // If supports are disabled, we can skip the model scan. if(!po.m_config.supports_enable.getBool()) return; if (!po.m_supportdata) { auto &meshp = po.get_mesh_to_print(); assert(meshp); po.m_supportdata = std::make_unique(*meshp); } po.m_supportdata->input.zoffset = csgmesh_positive_bb(po.m_mesh_to_slice) .min.z(); const ModelObject& mo = *po.m_model_object; BOOST_LOG_TRIVIAL(debug) << "Support point count " << mo.sla_support_points.size(); // Unless the user modified the points or we already did the calculation, // we will do the autoplacement. Otherwise we will just blindly copy the // frontend data into the backend cache. if (mo.sla_points_status != sla::PointsStatus::UserModified) { // calculate heights of slices (slices are calculated already) const std::vector& heights = po.m_model_height_levels; throw_if_canceled(); sla::SupportPointGenerator::Config config; const SLAPrintObjectConfig& cfg = po.config(); // the density config value is in percents: config.density_relative = float(cfg.support_points_density_relative / 100.f); config.minimal_distance = float(cfg.support_points_minimal_distance); config.head_diameter = float(cfg.support_head_front_diameter); // scaling for the sub operations double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0; double init = current_status(); auto statuscb = [this, d, init](unsigned st) { double current = init + st * d; if(std::round(current_status()) < std::round(current)) report_status(current, OBJ_STEP_LABELS(slaposSupportPoints)); }; // Construction of this object does the calculation. throw_if_canceled(); sla::SupportPointGenerator auto_supports( po.m_supportdata->input.emesh, po.get_model_slices(), heights, config, [this]() { throw_if_canceled(); }, statuscb); // Now let's extract the result. std::vector& points = auto_supports.output(); throw_if_canceled(); MeshSlicingParamsEx params; params.closing_radius = float(po.config().slice_closing_radius.value); std::vector blockers = slice_volumes(po.model_object()->volumes, po.m_model_height_levels, po.trafo(), params, [](const ModelVolume *vol) { return vol->is_support_blocker(); }); std::vector enforcers = slice_volumes(po.model_object()->volumes, po.m_model_height_levels, po.trafo(), params, [](const ModelVolume *vol) { return vol->is_support_enforcer(); }); SuppPtMask mask{blockers, enforcers, po.config().support_enforcers_only.getBool()}; filter_support_points_by_modifiers(points, mask, po.m_model_height_levels); po.m_supportdata->input.pts = points; BOOST_LOG_TRIVIAL(debug) << "Automatic support points: " << po.m_supportdata->input.pts.size(); // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass // the update status to GLGizmoSlaSupports report_status(-1, L("Generating support points"), SlicingStatus::RELOAD_SLA_SUPPORT_POINTS); } else { // There are either some points on the front-end, or the user // removed them on purpose. No calculation will be done. po.m_supportdata->input.pts = po.transformed_support_points(); } } void SLAPrint::Steps::support_tree(SLAPrintObject &po) { if(!po.m_supportdata) return; // If the zero elevation mode is engaged, we have to filter out all the // points that are on the bottom of the object if (is_zero_elevation(po.config())) { remove_bottom_points(po.m_supportdata->input.pts, float( po.m_supportdata->input.zoffset + EPSILON)); } po.m_supportdata->input.cfg = make_support_cfg(po.m_config); po.m_supportdata->input.pad_cfg = make_pad_cfg(po.m_config); // scaling for the sub operations double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0; double init = current_status(); sla::JobController ctl; ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) { double current = init + st * d; if (std::round(current_status()) < std::round(current)) report_status(current, OBJ_STEP_LABELS(slaposSupportTree), SlicingStatus::DEFAULT, logmsg); }; ctl.stopcondition = [this]() { return canceled(); }; ctl.cancelfn = [this]() { throw_if_canceled(); }; po.m_supportdata->create_support_tree(ctl); if (!po.m_config.supports_enable.getBool()) return; throw_if_canceled(); // Create the unified mesh auto rc = SlicingStatus::RELOAD_SCENE; // This is to prevent "Done." being displayed during merged_mesh() report_status(-1, L("Visualizing supports")); BOOST_LOG_TRIVIAL(debug) << "Processed support point count " << po.m_supportdata->input.pts.size(); // Check the mesh for later troubleshooting. if(po.support_mesh().empty()) BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty"; report_status(-1, L("Visualizing supports"), rc); } void SLAPrint::Steps::generate_pad(SLAPrintObject &po) { // this step can only go after the support tree has been created // and before the supports had been sliced. (or the slicing has to be // repeated) if(po.m_config.pad_enable.getBool()) { if (!po.m_supportdata) { auto &meshp = po.get_mesh_to_print(); assert(meshp); po.m_supportdata = std::make_unique(*meshp); } // Get the distilled pad configuration from the config // (Again, despite it was retrieved in the previous step. Note that // on a param change event, the previous step might not be executed // depending on the specific parameter that has changed). sla::PadConfig pcfg = make_pad_cfg(po.m_config); po.m_supportdata->input.pad_cfg = pcfg; sla::JobController ctl; ctl.stopcondition = [this]() { return canceled(); }; ctl.cancelfn = [this]() { throw_if_canceled(); }; po.m_supportdata->create_pad(ctl); if (!validate_pad(po.m_supportdata->pad_mesh.its, pcfg)) throw Slic3r::SlicingError( L("No pad can be generated for this model with the " "current configuration")); } else if(po.m_supportdata) { po.m_supportdata->pad_mesh = {}; } throw_if_canceled(); report_status(-1, L("Visualizing supports"), SlicingStatus::RELOAD_SCENE); } // Slicing the support geometries similarly to the model slicing procedure. // If the pad had been added previously (see step "base_pool" than it will // be part of the slices) void SLAPrint::Steps::slice_supports(SLAPrintObject &po) { auto& sd = po.m_supportdata; if(sd) sd->support_slices.clear(); // Don't bother if no supports and no pad is present. if (!po.m_config.supports_enable.getBool() && !po.m_config.pad_enable.getBool()) return; if(sd) { auto heights = reserve_vector(po.m_slice_index.size()); for(auto& rec : po.m_slice_index) heights.emplace_back(rec.slice_level()); sla::JobController ctl; ctl.stopcondition = [this]() { return canceled(); }; ctl.cancelfn = [this]() { throw_if_canceled(); }; sd->support_slices = sla::slice(sd->tree_mesh.its, sd->pad_mesh.its, heights, float(po.config().slice_closing_radius.value), ctl); } for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i) po.m_slice_index[i].set_support_slice_idx(po, i); apply_printer_corrections(po, soSupport); // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update // status to the 3D preview to load the SLA slices. report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW); } // get polygons for all instances in the object static ExPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o) { if (!record.print_obj()) return {}; ExPolygons polygons; auto &input_polygons = record.get_slice(o); auto &instances = record.print_obj()->instances(); bool is_lefthanded = record.print_obj()->is_left_handed(); polygons.reserve(input_polygons.size() * instances.size()); for (const ExPolygon& polygon : input_polygons) { if(polygon.contour.empty()) continue; for (size_t i = 0; i < instances.size(); ++i) { ExPolygon poly; // We need to reverse if is_lefthanded is true but bool needreverse = is_lefthanded; // should be a move poly.contour.points.reserve(polygon.contour.size() + 1); auto& cntr = polygon.contour.points; if(needreverse) for(auto it = cntr.rbegin(); it != cntr.rend(); ++it) poly.contour.points.emplace_back(it->x(), it->y()); else for(auto& p : cntr) poly.contour.points.emplace_back(p.x(), p.y()); for(auto& h : polygon.holes) { poly.holes.emplace_back(); auto& hole = poly.holes.back(); hole.points.reserve(h.points.size() + 1); if(needreverse) for(auto it = h.points.rbegin(); it != h.points.rend(); ++it) hole.points.emplace_back(it->x(), it->y()); else for(auto& p : h.points) hole.points.emplace_back(p.x(), p.y()); } if(is_lefthanded) { for(auto& p : poly.contour) p.x() = -p.x(); for(auto& h : poly.holes) for(auto& p : h) p.x() = -p.x(); } poly.rotate(double(instances[i].rotation)); poly.translate(Point{instances[i].shift.x(), instances[i].shift.y()}); polygons.emplace_back(std::move(poly)); } } return polygons; } void SLAPrint::Steps::initialize_printer_input() { auto &printer_input = m_print->m_printer_input; // clear the rasterizer input printer_input.clear(); size_t mx = 0; for(SLAPrintObject * o : m_print->m_objects) { if(auto m = o->get_slice_index().size() > mx) mx = m; } printer_input.reserve(mx); auto eps = coord_t(SCALED_EPSILON); for(SLAPrintObject * o : m_print->m_objects) { coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs; for(const SliceRecord& slicerecord : o->get_slice_index()) { if (!slicerecord.is_valid()) throw Slic3r::SlicingError( L("There are unprintable objects. Try to " "adjust support settings to make the " "objects printable.")); coord_t lvlid = slicerecord.print_level() - gndlvl; // Neat trick to round the layer levels to the grid. lvlid = eps * (lvlid / eps); auto it = std::lower_bound(printer_input.begin(), printer_input.end(), PrintLayer(lvlid)); if(it == printer_input.end() || it->level() != lvlid) it = printer_input.insert(it, PrintLayer(lvlid)); it->add(slicerecord); } } } // Merging the slices from all the print objects into one slice grid and // calculating print statistics from the merge result. void SLAPrint::Steps::merge_slices_and_eval_stats() { initialize_printer_input(); auto &print_statistics = m_print->m_print_statistics; auto &printer_config = m_print->m_printer_config; auto &material_config = m_print->m_material_config; auto &printer_input = m_print->m_printer_input; print_statistics.clear(); const double area_fill = printer_config.area_fill.getFloat()*0.01;// 0.5 (50%); const double fast_tilt = printer_config.fast_tilt_time.getFloat();// 5.0; const double slow_tilt = printer_config.slow_tilt_time.getFloat();// 8.0; const double hv_tilt = printer_config.high_viscosity_tilt_time.getFloat();// 10.0; const double init_exp_time = material_config.initial_exposure_time.getFloat(); const double exp_time = material_config.exposure_time.getFloat(); const int fade_layers_cnt = m_print->m_default_object_config.faded_layers.getInt();// 10 // [3;20] const auto width = scaled(printer_config.display_width.getFloat()); const auto height = scaled(printer_config.display_height.getFloat()); const double display_area = width*height; double supports_volume(0.0); double models_volume(0.0); double estim_time(0.0); std::vector layers_times; layers_times.reserve(printer_input.size()); size_t slow_layers = 0; size_t fast_layers = 0; const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1); double fade_layer_time = init_exp_time; execution::SpinningMutex mutex; using Lock = std::lock_guard; // Going to parallel: auto printlayerfn = [this, // functions and read only vars area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, hv_tilt, material_config, delta_fade_time, // write vars &mutex, &models_volume, &supports_volume, &estim_time, &slow_layers, &fast_layers, &fade_layer_time, &layers_times](size_t sliced_layer_cnt) { PrintLayer &layer = m_print->m_printer_input[sliced_layer_cnt]; // vector of slice record references auto& slicerecord_references = layer.slices(); if(slicerecord_references.empty()) return; // Layer height should match for all object slices for a given level. const auto l_height = double(slicerecord_references.front().get().layer_height()); // Calculation of the consumed material ExPolygons model_polygons; ExPolygons supports_polygons; size_t c = std::accumulate(layer.slices().begin(), layer.slices().end(), size_t(0), [](size_t a, const SliceRecord &sr) { return a + sr.get_slice(soModel).size(); }); model_polygons.reserve(c); c = std::accumulate(layer.slices().begin(), layer.slices().end(), size_t(0), [](size_t a, const SliceRecord &sr) { return a + sr.get_slice(soSupport).size(); }); supports_polygons.reserve(c); for(const SliceRecord& record : layer.slices()) { ExPolygons modelslices = get_all_polygons(record, soModel); for(ExPolygon& p_tmp : modelslices) model_polygons.emplace_back(std::move(p_tmp)); ExPolygons supportslices = get_all_polygons(record, soSupport); for(ExPolygon& p_tmp : supportslices) supports_polygons.emplace_back(std::move(p_tmp)); } model_polygons = union_ex(model_polygons); double layer_model_area = 0; for (const ExPolygon& polygon : model_polygons) layer_model_area += area(polygon); if (layer_model_area < 0 || layer_model_area > 0) { Lock lck(mutex); models_volume += layer_model_area * l_height; } if(!supports_polygons.empty()) { if(model_polygons.empty()) supports_polygons = union_ex(supports_polygons); else supports_polygons = diff_ex(supports_polygons, model_polygons); // allegedly, union of subject is done withing the diff according to the pftPositive polyFillType } double layer_support_area = 0; for (const ExPolygon& polygon : supports_polygons) layer_support_area += area(polygon); if (layer_support_area < 0 || layer_support_area > 0) { Lock lck(mutex); supports_volume += layer_support_area * l_height; } // Here we can save the expensively calculated polygons for printing ExPolygons trslices; trslices.reserve(model_polygons.size() + supports_polygons.size()); for(ExPolygon& poly : model_polygons) trslices.emplace_back(std::move(poly)); for(ExPolygon& poly : supports_polygons) trslices.emplace_back(std::move(poly)); layer.transformed_slices(union_ex(trslices)); // Calculation of the slow and fast layers to the future controlling those values on FW const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill; const double tilt_time = material_config.material_print_speed == slamsSlow ? slow_tilt : material_config.material_print_speed == slamsHighViscosity ? hv_tilt : is_fast_layer ? fast_tilt : slow_tilt; { Lock lck(mutex); if (is_fast_layer) fast_layers++; else slow_layers++; // Calculation of the printing time double layer_times = 0.0; if (sliced_layer_cnt < 3) layer_times += init_exp_time; else if (fade_layer_time > exp_time) { fade_layer_time -= delta_fade_time; layer_times += fade_layer_time; } else layer_times += exp_time; layer_times += tilt_time; //// Per layer times (magical constants cuclulated from FW) static double exposure_safe_delay_before{ 3.0 }; static double exposure_high_viscosity_delay_before{ 3.5 }; static double exposure_slow_move_delay_before{ 1.0 }; if (material_config.material_print_speed == slamsSlow) layer_times += exposure_safe_delay_before; else if (material_config.material_print_speed == slamsHighViscosity) layer_times += exposure_high_viscosity_delay_before; else if (!is_fast_layer) layer_times += exposure_slow_move_delay_before; // Increase layer time for "magic constants" from FW layer_times += ( l_height * 5 // tower move + 120 / 1000 // Magical constant to compensate remaining computation delay in exposure thread ); layers_times.push_back(layer_times); estim_time += layer_times; } }; // sequential version for debugging: // for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i); execution::for_each(ex_tbb, size_t(0), printer_input.size(), printlayerfn, execution::max_concurrency(ex_tbb)); auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR; print_statistics.support_used_material = supports_volume * SCALING2; print_statistics.objects_used_material = models_volume * SCALING2; // Estimated printing time // A layers count o the highest object if (printer_input.size() == 0) print_statistics.estimated_print_time = NaNd; else { print_statistics.estimated_print_time = estim_time; print_statistics.layers_times = layers_times; } print_statistics.fast_layers_count = fast_layers; print_statistics.slow_layers_count = slow_layers; report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW); } // Rasterizing the model objects, and their supports void SLAPrint::Steps::rasterize() { if(canceled() || !m_print->m_archiver) return; // coefficient to map the rasterization state (0-99) to the allocated // portion (slot) of the process state double sd = (100 - max_objstatus) / 100.0; // slot is the portion of 100% that is realted to rasterization unsigned slot = PRINT_STEP_LEVELS[slapsRasterize]; // pst: previous state double pst = current_status(); double increment = (slot * sd) / m_print->m_printer_input.size(); double dstatus = current_status(); execution::SpinningMutex slck; // procedure to process one height level. This will run in parallel auto lvlfn = [this, &slck, increment, &dstatus, &pst] (sla::RasterBase& raster, size_t idx) { PrintLayer& printlayer = m_print->m_printer_input[idx]; if(canceled()) return; for (const ExPolygon& poly : printlayer.transformed_slices()) raster.draw(poly); // Status indication guarded with the spinlock { std::lock_guard lck(slck); dstatus += increment; double st = std::round(dstatus); if(st > pst) { report_status(st, PRINT_STEP_LABELS(slapsRasterize)); pst = st; } } }; // last minute escape if(canceled()) return; // Print all the layers in parallel m_print->m_archiver->draw_layers(m_print->m_printer_input.size(), lvlfn, [this]() { return canceled(); }, ex_tbb); } std::string SLAPrint::Steps::label(SLAPrintObjectStep step) { return OBJ_STEP_LABELS(step); } std::string SLAPrint::Steps::label(SLAPrintStep step) { return PRINT_STEP_LABELS(step); } double SLAPrint::Steps::progressrange(SLAPrintObjectStep step) const { return OBJ_STEP_LEVELS[step] * objectstep_scale; } double SLAPrint::Steps::progressrange(SLAPrintStep step) const { return PRINT_STEP_LEVELS[step] * (100 - max_objstatus) / 100.0; } void SLAPrint::Steps::execute(SLAPrintObjectStep step, SLAPrintObject &obj) { switch(step) { case slaposAssembly: mesh_assembly(obj); break; case slaposHollowing: hollow_model(obj); break; case slaposDrillHoles: drill_holes(obj); break; case slaposObjectSlice: slice_model(obj); break; case slaposSupportPoints: support_points(obj); break; case slaposSupportTree: support_tree(obj); break; case slaposPad: generate_pad(obj); break; case slaposSliceSupports: slice_supports(obj); break; case slaposCount: assert(false); } } void SLAPrint::Steps::execute(SLAPrintStep step) { switch (step) { case slapsMergeSlicesAndEval: merge_slices_and_eval_stats(); break; case slapsRasterize: rasterize(); break; case slapsCount: assert(false); } } }