From e675a5d5c6022b1a75da02b38d22cf08b6bc5c6f Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Tue, 24 Sep 2019 15:15:49 +0200 Subject: [PATCH] Reworked pad creation algorithm with new parameters: * brim size * force pad around object everywhere --- sandboxes/slabasebed/slabasebed.cpp | 10 +- src/libslic3r/CMakeLists.txt | 5 +- src/libslic3r/MTUtils.hpp | 34 +- src/libslic3r/PrintConfig.cpp | 18 + src/libslic3r/PrintConfig.hpp | 9 +- src/libslic3r/SLA/SLAAutoSupports.cpp | 30 +- src/libslic3r/SLA/SLAAutoSupports.hpp | 12 +- src/libslic3r/SLA/SLABasePool.cpp | 922 ------------------------ src/libslic3r/SLA/SLABasePool.hpp | 92 --- src/libslic3r/SLA/SLABoilerPlate.hpp | 67 +- src/libslic3r/SLA/SLACommon.hpp | 2 + src/libslic3r/SLA/SLAPad.cpp | 865 ++++++++++++++++++++++ src/libslic3r/SLA/SLAPad.hpp | 93 +++ src/libslic3r/SLA/SLASpatIndex.hpp | 9 +- src/libslic3r/SLA/SLASupportTree.cpp | 461 +++++------- src/libslic3r/SLA/SLASupportTree.hpp | 24 +- src/libslic3r/SLA/SLASupportTreeIGL.cpp | 44 +- src/libslic3r/SLAPrint.cpp | 117 ++- src/libslic3r/SLAPrint.hpp | 4 +- src/libslic3r/Tesselate.hpp | 15 +- src/slic3r/GUI/3DScene.cpp | 4 +- src/slic3r/GUI/ConfigManipulation.cpp | 3 + src/slic3r/GUI/GLCanvas3D.cpp | 6 +- src/slic3r/GUI/MainFrame.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 6 +- src/slic3r/GUI/Preset.cpp | 2 + src/slic3r/GUI/Tab.cpp | 2 + 27 files changed, 1410 insertions(+), 1448 deletions(-) delete mode 100644 src/libslic3r/SLA/SLABasePool.cpp delete mode 100644 src/libslic3r/SLA/SLABasePool.hpp create mode 100644 src/libslic3r/SLA/SLAPad.cpp create mode 100644 src/libslic3r/SLA/SLAPad.hpp diff --git a/sandboxes/slabasebed/slabasebed.cpp b/sandboxes/slabasebed/slabasebed.cpp index b8b94d86f..1996a1eb8 100644 --- a/sandboxes/slabasebed/slabasebed.cpp +++ b/sandboxes/slabasebed/slabasebed.cpp @@ -16,9 +16,9 @@ const std::string USAGE_STR = { namespace Slic3r { namespace sla { -Contour3D create_base_pool(const Polygons &ground_layer, +Contour3D create_pad(const Polygons &ground_layer, const ExPolygons &holes = {}, - const PoolConfig& cfg = PoolConfig()); + const PadConfig& cfg = PadConfig()); Contour3D walls(const Polygon& floor_plate, const Polygon& ceiling, double floor_z_mm, double ceiling_z_mm, @@ -45,7 +45,7 @@ int main(const int argc, const char *argv[]) { model.align_to_origin(); ExPolygons ground_slice; - sla::base_plate(model, ground_slice, 0.1f); + sla::pad_plate(model, ground_slice, 0.1f); if(ground_slice.empty()) return EXIT_FAILURE; ground_slice = offset_ex(ground_slice, 0.5); @@ -56,10 +56,10 @@ int main(const int argc, const char *argv[]) { bench.start(); - sla::PoolConfig cfg; + sla::PadConfig cfg; cfg.min_wall_height_mm = 0; cfg.edge_radius_mm = 0; - mesh = sla::create_base_pool(to_polygons(ground_slice), {}, cfg); + mesh = sla::create_pad(to_polygons(ground_slice), {}, cfg); bench.stop(); diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 5232be2b6..6aa197417 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -176,8 +176,8 @@ add_library(libslic3r STATIC miniz_extension.cpp SLA/SLACommon.hpp SLA/SLABoilerPlate.hpp - SLA/SLABasePool.hpp - SLA/SLABasePool.cpp + SLA/SLAPad.hpp + SLA/SLAPad.cpp SLA/SLASupportTree.hpp SLA/SLASupportTree.cpp SLA/SLASupportTreeIGL.cpp @@ -215,6 +215,7 @@ target_link_libraries(libslic3r qhull semver tbb + ${CMAKE_DL_LIBS} ) if(WIN32) diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 212752883..63ff6fb09 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -252,22 +252,15 @@ template struct remove_cvref template using remove_cvref_t = typename remove_cvref::type; -template class C, class T> -class Container : public C> -{ -public: - explicit Container(size_t count, T &&initval) - : C>(count, initval) - {} -}; - template using DefaultContainer = std::vector; /// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html -template class C = DefaultContainer> -inline C> linspace(const T &start, const T &stop, const I &n) +template class Container = DefaultContainer> +inline Container> linspace(const T &start, + const T &stop, + const I &n) { - Container vals(n, T()); + Container> vals(n, T()); T stride = (stop - start) / n; size_t i = 0; @@ -282,10 +275,13 @@ inline C> linspace(const T &start, const T &stop, const I &n) /// in the closest multiple of 'stride' less than or equal to 'end' and /// leaving 'stride' space between each value. /// Very similar to Matlab [start:stride:end] notation. -template class C = DefaultContainer> -inline C> grid(const T &start, const T &stop, const T &stride) +template class Container = DefaultContainer> +inline Container> grid(const T &start, + const T &stop, + const T &stride) { - Container vals(size_t(std::ceil((stop - start) / stride)), T()); + Container> + vals(size_t(std::ceil((stop - start) / stride)), T()); int i = 0; std::generate(vals.begin(), vals.end(), [&i, start, stride] { @@ -387,10 +383,12 @@ unscaled(const Eigen::Matrix &v) noexcept return v.template cast() * SCALING_FACTOR; } -template inline std::vector reserve_vector(size_t capacity) +template // Arbitrary allocator can be used +inline IntegerOnly> reserve_vector(I capacity) { - std::vector ret; - ret.reserve(capacity); + std::vector ret; + if (capacity > I(0)) ret.reserve(size_t(capacity)); + return ret; } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 78dd60a4b..148ffe141 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2694,6 +2694,17 @@ void PrintConfigDef::init_sla_params() def->max = 30; def->mode = comExpert; def->set_default_value(new ConfigOptionFloat(0.)); + + def = this->add("pad_brim_size", coFloat); + def->label = L("Pad brim size"); + def->tooltip = L("How far should the pad extend around the contained geometry"); + def->category = L("Pad"); + // def->tooltip = L(""); + def->sidetext = L("mm"); + def->min = 0; + def->max = 30; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(1.6)); def = this->add("pad_max_merge_distance", coFloat); def->label = L("Max merge distance"); @@ -2734,6 +2745,13 @@ void PrintConfigDef::init_sla_params() def->tooltip = L("Create pad around object and ignore the support elevation"); def->mode = comSimple; def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("pad_around_object_everywhere", coBool); + def->label = L("Pad around object everywhere"); + def->category = L("Pad"); + def->tooltip = L("Force pad around object everywhere"); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(false)); def = this->add("pad_object_gap", coFloat); def->label = L("Pad object gap"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 6a19edf84..e4ffe9199 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1022,6 +1022,9 @@ public: // The height of the pad from the bottom to the top not considering the pit ConfigOptionFloat pad_wall_height /*= 5*/; + + // How far should the pad extend around the contained geometry + ConfigOptionFloat pad_brim_size; // The greatest distance where two individual pads are merged into one. The // distance is measured roughly from the centroids of the pads. @@ -1042,7 +1045,9 @@ public: // ///////////////////////////////////////////////////////////////////////// // Disable the elevation (ignore its value) and use the zero elevation mode - ConfigOptionBool pad_around_object; + ConfigOptionBool pad_around_object; + + ConfigOptionBool pad_around_object_everywhere; // This is the gap between the object bottom and the generated pad ConfigOptionFloat pad_object_gap; @@ -1082,10 +1087,12 @@ protected: OPT_PTR(pad_enable); OPT_PTR(pad_wall_thickness); OPT_PTR(pad_wall_height); + OPT_PTR(pad_brim_size); OPT_PTR(pad_max_merge_distance); // OPT_PTR(pad_edge_radius); OPT_PTR(pad_wall_slope); OPT_PTR(pad_around_object); + OPT_PTR(pad_around_object_everywhere); OPT_PTR(pad_object_gap); OPT_PTR(pad_object_connector_stride); OPT_PTR(pad_object_connector_width); diff --git a/src/libslic3r/SLA/SLAAutoSupports.cpp b/src/libslic3r/SLA/SLAAutoSupports.cpp index 36378df39..65f590143 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.cpp +++ b/src/libslic3r/SLA/SLAAutoSupports.cpp @@ -16,6 +16,7 @@ #include namespace Slic3r { +namespace sla { /*float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2) { @@ -48,9 +49,16 @@ float SLAAutoSupports::distance_limit(float angle) const return 1./(2.4*get_required_density(angle)); }*/ -SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector& slices, const std::vector& heights, - const Config& config, std::function throw_on_cancel, std::function statusfn) -: m_config(config), m_emesh(emesh), m_throw_on_cancel(throw_on_cancel), m_statusfn(statusfn) +SLAAutoSupports::SLAAutoSupports(const sla::EigenMesh3D & emesh, + const std::vector &slices, + const std::vector & heights, + const Config & config, + std::function throw_on_cancel, + std::function statusfn) + : m_config(config) + , m_emesh(emesh) + , m_throw_on_cancel(throw_on_cancel) + , m_statusfn(statusfn) { process(slices, heights); project_onto_mesh(m_output); @@ -505,6 +513,21 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru } } +void remove_bottom_points(std::vector &pts, double gnd_lvl, double tolerance) +{ + // get iterator to the reorganized vector end + auto endit = + std::remove_if(pts.begin(), pts.end(), + [tolerance, gnd_lvl](const sla::SupportPoint &sp) { + double diff = std::abs(gnd_lvl - + double(sp.pos(Z))); + return diff <= tolerance; + }); + + // erase all elements after the new end + pts.erase(endit, pts.end()); +} + #ifdef SLA_AUTOSUPPORTS_DEBUG void SLAAutoSupports::output_structures(const std::vector& structures) { @@ -533,4 +556,5 @@ void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, const std::st } #endif +} // namespace sla } // namespace Slic3r diff --git a/src/libslic3r/SLA/SLAAutoSupports.hpp b/src/libslic3r/SLA/SLAAutoSupports.hpp index 38f21e6cf..d2f50f0a4 100644 --- a/src/libslic3r/SLA/SLAAutoSupports.hpp +++ b/src/libslic3r/SLA/SLAAutoSupports.hpp @@ -11,20 +11,22 @@ // #define SLA_AUTOSUPPORTS_DEBUG namespace Slic3r { +namespace sla { class SLAAutoSupports { public: struct Config { - float density_relative; - float minimal_distance; - float head_diameter; + float density_relative {1.f}; + float minimal_distance {1.f}; + float head_diameter {0.4f}; /////////////// inline float support_force() const { return 7.7f / density_relative; } // a force one point can support (arbitrary force unit) inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2) }; - SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector& slices, + SLAAutoSupports(const sla::EigenMesh3D& emesh, const std::vector& slices, const std::vector& heights, const Config& config, std::function throw_on_cancel, std::function statusfn); + const std::vector& output() { return m_output; } struct MyLayer; @@ -199,7 +201,9 @@ private: std::function m_statusfn; }; +void remove_bottom_points(std::vector &pts, double gnd_lvl, double tolerance); +} // namespace sla } // namespace Slic3r diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp deleted file mode 100644 index 53dfef404..000000000 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ /dev/null @@ -1,922 +0,0 @@ -#include "SLABasePool.hpp" -#include "SLABoilerPlate.hpp" - -#include "boost/log/trivial.hpp" -#include "SLABoostAdapter.hpp" -#include "ClipperUtils.hpp" -#include "Tesselate.hpp" -#include "MTUtils.hpp" - -// For debugging: -// #include -// #include -// #include "SVG.hpp" - -namespace Slic3r { namespace sla { - -/// This function will return a triangulation of a sheet connecting an upper -/// and a lower plate given as input polygons. It will not triangulate the -/// plates themselves only the sheet. The caller has to specify the lower and -/// upper z levels in world coordinates as well as the offset difference -/// between the sheets. If the lower_z_mm is higher than upper_z_mm or the -/// offset difference is negative, the resulting triangle orientation will be -/// reversed. -/// -/// IMPORTANT: This is not a universal triangulation algorithm. It assumes -/// that the lower and upper polygons are offsetted versions of the same -/// original polygon. In general, it assumes that one of the polygons is -/// completely inside the other. The offset difference is the reference -/// distance from the inner polygon's perimeter to the outer polygon's -/// perimeter. The real distance will be variable as the clipper offset has -/// different strategies (rounding, etc...). This algorithm should have -/// O(2n + 3m) complexity where n is the number of upper vertices and m is the -/// number of lower vertices. -Contour3D walls(const Polygon& lower, const Polygon& upper, - double lower_z_mm, double upper_z_mm, - double offset_difference_mm, ThrowOnCancel thr) -{ - Contour3D ret; - - if(upper.points.size() < 3 || lower.size() < 3) return ret; - - // The concept of the algorithm is relatively simple. It will try to find - // the closest vertices from the upper and the lower polygon and use those - // as starting points. Then it will create the triangles sequentially using - // an edge from the upper polygon and a vertex from the lower or vice versa, - // depending on the resulting triangle's quality. - // The quality is measured by a scalar value. So far it looks like it is - // enough to derive it from the slope of the triangle's two edges connecting - // the upper and the lower part. A reference slope is calculated from the - // height and the offset difference. - - // Offset in the index array for the ceiling - const auto offs = upper.points.size(); - - // Shorthand for the vertex arrays - auto& upoints = upper.points, &lpoints = lower.points; - auto& rpts = ret.points; auto& ind = ret.indices; - - // If the Z levels are flipped, or the offset difference is negative, we - // will interpret that as the triangles normals should be inverted. - bool inverted = upper_z_mm < lower_z_mm || offset_difference_mm < 0; - - // Copy the points into the mesh, convert them from 2D to 3D - rpts.reserve(upoints.size() + lpoints.size()); - ind.reserve(2 * upoints.size() + 2 * lpoints.size()); - for (auto &p : upoints) - rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm); - for (auto &p : lpoints) - rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm); - - // Create pointing indices into vertex arrays. u-upper, l-lower - size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1; - - // Simple squared distance calculation. - auto distfn = [](const Vec3d& p1, const Vec3d& p2) { - auto p = p1 - p2; return p.transpose() * p; - }; - - // We need to find the closest point on lower polygon to the first point on - // the upper polygon. These will be our starting points. - double distmin = std::numeric_limits::max(); - for(size_t l = lidx; l < rpts.size(); ++l) { - thr(); - double d = distfn(rpts[l], rpts[uidx]); - if(d < distmin) { lidx = l; distmin = d; } - } - - // Set up lnextidx to be ahead of lidx in cyclic mode - lnextidx = lidx + 1; - if(lnextidx == rpts.size()) lnextidx = offs; - - // This will be the flip switch to toggle between upper and lower triangle - // creation mode - enum class Proceed { - UPPER, // A segment from the upper polygon and one vertex from the lower - LOWER // A segment from the lower polygon and one vertex from the upper - } proceed = Proceed::UPPER; - - // Flags to help evaluating loop termination. - bool ustarted = false, lstarted = false; - - // The variables for the fitness values, one for the actual and one for the - // previous. - double current_fit = 0, prev_fit = 0; - - // Every triangle of the wall has two edges connecting the upper plate with - // the lower plate. From the length of these two edges and the zdiff we - // can calculate the momentary squared offset distance at a particular - // position on the wall. The average of the differences from the reference - // (squared) offset distance will give us the driving fitness value. - const double offsdiff2 = std::pow(offset_difference_mm, 2); - const double zdiff2 = std::pow(upper_z_mm - lower_z_mm, 2); - - // Mark the current vertex iterator positions. If the iterators return to - // the same position, the loop can be terminated. - size_t uendidx = uidx, lendidx = lidx; - - do { thr(); // check throw if canceled - - prev_fit = current_fit; - - switch(proceed) { // proceed depending on the current state - case Proceed::UPPER: - if(!ustarted || uidx != uendidx) { // there are vertices remaining - // Get the 3D vertices in order - const Vec3d& p_up1 = rpts[uidx]; - const Vec3d& p_low = rpts[lidx]; - const Vec3d& p_up2 = rpts[unextidx]; - - // Calculate fitness: the average of the two connecting edges - double a = offsdiff2 - (distfn(p_up1, p_low) - zdiff2); - double b = offsdiff2 - (distfn(p_up2, p_low) - zdiff2); - current_fit = (std::abs(a) + std::abs(b)) / 2; - - if(current_fit > prev_fit) { // fit is worse than previously - proceed = Proceed::LOWER; - } else { // good to go, create the triangle - inverted - ? ind.emplace_back(int(unextidx), int(lidx), int(uidx)) - : ind.emplace_back(int(uidx), int(lidx), int(unextidx)); - - // Increment the iterators, rotate if necessary - ++uidx; ++unextidx; - if(unextidx == offs) unextidx = 0; - if(uidx == offs) uidx = 0; - - ustarted = true; // mark the movement of the iterators - // so that the comparison to uendidx can be made correctly - } - } else proceed = Proceed::LOWER; - - break; - case Proceed::LOWER: - // Mode with lower segment, upper vertex. Same structure: - if(!lstarted || lidx != lendidx) { - const Vec3d& p_low1 = rpts[lidx]; - const Vec3d& p_low2 = rpts[lnextidx]; - const Vec3d& p_up = rpts[uidx]; - - double a = offsdiff2 - (distfn(p_up, p_low1) - zdiff2); - double b = offsdiff2 - (distfn(p_up, p_low2) - zdiff2); - current_fit = (std::abs(a) + std::abs(b)) / 2; - - if(current_fit > prev_fit) { - proceed = Proceed::UPPER; - } else { - inverted - ? ind.emplace_back(int(uidx), int(lnextidx), int(lidx)) - : ind.emplace_back(int(lidx), int(lnextidx), int(uidx)); - - ++lidx; ++lnextidx; - if(lnextidx == rpts.size()) lnextidx = offs; - if(lidx == rpts.size()) lidx = offs; - - lstarted = true; - } - } else proceed = Proceed::UPPER; - - break; - } // end of switch - } while(!ustarted || !lstarted || uidx != uendidx || lidx != lendidx); - - return ret; -} - -/// Offsetting with clipper and smoothing the edges into a curvature. -void offset(ExPolygon& sh, coord_t distance, bool edgerounding = true) { - using ClipperLib::ClipperOffset; - using ClipperLib::jtRound; - using ClipperLib::jtMiter; - using ClipperLib::etClosedPolygon; - using ClipperLib::Paths; - using ClipperLib::Path; - - auto&& ctour = Slic3rMultiPoint_to_ClipperPath(sh.contour); - auto&& holes = Slic3rMultiPoints_to_ClipperPaths(sh.holes); - - // If the input is not at least a triangle, we can not do this algorithm - if(ctour.size() < 3 || - std::any_of(holes.begin(), holes.end(), - [](const Path& p) { return p.size() < 3; }) - ) { - BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!"; - return; - } - - auto jointype = edgerounding? jtRound : jtMiter; - - ClipperOffset offs; - offs.ArcTolerance = scaled(0.01); - Paths result; - offs.AddPath(ctour, jointype, etClosedPolygon); - offs.AddPaths(holes, jointype, etClosedPolygon); - offs.Execute(result, static_cast(distance)); - - // Offsetting reverts the orientation and also removes the last vertex - // so boost will not have a closed polygon. - - bool found_the_contour = false; - sh.holes.clear(); - for(auto& r : result) { - if(ClipperLib::Orientation(r)) { - // We don't like if the offsetting generates more than one contour - // but throwing would be an overkill. Instead, we should warn the - // caller about the inability to create correct geometries - if(!found_the_contour) { - auto rr = ClipperPath_to_Slic3rPolygon(r); - sh.contour.points.swap(rr.points); - found_the_contour = true; - } else { - BOOST_LOG_TRIVIAL(warning) - << "Warning: offsetting result is invalid!"; - } - } else { - // TODO If there are multiple contours we can't be sure which hole - // belongs to the first contour. (But in this case the situation is - // bad enough to let it go...) - sh.holes.emplace_back(ClipperPath_to_Slic3rPolygon(r)); - } - } -} - -void offset(Polygon &sh, coord_t distance, bool edgerounding = true) -{ - using ClipperLib::ClipperOffset; - using ClipperLib::jtRound; - using ClipperLib::jtMiter; - using ClipperLib::etClosedPolygon; - using ClipperLib::Paths; - using ClipperLib::Path; - - auto &&ctour = Slic3rMultiPoint_to_ClipperPath(sh); - - // If the input is not at least a triangle, we can not do this algorithm - if (ctour.size() < 3) { - BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!"; - return; - } - - ClipperOffset offs; - offs.ArcTolerance = 0.01 * scaled(1.); - Paths result; - offs.AddPath(ctour, edgerounding ? jtRound : jtMiter, etClosedPolygon); - offs.Execute(result, static_cast(distance)); - - // Offsetting reverts the orientation and also removes the last vertex - // so boost will not have a closed polygon. - - bool found_the_contour = false; - for (auto &r : result) { - if (ClipperLib::Orientation(r)) { - // We don't like if the offsetting generates more than one contour - // but throwing would be an overkill. Instead, we should warn the - // caller about the inability to create correct geometries - if (!found_the_contour) { - auto rr = ClipperPath_to_Slic3rPolygon(r); - sh.points.swap(rr.points); - found_the_contour = true; - } else { - BOOST_LOG_TRIVIAL(warning) - << "Warning: offsetting result is invalid!"; - } - } - } -} - -/// Unification of polygons (with clipper) preserving holes as well. -ExPolygons unify(const ExPolygons& shapes) { - using ClipperLib::ptSubject; - - ExPolygons retv; - - bool closed = true; - bool valid = true; - - ClipperLib::Clipper clipper; - - for(auto& path : shapes) { - auto clipperpath = Slic3rMultiPoint_to_ClipperPath(path.contour); - - if(!clipperpath.empty()) - valid &= clipper.AddPath(clipperpath, ptSubject, closed); - - auto clipperholes = Slic3rMultiPoints_to_ClipperPaths(path.holes); - - for(auto& hole : clipperholes) { - if(!hole.empty()) - valid &= clipper.AddPath(hole, ptSubject, closed); - } - } - - if(!valid) BOOST_LOG_TRIVIAL(warning) << "Unification of invalid shapes!"; - - ClipperLib::PolyTree result; - clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero); - - retv.reserve(static_cast(result.Total())); - - // Now we will recursively traverse the polygon tree and serialize it - // into an ExPolygon with holes. The polygon tree has the clipper-ish - // PolyTree structure which alternates its nodes as contours and holes - - // A "declaration" of function for traversing leafs which are holes - std::function processHole; - - // Process polygon which calls processHoles which than calls processPoly - // again until no leafs are left. - auto processPoly = [&retv, &processHole](ClipperLib::PolyNode *pptr) { - ExPolygon poly; - poly.contour.points = ClipperPath_to_Slic3rPolygon(pptr->Contour); - for(auto h : pptr->Childs) { processHole(h, poly); } - retv.push_back(poly); - }; - - // Body of the processHole function - processHole = [&processPoly](ClipperLib::PolyNode *pptr, ExPolygon& poly) - { - poly.holes.emplace_back(); - poly.holes.back().points = ClipperPath_to_Slic3rPolygon(pptr->Contour); - for(auto c : pptr->Childs) processPoly(c); - }; - - // Wrapper for traversing. - auto traverse = [&processPoly] (ClipperLib::PolyNode *node) - { - for(auto ch : node->Childs) { - processPoly(ch); - } - }; - - // Here is the actual traverse - traverse(&result); - - return retv; -} - -Polygons unify(const Polygons& shapes) { - using ClipperLib::ptSubject; - - bool closed = true; - bool valid = true; - - ClipperLib::Clipper clipper; - - for(auto& path : shapes) { - auto clipperpath = Slic3rMultiPoint_to_ClipperPath(path); - - if(!clipperpath.empty()) - valid &= clipper.AddPath(clipperpath, ptSubject, closed); - } - - if(!valid) BOOST_LOG_TRIVIAL(warning) << "Unification of invalid shapes!"; - - ClipperLib::Paths result; - clipper.Execute(ClipperLib::ctUnion, result, ClipperLib::pftNonZero); - - Polygons ret; - for (ClipperLib::Path &p : result) { - Polygon pp = ClipperPath_to_Slic3rPolygon(p); - if (!pp.is_clockwise()) ret.emplace_back(std::move(pp)); - } - - return ret; -} - -// Function to cut tiny connector cavities for a given polygon. The input poly -// will be offsetted by "padding" and small rectangle shaped cavities will be -// inserted along the perimeter in every "stride" distance. The stick rectangles -// will have a with about "stick_width". The input dimensions are in world -// measure, not the scaled clipper units. -void breakstick_holes(ExPolygon& poly, - double padding, - double stride, - double stick_width, - double penetration) -{ - // SVG svg("bridgestick_plate.svg"); - // svg.draw(poly); - - auto transf = [stick_width, penetration, padding, stride](Points &pts) { - // The connector stick will be a small rectangle with dimensions - // stick_width x (penetration + padding) to have some penetration - // into the input polygon. - - Points out; - out.reserve(2 * pts.size()); // output polygon points - - // stick bottom and right edge dimensions - double sbottom = scaled(stick_width); - double sright = scaled(penetration + padding); - - // scaled stride distance - double sstride = scaled(stride); - double t = 0; - - // process pairs of vertices as an edge, start with the last and - // first point - for (size_t i = pts.size() - 1, j = 0; j < pts.size(); i = j, ++j) { - // Get vertices and the direction vectors - const Point &a = pts[i], &b = pts[j]; - Vec2d dir = b.cast() - a.cast(); - double nrm = dir.norm(); - dir /= nrm; - Vec2d dirp(-dir(Y), dir(X)); - - // Insert start point - out.emplace_back(a); - - // dodge the start point, do not make sticks on the joins - while (t < sbottom) t += sbottom; - double tend = nrm - sbottom; - - while (t < tend) { // insert the stick on the polygon perimeter - - // calculate the stick rectangle vertices and insert them - // into the output. - Point p1 = a + (t * dir).cast(); - Point p2 = p1 + (sright * dirp).cast(); - Point p3 = p2 + (sbottom * dir).cast(); - Point p4 = p3 + (sright * -dirp).cast(); - out.insert(out.end(), {p1, p2, p3, p4}); - - // continue along the perimeter - t += sstride; - } - - t = t - nrm; - - // Insert edge endpoint - out.emplace_back(b); - } - - // move the new points - out.shrink_to_fit(); - pts.swap(out); - }; - - if(stride > 0.0 && stick_width > 0.0 && padding > 0.0) { - transf(poly.contour.points); - for (auto &h : poly.holes) transf(h.points); - } - - // svg.draw(poly); - // svg.Close(); -} - -/// This method will create a rounded edge around a flat polygon in 3d space. -/// 'base_plate' parameter is the target plate. -/// 'radius' is the radius of the edges. -/// 'degrees' is tells how much of a circle should be created as the rounding. -/// It should be in degrees, not radians. -/// 'ceilheight_mm' is the Z coordinate of the flat polygon in 3D space. -/// 'dir' Is the direction of the round edges: inward or outward -/// 'thr' Throws if a cancel signal was received -/// 'last_offset' An auxiliary output variable to save the last offsetted -/// version of 'base_plate' -/// 'last_height' An auxiliary output to save the last z coordinate of the -/// offsetted base_plate. In other words, where the rounded edges end. -Contour3D round_edges(const ExPolygon& base_plate, - double radius_mm, - double degrees, - double ceilheight_mm, - bool dir, - ThrowOnCancel thr, - ExPolygon& last_offset, double& last_height) -{ - auto ob = base_plate; - auto ob_prev = ob; - double wh = ceilheight_mm, wh_prev = wh; - Contour3D curvedwalls; - - int steps = 30; - double stepx = radius_mm / steps; - coord_t s = dir? 1 : -1; - degrees = std::fmod(degrees, 180); - - // we use sin for x distance because we interpret the angle starting from - // PI/2 - int tos = degrees < 90? - int(radius_mm*std::cos(degrees * PI / 180 - PI/2) / stepx) : steps; - - for(int i = 1; i <= tos; ++i) { - thr(); - - ob = base_plate; - - double r2 = radius_mm * radius_mm; - double xx = i*stepx; - double x2 = xx*xx; - double stepy = std::sqrt(r2 - x2); - - offset(ob, s * scaled(xx)); - wh = ceilheight_mm - radius_mm + stepy; - - Contour3D pwalls; - double prev_x = xx - (i - 1) * stepx; - pwalls = walls(ob.contour, ob_prev.contour, wh, wh_prev, s*prev_x, thr); - - curvedwalls.merge(pwalls); - ob_prev = ob; - wh_prev = wh; - } - - if(degrees > 90) { - double tox = radius_mm - radius_mm*std::cos(degrees * PI / 180 - PI/2); - int tos = int(tox / stepx); - - for(int i = 1; i <= tos; ++i) { - thr(); - ob = base_plate; - - double r2 = radius_mm * radius_mm; - double xx = radius_mm - i*stepx; - double x2 = xx*xx; - double stepy = std::sqrt(r2 - x2); - offset(ob, s * scaled(xx)); - wh = ceilheight_mm - radius_mm - stepy; - - Contour3D pwalls; - double prev_x = xx - radius_mm + (i - 1)*stepx; - pwalls = - walls(ob_prev.contour, ob.contour, wh_prev, wh, s*prev_x, thr); - - curvedwalls.merge(pwalls); - ob_prev = ob; - wh_prev = wh; - } - } - - last_offset = std::move(ob); - last_height = wh; - - return curvedwalls; -} - -inline Point centroid(Points& pp) { - Point c; - switch(pp.size()) { - case 0: break; - case 1: c = pp.front(); break; - case 2: c = (pp[0] + pp[1]) / 2; break; - default: { - auto MAX = std::numeric_limits::max(); - auto MIN = std::numeric_limits::min(); - Point min = {MAX, MAX}, max = {MIN, MIN}; - - for(auto& p : pp) { - if(p(0) < min(0)) min(0) = p(0); - if(p(1) < min(1)) min(1) = p(1); - if(p(0) > max(0)) max(0) = p(0); - if(p(1) > max(1)) max(1) = p(1); - } - c(0) = min(0) + (max(0) - min(0)) / 2; - c(1) = min(1) + (max(1) - min(1)) / 2; - - // TODO: fails for non convex cluster -// c = std::accumulate(pp.begin(), pp.end(), Point{0, 0}); -// x(c) /= coord_t(pp.size()); y(c) /= coord_t(pp.size()); - break; - } - } - - return c; -} - -inline Point centroid(const Polygon& poly) { - return poly.centroid(); -} - -/// A fake concave hull that is constructed by connecting separate shapes -/// with explicit bridges. Bridges are generated from each shape's centroid -/// to the center of the "scene" which is the centroid calculated from the shape -/// centroids (a star is created...) -Polygons concave_hull(const Polygons& polys, double maxd_mm, ThrowOnCancel thr) -{ - namespace bgi = boost::geometry::index; - using SpatElement = std::pair; - using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; - - if(polys.empty()) return Polygons(); - - const double max_dist = scaled(maxd_mm); - - Polygons punion = unify(polys); // could be redundant - - if(punion.size() == 1) return punion; - - // We get the centroids of all the islands in the 2D slice - Points centroids; centroids.reserve(punion.size()); - std::transform(punion.begin(), punion.end(), std::back_inserter(centroids), - [](const Polygon& poly) { return centroid(poly); }); - - SpatIndex ctrindex; - unsigned idx = 0; - for(const Point &ct : centroids) ctrindex.insert(std::make_pair(ct, idx++)); - - // Centroid of the centroids of islands. This is where the additional - // connector sticks are routed. - Point cc = centroid(centroids); - - punion.reserve(punion.size() + centroids.size()); - - idx = 0; - std::transform(centroids.begin(), centroids.end(), - std::back_inserter(punion), - [¢roids, &ctrindex, cc, max_dist, &idx, thr] - (const Point& c) - { - thr(); - double dx = x(c) - x(cc), dy = y(c) - y(cc); - double l = std::sqrt(dx * dx + dy * dy); - double nx = dx / l, ny = dy / l; - - Point& ct = centroids[idx]; - - std::vector result; - ctrindex.query(bgi::nearest(ct, 2), std::back_inserter(result)); - - double dist = max_dist; - for (const SpatElement &el : result) - if (el.second != idx) { - dist = Line(el.first, ct).length(); - break; - } - - idx++; - - if (dist >= max_dist) return Polygon(); - - Polygon r; - auto& ctour = r.points; - - ctour.reserve(3); - ctour.emplace_back(cc); - - Point d(scaled(nx), scaled(ny)); - ctour.emplace_back(c + Point( -y(d), x(d) )); - ctour.emplace_back(c + Point( y(d), -x(d) )); - offset(r, scaled(1.)); - - return r; - }); - - // This is unavoidable... - punion = unify(punion); - - return punion; -} - -void base_plate(const TriangleMesh & mesh, - ExPolygons & output, - const std::vector &heights, - ThrowOnCancel thrfn) -{ - if (mesh.empty()) return; - // m.require_shared_vertices(); // TriangleMeshSlicer needs this - TriangleMeshSlicer slicer(&mesh); - - std::vector out; out.reserve(heights.size()); - slicer.slice(heights, 0.f, &out, thrfn); - - size_t count = 0; for(auto& o : out) count += o.size(); - - // Now we have to unify all slice layers which can be an expensive operation - // so we will try to simplify the polygons - ExPolygons tmp; tmp.reserve(count); - for(ExPolygons& o : out) - for(ExPolygon& e : o) { - auto&& exss = e.simplify(scaled(0.1)); - for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep)); - } - - ExPolygons utmp = unify(tmp); - - for(auto& o : utmp) { - auto&& smp = o.simplify(scaled(0.1)); - output.insert(output.end(), smp.begin(), smp.end()); - } -} - -void base_plate(const TriangleMesh &mesh, - ExPolygons & output, - float h, - float layerh, - ThrowOnCancel thrfn) -{ - auto bb = mesh.bounding_box(); - float gnd = float(bb.min(Z)); - std::vector heights = {float(bb.min(Z))}; - - for(float hi = gnd + layerh; hi <= gnd + h; hi += layerh) - heights.emplace_back(hi); - - base_plate(mesh, output, heights, thrfn); -} - -Contour3D create_base_pool(const Polygons &ground_layer, - const ExPolygons &obj_self_pad = {}, - const PoolConfig& cfg = PoolConfig()) -{ - // for debugging: - // Benchmark bench; - // bench.start(); - - double mergedist = 2*(1.8*cfg.min_wall_thickness_mm + 4*cfg.edge_radius_mm)+ - cfg.max_merge_distance_mm; - - // Here we get the base polygon from which the pad has to be generated. - // We create an artificial concave hull from this polygon and that will - // serve as the bottom plate of the pad. We will offset this concave hull - // and then offset back the result with clipper with rounding edges ON. This - // trick will create a nice rounded pad shape. - Polygons concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel); - - const double thickness = cfg.min_wall_thickness_mm; - const double wingheight = cfg.min_wall_height_mm; - const double fullheight = wingheight + thickness; - const double slope = cfg.wall_slope; - const double wingdist = wingheight / std::tan(slope); - const double bottom_offs = (thickness + wingheight) / std::tan(slope); - - // scaled values - const coord_t s_thickness = scaled(thickness); - const coord_t s_eradius = scaled(cfg.edge_radius_mm); - const coord_t s_safety_dist = 2*s_eradius + coord_t(0.8*s_thickness); - const coord_t s_wingdist = scaled(wingdist); - const coord_t s_bottom_offs = scaled(bottom_offs); - - auto& thrcl = cfg.throw_on_cancel; - - Contour3D pool; - - for(Polygon& concaveh : concavehs) { - if(concaveh.points.empty()) return pool; - - // Here lies the trick that does the smoothing only with clipper offset - // calls. The offset is configured to round edges. Inner edges will - // be rounded because we offset twice: ones to get the outer (top) plate - // and again to get the inner (bottom) plate - auto outer_base = concaveh; - offset(outer_base, s_safety_dist + s_wingdist + s_thickness); - - ExPolygon bottom_poly; bottom_poly.contour = outer_base; - offset(bottom_poly, -s_bottom_offs); - - // Punching a hole in the top plate for the cavity - ExPolygon top_poly; - ExPolygon middle_base; - ExPolygon inner_base; - top_poly.contour = outer_base; - - if(wingheight > 0) { - inner_base.contour = outer_base; - offset(inner_base, -(s_thickness + s_wingdist + s_eradius)); - - middle_base.contour = outer_base; - offset(middle_base, -s_thickness); - top_poly.holes.emplace_back(middle_base.contour); - auto& tph = top_poly.holes.back().points; - std::reverse(tph.begin(), tph.end()); - } - - ExPolygon ob; ob.contour = outer_base; double wh = 0; - - // now we will calculate the angle or portion of the circle from - // pi/2 that will connect perfectly with the bottom plate. - // this is a tangent point calculation problem and the equation can - // be found for example here: - // http://www.ambrsoft.com/TrigoCalc/Circles2/CirclePoint/CirclePointDistance.htm - // the y coordinate would be: - // y = cy + (r^2*py - r*px*sqrt(px^2 + py^2 - r^2) / (px^2 + py^2) - // where px and py are the coordinates of the point outside the circle - // cx and cy are the circle center, r is the radius - // We place the circle center to (0, 0) in the calculation the make - // things easier. - // to get the angle we use arcsin function and subtract 90 degrees then - // flip the sign to get the right input to the round_edge function. - double r = cfg.edge_radius_mm; - double cy = 0; - double cx = 0; - double px = thickness + wingdist; - double py = r - fullheight; - - double pxcx = px - cx; - double pycy = py - cy; - double b_2 = pxcx*pxcx + pycy*pycy; - double r_2 = r*r; - double D = std::sqrt(b_2 - r_2); - double vy = (r_2*pycy - r*pxcx*D) / b_2; - double phi = -(std::asin(vy/r) * 180 / PI - 90); - - - // Generate the smoothed edge geometry - if(s_eradius > 0) pool.merge(round_edges(ob, - r, - phi, - 0, // z position of the input plane - true, - thrcl, - ob, wh)); - - // Now that we have the rounded edge connecting the top plate with - // the outer side walls, we can generate and merge the sidewall geometry - pool.merge(walls(ob.contour, bottom_poly.contour, wh, -fullheight, - bottom_offs, thrcl)); - - if(wingheight > 0) { - // Generate the smoothed edge geometry - wh = 0; - ob = middle_base; - if(s_eradius) pool.merge(round_edges(middle_base, - r, - phi - 90, // from tangent lines - 0, // z position of the input plane - false, - thrcl, - ob, wh)); - - // Next is the cavity walls connecting to the top plate's - // artificially created hole. - pool.merge(walls(inner_base.contour, ob.contour, -wingheight, - wh, -wingdist, thrcl)); - } - - if (cfg.embed_object) { - ExPolygons bttms = diff_ex(to_polygons(bottom_poly), - to_polygons(obj_self_pad)); - - assert(!bttms.empty()); - - std::sort(bttms.begin(), bttms.end(), - [](const ExPolygon& e1, const ExPolygon& e2) { - return e1.contour.area() > e2.contour.area(); - }); - - if(wingheight > 0) inner_base.holes = bttms.front().holes; - else top_poly.holes = bttms.front().holes; - - auto straight_walls = - [&pool](const Polygon &cntr, coord_t z_low, coord_t z_high) { - - auto lines = cntr.lines(); - - for (auto &l : lines) { - auto s = coord_t(pool.points.size()); - auto& pts = pool.points; - pts.emplace_back(unscale(l.a.x(), l.a.y(), z_low)); - pts.emplace_back(unscale(l.b.x(), l.b.y(), z_low)); - pts.emplace_back(unscale(l.a.x(), l.a.y(), z_high)); - pts.emplace_back(unscale(l.b.x(), l.b.y(), z_high)); - - pool.indices.emplace_back(s, s + 1, s + 3); - pool.indices.emplace_back(s, s + 3, s + 2); - } - }; - - coord_t z_lo = -scaled(fullheight), z_hi = -scaled(wingheight); - for (ExPolygon &ep : bttms) { - pool.merge(triangulate_expolygon_3d(ep, -fullheight, true)); - for (auto &h : ep.holes) straight_walls(h, z_lo, z_hi); - } - - // Skip the outer contour, triangulate the holes - for (auto it = std::next(bttms.begin()); it != bttms.end(); ++it) { - pool.merge(triangulate_expolygon_3d(*it, -wingheight)); - straight_walls(it->contour, z_lo, z_hi); - } - - } else { - // Now we need to triangulate the top and bottom plates as well as - // the cavity bottom plate which is the same as the bottom plate - // but it is elevated by the thickness. - - pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true)); - } - - pool.merge(triangulate_expolygon_3d(top_poly)); - - if(wingheight > 0) - pool.merge(triangulate_expolygon_3d(inner_base, -wingheight)); - - } - - return pool; -} - -void create_base_pool(const Polygons &ground_layer, TriangleMesh& out, - const ExPolygons &holes, const PoolConfig& cfg) -{ - - - // For debugging: - // bench.stop(); - // std::cout << "Pad creation time: " << bench.getElapsedSec() << std::endl; - // std::fstream fout("pad_debug.obj", std::fstream::out); - // if(fout.good()) pool.to_obj(fout); - - out.merge(mesh(create_base_pool(ground_layer, holes, cfg))); -} - -} -} diff --git a/src/libslic3r/SLA/SLABasePool.hpp b/src/libslic3r/SLA/SLABasePool.hpp deleted file mode 100644 index eec426bbf..000000000 --- a/src/libslic3r/SLA/SLABasePool.hpp +++ /dev/null @@ -1,92 +0,0 @@ -#ifndef SLABASEPOOL_HPP -#define SLABASEPOOL_HPP - -#include -#include -#include - -namespace Slic3r { - -class ExPolygon; -class Polygon; -using ExPolygons = std::vector; -using Polygons = std::vector; - -class TriangleMesh; - -namespace sla { - -using ThrowOnCancel = std::function; - -/// Calculate the polygon representing the silhouette from the specified height -void base_plate(const TriangleMesh& mesh, // input mesh - ExPolygons& output, // Output will be merged with - float samplingheight = 0.1f, // The height range to sample - float layerheight = 0.05f, // The sampling height - ThrowOnCancel thrfn = [](){}); // Will be called frequently - -void base_plate(const TriangleMesh& mesh, // input mesh - ExPolygons& output, // Output will be merged with - const std::vector&, // Exact Z levels to sample - ThrowOnCancel thrfn = [](){}); // Will be called frequently - -// Function to cut tiny connector cavities for a given polygon. The input poly -// will be offsetted by "padding" and small rectangle shaped cavities will be -// inserted along the perimeter in every "stride" distance. The stick rectangles -// will have a with about "stick_width". The input dimensions are in world -// measure, not the scaled clipper units. -void breakstick_holes(ExPolygon &poly, - double padding, - double stride, - double stick_width, - double penetration = 0.0); - -Polygons concave_hull(const Polygons& polys, double max_dist_mm = 50, - ThrowOnCancel throw_on_cancel = [](){}); - -struct PoolConfig { - double min_wall_thickness_mm = 2; - double min_wall_height_mm = 5; - double max_merge_distance_mm = 50; - double edge_radius_mm = 1; - double wall_slope = std::atan(1.0); // Universal constant for Pi/4 - struct EmbedObject { - double object_gap_mm = 0.5; - double stick_stride_mm = 10; - double stick_width_mm = 0.3; - double stick_penetration_mm = 0.1; - bool enabled = false; - operator bool() const { return enabled; } - } embed_object; - - ThrowOnCancel throw_on_cancel = [](){}; - - inline PoolConfig() {} - inline PoolConfig(double wt, double wh, double md, double er, double slope): - min_wall_thickness_mm(wt), - min_wall_height_mm(wh), - max_merge_distance_mm(md), - edge_radius_mm(er), - wall_slope(slope) {} -}; - -/// Calculate the pool for the mesh for SLA printing -void create_base_pool(const Polygons& base_plate, - TriangleMesh& output_mesh, - const ExPolygons& holes, - const PoolConfig& = PoolConfig()); - -/// Returns the elevation needed for compensating the pad. -inline double get_pad_elevation(const PoolConfig& cfg) { - return cfg.min_wall_thickness_mm; -} - -inline double get_pad_fullheight(const PoolConfig& cfg) { - return cfg.min_wall_height_mm + cfg.min_wall_thickness_mm; -} - -} - -} - -#endif // SLABASEPOOL_HPP diff --git a/src/libslic3r/SLA/SLABoilerPlate.hpp b/src/libslic3r/SLA/SLABoilerPlate.hpp index 86e90f3b7..d7ce26bb2 100644 --- a/src/libslic3r/SLA/SLABoilerPlate.hpp +++ b/src/libslic3r/SLA/SLABoilerPlate.hpp @@ -8,35 +8,19 @@ #include #include +#include "SLACommon.hpp" +#include "SLASpatIndex.hpp" + namespace Slic3r { namespace sla { -/// Get x and y coordinates (because we are eigenizing...) -inline coord_t x(const Point& p) { return p(0); } -inline coord_t y(const Point& p) { return p(1); } -inline coord_t& x(Point& p) { return p(0); } -inline coord_t& y(Point& p) { return p(1); } - -inline coordf_t x(const Vec3d& p) { return p(0); } -inline coordf_t y(const Vec3d& p) { return p(1); } -inline coordf_t z(const Vec3d& p) { return p(2); } -inline coordf_t& x(Vec3d& p) { return p(0); } -inline coordf_t& y(Vec3d& p) { return p(1); } -inline coordf_t& z(Vec3d& p) { return p(2); } - -inline coord_t& x(Vec3crd& p) { return p(0); } -inline coord_t& y(Vec3crd& p) { return p(1); } -inline coord_t& z(Vec3crd& p) { return p(2); } -inline coord_t x(const Vec3crd& p) { return p(0); } -inline coord_t y(const Vec3crd& p) { return p(1); } -inline coord_t z(const Vec3crd& p) { return p(2); } - /// Intermediate struct for a 3D mesh struct Contour3D { Pointf3s points; std::vector indices; - void merge(const Contour3D& ctr) { + Contour3D& merge(const Contour3D& ctr) + { auto s3 = coord_t(points.size()); auto s = indices.size(); @@ -44,21 +28,27 @@ struct Contour3D { indices.insert(indices.end(), ctr.indices.begin(), ctr.indices.end()); for(size_t n = s; n < indices.size(); n++) { - auto& idx = indices[n]; x(idx) += s3; y(idx) += s3; z(idx) += s3; + auto& idx = indices[n]; idx.x() += s3; idx.y() += s3; idx.z() += s3; } + + return *this; } - void merge(const Pointf3s& triangles) { + Contour3D& merge(const Pointf3s& triangles) + { const size_t offs = points.size(); points.insert(points.end(), triangles.begin(), triangles.end()); indices.reserve(indices.size() + points.size() / 3); - - for(int i = (int)offs; i < (int)points.size(); i += 3) + + for(int i = int(offs); i < int(points.size()); i += 3) indices.emplace_back(i, i + 1, i + 2); + + return *this; } // Write the index triangle structure to OBJ file for debugging purposes. - void to_obj(std::ostream& stream) { + void to_obj(std::ostream& stream) + { for(auto& p : points) { stream << "v " << p.transpose() << "\n"; } @@ -72,6 +62,31 @@ struct Contour3D { using ClusterEl = std::vector; using ClusteredPoints = std::vector; +// Clustering a set of points by the given distance. +ClusteredPoints cluster(const std::vector& indices, + std::function pointfn, + double dist, + unsigned max_points); + +ClusteredPoints cluster(const PointSet& points, + double dist, + unsigned max_points); + +ClusteredPoints cluster( + const std::vector& indices, + std::function pointfn, + std::function predicate, + unsigned max_points); + + +// Calculate the normals for the selected points (from 'points' set) on the +// mesh. This will call squared distance for each point. +PointSet normals(const PointSet& points, + const EigenMesh3D& mesh, + double eps = 0.05, // min distance from edges + std::function throw_on_cancel = [](){}, + const std::vector& selected_points = {}); + /// Mesh from an existing contour. inline TriangleMesh mesh(const Contour3D& ctour) { return {ctour.points, ctour.indices}; diff --git a/src/libslic3r/SLA/SLACommon.hpp b/src/libslic3r/SLA/SLACommon.hpp index d8e258035..3a3f8bdcc 100644 --- a/src/libslic3r/SLA/SLACommon.hpp +++ b/src/libslic3r/SLA/SLACommon.hpp @@ -175,6 +175,8 @@ public: } }; +using PointSet = Eigen::MatrixXd; + } // namespace sla } // namespace Slic3r diff --git a/src/libslic3r/SLA/SLAPad.cpp b/src/libslic3r/SLA/SLAPad.cpp new file mode 100644 index 000000000..f102f33ff --- /dev/null +++ b/src/libslic3r/SLA/SLAPad.cpp @@ -0,0 +1,865 @@ +#include "SLAPad.hpp" +#include "SLABoilerPlate.hpp" +#include "SLASpatIndex.hpp" + +#include "boost/log/trivial.hpp" +#include "SLABoostAdapter.hpp" +#include "ClipperUtils.hpp" +#include "Tesselate.hpp" +#include "MTUtils.hpp" + +// For debugging: +// #include +// #include +#include "SVG.hpp" + +#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 sla { + +namespace { + +/// This function will return a triangulation of a sheet connecting an upper +/// and a lower plate given as input polygons. It will not triangulate the +/// plates themselves only the sheet. The caller has to specify the lower and +/// upper z levels in world coordinates as well as the offset difference +/// between the sheets. If the lower_z_mm is higher than upper_z_mm or the +/// offset difference is negative, the resulting triangle orientation will be +/// reversed. +/// +/// IMPORTANT: This is not a universal triangulation algorithm. It assumes +/// that the lower and upper polygons are offsetted versions of the same +/// original polygon. In general, it assumes that one of the polygons is +/// completely inside the other. The offset difference is the reference +/// distance from the inner polygon's perimeter to the outer polygon's +/// perimeter. The real distance will be variable as the clipper offset has +/// different strategies (rounding, etc...). This algorithm should have +/// O(2n + 3m) complexity where n is the number of upper vertices and m is the +/// number of lower vertices. +Contour3D walls( + const Polygon &lower, + const Polygon &upper, + double lower_z_mm, + double upper_z_mm, + double offset_difference_mm, + ThrowOnCancel thr = [] {}) +{ + Contour3D ret; + + if(upper.points.size() < 3 || lower.size() < 3) return ret; + + // The concept of the algorithm is relatively simple. It will try to find + // the closest vertices from the upper and the lower polygon and use those + // as starting points. Then it will create the triangles sequentially using + // an edge from the upper polygon and a vertex from the lower or vice versa, + // depending on the resulting triangle's quality. + // The quality is measured by a scalar value. So far it looks like it is + // enough to derive it from the slope of the triangle's two edges connecting + // the upper and the lower part. A reference slope is calculated from the + // height and the offset difference. + + // Offset in the index array for the ceiling + const auto offs = upper.points.size(); + + // Shorthand for the vertex arrays + auto& upts = upper.points, &lpts = lower.points; + auto& rpts = ret.points; auto& ind = ret.indices; + + // If the Z levels are flipped, or the offset difference is negative, we + // will interpret that as the triangles normals should be inverted. + bool inverted = upper_z_mm < lower_z_mm || offset_difference_mm < 0; + + // Copy the points into the mesh, convert them from 2D to 3D + rpts.reserve(upts.size() + lpts.size()); + ind.reserve(2 * upts.size() + 2 * lpts.size()); + for (auto &p : upts) + rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), upper_z_mm); + for (auto &p : lpts) + rpts.emplace_back(unscaled(p.x()), unscaled(p.y()), lower_z_mm); + + // Create pointing indices into vertex arrays. u-upper, l-lower + size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1; + + // Simple squared distance calculation. + auto distfn = [](const Vec3d& p1, const Vec3d& p2) { + auto p = p1 - p2; return p.transpose() * p; + }; + + // We need to find the closest point on lower polygon to the first point on + // the upper polygon. These will be our starting points. + double distmin = std::numeric_limits::max(); + for(size_t l = lidx; l < rpts.size(); ++l) { + thr(); + double d = distfn(rpts[l], rpts[uidx]); + if(d < distmin) { lidx = l; distmin = d; } + } + + // Set up lnextidx to be ahead of lidx in cyclic mode + lnextidx = lidx + 1; + if(lnextidx == rpts.size()) lnextidx = offs; + + // This will be the flip switch to toggle between upper and lower triangle + // creation mode + enum class Proceed { + UPPER, // A segment from the upper polygon and one vertex from the lower + LOWER // A segment from the lower polygon and one vertex from the upper + } proceed = Proceed::UPPER; + + // Flags to help evaluating loop termination. + bool ustarted = false, lstarted = false; + + // The variables for the fitness values, one for the actual and one for the + // previous. + double current_fit = 0, prev_fit = 0; + + // Every triangle of the wall has two edges connecting the upper plate with + // the lower plate. From the length of these two edges and the zdiff we + // can calculate the momentary squared offset distance at a particular + // position on the wall. The average of the differences from the reference + // (squared) offset distance will give us the driving fitness value. + const double offsdiff2 = std::pow(offset_difference_mm, 2); + const double zdiff2 = std::pow(upper_z_mm - lower_z_mm, 2); + + // Mark the current vertex iterator positions. If the iterators return to + // the same position, the loop can be terminated. + size_t uendidx = uidx, lendidx = lidx; + + do { thr(); // check throw if canceled + + prev_fit = current_fit; + + switch(proceed) { // proceed depending on the current state + case Proceed::UPPER: + if(!ustarted || uidx != uendidx) { // there are vertices remaining + // Get the 3D vertices in order + const Vec3d& p_up1 = rpts[uidx]; + const Vec3d& p_low = rpts[lidx]; + const Vec3d& p_up2 = rpts[unextidx]; + + // Calculate fitness: the average of the two connecting edges + double a = offsdiff2 - (distfn(p_up1, p_low) - zdiff2); + double b = offsdiff2 - (distfn(p_up2, p_low) - zdiff2); + current_fit = (std::abs(a) + std::abs(b)) / 2; + + if(current_fit > prev_fit) { // fit is worse than previously + proceed = Proceed::LOWER; + } else { // good to go, create the triangle + inverted + ? ind.emplace_back(int(unextidx), int(lidx), int(uidx)) + : ind.emplace_back(int(uidx), int(lidx), int(unextidx)); + + // Increment the iterators, rotate if necessary + ++uidx; ++unextidx; + if(unextidx == offs) unextidx = 0; + if(uidx == offs) uidx = 0; + + ustarted = true; // mark the movement of the iterators + // so that the comparison to uendidx can be made correctly + } + } else proceed = Proceed::LOWER; + + break; + case Proceed::LOWER: + // Mode with lower segment, upper vertex. Same structure: + if(!lstarted || lidx != lendidx) { + const Vec3d& p_low1 = rpts[lidx]; + const Vec3d& p_low2 = rpts[lnextidx]; + const Vec3d& p_up = rpts[uidx]; + + double a = offsdiff2 - (distfn(p_up, p_low1) - zdiff2); + double b = offsdiff2 - (distfn(p_up, p_low2) - zdiff2); + current_fit = (std::abs(a) + std::abs(b)) / 2; + + if(current_fit > prev_fit) { + proceed = Proceed::UPPER; + } else { + inverted + ? ind.emplace_back(int(uidx), int(lnextidx), int(lidx)) + : ind.emplace_back(int(lidx), int(lnextidx), int(uidx)); + + ++lidx; ++lnextidx; + if(lnextidx == rpts.size()) lnextidx = offs; + if(lidx == rpts.size()) lidx = offs; + + lstarted = true; + } + } else proceed = Proceed::UPPER; + + break; + } // end of switch + } while(!ustarted || !lstarted || uidx != uendidx || lidx != lendidx); + + return ret; +} + +// Same as walls() but with identical higher and lower polygons. +Contour3D inline straight_walls(const Polygon &plate, + double lo_z, + double hi_z, + ThrowOnCancel thr) +{ + return walls(plate, plate, lo_z, hi_z, .0 /*offset_diff*/, thr); +} + +// As it shows, the current offset_ex in ClipperUtils hangs if used in jtRound +// mode +ClipperLib::Paths fast_offset(const ClipperLib::Paths &paths, + coord_t delta, + ClipperLib::JoinType jointype) +{ + using ClipperLib::ClipperOffset; + using ClipperLib::etClosedPolygon; + using ClipperLib::Paths; + using ClipperLib::Path; + + ClipperOffset offs; + offs.ArcTolerance = scaled(0.01); + + for (auto &p : paths) + // If the input is not at least a triangle, we can not do this algorithm + if(p.size() < 3) { + BOOST_LOG_TRIVIAL(error) << "Invalid geometry for offsetting!"; + return {}; + } + + offs.AddPaths(paths, jointype, etClosedPolygon); + + Paths result; + offs.Execute(result, static_cast(delta)); + + return result; +} + + +// Function to cut tiny connector cavities for a given polygon. The input poly +// will be offsetted by "padding" and small rectangle shaped cavities will be +// inserted along the perimeter in every "stride" distance. The stick rectangles +// will have a with about "stick_width". The input dimensions are in world +// measure, not the scaled clipper units. +void breakstick_holes(Points& pts, + double padding, + double stride, + double stick_width, + double penetration) +{ + if(stride <= EPSILON || stick_width <= EPSILON || padding <= EPSILON) + return; + + // SVG svg("bridgestick_plate.svg"); + // svg.draw(poly); + + // The connector stick will be a small rectangle with dimensions + // stick_width x (penetration + padding) to have some penetration + // into the input polygon. + + Points out; + out.reserve(2 * pts.size()); // output polygon points + + // stick bottom and right edge dimensions + double sbottom = scaled(stick_width); + double sright = scaled(penetration + padding); + + // scaled stride distance + double sstride = scaled(stride); + double t = 0; + + // process pairs of vertices as an edge, start with the last and + // first point + for (size_t i = pts.size() - 1, j = 0; j < pts.size(); i = j, ++j) { + // Get vertices and the direction vectors + const Point &a = pts[i], &b = pts[j]; + Vec2d dir = b.cast() - a.cast(); + double nrm = dir.norm(); + dir /= nrm; + Vec2d dirp(-dir(Y), dir(X)); + + // Insert start point + out.emplace_back(a); + + // dodge the start point, do not make sticks on the joins + while (t < sbottom) t += sbottom; + double tend = nrm - sbottom; + + while (t < tend) { // insert the stick on the polygon perimeter + + // calculate the stick rectangle vertices and insert them + // into the output. + Point p1 = a + (t * dir).cast(); + Point p2 = p1 + (sright * dirp).cast(); + Point p3 = p2 + (sbottom * dir).cast(); + Point p4 = p3 + (sright * -dirp).cast(); + out.insert(out.end(), {p1, p2, p3, p4}); + + // continue along the perimeter + t += sstride; + } + + t = t - nrm; + + // Insert edge endpoint + out.emplace_back(b); + } + + // move the new points + out.shrink_to_fit(); + pts.swap(out); +} + +void breakstick_holes(Points &pts, const PadConfig::EmbedObject &c) +{ + breakstick_holes(pts, c.object_gap_mm, c.stick_stride_mm, + c.stick_width_mm, c.stick_penetration_mm); +} + +ExPolygons breakstick_holes(const ExPolygons &input, + const PadConfig::EmbedObject &cfg) +{ + ExPolygons ret = offset_ex(input, scaled(cfg.object_gap_mm), ClipperLib::jtMiter, 1); + + for (ExPolygon &p : ret) { + breakstick_holes(p.contour.points, cfg); + for (auto &h : p.holes) breakstick_holes(h.points, cfg); + } + + return ret; +} + +/// A fake concave hull that is constructed by connecting separate shapes +/// with explicit bridges. Bridges are generated from each shape's centroid +/// to the center of the "scene" which is the centroid calculated from the shape +/// centroids (a star is created...) +class ConcaveHull { + Polygons m_polys; + + Point centroid(const Points& pp) const + { + Point c; + switch(pp.size()) { + case 0: break; + case 1: c = pp.front(); break; + case 2: c = (pp[0] + pp[1]) / 2; break; + default: { + auto MAX = std::numeric_limits::max(); + auto MIN = std::numeric_limits::min(); + Point min = {MAX, MAX}, max = {MIN, MIN}; + + for(auto& p : pp) { + if(p(0) < min(0)) min(0) = p(0); + if(p(1) < min(1)) min(1) = p(1); + if(p(0) > max(0)) max(0) = p(0); + if(p(1) > max(1)) max(1) = p(1); + } + c(0) = min(0) + (max(0) - min(0)) / 2; + c(1) = min(1) + (max(1) - min(1)) / 2; + break; + } + } + + return c; + } + + inline Point centroid(const Polygon &poly) const { return poly.centroid(); } + + Points calculate_centroids() const + { + // We get the centroids of all the islands in the 2D slice + Points centroids = reserve_vector(m_polys.size()); + std::transform(m_polys.begin(), m_polys.end(), + std::back_inserter(centroids), + [this](const Polygon &poly) { return centroid(poly); }); + + return centroids; + } + + void merge_polygons() { m_polys = union_(m_polys); } + + void add_connector_rectangles(const Points ¢roids, + coord_t max_dist, + ThrowOnCancel thr) + { + namespace bgi = boost::geometry::index; + using PointIndexElement = std::pair; + using PointIndex = bgi::rtree>; + + // Centroid of the centroids of islands. This is where the additional + // connector sticks are routed. + Point cc = centroid(centroids); + + PointIndex ctrindex; + unsigned idx = 0; + for(const Point &ct : centroids) + ctrindex.insert(std::make_pair(ct, idx++)); + + m_polys.reserve(m_polys.size() + centroids.size()); + + idx = 0; + for (const Point &c : centroids) { + thr(); + + double dx = c.x() - cc.x(), dy = c.y() - cc.y(); + double l = std::sqrt(dx * dx + dy * dy); + double nx = dx / l, ny = dy / l; + + const Point &ct = centroids[idx]; + + std::vector result; + ctrindex.query(bgi::nearest(ct, 2), std::back_inserter(result)); + + double dist = max_dist; + for (const PointIndexElement &el : result) + if (el.second != idx) { + dist = Line(el.first, ct).length(); + break; + } + + idx++; + + if (dist >= max_dist) return; + + Polygon r; + r.points.reserve(3); + r.points.emplace_back(cc); + + Point d(scaled(nx), scaled(ny)); + r.points.emplace_back(c + Point(-d.y(), d.x())); + r.points.emplace_back(c + Point(d.y(), -d.x())); + offset(r, scaled(1.)); + + m_polys.emplace_back(r); + } + } + +public: + + ConcaveHull(const ExPolygons& polys, double merge_dist, ThrowOnCancel thr) + : ConcaveHull{to_polygons(polys), merge_dist, thr} {} + + ConcaveHull(const Polygons& polys, double mergedist, ThrowOnCancel thr) + { + if(polys.empty()) return; + + m_polys = polys; + merge_polygons(); + + if(m_polys.size() == 1) return; + + Points centroids = calculate_centroids(); + + add_connector_rectangles(centroids, scaled(mergedist), thr); + + merge_polygons(); + } + + // const Polygons & polygons() const { return m_polys; } + + ExPolygons to_expolygons() const + { + auto ret = reserve_vector(m_polys.size()); + for (const Polygon &p : m_polys) ret.emplace_back(ExPolygon(p)); + return ret; + } + + void offset_waffle_style(coord_t delta) { + ClipperLib::Paths paths = Slic3rMultiPoints_to_ClipperPaths(m_polys); + paths = fast_offset(paths, 2 * delta, ClipperLib::jtRound); + paths = fast_offset(paths, -delta, ClipperLib::jtRound); + m_polys = ClipperPaths_to_Slic3rPolygons(paths); + } + + static inline coord_t get_waffle_offset(const PadConfig &c) + { + return scaled(c.brim_size_mm + c.wing_distance() + c.wall_thickness_mm); + } + + static inline double get_merge_distance(const PadConfig &c) + { + return 2. * (1.8 * c.wall_thickness_mm) + c.max_merge_dist_mm; + } +}; + +// Part of the pad configuration that is used for 3D geometry generation +struct PadConfig3D { + double thickness, height, wing_height, slope; + + explicit PadConfig3D(const PadConfig &cfg2d) + : thickness{cfg2d.wall_thickness_mm} + , height{cfg2d.full_height()} + , wing_height{cfg2d.wall_height_mm} + , slope{cfg2d.wall_slope} + {} + + inline double bottom_offset() const + { + return (thickness + wing_height) / std::tan(slope); + } +}; + +// Outer part of the skeleton is used to generate the waffled edges of the pad. +// Inner parts will not be waffled or offsetted. Inner parts are only used if +// pad is generated around the object and correspond to holes and inner polygons +// in the model blueprint. +struct PadSkeleton { ExPolygons inner, outer; }; + +PadSkeleton divide_blueprint(const ExPolygons &bp) +{ + ClipperLib::PolyTree ptree = union_pt(bp); + + PadSkeleton ret; + ret.inner.reserve(size_t(ptree.Total())); + ret.outer.reserve(size_t(ptree.Total())); + + for (ClipperLib::PolyTree::PolyNode *node : ptree.Childs) { + ExPolygon poly(ClipperPath_to_Slic3rPolygon(node->Contour)); + for (ClipperLib::PolyTree::PolyNode *child : node->Childs) { + if (child->IsHole()) { + poly.holes.emplace_back( + ClipperPath_to_Slic3rPolygon(child->Contour)); + + traverse_pt_unordered(child->Childs, &ret.inner); + } + else traverse_pt_unordered(child, &ret.inner); + } + + ret.outer.emplace_back(poly); + } + + return ret; +} + +// A helper class for storing polygons and maintaining a spatial index of their +// bounding boxes. +class Intersector { + BoxIndex m_index; + ExPolygons m_polys; + +public: + + // Add a new polygon to the index + void add(const ExPolygon &ep) + { + m_polys.emplace_back(ep); + m_index.insert(BoundingBox{ep}, unsigned(m_index.size())); + } + + // Check an arbitrary polygon for intersection with the indexed polygons + bool intersects(const ExPolygon &poly) + { + // Create a suitable query bounding box. + auto bb = poly.contour.bounding_box(); + + std::vector qres = m_index.query(bb, BoxIndex::qtIntersects); + + // Now check intersections on the actual polygons (not just the boxes) + bool is_overlap = false; + auto qit = qres.begin(); + while (!is_overlap && qit != qres.end()) + is_overlap = is_overlap || poly.overlaps(m_polys[(qit++)->second]); + + return is_overlap; + } +}; + +// This dummy intersector to implement the "force pad everywhere" feature +struct DummyIntersector +{ + inline void add(const ExPolygon &) {} + inline bool intersects(const ExPolygon &) { return true; } +}; + +template +class _AroundPadSkeleton : public PadSkeleton +{ + // A spatial index used to be able to efficiently find intersections of + // support polygons with the model polygons. + _Intersector m_intersector; + +public: + _AroundPadSkeleton(const ExPolygons &support_blueprint, + const ExPolygons &model_blueprint, + const PadConfig & cfg, + ThrowOnCancel thr) + { + // We need to merge the support and the model contours in a special + // way in which the model contours have to be substracted from the + // support contours. The pad has to have a hole in which the model can + // fit perfectly (thus the substraction -- diff_ex). Also, the pad has + // to be eliminated from areas where there is no need for a pad, due + // to missing supports. + + add_supports_to_index(support_blueprint); + + ConcaveHull fullcvh = + wafflized_concave_hull(support_blueprint, model_blueprint, cfg, thr); + + auto model_bp_sticks = + breakstick_holes(model_blueprint, cfg.embed_object); + + ExPolygons fullpad = diff_ex(fullcvh.to_expolygons(), model_bp_sticks); + + remove_redundant_parts(fullpad); + + PadSkeleton divided = divide_blueprint(fullpad); + outer = std::move(divided.outer); + inner = std::move(divided.inner); + } + +private: + + // Add the support blueprint to the search index to be queried later + void add_supports_to_index(const ExPolygons &supp_bp) + { + for (auto &ep : supp_bp) m_intersector.add(ep); + } + + // Create the wafflized pad around all object in the scene. This pad doesnt + // have any holes yet. + ConcaveHull wafflized_concave_hull(const ExPolygons &supp_bp, + const ExPolygons &model_bp, + const PadConfig &cfg, + ThrowOnCancel thr) + { + auto allin = reserve_vector(supp_bp.size() + model_bp.size()); + + for (auto &ep : supp_bp) allin.emplace_back(ep.contour); + for (auto &ep : model_bp) allin.emplace_back(ep.contour); + + ConcaveHull ret{allin, ConcaveHull::get_merge_distance(cfg), thr}; + ret.offset_waffle_style(ConcaveHull::get_waffle_offset(cfg)); + + return ret; + } + + // To remove parts of the pad skeleton which do not host any supports + void remove_redundant_parts(ExPolygons &parts) + { + auto endit = std::remove_if(parts.begin(), parts.end(), + [this](const ExPolygon &p) { + return !m_intersector.intersects(p); + }); + + parts.erase(endit, parts.end()); + } +}; + +using AroundPadSkeleton = _AroundPadSkeleton; +using BrimPadSkeleton = _AroundPadSkeleton; + +class BelowPadSkeleton : public PadSkeleton +{ +public: + BelowPadSkeleton(const ExPolygons &support_blueprint, + const ExPolygons &model_blueprint, + const PadConfig & cfg, + ThrowOnCancel thr) + { + outer.reserve(support_blueprint.size() + model_blueprint.size()); + + for (auto &ep : support_blueprint) outer.emplace_back(ep.contour); + for (auto &ep : model_blueprint) outer.emplace_back(ep.contour); + + ConcaveHull ochull{outer, ConcaveHull::get_merge_distance(cfg), thr}; + + ochull.offset_waffle_style(ConcaveHull::get_waffle_offset(cfg)); + outer = ochull.to_expolygons(); + } +}; + +// Offset the contour only, leave the holes untouched +template +ExPolygon offset_contour_only(const ExPolygon &poly, coord_t delta, Args...args) +{ + ExPolygons tmp = offset_ex(poly.contour, delta, args...); + + if (tmp.empty()) return {}; + + Polygons holes = poly.holes; + for (auto &h : holes) h.reverse(); + + tmp = diff_ex(to_polygons(tmp), holes); + + if (tmp.empty()) return {}; + + return tmp.front(); +} + +bool add_cavity(Contour3D &pad, ExPolygon &top_poly, const PadConfig3D &cfg, + ThrowOnCancel thr) +{ + auto logerr = []{BOOST_LOG_TRIVIAL(error)<<"Could not create pad cavity";}; + + double wing_distance = cfg.wing_height / std::tan(cfg.slope); + coord_t delta_inner = -scaled(cfg.thickness + wing_distance); + coord_t delta_middle = -scaled(cfg.thickness); + ExPolygon inner_base = offset_contour_only(top_poly, delta_inner); + ExPolygon middle_base = offset_contour_only(top_poly, delta_middle); + + if (inner_base.empty() || middle_base.empty()) { logerr(); return false; } + + ExPolygons pdiff = diff_ex(top_poly, middle_base.contour); + + if (pdiff.size() != 1) { logerr(); return false; } + + top_poly = pdiff.front(); + + double z_min = -cfg.wing_height, z_max = 0; + double offset_difference = -wing_distance; + pad.merge(walls(inner_base.contour, middle_base.contour, z_min, z_max, + offset_difference, thr)); + + pad.merge(triangulate_expolygon_3d(inner_base, z_min, NORMALS_UP)); + + return true; +} + +Contour3D create_outer_pad_geometry(const ExPolygons & skeleton, + const PadConfig3D &cfg, + ThrowOnCancel thr) +{ + Contour3D ret; + + for (const ExPolygon &pad_part : skeleton) { + ExPolygon top_poly{pad_part}; + ExPolygon bottom_poly = + offset_contour_only(pad_part, -scaled(cfg.bottom_offset())); + + if (bottom_poly.empty()) continue; + + double z_min = -cfg.height, z_max = 0; + ret.merge(walls(top_poly.contour, bottom_poly.contour, z_max, z_min, + cfg.bottom_offset(), thr)); + + if (cfg.wing_height > 0. && add_cavity(ret, top_poly, cfg, thr)) + z_max = -cfg.wing_height; + + for (auto &h : bottom_poly.holes) + ret.merge(straight_walls(h, z_max, z_min, thr)); + + ret.merge(triangulate_expolygon_3d(bottom_poly, z_min, NORMALS_DOWN)); + ret.merge(triangulate_expolygon_3d(top_poly, NORMALS_UP)); + } + + return ret; +} + +Contour3D create_inner_pad_geometry(const ExPolygons & skeleton, + const PadConfig3D &cfg, + ThrowOnCancel thr) +{ + Contour3D ret; + + double z_max = 0., z_min = -cfg.height; + for (const ExPolygon &pad_part : skeleton) { + ret.merge(straight_walls(pad_part.contour, z_max, z_min,thr)); + + for (auto &h : pad_part.holes) + ret.merge(straight_walls(h, z_max, z_min, thr)); + + ret.merge(triangulate_expolygon_3d(pad_part, z_min, NORMALS_DOWN)); + ret.merge(triangulate_expolygon_3d(pad_part, z_max, NORMALS_UP)); + } + + return ret; +} + +Contour3D create_pad_geometry(const PadSkeleton &skelet, + const PadConfig & cfg, + ThrowOnCancel thr) +{ +#ifndef NDEBUG + SVG svg("pad_skeleton.svg"); + svg.draw(skelet.outer, "green"); + svg.draw(skelet.inner, "blue"); + svg.Close(); +#endif + + PadConfig3D cfg3d(cfg); + return create_outer_pad_geometry(skelet.outer, cfg3d, thr) + .merge(create_inner_pad_geometry(skelet.inner, cfg3d, thr)); +} + +Contour3D create_pad_geometry(const ExPolygons &supp_bp, + const ExPolygons &model_bp, + const PadConfig & cfg, + ThrowOnCancel thr) +{ + PadSkeleton skelet; + + if (cfg.embed_object.enabled) { + if (cfg.embed_object.everywhere) + skelet = BrimPadSkeleton(supp_bp, model_bp, cfg, thr); + else + skelet = AroundPadSkeleton(supp_bp, model_bp, cfg, thr); + } else + skelet = BelowPadSkeleton(supp_bp, model_bp, cfg, thr); + + return create_pad_geometry(skelet, cfg, thr); +} + +} // namespace + +void pad_blueprint(const TriangleMesh & mesh, + ExPolygons & output, + const std::vector &heights, + ThrowOnCancel thrfn) +{ + if (mesh.empty()) return; + TriangleMeshSlicer slicer(&mesh); + + auto out = reserve_vector(heights.size()); + slicer.slice(heights, 0.f, &out, thrfn); + + size_t count = 0; + for(auto& o : out) count += o.size(); + + // Unification is expensive, a simplify also speeds up the pad generation + auto tmp = reserve_vector(count); + for(ExPolygons& o : out) + for(ExPolygon& e : o) { + auto&& exss = e.simplify(scaled(0.1)); + for(ExPolygon& ep : exss) tmp.emplace_back(std::move(ep)); + } + + ExPolygons utmp = union_ex(tmp); + + for(auto& o : utmp) { + auto&& smp = o.simplify(scaled(0.1)); + output.insert(output.end(), smp.begin(), smp.end()); + } +} + +void pad_blueprint(const TriangleMesh &mesh, + ExPolygons & output, + float h, + float layerh, + ThrowOnCancel thrfn) +{ + float gnd = float(mesh.bounding_box().min(Z)); + + std::vector slicegrid = grid(gnd, gnd + h, layerh); + pad_blueprint(mesh, output, slicegrid, thrfn); +} + +void create_pad(const ExPolygons &sup_blueprint, + const ExPolygons &model_blueprint, + TriangleMesh & out, + const PadConfig & cfg, + ThrowOnCancel thr) +{ + Contour3D t = create_pad_geometry(sup_blueprint, model_blueprint, cfg, thr); + out.merge(mesh(std::move(t))); +} + +std::string PadConfig::validate() const +{ + if (bottom_offset() > brim_size_mm + wing_distance()) + return L("Pad brim size is too low for the current slope."); + + return ""; +} + +}} // namespace Slic3r::sla diff --git a/src/libslic3r/SLA/SLAPad.hpp b/src/libslic3r/SLA/SLAPad.hpp new file mode 100644 index 000000000..96b5834c2 --- /dev/null +++ b/src/libslic3r/SLA/SLAPad.hpp @@ -0,0 +1,93 @@ +#ifndef SLABASEPOOL_HPP +#define SLABASEPOOL_HPP + +#include +#include +#include + +namespace Slic3r { + +class ExPolygon; +class Polygon; +using ExPolygons = std::vector; +using Polygons = std::vector; + +class TriangleMesh; + +namespace sla { + +using ThrowOnCancel = std::function; + +/// Calculate the polygon representing the silhouette. +void pad_blueprint( + const TriangleMesh &mesh, // input mesh + ExPolygons & output, // Output will be merged with + const std::vector &, // Exact Z levels to sample + ThrowOnCancel thrfn = [] {}); // Function that throws if cancel was requested + +void pad_blueprint( + const TriangleMesh &mesh, + ExPolygons & output, + float samplingheight = 0.1f, // The height range to sample + float layerheight = 0.05f, // The sampling height + ThrowOnCancel thrfn = [] {}); + +struct PadConfig { + double wall_thickness_mm = 1.; + double wall_height_mm = 1.; + double max_merge_dist_mm = 50; + double wall_slope = std::atan(1.0); // Universal constant for Pi/4 + double brim_size_mm = 1.6; + + struct EmbedObject { + double object_gap_mm = 1.; + double stick_stride_mm = 10.; + double stick_width_mm = 0.5; + double stick_penetration_mm = 0.1; + bool enabled = false; + bool everywhere = false; + operator bool() const { return enabled; } + } embed_object; + + inline PadConfig() = default; + inline PadConfig(double thickness, + double height, + double mergedist, + double slope) + : wall_thickness_mm(thickness) + , wall_height_mm(height) + , max_merge_dist_mm(mergedist) + , wall_slope(slope) + {} + + inline double bottom_offset() const + { + return (wall_thickness_mm + wall_height_mm) / std::tan(wall_slope); + } + + inline double wing_distance() const + { + return wall_height_mm / std::tan(wall_slope); + } + + inline double full_height() const + { + return wall_height_mm + wall_thickness_mm; + } + + /// Returns the elevation needed for compensating the pad. + inline double required_elevation() const { return wall_thickness_mm; } + + std::string validate() const; +}; + +void create_pad(const ExPolygons &support_contours, + const ExPolygons &model_contours, + TriangleMesh & output_mesh, + const PadConfig & = PadConfig(), + ThrowOnCancel throw_on_cancel = []{}); + +} // namespace sla +} // namespace Slic3r + +#endif // SLABASEPOOL_HPP diff --git a/src/libslic3r/SLA/SLASpatIndex.hpp b/src/libslic3r/SLA/SLASpatIndex.hpp index 90dcdc362..20b6fcd58 100644 --- a/src/libslic3r/SLA/SLASpatIndex.hpp +++ b/src/libslic3r/SLA/SLASpatIndex.hpp @@ -39,14 +39,19 @@ public: insert(std::make_pair(v, unsigned(idx))); } - std::vector query(std::function); - std::vector nearest(const Vec3d&, unsigned k); + std::vector query(std::function) const; + std::vector nearest(const Vec3d&, unsigned k) const; + std::vector query(const Vec3d &v, unsigned k) const // wrapper + { + return nearest(v, k); + } // For testing size_t size() const; bool empty() const { return size() == 0; } void foreach(std::function fn); + void foreach(std::function fn) const; }; using BoxIndexEl = std::pair; diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 99f7bc8b3..dda858453 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -7,7 +7,7 @@ #include "SLASupportTree.hpp" #include "SLABoilerPlate.hpp" #include "SLASpatIndex.hpp" -#include "SLABasePool.hpp" +#include "SLAPad.hpp" #include #include @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include //! macro used to mark string used at localization, @@ -91,27 +93,34 @@ template struct _ccr {}; template<> struct _ccr { - using Mutex = SpinMutex; + using SpinningMutex = tbb::spin_mutex; + using LockingMutex = tbb::mutex; template static inline void enumerate(It from, It to, Fn fn) { - using TN = size_t; - auto iN = to - from; - TN N = iN < 0 ? 0 : TN(iN); + auto iN = to - from; + size_t N = iN < 0 ? 0 : size_t(iN); - tbb::parallel_for(TN(0), N, [from, fn](TN n) { fn(*(from + n), n); }); + tbb::parallel_for(size_t(0), N, [from, fn](size_t n) { + fn(*(from + decltype(iN)(n)), n); + }); } }; template<> struct _ccr { - struct Mutex { inline void lock() {} inline void unlock() {} }; +private: + struct _Mtx { inline void lock() {} inline void unlock() {} }; + +public: + using SpinningMutex = _Mtx; + using LockingMutex = _Mtx; template static inline void enumerate(It from, It to, Fn fn) { - for (auto it = from; it != to; ++it) fn(*it, it - from); + for (auto it = from; it != to; ++it) fn(*it, size_t(it - from)); } }; @@ -132,6 +141,8 @@ template double distance(const Vec& pp1, const Vec& pp2) { return distance(p); } +namespace { + Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI), double fa=(2*PI/360)) { @@ -175,10 +186,11 @@ Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI), Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); vertices.emplace_back(Vec3d(b(0), b(1), z)); - if(sbegin == 0) - facets.emplace_back((i == 0) ? Vec3crd(coord_t(ring.size()), 0, 1) : - Vec3crd(id - 1, 0, id)); - ++ id; + if (sbegin == 0) + facets.emplace_back((i == 0) ? + Vec3crd(coord_t(ring.size()), 0, 1) : + Vec3crd(id - 1, 0, id)); + ++id; } // General case: insert and form facets for each step, @@ -229,7 +241,7 @@ Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI), // h: Height // ssteps: how many edges will create the base circle // sp: starting point -Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d sp = {0,0,0}) +Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp = {0,0,0}) { Contour3D ret; @@ -289,6 +301,8 @@ Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d sp = {0,0,0}) return ret; } +const constexpr long ID_UNSET = -1; + struct Head { Contour3D mesh; @@ -300,25 +314,25 @@ struct Head { double r_pin_mm = 0.5; double width_mm = 2; double penetration_mm = 0.5; - + // For identification purposes. This will be used as the index into the // container holding the head structures. See SLASupportTree::Impl - long id = -1; + long id = ID_UNSET; // If there is a pillar connecting to this head, then the id will be set. - long pillar_id = -1; + long pillar_id = ID_UNSET; - inline void invalidate() { id = -1; } + inline void invalidate() { id = ID_UNSET; } inline bool is_valid() const { return id >= 0; } Head(double r_big_mm, double r_small_mm, double length_mm, double penetration, - Vec3d direction = {0, 0, -1}, // direction (normal to the dull end ) - Vec3d offset = {0, 0, 0}, // displacement + const Vec3d &direction = {0, 0, -1}, // direction (normal to the dull end) + const Vec3d &offset = {0, 0, 0}, // displacement const size_t circlesteps = 45): - steps(circlesteps), dir(direction), tr(offset), + steps(circlesteps), dir(direction), tr(offset), r_back_mm(r_big_mm), r_pin_mm(r_small_mm), width_mm(length_mm), penetration_mm(penetration) { @@ -347,7 +361,7 @@ struct Head { auto&& s1 = sphere(r_big_mm, make_portion(0, PI/2 + phi), detail); auto&& s2 = sphere(r_small_mm, make_portion(PI/2 + phi, PI), detail); - for(auto& p : s2.points) z(p) += h; + for(auto& p : s2.points) p.z() += h; mesh.merge(s1); mesh.merge(s2); @@ -373,7 +387,7 @@ struct Head { // To simplify further processing, we translate the mesh so that the // last vertex of the pointing sphere (the pinpoint) will be at (0,0,0) - for(auto& p : mesh.points) z(p) -= (h + r_small_mm - penetration_mm); + for(auto& p : mesh.points) p.z() -= (h + r_small_mm - penetration_mm); } void transform() @@ -393,11 +407,6 @@ struct Head { return 2 * r_pin_mm + width_mm + 2*r_back_mm - penetration_mm; } - static double fullwidth(const SupportConfig& cfg) { - return 2 * cfg.head_front_radius_mm + cfg.head_width_mm + - 2 * cfg.head_back_radius_mm - cfg.head_penetration_mm; - } - Vec3d junction_point() const { return tr + ( 2 * r_pin_mm + width_mm + r_back_mm - penetration_mm)*dir; } @@ -414,7 +423,7 @@ struct Junction { size_t steps = 45; Vec3d pos; - long id = -1; + long id = ID_UNSET; Junction(const Vec3d& tr, double r_mm, size_t stepnum = 45): r(r_mm), steps(stepnum), pos(tr) @@ -432,11 +441,11 @@ struct Pillar { Vec3d endpt; double height = 0; - long id = -1; + long id = ID_UNSET; // If the pillar connects to a head, this is the id of that head bool starts_from_head = true; // Could start from a junction as well - long start_junction_id = -1; + long start_junction_id = ID_UNSET; // How many bridges are connected to this pillar unsigned bridges = 0; @@ -461,22 +470,24 @@ struct Pillar { } } - Pillar(const Junction& junc, const Vec3d& endp): - Pillar(junc.pos, endp, junc.r, junc.steps){} + Pillar(const Junction &junc, const Vec3d &endp) + : Pillar(junc.pos, endp, junc.r, junc.steps) + {} - Pillar(const Head& head, const Vec3d& endp, double radius = 1): - Pillar(head.junction_point(), endp, head.request_pillar_radius(radius), - head.steps) + Pillar(const Head &head, const Vec3d &endp, double radius = 1) + : Pillar(head.junction_point(), endp, + head.request_pillar_radius(radius), head.steps) + {} + + inline Vec3d startpoint() const { - } - - inline Vec3d startpoint() const { return {endpt(X), endpt(Y), endpt(Z) + height}; } inline const Vec3d& endpoint() const { return endpt; } - Pillar& add_base(double baseheight = 3, double radius = 2) { + Pillar& add_base(double baseheight = 3, double radius = 2) + { if(baseheight <= 0) return *this; if(baseheight > height) baseheight = height; @@ -523,8 +534,6 @@ struct Pillar { indices.emplace_back(offs, offs + last, lcenter); return *this; } - - bool has_base() const { return !base.points.empty(); } }; // A Bridge between two pillars (with junction endpoints) @@ -532,9 +541,9 @@ struct Bridge { Contour3D mesh; double r = 0.8; - long id = -1; - long start_jid = -1; - long end_jid = -1; + long id = ID_UNSET; + long start_jid = ID_UNSET; + long end_jid = ID_UNSET; // We should reduce the radius a tiny bit to help the convex hull algorithm Bridge(const Vec3d& j1, const Vec3d& j2, @@ -550,17 +559,13 @@ struct Bridge { auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir); for(auto& p : mesh.points) p = quater * p + j1; } - - Bridge(const Junction& j1, const Junction& j2, double r_mm = 0.8): - Bridge(j1.pos, j2.pos, r_mm, j1.steps) {} - }; // A bridge that spans from model surface to model surface with small connecting // edges on the endpoints. Used for headless support points. struct CompactBridge { Contour3D mesh; - long id = -1; + long id = ID_UNSET; CompactBridge(const Vec3d& sp, const Vec3d& ep, @@ -594,123 +599,28 @@ struct CompactBridge { // A wrapper struct around the base pool (pad) struct Pad { TriangleMesh tmesh; - PoolConfig cfg; + PadConfig cfg; double zlevel = 0; Pad() = default; - Pad(const TriangleMesh& support_mesh, - const ExPolygons& modelbase, - double ground_level, - const PoolConfig& pcfg) : - cfg(pcfg), - zlevel(ground_level + - sla::get_pad_fullheight(pcfg) - - sla::get_pad_elevation(pcfg)) + Pad(const TriangleMesh &support_mesh, + const ExPolygons & model_contours, + double ground_level, + const PadConfig & pcfg, + ThrowOnCancel thr) + : cfg(pcfg) + , zlevel(ground_level + pcfg.full_height() - pcfg.required_elevation()) { - Polygons basep; - auto &thr = cfg.throw_on_cancel; - thr(); - // Get a sample for the pad from the support mesh - { - ExPolygons platetmp; + ExPolygons sup_contours; - float zstart = float(zlevel); - float zend = zstart + float(get_pad_fullheight(pcfg) + EPSILON); + float zstart = float(zlevel); + float zend = zstart + float(pcfg.full_height() + EPSILON); - base_plate(support_mesh, platetmp, grid(zstart, zend, 0.1f), thr); - - // We don't need no... holes control... - for (const ExPolygon &bp : platetmp) - basep.emplace_back(std::move(bp.contour)); - } - - if(pcfg.embed_object) { - - // If the zero elevation mode is ON, we need to process the model - // base silhouette. Create the offsetted version and punch the - // breaksticks across its perimeter. - - ExPolygons modelbase_offs = modelbase; - - if (pcfg.embed_object.object_gap_mm > 0.0) - modelbase_offs - = offset_ex(modelbase_offs, - float(scaled(pcfg.embed_object.object_gap_mm))); - - // Create a spatial index of the support silhouette polygons. - // This will be used to check for intersections with the model - // silhouette polygons. If there is no intersection, then a certain - // part of the pad is redundant as it does not host any supports. - BoxIndex bindex; - { - unsigned idx = 0; - for(auto &bp : basep) { - auto bb = bp.bounding_box(); - bb.offset(float(scaled(pcfg.min_wall_thickness_mm))); - bindex.insert(bb, idx++); - } - } - - ExPolygons concaveh = offset_ex( - concave_hull(basep, pcfg.max_merge_distance_mm, thr), - scaled(pcfg.min_wall_thickness_mm)); - - // Punching the breaksticks across the offsetted polygon perimeters - auto pad_stickholes = reserve_vector(modelbase.size()); - for(auto& poly : modelbase_offs) { - - bool overlap = false; - for (const ExPolygon &p : concaveh) - overlap = overlap || poly.overlaps(p); - - auto bb = poly.contour.bounding_box(); - bb.offset(scaled(pcfg.min_wall_thickness_mm)); - - std::vector qres = - bindex.query(bb, BoxIndex::qtIntersects); - - if (!qres.empty() || overlap) { - - // The model silhouette polygon 'poly' HAS an intersection - // with the support silhouettes. Include this polygon - // in the pad holes with the breaksticks and merge the - // original (offsetted) version with the rest of the pad - // base plate. - - basep.emplace_back(poly.contour); - - // The holes of 'poly' will become positive parts of the - // pad, so they has to be checked for intersections as well - // and erased if there is no intersection with the supports - auto it = poly.holes.begin(); - while(it != poly.holes.end()) { - if (bindex.query(it->bounding_box(), - BoxIndex::qtIntersects).empty()) - it = poly.holes.erase(it); - else - ++it; - } - - // Punch the breaksticks - sla::breakstick_holes( - poly, - pcfg.embed_object.object_gap_mm, // padding - pcfg.embed_object.stick_stride_mm, - pcfg.embed_object.stick_width_mm, - pcfg.embed_object.stick_penetration_mm); - - pad_stickholes.emplace_back(poly); - } - } - - create_base_pool(basep, tmesh, pad_stickholes, cfg); - } else { - for (const ExPolygon &bp : modelbase) basep.emplace_back(bp.contour); - create_base_pool(basep, tmesh, {}, cfg); - } + pad_blueprint(support_mesh, sup_contours, grid(zstart, zend, 0.1f), thr); + create_pad(sup_contours, model_contours, tmesh, pcfg); tmesh.translate(0, 0, float(zlevel)); if (!tmesh.empty()) tmesh.require_shared_vertices(); @@ -720,43 +630,18 @@ struct Pad { }; // The minimum distance for two support points to remain valid. -static const double /*constexpr*/ D_SP = 0.1; +const double /*constexpr*/ D_SP = 0.1; + +} // namespace enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers X, Y, Z }; -// Calculate the normals for the selected points (from 'points' set) on the -// mesh. This will call squared distance for each point. -PointSet normals(const PointSet& points, - const EigenMesh3D& mesh, - double eps = 0.05, // min distance from edges - std::function throw_on_cancel = [](){}, - const std::vector& selected_points = {}); - inline Vec2d to_vec2(const Vec3d& v3) { return {v3(X), v3(Y)}; } -bool operator==(const PointIndexEl& e1, const PointIndexEl& e2) { - return e1.second == e2.second; -} - -// Clustering a set of points by the given distance. -ClusteredPoints cluster(const std::vector& indices, - std::function pointfn, - double dist, - unsigned max_points); - -ClusteredPoints cluster(const PointSet& points, - double dist, - unsigned max_points); - -ClusteredPoints cluster( - const std::vector& indices, - std::function pointfn, - std::function predicate, - unsigned max_points); // This class will hold the support tree meshes with some additional bookkeeping // as well. Various parts of the support geometry are stored separately and are @@ -775,20 +660,20 @@ class SLASupportTree::Impl { // For heads it is beneficial to use the same IDs as for the support points. std::vector m_heads; std::vector m_head_indices; - std::vector m_pillars; std::vector m_junctions; std::vector m_bridges; - std::vector m_compact_bridges; + std::vector m_compact_bridges; + Pad m_pad; + Controller m_ctl; - Pad m_pad; - - using Mutex = ccr::Mutex; + using Mutex = ccr::SpinningMutex; + mutable TriangleMesh m_meshcache; mutable Mutex m_mutex; - mutable TriangleMesh meshcache; mutable bool meshcache_valid = false; - mutable double model_height = 0; // the full height of the model + mutable bool m_meshcache_valid = false; + mutable double m_model_height = 0; // the full height of the model public: double ground_level = 0; @@ -807,7 +692,7 @@ public: if (id >= m_head_indices.size()) m_head_indices.resize(id + 1); m_head_indices[id] = m_heads.size() - 1; - meshcache_valid = false; + m_meshcache_valid = false; return m_heads.back(); } @@ -825,7 +710,7 @@ public: pillar.start_junction_id = head.id; pillar.starts_from_head = true; - meshcache_valid = false; + m_meshcache_valid = false; return m_pillars.back(); } @@ -854,22 +739,10 @@ public: Pillar& pillar = m_pillars.back(); pillar.id = long(m_pillars.size() - 1); pillar.starts_from_head = false; - meshcache_valid = false; + m_meshcache_valid = false; return m_pillars.back(); } - const Head& pillar_head(long pillar_id) const - { - std::lock_guard lk(m_mutex); - assert(pillar_id >= 0 && pillar_id < long(m_pillars.size())); - - const Pillar& p = m_pillars[size_t(pillar_id)]; - assert(p.starts_from_head && p.start_junction_id >= 0); - assert(size_t(p.start_junction_id) < m_head_indices.size()); - - return m_heads[m_head_indices[p.start_junction_id]]; - } - const Pillar& head_pillar(unsigned headid) const { std::lock_guard lk(m_mutex); @@ -886,7 +759,7 @@ public: std::lock_guard lk(m_mutex); m_junctions.emplace_back(std::forward(args)...); m_junctions.back().id = long(m_junctions.size() - 1); - meshcache_valid = false; + m_meshcache_valid = false; return m_junctions.back(); } @@ -895,7 +768,7 @@ public: std::lock_guard lk(m_mutex); m_bridges.emplace_back(std::forward(args)...); m_bridges.back().id = long(m_bridges.size() - 1); - meshcache_valid = false; + m_meshcache_valid = false; return m_bridges.back(); } @@ -904,7 +777,7 @@ public: std::lock_guard lk(m_mutex); m_compact_bridges.emplace_back(std::forward(args)...); m_compact_bridges.back().id = long(m_compact_bridges.size() - 1); - meshcache_valid = false; + m_meshcache_valid = false; return m_compact_bridges.back(); } @@ -913,7 +786,7 @@ public: std::lock_guard lk(m_mutex); assert(id < m_head_indices.size()); - meshcache_valid = false; + m_meshcache_valid = false; return m_heads[m_head_indices[id]]; } @@ -933,9 +806,10 @@ public: const Pad &create_pad(const TriangleMesh &object_supports, const ExPolygons & modelbase, - const PoolConfig & cfg) + const PadConfig & cfg) { - m_pad = Pad(object_supports, modelbase, ground_level, cfg); + m_pad = Pad{object_supports, modelbase, ground_level, cfg, m_ctl.cancelfn}; + return m_pad; } @@ -946,7 +820,7 @@ public: // WITHOUT THE PAD!!! const TriangleMesh &merged_mesh() const { - if (meshcache_valid) return meshcache; + if (m_meshcache_valid) return m_meshcache; Contour3D merged; @@ -978,39 +852,39 @@ public: if (m_ctl.stopcondition()) { // In case of failure we have to return an empty mesh - meshcache = TriangleMesh(); - return meshcache; + m_meshcache = TriangleMesh(); + return m_meshcache; } - meshcache = mesh(merged); + m_meshcache = mesh(merged); // The mesh will be passed by const-pointer to TriangleMeshSlicer, // which will need this. - if (!meshcache.empty()) meshcache.require_shared_vertices(); + if (!m_meshcache.empty()) m_meshcache.require_shared_vertices(); - BoundingBoxf3 &&bb = meshcache.bounding_box(); - model_height = bb.max(Z) - bb.min(Z); + BoundingBoxf3 &&bb = m_meshcache.bounding_box(); + m_model_height = bb.max(Z) - bb.min(Z); - meshcache_valid = true; - return meshcache; + m_meshcache_valid = true; + return m_meshcache; } // WITH THE PAD double full_height() const { if (merged_mesh().empty() && !pad().empty()) - return get_pad_fullheight(pad().cfg); + return pad().cfg.full_height(); double h = mesh_height(); - if (!pad().empty()) h += sla::get_pad_elevation(pad().cfg); + if (!pad().empty()) h += pad().cfg.required_elevation(); return h; } // WITHOUT THE PAD!!! double mesh_height() const { - if (!meshcache_valid) merged_mesh(); - return model_height; + if (!m_meshcache_valid) merged_mesh(); + return m_model_height; } // Intended to be called after the generation is fully complete @@ -1032,11 +906,11 @@ public: // vector of point indices. template long cluster_centroid(const ClusterEl& clust, - std::function pointfn, + const std::function &pointfn, DistFn df) { switch(clust.size()) { - case 0: /* empty cluster */ return -1; + case 0: /* empty cluster */ return ID_UNSET; case 1: /* only one element */ return 0; case 2: /* if two elements, there is no center */ return 0; default: ; @@ -1116,8 +990,53 @@ class SLASupportTree::Algorithm { ThrowOnCancel m_thr; // A spatial index to easily find strong pillars to connect to. - PointIndex m_pillar_index; - + + class PillarIndex { + PointIndex m_index; + mutable ccr::LockingMutex m_mutex; + + public: + + template inline void guarded_insert(Args&&...args) + { + std::lock_guard lck(m_mutex); + m_index.insert(std::forward(args)...); + } + + template + inline std::vector guarded_query(Args&&...args) const + { + std::lock_guard lck(m_mutex); + return m_index.query(std::forward(args)...); + } + + template inline void insert(Args&&...args) + { + m_index.insert(std::forward(args)...); + } + + template + inline std::vector query(Args&&...args) const + { + return m_index.query(std::forward(args)...); + } + + template inline void foreach(Fn fn) { m_index.foreach(fn); } + template inline void guarded_foreach(Fn fn) + { + std::lock_guard lck(m_mutex); + m_index.foreach(fn); + } + + PointIndex guarded_clone() + { + std::lock_guard lck(m_mutex); + return m_index; + } + + } m_pillar_index; + + inline double ray_mesh_intersect(const Vec3d& s, const Vec3d& dir) { @@ -1382,7 +1301,7 @@ class SLASupportTree::Algorithm { // Align to center double available_dist = (startz - endz); double rounds = std::floor(available_dist / std::abs(zstep)); - startz -= 0.5 * (available_dist - rounds * std::abs(zstep));; + startz -= 0.5 * (available_dist - rounds * std::abs(zstep)); } auto pcm = m_cfg.pillar_connection_mode; @@ -1507,9 +1426,9 @@ class SLASupportTree::Algorithm { } bool search_pillar_and_connect(const Head& head) { - PointIndex spindex = m_pillar_index; + PointIndex spindex = m_pillar_index.guarded_clone(); - long nearest_id = -1; + long nearest_id = ID_UNSET; Vec3d querypoint = head.junction_point(); @@ -1530,8 +1449,8 @@ class SLASupportTree::Algorithm { auto nearpillarID = unsigned(nearest_id); if(nearpillarID < m_result.pillarcount()) { if(!connect_to_nearpillar(head, nearpillarID)) { - nearest_id = -1; // continue searching - spindex.remove(ne); // without the current pillar + nearest_id = ID_UNSET; // continue searching + spindex.remove(ne); // without the current pillar } } } @@ -1545,7 +1464,7 @@ class SLASupportTree::Algorithm { void create_ground_pillar(const Vec3d &jp, const Vec3d &sourcedir, double radius, - int head_id = -1) + long head_id = ID_UNSET) { // People were killed for this number (seriously) static const double SQR2 = std::sqrt(2.0); @@ -1554,7 +1473,7 @@ class SLASupportTree::Algorithm { double gndlvl = m_result.ground_level; Vec3d endp = {jp(X), jp(Y), gndlvl}; double sd = m_cfg.pillar_base_safety_distance_mm; - int pillar_id = -1; + long pillar_id = ID_UNSET; double min_dist = sd + m_cfg.base_radius_mm + EPSILON; double dist = 0; bool can_add_base = true; @@ -1567,7 +1486,7 @@ class SLASupportTree::Algorithm { // the ground level only. normal_mode = false; - double mv = min_dist - dist; + double mind = min_dist - dist; double azimuth = std::atan2(sourcedir(Y), sourcedir(X)); double sinpolar = std::sin(PI - m_cfg.bridge_slope); double cospolar = std::cos(PI - m_cfg.bridge_slope); @@ -1584,14 +1503,14 @@ class SLASupportTree::Algorithm { auto result = solver.optimize_max( [this, dir, jp, gndlvl](double mv) { - Vec3d endp = jp + SQR2 * mv * dir; - endp(Z) = gndlvl; - return std::sqrt(m_mesh.squared_distance(endp)); + Vec3d endpt = jp + SQR2 * mv * dir; + endpt(Z) = gndlvl; + return std::sqrt(m_mesh.squared_distance(endpt)); }, - initvals(mv), bound(0.0, 2 * min_dist)); + initvals(mind), bound(0.0, 2 * min_dist)); - mv = std::get<0>(result.optimum); - endp = jp + SQR2 * mv * dir; + mind = std::get<0>(result.optimum); + endp = jp + SQR2 * mind * dir; Vec3d pgnd = {endp(X), endp(Y), gndlvl}; can_add_base = result.score > min_dist; @@ -1651,7 +1570,7 @@ class SLASupportTree::Algorithm { } if(pillar_id >= 0) // Save the pillar endpoint in the spatial index - m_pillar_index.insert(endp, pillar_id); + m_pillar_index.guarded_insert(endp, unsigned(pillar_id)); } public: @@ -1717,9 +1636,9 @@ public: using libnest2d::opt::GeneticOptimizer; using libnest2d::opt::StopCriteria; - ccr::Mutex mutex; + ccr::SpinningMutex mutex; auto addfn = [&mutex](PtIndices &container, unsigned val) { - std::lock_guard lk(mutex); + std::lock_guard lk(mutex); container.emplace_back(val); }; @@ -1727,8 +1646,8 @@ public: [this, &nmls, addfn](unsigned fidx, size_t i) { m_thr(); - - auto n = nmls.row(i); + + auto n = nmls.row(Eigen::Index(i)); // for all normals we generate the spherical coordinates and // saturate the polar angle to 45 degrees from the bottom then @@ -1786,19 +1705,20 @@ public: auto oresult = solver.optimize_max( [this, pin_r, w, hp](double plr, double azm) { - auto n = Vec3d(std::cos(azm) * std::sin(plr), - std::sin(azm) * std::sin(plr), - std::cos(plr)).normalized(); + auto dir = Vec3d(std::cos(azm) * std::sin(plr), + std::sin(azm) * std::sin(plr), + std::cos(plr)).normalized(); double score = pinhead_mesh_intersect( - hp, n, pin_r, m_cfg.head_back_radius_mm, w); + hp, dir, pin_r, m_cfg.head_back_radius_mm, w); return score; }, initvals(polar, azimuth), // start with what we have - bound(3*PI/4, PI), // Must not exceed the tilt limit - bound(-PI, PI) // azimuth can be a full search - ); + bound(3 * PI / 4, + PI), // Must not exceed the tilt limit + bound(-PI, PI) // azimuth can be a full search + ); if(oresult.score > w) { polar = std::get<0>(oresult.optimum); @@ -1921,7 +1841,9 @@ public: ClusterEl cl_centroids; cl_centroids.reserve(m_pillar_clusters.size()); - for(auto& cl : m_pillar_clusters) { m_thr(); + for(auto& cl : m_pillar_clusters) { + m_thr(); + // place all the centroid head positions into the index. We // will query for alternative pillar positions. If a sidehead // cannot connect to the cluster centroid, we have to search @@ -1957,7 +1879,8 @@ public: // sidepoints with the cluster centroid (which is a ground pillar) // or a nearby pillar if the centroid is unreachable. size_t ci = 0; - for(auto cl : m_pillar_clusters) { m_thr(); + for(auto cl : m_pillar_clusters) { + m_thr(); auto cidx = cl_centroids[ci++]; @@ -2015,7 +1938,7 @@ public: }; std::vector modelpillars; - ccr::Mutex mutex; + ccr::SpinningMutex mutex; // TODO: connect these to the ground pillars if possible ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(), @@ -2161,7 +2084,7 @@ public: pill.base = tailhead.mesh; // Experimental: add the pillar to the index for cascading - std::lock_guard lk(mutex); + std::lock_guard lk(mutex); modelpillars.emplace_back(unsigned(pill.id)); return; } @@ -2184,13 +2107,10 @@ public: // in O(log) or even constant time with a set or an unordered set of hash // values uniquely representing a pair of integers. The order of numbers // within the pair should not matter, it has the same unique hash. - template static I pairhash(I a, I b) + template static IntegerOnly pairhash(I a, I b) { using std::ceil; using std::log2; using std::max; using std::min; - static_assert(std::is_integral::value, - "This function works only for integral types."); - I g = min(a, b), l = max(a, b); auto bits_g = g ? int(ceil(log2(g))) : 0; @@ -2414,7 +2334,8 @@ public: // We will sink the pins into the model surface for a distance of 1/3 of // the pin radius - for(unsigned i : m_iheadless) { m_thr(); + for(unsigned i : m_iheadless) { + m_thr(); const auto R = double(m_support_pts[i].head_front_radius); const double HWIDTH_MM = R/3; @@ -2617,8 +2538,9 @@ std::vector SLASupportTree::slice( auto bb = pad_mesh.bounding_box(); auto maxzit = std::upper_bound(grid.begin(), grid.end(), bb.max.z()); - - auto padgrid = reserve_vector(grid.end() - maxzit); + + long cap = grid.end() - maxzit; + auto padgrid = reserve_vector(size_t(cap > 0 ? cap : 0)); std::copy(grid.begin(), maxzit, std::back_inserter(padgrid)); TriangleMeshSlicer pad_slicer(&pad_mesh); @@ -2645,7 +2567,7 @@ std::vector SLASupportTree::slice( } const TriangleMesh &SLASupportTree::add_pad(const ExPolygons& modelbase, - const PoolConfig& pcfg) const + const PadConfig& pcfg) const { return m_impl->create_pad(merged_mesh(), modelbase, pcfg).tmesh; } @@ -2670,6 +2592,9 @@ SLASupportTree::SLASupportTree(const std::vector &points, generate(points, emesh, cfg, ctl); } +SLASupportTree::SLASupportTree(SLASupportTree &&o) = default; +SLASupportTree &SLASupportTree::operator=(SLASupportTree &&o) = default; + SLASupportTree::~SLASupportTree() {} } diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index d7f15c17b..d2797f5f6 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -9,7 +9,6 @@ #include "SLACommon.hpp" - namespace Slic3r { // Needed types from Point.hpp @@ -85,6 +84,11 @@ struct SupportConfig { // The shortest distance between a pillar base perimeter from the model // body. This is only useful when elevation is set to zero. double pillar_base_safety_distance_mm = 0.5; + + double head_fullwidth() const { + return 2 * head_front_radius_mm + head_width_mm + + 2 * head_back_radius_mm - head_penetration_mm; + } // ///////////////////////////////////////////////////////////////////////// // Compile time configuration values (candidates for runtime) @@ -104,7 +108,7 @@ struct SupportConfig { static const unsigned max_bridges_on_pillar; }; -struct PoolConfig; +struct PadConfig; /// A Control structure for the support calculation. Consists of the status /// indicator callback and the stop condition predicate. @@ -124,17 +128,6 @@ struct Controller { std::function cancelfn = [](){}; }; -using PointSet = Eigen::MatrixXd; - -//EigenMesh3D to_eigenmesh(const TriangleMesh& m); - -// needed for find best rotation -//EigenMesh3D to_eigenmesh(const ModelObject& model); - -// Simple conversion of 'vector of points' to an Eigen matrix -//PointSet to_point_set(const std::vector&); - - /* ************************************************************************** */ /// The class containing mesh data for the generated supports. @@ -174,6 +167,9 @@ public: SLASupportTree(const SLASupportTree&) = delete; SLASupportTree& operator=(const SLASupportTree&) = delete; + + SLASupportTree(SLASupportTree &&o); + SLASupportTree &operator=(SLASupportTree &&o); ~SLASupportTree(); @@ -192,7 +188,7 @@ public: /// Otherwise, the modelbase will be unified with the base plate calculated /// from the supports. const TriangleMesh& add_pad(const ExPolygons& modelbase, - const PoolConfig& pcfg) const; + const PadConfig& pcfg) const; /// Get the pad geometry const TriangleMesh& get_pad() const; diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp index 95d451271..05f8b1984 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -77,7 +77,7 @@ bool PointIndex::remove(const PointIndexEl& el) } std::vector -PointIndex::query(std::function fn) +PointIndex::query(std::function fn) const { namespace bgi = boost::geometry::index; @@ -86,7 +86,7 @@ PointIndex::query(std::function fn) return ret; } -std::vector PointIndex::nearest(const Vec3d &el, unsigned k = 1) +std::vector PointIndex::nearest(const Vec3d &el, unsigned k = 1) const { namespace bgi = boost::geometry::index; std::vector ret; ret.reserve(k); @@ -104,6 +104,11 @@ void PointIndex::foreach(std::function fn) for(auto& el : m_impl->m_store) fn(el); } +void PointIndex::foreach(std::function fn) const +{ + for(const auto &el : m_impl->m_store) fn(el); +} + /* ************************************************************************** * BoxIndex implementation * ************************************************************************** */ @@ -274,6 +279,8 @@ double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const { * Misc functions * ****************************************************************************/ +namespace { + bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2, double eps = 0.05) { @@ -289,11 +296,13 @@ template double distance(const Vec& pp1, const Vec& pp2) { return std::sqrt(p.transpose() * p); } +} + PointSet normals(const PointSet& points, const EigenMesh3D& mesh, double eps, std::function thr, // throw on cancel - const std::vector& pt_indices = {}) + const std::vector& pt_indices) { if(points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0) return {}; @@ -419,9 +428,17 @@ PointSet normals(const PointSet& points, return ret; } + namespace bgi = boost::geometry::index; using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >; +namespace { + +bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2) +{ + return e1.second < e2.second; +}; + ClusteredPoints cluster(Index3D &sindex, unsigned max_points, std::function( @@ -433,25 +450,22 @@ ClusteredPoints cluster(Index3D &sindex, // each other std::function group = [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster) - { + { for(auto& p : pts) { std::vector tmp = qfn(sindex, p); - auto cmp = [](const PointIndexEl& e1, const PointIndexEl& e2){ - return e1.second < e2.second; - }; - - std::sort(tmp.begin(), tmp.end(), cmp); + + std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements); Elems newpts; std::set_difference(tmp.begin(), tmp.end(), cluster.begin(), cluster.end(), - std::back_inserter(newpts), cmp); + std::back_inserter(newpts), cmp_ptidx_elements); int c = max_points && newpts.size() + cluster.size() > max_points? int(max_points - cluster.size()) : int(newpts.size()); cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c); - std::sort(cluster.begin(), cluster.end(), cmp); + std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements); if(!newpts.empty() && (!max_points || cluster.size() < max_points)) group(newpts, cluster); @@ -479,7 +493,6 @@ ClusteredPoints cluster(Index3D &sindex, return result; } -namespace { std::vector distance_queryfn(const Index3D& sindex, const PointIndexEl& p, double dist, @@ -496,7 +509,8 @@ std::vector distance_queryfn(const Index3D& sindex, return tmp; } -} + +} // namespace // Clustering a set of points by the given criteria ClusteredPoints cluster( @@ -558,5 +572,5 @@ ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points) }); } -} -} +} // namespace sla +} // namespace Slic3r diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 46d039c1f..d7083baeb 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1,6 +1,6 @@ #include "SLAPrint.hpp" #include "SLA/SLASupportTree.hpp" -#include "SLA/SLABasePool.hpp" +#include "SLA/SLAPad.hpp" #include "SLA/SLAAutoSupports.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" @@ -53,7 +53,7 @@ const std::array OBJ_STEP_LEVELS = 30, // slaposObjectSlice, 20, // slaposSupportPoints, 10, // slaposSupportTree, - 10, // slaposBasePool, + 10, // slaposPad, 30, // slaposSliceSupports, }; @@ -64,7 +64,7 @@ std::string OBJ_STEP_LABELS(size_t idx) case slaposObjectSlice: return L("Slicing model"); case slaposSupportPoints: return L("Generating support points"); case slaposSupportTree: return L("Generating support tree"); - case slaposBasePool: return L("Generating pad"); + case slaposPad: return L("Generating pad"); case slaposSliceSupports: return L("Slicing supports"); default:; } @@ -612,12 +612,13 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { return scfg; } -sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { - sla::PoolConfig::EmbedObject ret; +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(); @@ -628,17 +629,15 @@ sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { return ret; } -sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) { - sla::PoolConfig pcfg; +sla::PadConfig make_pad_cfg(const SLAPrintObjectConfig& c) { + sla::PadConfig pcfg; - pcfg.min_wall_thickness_mm = c.pad_wall_thickness.getFloat(); + pcfg.wall_thickness_mm = c.pad_wall_thickness.getFloat(); pcfg.wall_slope = c.pad_wall_slope.getFloat() * PI / 180.0; - // We do not support radius for now - pcfg.edge_radius_mm = 0.0; //c.pad_edge_radius.getFloat(); - - pcfg.max_merge_distance_mm = c.pad_max_merge_distance.getFloat(); - pcfg.min_wall_height_mm = c.pad_wall_height.getFloat(); + 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); @@ -646,6 +645,13 @@ sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& 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 @@ -663,17 +669,12 @@ std::string SLAPrint::validate() const sla::SupportConfig cfg = make_support_cfg(po->config()); - double pinhead_width = - 2 * cfg.head_front_radius_mm + - cfg.head_width_mm + - 2 * cfg.head_back_radius_mm - - cfg.head_penetration_mm; - double elv = cfg.object_elevation_mm; - - sla::PoolConfig::EmbedObject builtinpad = builtin_pad_cfg(po->config()); - if(supports_en && !builtinpad.enabled && elv < pinhead_width ) + sla::PadConfig padcfg = make_pad_cfg(po->config()); + sla::PadConfig::EmbedObject &builtinpad = padcfg.embed_object; + + if(supports_en && !builtinpad.enabled && elv < cfg.head_fullwidth()) return L( "Elevation is too low for object. Use the \"Pad around " "object\" feature to print the object without elevation."); @@ -686,6 +687,9 @@ std::string SLAPrint::validate() const "distance' has to be greater than the 'Pad object gap' " "parameter to avoid this."); } + + std::string pval = padcfg.validate(); + if (!pval.empty()) return pval; } double expt_max = m_printer_config.max_exposure_time.getFloat(); @@ -876,8 +880,7 @@ void SLAPrint::process() // Construction of this object does the calculation. this->throw_if_canceled(); - SLAAutoSupports auto_supports(po.transformed_mesh(), - po.m_supportdata->emesh, + SLAAutoSupports auto_supports(po.m_supportdata->emesh, po.get_model_slices(), heights, config, @@ -908,23 +911,13 @@ void SLAPrint::process() // 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 gnd = po.m_supportdata->emesh.ground_level(); - auto & pts = po.m_supportdata->support_points; double tolerance = po.config().pad_enable.getBool() ? po.m_config.pad_wall_thickness.getFloat() : po.m_config.support_base_height.getFloat(); - // get iterator to the reorganized vector end - auto endit = std::remove_if( - pts.begin(), - pts.end(), - [tolerance, gnd](const sla::SupportPoint &sp) { - double diff = std::abs(gnd - double(sp.pos(Z))); - return diff <= tolerance; - }); - - // erase all elements after the new end - pts.erase(endit, pts.end()); + remove_bottom_points(po.m_supportdata->support_points, + po.m_supportdata->emesh.ground_level(), + tolerance); } }; @@ -933,11 +926,11 @@ void SLAPrint::process() { if(!po.m_supportdata) return; - sla::PoolConfig pcfg = make_pool_config(po.m_config); + sla::PadConfig pcfg = make_pad_cfg(po.m_config); if (pcfg.embed_object) po.m_supportdata->emesh.ground_level_offset( - pcfg.min_wall_thickness_mm); + pcfg.wall_thickness_mm); if(!po.m_config.supports_enable.getBool()) { @@ -993,7 +986,7 @@ void SLAPrint::process() }; // This step generates the sla base pad - auto base_pool = [this](SLAPrintObject& po) { + 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) @@ -1001,10 +994,10 @@ void SLAPrint::process() if(po.m_config.pad_enable.getBool()) { // Get the distilled pad configuration from the config - sla::PoolConfig pcfg = make_pool_config(po.m_config); + sla::PadConfig pcfg = make_pad_cfg(po.m_config); ExPolygons bp; // This will store the base plate of the pad. - double pad_h = sla::get_pad_fullheight(pcfg); + double pad_h = pcfg.full_height(); const TriangleMesh &trmesh = po.transformed_mesh(); // This call can get pretty time consuming @@ -1015,15 +1008,19 @@ void SLAPrint::process() // 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::base_plate(trmesh, - bp, - float(pad_h), - float(po.m_config.layer_height.getFloat()), - thrfn); + sla::pad_blueprint(trmesh, bp, float(pad_h), + float(po.m_config.layer_height.getFloat()), + thrfn); } - pcfg.throw_on_cancel = thrfn; po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg); + auto &pad_mesh = po.m_supportdata->support_tree_ptr->get_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(); } @@ -1478,12 +1475,12 @@ void SLAPrint::process() slaposFn pobj_program[] = { - slice_model, support_points, support_tree, base_pool, slice_supports + slice_model, support_points, support_tree, generate_pad, slice_supports }; // We want to first process all objects... std::vector level1_obj_steps = { - slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposBasePool + slaposObjectSlice, slaposSupportPoints, slaposSupportTree, slaposPad }; // and then slice all supports to allow preview to be displayed ASAP @@ -1730,6 +1727,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vectorinvalidate_all_steps(); } else if (step == slaposSupportPoints) { - invalidated |= this->invalidate_steps({ slaposSupportTree, slaposBasePool, slaposSliceSupports }); + invalidated |= this->invalidate_steps({ slaposSupportTree, slaposPad, slaposSliceSupports }); invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval); } else if (step == slaposSupportTree) { - invalidated |= this->invalidate_steps({ slaposBasePool, slaposSliceSupports }); + invalidated |= this->invalidate_steps({ slaposPad, slaposSliceSupports }); invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval); - } else if (step == slaposBasePool) { + } else if (step == slaposPad) { invalidated |= this->invalidate_steps({slaposSliceSupports}); invalidated |= m_print->invalidate_step(slapsMergeSlicesAndEval); } else if (step == slaposSliceSupports) { @@ -1813,8 +1812,8 @@ double SLAPrintObject::get_elevation() const { // its walls but currently it is half of its thickness. Whatever it // will be in the future, we provide the config to the get_pad_elevation // method and we will have the correct value - sla::PoolConfig pcfg = make_pool_config(m_config); - if(!pcfg.embed_object) ret += sla::get_pad_elevation(pcfg); + sla::PadConfig pcfg = make_pad_cfg(m_config); + if(!pcfg.embed_object) ret += pcfg.required_elevation(); } return ret; @@ -1825,7 +1824,7 @@ double SLAPrintObject::get_current_elevation() const if (is_zero_elevation(m_config)) return 0.; bool has_supports = is_step_done(slaposSupportTree); - bool has_pad = is_step_done(slaposBasePool); + bool has_pad = is_step_done(slaposPad); if(!has_supports && !has_pad) return 0; @@ -1896,7 +1895,7 @@ bool SLAPrintObject::has_mesh(SLAPrintObjectStep step) const switch (step) { case slaposSupportTree: return ! this->support_mesh().empty(); - case slaposBasePool: + case slaposPad: return ! this->pad_mesh().empty(); default: return false; @@ -1908,7 +1907,7 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const switch (step) { case slaposSupportTree: return this->support_mesh(); - case slaposBasePool: + case slaposPad: return this->pad_mesh(); default: return TriangleMesh(); diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index a2bc1325a..a2cb517b2 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -21,7 +21,7 @@ enum SLAPrintObjectStep : unsigned int { slaposObjectSlice, slaposSupportPoints, slaposSupportTree, - slaposBasePool, + slaposPad, slaposSliceSupports, slaposCount }; @@ -54,7 +54,7 @@ public: bool is_left_handed() const { return m_left_handed; } struct Instance { - Instance(ObjectID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {} + Instance(ObjectID inst_id, const Point &shft, float rot) : instance_id(inst_id), shift(shft), rotation(rot) {} bool operator==(const Instance &rhs) const { return this->instance_id == rhs.instance_id && this->shift == rhs.shift && this->rotation == rhs.rotation; } // ID of the corresponding ModelInstance. ObjectID instance_id; diff --git a/src/libslic3r/Tesselate.hpp b/src/libslic3r/Tesselate.hpp index 02e86eb33..2dbe6caa1 100644 --- a/src/libslic3r/Tesselate.hpp +++ b/src/libslic3r/Tesselate.hpp @@ -10,12 +10,15 @@ namespace Slic3r { class ExPolygon; typedef std::vector ExPolygons; -extern std::vector triangulate_expolygon_3d (const ExPolygon &poly, coordf_t z = 0, bool flip = false); -extern std::vector triangulate_expolygons_3d(const ExPolygons &polys, coordf_t z = 0, bool flip = false); -extern std::vector triangulate_expolygon_2d (const ExPolygon &poly, bool flip = false); -extern std::vector triangulate_expolygons_2d(const ExPolygons &polys, bool flip = false); -extern std::vector triangulate_expolygon_2f (const ExPolygon &poly, bool flip = false); -extern std::vector triangulate_expolygons_2f(const ExPolygons &polys, bool flip = false); +const bool constexpr NORMALS_UP = false; +const bool constexpr NORMALS_DOWN = true; + +extern std::vector triangulate_expolygon_3d (const ExPolygon &poly, coordf_t z = 0, bool flip = NORMALS_UP); +extern std::vector triangulate_expolygons_3d(const ExPolygons &polys, coordf_t z = 0, bool flip = NORMALS_UP); +extern std::vector triangulate_expolygon_2d (const ExPolygon &poly, bool flip = NORMALS_UP); +extern std::vector triangulate_expolygons_2d(const ExPolygons &polys, bool flip = NORMALS_UP); +extern std::vector triangulate_expolygon_2f (const ExPolygon &poly, bool flip = NORMALS_UP); +extern std::vector triangulate_expolygons_2f(const ExPolygons &polys, bool flip = NORMALS_UP); } // namespace Slic3r diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 0e127a868..086ba7a74 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -417,7 +417,7 @@ void GLVolume::render(int color_id, int detection_id, int worldmatrix_id) const } bool GLVolume::is_sla_support() const { return this->composite_id.volume_id == -int(slaposSupportTree); } -bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposBasePool); } +bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposPad); } std::vector GLVolumeCollection::load_object( const ModelObject *model_object, @@ -501,7 +501,7 @@ void GLVolumeCollection::load_object_auxiliary( TriangleMesh convex_hull = mesh.convex_hull_3d(); for (const std::pair& instance_idx : instances) { const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first]; - this->volumes.emplace_back(new GLVolume((milestone == slaposBasePool) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); + this->volumes.emplace_back(new GLVolume((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); GLVolume& v = *this->volumes.back(); v.indexed_vertex_array.load_mesh(mesh); v.indexed_vertex_array.finalize_geometry(opengl_initialized); diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 9ca08809b..f76f752f0 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -349,15 +349,18 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config) toggle_field("pad_wall_thickness", pad_en); toggle_field("pad_wall_height", pad_en); + toggle_field("pad_brim_size", pad_en); toggle_field("pad_max_merge_distance", pad_en); // toggle_field("pad_edge_radius", supports_en); toggle_field("pad_wall_slope", pad_en); toggle_field("pad_around_object", pad_en); + toggle_field("pad_around_object_everywhere", pad_en); bool zero_elev = config->opt_bool("pad_around_object") && pad_en; toggle_field("support_object_elevation", supports_en && !zero_elev); toggle_field("pad_object_gap", zero_elev); + toggle_field("pad_around_object_everywhere", zero_elev); toggle_field("pad_object_connector_stride", zero_elev); toggle_field("pad_object_connector_width", zero_elev); toggle_field("pad_object_connector_penetration", zero_elev); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ff70fd451..04ef6fd42 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1767,7 +1767,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // SLA steps to pull the preview meshes for. typedef std::array SLASteps; - SLASteps sla_steps = { slaposSupportTree, slaposBasePool }; + SLASteps sla_steps = { slaposSupportTree, slaposPad }; struct SLASupportState { std::array::value> step; }; @@ -5340,8 +5340,8 @@ void GLCanvas3D::_load_sla_shells() m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id(); if (obj->is_step_done(slaposSupportTree) && obj->has_mesh(slaposSupportTree)) add_volume(*obj, -int(slaposSupportTree), instance, obj->support_mesh(), GLVolume::SLA_SUPPORT_COLOR, true); - if (obj->is_step_done(slaposBasePool) && obj->has_mesh(slaposBasePool)) - add_volume(*obj, -int(slaposBasePool), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false); + if (obj->is_step_done(slaposPad) && obj->has_mesh(slaposPad)) + add_volume(*obj, -int(slaposPad), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false); } double shift_z = obj->get_current_elevation(); for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) { diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 7c36f3665..6f39db86d 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -261,7 +261,7 @@ bool MainFrame::can_export_supports() const const PrintObjects& objects = m_plater->sla_print().objects(); for (const SLAPrintObject* object : objects) { - if (object->has_mesh(slaposBasePool) || object->has_mesh(slaposSupportTree)) + if (object->has_mesh(slaposPad) || object->has_mesh(slaposSupportTree)) { can_export = true; break; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index b172ad489..447ba37a3 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4467,10 +4467,10 @@ void Plater::export_stl(bool extended, bool selection_only) bool is_left_handed = object->is_left_handed(); TriangleMesh pad_mesh; - bool has_pad_mesh = object->has_mesh(slaposBasePool); + bool has_pad_mesh = object->has_mesh(slaposPad); if (has_pad_mesh) { - pad_mesh = object->get_mesh(slaposBasePool); + pad_mesh = object->get_mesh(slaposPad); pad_mesh.transform(mesh_trafo_inv); } @@ -4646,7 +4646,7 @@ void Plater::reslice_SLA_supports(const ModelObject &object, bool postpone_error // Otherwise calculate everything, but start with the provided object. if (!this->p->background_processing_enabled()) { task.single_model_instance_only = true; - task.to_object_step = slaposBasePool; + task.to_object_step = slaposPad; } this->p->background_process.set_task(task); // and let the background processing start. diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index d2503d349..a74e8cc5f 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -476,11 +476,13 @@ const std::vector& Preset::sla_print_options() "pad_enable", "pad_wall_thickness", "pad_wall_height", + "pad_brim_size", "pad_max_merge_distance", // "pad_edge_radius", "pad_wall_slope", "pad_object_gap", "pad_around_object", + "pad_around_object_everywhere", "pad_object_connector_stride", "pad_object_connector_width", "pad_object_connector_penetration", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index d3f331442..b375aa6a5 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3539,12 +3539,14 @@ void TabSLAPrint::build() optgroup->append_single_option_line("pad_enable"); optgroup->append_single_option_line("pad_wall_thickness"); optgroup->append_single_option_line("pad_wall_height"); + optgroup->append_single_option_line("pad_brim_size"); optgroup->append_single_option_line("pad_max_merge_distance"); // TODO: Disabling this parameter for the beta release // optgroup->append_single_option_line("pad_edge_radius"); optgroup->append_single_option_line("pad_wall_slope"); optgroup->append_single_option_line("pad_around_object"); + optgroup->append_single_option_line("pad_around_object_everywhere"); optgroup->append_single_option_line("pad_object_gap"); optgroup->append_single_option_line("pad_object_connector_stride"); optgroup->append_single_option_line("pad_object_connector_width");