From dfa4a58dc695a692adf987af5949d1ed3cb70d88 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 12 Nov 2019 10:28:00 +0100 Subject: [PATCH 1/2] Bump up C++ to 14 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f7829fc9a..4d23556a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,7 +102,7 @@ list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules/) enable_testing () # Enable C++11 language standard. -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) if(NOT WIN32) From 4e067c42f03bac019066849401fb42f70cacda87 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 12 Nov 2019 16:53:47 +0100 Subject: [PATCH 2/2] SLAPrint steps moved to separate module. * Lambdas replaced with class methods --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/SLA/Common.cpp | 235 +++---- src/libslic3r/SLA/Hollowing.cpp | 26 +- src/libslic3r/SLA/Hollowing.hpp | 21 +- src/libslic3r/SLA/RasterWriter.cpp | 2 + src/libslic3r/SLA/RasterWriter.hpp | 8 +- src/libslic3r/SLAPrint.cpp | 1017 +++------------------------- src/libslic3r/SLAPrint.hpp | 50 +- src/libslic3r/SLAPrintSteps.cpp | 848 +++++++++++++++++++++++ src/libslic3r/SLAPrintSteps.hpp | 68 ++ src/slic3r/GUI/Plater.cpp | 7 +- tests/libslic3r/test_hollowing.cpp | 6 +- 12 files changed, 1214 insertions(+), 1076 deletions(-) create mode 100644 src/libslic3r/SLAPrintSteps.cpp create mode 100644 src/libslic3r/SLAPrintSteps.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index f6934ac8e..0d483d997 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -150,6 +150,8 @@ add_library(libslic3r STATIC ShortestPath.cpp ShortestPath.hpp SLAPrint.cpp + SLAPrintSteps.cpp + SLAPrintSteps.hpp SLAPrint.hpp Slicing.cpp Slicing.hpp diff --git a/src/libslic3r/SLA/Common.cpp b/src/libslic3r/SLA/Common.cpp index f85731603..caabdd755 100644 --- a/src/libslic3r/SLA/Common.cpp +++ b/src/libslic3r/SLA/Common.cpp @@ -1,11 +1,13 @@ #include #include +#include #include #include #include #include #include + // Workaround: IGL signed_distance.h will define PI in the igl namespace. #undef PI @@ -351,128 +353,131 @@ PointSet normals(const PointSet& points, std::function thr, // throw on cancel const std::vector& pt_indices) { - if(points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0) + if (points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0) return {}; - + std::vector range = pt_indices; - if(range.empty()) { + if (range.empty()) { range.resize(size_t(points.rows()), 0); std::iota(range.begin(), range.end(), 0); } - - PointSet ret(range.size(), 3); - + + PointSet ret(range.size(), 3); + // for (size_t ridx = 0; ridx < range.size(); ++ridx) - tbb::parallel_for(size_t(0), range.size(), - [&ret, &range, &mesh, &points, thr, eps](size_t ridx) - { - thr(); - auto eidx = Eigen::Index(range[ridx]); - int faceid = 0; - Vec3d p; - - mesh.squared_distance(points.row(eidx), faceid, p); - - auto trindex = mesh.F().row(faceid); - - const Vec3d& p1 = mesh.V().row(trindex(0)); - const Vec3d& p2 = mesh.V().row(trindex(1)); - const Vec3d& p3 = mesh.V().row(trindex(2)); - - // We should check if the point lies on an edge of the hosting triangle. - // If it does then all the other triangles using the same two points - // have to be searched and the final normal should be some kind of - // aggregation of the participating triangle normals. We should also - // consider the cases where the support point lies right on a vertex - // of its triangle. The procedure is the same, get the neighbor - // triangles and calculate an average normal. - - // mark the vertex indices of the edge. ia and ib marks and edge ic - // will mark a single vertex. - int ia = -1, ib = -1, ic = -1; - - if(std::abs(distance(p, p1)) < eps) { - ic = trindex(0); - } - else if(std::abs(distance(p, p2)) < eps) { - ic = trindex(1); - } - else if(std::abs(distance(p, p3)) < eps) { - ic = trindex(2); - } - else if(point_on_edge(p, p1, p2, eps)) { - ia = trindex(0); ib = trindex(1); - } - else if(point_on_edge(p, p2, p3, eps)) { - ia = trindex(1); ib = trindex(2); - } - else if(point_on_edge(p, p1, p3, eps)) { - ia = trindex(0); ib = trindex(2); - } - - // vector for the neigboring triangles including the detected one. - std::vector neigh; - if(ic >= 0) { // The point is right on a vertex of the triangle - for(int n = 0; n < mesh.F().rows(); ++n) { - thr(); - Vec3i ni = mesh.F().row(n); - if((ni(X) == ic || ni(Y) == ic || ni(Z) == ic)) - neigh.emplace_back(ni); - } - } - else if(ia >= 0 && ib >= 0) { // the point is on and edge - // now get all the neigboring triangles - for(int n = 0; n < mesh.F().rows(); ++n) { - thr(); - Vec3i ni = mesh.F().row(n); - if((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) && - (ni(X) == ib || ni(Y) == ib || ni(Z) == ib)) - neigh.emplace_back(ni); - } - } - - // Calculate the normals for the neighboring triangles - std::vector neighnorms; neighnorms.reserve(neigh.size()); - for(const Vec3i& tri : neigh) { - const Vec3d& pt1 = mesh.V().row(tri(0)); - const Vec3d& pt2 = mesh.V().row(tri(1)); - const Vec3d& pt3 = mesh.V().row(tri(2)); - Eigen::Vector3d U = pt2 - pt1; - Eigen::Vector3d V = pt3 - pt1; - neighnorms.emplace_back(U.cross(V).normalized()); - } - - // Throw out duplicates. They would cause trouble with summing. We will - // use std::unique which works on sorted ranges. We will sort by the - // coefficient-wise sum of the normals. It should force the same - // elements to be consecutive. - std::sort(neighnorms.begin(), neighnorms.end(), - [](const Vec3d& v1, const Vec3d& v2){ - return v1.sum() < v2.sum(); - }); - - auto lend = std::unique(neighnorms.begin(), neighnorms.end(), - [](const Vec3d& n1, const Vec3d& n2) { - // Compare normals for equivalence. This is controvers stuff. - auto deq = [](double a, double b) { return std::abs(a-b) < 1e-3; }; - return deq(n1(X), n2(X)) && deq(n1(Y), n2(Y)) && deq(n1(Z), n2(Z)); - }); - - if(!neighnorms.empty()) { // there were neighbors to count with - // sum up the normals and then normalize the result again. - // This unification seems to be enough. - Vec3d sumnorm(0, 0, 0); - sumnorm = std::accumulate(neighnorms.begin(), lend, sumnorm); - sumnorm.normalize(); - ret.row(long(ridx)) = sumnorm; - } - else { // point lies safely within its triangle - Eigen::Vector3d U = p2 - p1; - Eigen::Vector3d V = p3 - p1; - ret.row(long(ridx)) = U.cross(V).normalized(); - } + ccr::enumerate( + range.begin(), range.end(), + [&ret, &mesh, &points, thr, eps](unsigned el, size_t ridx) { + thr(); + auto eidx = Eigen::Index(el); + int faceid = 0; + Vec3d p; + + mesh.squared_distance(points.row(eidx), faceid, p); + + auto trindex = mesh.F().row(faceid); + + const Vec3d &p1 = mesh.V().row(trindex(0)); + const Vec3d &p2 = mesh.V().row(trindex(1)); + const Vec3d &p3 = mesh.V().row(trindex(2)); + + // We should check if the point lies on an edge of the hosting + // triangle. If it does then all the other triangles using the + // same two points have to be searched and the final normal should + // be some kind of aggregation of the participating triangle + // normals. We should also consider the cases where the support + // point lies right on a vertex of its triangle. The procedure is + // the same, get the neighbor triangles and calculate an average + // normal. + + // mark the vertex indices of the edge. ia and ib marks and edge + // ic will mark a single vertex. + int ia = -1, ib = -1, ic = -1; + + if (std::abs(distance(p, p1)) < eps) { + ic = trindex(0); + } else if (std::abs(distance(p, p2)) < eps) { + ic = trindex(1); + } else if (std::abs(distance(p, p3)) < eps) { + ic = trindex(2); + } else if (point_on_edge(p, p1, p2, eps)) { + ia = trindex(0); + ib = trindex(1); + } else if (point_on_edge(p, p2, p3, eps)) { + ia = trindex(1); + ib = trindex(2); + } else if (point_on_edge(p, p1, p3, eps)) { + ia = trindex(0); + ib = trindex(2); + } + + // vector for the neigboring triangles including the detected one. + std::vector neigh; + if (ic >= 0) { // The point is right on a vertex of the triangle + for (int n = 0; n < mesh.F().rows(); ++n) { + thr(); + Vec3i ni = mesh.F().row(n); + if ((ni(X) == ic || ni(Y) == ic || ni(Z) == ic)) + neigh.emplace_back(ni); + } + } else if (ia >= 0 && ib >= 0) { // the point is on and edge + // now get all the neigboring triangles + for (int n = 0; n < mesh.F().rows(); ++n) { + thr(); + Vec3i ni = mesh.F().row(n); + if ((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) && + (ni(X) == ib || ni(Y) == ib || ni(Z) == ib)) + neigh.emplace_back(ni); + } + } + + // Calculate the normals for the neighboring triangles + std::vector neighnorms; + neighnorms.reserve(neigh.size()); + for (const Vec3i &tri : neigh) { + const Vec3d & pt1 = mesh.V().row(tri(0)); + const Vec3d & pt2 = mesh.V().row(tri(1)); + const Vec3d & pt3 = mesh.V().row(tri(2)); + Eigen::Vector3d U = pt2 - pt1; + Eigen::Vector3d V = pt3 - pt1; + neighnorms.emplace_back(U.cross(V).normalized()); + } + + // Throw out duplicates. They would cause trouble with summing. We + // will use std::unique which works on sorted ranges. We will sort + // by the coefficient-wise sum of the normals. It should force the + // same elements to be consecutive. + std::sort(neighnorms.begin(), neighnorms.end(), + [](const Vec3d &v1, const Vec3d &v2) { + return v1.sum() < v2.sum(); }); - + + auto lend = std::unique(neighnorms.begin(), neighnorms.end(), + [](const Vec3d &n1, const Vec3d &n2) { + // Compare normals for equivalence. + // This is controvers stuff. + auto deq = [](double a, double b) { + return std::abs(a - b) < 1e-3; + }; + return deq(n1(X), n2(X)) && + deq(n1(Y), n2(Y)) && + deq(n1(Z), n2(Z)); + }); + + if (!neighnorms.empty()) { // there were neighbors to count with + // sum up the normals and then normalize the result again. + // This unification seems to be enough. + Vec3d sumnorm(0, 0, 0); + sumnorm = std::accumulate(neighnorms.begin(), lend, sumnorm); + sumnorm.normalize(); + ret.row(long(ridx)) = sumnorm; + } else { // point lies safely within its triangle + Eigen::Vector3d U = p2 - p1; + Eigen::Vector3d V = p3 - p1; + ret.row(long(ridx)) = U.cross(V).normalized(); + } + }); + return ret; } diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index b224bc98c..c3763dbc9 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -1,5 +1,7 @@ #include +#include +#include #include #include @@ -98,18 +100,30 @@ remove_cvref_t _generate_interior(Mesh &&mesh, return omesh; } -TriangleMesh generate_interior(const TriangleMesh &mesh, const HollowingConfig &hc, const JobController &ctl) +std::unique_ptr generate_interior(const TriangleMesh & mesh, + const HollowingConfig &hc, + const JobController & ctl) { static const double MAX_OVERSAMPL = 7.; - // I can't figure out how to increase the grid resolution through openvdb API - // so the model will be scaled up before conversion and the result scaled - // down. Voxels have a unit size. If I set voxelSize smaller, it scales - // the whole geometry down, and doesn't increase the number of voxels. + // I can't figure out how to increase the grid resolution through openvdb + // API so the model will be scaled up before conversion and the result + // scaled down. Voxels have a unit size. If I set voxelSize smaller, it + // scales the whole geometry down, and doesn't increase the number of + // voxels. // // max 8x upscale, min is native voxel size auto voxel_scale = (1.0 + MAX_OVERSAMPL * hc.quality); - return _generate_interior(mesh, ctl, hc.min_thickness, voxel_scale, hc.closing_distance); + return std::make_unique( + _generate_interior(mesh, ctl, hc.min_thickness, voxel_scale, + hc.closing_distance)); +} + +bool DrainHole::operator==(const DrainHole &sp) const +{ + return (m_pos == sp.m_pos) && (m_normal == sp.m_normal) && + is_approx(m_radius, sp.m_radius) && + is_approx(m_height, sp.m_height); } }} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp index 93a1f90fd..cb5c2bd15 100644 --- a/src/libslic3r/SLA/Hollowing.hpp +++ b/src/libslic3r/SLA/Hollowing.hpp @@ -1,10 +1,14 @@ #ifndef SLA_HOLLOWING_HPP #define SLA_HOLLOWING_HPP -#include +#include +#include #include namespace Slic3r { + +class TriangleMesh; + namespace sla { struct HollowingConfig @@ -33,24 +37,19 @@ struct DrainHole , m_height(height) {} - bool operator==(const DrainHole &sp) const - { - return (m_pos == sp.m_pos) && (m_normal == sp.m_normal) && - is_approx(m_radius, sp.m_radius) && - is_approx(m_height, sp.m_height); - } + bool operator==(const DrainHole &sp) const; bool operator!=(const DrainHole &sp) const { return !(sp == (*this)); } - template void serialize(Archive &ar) + template inline void serialize(Archive &ar) { ar(m_pos, m_normal, m_radius, m_height); } }; -TriangleMesh generate_interior(const TriangleMesh &mesh, - const HollowingConfig & = {}, - const JobController &ctl = {}); +std::unique_ptr generate_interior(const TriangleMesh &mesh, + const HollowingConfig & = {}, + const JobController &ctl = {}); } } diff --git a/src/libslic3r/SLA/RasterWriter.cpp b/src/libslic3r/SLA/RasterWriter.cpp index b3da0d2a5..0d55b769d 100644 --- a/src/libslic3r/SLA/RasterWriter.cpp +++ b/src/libslic3r/SLA/RasterWriter.cpp @@ -1,4 +1,6 @@ #include + +#include "libslic3r/PrintConfig.hpp" #include #include diff --git a/src/libslic3r/SLA/RasterWriter.hpp b/src/libslic3r/SLA/RasterWriter.hpp index a7792d55d..62ed44ca8 100644 --- a/src/libslic3r/SLA/RasterWriter.hpp +++ b/src/libslic3r/SLA/RasterWriter.hpp @@ -9,11 +9,13 @@ #include #include -#include "libslic3r/PrintConfig.hpp" - #include -namespace Slic3r { namespace sla { +namespace Slic3r { + +class DynamicPrintConfig; + +namespace sla { // API to write the zipped sla output layers and metadata. // Implementation uses PNG raster output. diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 05440ad52..53a0e8223 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1,8 +1,6 @@ #include "SLAPrint.hpp" -#include "SLA/SupportTree.hpp" -#include "SLA/Pad.hpp" -#include "SLA/SupportPointGenerator.hpp" -#include "SLA/Hollowing.hpp" +#include "SLAPrintSteps.hpp" + #include "ClipperUtils.hpp" #include "Geometry.hpp" #include "MTUtils.hpp" @@ -14,9 +12,6 @@ #include #include -// For geometry algorithms with native Clipper types (no copies and conversions) -#include - // #define SLAPRINT_DO_BENCHMARK #ifdef SLAPRINT_DO_BENCHMARK @@ -33,78 +28,86 @@ namespace Slic3r { -class SLAPrintObject::SupportData : public sla::SupportableMesh + +bool is_zero_elevation(const SLAPrintObjectConfig &c) { -public: - sla::SupportTree::UPtr support_tree_ptr; // the supports - std::vector support_slices; // sliced supports + return c.pad_enable.getBool() && c.pad_around_object.getBool(); +} + +// Compile the argument for support creation from the static print config. +sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) +{ + sla::SupportConfig scfg; - inline SupportData(const TriangleMesh &t): sla::SupportableMesh{t, {}, {}} {} + scfg.enabled = c.supports_enable.getBool(); + scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat(); + scfg.head_back_radius_mm = 0.5*c.support_pillar_diameter.getFloat(); + scfg.head_penetration_mm = c.support_head_penetration.getFloat(); + scfg.head_width_mm = c.support_head_width.getFloat(); + scfg.object_elevation_mm = is_zero_elevation(c) ? + 0. : c.support_object_elevation.getFloat(); + scfg.bridge_slope = c.support_critical_angle.getFloat() * PI / 180.0 ; + scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat(); + scfg.max_pillar_link_distance_mm = c.support_max_pillar_link_distance.getFloat(); + switch(c.support_pillar_connection_mode.getInt()) { + case slapcmZigZag: + scfg.pillar_connection_mode = sla::PillarConnectionMode::zigzag; break; + case slapcmCross: + scfg.pillar_connection_mode = sla::PillarConnectionMode::cross; break; + case slapcmDynamic: + scfg.pillar_connection_mode = sla::PillarConnectionMode::dynamic; break; + } + scfg.ground_facing_only = c.support_buildplate_only.getBool(); + scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat(); + scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat(); + scfg.base_height_mm = c.support_base_height.getFloat(); + scfg.pillar_base_safety_distance_mm = + c.support_base_safety_distance.getFloat() < EPSILON ? + scfg.safety_distance_mm : c.support_base_safety_distance.getFloat(); - sla::SupportTree::UPtr &create_support_tree(const sla::JobController &ctl) - { - support_tree_ptr = sla::SupportTree::create(*this, ctl); - return support_tree_ptr; - } -}; + return scfg; +} -class SLAPrintObject::HollowingData +sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { -public: + sla::PadConfig::EmbedObject ret; - TriangleMesh interior; - // std::vector -}; - -namespace { - -// should add up to 100 (%) -const std::array OBJ_STEP_LEVELS = -{ - 5, // slaposHollowing, - 20, // slaposObjectSlice, - 5, // slaposDrillHolesIfHollowed - 20, // slaposSupportPoints, - 10, // slaposSupportTree, - 10, // slaposPad, - 30, // slaposSliceSupports, -}; - -// Object step to status label. The labels are localized at the time of calling, thus supporting language switching. -std::string OBJ_STEP_LABELS(size_t idx) -{ - switch (idx) { - case slaposHollowing: return L("Hollowing out the model"); - case slaposObjectSlice: return L("Slicing model"); - case slaposDrillHolesIfHollowed: return L("Drilling holes into hollowed 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:; + ret.enabled = is_zero_elevation(c); + + if(ret.enabled) { + ret.everywhere = c.pad_around_object_everywhere.getBool(); + ret.object_gap_mm = c.pad_object_gap.getFloat(); + ret.stick_width_mm = c.pad_object_connector_width.getFloat(); + ret.stick_stride_mm = c.pad_object_connector_stride.getFloat(); + ret.stick_penetration_mm = c.pad_object_connector_penetration + .getFloat(); } - assert(false); - return "Out of bounds!"; -}; + + return ret; +} -// Should also add up to 100 (%) -const std::array PRINT_STEP_LEVELS = +sla::PadConfig make_pad_cfg(const SLAPrintObjectConfig& c) { - 10, // slapsMergeSlicesAndEval - 90, // slapsRasterize -}; + sla::PadConfig pcfg; + + pcfg.wall_thickness_mm = c.pad_wall_thickness.getFloat(); + pcfg.wall_slope = c.pad_wall_slope.getFloat() * PI / 180.0; + + pcfg.max_merge_dist_mm = c.pad_max_merge_distance.getFloat(); + pcfg.wall_height_mm = c.pad_wall_height.getFloat(); + pcfg.brim_size_mm = c.pad_brim_size.getFloat(); + + // set builtin pad implicitly ON + pcfg.embed_object = builtin_pad_cfg(c); + + return pcfg; +} -// Print step to status label. The labels are localized at the time of calling, thus supporting language switching. -std::string PRINT_STEP_LABELS(size_t idx) +bool validate_pad(const TriangleMesh &pad, const sla::PadConfig &pcfg) { - switch (idx) { - case slapsMergeSlicesAndEval: return L("Merging slices and calculating statistics"); - case slapsRasterize: return L("Rasterizing layers"); - default:; - } - assert(false); return "Out of bounds!"; -}; - + // An empty pad can only be created if embed_object mode is enabled + // and the pad is not forced everywhere + return !pad.empty() || (pcfg.embed_object.enabled && !pcfg.embed_object.everywhere); } void SLAPrint::clear() @@ -590,87 +593,6 @@ std::string SLAPrint::output_filename(const std::string &filename_base) const return this->PrintBase::output_filename(m_print_config.output_filename_format.value, ".sl1", filename_base, &config); } -namespace { - -bool is_zero_elevation(const SLAPrintObjectConfig &c) { - return c.pad_enable.getBool() && c.pad_around_object.getBool(); -} - -// Compile the argument for support creation from the static print config. -sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { - sla::SupportConfig scfg; - - scfg.enabled = c.supports_enable.getBool(); - scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat(); - scfg.head_back_radius_mm = 0.5*c.support_pillar_diameter.getFloat(); - scfg.head_penetration_mm = c.support_head_penetration.getFloat(); - scfg.head_width_mm = c.support_head_width.getFloat(); - scfg.object_elevation_mm = is_zero_elevation(c) ? - 0. : c.support_object_elevation.getFloat(); - scfg.bridge_slope = c.support_critical_angle.getFloat() * PI / 180.0 ; - scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat(); - scfg.max_pillar_link_distance_mm = c.support_max_pillar_link_distance.getFloat(); - switch(c.support_pillar_connection_mode.getInt()) { - case slapcmZigZag: - scfg.pillar_connection_mode = sla::PillarConnectionMode::zigzag; break; - case slapcmCross: - scfg.pillar_connection_mode = sla::PillarConnectionMode::cross; break; - case slapcmDynamic: - scfg.pillar_connection_mode = sla::PillarConnectionMode::dynamic; break; - } - scfg.ground_facing_only = c.support_buildplate_only.getBool(); - scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat(); - scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat(); - scfg.base_height_mm = c.support_base_height.getFloat(); - scfg.pillar_base_safety_distance_mm = - c.support_base_safety_distance.getFloat() < EPSILON ? - scfg.safety_distance_mm : c.support_base_safety_distance.getFloat(); - - return scfg; -} - -sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { - sla::PadConfig::EmbedObject ret; - - ret.enabled = is_zero_elevation(c); - - if(ret.enabled) { - ret.everywhere = c.pad_around_object_everywhere.getBool(); - ret.object_gap_mm = c.pad_object_gap.getFloat(); - ret.stick_width_mm = c.pad_object_connector_width.getFloat(); - ret.stick_stride_mm = c.pad_object_connector_stride.getFloat(); - ret.stick_penetration_mm = c.pad_object_connector_penetration - .getFloat(); - } - - return ret; -} - -sla::PadConfig make_pad_cfg(const SLAPrintObjectConfig& c) { - sla::PadConfig pcfg; - - pcfg.wall_thickness_mm = c.pad_wall_thickness.getFloat(); - pcfg.wall_slope = c.pad_wall_slope.getFloat() * PI / 180.0; - - pcfg.max_merge_dist_mm = c.pad_max_merge_distance.getFloat(); - pcfg.wall_height_mm = c.pad_wall_height.getFloat(); - pcfg.brim_size_mm = c.pad_brim_size.getFloat(); - - // set builtin pad implicitly ON - pcfg.embed_object = builtin_pad_cfg(c); - - return pcfg; -} - -bool validate_pad(const TriangleMesh &pad, const sla::PadConfig &pcfg) -{ - // An empty pad can only be created if embed_object mode is enabled - // and the pad is not forced everywhere - return !pad.empty() || (pcfg.embed_object.enabled && !pcfg.embed_object.everywhere); -} - -} - std::string SLAPrint::validate() const { for(SLAPrintObject * po : m_objects) { @@ -740,775 +662,12 @@ bool SLAPrint::invalidate_step(SLAPrintStep step) void SLAPrint::process() { - using namespace sla; - using ExPolygon = Slic3r::ExPolygon; - if(m_objects.empty()) return; // Assumption: at this point the print objects should be populated only with // the model objects we have to process and the instances are also filtered - - // shortcut to initial layer height - double ilhd = m_material_config.initial_layer_height.getFloat(); - auto ilh = float(ilhd); - - coord_t ilhs = scaled(ilhd); - const size_t objcount = m_objects.size(); - - static const unsigned min_objstatus = 0; // where the per object operations start - static const unsigned max_objstatus = 50; // where the per object operations end - - // the coefficient that multiplies the per object status values which - // are set up for <0, 100>. They need to be scaled into the whole process - const double ostepd = (max_objstatus - min_objstatus) / (objcount * 100.0); - auto hollow_model = [](SLAPrintObject &po) { - - if (!po.m_config.hollowing_enable.getBool()) { - BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!"; - po.m_hollowing_data.reset(); - return; - } else { - BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!"; - } - - if (!po.m_hollowing_data) - po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); - - 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(); - po.m_hollowing_data->interior = - generate_interior(po.transformed_mesh(), {thickness, quality, closing_d}); - - if (po.m_hollowing_data->interior.empty()) - BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!"; - }; - - // 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). - - // Slicing the model object. This method is oversimplified and needs to - // be compared with the fff slicing algorithm for verification - auto slice_model = [this, ilhs, ilh](SLAPrintObject& po) { - - TriangleMesh hollowed_mesh; - - bool is_hollowing = po.m_config.hollowing_enable.getBool() && - po.m_hollowing_data; - - if (is_hollowing) { - hollowed_mesh = po.transformed_mesh(); - hollowed_mesh.merge(po.m_hollowing_data->interior); - hollowed_mesh.require_shared_vertices(); - } - - const TriangleMesh &mesh = is_hollowing ? hollowed_mesh : - po.transformed_mesh(); - - // We need to prepare the slice index... - - double lhd = m_objects.front()->m_config.layer_height.getFloat(); - float lh = float(lhd); - coord_t lhs = scaled(lhd); - auto && bb3d = mesh.bounding_box(); - 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 std::runtime_error( - 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()); - - TriangleMeshSlicer slicer(&mesh); - - po.m_model_slices.clear(); - slicer.slice(po.m_model_height_levels, - float(po.config().slice_closing_radius.value), - &po.m_model_slices, - [this](){ throw_if_canceled(); }); - - auto mit = slindex_it; - double doffs = m_printer_config.absolute_correction.getFloat(); - coord_t clpr_offs = scaled(doffs); - for(size_t id = 0; - id < po.m_model_slices.size() && mit != po.m_slice_index.end(); - id++) - { - // We apply the printer correction offset here. - if(clpr_offs != 0) - po.m_model_slices[id] = - offset_ex(po.m_model_slices[id], float(clpr_offs)); - - mit->set_model_slice_idx(po, id); ++mit; - } - - if(po.m_config.supports_enable.getBool() || - po.m_config.pad_enable.getBool()) - { - po.m_supportdata.reset( - new SLAPrintObject::SupportData(po.transformed_mesh()) ); - } - }; - - // In this step we check the slices, identify island and cover them with - // support points. Then we sprinkle the rest of the mesh. - auto support_points = [this, ostepd](SLAPrintObject& po) { - // If supports are disabled, we can skip the model scan. - if(!po.m_config.supports_enable.getBool()) return; - - if (!po.m_supportdata) - po.m_supportdata.reset( - new SLAPrintObject::SupportData(po.transformed_mesh())); - - 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) { - - // Hypothetical use of the slice index: - // auto bb = po.transformed_mesh().bounding_box(); - // auto range = po.get_slice_records(bb.min(Z)); - // std::vector heights; heights.reserve(range.size()); - // for(auto& record : range) heights.emplace_back(record.slice_level()); - - // calculate heights of slices (slices are calculated already) - const std::vector& heights = po.m_model_height_levels; - - this->throw_if_canceled(); - 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 = ostepd * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0; - double init = m_report_status.status(); - - auto statuscb = [this, d, init](unsigned st) - { - double current = init + st * d; - if(std::round(m_report_status.status()) < std::round(current)) - m_report_status(*this, current, - OBJ_STEP_LABELS(slaposSupportPoints)); - - }; - - // Construction of this object does the calculation. - this->throw_if_canceled(); - SupportPointGenerator auto_supports( - po.m_supportdata->emesh, po.get_model_slices(), heights, - config, [this]() { throw_if_canceled(); }, statuscb); - - // Now let's extract the result. - const std::vector& points = auto_supports.output(); - this->throw_if_canceled(); - po.m_supportdata->pts = points; - - BOOST_LOG_TRIVIAL(debug) << "Automatic support points: " - << po.m_supportdata->pts.size(); - - // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass - // the update status to GLGizmoSlaSupports - m_report_status(*this, - -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->pts = po.transformed_support_points(); - } - - // 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())) { - double tolerance = po.config().pad_enable.getBool() - ? po.m_config.pad_wall_thickness.getFloat() - : po.m_config.support_base_height.getFloat(); - - remove_bottom_points(po.m_supportdata->pts, - po.m_supportdata->emesh.ground_level(), - tolerance); - } - }; - - // In this step we create the supports - auto support_tree = [this, ostepd](SLAPrintObject& po) - { - if(!po.m_supportdata) return; - - sla::PadConfig pcfg = make_pad_cfg(po.m_config); - - if (pcfg.embed_object) - po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm); - - po.m_supportdata->cfg = make_support_cfg(po.m_config); - - // scaling for the sub operations - double d = ostepd * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0; - double init = m_report_status.status(); - JobController ctl; - - ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) { - double current = init + st * d; - if (std::round(m_report_status.status()) < std::round(current)) - m_report_status(*this, 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() - m_report_status(*this, -1, L("Visualizing supports")); - - BOOST_LOG_TRIVIAL(debug) << "Processed support point count " - << po.m_supportdata->pts.size(); - - // Check the mesh for later troubleshooting. - if(po.support_mesh().empty()) - BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty"; - - m_report_status(*this, -1, L("Visualizing supports"), rc); - }; - - // This step generates the sla base pad - auto generate_pad = [this](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()) - { - // Get the distilled pad configuration from the config - sla::PadConfig pcfg = make_pad_cfg(po.m_config); - - ExPolygons bp; // This will store the base plate of the pad. - double pad_h = pcfg.full_height(); - const TriangleMesh &trmesh = po.transformed_mesh(); - - // This call can get pretty time consuming - auto thrfn = [this](){ throw_if_canceled(); }; - - if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) { - // No support (thus no elevation) or zero elevation mode - // we sometimes call it "builtin pad" is enabled so we will - // get a sample from the bottom of the mesh and use it for pad - // creation. - sla::pad_blueprint(trmesh, bp, float(pad_h), - float(po.m_config.layer_height.getFloat()), - thrfn); - } - - po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg); - auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(MeshType::Pad); - - if (!validate_pad(pad_mesh, pcfg)) - throw std::runtime_error( - L("No pad can be generated for this model with the " - "current configuration")); - - } else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) { - po.m_supportdata->support_tree_ptr->remove_pad(); - } - - po.throw_if_canceled(); - auto rc = SlicingStatus::RELOAD_SCENE; - m_report_status(*this, -1, L("Visualizing supports"), rc); - }; - - // 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) - auto slice_supports = [this](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 && sd->support_tree_ptr) { - - std::vector heights; heights.reserve(po.m_slice_index.size()); - - for(auto& rec : po.m_slice_index) { - heights.emplace_back(rec.slice_level()); - } - - sd->support_slices = sd->support_tree_ptr->slice( - heights, float(po.config().slice_closing_radius.value)); - } - - double doffs = m_printer_config.absolute_correction.getFloat(); - coord_t clpr_offs = scaled(doffs); - for(size_t i = 0; - i < sd->support_slices.size() && i < po.m_slice_index.size(); - ++i) - { - // We apply the printer correction offset here. - if(clpr_offs != 0) - sd->support_slices[i] = - offset_ex(sd->support_slices[i], float(clpr_offs)); - - po.m_slice_index[i].set_support_slice_idx(po, i); - } - - // Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update - // status to the 3D preview to load the SLA slices. - m_report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW); - }; - - // Merging the slices from all the print objects into one slice grid and - // calculating print statistics from the merge result. - auto merge_slices_and_eval_stats = [this, ilhs]() { - - // clear the rasterizer input - m_printer_input.clear(); - - size_t mx = 0; - for(SLAPrintObject * o : m_objects) { - if(auto m = o->get_slice_index().size() > mx) mx = m; - } - - m_printer_input.reserve(mx); - - auto eps = coord_t(SCALED_EPSILON); - - for(SLAPrintObject * o : m_objects) { - coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs; - - for(const SliceRecord& slicerecord : o->get_slice_index()) { - 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(m_printer_input.begin(), - m_printer_input.end(), - PrintLayer(lvlid)); - - if(it == m_printer_input.end() || it->level() != lvlid) - it = m_printer_input.insert(it, PrintLayer(lvlid)); - - - it->add(slicerecord); - } - } - - m_print_statistics.clear(); - - using ClipperPoint = ClipperLib::IntPoint; - using ClipperPolygon = ClipperLib::Polygon; // see clipper_polygon.hpp in libnest2d - using ClipperPolygons = std::vector; - namespace sl = libnest2d::shapelike; // For algorithms - - // Set up custom union and diff functions for clipper polygons - auto polyunion = [] (const ClipperPolygons& subjects) - { - ClipperLib::Clipper clipper; - - bool closed = true; - - for(auto& path : subjects) { - clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); - clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed); - } - - auto mode = ClipperLib::pftPositive; - - return libnest2d::clipper_execute(clipper, ClipperLib::ctUnion, mode, mode); - }; - - auto polydiff = [](const ClipperPolygons& subjects, const ClipperPolygons& clips) - { - ClipperLib::Clipper clipper; - - bool closed = true; - - for(auto& path : subjects) { - clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); - clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed); - } - - for(auto& path : clips) { - clipper.AddPath(path.Contour, ClipperLib::ptClip, closed); - clipper.AddPaths(path.Holes, ClipperLib::ptClip, closed); - } - - auto mode = ClipperLib::pftPositive; - - return libnest2d::clipper_execute(clipper, ClipperLib::ctDifference, mode, mode); - }; - - // libnest calculates positive area for clockwise polygons, Slic3r is in counter-clockwise - auto areafn = [](const ClipperPolygon& poly) { return - sl::area(poly); }; - - const double area_fill = m_printer_config.area_fill.getFloat()*0.01;// 0.5 (50%); - const double fast_tilt = m_printer_config.fast_tilt_time.getFloat();// 5.0; - const double slow_tilt = m_printer_config.slow_tilt_time.getFloat();// 8.0; - - const double init_exp_time = m_material_config.initial_exposure_time.getFloat(); - const double exp_time = m_material_config.exposure_time.getFloat(); - - const int fade_layers_cnt = m_default_object_config.faded_layers.getInt();// 10 // [3;20] - - const auto width = scaled(m_printer_config.display_width.getFloat()); - const auto height = scaled(m_printer_config.display_height.getFloat()); - const double display_area = width*height; - - // get polygons for all instances in the object - auto get_all_polygons = - [](const ExPolygons& input_polygons, - const std::vector& instances, - bool is_lefthanded) - { - ClipperPolygons polygons; - 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) - { - ClipperPolygon poly; - - // We need to reverse if is_lefthanded is true but - bool needreverse = is_lefthanded; - - // should be a move - poly.Contour.reserve(polygon.contour.size() + 1); - - auto& cntr = polygon.contour.points; - if(needreverse) - for(auto it = cntr.rbegin(); it != cntr.rend(); ++it) - poly.Contour.emplace_back(it->x(), it->y()); - else - for(auto& p : cntr) - poly.Contour.emplace_back(p.x(), p.y()); - - for(auto& h : polygon.holes) { - poly.Holes.emplace_back(); - auto& hole = poly.Holes.back(); - hole.reserve(h.points.size() + 1); - - if(needreverse) - for(auto it = h.points.rbegin(); it != h.points.rend(); ++it) - hole.emplace_back(it->x(), it->y()); - else - for(auto& p : h.points) - hole.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; - } - - sl::rotate(poly, double(instances[i].rotation)); - sl::translate(poly, ClipperPoint{instances[i].shift(X), - instances[i].shift(Y)}); - - polygons.emplace_back(std::move(poly)); - } - } - return polygons; - }; - - double supports_volume(0.0); - double models_volume(0.0); - - double estim_time(0.0); - - 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; - - SpinMutex mutex; - using Lock = std::lock_guard; - - // Going to parallel: - auto printlayerfn = [this, - // functions and read only vars - get_all_polygons, polyunion, polydiff, areafn, - area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, delta_fade_time, - - // write vars - &mutex, &models_volume, &supports_volume, &estim_time, &slow_layers, - &fast_layers, &fade_layer_time](size_t sliced_layer_cnt) - { - PrintLayer& layer = 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 - - ClipperPolygons model_polygons; - ClipperPolygons 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(soModel).size(); - }); - - supports_polygons.reserve(c); - - for(const SliceRecord& record : layer.slices()) { - const SLAPrintObject *po = record.print_obj(); - - const ExPolygons &modelslices = record.get_slice(soModel); - - bool is_lefth = record.print_obj()->is_left_handed(); - if (!modelslices.empty()) { - ClipperPolygons v = get_all_polygons(modelslices, po->instances(), is_lefth); - for(ClipperPolygon& p_tmp : v) model_polygons.emplace_back(std::move(p_tmp)); - } - - const ExPolygons &supportslices = record.get_slice(soSupport); - - if (!supportslices.empty()) { - ClipperPolygons v = get_all_polygons(supportslices, po->instances(), is_lefth); - for(ClipperPolygon& p_tmp : v) supports_polygons.emplace_back(std::move(p_tmp)); - } - } - - model_polygons = polyunion(model_polygons); - double layer_model_area = 0; - for (const ClipperPolygon& polygon : model_polygons) - layer_model_area += areafn(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 = polyunion(supports_polygons); - else supports_polygons = polydiff(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 ClipperPolygon& polygon : supports_polygons) - layer_support_area += areafn(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 - ClipperPolygons trslices; - trslices.reserve(model_polygons.size() + supports_polygons.size()); - for(ClipperPolygon& poly : model_polygons) trslices.emplace_back(std::move(poly)); - for(ClipperPolygon& poly : supports_polygons) trslices.emplace_back(std::move(poly)); - - layer.transformed_slices(polyunion(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 = is_fast_layer ? fast_tilt : slow_tilt; - - { Lock lck(mutex); - if (is_fast_layer) - fast_layers++; - else - slow_layers++; - - - // Calculation of the printing time - - if (sliced_layer_cnt < 3) - estim_time += init_exp_time; - else if (fade_layer_time > exp_time) - { - fade_layer_time -= delta_fade_time; - estim_time += fade_layer_time; - } - else - estim_time += exp_time; - - estim_time += tilt_time; - } - }; - - // sequential version for debugging: - // for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i); - tbb::parallel_for(0, m_printer_input.size(), printlayerfn); - - auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR; - m_print_statistics.support_used_material = supports_volume * SCALING2; - m_print_statistics.objects_used_material = models_volume * SCALING2; - - // Estimated printing time - // A layers count o the highest object - if (m_printer_input.size() == 0) - m_print_statistics.estimated_print_time = std::nan(""); - else - m_print_statistics.estimated_print_time = estim_time; - - m_print_statistics.fast_layers_count = fast_layers; - m_print_statistics.slow_layers_count = slow_layers; - - m_report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW); - }; - - // Rasterizing the model objects, and their supports - auto rasterize = [this]() { - if(canceled()) return; - - // Set up the printer, allocate space for all the layers - sla::RasterWriter &printer = init_printer(); - - auto lvlcnt = unsigned(m_printer_input.size()); - printer.layers(lvlcnt); - - // 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 = m_report_status.status(); - - double increment = (slot * sd) / m_printer_input.size(); - double dstatus = m_report_status.status(); - - SpinMutex slck; - - // procedure to process one height level. This will run in parallel - auto lvlfn = - [this, &slck, &printer, increment, &dstatus, &pst] - (unsigned level_id) - { - if(canceled()) return; - - PrintLayer& printlayer = m_printer_input[level_id]; - - // Switch to the appropriate layer in the printer - printer.begin_layer(level_id); - - for(const ClipperLib::Polygon& poly : printlayer.transformed_slices()) - printer.draw_polygon(poly, level_id); - - // Finish the layer for later saving it. - printer.finish_layer(level_id); - - // Status indication guarded with the spinlock - { - std::lock_guard lck(slck); - dstatus += increment; - double st = std::round(dstatus); - if(st > pst) { - m_report_status(*this, st, - PRINT_STEP_LABELS(slapsRasterize)); - pst = st; - } - } - }; - - // last minute escape - if(canceled()) return; - - // Sequential version (for testing) - // for(unsigned l = 0; l < lvlcnt; ++l) lvlfn(l); - - // Print all the layers in parallel - tbb::parallel_for(0, lvlcnt, lvlfn); - - // Set statistics values to the printer - sla::RasterWriter::PrintStatistics stats; - stats.used_material = (m_print_statistics.objects_used_material + - m_print_statistics.support_used_material) / - 1000; - - int num_fade = m_default_object_config.faded_layers.getInt(); - stats.num_fade = num_fade >= 0 ? size_t(num_fade) : size_t(0); - stats.num_fast = m_print_statistics.fast_layers_count; - stats.num_slow = m_print_statistics.slow_layers_count; - stats.estimated_print_time_s = m_print_statistics.estimated_print_time; - - m_printer->set_statistics(stats); - }; - - using slaposFn = std::function; - using slapsFn = std::function; - - slaposFn pobj_program[] = - { - hollow_model, slice_model, [](SLAPrintObject&){}, support_points, support_tree, generate_pad, slice_supports - }; + Steps printsteps{this}; // We want to first process all objects... std::vector level1_obj_steps = { @@ -1520,10 +679,9 @@ void SLAPrint::process() slaposSliceSupports }; - slapsFn print_program[] = { merge_slices_and_eval_stats, rasterize }; SLAPrintStep print_steps[] = { slapsMergeSlicesAndEval, slapsRasterize }; - - double st = min_objstatus; + + double st = Steps::min_objstatus; BOOST_LOG_TRIVIAL(info) << "Start slicing process."; @@ -1538,10 +696,10 @@ void SLAPrint::process() std::array step_times {}; auto apply_steps_on_objects = - [this, &st, ostepd, &pobj_program, &step_times, &bench] + [this, &st, &printsteps, &step_times, &bench] (const std::vector &steps) { - unsigned incr = 0; + double incr = 0; for (SLAPrintObject *po : m_objects) { for (SLAPrintObjectStep step : steps) { @@ -1551,19 +709,19 @@ void SLAPrint::process() // throws the canceled signal. throw_if_canceled(); - st += incr * ostepd; + st += incr; if (po->m_stepmask[step] && po->set_started(step)) { - m_report_status(*this, st, OBJ_STEP_LABELS(step)); + m_report_status(*this, st, printsteps.label(step)); bench.start(); - pobj_program[step](*po); + printsteps.execute(step, *po); bench.stop(); step_times[step] += bench.getElapsedSec(); throw_if_canceled(); po->set_done(step); } - incr = OBJ_STEP_LEVELS[step]; + incr = printsteps.progressrange(step); } } }; @@ -1573,23 +731,22 @@ void SLAPrint::process() // this would disable the rasterization step // std::fill(m_stepmask.begin(), m_stepmask.end(), false); - - double pstd = (100 - max_objstatus) / 100.0; - st = max_objstatus; + + st = Steps::max_objstatus; for(SLAPrintStep currentstep : print_steps) { throw_if_canceled(); if (m_stepmask[currentstep] && set_started(currentstep)) { - m_report_status(*this, st, PRINT_STEP_LABELS(currentstep)); + m_report_status(*this, st, printsteps.label(currentstep)); bench.start(); - print_program[currentstep](); + printsteps.execute(currentstep); bench.stop(); step_times[slaposCount + currentstep] += bench.getElapsedSec(); throw_if_canceled(); set_done(currentstep); } - st += PRINT_STEP_LEVELS[currentstep] * pstd; + st += printsteps.progressrange(currentstep); } // If everything vent well @@ -1598,10 +755,10 @@ void SLAPrint::process() #ifdef SLAPRINT_DO_BENCHMARK std::string csvbenchstr; for (size_t i = 0; i < size_t(slaposCount); ++i) - csvbenchstr += OBJ_STEP_LABELS(i) + ";"; + csvbenchstr += printsteps.label(SLAPrintObjectStep(i)) + ";"; for (size_t i = 0; i < size_t(slapsCount); ++i) - csvbenchstr += PRINT_STEP_LABELS(i) + ";"; + csvbenchstr += printsteps.label(SLAPrintStep(i)) + ";"; csvbenchstr += "\n"; for (double t : step_times) csvbenchstr += std::to_string(t) + ";"; diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 6c8a6e26b..2edede109 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -3,8 +3,8 @@ #include #include "PrintBase.hpp" -//#include "PrintExport.hpp" #include "SLA/RasterWriter.hpp" +#include "SLA/SupportTree.hpp" #include "Point.hpp" #include "MTUtils.hpp" #include @@ -292,10 +292,33 @@ private: // Caching the transformed (m_trafo) raw mesh of the object mutable CachedObject m_transformed_rmesh; - class SupportData; + class SupportData : public sla::SupportableMesh + { + public: + sla::SupportTree::UPtr support_tree_ptr; // the supports + std::vector support_slices; // sliced supports + + inline SupportData(const TriangleMesh &t) + : sla::SupportableMesh{t, {}, {}} + {} + + sla::SupportTree::UPtr &create_support_tree(const sla::JobController &ctl) + { + support_tree_ptr = sla::SupportTree::create(*this, ctl); + return support_tree_ptr; + } + }; + std::unique_ptr m_supportdata; - class HollowingData; + class HollowingData + { + public: + + TriangleMesh interior; + // std::vector + }; + std::unique_ptr m_hollowing_data; }; @@ -346,7 +369,9 @@ class SLAPrint : public PrintBaseWithState { private: // Prevents erroneous use by other classes. typedef PrintBaseWithState Inherited; - + + class Steps; // See SLAPrintSteps.cpp + public: SLAPrint(): m_stepmask(slapsCount, true) {} @@ -402,8 +427,8 @@ public: template void transformed_slices(Container&& c) { m_transformed_slices = std::forward(c); } - - friend void SLAPrint::process(); + + friend class SLAPrint::Steps; public: @@ -479,6 +504,19 @@ private: friend SLAPrintObject; }; +// Helper functions: + +bool is_zero_elevation(const SLAPrintObjectConfig &c); + +sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c); + +sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c); + +sla::PadConfig make_pad_cfg(const SLAPrintObjectConfig& c); + +bool validate_pad(const TriangleMesh &pad, const sla::PadConfig &pcfg); + + } // namespace Slic3r #endif /* slic3r_SLAPrint_hpp_ */ diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp new file mode 100644 index 000000000..61d00dff8 --- /dev/null +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -0,0 +1,848 @@ +#include + +#include +#include +#include + +#include + +// For geometry algorithms with native Clipper types (no copies and conversions) +#include + +#include + +#include "I18N.hpp" + +//! 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 = { + 5, // slaposHollowing, + 20, // slaposObjectSlice, + 5, // slaposDrillHolesIfHollowed + 20, // slaposSupportPoints, + 10, // slaposSupportTree, + 10, // slaposPad, + 30, // slaposSliceSupports, +}; + +std::string OBJ_STEP_LABELS(size_t idx) +{ + switch (idx) { + case slaposHollowing: return L("Hollowing out the model"); + case slaposObjectSlice: return L("Slicing model"); + case slaposDrillHolesIfHollowed: return L("Drilling holes into hollowed 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::hollow_model(SLAPrintObject &po) +{ + + if (!po.m_config.hollowing_enable.getBool()) { + BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!"; + po.m_hollowing_data.reset(); + return; + } else { + BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!"; + } + + if (!po.m_hollowing_data) + po.m_hollowing_data.reset(new SLAPrintObject::HollowingData()); + + 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}; + auto meshptr = generate_interior(po.transformed_mesh(), hlwcfg); + if (meshptr) po.m_hollowing_data->interior = *meshptr; + + if (po.m_hollowing_data->interior.empty()) + BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!"; +} + +// 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) +{ + + TriangleMesh hollowed_mesh; + + bool is_hollowing = po.m_config.hollowing_enable.getBool() && + po.m_hollowing_data; + + if (is_hollowing) { + hollowed_mesh = po.transformed_mesh(); + hollowed_mesh.merge(po.m_hollowing_data->interior); + hollowed_mesh.require_shared_vertices(); + } + + const TriangleMesh &mesh = is_hollowing ? hollowed_mesh : + po.transformed_mesh(); + + // 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); + auto && bb3d = mesh.bounding_box(); + 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 std::runtime_error( + 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()); + + TriangleMeshSlicer slicer(&mesh); + + po.m_model_slices.clear(); + slicer.slice(po.m_model_height_levels, + float(po.config().slice_closing_radius.value), + &po.m_model_slices, + [this](){ m_print->throw_if_canceled(); }); + + auto mit = slindex_it; + double doffs = m_print->m_printer_config.absolute_correction.getFloat(); + coord_t clpr_offs = scaled(doffs); + for(size_t id = 0; + id < po.m_model_slices.size() && mit != po.m_slice_index.end(); + id++) + { + // We apply the printer correction offset here. + if(clpr_offs != 0) + po.m_model_slices[id] = + offset_ex(po.m_model_slices[id], float(clpr_offs)); + + mit->set_model_slice_idx(po, id); ++mit; + } + + if(po.m_config.supports_enable.getBool() || + po.m_config.pad_enable.getBool()) + { + po.m_supportdata.reset( + new SLAPrintObject::SupportData(po.transformed_mesh()) ); + } +} + +// 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) + po.m_supportdata.reset( + new SLAPrintObject::SupportData(po.transformed_mesh())); + + 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->emesh, po.get_model_slices(), heights, config, + [this]() { throw_if_canceled(); }, statuscb); + + // Now let's extract the result. + const std::vector& points = auto_supports.output(); + throw_if_canceled(); + po.m_supportdata->pts = points; + + BOOST_LOG_TRIVIAL(debug) << "Automatic support points: " + << po.m_supportdata->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->pts = po.transformed_support_points(); + } + + // 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())) { + double tolerance = po.config().pad_enable.getBool() ? + po.m_config.pad_wall_thickness.getFloat() : + po.m_config.support_base_height.getFloat(); + + remove_bottom_points(po.m_supportdata->pts, + po.m_supportdata->emesh.ground_level(), + tolerance); + } +} + +void SLAPrint::Steps::support_tree(SLAPrintObject &po) +{ + if(!po.m_supportdata) return; + + sla::PadConfig pcfg = make_pad_cfg(po.m_config); + + if (pcfg.embed_object) + po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm); + + po.m_supportdata->cfg = make_support_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->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()) + { + // Get the distilled pad configuration from the config + sla::PadConfig pcfg = make_pad_cfg(po.m_config); + + ExPolygons bp; // This will store the base plate of the pad. + double pad_h = pcfg.full_height(); + const TriangleMesh &trmesh = po.transformed_mesh(); + + if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) { + // No support (thus no elevation) or zero elevation mode + // we sometimes call it "builtin pad" is enabled so we will + // get a sample from the bottom of the mesh and use it for pad + // creation. + sla::pad_blueprint(trmesh, bp, float(pad_h), + float(po.m_config.layer_height.getFloat()), + [this](){ throw_if_canceled(); }); + } + + po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg); + auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(sla::MeshType::Pad); + + if (!validate_pad(pad_mesh, pcfg)) + throw std::runtime_error( + L("No pad can be generated for this model with the " + "current configuration")); + + } else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) { + po.m_supportdata->support_tree_ptr->remove_pad(); + } + + 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 && sd->support_tree_ptr) { + + std::vector heights; heights.reserve(po.m_slice_index.size()); + + for(auto& rec : po.m_slice_index) heights.emplace_back(rec.slice_level()); + + sd->support_slices = sd->support_tree_ptr->slice( + heights, float(po.config().slice_closing_radius.value)); + } + + double doffs = m_print->m_printer_config.absolute_correction.getFloat(); + coord_t clpr_offs = scaled(doffs); + + for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i) { + // We apply the printer correction offset here. + if (clpr_offs != 0) + sd->support_slices[i] = offset_ex(sd->support_slices[i], float(clpr_offs)); + + po.m_slice_index[i].set_support_slice_idx(po, i); + } + + // 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); +} + +using ClipperPoint = ClipperLib::IntPoint; +using ClipperPolygon = ClipperLib::Polygon; // see clipper_polygon.hpp in libnest2d +using ClipperPolygons = std::vector; + +static ClipperPolygons polyunion(const ClipperPolygons &subjects) +{ + ClipperLib::Clipper clipper; + + bool closed = true; + + for(auto& path : subjects) { + clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); + clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed); + } + + auto mode = ClipperLib::pftPositive; + + return libnest2d::clipper_execute(clipper, ClipperLib::ctUnion, mode, mode); +} + +static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPolygons& clips) +{ + ClipperLib::Clipper clipper; + + bool closed = true; + + for(auto& path : subjects) { + clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed); + clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed); + } + + for(auto& path : clips) { + clipper.AddPath(path.Contour, ClipperLib::ptClip, closed); + clipper.AddPaths(path.Holes, ClipperLib::ptClip, closed); + } + + auto mode = ClipperLib::pftPositive; + + return libnest2d::clipper_execute(clipper, ClipperLib::ctDifference, mode, mode); +} + +// get polygons for all instances in the object +static ClipperPolygons get_all_polygons( + const ExPolygons & input_polygons, + const std::vector &instances, + bool is_lefthanded) +{ + namespace sl = libnest2d::sl; + + ClipperPolygons polygons; + 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) + { + ClipperPolygon poly; + + // We need to reverse if is_lefthanded is true but + bool needreverse = is_lefthanded; + + // should be a move + poly.Contour.reserve(polygon.contour.size() + 1); + + auto& cntr = polygon.contour.points; + if(needreverse) + for(auto it = cntr.rbegin(); it != cntr.rend(); ++it) + poly.Contour.emplace_back(it->x(), it->y()); + else + for(auto& p : cntr) + poly.Contour.emplace_back(p.x(), p.y()); + + for(auto& h : polygon.holes) { + poly.Holes.emplace_back(); + auto& hole = poly.Holes.back(); + hole.reserve(h.points.size() + 1); + + if(needreverse) + for(auto it = h.points.rbegin(); it != h.points.rend(); ++it) + hole.emplace_back(it->x(), it->y()); + else + for(auto& p : h.points) + hole.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; + } + + sl::rotate(poly, double(instances[i].rotation)); + sl::translate(poly, ClipperPoint{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()) { + 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(); + + // libnest calculates positive area for clockwise polygons, Slic3r is in counter-clockwise + auto areafn = [](const ClipperPolygon& poly) { return - libnest2d::sl::area(poly); }; + + 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 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); + + 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; + + SpinMutex mutex; + using Lock = std::lock_guard; + + // Going to parallel: + auto printlayerfn = [ + // functions and read only vars + areafn, area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, delta_fade_time, + + // write vars + &mutex, &models_volume, &supports_volume, &estim_time, &slow_layers, + &fast_layers, &fade_layer_time](PrintLayer& layer, size_t 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 + + ClipperPolygons model_polygons; + ClipperPolygons 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(soModel).size(); + }); + + supports_polygons.reserve(c); + + for(const SliceRecord& record : layer.slices()) { + const SLAPrintObject *po = record.print_obj(); + + const ExPolygons &modelslices = record.get_slice(soModel); + + bool is_lefth = record.print_obj()->is_left_handed(); + if (!modelslices.empty()) { + ClipperPolygons v = get_all_polygons(modelslices, po->instances(), is_lefth); + for(ClipperPolygon& p_tmp : v) model_polygons.emplace_back(std::move(p_tmp)); + } + + const ExPolygons &supportslices = record.get_slice(soSupport); + + if (!supportslices.empty()) { + ClipperPolygons v = get_all_polygons(supportslices, po->instances(), is_lefth); + for(ClipperPolygon& p_tmp : v) supports_polygons.emplace_back(std::move(p_tmp)); + } + } + + model_polygons = polyunion(model_polygons); + double layer_model_area = 0; + for (const ClipperPolygon& polygon : model_polygons) + layer_model_area += areafn(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 = polyunion(supports_polygons); + else supports_polygons = polydiff(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 ClipperPolygon& polygon : supports_polygons) + layer_support_area += areafn(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 + ClipperPolygons trslices; + trslices.reserve(model_polygons.size() + supports_polygons.size()); + for(ClipperPolygon& poly : model_polygons) trslices.emplace_back(std::move(poly)); + for(ClipperPolygon& poly : supports_polygons) trslices.emplace_back(std::move(poly)); + + layer.transformed_slices(polyunion(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 = is_fast_layer ? fast_tilt : slow_tilt; + + { Lock lck(mutex); + if (is_fast_layer) + fast_layers++; + else + slow_layers++; + + + // Calculation of the printing time + + if (sliced_layer_cnt < 3) + estim_time += init_exp_time; + else if (fade_layer_time > exp_time) + { + fade_layer_time -= delta_fade_time; + estim_time += fade_layer_time; + } + else + estim_time += exp_time; + + estim_time += tilt_time; + } + }; + + // sequential version for debugging: + // for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i); + sla::ccr::enumerate(printer_input.begin(), printer_input.end(), printlayerfn); + + 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 = std::nan(""); + else + print_statistics.estimated_print_time = estim_time; + + 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()) return; + + auto &print_statistics = m_print->m_print_statistics; + auto &printer_input = m_print->m_printer_input; + + // Set up the printer, allocate space for all the layers + sla::RasterWriter &printer = m_print->init_printer(); + + auto lvlcnt = unsigned(printer_input.size()); + printer.layers(lvlcnt); + + // 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) / printer_input.size(); + double dstatus = current_status(); + + SpinMutex slck; + + // procedure to process one height level. This will run in parallel + auto lvlfn = [this, &slck, &printer, increment, &dstatus, &pst] (unsigned level_id) + { + if(canceled()) return; + + PrintLayer& printlayer = m_print->m_printer_input[level_id]; + + // Switch to the appropriate layer in the printer + printer.begin_layer(level_id); + + for(const ClipperLib::Polygon& poly : printlayer.transformed_slices()) + printer.draw_polygon(poly, level_id); + + // Finish the layer for later saving it. + printer.finish_layer(level_id); + + // 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; + + // Sequential version (for testing) + // for(unsigned l = 0; l < lvlcnt; ++l) lvlfn(l); + + // Print all the layers in parallel + tbb::parallel_for(0, lvlcnt, lvlfn); + + // Set statistics values to the printer + sla::RasterWriter::PrintStatistics stats; + stats.used_material = (print_statistics.objects_used_material + + print_statistics.support_used_material) / + 1000; + + int num_fade = m_print->m_default_object_config.faded_layers.getInt(); + stats.num_fade = num_fade >= 0 ? size_t(num_fade) : size_t(0); + stats.num_fast = print_statistics.fast_layers_count; + stats.num_slow = print_statistics.slow_layers_count; + stats.estimated_print_time_s = print_statistics.estimated_print_time; + + printer.set_statistics(stats); +} + +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 slaposHollowing: hollow_model(obj); break; + case slaposObjectSlice: slice_model(obj); break; + case slaposDrillHolesIfHollowed: 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); + } +} + +} diff --git a/src/libslic3r/SLAPrintSteps.hpp b/src/libslic3r/SLAPrintSteps.hpp new file mode 100644 index 000000000..c62558671 --- /dev/null +++ b/src/libslic3r/SLAPrintSteps.hpp @@ -0,0 +1,68 @@ +#ifndef SLAPRINTSTEPS_HPP +#define SLAPRINTSTEPS_HPP + +#include + +#include +#include + +namespace Slic3r { + +class SLAPrint::Steps +{ +private: + SLAPrint *m_print = nullptr; + +public: + // where the per object operations start and end + static const constexpr unsigned min_objstatus = 0; + static const constexpr unsigned max_objstatus = 50; + +private: + const size_t objcount; + + // shortcut to initial layer height + const double ilhd; + const float ilh; + const coord_t ilhs; + + // the coefficient that multiplies the per object status values which + // are set up for <0, 100>. They need to be scaled into the whole process + const double objectstep_scale; + + template void report_status(Args&&...args) + { + m_print->m_report_status(*m_print, std::forward(args)...); + } + + double current_status() const { return m_print->m_report_status.status(); } + void throw_if_canceled() const { m_print->throw_if_canceled(); } + bool canceled() const { return m_print->canceled(); } + void initialize_printer_input(); + +public: + Steps(SLAPrint *print); + + void hollow_model(SLAPrintObject &po); + void slice_model(SLAPrintObject& po); + void support_points(SLAPrintObject& po); + void support_tree(SLAPrintObject& po); + void generate_pad(SLAPrintObject& po); + void slice_supports(SLAPrintObject& po); + + void merge_slices_and_eval_stats(); + void rasterize(); + + void execute(SLAPrintObjectStep step, SLAPrintObject &obj); + void execute(SLAPrintStep step); + + static std::string label(SLAPrintObjectStep step); + static std::string label(SLAPrintStep step); + + double progressrange(SLAPrintObjectStep step) const; + double progressrange(SLAPrintStep step) const; +}; + +} + +#endif // SLAPRINTSTEPS_HPP diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 1d4926d92..615d971b4 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2882,11 +2882,12 @@ void Plater::priv::HollowJob::process() if (st < 100) update_status(int(st), s); }; - TriangleMesh omesh = sla::generate_interior(*m_object_mesh, m_cfg, ctl); + std::unique_ptr omesh = + sla::generate_interior(*m_object_mesh, m_cfg, ctl); - if (!omesh.empty()) { + if (omesh && !omesh->empty()) { m_output_mesh.reset(new TriangleMesh{*m_object_mesh}); - m_output_mesh->merge(omesh); + m_output_mesh->merge(*omesh); m_output_mesh->require_shared_vertices(); update_status(90, _(L("Indexing hollowed object"))); diff --git a/tests/libslic3r/test_hollowing.cpp b/tests/libslic3r/test_hollowing.cpp index 5e7f1a568..0cb1ac343 100644 --- a/tests/libslic3r/test_hollowing.cpp +++ b/tests/libslic3r/test_hollowing.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "libslic3r/SLA/Hollowing.hpp" #include #include "libslic3r/Format/OBJ.hpp" @@ -28,13 +29,14 @@ TEST_CASE("Negative 3D offset should produce smaller object.", "[Hollowing]") Benchmark bench; bench.start(); - Slic3r::TriangleMesh out_mesh = Slic3r::sla::generate_interior(in_mesh); + std::unique_ptr out_mesh_ptr = + Slic3r::sla::generate_interior(in_mesh); bench.stop(); std::cout << "Elapsed processing time: " << bench.getElapsedSec() << std::endl; - in_mesh.merge(out_mesh); + if (out_mesh_ptr) in_mesh.merge(*out_mesh_ptr); in_mesh.require_shared_vertices(); in_mesh.WriteOBJFile("merged_out.obj"); }