From ddd0a9abb69ca7715641efaaa34dbbf030b552ef Mon Sep 17 00:00:00 2001 From: tamasmeszaros <meszaros.q@gmail.com> Date: Tue, 11 Jun 2019 12:40:07 +0200 Subject: [PATCH 01/14] SPE-742: Builtin pad feature in zero elevation mode. --- .clang-format | 2 +- sandboxes/slabasebed/slabasebed.cpp | 32 +-- src/libslic3r/SLA/SLABasePool.cpp | 321 ++++++++++++++++++---- src/libslic3r/SLA/SLABasePool.hpp | 17 +- src/libslic3r/SLA/SLACommon.hpp | 7 + src/libslic3r/SLA/SLASupportTree.cpp | 391 ++++++++++++++++++++------- src/libslic3r/SLA/SLASupportTree.hpp | 21 +- src/libslic3r/SLAPrint.cpp | 126 ++++++--- 8 files changed, 685 insertions(+), 232 deletions(-) diff --git a/.clang-format b/.clang-format index d5740f689..9a2c3ce1d 100644 --- a/.clang-format +++ b/.clang-format @@ -46,7 +46,7 @@ BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeComma BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true -ColumnLimit: 75 +ColumnLimit: 78 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true diff --git a/sandboxes/slabasebed/slabasebed.cpp b/sandboxes/slabasebed/slabasebed.cpp index 0c34eb9f5..5393f61fd 100644 --- a/sandboxes/slabasebed/slabasebed.cpp +++ b/sandboxes/slabasebed/slabasebed.cpp @@ -15,7 +15,8 @@ const std::string USAGE_STR = { namespace Slic3r { namespace sla { -Contour3D create_base_pool(const ExPolygons &ground_layer, +Contour3D create_base_pool(const Polygons &ground_layer, + const Polygons &holes = {}, const PoolConfig& cfg = PoolConfig()); Contour3D walls(const Polygon& floor_plate, const Polygon& ceiling, @@ -42,37 +43,28 @@ int main(const int argc, const char *argv[]) { model.ReadSTLFile(argv[1]); model.align_to_origin(); - ExPolygons ground_slice; - sla::Contour3D mesh; -// TriangleMesh basepool; - + Polygons ground_slice; sla::base_plate(model, ground_slice, 0.1f); - if(ground_slice.empty()) return EXIT_FAILURE; -// ExPolygon bottom_plate = ground_slice.front(); -// ExPolygon top_plate = bottom_plate; -// sla::offset(top_plate, coord_t(3.0/SCALING_FACTOR)); -// sla::offset(bottom_plate, coord_t(1.0/SCALING_FACTOR)); + Polygon gndfirst; gndfirst = ground_slice.front(); + sla::offset_with_breakstick_holes(gndfirst, 0.5, 10, 0.3); + + sla::Contour3D mesh; + bench.start(); -// TriangleMesh pool; sla::PoolConfig cfg; cfg.min_wall_height_mm = 0; - cfg.edge_radius_mm = 0.2; - mesh = sla::create_base_pool(ground_slice, cfg); - -// mesh.merge(triangulate_expolygon_3d(top_plate, 3.0, false)); -// mesh.merge(triangulate_expolygon_3d(bottom_plate, 0.0, true)); -// mesh = sla::walls(bottom_plate.contour, top_plate.contour, 0, 3, 2.0, [](){}); - + cfg.edge_radius_mm = 0; + mesh = sla::create_base_pool(ground_slice, {}, cfg); + bench.stop(); cout << "Base pool creation time: " << std::setprecision(10) << bench.getElapsedSec() << " seconds." << endl; - -// auto point = []() + for(auto& trind : mesh.indices) { Vec3d p0 = mesh.points[size_t(trind[0])]; Vec3d p1 = mesh.points[size_t(trind[1])]; diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 171d2b8d0..9b3f80f1a 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -7,9 +7,9 @@ #include "Tesselate.hpp" // For debugging: -//#include <fstream> -//#include <libnest2d/tools/benchmark.h> -//#include "SVG.hpp" +// #include <fstream> +// #include <libnest2d/tools/benchmark.h> +#include "SVG.hpp" namespace Slic3r { namespace sla { @@ -180,9 +180,10 @@ Contour3D walls(const Polygon& lower, const Polygon& upper, } /// Offsetting with clipper and smoothing the edges into a curvature. -void offset(ExPolygon& sh, coord_t distance) { +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; @@ -199,11 +200,13 @@ void offset(ExPolygon& sh, coord_t distance) { return; } + auto jointype = edgerounding? jtRound : jtMiter; + ClipperOffset offs; offs.ArcTolerance = 0.01*mm(1); Paths result; - offs.AddPath(ctour, jtRound, etClosedPolygon); - offs.AddPaths(holes, jtRound, etClosedPolygon); + offs.AddPath(ctour, jointype, etClosedPolygon); + offs.AddPaths(holes, jointype, etClosedPolygon); offs.Execute(result, static_cast<double>(distance)); // Offsetting reverts the orientation and also removes the last vertex @@ -233,6 +236,49 @@ void offset(ExPolygon& sh, coord_t distance) { } } +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*mm(1); + Paths result; + offs.AddPath(ctour, edgerounding ? jtRound : jtMiter, etClosedPolygon); + offs.Execute(result, static_cast<double>(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; @@ -303,6 +349,118 @@ ExPolygons unify(const ExPolygons& shapes) { 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 offset_with_breakstick_holes(ExPolygon& poly, + double padding, + double stride, + double stick_width, + double penetration) +{ + // We do the basic offsetting first + const bool dont_round_edges = false; + offset(poly, coord_t(padding / SCALING_FACTOR), dont_round_edges); + + 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 = stick_width / SCALING_FACTOR; + double sright = (penetration + padding) / SCALING_FACTOR; + + // scaled stride distance + double sstride = stride / SCALING_FACTOR; + 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<double>() - a.cast<double>(); + 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 < sright) t += sright; + double tend = nrm - sright; + + 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<coord_t>(); + Point p2 = p1 + (sright * dirp).cast<coord_t>(); + Point p3 = p2 + (sbottom * dir).cast<coord_t>(); + Point p4 = p3 + (sright * -dirp).cast<coord_t>(); + 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); + }; + + transf(poly.contour.points); + for (auto &h : poly.holes) transf(h.points); + + svg.draw(poly); + svg.Close(); +} + /// Only a debug function to generate top and bottom plates from a 2D shape. /// It is not used in the algorithm directly. inline Contour3D roofs(const ExPolygon& poly, coord_t z_distance) { @@ -467,41 +625,38 @@ inline Point centroid(Points& pp) { return c; } -inline Point centroid(const ExPolygon& poly) { - return poly.contour.centroid(); +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...) -ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, - ThrowOnCancel throw_on_cancel = [](){}) +Polygons concave_hull(const Polygons& polys, double max_dist_mm = 50, + ThrowOnCancel throw_on_cancel = [](){}) { namespace bgi = boost::geometry::index; - using SpatElement = std::pair<BoundingBox, unsigned>; + using SpatElement = std::pair<Point, unsigned>; using SpatIndex = bgi::rtree< SpatElement, bgi::rstar<16, 4> >; - if(polys.empty()) return ExPolygons(); + if(polys.empty()) return Polygons(); + + const double max_dist = mm(max_dist_mm); - ExPolygons punion = unify(polys); // could be redundant + 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 ExPolygon& poly) { return centroid(poly); }); - - - SpatIndex boxindex; unsigned idx = 0; - std::for_each(punion.begin(), punion.end(), - [&boxindex, &idx](const ExPolygon& expo) { - BoundingBox bb(expo); - boxindex.insert(std::make_pair(bb, idx++)); - }); - + [](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); @@ -511,25 +666,32 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, idx = 0; std::transform(centroids.begin(), centroids.end(), std::back_inserter(punion), - [&punion, &boxindex, cc, max_dist_mm, &idx, throw_on_cancel] + [¢roids, &ctrindex, cc, max_dist, &idx, throw_on_cancel] (const Point& c) { throw_on_cancel(); 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; - double max_dist = mm(max_dist_mm); - - ExPolygon& expo = punion[idx++]; - BoundingBox querybb(expo); - - querybb.offset(max_dist); + + Point& ct = centroids[idx]; + std::vector<SpatElement> result; - boxindex.query(bgi::intersects(querybb), std::back_inserter(result)); - if(result.size() <= 1) return ExPolygon(); + ctrindex.query(bgi::nearest(ct, 2), std::back_inserter(result)); - ExPolygon r; - auto& ctour = r.contour.points; + 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); @@ -538,7 +700,7 @@ ExPolygons concave_hull(const ExPolygons& polys, double max_dist_mm = 50, ctour.emplace_back(c + Point( -y(d), x(d) )); ctour.emplace_back(c + Point( y(d), -x(d) )); offset(r, mm(1)); - + return r; }); @@ -576,13 +738,14 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, ExPolygons utmp = unify(tmp); - for(auto& o : utmp) { - auto&& smp = o.simplify(0.1/SCALING_FACTOR); + for(ExPolygon& o : utmp) { + auto&& smp = o.simplify(0.1/SCALING_FACTOR); // TODO: is this important? output.insert(output.end(), smp.begin(), smp.end()); } } -Contour3D create_base_pool(const ExPolygons &ground_layer, +Contour3D create_base_pool(const Polygons &ground_layer, + const ExPolygons &obj_self_pad = {}, const PoolConfig& cfg = PoolConfig()) { // for debugging: @@ -597,7 +760,7 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, // 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. - ExPolygons concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel); + 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; @@ -617,42 +780,37 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, Contour3D pool; - for(ExPolygon& concaveh : concavehs) { - if(concaveh.contour.points.empty()) return pool; - - // Get rid of any holes in the concave hull output. - concaveh.holes.clear(); + 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; - outer_base.holes.clear(); offset(outer_base, s_safety_dist + s_wingdist + s_thickness); - ExPolygon bottom_poly = outer_base; - bottom_poly.holes.clear(); + 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.contour; + top_poly.contour = outer_base; if(wingheight > 0) { - inner_base = outer_base; + inner_base.contour = outer_base; offset(inner_base, -(s_thickness + s_wingdist + s_eradius)); - middle_base = outer_base; + 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 = outer_base; double wh = 0; + 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. @@ -713,11 +871,56 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, wh, -wingdist, thrcl)); } - // 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(top_poly)); - pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true)); + if (cfg.embed_object) { + ExPolygons pp = diff_ex(to_polygons(bottom_poly), + to_polygons(obj_self_pad)); + + // Generate outer walls + auto fp = [](const Point &p, Point::coord_type z) { + return unscale(x(p), y(p), z); + }; + + auto straight_walls = [&pool, s_thickness, fp](const Polygon &cntr) + { + auto lines = cntr.lines(); + bool cclk = cntr.is_counter_clockwise(); + + for (auto &l : lines) { + auto s = coord_t(pool.points.size()); + pool.points.emplace_back(fp(l.a, -s_thickness)); + pool.points.emplace_back(fp(l.b, -s_thickness)); + pool.points.emplace_back(fp(l.a, 0)); + pool.points.emplace_back(fp(l.b, 0)); + + if(cclk) { + pool.indices.emplace_back(s + 3, s + 1, s); + pool.indices.emplace_back(s + 2, s + 3, s); + } else { + pool.indices.emplace_back(s, s + 1, s + 3); + pool.indices.emplace_back(s, s + 3, s + 2); + } + } + }; + + for (ExPolygon &ep : pp) { + pool.merge(triangulate_expolygon_3d(ep)); + pool.merge(triangulate_expolygon_3d(ep, -fullheight, true)); + + for (auto &h : ep.holes) straight_walls(h); + } + + // Skip the outer contour. TODO: make sure the first in the list + // IS the outer contour. + for (auto it = std::next(pp.begin()); it != pp.end(); ++it) + straight_walls(it->contour); + + } 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(top_poly)); + pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true)); + } if(wingheight > 0) pool.merge(triangulate_expolygon_3d(inner_base, -wingheight)); @@ -727,8 +930,8 @@ Contour3D create_base_pool(const ExPolygons &ground_layer, return pool; } -void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, - const PoolConfig& cfg) +void create_base_pool(const Polygons &ground_layer, TriangleMesh& out, + const ExPolygons &holes, const PoolConfig& cfg) { @@ -738,7 +941,7 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out, // std::fstream fout("pad_debug.obj", std::fstream::out); // if(fout.good()) pool.to_obj(fout); - out.merge(mesh(create_base_pool(ground_layer, cfg))); + out.merge(mesh(create_base_pool(ground_layer, holes, cfg))); } } diff --git a/src/libslic3r/SLA/SLABasePool.hpp b/src/libslic3r/SLA/SLABasePool.hpp index 3c88e58c8..0ed26b6e7 100644 --- a/src/libslic3r/SLA/SLABasePool.hpp +++ b/src/libslic3r/SLA/SLABasePool.hpp @@ -8,7 +8,9 @@ namespace Slic3r { class ExPolygon; +class Polygon; using ExPolygons = std::vector<ExPolygon>; +using Polygons = std::vector<Polygon>; class TriangleMesh; @@ -23,12 +25,24 @@ void base_plate(const TriangleMesh& mesh, // input mesh float layerheight = 0.05f, // The sampling height 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 offset_with_breakstick_holes(ExPolygon& poly, + double padding, + double stride, + double stick_width, + double penetration = 0.0); + 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 + bool embed_object = false; ThrowOnCancel throw_on_cancel = [](){}; @@ -42,8 +56,9 @@ struct PoolConfig { }; /// Calculate the pool for the mesh for SLA printing -void create_base_pool(const ExPolygons& base_plate, +void create_base_pool(const Polygons& base_plate, TriangleMesh& output_mesh, + const ExPolygons& holes, const PoolConfig& = PoolConfig()); /// TODO: Currently the base plate of the pool will have half the height of the diff --git a/src/libslic3r/SLA/SLACommon.hpp b/src/libslic3r/SLA/SLACommon.hpp index 855802759..2b72aa92c 100644 --- a/src/libslic3r/SLA/SLACommon.hpp +++ b/src/libslic3r/SLA/SLACommon.hpp @@ -72,6 +72,7 @@ public: ~EigenMesh3D(); inline double ground_level() const { return m_ground_level; } + inline double& ground_level() { return m_ground_level; } inline const Eigen::MatrixXd& V() const { return m_V; } inline const Eigen::MatrixXi& F() const { return m_F; } @@ -149,6 +150,12 @@ public: #endif /* SLIC3R_SLA_NEEDS_WINDTREE */ double squared_distance(const Vec3d& p, int& i, Vec3d& c) const; + inline double squared_distance(const Vec3d &p) const + { + int i; + Vec3d c; + return squared_distance(p, i, c); + } }; diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index cb2001024..43ffaed86 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -13,6 +13,7 @@ #include <libslic3r/Model.hpp> #include <libnest2d/optimizers/nlopt/genetic.hpp> +#include <libnest2d/optimizers/nlopt/subplex.hpp> #include <boost/log/trivial.hpp> #include <tbb/parallel_for.h> #include <libslic3r/I18N.hpp> @@ -71,6 +72,8 @@ const double SupportConfig::normal_cutoff_angle = 150.0 * M_PI / 180.0; // The shortest distance of any support structure from the model surface const double SupportConfig::safety_distance_mm = 0.5; +const double SupportConfig::pillar_base_safety_distance_mm = 0.5; + const double SupportConfig::max_solo_pillar_height_mm = 15.0; const double SupportConfig::max_dual_pillar_height_mm = 35.0; const double SupportConfig::optimizer_rel_score_diff = 1e-6; @@ -413,7 +416,7 @@ struct Pillar { assert(steps > 0); height = jp(Z) - endp(Z); - if(height > 0) { // Endpoint is below the starting point + if(height > EPSILON) { // Endpoint is below the starting point // We just create a bridge geometry with the pillar parameters and // move the data. @@ -556,28 +559,47 @@ struct Pad { PoolConfig cfg; double zlevel = 0; - Pad() {} + Pad() = default; Pad(const TriangleMesh& object_support_mesh, - const ExPolygons& baseplate, + const ExPolygons& modelbase, double ground_level, const PoolConfig& pcfg) : cfg(pcfg), - zlevel(ground_level + - (sla::get_pad_fullheight(pcfg) - sla::get_pad_elevation(pcfg)) ) + zlevel(ground_level + + sla::get_pad_fullheight(pcfg) - + sla::get_pad_elevation(pcfg)) { - ExPolygons basep; + Polygons basep; cfg.throw_on_cancel(); - + // The 0.1f is the layer height with which the mesh is sampled and then // the layers are unified into one vector of polygons. - base_plate(object_support_mesh, basep, + ExPolygons platetmp; + base_plate(object_support_mesh, platetmp, float(cfg.min_wall_height_mm + cfg.min_wall_thickness_mm), 0.1f, pcfg.throw_on_cancel); + + // We don't need the holes for the base plate from the supports + for (const ExPolygon &bp : platetmp) basep.emplace_back(bp.contour); + for (const ExPolygon &bp : modelbase) basep.emplace_back(bp.contour); + + if(pcfg.embed_object) { + + auto modelbase_sticks = modelbase; + for(auto& poly : modelbase_sticks) + sla::offset_with_breakstick_holes( + poly, + SupportConfig::pillar_base_safety_distance_mm, // padding + 10, // stride (mm) + 0.3, // stick_width (mm) + 0.1); // penetration (mm) - for(auto& bp : baseplate) basep.emplace_back(bp); + create_base_pool(basep, tmesh, modelbase_sticks, cfg); + } else { + create_base_pool(basep, tmesh, {}, cfg); + } - create_base_pool(basep, tmesh, cfg); tmesh.translate(0, 0, float(zlevel)); } @@ -763,9 +785,9 @@ public: } const Pad& create_pad(const TriangleMesh& object_supports, - const ExPolygons& baseplate, + const ExPolygons& modelbase, const PoolConfig& cfg) { - m_pad = Pad(object_supports, baseplate, ground_level, cfg); + m_pad = Pad(object_supports, modelbase, ground_level, cfg); return m_pad; } @@ -1149,7 +1171,7 @@ class SLASupportTree::Algorithm { auto hr = m.query_ray_hit(p + sd*dir, dir); if(ins_check && hr.is_inside()) { - if(hr.distance() > r + sd) hits[i] = HitResult(0.0); + if(hr.distance() > 2 * r + sd) hits[i] = HitResult(0.0); else { // re-cast the ray from the outside of the object auto hr2 = @@ -1264,9 +1286,12 @@ class SLASupportTree::Algorithm { // For connecting a head to a nearby pillar. bool connect_to_nearpillar(const Head& head, long nearpillar_id) { - - auto nearpillar = [this, nearpillar_id]() { return m_result.pillar(nearpillar_id); }; - if(nearpillar().bridges > m_cfg.max_bridges_on_pillar) return false; + + auto nearpillar = [this, nearpillar_id]() { + return m_result.pillar(nearpillar_id); + }; + + if (nearpillar().bridges > m_cfg.max_bridges_on_pillar) return false; Vec3d headjp = head.junction_point(); Vec3d nearjp_u = nearpillar().startpoint(); @@ -1369,6 +1394,108 @@ class SLASupportTree::Algorithm { return nearest_id >= 0; } + + // This is a proxy function for pillar creation which will mind the gap + // between the pad and the model bottom in zero elevation mode. + void create_ground_pillar(const Vec3d &jp, + const Vec3d &sourcedir, + double radius, + int head_id = -1) + { + // People were killed for this number (seriously) + static const double SQR2 = std::sqrt(2.0); + + double gndlvl = m_result.ground_level; + Vec3d endp = {jp(X), jp(Y), gndlvl}; + double sd = SupportConfig::pillar_base_safety_distance_mm; + int pillar_id = -1; + double min_dist = sd + m_cfg.base_radius_mm + EPSILON; + double dist = 0; + bool can_add_base = true; + bool normal_mode = true; + + if (m_cfg.object_elevation_mm < EPSILON + && (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) { + // Get the distance from the mesh. This can be later optimized + // to get the distance in 2D plane because we are dealing with + // the ground level only. + + normal_mode = false; + double mv = 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); + double cosazm = std::cos(azimuth); + double sinazm = std::sin(azimuth); + + auto dir = Vec3d(cosazm * sinpolar, sinazm * sinpolar, cospolar) + .normalized(); + + using namespace libnest2d::opt; + StopCriteria scr; + scr.stop_score = min_dist; + SubplexOptimizer solver(scr); + + 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)); + }, + initvals(mv), bound(0.0, 2 * min_dist)); + + mv = std::get<0>(result.optimum); + endp = jp + std::sqrt(2) * mv * dir; + Vec3d pgnd = {endp(X), endp(Y), gndlvl}; + can_add_base = result.score > min_dist; + + // We have to check if the bridge is feasible. + if (bridge_mesh_intersect(jp, dir, radius) < (endp - jp).norm()) { + normal_mode = true; + endp = {jp(X), jp(Y), gndlvl}; + } + else { + // If the new endpoint is below ground, do not make a pillar + if (endp(Z) < gndlvl) + endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off + else { + Pillar &plr = m_result.add_pillar(endp, pgnd, radius); + + if (can_add_base) + plr.add_base(m_cfg.base_height_mm, + m_cfg.base_radius_mm); + + pillar_id = plr.id; + } + + m_result.add_bridge(jp, endp, radius); + m_result.add_junction(endp, radius); + + // Add a degenerated pillar and the bridge. + // The degenerate pillar will have zero length and it will + // prevent from queries of head_pillar() to have non-existing + // pillar when the head should have one. + if (head_id >= 0) + m_result.add_pillar(unsigned(head_id), jp, radius); + } + } + + if (normal_mode) { + Pillar &plr = head_id >= 0 + ? m_result.add_pillar(unsigned(head_id), + endp, + radius) + : m_result.add_pillar(jp, endp, radius); + + if (can_add_base) + plr.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); + + pillar_id = plr.id; + } + + if(pillar_id >= 0) // Save the pillar endpoint in the spatial index + m_pillar_index.insert(endp, pillar_id); + } public: @@ -1447,9 +1574,9 @@ public: // (Quaternion::FromTwoVectors) and apply the rotation to the // arrow head. - double z = n(2); - double r = 1.0; // for normalized vector - double polar = std::acos(z / r); + double z = n(2); + double r = 1.0; // for normalized vector + double polar = std::acos(z / r); double azimuth = std::atan2(n(1), n(0)); // skip if the tilt is not sane @@ -1473,14 +1600,14 @@ public: std::cos(polar)).normalized(); // check available distance - double t = pinhead_mesh_intersect( - hp, // touching point - nn, // normal - pin_r, - m_cfg.head_back_radius_mm, - w); + EigenMesh3D::hit_result t + = pinhead_mesh_intersect(hp, // touching point + nn, // normal + pin_r, + m_cfg.head_back_radius_mm, + w); - if(t <= w) { + if(t.distance() <= w) { // Let's try to optimize this angle, there might be a // viable normal that doesn't collide with the model @@ -1523,12 +1650,17 @@ public: // save the verified and corrected normal m_support_nmls.row(fidx) = nn; - if(t > w) { - // mark the point for needing a head. - m_iheads.emplace_back(fidx); - } else if( polar >= 3*PI/4 ) { - // Headless supports do not tilt like the headed ones so - // the normal should point almost to the ground. + if (t.distance() > w) { + // Check distance from ground, we might have zero elevation. + if (hp(Z) + w * nn(Z) < m_result.ground_level) { + m_iheadless.emplace_back(fidx); + } else { + // mark the point for needing a head. + m_iheads.emplace_back(fidx); + } + } else if (polar >= 3 * PI / 4) { + // Headless supports do not tilt like the headed ones + // so the normal should point almost to the ground. m_iheadless.emplace_back(fidx); } } @@ -1594,16 +1726,22 @@ public: // from each other in the XY plane to not cross their pillar bases // These clusters of support points will join in one pillar, // possibly in their centroid support point. + auto pointfn = [this](unsigned i) { return m_result.head(i).junction_point(); }; - auto predicate = [this](const SpatElement& e1, const SpatElement& e2) { + + auto predicate = [this](const SpatElement &e1, + const SpatElement &e2) { double d2d = distance(to_2d(e1.first), to_2d(e2.first)); double d3d = distance(e1.first, e2.first); - return d2d < 2 * m_cfg.base_radius_mm && - d3d < m_cfg.max_bridge_length_mm; + return d2d < 2 * m_cfg.base_radius_mm + && d3d < m_cfg.max_bridge_length_mm; }; - m_pillar_clusters = cluster(ground_head_indices, pointfn, predicate, + + m_pillar_clusters = cluster(ground_head_indices, + pointfn, + predicate, m_cfg.max_bridges_on_pillar); } @@ -1615,7 +1753,7 @@ public: void routing_to_ground() { const double pradius = m_cfg.head_back_radius_mm; - const double gndlvl = m_result.ground_level; + // const double gndlvl = m_result.ground_level; ClusterEl cl_centroids; cl_centroids.reserve(m_pillar_clusters.size()); @@ -1648,13 +1786,8 @@ public: Head& h = m_result.head(hid); h.transform(); - Vec3d p = h.junction_point(); p(Z) = gndlvl; - auto& plr = m_result.add_pillar(hid, p, h.r_back_mm) - .add_base(m_cfg.base_height_mm, - m_cfg.base_radius_mm); - // Save the pillar endpoint and the pillar id in the spatial index - m_pillar_index.insert(plr.endpoint(), unsigned(plr.id)); + create_ground_pillar(h.junction_point(), h.dir, h.r_back_mm, h.id); } // now we will go through the clusters ones again and connect the @@ -1681,15 +1814,12 @@ public: !search_pillar_and_connect(sidehead)) { Vec3d pstart = sidehead.junction_point(); - Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; + //Vec3d pend = Vec3d{pstart(X), pstart(Y), gndlvl}; // Could not find a pillar, create one - auto& pillar = m_result.add_pillar(unsigned(sidehead.id), - pend, pradius) - .add_base(m_cfg.base_height_mm, - m_cfg.base_radius_mm); - - // connects to ground, eligible for bridging - m_pillar_index.insert(pend, unsigned(pillar.id)); + create_ground_pillar(pstart, + sidehead.dir, + pradius, + sidehead.id); } } } @@ -1718,12 +1848,7 @@ public: m_result.add_bridge(hjp, endp, head.r_back_mm); m_result.add_junction(endp, head.r_back_mm); - auto groundp = endp; - groundp(Z) = m_result.ground_level; - auto& newpillar = m_result.add_pillar(endp, groundp, head.r_back_mm) - .add_base(m_cfg.base_height_mm, - m_cfg.base_radius_mm); - m_pillar_index.insert(groundp, unsigned(newpillar.id)); + this->create_ground_pillar(endp, dir, head.r_back_mm); }; std::vector<unsigned> modelpillars; @@ -1883,6 +2008,28 @@ public: m_pillar_index.insert(pillar.endpoint(), pillid); } } + + // Helper function for interconnect_pillars where pairs of already connected + // pillars should be checked for not to be processed again. This can be done + // 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<class I> static I pairhash(I a, I b) + { + using std::ceil; using std::log2; using std::max; using std::min; + + static_assert(std::is_integral<I>::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; + + // Assume the hash will fit into the output variable + assert((l ? (ceil(log2(l))) : 0) + bits_g < int(sizeof(I) * CHAR_BIT)); + + return (l << bits_g) + g; + } void interconnect_pillars() { // Now comes the algorithm that connects pillars with each other. @@ -1900,17 +2047,23 @@ public: double min_height_ratio = 0.5; std::set<unsigned long> pairs; - + + // A function to connect one pillar with its neighbors. THe number of + // neighbors is given in the configuration. This function if called + // for every pillar in the pillar index. A pair of pillar will not + // be connected multiple times this is ensured by the 'pairs' set which + // remembers the processed pillar pairs auto cascadefn = [this, d, &pairs, min_height_ratio, H1] (const SpatElement& el) { - Vec3d qp = el.first; - - const Pillar& pillar = m_result.pillar(el.second); + Vec3d qp = el.first; // endpoint of the pillar + const Pillar& pillar = m_result.pillar(el.second); // actual pillar + + // Get the max number of neighbors a pillar should connect to unsigned neighbors = m_cfg.pillar_cascade_neighbors; - // connections are enough for one pillar + // connections are already enough for the pillar if(pillar.links >= neighbors) return; // Query all remaining points within reach @@ -1924,21 +2077,21 @@ public: return distance(e1.first, qp) < distance(e2.first, qp); }); - for(auto& re : qres) { + for(auto& re : qres) { // process the queried neighbors - if(re.second == el.second) continue; + if(re.second == el.second) continue; // Skip self auto a = el.second, b = re.second; - // I hope that the area of a square is never equal to its - // circumference - auto hashval = 2 * (a + b) + a * b; - + // Get unique hash for the given pair (order doesn't matter) + auto hashval = pairhash(a, b); + + // Search for the pair amongst the remembered pairs if(pairs.find(hashval) != pairs.end()) continue; const Pillar& neighborpillar = m_result.pillars()[re.second]; - // this neighbor is occupied + // this neighbor is occupied, skip if(neighborpillar.links >= neighbors) continue; if(interconnect(pillar, neighborpillar)) { @@ -1960,47 +2113,75 @@ public: if(pillar.links >= neighbors) break; } }; - + + // Run the cascade for the pillars in the index m_pillar_index.foreach(cascadefn); - + + // We would be done here if we could allow some pillars to not be + // connected with any neighbors. But this might leave the support tree + // unprintable. + // + // The current solution is to insert additional pillars next to these + // lonely pillars. One or even two additional pillar might get inserted + // depending on the length of the lonely pillar. + size_t pillarcount = m_result.pillars().size(); - + + // Again, go through all pillars, this time in the whole support tree + // not just the index. for(size_t pid = 0; pid < pillarcount; pid++) { auto pillar = [this, pid]() { return m_result.pillar(pid); }; - + + // Decide how many additional pillars will be needed: + unsigned needpillars = 0; - if(pillar().bridges > m_cfg.max_bridges_on_pillar) needpillars = 3; - else if(pillar().links < 2 && pillar().height > H2) { + if (pillar().bridges > m_cfg.max_bridges_on_pillar) + needpillars = 3; + else if (pillar().links < 2 && pillar().height > H2) { // Not enough neighbors to support this pillar needpillars = 2 - pillar().links; - } - else if(pillar().links < 1 && pillar().height > H1) { + } else if (pillar().links < 1 && pillar().height > H1) { // No neighbors could be found and the pillar is too long. needpillars = 1; } - // Search for new pillar locations - bool found = false; - double alpha = 0; // goes to 2Pi - double r = 2 * m_cfg.base_radius_mm; - Vec3d pillarsp = pillar().startpoint(); + // Search for new pillar locations: + + bool found = false; + double alpha = 0; // goes to 2Pi + double r = 2 * m_cfg.base_radius_mm; + Vec3d pillarsp = pillar().startpoint(); + + // temp value for starting point detection Vec3d sp(pillarsp(X), pillarsp(Y), pillarsp(Z) - r); - std::vector<bool> tv(needpillars, false); - std::vector<Vec3d> spts(needpillars); + // A vector of bool for placement feasbility + std::vector<bool> canplace(needpillars, false); + std::vector<Vec3d> spts(needpillars); // vector of starting points + + double gnd = m_result.ground_level; + double min_dist = SupportConfig::pillar_base_safety_distance_mm + + m_cfg.base_radius_mm + EPSILON; + while(!found && alpha < 2*PI) { - - for(unsigned n = 0; n < needpillars; n++) { - double a = alpha + n * PI/3; - Vec3d s = sp; + for (unsigned n = 0; n < needpillars; n++) { + double a = alpha + n * PI / 3; + Vec3d s = sp; s(X) += std::cos(a) * r; s(Y) += std::sin(a) * r; spts[n] = s; + + // Check the path vertically down auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar().r); - tv[n] = std::isinf(hr.distance()); + + // If the path is clear, check for pillar base collisions + canplace[n] = std::isinf(hr.distance()) + && m_mesh.squared_distance({s(X), s(Y), gnd}) + > min_dist; } - found = std::all_of(tv.begin(), tv.end(), [](bool v){return v;}); + found = std::all_of(canplace.begin(), canplace.end(), + [](bool v) { return v; }); // 20 angles will be tried... alpha += 0.1 * PI; @@ -2010,7 +2191,7 @@ public: newpills.reserve(needpillars); if(found) for(unsigned n = 0; n < needpillars; n++) { - Vec3d s = spts[n]; double gnd = m_result.ground_level; + Vec3d s = spts[n]; Pillar p(s, Vec3d(s(X), s(Y), gnd), pillar().r); p.add_base(m_cfg.base_height_mm, m_cfg.base_radius_mm); @@ -2075,9 +2256,12 @@ public: // This is only for checking double idist = bridge_mesh_intersect(sph, dir, R, true); double dist = ray_mesh_intersect(sj, dir); + if (std::isinf(dist)) + dist = sph(Z) - m_result.ground_level - HWIDTH_MM; - if(std::isinf(idist) || std::isnan(idist) || idist < 2*R || - std::isinf(dist) || std::isnan(dist) || dist < 2*R) { + if(std::isnan(idist) || idist < 2*R || + std::isnan(dist) || dist < 2*R) + { BOOST_LOG_TRIVIAL(warning) << "Can not find route for headless" << " support stick at: " << sj.transpose(); @@ -2214,7 +2398,9 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points, return pc == ABORT; } -SLASupportTree::SLASupportTree(): m_impl(new Impl()) {} +SLASupportTree::SLASupportTree(double gnd_lvl): m_impl(new Impl()) { + m_impl->ground_level = gnd_lvl; +} const TriangleMesh &SLASupportTree::merged_mesh() const { @@ -2226,7 +2412,7 @@ void SLASupportTree::merged_mesh_with_pad(TriangleMesh &outmesh) const { outmesh.merge(get_pad()); } -SlicedSupports SLASupportTree::slice(float layerh, float init_layerh) const +std::vector<ExPolygons> SLASupportTree::slice(float layerh, float init_layerh) const { if(init_layerh < 0) init_layerh = layerh; auto& stree = get(); @@ -2247,34 +2433,29 @@ SlicedSupports SLASupportTree::slice(float layerh, float init_layerh) const fullmesh.merge(get_pad()); fullmesh.require_shared_vertices(); // TriangleMeshSlicer needs this TriangleMeshSlicer slicer(&fullmesh); - SlicedSupports ret; + std::vector<ExPolygons> ret; slicer.slice(heights, 0.f, &ret, get().ctl().cancelfn); return ret; } -SlicedSupports SLASupportTree::slice(const std::vector<float> &heights, +std::vector<ExPolygons> SLASupportTree::slice(const std::vector<float> &heights, float cr) const { TriangleMesh fullmesh = m_impl->merged_mesh(); fullmesh.merge(get_pad()); fullmesh.require_shared_vertices(); // TriangleMeshSlicer needs this TriangleMeshSlicer slicer(&fullmesh); - SlicedSupports ret; + std::vector<ExPolygons> ret; slicer.slice(heights, cr, &ret, get().ctl().cancelfn); return ret; } -const TriangleMesh &SLASupportTree::add_pad(const SliceLayer& baseplate, +const TriangleMesh &SLASupportTree::add_pad(const ExPolygons& modelbase, const PoolConfig& pcfg) const { -// PoolConfig pcfg; -// pcfg.min_wall_thickness_mm = min_wall_thickness_mm; -// pcfg.min_wall_height_mm = min_wall_height_mm; -// pcfg.max_merge_distance_mm = max_merge_distance_mm; -// pcfg.edge_radius_mm = edge_radius_mm; - return m_impl->create_pad(merged_mesh(), baseplate, pcfg).tmesh; + return m_impl->create_pad(merged_mesh(), modelbase, pcfg).tmesh; } const TriangleMesh &SLASupportTree::get_pad() const diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index 66677e4d7..1602316e8 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -24,10 +24,11 @@ class TriangleMesh; class Model; class ModelInstance; class ModelObject; +class Polygon; class ExPolygon; -using SliceLayer = std::vector<ExPolygon>; -using SlicedSupports = std::vector<SliceLayer>; +using Polygons = std::vector<Polygon>; +using ExPolygons = std::vector<ExPolygon>; namespace sla { @@ -90,6 +91,10 @@ struct SupportConfig { // The shortest distance of any support structure from the model surface static const double safety_distance_mm; + + // The shortest distance between a pillar base perimeter from the model + // body. This is only useful when elevation is set to zero. + static const double pillar_base_safety_distance_mm; static const double max_solo_pillar_height_mm; static const double max_dual_pillar_height_mm; @@ -160,7 +165,7 @@ class SLASupportTree { public: - SLASupportTree(); + SLASupportTree(double ground_level = 0.0); SLASupportTree(const std::vector<SupportPoint>& pts, const EigenMesh3D& em, @@ -179,12 +184,16 @@ public: void merged_mesh_with_pad(TriangleMesh&) const; /// Get the sliced 2d layers of the support geometry. - SlicedSupports slice(float layerh, float init_layerh = -1.0) const; + std::vector<ExPolygons> slice(float layerh, float init_layerh = -1.0) const; - SlicedSupports slice(const std::vector<float>&, float closing_radius) const; + std::vector<ExPolygons> slice(const std::vector<float>&, float closing_radius) const; /// Adding the "pad" (base pool) under the supports - const TriangleMesh& add_pad(const SliceLayer& baseplate, + /// modelbase will be used according to the embed_object flag in PoolConfig. + /// If set, the plate will interpreted as the model's intrinsic pad. + /// 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; /// Get the pad geometry diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index f6a1c429e..f4599a266 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -32,8 +32,8 @@ class SLAPrintObject::SupportData { public: sla::EigenMesh3D emesh; // index-triangle representation std::vector<sla::SupportPoint> support_points; // all the support points (manual/auto) - SupportTreePtr support_tree_ptr; // the supports - SlicedSupports support_slices; // sliced supports + SupportTreePtr support_tree_ptr; // the supports + std::vector<ExPolygons> support_slices; // sliced supports inline SupportData(const TriangleMesh& trmesh): emesh(trmesh) {} }; @@ -471,7 +471,7 @@ void SLAPrint::set_task(const TaskParams ¶ms) int n_object_steps = int(params.to_object_step) + 1; if (n_object_steps == 0) - n_object_steps = (int)slaposCount; + n_object_steps = int(slaposCount); if (params.single_model_object.valid()) { // Find the print object to be processed with priority. @@ -486,7 +486,7 @@ void SLAPrint::set_task(const TaskParams ¶ms) // Find out whether the priority print object is being currently processed. bool running = false; for (int istep = 0; istep < n_object_steps; ++ istep) { - if (! print_object->m_stepmask[istep]) + if (! print_object->m_stepmask[size_t(istep)]) // Step was skipped, cancel. break; if (print_object->is_step_started_unguarded(SLAPrintObjectStep(istep))) { @@ -502,7 +502,7 @@ void SLAPrint::set_task(const TaskParams ¶ms) if (params.single_model_instance_only) { // Suppress all the steps of other instances. for (SLAPrintObject *po : m_objects) - for (int istep = 0; istep < (int)slaposCount; ++ istep) + for (size_t istep = 0; istep < slaposCount; ++ istep) po->m_stepmask[istep] = false; } else if (! running) { // Swap the print objects, so that the selected print_object is first in the row. @@ -512,15 +512,15 @@ void SLAPrint::set_task(const TaskParams ¶ms) } // and set the steps for the current object. for (int istep = 0; istep < n_object_steps; ++ istep) - print_object->m_stepmask[istep] = true; - for (int istep = n_object_steps; istep < (int)slaposCount; ++ istep) - print_object->m_stepmask[istep] = false; + print_object->m_stepmask[size_t(istep)] = true; + for (int istep = n_object_steps; istep < int(slaposCount); ++ istep) + print_object->m_stepmask[size_t(istep)] = false; } else { // Slicing all objects. bool running = false; for (SLAPrintObject *print_object : m_objects) for (int istep = 0; istep < n_object_steps; ++ istep) { - if (! print_object->m_stepmask[istep]) { + if (! print_object->m_stepmask[size_t(istep)]) { // Step may have been skipped. Restart. goto loop_end; } @@ -536,8 +536,8 @@ void SLAPrint::set_task(const TaskParams ¶ms) this->call_cancel_callback(); for (SLAPrintObject *po : m_objects) { for (int istep = 0; istep < n_object_steps; ++ istep) - po->m_stepmask[istep] = true; - for (int istep = n_object_steps; istep < (int)slaposCount; ++ istep) + po->m_stepmask[size_t(istep)] = true; + for (auto istep = size_t(n_object_steps); istep < slaposCount; ++ istep) po->m_stepmask[istep] = false; } } @@ -555,9 +555,9 @@ void SLAPrint::set_task(const TaskParams ¶ms) void SLAPrint::finalize() { for (SLAPrintObject *po : m_objects) - for (int istep = 0; istep < (int)slaposCount; ++ istep) + for (size_t istep = 0; istep < slaposCount; ++ istep) po->m_stepmask[istep] = true; - for (int istep = 0; istep < (int)slapsCount; ++ istep) + for (size_t istep = 0; istep < slapsCount; ++ istep) m_stepmask[istep] = true; } @@ -599,17 +599,29 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { return scfg; } +bool use_builtin_pad(const SLAPrintObjectConfig& c) { + return c.support_object_elevation.getFloat() <= EPSILON && + c.pad_enable.getBool(); +} + sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) { sla::PoolConfig pcfg; pcfg.min_wall_thickness_mm = c.pad_wall_thickness.getFloat(); - pcfg.wall_slope = c.pad_wall_slope.getFloat(); - pcfg.edge_radius_mm = c.pad_edge_radius.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(); + // set builtin pad implicitly ON + pcfg.embed_object = use_builtin_pad(c); + return pcfg; } + } std::string SLAPrint::validate() const @@ -632,8 +644,10 @@ std::string SLAPrint::validate() const cfg.head_width_mm + 2 * cfg.head_back_radius_mm - cfg.head_penetration_mm; + + double elv = cfg.object_elevation_mm; - if(supports_en && pinhead_width > cfg.object_elevation_mm) + if(supports_en && elv > EPSILON && elv < pinhead_width ) return L("Elevation is too low for object."); } @@ -818,23 +832,55 @@ void SLAPrint::process() BOOST_LOG_TRIVIAL(debug) << "Automatic support points: " << po.m_supportdata->support_points.size(); - // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass the update status to GLGizmoSlaSupports - m_report_status(*this, -1, L("Generating support points"), SlicingStatus::RELOAD_SLA_SUPPORT_POINTS); + // Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass + // the update status to GLGizmoSlaSupports + m_report_status(*this, + -1, + L("Generating support points"), + SlicingStatus::RELOAD_SLA_SUPPORT_POINTS); } else { - // There are either some points on the front-end, or the user removed them on purpose. No calculation will be done. + // There are either some points on the front-end, or the user + // removed them on purpose. No calculation will be done. po.m_supportdata->support_points = po.transformed_support_points(); } + + // If the builtin pad mode is engaged, we have to filter out all the + // points that are on the bottom of the object + if(use_builtin_pad(po.m_config)) { + double gnd = po.m_supportdata->emesh.ground_level(); + auto & pts = po.m_supportdata->support_points; + + // get iterator to the reorganized vector end + auto endit = std::remove_if( + pts.begin(), + pts.end(), + [&po, gnd](const sla::SupportPoint &sp) { + double diff = std::abs(gnd - double(sp.pos(Z))); + return diff <= po.m_config.pad_wall_thickness.getFloat(); + }); + + // erase all elements after the new end + pts.erase(endit, pts.end()); + } }; // In this step we create the supports auto support_tree = [this, ostepd](SLAPrintObject& po) { if(!po.m_supportdata) return; + + sla::PoolConfig pcfg = make_pool_config(po.m_config); + + if(pcfg.embed_object) + po.m_supportdata->emesh.ground_level() += pcfg.min_wall_thickness_mm; if(!po.m_config.supports_enable.getBool()) { + // Generate empty support tree. It can still host a pad - po.m_supportdata->support_tree_ptr.reset(new SLASupportTree()); + po.m_supportdata->support_tree_ptr.reset( + new SLASupportTree(po.m_supportdata->emesh.ground_level())); + return; } @@ -856,7 +902,7 @@ void SLAPrint::process() ctl.stopcondition = [this](){ return canceled(); }; ctl.cancelfn = [this]() { throw_if_canceled(); }; - + po.m_supportdata->support_tree_ptr.reset( new SLASupportTree(po.m_supportdata->support_points, po.m_supportdata->emesh, scfg, ctl)); @@ -894,27 +940,26 @@ void SLAPrint::process() if(po.m_config.pad_enable.getBool()) { - double wt = po.m_config.pad_wall_thickness.getFloat(); - double h = po.m_config.pad_wall_height.getFloat(); - double md = po.m_config.pad_max_merge_distance.getFloat(); - // Radius is disabled for now... - double er = 0; // po.m_config.pad_edge_radius.getFloat(); - double tilt = po.m_config.pad_wall_slope.getFloat() * PI / 180.0; - double lh = po.m_config.layer_height.getFloat(); - double elevation = po.m_config.support_object_elevation.getFloat(); - if(!po.m_config.supports_enable.getBool()) elevation = 0; - sla::PoolConfig pcfg(wt, h, md, er, tilt); + // Get the distilled pad configuration from the config + sla::PoolConfig pcfg = make_pool_config(po.m_config); - ExPolygons bp; - double pad_h = sla::get_pad_fullheight(pcfg); - auto&& trmesh = po.transformed_mesh(); + ExPolygons bp; // This will store the base plate of the pad. + double pad_h = sla::get_pad_fullheight(pcfg); + const TriangleMesh &trmesh = po.transformed_mesh(); // This call can get pretty time consuming auto thrfn = [this](){ throw_if_canceled(); }; - if(elevation < pad_h) { - // we have to count with the model geometry for the base plate - sla::base_plate(trmesh, bp, float(pad_h), float(lh), thrfn); + if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) { + // No support (thus no elevation) or zero elevation mode + // we sometimes call it "builtin pad" is enabled so we will + // get a sample from the bottom of the mesh and use it for pad + // creation. + sla::base_plate(trmesh, + bp, + float(pad_h), + float(po.m_config.layer_height.getFloat()), + thrfn); } pcfg.throw_on_cancel = thrfn; @@ -1647,7 +1692,7 @@ double SLAPrintObject::get_elevation() const { // 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); - ret += sla::get_pad_elevation(pcfg); + if(!pcfg.embed_object) ret += sla::get_pad_elevation(pcfg); } return ret; @@ -1661,8 +1706,9 @@ double SLAPrintObject::get_current_elevation() const if(!has_supports && !has_pad) return 0; - else if(has_supports && !has_pad) + else if(has_supports && !has_pad) { return se ? m_config.support_object_elevation.getFloat() : 0; + } return get_elevation(); } @@ -1786,7 +1832,7 @@ std::vector<sla::SupportPoint> SLAPrintObject::transformed_support_points() cons ret.reserve(spts.size()); for(sla::SupportPoint& sp : spts) { - Vec3d transformed_pos = trafo() * Vec3d(sp.pos(0), sp.pos(1), sp.pos(2)); + Vec3d transformed_pos = trafo() * Vec3d(double(sp.pos(0)), double(sp.pos(1)), double(sp.pos(2))); ret.emplace_back(transformed_pos(0), transformed_pos(1), transformed_pos(2), sp.head_front_radius, sp.is_new_island); } From b7e3ee0709c9ca3a6da49f4c933d1f079dba27a0 Mon Sep 17 00:00:00 2001 From: tamasmeszaros <meszaros.q@gmail.com> Date: Tue, 11 Jun 2019 15:35:09 +0200 Subject: [PATCH 02/14] Refactor, fix wall normals and gap detection. --- src/libslic3r/SLA/SLABasePool.cpp | 66 +++++++++++++++------------- src/libslic3r/SLA/SLASupportTree.cpp | 12 +++-- 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 9b3f80f1a..a882769f6 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -857,6 +857,7 @@ Contour3D create_base_pool(const Polygons &ground_layer, 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 @@ -872,55 +873,58 @@ Contour3D create_base_pool(const Polygons &ground_layer, } if (cfg.embed_object) { - ExPolygons pp = diff_ex(to_polygons(bottom_poly), - to_polygons(obj_self_pad)); + 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; - // Generate outer walls - auto fp = [](const Point &p, Point::coord_type z) { - return unscale(x(p), y(p), z); - }; - - auto straight_walls = [&pool, s_thickness, fp](const Polygon &cntr) - { + auto straight_walls = + [&pool](const Polygon &cntr, coord_t z_low, coord_t z_high) { + auto lines = cntr.lines(); - bool cclk = cntr.is_counter_clockwise(); for (auto &l : lines) { auto s = coord_t(pool.points.size()); - pool.points.emplace_back(fp(l.a, -s_thickness)); - pool.points.emplace_back(fp(l.b, -s_thickness)); - pool.points.emplace_back(fp(l.a, 0)); - pool.points.emplace_back(fp(l.b, 0)); + 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)); - if(cclk) { - pool.indices.emplace_back(s + 3, s + 1, s); - pool.indices.emplace_back(s + 2, s + 3, s); - } else { - pool.indices.emplace_back(s, s + 1, s + 3); - pool.indices.emplace_back(s, s + 3, s + 2); - } + pool.indices.emplace_back(s, s + 1, s + 3); + pool.indices.emplace_back(s, s + 3, s + 2); } }; - - for (ExPolygon &ep : pp) { - pool.merge(triangulate_expolygon_3d(ep)); + + coord_t z_lo = -mm(fullheight), z_hi = -mm(wingheight); + for (ExPolygon &ep : bttms) { pool.merge(triangulate_expolygon_3d(ep, -fullheight, true)); - - for (auto &h : ep.holes) straight_walls(h); + for (auto &h : ep.holes) straight_walls(h, z_lo, z_hi); } - // Skip the outer contour. TODO: make sure the first in the list - // IS the outer contour. - for (auto it = std::next(pp.begin()); it != pp.end(); ++it) - straight_walls(it->contour); + // 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(top_poly)); + 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)); diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 43ffaed86..9705e8600 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -2164,7 +2164,10 @@ public: m_cfg.base_radius_mm + EPSILON; while(!found && alpha < 2*PI) { - for (unsigned n = 0; n < needpillars; n++) { + for (unsigned n = 0; + n < needpillars && (!n || canplace[n - 1]); + n++) + { double a = alpha + n * PI / 3; Vec3d s = sp; s(X) += std::cos(a) * r; @@ -2173,11 +2176,12 @@ public: // Check the path vertically down auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar().r); + Vec3d gndsp{s(X), s(Y), gnd}; // If the path is clear, check for pillar base collisions - canplace[n] = std::isinf(hr.distance()) - && m_mesh.squared_distance({s(X), s(Y), gnd}) - > min_dist; + canplace[n] = std::isinf(hr.distance()) && + std::sqrt(m_mesh.squared_distance(gndsp)) > + min_dist; } found = std::all_of(canplace.begin(), canplace.end(), From 6877c075dc14c24d8b036ade7cf540ce228dd061 Mon Sep 17 00:00:00 2001 From: tamasmeszaros <meszaros.q@gmail.com> Date: Tue, 11 Jun 2019 17:57:39 +0200 Subject: [PATCH 03/14] SPE-742: Parameter layer for zero elevation feature. --- src/libslic3r/PrintConfig.cpp | 47 ++++++++++++++- src/libslic3r/PrintConfig.hpp | 26 ++++++++- src/libslic3r/SLA/SLABasePool.hpp | 15 +++-- src/libslic3r/SLA/SLACommon.hpp | 6 +- src/libslic3r/SLA/SLASupportTree.cpp | 14 ++--- src/libslic3r/SLA/SLASupportTree.hpp | 11 ++-- src/libslic3r/SLAPrint.cpp | 31 +++++++--- src/slic3r/GUI/Preset.cpp | 4 ++ src/slic3r/GUI/Tab.cpp | 85 ++++++++++++++++++---------- 9 files changed, 179 insertions(+), 60 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 87ea26301..45ff20e78 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2490,6 +2490,19 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(1.0)); + + def = this->add("support_base_safety_distance", coFloat); + def->label = L("Support base safety distance"); + def->category = L("Supports"); + def->tooltip = L( + "The minimum distance of the pillar base from the model in mm. " + "Makes sense in zero elevation mode where a gap according " + "to this parameter is inserted between the model and the pad."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 10; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0.5)); def = this->add("support_critical_angle", coFloat); def->label = L("Critical angle"); @@ -2523,7 +2536,9 @@ void PrintConfigDef::init_sla_params() def = this->add("support_object_elevation", coFloat); def->label = L("Object elevation"); def->category = L("Supports"); - def->tooltip = L("How much the supports should lift up the supported object."); + def->tooltip = L("How much the supports should lift up the supported object. " + "If this value is zero, the bottom of the model geometry " + "will be considered as part of the pad."); def->sidetext = L("mm"); def->min = 0; def->max = 150; // This is the max height of print on SL1 @@ -2609,6 +2624,36 @@ void PrintConfigDef::init_sla_params() def->max = 90; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(45.0)); + + def = this->add("pad_object_connector_stride", coFloat); + def->label = L("Pad object connector stride"); + def->category = L("Pad"); + def->tooltip = L("Distance between two connector sticks between " + "the object pad and the generated pad."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(10)); + + def = this->add("pad_object_connector_width", coFloat); + def->label = L("Pad object connector width"); + def->category = L("Pad"); + def->tooltip = L("The width of the connectors sticks which connect the " + "object pad and the generated pad."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0.3)); + + def = this->add("pad_object_connector_penetration", coFloat); + def->label = L("Pad object connector penetration"); + def->category = L("Pad"); + def->tooltip = L( + "How much should the tiny connectors penetrate into the model body."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0.3)); } void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 1da22b377..e632e6946 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -983,6 +983,9 @@ public: // The height of the pillar base cone in mm. ConfigOptionFloat support_base_height /*= 1.0*/; + + // The minimum distance of the pillar base from the model in mm. + ConfigOptionFloat support_base_safety_distance; /*= 1.0*/; // The default angle for connecting support sticks and junctions. ConfigOptionFloat support_critical_angle /*= 45*/; @@ -996,7 +999,7 @@ public: // The elevation in Z direction upwards. This is the space between the pad // and the model object's bounding box bottom. Units in mm. ConfigOptionFloat support_object_elevation /*= 5.0*/; - + /////// Following options influence automatic support points placement: ConfigOptionInt support_points_density_relative; ConfigOptionFloat support_points_minimal_distance; @@ -1021,6 +1024,23 @@ public: // The slope of the pad wall... ConfigOptionFloat pad_wall_slope; + + // ///////////////////////////////////////////////////////////////////////// + // Zero elevation mode parameters: + // - The object pad will be derived from the the model geometry. + // - There will be a gap between the object pad and the generated pad + // according to the support_base_safety_distance parameter. + // - The two pads will be connected with tiny connector sticks + // ///////////////////////////////////////////////////////////////////////// + + // How far to place the connector sticks on the object pad perimeter + ConfigOptionFloat pad_object_connector_stride; + + // The width of the connectors sticks + ConfigOptionFloat pad_object_connector_width; + + // How much should the tiny connectors penetrate into the model body + ConfigOptionFloat pad_object_connector_penetration; protected: void initialize(StaticCacheBase &cache, const char *base_ptr) @@ -1038,6 +1058,7 @@ protected: OPT_PTR(support_pillar_widening_factor); OPT_PTR(support_base_diameter); OPT_PTR(support_base_height); + OPT_PTR(support_base_safety_distance); OPT_PTR(support_critical_angle); OPT_PTR(support_max_bridge_length); OPT_PTR(support_max_pillar_link_distance); @@ -1050,6 +1071,9 @@ protected: OPT_PTR(pad_max_merge_distance); OPT_PTR(pad_edge_radius); OPT_PTR(pad_wall_slope); + OPT_PTR(pad_object_connector_stride); + OPT_PTR(pad_object_connector_width); + OPT_PTR(pad_object_connector_penetration); } }; diff --git a/src/libslic3r/SLA/SLABasePool.hpp b/src/libslic3r/SLA/SLABasePool.hpp index 0ed26b6e7..129f7ccd4 100644 --- a/src/libslic3r/SLA/SLABasePool.hpp +++ b/src/libslic3r/SLA/SLABasePool.hpp @@ -42,7 +42,14 @@ struct PoolConfig { double max_merge_distance_mm = 50; double edge_radius_mm = 1; double wall_slope = std::atan(1.0); // Universal constant for Pi/4 - bool embed_object = false; + 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 = [](){}; @@ -61,11 +68,7 @@ void create_base_pool(const Polygons& base_plate, const ExPolygons& holes, const PoolConfig& = PoolConfig()); -/// TODO: Currently the base plate of the pool will have half the height of the -/// whole pool. So the carved out space has also half the height. This is not -/// a particularly elegant solution, the thickness should be exactly -/// min_wall_thickness and it should be corrected in the future. This method -/// will return the correct value for further processing. +/// Returns the elevation needed for compensating the pad. inline double get_pad_elevation(const PoolConfig& cfg) { return cfg.min_wall_thickness_mm; } diff --git a/src/libslic3r/SLA/SLACommon.hpp b/src/libslic3r/SLA/SLACommon.hpp index 2b72aa92c..2666f2a9c 100644 --- a/src/libslic3r/SLA/SLACommon.hpp +++ b/src/libslic3r/SLA/SLACommon.hpp @@ -60,7 +60,7 @@ class EigenMesh3D { Eigen::MatrixXd m_V; Eigen::MatrixXi m_F; - double m_ground_level = 0; + double m_ground_level = 0, m_gnd_offset = 0; std::unique_ptr<AABBImpl> m_aabb; public: @@ -71,8 +71,8 @@ public: ~EigenMesh3D(); - inline double ground_level() const { return m_ground_level; } - inline double& ground_level() { return m_ground_level; } + inline double ground_level() const { return m_ground_level + m_gnd_offset; } + inline void ground_level_offset(double o) { m_gnd_offset = o; } inline const Eigen::MatrixXd& V() const { return m_V; } inline const Eigen::MatrixXi& F() const { return m_F; } diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 9705e8600..b9f2cc14e 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -72,8 +72,6 @@ const double SupportConfig::normal_cutoff_angle = 150.0 * M_PI / 180.0; // The shortest distance of any support structure from the model surface const double SupportConfig::safety_distance_mm = 0.5; -const double SupportConfig::pillar_base_safety_distance_mm = 0.5; - const double SupportConfig::max_solo_pillar_height_mm = 15.0; const double SupportConfig::max_dual_pillar_height_mm = 35.0; const double SupportConfig::optimizer_rel_score_diff = 1e-6; @@ -590,10 +588,10 @@ struct Pad { for(auto& poly : modelbase_sticks) sla::offset_with_breakstick_holes( poly, - SupportConfig::pillar_base_safety_distance_mm, // padding - 10, // stride (mm) - 0.3, // stick_width (mm) - 0.1); // penetration (mm) + 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); create_base_pool(basep, tmesh, modelbase_sticks, cfg); } else { @@ -1407,7 +1405,7 @@ class SLASupportTree::Algorithm { double gndlvl = m_result.ground_level; Vec3d endp = {jp(X), jp(Y), gndlvl}; - double sd = SupportConfig::pillar_base_safety_distance_mm; + double sd = m_cfg.pillar_base_safety_distance_mm; int pillar_id = -1; double min_dist = sd + m_cfg.base_radius_mm + EPSILON; double dist = 0; @@ -2160,7 +2158,7 @@ public: std::vector<Vec3d> spts(needpillars); // vector of starting points double gnd = m_result.ground_level; - double min_dist = SupportConfig::pillar_base_safety_distance_mm + + double min_dist = m_cfg.pillar_base_safety_distance_mm + m_cfg.base_radius_mm + EPSILON; while(!found && alpha < 2*PI) { diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index 1602316e8..93627d10c 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -81,6 +81,10 @@ struct SupportConfig { // The elevation in Z direction upwards. This is the space between the pad // and the model object's bounding box bottom. double object_elevation_mm = 10; + + // The shortest distance between a pillar base perimeter from the model + // body. This is only useful when elevation is set to zero. + const double pillar_base_safety_distance_mm = 0.5; // ///////////////////////////////////////////////////////////////////////// // Compile time configuration values (candidates for runtime) @@ -91,10 +95,6 @@ struct SupportConfig { // The shortest distance of any support structure from the model surface static const double safety_distance_mm; - - // The shortest distance between a pillar base perimeter from the model - // body. This is only useful when elevation is set to zero. - static const double pillar_base_safety_distance_mm; static const double max_solo_pillar_height_mm; static const double max_dual_pillar_height_mm; @@ -186,7 +186,8 @@ public: /// Get the sliced 2d layers of the support geometry. std::vector<ExPolygons> slice(float layerh, float init_layerh = -1.0) const; - std::vector<ExPolygons> slice(const std::vector<float>&, float closing_radius) const; + std::vector<ExPolygons> slice(const std::vector<float> &, + float closing_radius) const; /// Adding the "pad" (base pool) under the supports /// modelbase will be used according to the embed_object flag in PoolConfig. diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index f4599a266..14cf2b6ff 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -599,9 +599,20 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { return scfg; } -bool use_builtin_pad(const SLAPrintObjectConfig& c) { - return c.support_object_elevation.getFloat() <= EPSILON && - c.pad_enable.getBool(); +sla::PoolConfig::EmbedObject use_builtin_pad(const SLAPrintObjectConfig& c) { + sla::PoolConfig::EmbedObject ret; + + ret.enabled = c.support_object_elevation.getFloat() <= EPSILON && + c.pad_enable.getBool(); + + if(ret.enabled) { + ret.object_gap_mm = c.support_base_safety_distance.getFloat(); + ret.stick_width_mm = c.pad_object_connector_width.getFloat(); + ret.stick_stride_mm = c.pad_object_connector_stride.getFloat(); + ret.stick_width_mm = c.pad_object_connector_penetration.getFloat(); + } + + return ret; } sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) { @@ -871,9 +882,10 @@ void SLAPrint::process() if(!po.m_supportdata) return; sla::PoolConfig pcfg = make_pool_config(po.m_config); - - if(pcfg.embed_object) - po.m_supportdata->emesh.ground_level() += pcfg.min_wall_thickness_mm; + + if (pcfg.embed_object) + po.m_supportdata->emesh.ground_level_offset( + pcfg.min_wall_thickness_mm); if(!po.m_config.supports_enable.getBool()) { @@ -1635,13 +1647,18 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf || opt_key == "support_critical_angle" || opt_key == "support_max_bridge_length" || opt_key == "support_max_pillar_link_distance" + || opt_key == "support_base_safety_distance" ) { steps.emplace_back(slaposSupportTree); } else if ( opt_key == "pad_wall_height" || opt_key == "pad_max_merge_distance" || opt_key == "pad_wall_slope" - || opt_key == "pad_edge_radius") { + || opt_key == "pad_edge_radius" + || opt_key == "pad_object_connector_stride" + || opt_key == "pad_object_connector_width" + || opt_key == "pad_object_connector_penetration" + ) { steps.emplace_back(slaposBasePool); } else { // All keys should be covered. diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 22ddf3449..86eee1902 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -461,6 +461,7 @@ const std::vector<std::string>& Preset::sla_print_options() "support_pillar_widening_factor", "support_base_diameter", "support_base_height", + "support_base_safety_distance", "support_critical_angle", "support_max_bridge_length", "support_max_pillar_link_distance", @@ -474,6 +475,9 @@ const std::vector<std::string>& Preset::sla_print_options() "pad_max_merge_distance", "pad_edge_radius", "pad_wall_slope", + "pad_object_connector_stride", + "pad_object_connector_width", + "pad_object_connector_penetration", "output_filename_format", "default_sla_print_profile", "compatible_printers", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6cd270e5b..8ca47f1b1 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3481,6 +3481,7 @@ void TabSLAPrint::build() // optgroup->append_single_option_line("support_pillar_widening_factor"); optgroup->append_single_option_line("support_base_diameter"); optgroup->append_single_option_line("support_base_height"); + optgroup->append_single_option_line("support_base_safety_distance"); optgroup->append_single_option_line("support_object_elevation"); optgroup = page->new_optgroup(_(L("Connection of the support sticks and junctions"))); @@ -3501,7 +3502,11 @@ void TabSLAPrint::build() // 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_object_connector_stride"); + optgroup->append_single_option_line("pad_object_connector_width"); + optgroup->append_single_option_line("pad_object_connector_penetration"); + page = add_options_page(_(L("Advanced")), "wrench"); optgroup = page->new_optgroup(_(L("Slicing"))); optgroup->append_single_option_line("slice_closing_radius"); @@ -3541,42 +3546,64 @@ void TabSLAPrint::reload_config() void TabSLAPrint::update() { - if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF) + if (m_preset_bundle->printers.get_selected_preset().printer_technology() + == ptFFF) return; // #ys_FIXME -// #ys_FIXME - m_update_cnt++; + // #ys_FIXME + m_update_cnt++; - double head_penetration = m_config->opt_float("support_head_penetration"); - double head_width = m_config->opt_float("support_head_width"); - if(head_penetration > head_width) { - wxString msg_text = _(L("Head penetration should not be greater than the head width.")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Invalid Head penetration")), wxICON_WARNING | wxOK); - DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_OK) { - new_conf.set_key_value("support_head_penetration", new ConfigOptionFloat(head_width)); - } + double head_penetration = m_config->opt_float("support_head_penetration"); + double head_width = m_config->opt_float("support_head_width"); + if (head_penetration > head_width) { + wxString msg_text = _( + L("Head penetration should not be greater than the head width.")); - load_config(new_conf); - } + auto dialog = new wxMessageDialog(parent(), + msg_text, + _(L("Invalid Head penetration")), + wxICON_WARNING | wxOK); - double pinhead_d = m_config->opt_float("support_head_front_diameter"); - double pillar_d = m_config->opt_float("support_pillar_diameter"); - if(pinhead_d > pillar_d) { - wxString msg_text = _(L("Pinhead diameter should be smaller than the pillar diameter.")); - auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Invalid pinhead diameter")), wxICON_WARNING | wxOK); - DynamicPrintConfig new_conf = *m_config; - if (dialog->ShowModal() == wxID_OK) { - new_conf.set_key_value("support_head_front_diameter", new ConfigOptionFloat(pillar_d / 2.0)); - } + DynamicPrintConfig new_conf = *m_config; + if (dialog->ShowModal() == wxID_OK) { + new_conf.set_key_value("support_head_penetration", + new ConfigOptionFloat(head_width)); + } - load_config(new_conf); - } + load_config(new_conf); + } - m_update_cnt--; + double pinhead_d = m_config->opt_float("support_head_front_diameter"); + double pillar_d = m_config->opt_float("support_pillar_diameter"); + if (pinhead_d > pillar_d) { + wxString msg_text = _(L( + "Pinhead diameter should be smaller than the pillar diameter.")); - if (m_update_cnt == 0) - wxGetApp().mainframe->on_config_changed(m_config); + auto dialog = new wxMessageDialog(parent(), + msg_text, + _(L("Invalid pinhead diameter")), + wxICON_WARNING | wxOK); + + DynamicPrintConfig new_conf = *m_config; + if (dialog->ShowModal() == wxID_OK) { + new_conf.set_key_value("support_head_front_diameter", + new ConfigOptionFloat(pillar_d / 2.0)); + } + + load_config(new_conf); + } + + // if(m_config->opt_float("support_object_elevation") < EPSILON && + // m_config->opt_bool("pad_enable")) { + // // TODO: disable editding of: + // // pad_object_connector_stride + // // pad_object_connector_width + // // pad_object_connector_penetration + // } + + m_update_cnt--; + + if (m_update_cnt == 0) wxGetApp().mainframe->on_config_changed(m_config); } } // GUI From c80aae1bdb3d87a2a27c973232eda33f9444f153 Mon Sep 17 00:00:00 2001 From: tamasmeszaros <meszaros.q@gmail.com> Date: Tue, 11 Jun 2019 18:19:58 +0200 Subject: [PATCH 04/14] Fixes for the parameter layer - Elevation value satisfied with no supports as well - Removed debug svg writing - Gap and sticks made optional in zero elevation pad. --- src/libslic3r/SLA/SLABasePool.cpp | 24 ++++++++++++++---------- src/libslic3r/SLA/SLASupportTree.hpp | 2 +- src/libslic3r/SLAPrint.cpp | 12 ++++++------ 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index a882769f6..62a078cb7 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -9,7 +9,7 @@ // For debugging: // #include <fstream> // #include <libnest2d/tools/benchmark.h> -#include "SVG.hpp" +// #include "SVG.hpp" namespace Slic3r { namespace sla { @@ -390,11 +390,13 @@ void offset_with_breakstick_holes(ExPolygon& poly, double penetration) { // We do the basic offsetting first - const bool dont_round_edges = false; - offset(poly, coord_t(padding / SCALING_FACTOR), dont_round_edges); + static const bool dont_round_edges = false; + + if(padding > 0.0) + offset(poly, coord_t(padding / SCALING_FACTOR), dont_round_edges); - SVG svg("bridgestick_plate.svg"); - svg.draw(poly); + // 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 @@ -453,12 +455,14 @@ void offset_with_breakstick_holes(ExPolygon& poly, out.shrink_to_fit(); pts.swap(out); }; - - transf(poly.contour.points); - for (auto &h : poly.holes) transf(h.points); - svg.draw(poly); - svg.Close(); + 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(); } /// Only a debug function to generate top and bottom plates from a 2D shape. diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index 93627d10c..8602d8a46 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -84,7 +84,7 @@ struct SupportConfig { // The shortest distance between a pillar base perimeter from the model // body. This is only useful when elevation is set to zero. - const double pillar_base_safety_distance_mm = 0.5; + double pillar_base_safety_distance_mm = 0.5; // ///////////////////////////////////////////////////////////////////////// // Compile time configuration values (candidates for runtime) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 14cf2b6ff..78bd50220 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -595,7 +595,10 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat(); scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat(); scfg.base_height_mm = c.support_base_height.getFloat(); - + scfg.pillar_base_safety_distance_mm = + c.support_base_safety_distance.getFloat() < EPSILON ? + scfg.safety_distance_mm : c.support_base_safety_distance.getFloat(); + return scfg; } @@ -1699,10 +1702,8 @@ bool SLAPrintObject::invalidate_all_steps() } double SLAPrintObject::get_elevation() const { - bool se = m_config.supports_enable.getBool(); - double ret = se? m_config.support_object_elevation.getFloat() : 0; + double ret = m_config.support_object_elevation.getFloat(); - // if the pad is enabled, then half of the pad height is its base plate if(m_config.pad_enable.getBool()) { // Normally the elevation for the pad itself would be the thickness of // its walls but currently it is half of its thickness. Whatever it @@ -1717,14 +1718,13 @@ double SLAPrintObject::get_elevation() const { double SLAPrintObject::get_current_elevation() const { - bool se = m_config.supports_enable.getBool(); bool has_supports = is_step_done(slaposSupportTree); bool has_pad = is_step_done(slaposBasePool); if(!has_supports && !has_pad) return 0; else if(has_supports && !has_pad) { - return se ? m_config.support_object_elevation.getFloat() : 0; + return m_config.support_object_elevation.getFloat(); } return get_elevation(); From 12396c3051a7abe26d33afe244bcabc938653e3e Mon Sep 17 00:00:00 2001 From: tamasmeszaros <meszaros.q@gmail.com> Date: Wed, 12 Jun 2019 13:15:42 +0200 Subject: [PATCH 05/14] Fine tuning parameters and fixing pad wings when greater gaps are used. --- src/libslic3r/PrintConfig.cpp | 15 +++++++++++++-- src/libslic3r/PrintConfig.hpp | 4 ++++ src/libslic3r/SLA/SLABasePool.cpp | 22 ++++++++-------------- src/libslic3r/SLA/SLABasePool.hpp | 10 +++++----- src/libslic3r/SLA/SLASupportTree.cpp | 18 +++++++++++++----- src/libslic3r/SLAPrint.cpp | 26 +++++++++++++++++++------- src/slic3r/GUI/Preset.cpp | 1 + src/slic3r/GUI/Tab.cpp | 1 + 8 files changed, 64 insertions(+), 33 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 45ff20e78..377d53bef 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2502,7 +2502,7 @@ void PrintConfigDef::init_sla_params() def->min = 0; def->max = 10; def->mode = comExpert; - def->set_default_value(new ConfigOptionFloat(0.5)); + def->set_default_value(new ConfigOptionFloat(1)); def = this->add("support_critical_angle", coFloat); def->label = L("Critical angle"); @@ -2625,6 +2625,17 @@ void PrintConfigDef::init_sla_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(45.0)); + def = this->add("pad_object_gap", coFloat); + def->label = L("Pad object gap"); + def->category = L("Pad"); + def->tooltip = L("The gap between the object bottom and the generated " + "pad in zero elevation mode."); + def->sidetext = L("mm"); + def->min = 0; + def->max = 10; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(1)); + def = this->add("pad_object_connector_stride", coFloat); def->label = L("Pad object connector stride"); def->category = L("Pad"); @@ -2643,7 +2654,7 @@ void PrintConfigDef::init_sla_params() def->sidetext = L("mm"); def->min = 0; def->mode = comExpert; - def->set_default_value(new ConfigOptionFloat(0.3)); + def->set_default_value(new ConfigOptionFloat(0.5)); def = this->add("pad_object_connector_penetration", coFloat); def->label = L("Pad object connector penetration"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index e632e6946..b5ddc4f13 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1033,6 +1033,9 @@ public: // - The two pads will be connected with tiny connector sticks // ///////////////////////////////////////////////////////////////////////// + // This is the gap between the object bottom and the generated pad + ConfigOptionFloat pad_object_gap; + // How far to place the connector sticks on the object pad perimeter ConfigOptionFloat pad_object_connector_stride; @@ -1071,6 +1074,7 @@ protected: OPT_PTR(pad_max_merge_distance); OPT_PTR(pad_edge_radius); OPT_PTR(pad_wall_slope); + OPT_PTR(pad_object_gap); OPT_PTR(pad_object_connector_stride); OPT_PTR(pad_object_connector_width); OPT_PTR(pad_object_connector_penetration); diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 62a078cb7..4a1259b5a 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -383,18 +383,12 @@ Polygons unify(const Polygons& shapes) { // 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 offset_with_breakstick_holes(ExPolygon& poly, - double padding, - double stride, - double stick_width, - double penetration) -{ - // We do the basic offsetting first - static const bool dont_round_edges = false; - - if(padding > 0.0) - offset(poly, coord_t(padding / SCALING_FACTOR), dont_round_edges); - +void breakstick_holes(ExPolygon& poly, + double padding, + double stride, + double stick_width, + double penetration) +{ // SVG svg("bridgestick_plate.svg"); // svg.draw(poly); @@ -428,8 +422,8 @@ void offset_with_breakstick_holes(ExPolygon& poly, out.emplace_back(a); // dodge the start point, do not make sticks on the joins - while (t < sright) t += sright; - double tend = nrm - sright; + while (t < sbottom) t += sbottom; + double tend = nrm - sbottom; while (t < tend) { // insert the stick on the polygon perimeter diff --git a/src/libslic3r/SLA/SLABasePool.hpp b/src/libslic3r/SLA/SLABasePool.hpp index 129f7ccd4..8aa4f5f41 100644 --- a/src/libslic3r/SLA/SLABasePool.hpp +++ b/src/libslic3r/SLA/SLABasePool.hpp @@ -30,11 +30,11 @@ void base_plate(const TriangleMesh& mesh, // input mesh // 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 offset_with_breakstick_holes(ExPolygon& poly, - double padding, - double stride, - double stick_width, - double penetration = 0.0); +void breakstick_holes(ExPolygon &poly, + double padding, + double stride, + double stick_width, + double penetration = 0.0); struct PoolConfig { double min_wall_thickness_mm = 2; diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index b9f2cc14e..ba14b1811 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -578,23 +578,31 @@ struct Pad { float(cfg.min_wall_height_mm + cfg.min_wall_thickness_mm), 0.1f, pcfg.throw_on_cancel); - // We don't need the holes for the base plate from the supports for (const ExPolygon &bp : platetmp) basep.emplace_back(bp.contour); - for (const ExPolygon &bp : modelbase) basep.emplace_back(bp.contour); + if(pcfg.embed_object) { - auto modelbase_sticks = modelbase; - for(auto& poly : modelbase_sticks) - sla::offset_with_breakstick_holes( + + if (pcfg.embed_object.object_gap_mm > 0.0) + modelbase_sticks + = offset_ex(modelbase_sticks, + coord_t(pcfg.embed_object.object_gap_mm + / SCALING_FACTOR)); + + for(auto& poly : modelbase_sticks) { + basep.emplace_back(poly); + 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); + } create_base_pool(basep, tmesh, modelbase_sticks, cfg); } else { + for (const ExPolygon &bp : modelbase) basep.emplace_back(bp.contour); create_base_pool(basep, tmesh, {}, cfg); } diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 78bd50220..bff4c9587 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -602,17 +602,18 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { return scfg; } -sla::PoolConfig::EmbedObject use_builtin_pad(const SLAPrintObjectConfig& c) { +sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { sla::PoolConfig::EmbedObject ret; ret.enabled = c.support_object_elevation.getFloat() <= EPSILON && c.pad_enable.getBool(); if(ret.enabled) { - ret.object_gap_mm = c.support_base_safety_distance.getFloat(); - ret.stick_width_mm = c.pad_object_connector_width.getFloat(); - ret.stick_stride_mm = c.pad_object_connector_stride.getFloat(); - ret.stick_width_mm = c.pad_object_connector_penetration.getFloat(); + ret.object_gap_mm = c.pad_object_gap.getFloat(); + ret.stick_width_mm = c.pad_object_connector_width.getFloat(); + ret.stick_stride_mm = c.pad_object_connector_stride.getFloat(); + ret.stick_penetration_mm = c.pad_object_connector_penetration + .getFloat(); } return ret; @@ -631,7 +632,7 @@ sla::PoolConfig make_pool_config(const SLAPrintObjectConfig& c) { pcfg.min_wall_height_mm = c.pad_wall_height.getFloat(); // set builtin pad implicitly ON - pcfg.embed_object = use_builtin_pad(c); + pcfg.embed_object = builtin_pad_cfg(c); return pcfg; } @@ -663,6 +664,16 @@ std::string SLAPrint::validate() const if(supports_en && elv > EPSILON && elv < pinhead_width ) return L("Elevation is too low for object."); + + sla::PoolConfig::EmbedObject builtinpad = builtin_pad_cfg(po->config()); + if(supports_en && builtinpad.enabled && + cfg.pillar_base_safety_distance_mm < builtinpad.object_gap_mm) { + return L( + "The endings of the support pillars will be deployed on the " + "gap between the object and the pad. 'Support base safety " + "distance' has to be greater than the 'Pad object gap' " + "parameter to avoid this."); + } } return ""; @@ -861,7 +872,7 @@ void SLAPrint::process() // If the builtin pad mode is engaged, we have to filter out all the // points that are on the bottom of the object - if(use_builtin_pad(po.m_config)) { + if(builtin_pad_cfg(po.m_config)) { double gnd = po.m_supportdata->emesh.ground_level(); auto & pts = po.m_supportdata->support_points; @@ -1658,6 +1669,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf || opt_key == "pad_max_merge_distance" || opt_key == "pad_wall_slope" || opt_key == "pad_edge_radius" + || opt_key == "pad_object_gap" || opt_key == "pad_object_connector_stride" || opt_key == "pad_object_connector_width" || opt_key == "pad_object_connector_penetration" diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 86eee1902..4b4597033 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -475,6 +475,7 @@ const std::vector<std::string>& Preset::sla_print_options() "pad_max_merge_distance", "pad_edge_radius", "pad_wall_slope", + "pad_object_gap", "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 8ca47f1b1..032bf95df 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3503,6 +3503,7 @@ void TabSLAPrint::build() // optgroup->append_single_option_line("pad_edge_radius"); optgroup->append_single_option_line("pad_wall_slope"); + 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"); optgroup->append_single_option_line("pad_object_connector_penetration"); From 10897524df042dae63939cec8251f93940de7916 Mon Sep 17 00:00:00 2001 From: tamasmeszaros <meszaros.q@gmail.com> Date: Wed, 12 Jun 2019 15:29:24 +0200 Subject: [PATCH 06/14] Fixes for gap detection and case with no pad, but zero elevation. --- src/libslic3r/SLA/SLASupportTree.cpp | 21 ++++++++++++++++----- src/libslic3r/SLAPrint.cpp | 21 +++++++++++---------- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index ba14b1811..c2540ba28 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -1410,6 +1410,7 @@ class SLASupportTree::Algorithm { { // People were killed for this number (seriously) static const double SQR2 = std::sqrt(2.0); + static const Vec3d DOWN = {0.0, 0.0, -1.0}; double gndlvl = m_result.ground_level; Vec3d endp = {jp(X), jp(Y), gndlvl}; @@ -1451,20 +1452,30 @@ class SLASupportTree::Algorithm { initvals(mv), bound(0.0, 2 * min_dist)); mv = std::get<0>(result.optimum); - endp = jp + std::sqrt(2) * mv * dir; + endp = jp + SQR2 * mv * dir; Vec3d pgnd = {endp(X), endp(Y), gndlvl}; can_add_base = result.score > min_dist; + + auto abort_in_shame = + [&normal_mode, &can_add_base, &endp, jp, gndlvl]() + { + normal_mode = true; + can_add_base = false; // Nothing left to do, hope for the best + endp = {jp(X), jp(Y), gndlvl}; + }; // We have to check if the bridge is feasible. - if (bridge_mesh_intersect(jp, dir, radius) < (endp - jp).norm()) { - normal_mode = true; - endp = {jp(X), jp(Y), gndlvl}; - } + if (bridge_mesh_intersect(jp, dir, radius) < (endp - jp).norm()) + abort_in_shame(); else { // If the new endpoint is below ground, do not make a pillar if (endp(Z) < gndlvl) endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off else { + + if (!std::isinf(bridge_mesh_intersect(endp, DOWN, radius))) + abort_in_shame(); + Pillar &plr = m_result.add_pillar(endp, pgnd, radius); if (can_add_base) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index bff4c9587..99e2915ea 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -439,12 +439,10 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf update_apply_status(this->invalidate_all_steps()); m_objects = print_objects_new; // Delete the PrintObjects marked as Unknown or Deleted. - bool deleted_objects = false; for (auto &pos : print_object_status) if (pos.status == PrintObjectStatus::Unknown || pos.status == PrintObjectStatus::Deleted) { update_apply_status(pos.print_object->invalidate_all_steps()); delete pos.print_object; - deleted_objects = true; } if (new_objects) update_apply_status(false); @@ -870,19 +868,22 @@ void SLAPrint::process() po.m_supportdata->support_points = po.transformed_support_points(); } - // If the builtin pad mode is engaged, we have to filter out all the + // If the zero elevation mode is engaged, we have to filter out all the // points that are on the bottom of the object - if(builtin_pad_cfg(po.m_config)) { - double gnd = po.m_supportdata->emesh.ground_level(); - auto & pts = po.m_supportdata->support_points; - + if (po.config().support_object_elevation.getFloat() <= EPSILON) { + 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(), - [&po, gnd](const sla::SupportPoint &sp) { + [tolerance, gnd](const sla::SupportPoint &sp) { double diff = std::abs(gnd - double(sp.pos(Z))); - return diff <= po.m_config.pad_wall_thickness.getFloat(); + return diff <= tolerance; }); // erase all elements after the new end @@ -1352,7 +1353,7 @@ void SLAPrint::process() }; // Rasterizing the model objects, and their supports - auto rasterize = [this, max_objstatus]() { + auto rasterize = [this]() { if(canceled()) return; // collect all the keys From 4ffe3278bee9bd7441fd3a5410376cd0586947db Mon Sep 17 00:00:00 2001 From: tamasmeszaros <meszaros.q@gmail.com> Date: Wed, 12 Jun 2019 17:09:40 +0200 Subject: [PATCH 07/14] Hotfix for pad shape deduction. --- src/libslic3r/SLA/SLASupportTree.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index c2540ba28..4910226cc 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -580,9 +580,8 @@ struct Pad { for (const ExPolygon &bp : platetmp) basep.emplace_back(bp.contour); - if(pcfg.embed_object) { - auto modelbase_sticks = modelbase; + ExPolygons modelbase_sticks = modelbase; if (pcfg.embed_object.object_gap_mm > 0.0) modelbase_sticks @@ -591,7 +590,7 @@ struct Pad { / SCALING_FACTOR)); for(auto& poly : modelbase_sticks) { - basep.emplace_back(poly); + basep.emplace_back(poly.contour); sla::breakstick_holes( poly, pcfg.embed_object.object_gap_mm, // padding From d1ed3d40c1c1da26f6d0b0da9e2d54a928050796 Mon Sep 17 00:00:00 2001 From: tamasmeszaros <meszaros.q@gmail.com> Date: Wed, 12 Jun 2019 17:23:12 +0200 Subject: [PATCH 08/14] Fix build on windows. This issue is annoying. --- src/libslic3r/SLAPrint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 99e2915ea..3c7341656 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1353,7 +1353,7 @@ void SLAPrint::process() }; // Rasterizing the model objects, and their supports - auto rasterize = [this]() { + auto rasterize = [this, max_objstatus]() { if(canceled()) return; // collect all the keys From e4cb75eddeaecb6a6c2b6d9694113c8fd82b59f4 Mon Sep 17 00:00:00 2001 From: tamasmeszaros <meszaros.q@gmail.com> Date: Wed, 12 Jun 2019 17:33:04 +0200 Subject: [PATCH 09/14] Fix build on Mac --- src/libslic3r/SLA/SLASupportTree.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 4910226cc..b74f73d17 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -1472,8 +1472,8 @@ class SLASupportTree::Algorithm { endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off else { - if (!std::isinf(bridge_mesh_intersect(endp, DOWN, radius))) - abort_in_shame(); + auto hit = bridge_mesh_intersect(endp, DOWN, radius); + if (!std::isinf(hit.distance())) abort_in_shame(); Pillar &plr = m_result.add_pillar(endp, pgnd, radius); From 90a854f7045b4b4295d2e0a31a5007175c7267bc Mon Sep 17 00:00:00 2001 From: tamasmeszaros <meszaros.q@gmail.com> Date: Mon, 17 Jun 2019 13:02:49 +0200 Subject: [PATCH 10/14] Fix levitation when supports are disabled. --- src/libslic3r/SLAPrint.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index d3c931d34..9fe0687b5 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -604,8 +604,8 @@ sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) { sla::PoolConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) { sla::PoolConfig::EmbedObject ret; - ret.enabled = c.support_object_elevation.getFloat() <= EPSILON && - c.pad_enable.getBool(); + ret.enabled = c.support_object_elevation.getFloat() <= EPSILON && + c.pad_enable.getBool() && c.supports_enable.getBool(); if(ret.enabled) { ret.object_gap_mm = c.pad_object_gap.getFloat(); @@ -1737,7 +1737,8 @@ bool SLAPrintObject::invalidate_all_steps() } double SLAPrintObject::get_elevation() const { - double ret = m_config.support_object_elevation.getFloat(); + bool en = m_config.supports_enable.getBool(); + double ret = en ? m_config.support_object_elevation.getFloat() : 0.; if(m_config.pad_enable.getBool()) { // Normally the elevation for the pad itself would be the thickness of From 778b2cf293d3082162b97a2c7d40add410f0fde7 Mon Sep 17 00:00:00 2001 From: tamasmeszaros <meszaros.q@gmail.com> Date: Mon, 17 Jun 2019 18:06:52 +0200 Subject: [PATCH 11/14] WIP on removing unused parts of pad --- src/libslic3r/SLA/SLABasePool.cpp | 45 +++++--- src/libslic3r/SLA/SLABasePool.hpp | 7 +- src/libslic3r/SLA/SLASpatIndex.hpp | 65 ++++++++--- src/libslic3r/SLA/SLASupportTree.cpp | 93 +++++++++++----- src/libslic3r/SLA/SLASupportTreeIGL.cpp | 136 ++++++++++++++++++------ 5 files changed, 254 insertions(+), 92 deletions(-) diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 4e1e03018..48d615a29 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -666,24 +666,19 @@ Polygons concave_hull(const Polygons& polys, double max_dist_mm = 50, return punion; } -void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, - float layerh, ThrowOnCancel thrfn) +void base_plate(const TriangleMesh & mesh, + ExPolygons & output, + const std::vector<float> &heights, + ThrowOnCancel thrfn) { - TriangleMesh m = mesh; - m.require_shared_vertices(); // TriangleMeshSlicer needs this - TriangleMeshSlicer slicer(&m); - - auto bb = mesh.bounding_box(); - float gnd = float(bb.min(Z)); - std::vector<float> heights = {float(bb.min(Z))}; - for(float hi = gnd + layerh; hi <= gnd + h; hi += layerh) - heights.emplace_back(hi); - - std::vector<ExPolygons> out; out.reserve(size_t(std::ceil(h/layerh))); + // m.require_shared_vertices(); // TriangleMeshSlicer needs this + TriangleMeshSlicer slicer(&mesh); + + std::vector<ExPolygons> 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); @@ -692,15 +687,31 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h, 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<float> 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()) diff --git a/src/libslic3r/SLA/SLABasePool.hpp b/src/libslic3r/SLA/SLABasePool.hpp index 8aa4f5f41..67b9ccdcb 100644 --- a/src/libslic3r/SLA/SLABasePool.hpp +++ b/src/libslic3r/SLA/SLABasePool.hpp @@ -21,10 +21,15 @@ using ThrowOnCancel = std::function<void(void)>; /// 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 zlevel = 0.1f, // Plate creation level + 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<float>&, // 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 diff --git a/src/libslic3r/SLA/SLASpatIndex.hpp b/src/libslic3r/SLA/SLASpatIndex.hpp index e5fbfa7d4..90dcdc362 100644 --- a/src/libslic3r/SLA/SLASpatIndex.hpp +++ b/src/libslic3r/SLA/SLASpatIndex.hpp @@ -7,13 +7,15 @@ #include <Eigen/Geometry> +#include <libslic3r/BoundingBox.hpp> + namespace Slic3r { namespace sla { typedef Eigen::Matrix<double, 3, 1, Eigen::DontAlign> Vec3d; -using SpatElement = std::pair<Vec3d, unsigned>; +using PointIndexEl = std::pair<Vec3d, unsigned>; -class SpatIndex { +class PointIndex { class Impl; // We use Pimpl because it takes a long time to compile boost headers which @@ -21,30 +23,67 @@ class SpatIndex { std::unique_ptr<Impl> m_impl; public: - SpatIndex(); - ~SpatIndex(); + PointIndex(); + ~PointIndex(); - SpatIndex(const SpatIndex&); - SpatIndex(SpatIndex&&); - SpatIndex& operator=(const SpatIndex&); - SpatIndex& operator=(SpatIndex&&); + PointIndex(const PointIndex&); + PointIndex(PointIndex&&); + PointIndex& operator=(const PointIndex&); + PointIndex& operator=(PointIndex&&); - void insert(const SpatElement&); - bool remove(const SpatElement&); + void insert(const PointIndexEl&); + bool remove(const PointIndexEl&); inline void insert(const Vec3d& v, unsigned idx) { insert(std::make_pair(v, unsigned(idx))); } - std::vector<SpatElement> query(std::function<bool(const SpatElement&)>); - std::vector<SpatElement> nearest(const Vec3d&, unsigned k); + std::vector<PointIndexEl> query(std::function<bool(const PointIndexEl&)>); + std::vector<PointIndexEl> nearest(const Vec3d&, unsigned k); // For testing size_t size() const; bool empty() const { return size() == 0; } - void foreach(std::function<void(const SpatElement& el)> fn); + void foreach(std::function<void(const PointIndexEl& el)> fn); +}; + +using BoxIndexEl = std::pair<Slic3r::BoundingBox, unsigned>; + +class BoxIndex { + class Impl; + + // We use Pimpl because it takes a long time to compile boost headers which + // is the engine of this class. We include it only in the cpp file. + std::unique_ptr<Impl> m_impl; +public: + + BoxIndex(); + ~BoxIndex(); + + BoxIndex(const BoxIndex&); + BoxIndex(BoxIndex&&); + BoxIndex& operator=(const BoxIndex&); + BoxIndex& operator=(BoxIndex&&); + + void insert(const BoxIndexEl&); + inline void insert(const BoundingBox& bb, unsigned idx) + { + insert(std::make_pair(bb, unsigned(idx))); + } + + bool remove(const BoxIndexEl&); + + enum QueryType { qtIntersects, qtWithin }; + + std::vector<BoxIndexEl> query(const BoundingBox&, QueryType qt); + + // For testing + size_t size() const; + bool empty() const { return size() == 0; } + + void foreach(std::function<void(const BoxIndexEl& el)> fn); }; } diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index 41040e89e..c38ff34e1 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -569,37 +569,74 @@ struct Pad { sla::get_pad_elevation(pcfg)) { Polygons basep; - cfg.throw_on_cancel(); + auto &thr = cfg.throw_on_cancel; - // The 0.1f is the layer height with which the mesh is sampled and then - // the layers are unified into one vector of polygons. - ExPolygons platetmp; - base_plate(object_support_mesh, platetmp, - float(cfg.min_wall_height_mm + cfg.min_wall_thickness_mm), - 0.1f, pcfg.throw_on_cancel); + thr(); - for (const ExPolygon &bp : platetmp) basep.emplace_back(bp.contour); + // Get a sample for the pad from the support mesh + { + ExPolygons platetmp; + float plateZ = float(get_pad_fullheight(pcfg) + EPSILON); + + base_plate(object_support_mesh, platetmp, plateZ, 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) { - ExPolygons modelbase_sticks = modelbase; + // 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_sticks = modelbase; + if (pcfg.embed_object.object_gap_mm > 0.0) modelbase_sticks = offset_ex(modelbase_sticks, - coord_t(pcfg.embed_object.object_gap_mm - / SCALING_FACTOR)); + float(scaled(pcfg.embed_object.object_gap_mm))); + 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 pad_stickholes; pad_stickholes.reserve(modelbase.size()); for(auto& poly : modelbase_sticks) { - basep.emplace_back(poly.contour); - 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); + + if (!bindex.query(poly.contour.bounding_box(), + BoxIndex::qtIntersects).empty()) { + + basep.emplace_back(poly.contour); + + 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; + } + + 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, modelbase_sticks, cfg); + 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); @@ -630,7 +667,7 @@ inline Vec2d to_vec2(const Vec3d& v3) { return {v3(X), v3(Y)}; } -bool operator==(const SpatElement& e1, const SpatElement& e2) { +bool operator==(const PointIndexEl& e1, const PointIndexEl& e2) { return e1.second == e2.second; } @@ -647,7 +684,7 @@ ClusteredPoints cluster(const PointSet& points, ClusteredPoints cluster( const std::vector<unsigned>& indices, std::function<Vec3d(unsigned)> pointfn, - std::function<bool(const SpatElement&, const SpatElement&)> predicate, + std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate, unsigned max_points); // This class will hold the support tree meshes with some additional bookkeeping @@ -974,7 +1011,7 @@ class SLASupportTree::Algorithm { ThrowOnCancel m_thr; // A spatial index to easily find strong pillars to connect to. - SpatIndex m_pillar_index; + PointIndex m_pillar_index; inline double ray_mesh_intersect(const Vec3d& s, const Vec3d& dir) @@ -1367,7 +1404,7 @@ class SLASupportTree::Algorithm { } bool search_pillar_and_connect(const Head& head) { - SpatIndex spindex = m_pillar_index; + PointIndex spindex = m_pillar_index; long nearest_id = -1; @@ -1747,8 +1784,8 @@ public: return m_result.head(i).junction_point(); }; - auto predicate = [this](const SpatElement &e1, - const SpatElement &e2) { + auto predicate = [this](const PointIndexEl &e1, + const PointIndexEl &e2) { double d2d = distance(to_2d(e1.first), to_2d(e2.first)); double d3d = distance(e1.first, e2.first); return d2d < 2 * m_cfg.base_radius_mm @@ -2070,7 +2107,7 @@ public: // be connected multiple times this is ensured by the 'pairs' set which // remembers the processed pillar pairs auto cascadefn = - [this, d, &pairs, min_height_ratio, H1] (const SpatElement& el) + [this, d, &pairs, min_height_ratio, H1] (const PointIndexEl& el) { Vec3d qp = el.first; // endpoint of the pillar @@ -2083,13 +2120,13 @@ public: if(pillar.links >= neighbors) return; // Query all remaining points within reach - auto qres = m_pillar_index.query([qp, d](const SpatElement& e){ + auto qres = m_pillar_index.query([qp, d](const PointIndexEl& e){ return distance(e.first, qp) < d; }); // sort the result by distance (have to check if this is needed) std::sort(qres.begin(), qres.end(), - [qp](const SpatElement& e1, const SpatElement& e2){ + [qp](const PointIndexEl& e1, const PointIndexEl& e2){ return distance(e1.first, qp) < distance(e2.first, qp); }); diff --git a/src/libslic3r/SLA/SLASupportTreeIGL.cpp b/src/libslic3r/SLA/SLASupportTreeIGL.cpp index c368b8604..04e6f79c7 100644 --- a/src/libslic3r/SLA/SLASupportTreeIGL.cpp +++ b/src/libslic3r/SLA/SLASupportTreeIGL.cpp @@ -29,69 +29,137 @@ namespace sla { using igl::PI; /* ************************************************************************** - * SpatIndex implementation + * PointIndex implementation * ************************************************************************** */ -class SpatIndex::Impl { +class PointIndex::Impl { public: - using BoostIndex = boost::geometry::index::rtree< SpatElement, + using BoostIndex = boost::geometry::index::rtree< PointIndexEl, boost::geometry::index::rstar<16, 4> /* ? */ >; BoostIndex m_store; }; -SpatIndex::SpatIndex(): m_impl(new Impl()) {} -SpatIndex::~SpatIndex() {} +PointIndex::PointIndex(): m_impl(new Impl()) {} +PointIndex::~PointIndex() {} -SpatIndex::SpatIndex(const SpatIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} -SpatIndex::SpatIndex(SpatIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} +PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} +PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} -SpatIndex& SpatIndex::operator=(const SpatIndex &cpy) +PointIndex& PointIndex::operator=(const PointIndex &cpy) { m_impl.reset(new Impl(*cpy.m_impl)); return *this; } -SpatIndex& SpatIndex::operator=(SpatIndex &&cpy) +PointIndex& PointIndex::operator=(PointIndex &&cpy) { m_impl.swap(cpy.m_impl); return *this; } -void SpatIndex::insert(const SpatElement &el) +void PointIndex::insert(const PointIndexEl &el) { m_impl->m_store.insert(el); } -bool SpatIndex::remove(const SpatElement& el) +bool PointIndex::remove(const PointIndexEl& el) { return m_impl->m_store.remove(el) == 1; } -std::vector<SpatElement> -SpatIndex::query(std::function<bool(const SpatElement &)> fn) +std::vector<PointIndexEl> +PointIndex::query(std::function<bool(const PointIndexEl &)> fn) { namespace bgi = boost::geometry::index; - std::vector<SpatElement> ret; + std::vector<PointIndexEl> ret; m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret)); return ret; } -std::vector<SpatElement> SpatIndex::nearest(const Vec3d &el, unsigned k = 1) +std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) { namespace bgi = boost::geometry::index; - std::vector<SpatElement> ret; ret.reserve(k); + std::vector<PointIndexEl> ret; ret.reserve(k); m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret)); return ret; } -size_t SpatIndex::size() const +size_t PointIndex::size() const { return m_impl->m_store.size(); } -void SpatIndex::foreach(std::function<void (const SpatElement &)> fn) +void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) +{ + for(auto& el : m_impl->m_store) fn(el); +} + +/* ************************************************************************** + * BoxIndex implementation + * ************************************************************************** */ + +class BoxIndex::Impl { +public: + using BoostIndex = boost::geometry::index:: + rtree<BoxIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>; + + BoostIndex m_store; +}; + +BoxIndex::BoxIndex(): m_impl(new Impl()) {} +BoxIndex::~BoxIndex() {} + +BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} +BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} + +BoxIndex& BoxIndex::operator=(const BoxIndex &cpy) +{ + m_impl.reset(new Impl(*cpy.m_impl)); + return *this; +} + +BoxIndex& BoxIndex::operator=(BoxIndex &&cpy) +{ + m_impl.swap(cpy.m_impl); + return *this; +} + +void BoxIndex::insert(const BoxIndexEl &el) +{ + m_impl->m_store.insert(el); +} + +bool BoxIndex::remove(const BoxIndexEl& el) +{ + return m_impl->m_store.remove(el) == 1; +} + +std::vector<BoxIndexEl> BoxIndex::query(const BoundingBox &qrbb, + BoxIndex::QueryType qt) +{ + namespace bgi = boost::geometry::index; + + std::vector<BoxIndexEl> ret; ret.reserve(m_impl->m_store.size()); + + switch (qt) { + case qtIntersects: + m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret)); + break; + case qtWithin: + m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret)); + } + + return ret; +} + +size_t BoxIndex::size() const +{ + return m_impl->m_store.size(); +} + +void BoxIndex::foreach(std::function<void (const BoxIndexEl &)> fn) { for(auto& el : m_impl->m_store) fn(el); } @@ -352,12 +420,14 @@ PointSet normals(const PointSet& points, return ret; } namespace bgi = boost::geometry::index; -using Index3D = bgi::rtree< SpatElement, bgi::rstar<16, 4> /* ? */ >; +using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >; -ClusteredPoints cluster(Index3D& sindex, unsigned max_points, - std::function<std::vector<SpatElement>(const Index3D&, const SpatElement&)> qfn) +ClusteredPoints cluster(Index3D &sindex, + unsigned max_points, + std::function<std::vector<PointIndexEl>( + const Index3D &, const PointIndexEl &)> qfn) { - using Elems = std::vector<SpatElement>; + using Elems = std::vector<PointIndexEl>; // Recursive function for visiting all the points in a given distance to // each other @@ -365,8 +435,8 @@ ClusteredPoints cluster(Index3D& sindex, unsigned max_points, [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster) { for(auto& p : pts) { - std::vector<SpatElement> tmp = qfn(sindex, p); - auto cmp = [](const SpatElement& e1, const SpatElement& e2){ + std::vector<PointIndexEl> tmp = qfn(sindex, p); + auto cmp = [](const PointIndexEl& e1, const PointIndexEl& e2){ return e1.second < e2.second; }; @@ -410,12 +480,12 @@ ClusteredPoints cluster(Index3D& sindex, unsigned max_points, } namespace { -std::vector<SpatElement> distance_queryfn(const Index3D& sindex, - const SpatElement& p, +std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex, + const PointIndexEl& p, double dist, unsigned max_points) { - std::vector<SpatElement> tmp; tmp.reserve(max_points); + std::vector<PointIndexEl> tmp; tmp.reserve(max_points); sindex.query( bgi::nearest(p.first, max_points), std::back_inserter(tmp) @@ -442,7 +512,7 @@ ClusteredPoints cluster( for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); return cluster(sindex, max_points, - [dist, max_points](const Index3D& sidx, const SpatElement& p) + [dist, max_points](const Index3D& sidx, const PointIndexEl& p) { return distance_queryfn(sidx, p, dist, max_points); }); @@ -452,7 +522,7 @@ ClusteredPoints cluster( ClusteredPoints cluster( const std::vector<unsigned>& indices, std::function<Vec3d(unsigned)> pointfn, - std::function<bool(const SpatElement&, const SpatElement&)> predicate, + std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate, unsigned max_points) { // A spatial index for querying the nearest points @@ -462,10 +532,10 @@ ClusteredPoints cluster( for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); return cluster(sindex, max_points, - [max_points, predicate](const Index3D& sidx, const SpatElement& p) + [max_points, predicate](const Index3D& sidx, const PointIndexEl& p) { - std::vector<SpatElement> tmp; tmp.reserve(max_points); - sidx.query(bgi::satisfies([p, predicate](const SpatElement& e){ + std::vector<PointIndexEl> tmp; tmp.reserve(max_points); + sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){ return predicate(p, e); }), std::back_inserter(tmp)); return tmp; @@ -482,7 +552,7 @@ ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points) sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i))); return cluster(sindex, max_points, - [dist, max_points](const Index3D& sidx, const SpatElement& p) + [dist, max_points](const Index3D& sidx, const PointIndexEl& p) { return distance_queryfn(sidx, p, dist, max_points); }); From d7684188f90c8eb2ae1f7bc881aa918d3177ecd6 Mon Sep 17 00:00:00 2001 From: tamasmeszaros <meszaros.q@gmail.com> Date: Tue, 18 Jun 2019 11:24:50 +0200 Subject: [PATCH 12/14] Removing unused pad parts working --- src/libslic3r/MTUtils.hpp | 54 ++++++++++++++++++++++++++++ src/libslic3r/SLA/SLASupportTree.cpp | 9 +++-- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 7e91ace32..ee70f535d 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -5,6 +5,9 @@ #include <mutex> // for std::lock_guard #include <functional> // for std::function #include <utility> // for std::forward +#include <vector> +#include <algorithm> +#include <cmath> namespace Slic3r { @@ -182,6 +185,57 @@ public: inline bool empty() const { return size() == 0; } }; +template<class T> +struct remove_cvref +{ + using type = + typename std::remove_cv<typename std::remove_reference<T>::type>::type; +}; + +template<class T> +using remove_cvref_t = typename remove_cvref<T>::type; + +template<template<class> class C, class T> +class Container: public C<remove_cvref_t<T>> { +public: + explicit Container(size_t count, T&& initval): + C<remove_cvref_t<T>>(count, initval) {} +}; + +template<class T> using DefaultContainer = std::vector<T>; + +/// Exactly like Matlab https://www.mathworks.com/help/matlab/ref/linspace.html +template<class T, class I, template<class> class C = DefaultContainer> +inline C<remove_cvref_t<T>> linspace(const T &start, const T &stop, const I &n) +{ + Container<C, T> vals(n, T()); + T stride = (stop - start) / n; + + size_t i = 0; + std::generate(vals.begin(), vals.end(), [&i, start, stride] { + return start + i++ * stride; + }); + + return vals; +} + +/// A set of equidistant values starting from 'start' (inclusive), ending +/// 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 T, template<class> class C = DefaultContainer> +inline C<remove_cvref_t<T>> grid(const T &start, const T &stop, const T &stride) +{ + Container<C, T> vals(size_t(std::ceil((stop - start) / stride)), T()); + + int i = 0; + std::generate(vals.begin(), vals.end(), [&i, start, stride] { + return start + i++ * stride; + }); + + return vals; +} + } #endif // MTUTILS_HPP diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index c38ff34e1..cfb5c2e74 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -9,6 +9,7 @@ #include "SLASpatIndex.hpp" #include "SLABasePool.hpp" +#include <libslic3r/MTUtils.hpp> #include <libslic3r/ClipperUtils.hpp> #include <libslic3r/Model.hpp> @@ -559,7 +560,7 @@ struct Pad { Pad() = default; - Pad(const TriangleMesh& object_support_mesh, + Pad(const TriangleMesh& support_mesh, const ExPolygons& modelbase, double ground_level, const PoolConfig& pcfg) : @@ -576,9 +577,11 @@ struct Pad { // Get a sample for the pad from the support mesh { ExPolygons platetmp; - float plateZ = float(get_pad_fullheight(pcfg) + EPSILON); - base_plate(object_support_mesh, platetmp, plateZ, 0.1f, thr); + float zstart = float(zlevel); + float zend = zstart + float(get_pad_fullheight(pcfg) + EPSILON); + + base_plate(support_mesh, platetmp, grid(zstart, zend, 0.1f), thr); // We don't need no... holes control... for (const ExPolygon &bp : platetmp) From 3b0e0aaed43389c66bf3eebed035353b94da2dc7 Mon Sep 17 00:00:00 2001 From: tamasmeszaros <meszaros.q@gmail.com> Date: Thu, 20 Jun 2019 13:01:48 +0200 Subject: [PATCH 13/14] Fixes for degenerate cases. --- src/libslic3r/SLA/SLABasePool.cpp | 75 ++++++++++++++-------------- src/libslic3r/SLA/SLACommon.hpp | 1 + src/libslic3r/SLA/SLASupportTree.cpp | 59 +++++++++++++++------- src/libslic3r/SLAPrint.cpp | 8 --- 4 files changed, 80 insertions(+), 63 deletions(-) diff --git a/src/libslic3r/SLA/SLABasePool.cpp b/src/libslic3r/SLA/SLABasePool.cpp index 48d615a29..ace9cdd85 100644 --- a/src/libslic3r/SLA/SLABasePool.cpp +++ b/src/libslic3r/SLA/SLABasePool.cpp @@ -239,48 +239,49 @@ void offset(ExPolygon& sh, coord_t distance, bool edgerounding = true) { } } - 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; +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); + 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; - } + // 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<double>(distance)); + ClipperOffset offs; + offs.ArcTolerance = 0.01 * scaled(1.); + Paths result; + offs.AddPath(ctour, edgerounding ? jtRound : jtMiter, etClosedPolygon); + offs.Execute(result, static_cast<double>(distance)); - // Offsetting reverts the orientation and also removes the last vertex - // so boost will not have a closed polygon. + // 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!"; - } - } - } - } + 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) { diff --git a/src/libslic3r/SLA/SLACommon.hpp b/src/libslic3r/SLA/SLACommon.hpp index 2666f2a9c..eb986a259 100644 --- a/src/libslic3r/SLA/SLACommon.hpp +++ b/src/libslic3r/SLA/SLACommon.hpp @@ -73,6 +73,7 @@ public: inline double ground_level() const { return m_ground_level + m_gnd_offset; } inline void ground_level_offset(double o) { m_gnd_offset = o; } + inline double ground_level_offset() const { return m_gnd_offset; } inline const Eigen::MatrixXd& V() const { return m_V; } inline const Eigen::MatrixXi& F() const { return m_F; } diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index cfb5c2e74..4de5c4c59 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -530,6 +530,7 @@ struct CompactBridge { const Vec3d& ep, const Vec3d& n, double r, + bool endball = true, size_t steps = 45) { Vec3d startp = sp + r * n; @@ -543,12 +544,14 @@ struct CompactBridge { double fa = 2*PI/steps; auto upperball = sphere(r, Portion{PI / 2 - fa, PI}, fa); for(auto& p : upperball.points) p += startp; - - auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); - for(auto& p : lowerball.points) p += endp; - + + if(endball) { + auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa); + for(auto& p : lowerball.points) p += endp; + mesh.merge(lowerball); + } + mesh.merge(upperball); - mesh.merge(lowerball); } }; @@ -594,13 +597,17 @@ struct Pad { // base silhouette. Create the offsetted version and punch the // breaksticks across its perimeter. - ExPolygons modelbase_sticks = modelbase; - + ExPolygons modelbase_offs = modelbase; + if (pcfg.embed_object.object_gap_mm > 0.0) - modelbase_sticks - = offset_ex(modelbase_sticks, + 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; @@ -611,14 +618,27 @@ struct Pad { } } + // Punching the breaksticks across the offsetted polygon perimeters ExPolygons pad_stickholes; pad_stickholes.reserve(modelbase.size()); - for(auto& poly : modelbase_sticks) { + for(auto& poly : modelbase_offs) { - if (!bindex.query(poly.contour.bounding_box(), - BoxIndex::qtIntersects).empty()) { + std::vector<BoxIndexEl> qres = + bindex.query(poly.contour.bounding_box(), + BoxIndex::qtIntersects); + + if (!qres.empty()) { + + // 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(), @@ -627,7 +647,8 @@ struct Pad { else ++it; } - + + // Punch the breaksticks sla::breakstick_holes( poly, pcfg.embed_object.object_gap_mm, // padding @@ -638,7 +659,7 @@ struct Pad { pad_stickholes.emplace_back(poly); } } - + create_base_pool(basep, tmesh, pad_stickholes, cfg); } else { for (const ExPolygon &bp : modelbase) basep.emplace_back(bp.contour); @@ -1495,12 +1516,13 @@ class SLASupportTree::Algorithm { Vec3d pgnd = {endp(X), endp(Y), gndlvl}; can_add_base = result.score > min_dist; + double gnd_offs = m_mesh.ground_level_offset(); auto abort_in_shame = - [&normal_mode, &can_add_base, &endp, jp, gndlvl]() + [gnd_offs, &normal_mode, &can_add_base, &endp, jp, gndlvl]() { normal_mode = true; can_add_base = false; // Nothing left to do, hope for the best - endp = {jp(X), jp(Y), gndlvl}; + endp = {jp(X), jp(Y), gndlvl - gnd_offs }; }; // We have to check if the bridge is feasible. @@ -2317,7 +2339,8 @@ public: double idist = bridge_mesh_intersect(sph, dir, R, true); double dist = ray_mesh_intersect(sj, dir); if (std::isinf(dist)) - dist = sph(Z) - m_result.ground_level - HWIDTH_MM; + dist = sph(Z) - m_mesh.ground_level() + + m_mesh.ground_level_offset(); if(std::isnan(idist) || idist < 2*R || std::isnan(dist) || dist < 2*R) @@ -2329,7 +2352,7 @@ public: } Vec3d ej = sj + (dist + HWIDTH_MM)* dir; - m_result.add_compact_bridge(sp, ej, n, R); + m_result.add_compact_bridge(sp, ej, n, R, !std::isinf(dist)); } } }; diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 789a8120f..9e8b4bea2 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -1084,9 +1084,6 @@ void SLAPrint::process() using ClipperPolygons = std::vector<ClipperPolygon>; namespace sl = libnest2d::shapelike; // For algorithms - // If the raster has vertical orientation, we will flip the coordinates -// bool flpXY = m_printer_config.display_orientation.getInt() == SLADisplayOrientation::sladoPortrait; - // Set up custom union and diff functions for clipper polygons auto polyunion = [] (const ClipperPolygons& subjects) { @@ -1194,11 +1191,6 @@ void SLAPrint::process() sl::translate(poly, ClipperPoint{instances[i].shift(X), instances[i].shift(Y)}); -// if (flpXY) { -// for(auto& p : poly.Contour) std::swap(p.X, p.Y); -// for(auto& h : poly.Holes) for(auto& p : h) std::swap(p.X, p.Y); -// } - polygons.emplace_back(std::move(poly)); } } From 548f19462a9b55fd573a1e529a6be2fe0343b169 Mon Sep 17 00:00:00 2001 From: tamasmeszaros <meszaros.q@gmail.com> Date: Fri, 28 Jun 2019 15:42:59 +0200 Subject: [PATCH 14/14] Fix formatting --- src/libslic3r/MTUtils.hpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index f83d38a42..42992223f 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -244,21 +244,21 @@ template<class C> bool all_of(const C &container) }); } -template<class T> -struct remove_cvref +template<class T> struct remove_cvref { using type = typename std::remove_cv<typename std::remove_reference<T>::type>::type; }; -template<class T> -using remove_cvref_t = typename remove_cvref<T>::type; +template<class T> using remove_cvref_t = typename remove_cvref<T>::type; template<template<class> class C, class T> -class Container: public C<remove_cvref_t<T>> { +class Container : public C<remove_cvref_t<T>> +{ public: - explicit Container(size_t count, T&& initval): - C<remove_cvref_t<T>>(count, initval) {} + explicit Container(size_t count, T &&initval) + : C<remove_cvref_t<T>>(count, initval) + {} }; template<class T> using DefaultContainer = std::vector<T>; @@ -268,13 +268,13 @@ template<class T, class I, template<class> class C = DefaultContainer> inline C<remove_cvref_t<T>> linspace(const T &start, const T &stop, const I &n) { Container<C, T> vals(n, T()); - T stride = (stop - start) / n; - - size_t i = 0; + + T stride = (stop - start) / n; + size_t i = 0; std::generate(vals.begin(), vals.end(), [&i, start, stride] { - return start + i++ * stride; + return start + i++ * stride; }); - + return vals; }